Repository for word-information-generator

Yesterday I made a blog post about developing a program in Python that provided plenty of useful information given any word passed as a command-line argument. Well, the program seems feature complete now, so I have created a GitHub repository for it (to be honest, I should have created one from the beginning, as I had to backtrack some severe changes). Anybody who knows how to run Python programs should be able to use it. Otherwise, the instructions given on the repository might be enough.

Link to the GitHub repository for word-information-generator

I have been running the program already while writing my current novel. Along the way, if I realize that I want the program to offer me further information, I’ll likely add that in, so there may be further updates in the future.

Information about words thanks to ChatGPT

Yesterday, the shady company behind ChatGPT sent me an API key so I could do extra stuff with their GPT-4 AI model. I was mainly interested in using it for Auto-GPT.

Don’t you know what’s Auto-GPT? Some clever people figured out that if you give ChatGPT access to the internet and various other tools (such as your operating system’s commands), and trap it in a loop of reasoning, planning and criticizing itself, you can drop into that loop some task, such as growing your business or gathering particular information from the web, and ChatGPT will work itself to the bone for you. They called this implementation Auto-GPT, and it’s the closest thing we got, that I’m aware of, to AGI (artificial general intelligence), which is the holy grail of AI, and possibly the thing that will kill us all.

Anyway, here’s a video that shows you what this Auto-GPT can do (the video includes plenty of cool new stuff about AI):

I was aware that Auto-GPT can write, test and run Python code for you (apparently just Python; although I dislike the language, it seems to be the favorite of scripters who want stuff done quick, so you must be familiar with it). I started thinking about what I could tell Auto-GPT to do that would help me for real, and I came down to the fact that I look up words very, very often while writing, mostly to check particular definitions or to get synonyms. I search the definitions of words so often, in fact, that Google has at times demanded that I proved that I was human. So what if ChatGPT could write me a Python program that would provide all the information I need from a word, with a single command from Powershell?

The instructions were clear enough. Auto-GPT did write code that gave me synonyms, antonyms and some other shit for any word I would input, but when I ordered it to change the code so that the word got passed as an argument, Auto-GPT got mired in trying to figure out how to pass command-line arguments from within the Docker container with some dedicated functions.

When I gave it a break so it could write tests for the function in another file, it had trouble correcting the original code so that the tests would pass, but I think that was mostly my fault, as ChatGPT would need to have previous knowledge of, for example, what synonyms a word would have, and in that case, what’s the point of writing a test?

Anyway, I got bored with Auto-GPT itself, but not with the notion that ChatGPT could write that Python program, so that’s what I forced it to do in a couple of hours. Behold the results of passing the word “horse” as an argument:

Information about horse

Meaning of horse

  • solid-hoofed herbivorous quadruped domesticated since prehistoric times
  • a padded gymnastic apparatus on legs
  • troops trained to fight on horseback
  • a framework for holding wood that is being sawed
  • a chessman shaped to resemble the head of a horse; can move two squares horizontally and one vertically (or vice
    versa)
  • provide with a horse or horses

Part of speech for horse

  • verb (transitive)
  • noun

Etymology of horse

  • “solidungulate perissodactyl mammal of the family Equidæ and genus Equus” [Century Dictionary], Old
    Englishhors”horse,” from Proto-Germanicharss-(source also of Old Norsehross, Old Frisian, Old Saxonhors, Middle Dutchors, Dutchros, Old High Germanhros, GermanRoß”horse”), of unknown origin. By some, connected to PIE rootkers-“to run,” source of Latincurrere”to run.” Boutkan prefers the theory that it is a loan-word from an Iranian language
    (Sarmatian) also borrowed into Uralic (compare Finnishvarsa”foal”),The usual Indo-European word is represented by Old
    Englisheoh, Greekhippos, Latinequus, from PIE rootekwo-. Another Germanic “horse” word is Old Englishvicg, from Proto- Germanicwegja-(source also of Old Frisianwegk-, Old Saxonwigg, Old Norsevigg), which is of uncertain origin. In many
    other languages, as in English, this root has been lost in favor of synonyms, probably via superstitious taboo on
    uttering the name of an animal so important in Indo-European religion. For the Romanic words (Frenchcheval,
    Spanishcaballo) seecavalier(n.); for Dutchpaard, GermanPferd, seepalfrey; for Swedishhäst, Danishhestseehenchman. As
    plural Old English had collective singularhorseas well ashorses, in Middle English also sometimeshorsen, buthorseshas
    been the usual plural since 17c.Used at least since late 14c. of various devices or appliances which suggest a horse (as
    insawhorse), typically in reference to being “that upon which something is mounted.” For sense of “large, coarse,”
    seehorseradish. Slang use for “heroin” is attested by 1950. Toride a horse that was foaled of an acorn(1670s) was
    through early 19c. a way to say “be hanged from the gallows.”Horse latitudesfirst attested 1777, the name of unknown
    origin, despite much speculation.Horse-pistol, “large one-handed pistol used by horseback riders,” is by 1704. Adead
    horseas a figure for something that has ceased to be useful is from 1630s; toflog a dead horse”attempt to revive
    interest in a worn-out topic” is from 1864.HORSEGODMOTHER, a large masculine wench; one whom it is difficult to rank
    among the purest and gentlest portion of the community. [John Trotter Brockett, “A Glossary of North Country Words,”
    1829]The term itself is attested from 1560s.The horse’s mouthas a source of reliable information is from 1921, perhaps
    originally of racetrack tips, from the fact that a horse’s age can be determined accurately by looking at its teeth.
    Toswap horses while crossing the river(a bad idea) is from the American Civil War and appears to have been originally
    one of Abe Lincoln’s stories.Horse-and-buggymeaning “old-fashioned” is recorded from 1926 slang, originally in reference
    to a “young lady out of date, with long hair.” Tohold (one’s) horses”restrain one’s enthusiasm, be patient” is from
    1842, American English; the notion is of keeping a tight grip on the reins.

Synonyms of horse

  • horse_cavalry
  • Equus_caballus
  • sawbuck
  • buck
  • sawhorse
  • cavalry
  • horse
  • knight
  • gymnastic_horse

Related phrases and expressions with horse

  • to the horse in English
  • your high horse and don
  • is a horse dick !
  • : The horse ?
  • , that horse is peeing
  • a Spanish horse .
  • on a horse ?
  • [ a horse and carriage
  • [ the horse and carriage
  • get a horse too .
  • take a horse !
  • taking the horse and I
  • smells like horse shit .
  • I mean horse .
  • got that horse and his
  • — Horse carriages ,
  • only a horse could love
  • is a horse !
  • and the horse didn ‘
  • take this horse and I
  • your new horse , honey
  • light that horse on fire
  • get a horse thief financing
  • the smelly horse carriages on
  • with this horse .

Semantic field(s) of horse

  • chessman
  • provide
  • gymnastic_apparatus
  • framework
  • equine
  • military_personnel

Hyponyms of horse

  • pommel_horse
  • eohippus
  • pony
  • stalking-horse
  • pinto
  • sorrel
  • steeplechaser
  • liver_chestnut
  • mesohippus
  • roan
  • remount
  • hack
  • wild_horse
  • workhorse
  • palomino
  • pony
  • gee-gee
  • pacer
  • stablemate
  • male_horse
  • bay
  • racehorse
  • harness_horse
  • protohippus
  • chestnut
  • trestle
  • vaulting_horse
  • hack
  • mare
  • saddle_horse
  • stepper
  • post_horse
  • polo_pony

Hypernyms of horse

  • military_personnel
  • gymnastic_apparatus
  • chessman
  • equine
  • framework
  • provide

Meronyms of horse

  • horseback
  • cavalryman
  • encolure
  • foal
  • gaskin
  • horse’s_foot
  • poll
  • horsemeat
  • withers

Domain-specific words related to horse

  • armed_forces
  • military_machine
  • chess
  • war_machine
  • chess_game
  • armed_services
  • military

Associated nouns with horse

  • horse

Associated verbs with horse

  • horse

Stylistic variations of horse

  • bathorse
  • horsepox
  • horseflesh
  • dishorse
  • horselike
  • horselaughter
  • ahorseback
  • horsewhipper
  • sawhorse
  • demihorse
  • horsekeeper
  • Horsetown
  • horsefettler
  • horseshoe
  • horsehead
  • ahorse
  • horseman
  • horsetree
  • underhorsed
  • woodhorse
  • horsehide
  • drawhorse
  • horsewomanship
  • overhorse
  • horsetongue
  • horsemint
  • horseleech
  • horsecloth
  • clotheshorse
  • horseboy
  • horseherd
  • horseload
  • horseplay
  • horsepower
  • horsedom
  • horsefish
  • horsetail
  • horsepond
  • horsefair
  • horsehood
  • rearhorse
  • horselaugh
  • horsemastership
  • horsefly
  • cockhorse
  • waterhorse
  • horsehair
  • horseway
  • horsebreaker
  • horsemonger
  • horsewoman
  • unhorse
  • horsegate
  • horsehoof
  • horseweed
  • horser
  • horseshoer
  • horsefight
  • horsewood
  • horselaugher
  • horsemanship
  • studhorse
  • horsecraft
  • horsefoot
  • horsejockey
  • horseback
  • horseplayful
  • horsewhip
  • underhorse
  • horsehaired
  • horsebacker
  • hobbyhorse
  • horsecar
  • horseless

A couple of weird points about this implementation, although they don’t bother me:

  • I gather the etymology from a website, but some words end up stuck together for whatever reason. Also, no paragraphs. Not sure if it can be fixed, because the html tags don’t come through the request.
  • The section “Related phrases and expressions” only looks for a few words around the passed word, from a dataset that ChatGPT recommended. The results are often strange.

Because I’m obsessive (and compulsive), I kept bothering ChatGPT by telling it to come up with more useful information that the program could provide about any given word. I didn’t know what a hyponym was.

Anyway, this little program ended up being a great tool for writing, which is what I should have done with my afternoon instead of getting involved with ChatGPT. Its auto version has huge potential; I probably need to come up with better use cases.

Interdimensional Prophets (Game Dev) #6


I had finished programming the non-visual part of Team Struggles (a part of the encounter system that involves character traits and psychological dimensions against some performance thresholds) when I faced the fact that the game was loading too damn slow. I admit, I have been a bit overeager demanding more anime photo IDs from Midjourney, and they are completely unoptimized, but still, I figured that this project could load much faster. So I figured the following solutions:

  • Lazy loading. Instead of loading encounter, biome, and photo ID images at once, just the image path is registered. Right before I need to draw a certain image, I check if it has been loaded, and if it hasn’t, I load it. That makes it so that the many images that won’t be seen in a particular testing session won’t need to be loaded at all. This change alone has sped up game loading significantly.
  • Multithreading. This project didn’t feature any multithreading up to this point, as it is a static, 2D strategy game, but the process of loading the various parts (most of them from TOML files) could use some multithreading. My previous experience with this subject involved trying to develop a Dwarf Fortress-like simulation in Python, only to realize that Python isn’t suited for multiprocessing, nor remotely big simulations at all, due to its garbage-collected nature and a core that is locked to a single thread. However, Rust has mature crates that make multiprocessing relatively simple.

I asked GPT-4 to give me an overview of multiprocessing in the Rust programming language. It suggested a combination of the “rayon” and “crossbeam-channel” crates. The process works like this:


let (sender, ecs_receiver) = crossbeam_channel::unbounded();


You declare a sender and a receiver. The sender part will put on a queue the work done from a different thread, and the receiver will remain on the main thread to try to figure out what it can extract from the queue. However, those threads don’t need to disconnect: they are open channels. I assume that you could have a dedicated thread pumping out pathfinding-related calculations back to the main thread.

Spawning a thread is as easy as the following:

       std::thread::spawn(move || {

            load_ecs_threaded(sender);

        });

The “move” order, or whatever you would call it, is tricky. Any information at all that you are sending from the main thread changes its ownership, even if you clone it normally, so you need to use the “Arc” library to clone it in some special way. Not sure how expensive it is.

Anyway, “load_ecs_threaded(sender)” is in this case the function that will run in the spawned thread. The definition and contents are the following:

use crate::{

    gui::image_impl::ImageImpl,

    world::{create_world, ecs::ECS},

};

pub fn load_ecs_threaded(sender: crossbeam_channel::Sender<ECS<ImageImpl>>) {

    sender

        .send(create_world::<ImageImpl, ECS<ImageImpl>>())

        .unwrap();

}

That function merely sends through the sender the results of the “create_world” function, that registers all necessary components with “specs” Entity-Component System.

You won’t be able to check if the spawned threads have done anything unless you are running some sort of loop on the main thread. In this case I’m running the game with the 2D game dev “ggez” crate, which operates a simple, but well-working, game loop. From there, you need to rely on the “receiver” part of the channel to try to receive data:

        if let Ok(ecs) = self.ecs_receiver.try_recv() {

            match self.shared_resources.try_lock() {

                Ok(mut bound_shared_resources) => {

                    bound_shared_resources.set_ecs(ecs);

                    self.progress_text = Text::new(“Loaded Entity-Component System”.to_string());

                }

                Err(error) => return Err(GameError::CustomError(format!(“Couldn’t lock shared resources to set the world instance. Error: {}”,

                error))

                ),

            }

        }

Through the call “ecs_receiver.try_recv()” I will get either an Ok or an Error. An error may just be that the channel is empty because the remote function hasn’t finished working, so we just check Ok. In that case, the thread has finished doing its job. We gather the results (the “ecs” in this case) and store it into our shared_resources as I did previously.

That’s all. You need to be careful, though, because there are some structs that you can’t send through channels. For example, you can’t send the graphical context of “ggez”, meaning that you always need to load images in the main thread. You also can’t send the random number generator through, as it’s explicitly working on a single thread. But I haven’t found any issue sending my game structs.

Now that the game doesn’t seem to freeze on launch, I can focus on implementing the visual aspect of Team Struggles.

Interdimensional Prophets (Game Dev) #5


A couple of entries ago I presented my first version of the encounter screen. As the team of explorers wanders around in the map, the stored encounters will get shuffled, and the first one whose condition gets triggered will present itself. Here’s the somewhat updated screen:

I was checking out the moddability of this game by changing most pictures to manga/anime aesthetics, and I realized that I liked it more this way. With a simple change of directory names, all names and pictures could get swapped to American ones. In any case, this screen presents what encounter has been triggered. The description gives a brief overview of the situation. The rest of the text informs that this encounter, at least the psychological part of it, will test each team member’s self-regulation (which is one of the psychological dimensions, the grouping of a few psychological criteria).

Yes, I know that there’s a lot of black space. Don’t know what to do about that.

Once you click the round button on the lower right, you are shown the results of the psychological test:

A brief text indicates the reason of this psychological test; in this introductory event/encounter to a narrative line called “The Verdant Assembly,” the characters test their self-regulation against the overwhelmingly lush and alien surroundings. For each character, the average value for that psychological dimension gets tested against a series of performance thresholds in the TOML files. The highest threshold they pass, they get that reward (or punishment). In game terms, an Encounter is associated with a series of Outcomes. Here’s how the outcomes for this encounter look like in the raw TOML file:

# Possible placeholders:

#

# {CHARACTER_NAME}

# {CHARACTER_FIRST_NAME}

[[outcomes]]

identifier = 1

outcome_type = “PsychologicalTest”

description = “Overwhelmed by the alien nature of this plant-based world.”

consequences_identifier = 1

[[outcomes]]

identifier = 2

outcome_type = “PsychologicalTest”

description = “{CHARACTER_FIRST_NAME} becomes fascinated by the plant-based entities, leading to increased motivation and a desire to learn from them.”

consequences_identifier = 2

They are self-explanatory. The most important part is that they link to another store of game entities, the Consequences. I intended to unify the concept of game consequences to a single block of game logic that could be applied to psychological tests and, in the future, to team struggles. The TOML file of related consequences is the following:

[[consequences]]

identifier = 1

illness_identifiers = []

injury_identifiers = []

mental_status_effect_identifiers = [1, 2]

character_trait_identifiers = [1]

add_features_identifiers = []

remove_features_identifiers = []

[[consequences]]

identifier = 2

illness_identifiers = []

injury_identifiers = []

mental_status_effect_identifiers = [3]

character_trait_identifiers = []

add_features_identifiers = []

remove_features_identifiers = []

It is quite inexpressive in its contents because it only links to other entities through their identifiers. However, the outcome of each psychological test and team struggle could have any of the following consequences (or all of them):

  • The team member(s) involved receives one or many illnesses. Illnesses reduce the team member’s health every turn until they run out or are cured.
  • Receives one or many injuries. Instant reduction of health, and the permanent ones even reduce max health.
  • Receives one or many mental statuses (like Confused or Discouraged). They increase or reduce the value for associated psychological criteria.
  • Receives one or many character traits (like Terrified of Octopi, or Botanist). These help or hinder during team struggles.
  • The exploration zone the team is exploring either gains or loses features. For example, if some outcome enrages the natives, the exploration zone could gain the feature Enraged Natives, which would present more combat-oriented encounters in the future.

The consequences are already being applied in the code (which was some heavy amount of code, well-tested thanks to test-driven development), and once I get around to implementing team struggles, their consequences will work seamlessly with the code already written.

In the near future I’m going to focus on making sure that encounters can be blocked by other encounters or even their outcomes, if necessary. For example, if during a team struggle the team screws up bad enough to unleash something dangerous, that should cut off access to more positive branches of that same narrative.

A detail about Rust’s fastidious nature: this is a programming language built upon security and protection against the nastiest bugs from the C++ era. As far as I can tell, in Rust it’s impossible to corrupt some memory allocation that it wasn’t supposed to touch. That forces you to change your approach to programming in quite a few ways, but not because Rust is annoying for no reason, but because in other languages you were doing dangerous things. In my code, I was passing around a SharedGameResources entity that had access to “specs” Entity-Component System (that’s a whole thing; if you are interested, google it) as well as the stores of data loaded from TOML files. At one point I had to borrow that SharedGameResources entity both as immutable (just to read from the stores) as well as mutable (to write the results in the components). That’s impossible. Although it forced me to rewrite some basic architectural code, it illuminated the point that stores of game systems (like the “databases” of mental status effects or of character traits) are separate to the Entity-Component System, which handles a lot of mutation. In the end, Rust’s compiler steers you towards proper architecture, because you simply can’t run your program otherwise.

Interdimensional Prophets (Game Dev) #4


As I was writing unit tests for a perilous, convoluted part of the game logic, which I wanted to lock in place as I moved forward, I realized that to test one relatively small part of the code, I would need to create both World, the main entity of the Entity-Component System “specs”, as well as Image, which is tied to the Context of the 2D game dev “ggez” crate. World is heavy by itself to fire up for a simple unit test, but Images themselves may not even be feasible, as they are glued to the graphical context (no graphics should run during unit tests), and they are tied to a single thread, while the unit tests run in all CPUs.

Therefore, I came to the dreaded conclusion: I needed to refactor their entire code into traits (interfaces). Traits are contracts that encapsulate a behavior without implementing anything. If you program to traits (interfaces) instead of to classes, you are working with future, potentially unimplemented structures whose details are irrelevant to you or the compiler, because they are only forced to fulfill a contract (the trait/interface). It’s like telling an animal to say something; a dog would bark, a cat would meow, but both have fulfilled the contract of “talking,” which is apparently all you cared about.

It just happens that traits in Rust involve generics, and generics in Rust are unholy. Due to Rust’s welcome but tough borrowing rules and lifetime whatevers, Refactoring any behavior to traits involves dealing with bizarre generic declarations, and worse yet, nearly incomprehensible lifetime signatures.

Behold the horror:

pub struct MainState<‘a, T: ImageBehavior + ‘static + std::marker::Send + std::marker::Sync + Clone, U: WorldBehavior<T>> {

    active_stage: Option<GameStage>,

    base_state: BaseState<‘a, T, U>,

    expedition_state: ExpeditionState<‘a, T, U>,

    encounter_state: EncounterState<‘a, T, U>,

    shared_game_logic: Arc<Mutex<SharedGameLogic>>,

}

That’s the definition of MainState, the head honcho of the state machine that figures out which other stage needs to update, or draw on the screen. It declares that it’s somehow involved, to start, with an ImageBehavior contract. Every image throughout the program, except in the launcher, is now unaware of what type of structure it’s actually dealing with, except that it fulfills the contract of being static (I assume images are fixed in memory or something), they Send and Sync for multithreading, and can be Cloned. That’s the hardest one. Then we have WorldBehavior, which abstracts away the entirety of the “specs” Entity-Component System into a wrapped class. The resulting contract for WorldBehavior, if I may say so myself, is a thing of beauty:

pub trait WorldBehavior<T: ImageBehavior + std::marker::Sync + std::marker::Send + Clone> {

    fn new(world: World) -> Self;

    fn join_coordinates_and_tile_biomes(&self) -> Vec<((i32, i32), TileBiome)>;

    fn retrieve_player_coordinates(&self) -> Option<(i32, i32)>;

    fn retrieve_biome_at_player_position(&self) -> Option<Biome>;

    fn retrieve_unique_character_traits_of_team_members(&self)

        -> HashSet<CharacterTraitIdentifier>;

    fn count_team_members(&self) -> u32;

    fn calculate_average_mental_strain_of_team(&self) -> f32;

    fn calculate_average_health_of_team(&self) -> f32;

    fn set_player_position(&mut self, x: i32, y: i32);

    fn retrieve_player_entity(&self) -> Entity;

    fn retrieve_name_of_entity(&self, entity: Entity) -> Name;

    fn retrieve_psychological_profile_of_entity(&self, entity: Entity) -> PsychologicalProfile;

    fn retrieve_health_of_entity(&self, entity: Entity) -> CharacterHealth;

    fn retrieve_team_members(&self) -> Vec<Entity>;

    fn retrieve_team_members_except_player(&self) -> Vec<Entity>;

    fn retrieve_photo_id_of_entity(&self, entity: Entity) -> PhotoId<T>;

    fn move_direction(&mut self, direction: Direction);

    fn create_entity(&mut self) -> EntityBuilder<‘_>;

}

With that contract in place, there’s no more need to deal with “specs” way of working. You want to retrieve the player entity? Call “retrieve_player_entity”. Do you want a calculation of the average mental strain of the entire team? Call “calculate_average_mental_strain_of_team”. Of course, the contract can be developed further as more information or calculations need to be gathered.

I’m at ease now that I have managed to refactor those two unit test blockers into behaviors, but it took hours, and if it weren’t for the indefatigable help of GPT-4, I wouldn’t have been able to do it. But thankfully there shouldn’t be any major obstacles to unit test every part of the code now, which should speed up development significantly.

A boring entry compared with the three previous ones, perhaps, but programming is a fight against entropy: the further you build your system, the harder it becomes to change. You have to stop every few days (if not once a day) to make sure that some part of the code isn’t rotting already.

Interdimensional Prophets (Game Dev) #3


The core loop of this game/experiment of mine consists of the encounter system. As the team of explorers (consisting of four members for balancing reasons, like in Arkham Horror LCG) ventures through strange new worlds, they will face encounters (psychological tests, team struggles) in the following circumstances: either the player ends the turn deliberately, or he/she moves the team to a different tile. That will trigger the code to shuffle the potentially very, very large list of encounters loaded from a TOML file, and then a complex function will determine which will be the encounter that the team of explorers will face based on numerous conditions.

The following is an example encounter from the TOML file, that gets loaded to the game on start.

encounters.toml

[[encounters]]

identifier = 1

title = “A New World”

image_path = “/resources/encounters/1.png”

description = “We have made it. Nobody believed we would manage to create a portal to a multiverse, but here we are, in this strange new world. I take a deep breath as I gaze at the field of grass.”

duration_in_turns = 1

encounter_condition = 1

psychological_test_identifier = 1

[[encounter_conditions]]

identifier = 1

requirements_biome_identifiers = [1]

requirements_feature_identifiers = [

    { AnyRequired = [] },

    { AllRequired = [] },

    { AnyExcluded = [] },

]

requirements_encounter_identifiers = [

    { AnyPreviousEncounter = [] },

    { AllPreviousEncounters = [] },

    { AnyExcludedEncounter = [] },

]

requirements_trait_identifiers = [

    { AnyRequired = [] },

    { AllRequired = [] },

    { AnyExcluded = [] },

]

team_size_requirements = [

    { MinimumTeamSize = 1 },

    { MaximumTeamSize = 6 },

]

team_overall_mental_health_requirements = [

    { MinimumTeamOverallMentalHealth = 0.0 },

    { MaximumTeamOverallMentalHealth = 100.0 },

]

team_overall_health_requirements = [

    { MinimumTeamOverallHealth = 0.0 },

    { MaximumTeamOverallHealth = 100.0 },

]

time_spent_in_alternate_earth_requirements = [

    { MinimumTimeSpentInAlternateEarth = 0 },

    { MaximumTimeSpentInAlternateEarth = 10 },

]

Each encounter is, so far, associated with a psychological test, but soon enough they will be associated with a team struggle as well. The most important part of an encounter is the conditions for it to trigger, which can be the following:

  • The current biome is any of the required (obligatory: all encounters should be attached to at least one biome).
  • The features associated with this exploration zone (ex. NativeInhabitants, Irradiated) are compatible with the any/all/excluded specifications.
  • The list of previous encounters is compatible with the any/all/excluded specifications. This should allow for story-like threads of narrative that depend on previous “chapters” having been passed.
  • The combined list of character traits (from all team members) is compatible with the any/all/required specifications.
  • The team size should be between the set values, if any.
  • The team’s overall mental health should be between the set values, if any.
  • The team’s overall health should be between the set values, if any.
  • The time spent wandering in the current exploration zone should be between the set values, if any.

This allows, for example, for encounters with mythical beasts that only inhabit certain biomes, maybe during some lunar condition (which would be a feature), and only when the team of explorers has trackers (which would be a character trait). Also, when the team’s mental health is deteriorating and their overall health is becoming dangerous, encounters about the team stopping to rest and heal could pop up.

When an encounter triggers, the user is presented with the following window:

I’m not an interface man. I need to figure out which background color would be preferable to plain black, but white and such other clear options seem too grating to me. Anyway, the photo on the upper left is the one associated with the encounter. The row of photo IDs under that is the team of explorers, starting with the player. Regarding text, there’s the title, a short description (thankfully you can program text wrapping). A blue text that you can barely distinguish from here announces that there’s a psychological test associated with the encounter, and that it tests the following psychological dimensions: cognitive abilities and interpersonal skills. Then the values of those psychological dimensions are displayed for each team member.

In the lower right you get a reminder of what biome you are currently in (in this case, a temperate grassland). The fancy button to its left is the one to trigger the psychological test. I have already written the code to gather all the results, which will appear under the description of the encounter.

Thank you Midjourney for the effortless AI images. Regarding GPT-4, I make a point of bothering it by presenting my code and asking “this represents an Outcome given [game concept]. Can you offer any suggestions and improvements?” The AI is eager to point out what I’ve done wrong, with no regard to my feelings. As such, it has proven to be an invaluable tool while programming. Also, when I suspect there’s more to squeeze out of a concept (such as psychological tests, team struggles, outcomes, etc.), I ask GPT-4 if it can come up with more features for that concept, and more often than not, it provides very valuable insight.

Anyway, I have been feeling guilty because I’m mainly a writer and I have neglected my novel for a week. I think that for the foreseeable future I will write one day, program the next. Programming is very addictive for the obsessive mind I was born with, as long as you don’t end up in some ditch of which you don’t know how to climb out.

Interdimensional Prophets (Game Dev) #2


Although I had managed to develop the code to load environments (now called exploration zones) from a Lua file, to pick one and then create a map using the biomes that the exploration zone allowed, the process of loading relatively simple data from Lua annoyed me. It seemed way too complex for this day and age, even though GPT-4 wrote most of the code helped me. I asked the AI for preferable alternatives, and it suggested either JSON files or TOML ones. TOML seemed fancier and better somehow (I have already forgotten the reasons), so I have spent some hours going through the somewhat grueling process of destroying the basic code that worked, to improve the system.

Thankfully, deserializing TOML files to Rust instances is trivial thanks to the “serde” crate. In addition, it had worried me that the process of adding a new type of biome or a feature (some aspect of the exploration zones, like bad weather) required me adding a new element to the corresponding enum, which meant that no user of the game (meaning me for the most part) would be able to add new biomes nor features without access to the code. Obviously that’s terrible for modding; the holy grail of modding consists on having every piece of game data exposed to be fondled by the greasy fingers of the users.

Behold the TOML files that now store all the game data for biomes, features of an exploration zone, and the exploration zones themselves (obviously the data itself is made of examples):

biomes.TOML

[[biome]]

identifier = “temperate_grassland”

name = “Temperate Grassland”

description = “A biome characterized by rolling grasslands and few trees.”

[[biome]]

identifier = “sky_islands”

name = “Sky Islands”

description = “A biome consisting of floating islands high in the sky.”

[[biome]]

identifier = “alpine”

name = “Alpine”

description = “A biome consisting of high land and some pines.”

features.TOML

[[features]]

identifier = “haunted”

name = “Haunted”

description = “a supernatural presence, allowing encounters with ghosts.”

[[features]]

identifier = “ancient_ruins”

name = “Ancient Ruins”

description = “contains the remains of an ancient civilization, with hidden treasures and traps.”

[[features]]

identifier = “unstable_geology”

name = “Unstable Geology”

description = “frequent earthquakes and volcanic activity, posing geological hazards for the team.”

[[features]]

identifier = “radioactive”

name = “Radioactive”

description = “high levels of radiation, potentially causing health problems and mutating local flora and fauna.”

exploration_zones.TOML

[[exploration_zones]]

identifier = “zone_1”

name = “Verdant Valley”

description = “A lush valley with dense forests, clear rivers, and abundant wildlife.”

allowed_biomes = [“temperate_forest”, “river”, “temperate_grassland”]

features = [“rich_flora”, “native_inhabitants”]

[[exploration_zones]]

identifier = “zone_2”

name = “Frozen Tundra”

description = “A vast, frozen wasteland with treacherous ice and snow, and sparse vegetation.”

allowed_biomes = [“tundra”, “ice”, “glacier”]

features = [“extreme_cold”, “limited_visibility”]

From now on, as long as the identifier of a biome written in the exploration_zones TOML file matches with an entry in the biomes TOML file, the game will work properly and use that biome (and its image). If not, the game will burn to the ground (in Rust’s terms, it will panic).

Now I will move on the encounter system, which is composed of various steps:

-A very complex determination of whether or not any given encounter will trigger (can depend on certain biomes and/or features and/or previous encounters having been seen and/or the team members having certain traits and/or the team size being between a certain range and/or the team’s average mental health being between a certain range and/or the team’s overall health being between a certain range and/or the time spent exploring being between a certain range. That code is already written and is the most unit tested area of the game so far (thanks to GPT-4).

-If an encounter triggers, then each team member should go through a number of psychological tests. For example, having walking octopi wanting to drink your blood may test their self-regulation and/or coping skills. The outcomes are on a range of thresholds, so worse outcomes should cause worse consequences, and so on. Those will also end up as TOML files.

-After all the psychological tests pass, there should be one or more team tests. The way these tests work, the best one that GPT-4 and I have come up with, is adding beneficial character traits * their individual weight against negative character traits * their weight, and applying outcomes based on a range like in the case of the psych tests.

If that psychological test thing followed by team test reminds you of something, yes, I was inspired by the Arkham Horror LCG, my favorite card game. In that game, before your turn starts, you are forced to deal with some nefarious encounter (it’s always unnerving to see that card coming from the deck) that you have to face with your abilities. The encounter can make you lose sanity and/or health, or who knows what other horrors. Afterwards you can act with your cards, and other players can get involved if you share a location with them. Hence my notion of a round of psychological tests followed by a team test/struggle.

I won’t go through the trouble of creating a anything visual for this encounter system until it works well in the console and is well-tested. The encounter system seems like the toughest nut to crack of this game, and I’m for one excited to face how to implement it.

If you, whoever the hell you are reading this, can come up with some idea for this game, please tell me. If it’s good, I’ll implement it. Otherwise I’ll yell at you and call you stupid.

Interdimensional Prophets (Game Dev) #1


A couple of years ago I wrote a wild (and long) free verse poem about some unhinged scientist who was leading teams of unfortunate people through an interdimensional portal to explore alternate Earths. This is the link to that poem (it requires a rewrite, though, particularly to add periods). I was fascinated by the potential for stories that such a concept included. I played around with the notion of developing some game around it, but my experience with programming solo was more often than not the same: I tried to implement some general game concept only to find myself hitting my head against an implementation detail that had seemed easy to solve. Eventually I discarded all my grand programming ideas. One of them involved Python, and it was the language itself that ended up pissing me off.

Enter GPT-4, the most advanced AI that I have ever interacted with. Turns out that GPT-4 is great at programming in Rust. Literally, you tell that damn thing to write unit tests for your code, and it does. I remain constantly amazed by its insight. In a couple of days, I cobbled this stuff together:

This is the beginning of the “base” screen, set on regular Earth, where the team will manage its staff, handle their health and psychological problems, and, more importantly, delve into alternate Earths through the portal. A description gives the general notion of the alternate Earth on the other side of the portal: “A bizarre, otherworldly environment dominated by massive fungi, and strange creatures.”

This is the map view during exploration. The icon represents the actual position of the team. I didn’t mention it before, but the person on the upper left side is the player (for now generated randomly), who will lead the team of explorers to face the dangers alongside them.

I already have many, many different environments written and illustrated, thanks to the back-and-forth between GPT-4 and myself, and Midjourney for the images. A bigger example, the Stormy Desert biome:

All these environments are written in a Lua file, so they are intrinsically moddable (so far, as long as the Biomes and the environment Features are coded in game). Here’s an example of a single environment in the file environments.lua:

    Highland = {

        description = “A high-altitude environment consisting of rolling hills, plateaus, and mountains, with a mix of grasslands, forests, and rocky terrain.”,

        allowed_biomes = {

            “Alpine”,

            “TemperateGrassland”,

            “Steppe”,

            “TemperateDeciduousForest”,

            “Taiga”,

            “Tundra”,

            “SkyIslands”,

            “AncientRuins”,

        },

        features = {

            “HighAltitude”,

            “Mountains”,

            “Avalanches”,

            “RockSlides”,

            “MountainClimbing”,

            “TreacherousPaths”,

            “LimitedResources”,

            “Isolation”,

            “ExtremeCold”,

            “Frostbite”,

            “Hypothermia”,

            “NativeInhabitants”,

            “ResourceCompetition”,

            “TerritorialConflicts”,

            “CaveSystems”,

            “HiddenCoves”,

            “LostCivilizations”,

            “AncientRelics”,

        }

    },

The combination of biomes and features will determine which types of encounters the team will face. That’s a whole different system I’m developing, and that I intend to be fully moddable as well.

I wanted each team member to be as psychologically complex as possible. Each encounter will test one or more psychological dimensions of their personality. For example, if they come across walking octopi that try to drink the team members’ blood (which happens in the poem), certain psychological dimensions will be tested.

For now, all psychological dimensions that GPT-4 and I have discovered (mostly the AI, though) are Interpersonal Skills, Cognitive Abilities, Self-Regulation, Coping Skills, Drive, Cross-Cultural Skills, and Mental Strain. For example, a single psychological test of that encounter could test a team member’s Coping Skills, and if he fails, his Anxiety psychological criterion could increase permanently. They could also lose health, acquire traits, etc. The notion is that when Mental Strain reaches 100, they are committed to a mental institution. They may quit some time earlier, though. The health system is very barebones at the moment (literally just a 0.0 to 100.0 value), but I want to create a whole system for that as well, including permanent injuries.

The grouping of psychological criterion to psychological dimensions is the following:

  • Interpersonal Skills: Extraversion, Agreeableness, Social Skills, Empathy, Conflict Resolution, Teamwork
  • Drive: Conscientiousness, Leadership, Risk-Taking, Time Management
  • Self-Regulation: Emotional Stability, Emotional Intelligence, Self-Esteem, Self-Awareness
  • Cognitive Abilities: Openness to Experience, Problem-Solving, Creativity, Situational Awareness, Decision-Making
  • Coping Skills: Resilience, Coping Strategies, Adaptability
  • Cross-Cultural Skills: Cultural Competence
  • Mental Strain: Anxiety, Depression, Stress

Right now, when a new team member is created, every psychological criterion is assigned a number from 0.0 to 100.0 on a normal distribution. GPT-4 even wrote psychological reports from the perspective of the team leader. The following is such a report generated in-game for the team leader herself:

Some experience working with others and collaborating towards shared goals. They may need additional support in order to effectively build relationships with others and resolve conflicts.
Average cognitive abilities and is capable of problem solving and critical thinking. They may need additional support or training in order to tackle more complex problems and situations.
Struggles to manage their emotions and reactions in high-pressure situations or when encountering unexpected events. They may be prone to panic or irrational behavior, making them a potential liability to any team exploring alternate Earths. They may require significant support and training to effectively manage their emotions and reactions in these situations.
Some coping skills and is able to manage stressful situations to some extent. However, they may require additional support or guidance in order to effectively deal with unexpected events or extremely stressful situations that may arise while exploring alternate Earths.
Some motivation and drive to achieve their goals, but may struggle to maintain focus and commitment when faced with obstacles. They may require additional support and encouragement to stay on track and fulfill their responsibilities on an exploration team.
Solid experience with cross-cultural communication and collaboration. They are able to adapt their communication style and approach to effectively engage with people from different cultures and backgrounds. They may benefit from additional training or exposure to further enhance their cross-cultural skills.
The candidate’s mental health is poor, and they may be highly vulnerable to the extreme stress and potential trauma of exploring alternate Earths. It would be inadvisable to consider them for such a high-risk job without extensive support and preparation.

As you can figure out, I’m quite pumped up about developing this shit, to the extent that I haven’t written any fiction in two days (that’s a lot for me).

Please, if you can come up with any ideas, I’d love to hear and implement them. One of the worst parts of programming for me is having built a careful architecture only to realize that a better idea will require a whole restructuring. I’ve already had to do that twice for the encounter system. So I want the best ideas first.

An example of GPT-4 as a programming aide

Here’s a little example of the value of GPT-4 (the most advanced multimodal large language model, meaning a state-of-the-art set of neural networks) as a programming consultant. After a long back and forth between the AI and myself, I was trying to refactor the determination of what was the tile type of a map at a certain hex coordinate, but the safety measures (greatly appreciated in general) of Rust were alerting me that I was trying to borrow a variable from a scope that was about to disappear, which is illegal. Such borrowing issues make refactoring in Rust vastly more thorny than in any other language of which I’m aware, but it also prevents memory bugs. I couldn’t remember how to solve it (I’m quite rusty myself), so I just asked GPT-4. Here’s the exchange:

Programming along with GPT-4 is like being able to rely on a sometimes forgetful veteran who will always stop whatever it is doing to help you. Given that it’s very unlikely that programming will trigger OpenAI’s content moderation systems, I don’t see why you wouldn’t use GPT-4 constantly while programming (as long as you can afford the subscription).

Refactoring my “Fire in the Lake” software

Although I had been refactoring bunches of code present in some methods into other methods, and created a couple additional structs when the original structs were handling concerns beyond what they were originally created for, at this point of the project some structs were accumulating massive amounts of methods. Refactoring them would require identifying the general purposes of the methods involved, classifying them and then isolating them from the original code so that area of the codebase wouldn’t be so brittle. I focused first in the area that handles executing the player or bot commands. As a general overview, each of the four players will push through the mouth of the system some words that will relate to their intentions, what they want to do in this turn. If they push through words like “event”, “operation”, “pass”, the system needs to understand that the player wants to play the active card for the event, or wants to perform an operation, or intends to pass. However, we also have words like “6”, “an loc”, “saigon”, “pacify”, “rally”. The system should reliably handle what kind of operation the player wants to play, in what space or spaces of the board, or stuff like how many troops it wants to deploy. In my initial design I simply passed an array of Strings through the command execution system, and those methods had the responsibility to figure out the intention of the player from whatever subset of words the method received. That was a clear violation of the separation of concerns, given that those methods should simply have to focus on executing an operation, a special activity, or delegating declaring that player as having passed, for example.

Before dealing with that major issue, I needed to take some scissors to the execute commands function. The mess of switches and ifs ended up being distributed into isolated functions that handled executing the events, others that executed each kind of operation, others that executed each kind of special activity, another that handled passing, and the biggest group, one that handled atomic executions such as deploying some units somewhere, manipulating the resources of a faction, setting a space to a level of support, improving the trail that the NVA faction uses, etc. That refactorization into a couple dozen files passed the integration tests without much trouble; thankfully, the three almost end-to-end tests that I wrote and that simulated three entire turns of the game are very resistant to refactorization.

There was another section of the code that handled the entirety of the flow of the game: how to deal with the couple of current cards, how to determine what factions were eligible or ineligible, how to slot those factions so that other parts of the system would know that the choices of those factions were already occupied, etc. It ended up being a tremendous chunk of code. I found three major different concerns for the methods involved: some methods just handled the general game flow: setting the active card, informing whether the turn had ended, moving to the next eligible faction according to the order shown in the active card, taking in the general player choices, being able to answer to whoever asked whether the turn had ended or not, etc. Another concern involved handling all the different possibilities for the factions: whether some faction is eligible, whether all are eligible, whether some faction has passed, determining the eligibility of a faction depending on some previous one’s actions, etc. The final concern was related with slotting the factions and moving them around into “holes” that can only hold either one of them at a time or four (maximum number of players). Whether a player has chosen, for example, to perform an operation without a special activity, is very important in this game system, because that means that the following player can only do a limited operation, while if the first player had chosen to play the card’s event, the second player would have been able to do a full operation plus a special activity.

The most pressing concern regarding the following features I had to implement had to do with the fact that I was passing an array of words from the “mouth” of the system to the lowest levels that handled command execution (in some cases to the very lowest level of executing atomic commands). It reeked of primitive envy; what the system should truly know is whether the player intended to perform one of the main choices (passing, event, operation), what operation if the player went that route (ex. rally, train, sweep), whether the player chose to do the additional action allowed for some of the main operations (such as improving the trail), or if the player chose a special activity, which one. We also have the fact that the player might have written the names of spaces (can be cities, provinces or lines of communication), and the program has to figure out if those locations are related to the event, the operation or the special activity. So I refactored out the entirety of that system and created an interpreter that chews the initial commands and just registers the player intentions. It can be asked “does the player want to activate the event?”, which returns a boolean response, and it can be asked to provide the locations associated with the event, for example. Far, far cleaner than the original, system, and not particularly hard to write. It suffers from a case of “excess of switches/ifs”, but it’s not pressing to figure out how to refactor that out. It reads each word as it comes, and depending on what intentions had been “detected”, it will convert to internal enums stuff like the names of places. If it receives the name of a space, the program determines whether, for example, the name actually refers to a space, and in that case if the player had previously sent the word to perform a operation, and in that case if it also had sent the word to perform a special activity. In that case the following spaces would be sent to a list of spaces associated with the special activity, but they would get associated to the operation or the event in other cases.

Again, thanks to the integration tests, the first time I got the major changes to compile, they immediately passed all the tests, so I knew that the previously codified functionality remained intact. That’s the advantage of test driven design: you can dismantle main areas of the code, improve it substantially, and remain confident that everything keeps working as it should. You wouldn’t dare to risk major changes otherwise.