Fiction, of the Interactive Variety
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 lamp
2
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.
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
Or other languages; there have been parser-IF works released in French, Italian, Spanish, and probably more. ↩︎
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. ↩︎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. ↩︎
I have not attempted this myself (yet?), although a portable e-ink device does sound like the ideal IF platform. ↩︎
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. ↩︎
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. ↩︎
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].’ ↩︎
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. ↩︎
Or a certain Jedi. ↩︎
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 instructionUse authorial modesty.
, which excludes it from this listing. ↩︎