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 RED = 1;
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:
Colour is a kind of value. The colours are red, blue and green. A
block is a kind of thing. A block has a colour. A block is usually
blue. Before printing the name of a block: say "[colour] ". Before
printing the plural name of a block: say "[colour] ". Understand the
colour property as describing a block.
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). [example in 4.4] -- 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.
Just an observation here. It's not the fact that the language is natural
that allows for this powerfully concise series of statements. You could,
after all, express the same with a more specialized notation (think
shorthand, for instance). The real magic is that a generalized
infrastructure has been created to support this level of abstraction. You
see, most of the "programming" languages we've discussed here in the past
deal with things on a very low level. Much of the work of a keyword
statement in a language does very low level things such as adding two
numbers together, or setting a variable equal to some value. Much of the
higher level functionality has been handled by authors having to use these
low-level statements to build their own algorithms and structures. Further,
these structures have seldom been generalized. Now what's also happening
here is that because of the dual compilation process, symbols and their
string values are being usefully associated. For instance, if I write "Mr
Darcy is in the Gazebo" the phrase "Mr Darcy" is converted by the
compilation process into: (a) an object symbolic reference; (b) an object's
description; (c) an object's vocabulary. We can of course see where there
might be exceptions to any of these three uses of the shorthand notation
(i.e. we want more vocabulary, or somesuch). That kind of support isn't
available in I6 or TADS (although curiously enough I'm encountering it in
TADS 3 REPL because all commands in REPL are entered on the command line and
not compiled.)
So a statement such as "Colour is a kind of value." represents derivation,
but here again, it's not derivation in terms that one can simply translate
into object inheritance, rather it's more like enumeration because its kind
is "value" and not "person" or some other category. Now in other programming
languages this distinction would be made more explicit. Enumerators are
defined one way, objects another. So while there is the precedence of method
overloading that is similar to this, programmatically (and without making
use of a precompiler or secondary compilation) you'd have to code something
that would look like isAKindOf(Colour, value). But your troubles wouldn't
end there. In I7 there's no difference between Colour, COLOUR, or cOlOur.
They all map to the same symbolic (although their short names appear to be
determined at the point when they are defined by the "is a" clause.
Granted, the natural language makes it read more like a book, but the
notation involved could have been more mathematical and you'd accomplish the
same thing. You'd just need a mathematician to translate it for you.
--Kevin
The natural language does come in to *some* extent. The list of colors
is given in English ("red, blue, and yellow"). The system is able
to insert these into your game both as the game's descriptions and the
player's descriptions (in typed commands). And then game code and
player commands can both refer to "the green block" where appropriate.
A different notation would transform something else into "the green
block" in the player's world; so how is that more specialized? What
task is it more specialized to perform? The whole point is to be able
to specify the block which is green.
(I did, after all, come up with the "mass nouns" code in I6, which did
some of the same work. That had the abstractions. But it was a damn
nuisance to use. It is reasonable to say that this makes it useless;
at least, I don't think anybody has used it.)
The compiler abstractions are certainly magical, but I do see the
basic idea as the observation that both the author and the player are
talking about the same model world, and (in some cases) have similar
requirements for making specifications about it. That's what the
infrastructure *does*.
--Z
--
"And Aholibamah bare Jeush, and Jaalam, and Korah: these were the borogoves..."
*
If the Bush administration hasn't shipped you to Syria for interrogation, it's
for one reason: they don't feel like it. Not because of the Eighth Amendment.
> A different notation would transform something else into "the green
> block" in the player's world; so how is that more specialized? What
> task is it more specialized to perform? The whole point is to be able
> to specify the block which is green.
I can understand why Inform 7 conflates the conceptual and the
linguistic part of programming IF (which is what you are describing),
but I am not sure that this is more natural. The model of a conceptual
vocabulary (unfettered by the demands and unhindered by the oddities of
natural language) used by the programmer entirely separate from a
linguistic vocabulary (mimicking natural language as closely as is
advantageous for the reading experience) makes it clear that the logic
of the game and the way the user interacts with it are in fact separate.
From the point of view of the programmer - and especially from the point
of view of the programmer who wishes to write one common conceptual core
for a set of games that will interact with the reader using different
natural languages - the traditional approach seems a lot more natural
than that chosen by Inform 7.
After all, the programmer wishes to specify that the block is green, not
that "the block is green" is true* - but Inform 7 assumes him to say
both at once. This is tricky.
Regards,
Victor
* He might want to say that "het blok is groen" or "le cube est vert" or
"der Block ist grĂ¼n" is true instead.
By more specialized I mean that one could translate phrases such as "is a
kind of" into something more akin to a mathematical symbol, write the code
using those symbols and achieve the same results. For instance, if a
notation such as => were used for the "is/are a kind of" you'd state "(x, y)
=> z" rather than "x and y are a kind of z". Notations of set theory and so
forth. One can easlily argue, however, that the use of verb tenses,
conjugations, etc, provides a pretty compact notation with rules that have
been, by and large, inculcated from early childhood.
It does seem apparent that I7 transcends I6 in several particulars. But I've
been wanting to ask the team, are there any areas where I7 doesn't map to
I6? In other words, is I7 a superset of I6 such that anything I could
conceivably code in I6 could be coded in I7? Or are there elements of I6
that have no I7 equivalent?
--Kevin
But the mapping is not a simple line-by-line translation. For instance,
the initial state of the world is worked out by accumulating inferences
from all of the direct assertions made in the source text; this is then
used to form a model; and only then is that model compiled as a long
series of Object and Class directives in I6. I7 doesn't write these as
it goes along, so to speak. And even when it comes to procedural code,
I7 will sometimes compile innocent-looking expressions to loops, or to
function calls which do non-trivial calculations. (All the same, for
quite a lot of instructions I7 does correspond fairly closely to I6 and
needs only perform a sort of macro substitution.)
That's pretty much the point Graham is making in his white paper, I
believe. I don't have much to add beyond what I've said.
(But don't forget relations like "A is in B", "A is near B", which
have been critical to IF since its beginning.)
> It does seem apparent that I7 transcends I6 in several particulars. But I've
> been wanting to ask the team, are there any areas where I7 doesn't map to
> I6? In other words, is I7 a superset of I6 such that anything I could
> conceivably code in I6 could be coded in I7? Or are there elements of I6
> that have no I7 equivalent?
(Ignoring the fact that you can drop bits of I6 code in...)
There are a bunch of small things. For example, an I6 object property
could contain an object, a string, a dict word, a function, a number,
or nothing. You could make such a property polymorphic -- able to
contain any of these[*], with behavior appropriate to whatever was in
there. In I7, properties are strongly typed; you can't mix text with
object, and I don't think you can put a function in at all.
[* with some qualms about mixing numbers with other things.]
This doesn't mean you can't accomplish the same thing. However, you
have to go about some things from a different direction.
To be literal, I7 is not a superset of I6 (since I7 *generates* I6
code) and neither is I6 a superset of I7.
I7 makes certain things much easier than I6 did. Some tasks (complex
computation) are much harder in I7 -- or at least much wordier and
harder to read -- and others (screen effects) are impossible. My
feeling so far is that for anything to do with the model world,
command parsing, and object description, I7 is no harder than I6 and
usually easier. If you want to factor primes or play Tetris, you stick
in I6 code.
--Z
--
"And Aholibamah bare Jeush, and Jaalam, and Korah: these were the borogoves..."
*
If the Bush administration hasn't thrown you in military prison
without trial, it's for one reason: they don't feel like it. Not
because of the Fifth Amendment.
Do the limitations of strong typing on I7 code apply to "dropped in" I6 code
as well? Or does the I7 compiler simply pass I6 code verbatim to the I6
compiler?
--Kevin
I6 code is passed on verbatim. You can use this to subvert the strong
typing system of I7. I'm sure this will cause fascinatingly broken
game behavior, which will be your fault. :)
--Z
--
"And Aholibamah bare Jeush, and Jaalam, and Korah: these were the borogoves..."
*
If the Bush administration hasn't subjected you to searches without a warrant,
it's for one reason: they don't feel like it. Not because you're an American.
Yes, I'm sure it would be :) But given that I7 is beta I expect that future
releases will provide decreasing need for this particular facility. It's a
bit like dropping down into dos from windows... My expectation is that the
syntax for complex calculations can be improved over time as the language
develops and matures. But it is interesting to note that, should you need
to, you can attempt such madness.
--Kevin
> Several people have asked me to discuss in depth why I find I7 more
> powerful than I6 for complex projects. I touched on some of this in the
> recent SPAG interview, but because the answer was so large, I didn't
> want to hold forth about it as long as I felt the topic deserved.
A complete and very interesting article.
Also, a painful remainder of the things a lot of people will be
missing. For example, all the non-english readers and wannabe game
creators. Pity.