From: Glenn Hutchings Date: Sun, 3 Apr 2016 17:11:36 +0000 (+0100) Subject: Add chapter 5. X-Git-Url: https://jxself.org/git/?a=commitdiff_plain;h=56fc4ded640e53427a37c9d4fb783bd95ba310c3;p=ibg.git Add chapter 5. --- diff --git a/chapters/05.rst b/chapters/05.rst new file mode 100644 index 0000000..87b8988 --- /dev/null +++ b/chapters/05.rst @@ -0,0 +1,610 @@ +================= + Heidi revisited +================= + +.. epigraph:: + + | *I was an innkeeper, who loved to carouse;* + | *J was a joiner, and built up a house.* + +In even the simplest story, there's bound to be scope for the player to +attempt activities that you hadn't anticipated. Sometimes there may be +alternative ways of approaching a problem: if you can't be sure which +approach the player will take, you really ought to allow for all +possibilities. Sometimes the objects you create and the descriptions you +provide may suggest to the player that doing such-and-such should be +possible, and, within reason, you ought to allow for that also. The basic +game design is easy: what takes the time, and makes a game large and +complex, is taking care of all the *other* things that the player may think +of trying. + +Here, we try to illustrate what this means by addressing a few of the more +glaring deficiencies in our first game. + +Listening to the bird +===================== + +Here's a fragment of the game being played: + +.. code-block:: transcript + + Deep in the forest + Through the dense foliage, you glimpse a building to the west. A track heads + to the northeast. + + You can see a baby bird here. + + >EXAMINE THE BIRD + Too young to fly, the nestling tweets helplessly. + + >LISTEN TO BIRD + You hear nothing unexpected. + + > + +That's not too smart, is it? Our description specifically calls the +player's attention to the sound of the bird -- and then she finds out that +we've got nothing special to say about its helpless tweeting. + +The library has a stock of actions and responses for each of the game's +defined verbs, so it can handle most of the player's input with a default, +standard behaviour instead of remaining impertinently silent or saying that +it doesn't understand what the player intends. "You hear nothing +unexpected" is the library's standard LISTEN response, good enough after +LISTEN TO NEST or LISTEN TO TREE, but fairly inappropriate here; we really +need to substitute a more relevant response after LISTEN TO BIRD. Here's +how we do it: + +.. code-block:: inform6 + + Object bird "baby bird" forest + with description "Too young to fly, the nestling tweets helplessly.", + name 'baby' 'bird' 'nestling', + before [; + Listen: + print "It sounds scared and in need of assistance.^"; + return true; + ], + has ; + +We'll go through this a step at a time: + +#. We've added a new ``before`` property to our bird object. The + interpreter looks at the property *before* attempting to perform any + action which is directed specifically at this object:: + + before [; ... ], + +#. The value of the property is an embedded routine, containing a label and + two statements:: + + Listen: + print "It sounds scared and in need of assistance.^"; + return true; + +#. The label is the name of an action, in this case ``Listen``. What we're + telling the interpreter is: if the action that you're about to perform + on the bird is a ``Listen``, execute these statements first; if it's any + other action, carry on as normal. So, if the player types EXAMINE BIRD, + PICK UP BIRD, PUT BIRD IN NEST, HIT BIRD or FONDLE BIRD, then she'll get + the standard response. If she types LISTEN TO BIRD, then our two + statements get executed before anything else happens. We call this + "trapping" or "intercepting" the action of Listening to the bird. + +#. The two statements that we execute are, first:: + + print "It sounds scared and in need of assistance.^"; + + which causes the interpreter to display the string given in double + quotes; remember that a ``^`` character in a string appears as a + newline. Second, we execute:: + + return true; + + which tells the interpreter that it doesn't need to do anything else, + because we've handled the ``Listen`` action ourselves. And the game now + behaves like this -- perfect: + + .. code-block:: transcript + + >LISTEN TO BIRD + It sounds scared and in need of assistance. + + > + +The use of the ``return true`` statement probably needs a bit more +explanation. An object's ``before`` property traps an action aimed at that +object right at the start, before the interpreter has started to do +anything. That's the point at which the statements in the embedded routine +are executed. If the last of those statements is ``return true`` then the +interpreter assumes that the action has been dealt with by those +statements, and so there's nothing left to do: no action, no message; +nothing. On the other hand, if the last of the statements is ``return +false`` then the interpreter carries on to perform the default action as +though it hadn't been intercepted. Sometimes that's what you want it to +do, but not here: if instead we'd written this: + +.. code-block:: inform6 + + Object bird "baby bird" forest + with description "Too young to fly, the nestling tweets helplessly.", + name 'baby' 'bird' 'nestling', + before [; + Listen: + print "It sounds scared and in need of assistance.^"; + return false; + ], + has ; + +then the interpreter would first have displayed our string, and then +carried on with its normal response, which is to display the standard +message: + +.. code-block:: transcript + + >LISTEN TO BIRD + It sounds scared and in need of assistance. + You hear nothing unexpected. + + > + +This technique -- intercepting an action aimed at a particular object in +order to do something appropriate for that object -- is one that we'll use +again and again. + +Entering the cottage +==================== + +At the start of the game the player character stands "outside a cottage", which +might lead her to believe that she can go inside: + +.. code-block:: transcript + + In front of a cottage + You stand outside a cottage. The forest stretches east. + + >IN + You can't go that way. + + > + +Again, that isn't perhaps the most appropriate response, but it's easy to +change: + +.. code-block:: inform6 + + Object before_cottage "In front of a cottage" + with description + "You stand outside a cottage. The forest stretches east.", + e_to forest, + in_to "It's such a lovely day -- much too nice to go inside.", + cant_go "The only path lies to the east.", + has light; + +The ``in_to`` property would normally link to another room, in the same way +as the ``e_to`` property contain the internal ID of the ``forest`` object. +However, if instead you set its value to be a string, the interpreter +displays that string when the player tries the IN direction. Other -- +unspecified -- directions like NORTH and UP still elicit the standard "You +can't go that way" response, but we can change that too, by supplying a +``cant_go`` property whose value is a suitable string. We then get this +friendlier behaviour: + +.. code-block:: transcript + + In front of a cottage + You stand outside a cottage. The forest stretches east. + + >IN + It's such a lovely day -- much too nice to go inside. + + >NORTH + The only path lies to the east. + + >EAST + + Deep in the forest + ... + +There's another issue here; since we haven't actually implemented an object +to represent the cottage, a perfectly reasonable EXAMINE COTTAGE command +receives the obviously nonsensical reply "You can't see any such thing". +That's easy to fix; we can add a new ``cottage`` object, making it a piece +of ``scenery`` just like the ``tree``: + +.. code-block:: inform6 + + Object cottage "tiny cottage" before_cottage + with description "It's small and simple, but you're very happy here.", + name 'tiny' 'cottage' 'home' 'house' 'hut' 'shed' 'hovel', + has scenery; + +This solves the problem, but promptly gives us another unreasonable +response: + +.. code-block:: transcript + + In front of a cottage + You stand outside a cottage. The forest stretches east. + + >ENTER COTTAGE + That's not something you can enter. + + > + +The situation here is similar to our LISTEN TO BIRD problem, and the +solution we adopt is similar as well: + +.. code-block:: inform6 + + Object cottage "tiny cottage" before_cottage + with description "It's small and simple, but you're very happy here.", + name 'tiny' 'cottage' 'home' 'house' 'hut' 'shed' 'hovel', + before [; + Enter: + print_ret "It's such a lovely day -- much too nice to go inside."; + ], + has scenery; + +We use a ``before`` property to intercept the ``Enter`` action applied to +the cottage object, so that we can display a more appropriate message. +This time, however, we've done it using one statement rather than two. It +turns out that the sequence "``print`` a string which ends with a newline +character, and then ``return true``" is so frequently needed that there's a +special statement which does it all. That is, this single statement (where +you'll note that the string doesn't need to end in ``^``):: + + print_ret "It's such a lovely day -- much too nice to go inside."; + +works exactly the same as this pair of statements:: + + print "It's such a lovely day -- much too nice to go inside.^"; + return true; + +We could have used the shorter form when handling LISTEN TO BIRD, and we +*will* use it from now on. + +Climbing the tree +================= + +In the clearing, holding the nest and looking at the tree, the player is +meant to type UP. Just as likely, though, she'll try CLIMB TREE (which +currently gives the completely misleading response "I don't think much is +to be achieved by that"). Yet another opportunity to use a ``before`` +property, but now with a difference. + +.. code-block:: inform6 + + Object tree "tall sycamore tree" clearing + with description + "Standing proud in the middle of the clearing, + the stout tree looks easy to climb.", + name 'tall' 'sycamore' 'tree' 'stout' 'proud', + before [; + Climb: + PlayerTo(top_of_tree); + return true; + ], + has scenery; + +This time, when we intercept the ``Climb`` action applied to the ``tree`` +object, it's not in order to display a better message; it's because we want +to move the player character to another room, just as if she'd typed UP. +Relocating the player character is actually quite a complex business, but +fortunately all of that complexity is hidden: there's a standard **library +routine** to do the job, not one that we've written, but one that's +provided as part of the Inform system. + +You'll remember that, when we first mentioned routines (see "Standalone +routines" on page 57), we used the example of ``Initialise()`` and said +that "the routine's name followed by opening and closing parentheses is all +that it takes to call a routine". That was true for ``Initialise()``, but +not quite the whole story. To move the player character, we've got to +specify where we want her to go, and we do that by supplying the internal +ID of the destination room within the opening and closing parentheses. +That is, instead of just ``PlayerTo()`` we call ``PlayerTo(top_of_tree)``, +and we describe ``top_of_tree`` as the routine's **argument**. + +Although we've moved the player character to another room, we're still in +the middle of the intercepted ``Climb`` action. As previously, we need to +tell the interpreter that we've dealt with the action, and so we don't want +the standard rejection message to be displayed. The ``return true`` +statement does that, as usual. + +Dropping objects from the tree +============================== + +In a normal room like the ``forest`` or the ``clearing``, the player can +DROP something she's carrying and it'll effectively fall to the ground at +her feet. Simple, convenient, predictable -- except when the player is at +the top of the tree. Should she DROP something from up there, having it +land nearby might seem a bit improbable; much more likely that it would +fall to the clearing below. + +It looks like we might want to intercept the ``Drop`` action, but not quite +in the way we've been doing up until now. For one thing, we don't want to +complicate the definitions of the ``bird`` and the ``nest`` and any other +objects we may introduce: much better to find a general solution that will +work for all objects. And second, we need to recognise that not all +objects are droppable; the player can't, for example, DROP THE BRANCH. + +The best approach to the second problem is to intercept the ``Drop`` action +*after* it has occurred, rather than beforehand. That way, we let the +library take care of objects which aren't being held or which can't be +dropped, and only become involved once a ``Drop`` has been successful. And +the best approach to the first problem is to do this particular +interception not on an object-by-object basis, as we have been doing so +far, but instead for every ``Drop`` which takes place in our troublesome +``top_of_tree`` room. This is what we have to write: + +.. code-block:: inform6 + + Object top_of_tree "At the top of the tree" + with description "You cling precariously to the trunk.", + d_to clearing, + after [; + Drop: + move noun to clearing; + return false; + ], + has light; + +Let's again take it a step at a time: + +#. We've added a new ``after`` property to our ``top_of_tree`` object. The + interpreter looks at the property *subsequent to* performing any action in + this room:: + + after [; ... ], + +#. The value of the property is an embedded routine, containing a label and + two statements:: + + Drop: + move noun to clearing; + return false; + +#. The label is the name of an action, in this case ``Drop``. What we're + telling the interpreter is: if the action that you've just performed + here is a ``Drop``, execute these statements before telling the player + what you've done; if it's any other action, carry on as normal. + +#. The two statements that we execute are first:: + + move noun to clearing; + + which takes the object which has just been moved from the ``player`` + object to the ``top_of_tree`` object (by the successful ``Drop`` action) + and moves it again so that its parent becomes the ``clearing`` object. + That ``noun`` is a library variable that always contains the internal ID + of the object which is the target of the current action. If the player + types DROP NEST, ``noun`` contains the internal ID of the ``nest`` + object; if she types DROP NESTLING then ``noun`` contains the internal + ID of the ``bird`` object. Second, we execute:: + + return false; + + which tells the interpreter that it should now let the player know + what's happened. Here's the result of all this: + + .. code-block:: transcript + + At the top of the tree + You cling precariously to the trunk. + + You can see a wide firm bough here. + + >DROP NEST + Dropped. + + >LOOK + + At the top of the tree + You cling precariously to the trunk. + + You can see a wide firm bough here. + + >DOWN + + A forest clearing + A tall sycamore stands in the middle of this clearing. The path winds + southwest through the trees. + + You can see a bird's nest (in which is a baby bird) here. + + > + +Of course, you might think that the standard message "Dropped" is slightly +unhelpful in these non-standard circumstances. If you prefer to hint at +what's just happened, you could use this alternative solution: + +.. code-block:: inform6 + + Object top_of_tree "At the top of the tree" + with description "You cling precariously to the trunk.", + d_to clearing, + after [; + Drop: + move noun to clearing; + print_ret "Dropped... to the ground far below."; + ], + has light; + +The ``print_ret`` statement does two things for us: displays a more +informative message, and returns ``true`` to tell the interpreter that +there's no need to let the player know what's happened -- we've handled +that ourselves. + +Is the bird in the nest? +======================== + +The game ends when the player character puts the nest onto the branch. Our +assumption here is that the bird is inside the nest, but this might not be +so; the player may have first taken up the bird and then gone back for the +nest, or vice versa. It would be better not to end the game until we'd +checked for the bird actually being in the nest; fortunately, that's easy +to do: + +.. code-block:: inform6 + + Object branch "wide firm bough" top_of_tree + with description "It's flat enough to support a small object.", + name 'wide' 'firm' 'flat' 'bough' 'branch', + each_turn [; if (bird in nest && nest in branch) deadflag = 2; ], + has static supporter; + +The extended ``if`` statement:: + + if (bird in nest && nest in branch) deadflag = 2; + +should now be read as: "Test whether the ``bird`` is currently in (or on) +the ``nest``, and whether the ``nest`` is currently on (or in) the +``branch``; if both parts are ``true``, set the value of ``deadflag`` to 2; +otherwise, do nothing". + +Summing up +========== + +You should by now have some appreciation of the need not only to handle the +obvious actions which were at the forefront of your mind when designing the +game, but also as many as you can of the other possible ways that a player +may choose to interact with the objects presented to her. Some of those +ways will be highly intelligent, some downright dumb; in either case you +should try to ensure that the game's response is at least sensible, even +when you're telling the player "sorry, you can't do that". + +The new topics that we've encountered here include these: + +.. rubric:: Object properties + +Objects can have a ``before`` property -- if there is one, the interpreter +looks at it *before* performing an action which in some way involves that +object. Similarly, you can provide an ``after`` property, which the +interpreter looks at *after* performing an action but before telling the +player what's happened. Both ``before`` and ``after`` properties can be +used not only with tangible objects like the ``bird``, ``cottage`` and +``tree`` (when they intercept actions aimed at that particular object) but +also with rooms (when they intercept actions aimed at any object in that +room). + +The value of each ``before`` and ``after`` property is an embedded routine. +If such a routine ends with ``return false``, the interpreter then carries +on with the next stage of the action which has been intercepted; if it ends +with ``return true``, the interpreter does nothing further for that action. +By combining these possibilities, you can supplement the work done by a +standard action with statements of your own, or you can replace a standard +action completely. + +Previously, we've seen connection properties used with the internal ID of +the room to which they lead. In this chapter, we showed that the value +could also be a string (explaining why movement in that direction isn't +possible). Here are examples of both, and also of the ``cant_go`` property +which provides just such an explanation for *all* connections that aren't +explicitly listed:: + + e_to forest, + in_to "It's such a lovely day -- much too nice to go inside.", + cant_go "The only path lies to the east.", + +.. rubric:: Routines and arguments + +The library includes a number of useful routines, available to perform +certain common tasks if you require them; there's a list in "Library +routines" on page 264. We used the ``PlayerTo`` routine, which moves the +player character from her current room to another one -- not necessarily +adjacent to the first room. + +When calling ``PlayerTo``, we had to tell the library which room is the +destination. We did this by supplying that room's internal ID within +parentheses, thus:: + + PlayerTo(clearing); + +A value given in parentheses like that is called an **argument** of the +routine. In fact, a routine can have more than one argument; if so, +they're separated by commas. For example, to move the player character to +a room *without* displaying that room's description, we could have supplied +a second argument:: + + PlayerTo(clearing,1); + +In this example, the effect of the ``1`` is to prevent the description +being displayed. + +.. rubric:: Statements + +We encountered several new statements: + +``return true;`` + +``return false;`` + We used these at the end of embedded routines to control what the + interpreter did next. + +``print "string";`` + +``print_ret "string";`` + The ``print`` statement simply displays the string of characters + represented here by *string*. The ``print_ret`` statement also does + that, then outputs a newline character, and finally executes a ``return + true;`` + +``if (condition && condition ) ...`` + We extended the simple ``if`` statement that we met before. The ``&&`` + (to be read as "and") is an operator commonly used when testing for + more than one condition at the same time. It means "if this condition + is true *and* this condition is also true *and* ..." There's also a + ``||`` operator, to be read as "or", and a "not" operator ``~~``, which + turns true into false and vice versa. + + .. note:: + + In addition, there are ``&``, ``|`` and ``~`` operators, but they do + a rather different job and are much less common. Take care not to + get them confused. + +``move obj_id to parent_obj_id;`` + The ``move`` statement rearranges the object tree, by making the first + ``obj_id`` a child of the ``parent_obj_id``. + +.. rubric:: Actions + +We've talked a lot about intercepting actions like ``Listen``, ``Enter``, +``Climb`` and ``Drop``. An action is a generalised representation of +something to be done, determined by the verb which the player types. For +example, the verbs HEAR and LISTEN are ways of saying much the same thing, +and so both result in the same action: ``Listen``. Similarly, verbs like +ENTER, GET INTO, SIT ON and WALK INSIDE all lead to an action of ``Enter``, +CLIMB and SCALE lead to Climb, and DISCARD, DROP, PUT DOWN and THROW all +lead to ``Drop``. This makes life much easier for the designer; although +Inform defines quite a lot of actions, there are many fewer than there are +ways of expressing those same actions using English verbs. + +Each action is represented internally by a number, and the value of the +current action is stored in a library variable called, erm, ``action``. +Two more variables are also useful here: ``noun`` holds the internal ID of +the object which is the focus of the action, and ``second`` holds the +internal ID of the secondary object (if there is one). Here are some +examples of these: + +=============================== ====== ======= ======= +Player types action noun second +------------------------------- ------ ------- ------- +LISTEN Listen nothing nothing +LISTEN TO THE BIRD Listen bird nothing +PICK UP THE BIRD Take bird nothing +PUT BIRD IN NEST Insert bird nest +DROP THE NEST Drop nest nothing +PUT NEST ON BRANCH PutOn nest branch +=============================== ====== ======= ======= + +The value ``nothing`` is a built-in constant (like ``true`` and ``false``) +which means, well, there isn't any object to refer to. There's a list of +standard library actions in "Group 1 actions" on page 270, "Group 2 +actions" on page 271 and "Group 3 actions" on page 271. + +We've now reached the end of our first game. In these three chapters we've +shown you the basic principles on which almost all games are based, and +introduced you to many of the components that you'll need when creating +more interesting IF. We suggest that you take one last look at the source +code (see "Heidi" story on page 213), and then move on to the next stage.