Inform - Resources - Examples

Starting  

Problems  
Objects  
Testing I  

Characters  
Frills  
Testing II  
References  

Alice: An Inform Tutorial (continued)

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.

4. Common difficulties

The most complicated parts of the Inform language are to do with which order routines are evaluated during play, and what to return from routines to affect this order. These parts cause more problems to beginning programmers than any of the other features of the language.

4.1 Return codes

A routine inside an object returns 0 (false) by default, and a routine outside of an object returns 1 (true) by default. You can think of this as meaning that there's an invisible `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 special' or maybe `don't do anything at all'.

Here are the meanings of the return codes of the most commonly-used routines:

`before'
`life'
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.
In an object's `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.

4.2 Evaluation order

Here's a summary of what the Inform library does when the player types a command (DM 6).
  1. Parse the command, setting up the variables `action', `noun' and `second' appropriately.
  2. Call `GamePreRoutine' (if there is one). If this returns non­zero, stop here.
  3. Call the `before' routine of the player. If this returns non­zero, stop here.
  4. Call the `before' routine of the current location. If this returns non­zero, stop here.
  5. Call the `before' routine of the object `noun' (if any). If this returns non­zero, stop here.
  6. Carry out the action, if possible. If there was no action to carry out (i.e., the action didn't do anything interesting, for example `jump' or `smell', or the action failed, for example `put apple in banana') then print a failure message and stop here.
  7. Call the `after' routine of the player. If this returns non­zero, stop here.
  8. Call the `after' routine of the current location. If this returns non­zero, stop here.
  9. Call the `after' routine of the object `noun' (if any). If this returns non­zero, stop here.
  10. Call `GamePostRoutine' (if there is one). If this returns non­zero, stop here.
  11. Print a message describing what happened at stage 6.
It will be seen from this table that `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.

5. Adding objects

Now that I have a room, I can fill it up with objects. I'll start with the simplest objects, and work up to the most complicated (the kittens). I'll try to add the objects in such a way that the code can be tested after each addition (but if two objects refer to each other this won't be possible).

5.1 The red queen: a simple object

The simplest objects just have a `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').

5.2 The chess board

The chess board is not quite so simple. Because it doesn't appear in the room description, I'll give it an `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.";

5.3 The hearth: a scenery object

The fireplace is mentioned in the initial room description, so I probably ought to provide a `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.";

5.4 The rug: a complex object

Often, the best way to deal with more complicated objects is to consider all the commands that the player might type that ought to make the object behave differently from the default way in which the library handles that action.

In order to do this, it helps to be familiar with the operation of the library. Table 6A 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.

  • `take rug' - so I make it `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').
  • `pull rug' and `push rug' - I provide a `before' routine that deals with these.
  • `put red queen on rug' - make it a `supporter'.
  • `stand on rug' - make it `enterable'.
  • `look under rug' - provide a `before' routine for the `LookUnder' action (this routine prevents Alice from looking under the rug while she is standing on it!).
Some other points of interest:
  • I call the rug `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.
  • The rug has the `concealed' attribute because it appears in the room description.
  • The `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.";
            }
        ];

5.5 The arm­chair

My puzzle involves not being able to move the arm­chair if the kittens are playing near it. But since I'm saving the kittens for last (because they're the most complicated objects) I can't do a full implementation of the arm­chair. Instead, I'll do the best I can, leaving a comment in the place where I ought to check for the kittens. Here the relevant commands to consider are the following.
  • `take arm­chair' - make it `static'.
  • `put chess board on arm­chair' - make it a `supporter'.
  • `enter arm­chair' - make it `enterable'.
  • `push arm­chair' or `pull arm­chair' - these (if successful) just toggle the position of the arm­chair; either it's close enough to the mantelpiece to climb up, or it isn't. I'll use the `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.";
        ];

5.6 The mantelpiece

For the mantelpiece, I need to consider the following commands.
  • `climb on mantelpiece' - make it `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.
  • `put red queen on mantelpiece' - make it `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.
  • `take red queen from mantelpiece' - the same discussion applies.
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.";
        ];

5.7 The mirror

The mirror presents the following problems.
  • `examine mirror' - the amount of the room that Alice can see in the mirror varies, depending on where she is - if she's standing on the floor, she can only see the ceiling in the mirror. If she's on the arm­chair she can see the whole room in the mirror. If she's on the mantelpiece, then she should be able to see the mirror beginning to melt away.
  • `touch mirror', `push mirror', `pull mirror' - Alice shouldn't be able to touch the mirror unless she's standing on the mantelpiece. But if she's standing on the mantelpiece, then her hand should go right through.
  • `take mirror' - make it `static'.
  • `enter mirror' - if I get around to writing the next part of the game, then this will cause Alice to move to the looking­glass house (using the `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...";
        ];

5.8 The ball of worsted

(Worsted is a kind of fine wool used for embroidery.) This object will start out neatly rolled into a ball, but when its given to a kitten, it will get into a tangled state. I'll use the `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.";
        ];

6. Testing, part 1

Now I have a program, alice2.inf (browse | download), which, although incomplete, will compile and run, and allow me to attempt a few actions. I advise you to compile this fragment of a game, and test it yourself to see what problems you can discover (there are quite a few!), before I start to list the ones I found.
 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)
                "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 or DM4 Table 6 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' [since this tutorial was first written, it has done just that -- GN], 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)
		"You can't reach ", (the) noun, " from up here.";
	    if (inp2 ~= 1 && second ~= 0 && Inside(second,mantelpiece) == 0)
		"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 (DM4 6), 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!

Continue to section 7.


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.