Roger Firth's IF pages

Home

Inform 6: Frequently Asked Questions

Email
Back up

Into
the Intro

Setting
the scene

Preparing
to program

Learning
the lingo

Dabbling
in data

Operating
on objects

Verbal
versatility

Bothered
by bugs

History and
hereafter

Worldly
woes

Inside
information

Tips and
techniques

These advanced topics present a few useful tricks:

What's the associativity of ~/~~?
How do I write an XOR function?
Can I use expressions in <...>?
Is there a Library routine to print direction names?
Can I introduce a short time delay?
    Z-Machine and Glulx solutions
Why can't I address "Dr.Jekyll" or "Mr.Hyde"?
How do I right-align printed numbers?
Can an 'achieved task' have a negative score?
Are the points awarded by a 'scored' object adjustable?
ChooseObjects() is messing up TAKE ALL. What can I do?
Can ChooseObjects() tell if it's evaluating noun or second?
How can I change the size of a string or table array?
Can I prompt the player to key in some information?
How do I put single characters into the dictionary?
Why doesn't 'my' work in a name property?
How can the player input numbers bigger than 10000?

(For issues which are even more esoteric than these, visit the Inform Patch List.)

What's the associativity of ~/~~?

There's a problem in the way that Inform handles the two NOT operators in an expression, in that they don't work quite as you might anticipate:

More information in the DM:
§1.6 §1.8

If you write these:

...they're taken as:

...rather than as:

~X&Y  or  ~X|Y
~(X&Y)  or  ~(X|Y)
(~X)&Y  or  (~X)|Y
~~X&&Y  or  ~~X||Y
~~(X&&Y)  or  ~~(X||Y)
(~~X)&&Y  or  (~~X)||Y

You need to include the parentheses (as shown in the rightmost column) to be sure of getting the 'correct' result.

How do I write an XOR function?

Inform doesn't come with built-in XOR (Exclusive OR) operators, but you can achieve the effect with these routines:

  [ XOR a b; return (a | b) & (~(a & b)); ];        ! Bitwise Exclusive OR

  [ XXOR a b; return (a || b) && (~~(a && b)); ];   ! Logical Exclusive OR

Can I use expressions in <...>?

The <...> statement (to trigger an action) and the <<...>> statement (to trigger an action and return true) are usually invoked with a literal 'action' argument followed by zero, one or two variable names, as in:

More information in the DM:
§6

  <<Inv>>;
  <<Examine self>>;
  <<Insert noun second>>;

These work fine; what isn't so successful is trying to use a variable for the first argument, or expressions for the second and third arguments. The trick is simple: just enclose things in parentheses (...). For example:

  <<(action) noun (parent(self))>>;

Is there a Library routine to print direction names?

More information in the DM:
§37

Yes: LanguageDirection(), which take a direction property (like e_to) as its argument:

  LanguageDirection(property);

If you're starting from a direction object (like e_obj), just change the call to this:

  LanguageDirection(object.door_dir);

Can I introduce a short time delay?

More information in the DM:
§42

You may occasionally want your game to pause for a short period, for example between characters appearing tickertape-style (rather than as the normal instantaneous output stream). The Z-machine is not well-equipped to handle time delays, but on some interpreters you can achieve the effect using the @read_char assembler statement; here's one approach:

  [ Sleep x; if (x) @read_char 1 x Sleep ->x; ];

The routine's argument x is the length of the required delay in tenths of a second; for example, Sleep(20) will pause for about two seconds. A side-effect of using @read_char is that the player can press any key to immediately terminate the delay -- useful for those with experience and/or a low boredom threshold. This example prints the characters in a string array, with an optional delay after each:

More information in the DM:
§2.4

  Array welcome_msg string "Welcome to the game!";

  [ PrintStringArray the_array the_delay i;
          for (i=1 : i<=the_array->0 : i++) {
              print (char) the_array->i;
              Sleep(the_delay);
              };
          ];

  [ Initialise;
          location = farmyard;
          new_line; PrintStringArray(welcome_msg, 5); "^";
          ];

If you're using Glulx you have access to a more powerful and flexible system of timed events, though it's slightly more trouble to set up. In addition to a Sleep() routine, you need also to define an event handler, something like this:

  [ Sleep x;      ! For compatibility, delay in 10ths of a second
          if (glk_gestalt(gestalt_Timer) && x > 0) {
              glk_request_timer_events(x*100);
              KeyCharPrimitive();
              glk_request_timer_events(0);
              }
          ];

  [ HandleGlkEvent ev context abortres;
          switch (ev-->0) {
              evtype_Timer:
                  if (context == 1) glk_cancel_char_event(gg_mainwin);
                  else              glk_cancel_line_event(gg_mainwin, GLK_NULL);
                  abortres-->0 = 0;
                  return 2;
              }
          ];

Why can't I address "Dr.Jekyll" or "Mr.Hyde"?

The Inform parser treats "." and "THEN" as separators between commands, so these three examples all do the same thing:

  > X DAGGER
  > TAKE IT

  >X DAGGER THEN TAKE IT

  >X DAGGER.TAKE IT

This handling of "." becomes a problem if you wish to use it as part of a dictionary word, for example in "DR." or "MR." to indicate an abbreviation. The easiest way round the difficulty is to supply a BeforeParsing() entry point, which is called after the parser has read in some text and set up the buffer (raw text) and parse (parsed text) arrays, but before doing anything else. Here's a suggested routine:

More information in the DM:
§2.5 §30

  [ BeforeParsing x;
      for (wn=2 : wn<parse->1 : wn++)
          if (buffer->(parse->(4*wn + 1)) == '.' &&
            parse-->(2*wn - 3) == 'dr' or 'mr' or 'mrs' or 'prof' or 'rev' or 'st' &&
            parse-->(2*wn + 1) == 'jekyll' or 'hyde') {
              buffer->(parse->(4*wn + 1)) = ' ';
              x++;
          }
      if (x) @tokenise buffer parse;
  ];

In this example:

The routine scans the parse array. If it finds three consecutive entries X.Y, where X is "DR" or "MR" and Y is "JEKYLL" or "HYDE" then it overwrites the "." in the raw text buffer with a space, and calls @tokenise to re-create the parse array from the modified buffer. The NPC's name property handles the outcome in the usual way:

  Object  "Dr Jekyll"
    with  name 'dr' 'jekyll' 'doctor',
          ...
    has   animate proper;

How do I right-align printed numbers?

When printing, Inform treats numbers as signed decimal values, and outputs the digits left-aligned (for example "123"). If you want to insert leading spaces or zeroes to force right-alignment (giving "  123" or "00123"), for example when printing a column of figures, you could use this routine:

  [ AlignNum
      num     ! number to be aligned (unsigned 0-65535)
      pad     ! right-align by inserting this char (default is left-align)
      wid     ! field width for right-alignment (default is 5)
      d e;    ! local variables
      d = 5;
      if (wid == 0) wid = 5;
      if (num >= 0)
          switch (num) {
               0 to    9: d = 1;
              10 to   99: d = 2;
             100 to  999: d = 3;
            1000 to 9999: d = 4;
          }
      else {
          num = (num & $7FFF)*2 - (num + 30000);
          for (e=3 : UnsignedCompare(num,9999)==1 : num=num-10000) e++;
          switch (num) {
               0 to    9: e = e * 1000;
              10 to   99: e = e * 100;
             100 to  999: e = e * 10;
          }
      }
      if (pad) for (d = wid-d : d>0 : d--) print (char) pad;
      if (e) print e;
      print num;
  ];

The first parameter is the number to be printed. Note that this is treated as an unsigned value, in the range 0-65535. The second parameter is a single character. It's optional, typically ' ' or '0', and is inserted as initial padding to cause the number to be right-aligned. If this parameter is omitted, the number is left-aligned. The third parameter is a number. It's optional, and specifies the width of the right-aligned number. If this parameter is omitted, the default width is five characters. If the value that you specify is too small, the number overflows; it is not truncated to fit the specified width.

To display a four-digit safe combination, where any of the digits could be zero, you might produce a line like "The safe combination is '0107'." by these three statements:

  print "The safe combination is '";
  AlignNum(safe.combination,'0',4);
  print "'.^";

You can't use AlignNum() directly as a print rule, because it usually requires three parameters, and a print rule handles only one. Instead, you could create a separate print rule as a routine which calls AlignNum(), and use it within a single print statement like this:

  [ AlignZ4 n; AlignNum(n,'0',4); ];

  print "The safe combination is '", (AlignZ4) safe.combination, "'.^";

Can an 'achieved task' have a negative score?

More information in the DM:
§22

Inform offers two built-in scoring systems: a simpler one of awarding points for collecting objects and visiting rooms, and a more advanced scheme which keeps track of which 'tasks' the player has accomplished. In this second system, the value of each scored task must lie in the range 0-255. Occasionally, you'd like to be able to award a score higher than that, or to penalize the player by deducting points -- that is, awarding a negative score for a prohibited task. For this to work, the task_scores array, which is where you define the score for each defined task 0...N-1, must be configured as a word array rather than the current byte array. That is, instead of creating the array like this:

  Constant TASKS_PROVIDED;
  Constant NUMBER_TASKS 5;
  Constant MAX_SCORE 25;

  Array task_scores -> 3 7 3 5 7;

you create an array of words:

  Array task_scores --> 3 7 3 5 7;

However, there's more to it than that; you also need to fix those parts of the Library which are currently expecting a byte array. You do this within your game file by Replacing two Library routines:

  Replace Achieved;
  Replace FullScoreSub;
  ...
  Include "VerbLib";
  ...
  [ Achieved num;
    if (task_done->num==0)
    {   task_done->num=1;
        score = score + task_scores-->num;     ! CHANGED
    }
  ];

  [ FullScoreSub i;
    ScoreSub();
    if (score==0 || TASKS_PROVIDED==1) rfalse;
    new_line;
    L__M(##FullScore,1);

    for (i=0:i<NUMBER_TASKS:i++)
        if (task_done->i==1)
        {   PANum(task_scores-->i);            ! CHANGED
            PrintTaskName(i);
        }

    if (things_score~=0)
    {   PANum(things_score); L__M(##FullScore,2); }
    if (places_score~=0)
    {   PANum(places_score); L__M(##FullScore,3); }
    new_line; PANum(score); L__M(##FullScore,4);
  ];

Those two routines are copied from verblibm.h, and in each case a single line needs to be changed (so that task_scores-> becomes task_scores-->).

Fortunately, version 6/11 of the Library makes this much simpler. Those accesses to the byte array are isolated in a little routine, which you simply Replace:

  Replace TaskScore;

  [ TaskScore i; return task_scores-->i; ];

Once you've done that, by either method, you can happily award large and negative scores (the latter enclosed in parentheses):

  Array task_scores --> 300 700 300 (-100) 700;

Are the points awarded by a 'scored' object adjustable?

More information in the DM:
§22

In the built-in scoring system of awarding points for collecting objects and visiting rooms, the score is determined by the values of the constants OBJECT_SCORE (default of 4) for the first time you pick up an object with a scored attribute, and ROOM_SCORE (default of 5) for entering a scored room.

If you wish for greater flexibility, it's very straightforward to enhance the two Library routines responsible for this system, by creating a scored_value individual property:

  Replace NoteObjectAcquisitions;
  Replace ScoreArrival;
  ...
  Include "Parser";
  Include "VerbLib";
  ...
  [ NoteObjectAcquisitions i s;
      objectloop (i in player)
          if (i hasnt moved) {
              give i moved;
              if (i has scored) {
                  if (i provides scored_value) s = i.scored_value();
                  else                         s = OBJECT_SCORE;
                  score = score + s;
                  things_score = things_score + s;
              }
          }
  ];

  [ ScoreArrival s;
      if (location hasnt visited) {
          give location visited;
          if (location has scored) {
              if (location provides scored_value) s = location.scored_value();
              else                                s = ROOM_SCORE;
              score = score + s;
              places_score = places_score + s;
          }
      }
  ];

Those two routines are copied from parserm.h and verblibm.h respectively, and in each case the value of a new local variable 's' is set either to the object or room's scored_value property (if it has such a property) or to the appropriate constant (which is what currently happens). scored_value can be a numeric value -- the number of points to award -- or a routine which returns such a number. Here are a couple of examples:

  Object  puzzle_room "Puzzle chamber"
    with  description "A bare room.",
          scored_value 10,
    has   scored light;

  Object  -> box "box"
    with  name 'box',
          with_key key,
    has   scored container openable ~open lockable locked;

  Object  -> key "key"
    with  name 'key',
          scored_value [;
              if (box has moved) return 2;
              print "^[You'd have scored more by taking the box first.]^";
              return 1;
          ],
    has   scored;

Note that the 'box' object has a scored attribute but no scored_value property, and so will award the default number of points on first being TAKEn (a scored_value property with no associated scored attribute is ignored). And the result is...

  Puzzle chamber
  A bare room.

  You can see a box (which is closed) and a key here.

  [Your score has just gone up by ten points.]

  >GET KEY
  Taken.

  [You'd have scored more by taking the box first.]

  [Your score has just gone up by one point.]

  >GET BOX
  Taken.

  [Your score has just gone up by four points.]

ChooseObjects() is messing up TAKE ALL. What can I do?

Inform provides a ChooseObjects() entry point so that you can influence how the parser chooses between ambiguous object names, typically because you've some additional knowledge about the objects and their situation to which the parser isn't privy. In general, it's better to keep any ChooseObjects() routine short and specific, instead controlling object selection using an object's parse_name property wherever possible.

More information in the DM:
§33

ChooseObjects() is called in two situations: once (with the code argument equal to 2) to influence which objects are preferred matches to a noun phrase by returning a rank from 0 to 9; and then at a later stage, if the player has typed ALL, it is called with code 1 or 0 to choose whether to accept or reject individual objects. A generic routine would look like this:

  [ ChooseObject obj code;
      switch (code) {
       2: ! Parser wants an 'appropriateness' rating forobj
          ! Inspect obj, and then...
          return 0;   ! Sorry -- can't offer any guidance, or
          return 1;   ! Very slight preference forobj, or
          ...
          return 9;   ! Very strong preference forobj.
       1, ! Parser proposes to include obj in ALL.
       0: ! Parser proposes to exclude obj from ALL.
          ! Inspect obj, and then...
          return 0;   ! Agree with Parser, or
          return 1;   ! Force inclusion, or
          return 2:   ! Force exclusion.
      }
  ];

If both of these methods are employed at the same time, this can lead to ChooseObjects(obj,1) being called only for objects to which ChooseObjects(obj,2) has just given a high ranking. If the individual object(s) proposed in that way are then rejected (by returning 2), then ALL doesn't refer to anything and a "There are none at all available!" message results.

The way to separate these two processes is to insert this line in your ChooseObjects():

  [ ChooseObject obj code;
      switch (code) {
       2: ! Parser wants an 'appropriateness' hint forobj
          if (indef_wanted == 100) return 0;
          ! Inspect obj, and then...
          ...
  ];

This checks an undocumented global variable to see if the parser is handling an ALL and will shortly be proposing inclusions/exclusions. The resulting refusal to give an 'appropriateness' rating means that TAKE ALL will no longer be affected.

Can ChooseObjects() tell if it's evaluating noun or second?

As explained in the previous topic, the ChooseObjects() entry point can be used to influence which objects are preferred matches to a noun phrase. Here, we'll show how the first object chosen (the variable noun) can affect the second object chosen (the variable second). We'll illustrate this usage by inventing a couple of new verbs:

  [ LoosenSub;  "You can't loosen that!"; ];
  [ TightenSub; "You can't tighten that!"; ];

  Verb 'loosen'  * noun 'with' held  -> Loosen;
  Verb 'tighten' * noun 'with' held  -> Tighten;

The idea is that the first object -- we'll be using a 10mm brass bolt -- can be loosened or tightened by use of the second object, an implement of some form. Here are three possible tools (though only two of them are capable of doing the job):

  Object  spanner10 "10mm spanner"
    with  name '10mm' 'spanner' 'tool';

  Object  spanner15 "15mm spanner"
    with  name '15mm' 'spanner' 'tool';

  Object  wrench "adjustable wrench"
    with  name 'adjustable' 'wrench' 'tool';

And here is the skeleton of our brass bolt, together with a useful print rule routine:

  Object  "10mm brass bolt"
    with  name '10mm' 'brass' 'bolt',
          description [; print_ret (The) self, " is currently ", (showState) self, "."; ],
          tightness ##Tighten,
          with_tools spanner10 wrench,
          ...
    has   static;

  [ showState o; if (o.tightness == ##Tighten) print "tight"; else print "loose"; ];

You'll notice that we've invented two individual properties. tightness indicates the current state of the bolt, defined as one of the action values ##Tighten (the bolt is currently tight) or ##Loosen (it's currently loose). More interesting is with_tools, which contains a list of the tools which can be used to adjust the bolt: the 10mm spanner and the adjustable wrench are both acceptable, but the 15mm spanner isn't listed, and so won't do the job.

Here's the full definition, with the addition of the before property which does the actual work of changing the bolt's state:

  Object  "10mm brass bolt"
    with  name '10mm' 'brass' 'bolt',
          description [; print_ret (The) self, " is currently ", (showState) self, "."; ],
          tightness ##Tighten,
          with_tools spanner10 wrench,
          before [ i n;
            Loosen, Tighten:
              if (self.tightness == action)
                  print_ret (The) self, " is already ", (showState) self, ".";
              n = self.#with_tools / WORDSIZE; ! number of possible tools
              for (i=0 : i<n : i++)
                  if (self.&with_tools-->i == second or -second) {
                      self.&with_tools-->i = -second;
                      self.tightness = action;
                      print_ret (The) self, " is now ", (showState) self, ".";
                  }
              print_ret (The) second, " doesn't seem to fit ", (the) self, ".";
          ],
    has   static;

This isn't as complex as it looks. The property is preventing the bolt from being tightened unless it's currently loose and vice versa, and then checking that the second object is one of the acceptable tools listed in the with_tools property (if it's not, the "doesn't seem to fit" message appears). If an acceptable tool is employed, the bolt's state is changed (by self.tightness = action;). The only slightly odd thing is the statement self.&with_tools-->i = -second;, which updates the list of acceptable tools to mark the one that the player has actually used. Why have we done that? You'll see in a minute or two.

All of the code that we've written so far will work just as it is. However, since the point of this topic is to illustrate the benefits that ChooseObjects() can offer, we'd better bring such a routine into play. This is what we want it to do, when evaluating potential tool objects for second: (1) prefer objects in the noun.with_tools list to ones which aren't listed, (2) prefer objects at the start of the list to those at the end, and (3) prefer objects which have already been used to those which haven't. Here's the code to do all that:

  [ ChooseObjects obj code
      i n o;
      if (code ~= 2) rfalse; ! Agree with Parser's ALL decisions
      ! Give an 'appropriateness' score to obj
      if (parameters > 0 && action_to_be == ##Loosen or ##Tighten) {
          ! Is obj a suitable tool?
          o = inputobjs-->2; ! object to be Loosened/Tightened
          if (o ofclass Object && o provides with_tools) {
              n = o.#with_tools / WORDSIZE; ! number of possible tools
              for (i=0 : i<n : i++) ! tool used already, in order of list
                  if (o.&with_tools-->i == -obj) return max(9-i, 7); ! 9, 8, 7, 7...
              for (i=0 : i<n : i++) ! tool not yet used, in order of list
                  if (o.&with_tools-->i == obj)  return max(6-i, 1); ! 6, 5, 4, 3, 2, 1, 1...
          }
      }
      return 0; ! not Loosen/Tighten, or not a listed tool
  ];

  [ max a b; if (a < b) return b; return a; ];

When evaluating candidates for second in the context of Loosen/Tighten, this routine returns a ranking of 6 for the 10mm spanner (first in the list), and 5 for the wrench (next in the list); if more tools were listed, they'd get 4, then 3, then 2, then any further tools would all rank as 1. The 15mm spanner isn't in the with_tools list, so gets the default rank of 0. However, once the 10mm spanner has been used its ranking increases to 9, while the wrench would rank as 8 after succcessful use. The effect of all this is that the player can now type simply LOOSEN BOLT with a strong probability that the parser can infer which tool is intended, without having to ask "Which did you mean...?".

We've highlighted two undocumented variables in the definition of ChooseObjects(), and they're the key to making this work. parameters holds 0 while the routine is evaluating candidates for noun, and 1 while evaluating second; in the latter case the array element inputobjs-->2 holds the object previously chosen as noun. However, be warned: this all works only for simple grammars such as our Loosen and Tighten verbs. It can't be used in grammars which employ the multiexcept or multiinside tokens, or the reverse keyword, because in these cases noun is evaluated after second, and so isn't available to influence the decision-making process.

How can I change the size of a string or table array?

More information in the DM:
§2.4

When you declare a string (or table) array, Inform stores the array's element count in the first byte (or word). For example, this declaration:

  Array ourPets table "cat" "dog" "rabbit" "guineapig";

creates an array with five elements. ourPets-->1 hold the address of the string "cat"; ourPets-->2 refers to "dog", ourPets-->3 to "rabbit" and ourPets-->4 to "guineapig". In addition, the compiler places the count of the number of entries -- 4 in this example -- in ourPets-->0.

The problem comes should you try to modify that 'count' value at runtime. For example, suppose the kids grow up and change their taste in pets. You might write this code:

  ourPets-->3 = "pony";
  ourPets-->2 = "conger eel";
  ourPets-->1 = "tarantula";
  ourPets-->0 = 3;

Unfortunately, it isn't that easy. On the Z-machine in Strict mode, you'll get a runtime error from the ourPets-->0 = 3; statement; Glulx gives a compiler error for the same thing. Inform is being a little over-protective here; there's no reason why you shouldn't be able to change the count value, providing the new value is something sensible, but alas you can't.

Well, you can; you've just got to cheat a little. The easiest way round the problem is to conceal the fact that you're updating a string or table array's count by writing a couple of tiny routines:

  [ StoreStringSize arr val; arr->0 = val; ];

  [ StoreTableSize arr val; arr-->0 = val; ];

then you can replace ourPets-->0 = 3; with StoreTableSize(ourPets,3); to perform the assignment without upsetting the compiler.

The alternative technique is to drop into assembly language -- slightly more efficient, slightly more trouble. The code you need is one of these:

  @storeb  arr 0 val; ! arr->0  = val (Z-machine)
  @storew  arr 0 val; ! arr-->0 = val (Z-machine)
  @astoreb arr 0 val; ! arr->0  = val (Glulx)
  @astore  arr 0 val; ! arr-->0 = val (Glulx)

Can I prompt the player to key in some information?

More information in the DM:
§2.5 §42

As the DM4 says, Inform's support for reading from the keyboard is fairly limited. If you wish to bypass the parser and handle keyboard input directly, you've only two choices, and both are imperfect: you can use the read statement (or the @aread assembly language statement) to input a line of text, or the @read_char assembly language statement (or the KeyCharPrimitive() routine, which also works with Glulx) to accept a single character:

If you're asking the player to key in her name, location or other relatively lengthy string, then on balance it's probably more important to offer the editing convenience of Backspace than it is to maintain the case of what she types. So, here's a routine, and its Glulx equivalent, to read a line of characters from the keyboard. Note that the characters are placed in a buffer array -- one whose first word contains the number of characters that have been typed, and whose subsequent bytes contain those characters. The routine returns the number of typed characters:

  #Ifdef TARGET_ZCODE;

  [ KeyLine buf max;
      buf->0 = max;
      buf->1 = 0;
      read buf 0;
      buf->0 = 0;
      return buf-->0;
  ];

  #Ifnot; ! TARGET_GLULX

  [ KeyLine buf max;
      glk($00D0, gg_mainwin, buf+WORDSIZE, max, 0); ! request_line_event
      while (true) {
          glk($00C0, gg_event); ! select
          if (gg_event-->0 == 3 && gg_event-->1 == gg_mainwin) break;
      }
      buf-->0 = gg_event-->2;
      return buf-->0;
  ];

  #Endif; ! TARGET_

Here's how you use the routine to ask the player to type in her name:

  Constant playerName_SIZE 20;
  Array    playerName buffer playerName_SIZE;

  do
      print "What's your name? ";
  until (KeyLine(playerName, playerName_SIZE));

The do loop repeats the prompt until the player types something. And here's a routine to display the name:

  [ PrintString buf
      i;
      for (i=0 : i<buf-->0 : i++)
          print (char) buf->(i+WORDSIZE);
  ];

You can use this either as a printing routine, or as a print rule:

  print "Hello, ";
  PrintString(playerName);
  ".";

  "Hello, ", (PrintString) playerName, ".";

It's time to think about case conversion; the current effect (on the Z-machine) isn't quite right:

  What's your name? Mary Jane
  Hello, mary jane.

This routine capitalises the first letter of each word in the string -- suitable if the string is a name (Paul O'Brian, Jean-Paul Sartre) or a location (Weston-Super-Mare, Los Angeles):

  [ CapitaliseString buf
      i c flg;
      for (i=0,flg=true : i<buf-->0 : i++) {
          c = buf->(i+WORDSIZE);
          if (c >= 'a' && c <= 'z') {
              if (flg) buf->(i+WORDSIZE) = UpperCase(c);
              flg = false;
          }
          else
              flg = true;
      }
  ];

Here's how to test whether two buffer strings are identical:

  [ CompareStrings bufA bufB
      i;
      if (bufA-->0 ~= bufB-->0) rfalse;
      for (i=0 : i<bufA-->0 : i++)
          if (Lowercase(bufA->(i+WORDSIZE)) ~= LowerCase(bufB->(i+WORDSIZE))) rfalse;
      rtrue;
  ];

And finally, all of the routines working together:

  Constant playerName_SIZE 20;
  Array    playerName buffer playerName_SIZE;
  Array    extraName1 buffer "Roger";
  Array    extraName2 buffer "Sonja";

  do
      print "What's your name? ";
  until (KeyLine(playerName, playerName_SIZE));
  CapitaliseString(playerName);
  if (CompareStrings(playerName, extraName1) || CompareStrings(playerName, extraName2))
      "Welcome, oh honoured one.";
  else
      "Hello, ", (PrintString) playerName, ".";
  

Note, in passing, that our "What's your name" prompt ends with a "? "; you could also use ">> ", but it's best to prevent confusion with the parser's prompt by not using just "> ".

How do I put single characters into the dictionary?

The most common way of adding to the dictionary is via an object's name property. A typical name property looks like this:

  name 'xray' 'x-ray' 'camera' 'machine',

but if you want to add just one character, you have to be a little devious. For example, to add a dash on its own you can't simply include '-' in the list, because single characters in single quotes are always treated as ZSCII character constants (even in name properties, where that makes no logical sense). So instead, use any of these:

  name '-//' ...
  name '@{2D}//'...
  name "-" ...

in which 2D is the Unicode hexadecimal value for a dash. The first of those three forms is generally preferred, except where the character you're trying to add is a forward slash. In that case, use either of the other two forms:

  name '@{2F}//' ...
  name "/" ...

Why doesn't 'my' work in a name property?

The parser treats MY (also HIS, HER, ITS and THEIR) as special descriptors, in roughly the same way as THE, A and SOME are handled automatically. This works nicely most of the time; consider a simple piece of code:

  Object  -> "red hat"
    with  name 'red' 'hat',
    has   clothing;

  Object  -> "blue hat"
    with  name 'blue' 'hat',
    has   clothing;

Here, you can see how MY HAT is taken to mean the hat in the player's possession (and by implication, HAT without MY means any hats not being held):

  You can see a red hat and a blue hat here.

  >GET HAT
  Which do you mean, the red hat or the blue hat?

  >RED
  Taken.

  >EXAMINE HAT
  (the blue hat)
  You see nothing special about the blue hat.

  >EXAMINE MY HAT
  You see nothing special about the red hat.

Sometimes, though, you'd like MY to reflect ownership rather than current possession. Perhaps the blue hat is a family heirloom or a badge of office: you want to refer to it as MY HAT whether or not you're holding it. There's slightly more to this than just adding 'my' to the blue hat's name property; you also need to allow for the parser's automatic handling (which consumes the MY before considering the words listed in name). A tiny parse_name property does the trick:

  Object  -> "blue hat"
    with  name 'my' 'blue' 'hat',
          parse_name [;
              if (parser_action == ##TheSame) return -2;
              if (indef_type & MY_BIT) wn--;
              return -1;
          ],
    has   clothing;

And now you get this:

  >GET RED HAT
  Taken.

  >EXAMINE HAT
  (the blue hat)
  You see nothing special about the blue hat.

  >EXAMINE MY HAT
  You see nothing special about the blue hat.

  >EXAMINE RED HAT
  You see nothing special about the red hat.

Note that this still leaves MY referring to objects in your possession if there's no match against a name property. If you think that'll be confusing, you can disable it by adding to your Initialise():

  LanguageDescriptors-->1 = 'this';

How can the player input numbers bigger than 10000?

More information in the DM:
§31

One of the standard grammar tokens is number, which matches any decimal value in the range 0..10000. Sometimes you may need to use values larger than this, or less than zero, so here's a general parsing routine AnyNumber(), based on Exercise 92 in the DM4, which works with the full range of decimal numbers -- and their binary and hexadecimal equivalents -- for both Z-machine and Glulx.

  #Ifdef TARGET_ZCODE;                ! decimal range is -32768 to 32767
  Constant MAX_DECIMAL_SIZE 5;
  Constant MAX_DECIMAL_BASE 3276;
  #Ifnot; ! TARGET_GLULX              ! decimal range is -2147483648 to 2147483647
  Constant MAX_DECIMAL_SIZE 10;
  Constant MAX_DECIMAL_BASE 214748364;
  #Endif; ! TARGET_

  [ AnyNumber wa we
      sign base digit digit_count num;

      if (wa == 0) { wa = WordAddress(wn); we = WordLength(wn); }
      we = wa + we;
      sign = 1; base = 10;
      if     (wa->0 == '-') { sign = -1; wa++; }
      else {
          if (wa->0 == '$') { base = 16; wa++; }
          if (wa->0 == '$') { base = 2;  wa++; }
      }
      if (wa >= we) return GPR_FAIL;  ! no digits after -/$
      while (wa->0 == '0') wa++;      ! skip leading zeros
      for (num=0,digit_count=1 : wa<we : wa++,digit_count++) {
          switch (wa->0) {
            '0' to '9': digit = wa->0 - '0';
            'A' to 'F': digit = wa->0 - 'A' + 10;
            'a' to 'f': digit = wa->0 - 'a' + 10;
            default:    return GPR_FAIL;
          }
          if (digit >= base) return GPR_FAIL;
          switch (base) {
            16:     if (digit_count > 2*WORDSIZE)  return GPR_FAIL;
            2:      if (digit_count > 8*WORDSIZE)  return GPR_FAIL;
            10:
              if (digit_count >  MAX_DECIMAL_SIZE) return GPR_FAIL;
              if (digit_count == MAX_DECIMAL_SIZE) {
                  if (num >  MAX_DECIMAL_BASE)     return GPR_FAIL;
                  if (num == MAX_DECIMAL_BASE) {
                      if (sign == 1  && digit > 7) return GPR_FAIL;
                      if (sign == -1 && digit > 8) return GPR_FAIL;
                  }
              }
          }
          num = base*num + digit;
      }
      parsed_number = num * sign;
      wn++;
      return GPR_NUMBER;
  ];

In accordance with the rules governing general parsing routines, AnyNumber() returns either the constant GPR_NUMBER if it matches a valid number (whose value is in the library variable parsed_number), or the constant GPR_FAIL if there is no match. You might use anynumber in a replacement for the standard SET grammar (note that special is an undocumented token, described in 1996 by the DM3 as "obsolete and best avoided", which matches either a decimal value or a dictionary word)

  Verb 'set' 'adjust'
      * noun                                      -> Set
      * noun 'to' special                         -> SetTo;

with a version accepting the full range of numbers:

  Extend 'set' replace
      * noun                                      -> Set
      * noun 'to' anynumber                       -> SetTo;

You could implement addition and subtraction in a similar manner:

  [ IncreaseSub; "You can't increase that!"; ];

  [ DecreaseSub; "You can't decrease that!"; ];

  Verb 'increase' 'increment' 'inc'
      * noun 'by' anynumber                       -> Increase;

  Verb 'add'
      * anynumber 'to' noun                       -> Increase reverse;

  Verb 'decrease' 'decrement' 'dec'
      * noun 'by' anynumber                       -> Decrease;

  Verb 'subtract'
      * anynumber 'from' noun                     -> Decrease reverse;

The job of turning a string of decimal digits into a single numeric value is handled by the library routine TryNumber(), which is where the 0..10000 limit originates. One way of bypassing this limitation is by defining a ParseNumber() entry point routine, but this turns out to be slightly less useful than you'd think: if ParseNumber() rejects a string like "99999" (because 32767 is the Z-machine's limit) then TryNumber() accepts and processes it, returning the truncated value 10000.

An alternative approach is simply to Replace TryNumber() with your own code. A definition which leverages the AnyNumber() routine shown earlier is:

  [ TryNumber wordnum
      x y z;

      ! accept "one" to "twenty"
      y = wn; wn = wordnum; z = NextWord(); wn = y;
      z = NumberWord(z); if (z) return z;

      ! if provided, use ParseNumber() entry point
      x = WordAddress(wordnum); y = WordLength(wordnum);
      z = ParseNumber(x, y); if (z) return z;

      ! accept any integer value
      if (AnyNumber(x, y) == GPR_FAIL) return -1000;
      wn--;
      return parsed_number;
  ];

The advantage of this approach is that the (very nearly) full range of parsed numeric values is now available everywhere using the existing number token, without you needing to replace verb grammars or make other code changes. The disadvantage is that "-1000" can't be input, since that's the value which TryNumber() returns to indicate a non-numeric value, and we have to retain this for compatibility with existing library code (the use of anynumber as a grammar token avoids this problem).

Into
the Intro

Setting
the scene

Preparing
to program

Learning
the lingo

Dabbling
in data

Operating
on objects

Verbal
versatility

Bothered
by bugs

History and
hereafter

Worldly
woes

Inside
information

Tips and
techniques