Intfic / Designing a standard library for interactive fiction
We usually think of programming languages and their standard libraries as forming one coherent whole, and for general-purpose languages this might as well be the case. But in text adventure authoring systems the two are more divorced. Often, the language proper has few or no features specific to interactive fiction, and it's the library that makes it into an authoring system, properly speaking. Such is the case with TADS 3, which has recently acquired a new standard library called adv3lite and Inform 6, which has several alternatives, none especially popular.
A special case is Alan 3, which I reviewed a while ago, and which has enough built in functionality that you can in principle do just fine using just the raw language. However, it does have a standard library made by Anssi Raisanen, which is impressively rich as such things go.
Then why did I choose to make my own instead?
Why, because I could! Just kidding. But seriously, the task seemed much less daunting than it would have been in any other authoring system. In fact I only thought it feasible at all due to not having to code a parser, as one is built into the interpreter. Designing the library also helped my learning process a lot. But the main reason was that the existing library is based on a philosophy I just couldn't agree with. Namely, authors are expected to customize it for each and every game. And that means forty thousand lines of code to understand and maintain with each new project.
Now, I can see why it was designed this way -- certain peculiarities in the language make the approach tempting. And if you're making an epic on the scale of Worlds Apart for example, the overhead may not be that big. Besides, a large proportion of those forty thousand lines, possibly as much as half, are comments, and the author's understanding of Alan helped me more than once when I was at a loss to figure out how this or that feature is supposed to work.
So, what's in a standard library for interactive fiction?
First, of course, there are the meta verbs such as save and transcript on/off. Except the language doesn't let you mark them as meta, so they'll use up a turn anyway. Then the absolute basics, "look" and "examine", to which I've also added "wait" and "search". (Also "use", because many players, especially those less familiar with the genre conventions, will try it). Basic door and device classes, too, which are needed in my game, and last but not least a full set of conversation verbs since the parser is complex enough to support them.
Inventory management deserves special mention. It's tempting to make the hero himself into a container (that's the PC in Alan lingo), but that would require predefining him at the library level, and once you do that there's no way to further customize him within the game, e.g. by adding a description. So instead I made a separate object that invisibly follows the hero. It's even possible in theory to replace it with an inner location, so you can pick up and carry actors, but for now I went with the simplest thing that could possibly work.
the player_inventory isa object name inventory description "" with container taking thing. -- So you can *attempt* to pick up actors. header "You are carrying:" else "You are empty-handed." verb examine does only list this. end verb. verb take does only list this. end verb. end the player_inventory. add to every location entered if current actor = hero then locate player_inventory here. end if. end add to.
Speaking of that, a thorny issue in IF library design is how generally to define verbs. If actors aren't portable, for example, you might decide to have the verb take only apply to objects (which is the Alan default anyway), or even a Portable class defined for this purpose. But often it makes sense to try picking something up even if the game won't actually allow it. For example, your cat. And even for obviously fixed objects, you may want to customize the failure message, or even toggle the portable flag, e.g. if the hero suddenly develops superpowers! So be as general as possible.
On the other hand, I was tempted to add the darkness code to the predefined Location class, but reconsidered for two reasons. First, few or no rooms in any game will ever need to be darkened, so the respective checks will be a useless overhead most of the time. Second, having a distinct DarkRoom class means you can bypass it entirely and roll your own mechanism if you want. Especially as I went with a primitive implementation which doesn't consider portable light sources. (Nor is such a thing defined in the library.)
every darkroom isa location is dark. description check this is not dark else "It's too dark to see much." verb examine check this is not dark else "It's too dark to get a good look." end verb. end every.
While on the subject of classes, I've run into a number of language limitations. One, as mentioned in another article, is an inability to redirect actions. Which means that going through a locked door would require the player to unlock and open the door manually every time, and I didn't even want to think about having a known key mechanism. So instead I made a rudimentary door class and allowed players to simply pass the exit checks silently if they are carrying the right key. There is also the fact that object properties in Alan can't have a null value, so a door must have an "other side". But that eliminates an entire class of potential programming bugs, so.
(On a related note, you can't define a "catch-all" action for verbs. You can group verbs in action declarations if their syntaxes are compatible, but you can't say "for any other verb, do this". So no Distant or Unimportant classes for you; the amount of boilerplate required would be beyond absurd. That's also because of the strong typing.)
Another problem is that you might want to open and close objects that aren't doors, so those verbs really have to be defined on the Object class. And that caused a problem with the Device class, where I did the same with "turn on/off". Which in turn required me to use the "only" verb qualifier... and that meant that "after" refused to work on an instance of the same class in the game. Certainly, using "before" to schedule an event had the desired effect, but still.
A final, minor addition was that of a predefined Limbo location with all the usual compass directions defined on it. That serves the dual purpose of having an "out of play" location that can be tested (and where objects can be sent if needed) and letting Alan know that 'ne' is a direction even if no exit in the game points that way. Otherwise, since exit names are entirely arbitrary in the language, it wouldn't know to tell me "you can't go that way".
the limbo isa location exit north, n, south, s, east, e, west, w to limbo. exit northeast, ne, southeast, se to limbo. exit southwest, sw, northwest, nw to limbo. exit up, u, down, d, 'in', out to limbo. end the limbo.
I ended up with three classes and 28 verbs (plus another 20 in the game), spanning 327 lines of code. Most importantly, each section of the library stands on its own. For example, if you want a game with no inventory management, you should be able to excise the respective code without touching anything else. Also, most of the verbs I did include, apart from wait/use/search, have non-trivial implementations; I really don't see the point of including sense verbs in games that won't use them, for example. There's the illusion of freedom, and then there's confusing the player with too many false options.
add to every object verb 'use' does "How, exactly?" end verb. verb search does "You find nothing of interest." end verb. end add to. syntax 'use' = 'use' (obj). syntax search = 'search' (obj). search = 'look' 'in' (obj). search = 'look' 'inside' (obj).
You can find the library packed with the game's source code; feel free to use it in your own projects, and I will consider more additions as needed in actual games. Happy authoring!