
An Inform tutorial, by Gareth Rees, April 1995.
You'll need to have the following background to appreciate this tutorial:
Through the Looking-Glass starts out in a room in Alice's house, in which Alice is playing with two kittens, one black and one white. The text mentions an arm-chair, a ball of worsted, a fireplace, a hearth-rug, a chess set (with red and white pieces), a clock, a cat called Dinah, and a looking-glass above the fireplace. It should be possible to involve many of these objects in the puzzle.
The puzzle will be to get through the looking-glass. How can that be made difficult? Perhaps the mantelpiece is too high for Alice to climb up onto without a chair, and the arm-chair needs to be pushed over to the mantelpiece. But the kittens get in the way of the chair, and Alice wouldn't want to hurt them, would she? So she has to distract the kittens by getting one of them tangled up in the ball of worsted, and giving the other one a chess piece to play with. The chess pieces have become lost, but she can find the red queen under the hearth-rug.
That seems like enough complexity for the first room of a game.
My first attempt is nothing more than a few simple modifications to the
Shell game from the Inform distribution.
Constant Story "THROUGH THE LOOKING GLASS"; Constant Headline "^An Interactive Tutorial^by Gareth Rees^"; Constant DEBUG;The
Story and Headline constants are printed by the library in the
opening banner. The DEBUG constant gives me access to some debugging
commands while testing (that line should be removed or commented out
when the game is eventually released).
Include "parser"; Include "verblib"; Include "grammar";The Inform library is broken up into three parts: the Parser, the Verb Library, and the Grammar. The reason why it is split into parts is that it may be necessary to set constants or define functions after including one library and before including the other (DM 156).
I need at least one object to be able to compile and run a game, so I define the initial location.
Object Drawing_Room "Drawing room"
has light
with name "snow",
description "The gentle sound of snow against the window pane \
suggests that it's cold outside, and you're glad to be here \
in the warmth. The drawing-room is reflected in the large \
looking-glass on the wall above the mantelpiece, and a very \
comfortable room it is too, with a warm hearth, a soft rug, \
and an arm-chair that you can curl up and sleep in.";
Note that this room description mentions a number of objects; when I
define these objects I'll give them the concealed or scenery
attributes to stop the library from mentioning them twice - I want to
avoid output like the following.
... The drawing-room is reflected in the large looking-glass ...
You can also see a looking-glass here.
The only obligatory piece of code is the Initialise function, which
sets the location of Alice to the initial room, and prints a welcoming
message (traditionally preceded by a handful of newlines because of a
bug in the ITF Infocom interpreter, which doesn't display anything until
it starts to scroll the screen, which is after printing 20 lines or so).
[ Initialise;
location = Drawing_Room;
print "^^^^^It's a cold winter day outside, but in the looking-glass \
house it's summer. All you need to do is pretend there's a way of \
getting through into it somehow...^^";
];
end;
This gives me a program, alice1.inf, that can
be compiled and tested, although it doesn't do very much yet.
rfalse statement just before the
closing square bracket of a routine inside an object, and at the end of
each block of code dealing with an action (and an invisible rtrue just
before the closing square bracket of a routine outside an object), so
that
Object thing
with ...
before [;
Action1: ...
Action2: ...
];
really means
Object thing
with ...
before [;
Action1: ... rfalse;
Action2: ... rfalse;
];
and
[ Routine; ... ];really means
[ Routine; ... rtrue; ];You can override this default using the
return, rtrue and rfalse
statements, or by omitting the print instruction from a print
statement, because
"Text in double-quotes";
really means
print "Text in double-quotes"; new_line; rtrue;
The use to which the return code of a function is put varies from
function to function, but usually when the library calls a function of
yours, then you return 0 to mean apply the usual library rules, and non-zero to mean
do something specialor maybe
don't do anything at all.
Here are the meanings of the return codes of the most commonly-used routines:
beforelife
True (1) means that the routine has successfully and completely dealt with the player's input, and the library shouldn't do anything more.
False (0) means that the usual library rules should now apply to the player's input (note that this doesn't necessarily mean that the routine has done nothing).
after
True (1) means that the after routine has printed a
suitable response to the player's input, and the library shouldn't do
anything more.
False (0) means that the usual library message should now be printed (again, this doesn't necessarily mean that the routine has done nothing).
describe
True (1) means that the describe routine has printed a
suitable description. The library shouldn't add anything else.
False (0) means that the library should print the usual description of the object.
before routine, it's very common to want to
execute some code, print some text to tell the player what has happened,
and then to return 1 to prevent the library from doing anything else. A
good trick in this situation is to omit the `print' keyword from the
last print statement. Much of the code in the example game developed
here uses this trick.If in doubt, read the Designer's Manual, and look carefully at the example games to see how they use return codes.
action, noun and
second appropriately.
GamePreRoutine (if there is one). If this returns non-zero,
stop here.
before routine of the player. If this returns non-zero,
stop here.
before routine of the current location. If this returns
non-zero, stop here.
before routine of the object noun (if any). If this
returns non-zero, stop here.
after routine of the player. If this returns non-zero,
stop here.
after routine of the current location. If this returns
non-zero, stop here.
after routine of the object noun (if any). If this
returns non-zero, stop here.
GamePostRoutine (if there is one). If this returns non-zero,
stop here.
before routines should be
used to change completely the effect of a particular action, and that
`after' routines should be used to change the message associated with a
particular action, or to cause something else to happen after an action
has been successfully completed.
For example (after Adventure), a delicate ming vase that
shattered when the player dropped it could be coded by using the after
routine of the vase to look for the Drop action. Or (after
Hitch-hiker's) a room with holes in the floor through which
any dropped object fell could be coded by using the after routine of
the room to look for the Drop action.
name and a description, and the red
queen is such an object. The following definition appears after the
drawing-room.
Object red_queen "red queen"
with name "red" "queen",
description "She's a fierce little chess piece.";
Since she's absent from the game to start with, I don't specify which
object is her parent (Inform will ensure that her parent is `nothing').
initial description. And Alice
might try to put red queen on chess board, so I'll make it a
supporter.
Object chess_board "chess board" Drawing_Room
has supporter
with name "chess" "board" "checker" "chequer" "chessboard",
initial "An abandoned chess board lies on the floor.",
description "It's left here from the game you were playing just \
now, but the pieces are all missing - the kittens will insist \
on playing with them.";
scenery object to represent it. The
object plays no role in the game; it's just there for decoration.
Object hearth "hearth" Drawing_Room
has scenery
with name "hearth" "fire" "place" "fireplace",
description "Looking at the hearth, you wonder if they have a \
hearth in the looking-glass house. You can never tell by \
looking, unless your fire smokes, and then smoke comes up in \
the looking-glass room too - but that may be only pretence, \
just to make it look as if they had a fire.";
In order to do this, it helps to be familiar with the operation of the
library. Chapter 5 of the Designer's Manual lists all the actions that
the library knows how to deal with, and it's probably worth looking
briefly at the source code (in the Verb Library) to see exactly what
they all do - in the cases of Take, Enter, and Insert the algorithm
is very complex. You may also need to look at the Grammar file to see
which actions are generated by which player commands.
Also, when code fails to do what you intended, you'll often find it worthwhile to look at the library source to see exactly what's going on.
In any case, the rug needs to respond to the following commands.
static, and also provide a before
routine for the Take action to provide a better message than the
library's default message (That's hardly portable).
before routine that deals
with these.
supporter.
enterable.
before routine for the LookUnder
action (this routine prevents Alice from looking under the rug while she
is standing on it!).
rug rather than hearth-rug because otherwise I
will end up with confusion over whether the word hearth refers to the
fireplace or to the hearth-rug.
concealed attribute because it appears in the room
description.
general attribute is used to stop Alice finding the red queen under
the rug more than once.
Object rug "rug" Drawing_Room
has concealed static supporter enterable
! general if you've found the red queen under it
with name "hearthrug" "hearth-rug" "rug" "indian" "arabian" "beautiful"
"soft",
description "It's a beautiful rug, made in some far off country, \
perhaps India or Araby, wherever those might be.",
before [;
Take: "The rug is much too large and heavy for you to carry.";
Push,Pull: "But a hearth-rug is meant to be next to the hearth!";
LookUnder:
if (player in self)
"You try to lift up a corner of the rug, but fail. After \
a while, you realise that this is because you are \
standing on it. How curious the world is!";
if (self hasnt general) {
give self general;
move red_queen to player;
"You lift up a corner of the rug and, peering underneath, \
discover the red queen from the chess set.";
}
];
static.
supporter.
enterable.
general attribute
to indicate which.
Object armchair "arm-chair" Drawing_Room
has static concealed supporter enterable
! general if its by the mantelpiece
with name "arm" "chair" "armchair" "arm-chair",
description "It's a huge arm-chair, the perfect place for a kitten \
or a little girl to curl up in and doze.",
before [;
Push,Pull:
! code to check for the kittens
if (self has general) {
give self ~general;
"You push the arm-chair away from the hearth.";
}
give self general;
"You push the arm-chair over to the hearth.";
];
enterable. But since its so high
up, I should prevent Alice from entering it unless she's standing on
the arm-chair. Also, she can't climb up if she's holding anything in her
hands.
supporter. If it's too
high for Alice to climb up onto unless she's on the arm-chair, it's
probably too high to put things on too. So a similar check is needed.
Object mantelpiece "mantelpiece" Drawing_Room
has concealed supporter enterable
with name "mantel" "mantelpiece",
description "It's higher off the ground than your head, but it \
looks wide enough and sturdy enough to support you.",
before [;
Enter,Climb:
if (player notin armchair)
"The mantelpiece is much too high to climb up onto.";
if (armchair hasnt general)
"You can't reach the mantelpiece from here.";
if (children(player) > 0)
"Your hands are too full.";
PutOn,LetGo:
if (player notin self && (player notin armchair ||
armchair hasnt general))
"The mantelpiece is so high that you can't reach.";
];
static.
PlayerTo function). For the moment, I just cause her to
win the game.
Object mirror "looking-glass" Drawing_Room
has static concealed
with name "mirror" "looking" "glass" "looking-glass",
description [;
if (player in mantelpiece)
"Strangely, the glass is beginning to melt away, just \
like a bright silvery mist.";
if (player in armchair)
"In the looking-glass you can see the drawing-room of the \
looking-glass house. What you can see is very much the \
same as this drawing-room, only all reversed, left for \
right. But you are sure that out of the corners of the \
glass, where you can't see, the looking-glass world is \
quite different from yours.";
"In the looking-glass you can see the ceiling of the \
drawing-room of the looking-glass house. It looks much the \
same as the ceiling of your drawing-room.";
],
before [;
if (action ~= ##Examine && player notin mantelpiece)
"You can't reach the looking-glass from where you're \
standing.";
Touch,Pull,Push:
"Your hand goes right through the silvery mist!";
Enter:
! Really, move Alice to the looking-glass house.
deadflag = 2;
"Your hand goes right through the silvery mist, and in \
another moment the rest of you follows, and you are through \
the glass...";
];
general attribute to
indicate whether its tangled or not. I want to be able to take care of
the player typing untangle worsted or roll up worsted, so I define a
new action, and some new grammar to go with it (after the inclusion of
the grammar library):
Verb "roll" "untangle" "wind"
* noun -> Untangle
* "up" noun -> Untangle
* noun "up" -> Untangle;
and write a basic routine to deal with untangle chess board, and so on.
[ UntangleSub; "What curious ideas you have!"; ];Now I can create the ball of worsted; note that like the mirror, its description can vary.
Object worsted "ball of worsted" Drawing_Room
! general if its in a tangle
with name "ball" "of" "worsted" "fine" "blue" "wool",
initial "A discarded ball of worsted lies on the floor here.",
description [;
if (self has general)
"It's in a terrible tangle. All that time you spent \
rolling it up, and now look at it!";
"It's a ball of fine blue wool, all rolled up in preparation \
for some embroidery.";
],
before [;
Untangle:
give self ~general;
"You're as quick as can be at rolling up balls of wool, \
though you say so yourself! Soon it's neat and tidy again.";
];
1. > throw queen at mirror
You can't reach the looking-glass from where you're standing.
This is easy to correct; just add the following to the mirror's before
routine:
ThrownAt: "You don't want seven years' bad luck, do you?";
and change action ~= ##Examine to action ~= ##Examine or ##ThrownAt.
2. > get on chair
You get onto the arm-chair.
> push chair
You push the arm-chair over to the hearth.
Oops. Alice shouldn't be able to push the chair if she's in it, or on
the mantelpiece, or on the rug! Add the following at the start of the
arm-chair's Push,Pull action.
if (player notin Drawing_Room)
print_ret "You'll have to get off ", (the) parent(player),
" first.";
3. > climb mantelpiece
I don't think much is to be achieved by that.
The Climb action doesn't do the right thing here (see the Verb Library
for details), so I have to add my own code to the end of the
mantelpiece's Enter,Climb action.
move player to mantelpiece;
"You scramble up onto the mantelpiece.";
4. > get on mantelpiece
But you're already on the arm-chair.
The solution to the previous problem also solved this problem.
5. > enter chair
You get onto the arm-chair.
> look under rug
You find nothing of interest.
Alice shouldn't be able to look under the rug when she's in the arm-chair
or on the mantelpiece. So add the following to the start of the rug's
LookUnder action.
if (player in mantelpiece || player in armchair)
"You're unable to reach the rug from here.";
6. > examine red queen
She's a fierce little chess piece.
> drop her
I'm not sure what "her" refers to.
I need to give the queen the female attribute.After making the above changes, Alice can climb onto the mantelpiece, and some more checking reveals a few more bugs.
> enter mantelpiece
You scramble up onto the mantelpiece.
> down
You'll have to get off the mantelpiece first.
> get on chair
But you're already on the mantelpiece.
> get board
Taken.
> get off
You are on your own two feet again.
I can take care of down and get off (and also exit and out) by
adding a before routine to the drawing-room.
before [;
if (player notin Mantelpiece) rfalse;
Exit,Go:
if (noun == d_obj or out_obj)
"That's not the way to get down from a mantelpiece!";
];
And I can introduce code to the arm-chair's before routine allow Alice
to climb down from the mantelpiece to the arm-chair.
Climb,Enter:
move player to armchair
"You jump into the warm and comfortable arm-chair.";
The final problem (being able to touch objects on the floor while on the
mantelpiece) is very tricky to solve. Inform doesn't support the idea
of physically reachable from here, but we can amend the
before
routine of the drawing-room to look like this:
before [;
if (player notin Mantelpiece) rfalse;
Exit,Go:
if (noun == d_obj or out_obj)
"That's not the way to get down from a mantelpiece!";
Examine,Enter,ThrowAt,ThrownAt: ;
default:
if (inp1 ~= 1 && noun ~= 0 && Inside(noun,mantelpiece) == 0)
print_ret "You can't reach ", (the) noun, " from up \
here.";
if (inp2 ~= 1 && second ~= 0 && Inside(second,mantelpiece) == 0)
print_ret "You can't reach ", (the) second, " from up \
here.";
];
The actions Examine, Enter, ThrowAt and ThrownAt are all actions
that will work regardless of where the object is, but all remaining
actions will only work if the objects are to hand. The code that checks
that they really are to hand has to be careful, because noun and
second might not always be objects (they might be numbers, for
example). However, the variable inp1 1 if noun is not an object,
and inp2 is 1 if second is not an object (DM196), so we can check
these first.
We also have to be careful not just to use the test noun in
mantelpiece to see if the noun is at hand, because that would fail if
the object were carried by the player. Instead, we use the function
Inside to carry out this test. The function takes two objects arnd
returns 1 if the first object is inside the second, or 0 otherwise.
[ Inside x y;
do {
x = parent(x);
} until (x == 0 or y);
if (x == 0) rfalse;
];
These problems arise from having several enterable objects in the same
room, and trying to restrict what Alice is allowed to do, according to
which of the objects she's on. This has turned out to be a much more
complicated situation to program than it seemed at first!
Truly sophisticated programming of characters will involve you in cutting-edge 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 ongoing 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.
life, before, and after routines.
daemon and each_turn routines.
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.
I can make the programming much simpler by creating a class
Kitten_Class 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 1 to 3, so add the following to the red queen.
after [;
Take:
if (white_kitten.state == 1)
white_kitten.state = 3;
if (black_kitten.state == 1)
black_kitten.state = 3;
];
Alice can't push the arm-chair 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 arm-chair's before routine, where I had previously put a
comment (and don't forget to add a local variable i to the routine).
if (white_kitten in player || black_kitten in player)
"Not with a kitten in your arms!";
if (white_kitten.state == 3) i = white_kitten;
else if (black_kitten.state == 3) i = black_kitten;
if (i ~= 0)
print_ret "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 2 to 3, so give the ball an after
routine.
after [;
Take:
if (white_kitten.state == 2)
white_kitten.state = 3;
if (black_kitten.state == 2)
black_kitten.state = 3;
];
I've used the state property to describe the kitten's state, so I had
better declare it. I'll also declare 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).
Property other_kitten; Property state; ! 0=held, 1=red queen, 2=ball of wool, 3=arm-chairNow I can start to code up the kittens. My first attempt at a definition is the following (the kittens start out playing by the arm-chair, so they are in state 3).
Class Kitten_Class
has animate
with state 3,
name "kitten" "kitty" "cat",
description [;
print_ret "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, ".";
];
Object white_kitten "white kitten" Drawing_Room
class Kitten_Class
with name "white",
number 0,
other_kitten black_kitten;
Object black_kitten "black kitten" Drawing_Room
class Kitten_Class
with name "black",
number 1,
other_kitten white_kitten;
(I'll explain why the kittens have numbers below; be patient for a
bit).If you experiment with this definition, you'll discover that you get the following response.
> 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 Chapter 20 of the Designer's
Manual (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 new-line, otherwise there
won't be any space between it and any preceding text).
describe [ i;
switch (self.state) {
1: print_ret "^A ", (name) self, " is playing with the red \
queen.";
2: print_ret "^A ", (name) self, " is playing with a ball of \
worsted.";
3: if (self has general) rtrue;
if ((self.other_kitten).state == 3) {
i = self.other_kitten;
give i general;
"^Two kittens, one white and one black, are playing \
together by the arm-chair.";
}
print_ret "^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.Having given the kittens more complicated descriptions, I now run the risk of the following happening.
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 ~= 2 &&
black_kitten.state ~= 2)
"^A discarded ball of worsted lies on the floor here.";
rtrue;
],
I write a similar routine for the red queen (not shown).
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).
before [;
Take:
if (self.other_kitten in player)
"You can't hold two kittens at once!";
self.state = 0;
move self to player;
print_ret "You pick up ", (the) self, ". What a beautiful \
creature it is!";
],
after [;
Drop:
self.state = 3;
move self to Drawing_Room;
print_ret (The) self, " squirms out of your arms and scampers \
away.";
Transfer,PutOn,Insert:
self.state = 3;
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:
print_ret "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,
print_ret " 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 = 2;
print ", quickly turning the neat ball into a tangle";
}
else self.state = 1;
".";
],
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 number property of 0 or 1 which toggles each
turn, and allow the kitten to do something only if its number is 0.
Here's the start of the daemon routine.
daemon [ i;
give self ~general;
self.number = 1 - self.number;
if (self.number == 1 || random(3) == 2) rtrue;
new_line;
print (The) self;
switch (self.state) {
0: 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 = 3;
" 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);
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).
> 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.
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.Another response that I could add is the following.
> 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.";
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 non-obvious 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 non-existent 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 arm-chair, since the description of the arm-chair
doesn't mention where it is in the room. A good idea might be to change
the description of the arm-chair to a routine that says whether it's
next to the fireplace or the window (say).
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.
Alpha-testing usually refers to testing that goes on while a program is still in development; beta-testing 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, beta-testing is very important. (Infocom had three stages of testing: alpha- and beta-testing were in-house, and gamma-testing involved a large number of out-of-house players.)
Through the Looking Glass is still in beta-testing (there is a sense in which no complex enough game ever emerges from this stage!), and I've received the following reports:
Peter Grundy <pgrundy@commerce.otago.ac.nz> suggested:
Graham Nelson <nelson@vax.ox.ac.uk> noticed:
Martin Braun <100106.2673@compuserve.com> noticed:
But you are already on the mantelpiece, about what I hadn't any doubts.
Charles Briscoe-Smith <cpbs@ukc.ac.uk> noticed some of the above, and in addition:
it never refers to
them, despite the fact that the game, following Carroll, always uses
it to refer to a kitten.
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. Fixing this bug thus involves editing the parser sources - not
an easy task - or waiting for the next release of the library, which
will, with luck, provide a solution.
To solve problem 9, the best solution is probably to Replace the
ResetVagueWords library function.
[If you want to test this game too, then you may fetch the version 5 story file. Please e-mail me with your report; all comments gratefully received]
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
top-down 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 Looking-Glass example games into the public domain.
Doug Atkinson wrote Alice Through the Looking-Glass, Part Two. Any offers for Part Three?