Inform 7 for Programmers/Part 4
Arrays Have Been Tabled
Inform is not a language of computation. It is a language designed to hold secrets for the player to discover. So its two-dimensional arrays -- tables -- are primarily intended for a single, occasional lookup. Tables are statically-allocated like everything else, and the words "row" and "column" are used a lot in lieu of (x,y) coordinates.
Here's an example table from the docs:
|Table 2.1 - Selected Elements|
|Element (some text)||Symbol (some text)||Atomic number (a number)||Atomic weight (a number)|
|Table of Selected Elements|
|Element||Symbol||Atomic number||Atomic weight|
A blank line and the keyword "Table" begin the whole affair. At least one tab character is required to end a column and start the next. Multiple tabs and spaces are OK; the first tab character Inform comes across melds together all whitespace surrounding it. Type information can either be left off -- Inform will deduce it mostly correctly -- or can be given in the two styles shown. Both styles have problems. The first, "inline" style only allows "object" for objects, not a particular class (like "person"). The in-row style allows specific class names, but introduces a bug -- row #1 will be a blank row that doesn't correctly flag itself as blank. This is because we can:
|Table of Energy Proponents|
|with 4 blank rows.|
..only put the type in-row when it would be blank otherwise (and defeat the inference). A blank entry is denoted by the -- double-dash. The last line ("with X blank rows") statically allocates some extra space. A character's responses to the player's inquiries on various topics has special syntax, a column entitled "topic" whose type will also be Topic. Remember that topics are (simplistic) regexes, so they can't be printed.
|Table of NPC Responses|
|"hi/hello"||"He says, 'Well hello there!'"|
|"bye/goodbye"||"'Take care,' he answers."|
There's several phrases to work with tables, but it's still one of the more cumbersome parts of the language. This is because we must "choose" a particular row and then work with each "(column name) entry" as if they were each a standalone variable. We can't pass a chosen row to another function. We can't choose two rows simultaneously, either in the same table or in different tables. We can't copy rows to other rows. We can't look up a row based on multiple criteria except by a laborious explicit loop with O(n) running time and hard-coded queries. Finally, we can't create N-dimensional arrays except by creating a column of type "table-name" and the creation of a lot of statically-allocated sub-tables by hand!
Rather than explaining every detail of the procedures and functions related to tables, I'll list most of the examples from the docs (Chapter 15), and capitalize the names of the parameters. Their operation should be relatively obvious. The following are all imperative.
- if Wind is a Fuel listed in The Table Of Energy Proponents ...
- blank out the whole row;
- choose row My Favorite Number in The Table Of Energy Proponents;
- if there is no Proponent entry ...
- choose row with Danger of 10 in The Table Of Energy Proponents;
- if there is a Proponent corresponding to a Fuel of Geothermal in The Table Of Energy Proponents ...
- choose a blank row in Table 2.1;
- change Element entry to "Fluorine";
- change Atomic Number entry to 9;
- if there is an Atomic Number in row 2 of The Table Of Standard Elements ...
- sort The Table Of Energy Proponents in reverse Danger order;
- sort The Table Of Energy Proponents in random order;
These are value-returning functions:
- ... the number of blank rows in Table 2.1 ...
- ... the number of filled rows in Table 2.1 ...
- ... the Proponent corresponding to a Fuel of Hydrogen in The Table Of Energy Proponents ...
- ... Symbol in row 3 of The Table Of Selected Elements ..
And these are examples of putting a table lookup in a rule header; the table-specific part begins with a column name, followed by "listed in", and then a table name:
- after taking An Item listed in The Table Of Treasures: [...].
- after asking The Guru about A Topic listed in The Table Of NPC Responses: [...].
One concession to ease of use is that its sort routine is stable: if multiple rows have the same value in the column we sort on, then their order vis-a-vis each other will not change.
Named Values Everywhere
Inform uses named values everywhere; it is usually properties that remain anonymous. We can explicitly create a new named value:
- A limb is a kind of value. The limbs are left leg, left arm, right leg, right arm, the neck, and the back.
- Tattoos are a kind of value. The tattoos are mom, barbed wire, wings, a kanji, a pseudo-photograph, and some obscure symbol.
Named values can have spaces in their names, but they cannot have their names abbreviated like objects can. Inform generally allows almost any word to be part of a named value, but be warned, if you put words like "when" or "is" in a named value, it may create compilation problems when used in other, particular places.
Named values are a type, similar to Time or Number but finite in length, that can be used in many places.
- change X to the limb after the left leg;
- change X to the limb before the back;
- Definition: a limb is hurt if [...].
- Tattooing is an action applying to one limb and one thing.
- Understand "tattoo [limb] of [someone]" as tattooing.
- Exposition relates various people to various tattoos. The verb to be tattooed with implies the exposition relation.
- Bob is tattooed with some obscure symbol.
- [...]; repeat with X running through each limb begin;
In the above, the action "tattooing", the relation "is/are tattooed with", and the named value "tattoos" are all distinct from one another.
Named values can also be given properties, as an object can.
Named values can be defined by a table. Since tables can be continued and amended by client code at compile-time, this is a handy way to leave our named values "open".
- Tattoos are a kind of value. Some tattoos are defined by the Table of Designs.
- Table of Designs
- barbed wire
- Table of Designs (continued)
- Jean-Pierre 4-ever
Inform automatically creates some named values that correspond to other parts of the language. For example, "the containment relation" is an example of the "abstract-relation" named value, and corresponds to a built-in relation. If we need to pass a relation to the pathfinding function, it is the named value we are passing. Our own relations would be treated similarly: "the marriage relation". Likewise, "action-name" is the umbrella for "the looking action", "the accusing it of action", etc.
- Causality relates an action-name (called the cause) to an action-name (called the effect) when [...].
- SecondOrderRelation relates an abstract-relation (called the scaffold) to an action-name (called the side effect) when [...].
Though it's probably obvious by now, text is put between double quotes; within, text in square brackets is a To Say phrase (a procedure call). Where other languages use the word print, Inform uses the word say. Punctuation note: the ' apostrophe expands to a double quote, except for standard verb contractions like don't or can't. Otherwise, substitutions are frequently required for certain punctuation. So to print:
- Rene Descartes said, "I think not!" and promptly disappeared.
- So don't ever say that, 'k?
- say "Rene Descartes said, 'I think not!' and promptly disappeared.[line break]So don't ever say that, [']k?";
Variables and objects can be put in the square brackets to print their current value and their name, respectively. (The outputted name of an object can be overridden by a rule in the Printing The Name Of Something activity; more on activities later.) Line breaks are automatically appended if the last character is a period, question mark, or exclamation mark.
Inform was originally designed to make games with a very small memory footprint, so a game's output text is compressed, and language support for regexes is poor. The Text datatype is this compressed text. Indexed Text -- a later and still optional addition to the language -- isn't compressed, so regex operations will work on it. Typecasting between the two is mostly automatic and is one of the very few (if not only) cases of implicit casting. Also, both text types used the double quotes identically, so constructions like the following are needed to avoid creating a plain Text type by accident:
- let T be indexed text;
- let T be "Hello World";
Typically, access to input text isn't direct. Modification is mediated through the Snippet type, the Topic type, and a couple of phrases. A snippet is a pair of numbers, joined together like fixed-point notation, that represent a range of words. "The 3 words starting at word #2" would be the snippet 203. There are only three built-in snippet variables, "the player's command", "the matched text", and the confusingly-named "the topic understood".
- if the player's command does not include "please/thanks", say "How rude!";
- if the player's command includes "please/thanks", cut the matched text;
- if the player's command includes "hi", replace the matched text with "hello";
- if the player's command matches "hello world", say "You're a programmer, aren't you?";
- if the player's command does not match "hello world", say "To code... perchance, to write?";
Topics are simple textual patterns, a poor man's regex, that attempt to match input text. They are also written simply with double-quotes, and their only operator is the / slash as high-precedence altercation. In the above example, "please/thanks" is a topic (the type was inferred from the "include/includes" function). Topics are frequently used in a game's conversation system, where implementing as objects everything that's interesting to ask about would eat memory. Topics are also why the object hierarchy does not have an Idea or Concept class, since variables can track any information gained that isn't implicit in the owning of a physical object. We'll see more of Topics, and the peculiar snippet "the topic understood", when we examine tables.
Indexed text is a late addition to Inform, and is cumbersome to deal with because the language tries to remain statically-allocated. As a result, our string-manipulation type has a lot of restrictions on where and how it can appear. But it is finally possible to gain direct access to the player's input by typecasting the snippet to indexed text:
- let N be indexed text;
- let N be the player's command;
- change the text of the player's command to N;
Know that the last line is a very special one-off syntax designed to blend in with the rest of the language -- even the word "the" is required. Snippets do not have properties, other snippets cannot be used in that line, and there's only one parameter there -- N, in this case -- not two. We won't cover the regex engine here -- see Chapter 19 "Advanced Text" -- except to say it executes very slowly.
Precisely One Spoon-unit Of Sugar
Units are user-defined numeric types with some nifty syntactic sugar, sugar that extends even to our player. Generally in programming, numbers are numbers: the programmer must remember one variable measures in pixels while another measures in picos, and to mix the two makes bad medicine. But Inform allows us to define unique ways of writing the numbers -- by wrapping identifiers and punctuation marks (the "preamble") around them. The individual numbers ("parts") in the construction can be given names for later referencing.
- Money is a kind of value. $19.99 specifies some money with parts dollars (without leading zeros) and cents (optional, preamble optional).
- My wallet is some money that varies. My wallet is usually $20.75.
- A thing has some money called the price. The price of a thing is usually $5.
- The price of Bob is $2.05.
- say "[the dollars part of the price of Bob]";
We can only add and subtract units, assuming the units match. To allow multiplication and division, we can specify what new unit is created. Note the re-use of the specify/specifies verb.
- A length is a kind of value. 10 m specifies a length.
- An area is a kind of value. 10 sq m specifies an area.
- A length times a length specifies an area.
We can always multiply and divide a unit by a plain number. Multiplying and dividing by one is how we explicitly typecast between units and numbers.
Inform automatically creates a new Understand token from units. The player writes the unit the same way(s) as the programmer.
- Understand "donate [money]" as donating. Donating is an action applying to some money.
Finally, we can re-use the "implies" statement from defining relations for an extra dose of sugar.
- The verb to cost (it costs, they cost, it is costing) implies the price property.
- The jeans cost $19.95.
Note that we didn't define a relation there, though it may look like it. But we could:
- Fanciness relates a thing (called X) to some money (called Y) when the price of X > Y. The verb to be fancier than implies the fanciness relation.
- let L be the list of things fancier than $2.50;
- let B be the list of things fancier than the price of jeans;
The built-in type Time is not a unit, but pretends to be. Time's parts -- hours and minutes -- are calculated from a 4 AM minute-count, not stored as separate numbers as units are done.
Actions are "on stage" because our player sees the character(s) performing them. But sometimes backstage jobs need to get done. Inform is definitely not concurrent, so "backstage" isn't meant in the way that "background UNIX utility" is meant. It's higher-level than that. Anything that utilizes three rulebooks, and all the machinery that that entails, isn't going to be overriding addition nor running every tenth of a second. Rather, activities are used for tasks such as AI, prose generation, parsing, complex decision-making, etc., and frequently it's useful to have hooks before and after such tasks, in case client code wants to shuffle things around just before the task, and restore them just after. Also, rulebooks & phrases can ask if they're being used in the context of an activity.
- Rule for printing the name of the sack while the sack is not carried: say "your abandoned sack".
- [...]; if the printing the name activity is going on, [...]
Though we occasionally need to add a rule to a pre-existing activity to get it to print what we want, it's rare that they need be extensively modified or directly invoked. Generally, adding a rule to an activity is "hacking lite".
The built-in activities are listed in Chapter 17; just scan the headings from section 17.9 on down. Grouped roughly by task and order, they are:
- At or near the beginning of a new game:
- Starting the virtual machine
- Constructing the status line
- Printing the banner text
- At the end of the game:
- Printing the player's obituary
- Amusing a victorious player
- Reading a command ("After reading a command" is a popular hook for pre-processors because it happens before the rest of these)
- Deciding whether all includes (the player's keyword ALL causes problems; (please don't Take the Sun))
- Deciding the scope of something (for when the visibility and accessability rules miss a case; this is called a lot by the system)
- Deciding the concealed possessions of something (similar to scope, but not so heavy-handed; this is also called a lot)
- Clarifying the parser's choice of something (when the parser takes a guess at what the player meant, it tells you)
- Asking which do you mean (when the parser doesn't want to take a guess, it asks you; "Which X did you mean?")
- Supplying a missing noun (when the author takes a guess at what you meant)
- Supplying a missing second noun (ditto)
- Printing a parser error (all of them are listed on its doc page)
- The all-important Looking Action, in chronological order and indented according to caller/callee:
- Printing room description details of something (or are we in Darkness? If so, skip down)
- Printing the locale description of something (of the room, then of the cage we're in, etc.)
- Choosing notable locale objects for something (what's in here? what's important and needs describing first?)
- Printing a locale paragraph about (which doesn't actually print anything, just demotes or promotes things)
- Writing a paragraph about (actually prints)
- Listing nondescript items of something (items lacking descriptive prose are just listed at the end, via the Listing Contents Of activity)
- Darkness, when Looking doesn't work
- Printing the announcement of darkness (when you flick the light switch, this happens)
- Printing the name of a dark room ("the dark" is implemented as a room by itself, not as a condition in an existing room)
- Printing the description of a dark room
- Printing a refusal to act in the dark (almost all standard actions "require light" -- even when they're conversation!)
- Printing the announcement of light
- Implicitly taking something (usually, we allow the system to save the player the step of Taking something before using it)
- (oddly, the built-in pathfinding is not an activity)
Output Prose Generation:
- of a particular thing:
- Printing the name of something
- Printing the plural name of something
- Printing a number of something
- of a list of contents:
- Listing contents of something (pretty-prints a list of objects "held" by the sole object passed to it; used by the Inventory action, the Looking action (for the nondescript items), and saying "[contents of...]")
- Grouping together something (utilized by the above to offer some control on the order)
|Inform 7 for Programmers by Ron Newcomb|
|Contents - Part 1 - Part 2 - Part 3 - Part 4 - Part 4b - Part 5|