Fiction, of the Interactive Variety

Posted:
Tags:

Interactive fiction (or IF) is one of the oldest forms of computer games, dating back to the 1970s; and yet, there is a certain magic to the experience that I feel other, more modern formats would be hard-pressed to replicate.

For those unfamiliar, IF encompasses pretty much any game—or, perhaps more broadly, interactive work—where the player interacts with the world primarily by reading and/or writing text. The category famously includes parser-based games, commonly called “text adventures,” where the player types more-or-less-natural English1 commands such as take lamp2 or go east and the game describes what happens as a result of those actions. However, it also includes choice-based games, in which you select from a number of pre-defined options to direct the course of the narrative. Opinions differ on whether to include gamebooks like the Choose Your Own Adventure series, which are akin to choice-based games but aren’t software; or visual novels like Steins;Gate, which are software but which some might consider to be too dependent on sound and graphics, and often have a comparatively limited degree of interactivity. The modern IF community has largely moved away from the “text adventure” term, partly to be inclusive of non-parser works and partly because many interactive stories now are written with a more literary, narrative- or character-driven focus, as compared to earlier games’ focus on solving puzzles for puzzles’ sakes.

While I did read a few Star Wars-themed Choose Your Own Adventure books in my youth, and I have played a handful of visual novels, it’s the parser-based games that I really find captivating. The illusion that you are “speaking” to the game—and that the game understands—is a powerful one, as is the corresponding illusion of freedom. These games felt open-world long before the modern trend, and while a masterpiece like The Legend of Zelda: Breath of the Wild3 affords a huge variety of possibilities (often with humorous results), you still cannot even attempt an action unless the UI gives you that option. A parser game may not understand or allow everything you can possibly think of—hence, “illusion”—but you can try it anyways, and the best games manage to anticipate enough of what a player is likely to attempt that they can at least reward the player with a snarky quip; something that says “I’m not going to let you do that, but I saw what you were going for.”

IF has some practical advantages, too. Thanks to the continued influence of the virtual Z-machine and its more capable successor, Glulx, most parser games will run on nearly anything, including web browsers, smartphones, and even a (jailbroken) Kindle.4 It’s inherently turn-based, with each turn taking only as long as you need to consider what to do—which can be done away from the screen—and type it in, making it ideal for squeezing into a busy schedule. IF games hardly ever involve sound or music, so you can play it in quiet or public environments without needing headphones and without degrading the experience.

Some IF games do include sound effects, music, and/or art to a limited degree, but it’s not a requirement.5 Consequently, in my estimation, IF may well have the lowest barrier to entry of any form of game development; if you can write, you can independently create a work of IF. Which, after five paragraphs, brings me at last to the intended topic of this blog post:

The Inform programming language

Ever since I first played a text adventure (Infocom’s 19806 debut game Zork, which I encountered probably around 2010), I’ve wanted to make one of my own. Being a programmer, over the years I’ve tried perhaps two or three times to put together an engine that could handle basic parsing and world-modeling, but I never got far before a lack of either time or interest (or both) put an end to the project. My latest attempt concluded when I came to realize that the amount of effort required to make something even remotely on par with the features of Infocom’s engines forty years ago well exceeded what I was prepared to commit to. On the plus side, this realization led me to (re)discover Inform.7

Inform is a language that was specifically designed for authoring parser-IF games, and it is fascinating to me. It aims to make the act of writing IF as accessible as… writing, and it does so by eschewing traditional programming paradigms in favor of a rules-based world model shaped by declarative, very-nearly-natural English phrases.

As an example, the simplest valid Inform program looks like this:
The Study is a room.

That’s it; that's the entire program. Of course, it’s not terribly exciting;8 this just creates a single, empty room (called “Study”) and dumps the player in it, with nothing to do.

I spent a few hours playing around with the syntax, and in the process whipped up something that I think makes for a decent little demo. It’s far from perfect, but it’s enough to show off most of the basic functionality. I’ll let you try out the resulting game first, and then highlight some interesting bits from the code down below (the full source is on GitHub). If you’re brand-new to IF and you’re not sure what to type, take a look at Andrew Plotkin’s IF reference card.

Loading...

If we ran that first, minimal program—The study is a room.—then the initial text above would have stopped with the room title, “Study.” The program would have understood most of the standard verbs, but it would have stopped any attempted action with one of several default responses.

The first thing we might do to improve upon this is to give the room a proper description:

The description of the Study is "Not quite cramped, but not expansive either; a stately desk pushed against the far wall fills most of the space. An aging wheeled chair stands vigil before it."

Unsurprisingly, assigning descriptions to things is something that happens a lot in Inform code. Consequently, you can take a bit of a shortcut by putting the quoted text immediately after the declaration of the thing:

The Study is a room. "Not quite cramped, but…

If we stopped here, the player would be able to read that there is a desk and a chair in the room, but if they tried to do something like examine desk, the game would respond with “You can’t see any such thing.” We can bring something called a desk into being with:

The stately desk is in the Study.

but this leaves us with a few problems. First, the “desk” won’t act like a desk, because Inform doesn’t actually know what a desk is. Secondly, the game will print “You can also see a stately desk here.” at the end of its initial output; by default, Inform lists all the objects contained in a room, and it doesn’t know that we’ve already mentioned the desk in the room description.

Modeling the desk

Here’s how we can make the desk actually seem like a desk (with a drawer, no less):

The stately desk is scenery in the Study. Understand "table" as the desk. It is an enterable supporter. The description is "Standing proud and squarish, the desk appears worn yet still elegant to look upon. Its dark walnut surface is covered with innumerable faint scuffs and scrapes denoting its many years of service. There is a wide, shallow drawer embedded in front[if the drawer is open], which is currently open[end if].".

A drawer is part of the stately desk. The description is "Like the rest of the desk, the drawer is faced with walnut and worn from age. It is adorned with an antique-brass handle.". The drawer is a closed openable container.

There’s a lot to digest in those two paragraphs. First, scenery implies that the desk is a part of the room, as opposed to a more general object that the player might carry around with them. This one word immediately disallows actions such as take desk, replying “That’s fixed in place.” (We can override this message with something more specific, but I haven’t done so here.) It also tells Inform that we, the author, are taking responsibility for alerting the player to the desk’s presence in the room, typically via the room description.

Next, we have Understand "table" as the desk.. We declared the desk with the two-word identifier stately desk, which means the player could type any of examine stately desk, examine desk, or (somewhat awkwardly) examine stately in order to see the desk’s description. However, in real life, a person can refer to a desk as a table (or vice versa) and others will naturally know what they mean. This sentence allows the game to understand that if the player says examine table, they mean the desk.

Notice also that we’ve said the desk here, not the stately desk, and in the very next sentence we say just It. The Inform compiler contains some very powerful inference rules that allow it to work out that, in each case, we are referring to the object stately desk. If we were to introduce another desk, however, then we might need to be more specific.

Next: It is an enterable supporter. A supporter is a thing that we can put other things on top of; an enterable supporter is a thing that the player can themselves be on top of. This makes commands like stand on desk or put key on desk work.

The next sentence is a description, which we’ve seen before. Unlike a room description, which is printed automatically when the player enters a room (or enters the look command), an object description only appears if the player chooses to examine that object. This allows the author to hide puzzle clues, requiring the player to look around carefully; or just to provide some extra atmosphere. This particular description also demonstrates Inform’s text-substitution syntax: [if the drawer is open], which is currently open[end if].. The text between the two bracketed expressions will only be printed—quelle surprise—if the desk drawer is open. (Note once again that the Inform compiler is smart enough to deal with the reference to drawer here, even though we haven’t actually declared this object just yet.)

Finally, we move on to the drawer. A drawer is part of the stately desk. means that anything the author or the player says about the desk applies to the drawer too, such as which room it’s in. Further, the player can’t attempt to do things like take drawer; instead they would have to take desk, which we’ve already forbade by declaring the desk as scenery. A container is a similar concept to a supporter, except that we can put things in it instead of on top of it. An openable container is one that can be opened and closed with e.g. open drawer. Typically, the contents of a container are only visible to the player when it is open. A container can also be enterable, like supporters; for instance, we might define an armoire that the player can hide in.

That covers pretty much everything that you need to model simple things, like most furniture. However, this demo contains two other, more complicated objects that need additional rules: the wheeled chair and the desk lamp.

Modeling the chair

Like the desk, the chair is scenery and an enterable supporter, so the player can’t take the chair but they can sit in the chair. However, Inform’s default message about taking scenery—“That’s fixed in place”—doesn’t make much sense for a chair that we’ve explicity described as having wheels, so we’ll have to override it:

Instead of taking the wheeled chair, say "It's rather too bulky to carry around. Besides, it would be a shame to separate it from the desk.".

The instead of phrase is the easiest way to customize the behavior of existing verbs, or to define new ones. A player might reasonably expect to be able to push a wheeled chair around, and most wheeled desk chairs can also spin:

Instead of pushing or pulling the wheeled chair, say "[if the player is in the chair]You glide[otherwise]It glides[end if] easily across the floor."

Spinning is an action applying to one thing. Understand "spin [something]" as spinning.

Instead of spinning the wheeled chair, say "The chair spins around, creaking slightly[if the player is on the wheeled chair]. You feel a little dizzy[end if]."

Manipulating the chair like this doesn’t actually need to have any effect on the state of the game world—we’re not tracking the precise location of the chair within the room, and the player can imagine things however they darn well please—so all we need to do to implement these commands is print out an appropriate description. We do, however, need to prevent the player from spinning things that aren’t the chair:

Instead of spinning something, say "That wasn't designed to be spun."

An experienced practitioner of more traditional programming languages,9 seeing such an absolute statement, might feel a warning tingle in the back of their head. Surely it ought to be Instead of spinning something that is not the wheeled chair? And sure, that syntax works too, but it isn't necessary. The rule Instead of spinning the wheeled chair is more specific than Instead of spinning something, so it takes precedence, even if we put the general case higher up in the source code (and indeed, I did in the actual program; it seems to be good practice to define the default response to a verb immediately after defining the verb itself).

Modeling the lamp

The desk lamp is the most complicated object in the demo: it can be turned on and off, it can be unplugged and plugged back in, and you can even remove the lightbulb. You can also pick it up, but that’s default behavior for objects in Inform; unlike the other items discussed so far, we just have to not override it by not declaring the lamp as scenery:

The lamp is on the desk. The printed name of the lamp is "desk lamp".

Note the printed name phrase; this permits us to give the lamp a different name in the text seen by the player than we use in the program itself. In this case, Inform would have understood “lamp” anyways, just like with the stately desk earlier, but we could use entirely different terms if that suited our purposes.

Fortunately, Inform has built-in ways of handling things that can be switched on or off (called devices) and which can provide light:

The lamp is a switched on device. The lamp is lit.

We have to create a custom property to track whether the lamp is plugged in, however, and also custom actions to allow the player to unplug and plug in the lamp:

The lamp can be plugged or unplugged. The lamp is plugged.

Plugging is an action applying to one thing. Understand "plug in [something]" as plugging.

Carry out plugging the lamp:
    now the lamp is plugged;
    if the lamp is switched on and the lamp is operable:
        now the lamp is lit.

Report plugging the lamp:
    Say "You plug in [the lamp][if the lamp is switched on and the lamp is operable]. The bulb flares to life[end if].".

Instead of plugging the lamp when the lamp is plugged:
    Say "[The lamp] is already plugged in.".

To decide if the lamp is operable:
    if the lamp is plugged and the lightbulb is in the socket:
        decide yes;
    otherwise:
        decide no.

We also need to define an action to unplug the lamp, but that’s very similar to the code for plug in.

Carry out and Report are related to Instead of, but a little more complex. Both occur only when an action is successful; we can preempt them by defining an Instead of rule, or with a Check plugging: rule that determines whether it is possible to perform the action. Carry out is where we define the alterations to the game state that should occur as a result of the action (which wasn’t necessary for pushing or spinning the chair), and Report is where we describe the results to the player.

Naturally, the lamp should only light up when it is plugged in and turned on and has a lightbulb in it; and those three conditions can change independently, so we need rules applying to each condition that, after every change, check whether all three are now satisfied. It would be tedious to write out if the lamp is plugged and the lamp is switched on and the lightbulb is in the socket every time, but we can shorten this somewhat by defining an operable property and explaining to Inform how to decide if the lamp is operable.

The removable lightbulb works in a similar fashion to the desk drawer, except that we want to make sure the player can’t put other things into the lamp that wouldn’t make sense:

The socket is part of the lamp. The socket is a container.

Instead of examining the socket, try examining the lamp.

Instead of inserting something into the socket:
    if the noun is the lightbulb:
        now the lightbulb is in the socket;
        say "You screw the bulb into the lamp[if the lamp is operable and the lamp is switched on]. As you finish, the bulb flares to life[end if].";
        if the lamp is plugged and the lamp is switched on:
            now the lamp is lit;
    otherwise:
        say "That's not a lightbulb."

Instead of inserting something into the lamp, try inserting the noun into the socket.

Notice that the socket is a container, but not an openable one; we can put things in it, but close socket would be regarded as nonsense. Putting things into a container is known internally by Inform as inserting something into it. The state change now the lightbulb is in the socket would ordinarily occur automatically when inserting, but since we’ve used an Instead of rule to circumvent the normal behavior, we have to handle this ourselves.

We also define two Instead of rules to redirect examining and inserting actions between the socket and the lamp itself. This helps make interactions involving the socket a little more natural. The player can say put lightbulb in lamp instead of having to say in socket, which is especially important given that we haven’t actually mentioned the socket to the player at all. As far as they’re concerned, the socket need not even exist; the only reason we’ve bothered to name it is because a single, non-compound object in Inform cannot be both a device and a container (or any other two kinds of thing).

I haven’t even mentioned the door or its key yet, but this post has already become quite long. The last feature I will point out is that Inform, like many languages, includes a mechanism for re-using code between projects:

Include Locksmith by Emily Short.

Inform calls these extensions. Here, “Locksmith” is the name of the extension, and Emily Short is the person who wrote it. Specifying the author’s name serves both to disambiguate between extensions which may have the same name, and also to give credit to said author directly in the source code.10 The Locksmith extension implements a number of lock-and-key niceties, such as automatically unlocking a locked door if the player attempts to open door while carrying the appropriate key, without the player having to explicitly unlock door first.

Well, that’s probably about enough from me for today. If this has piqued your interest, there are hundreds more (and no doubt, better) examples available in the Inform documentation, and the manual itself is quite interesting and readable.

Footnotes

  1. Or other languages; there have been parser-IF works released in French, Italian, Spanish, and probably more. ↩︎

  2. Aside from the earliest examples, most parser-based games would also accept the more complete sentence take the lamp, but there is something to be said for brevity from the player’s perspective. ↩︎

  3. Just to be 100% clear: I am not trying to claim that IF is necessarily better than modern games, including but not limited to TLoZ: BotW; only that IF still has something to offer even in today’s gaming landscape. ↩︎

  4. I have not attempted this myself (yet?), although a portable e-ink device does sound like the ideal IF platform. ↩︎

  5. IF games typically are expected to have cover art, but this doesn’t have to be anything spectacular, and in any case it’s a far cry from the kinds of assets that are necessary for more graphically-oriented video games. Some IF authors also choose to create “feelies,” in the tradition of Infocom games, though again this isn’t a requirement. ↩︎

  6. 1980 saw the first release of Zork for personal computers, by Infocom; the game was originally written on a mainframe at MIT in 1977 by a group of then-students who went on to found Infocom in 1979. The personal computers of the day weren’t capable of running the entirety of mainframe-Zork, so they split the content (with many alterations) into three parts published in 1980, ’81, and ’82. ↩︎

  7. The current Inform language was originally known as Inform 7, and indeed that name still appears in many places, including on the official website. However, Inform 7 was a massive departure from Inform 6, and Inform 7 programs actually transpile to Inform 6 code en route to their final executable form. Per the recent announcement of the first open-source release, “[w]e now usually call it simply Inform, since the ‘7’ is not meaningfully a version number any longer, except to make clear that we don’t mean [Inform 6].’ ↩︎

  8. Yes, I just used a semicolon two sentences in a row. Ordinarily I would consider that somewhat gauche, but I've got to make up for the lack of semicolons in the code segments somehow. ↩︎

  9. Or a certain Jedi. ↩︎

  10. Ordinarily, the names and authors of any extensions used are also credited in-game if the player runs the version command; however, Locksmith is included with the Inform compiler as a sort of “built-in” extension, and (presumably because of this) its code is prefaced with the instruction Use authorial modesty., which excludes it from this listing. ↩︎