This is also largely a discussion of my own experience with the system as an author of games, rather than as a contributor to the design process.
Here's the grandiose abstract claim: I7 provides many of the essential tools from which a world model, descriptive functionality, and parser are assembled. Namely
- ways of talking about how objects are characterized in themselves;
- ways of talking about how objects relate to one another
- ways for displaying that information to the player
- ways to parse that information.
My empirical evidence that this is effective: there was one dizzying week when I wrote 80 pieces of example code, including things to do cutting, burning, basic liquid, a rope, telephones, and about a dozen other things on the standard list of irritating IF problems. No, these aren't the absolute complete last-word implementations of these things, but they are sufficient for many purposes, and could be expanded to cover many variations. Some of these are things I banged my head against for weeks or months back when I was first using Inform 6. (My first project was to write a general set of interlocking libraries to handle liquids, ropes, divisible items, fire, etc.; shockingly I never finished it.) I was partly stymied back then because I wasn't very good at Inform 6, but none of these problems ever became exactly easy to solve.
What follows is an attempt to describe what the important abstractions are and what they contribute to the power of the language; I conclude with some more observations about my own particular experiences so far.
Characterizing Objects: or, one really major reason the natural language aspect is useful for IF
Here's what I mean by "how objects are characterized". In many IF works, one wants to add features to the world model, so that, for instance, all objects have a color. In older versions of Inform, this meant slapping a color property on all the objects (or on a class that was the parent of everything colored) to keep track of this information. The identity of the color was perhaps represented by a constant. E.g.:
Constant BLUE = 2;
...
If you wanted to print the color of an object automatically as part of its name or at some point in the room description, you had to write a third set of code for that—probably another routine that would take the constants representing colors, and turn them into a switch statement to print "blue", "red", and so on:
[ PrintColors color; switch { RED: say "red"; BLUE: say "blue"; ... } ];
In some circumstances it might be possible to circumvent this by making the color property a string (with color "red"), but there are also drawbacks, especially if we want properties that are understood to come in an order (i.e, red is before orange which is before yellow)—in that case reliance on constants may be important.
Worst, if you wanted to parse the name of an object so that 'the white book' would be recognized when (and only as long as) the book remained white, you would then have to write a third and separate set of code for the specialized parsing. This required reiterating the names of all your colors as input-strings instead of constants, and building the parse-code into objects (usually in the ultra-heinous parse_name routine). One of my early wishlist requests for I7 was along the lines of "make it so I never need parse_name ever, ever again."
Though these three aspects all represented the same fact in the abstract world—that things have color—they are completely unconnected in the code. It is easy to introduce bugs. Moreover, all the coding tended to be tedious and painstaking, so the inclination was to avoid this kind of effect unless it was really necessary somehow to the gameplay. In Inform 7, when you define something like color into the world model, it automatically learns how to parse and print that idea as well. Here's the equivalent concept in I7 text:
Here in a short paragraph we establish what color is (or colour—guess which of us wrote this one); what the available colors in our world model are; how they apply to a specific kind of object; and how we want to describe and parse that aspect of the world model.
I could imagine that this might be done with a more code-like language, but it is a feature that arose naturally from the natural language concept, and is expressed most easily in natural language, I think.
The implications of this are wider than I had initially guessed. For instance:
- Objects that change characteristic freely midgame (like the colored blocks, which can be repainted).
- Letters whose nature is described to the player only after they have been read once. [example in 16.10]
- Similar objects that are distinguishable from one another only some of the time, as when the player is wearing IR goggles. [example in 15.17]
- Liquid containers that change description and reference depending on what they contain. [several versions; see the recipe index]
With some not-terribly-heinous additional coding (and leveraging some of I7's other power), we can also get Inform to
- Recognize references to strings by length, as in "the ten inch string", even when strings can be cut and "ten inch" is not a permanent aspect of the string (since by the next move we may well have an eight inch string and a two inch string) [see 15.15]
- Review the status of objects with multiple parts, and change the name of the object depending on that review—so, for instance, I might sew pieces together to make a garment, and take those pieces apart again, and Inform changes its opinion of what kind of garment it is depending on its composition (with appropriate modification of the description and parsing). [see 15.18.]
With further work (okay, this was hard, at least for me, but mostly because the vector-resolution with dot products in integer arithmetic kicked my ass for a while; someone more savvy might have accomplished it faster):
- Evaluate the status of a liquid mixture and name it with the name of the appropriate recipe depending on the exact proportions of the components it contains.
Even with the genuinely hard parts, though, we're talking about an afternoon and an evening of work, and this is a problem I had thought about and knocked my head against for Savoir-Faire and never ever solved in a way that made me happy; SF never does get to where it describes recipes really automatically and intelligently. The ultimate implementation in I7, moreover, makes it comparatively easy to add in new recipes. I will not try to describe this further, but the code is there to look at (again, see the Recipe index for the various liquid treatments).
Now, in my opinion this kind of connection between what the world models, what the output prints, and what the parser understands is a large part of what makes a game feel smooth and solid, and allows the player to understand a new kind of simulation. I have mostly talked about properties with discrete values here (red, yellow, blue), but Inform can also be taught forms for units of measure, as well (which is where the parsing of arbitrary string length comes into play).
Relations
Relations are a more significant departure from what has gone before. At the code level, object relations largely boil down to adding array properties to objects. If I add to my game the idea that one object can be underneath another object, I might implement that by making an array on each object that listed the things hidden beneath it. But in I6 this array would be cumbersome to access and write instructions about, and I6 provides no automatic bookkeeping. Inform 7 does.
Here's what I mean. Inform recognizes a number of different types of relation: they can be one-to-one, one-to-many/many-to-one, or many-to-many; they can be reciprocal or not; they can be many-to-many "in groups" (about which more in a minute).
Telephones use one-to-one reciprocal relations: you can only be talking to one person at a time,* and when you are, that person is also talking to you. Inform keeps this clean and tidy. When you form a connection one-way (X is talking to Y), the reverse is formed (Y is talking to X); when you cut the connection from one end (X is no longer talking to Y; or, X is now talking to Z) the other end is also handled properly (Y is no longer talking to X). The relations tools also give some handy shorthand for referring to the other half of a reciprocal relation; so I can decide for instance that if the player has a telephone connection with someone, that someone will be called "the other party", and can then be referred to as such.
* I could never figure out three-way calling on my phone, anyway.
The situation of things being under other things uses a many-to-one, non-reciprocal relation. You might have a sock, a dustbunny, and a magazine under the sofa, but none of these things can then also be under something else (which is why it is many-to-one), and the sofa is not then under the sock (which is why it is not reciprocal).
Ropes (at least the way I chose to handle them) make heavy use of a relation that expresses being-tied-to. In I7 one of the kinds of relation is a group relation, so that anything "tied to" one item is automatically joined to a set with all the other things tied to that object. If we were worrying about rope lengths and so on, this might not be ideal; but for what I wanted it was fine, because now any time I tied a rope to something, I could refer to the list of things connected to the rope; I could talk about what happens if the player tries to move while tied to a fixed or scenery object; I could write conditions like "if the player is tied to the door" without concerning myself with the number of intervening pieces, or whether he has tied himself with one length of rope or two adjoined strings or a chain-and-grappling hook. The rope implementation in the included example [12.7] covers raising objects by rope, dragging objects from room to room, tying the rope to the player in order to lower oneself into a location, opening doors from the next room by tying the rope on, and so on. There are more features one could add to this, but I stopped there. The expressiveness and compactness one gains from the grouping relation is tremendous; again, this was more progress in a couple of hours than I ever made with the problem in I6, and the results are more readable, too.
Moreover, I7 adds some functions that are not in I6 at all, such as pathfinding through any defined relation. If I define a bunch of conversation objects (for instance), I can then write a relation to say "love suggests marriage; marriage suggests children and divorce; divorce suggests lawyers"; then have my NPCs goal-seek a path through the conceptual space in order to direct the conversation in the way they want, by finding the best path from the current conversation topic to the one they actually want to talk about. [See the "Glass" worked example, e.g.] Conversational pathfinding appeals to me because I have spent a lot of time wondering about how to solve exactly this problem, but it is possible to express all sorts of other ideas with relational pathfinding: family lineage, nodes on a network, etc.
There are fun output things one can do with relations as well: "[list of things which are under the sofa]" is a valid thing to print, as is "[a random thing which is under the sofa]". Relations also help with the expression of rules that might otherwise involve more laboriously explicit routines: "now every thing under the sofa is carried by the exasperated maid." It has been noted that explicit loops in I7 are a little wordy, and this is true; it's also worth noting on the other hand that the need for explicit loops is greatly reduced by constructions of this kind.
Rule-based Language
The rule-based structure of I7 brings considerable flexibility and an encouragement to write expandable code. It is now much easier than it used to be to replace bits of the standard library, or expand a library action without changing most of its behavior. Moreover, it tends to be easy to write general behavior and then expand or add exceptions later without disrupting the essential structure: more-particular rules take precedence over the less-particular rules, so you can write the less-particular ones and then add your special cases without altering the substructure at all.
The rule-based design also makes it much, much easier to define complex goal-seeking behavior for NPCs: you tell the NPC to do the goal-action, and then stack a bunch of before rules so that if he is prohibited from doing that goal, he will take appropriate intermediate steps first. This is in itself a fairly large topic, so I don't want to go completely off on a tangent to discuss it; moreover, I'm still working through some of the implications myself. Still, anyone interested may want to look at the examples in chapter 11 of the manual, and then check out the worked examples: the episodes of "When in Rome" play with these ideas, especially episode 2, which is basically an all-game-long exploration of NPCs with agendas and some basic planning abilities. In no way have I begun to uncover all that can be done with this, but the experiment has gone a lot farther than I ever managed to get in I6. (Here, too, there comes into play one of the major additions to the I6 world model qua model, as opposed to the language: NPCs can now be told to carry out actions just as the player can.)
Activities
Activities are something of a mixed bag, but in general, they exist to express the concept of context—what is Inform doing at the moment?—so that I can, say, write different rules for naming something during an inventory listing or during a room description; or I could write yet a third rule for naming an object during conversation output, by first defining my own conversational activities. Though this is not a requirement, activities most commonly offer control over aspects of what gets printed to the screen.
The most valuable of activities by three or four country miles is the seemingly-light-weight "rule for printing the name of something", which can be used with almost infinite flexibility to affect how something is named to the player, and/or to record the fact that something has been named, as you like. Bronze is peppered with examples of this in action, both to turn on the boldface that marks important objects for the newbie user, and to record for the hint system which items are "seen". The in-documentation examples are pretty well crammed with uses of it as well.
Scenes, Scheduling, Et Al
Inform 7 also goes further than Inform 6 in offering tools for plot and time management rather than thing and map management. Some of these things I did have myself in I6, but only because I had laboriously built a scene mechanism; Inform 7 provides this up front. These things make it much easier to build a story-oriented game with several sections and keep all the pieces in order, since it is possible to make various aspects of play depend on which scene is currently in progress. It is entirely possible to dispense with a traditional map and stitch together a game out of scenes in one notional room. "Glass" is a low-key experiment in this, but there's no reason it couldn't go a bit further. I have only recently begun to play with some of these issues and so don't have as much finished code to present here, but I believe it would be possible to combine the scene structure with the kind of goal-seeking mechanisms I described for NPCs, and come up with neat things by way of storyteller AI.
Experiential Report
I find that these features have several effects on my coding process. One is that I am able to draft a world model much more quickly than I could otherwise. I do of course need to spend a little while thinking about what relations and characteristics are going to be necessary in order to simulate what I want to simulate, but sketching out a general set of rules using these concepts is fairly rapid. I also find that I tend to think of the input and output aspects of a simulation at the same time that I think of the code representation; I am now less likely to write a detailed simulation of something and put off the grody parsing aspects until later.
The second, subtler advantage is that I find that framing world-model design problems explicitly in these terms—what are the essential object characteristics? what are the essential relations between objects? do these relations relate things one-to-many or many-to many? are they reciprocal relations, or not?—this process allows me to understand the model more clearly and design it more accurately at the outset. Earlier, I6-based versions of my lock-and-key-handling extension gave each member of the key kind a property that could hold just one object, to record what the key was known to unlock. But, of course, that's just a short-hand for convenience purposes: keys can unlock multiple objects, and my extension ignored that fact because it was more trouble to implement accurately. In Inform 7 it is much easier to express the idea of a relation to multiple objects, and thus much more likely that I will implement correctly at the outset and not have to revise the design later.
To take a step back even from the coding aspect, I think this way of structuring the language helps me think better about the game/story design, too. When I make a new model world in I7, I ask: what is the significant mode of interaction here? what aspects of an object in this model are so important that I am going to describe them to the player and parse them again? what are the fundamental ways that objects interact with one another in this model? The I6 world model more or less answered these questions for you: the fundamental features of a thing are whether it is open, whether it is locked, whether it is providing light; the fundamental relations are containment (or support) and room-connection. These are so deeply embedded in the system that it becomes hard to change the output, so the newsgroup archives are cluttered with posts about how to turn off (for instance) the text that automatically prints "(which is empty)" after the name of a container, even if you don't want it to.
Moving away from that paradigm, I find, encourages more inventive thought about what the model should cover. Bronze, for instance, contains relations to express which objects are necessary to solve the puzzles associated with other objects. It is somewhat primitive, but it does have built into the world model some vague idea of the puzzle plot that the player encounters, and this is what drives its automatic hinting.
One of the things I hope I7 will do is encourage people to think about their IF from the ground up, in the sense of considering what scaffolding they need to build to support the story/game they want to write. Okay, it's not traditional to model puzzle-relations, but if it fits your concept, why not? It's not traditional to model the connections between topics of conversation either, but if you like, you can add that. There's not one perfectable ideal world model for IF.
So yes, I7 still has syntax; you still have to think logically; it doesn't understand everything you type. Moreover, there are indeed points where the natural language breaks down or goes away. One of my favorite devices of Inform 7 is the table, which is essentially a fancy array—but it turns out to be an extremely compact and effective way of storing information. I realize that this deviates to some degree from the idea of natural language. But I7 does recognize that there are kinds of information that really are not best presented in English sentence form. Where a book might have a table, so must the code. There is no point in forcing a natural language representation of something when in fact we don't think of it that way most naturally. Some critics have pointed out these intrusions of non-natural features as a sign that the natural language idea is a bad one; but I think that's not it at all. The natural language idea is fine and it does well at conveying many of the things one wants to convey in IF, but it is right to insert exemptions in some circumstances.
Meanwhile, the improvements to ease of use go far beyond the surface legibility of the code (though that is also quite useful, especially if you have to come back to an old project after a break). It has become easier to articulate the ways a puzzle (or plot) should work, and much of the coding process has become more transparently related to the fundamental concepts of the world model and design.
And the natural language—in addition to being a clean way to express relations and excellent for solving the input-code-output problem—also does a lot for the longevity of an interrupted project. I have a game I started in I7 almost two years ago, and while it has needed repeated updates because of the syntax changes, I find I still pretty much understand what it's supposed to do.
This is not to say that I can think of no further improvements to make. There are rough corners here and there that I'd like to see smoothed down, and features I would like to see added in. I am looking forward to the day when we can compile to Glulx, and I do want to see both sound and image handled at that point; there is still work to be done on the idea of the mass noun, perhaps. It would be beyond spiffy if the parser were as intelligent about author-defined relations as it is about author-defined properties, and could understand phrases like "everything in the box" or "everyone related to Marie"—but that may be on into the territory of pure pipe dream, and is probably much less relevant to the way we actually play IF.
All the same, there is major power in this system.
This article copyright © 2006, Emily Short