DM4 §2: The state of play

## §2 The state of play

### §2.1 Directives construct things

Every example program so far has consisted only of a sequence of routines, each within beginning and end markers `[` and `]`. Such routines have no way of communicating with each other, and therefore of sharing information with each other, except by calling each other back and forth. This arrangement is not really suited to a large program whose task may be to simulate something complicated, such as the world of an adventure game: instead, some central registry of information is needed, to which all routines can have access. In the author's game ‘Curses’, centrally-held information ranges from the current score, held in a single variable called `score`, to Madame Sosostris's tarot pack, which uses an array of variables representing the cards on the pack, to a slide-projector held as an “object”: a bundle of variables and routines encoding the relevant rules of the game, such as that the whitewashed wall is only lit up when the slide projector is switched on.

Every Inform source program is a list of constructions, made using commands called “directives”. These are quite different from the statements inside routines, because directives create something at compilation time, whereas statements are only instructions for the interpreter to follow later, when the story file is being played.

In all there are 38 Inform directives, but most of them are seldom used, or else are just conveniences to help you organise your source code: for instance `Include` means “now include another whole file of source code here”, and there are directives for “if I've set some constant at the start of the code, then don't compile this next bit” and so on. The 10 directives that matter are the ones creating data structures, and here they are:

    [         Array     Attribute    Class       Constant
    Extend    Global    Object       Property    Verb

The directive written `[`, meaning “construct a routine containing the following statements, up to the next `]`”, was the subject of §1. The four directives to do with objects, `Attribute`, `Class`, `Object` and `Property`, will be the subject of §3. The two directives to do with laying out grammar, `Verb` and `Extend`, are intimately tied up with the needs of adventure games using the Inform library, and are useless for any other purpose, so these are left until §30. That leaves just `Array`, `Constant` and `Global`.

### §2.2 Constants

The simplest construction you can make is of a `Constant`. The following program, an unsatisfying game of chance, shows a typical usage:

    Constant MAXIMUM_SCORE = 100;

    [ Main;
      print "You have scored ", random(MAXIMUM_SCORE),
          " points out of ", MAXIMUM_SCORE, ".^";
    ];

The maximum score value is used twice in the routine `Main`. The resulting story file is exactly the same as it would have been if the constant definition were not present, and `MAXIMUM_SCORE` were replaced by 100 in both places where it occurs. But the advantage of using `Constant` is that it makes it possible to change this value from 100 to, say, 50 with only a single change to the source code, and it makes the source code more legible.

People often write the names of constants in full capitals, but this is not compulsory. Another convention is that the `=` sign, which is optional, is often left out if the value is a piece of text rather than a number. If no value is specified for a constant, as in the line

    Constant BETA_TEST_VERSION;

then the constant is created with value 0.

A constant can be used from anywhere in the source code after the line on which it is declared. Its value cannot be altered.

### §2.3 Global variables

The variables in §1 were all “local variables”, each owned privately by its own routine, inaccessible to the rest of the program and destroyed as soon as the routine stops. A “global variable” is permanent and its value can be used or altered from every routine.

The directive for declaring a global variable is `Global`. For example:

    Global score = 36;

This creates a variable called `score`, which at the start of the program has the value 36. (If no initial value is given, it starts with the value 0.)

A global variable can be altered or used from anywhere in the source code after the line on which it is declared.

### §2.4 Arrays

An “array” is an indexed collection of variables, holding a set of numbers organised into a sequence. To see why this useful, suppose that a pack of cards is to be simulated. You could define 52 different variables with `Global`, with names like `Ace_of_Hearts`, to hold the position of each card in the pack: but then it would be very tiresome to write a routine to shuffle them around.

Instead, you can declare an array:

    Array pack_of_cards --> 52;

which creates a stock of 52 variables, called the “entries” of the array, and referred to in the source code as

    pack_of_cards-->0   pack_of_cards-->1   ...   pack_of_cards-->51

and the point of this is that you can read or alter the variable for card number `i` by calling it `pack_of_cards-->i`. Here is an example program, in full, for shuffling the pack:

    Constant SHUFFLES = 100;
    Array pack_of_cards --> 52;
    [ ExchangeTwo x y z;
      !   Randomly choose two different numbers between 0 and 51:
      while (x==y) {
          x = random(52) - 1; y = random(52) - 1;
      }
      z = pack_of_cards-->x; pack_of_cards-->x = pack_of_cards-->y;
      pack_of_cards-->y = z;
    ];
    [ Card n;
      switch(n%13) {
          0: print "Ace";
          1 to 9: print n%13 + 1;
          10: print "Jack";
          11: print "Queen";
          12: print "King";
      }
      print " of ";
      switch(n/13) {
          0: print "Hearts"; 1: print "Clubs";
          2: print "Diamonds"; 3: print "Spades";
      }
    ];
    [ Main i;
      !   Create the pack in quot;factory order":
      for (i=0:i<52:i++) pack_of_cards-->i = i;
      !   Exchange random pairs of cards for a while:
      for (i=1:i<=SHUFFLES:i++) ExchangeTwo();
      print "The pack has been shuffled into the following order:^";
      for (i=0:i<52:i++)
          print (Card) pack_of_cards-->i, "^";
    ];

The cards are represented by numbers in the range 0 (the Ace of Hearts) to 51 (the King of Spades). The pack itself has 52 positions, from position 0 (top) to position 51 (bottom). The entry `pack_of_cards-->i` holds the number of the card in position `i`. A new pack as produced by the factory would come with Ace of Hearts on top (card 0 in position 0), running down to the King of Spades on the bottom (card 51 in position 51).

▲ A hundred exchanges is only just enough. Redefining `SHUFFLES` as 10,000 takes a lot longer, while redefining it as 10 makes for a highly suspect result. Here is a more efficient method of shuffling (contributed by Dylan Thurston), perfectly random in just 51 exchanges.

    pack_of_cards-->0 = 0;
    for (i=1:i<52:i++) {
        j = random(i+1) - 1;
        pack_of_cards-->i = pack_of_cards-->j; pack_of_cards-->j = i;
    }

· · · · ·

In the above example, the array entries are all created containing 0. Instead, you can give a list of constant values. For example,

    Array small_primes --> 2 3 5 7 11 13;

is an array with six entries, `small_primes-->0` to `small_primes-->5`, initially holding 2, 3, 5, 7, 11 and 13.

The third way to create an array gives some text as an initial value, occasionally useful because one popular use for arrays is as “strings of characters” or “text buffers”. For instance:

    Array players_name --> "Frank Booth";

is equivalent to the directive:

    Array players_name --> 'F' 'r' 'a' 'n' 'k' ' ' 'B' 'o' 'o' 't' 'h';

Literal text like "Frank Booth" is a constant, not an array, and you can no more alter its lettering than you could alter the digits of the number 124. The array `players_name` is quite different: its entries can be altered. But this means it cannot be treated as if it were a string constant, and in particular can't be printed out with `print (string)`. See below for the right way to do this.

• WARNING
In the pack of cards example, the entries are indexed 0 to 51. It's therefore impossible for an interpreter to obey the following statement:

    pack_of_cards-->52 = 0;

because there is no entry 52. Instead, the following message will be printed when it plays:

[** Programming error: tried to write to -->52 in the array “pack_of_cards”, which has entries 0 up to 51 **]

Such a mistake is sometimes called breaking the bounds of the array.

· · · · ·

The kind of array constructed above is sometimes called a “word array”. This is the most useful kind and many game designers never use the other three varieties at all.

▲ The first alternative is a “byte array”, which is identical except that its entries can only hold numbers in the range 0 to 255, and that it uses the notation `->` instead of `-->`. This is only really useful to economise on memory usage in special circumstances, usually when the entries are known to be characters, because ZSCII character codes are all between 0 and 255. The “Frank Booth” array above could safely have been a byte array.

▲ In addition to this, Inform provides arrays which have a little extra structure: they are created with the 0th entry holding the number of entries. A word array with this property is called a `table`; a byte array with this property is a `string`. For example, the table

    Array continents table 5;

has six entries: `continents-->0`, which holds the number 5, and further entries `continents-->1` to `continents-->5`. If the program changed `continents-->0` this would not magically change the number of array entries, or indeed the number of continents.

▲▲ One main reason you might want some arrangement like this is to write a general routine which can be applied to any array. Here is an example using `string` arrays:

    Array password string "danger";
    Array phone_number string "0171-930-9000";
    ...
    print "Please give the password ", (PrintStringArray) password,
        " whenever telephoning Universal Exports at ",
        (PrintStringArray) phone_number, ".";
    ...
    [ PrintStringArray the_array i;
      for (i=1: i<=the_array->0: i++) print (char) the_array->i;
    ];

Such routines should be written with care, as the normal checking of array bounds isn't performed when arrays are accessed in this indirect sort of fashion, so any mistake you make may cause trouble elsewhere and be difficult to diagnose.

▲▲ With all data structures (i.e., with objects, strings, routines and arrays) Inform calls by reference, not by value. So, for instance:

    [ DamageStringArray the_array i;
      for (i=1: i<=the_array->0: i++) {
          if (the_array->i == 'a' or 'e' or 'i' or 'o' or 'u')
              the_array->i = random('a', 'e', 'i', 'o', 'u');
          print (char) the_array->i;
      }
    ];

means that the call `DamageStringArray(password_string)` will not just print (say) “dungor” but also alter the one and only copy of `password_string` in the story file.

### §2.5 Reading into arrays from the keyboard

Surprisingly, perhaps, given that Inform is a language for text adventure games, support for reading from the keyboard is fairly limited. A significant difference of approach between Inform and many other systems for interactive fiction is that mechanisms for parsing textual commands don't come built into the language itself. Instead, game designers use a standard Inform parser program which occupies four and a half thousand lines of Inform code.

Reading single key-presses, perhaps with time-limits, or for that matter reading the mouse position and state (in a Version 6 game) requires the use of Inform assembly language: see §42.

A statement called `read` does however exist for reading in a single line of text and storing it into a byte array:

    read text_array 0;

You must already have set `text_array->0` to the maximum number of characters you will allow to be read. (If this is N, then the array must be defined with at least N + 3 entries, the last of which guards against overruns.) The number of characters actually read, not counting the carriage return, will be placed into `text_array->1` and the characters themselves into entries from `text_array->2` onwards. For example, if the player typed “GET IN”:

`->0` | `1` | `2` | `3` | `4` | `5` | `6` | `7`
max | characters | text typed by player, reduced to lower case
60 | 6 | `'g'` | `'e'` | `'t'` | `' '` | `'i'` | `'n'`

The following echo chamber demonstrates how to read from this array:

    Array text_array -> 63;
    [ Main c x;
      for (::) {
          print "^> ";
          text_array->0 = 60;
          read text_array 0;
          for (x=0:x<text_array->1:x++) {
              c = text_array->(2+x);
              print (char) c; if (c == 'o') print "h";
          }
      }
    ];

· · · · ·

▲ `read` can go further than simply reading in the text: it can work out where the words start and end, and if they are words registered in the story file's built-in vocabulary, known as the “dictionary”. To produce all this information, `read` needs to be supplied with a second array:

    read text_array parse_array;

`read` not only stores the text (just as above) but breaks down the line into a sequence of words, in which commas and full stops count as separate words in their own right. (An example is given in Chapter IV, §30.) In advance of this `parse_array->0` must havebeen set to W, the maximum number of words you want to parse. Any further text will be ignored. `parse_array` should have at least 4W + 2 entries, because `parse_array->1` is set to the actual number of words parsed, and then a four-entry block is written into the array for each word parsed. Numbering the words as 1, 2, 3, …, the number of letters in word n is written into `parse_array->(n*4)`, and the position of the start of the word in `text_array`. The dictionary value of the word, or zero if it isn't recognised, is stored as `parse_array-->(n*2-1)`. The corresponding parsing array to the previous text array, for the command “GET IN”, looks like so:

`->0` | `1` | `2` | `3` | `4` | `5` | `6` | `7` | `8` | `9`
max | words | first word | second word
10 | 2 | `'get'` | 2 | 3 | `'in'` | 5 | 2

In this example both words were recognised. The word “get” began at position `->2` in the text array, and was 3 characters long; the word “in” began at `->5` and was 2 characters long. The following program reads in text and prints back an analysis:

    Array text_array -> 63;
    Array parse_array -> 42;
    [ Main w x length position dict;
      w = 'mary'; w = 'had'; w = 'a//'; w = 'little'; w = 'lamb';
      for (::) {
          print "^> ";
          text_array->0 = 60; parse_array->0 = 10;
          read text_array parse_array;
          for (w=1:w<=parse_array->1:w++) {
              print "Word ", w, ": ";
              length = parse_array->(4*w);
              position = parse_array->(4*w + 1);
              dict = parse_array-->(w*2-1);
              for (x=0:x<length:x++)
                  print (char) text_array->(position+x);
              print " (length ", length, ")";
              if (dict) print " equals '", (address) dict, "'^";
              else print " is not in the dictionary^";
          }
      }
    ];

Note that the pointless-looking first line of `Main` adds five words to the dictionary. The result is:

`>MARY, hello`
Word 1: mary (length 4) equals 'mary'
Word 2: , (length 1) is not in the dictionary
Word 3: hello (length 5) is not in the dictionary

▲ What goes into the dictionary? The answer is: any of the words given in the name of an object (see §3), any of the verbs and prepositions given in grammar by `Verb` and `Extend` directives (see §26), and anything given as a dictionary-word constant. The last is convenient because it means that code like

    if (parse_array -->(n*2-1)) == 'purple';

does what it looks as if it should. When compiling this line, Inform automatically adds the word “purple” to the story file's dictionary, so that any `read` statement will recognise it.

• REFERENCES
Evin Robertson's function library `"array.h"` provides some simple array-handling utilities. •L. Ross Raszewski's function library `"istring.h"` offers Inform versions of the ANSI C string-handling routines, including `strcmp()`, `strcpy()` and `strcat()`. The further extension `"znsi.h"` allows the printing out of string arrays with special escape sequences like `[B` interpreted as “bold face.” (See also the same author's `"ictype.h"`.) •Adam Cadre's function library `"flags.h"` manages an array of boolean values (that is, values which can only be `true` or `false`) so as to use only one-sixteenth as much memory as a conventional array, though at some cost to speed of access.