Inform - Resources - Examples

Starting  

Problems  
Objects  
Testing I  

Characters  
Frills  
Testing II  
References  

Alice: An Inform Tutorial (concluded)

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.

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.

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 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.

  1. State, which is a set of values that describe how the actions of the character vary. We are familiar with some aspects of character state that are handled by the Inform library, such as which room a character is in, but we will have to invent other aspects; for example, how well­disposed a merchant is to the player, how many times the player asked the wizard for help, whether or not the player has bribed the guard, and so on.

  2. Reactions, which are things the character does in response to interaction from the player, for example when the player talks to the character, attacks him, kisses him, offers things to him, tries to steal things from him, and so on. In Inform these are typically coded in the `life', `before', and `after' routines.

  3. Autonomous actions, which are things the character does of his own accord, without special prompting from the player. In Inform, these are encoded in the `daemon' and `each_turn' routines.
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 arm­chair and getting in Alice's way. (I could go for more complicated behaviour, allowing the kittens to be on the arm­chair, or on the mantelpiece, or on the rug, or under the arm­chair, 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.'

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.

      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.)

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 == 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).

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 arm­chair, so they are in state CHAIR_STATE).

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.)

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 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 new­line, 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.

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 ~= 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).

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 = 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 play­testers are very good, then they'll suggest commands that they tried which ought to produce a more interesting response.

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.
  • `look at hearth in mirror' - you can't see the hearth in the mirror.
  • `look at mirror in mirror' - ditto.
  • `look at myself in the mirror' - should respond with `the looking­glass Alice', not `the looking­glass yourself'!
  • The description of objects in the mirror should change depending on where you are - e.g. you can't see things from the floor, you can see things from the arm­chair, and things are fuzzy and misty when seen from the mantelpiece.
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.

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.";

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 non­obvious solutions; and that the player need never flounder in want of a task to attempt.

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'.

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 beta­tested.

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:

  1. How about adding verbs to stroke, pat or tickle the kittens?

    Graham Nelson <graham@gnelson.demon.co.uk> noticed:

  2. The command `examine red pawn' works but `examine red pawns' doesn't. Also, I think it would be nice if attempts to examine the black pieces could be met by a polite demurral that the set is in white and red.
  3. The command `enter hearth' will occur to inveterate Curses players.

    Martin Braun <100106.2673@compuserve.com> noticed:

  4. If you put an object on the mantelpiece, climb up to it and type `take all', this object does not respond to the command (though you can pick it up individually).
  5. You can't talk to the kittens while on the mantelpiece.
  6. On the mantelpiece, the command `go down' produces the rather strange response `But you are already on the mantelpiece', about what I hadn't any doubts.
  7. When I discover the red queen under the rug, the game moves the queen to my possessions. I would like the game to tell me that I picked her up instead of only having found her.
  8. If one of the kittens plays with the ball and you pick it up again, the game describes it as unrolled. If you then give it to the kittens a second time, the response still says that the kitten is turning the neat ball (which it isn't any more) into a tangle.

    Charles Briscoe-Smith <cpbs@ukc.ac.uk> noticed some of the above, and in addition:

  9. Because the kittens are animate, the pronoun `it' never refers to them, despite the fact that the game, following Carroll, always uses `it' to refer to a kitten.
  10. The commands `get down', `climb down', `get off mantelpiece', `get down from mantelpiece', `get onto chair', `jump' and `jump onto/into chair' don't work; but if the player types them while on the mantelpiece, the game should move the player to the armchair.
Most of these bugs should be easy to fix, and are left as an exercise for the reader.

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.

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!

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.

-- Gareth Rees, 1995

12. References

Alice Through the Looking­Glass, 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 Looking­Glass, 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 web site has not been fully supported since April 2008. Information may be out of date. This page was originally managed by Graham Nelson (graham@gnelson.demon.co.uk) assisted by C Knight.