I presented this talk at Penguicon 7,
on May 3, 2009.
I also gave a (high-speed) rendition at
BarCamp Boston 5,
April 18, 2010.
Download the slides for this talk (PDF, 325k).
As I write this, Inform 7 is approaching its third birthday. I7 is a tool
for creating interactive fiction (text adventure games). Like all the most
powerful IF development tools, I7 is a programming language — a powerful
and peculiar one.
Inform 7 gets a lot of attention for its English-like syntax. I’m not going
to talk about the natural-language aspects of I7. I’m going to talk about
the underlying programming model, the system of rules and rulebooks. That’s
less attention-grabbing than the flashy syntax; but, in my opinion, it’s
equally radical. And perhaps a more important development, in the long run.
To be fair, I also like talking about the rule-based programming model
because I contributed some of its ideas, back when I7 was first taking
shape. I’m not claiming authorship here, mind you. I got into a long and
digressive email conversation with Graham Nelson and Emily Short, in which
we all threw ideas around, and then Graham went ahead and spent six years
developing his ideas. I shoved mine on the shelf.
This means that I will talk about I7 for a while, and then break into a wild
flight of “but this is how I think it should be done!” And then
finish up with all the reasons I haven’t made it work yet. Such is a
hacker’s life.
The rule-based programming model is not new. It’s got connections to logical
programming languages, as in Prolog. It’s related to (or maybe the same as)
aspect-oriented programming. But why do I like this model? Well, why does
anybody like any kind of programming model?
A programming language is a tool for handling design complexity. That’s what
all of computer science is, really — languages, libraries, type systems,
garbage collectors, everything you learn about programming. They’re ways to
build more and more complex designs without losing your grip.
The way you manage complexity is to be able to ignore it. A good programming
tool lets you forget about some part of the problem, so that you can focus
on some other part. And it ensures that when you return to the parts you
forgot, you haven’t accidentally broken them.
The first work of IF, Adventure, was a messy FORTRAN hack. The
second, Dungeon (or Zork), was created at MIT, so it was a
messy LISP hack. (Actually a LISP dialect called MDL, which is why you see
angle brackets as well as parentheses.)
These were messy because they were built as games, not game-design tools.
The creators were figuring out how to build IF worlds on the fly.
The MIT gang went on to create the first well-known IF design
system, under the Infocom banner. It consisted of a virtual
machine, a language called ZIL, a compiler for the language, and a parser
library written in the language. ZIL looked a lot like MDL, but it actually
had a much simpler structure; it was essentially BASIC written with
parentheses. (I mean angle brackets.)
Adventure and Dungeon sparked lots of imitators. The next twenty years gave
us a whole roster of IF development systems. These were mostly created by
people who had learned to program in the 70s and 80s, so the languages
followed the programming models you’d expect: BASIC-like, C-like,
Pascal-like. Although, to be fair, a couple were genuinely LISP-y.
How did these systems compare? Small code samples, like the ones above, show
off difference in syntax. But the underlying code model is important too.
For example, the version of Adventure I quoted was written almost
completely without functions. (Just two small utility functions are used.)
The parser and game logic are all one mass of loops and gotos. Nobody would
consider writing a complex program that way today.
So, the simplest IF systems had if/else statements. The more advanced ones
had functions and data structures. The most complex aspired to what people
thought of as the sacred peak of computer science: object-oriented
programming.
It’s not hard to imagine why designers moved towards OO design tools. Think
about what a typical IF game comprises. You’ve got some rooms, which
contains objects. Some objects are containers, so they have other objects
inside them. And then you have some representation of the player, which can
also contain objects.
Everybody likes to generalize, and the obvious generalization is to say that
a container is a kind of object. Then the player looks like a special
object, and rooms can be objects too (or maybe a special kind of container).
Presto, everything in the world is an object. Maybe object-oriented
programming would be a good fit!
Here’s a common OO pattern: an object field (or property). Every object in
the game has a description, its response to the “EXAMINE” command. A field
works nicely for that. We can define a description field in the base
class, of type string. This basic description applies to all objects, as a
default; we can then override it for specific objects.
This is working really well! It gets a little trickier, though, because many
objects have dynamic descriptions. They change over the course of
the game. For example, the bottle can be filled with either water or oil.
We could handle this by assigning a new value to Bottle.description every
time the bottle changed state. Sometimes that’s practical. But we’d like a
more flexible option; we’d like to associate some code with the bottle which
computes the current description.
Code associated with an object is, of course, an object method. Here’s the
object description implemented as a method instead of a string field.
By the way, this model leaves us with a lot of one-line print methods. Many
descriptions aren’t dynamic. So it winds up being convenient to
allow a shortcut, where you say “description: STRING” and interpret that as
a method that prints STRING. Inform 6 has this shortcut, for example. But
it’s syntactic sugar; it doesn’t change the model.
I’ve used a more interesting shortcut here. Some OO languages, like Java,
are very strict about the distinction between objects and classes. You
define classes, and then instantiate them to create objects.
That’s a nuisance in IF, because most objects in an IF game are unique.
Adventure only has one glass bottle. It would be tedious to have to
define a Bottle class and then do Bottle.new(). So OO IF languages tend to
use a prototyping model: you can define a one-off object with its own fields
and methods, and then maybe subclass it if you need to. That’s what I’ve
done above.
Descriptions are a bit of a special case. The basic behavior of an IF game
is a read-parse-perform loop: read a command from the player, parse it into
action data, and then execute the action. How does that look in an OO
system?
Let the parser be a black box. (A black box with months of fiddly work
inside it, sure.) It sucks in the player’s command, and spits out a tuple
(VERB, NOUN). OO doctrine says that a VERB is a method, and a NOUN is an
object. So we call that method of that object. End of turn, read the next
command.
So we define a bunch of objects, give them appropriate action methods, drop
the parser on top, and we’ve got a game, right? Right… until we try to do
anything more complicated. Then it falls apart like a bundle of ball
bearings.
When it’s dark, you can’t examine anything; or rather, you always get the
same response: “It’s too dark to do that.” But where do we put that check?
It applies to all objects, but if we put it in the Object.examine() method,
any specific object method will short-circuit it. We could paste the same
check into Sword.examine(), Lamp.examine(), … every object in the game…
but that’s the kind of repetitive coding style that OO was supposed to
fix.
What about the “LOOK” command? That doesn’t have an object at all, which is
a little problematic for object-oriented code. Maybe single-verb commands
should be routed to the room. That makes sense for “LOOK”, but not so much
for “INVENTORY”.
“PUT MEAT IN BASKET” is even worse — it has two objects, a direct
object and an indirect object. We could invoke a method on the Meat object
or the Basket object. Really, we’d want to invoke methods on both,
because either one could customize the default “PUT X IN Y” action. (A
thimble is too small to put most objects in; smoke is too diffuse to be put
into anything.)
The method we’re writing might belong on some other object entirely. Imagine
a room with an orc and a pie. If you try to take the pie, the orc kills you,
unless you’ve killed him first or otherwise removed him from the room. It
would be logical to send the orc object a message for every “TAKE” action in
the room. Where does this stop? Should we invoke a method on every object in
the room for every action? (In what order?)
Another fun case: some checks apply to multiple actions. An electrifie