Characters I'm taking a puzzle which is tricky to implement in Inform, and trying to take you through the steps needed to turn it into working code.
Truly sophisticated programming of characters will involve you in
cuttingedge areas of Artificial Intelligence research, and this is well
beyond the scope of this tutorial (but see David Graves' essay `Bringing
Characters to Life' for a criticism of existing characters in
adventure games and some suggestions for future directions, and the
papers of the Carnegie-Mellon Oz
Project for some research). However, you can do fairly well
without going to quite such lengths.
We can get a handle on this complexity by dividing up the functionality
of a character into three parts.
I can make the programming much simpler by creating a class called
`Kitten' and making each kitten a member. This will give the two
kittens exactly the same behaviour, which is probably just about
excusable for kittens, but this strategy probably won't convince if you
try to apply the same technique to humans!
The first changes to make are those which affect other objects; I'll take
the objects in order.
If Alice takes the red queen away from a kitten who's playing with it,
then the kitten will change state from QUEEN_STATE to CHAIR_STATE,
so add the following to the red queen.
Alice can't push the armchair while the kittens are playing near it, nor
while she is carrying a kitten (otherwise she could give one kitten the
worsted and pick up the other, and solve the puzzle that way). So add the
following to the armchair's `before' routine, where I had previously put a
comment (and don't forget to add a local variable `i' to the routine).
Now I can start to code up the kittens. My first attempt at a
definition is the following (the kittens start out playing by the
armchair, so they are in state CHAIR_STATE).
If you experiment with this definition, you'll discover that you get the
following response.
Having given the kittens more complicated descriptions, I now run the
risk of the following happening.
Next, the kittens' reactions. I have to provide a `before' routine for
the `Take' action, because otherwise the library will respond `I don't
think the white kitten would care for that.' I also provide special
messages for a few other actions, to make sure that the kitten always ends
up on the floor of the room (if you dropped a kitten while standing on the
chair, the kitten would end up in the chair).
A nice touch would be to provide for the following response (which
provides a subtle clue as to the where the red queen might have gone,
and what use it would be once you find it).
Another response that I could add is the following.
In this example, the task (to get through the mirror) is given at the
very start, in the introductory text. The chain of puzzles seems fairly
clear, and the only nonobvious puzzle is that you have to give objects
to the kittens to distract them. But the description of the chess board
and the response to examining nonexistent chess pieces suggests that
the kittens like to play with them, and that they get lost in odd
places.
In my opinion, the weakest part of the puzzle is figuring out that you
have to push the armchair, since the description of the armchair
doesn't mention where it is in the room. A good idea might be to change
the `description' of the armchair to a routine that says whether it's
next to the fireplace or the window (say).
Alphatesting usually refers to testing that goes on while a
program is still in development; betatesting is the testing of a
supposedly complete program. Because of the enormous number of different
states an adventure game can potentially get into, and the number of
different user inputs it has to somehow produce sensible responses to,
betatesting is very important. (Infocom had three stages of
testing: alpha and betatesting were inhouse, and
gammatesting involved a large number of outofhouse
players.)
Through the Looking Glass is still in betatesting
(there is a sense in which no complex enough game ever emerges from this
stage!), and I've received the following reports:
Graham Nelson <graham@gnelson.demon.co.uk> noticed:
Martin Braun <100106.2673@compuserve.com> noticed:
Charles Briscoe-Smith <cpbs@ukc.ac.uk> noticed some of the
above, and in addition:
However, problem 4 is to some extent a problem with the library. It is
a result of the way the library interprets `all' as referring only to
objects that aren't contained in other objects in the current room;
arguably it should be changed so that rather than referring to objects
in the current room, `all' refers to objects with the same parent as the
player. [The library has come a fair way since this tutorial was
first written. Such a problem would today be handled using the
ChangeObjects entry point, which allows the designer to change the
interpretation of "all" -- GN]
To solve problem 9, the best solution is probably to Replace the
`ResetVagueWords' library function. [Again, not any more: calling
SetPronoun('it',white_kitten) will do the trick.]
You can read a transcript of the
game being played here.
I've tried to make it clear that the way an adventure game progresses is
by gradual accretion and modification of code, rather than by the
`topdown' design of structured programming. Each new object may
interact with the old ones in complicated ways, forcing you to modify
your old code, and testing tends to indicate lots of small problems that
need fixing. The code actually went through considerably more revisions
than appear on this page!
I'd be very glad to receive feedback on the ideas and advice in this
tutorial - do you think it goes into too much detail, or too little?
Were there things you wanted explaining? Could some of the examples
have been coded more clearly?
If you want to use the code or the puzzle ideas developed in this
tutorial in your own games, feel free. I hereby donate the source code
of the `Through the LookingGlass' example games into the public
domain. -- Gareth Rees, 1995
Starting
Frills
Testing II
References
Alice: An Inform Tutorial (concluded)
7. Adding characters
Plausible characters are the most complicated part of any adventure
game; they make the most infernal of devices seem easy to program by
comparison.
Obviously, the character's state will affect his actions and
reactions, and these in turn will alter his state.
7.1 The kittens
In the case of the two kittens, their state just describes where they
are and what they are playing with: each kitten is either being carried
by Alice, playing with the red queen, playing with the ball of
worsted or playing near the armchair and getting in Alice's way.
(I could go for more complicated behaviour, allowing the kittens to be
on the armchair, or on the mantelpiece, or on the rug, or under the
armchair, and so on, but for simplicity I'll prevent the kittens from
getting into these states). These states are going to be represented
by numbers, but numbers are hard to remember, so first I'll define:
Constant HELD_STATE = 0; ! Being held
Constant QUEEN_STATE = 1; ! Playing with the Red Queen
Constant WOOL_STATE = 2; ! Playing with the worsted
Constant CHAIR_STATE = 3; ! In the way of the chair
The kittens will need to react to being spoken to, being kissed, being
picked up, being put on things, being put down and being given objects
to play with. I'll give them lots of autonomous actions, but these
will just be random messages along the lines of `The white kitten chases
its tail.'
after [;
Take:
if (white_kitten.state == QUEEN_STATE)
white_kitten.state = CHAIR_STATE;
if (black_kitten.state == QUEEN_STATE)
black_kitten.state = CHAIR_STATE;
];
(If there had been many kittens, rather than just two, this rule
could have been written like this:
objectloop(x ofclass Kitten)
if (x.state == QUEEN_STATE)
x.state = CHAIR_STATE
and adding a variable `x', but for just two kittens, it isn't worth it.)
if (white_kitten in player || black_kitten in player)
"Not with a kitten in your arms!";
if (white_kitten.state == CHAIR_STATE) i = white_kitten;
else if (black_kitten.state == CHAIR_STATE) i = black_kitten;
if (i ~= 0)
"You are about to start moving the chair when you
notice that ", (the) i, " is right in the way. It's a
good thing you spotted it, or you would have squashed
flat the poor little thing.";
If Alice takes the ball of worsted away from a kitten that's playing with
it, the kitten will change state from WOOL_STATE to CHAIR_STATE, so give
the ball an `after' routine.
after [;
Take:
if (white_kitten.state == WOOL_STATE)
white_kitten.state = CHAIR_STATE;
if (black_kitten.state == WOOL_STATE)
black_kitten.state = CHAIR_STATE;
];
I've used the `state' property to describe the kitten's state. This is
something I've invented, not a property which has any meaning to the
Inform library. Likewise, I'm also naming a property `other_kitten' so that
each kitten can work out which kitten is the other (remember that they're
going to be using exactly the same code).
Class Kitten
has animate
with state CHAIR_STATE,
name "kitten" "kitty" "cat",
description [;
"What a beautiful kitten ", (the) self, " is. Why,
it's quite definitely your favourite of the pair, and
much prettier than that naughty ",
(name) self.other_kitten, ".";
];
Kitten white_kitten "white kitten" Drawing_Room
with name "white",
this_kittens_turn false,
other_kitten black_kitten;
Kitten black_kitten "black kitten" Drawing_Room
with name "black",
this_kittens_turn true,
other_kitten white_kitten;
(I'll explain why the kittens have `this_kittens_turn' properties below;
be patient for a bit. It's another new property I've invented, not one that
means anything to the Inform library.)
> take kittens
You can't see any such thing.
How to get them to respond to plurals? I'll just remove the `name'
property from the kitten class, and copy (with a few modifications) the
`parse_name' code for the crown class from
Section 29 of the Designer's
Manual 4th Ed. (essentially, this recognises any string of the words `black' or
`white', `kitten', `kitty', `cat', `kittens', and `cats' as referring to
a kitten, but if either of the last two words appears then the variable
`parser_action' is set to `##PluralFound', which tells the parser that a
plural word has been found).
parse_name [ w ok n;
do {
ok = 0;
w = NextWord();
if (w == 'kittens' or 'cats') {
ok = 1; n++; parser_action=##PluralFound;
}
if (w == 'kitten' or 'kitty' or 'cat' ||
w == ((self.&name)-->0)) {
ok = 1; n++;
}
} until (ok == 0);
return n;
],
Also, the initial description of the kittens (`You can also see a white
kitten and a black kitten here') leaves something to be desired. I want
the description of the kittens to vary according to what they are doing.
So I use a `describe' routine, as follows (note that the text produced
by a `describe' routine must start with a newline, otherwise there
won't be any space between it and any preceding text).
describe [ i;
switch (self.state) {
QUEEN_STATE:
"^A ", (name) self, " is playing with the red queen.";
WOOL_STATE:
"^A ", (name) self, " is playing with a ball of worsted.";
CHAIR_STATE:
if (self has general) rtrue;
if ((self.other_kitten).state == CHAIR_STATE) {
i = self.other_kitten;
give i general;
"^Two kittens, one white and one black, are playing
together by the arm-chair.";
}
"^A ", (name) self, " is playing by the arm-chair.";
default: rtrue;
}
],
daemon [;
give self ~general;
];
Why the use of the `general' attribute and the `daemon' routine?
Because when the kittens are playing together they are described
together. I want to avoid output like the following.
Two kittens, one white and one black, are playing together by the arm-chair.
Two kittens, one white and one black, are playing together by the arm-chair.
(one line from the white kitten, one from the black). So when one
kitten outputs this line, it sets the `general' flag on the other so
that it knows not to describe itself (by returning 1 from `describe').
The `daemon' routine makes sure that all the `general' flags are cleared
at the end of each turn, ready for the next.
A discarded ball of worsted lies on the floor here.
A white kitten is playing with a ball of worsted.
So I delete the ball of worsted's `initial' string, and add code to stop
it appearing in descriptions if the kitten is playing with it.
describe [;
if (white_kitten.state ~= WOOL_STATE &&
black_kitten.state ~= WOOL_STATE)
"^A discarded ball of worsted lies on the floor here.";
rtrue;
],
I write a similar routine for the red queen (not shown).
before [;
Take:
if (self.other_kitten in player)
"You can't hold two kittens at once!";
self.state = HELD_STATE;
move self to player;
"You pick up ", (the) self, ". What a beautiful
creature it is!";
],
after [;
Drop:
self.state = CHAIR_STATE;
move self to Drawing_Room;
print_ret (The) self, " squirms out of your arms and scampers
away.";
Transfer,PutOn,Insert:
self.state = CHAIR_STATE;
print (The) self, " jumps off ", (the) parent(self);
move self to Drawing_Room;
", landing lightly on the floor before scampering away.";
],
The rest of the reactions are covered by the `life' routine.
life [;
Ask,Answer,Order:
print_ret (The) self, " twitches its whiskers and looks at
you with such a clever expression that you are certain it
understands every word you are saying.";
Kiss:
"You give ", (the) self, " a little kiss on its
nose, and it looks sweetly and demurely at you.";
Attack: "You would never do such a beastly thing to such
a defenceless little animal!";
Show:
print_ret (The) self, " bats a paw at ", (the) noun, ".";
Give,ThrowAt:
if (noun ~= red_queen or worsted) {
if (action == ##ThrowAt) {
move noun to Drawing_Room;
print "You toss ", (the) noun, " onto the floor, but ",
(the) self;
}
else print (The) self,
" just examines ", (the) noun,
" with a quizzical expression.";
}
print "You toss ", (the) noun, " onto the floor and ", (the) self;
if (self in player)
print " squirms out of your grasp and";
move noun to Drawing_Room;
move self to Drawing_Room;
print " scampers after it";
if (noun == worsted) {
give worsted general;
self.state = WOOL_STATE;
print ", quickly turning the neat ball into a tangle";
}
else self.state = QUEEN_STATE;
".";
],
Finally, I have the kittens' autonomous actions. It would be too
confusing if each kitten output a message every turn. Instead, I make
the kittens alternate their messages, and I give each kitten a one in
three chance of producing no message at all. I ensure the former by
giving each kitten a `this_kittens_turn' property, always true or false,
which toggles each turn; and I allow the kitten to do something only if
its `this_kittens_turn' is true.
Here's the start of the `daemon' routine.
daemon [ i;
give self ~general;
self.this_kittens_turn = 1 - self.this_kittens_turn;
if (self.this_kittens_turn || random(3) == 2) rtrue;
new_line;
print (The) self;
switch (self.state) {
HELD_STATE:
switch(random(5)) {
1: " mews plaintively.";
2: " purrs quietly to itself.";
3: " purrs contentedly to itself.";
4: " rubs its ears against you.";
5: move self to Drawing_Room;
self.state = CHAIR_STATE;
" squirms out of your arms and scampers away.";
}
There are similar lists of random actions for the other states a kitten can
be in (not shown).
The daemons need to be set going at the start of the game, so add the
following to the `Initialise' routine.
StartDaemon(white_kitten);
StartDaemon(black_kitten);
8. Frills
Among the last changes made to a game are the small frills and thrills;
extra responses that add little or nothing to the plot, but make it more
pleasant to play. If your playtesters are very good, then they'll
suggest commands that they tried which ought to produce a more
interesting response.
> take white bishop
Alas, that chess piece seems to be missing. Those naughty kittens!
I could do this with a dummy object whose `before' routine just
provides the above message. My first guess was to write the following.
Object chess_pieces "chess pieces" Drawing_Room
has scenery
with name "white" "red" "pawn" "rook" "castle" "knight" "horse"
"bishop" "queen" "king",
before [;
"Alas, that chess piece seems to be missing. Those naughty
kittens!";
];
But I have to be more careful than that; consider the following
problems with the above.
> examine white
Which do you mean, the white kitten or the chess pieces?
> take red queen
Which do you mean, the red queen or the chess pieces?
I'll solve this problem by writing a `parse_name' routine to parse the
exact sequences of words that I want (`white pawn' to `white king',
`red pawn' to `red king', and `pawn' to `king'), and no others.
Object chess_pieces "chess pieces" Drawing_Room
has scenery
with parse_name [ w colour n;
w = NextWord();
if (w == 'white' or 'red') {
n ++;
colour = w;
w = NextWord();
}
if (w == 'pawn' or 'rook' or 'castle' ||
w == 'knight' or 'horse' or 'bishop' ||
w == 'king' || (w == 'queen' &&
(colour == 'white' || rug hasnt general))) return n + 1;
return 0;
],
before [;
"Alas, that chess piece seems to be missing. Those naughty
kittens!";
];
Another interesting addition would be the following (there's something
like this in the game `Curses').
> look at red queen in the looking-glass
The looking-glass red queen looks just like the real red queen,
only all reversed, left for right.
I should be careful in implementing this, and consider the following
commands.
I'll also need to add some grammar (after the inclusion of the
Grammar library file):
Extend "look"
* "at" noun "in" noun -> Reflect;
Extend "examine"
* noun "in" noun -> Reflect;
and an extra action routine.
[ ReflectSub;
if (second ~= mirror) "What a strange idea!";
if (noun == hearth or mirror || (player notin mantelpiece &&
player notin armchair))
"You can't see that in the looking-glass.";
print "The looking-glass ";
if (noun == player) print "Alice";
else PrintShortName(noun);
if (player in mantelpiece) " looks very misty and blurred.";
print " looks just like the real ";
if (noun == player) print "Alice";
else PrintShortName(noun);
" only all reversed, left for right.";
];
I also need to add `Reflect' to the actions that can be used when the
player is on the mantelpiece, and to the actions that can be used on the
mirror.
> put red queen on chessboard
Alone on the chess board, the red queen is monarch of all she
surveys.
All I have to do is add the following to the red queen's `after' routine.
PutOn,Transfer,Insert:
if (second == chess_board)
"Alone on the chess board, the red queen is monarch of all
she surveys.";
9. Checking the consistency of the game
Having finished the construction of a reasonably complicated puzzle,
it's important to go back and check that it's a fair puzzle; that it
allows a reasonable choice of commands; that there are clues to any
nonobvious solutions; and that the player need never flounder in want
of a task to attempt.
description [;
print "It's a huge arm-chair, the perfect place for a kitten
or a little girl to curl up in and doze. It has been
pushed over to the ";
if (self has general) "fireplace.";
"window.";
],
Having mentioned the window, I also have to add an object to represent it.
Object window "window" Drawing_Room
has scenery
with name "window" "pane",
description "Outside the window it's snowing gently, and you're
glad to be in here in the warmth.",
before [;
Open: "You wouldn't want to catch a chill, would you? Better
leave the window shut.";
Search: <<Examine self>>;
];
The `Search' action is generated by the command `look through the
window', so we just cause it to do the same thing as `examine window'.
10. Testing, part 2
With all these changes included, I have a game (well, the first room of
a game, anyway), alice3.inf (browse | download),
that's ready to be betatested.Peter Grundy <pgrundy@commerce.otago.ac.nz> suggested:
Most of these bugs should be easy to fix, and are left as an
exercise for the reader.11. Conclusion
So that's the first room of an adventure game: nearly 500 lines of code
(around 200 of which are needed for the kittens). How many more rooms
to go before you have a complete game? At least twenty or thirty. Not
all will be as complicated as this, but if you plan to have plausible
characters in your game then they'll need to be at least as complicated
as the kittens, and probably much more so! 12. References
Alice Through the LookingGlass, Part Two Doug Atkinson
Download
A continuation of this tutorial adding new scenes, written using Inform 5.5. Plain text tutorial with accompanying Inform source to download.
Alice Through the LookingGlass, Part Three John Wood
Link
A continuation of this tutorial adding new scenes, and updating Doug Atkinson's Part Two to Inform 6. HTML document with accompanying Inform source and the occasional Tenniel illustration.
Any offers for Part Four?
Through the Looking Glass Lewis Carroll
Link
The source material: Carroll's classic available online.
Bringing Characters to Life David Graves
Link
The Craft of Adventure Graham Nelson
Download
The Inform Designer's Manual Graham Nelson
Link
The Oz Project Alma Whitten and Scott Reilly
Link
Completed Alice source code Gareth Rees, Graham Nelson
Download
The finished source from this tutorial (with strokeable kittens!) to download for Inform 6.
Compiled Alice file ((1 May 2003)) David Cornelson
Download
The 'Z-code' file compiled by Inform 6.21, from the source as updated to Inform 6 by David Cornelson. You need a zcode interpreter to run the file, which is the end result of this tutorial.
Last updated 23 June 2004.
This site is no longer supported; information may be out of date.
Maintained as a historical archive by the Interactive Fiction Technology Foundation.
Copyright 1993-2018 IFTF, CC-BY-SA unless otherwise noted.
This page was originally managed by Graham Nelson (graham@gnelson.demon.co.uk) assisted by C Knight.