12. Captain Fate: take 3

W was a watchman, and guarded the door;
X was expensive, and so became poor.
../_images/picW.png

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, 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:

  1. Add four lines near the head of the program (before you include Parser.h).

    Replace MakeMatch;
    Replace Identical;
    Replace NounDomain;
    Replace TryGivenObject;
    
  2. Include the pname.h header just after you include Parser.h.

    Include "Parser";
    Include "pname";
    
  3. 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.

--- T Y P E ---

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:

--- T Y P E ---

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:

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. The text file 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:

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:

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:

--- T Y P E ---

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:

--- T Y P E ---

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):

--- T Y P E ---

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:

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 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:

--- T Y P E ---

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:

--- T Y P E ---

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:

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:

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.