From: David Griffith Date: Sat, 16 Apr 2016 03:51:13 +0000 (-0700) Subject: Added chapter 12. X-Git-Url: https://jxself.org/git/?a=commitdiff_plain;h=6ca42969f45428daf7299356ac622a4640b078f9;p=ibg.git Added chapter 12. --- diff --git a/chapters/12.rst b/chapters/12.rst new file mode 100644 index 0000000..595c0b1 --- /dev/null +++ b/chapters/12.rst @@ -0,0 +1,573 @@ +==================== +Captain Fate: take 3 +==================== + +.. epigraph:: + + | *W was a watchman, and guarded the door;* + | *X was expensive, and so became poor.* + +.. only:: html + + .. image:: /images/picW.png + :align: left + +.. raw:: latex + + \dropcap{w} + +e've given ourselves an interesting challenge by overusing that +convenient word "toilet", and here we show you how we resolve the +ambiguities that have been introduced. Also, it's time for the eponymous +owner of Benny's café to be developed in full. + +Too many toilets +================ + +If you check the ``name`` properties of the toilet door, the toilet key +and the toilet room, you’ll see that the dictionary word ``'toilet'`` +occurs in all of them. There won't be any problems if players mention +the words DOOR or KEY, but we reach a strange impasse should they try to +perform some action with just the word TOILET. The interpreter has to +think fast: is the player talking about the key? About the door? Or +about the toilet? Unable to decide, it asks: "Which do you mean, the +door to the toilet, the toilet key or the toilet?" + +And guess what? Players will never be able to refer to the toilet object +(unless they type BATH ROOM or REST ROOM, not an obvious choice since we +haven't used those phrases anywhere visible). If the player answers +TOILET the parser will still have three objects with that dictionary +word as a possible name, so it will ask again, and again -- until we +give it some dictionary word which is not ambiguous. A human reader +would be able to understand that the word TOILET alone refers to the +room, but the interpreter won't -- unless we help it a little. + +We could work around this problem in more than one way, but we'll take +this opportunity of demonstrating the use of a third-party library +package. + +When experienced designers find a problem which is not easily solvable, +they may come up with a smart solution and then consider that others +could benefit from the effort. The product of this generosity takes the +form of a library extension: the solution neatly packaged as a file that +other designers can incorporate into their source code. These files can +be found in the IF Archive: go to +``http://mirror.ifarchive.org/indexes/if-archive.html`` and then select +"``.../infocom``", "``.../compilers``", "``.../inform6``", +"``.../library``", and "``.../contributions``". All of these files +contain Inform code. To use a library extension (also known as a library +contribution), you should download it and read the instructions (usually +embedded as comments in the file, but occasionally supplied separately) +to discover what to do next. Normally, you ``Include`` it (as we have +already done with ``Parser``, ``VerbLib`` and ``Grammar``), but often +there are rules about where exactly this Include should be placed in +your source code. It is not unusual to find other suggestions and +warnings. + +To help us out of the disambiguation problem with the word TOILET, we +are going to use Neil Cerutti's extension ``pname.h``, which is designed +for situations precisely like this. First, we follow the link to the IF +archive and download the compressed file ``pname.zip``, which contains +two more files: ``pname.h`` and ``pname.txt``. We place these files in +the folder where we are currently developing our game or, if using the +environment we proposed in "Tools of the trade" on page 17, in the +``Inform\Lib\Contrib`` folder. The text file offers instructions about +installation and usage. Here we find a warning: + + This version of pname.h is recommended for use only with version 6/10 + of the Inform Library. + +We're actually using a later version, but this doesn't seem to cause a +problem. Most extensions aren't so fussy, but ``pname.h`` fiddles with +some routines at the heart of the standard library; these may not be +identical in other Inform versions. + +The introduction explains what ``pname.h`` does for you; namely, it lets +you avoid using complicated ``parse_name`` routines to disambiguate the +player's input when the same dictionary word refers to more than one +item. A ``parse_name`` routine would have been the solution to our +problem before the existence of this file, and it qualifies as an +advanced programming topic, difficult to master on a first approach. +Fortunately, we don't need to worry. Neil Cerutti explains: + + The ``pname.h`` package defines a new object property, ``pname`` + (short for phrase name), with a similar look and feel to the standard + ``name`` property: both contain a list of dictionary words. However, + in a ``pname`` property the order of the words is significant, and + special operators ``'.p'`` ``'.or'`` and ``'.x'`` enable you to embed + some intelligence into the list. In most cases where the standard + ``name`` property isn't enough, you can now just replace it with a + ``pname`` property, rather than write a ``parse_name`` property + routine. + +We'll soon see how it works. Let's take a look at the installation +instructions: + + To incorporate this package into your program, do three things: + + #. Add four lines near the head of the program (before you include + ``Parser.h``). + + ``Replace MakeMatch;`` + ``Replace Identical;`` + ``Replace NounDomain;`` + ``Replace TryGivenObject;`` + + #. Include the ``pname.h`` header just after you include ``Parser.h``. + ``Include "Parser";`` + ``Include "pname";`` + + #. Add ``pname`` properties to those objects which require phrase + recognition. + +It seems simple enough. So, following steps one and two, we add those +``Replace...`` lines before the inclusion of ``Parser``, and we include +``pname.h`` right after it. ``Replace`` tells the compiler that we're +providing replacements for some standard routines. + +.. code-block:: inform6 + + Constant Story "Captain Fate"; + Constant Headline + "^A simple Inform example + ^by Roger Firth and Sonja Kesserich.^"; + Release 3; Serial "040804"; ! for keeping track of public releases + + Constant MANUAL_PRONOUNS; + + Replace MakeMatch; ! requited by pname.h + Replace Identical; + Replace NounDomain; + Replace TryGivenObject; + + Include "Parser"; + Include "pname"; + !... + +Now our source code is ready to benefit from the library package. How +does it work? We have acquired a new property -- ``pname`` -- which can +be added to some of our objects, and which works pretty much like a +``name`` property. In fact, it should be used *instead* of a ``name`` +property where we have a disambiguation problem. Let’s change the +relevant lines for the toilet door and the toilet key: + +.. todo:: + + Maybe specially highlight the lines using pname? + +.. code-block:: inform6 + + Object toilet_door + with pname '.x' 'red' '.x' 'toilet' 'door', + short_name [; + !... + + Object toilet_key "toilet key" benny + with pname '.x' 'toilet' 'key', + article "the", + !... + +while leaving the ``outside_of_toilet`` unchanged: + +.. code-block:: inform6 + + Object outside_of_toilet "toilet" cafe + with name 'toilet' 'bath' 'rest' 'room' 'bathroom' 'restroom', + before [; + !... + +We are now using a new operator -- ``'.x'`` -- in our ``pname`` word +lists. explains + + The first dictionary word to the right of a ``'.x'`` operator is + interpreted as optional. + +and this makes the dictionary word ``'toilet'`` of lesser importance for +these objects, so that at run-time players could refer to the DOOR or +TOILET DOOR or the KEY or TOILET KEY -- but not simply to the TOILET -- +when referring to either the door or the key. And, by leaving unchanged +the name property of the outside_of_toilet object – where there is also +another ``'toilet'`` entry -- the ``pname`` properties will tell the +interpreter to discard the key and the door as possible objects to be +considered when players refer just to TOILET. Looking at it in terms of +the English language, we've effectively said that "TOILET" is an +adjective in the phrases "TOILET DOOR" and "TOILET KEY", but a noun when +used on its own to refer to the room. + +The ``pname.h`` package has additional functionality to deal with more +complex phrases, but we don't need it in our example game. Feel free, +however, to read ``pname.txt`` and discover what this fine library +extension can do for you: it's an easy answer to many a disambiguation +headache. + + +Don't shoot! I'm only the barman +================================ + +A lot of the action of the game happens around Benny, and his definition +needs a little care. Let's explain what we want to happen. + + So the door is locked and the player, after discovering what the note + stuck on the toilet door said, will eventually ask Benny for the key. + Sadly, Benny allows use of the toilet only to customers, a remark + he'll make looking pointedly at the menu board behind him. The player + will have to ask for a coffee first, thereby qualifying as a customer + in Benny's eyes and thus entitled to make use of the toilet. At last! + Rush inside, change into Captain Fate’s costume and fly away to save + the day! + +Except that the player neither paid for the coffee, nor returned the +toilet key. Benny will have to stop the player from leaving the café in +these circumstances. To prevent unnecessary complication, there will be +a coin near the lavatory, enough cash to pay for the coffee. And that +about sums it all up; pretty simple to describe -- not so simple to +code. Remember Benny's basic definition from the previous chapter: + +.. code-block:: inform6 + + Object benny "Benny" cafe + with name 'benny', + description + "A deceptively FAT man of uncanny agility, Benny entertains his + customers crushing coconuts against his forehead when the mood + strikes him.", + has scenery animate male proper transparent; + +We can now add some complexity, beginning with a ``life`` property. In +generic form: + +.. code-block:: inform6 + + life [; + Give: !... code for giving objects to Benny + Attack: !... code to deal with player's aggressive moves + Kiss: !... code about the player getting tender on Benny + Ask,Tell,Answer: !... code to handle conversation + ], + +We have seen some of these actions before. We'll take care of the easier +ones: + + Attack: + if (costume has worn) { + deadflag = 4; + print "Before the horror-stricken eyes of the surrounding + people, you MAGNIFICENTLY jump OVER the counter and + attack Benny with REMARKABLE, albeit NOT sufficient, + speed. Benny receives you with a TREACHEROUS upper-cut + that sends your GRANITE JAW flying through the cafe.^^ + ~These guys in pyjamas think they can bully innocent + folk,~ snorts Benny, as the EERIE hands of DARKNESS + engulf your vision and you lose consciousness."; + } + else + "That would be an unlikely act for MEEK John Covarth."; + + Kiss: + "This is no time for MINDLESS infatuation."; + + Ask,Tell,Answer: + "Benny is too busy for idle chit-chat."; + +Attacking Benny is not wise. If the player is still dressed as John +Covarth, the game displays a message refusing to use violence by reason +of staying in character as a worthless wimp. However, if Captain Fate +attempts the action, we'll find that there is more to Benny than meets +the eye, and the game is lost. Kissing and conversation are disallowed +by a couple of tailored responses. + +The Give action is a bit more complicated, since Benny reacts to certain +objects in a special and significant way. Bear in mind that Benny's +definition needs to keep track of whether the player has asked for a +coffee (thereby becoming a customer and thus worthy of the key), whether +the coffee has been paid for, and whether the toilet key has been +returned. The solution, yet again (this really is a most useful +capability), is more local property variables: + +.. code-block:: inform6 + + Object benny "Benny" cafe + with name 'benny', + description + "A deceptively FAT man of uncanny agility, Benny entertains his + customers crushing coconuts against his forehead when the mood + strikes him.", + coffee_asked_for false, ! has player asked for a coffee? + coffee_not_paid false, ! is Benny waiting to be paid? + key_not_returned false, ! is Benny waiting for the key? + live [; + !... + +Now we are ready to tackle the ``Give`` action of the ``life`` property, +which deals with commands like GIVE THE KEY TO BENNY (in a moment, we'll +come to the ``Give`` action of the ``orders`` property, which deals with +commands like BENNY, GIVE ME THE KEY): + +.. code-block:: inform6 + + Give: + switch (noun) { + clothes: + "You NEED your unpretentious John Covarth clothes."; + costume: + "You NEED your stupendous ACID-PROTECTIVE suit."; + toilet_key: + self.key_not_returned = false; + move toilet_key to benny; + "Benny nods as you ADMIRABLY return his key."; + coin: + remove coin; + self.coffee_not_paid = false; + print "With marvellous ILLUSIONIST gestures, you produce the + coin from the depths of your "; + if (costume has worn) print "BULLET-PROOF costume"; + else print "ordinary street clothes"; + " as if it had dropped on the counter from Benny's ear! + People around you clap politely. Benny takes the coin + and gives it a SUSPICIOUS bite. ~Thank you, sir. Come + back anytime,~ he says."; + } + +The Give action in the ``life`` property holds the variable ``noun`` as +the object offered to the NPC. Remember that we can use the ``switch`` +statement as shorthand for: + +.. code-block:: inform6 + + if (noun == costume) { whatever }; + if (noun == clothes) { whatever }; + !... + +We won't let players give away their clothes or their costume (yes, an +improbable action, but you never know). The toilet key and the coin are +successfully transferred. The property ``key_not_returned`` will be set +to true when we receive the toilet key from Benny (we have not coded +that bit yet), and now, when we give it back, it's reset to ``false``. +The ``move`` statement is in charge of the actual transfer of the object +from the player's inventory to Benny, and we finally display a +confirmation message. With the coin, we find a new statement: +``remove``. This extracts the object from the object tree, so that it +now has no parent. The effect is to make it disappear from the game +(though you are not destroying the object permanently -- and indeed you +could return it to the object tree using the ``move`` statement); as far +as the player is concerned, there isn’t a COIN to be found anywhere. The +``coffee_not_paid`` property will be set to true when Benny serves us +the cup of coffee (again, we’ll see that in a moment); now we reset it +to ``false``, which liberates the player from debt. This culminates with +the ``"..."`` print-and-return statement, telling the player that the +action was successful. In passing, remember that in "A homely +atmosphere" on page 131 we defined the counter such that PUT KEY ON +COUNTER is automatically translated into GIVE KEY TO BENNY . + +Why move the key to Benny but remove the coin instead? Once players +qualify as customers by ordering a coffee, they will be able to ask for +the key and return it as many times as they like, so it seems sensible +to keep the key around. The coin, however, will be a one-shot. We won't +let players ask for more than one coffee, to prevent their debt from +growing ad infinitum -- besides, they came in here to change, not to +indulge in caffeine. Once the coin is paid, it disappears for good, +supposedly into Benny's greedy pockets. No need to worry about it any +more. + +The benny object needs also an ``orders`` property, just to take care of +the player's requests for coffee and the key, and to fend off any other +demands. The ``Give`` action in an ``orders`` property deals with inputs +like ASK BENNY FOR THE KEY or BENNY, GIVE ME THE KEY. The syntax is +similar to that of the ``life`` property: + +.. code-block:: inform6 + + orders [; ! handles ASK BENNY FOR X and BENNY, GIVE ME XXX + Give: + if (second ~= player or nothing) "Benny looks at you strangely."; + switch (noun) { + toilet_key: + if (toilet_key in player) "But you DO have the key already."; + if (self.coffee_asked_for == true) + if (toilet_key in self) { + move toilet_key to player; + self.key_not_returned = true; + "Benny tosses the key to the rest rooms on the + counter, where you grab it with a dextrous and + precise movement of your HYPER-AGILE hand."; + } + else + "~Last place I saw that key, it was in YOUR + possession,~ grumbles Benny. ~Be sure to return it + before you leave.~"; + else + "~Toilet is only fer customers,~ he grumbles, looking + pointedly at a menu board behind him."; + coffee: + if (self.coffee_asked_for == true) + "One coffee should be enough."; + move coffee to counter; + self.coffee_asked_for = self.coffee_not_paid = true; + "With two gracious steps, Benny places his world-famous + Cappuccino in front of you."; + food: + "Food will take too much time, and you must change NOW."; + menu: + "With only the smallest sigh, Benny nods towards the menu + on the wall behind him."; + default: + "~I don't think that's on the menu, sir.~"; + } + ], + +* We test the value of ``second`` in order to trap over-generous + gestures such as BENNY, GIVE COFFEE TO CUSTOMERS . Then we consider + potential requests. + +* **Toilet key:** first, we check whether players already have the key + or not, and complain if they do, stopping execution thanks to the + implicit ``return true`` of the ``"..."`` statement. If players don’t + have the key, we proceed to check whether they've asked for a coffee + yet, by testing the ``coffee_asked_for`` property. If this is true , + we should also check if the key is actually one of Benny’s + possessions -- a perverse player could get the key, then drop it + somewhere and ask for it again; if this should happen, we indicate + that Benny is nobody's fool with the message ``"~Last place I saw + that key..."``. Once all these fitting conditions are ``true``, + players will get the key, which means that they have to return it -- + the ``key_not_returned`` property becomes ``true`` -- and we display + a suitable message. However, if the player didn't ask for a coffee, + Benny refuses to oblige, mentioning for the first time the menu board + where players will be able to see a picture of a cup of coffee when + they EXAMINE it. Take care to see how all the ``else`` clauses pair + up with the appropriate if statements, triggering responses for each + of the conditions that wasn't met. + +* **Coffee:** we check whether players have already asked for a coffee, + by testing the ``coffee_asked_for`` property, and refuse to serve + another one if ``true``. If ``false``, we place the coffee on the + counter, and set the properties ``coffee_asked_for`` and + ``coffee_not_paid`` to ``true``. The message bit you know about. + +* **Food:** we'll provide an object to deal with all of the delicious + comestibles to be found in the café, specifically those (such as + "pastries and sandwiches") mentioned in our descriptions. Although + that object is not yet defined, we code ahead to thwart player's + gluttony in case they choose to ask Benny for food. + +* **Menu:** our default response -- "I don’t think that’s on the menu, + sir" -- isn’t very appropriate if the player asks for a menu, so we + provide a better one. + +* **Default:** this takes care of anything else that the player asks + Benny for, displaying his curt response. + +And before you know it, Benny's object is out of the way; however, don't +celebrate too soon. There’s still some Benny-related behaviour that, +curiously enough, doesn’t happen in Benny's object; we're talking about +Benny's reaction if the player tries to leave without paying or +returning the key. We promised you that Benny would stop the player, and +indeed he will. But where? + +We must revisit the café room object: + +.. code-block:: inform6 + + Room cafe "Inside Benny's cafe" + with description + "Benny's offers the FINEST selection of pastries and sandwiches. + Customers clog the counter, where Benny himself manages to + serve, cook and charge without missing a step. At the north side + of the cafe you can see a red door connecting with the toilet.", + before [; + Go: ! The player is about to depart. Is he making for the street? + if (noun ~= s_obj) return false; + if (benny.coffee_not_paid == true || + benny.key_not_returned == true) { + print "Just as you are stepping into the street, the big hand + of Benny falls on your shoulder."; + if (benny.coffee_not_paid == true && + benny.key_not_returned == true) + "^^~Hey! You've got my key and haven't paid for the + coffee. Do I look like a chump?~ You apologise as only a + HERO knows how to do and return inside."; + if (benny.coffee_not_paid == true) + "^^~Just waidda minute here, Mister,~ he says. + ~Sneaking out without paying, are you?~ You quickly + mumble an excuse and go back into the cafe. Benny + returns to his chores with a mistrusting eye."; + if (benny.key_not_returned == true) + "^^~Just where you think you're going with the toilet + key?~ he says. ~You a thief?~ As Benny forces you back + into the cafe, you quickly assure him that it was only + a STUPEFYING mistake."; + } + if (costume has worn) { + deadflag = 5; ! you win! + "You step onto the sidewalk, where the passing pedestrians + recognise the rainbow EXTRAVAGANZA of Captain FATE's costume + and cry your name in awe as you JUMP with sensational + momentum into the BLUE morning skies!"; + } + ], + first_time_out false, ! Captain Fate's first appearance? + after [; + Go: ! The player has just arrived. Did he come from the toilet? + if (noun ~= s_obj) return false; + if (costume has worn && self.first_time_out == false) { + self.first_time_out = true; + StartDaemon(customers); + } + ], + s_to street, + n_to toilet_door; + +Once again, we find that the solution to a design problem is not +necessarily unique. Remember what we saw when dealing with the player's +description: we could have assigned a new value to the +``player.description`` variable, but opted to use the +``LibraryMessages`` object instead. This is a similar case. The code +causing Benny to intercept the forgetful player could have been added, +perhaps, to a ``daemon`` property in Benny’s definition. However, since +the action to be intercepted is always the same one and happens to be a +movement action when the player tries to leave the café room, it is also +possible to code it by trapping the ``Go`` action of the room object. +Both would have been right, but this is somewhat simpler. + +We have added a ``before`` property to the room object (albeit a longish +one), just dealing with the ``Go`` action. As we mentioned in an earlier +chapter, this technique lets you trap the player who is about to exit a +room before the movement actually takes place, a good moment to +interfere if we want to prevent escape. The first line: + +.. code-block:: inform6 + + if (noun ~= s_obj) return false; + +is telling the interpreter that we want to tamper only with southwards +movement, allowing the interpreter to apply normal rules for the other +available directions. + +From here on, it's only conditions and more conditions. The player may +attempt to leave: + +* without paying for the coffee and without returning the key, + +* having paid for the coffee, but without returning the key, + +* having returned the key, but not paid for the coffee, or + +* free of sin and accountable for nothing in the eyes of all men (well, + in the eye of Benny, at least). + +The first three are covered by the test: + +.. code-block:: inform6 + + if (benny.coffee_not_paid == true || benny.key_not_returned == true) ... + +that is, if either the coffee is not paid for *or* if the key is not +returned. When this condition is ``false``, it means that both +misdemeanours have been avoided and that the player is free to go. +However, when this condition is ``true``, the hand of Benny falls on the +player's shoulder and then the game displays a different message +according to which fault or faults the player has committed. + +If the player is free to go, and is wearing the crime-fighting costume, +the game is won. We tell you how that's reported in the next chapter, +where we finish off the design.