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
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
Touchto represent an event where an object is touched.
- Create subclasses of
Pushto represent their respective events. Before handling their own action, delegate to the superclass.
- Ensure that on handling any
Touchevent, the lever electrifies the player.
- Let the parser determine that the user wants to pull or push the lever
- Send a
Pushevent 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!