Eye of the Chonker

EctoComp 2023 is here, and I’m using it as an excuse to finally complete a parser-based IF using Inform 7. I’ve been playing around with the language for a couple of years, but it’s only in the last few months that I’ve really hunkered down to learn the grammar, syntax and basic concepts of this remarkable feat of language design.

I wanted to write a game for IF Comp 2023, but the pressure to learn a language while designing puzzles and a story for a game led to overwhelm and I tripped up at the finish line. I don’t think I’m going to have the same problem for EctoComp, though; I’m really starting to get the feel of Inform into my mind (and fingers), and I’m working with an amazing co-author this time around, which has led to a lot less anxiety.

Since we’re doing La Petite Mort (wherein we have four total hours to complete our masterpiece), there’s not a lot of time to spend getting clarity on how to do stuff in Inform. Luckily, I have a couple of other open projects, and I’ve been using those to teach myself the fundamentals so we will be prepared when it comes time to finish our untitled monster game.

Exploring Inform 7 extensions has been a big part of this learning process. I actively avoided extensions when I first started learning the language, but some time on the Interactive Fiction Forum made me realize the dire error of that strategy. Extensions make it easy to do stuff that would be harder for a total beginner to figure out on their own, sure, but they are also often very well-written bits of I7 code that even a beginner can grok with a little bit of time and effort.

I had a chance to try out the “Simple Followers” extension by Emily Short. This is exactly what we need for our game’s monster. For some reason, Short designed this extension in a way that relates various people to one person rather than various people to one thing (or various things to one thing). Before modifying this relation, any object I declared would automatically be instantiated as a type of person, even if I didn’t explicitly state it was. This was messing things up because it meant when I tried to pick up the ball (which was being followed by the cat in my example), the interpreter complained “The ball probably wouldn’t like that” (since generally you can’t pick up a person).

I’m starting to think of other uses for this extension, for example having a game in which a player lets loose a carrier pigeon and follows it back to a specific location by having the pigeon want to follow an object in said location. This could also, with some additional rules, let you simulate gravity by having an object be unable to follow in a certain direction (i.e., up).

Enter the Chonker

Yesterday, I created a game called “Eye of the Chonker.” It’s an experiment in trying to create a simple follower of my own with some customized behavior. The basic premise is that the player starts with a handful of delicious meats and when they drop those meats around the house, a hungry cat will make her way to the nearest room with food, eat the food, and then meow annoyingly or go to the next-closest room with food, ad infinitum.

I’ve sent the link to this game to a few friends, including some people who don’t regularly play parser games. The consensus is that it was fun, and I think that’s because even though the game is absolutely tiny, it’s got an NPC that seems to have some level of agency as well as enough textual variety to hold a player’s attention for the 90 seconds or so that it takes to play. 

A better understanding of the finer points of rules, actions, phrases, and relations is going to open up a lot of possibilities, but the thing I’m most looking to do right now is refactor this code and use it as a sort of Inform koan so that there are less steps towards spiffy code in the future.

For one thing, it’s pretty clear looking at this code that there’s a lot of stuff that could be broken out into its own… what? Rule? Action? Phrase? Activity? Simply attempting to decouple things is going to help me learn how these vaguely related concepts are actually different. Because I’m pretty sure the following code incorporates aspects of most of these:

Every turn:
    if the location of the cat contains food:
   	 let snack be a random food in the location of the cat;
   	 say "The cat puts her snout to the ground and quickly hones in on the [snack] [if the location of the player is the location of the cat]here[otherwise] in [the location of cat][end if]. ";
   	 say "She scarfs down the [snack].[line break]Thirty seconds later she begins meowing again.";
   	 now snack is nowhere;
    otherwise:
   	 let catroom be the location of the cat;
   	 let targetroom be a random room containing food;
   	 repeat with room running through the list of rooms containing food:
   		 let the total distance be the number of moves from catroom to room;
   		 if the total distance is less than the number of moves from catroom to targetroom:
   			 now targetroom is room;
   	 if catroom is not targetroom and targetroom is not nothing:   	 
   		 let next-way be the best route from the catroom to targetroom, using doors;
   		 if next-way is a direction:
   			 try the cat trying going next-way;
   	 otherwise:
   		 say "An annoyed meowing comes from [location of cat].";
   		 if there is no room containing food:
   			 say "(Try wandering to a room and dropping one of your food items.)";

There are a few ways I can think to make this less (for lack of a better term) gross. The first thing would be to abstract out the code that determines the next closest food source to the cat, this stuff in the otherwise block:

let catroom be the location of the cat;
    let targetroom be a random room containing food;
    repeat with room running through the list of rooms containing food:
        let the total distance be the number of moves from catroom to room;
        if the total distance is less than the number of moves from catroom to targetroom:
            now targetroom is room;

I’d much rather be able to simply reference the nearest food source (or the nearest food source to the cat, depending on how this is implemented). As it turns out, what we want here is called a “phrase to decide”:

To decide which room is the nearest food source:
    let catroom be the location of the cat;
    ...

So now my event loop looks a little cleaner:

if the nearest food source is not nothing:
    let next-way be the best route from the location of the cat to the nearest food source, using doors;
    if next-way is a direction:
        try the cat trying going next-way;

But ultimately I want to reduce this down so I can simply say:

Try the cat trying going toward the nearest food source

So how do we do this? We have to create another deciding phrase…

To decide which direction is toward (R - a room):
    let next-way be the best route from the location of the cat to the nearest food source, using doors;
    if next-way is a direction:
        decide on next-way.

Again, this could be made more general so that we could use this to determine which direction any object/entity is from any room, but my reasoning is that there wouldn’t be any point since this toy example game isn’t going to be used for anything else. At that point, yak shaving is going to result in a fuzzier yak.

There are mechanisms in the language that could lead to even cleaner code (I’m pretty sure rulebooks and phrasebooks, for example), but honestly I’m just happy I was able to complete this project today. It took longer than four hours, but I’ll be able to move a lot more quickly the next time I use some of these techniques and patterns (which will be pretty soon).