IF and World Representation

I've tried a few times to make a framework for Interactive Fiction (more specifically, text-based natural-language-controlled games). I've had some success, but I feel like each time, I've jumped into coding too quickly before I've really thought about the underlying logic in sufficient detail.

This post aims to brain-dump my current thoughts on the best ways to represent worlds and actions taken in those worlds using modern programming techniques. Because of that last criterion, I probably won't put much effort into looking at the source of things like Zork and Adventure: although they're great games, they were written with plenty of limitations in mind that I don't have right now.

Just as a quick disclaimer: unlike a lot of IF technology (like, for instance, Inform), I don't intend my framework to be particularly accessible to non-programmers. It'd be nice, sure, but I think the best underlying design will arise from making a product that works well for programmers, using well-known design patterns etc.

One essay I've found particularly challenging is Andrew Plotkin's Rule-Based Programming in Interactive Fiction. It introduces the most commonly faced problems on the technical side of IF, and introduces a few commonly-suggested solutions.

In particular, one thing caught my eye: the problem that "OO has no notion of methods subclassing other methods". He's completely right: it doesn't. But it made me consider a very subtly different approach based on two important Python-specific facts:

  • "Methods" aren't anything special - they're basically functions with the first argument being an object instance; and
  • "Functions" aren't special either; we can safely substitute a builtin function for an object instance implementing a __call__ method.

So: why not implement a system where the "verbs" are represented by a single callable? Following his example of the electrified lever:

  • Create a class Touch to represent an event where an object is touched.
  • Create subclasses of Touch called Pull and Push to represent their respective events. Before handling their own action, delegate to the superclass.
  • Ensure that on handling any Touch event, the lever electrifies the player.
  • Let the parser determine that the user wants to pull or push the lever
  • Send a Pull or Push event to the lever accordingly

This example is hand-wavy and doesn't mention specifics of where exactly the logic of player-electrification or action-interruption (the player can't pull the lever if it's just electrified them!) takes place. Hopefully, though, it goes some way to demonstrating that all hope is not lost for object-oriented programming with IF.

Coincidentally, this also solves the problem of operation-precedence mentioned further down. Python gives us a strictly-defined method resolution order even for multiple inheritance (e.g. if a chalice is both a Treasure and a Cup). This means we don't really need to worry about "exceptional cases" - using our model, that's just a scary way of describing specialization, and using introspection it's very easy to verify it's doing exactly what we want it to.

There's a lot more to think about: Andrew Plotkin's observation that tweaking IF leads to N-squared interactions as the game grows is really starting to hit home. It's a problem that seems so much easier than it really is. Hopefully I'll be able to continue solving it piece-by-piece!