A Few Inform Programming Tips for Platypus Users

The Platypus library is a replacement for the standard library used with the Inform programming language for developing interactive fiction. It's not in very common use; as far as I know, at this time there are no full length games that have been programmed with it. Nevertheless, it contains a lot of improvements over the standard library, and I'm glad that I chose it for my current work in progress. Because the Platypus library is very heavily based on the standard library, some of these tips may be helpful to non-Platypus users too. Under each tip's heading, I've made a note about which libraries it applies to.

Platypus release 4 does have a few annoying bugs. In particular, there are a few problems with floating objects, and there is some code that causes the inform 6.21 compiler to produce slightly illegal zcode, which the new 6.30 compiler, in strict mode, catches and turns into run-time errors. I've written a patch to release 4 that fixes the bugs I've found. (By the way, I do strongly recommend moving to the 6.30 compiler if you're using an older version.)

Adding a "GO BACK" verb

[Platypus only]

The pathfinding functionality in Platypus makes a "GO BACK" verb fairly trivial to implement. I've written a small library extension that does this. Follow the instructions in that file once you've downloaded it.

The adjective property

[Platypus only]

Platypus provides the adjective property, which allows an author to give a list of words that will match a noun but less strongly than a word in a name property.

Suppose you have a location with the following objects:

Object KeyLime "key lime"
  with name 'lime',
  adjective 'key';

Object HouseKey "house key"
  with name 'key',
  adjective 'house';

Object House "white house"
  with name 'house',
  adjective 'white';
With these object definitions, the player can refer to "key", "key lime", "house", "house key", and "white", and the game will pick the object that you would expect for each.

Platypus lets you define WEAK_ADJECTIVES as a constant; if you do, the game will require at least one word in a name property to be present for an object to be matched. I suggest that you not do this; you'll only end up annoying players who, for example, type >X WHITE knowing that the game should be able to tell that they mean the white house.

Platypus does not care about the order of adjectives and nouns. So a if a game contains a "pot plant", and a "plant pot", and you set up the name and adjective properties in the obvious way, the correct object will be chosen if the player refers to "pot" or "plant", but "pot plant" and "plant pot" cannot be distinguished, and will cause a disambiguation message to be printed. For something like this you'll still want to use a parse_name property.

An important thing to remember is that words in the adjective property need not be literally adjectives. For example, if your game contains both a motorcycle and a copy of Zen and the Art of Motorcycle Maintenance, making 'motorcycle' an adjective in the latter will ensure that the word "motorcycle" alone will refer to the motorcycle. If you have implemented both the player's nose and a giant nose statue, you could make 'nose' an adjective for the former, since the player is less likely to be refering to it.

Another reason the adjective property is useful is simply that now there are two properties for name words instead of one. You can put generic names that apply to all of the objects in a class in the class definition, and put words that distinguish particular instances of that class in the adjective property of each instance. And if during the game you want to change how an object is referred to, (for instance, a "green light" becomes a "red light",) you can change just the adjective property while leaving the name property alone.

Platypus also helpfully provides a words property, which is intermediate in both power and complexity between using name and adjective, and using parse_name. Individual words that the parser is trying to map to an object are passed into the words function, which should return the property, (name or adjective,) that the word would fit into if you were using those properties.

Object Amulet
  with
    damage 0,
    words [wd;
      switch (wd)
      {
        'gleaming':  if (self.damage == 0) return adjective;
        'dented':    if (self.damage == 1) return adjective;
        'broken':    if (self.damage == 2) return adjective;
        'amulet':    return name;
      }
    ];
    short_name [;
      switch (self.damage)
      {
        0: print "gleaming";
        1: print "dented";
        2: print "broken";
      }
      print " amulet"; rtrue;
    ],
    description [;
      "It's just some ", name (self), " your aunt gave to you.";
    ],

Note that objects with a words property should not have name or adjective properties.

The disambiguate property

[Platypus, but easily adaptable to Standard]

Suppose you are coding a knife object, and you want the game to infer that the player wants to use the knife for cutting when he or she types >CUT STEAK. In the standard inform lib, this is handled by the ChooseObjects entry point routine. In Platypus a more convenient per object ChooseObjects is avalable: the disambiguate property. It works exactly like ChooseObjects, so you can refer to the DM4 to see how it works. For our purposes it will suffice to note that it is called with an argument that is a code for the circumstance in which it is called, which is equal to 2 when it is trying to pick an object to disambiguate to. We'll also need to look at the action_to_be global, which contains the action corresponding to the current grammar line. (Since we haven't yet determined the grammar line to use at this point, the action global isn't set yet.)

For verbs with more than one object, it's important to know which object you're looking at in the disambiguation process. (We want the knife to be the inferred object for what you cut with, but not what you cut.) Here's a useful utility function for telling whether we are looking at the direct object:

[ ObjectIsDirect;
  if (action_reversed)
    return parameters;
  else
    return ~~parameters;
];
The parameters global tells us which object we're on, and action_reversed tells us whether the grammar line is reversed, with the indirect object first. Now here's our knife example:
Object SteakKnife "steak knife"
  with name 'knife',
    adjective 'steak',
    description "It's your trusty serrated steak knife.",
    disambiguate [code;
      if ((action_to_be == ##Cut) && (code == 2))
      {
	if (ObjectIsDirect())
	  rfalse;
	else
	  return 4;
      }
    ];
The value returned by disambiguate should be 0 if the object should not be considered a match, and between 1 and 9 if it should. (Higher numbers indicate better matches.

Fun with Zcharacter

[Platypus or Standard]

Here's an interesting snippet of code that does almost nothing, but can save a few hundred bytes in a finished game:

Zcharacter
  "abcdefghijklmnop.rstuvwxy,"
  "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  "0123456789[];?!'#zq-:()";
(You'll need to make this the very first thing in your source file.)

What does this do? Remember that the characters in the first line of the Zcharacter directive take up one five bit unit in ZSCII, so three of them can fit into two bytes. Characters in the next two lines use two units, and all other characters use four. A big part of the savings comes from picking the right punctuation to put on that last line. It also occured to me that the period and comma occur more frequently in English text than the least common lowercase letters. It may be possible to do a little better than this; you can experiment if you like.

Note that things can break if you put lower case letters in the second row. I don't know why this is, but in any case, it's easily avoided. The letters 'q' and 'z' now take two 5 bit units. Remember that in distinguishing if input matches a dictionary word, an inform game only looks at the first nine units, so if your game includes both a 'quetzal' and a 'quetzalcoatl', it will no longer be able to distinguish between them.

Unless you absolutely need a few hundred bytes to fit your game into a .z8, this is really quite useless, but I thought it was fun, and exploring this taught me a bit about how Zcharacter works, so it was worth it for me.


Last modified: Fri Mar 26 19:32:38 PST 2004