§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.