Added chapter 12.
authorDavid Griffith <dave@661.org>
Sat, 16 Apr 2016 03:51:13 +0000 (20:51 -0700)
committerDavid Griffith <dave@661.org>
Sat, 16 Apr 2016 03:51:13 +0000 (20:51 -0700)
chapters/12.rst [new file with mode: 0644]

diff --git a/chapters/12.rst b/chapters/12.rst
new file mode 100644 (file)
index 0000000..595c0b1
--- /dev/null
@@ -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.