
Tutorial for Platypus release 4+
================================

    Send questions, comments, and bug reports to platypushome@yahoo.com.

    Conventions followed in this document:

      Attribute:          -light-
      Class, or member:   [Rooms]
      Global variables:   =actor=
      Obsolete:           lockable*
      Property:           +description+
      Property routine:   +parse_name()+
      Routine:            InDark()
      Googly eyes:        (@)(@)

    Summary section references are enclosed in {curly braces}. For
    example, {4d} means "see section 4d of the Summary".

1. Introduction

    This document provides an introduction to developing a simple work of
    interactive fiction using the Platypus library for Inform. It is
    intended for those who are already familiar with the standard library.
    It will probably be inadequate for someone who is altogether new to
    Inform.

    Use of the compiler itself will not be covered at all. It is assumed
    that you will configure it appropriately so that the Platypus library
    files can be found (e.g. through an ICL file).

    Note that although Infix can be used with Platypus, it is not included.
    You should copy "infix.h" from the set of standard library files into
    your Platypus directory. Infix does not, as of this writing, work with
    Glulx.

    Note also that both Platypus and the standard library have files called
    "English.h" and they are NOT identical. Thus, you should keep the two
    sets of files separate (not in the same directory).


2. Getting Started

    (Note: A completed "tutorial.inf" file is included with the Platypus
    distribution.)

    We start with "skeleton.inf", which supplies the essentials:


        Constant Story    "INSERT TITLE HERE";
        Constant Headline "INSERT DESCRIPTION/COPYRIGHT HERE";

        Include "First";

        Include "Middle";

        !       OPTIONAL COMPONENTS:
        ! Include "footnote";
        ! Include "scenery";
        ! Include "nameable";


        !       MAIN CODE GOES HERE

        Include "Last";

        !       NEW GRAMMAR GOES HERE


    No Initialise() routine is provided by the skeleton, because it is
    supported, but not required, by the library.


3. Making Room

    Let's get started by adding a room to the "main code" section:
    

        Rooms Laboratory "Laboratory"
          has light,
          with
            name 'laboratory' 'lab',
            description "This room is remarkable for its non-descriptness. 
                         There is a single exit to the south.",
            dirs sdir Closet,
            startup [;
                move player to self;
            ];


    The first thing to notice is that the room is of the [Rooms] class
    {3}. The [Rooms] class provides the +fpsa+ property which is used by
    FindPath() {6a}, a routine used to find a path between two rooms. The
    class also provides some simple default behavior for a room, such as
    converting LISTEN TO LAB into an ordinary LISTEN command. (You can
    override this in the individual room's +respond()+ property.)

    That brings us to our next point: the +name+ property now contains the
    actual names of the room rather than "scenery" words. This is
    particularly important for the GO TO command {14a}, which allows the
    player to go back to an already-visited room. The GO TO command finds
    a path for the player using FindPath().
    
    The next change to notice is the +dirs+ property {3c}. This replaces all
    of the direction properties (n_to*, sw_to*, etc.) from the standard
    library. There are two forms the +dirs+ property can take, array and
    routine. In the array form, it contains a list of one or more direction
    objects (ndir, swdir, outdir, etc.), after each set of which is a room
    object, a door object, or a string to print. We'll see the routine form
    in a moment.
    
    Finally, we come to the +startup()+ property. This property routine is
    called for any object that provides it when the program first starts.
    We've used the Laboratory's +startup()+ property to move the =player=
    there, as that is where we want to begin the story. Another way to do
    this would be to provide an Initialise() routine.
    
    We could also have specified the player's starting location by setting
    player.location directly. Both methods are acceptable ONLY in
    Initialise() or a +startup()+ routine. Once the game is underway, move
    [Actors] by calling MoveTo() {11b} (or using ##Go).
    
    As it stands, the program is not compilable, because we haven't
    supplied the Closet object yet. After the code for Laboratory, let's
    add:


        Rooms Closet "Broom Closet"
          has light,
          with
            name 'closet',
            adjective 'broom',
            description "This is just a small, plain closet.",
            dirs [ d;
                if (d == ndir or outdir) return Laboratory;
            ],
            points 2;


    ...which introduces two new properties, +adjective+ and +points+. The
    words in +adjective+ can be used to refer to the object, but the
    parser will give priority to the +name+ property. This becomes
    important when we add a broom, as we'll do in the next section.
    
    When a room has a +points+ property, the =player= receives the
    +points+ upon first entering the room. We'll look at other uses for
    this property later. If you type SCORE, you will see that the library
    has automatically added the +points+ of the Closet to the maximum
    score. The maximum score is held in a global variable,
    =maximum_score=, so it can be set manually if desired. +points+ can be
    negative, and negative +points+ do not affect the default maximum
    score.
    
    As promised, we also see the routine form of +dirs+ (or +dirs()+). It
    takes a direction object as a parameter and returns the room object
    (or door, or string) that the direction leads to, or 0 if there is no
    exit in that direction.
    

4. Making Broom

    Beneath the code for Broom Closet, we add a rather poorly-implemented
    broom:


        Object -> broom "broom"
          with
            name 'broom',
            description "It's broom-like.";


    Now, compile and run the program. GO SOUTH into Broom Closet and enter
    EXAMINE BROOM. The parser automatically matches the command against
    the broom and not Broom Closet, because 'broom' is the +name+ of the
    broom, but only an +adjective+ for the closet.

    (As it happens, if you change 'broom' from an +adjective+ to a +name+
    for Broom Closet, the parser would still assume that EXAMINE BROOM
    referred to the broom. That is because the player is considered less
    likely to be referring to the name of a room, except in a GO TO command.
    However, the parser would then have printed "(the broom)" to indicate its
    assumption.)
    
    PICK UP THE BROOM, GO NORTH back into Laboratory, and DROP IT. Then,
    GO TO CLOSET and enter EXAMINE BROOM. Now the parser assumes you mean
    the Broom Closet, because it is the only object in sight that matches
    the word 'broom'. The +respond()+ routine for the [Rooms] class
    converts an ##Examine action on the room into a ##Look command. (We'll
    cover +respond()+ later.)

    If you provide the constant WEAK_ADJECTIVES, the above experiment does
    not work. With WEAK_ADJECTIVES, the words in an object's +adjective+
    property can only supplement names, not replace them. In other words,
    the player must supply at least one +name+ in order to refer to an
    object. (This is the way adjectives work in TADS, for example.) In the
    above example, the word 'broom' would never match the Closet unless
    the word 'closet' were also entered.


5. I Like To Say "Beaker"

    Going back a bit, let's add the following items under the code for
    Laboratory:


        Object -> workbench "workbench"
          has supporter hider static,
          with
            name 'workbench' 'bench' 'table',
            description "A sturdy table.",
            allow_entry [ a;
                if (a == upon) rtrue;
            ];

        Object -> -> beaker "beaker"
          has container transparent open,
          with
            name 'beaker' 'bottle',
            adjective 'glass',
            description "A glass bottle with a wide mouth.",
            points 5;


    Now recompile and run the program. You will see the workbench is now
    in the Laboratory. But where is the beaker?
    
    To answer that, let's first look at the workbench code, starting with
    the "has" line. The workbench is both a -supporter- and a -hider-. A
    -supporter- can have objects placed upon them, while a hider can have
    objects placed under it.
    
    In the standard library, an object can be a -supporter- or a
    -container-, but not both. In Platypus, an object can be a -supporter-,
    a -container-, or a -hider-, or any combination of the three {5}. The
    library needs to know whether the object's children are on top of it,
    inside of it, or underneath it. This is handled via three attributes:
    -upon-, -inside-, and -under- {5a}.
  
    We did not give the beaker any of those attributes. For that reason, it
    is considered "buried" inside the workbench and inaccessible {5b}.
    Children of a "holder", that is, a -supporter-, -container-, or -hider-,
    must have one (and only one!) of the three position attributes in order
    to exist in the game environment (i.e., to be in scope). (Exception: if
    an object is -transparent-, children with no position attribute are in
    scope, but are treated as attached to the parent and can't be taken.) Of
    course, the position attribute given must match one of the parent's
    holder attributes.
    
    Since the workbench is both a -supporter- and a -hider-, we can give the
    beaker either the -upon- or the -under- attribute in order to bring it
    into play. Let's put it on top of the workbench. Add -upon- to the list
    of attributes provided in the "has" line for the beaker and recompile.
    Now you should see the beaker.
    
    To make things a bit less tedious, there is a shortcut to setting up
    the positions of objects. Call SetDefaultObjectPositions() {11a} in
    your Initialise() routine or a +startup()+ property. It will
    automatically set an appropriate position attribute for any object
    that is in need of one. Of course, if more than one position is
    possible, the routine cannot know your intentions. A strict order of
    priority is followed: -upon- is preferred to -inside-, which is
    preferred to -under-.
    
    The MoveTo() routine {11b} can be called in order to move objects once
    the game is underway. If we wanted to "teleport" the broom onto the
    workbench, we would call MoveTo(broom, workbench, upon). The first
    parameter is the object to move. The second is the object to move it
    to (that is, its new parent). The third parameter, which is optional,
    sets the position attribute. If it is not provided, it will be set by
    default as described in the preceding paragraph. MoveTo() is also used
    for moving [Actors], including the =player=.

    The beaker has been given the +points+ property. Because it is a takeable
    object, picking up the beaker gives the player 5 points (only the first
    time!). Once again, the +points+ are automatically figured into the 
    =maximum_score= (now at 7).

    Let's go back to the workbench code, and give it the -transparent-
    attribute. In addition to its effect on containers, -transparent- causes
    the -under- contents of a -hider- to be listed in room descriptions. (Of
    course, this makes "hider" a misnomer.) We want the player to readily
    see anything that has been placed under the workbench. Without
    -transparent-, the player would have to LOOK UNDER THE WORKBENCH in
    order to see what's there, although the objects would still be in scope.

    At this point, you may want to try putting the broom under the bench
    and typing LOOK, LOOK UNDER THE BENCH, LOOK ON THE BENCH, TREE BENCH.
    You could also try ;GIVE BENCH ~TRANSPARENT, assuming you have
    compiled with Infix. The broom will no longer be shown in the room
    description, though it will still be present, hidden under the bench.

    We also gave the workbench the +allow_entry()+ property routine {5c},
    which replaces the enterable* attribute. It takes one parameter, a
    position attribute, and returns true if the object can be entered by
    [Actors] in that way. The +allow_entry()+ routine for the workbench
    returns true for -upon-, but not for -under-, so [Actors] can climb
    onto the workbench but cannot crawl beneath it.


6. Down and Dirty

    To illustrate some other points, we'll make some changes to the
    workbench. Delete -transparent- from the "has" line, and change
    the +allow_entry()+ routine to always return true. Then we add an 
    +inside_description()+:
    

        Object -> workbench "workbench"
          has supporter hider static,
          with
            name 'workbench' 'bench' 'table',
            description "A sturdy table.",
            inside_description [;
                if (actor has under) "It's dusty down here.";
            ],
            allow_entry [;
                rtrue;
            ];


    It is now possible to CRAWL UNDER THE BENCH. Because it is not
    -transparent-, the normal room description is not shown while the player
    is beneath it, nor are the items -upon- the bench shown. However, LOOK
    ON BENCH still works, and scope is unaffected.

    Notice that +inside_description()+ checks to see that the =actor= is
    -under- the bench, as the text would be incongruous if the actor were
    on top of it. A more proper way of writing the condition would be:

        if (IndirectlyContains(self, actor) == under)
    
    By using IndirectlyContains() {11f}, we would ensure that the "dusty"
    message would be printed even if the player were sitting -upon- a rug
    which was itself -under- the bench.
    
    Let's modify the workbench so that the player cannot see or reach the
    items on top of it while beneath it. This will require the use of
    +respond() and +meddle()+.
    
    Under the standard library, there are five "reaction" properties: three
    individual (before*, after*, and life*), and two of general scope
    (react_before* and react_after*).

    In Platypus, there are -- count them -- ten reaction properties {2c}:
    six individual (+respond_early()+, +respond_early_indirect()+,
    +respond()+, +respond_indirect(), +respond_late()+, and
    +respond_late_indirect()+) and four of general scope (+meddle_early()+,
    +meddle()+, +meddle_late()+, and +meddle_late_late()+).

    +respond_early()+ and +respond_late()+ work just like before* and after*.
    The only difference arises with respect to rooms, where they only react to
    actions directed at the room itself, not to all actions taking place
    within it.
    
    +repond_early_indirect()+ and +respond_late_indirect()+ are much the
    same, except that they are called for the indirect object (if any).
    (Fake actions for indirect objects, such as ##LetGo* and ##ThrownAt*,
    are not used by Platypus.)

    +meddle_early()+ and +meddle_late()+ work like react_before* and
    react_after*. Again, they are different only when provided by rooms, in
    which case they act like before* and after*, reacting to actions which
    take place in the room. Unlike any other object, an actor's +location+
    can react to actions that take place out of scope (i.e., inside an
    opaque container).

    +respond()+ and +meddle()+ are called immediately before an action takes
    place, but after the library has determined that the action is possible.
    +respond()+ is called for the direct (=noun=) and +respond_indirect()+
    for the indirect object (=second=) of the action, and +meddle()+ is
    called for every object in scope.
    
    To give an example, if the =actor= tries to ##Take something which is
    locked in a -transparent- box, +respond()+ and +meddle()+ won't be
    called at all, because the action is impossible and does not reach the
    "about to happen" stage.

    There is no equivalent to life*, as there is no need for one. In most
    cases, +respond()+ or +respond_indirect()+ would be used instead.

    First, we add a +respond()+ routine to the workbench to prevent the
    player from using ##LookOn on it while under it. Then we add a
    +meddle()+ routine to prevent the player from doing anything with the
    items on the workbench while under it:
    
    
        respond [;
            LookOn:
                if (IndirectlyContains(self, actor) == under)
                "You'll have to get out from under the workbench first.";
        ],
        meddle [;
            if (noun == 0 || IndirectlyContains(self, actor) ~= under)
                rfalse;
            if (IndirectlyContains(self, noun) == upon
                || (second && IndirectlyContains(self, second) == upon))
                "You'll have to get out from under the workbench first.";
        ];


    We could have used =player= in place of =actor= in our conditions, since
    there are no other [Actors] in the program so far, but using =actor= is
    a good habit. Of course, if we do introduce any [Actors] who might
    trigger these reactions, we will need to modify them anyway, to prevent
    incongruous "You'll have to get out..." messages from appearing. The
    simplest way to do so would be to preface them with:

        if (actor ~= player) rfalse;
    
    which would prevent them from applying to anyone other than the
    =player=.
    
    
7. Life's Little Conundrums

    So far, the player can score 2 points by entering the Closet, and 5
    points for picking up the beaker, but those points aren't very
    satisfying. Let's add a more ambitious goal, like putting the broom
    into the beaker. First, we create an object to represent the task {7}:


        Object silly "finding a silly bug" with points 10;

    
    ... being careful not to put the object before anything using the ->
    syntax. Then we give the beaker a +respond_late_indirect()+ routine:

     
            respond_late_indirect [;
                Insert: if (noun == broom)
                            Achieved(silly);
            ];


    since the broom is the indirect object (that is, the =second=) of
    ##Insert in this case.

    Now, compile and run the program, TAKE THE BEAKER AND GO SOUTH. Then,
    TAKE THE BROOM, AND PUT IT IN THE BEAKER. Then get your FULL SCORE.
    
    Tasks are listed in the order in which they are achieved. You can,
    however, change this by giving them +number+ properties. {7b} By
    default, all tasks have a +number+ of 0, and negative numbers are
    allowed (and cause tasks to appear earlier in the list, of course).
    You can give the same number to as many tasks as you like; they will
    be subsorted in order of achievement.

    The "finding sundry items" and "visiting various places" lines (which
    appear if the player receives +points+ from a taking an item or entering
    a room with +points+) are treated as tasks numbered 30000 and 30001,
    respectively. Thus, they will generally appear at the end of the list.
    You are free to change the +number+ properties of the finding_items and
    visiting_places objects (e.g., during startup) to relocate them in the
    list of achievements.


8. Even More Useless Information

    For no reason at all, let's attach a footnote to the broom's
    +description+. First we have to include the footnote code {7d}, so we
    delete the ! before the line:


        Include "footnote";
    
    
    Then we create our footnote:
    
    
        Footnotes broomnote "Like a broom, in other words.";
        
        
    And change the +description+ of the broom:
    
    
        description [;
            "It's broom-like.",(note) broomnote;
        ];


    And that's it. Use the NOTE command {7g} to view the footnote (once
    you've examined the broom), in this case, NOTE 1. Use the NOTES
    command {7h} to review all of the footnotes that you have viewed with
    the NOTE command.

    We could also give the broomnote a +number+ property if we wanted the
    note to have a specific number (anything from 1 to 32767) {7e}.
    You should not give the same number to more than one note.

    If you want the note references to stop appearing after the player has 
    read the associated notes, put:
    
    
        give FootnoteGizmo general;
    
    
    in your startup code {7f}.
    

9. I'd Like To Thank The Academy

    Now we add a more complicated object:


       Actors scientist "scientist"
         has activedaemon female,
         with
          location Laboratory,
          name 'scientist' 'woman',
          description
            "She looks like an ordinary scientist.",
          daemon [;
            if (random(2) == 1)
            {  switch(random(2))
               {  1: if (self in Laboratory)
                       self.perform(##Go, sdir);
                     else
                       self.perform(##Go, ndir);
                  2: if (beaker in self)
                     {  if (self in Laboratory)
                          self.perform(##PutOn, beaker, workbench);
                     }
                     else if (TestScope(scientist, beaker))
                     {  if (IndirectlyContains(workbench, beaker) ~= under)
                        {  if (beaker in player)
                           {  MoveTo(beaker, scientist);
                              "~What are you doing with that?~ the 
                              scientist exclaims, taking the beaker 
                              away.";
                           }
                           else self.perform(##Take, beaker);
                        }
                        else if (TestScope(scientist))
                               "The scientist looks around, scratching her
                                head.";
                     }
               }
               rtrue;
            }
            if (TestScope(self) == 0) rfalse;
            switch(random(5))
            {  1: "^The scientist says, ~Hmmm.~";
               3: "^The scientist says, ~Aha!~";
            }
        ];

        Object -> coat "white coat"
          has clothing worn,
          with
            name 'coat',
            adjective 'white' 'lab',
            description "Very official.";


    Let's begin at the beginning. The scientist is of the [Actors] class
    {4b}. This will allow us to invoke the +perform()+ property {4c} for her,
    as we'll see shortly. We've given her the -activedaemon- attribute so
    that her +daemon()+ will be running as soon as the game starts. (We could 
    also call StartDaemon(scientist), which would do the same thing.)

    Her +location+ property {4e} has been given the initial value of
    Laboratory. This will cause the library to automatically place her
    there at startup. The +name+ and +description+ lines should be
    self-explanatory, so let's skip ahead to her +daemon()+.

    The first switch statement has 2 possible outcomes. The first simply
    causes the scientist to walk from one of the two rooms to the other by
    invoking +perform()+ with a ##Go action, with the appropriate
    direction object. The library will display the arrival or departure 
    message to the player, as appropriate.

    The second possibility deals with the beaker. If the scientist is
    carrying the beaker, she will place it on the workbench (again using
    +perform()+, this time with ##PutOn) if she is in the Laboratory.
    Otherwise, she will take the beaker if it is in scope to her, unless
    it is hidden under the workbench. If the =player= is holding the
    beaker, she will still take it, but we cannot use +perform()+ for
    this, because ##Take does not allow objects to be stolen from other
    [Actors]. Instead, we call MoveTo() and print an appropriate message.
    Note that because we use IndirectlyContains() to check whether the
    beaker is under the workbench, the player can hide the beaker from her
    either by putting it under the workbench, or by crawling under there
    while holding it.

    If the beaker is not in scope to the scientist, we simply print a message 
    expressing her confusion, if she is in scope to the =player=.
    
    There is a 1-in-2 chance of bypassing the first switch statement
    altogether, in which case we just print a random message (sometimes).
    But first we call TestScope() to prevent the message from being
    printed if the player isn't around.

    The scientist's coat will be in scope to the player whenever the scientist
    is, because it is -worn-. However, anything the scientist is carrying,
    such as the beaker, will not be. In other words, once the scientist picks
    up the beaker, it disappears as far as the player is concerned until she
    puts it down again. (It remains in scope for her, of course.) We can
    change this by giving her -transparent-. The player will then be able to
    see (and refer to) what she is carrying.


10. Things Are Not What They Seem

    Now, to illustrate a few final points, we modify the broom object, and
    add another actor:


		Object -> broom "broom"
		  with
		    short_name [;
		        give self activedaemon;
		    ],
		    parse_name [     wd c fl;
		        while ((wd = NextWord()) ~= 0)
		        {   if (wd == 'broom')
		            {   c++; fl = 1; }
		            else if (wd == 'alien' && fl)
		            {   c++; give self general; }
		            else break;
		        }
		        return c;
		    ],
		    description [;
		        "It's broom-like.",(note) broomnote;
		    ],
		    meddle_early [;
		        if (self has general)
		        {   Transmogrify(broom, broom_alien);
		            give self ~activedaemon;
		            give broom_alien activedaemon;
		            print "(Lucky guess.)^";
		        }
		    ],
		    daemon [;
		        if (TestScope(self) == 0)
		        {   Transmogrify(broom, broom_alien);
		            give self ~activedaemon;
		            give broom_alien activedaemon;
		        }
		    ];

		Actors broom_alien "broom alien"
		  has neuter,
		  with
		    name 'broom' 'alien',
		    description "Weird.",
		    allow_take [; rtrue; ],
		    daemon [;
		        if (self has general)
		        {   give self ~general;
		            return;
		        }
		        give self general;
		        if (self in workbench) self.perform(##Exit);
		        if (self.location == player.location) return;
		        if (FindPath(self.location, player.location, self))
		            self.perform(##Go, self.&path_moves-->0);
		    ],
		    messages [;
		        ExitFromUpon: "^#A# hops off #o#.";
		        ExitFromUnder:
		            if (lm_o has transparent
		                || IndirectlyContains(lm_o, player) == under)
		                "^#A# skitters out from under #o#.";
		            "^#A# appears from under #o#.";
		        Go: if (lm_n == 5002)
		                "^#A# hops into the room.";
		    ];
    
    Yikes! Well, the broom's not quite as boring now. It is in fact a
    broom alien. There are two ways the player can discover that. The
    first is by calling it "broom alien". The second is by leaving it
    behind, in which case it will follow the player around (which an
    ordinary broom would be unlikely to do). However, the alien is slow
    and only gets to move every other turn.

    Let's go over the code for the broom object. The +short_name()+
    routine does not print its name, but instead activates its +daemon()+.
    This is a sneaky way of causing the +daemon()+ to activate the first
    time the player sees the broom. Once started, the +daemon()+ causes
    the broom to change into the broom alien as soon as the player is out
    of scope. This is done with a call to Transmogrify(), a routine which
    can be used whenever something in the game is changed such that it
    must be represented by a different object from that point. We also
    start the alien object's +daemon()+ and stop that of the broom.

    A +parse_name()+ routine takes the place of +name+ and +adjective+. In
    this case, we use it to determine whether the player has referred to the
    broom as a "broom alien". If so, the broom is swapped immediately with the
    alien, and the player's deduction is acknowledged. This is handled in the
    broom's +meddle_early()+ routine, since by that time parsing is finished.

    Turning to the broom_alien object, we have given it an +allow_take()+
    property {4j} which always returns true. This allows any other actor
    to pick up the alien. The +daemon()+ for the alien toggles its
    -general- attribute, and returns immediately if it was set. This
    effectively causes the +daemon()+ to only execute every other turn. It
    always leaves the workbench if it has been placed on or under it, via
    an ##Exit action.

    If the alien is not near (in scope of) the =player=, then FindPath()
    is called to find a path from the alien's +location+ to the player's.
    Because the =player= is a moving target, we call FindPath() again for
    each move, rather than calling it once and following the complete
    path. (Ignore for the moment the fact that there are only two rooms
    in the game.) If FindPath() returns true, indicating that a path was
    found, we execute a ##Go action for the alien, with the first
    direction object in the alien's +path_moves+ property.

    The [Actors] class provides the three properties that are set by
    FindPath(): +path_length+, +path_moves+, and +path_rooms+.
    +path_length+ indicates the number of moves in the path, +path_moves+
    contains the directions to ##Go in, and +path_rooms+ contains a list
    of the [Rooms] that the directions lead to. So, after executing a ##Go
    action with the first direction on the list, the =actor= should be in
    the room indicated by the first entry in +path_rooms+. If not,
    something has gone wrong with the path: either the =actor= was unable
    to go in the given direction due to some obstacle, or the room that
    the direction leads to has changed. And so on for each move in the
    path.

    For the simple movement of the broom alien, we never look at anything
    except the first entry of its +path_moves+ property. Note that
    although the alien will exit from the workbench, it cannot escape if
    put into the beaker. Also, the library takes care of updating an
    actor's +location+ even when the actor is carried around by someone
    else.
    
    We have provided a +messages()+ property (making use of print codes
    {4i}) for the alien to replace the generic messages associated with
    its movements. The library will, of course, only print these messages
    when the alien is in scope to the player. Notice that the message for
    ##ExitFromUnder changes depending on whether the alien is appearing
    from beneath an opaque hider.

    
11. Conclusion

    That concludes the tutorial. If you haven't already, now might be a
    good time to look through the summary and reference files for the
    things that weren't covered here. Unless the room is on fire. Then it
    might be a good time to leave.
