QuollAlpha
Quoll
Quoll

2022-03-21 10:05: Inform 7: Easy for Writers, Hard for Programmers

Hello,

One of the things I hope to use this dev diary for is to discuss various problems I have had during the game and the solutions that I came up with to solve them. It's not that my way of doing things is great, and that everyone should do it, it's just that these kind of hoary development tales can be fun and educational even if I do come up with a bad solution!

On Ratholes

"Rathole" is a term that I picked up working at Microsoft. Corporations tend to develop an internal language all of their own, as analogies and terms that helped serve one manager or dev are put into use by others and then become just this weird piece of corporate jargon. Given a gigantic corporation like Microsoft, and how many people who have worked there often end up at other companies, these terms often begin to ooze outward into the general sphere of software developers. "Dogfood" is probably the most familiar one, which is about using your own products to make your product. This is so that issues with the product become personal annoyances and you're more likely to fix them1.

"Rathole", however, describes a different, less positive thing. I feel like the original term may have developed as a joking reference to a "rabbit hole", which in its figurative sense means something that if you continue to follow will take you to a bizarre land, far away from reality (Thanks, Lewis Carroll!). To "rathole" on something means to dive deeply into focusing on a matter that seems to be generally unimportant, often refusing to come back out into the light until you solve the problem or give up. Yes, I do think I'm pretty damn clever for calling my game about my rat fursona "An Interactive Reality Programming Rathole".

My hubris, of course, has been punished by making the development of this game into its own series of ratholes. Let me share one.

Inform 7

This game is mostly written in a programming language named Inform 7. Inform 7 is specifically written to make "interactive fiction", or the term I generally prefer, "text adventures";2. Back in the 1980s when doing graphics often required immense effort and complex assembly language, many games often only had a text interface. Of these text-based games, "interactive fiction" ruled the roost.

Interactive fiction back in the day was usually written in second-person to help increase the ability of the player to immerse themself into the world. First, the current location of the player character would be described. Then, a prompt would be shown for an input of a command. The commands used a very crude English parser that often became something the player had to struggle with. A bit like how controlling the camera in modern 3D games can be a challenge of its own.

Skipping the spew at the start of the game, here is the classic opening to Zork I, which, while not the first such game is the one that I think most captured people's imaginations, and ends up being one I reference a lot in my writing in Quoll3:

West of House
You are standing in an open field west of a white house, with a boarded front door.

There is a small mailbox here.

>

In response, the player can then type in commands like "EXAMINE MAILBOX"4:

West of House
You are standing in an open field west of a white house, with a boarded front door.

There is a small mailbox here.

>examine mailbox
The small mailbox is closed.

>open mailbox
Opening the small mailbox reveals a leaflet.

>read leaflet
(Taken)
"WELCOME TO ZORK!

ZORK is a game of adventure, danger, and low cunning. In it you will explore some of the most amazing territory ever seen by mortals. No computer should be without one!"

>reticulate splines
I don't know the word "reticulate".

>

So to drag this back to Inform 7, Inform 7 is designed to make text adventures like Zork. Let me talk more about how that looks.

Model World of Inform 7

Quick aside: when talking about programming, especially programming in Inform 7, common words like "room" are often used in a very particular technical sense. I will try to distinguish when I'm using them in the technical Inform 7 sense by capitalizing them like "Room".

Ok, So looking at the transcript, you can notice a few things. The player character first reports they5 are in a location called "West of House". These locations are, due to reasons of history, called "Rooms" in Inform 7. There are objects in these Rooms that can be picked up or interacted with that Inform 7 calls "Things"6. Some Things may be fixed in place, or just be scenery that can't really be interacted with, and some Things might be Persons like our player character here who is also a Thing in this Room.

But how does the game know stuff about these Things? One way is that Things have Properties. These Properties of Things can be queried by the code to decide what to do next as it simulates Things interacting with each other. Here is some Inform 7 code that I wrote while debugging the bug I'm going to talk about in this diary entry7:

Debug Room is a Room.

A Species is a Kind of Value. The Species are cat, dog, and mouse. A Person has a Species.

The player is female. The player is cat.

I first create a Room named "Debug Room". I describe a Kind of Value called "Species" that can be either "cat", "dog", or "mouse". And then I say that the Person Kind of Thing has Property of Kind "Species". The Person Kind of Thing was defined in advance so a Person also has a Gender Property, which I set in the next line to be "female" for the player. I finish out by setting the player to have a species of "cat".

This idea of objects with properties is a version of what has been the dominant programming language paradigm for my career, object-oriented programming8. The above code could just as easily be written in C# as9:

Room debugRoom = new Room("Debug Room");
// ...
public enum Species
{
    Cat,
    Dog,
    Mouse
}
// ...
public class Person
{
    Gender CurrentGender {get; set;}
    Species CurrentSpecies {get; set;}
}
// ...
Player player = new Person()
    {
        CurrentGender = Gender.Female,
        CurrentSpecies = Species.Cat
    };

Don't worry if you don't understand enough C# to read that, my point is to demonstrate that this is a paradigm that is very common. Any way, let's set another Property of the player, the description, which is printed when you type something like "EXAMINE ME":

The description of the player is "You look like your normal self, [player tail status] as you stand there
[player sound status]."

To say player tail status:
    say "your tail ";
    if the player is cat:
        say "swishing [if the player is female]under your skirt[otherwise]over your pants[end if]";
    otherwise if the player is dog:
        say "wagging [if the player is female]under your skirt[otherwise]over your pants[end if]";
    otherwise if the player is mouse:
        say "hanging limply [if the player is female]under your skirt[otherwise]down your pants[end if]".

To say player sound status:
    say "[if the player is cat]purring[otherwise if the player is dog]panting[otherwise]chittering[end if]".

The description is written to use an Inform 7 feature called "Substitutions". This lets me write something that may be complicated, such as what current type of tail the player has, with a short phrase like [player tail status]. I can define [player tail status] and [player sound status] as Phrases describing how to "say" those Substitutions. In those Phrases, I use several other Substitutions like [if the player is female]. The [if ...]...[otherwise]...[end if] Substitution was defined by Inform 7, and lets me pass in almost any condition like player is female to change what text is printed.

But more to our immediate point, I am querying the Properties of the player with my conditions here like if the player is cat to decide what to write. Now how do we change these Properties so we can have a fun little game where the player is able to transform their gender and species? That's where things get tougher.

Who Makes the Rules

Inform 7 code, like most other code, is made up of declarations that define entities, such as:

Declarations like these made up most of our first transcript:

Debug Room is a Room.

A Species is a Kind of Value. The Species are cat, dog, and mouse. A Person has a Species.

The player is female. The player is cat.

We can also make Declarations of Phrases that define new terms we can use elsewhere in the code as [player sound status] was:

To say player sound status:
    say "[if the player is cat]purring[otherwise if the player is dog]panting[otherwise]chittering[end if]".

There are more types of Declarations than these. Of importance to us and the main functionality that Inform provides to officiate what happens during a run of the game are the Rules in their various Rulebooks:

The magical switch is a device in Debug Room. The magical switch is switched on.

Carry out switching on the magical switch:
    now the player is female.

Carry out switching off the magical switch:
    now the player is male.

The first few lines define the "magical switch" Thing in Debug Room. The magical switch is a Kind of Thing called a Device which the player can attempt to "SWITCH ON" and "SWITCH OFF". To adjudicate the results of the "switching on" and "switching off" Actions, two Rules have been put into the "Carry out" Rulebook for each Action. These Rules change the gender of the player.

Don't be too upset if you think that last paragraph summarizing the code feels like it's introducing a lot of concepts, and that it's unclear where those concepts were introduced in the code (not to mention this entry!). This is part of the sublime beauty of Inform 7 in that a lot of the heavy behind-the-scenes concepts are "hidden" in such a fashion that a person writing in it may never even realize these concepts are coming into play. Inform 7 is a programming language that is very much aimed at people who are not familiar with programming. Thus, it tries not to bog down its "authors", the term for coders in its documentation, in a lot of concepts early. Figuring out that you can write what the magical switch does when switched on in a Rule which is named Carry out switching on the magical switch can be done without even realizing that there is a Rulebook named Carry out for each Action.

The manual for Inform 7, Writing with Inform, does not even introduce the Carry out Rulebook until its second time explaining actions. In theory, a person could write a whole simple game never learning about it. It's honestly very great. And then we programmers fuck it up.

A Very Brief Introduction to Functional Programming

So, one of the programming language paradigms that have come to challenge object-oriented programming is functional programming. "Objects", in the programming language sense, are an unholy matrimony between data and code. You box up some data into a box, and specify methods that define the code that messes with the data in the box. But it doesn't have to be like that!

Functional programming mainly is a sort of response that tying data and code together is bad. Method calls often change the data of the objects they are part of, that's kind of their point. But this means that each call to a method can have "side-effects" that are not made clear to code calling them. A simple example of a reasonable "side-effect" might be a list that keeps track of a count of how many items are in it. When you call a method to add an item to the list, obviously that count will likely be increased too. It's not really a "side-effect", just an "effect".

But what if, when you called a method to check if an item was in this list, this count went up for some reason? It doesn't make sense for that value to change then. The size of the list is not changing, after all. But there's also nothing stopping that count from changing in the method call except for presumably the good coding of the person who wrote the method. Even if every method is judicious in how it touches the data of its own object, these methods can still call methods on whatever objects you pass in, which may change their data. This can even happen directly if an object allows write access to its data by setting some of it as public.

Programmers quickly learn not to trust anyone. Not even themselves. Making mistakes or just plain misunderstanding things is too common for that. It's incredibly easy to think you understand how your code works to only see the computer reveal that you're wrong. It's easy to trust documentation, only to realize that the writers of the documentation were wrong and didn't account for a bug in the actual implementation. So this big trust fall of object-oriented programming is just not cool. What else we got?

Functional programming attempts to reorient languages away from mutable objects that combine both data and code to immutable data that can be taken in by "pure" functions that are side-effect free. These functions may take in arguments and produce a result, but they never modify their inputs or maintain "state" of their own. Proponents believe this move away from mutability makes code easier to reason about, and thus, lessens the cognitive load on programmers. Having code that your can understand better helps you to avoid mistakes. Detractors point out that the changing of values is almost always part of why code runs in the real-world and these languages often still allow you to mutate data, just hidden behind arcane mechanisms like Haskell's monads.

I, personally, am driven to accept the sort of hybrid paradigm taken by languages like the one I'm most fluent in, C#. These languages accept mutable objects, but also provide mechanisms for working in a more functional manner. One of these is the idea of "functions as first-class objects". That is, much like how I defined variables of type Room way back in my example of C# above, you can have variables that hold functions:

// A int Variable
int theAnswer = 42;

// A Room Variable
Room debugRoom = new Room("Debug Room");

// A function
int Double(int i)
{
    return i * 2;
}

// A function Variable
Func<int, int> myFunction = Double;

Once again, do not get too worried about syntax here, the concept that myFunction is a variable and thus a name I can use in my code. I can call the function, and even change its definition to another function:

// Calling through the variable, doubleAnswer will be 84
int doubleAnswer = myFunction(theAnswer)

// Another function
int Quintuple(int i)
{
    return i * 5;
}

// Changing variable to a different function
myfunction = Quintuple;

// Calling through the variable, quintuple answer will be 210
int quintupleAnswer = myFunction(theAnswer)

While this still has the idea of a mutable variable, the functions themselves are pure. It's not the perfect heaven of purely functional languages, but it gives me a way to be able to use pure functions in otherwise object-oriented code.

We've kind of gotten into the woods, here... Let's get back to our concrete example of making switches that transform the player in Inform.

What Happens when the Rules Aren't Kind

Because it's been a while, let me remind you where we are:

The magical switch is a device in Debug Room. The magical switch is switched on.

Carry out switching on the magical switch:
    now the player is female.

Carry out switching off the magical switch:
    now the player is male.

We have a magical switch that can change the gender of the player. That's nice but I want a switch that either changes gender or changes species based on another switch. And I may have a bunch of other types of changes I want to assign to this switch during the game and don't want to write a big if-otherwise block going over them all. Heck, I may want multiple of these switches all of which can have various effects! At its heart this is what Quoll is. The switches will be hidden behind a computer you have to write a program for to flip them, but the rest is what my goals are. Let's try some stuff!

Inform 7 very much also recognizes Rules as a Kind that it can work with. Rules can have some Kind that they are "based" on that they work with as input. Let's first define a new Kind of Device that all these switches will be, a transformation switch:

A transformation switch is a Kind of Device. A transformation switch has a Truth State Based Rule called the effect.

Carry out switching on a transformation switch (called S):
    follow the effect of S for True.

Carry out switching off a transformation switch (called S):
    follow the effect of S for False.

In this code we define a new Kind of Device, "transformation switch". We say that a transformation switch has a Truth State Based Rule Property that is called effect. Truth State is just what Inform 7 calls a value that can be either True or False, which is more often called a "boolean value" after George Boole, a mathematician who described such values. We then redefine the Carry out rules to defer to the effect Rule.

We can attempt to write some effect rules, putting them into a new Rulebook, since we need one for every Rule and don't want to put them in one of the existing Rulebooks:

The Switch Effect Rules are a truth state based rulebook.

A Switch Effect Rule for a truth state (called T) (this is the toggle-gender rule):
    if T is true:
        now the player is female;
    otherwise:
        now the player is male.

A Switch Effect Rule for a truth state (called T) (this is the toggle-species rule):
    if T is true:
        now the player is cat;
    otherwise:
        now the player is dog.

This defines a new Rulebook Switch Effect Rules and adds two Rules to it. Note that these Rules take in an argument rule for a truth state (called T) and are given a name like (this is the toggle-species rule). The first Rule, "the toggle-gender rule", changes the gender of the player, and the second Rule, "the toggle-species rule" changes the species of the player.

Now let's recreate our magical switch as a transformation switch, and add an effect switch to toggle the effect:

The magical switch is a transformation switch in Debug Room. The magical switch is switched on.

The effect switch is a device in Debug Room. The effect switch is switched off.

Carry out switching on the effect switch:
    now the effect of the magical switch is the toggle-species rule.

Carry out switching off the effect switch:
    now the effect of the magical switch is the toggle-gender rule.

This is where everything blew up. The Inform 7 compiler rejected this code with this error message:

In the sentence 'now the effect the of magical switch is the toggle-gender rule' , it looks as if you intend 'now the effect of the magical switch is the toggle-gender rule' to be asserting something, but that tries to set the value of the 'effect' property to a rule - which must be wrong because this property has to be a truth states based rule.

What? Huh? But our Rules are Truth State Based Rules, not just plain Rules that are more formally called Action Based Rules. Unfortunately, this seems to just be a bug in the compiler that anyone who uses languages that are picky about type (or Kind in this case) should be familiar with. These languages sometimes have trouble determining the actual type of things at compile time. C# is such a language, and if you look at the C# code samples so far, you may notice a lot of places where I'm careful to specify what type variables are.

Languages can have ways to work around the compiler being confused here, but Inform 7 does not seem to have those. It can be argued that this is a bug in the compiler, but there's another way to look at this. Namely, I'm trying to import how I would do things in C# to Inform 7 and that's not how Inform 7 wants me to do it. The paradigms of Inform 7 are different. Rules are not really "first-class objects".

When Runs the Rules

I basically realized that I was working against a paradigm here, and looked back over all the information you can include in the preamble of the Rule. We've seen most of them:

A Switch Effect Rule for a truth state (called T) (this is the toggle-gender rule):

A Switch Effect rule puts this Rule in the Switch Effect Rules Rulebook. This Rule is based on a Truth state (called T). It has a name, "the toggle-gender rule".

But there are more things you can specify when declaring a Rule, and in particular my solution was in noticing rules could have conditions:

Check entering The Men's Bathroom while the player is female:
    say "You are about to head in, but stop, when you realize it's the wrong bathroom. You'll need a good reason to
    head in there.";
    stop the action.

This is a Rule that goes into the Check Rulebook that is consulted for the entering Action, in particular when the player is entering a Room called The Men's Bathroom. But on top of that, the Rule only runs under a certain Condition, while the player is female. If the player switches gender to male (perhaps through our switches in the model world), this Rule will not run. Pretty straightforward, right?

But this provides the solution. Rather than changing out the Rule during play, just limit when it runs by setting certain conditions. Building on these conditions, Inform 7 allows the player to define Scenes that trigger when a condition is met:

Grue Attack is a Scene. Grue Attack begins when the player opens the grue cage.

And we could write another rule to save our hapless player who is not going to bother with gender norms when slavering grues that might eat her are about:

Check entering The Men's Bathroom when the player is female during Grue Attack:
    say "You pause a second before entering the wrong bathroom, but as a grue nears, you decide to just bolt in. Better
    to be in the wrong bathroom than a grue's stomach.";
    continue the action.

This Rule only runs during Grue Attack so the Gender Norms of the Model World are kept safe. Yay? Maybe our player character will fight those next when she's done with the grues.

Using a Scene made the most sense for me in Quoll, but it isn't necessary for a working version of the transformation switch scenario:

The magical switch is a device in Debug Room. The magical switch is switched on.

The Switch Effect Rules are a Device based rulebook.

Carry out switching on the magical switch:
    abide by the Switch Effect Rules for the magical switch.

Carry out switching off the magical switch:
    abide by the Switch Effect Rules for the magical switch.

The effect switch is a device in Debug Room. The effect switch is switched off.

A Switch Effect Rule for the magical switch while the effect switch is switched off:
    if the magical switch is switched off:
        now the magical switch is switched on;
        now the player is female;
    otherwise:
        now the magical switch is switched off;
        now the player is male.

A Switch Effect Rule for the magical switch while the effect switch is switched on:
    if the magical switch is switched off:
        now the magical switch is switched on;
        now the player is cat;
    otherwise:
        now the magical switch is switched off;
        now the player is dog.

Now, the Switch Effect Rules are based on the switch itself rather than its state. This allows us to provide different Switch Effect Rules for different switches. We can still have other things, like the effects switch control when those rules run, by supplying conditions to the Rules. This works, and it fits the paradigms of Inform 7. It'll do.

Your Not-so-humble Dev,


  1. The term "dogfood" allegedly comes from the real job that a human has at a pet food factory test-tasting dog food. I mean, yeah, one person eating a bit of dog food to help ensure other people's dogs won't immediately get sick seems worth it. They should probably get paid way better than they are being paid, I bet.

  2. "Interactive fiction" is literally a buzzword Infocom used to sell their games! Wake up, sheeple!

  3. I've actually never played Zork I or any of its sequels to completion myself. I've read playthroughs and there's lots of amusing writing and interesting challenges. It's a good game, but the design sensibilities are... dated. I think there's a handful of difficulty through obscurity that was tolerated more in those days that are unacceptable now. It was the first of its kind and I think Infocom, the developers, really improved as they began iterating on the idea. Feel free to play it and tell me how wrong I am. There's a link to an online version in the next footnote.

  4. I'm using all-caps here so it's clear what's a command and what's not, but most people generally type in lower case when they play these games. All-caps is also what I am using in the game to imply commands the player can type in. It's a common way to denote the distinction. And yes, I booted up a version of Zork I you can play in a browser to make this transcript.

  5. This agender terminology for the player character is not me being a wacky SJW. Zork and other Infocom games often strove to make their player characters AFGNCAAPs, that is "Ageless, Faceless, Gender Neutral, Culturally Ambiguous Adventure People". There's a pretty simple reason: it allows more players to project themselves onto such a character. It's why silent protagonists abound in video games. It can make somewhat challenging writing, but I think it's generally worth it unless you want to very specifically have a certain character as the player character as I do in Quoll.

  6. Rooms and Things actually both inherit from a Kind called "Object", but don't keep that in mind right now.

  7. You probably notice how close this looks to normal English. Put a pin in that, its closeness to English is one of the major good/bad things about Inform 7.

  8. There is a reasonable argument to make that object-oriented programming was a mistake. I definitely don't disagree with that argument, but it's too late. We live in the bad timeline and it's still the Queen. We'll just have to struggle on, under her rule, no matter how many clever languages named after mathematicians we write.

  9. This is probably not the way you would write this in C#, and I have excluded what would likely be other code tying it all together. That's what all the "// ..." comments represent. But I wanted to match the Inform 7 code closely.

Jayce Mitchell