==================== Captain Fate: take 3 ==================== .. epigraph:: | |CENTER| *W was a watchman, and guarded the door;* | |CENTER| *X was expensive, and so became poor.* .. only:: html .. image:: /images/picW.png :align: left |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 :doc:`02`, 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``). .. code-block:: inform Replace MakeMatch; Replace Identical; Replace NounDomain; Replace TryGivenObject; #. Include the ``pname.h`` header just after you include ``Parser.h``. .. code-block:: inform 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:: inform 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: .. code-block:: inform 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:: inform 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:: inform 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:: inform 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: .. code-block:: inform 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:: inform 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:: inform 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:: inform 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 :ref:`homely-atmos` 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:: inform 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:: inform 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:: inform 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:: inform 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.