The Inform Designer's Manual Designers 4
designers_manual_4
User Manual:
Open the PDF directly: View PDF
.
Page Count: 571
| Download | |
| Open PDF In Browser | View PDF |
The Inform Designer’s Manual
Graham Nelson
Fourth Edition 2001
The Inform Designer’s Manual, Fourth Edition
Author: Graham Nelson
Editor: Gareth Rees
Proofreaders: Toby Nelson, Andrew Plotkin, Torbjörn Andersson
Cover: Wing of a Roller (watercolour and gouache on vellum, 1512),
Albrecht Dürer (1471–1528)
Notice of Copyright
Inform, the program and its source code, its example games and documentation,
c Graham Nelson 1993,
including the text and typography of this book, are copyright
94, 95, 96, 97, 98, 99, 2000, 01.
The Inform software may be freely distributed provided that: (a) distributed copies
are not substantially different from those archived by the author, (b) this and other
copyright messages are always retained in full, and (c) no profit is involved. The
same conditions apply to distribution or conversion of any electronic form of this book
which may be made available by the author. Exceptions to these conditions must be
negotiated directly with the author.
A story file produced with the Inform system belongs to whoever wrote it and may be
sold for profit if so desired, without the need for royalty payment, provided that it prints
a game banner conforming to the standard library’s banner at an early stage in play: in
particular, this banner must contain the information that the story file was compiled by
Inform, and the version numbers of compiler and library used.
The author assumes no liability for errors and omissions in this book, or for damages
or loss of revenue resulting from the use of the information contained herein, or the use
of any of the software described herein. Inform software is supplied ‘‘as is’’ and carries
no warranty, actual or implied.
First edition September 1994
Second expanded edition October 1995
Third edition September 1996
Web edition with minor revisions May 1997
Fourth expanded edition May 2001: Release 4/1
ii
Contents
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Chapter I: The Inform Language
§1
Routines
1.1.
1.2.
1.3.
1.4.
1.5.
1.6.
1.7.
1.8.
1.9.
1.10.
1.11.
1.12.
1.13.
1.14.
1.15.
§2
The state of play
2.1.
2.2.
2.3.
2.4.
2.5.
§3
Getting started . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Hello World . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Routine calls, errors and warnings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Numbers and other constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Arithmetic, assignment and bitwise operators . . . . . . . . . . . . . . . . . . . . . . . . 12
Arguments and return values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Conditions: if, true and false . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Code blocks, else and switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
while, do. . . until, for, break, continue . . . . . . . . . . . . . . . . . . . . . . . . . . 24
How text is printed . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
The print and print ret statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Other printing statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Generating random numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Deprecated ways to jump around . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Directives construct things . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Global variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Reading into arrays from the keyboard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Objects
3.1.
3.2.
3.3.
3.4.
3.5.
3.6.
3.7.
3.8.
3.9.
3.10.
3.11.
3.12.
3.13.
3.14.
Objects, classes, metaclasses and nothing . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
The object tree . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
Declaring objects 1: setting up the object tree . . . . . . . . . . . . . . . . . . . . . . . . 50
Tree statements: move, remove, objectloop . . . . . . . . . . . . . . . . . . . . . . . . . 51
Declaring objects 2: with and provides . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Declaring objects 3: private properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
Declaring objects 4: has and give . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
Declaring objects 5: class inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
Passing messages up to the superclass . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Creating and deleting objects during play. . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Sending messages to routines and strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
Common properties and Property . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
Philosophy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
iii
Chapter II: Introduction to Designing
§4
‘Ruins’ begun . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
Beginning to lay ‘Ruins’; including library files; the Initialise routine;
some properties of mushrooms; name, description and initial; edible
foodstuffs; introducing before and after rules; the stone steps.
§5
Introducing messages and classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
Recap of message-sending: a parrot; classes for treasure artifacts: the pygmy
statuette, the honeycomb; how clashes are sorted out in class inheritance,
additivity.
§6
Actions and reactions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
Actions are attempts; generating them with <, <<; the actor, action, noun
and second noun; the ## notation; actions fall into three groups; fake actions
like ThrownAt; creating new actions, the Blorple example; how actions are
processed, over ‘Before’, ‘During’ and ‘After’ stages.
§7
Infix and the debugging verbs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
Three levels; general strategies; command lists and ‘‘recording’’, ‘‘replay’’
and ‘‘random’’ debugging verbs; ‘‘showobj’’, ‘‘tree’’, ‘‘showverb’’, ‘‘scope’’;
keeping watch of ‘‘actions’’, ‘‘changes’’, ‘‘messages’’, ‘‘timers’’; tracing
the parser with ‘‘trace’’; supernatural ability to ‘‘purloin’’, ‘‘abstract’’,
‘‘goto’’, ‘‘gonear’’; the Infix ‘‘;’’, ‘‘;give’’, ‘‘;move’’, ‘‘;remove’’, ‘‘;examine’’, ‘‘;watch’’, ‘‘;inventory’’; marking routines with a * to be watched.
Chapter III: The Model World
§8
Places and scenery. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
The Square Chamber of ‘Ruins’; what ‘‘you don’t need to refer to’’; static
and scenery objects; spreading mist with found_in; α Canis Minoris; the
five senses and reaction rules; rooms have before and after too.
§9
Directions and the map . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
Giving ‘Ruins’ a small map; n_to, d_to, etc.; when you cant_go; direction
objects in the compass; not the same as direction properties.
§10 Food and drink . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
edible foodstuffs; the Drink action; difficulties with liquids.
§11 Clothing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
Wear and Disrobe; wearable items of clothing; a jade face mask.
§12 Containers, supporters and sub-objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
Containers: container, supporter, capacity, open, openable; locks and
keys: locked, lockable, with_key; LetGo and Receive to trap use of a
container: a chasm; transparency and component parts.
iv
§13 Doors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
How to create a door; door_to, door_dir; when_open, when_closed; a
stone door for ‘Ruins’; a two-way door, the ‘Advent’ grate; why door_dir is
needed and how to trap every attempt to go through.
§14 Switchable objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
switchable and on: when_on, when_off; the Gotham City searchlight; a
sodium lamp; describe taking precedence.
§15 Things to enter, travel in and push around . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
enterable objects: a slab altar; vehicles: KAR 1; special rule about the
Go action when inside something enterable; the PushDir action: a huge
pumice-stone ball; pushing up and down.
§16 Reading matter and consultation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
The Consult action, ‘‘look up’’; consult_from and consult_words: a dictionary of glyphs, Tyndale’s Bible; making ‘‘read’’ and ‘‘examine’’ different.
§17 People and animals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
animate objects and the life rule; a coiled snake and a mummified priest;
Blofeld’s reactions; talkable objects; some people are transparent.
§18 Making conversation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
Orders are actions for other people; giving people their own grammars; untypeable verbs; several voice-activated machines; fake fake actions; telephony.
§19 The light and the dark . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
Light versus darkness is automatic; modifying the darkness; going from dark
into dark and the DarkToDark entry point; rules on ‘when there is light’.
§20 Daemons and the passing of time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
Daemons and the daemon property; starting and stopping them; background
daemons; timers (fuses); time_left and time_out; each_turn events for
places and nearby objects; the time of day; changing it with SetTime; on
the status line; midnight, sunrise, sunset; the exact sequence of events at
end-of-turn.
§21 Starting, moving, changing and killing the player . . . . . . . . . . . . . . . . . . . . . . 152
What Initialise should do; the location; initial restoration; teleportation
and the PlayerTo routine; what happens when the room changes: NewRoom,
initial for a room, visited; giving the player his own before rule; using
ChangePlayer to transform him into any object; multi-character games; life
and deadflag; the DeathMessage routine; resurrection and the AfterLife.
§22 Miscellaneous constants, scoring, quotations . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
Story and Headline; MAX_CARRIED; the automatic ‘‘sack object’’; ‘amusing’
rewards for the victorious; two scoring systems: for places and items, or for
completing tasks; rankings and PrintRank; automatic score notification and
notify_mode; ‘‘objects’’ and ‘‘places’’ verbs, removable with NO_PLACES;
boxed quotations.
v
§23 ‘Ruins’ revisited. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .166
Further examples to complete ‘Ruins’; a map and a step by step solution of
the final game.
§24 The world model described. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176
¶1. Substance – ¶2. Containment – ¶3. Space – ¶4. Sense – ¶5. Time –
¶6. Action.
§25 Extending and redefining the world model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
Enriching the model; Egyptian amulets and their spells; making a new library
file; the LibraryMessages system for changing messages like ‘‘Dropped.’’;
changing the prompt; the last resort of Replace directives; using the world
model description; fire and flammability.
Chapter IV: Describing and Parsing
§26 Describing objects and rooms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
print (The) obj, ... (the) obj and so on; indefinite and definite article; proper nouns; the short_name of an object; invent strings and
routines; exactly how inventory lines are printed; a matchbook; describe
routines; exactly how rooms are described; Locale.
§27 Listing and grouping objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
The list-maker WriteListFrom; its style bitmap; examples: tall and wide
inventories; grouping similar items together in lists: foodstuffs, Scrabble
pieces and denominations of coin.
§28 How nouns are parsed . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206
How name is used; a fried green tomato turning red; the parser breaks text
into a stream of words; wn and NextWord; reading words as numbers or from
their raw text; a parse_name routine is much more flexible than name; the
ParseNoun entry point; distinguishing adjectives from nouns.
§29 Plural names for duplicated objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
Collections of indistinguishable objects; a bag of six coins; the plural
property for printing out plurals; definition of ‘indistinguishable’; writing
parse_name routines to allow plurals to be understood; class of crowns.
§30 How verbs are parsed . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
The parser’s fundamental method; BeforeParsing entry point; the actor
and verb word; synonyms for verbs; definitions of grammar, line and token;
action_to_be; Verb directive: a simplified ‘‘take’’ grammar; meta verbs;
grammar creates actions; creating an ‘‘xyzzy’’ verb; how to Extend grammar
for an existing verb: pushing numbered buttons; priority: replace, first,
last; splitting synonymous verbs apart with Extend only; the UnknownVerb
and PrintVerb entry points.
vi
§31 Tokens of grammar. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .222
Full list of grammar tokens; prepositions; noun and held; implicit taking;
tokens allowing multiple objects like ‘‘all’’; filtering out nouns by attribute:
‘‘use’’ verb; and by general routine: ‘‘free’’ verb; parsing numbers: ‘‘type’’
verb, ParseNumber; general parsing routines; reading from the parser’s
raw text buffer and parse table; exercises, including French, telephone
and floating-point numbers, times of day, adding a third parameter to a
grammar line.
§32 Scope and what you can see . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231
The definition of ‘in scope’; touchability is stricter than scope; answering
questions: ‘‘what is a grue’’; scope=... tokens with programmable scope;
scope_stage, ScopeWithin and PlaceInScope; changing the global definition of ‘in scope’ using InScope; scope_reason; looping over and testing
scope; making the rules more sensitive to darkness; a long room divided by a
glass wall; the add_to_scope property for component parts of containers.
§33 Helping the parser out of trouble . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237
Parser error messages and ParserError; ambiguity-resolution and influencing it with ChooseObjects; making ‘‘eat’’ prefer edible objects; redefining
‘‘all’’; exactly how ambiguities are resolved.
Chapter V: Natural Language
§34 Linguistics and the Inform parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
¡Bienvenido a Aventura!; Informese; commands, verb phrases, prepositions,
noun phrases, gender-number-animation (GNA), descriptors, nouns, pronouns; example of parsing; grammatical features not present in Informese.
§35 Case and parsing noun phrases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252
Flexion and cases; parsing inflected noun phrases; for example, Old English
dative word-endings.
§36 Parsing non-English languages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257
Compromises; accented characters at the keyboard?; dialects; language
definition files; Zcharacter and the customisation of ZSCII; specifying
pronouns and descriptors; translating natural languages to Informese.
§37 Names and messages in non-English languages . . . . . . . . . . . . . . . . . . . . . . . . . 269
Gender-number-animation of short names; agreement with articles and cases;
contraction forms and articles; library messages of all kinds.
vii
Chapter VI: Using the Compiler
§38 Controlling compilation from within . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278
Include; conditional compilation: If..., Ifnot, Endif; Message; linking in
the library; writing new modules to link in; serial and release numbers.
§39 Controlling compilation from without . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
Switches; memory settings; pathname variables; Inform Control Language
(ICL).
§40 All the Inform error messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290
Fatal errors; errors, including linker and assembler errors; warnings, including obsolete usage warnings.
Chapter VII: The Z-Machine
§41 Architecture and assembly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304
ZIL, Inform and the Z-machine; its Versions; assembly language; store and
branch opcodes, labels; memory map and stack.
§42 Devices and opcodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 308
The Standard for interpreters; input and output streams; the Version 5
and Version 6 screen models; colours, windows and status lines; interrupt
countdowns; pictures; sounds; keyboard reading with timed interrupts;
terminating characters; the mouse; menus; loading and saving auxiliary files;
throwing and catching stack frames.
§43 Pictures, sounds, blurbs and Blorb . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 321
What a Blorb file is; the perlBlorb utility program; blurb files; palette,
resolution, scaling; specifying what sounds and pictures belong to a story file.
§44 Case study: a library file for menus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326
Invisiclues; basic design; the Option class; the Menu class; the SwitchOption
class.
§45 Limitations and getting around them . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334
Story file size; readable memory size; grammar; vocabulary; dictionary
resolution; objects, attributes, properties, names; function and message
arguments; recursion and stack usage; abbreviations in text.
viii
Chapter VIII: The Craft of Adventure
§46 A brief history of interactive fiction
§46.1.
§46.2.
§46.3.
§46.4.
Precursors and university games 1972–81 . . . . . . . . . . . . . . . . . . . . . . . . . . 342
The commercial boom 1982–86 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349
The growth of a community c. 1985–91 . . . . . . . . . . . . . . . . . . . . . . . . . . . 357
Newsgroups and revival 1992– . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360
§47 Realities and origins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 364
Fictional backgrounds; crimes against mimesis; historical research; book
adaptations.
§48 A triangle of identities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368
Player, protagonist and narrator; participatory magic; the overture and other
narrated text.
§49 Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375
Size and density; the prologue, middle game and end game; wide versus
narrow; lattice diagrams, including one for ‘Ruins’.
§50 The design of puzzles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 382
General remarks on good and bad puzzles; mazes, light sources, capacity and
exhaustion, timed puzzles, utility objects, keys and doors, machinery and
vehicles, fire, water, air, earth, plants, animals, monsters, people, ropes and
chains, riddles, decipherment puzzles; clues, luck and accidental solutions;
optional and multiple solutions; rewards.
§51 The room description . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 396
Examples of good, mediocre and poor description; how much space is one
location?; variable descriptions; outdoor games; differing perspectives.
§52 Finishing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 402
Scoring systems; responding to wrong guesses; examples of typical bugs;
play-testing and editing; concluding homily.
APPENDICES
§A1 Library attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 408
§A2 Library properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412
§A3 Library routines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 419
§A4 Library message numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426
§A5 Entry point routines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 431
§A6 Answers to all the exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 435
ix
TABLES
Table 1
Table 2
Table 3
Table 4
Table 5
Table 6
Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 517
The ZSCII Character Set . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 519
Command Line Switches . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 521
Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 522
Directives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 523
Library actions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 524
INDEX
Cited Works of Interactive Fiction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 528
Index of Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 534
General Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 536
x
Introduction
Inform is a system for creating adventure games, and this is the
book to read about it. It translates an author’s textual description
into a simulated world which can be explored by readers using
almost any computer, with the aid of an ‘‘interpreter’’ program.
Inform is a suite of software, called the ‘‘library’’, as well as a compiler.
Without the library, it would be a major undertaking to design even the smallest
game. The library has two ingredients: the ‘‘parser’’, which tries to make sense
of the player’s typed commands, and the ‘‘world model’’, a complex web of
standard rules, such as that people can’t see without a source of light. Given
these, the designer only needs to describe places and items, mentioning any
exceptional rules that apply. (‘‘There is a bird here, which is a normal item
except that you can’t pick it up.’’) This manual describes Inform 6.21 (or
later), with library 6/9 (or later), but earlier Inform 6 releases are similar.
Since its invention in 1993, Inform has been used to design some hundreds
of works of interactive fiction, in eight languages, reviewed in periodicals
ranging in specialisation from XYZZYnews (www.xyzzynews.com) to The New
York Times (see Edward Rothstein’s ‘Connections’ column for 6 April 1998).
It accounts for around ten thousand postings per year to Internet newsgroups.
Commercially, Inform has been used as a multimedia games prototyping tool.
Academically, it has turned up in syllabuses and seminars from computer
science to theoretical architecture, and appears in books such as Cybertext:
Perspectives on Ergodic Literature (E. J. Aarseth, Johns Hopkins Press, 1997).
Having started as a revival of the then-disused Infocom adventure game format,
the ‘‘Z-Machine’’, Inform came full circle when it produced Infocom’s only
text game of the 1990s: ‘Zork: The Undiscovered Underground’, by Mike
Berlyn and Marc Blank.
Nevertheless, Inform is not the only system available, and the intending
game designer should shop around. This author at least has long admired the
elegance of Mike Roberts’s Text Adventure Design System (TADS).
·
·
·
·
·
In trying to be both a tutorial and reference work, this book aims itself in
style halfway between the two extremes of manual, Tedium and Gnawfinger’s
Elements of Batch Processing in COBOL-66, third edition, and Mr Blobby’s
Blobby Book of Computer Fun. (This makes some sections both leaden and
patronising.) Diversionary or seldom-needed passages are marked with a
warning triangle 4 or two. Examples of program are set in typewriter font.
Mundane or irrelevant passages in longer examples are sometimes replaced
with a line reading just ‘‘...’’. Further examples are couched instead as
exercises, with answers given in §A6. Many are intended not for first readers
but to help those returning for some variation on the usual rules.
The reader viewing this 571pp text with Acrobat as a PDF file may want
to take advantage of some navigational aids. All chapters and sections (and
most subsections) are bookmarked: try the ‘‘Show Bookmarks’’ feature of
Acrobat. Clicking on a page number in purple ink links to that page; similarly
for section numbers like §4. The reddish EXERCISE heading for any exercise
links to its answer. The page numbers at the bottom right corner of each page
link to page iii where the Contents begin, except in the case of page iii itself,
which links to the General Index.
Chapter I introduces the language used to describe games without doing
any actual game design. The real flavour of Inform begins in Chapter II,
so that readers may want to begin there, turning back to Chapter I only as
needed. Equally, the essential material designers need is mostly done by
halfway through Chapter IV, so the book is not as epic as it looks.
One way to get a feeling for Inform is to design a simple game and to add
an example of each new feature as it turns up. This book does just that with an
eleven-location short story of 1930s archaeology, called ‘Ruins’, taking it from
opening lines in §4 to a completed work with a step-by-step solution by the end
of §23. Other example games for Inform include ‘Advent’, ‘Adventureland’,
‘Balances’, ‘Toyshop’ and ‘Museum of Inform’.
·
·
·
·
·
The Alexandrian Library of the interactive fiction world is an anonymous FTP
site at the German National Research Centre for Computer Science, where
Volker Blasius and David Kinder maintain the archive:
ftp://ftp.gmd.de/if-archive/
Its unrivalled collection of early and modern interactive fiction (IF), including
two dozen rival design systems, makes ftp.gmd.de the essential port of call for
the IF tourist. There are also about seventy different ‘‘library extensions’’ for
Inform and the source code of around fifty Inform-written games as further
examples. Inform’s home on the World Wide Web, which includes Gareth
Rees’s ‘Alice Through the Looking-Glass’ tutorial, is located at:
http://www.gnelson.demon.co.uk/inform.html
Here you can find The Inform Technical Manual, a dry account of internal
workings for the benefit of those working on the compiler, and The Z-Machine
Standards Document, which minutely defines the run-time format.
·
·
·
·
·
Much of this book is given over to syntax and implementation, but the world
model and its underlying rules are at the heart of Inform, and they owe much
to earlier work in the field. Ideas of how to represent places and the location of
items are owed to Will Crowther (c. 1973), while the use of a tree to represent
containment derives from work towards ‘Zork’ (c. 1978) by students at MIT,
whose artificial intelligence lab promoted the doctrine that such structures
occur ‘‘naturally’’. (Terry Winograd’s ‘SHRDLU’ (1972) had provided an
adventure-like parser for a world of blocks supporting pyramids: it recognised
pronouns, and allowed the ‘‘game’’ state to be saved.) The completion of
today’s standard model of space, objects, lighting and the passage of time can
conveniently be dated to April 1979, the publication date of the influential
IEEE Computer article ‘Zork: A Computerized Fantasy Simulation Game’ by
P. David Lebling, Marc Blank and Tim Anderson. The question of how
best to model supplementary world rules, of the ‘‘you can’t pick up the
slippery eel’’ variety, is less settled. Influenced by post-‘Zork’ Infocom, TADS
(c. 1987) has a ‘‘verification stage’’ in processing actions, for instance, which
may well be a better scheme than Inform’s before and after. The latter
derive from a second tradition, based in Cambridge, England, not Cambridge,
Massachusetts: in the first instance from an incremental multi-player game
called ‘Tera’ for the Cambridge University mainframe Phoenix, written by
Dilip Sequeira and the author in 1990. (The compiler was called Teraform, a
pun of Dr Sequeira’s. ‘Tera’ was the sequel to ‘Giga’ and before that ‘Mega’
– anagram of ‘game’ – by Mark Owen and Matthew Richards.) This stole
from the vastly more significant Phoenix game assembler by David Seal and
Jonathan Thackray which in turn dates back to their creation, with Jonathan
Partington, of ‘Acheton’ (c. 1979), the first substantial game written outside
America.
·
·
·
·
·
In the defensive words of its first, makeshift documentation, ‘‘Inform is an
easel, not a painting’’. Though I am no longer so sure that the two can be
divided. While revising this book, I have been ruefully reminded of Coleridge’s
notebooks, that vaulted miscellany of staircases and hidden doors, connections
found and lost, plans abandoned and made again. Slipping away from my
attempts to index and organise it, this book, too, remains for me a maze of
twisty little passages, all different, a kind of interactive fiction in itself.
Over the last seven years, more than a thousand people have trodden these
passages, repairing or ramifying by turns with amendments and suggestions:
far too many to name but I would like to thank them all, and especially
Volker Blasius, Kevin Bracey, Mark Howell, Stefan Jokisch, Kirk Klobe,
the ever avuncular Bob Newell, Robert Pelak, Jørund Rian, Dilip Sequeira,
Richard Tucker, Christopher Wichura, John Wood and the games designers of
Activision. Particular thanks go to Mike Berlyn, who besides being one of the
subjects of this book (he was, after all, instrumental in 1982–3 in subverting
the genre’s initial ‘‘puzzles for treasures’’ definition), was also instrumental in
the reshaping of the text as an unabashed book. His enthusiasm was greatly
valued.
Few books can have been so fortunate in receiving the generous attention
of others as this new edition. First and foremost its every paragraph was
reshaped by Gareth Rees’s skilled, tactful and invariably wise editing. The
stack of proofs of draft chapters, meticulously annotated in his calligraphic
handwriting, is now five and a half inches high; his contribution to the ideas in
this book was equally substantial, and especially in Chapter VIII. Toby Nelson
and Andrew Plotkin each proof-read the text, making helpful comments such
as ‘‘Had a bad day at the office?’’ next to one of the paragraphs, and ‘‘Inform is
curled up in my head, like a parasitic alien that doesn’t spring out and eat the
host for a surprisingly long time.’’ Torbjörn Andersson checked the solutions
to the exercises: no mean feat, and it resulted in no less than 242 specific
suggestions, only four of them pointing out that I’d got the accent in his name
wrong again. Later on he proofed the whole book again, so that at times
it seemed it would never be finished (delays, however, being solely my own
fault). Michael Baum’s census of known Z-machine story files greatly assisted
the bibliography of cited works. Paul David Doherty, interactive fiction’s most
careful and dedicated historian, was tireless in tracking down early games
designers and in vetting §46 for factual accuracy: opinions and errors remain,
of course, my own. Inform designers too numerous to name participated in a
survey of what they would like to see appearing in this book. A generous award
from the Society of Authors in 1997 allowed me more time for writing.
One final word. I should like to dedicate this book, impertinently perhaps,
to our illustrious predecessors: Will Crowther, Don Woods and the authors of
Infocom, Inc.
Graham Nelson
University of Oxford
April 1993 – April 2001
Chapter I: The Inform Language
Language is a cracked kettle on which we beat out tunes for bears
to dance to, while all the time we long to move the stars to pity.
— from the letters of Gustave Flaubert (1821–1880)
§1
Routines
If you have already dipped into Chapter II and looked at how some
simple games and puzzles are designed, you’ll have seen that any
really interesting item needs to be given instructions on how to
behave, and that these instructions are written in a language of
their own. Chapter I is about that language, and its examples are mostly short
programs to carry out mundane tasks, undistracted by the lure of adventure.
§1.1 Getting started
Inform turns your description of a game (or other program), called the ‘‘source
code’’, into a ‘‘story file’’ which can be played (or run through) using an
‘‘interpreter’’. Interpreter programs are available for very many models of
computer, and if you can already play Infocom’s games or other people’s
Inform games on your machine then you already have an interpreter. There
are several interpreter programs available, in a sort of friendly rivalry. You
should be able to use whichever you prefer and even the oldest, most rickety
interpreter will probably not give serious trouble. A good sign to look out for
is compliance with the exacting Z-Machine Standards Document, agreed by an
informal committee of interested parties between 1995 and 1997. At time of
writing, the current version is Standard 1.0, dating from June 1997.
Turning source code into a story file is called ‘‘compilation’’, and Inform
itself is the compiler. It’s also supplied with a whole slew of ready-made source
code called the ‘‘library’’ and giving all the rules for adventure games. The
story of what the library does and how to work with it occupies most of this
manual, but not this chapter.
§
It isn’t practicable to give installation instructions here, because they vary
so much from machine to machine, but before you can go any further you’ll
need to install Inform: try downloading the software for your own machine
from ftp.gmd.de, which should either install itself or give instructions. A
useful test exercise would be to try to create the ‘‘Hello World’’ source code
given below, then to compile it with Inform, and finally ‘‘play’’ it on your
interpreter. (You can type up source code with any text editor or even with a
word-processor, provided you save its documents as ‘‘text only’’.)
Inform can run in a number of slightly different ways, controlled by
‘‘switches’’. The way to set these varies from one installation to another.
Note that this chapter assumes that you’re running Inform in ‘‘Strict mode’’,
controlled by the -S switch, which is normally set and ensures that helpful
error messages will be printed if a story file you have compiled does something
it shouldn’t.
§1.2 Hello World
Traditionally, all programming language tutorials begin by giving a program
which does nothing but print ‘‘Hello world’’ and stop. Here it is in Inform:
! "Hello world" example program
[ Main;
print "Hello world^";
];
The text after the exclamation mark is a ‘‘comment’’, that is, it is text written
in the margin by the author to remind himself of what is going on here. Such
text means nothing to Inform, which ignores anything on the same line and
to the right of an exclamation mark. In addition, any gaps made up of line
and page breaks, tab characters and spaces are treated the same and called
‘‘white space’’, so the layout of the source code doesn’t much matter. Exactly
the same story file would be produced by:
[
Main
;
print
"Hello world^"
;
]
;
§
or, at the other extreme, by:
[Main;print"Hello world^";];
Laying out programs legibly is a matter of personal taste.
4
The exception to the rule about ignoring white space is inside quoted text, where
"Hello
world^" and "Hello world^" are genuinely different pieces of text and are
treated as such. Inform treats text inside quotation marks with much more care than its
ordinary program material: for instance, an exclamation mark inside quotation marks
will not cause the rest of its line to be thrown away as a comment.
Inform regards its source code as a list of things to look at, divided up by
semicolons ;. These things are generally objects, of which more later. In this
case there is only one, called Main, and it’s of a special kind called a ‘‘routine’’.
Every program has to contain a routine called Main. When a story file is
set running the interpreter follows the first instruction in Main, and it carries
on line by line from there. This process is called ‘‘execution’’. Once the Main
routine is finished, the interpreter stops.
These instructions are called ‘‘statements’’, a traditional term in computing albeit an ungrammatical one. In this case there is only one statement:
print "Hello world^";
Printing is the process of writing text onto the computer screen. This statement
prints the two words ‘‘Hello world’’ and then skips the rest of the line (or ‘‘prints
a new-line’’), because the ^ character, in quoted text, means ‘‘new-line’’. For
example, the statement
print "Blue^Red^Green^";
prints up:
Blue
Red
Green
print is one of 28 different statements in the Inform language. Only about 20
of these are commonly used, but the full list is as follows:
box
give
objectloop
restore
string
break
if
print
return
style
continue
inversion
print_ret
rfalse
switch
do
jump
quit
rtrue
while
font
move
read
save
for
new_line
remove
spaces
§
§1.3 Routine calls, errors and warnings
The following source code has three routines, Main, Rosencrantz and Hamlet:
[ Main;
print "Hello from Elsinore.^";
Rosencrantz();
];
[ Rosencrantz;
print "Greetings from Rosencrantz.^";
];
[ Hamlet;
print "The rest is silence.^";
];
The resulting program prints up
Hello from Elsinore.
Greetings from Rosencrantz.
but the text ‘‘The rest is silence.’’ is never printed. Execution begins at Main,
and ‘‘Hello from Elsinore’’ is printed; next, the statement Rosencrantz()
causes the Rosencrantz routine to be executed. That continues until it ends
with the close-routine marker ], whereupon execution goes back to Main just
after the point where it left off: since there is nothing more to do in Main, the
interpreter stops. Thus, Rosencrantz is executed but Hamlet is not.
In fact, when the above source code is compiled, Inform notices that
Hamlet is never needed and prints out a warning to that effect. The exact text
produced by Inform varies from machine to machine, but will be something
like this:
RISC OS Inform 6.20 (10th December 1998)
line 8: Warning: Routine "Hamlet" declared but not used
Compiled with 1 warning
Errors are mistakes in the source which cause Inform to refuse to compile it,
but this is only a warning. It alerts the programmer that a mistake may have
been made (because presumably the programmer has simply forgotten to put
in a statement calling Hamlet) but it doesn’t prevent the compilation from
taking place. Note that the opening line of the routine Hamlet occurs on the
8th line of the program above.
§
There are usually mistakes in a newly-written program and one goes
through a cycle of running a first draft through Inform, receiving a batch of
error messages, correcting the draft according to these messages, and trying
again. A typical error message would occur if, on line 3, we had mistyped
Rosncrantz() for Rosencrantz(). Inform would then have produced:
RISC OS Inform 6.20 (10th December 1998)
line 5: Warning: Routine "Rosencrantz" declared but not used
line 8: Warning: Routine "Hamlet" declared but not used
line 3: Error: No such constant as "Rosncrantz"
Compiled with 1 error and 2 warnings (no output)
The error message means that on line 3 Inform ran into a name which did
not correspond to any known quantity: it’s not the name of any routine, in
particular. A human reader would immediately realise what was intended,
but Inform doesn’t, so that it goes on to warn that the routine Rosencrantz
is never used. Warnings (and errors) are quite often produced as knock-on
effects of other mistakes, so it is generally a good idea to worry about fixing
errors first and warnings afterward.
Notice that Inform normally doesn’t produce the final story file if errors
occurred during compilation: this prevents it from producing damaged story
files.
§1.4 Numbers and other constants
Inform numbers are normally whole numbers in the range −32,768 to 32,767.
(Special programming is needed to represent larger numbers or fractions, as
we shall see when parsing phone numbers in Chapter IV.) There are three
notations for writing numbers in source code: here is an example of each.
-4205
$3f08
$$1000111010110
The difference is the radix, or number base, in which they are expressed. The
first is in decimal (base 10), the second hexadecimal (base 16, where the digits
after 9 are written a to f or A to F) and the third binary (base 2). Once
Inform has read in a number, it forgets which notation was used: for instance,
if the source code is altered so that $$10110 is replaced by 22, this makes no
difference to the story file produced.
§
A print statement can print numbers as well as text, though it always
prints them back in ordinary decimal notation. For example, the program
[ Main;
print "Today’s number is ", $3f08, ".^";
];
prints up
Today’s number is 16136.
since 16,136 in base 10 is the same number as 3f08 in hexadecimal.
Literal quantities written down in the source code are called ‘‘constants’’.
Numbers are one kind; strings of text like "Today’s number is " are another.
A third kind are characters, given between single quotation marks. For
instance, ’x’ means ‘‘the letter lower-case x’’. A ‘‘character’’ is a single letter
or typewriter-symbol.
4
Just as $3f08 is a fancy way of writing the number 16,136, so ’x’ is a fancy way
of writing the number 120. The way characters correspond to numeric values is given
by a code called ZSCII, itself quite close to the traditional computing standard called
ASCII. 120 means ‘‘lower-case x’’ in ASCII, too, but ZSCII does its own thing with
non-English characters like ‘‘é’’. (You can type accented letters directly into source
code: see §1.11.) The available characters and their ZSCII values are laid out in
Table 2.
Inform also provides a few constants named for convenience, the most
commonly used of which are true and false. A condition such as ‘‘the
jewelled box is unlocked’’ will always have either the value true or the value
false.
4
Once again these are numbers in disguise. true is 1, false 0.
4
Inform is a language designed with adventure games in mind, where a player
regularly types in commands like ‘‘unlock the box’’, using a fairly limited vocabulary.
Writing a word like ’box’ in the source code, in single-quotation marks, adds it to the
‘‘dictionary’’ or vocabulary of the story file to be compiled. This is a kind of constant,
too: you can use it for writing code like ‘‘if the second word typed by the player is
’box’, then. . .’’.
4
If you need to lodge a single-letter word (say ‘‘h’’) into the dictionary, you can’t
put it in single quotes because then it would look like a character constant (’h’).
Instead, the best you can write is ’h//’. (The two slashes are sometimes used to
tack a little linguistic information onto the end of a dictionary word, so that in some
circumstances, see §29, you might want to write ’pears//p’ to indicate that ‘‘pears’’
must go into the dictionary marked as a plural. In this case the two slashes only serve
to clarify that it isn’t a character.)
§
4 You can put an apostrophe ’ into a dictionary word by writing it as ^: for instance
’helen^s’.
44 In two places where dictionary words often appear, the name slot of an object
definition and in grammar laid out with Verb and Extend, you’re allowed to use single
or double quotes interchangeably, and people sometimes do. For clarity’s sake, this
book tries to stick to using single quotes around dictionary words at all times. The
handling of dictionary words is probably the single worst-designed bit of syntax in
Inform, but you’re past it now.
§1.5 Variables
Unlike a literal number, a variable is able to vary. It is referred to by its name
and, like the ‘‘memory’’ key on some pocket calculators, remembers the last
value placed in it. For instance, if oil_left has been declared as a variable
(see below), then the statement
print "There are ", oil_left, " gallons remaining.^";
would cause the interpreter to print ‘‘There are 4 gallons remaining.’’ if
oil_left happened to be 4, and so on. It’s possible for the statement to be
executed many times and produce different text at different times.
Inform can only know the named quantity oil_left is to be a variable
if the source code has ‘‘declared’’ that it is. Each routine can declare its own
selection of up to 15 variables on its opening line. For example, in the program
[ Main alpha b;
alpha = 2200;
b = 201;
print "Alpha is ", alpha, " while b is ", b, "^";
];
the Main routine has two variables, alpha and b.
Going back to the Main routine above, the = sign which occurs twice is an
example of an ‘‘operator’’: a notation usually made up of the symbols on the
non-alphabetic keys on a typewriter and which means something is to be done
with or calculated from the items it is written next to. Here = means ‘‘set equal
to’’. When the statement alpha = 2200; is interpreted, the current value of
the variable alpha is changed to 2,200. It then keeps that value until another
such statement changes it. All variables have the value 0 until they are first set.
§
The variables alpha and b are called ‘‘local variables’’ because they are
local to Main and are its private property. The source code
[ Main alpha;
alpha = 2200;
Rival();
];
[ Rival;
print alpha;
];
causes an error on the print statement in Rival, since alpha does not exist
there. Indeed, Rival could even have defined a variable of its own also called
alpha and this would have been an entirely separate variable.
·
·
·
·
·
That’s now two kinds of name: routines have names and so have variables.
Such names, for instance Rival and alpha, are called ‘‘identifiers’’ and can
be up to 32 characters long. They may contain letters of the alphabet, decimal
digits or the underscore _ character (often used to impersonate a space). To
prevent them looking too much like numbers, though, they cannot start with
a decimal digit. The following are examples of legal identifiers:
turns_still_to_play
room101
X
Inform ignores any difference between upper and lower case letters in such
names, so for instance room101 is the same name as Room101.
§1.6 Arithmetic, assignment and bitwise operators
The Inform language is rich with operators, as Table 1 shows. This section
introduces a first batch of them.
A general mixture of quantities and operators, designed to end up with a
single resulting quantity, is called an ‘‘expression’’. For example: the statement
seconds = 60*minutes + 3600*hours;
sets the variable seconds equal to 60 times the variable minutes plus 3600
times the variable hours. White space is not needed between operators and
§
‘‘operands’’ (the quantities they operate on, such as 3600 and hours). The
spaces on either side of the + sign were written in just for legibility.
The arithmetic operators are the simplest ones. To begin with, there are
+ (plus), - (minus), * (times) and / (divide by). Dividing one whole number
by another usually leaves a remainder: for example, 3 goes into 7 twice, with
remainder 1. In Inform notation,
7/3 evaluates to 2 and 7%3 evaluates to 1
the % operator meaning ‘‘remainder after division’’, usually called just ‘‘remainder’’.
4
The basic rule is that a == (a/b)*b + (a%b), so that for instance:
13/5 == 2
13%5 == 3
13/-5 == -2
13%-5 == 3
-13/5 == -2
-13%5 == -3
-13/-5 == 2
-13%-5 == -3
• WARNING
Dividing by zero, and taking remainder after dividing by zero, are impossible.
You must write your program so that it never tries to. It’s worth a brief aside
here on errors, because dividing by zero offers an example of how and when
Inform can help the programmer to spot mistakes. The following source code:
[ Main; print 73/0; ];
won’t compile, because Inform can see that it definitely involves something
illegal:
line 2: Error: Division of constant by zero
>
print 73/0;
However, Inform fails to notice anything amiss when compiling this:
[ Main x; x = 0; print 73/x; ];
and this source code compiles correctly. When the resulting story file is
interpreted, however, the following will be printed:
[** Programming error: tried to divide by zero **]
This is only one of about fifty different programming errors which can turn up
when a story file is interpreted. As in this case, they arise when the interpreter
has accidentally been asked to do something impossible. The moral is that just
because Inform compiles source code without errors, it does not follow that
the story file does what the programmer intended.
§
4 Since an Inform number has to be between −32,768 and 32,767, some arithmetic
operations overflow. For instance, multiplying 8 by 5,040 ought to give 40,320, but
this is over the top and the answer is instead −25,216. Unlike dividing by zero, causing
arithmetic overflows is perfectly legal. Some programmers make deliberate use of
overflows, for instance to generate apparently random numbers.
44 Only apparently random, because overflows are perfectly predictable. Inform story
files store numbers in sixteen binary digits, so that when a number reaches 216 = 65,536
it clocks back round to zero (much as a car’s odometer clocks over from 999999 miles
to 000000). Now, since 65,535 is the value that comes before 0, it represents −1, and
65,534 represents −2 and so on. The result is that if you start with zero and keep
adding 1 to it, you get 1, 2, 3, . . . , 32,767 and then −32,768, −32,767, −32,766, . . .,
−1 and at last zero again. Here’s how to predict an overflowing multiplication, say:
first, multiply the two numbers as they stand, then keep adding or subtracting 65,536
from the result until it lies in the range −32,768 to 32,767. For example, 8 multiplied
by 5,040 is 40,320, which is too big, but we only need to subtract 65,536 once to bring
it into range, and the result is −25,216.
·
·
·
·
·
In a complicated expression the order in which the operators work may affect
the result. As most human readers would, Inform works out both of
3 + 2 * 6
2 * 6 + 3
as 15, because the operator * has ‘‘precedence’’ over + and so is acted on first.
Brackets may be used to overcome this:
(3 + 2) * 6
2 * (6 + 3)
evaluate to 30 and 18 respectively. Each operator has such a ‘‘precedence
level’’. When two operators have the same precedence level (for example, +
and - are of equal precedence) calculation is (almost always) ‘‘left associative’’,
that is, carried out left to right. So the notation a-b-c means (a-b)-c and not
a-(b-c). The standard way to write formulae in maths is to give + and - equal
precedence, but lower than that of * and / (which are also equal). Inform
agrees and also pegs % equal to * and /.
The last purely arithmetic operator is ‘‘unary minus’’. This is also written
as a minus sign - but is not quite the same as subtraction. The expression:
-credit
§
means the same thing as 0-credit. The operator - is different from all
those mentioned so far because it operates only on one value. It has higher
precedence than any of the five other arithmetic operators. For example,
-credit - 5
means (-credit) - 5 and not -(credit - 5).
One way to imagine precedence is to think of it as glue attached to the
operator. A higher level means stronger glue. Thus, in
3 + 2 * 6
the glue around the * is stronger than that around the +, so that 2 and 6 belong
bound to the *.
4
Some languages have a ‘‘unary plus’’ too, but Inform hasn’t.
· · · · ·
Some operators don’t just work out values but actually change the current
settings of variables: expressions containing these are called ‘‘assignments’’.
One such is ‘‘set equals’’:
alpha = 72
sets the variable alpha equal to 72. Like + and the others, it also comes up
with an answer: which is the value it has set, in this case 72.
The other two assignment operators are ++ and --, which will be familiar
to any C programmer. They are unary operators, and mean ‘‘increase (or
decrease) the value of this variable by one’’. If the ++ or -- goes before the
variable, then the increase (or decrease) happens before the value is read off;
if after, then after. For instance, if variable currently has the value 12 then:
variable++ evaluates to 12 and leaves variable set to 13;
++variable evaluates to 13 and leaves variable set to 13;
variable-- evaluates to 12 and leaves variable set to 11;
--variable evaluates to 11 and leaves variable set to 11.
These operators are provided as convenient shorthand forms, since their effect
could usually be achieved in other ways. Note that expressions like
500++
(4*alpha)--
34 = beta
are quite meaningless: the values of 500 and 34 cannot be altered, and Inform
knows no way to adjust alpha so as to make 4*alpha decrease by 1. All three
will cause compilation errors.
§
·
·
·
·
·
The ‘‘bitwise operators’’ are provided for manipulating binary numbers on
a digit-by-digit basis, something which is only done in programs which are
working with low-level data or data which has to be stored very compactly.
Inform provides &, bitwise AND, |, bitwise OR and ~, bitwise NOT. For each
digit, such an operator works out the value in the answer from the values in
the operands. Bitwise NOT acts on a single operand and results in the number
whose i-th binary digit is the opposite of that in the operand (a 1 for a 0, a 0
for a 1). Bitwise AND (and OR) acts on two numbers and sets the i-th digit to
1 if both operands have (either operand has) i-th digit set. All Inform numbers
are sixteen bits wide. So:
$$10111100 & $$01010001
$$10111100 | $$01010001
~ $$01010001
==
==
==
$$0000000000010000
$$0000000011111101
$$1111111110101110
§1.7 Arguments and Return Values
Here is one way to imagine how an Inform routine works: you feed some values
into it, it then goes away and works on them, possibly printing some text out
or doing other interesting things, and it then returns with a single value which
it gives back to you. As far as you’re concerned, the transaction consists of
turning a group of starting values, called ‘‘arguments’’, into a single ‘‘return
value’’:
A1 , A2 , A3 , . . . −→ Routine −→ R
The number of arguments needed varies with the routine: some, like Main
and the other routines in this chapter so far, need none at all. (Others need
anything up to seven, which is the maximum number allowed by Inform.) On
the other hand, every routine without exception produces a return value. Even
when it looks as if there isn’t one, there is. For example:
[ Main;
Sonnet();
];
[ Sonnet;
print "When to the sessions of sweet silent thought^";
print "I summon up remembrance of things past^";
];
§
Main and Sonnet both take no arguments, but they both return a value: as it
happens this value is true, in the absence of any instruction to the contrary.
(As was mentioned earlier, true is the same as the number 1.) The statement
Sonnet(); calls Sonnet but does nothing with the return value, which is just
thrown away. But if Main had instead been written like so:
[ Main;
print Sonnet();
];
then the output would be
When to the sessions of sweet silent thought
I summon up remembrance of things past
1
because now the return value, 1, is not thrown away: it is printed out.
You can call it a routine with arguments by writing them in a list,
separated by commas, in between the round brackets. For instance, here is a
call supplying two arguments:
Weather("hurricane", 12);
When the routine begins, the value "hurricane" is written into its first local
variable, and the value 12 into its second. For example, suppose:
[ Weather called force;
print "I forecast a ", (string) called, " measuring force ",
force, " on the Beaufort scale.^";
];
Leaving the details of the print statement aside for the moment, the call to
this routine produces the text:
I forecast a hurricane measuring force 12 on the Beaufort scale.
The Weather routine finishes when its ] end-marker is reached, whereupon
it returns true, but any of the following statements will finish a routine the
moment they are reached:
rfalse;
rtrue;
which returns false,
which returns true,
§
return;
return hvaluei;
which also returns true,
which returns hvaluei.
For example, here is a routine to print out the cubes of the numbers 1 to 5:
[ Main;
print Cube(1),
print Cube(2),
print Cube(3),
print Cube(4),
print Cube(5),
];
[ Cube x;
return x*x*x;
];
" ";
" ";
" ";
" ";
"^";
When interpreted, the resulting story file prints up the text:
1 8 27 64 125
4 Any ‘‘missing arguments’’ in a routine call are set equal to zero, so the call Cube()
is legal and does the same as Cube(0). What you mustn’t do is to give too many
arguments: Cube(1,2) isn’t possible because there is no variable to put the 2 into.
4
A hazardous, but legal and sometimes useful practice is for a routine to call itself.
This is called recursion. The hazard is that the following mistake can be made, probably
in some much better disguised way:
[ Disaster; return Disaster(); ];
Despite the reassuring presence of the word return, execution is tied up forever, unable
to finish evaluating the return value. The first call to Disaster needs to make a second
before it can finish, the second needs to make a third, the third. . . and so on. (Actually,
for ‘‘forever’’ read ‘‘until the interpreter runs out of stack space and halts’’, but that’s
little comfort.)
§1.8 Conditions: if, true and false
The facilities described so far make Inform about as powerful as the average
small programmable calculator. To make it a proper programming language, it
needs much greater flexibility of action. This is provided by special statements
§
which control whether or not, and if so how many times or in what order,
other statements are executed. The simplest is if:
if (hconditioni) hstatementi
which executes the hstatementi only if the hconditioni, when it is tested, turns
out to be true. For example, when the statement
if (alpha == 3) print "Hello";
is executed, the word ‘‘Hello’’ is printed only if the variable alpha currently
has value 3. It’s important not to confuse the == (test whether or not equal
to) with the = operator (set equal to). But because it’s easy to write something
plausible like
if (alpha = 3) print "Hello";
by accident, which always prints ‘‘Hello’’ because the condition evaluates to 3
which is considered non-zero and therefore true (see below), Inform will issue
a warning if you try to compile something like this. (‘=’ used as condition: ‘==’
intended?)
·
·
·
·
·
Conditions are always given in brackets. There are 12 different conditions in
Inform (see Table 1), six arithmetic and half a dozen to do with objects. Here
are the arithmetic ones:
(a
(a
(a
(a
(a
(a
== b)
~= b)
>= b)
<= b)
> b)
< b)
a equals b
a doesn’t equal b
a is greater than or equal to b
a is less than or equal to b
a is greater than b
a is less than b
A useful extension to this set is provided by the special operator or, which
gives alternative possibilities. For example,
if (alpha == 3 or 4) print "Scott";
if (alpha ~= 5 or 7 or 9) print "Amundsen";
where two or more values are given with the word or between. ‘‘Scott’’ is
printed if alpha has value either 3 or 4, and ‘‘Amundsen’’ if the value of alpha
§
is not 5, is not 7 and is not 9. or can be used with any condition, and any
number of alternatives can be given. For example
if (player in Forest or Village or Building) ...
often makes code much clearer than writing three separate conditions out. Or
you might want to use
if (x > 100 or y) ...
to test whether x is bigger than the minimum of 100 and y.
Conditions can also be built up from simpler ones using the three logical
operators &&, || and ~~, pronounced ‘‘and’’, ‘‘or’’ and ‘‘not’’. For example,
if (alpha == 1 && (beta > 10 || beta < -10)) print "Lewis";
if (~~(alpha > 6)) print "Clark";
‘‘Lewis’’ is printed if alpha equals 1 and beta is outside the range −10 to 10;
‘‘Clark’’ is printed if alpha is less than or equal to 6.
4
&& and || work left to right and stop evaluating conditions as soon as the final
outcome is known. So for instance if (A && B) ... will work out A first. If this is
false, there’s no need to work out B. This is sometimes called short-cut evaluation
and can be convenient when working out conditions like
if (x~=nothing && TreasureDeposited(x)==true) ...
where you don’t want TreasureDeposited to be called with the argument nothing.
· · · · ·
Conditions are expressions like any other, except that their values are always
either true or false. You can write a condition as a value, say by copying it
into a variable like so:
lower_caves_explored = (Y2_Rock_Room has visited);
This kind of variable, storing a logical state, is traditionally called a ‘‘flag’’.
Flags are always either true or false: they are like the red flag over the beach
at Lee-on-Solent in Hampshire, which is either flying, meaning that the army
is using the firing range, or not flying, when it is safe to walk along the shore.
Flags allow you to write natural-looking code like:
if (lower_caves_explored) print "You’ve already been that way.";
The actual test performed by if (x) ... is x~=0, not that x==true, because
all non-zero quantities are considered to represent truth whereas only zero
represents falsity.
§
·
·
·
·
·
4
Now that the if statement is available, it’s possible to give an example of a
recursion that does work:
[ GreenBottles n;
print n, " green bottles, standing on a wall.^";
if (n == 0) print "(And an awful lot of broken glass.)^";
else {
print "And if one green bottle should accidentally fall^";
print "There’d be ", n-1, " green bottles.^";
return GreenBottles(n-1);
}
];
Try calling GreenBottles(10). It prints the first verse of the song, then before returning
it calls GreenBottles(9) to print the rest. So it goes on, until GreenBottles(0). At
this point n is zero, so the text ‘‘(And an awful lot of broken glass.)’’ is printed
for the first and only time. GreenBottles(0) then returns back to GreenBottles(1),
which returns to GreenBottles(2), which. . . and so on until GreenBottles(10) finally
returns and the song is completed.
44 Thus execution reached ‘‘ten routines deep’’ before starting to return back up, and
each of these copies of GreenBottles had its own private copy of the variable n. The
limit to how deep you are allowed to go varies from one player’s machine to another,
but here is a rule of thumb, erring on the low side for safety’s sake. (On a standard
interpreter it will certainly be safe.) Total up 4 plus the number of its variables for each
routine that needs to be running at the same time, and keep the total beneath 1,000.
Ten green bottles amounts only to a total of 10 × 5 = 50, and as it seems unlikely that
anyone will wish to read the lyrics to ‘‘two hundred and one green bottles’’ the above
recursion is safe.
§1.9 Code blocks, else and switch
A feature of all statements choosing what to do next is that instead of just
giving a single hstatementi, one can give a list of statements grouped together
into a unit called a ‘‘code block’’. Such a group begins with an open brace {
and ends with a close brace }. For example,
if (alpha > 5) {
v = alpha*alpha;
print "The square of alpha is ", v, ".^";
}
§
If alpha is 3, nothing is printed; if alpha is 9,
The square of alpha is 81.
is printed. (The indentation used in the source code, like all points of source
code layout, is a matter of personal taste.†) In some ways, code blocks are like
routines, and at first it may seem inconsistent to write routines between [ and
] brackets and code blocks between braces { and }. However, code blocks
cannot have private variables of their own and do not return values; and it is
possible for execution to break out of code blocks again, or to jump from block
to block, which cannot happen with routines.
·
·
·
·
·
An if statement can optionally have the form
if (hconditioni) hstatement1i else hstatement2i
whereupon hstatement1i is executed if the condition is true, and hstatement2i
if it is false. For example,
if (alpha == 5) print "Five."; else print "Not five.";
Note that the condition is only checked once, so that the statement
if (alpha == 5) {
print "Five.";
alpha = 10;
}
else print "Not five.";
cannot ever print both ‘‘Five’’ and then ‘‘Not five’’.
4 The else clause has a snag attached: the problem of ‘‘hanging elses’’. In the
following, which if statement does the else attach to?
if (alpha == 1) if (beta == 2)
print "Clearly if alpha=1 and beta=2.^";
else
print "Ambiguous.^";
† Almost everybody indents each code block as shown, but the position of the open
brace is a point of schism. This book adopts the ‘‘One True Brace Style’’, as handed
down in such sacred texts as the original Unix source code and Kernighan and Ritchie’s
textbook of C. Other conventions place the open brace vertically above its matching
closure, perhaps on its own otherwise blank line of source code.
§
Without clarifying braces, Inform pairs an else to the most recent if. (Much as
the U.S. Supreme Court rigorously interprets muddled laws like ‘‘all animals must be
licensed, except cats, other than those under six months old’’ by applying a so-called
Last Antecedent Rule to ambiguous qualifications like this ‘‘other than. . .’’.) The
following version is much better style:
if (alpha == 1) {
if (beta == 2)
print "Clearly if alpha=1 and beta=2.^";
else
print "Clearly if alpha=1 but beta not 2.^";
}
·
·
·
·
·
The if . . . else . . . construction is ideal for switching execution between two
possible ‘‘tracks’’, like railway signals, but it is a nuisance trying to divide
between many different outcomes this way. To follow the analogy, the switch
construction is like a railway turntable.
print "The train on platform 1 is going to ";
switch (DestinationOnPlatform(1)) {
1: print "Dover Priory.";
2: print "Bristol Parkway.";
3: print "Edinburgh Waverley.";
default: print "a siding.";
}
The default clause is optional but must be placed last if at all: it is executed
when the original expression matches none of the other values. Otherwise
there’s no obligation for these clauses to be given in numerical or any other
order. Each possible alternative value must be a constant, so
switch (alpha) {
beta: print "The variables alpha and beta are equal!";
}
will produce a compilation error. (But note that in a typical game, the name
of an object or a location, such as First_Court, is a constant, so there is no
problem in quoting this as a switch value.)
§
Any number of outcomes can be specified, and values can be grouped
together in lists separated by commas, or in ranges like 3 to 6. For example:
print "The mission Apollo ", num, " made ";
switch (num) {
7, 9: print "a test-flight in Earth orbit.";
8, 10: print "a test-flight in lunar orbit.";
11, 12, 14 to 17: print "a landing on the Moon.";
13: print "it back safely after a catastrophic explosion.";
}
Each clause is automatically a code block, so a whole run of statements can be
given without the need for any braces { and } around them.
4
If you’re used to the C language, you might want to note a major difference:
Inform doesn’t have ‘‘case fall-through’’, with execution running from one case to the
next, so there’s no need to use break statements.
44 A good default clause for the above example would be a little complicated:
Apollo 1 was lost in a ground fire, causing a shuffle so that 2 and 3 never happened,
while automatic test-flights of the Saturn rocket were unofficially numbered 4 to 6.
Apollo 20 was cancelled to free up a heavy launcher for the Skylab station, and 18 and
19 through budget cuts, though the Apollo/Soyuz Test Project (1975) is sometimes
unhistorically called Apollo 18. The three Apollo flights to Skylab were called Skylab 2,
3 and 4. All six Mercury capsules were numbered 7, while at time of writing the Space
Shuttle mission STS-88 has just landed and the next to launch, in order, are projected
to be 96, 93, 99, 103, 101 and 92. NASA has proud traditions.
§1.10 while, do . . . until, for, break, continue
The other four Inform control constructions are all ‘‘loops’’, that is, ways to
repeat the execution of a given statement or code block. Discussion of one of
them, called objectloop, is deferred until §3.4.
The two basic forms of loop are while and do . . . until:
while (hconditioni) hstatementi
do hstatementi until (hconditioni)
The first repeatedly tests the condition and, provided it is still true, executes
the statement. If the condition is not even true the first time, the statement is
§
never executed even once. For example:
[ SquareRoot
while (x*x
if (x*x ==
return x ];
n x;
< n) x = x + 1;
n) return x;
1;
which is a simple if rather inefficient way to find square roots, rounded down
to the nearest whole number. If SquareRoot(200) is called, then x runs up
through the values 0, 1, 2, . . . , 14, 15, at which point x*x is 225: so 14 is
returned. If SquareRoot(0) is called, the while condition never holds at all
and so the return value is 0, made by the if statement.
The do . . . until loop repeats the given statement until the condition is
found to be true. Even if the condition is already satisfied, like (true), the
statement is always executed the first time through.
·
·
·
·
·
One particular kind of while loop is needed so often that there is an abbreviation for it, called for. This can produce any loop in the form
hstarti
while (hconditioni) {
...
hupdatei
}
where hstarti and hupdatei are expressions which actually do something, such
as setting a variable. The notation to achieve this is:
for (hstarti : hconditioni : hupdatei) . . .
Note that if the condition is false the very first time, the loop is never executed.
For instance, this prints nothing:
for (counter=1 : counter<0 : counter++) print "Banana";
Any of the three parts of a for statement can be omitted. If the condition is
missed out, it is assumed always true, so that the loop will continue forever,
unless escaped by other means (see below).
For example, here is the while version of a common kind of loop:
counter = 1;
while (counter <= 10) {
print counter, " ";
counter++;
}
§
which produces the output ‘‘1 2 3 4 5 6 7 8 9 10’’. (Recall that counter++
adds 1 to the variable counter.) The abbreviated version is:
for (counter=1 : counter<=10 : counter++)
print counter, " ";
4
Using commas, several assignments can be joined into one. For instance:
i++, score=50, j++
is a single expression. This is never useful in ordinary code, where the assignments can
be divided up by semicolons in the usual way. But in for loops it can be a convenience:
for (i=1, j=5: i<=5: i++, j--) print i, " ", j, ", ";
produces the output ‘‘1 5, 2 4, 3 3, 4 2, 5 1,’’.
44 Comma , is an operator, and moreover is the one with the lowest precedence
level. The result of a,b is always b, but a and b are both evaluated and in that order.
·
·
·
·
·
On the face of it, the following loops all repeat forever:
while (true) hstatementi
do hstatementi until (false)
for (::) hstatementi
But there is always an escape. One way is to return from the current routine.
Another is to jump to a label outside the loop (see below), though if one only
wants to escape the current loop then this is seldom good style. It’s neatest to
use the statement break, which means ‘‘break out of’’ the current innermost
loop or switch statement: it can be read as ‘‘finish early’’. All these ways out
are entirely safe, and there is no harm in leaving a loop only half-done.
The other simple statement used inside loops is continue. This causes
the current iteration to end immediately, but the loop then continues. In
particular, inside a for loop, continue skips the rest of the body of the loop
and goes straight to the hupdatei part. For example,
for (i=1: i<=5: i++) {
if (i==3) continue;
print i, " ";
}
will output ‘‘1 2 4 5’’.
§
·
·
·
·
·
4
The following routine is an curious example of a loop which, though apparently
simple enough, contains a trap for the unwary.
[ RunPuzzle n count;
do {
print n, " ";
n = NextNumber(n);
count++;
}
until (n==1);
print "1^(taking ", count, " steps to reach 1)^";
];
[ NextNumber n;
if (n%2 == 0) return n/2;
! If n is even, halve it
return 3*n + 1;
! If n is odd, triple and add 1
];
The call RunPuzzle(10), for example, results in the output
10 5 16 8 4 2 1
(taking 6 steps to reach 1)
The definition of RunPuzzle assumes that, no matter what the initial value of n, enough
iteration will end up back at 1. If this did not happen, the interpreter would lock up
into an infinite loop, printing numbers forever. The routine is apparently very simple,
so it would seem reasonable that by thinking carefully enough about it, we ought to
be able to decide whether or not it will ever finish. But this is not so easy as it looks.
RunPuzzle(26) takes ten steps, but there again, RunPuzzle(27) takes 111. Can this
routine ever lock up into an infinite loop, or not?
44 The answer, which caught the author by surprise, is: yes. Because of Inform’s
limited number range, eventually the numbers reached overflow 32,767 and Inform
interprets them as negative – and quickly homes in on the cycle −1, −2, −1, −2, . . .
This first happens to RunPuzzle(447). Using proper arithmetic, unhindered by a
limited number range, the answer is unknown. As a student in Hamburg in the 1930s,
Lothar Collatz conjectured that every positive n eventually reaches 1. Little progress
has been made (though it is known to be true if n is less than 240 ), and even Paul Erdös
said of it that ‘‘Mathematics is not yet ready for such problems.’’ See Jeffrey Lagarias’s
bibliography The 3x + 1 Problem and its Generalisations (1996).
§
§1.11 How text is printed
Adventure games take a lot of trouble over printing, so Inform is rich in features
to make printing elegant output more easy. During story-file interpretation,
your text will automatically be broken properly at line-endings. Inform story
files are interpreted on screen displays of all shapes and sizes, but as a
programmer you can largely ignore this. Moreover, Inform itself will compile a
block of text spilling over several source-code lines into normally-spaced prose,
so:
print "Here in her hairs
the painter plays the spider, and hath woven
a golden mesh t’untrap the hearts of men
faster than gnats in cobwebs";
results in the line divisions being replaced by a single space each. The text
printed is: ‘‘Here in her hairs the painter plays the spider, and hath woven a
golden mesh. . .’’ and so on. There is one exception to this: if a line finishes
with a ^ (new-line) character, then no space is added.
4
You shouldn’t type double-spaces after full stops or other punctuation, as this can
spoil the look of the final text on screen. In particular, if a line break ends up after
the first space of a double-space, the next line will begin with the second space. Since
many typists habitually double-space, Inform has a switch -d1 which will contract ‘‘.
’’ to ‘‘. ’’ wherever it occurs, and a further setting -d2 which also contracts ‘‘! ’’ to
‘‘! ’’ and ‘‘? ’’ to ‘‘? ’’. A modest warning, though: this can sometimes make a mess
of diagrams. Try examining the 1851 Convention on Telegraphy Morse Code chart
in ‘Jigsaw’, for instance, where the present author inadvertently jumbled the spacing
because -d treated the Morse dots as full stops.
· · · · ·
When a string of text is printed up with print, the characters in the string
normally appear exactly as given in the source code. However, four characters
have special meanings. ^ means ‘‘print a new-line’’. The tilde character ~,
meaning ‘‘print a quotation mark’’, is needed since quotation marks otherwise
finish strings. Thus,
print "~Look,~ says Peter. ~Socks can jump.~^Jane agrees.^";
is printed as
‘‘Look,’’ says Peter. ‘‘Socks can jump.’’
Jane agrees.
§
The third remaining special character is @, occasionally used for accented
characters and other unusual effects, as described below. Finally, \ is reserved
for ‘‘folding lines’’, and is no longer needed but is retained so that old programs
continue to work.
4 If you want to print an actual ~, ^, @ or \, you may need one of the following
‘‘escape sequences’’. A double @ sign followed by a decimal number means the character
with the given ZSCII value, so in particular
@@92
@@94
comes out as ‘‘\’’
comes out as ‘‘^’’
·
comes out as ‘‘@’’
comes out as ‘‘~’’
@@64
@@126
·
·
·
·
A number of Inform games have been written in European languages other
than English, and even English-language games need accented letters from
time to time. Inform can be told via command-line switches to assume that
quoted text in the source code uses any of the ISO 8859-1 to -9 character sets,
which include West and Central European forms, Greek, Arabic, Cyrillic and
Hebrew. The default is ISO Latin-1, which means that you should be able to
type most standard West European letters straight into the source code.
4
If you can’t conveniently type foreign accents on your keyboard, or you want to
make a source code file which could be safely taken from a PC to a Macintosh or vice
versa, you can instead type accented characters using @. (The PC operating system
Windows and Mac OS use incompatible character sets, but this incompatibility would
only affect your source code. A story file, once compiled, behaves identically on PC
and Macintosh regardless of what accented letters it may contain.) Many accented
characters can be written as @, followed by an accent marker, then the letter on which
the accent appears:
@^ put a circumflex on the next letter: a e i o u A E I O or U
@’ put an acute on the next letter: a e i o u y A E I O U or Y
@‘ put a grave on the next letter: a e i o u A E I O or U
@: put a diaeresis on the next letter: a e i o u A E I O or U
@c put a cedilla on the next letter: c or C
@~ put a tilde on the next letter: a n o A N or O
@\ put a slash on the next letter: o or O
@o put a ring on the next letter: a or A
A few other letter-forms are available: German ß (@ss), ligatures (@oe, @ae, @OE, @AE),
Icelandic ‘‘thorn’’ @th and ‘‘eth’’ @et, a pounds-sterling sign (@LL), Spanish inverted
punctuation (@!! and @??) and continental European quotation marks (@<< and @>>):
see Table 2. For instance,
print "Les @oeuvres d’@AEsop en fran@ccais, mon @’el@‘eve!";
print "Na@:ive readers of the New Yorker re@:elected Mr Clinton.";
print "Gau@ss first proved the Fundamental Theorem of Algebra.";
§
Accented characters can also be referred to as constants, like other characters. Just as
’x’ represents the character lower-case-X, so ’@^A’ represents capital-A-circumflex.
44 It takes a really, really good interpreter with support built in for Unicode and
access to the proper fonts to use such a story file, but in principle you can place any
Unicode character into text by quoting its Unicode value in hexadecimal. For instance,
@{a9} produces a copyright sign (Unicode values between $0000 and $00ff are equal
to ISO Latin1 values); @{2657} is a White bishop chess symbol; @{274b} is a ‘‘heavy
eight teardrop-spoked propeller asterisk’’; @{621} is the Arabic letter Hamza, and so
on for around 30,000 more, including vast sets for Pacific Rim scripts and even for
invented ones like Klingon or Tolkien’s Elvish. Of course none of these new characters
are in the regular ZSCII set, but ZSCII is configurable using Inform’s Zcharacter
directive. See §36 for more.
·
·
·
·
·
4 The remaining usage of @ is hacky but powerful. Suppose you are trying to
implement some scenes from Infocom’s spoof of 1930s sci-fi, ‘Leather Goddesses of
Phobos’, where one of the player’s companions has to be called Tiffany if the player is
female, and Trent if male. The name turns up in numerous messages and it would be
tiresome to keep writing
if (female_flag) print "Tiffany"; else print "Trent";
Instead you can use one of Inform’s 32 ‘‘printing-variables’’ @00 to @31. When the text
@14 is printed, for instance, the contents of string 14 are substituted in. The contents
of string 14 can be set using the string statement, so:
if (female_flag) string 14 "Tiffany"; else string 14 "Trent";
...
print "You offer the untangling cream to @14, who whistles.^";
The value specified by a string statement has to be a literal, constant bit of text. There
are really hacky ways to get around this, but if you needed to then you’d probably be
better off with a different solution anyway.
§1.12 The print and print_ret statements
The print and print_ret statements are almost identical. The difference is
that the second prints out a final and extra new-line character, and then causes
a return from the current routine with the value true. Thus, print_ret should
be read as ‘‘print this, print a new-line and then return true’’, and so
print_ret "That’s enough of that.";
§
is equivalent to
print "That’s enough of that.^"; rtrue;
As an abbreviation, it can even be shortened to:
"That’s enough of that.";
Although Inform newcomers are often confused by the fact that this innocently
free-standing bit of text actually causes a return from the current routine, it’s
an abbreviation which pays dividends in adventure-writing situations:
if (fuse_is_lit) { deadflag = true; "The bomb explodes!"; }
"Nothing happens.";
Note that if the source code:
[ Main;
"Hello, and now for a number...";
print 21*764;
];
is compiled, Inform will produce the warning message:
line 3: Warning: This statement can never be reached.
>
print 21*764;
because the bare string on line 2 is printed using print_ret: so the text is
printed, then a new-line is printed, and then a return takes place immediately.
As the warning message indicates, there is no way the statement on line 3 can
ever be executed.
So what can be printed? The answer is a list of terms, separated by
commas. For example,
print "The value is ", value, ".";
contains three terms. A term can take the following forms:
printed as a (signed, decimal) number
ha valuei
htext in double-quotesi printed as text
(hrulei) hvaluei
printed according to some special rule
Inform provides a stock of special printing rules built-in, and also allows the
programmer to create new ones. The most important rules are:
(char)
(string)
(address)
(name)
print out the character which this is the ZSCII code for
print out this string
print out the text of this dictionary word
print out the name of this object (see §3)
Games compiled with the Inform library have several other printing rules builtin (like print (The) ...), but as these aren’t part of the Inform language as
such they will be left until §26.
§
4
print (string) ... requires a little explanation. Of the following lines, the first
two print out ‘‘Hello!’’ but the third prints only a mysterious number:
print (string) "Hello!";
x = "Hello!"; print (string) x;
x = "Hello!"; print x;
This is because strings are internally represented by mysterious numbers. print
(string) means ‘‘interpret this value as the mysterious number of a string, and print
out that string’’: it is liable to give an error, or in some cases print gibberish, if applied
to a value which isn’t the mysterious number of any string.
· · · · ·
Any Inform program can define its own printing rules simply by providing a
routine whose name is the same as that of the rule. For example, the following
pair of routines provides for printing out a value as a four-digit, unsigned
hexadecimal number:
[ hex x y;
y = (x & $7f00) / $100;
if (x<0) y = y + $80;
x = x & $ff;
print (hexdigit) y/$10,
(hexdigit) x/$10,
];
[ hexdigit x;
x = x % $10;
switch (x) {
0 to 9: print x;
10: print "a"; 11:
13: print "d"; 14:
}
];
(hexdigit) y,
(hexdigit) x;
print "b";
print "e";
12: print "c";
15: print "f";
You can paste these two routines into any Inform source code to make these
new printing rules hex and hexdigit available to that code. For example,
print (hex) 16339; will then print up ‘‘3fd3’’, and print (hex) -2; will
print ‘‘fffe’’. Something to look out for is that if you inadvertently write
print hex(16339);
then the text printed will be ‘‘3fd31’’, with a spurious 1 on the end: because
you’ve printed out the return value of the routine hex, which was true, or in
other words 1.
§
§1.13 Other printing statements
Besides print and print_ret, several other statements can also be used for
printing.
new_line
prints a new-line, otherwise known as a carriage return (named for the carriage
which used to move paper across a typewriter). This is equivalent to
print "^"
but is a convenient abbreviation. Similarly,
spaces hnumberi
prints a sequence of the given number of spaces.
box hstring1i . . .hstringni
displays a reverse-video panel in the centre of the screen, containing each
string on its own line. For example, the statement
box "Passio domini nostri" "Jesu Christi Secundum" "Joannem";
displays the opening line of the libretto to Arvo Pärt’s ‘St John Passion’:
Passio domini nostri
Jesu Christi Secundum
Joannem
· · · · ·
Text is normally displayed in an unembellished but legible type intended to
make reading paragraphs comfortable. Its actual appearance will vary from
machine to machine running the story file. On most machines, it will be
displayed using a ‘‘font’’ which is variably-pitched, so that for example a ‘‘w’’
will be wider on-screen than an ‘‘i’’. Such text is much easier to read, but
makes it difficult to print out diagrams. The statement
print "+------------+
^+
Hello
+
^+------------+^";
§
will print something irregular if the letters in ‘‘Hello’’ and the characters ‘‘-’’,
‘‘+’’ and ‘‘ ’’ (space) do not all have the same width on-screen:
+------------+
+ Hello +
+------------+
Because one sometimes does want to print such a diagram, to represent a
sketch-map, say, or to print out a table, the statement font is provided:
font on
font off
font off switches into a fixed-pitch display style. It is now guaranteed that
the interpreter will print all characters at the same width. font on restores the
usual state of affairs.
In addition, you can choose the type style from a small set of possibilities:
roman (the default), boldface, underlined or otherwise emphasized (some
interpreters will use italic for this), reverse-colour:
style
style
style
style
roman
bold
underline
reverse
‘‘Reverse colour’’ would mean, for instance, yellow on blue if the normal
text appearance happened to be blue on yellow. An attempt will be made to
approximate these effects whatever kind of machine is running the story file.
4
Changes of foreground and background colours, so memorably used in games
like Adam Cadre’s ‘Photopia’, can be achieved with care using Z-machine assembly
language. See §42.
§1.14 Generating random numbers
Inform provides a small stock of functions ready-defined, but which are used
much as other routines are. As most of these concern objects, only one will be
give here: random, which has two forms:
random(N)
§
returns a random number in the range 1, 2, . . . , N, each of these outcomes
being (in theory) equally likely. N should be a positive number, between 1 and
32,767, for this to work properly.
random(htwo or more constant quantities, separated by commasi)
returns a random choice from the given selection of constant values. Thus,
print (string) random("red", "blue", "green", "violet");
prints the name of one of these four colours at random. Likewise,
print random(13, 17);
has a 50% chance of printing ‘‘13’’, and a 50% chance of printing ‘‘17’’.
4 Random numbers are produced inside the interpreter by a ‘‘generator’’ which is
not truly random in its behaviour. Instead, the sequence of numbers it produces depend
on a value inside the generator called the ‘‘seed’’. Setting this value is called ‘‘seeding
the random-number generator’’. Whenever it has the same seed value, the same
sequence of random values will be harvested. This is called ‘‘pseudo-randomness’’.
An interpreter normally goes to some effort to start up with a seed value which is
unpredictable: say, the current time of day in milliseconds. Because you just might
want to make it predictable, though, you can change the seed value within the story
file by calling random(N) for a negative number N of your own choosing. (This returns
zero.) Seeding is sometimes useful to test a game which contains random events or
delays. For example if you have release 29 of Infocom’s game ‘Enchanter’, you may
be surprised to know that you can type ‘‘#random 14’’ into it, after which its random
events will be repeatable and predictable. Internally, ‘Enchanter’ does this by calling
random(-14).
§1.15 Deprecated ways to jump around
There are four statements left which control the flow of execution, but one
should try to avoid using any of them if, for instance, a while loop would do
the job more tidily. Please stop reading this section now.
4
Oh very well. ‘Deprecated’ is too strong a word, anyway, as there are circumstances
where jumping is justified and even elegant: to break out of several loops at once, for
instance, or to construct a finite state machine. It’s the gratuitous use of jumping which
is unfortunate, as this rapidly decreases the legibility of the source code.
§
4
The jump statement transfers execution to some named place in the same routine.
(Some programming languages call this goto.) To use jump a notation is needed to
mark particular places in the source code. Such markers are called ‘‘labels’’. For
example, here is a never-ending loop made by hand:
[ Main i;
i=1;
.Marker;
print "I have now printed this ", i++, " times.^";
jump Marker;
];
This routine has one label, Marker. A statement consisting only of a full stop and then
an identifier means ‘‘put a label here and call it this’’. Like local variables, labels belong
to the routines defining them and cannot be used by other routines.
4
The quit statement ends interpretation of the story file immediately, as if a return
had taken place from the Main routine. This is a drastic measure, best reserved for error
conditions so awful that there is no point carrying on.
44 An Inform story file has the ability to save a snapshot of its entire state and to
restore back to that previous state. This snapshot includes values of variables, the point
where code is currently being executed, and so on. Just as we cannot know if the
universe is only six thousand years old, as creationists claim, having been endowed by
God with a carefully faked fossil record, so an Inform story file cannot know if it has
been executing all along or if it was only recently restarted. The statements required
are save and restore:
save hlabeli
restore hlabeli
This is a rare example of an Inform feature which may depend on the host machine’s
state of health: for example, if all disc storage is full, then save will fail. It should always
be assumed that these statements may well fail. A jump to the label provided occurs if
the operation has been a success. This is irrelevant in the case of a restore since, if
all has gone well, execution is now resuming from the successful branch of the save
statement: because that is where execution was when the state was saved.
44 If you don’t mind using assembly language (see §42), you can imitate exceptions,
in the sense of programming languages like Java. The opcodes you would need are
@throw and @catch.
• REFERENCES
If you need to calculate with integers of any size then the restriction of Inform numbers
to the range −32,768 to 32,767 is a nuisance. The function library "longint.h", by
Chris Hall and Francis Irving, provides routines to calculate with signed and unsigned
§
4-byte integers, increasing the range to about ±2,147,000,000. •L. Ross Raszewski’s
function library "ictype.h" is an Inform version of the ANSI C routines "ctype.h",
which means that it contains routines to test whether a given character is lower or
upper case, or is a punctuation symbol, and so forth. (See also the same author’s
"istring.h".)
§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:
[
Extend
Array
Global
Attribute
Object
Class
Property
Constant
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 "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).
4
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.
4
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.
§
4
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.
44 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.
44 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
max
characters
60
6
2
3
4
5
6
7
text typed by player, reduced to lower case
’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:x1:x++) {
c = text_array->(2+x);
print (char) c; if (c == ’o’) print "h";
}
}
];
·
·
·
·
·
§
4
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 have
been 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
max
words
10
2
2
3
4
5
first word
’get’
2
6
7
8
9
second word
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(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
4 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
•Adam Cadre’s function library "flags.h" manages an array of
"ictype.h".)
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.
§3
Objects and classes
Objects make up the substance of the world.
—Ludwig Wittgenstein (1889–1951), Tractatus
§3.1 Objects, classes, metaclasses and nothing
In Inform, objects are little bundles of routines and variables tied
up together. Dividing up the source code into objects is a good way
to organise any large, complicated program, and makes particular
sense for an adventure game, based as it usually is on simulated
items and places. One item in the simulated world corresponds to one
‘‘object’’ in the source code. Each of these pieces of the story file should
take responsibility for its own behaviour, so for instance a brass lamp in an
adventure game might be coded with an Inform object called brass_lamp,
containing all of the game rules which affect the lamp. Then again, objects do
have to interact.
In West Pit
You are at the bottom of the western pit in the twopit room. There is a large
hole in the wall about 25 feet above you.
There is a tiny little plant in the pit, murmuring ‘‘Water, water, . . .’’
In this moment from ‘Advent’, the player is more or less openly invited to
water the plant. There might be many ways to bring water to it, or indeed to
bring liquids other than water, and the rules for what happens will obviously
affect the bottle carrying the water, the water itself and the plant. Where
should the rules appear in the source code? Ideally, the plant object should
know about growing and the bottle about being filled up and emptied. Many
people feel that the most elegant approach is for the bottle, or any other flask,
not to interfere with the plant directly but instead to send a ‘‘message’’ to the
plant to say ‘‘you have been watered’’. It’s then easy to add other solutions
to the same puzzle: a successful rain dance, for instance, could also result in a
‘‘you have been watered’’ message being sent to plant. The whole behaviour
of the plant could be altered without needing even to look at the rain-dance
§
or the bottle source code. Objects like this can frequently be cut out of the
source code for one game and placed into another, still working.
This traffic of messages between objects goes on continuously in Informcompiled adventure games. When the player tries to pick up a wicker cage,
the Inform library sends a message to the cage object asking if it minds being
picked up. When the player tries to go north from the Hall of Mists, the library
sends a message to an object called Hall_of_Mists asking where that would
lead, and so on.
·
·
·
·
·
Typical large story files have so many objects (‘Curses’, for instance, has 550)
that it is convenient to group similar objects together into ‘‘classes’’. For
instance, ‘Advent’ began life as a simulation of part of the Mammoth and Flint
Ridge cave system of Kentucky, caves which contain many, many dead ends.
The Inform source code could become very repetitive without a class like so:
Class DeadEndRoom
with short_name "Dead End",
description "You have reached a dead end.",
cant_go "You’ll have to go back the way you came.";
Leaving the exact syntax for later, this code lays out some common features of
dead ends. All kinds of elegant things can be done with classes if you like, or
not if you don’t.
Objects can belong to several different classes at once, and it is sometimes
convenient to be able to check whether or not a given object belongs to a
given class. For instance, in adventures compiled with the Inform library, a
variable called location always holds the player’s current position, so it might
be useful to do this:
if (location ofclass DeadEndRoom) "Perhaps you should go back.";
·
·
·
·
·
Items and places in an Inform game always belong to at least one class,
whatever you define, because they always belong to the ‘‘metaclass’’ Object.
(‘‘Meta’’ from the Greek for ‘‘beyond’’.) As we shall see, all of the objects
explicitly written out in Inform source code always belong to Object. Though
you seldom need to know this, there are three other metaclasses. Classes turn
out to be a kind of object in themselves, and belong to Class. (So that Class
belongs to itself. If you enjoy object-oriented programming, this will give you
§
a warm glow inside). And although you almost never need to know or care,
routines as in §1 are internally considered as a kind of object, of class Routine;
while strings in double-quotes are likewise of class String. That’s all, though.
If in doubt, you can always find out what kind of object obj is, using the
built-in function metaclass(obj). Here are some example values:
metaclass("Violin Concerto no. 1") == String
metaclass(Main) == Routine
metaclass(DeadEndRoom) == Class
metaclass(silver_bars) == Object
Classes are useful and important, but for most game-designing purposes it’s
now safe to forget all about metaclasses.
It turns out to be useful to have a constant called nothing and meaning
‘‘no object at all’’. It really does mean that: nothing is not an object. If you try
to treat it as one, many programming errors will be printed up when the story
file is played.
4
If X is not an object at all, then metaclass(X) is nothing, and in particular
metaclass(nothing) is nothing.
§3.2 The object tree
Objects declared in the source code are joined together in an ‘‘object tree’’
which grows through every Inform story file. Adventure games use this to
represent which items are contained inside which other items.
It’s conventional to think of this as a sort of family tree without marriages.
Each object has a parent, a child and a sibling. Such a relation is always either
another object in the tree, or else nothing, so for instance the parent of an
orphan would be nothing. Here is a sample object tree:
Meadow
↓ child
sibling
Mailbox
−→
↓ child
Note
Player
↓ child
sibling
Sceptre
−→
Bottle
sibling
−→
sibling
−→
Torch
↓ child
Battery
Stone
The Mailbox and Player are both children of the Meadow, which is their
parent, but only the Mailbox is the child of the Meadow. The Stone is the
sibling of the Torch, which is the sibling of the Bottle, and so on.
§
Inform provides special functions for reading off positions in the tree:
parent, sibling do the obvious things, and child gives the first child:
in addition there’s a function called children which counts up how many
children an object has (note that grandchildren don’t count as children). Here
are some sample values:
parent ( Mailbox )
children ( Player )
child ( Player )
child ( Sceptre )
sibling ( Torch )
==
==
==
==
==
Meadow
4
Sceptre
nothing
Stone
• WARNING
It is incorrect to apply these functions to the value nothing, since it is not an
object. If you write a statement like print children(x); when the value of x
happens to be nothing, the interpreter will print up the message:
[** Programming error: tried to find the ‘‘children’’ of nothing **]
You get a similar error message if you try to apply these tree functions to a
routine, string or class.
§3.3 Declaring objects 1: setting up the tree
Objects are made with the directive Object. Here is a portion of source code,
with the bulk of the definitions abbreviated to ‘‘...’’:
Object
Object
Object
Object
Object
Bucket ...
-> Starfish ...
-> Oyster ...
-> -> Pearl ...
-> Sand ...
The resulting tree looks a little like the source code turned on its side:
Bucket
↓ child
Starfish
sibling
−→
sibling
Oyster
−→
↓ child
Pearl
Sand
§
The idea is that if no arrows -> are given in the Object definition, then the
object has no parent. If one -> is given, then the object is made a child of the
last object defined with no arrows; if two are given, it’s made a child of the last
object defined with only one arrow; and so on.
An object definition consists of a ‘‘head’’ followed by a ‘‘body’’, itself
divided into ‘‘segments’’, though there the similarity with caterpillars ends.
The head takes the form:
Object harrowsi hidentifieri "textual name" hparenti
(1) The harrowsi are as described above. Note that if one or more arrows
are given, that automatically specifies what object this is the child of, so a
hparenti cannot be given as well.
(2) The hidentifieri is what the object can be called inside the program, in the
same way that a variable or a routine has a name.
(3) The "textual name" can be given if the object’s name ever needs to be
printed by the program when it is running.
(4) The hparenti is an object which this new object is to be a child of. This is
an alternative to supplying arrows.
All four parts are optional, so that even this bare directive is legal:
Object;
though it makes a nameless and featureless object which is unlikely to be
useful.
§3.4 Tree statements: move, remove, objectloop
The positions of objects in the tree are by no means fixed: objects are created
in a particular formation but then shuffled around extensively during the story
file’s execution. (In an adventure game, where the objects represent items and
rooms, objects are moved across the tree whenever the player picks something
up or moves around.) The statement
move hobjecti to hobjecti
moves the first-named object to become a child of the second-named one. All
of the first object’s own children ‘‘move along with it’’, i.e., remain its own
children.
§
For instance, starting from the tree as shown in the diagram of §3.2 above,
move Bottle to Mailbox;
results in the tree
Meadow
↓ child
Mailbox
↓ child
sibling
Bottle
−→
sibling
−→
Note
Player
↓ child
sibling
Sceptre
−→
sibling
−→
Torch
↓ child
Battery
Stone
When an object becomes the child of another in this way, it always becomes
the ‘‘eldest’’ child: that is, it is the new child() of its parent, pushing the
previous children over into being its siblings. In the tree above, Bottle has
displaced Note just so.
You can only move one object in the tree to another: you can’t
move Torch to nothing;
because nothing is not an object. Instead, you can detach the Torch branch
from the tree with
remove Torch;
and this would result in:
Meadow
↓ child
Mailbox
↓ child
sibling
Bottle
−→
sibling
−→
Note
Player
↓ child
sibling
Sceptre
−→
Torch
↓ child
Battery
Stone
The ‘‘object tree’’ is often fragmented like this into many little trees, and is not
so much a tree as a forest.
• WARNING
It would make no sense to have a circle of objects each containing the next, so
if you try to move Meadow to Note; then you’ll only move the interpreter to
print up:
[** Programming error: tried to move the Meadow to the note, which would
make a loop: Meadow in note in mailbox in Meadow **]
§
·
·
·
·
·
Since objects move around a good deal, it’s useful to be able to test where an
object currently is, and the condition in is provided for this. For example,
Bottle in Mailbox
is true if and only if the Bottle is one of the direct children of the Mailbox.
(Bottle in Mailbox is true, but Bottle in Meadow is false.) Note that
X in Y
is only an abbreviation for parent(X)==Y but it occurs so often that it’s worth
having. Similarly, X notin Y means parent(X)~=Y. X has to be a bona-fide
member of the object tree, but Y is allowed to be nothing, and testing X in
nothing reveals whether or not X has been removed from the rest of the tree.
·
·
·
·
·
The remaining loop statement left over from §1 is objectloop.
objectloop(hvariable-namei) hstatementi
runs through the hstatementi once for each object in the game, putting each
object in turn into the variable. For example,
objectloop(x) print (name) x, "^";
prints out a list of the textual names of every object in the game. More
powerfully, any condition can be written in the brackets, as long as it begins
with a variable name.
objectloop (x in Mailbox) print (name) x, "^";
prints the names only of those objects which are direct children of the Mailbox
object.
The simple case where the condition reads ‘‘hvariablei in hobjecti’’ is
handled in a faster and more predictable way than other kinds of objectloop:
the loop variable is guaranteed to run through the children of the object in
sibling order, eldest down to youngest. (This is faster because it doesn’t waste
time considering every object in the game, only the children.) If the condition
is not in this form then no guarantee is made as to the order in which the
objects are considered.
§
• WARNING
When looping over objects with in, it’s not safe to move these same objects
around: this is like trying to cut a branch off an elm tree while sitting on it.
Code like this:
objectloop(x in Meadow) move x to Sandy_Beach;
looks plausible but is not a safe way to move everything in the Meadow, and
will instead cause the interpreter to print up
[** Programming error: objectloop broken because the object mailbox was
moved while the loop passed through it **]
Here is a safer way to move the meadow’s contents to the beach:
while (child(Meadow)) move child(Meadow) to Sandy_Beach;
This works because when the Meadow has no more children, its child is then
nothing, which is the same as false.
4
But it moves the eldest child first, with the possibly undesirable result that the
children arrive in reverse order (Mailbox and then Player, say, become Player and then
Mailbox). Here is an alternative, moving the youngest instead of the eldest child each
time, which keeps them in the same order:
while (child(Meadow)) {
x = child(Meadow); while (sibling(x)) x = sibling(x);
move x to Sandy_Beach;
}
Keeping children in order can be worth some thought when game designing. For
instance, suppose a tractor is to be moved to a farmyard in which there is already a
barn. The experienced game designer might do this like so:
move tractor to Farmyard;
move barn to Farmyard;
Although the barn was in the farmyard already, the second statement wasn’t redundant:
because a moved object becomes the eldest child, the statement does this:
Farmyard
↓ child
tractor
sibling
−→
=⇒
barn
Farmyard
↓ child
barn
sibling
−→
tractor
And this is desirable because the ordering of paragraphs in room descriptions tends
to follow the ordering of items in the object tree, and the designer wants the barn
mentioned before the tractor.
§
4 An objectloop range can be any condition so long as a named local or global
variable appears immediately after the open bracket. This means that
objectloop (child(x) == nothing) ...
isn’t allowed, because the first thing after the bracket is child, but a dodge to get
around this is:
objectloop (x && child(x) == nothing) ...
The loop variable of an objectloop can never equal false, because that’s the same as
nothing, which isn’t an object.
44 The objectloop statement runs through all objects of metaclass Object or Class,
but skips any Routine or String.
§3.5 Declaring objects 2: with and provides
So far Objects are just tokens with names attached which can be shuffled
around in a tree. They become interesting when data and routines are attached
to them, and this is what the body of an object definition is for. The body
contains four different kinds of segments, introduced by the keywords:
with
has
class
private
These are all optional and can be given in any order.
4
They can even be given more than once: that is, there can be two or more of a
given kind, which Inform will combine together as if they had been defined in one go.
(This is only likely to be useful for automated Inform-writing programs.)
·
·
·
·
·
The most important segment is with, which specifies variables and, as we shall
see, routines and even arrays, to be attached to the object. For example,
Object magpie "black-striped bird"
with wingspan, worms_eaten;
attaches two variables to the bird, one called wingspan, the other called
worms_eaten. Commas are used to separate them and the object definition
§
as a whole ends with a semicolon, as always. Variables of this kind are called
properties, and are referred to in the source code thus:
magpie.wingspan
magpie.worms_eaten
Properties are just like global variables: any value you can store in a variable
can be stored in a property. But note that
crested_grebe.wingspan
magpie.wingspan
are different and may well have different values, which is why the object whose
wingspan it is (the magpie or the grebe) has to be named.
The property wingspan is said to be provided by both the magpie and
crested_grebe objects, whereas an object whose with segment didn’t name
wingspan would not provide it. The dot . operator can only be used to set the
value of a property which is provided by the object on the left of the dot: if not
a programming error will be printed up when the story file is played.
The presence of a property can be tested using the provides condition.
For example,
objectloop (x provides wingspan) ...
executes the code ... for each object x in the program which is defined with a
wingspan property.
4
Although the provision of a property can be tested, it can’t be changed while the
program is running. The value of magpie.wingspan may change, but not the fact that
the magpie provides a wingspan.
44 Some special properties, known as ‘‘common properties’’, can have their values
read (but not changed) even for an object which doesn’t provide them. All of the
properties built into the Inform library are common properties. See §3.14.
· · · · ·
When the magpie is created as above, the initial values of
magpie.wingspan
magpie.worms_eaten
are both 0. To create the magpie with a given wingspan, we have to specify an
initial value, which we do by giving it after the name, e.g.:
Object magpie "black-striped bird"
with wingspan 5, worms_eaten;
The story file now begins with magpie.wingspan equal to 5, though magpie.worms_eaten still equal to 0.
§
·
·
·
·
·
A property can contain a routine instead of a value. In the definition
Object magpie "black-striped bird"
with name ’magpie’ ’bird’ ’black-striped’ ’black’ ’striped’,
wingspan 5,
flying_strength [;
return magpie.wingspan + magpie.worms_eaten;
],
worms_eaten;
The value of magpie.flying_strength is given as a routine, in square brackets
as usual. Note that the Object continues where it left off after the routineend marker, ]. Routines which are written in as property values are called
‘‘embedded’’ and are the way objects receive messages, as we shall see.
4
If, during play, you want to change the way a magpie’s flying strength is calculated,
you can simply change the value of its property:
magpie.flying_strength = ExhaustedBirdFS;
where ExhaustedBirdFS is the name of a routine to perform the new calculation.
4
Embedded routines are just like ordinary ones, with two exceptions:
(1) An embedded routine has no name of its own, since it is referred to as a property
such as magpie.flying_strength instead.
(2) If execution reaches the ] end-marker of an embedded routine, then it returns
false, not true (as a non-embedded routine would).
44 Properties can be arrays instead of variables. If two or more consecutive values
are given for the same property, it becomes an array. Thus,
Object magpie "black-striped bird"
with name ’magpie’ ’bird’ ’black-striped’ ’black’ ’striped’,
wingspan 5, worms_eaten;
You can’t write magpie.name because there is no single value: rather, there is an -->
array (see §2.4). This array must be accessed using two special operators, .& and .#,
for the array and its length, as follows.
magpie.&name
means ‘‘the array held in magpie’s name property’’, so that the actual name values are
in the entries
magpie.&name-->0, magpie.&name-->1, ..., magpie.&name-->4
§
The size of this array can be discovered with
magpie.#name
which evaluates to the twice the number of entries, in this case, to 10. Twice the
number of entries because that is the number of bytes in the array: people fairly often
use property arrays as byte arrays to save on memory.
4
name is a special property created by Inform, intended to hold dictionary words
which can refer to an object.
§3.6 Declaring objects 3: private properties
4
A system is provided for ‘‘encapsulating’’ certain properties so that only the object
itself has access to them. These are defined by giving them in a segment of the object
declaration called private. For instance,
Object sentry "sentry"
private pass_number 16339,
with challenge [ attempt;
if (attempt == sentry.pass_number)
"Approach, friend!";
"Stand off, stranger.";
];
provides for two properties: challenge, which is public, and pass_number, which can
be used only by the sentry’s own embedded routines.
44 This makes the provides condition slightly more interesting than it appeared in
the previous section. The answer to the question of whether or not
sentry provides pass_number
depends on who’s asking: this condition is true if it is tested in one of the sentry’s own
routines, and elsewhere false. A private property is so well hidden that nobody else
can even know whether or not it exists.
§3.7 Declaring objects 4: has and give
In addition to properties, objects have flags attached, called ‘‘attributes’’.
(Recall that flags are a limited form of variable which can only have two values,
§
sometimes called set and clear.) Unlike property names, attribute names have
to be declared before use with a directive like:
Attribute hungry;
Once this declaration is made, every object in the tree has a hungry flag
attached, which is either true or false at any given time. The state can be
tested with the has condition:
magpie has hungry
is true if and only if the magpie’s hungry flag is currently set. You can also test
if magpie hasnt hungry. There’s no apostrophe in hasnt.
The magpie can now be born hungry, using the has segment in its
declaration:
Object magpie "black-striped bird"
with wingspan, worms_eaten
has hungry;
The has segment contains a list (without commas in between) of the attributes
which are initially set: for instance, the steel grate in the Inform example game
‘Advent’ includes the line
has
static door openable lockable locked;
The state of an attribute can be changed during play using the give
statement:
give magpie hungry;
sets the magpie’s hungry attribute, and
give magpie ~hungry;
clears it again. The give statement can take more than one attribute at a time,
too:
give double_doors_of_the_horizon ~locked openable open;
means ‘‘clear locked and set openable and open’’.†
† The refrain from the prelude to Act I of Philip Glass’s opera Akhnaten is ‘‘Open
are the double doors of the horizon/ Unlocked are its bolts’’.
§
44 An attribute can also have a tilde ~ placed in front in the has part of an object
declaration, indicating ‘‘this is definitely not held’’. This is usually what would have
happened anyway, except that class inheritance (see below) might have passed on an
attribute: if so, this is how to get rid of it again. Suppose there is a whole class of steel
grates like the one in ‘Advent’ mentioned above, providing for a dozen grates scattered
through a game, but you also want a loose grate L whose lock has been smashed. If
L belongs to the class, it will start the game with attributes making it locked like the
others, because the class sets these automatically: but if you include has ~lockable
~locked; in its declaration, these two attributes go away again.
§3.8 Declaring objects 5: class inheritance
A class is a prototype design from which other objects are manufactured. These
resulting objects are sometimes called instances or members of the class, and
are said to inherit from it.
Classes are useful when a group of objects are to have common features.
In the definition of the magpie above, a zoologically doubtful formula was laid
out for flying strength:
flying_strength [;
return magpie.wingspan + magpie.worms_eaten;
],
This formula ought to apply to birds in general, not just to magpies, and in the
following definition it does:
Attribute flightless;
Class Bird
with wingspan 7,
flying_strength [;
if (self has flightless) return 0;
return self.wingspan + self.worms_eaten;
],
worms_eaten;
Bird "ostrich" with wingspan 3, has flightless;
Bird "magpie" with wingspan 5;
Bird "crested grebe";
Bird "Great Auk" with wingspan 15;
Bird "early bird" with worms_eaten 1;
Facts about birds in general are now located in a class called Bird. Every
example of a Bird automatically provides wingspan, a flying_strength
§
routine and a count of worms_eaten. Notice that the Great Auk is not content
with the average avian wingspan of 7, and insists on measuring 15 across. This
is an example of inheritance from a class being over-ridden by a definition
inside the object. The actual values set up are as follows:
B
B.wingspan
B.worms_eaten
ostrich
magpie
crested grebe
Great Auk
early bird
3
5
7
15
7
0
0
0
0
1
Note also the use of the special value self in the definition of Bird. It means
‘‘whatever bird I am’’: if the flying_strength routine is being run for the
ostrich, then self means the ostrich, and so on.
The example also demonstrates a general rule: to create something, begin
its declaration with the name of the class you want it to belong to: a plain
Object, a Class or now a Bird.
4
Sometimes you need to specify that an object belongs to many classes, not just
one. You can do this with the class segment of the definition, like so:
Object "goose that lays the golden eggs"
class Bird Treasure;
This goose belongs to three classes: Object of course, as all declared objects do, but also
Bird and Treasure. (It inherits from Object first and then Bird and then Treasure,
attribute settings and property values from later-mentioned classes overriding earlier
ones, so if these classes should give contradictory instructions then Treasure gets
the last word.) You can also make class definitions have classes, or rather, pass on
membership of other classes:
Class BirdOfPrey
class Bird
with wingspan 15,
people_eaten;
BirdOfPrey kestrel;
makes kestrel a member of both BirdOfPrey and of Bird. Dutiful apostles of objectoriented programming may want to call BirdOfPrey a ‘‘subclass’’ of Bird. Indeed, they
may want to call Inform a ‘‘weakly-typed language with multiple-inheritance’’, or more
probably a ‘‘shambles’’.
§
44 For certain ‘‘additive’’ common properties, clashes between what classes say and
what an instance says are resolved differently: see §5. Inform’s built-in property name is
one of these.
§3.9 Messages
Objects communicate with each other by means of messages. A message has a
sender, a receiver and some parameter values attached, and it always produces
a reply, which is just a single value. For instance,
x = plant.pour_over(cold_spring_water);
sends the message pour_over with a single parameter, cold_spring_water,
to the object plant, and puts the reply value into x.
In order to receive this message, plant has to provide a pour_over
property. If it doesn’t, then the interpreter will print something like
[** Programming error: the plant (object number 21) has no property
pour over to send message **]
when the story file is played. The pour_over property will normally be a
routine, perhaps this one:
pour_over [ liquid;
remove liquid;
switch(liquid) {
oil: "The plant indignantly shakes the oil off its
leaves and asks, ~Water?~";
...
}
];
Inside such a routine, self means the object receiving the message and
sender means the object which sent it. In a typical Inform game situation,
sender will often be the object InformLibrary, which organises play and
sends out many messages to items and places in the game, consulting them
about what should happen next. Much of any Inform game designer’s time is
spent writing properties which receive messages from InformLibrary: before,
after, each_turn and n_to among many others.
You can see all the messages being sent in a game as it runs using the
debugging verb ‘‘messages’’: see §7 for details. This is the Inform version of
listening in on police-radio traffic.
§
4
It was assumed above that the receiving property value would be a routine. But
this needn’t always be true. It can instead be: nothing, in which case the reply value
is also nothing (which is the same as zero and the same as false). Or it can be an
Object or a Class, in which case nothing happens and the object or class is sent back
as the reply value. Or it can be a string in double-quotes, in which case the string is
printed out, then a new-line is printed, and the reply value is true.
4
This can be useful. Here is approximately what happens when the Inform library
tries to move the player northeast from the current room (the location) in an adventure
game (leaving out some complications to do with doors):
if (location provides ne_to) {
x = location.ne_to();
if (x == nothing) "You can’t go that way.";
if (x ofclass Object) move player to x;
} else "You can’t go that way.";
This neatly deals with all of the following cases:
Object Octagonal_Room "Octagonal Room"
with ...
ne_to "The way north-east is barred by an invisible wall!",
w_to Courtyard,
e_to [;
if (Amulet has worn) {
print "A section of the eastern wall suddenly parts
before you, allowing you into...^";
return HiddenShrine;
}
],
s_to [;
if (random(5) ~= 1) return Gateway;
print "The floor unexpectedly gives way, dropping you
through an open hole in the plaster...^";
return random(Maze1, Maze2, Maze3, Maze4);
];
Noteworthy here is that the e_to routine, being an embedded routine, returns false
which is the same as nothing if the ] end-marker is reached, so if the Amulet isn’t
being worn then there is no map connection east.
44 The receiving property can even hold an array of values, in which case the message
is sent to each entry in the array in turn. The process stops as soon as one of these
entries replies with a value other than nothing or false. If every entry is tried and
they all replied nothing, then the reply value sent back is nothing. (This is useful to
the Inform library because it allows before rules to be accumulated from the different
classes an object belongs to.)
§
§3.10 Passing messages up to the superclass
4 It fairly often happens that an instance of a class needs to behave almost, but not
quite, as the class would suggest. For instance, suppose the following Treasure class:
Class Treasure
with deposit [;
if (self provides deposit_points)
score = score + self.deposit_points;
else score = score + 5;
move self to trophy_case;
"You feel a sense of increased esteem and worth.";
];
and we want to create an instance called Bat_Idol which flutters away, resisting
deposition, but only if the room is dark:
Treasure Bat_Idol "jewelled bat idol"
with deposit [;
if (location == thedark) {
remove self;
"There is a clinking, fluttering sound!";
}
...
];
In place of ..., what we want is all of the previous source code about depositing
treasures. We could just copy it out again, but a much neater trick is to write:
self.Treasure::deposit();
Instead of sending the message deposit, we send the message Treasure::deposit,
which means ‘‘what deposit would do if it used the value defined by Treasure’’. The
double-colon :: is called the ‘‘superclass operator’’. (The word ‘‘superclass’’, in this
context, is borrowed from the Smalltalk-80 language.)
44 object.class::property is the value of property which the given object would
normally inherit from the given class. (Or it gives an error if the class doesn’t provide
that property or if the object isn’t a member of that class).
44 It’s perfectly legal to write something like x = Treasure::deposit; and then to
send Bat_Idol.x();.
§
§3.11 Creating and deleting objects during play
In an adventure-game setting, object creation is useful for something like a
beach full of stones: if the player wants to pick up more and more stones, the
game needs to create a new object for each stone brought into play.
Besides that, it is often elegant to grow structures organically. A maze
of caves being generated during play, for example, should have new caves
gradually added onto the map as and when needed.
The trouble with this is that since resources cannot be infinite, the caveobjects have to come from somewhere, and at last they come no more. The
program must be able to cope with this, and it can present the programmer
with real difficulties, especially if the conditions that will prevail when the
supply runs out are hard to predict.
Inform does allow object creation during play, but it insists that the
programmer must specify in advance the maximum resources which will ever
be needed. (For example, the maximum number of beach stones which can
ever be in play.) This is a nuisance, but means that the resulting story file will
always work, or always fail, identically on every machine running it. It won’t
do one thing on the designer’s 256-megabyte Sun workstation in Venice and
then quite another on a player’s personal organiser in a commuter train in New
Jersey.
·
·
·
·
·
If you want to create objects, you need to define a class for them and to specify
N , the maximum number ever needed at once. Objects can be deleted once
created, so if all N are in play then deleting one will allow another to be
created.
Suppose the beach is to contain up to fifty pebbles. Then:
Class Pebble(50)
with name ’smooth’ ’pebble’,
short_name "smooth pebble from the beach";
Pebble is an ordinary class in every respect, except that it has the ability to
create up to N = 50 instances of itself.
Creating and destroying objects is done by sending messages to the class
Pebble itself, so for instance sending the message Pebble.create() will bring
another pebble into existence. Classes can receive five different messages, as
follows:
§
remaining()
How many more instances of this class can be created?
create(hparametersi)
Replies with a newly created object of this class, or else with nothing if no
more can be created. If given, the parameters are passed on to the object so
that it can use them to configure itself (see below).
destroy(I)
Destroys the instance I, which must previously have been created. You can’t
destroy an object which you defined by hand in the source code. (This is quite
unlike remove, which only takes an object out of the tree for a while but keeps
it in existence, ready to be moved back again later.)
recreate(I, hparametersi)
Re-initialises the instance I, as if it had been destroyed and then created again.
copy(I, J)
Copies the property and attribute values from I to be equal to those of J,
where both have to be instances of the class. (If a property holds an array, this
is copied over as well.)
4 It’s rather useful that recreate and copy can be sent for any instances, not just
instances which have previously been created. For example,
Plant.copy(Gilded_Branch, Poison_Ivy)
copies over all the features of a Plant from Poison_Ivy to Gilded_Branch, but leaves
any other properties and attributes of the gilded branch alone. Likewise,
Treasure.recreate(Gilded_Branch)
only resets the properties to do with Treasure, leaving the Plant properties alone.
4 If you didn’t give a number like (50) in the class definition, then you’ll find that
N is zero. copy will work as normal, but remaining will return zero and create will
always return nothing. There is nothing to destroy and since this isn’t a class which
can create objects, recreate will not work either. (Oh, and don’t try to send these
messages to the class Class: creating and destroying classes is called ‘‘programming’’,
and it’s far too late when the game is already being played.)
44 You can even give the number as (0). You might do this either so that Class
Gadget(MAX_GADGETS) in some library file will work even if the constant MAX_GADGETS
happens to be zero. Or so that you can at least recreate existing members of the class
even if you cannot create new ones.
§
·
·
·
·
·
The following example shows object creation used in a tiny game, dramatising
a remark attributed to Isaac Newton (though it appears only in Brewster’s
Memoirs of Newton).
Constant Story "ISAAC NEWTON’S BEACH";
Constant Headline "^An Interactive Metaphor^";
Include "Parser";
Include "VerbLib";
Class Pebble(50)
with name ’smooth’ ’pebble’ ’stone’ ’pebbles//p’ ’stones//p’,
short_name "smooth pebble from the beach",
plural "smooth pebbles from the beach";
Object Shingle "Shingle"
with description
"You seem to be only a boy playing on a sea-shore, and
diverting yourself in finding a smoother pebble or a
prettier shell than ordinary, whilst the great ocean of
truth lies all undiscovered before you.",
has light;
Object -> "pebble"
with name ’smoother’ ’pebble’ ’stone’ ’stones’ ’shingle’,
initial "The breakers drain ceaselessly through the shingle,
spilling momentary rock-pools.",
before [ new_stone;
Take:
new_stone = Pebble.create();
if (new_stone == nothing)
"You look in vain for a stone smoother than
the fifty ever-smoother stones you have
gathered so far.";
move new_stone to Shingle;
<>;
],
has static;
[ Initialise;
location = Shingle; "^^^^^Welcome to...^";
];
Include "Grammar";
In this very small adventure game, if the player types ‘‘take a pebble’’, he will
get one: more surprisingly, if he types ‘‘take a smoother pebble’’ he will get
another one, and so on until his inventory listing reads ‘‘fifty smooth pebbles
§
from the beach’’. (See §29 for how to make sure identical objects are described
well in Inform adventure games.) Notice that a newly-created object is in
nothing, that is, is outside the object tree, so it must be moved to Shingle in
order to come to the player’s hand.
·
·
·
·
·
However smooth, one pebble is much like another. More complicated objects
sometimes need some setting-up when they are created, and of course in good
object-oriented style they ought to do this setting-up for themselves. Here is a
class which does:
Class Ghost(7)
with haunting,
create [;
self.haunting = random(Library, Ballroom, Summer_House);
move self to self.haunting;
if (self in location)
"^The air ripples as if parted like curtains.";
];
What happens is that when the program sends the message
Ghost.create();
the class Ghost creates a new ghost G, if there aren’t already seven, and then
sends a further message
G.create();
This new object G chooses its own place to haunt and moves itself into place.
Only then does the class Ghost reply to the outside program. A class can also
give a destroy routine to take care of the consequences of destruction, as in
the following example:
Class Axe(30);
Class Dwarf(7)
with create [ x;
x = Axe.create(); if (x ~= nothing) move x to self;
],
destroy [ x;
objectloop (x in self && x ofclass Axe) Axe.destroy(x);
];
§
A new axe is created whenever a new dwarf is created, while stocks last, and
when a dwarf is destroyed, any axes it carries are also destroyed.
Finally, you can supply create with up to 3 parameters. Here is a case
with only one:
Class GoldIngot(10)
with weight, value,
create [ carats;
self.value = 10*carats;
self.weight = 20 + carats;
];
and now GoldIngot.create(24) will create a 24-carat gold ingot.
§3.12 Sending messages to routines and strings
4 §3.9 was about sending messages to Objects, and then in §3.11 it turned out that
there are five messages which can be sent to a Class. That’s two of the four metaclasses,
and it turns out that you can send messages to a Routine and a String too.
The only message you can send to a Routine is call, and all this does is to call it.
So if Explore is the name of a routine,
Explore.call(2, 4);
and
Explore(2, 4);
do exactly the same as each other. This looks redundant, except that it allows a little
more flexibility: for instance
(random(Hello, Goodbye)).call(7);
has a 50% chance of calling Hello(7) and a 50% chance of calling Goodbye(7). As
you might expect, the call message replies with whatever value was returned by the
routine.
Two different messages can be sent to a String. The first is print, which is
provided because it logically ought to be, rather than because it’s useful. So, for
example,
("You can see an advancing tide of bison!").print();
prints out the string, followed by a new-line, and evaluates as true.
The second is print_to_array. This copies out the text of the string into entries
2, 3, 4, . . . of the supplied byte array, and writes the number of characters as a word
§
into entries 0 and 1. (Make sure that the byte array is large enough to hold the text of
the string.) For instance:
Array Petals->30;
...
("A rose is a rose is a rose").print_to_array(Petals);
will leave Petals-->0 set to 26 and place the letters ’A’, ’ ’, ’r’, ’o’, . . ., ’e’ into
the entries Petals->2, Petals->3, . . ., Petals->27. For convenience, the reply value
of the message print_to_array is also 26. You can use this message to find the length
of a string, copying the text into some temporary array and only making use of this
return value.
§3.13 Common properties and Property
44 Many classes, the Bird class for example, pass on properties to their members.
Properties coming from the class Object are called ‘‘common properties’’. Every
item and place in an adventure game belongs to class Object, so a property inherited
from Object will be not just common but well-nigh universal. Properties which aren’t
common are sometimes called ‘‘individual’’.
The Inform library sets up the class Object with about fifty common properties.
Story files would be huge if all of the objects in a game actually used all of these
common properties, so a special rule applies: you can read a common property for any
Object, but you can only write to it if you’ve written it into the object’s declaration yourself.
For instance, the library contains the directive
Property cant_go "You can’t go that way.";
This tells Inform to add cant_go to the class definition for Object. The practical
upshot is that you can perform
print_ret (string) location.cant_go;
whatever the location is, and the resulting text will be ‘‘You can’t go that way.’’ if the
location doesn’t define a cant_go value of its own. On the other hand
location.cant_go = "Please stop trying these blank walls.";
will only work if location actually provides a cant_go value of its own, which you can
test with the condition location provides cant_go.
§
44 Using the superclass operator you can read and even alter the default values of
common properties at run-time: for instance,
location.Object::cant_go = "Another blank wall. Tsk!";
will substitute for ‘‘You can’t go that way.’’
44 The Inform library uses common properties because they’re marginally faster to
access and marginally cheaper on memory. Only 62 are available, of which the compiler
uses 3 and the library a further 47. On the other hand, you can have up to 16,320
individual properties, which in practical terms is as good as saying they are unlimited.
§3.14 Philosophy
44 ‘‘Socialism is all very well in practice, but does it work in theory?’’ (Stephen Fry).
While the chapter is drizzling out into small type, this last section is aimed at those
readers who might feel happier with Inform’s ideas about classes and objects if only
there were some logic to it all. Other readers may feel that it’s about as relevant to
actual Inform programming as socialist dialectic is to shopping for groceries. Here are
the rules anyway:
(1) Story files are made up of objects, which may have variables attached of various
different kinds, which we shall here call ‘‘properties’’.
(2) Source code contains definitions of both objects and classes. Classes are abstract
descriptions of common features which might be held by groups of objects.
(3) Any given object in the program either is, or is not, a member of any given class.
(4) For every object definition in the source code, an object is made in the story file.
The definition specifies which classes this object is a member of.
(5) If an object X is declared as a member of class C, then X ‘‘inherits’’ property values
as given in the class definition of C.
Exact rules of inheritance aren’t relevant here, except perhaps to note that one of the
things inherited from class C might be the membership of some other class, D.
(6) For every class definition, an object is made in the story file to represent it, called
its ‘‘class-object’’.
For example, suppose we have a class definition like:
Class Shrub
with species;
The class Shrub will generate a class-object in the final program, also called Shrub.
This class-object exists to receive messages like create and destroy and, more
§
philosophically, to represent the concept of ‘‘being a shrub’’ within the simulated
world.
The class-object of a class is not normally a member of that class. The concept of
being a shrub is not itself a shrub, and the condition Shrub ofclass Shrub is false.
Individual shrubs provide a property called species, but the class-object of Shrub does
not: the concept of being a shrub has no single species.
(7) Classes which are automatically defined by Inform are called ‘‘metaclasses’’. There
are four of these: Class, Object, Routine and String.
It follows by rule (6) that every Inform program contains the class-objects of these four,
also called Class, Object, Routine and String.
(8) Every object is a member of one, and only one, metaclass:
(8.1) The class-objects are members of Class, and no other class.
(8.2) Routines in the program, including those given as property values, are
members of Routine and no other class.
(8.3) Constant strings in the program, including those given as property values,
are members of String, and of no other class.
(8.4) The objects defined in the source code are members of Object, and possibly
also of other classes defined in the source code.
It follows from (8.1) that Class is the unique class whose class-object is one of its own
members: so Class ofclass Class is true.
(9) Contrary to rules (5) and (8.1), the class-objects of the four metaclasses do not
inherit from Class.
(10) Properties inherited from the class-object of the metaclass Object are read-only
and cannot be set.
·
·
·
·
·
To see what the rules entail means knowing the definitions of the four metaclasses.
These definitions are never written out in any textual form inside Inform, as it happens,
but this is what they would look like if they were. (Metaclass is an imaginary directive,
as the programmer isn’t allowed to create new metaclasses.)
Metaclass Object
with name,
...;
A class from which the common properties defined by Property are inherited, albeit
(by rule (10), an economy measure) in read-only form.
Metaclass Class
with create
[ ...; ... ],
recreate [ instance ...; ... ],
destroy
[ instance; ... ],
copy
[ instance1 instance2; ... ],
remaining [; ... ];
§
So class-objects respond only to these five messages and provide no other properties:
except that by rule (9), the class-objects Class, Object, Routine and String provide
no properties at all. The point is that these five messages are concerned with object
creation and deletion at run time. But Inform is a compiler and not, like Smalltalk-80 or
other highly object-oriented languages, an interpreter. Rule (9) expresses our inability
to create the program while it is actually running.
Metaclass Routine
with call [ parameters...; ... ];
Routines therefore provide only call.
Metaclass String
with print
[; print_ret (string) self; ],
print_to_array [ array; ... ];
Strings therefore provide only print and print_to_array.
• REFERENCES
L. Ross Raszewski’s library extension "imem.h" manages genuinely dynamic memory
allocation for objects, and is most often used when memory is running terribly short.
Chapter II: Introduction to Designing
But ‘why then publish?’ There are no rewards
Of fame or profit when the world grows weary.
I ask in turn why do you play at cards?
Why drink? Why read? To make some hour less dreary.
It occupies me to turn back regards
On what I’ve seen or pondered, sad or cheery,
And what I write I cast upon the stream
To swim or sink. I have had at least my dream.
— Lord Byron (1788–1824), Don Juan, canto XIV
§4
‘Ruins’ begun
This chapter introduces five fundamentals of Inform: how to
construct games; messages, classes and actions; and how to debug
games. Chapter III then makes a systematic exploration of the
model world available to Inform games. Throughout both chapters,
examples gradually build up a modest game called ‘Ruins’, a tale of Central
American archaeology in the 1930s. Here is its first state:
Constant Story "RUINS";
Constant Headline "^An Interactive Worked Example^
Copyright (c) 1999 by Angela M. Horns.^";
Include "Parser";
Include "VerbLib";
Object Forest "~Great Plaza~"
with description
"Or so your notes call this low escarpment of limestone,
but the rainforest has claimed it back. Dark olive
trees crowd in on all sides, the air steams with the
mist of a warm recent rain, midges hang in the air.
~Structure 10~ is a shambles of masonry which might
once have been a burial pyramid, and little survives
except stone-cut steps leading down into darkness below.",
§
has light;
[ Initialise;
location = Forest;
"^^^Days of searching, days of thirsty hacking through the briars of
the forest, but at last your patience was rewarded. A discovery!^";
];
Include "Grammar";
If you can compile this tiny beginning successfully, Inform is probably set up
and working properly on your computer. Compilation may take a few seconds,
because although you have only written twenty lines or so, the Include
directives paste in another seven and a half thousand. This is ‘‘the Library’’,
a computer program which acts as umpire during play. The library is divided
into three parts:
Parser
VerbLib
Grammar
which decodes what the player types;
how actions, like ‘‘take’’ or ‘‘go north’’, work;
the verbs and phrases which the game understands.
It does matter what order the three lines beginning with Include come in, and
it sometimes matters where your own code goes with respect to them: objects
shouldn’t be declared until after the inclusion of the parser, for instance. For
now, follow the structure above, with everything interesting placed between
the inclusions of VerbLib and Grammar.
·
·
·
·
·
The two constants at the beginning are text giving the game’s name and
copyright message, which the library needs in order to print out the ‘‘banner’’
announcing the game. Similarly, the library expects to find a routine named
Initialise somewhere in your source code. This routine is called when the
game starts up, and is expected to carry out any last setting-up operations
before play begins. In most games, it also prints up a ‘welcome’ message, but
the one thing it has to do is to set the location variable to the place where
the player begins. And this means that every game has to declare at least one
object, too: the room where the player begins.
4 In this book places are often called ‘‘rooms’’ even when outdoors (like Forest) or
underground. This goes back at least to Stephen Bishop’s 1842 map of the Mammoth
and Flint Ridge cave system of Kentucky, which was the setting of the first adventure
game, ‘Advent’, also called ‘Colossal Cave’ (1973). The author, Will Crowther, was a
caver and used the word ‘‘room’’ in its caving sense. Don Woods, who recast the game
in 1976–7, confused the word further with its everyday sense. Players of adventure
games continue to call locations ‘‘rooms’’ to this day.
§
·
·
·
·
·
‘Ruins’ is at this stage an exceedingly dull game:
Days of searching, days of thirsty hacking through the briars of the forest,
but at last your patience was rewarded. A discovery!
RUINS
An Interactive Worked Example
Copyright (c) 1998 by Angela M. Horns.
Release 1 / Serial number 990220 / Inform v6.20 Library 6/8
‘‘Great Plaza’’
Or so your notes call this low escarpment of limestone, but the rainforest has
claimed it back. Dark olive trees crowd in on all sides, the air steams with
the mist of a warm recent rain, midges hang in the air. ‘‘Structure 10’’ is
a shambles of masonry which might once have been a burial pyramid, and
little survives except stone-cut steps leading down into darkness below.
>inventory
You are carrying nothing.
>north
You can’t go that way.
>wait
Time passes.
>quit
Are you sure you want to quit? yes
·
·
·
·
·
In an Inform game, objects are used to simulate everything: rooms and items
to be picked up, scenery, the player, intangible things like mist and even some
abstract ideas, like the direction ‘‘north’’ or the idea of ‘‘darkness’’. The library
itself is present as an object, called InformLibrary, though like the concept of
‘‘north’’ it cannot be picked up or visited during play. All told, ‘Ruins’ already
contains twenty-four objects.
It is time to add something tangible, by writing the following just after the
definition of Forest:
Object -> mushroom "speckled mushroom"
with name ’speckled’ ’mushroom’ ’fungus’ ’toadstool’;
The arrow -> means that the mushroom begins inside the previous object,
which is to say, the Forest. If the game is recompiled, the mushroom is now in
play: the player can call it ‘‘speckled mushroom’’, ‘‘mushroom’’, ‘‘toadstool’’
and so on. It can be taken, dropped, looked at, looked under and so on.
§
However, it only adds the rather plain line ‘‘There is a speckled mushroom
here.’’ to the Forest’s description. Here is a more decorative species:
Object -> mushroom "speckled mushroom"
with name ’speckled’ ’mushroom’ ’fungus’ ’toadstool’,
initial
"A speckled mushroom grows out of the sodden earth, on
a long stalk.";
The initial message is used to tell the player about the mushroom when
the Forest is described. (Once the mushroom has been picked or moved,
the message is no longer used: hence the name ‘initial’.) The mushroom
is, however, still ‘‘nothing special’’ when the player asks to ‘‘look at’’ or
‘‘examine’’ it. To provide a more interesting close-up view, we must give the
mushroom its own description:
Object -> mushroom "speckled mushroom"
with name ’speckled’ ’mushroom’ ’fungus’ ’toadstool’,
initial
"A speckled mushroom grows out of the sodden earth, on
a long stalk.",
description
"The mushroom is capped with blotches, and you aren’t
at all sure it’s not a toadstool.",
has edible;
Now if we examine the mushroom, as is always wise, we get a cautionary hint.
But the edible notation means that it can be eaten, so that for the first time
the player can change the game state irrevocably: from a game with a forest
and a mushroom into a game with just a forest.
The mushroom shows the two kinds of feature something can have: a
‘‘property’’ with some definite value or list of values and an ‘‘attribute’’, which
is either present or not but has no particular value. name, initial and
description are all properties, while light and edible are attributes. The
current state of these properties changes during play: for instance, it can be
changed by code like the following.
mushroom.description = "You’re sure it’s a toadstool now.";
give mushroom light;
if (mushroom has edible) print "It’s definitely edible.^";
light is the attribute for ‘‘giving off light’’. The Forest was defined as having
light on account of daylight, so it doesn’t much matter whether or not the
mushroom has light, but for the sake of botanical verisimilitude it won’t have
light in the final game.
§
·
·
·
·
·
Declaring objects has so far been a matter of filling in forms: fill some text into
the box marked description, and so on. We could go much further like this,
but for the sake of example it’s time to add some rules:
after [;
Take: "You pick the mushroom, neatly cleaving its thin stalk.";
Drop: "The mushroom drops to the ground, battered slightly.";
],
The property after doesn’t just have a string for a value: it has a routine of
its own. What happens is that after something happens to the mushroom, the
library asks the mushroom if it would like to react in some way. In this case,
it reacts only to Take and Drop, and the only effect is that the usual messages
(‘‘Taken.’’ ‘‘Dropped.’’) are replaced by new ones. (It doesn’t react to Eat, so
nothing out of the ordinary happens when it’s eaten.) ‘Ruins’ can now manage
a briefly plausible dialogue:
‘‘Great Plaza’’
Or so your notes call this low escarpment of limestone, but the rainforest has
claimed it back. Dark olive trees crowd in on all sides, the air steams with
the mist of a warm recent rain, midges hang in the air. ‘‘Structure 10’’ is
a shambles of masonry which might once have been a burial pyramid, and
little survives except stone-cut steps leading down into darkness below.
A speckled mushroom grows out of the sodden earth, on a long stalk.
>get mushroom
You pick the mushroom, neatly cleaving its thin stalk.
>look at it
The mushroom is capped with blotches, and you aren’t at all sure it’s not a
toadstool.
>drop it
The mushroom drops to the ground, battered slightly.
4
Gareth Rees persuasively advocates writing this sort of transcript, of an ideal
sequence of play, first, and worrying about how to code up the design afterwards. Other
designers prefer to build from the bottom up, crafting the objects one at a time and
finally bringing them together into the narrative.
·
·
·
·
·
The mushroom is a little more convincing now, but still does nothing. Here is
a more substantial new rule:
§
before [;
Eat: if (random(100) <= 30) {
deadflag = true;
"The tiniest nibble is enough. It was a toadstool,
and a poisoned one at that!";
}
"You nibble at one corner, but the curious taste
repels you.";
],
The library consults before just before the player’s intended action would
take place. So when the player tries typing, say, ‘‘eat the mushroom", what
happens is: in 30% of cases, death by toadstool poisoning; and in the other
70%, a nibble of a corner of fungus, without consuming it completely.
Like location, deadflag is a variable belonging to the library. It’s
normally false, meaning that the player is still alive and playing. Setting it to
true thus kills the player. (Setting it to 2 causes the player to win the game
and there are other uses: see §21.)
If the ‘‘tiniest nibble’’ text is printed, the rule ends there, and does not
flow on into the second ‘‘You nibble at’’ text. So one and only one message is
printed. Here is how this is achieved: although it’s not obvious from the look
of the program, the before routine is being asked the question ‘‘Do you want
to interfere with the usual rules?’’. It must reply, that is, return, either true or
false meaning yes or no. Because this question is asked and answered many
times in a large Inform game, there are several abbreviations for how to reply.
For example,
return true;
and
rtrue;
both do the same thing. Moreover,
print_ret "The tiniest nibble... ...at that!";
performs three useful tasks: prints the message, then prints a carriage return,
and then returns true. And this is so useful that a bare string
"The tiniest nibble... ...at that!";
§
is understood to mean the same thing. To print the text without returning, the
statement print has to be written out in full. Here is an example:
before [;
Taste: print "You extend your tongue nervously.^";
rfalse;
];
In this rule, the text is printed, but the answer to ‘‘Do you want to interfere?’’
is no, so the game will then go on to print something anodyne like ‘‘You taste
nothing unexpected.’’ (In fact the rfalse was unnecessary, because if a rule
like this never makes any decision, then the answer is assumed to be false.)
• EXERCISE 1
The present after routine for the mushroom is misleading, because it says the
mushroom has been picked every time it’s taken (which will be odd if it’s taken,
dropped then taken again). Correct this.
·
·
·
·
·
The following example of ‘‘form-filling’’ is typical of the way that the library
provides for several standard kinds of object. This one is a kind of door, which
will be gone into properly in §13, but for now suffice to say that a door doesn’t
literally have to be a door: it can be any object which comes in between where
the player is and where the player can go. Because the object is also marked as
scenery (see §8), it isn’t given any special paragraph of description when the
Forest is described. Finally, it is marked as static to prevent the player from
being able to pick it up and walk off with it.
Object -> steps "stone-cut steps"
with name ’steps’ ’stone’ ’stairs’ ’stone-cut’ ’pyramid’ ’burial’
’structure’ ’ten’ ’10’,
description
"The cracked and worn steps descend into a dim chamber.
Yours might be the first feet to tread them for five
hundred years.",
door_to Square_Chamber,
door_dir d_to
has scenery static door open;
We also need to add a new line to the Forest’s definition to tell it that the way
down is by these steps:
Object Forest "~Great Plaza~"
...
d_to steps,
§
Now ‘‘examine structure 10’’, ‘‘enter stone-cut pyramid’’ and so forth will all
work.
• EXERCISE 2
Except of course that now ‘Ruins’ won’t compile, because Inform expects to find a
room called Square_Chamber which the steps lead to. Design one.
§5
Introducing messages and classes
On a round ball
A workman that hath copies by, can lay
An Europe, Afrique and an Asia,
And quickly make that, which was nothing, All.
— John Donne (1571?–1631), Valediction: Of Weeping
Though §4 was a little vague in saying ‘‘the library asks the mushroom if it would like to react’’, something basic was happening in
Inform terms: the object InformLibrary was sending the message
before to the object mushroom. Much more on how actions take
place in §6, but here is roughly what the library does if the player types ‘‘eat
mushroom’’:
if (mushroom.before() == false) {
remove mushroom;
if (mushroom.after() == false)
print "You eat the mushroom. Not bad.^";
}
The library sends the message before to ask the mushroom if it minds being
eaten; then, if not, it consumes the mushroom; then it sends the message
after to ask if the mushroom wishes to react in some way; then, if not, it
prints the usual eating-something text. In response to the messages before
and after, the mushroom is expected to reply either true, meaning ‘‘I’ll take
over from here’’, or false, meaning ‘‘carry on’’.
Most of the other properties in §4 are also receiving messages. For
example, the message
mushroom.description();
is sent when the player tries to examine the mushroom: if the reply is false then
the library prints ‘‘You see nothing special about the speckled mushroom.’’
Now the mushroom was set up with
description
"The mushroom is capped with blotches, and you aren’t at all
sure it’s not a toadstool.",
§
which doesn’t look like a rule for receiving a message, but it is one all the
same: it means ‘‘print this text out, print a new-line and reply true’’. A more
complicated rule could have been given instead, as in the following elaboration
of the stone-cut steps in ‘Ruins’:
description [;
print "The cracked and worn steps descend into a dim chamber.
Yours might ";
if (Square_Chamber hasnt visited)
print "be the first feet to tread";
else print "have been the first feet to have trodden";
" them for five hundred years. On the top step is inscribed
the glyph Q1.";
],
visited is an attribute which is currently held only by rooms which the player
has been to. The glyphs will come into the game later on.
The library can send out about 40 different kinds of message, before
and description being two of these. The more interesting an object is, the
more ingeniously it will respond to these messages. An object which ignores
all incoming messages will be lifeless and inert in play, like a small stone.
4
Some properties are just properties, and don’t receive messages. Nobody ever
sends a name message, for instance: the name property is just what it seems to be, a list
of words.
·
·
·
·
·
So the library is sending out messages to your objects all the time during play.
Your objects can also send each other messages, including ‘‘new’’ ones that
the library would never send. It’s sometimes convenient to use these to trigger
off happenings in the game. For example, one way to provide hints in ‘Ruins’
might be to include a macaw which squawks from time to time, for a variety of
reasons:
Object -> macaw "red-tailed macaw"
with name ’red’ ’tailed’ ’red-tailed’ ’macaw’ ’bird’,
initial "A red-tailed macaw eyes you from an upper branch.",
description "Beautiful plumage.",
before [;
Take: "The macaw flutters effortlessly out of reach.";
],
squawk [ utterance;
if (self in location)
§
print "The macaw squawks, ~", (string) utterance,
"! ", (string) utterance, "!~^^";
has
],
animate;
(For the final version of ‘Ruins’ the designer thought better of the macaw and
removed it, but it still makes a good example.) We might then, for instance,
change the after rule for dropping the mushroom to read:
Drop: macaw.squawk("Drop the mushroom");
"The mushroom drops to the ground, battered slightly.";
so that the maddening creature would squawk ‘‘Drop the mushroom! Drop
the mushroom!’’ each time this was done. At present it would be an error
to send a squawk message to any object other than the macaw, since only the
macaw has been given a rule telling it what to do if it receives one.
·
·
·
·
·
In most games there are groups of objects with certain rules in common, which
it would be tiresome to have to write out many times. For making such a
group, a class definition is simpler and more elegant. These closely resemble
object definitions, but since they define prototypes rather than actual things,
they have no initial location. (An individual tree may be somewhere, but the
concept of being a tree has no particular place.) So the ‘header’ part of the
definition is simpler.
For example, the scoring system in ‘Ruins’ works as follows: the player,
an archaeologist of the old school, gets a certain number of points for each
‘treasure’ (i.e., cultural artifact) he can filch and put away into his packing
case. Treasures clearly have rules in common, and the following class defines
them:
Class Treasure
with cultural_value 5, photographed_in_situ false,
before [;
Take, Remove:
if (self in packing_case)
"Unpacking such a priceless artifact had best wait
until the Carnegie Institution can do it.";
if (self.photographed_in_situ == false)
"This is the 1930s, not the bad old days. Taking an
artifact without recording its context is simply
looting.";
Photograph:
§
if (self has moved)
"What, and fake the archaeological record?";
if (self.photographed_in_situ) "Not again.";
],
after [;
Insert:
if (second == packing_case)
{
score = score + self.cultural_value;
"Safely packed away.";
}
Photograph: self.photographed_in_situ = true;
];
(The packing case won’t be defined until §12, which is about containers.) Note
that self is a variable, which always means ‘‘whatever object I am’’. If we
used it in the definition of the mushroom it would mean the mushroom: used
here, it means whatever treasure happens to be being dealt with. Explanations
about Insert and Remove will come later (in §12). The action Photograph is
not one of the standard actions built in to the library, and will be added to
‘Ruins’ in the next section.
An object of the class Treasure automatically inherits the properties and
attributes given in the class definition. Here for instance is an artifact which
will eventually be found in the Stooped Corridor of ‘Ruins’:
Treasure -> statuette "pygmy statuette"
with name ’snake’ ’mayan’ ’pygmy’ ’spirit’ ’precious’ ’statuette’,
description
"A menacing, almost cartoon-like statuette of a pygmy spirit
with a snake around its neck.",
initial "A precious Mayan statuette rests here!";
From Treasure, this statuette inherits a cultural_value score of 5 and the
rules about taking and dropping treasures. If it had itself set cultural_value
to 15, say, then the value would be 15, because the object’s actual definition
always takes priority over anything the class might have specified. Another
of the five ‘Ruins’ treasures, which will be found in the Burial Shaft, has a
subtlety in its definition:
Treasure -> honeycomb "ancient honeycomb"
with article "an",
name ’ancient’ ’old’ ’honey’ ’honeycomb’,
description "Perhaps some kind of funerary votive offering.",
initial
"An exquisitely preserved, ancient honeycomb rests here!",
§
has
after [;
Eat: "Perhaps the most expensive meal of your life. The
honey tastes odd, perhaps because it was used to
store the entrails of the Lord buried here, but still
like honey.";
],
edible;
The subtlety is that the honeycomb now has two after rules: a new one of its
own, plus the existing one that all treasures have. Both apply, but the new one
happens first.
44 So comparing cultural_value and after, there seems to be an inconsistency.
In the case of cultural_value, an object’s own given value wiped out the value from
the class, but in the case of after, the two values were joined up into a list. Why?
The reason is that some of the library’s properties are ‘‘additive’’, so that their values
accumulate into a list when class inheritance takes place. Three useful examples are
before, after and name.
44 Non-library properties you invent (like squawk or cultural_value) will never be
additive, unless you declare them so with a directive like
Property additive squawk;
before squawk is otherwise mentioned. (Or you could imitate similar kinds of inheritance
using the superclass operator.)
• REFERENCES
See ‘Balances’ for an extensive use of message-sending, and defines several complicated
classes: see especially the white cube, spell and scroll classes.
•‘Advent’ has a
treasure-class similar to this one, and uses class definitions for the many similar maze
and dead-end rooms, as well as the sides of the fissure. •‘Toyshop’ contains one easy
class (the wax candles) and one unusually hard one (the building blocks).
•Class
definitions can be worthwhile even when as few as two objects use them, as can be seen
from the two kittens in ‘Alice Through the Looking-Glass’.
§6
Actions and reactions
Only the actions of the just
Smell sweet and blossom in their dust.
— James Shirley (1594–1666),
The Contention of Ajax and Ulysses
[Greek is] a language obsessed with action, and with the joy of seeing action
multiply from action, action marching relentlessly ahead and with yet more
actions filing in from either side to fall into neat step at the rear, in a long
straight rank of cause and effect, to what will be inevitable, the only possible
end.
— Donna Tartt, The Secret History
Inform is a language obsessed with actions. An ‘action’ is an attempt
to perform one simple task: for instance,
Inv
Take sword
Insert gold_coin cloth_bag
are all examples. Here the actual actions are Inv (inventory), Take and Insert.
An action has none, one or two objects supplied with it (or, in a few special
cases, some numerical information rather than objects). It also has an ‘‘actor’’,
the person who is to perform the action, usually the player. Most actions are
triggered off by the game’s parser, whose job can be summed up as reducing
the player’s keyboard commands to actions: ‘‘take my hat off’’, ‘‘remove
bowler’’ or ‘‘togli il cappello’’ (if in an Italian game) might all cause the same
action. Some keyboard commands, like ‘‘drop all’’, cause the parser to fire
off whole sequences of actions: others, like ‘‘empty the sack into the umbrella
stand’’, cause only a single action but one which may trigger off an avalanche
of other actions as it takes place.
An action is only an attempt to do something: it may not succeed. Firstly,
a before rule might interfere, as we have seen already. Secondly, the action
might not even be very sensible. The parser will happily generate the action
Eat iron_girder if the player asked to do so in good English. In this case,
even if no before rule interferes, the normal game rules will ensure that the
girder is not consumed.
Actions can also be generated by your own code, and this perfectly
simulates the effect of a player typing something. For example, generating
§
a Look action makes the game produce a room description as if the player
had typed ‘‘look’’. More subtly, suppose the air in the Pepper Room causes
the player to sneeze each turn and drop something at random. This could
be programmed directly, with objects being moved onto the floor by explicit
move statements. But then suppose the game also contains a toffee apple,
which sticks to the player’s hands. Suddenly the toffee apple problem has
an unintended solution. So rather than moving the objects directly to the
floor, the game should generate Drop actions, allowing the game’s rules to be
applied. The result might read:
You sneeze convulsively, and lose your grip on the toffee apple. . .
The toffee apple sticks to your hand!
which is at least consistent.
As an example of causing actions, an odorous low_mist will soon settle
over ‘Ruins’. It will have the description ‘‘The mist carries an aroma
reminisicent of tortilla.’’ The alert player who reads this will immediately
type ‘‘smell mist’’, and we want to provide a better response than the game’s
stock reply ‘‘You smell nothing unexpected.’’ An economical way of doing
this is to somehow deflect the action Smell low_mist into the action Examine
low_mist instead, so that the ‘‘aroma of tortilla’’ message is printed in this
case too. Here is a suitable before rule to do that:
Smell: ; rtrue;
The statement causes the action Examine low_mist to be
triggered off immediately, after which whatever was going on at the time
resumes. In this case, the action Smell low_mist resumes, but since we
immediately return true the action is stopped dead.
Causing an action and then returning true is so useful that it has an
abbreviation, putting the action in double angle-brackets. For example, the
following could be added to ‘Ruins’ if the designer wanted to make the
stone-cut steps more enticing:
before [;
Search: <>;
],
If a player types ‘‘search steps’’, the parser will produce the action Search
steps and this rule will come into play: it will generate the action Enter steps
instead, and return true to stop the original Search action from going any
further. The net effect is that one action has been diverted into another.
§
·
·
·
·
·
At any given time, just one action is under way, though others may be waiting
to resume when the current one has finished. The current action is always
stored in the four variables
actor
action
noun
second
actor, noun and second hold the objects involved, or the special value nothing
if they aren’t involved at all. (There’s always an actor, and for the time being
it will always be equal to player.) action holds the kind of action. Its possible
values can be referred to in the program using the ## notation: for example
if (action == ##Look) ...
tests to see if the current action is a Look.
4 Why have ## at all, why not just write Look? Partly because this way the reader
of the source code can see at a glance that an action type is being referred to, but also
because the name might be used for something else. For instance there’s a variable
called score (holding the current game score), quite different from the action type
##Score.
44 For a few actions, the ‘noun’ (or the ‘second noun’) is actually a number (for
instance, ‘‘set timer to 20’’ would probably end up with noun being timer and second
being 20). Occasionally one needs to be sure of the difference, e.g., to tell if second
is holding a number or an object. It’s then useful to know that there are two more
primitive variables, inp1 and inp2, parallel to noun and second and usually equal to
them – but equal to 1 to indicate ‘‘some numerical value, not an object’’.
·
·
·
·
·
The library supports about 120 different actions and most large games will add
some more of their own. The full list, given in Table 6, is initially daunting,
but for any given object most of the actions are irrelevant. For instance, if
you only want to prevent an object from entering the player’s possession, you
need only block the Take action, unless the object is initially in something
or on something, in which case you need to block Remove as well. In the
author’s game ‘Curses’, one exceptional object (Austin, the cat) contains rules
concerning 15 different actions, but the average is more like two or three
action-rules per object.
The list of actions is divided into three groups, called Group 1, Group 2
and Group 3:
§
1. Group 1 contains ‘meta’ actions for controlling the game, like Score and
Save, which are treated quite differently from other actions as they do not
happen in the ‘‘model world’’.
2. Actions in group 2 normally do something to change the state of the model
world, or else to print important information about it. Take (‘‘pick up’’)
and Inv (‘‘inventory’’) are examples of each. Such actions will affect any
object which doesn’t block them with a before rule.
3. Finally, group 3 actions are the ones which normally do nothing but print
a polite refusal, like Pull (‘‘it is fixed in place’’), or a bland response, like
Listen (‘‘you hear nothing unexpected’’). Such actions will never affect
any object which doesn’t positively react with a before rule.
4
Some of the group 2 actions can be ignored by the programmer because they
are really only keyboard shorthands for the player. For example, means ‘‘empty the contents of the rucksack onto the table’’ and is automatically
broken down into a stream of actions like and . You needn’t write rules concerning Empty, only Remove and PutOn.
4
Most of the library’s group 2 actions are able to ‘‘run silently’’. This means that
if the variable keep_silent is set to true, then the actions print nothing in the event
of success. The group 2 actions which can’t run silently are exactly those ones whose
successful operation does nothing but print: Wait, Inv, Look, Examine, Search.
• 4EXERCISE 3
‘‘The door-handle of my room. . . was different from all other door-handles in the world,
inasmuch as it seemed to open of its own accord and without my having to turn it,
so unconscious had its manipulation become. . .’’ (Marcel Proust). Use silent-running
actions to make an unconsciously manipulated door: if the player tries to pass through
when it’s closed, print ‘‘(first opening the door)’’ and do so. (You need to know some
of §13, the section on doors, to answer this.)
• 44EXERCISE 4
Now add ‘‘(first unlocking the door with . . .)’’, automatically trying to unlock it using
either a key already known to work, or failing that, any key carried by the player which
hasn’t been tried in the lock before.
·
·
·
·
·
4
Some actions happen even though they don’t arise directly from anything the player
has typed. For instance, an action called ThrownAt is listed under group 3 in Table 6.
It’s a side-effect of the ordinary ThrowAt action: if the player types ‘‘throw rock at
dalek’’, the parser generates the action ThrowAt rock dalek. As usual the rock is sent
a before message asking if it objects to being thrown at a Dalek. Since the Dalek may
also have an opinion on the matter, another before message is sent to the Dalek, but
§
this time with the action ThrownAt. A dartboard can thus distinguish between being
thrown, and having things thrown at it:
before [;
ThrowAt: "Haven’t you got that the wrong way round?";
ThrownAt:
if (noun==dart) {
move dart to self;
if (random(31)==1)
print (string) random("Outer bull", "Bullseye");
else {
print (string) random("Single", "Double", "Triple");
print " ", (number) random(20);
}
"!";
}
move noun to location;
print_ret (The) noun, " bounces back off the board.";
],
Such an imaginary action – usually, as in this case, a perfectly sensible action seen from
the point of view of the second object involved, rather than the first – is sometimes
called a ‘‘fake action’’. Two things about it are fake: there’s no grammar that produces
ThrownAt, and there’s no routine called ThrownAtSub. The important fake actions are
ThrownAt, Receive and LetGo, the latter two being used for containers: see §12.
44 If you really need to, you can declare a new fake action with the directive
Fake_action hAction-namei;. You can then cause this action with < and > as usual.
• 44EXERCISE 5
ThrownAt would be unnecessary if Inform had an idea of before and after routines
which an object could provide if it were the second noun of an action. How might this
be implemented?
44 Very occasionally, in the darker recesses of §18 for instance, you want ‘‘fake fake
actions’’, actions which are only halfway faked in that they still have action routines.
Actually, these are perfectly genuine actions, but with the parser’s grammar jinxed so
that they can never be produced whatever the player types.
·
·
·
·
·
The standard stock of actions is easily added to. Two things are necessary to
create a new action: first one must provide a routine to make it happen. For
instance:
[ BlorpleSub;
"You speak the magic word ~Blorple~. Nothing happens.";
];
§
Every action has to have a ‘‘subroutine’’ like this, the name of which is always
the name of the action with Sub appended. Secondly, one must add grammar
so that Blorple can actually be called for. Far more about grammar in
Chapter IV: for now we add the simplest of all grammar lines, a directive
Verb ’blorple’ * -> Blorple;
placed after the inclusion of the Grammar file. The word ‘‘blorple’’ can now be
used as a verb. It can’t take any nouns, so the parser will complain if the player
types ‘‘blorple daisy’’.
Blorple is now a typical Group 3 action. before rules can be written for
it, and it can be triggered off by a statement like
;
The unusual action in ‘Ruins’, Photograph, needs to be a Group 2 action,
since it actually does something, and objects need to be able to react with
after rules. (Indeed, the definition of the Treasure class in the previous
section contains just such an after rule.) A photographer needs a camera:
Object -> -> camera "wet-plate camera"
with name ’wet-plate’ ’plate’ ’wet’ ’camera’,
description
"A cumbersome, sturdy, stubborn wooden-framed wet plate
model: like all archaeologists, you have a love-hate
relationship with your camera.";
(This is going to be inside a packing case which is inside the Forest, hence the
two arrows ->.) And now the action subroutine. The sodium lamp referred to
will be constructed in §14.
[ PhotographSub;
if (camera notin player) "Not without the use of your camera.";
if (noun == player) "Best not. You haven’t shaved since Mexico.";
if (children(player) > 1)
"Photography is a cumbersome business, needing the use of both
hands. You’ll have to put everything else down.";
if (location == Forest) "In this rain-soaked forest, best not.";
if (location == thedark) "It is far too dark.";
if (AfterRoutines()) return;
"You set up the elephantine, large-format, wet-plate camera, adjust
the sodium lamp and make a patient exposure of ", (the) noun, ".";
];
§
What makes this a Group 2 action is that, if the action successfully takes place,
then the library routine AfterRoutines is called. This routine takes care of all
the standard rules to do with after (see below), and returns true if any object
involved has dealt with the action and printed something already. (Failing
that, the message ‘‘You set up. . .’’ will be printed.) Finally, some grammar for
the parser:
Verb ’photograph’ * noun -> Photograph;
This matches input like ‘‘photograph statuette’’, because the grammar token
noun tells the parser to expect the name of a visible object. See §30 and §31 for
much more on grammar.
4
To make a Group 1 action, define the verb as meta (see §30).
·
·
·
·
·
Actions are processed in a simple way, but one which involves many little
stages. There are three main stages:
(1) ‘Before’, for group 2 and 3 actions. An opportunity for your code to
interfere with or block altogether what might soon happen.
(2) ‘During’, for all actions. The library takes control and decides if the action
makes sense according to its normal world model: for example, only an
edible object may be eaten; only an object in the player’s possession can
be thrown at somebody, and so on. If the action is impossible, a complaint
is printed and that’s all. Otherwise the action is now carried out.
(3) ‘After’, for group 2 actions. An opportunity for your code to react to what
has happened, after it has happened but before any text announcing it
has been printed. If it chooses, your code can print and cause an entirely
different outcome. If your code doesn’t interfere, the library reports back
to the player (with such choice phrases as ‘‘Dropped.’’).
4
Group 1 actions, like Score, have no ‘Before’ or ‘After’ stages: you can’t (easily)
stop them from taking place. They aren’t happening in the game’s world, but in the
player’s.
4
The ‘Before’ stage consults your code in five ways, and occasionally it’s useful to
know in what order:
(1a) The GamePreRoutine is called, if you have written one. If it returns true, nothing
else happens and the action is stopped.
(1b) The orders property of the player is called on the same terms. For more details,
see §18.
(1c) And the react_before of every object in scope, which roughly means ‘in the
vicinity’. For more details, see §32.
§
(1d) And the before of the current room.
(1e) If the action has a first noun, its before is called on the same terms.
4
The library processes the ‘During’ stage by calling the action’s subroutine: for
instance, by calling TakeSub.
4
The ‘After’ stage only applies to Group 2 actions, as all Group 3 actions have been
wound up with a complaint or a bland response at the ‘During’ stage. During ‘After’
the sequence is as follows: (3a) react_after rules for every object in scope (including
the player object); (3b) the room’s after; (3c) the first noun’s after and (3d) finally
GamePostRoutine.
44 To some extent you can even meddle with the ‘During’ stage, and thus even
interfere with Group 1 actions, by unscrupulous use of the LibraryMessages system.
See §25.
·
·
·
·
·
As mentioned above, the parser can generate decidedly odd actions, such
as Insert camel eye_of_needle. The parser’s policy is to allow any action
which the player has clearly asked for at the keyboard, and it never uses
knowledge about the current game position except to resolve ambiguities. For
instance, ‘‘take house’’ in the presence of the Sydney Opera House and also
a souvenir model of the same will be resolved in favour of the model. But if
there is no model to cloud the issue, the parser will cheerfully generate Take
Sydney_Opera_House.
Actions are only checked for sensibleness after the before stage. In many
ways this is a good thing, because in adventure games the very unlikely is
sometimes correct. But sometimes it needs to be remembered when writing
before rules. Suppose a before rule intercepts the action of putting the
mushroom in the crate, and exciting things happen as a result. Now even if the
mushroom is, say, sealed up inside a glass jar, the parser will still generate the
action Insert mushroom crate, and the before rule will still cut in, because
the impossibility the action hasn’t yet been realised.
The upshot of this is that the exciting happening should be written not as
a before but as an after rule, when it’s known that the attempt to put the
mushroom in the crate has already succeeded.
4
That’s fine if it’s a Group 2 action you’re working with. But consider the following
scenario: a siren has a cord which needs to be pulled to sound the alarm. But the siren
can be behind glass, and is on the other side of a barred cage in which the player is
imprisoned. You need to write a rule for Pull cord, but you can’t place this among
the cord’s after rules because Pull is a group 3 action and there isn’t any ‘‘after’’: so
it has to be a before rule. Probably it’s best to write your own code by hand to check
§
that the cord is reachable. But an alternative is to call the library’s routine:
ObjectIsUntouchable(item, silent_flag, take_flag)
This determines whether or not the player can touch item, returning true if there is
some obstruction. If silent_flag is true, or if there’s no obstruction anyway, nothing
will be printed. Otherwise a suitable message will be printed up, such as ‘‘The barred
cage isn’t open.’’ So a safe way to write the cord’s before rule would be:
before [;
Pull: if (ObjectIsUntouchable(self)) rtrue;
"~Vwoorp! Vwoorp!~";
],
ObjectIsUntouchable can also be a convenience when writing action subroutines for
new actions of your own.
44 If you set take_flag, then a further restriction will be imposed: the item must not
belong to something or someone already: specifically, it must not be in the possession
of an animate or a transparent object that isn’t a container or supporter. For
instance, the off button on a television set can certainly be touched, but if take_flag
is true, then ObjectIsUntouchable will print up ‘‘That seems to be a part of the
television set.’’ and return true to report an obstacle.
• REFERENCES
In a game compiled with the -D for ‘‘Debugging’’ switch set, the ‘‘actions’’ verb
will result in trace information being printed each time any action is generated. Try
putting many things into a rucksack and asking to ‘‘empty’’ it for an extravagant list.
•Diverted actions (using << and >>) are commonplace. They’re used in about 20 places
in ‘Advent’: a good example is the way ‘‘take water’’ is translated into a Fill bottle
action. •L. Ross Raszewski’s library extension "yesno.h" makes an interesting use
of react_before to handle semi-rhetorical questions. For instance, suppose the player
types ‘‘eat whale’’, an absurd command to which the game replies ‘‘You can fit a blue
whale in your mouth?’’ Should the player take this as a literal question and type ‘‘yes’’,
the designer might want to be able to reply ‘‘Oh. I should never have let you go through
all those doors.’’ How might this be done? The trick is that, when the game’s first reply
is made, an invisible object is moved into play which does nothing except to react to a
Yes action by making the second reply.
§7
Infix and the debugging verbs
If builders built buildings the way programmers write programs,
the first woodpecker that came along would destroy civilisation.
— old computing adage
Infocom fixed 1,695 documented bugs in the course of getting
‘Sorcerer’ from rough draft to first released product. Alpha testing
of ‘A Mind Forever Voyaging’ turned up one bug report every
three minutes. Adventure games are exhausting programs to test
and debug because of the number of states they can get into, many of them
unanticipated. (For instance, if the player solves the ‘‘last’’ puzzle first, do the
other puzzles still work properly? Are they still fair?) The main source of error
is simply the designer not noticing that some states are possible. The Inform
library can’t help with this, but it does contain some useful features designed
to help the tester. These are worth finding out about, because if you’re going to
code up a game, you’ll be spending a lot of time testing one thing or another.
Inform has three main debugging features, each making the story file larger
and slower than it needs to be: each can be turned on or off with compiler
switches. One feature is ‘‘strict mode’’ (switch -S), which checks that the story
file isn’t committing sins such as over-running arrays or treating nothing as
if it were an object: trying to calculate child(nothing), for instance. Strict
mode is on by default, and automatically sets Debug mode whenever it is on.
To get rid of strict mode, compile with -~S to turn switch S off.
Over and above this is the extensive ‘‘Infix’’ (switch -X), a far more potent
set of debugging verbs, which significantly adds to the size of a story file (so
that a really large story file might not be able to fit it in). Infix allows you to
watch changes happening to objects and monitor routines of your choice, to
send messages, call routines, set variables and so forth. Like Strict mode, Infix
automatically switches Debug mode on.
Debug mode (switch -D) adds only a small suite of commands, the
‘‘debugging verbs’’, to any game. For instance, typing ‘‘purloin mousetrap’’
allows you to take the mousetrap wherever it happens to be in the game. The
debugging verbs do take up extra space, but very little, and note that even if
Strict mode and Infix are both off, you can still have Debug on its own.
The Infix and Debug modes give the player of a story file what amount to
god-like powers, which is fine for testing but not for final release versions. As a
precaution against accidents, the end of a game’s printed banner indicates Infix
§
mode with a capital letter ‘X’, Debug with ‘D’, and Strict with ‘S’. Your source
code can also detect which are set: in Strict mode the constant STRICT_MODE
is defined; in Debug mode the constant DEBUG; with Infix the constant INFIX.
§7.1 Debugging verbs for command sequences
The basic testing technique used by most designers is to keep a master-list of
commands side by side with the growing game: a sequence which takes the
player from the beginning, explores everywhere, thoroughly tests every blind
alley, sudden death and textual response and finally wins the game.
‘‘recording’’ or ‘‘recording on’’ or ‘‘recording off’’
Records all the commands you type into a file on your machine (your interpreter
will probably ask you for a filename). When a new region of game is written,
you may want to turn recording on and play through it: you can then add the
resulting file to the master-list which plays the entire game.
‘‘replay’’
This immensely useful verb plays the game taking commands from a file on
your machine, rather than from the keyboard. This means you can test every
part of the entire game with minimal effort by replaying the master-list of
commands through it.
‘‘random’’
If you’re going to replay such recordings, you need the game to behave
predictably: so that chance events always unfold in the same way. This means
nobbling the random number generator, and the ‘‘random’’ verb does just that:
i.e., after any two uses of ‘‘random’’, the same stream of random numbers
results. So you want the first command in your master-list to be ‘‘random’’ if
your game has any chance events in it.
4
If you have written a large and complicated game, you may well want to release
occasional updates to correct mistakes found by players. But tampering with the code
always runs the risk that you may fix one thing only to upset another. A useful insurance
policy is to keep not only the master list of commands, but also the transcript of the text
it should produce. Then when you amend something, you can replay the master list
again and compare the new transcript with the old, ideally with a program like the Unix
utility ‘‘diff’’. Any deviations mean a (possibly unintended) side effect of something
you’ve done.
§
§7.2 Undo
Every Inform game provides the ‘‘undo’’ verb, which exactly restores the
position before the last turn took place. Though this is not advertised, ‘‘undo’’
can even be used after death or victory, typed in at the ‘‘Would you like to
RESTART, RESTORE a saved game. . .’’ prompt. It can be useful to include
fatal moves, followed by ‘‘undo’’, in the master-list of commands, so testing
death as well as life.
§7.3 Debugging verbs which print useful information
‘‘showobj’’ hanythingi
‘‘showobj’’ is very informative about the current state of an object, revealing
which attributes it presently has and the values of its properties. hanythingi
can be the name of any object anywhere in the game (not necessarily in sight),
or even the number of an object, which you might need if the object doesn’t
have a name.
‘‘tree’’ or ‘‘tree’’ hanythingi
To see a listing of the objects in the game and how they contain each other,
type ‘‘tree’’, and to see the possessions of one of them alone, use ‘‘tree hthati’’.
So ‘‘tree me’’ is quite like ‘‘inventory’’.
‘‘showverb’’ hverbi
For instance, ‘‘showverb unlock’’. This prints out what the parser thinks is the
grammar for the named verb, in the form of an Inform Verb directive. This is
useful if you’re using the Extend directive to alter the library’s grammar and
want to check the result.
‘‘scope’’ or ‘‘scope’’ hanythingi
Prints a list of all the objects currently in scope, and can optionally be given
the name of someone else you want a list of the scope for (‘‘scope pirate’’).
Roughly speaking, something is in your scope if you can see it from where you
are: see §32.
§
§7.4 Debugging verbs which trace activity behind the scenes
Tracing is the process of printing up informative text which describes changes
as they happen. Each of the following verbs can be given on its own, which sets
tracing on; or followed by the word ‘‘on’’, which does the same; or followed
by ‘‘off’’, which turns it off again.
‘‘actions’’ or ‘‘actions on’’ or ‘‘actions off’’
Traces all the actions generated in the game. For instance, here’s what happens
if you unlock and then enter the steel grate in ‘Advent’:
>enter grate
[ Action Enter with noun 51 (steel grate) ]
[ Action Go with noun 51 (steel grate) (from < > statement) ]
...
Which reveals that the Enter action has handed over to Go.
‘‘messages’’ or ‘‘messages on’’ or ‘‘messages off’’
Traces all messages sent between objects in the game. (Except for short_name
messages, because this would look chaotic, especially when printing the status
line.)
‘‘timers’’ or ‘‘timers on’’ or ‘‘timers off’’
Turning on ‘‘timers’’ shows the state of all active timers and daemons at the
end of each turn. Typing this in the start of ‘Advent’ reveals that three daemons
are at work: two controlling the threatening little dwarves and the pirate, and
one which monitors the caves to see if every treasure has been found yet.
‘‘changes’’ or ‘‘changes on’’ or ‘‘changes off’’
Traces all movements of any object and all changes of attribute or property
state.
>switch lamp on
[Giving brass lantern on]
[Giving brass lantern light]
You switch the brass lantern on.
[Setting brass lantern.power_remaining to 329]
In Debris Room
§
You are in a debris room filled with stuff washed in from the surface. A low
wide passage with cobbles becomes plugged with mud and debris here, but
an awkward canyon leads upward and west.
A note on the wall says, ‘‘Magic word XYZZY.’’
A three foot black rod with a rusty star on one end lies nearby.
[Giving In Debris Room visited]
Warning: this verb has effect only if the story file was compiled with the -S
switch set, which it is by default.
4
Two things ‘‘changes’’ will not notice: (i) changes in the workflag attribute,
because this flickers rapidly on and off with only temporary significance as the parser
works, and (ii) changes to the entries in an array which is held in a property.
‘‘trace’’ or ‘‘trace’’ hnumberi or ‘‘trace off’’
There are times when it’s hard to work out what the parser is up to and why
(actually, most times are like this: but sometimes it matters). The parser is
written in levels, the lower levels of which are murky indeed. Most of the
interesting things happen in the middle levels, and these are the ones for which
tracing is available. The levels which can be traced are:
Level 1 Parsing a hgrammar linei
Level 2 Individual tokens of a hgrammar linei
Level 3 Parsing a hnoun phrasei
Level 4 Resolving ambiguities and making choices of object(s)
Level 5 Comparing text against an individual object
‘‘trace" or ‘‘trace on" give only level 1 tracing. Be warned: ‘‘trace 5" can
produce reams and reams of text. There is a level lower even than that, but
it’s too busy doing dull spade-work to waste time printing. There’s also a level
0, but it consists mostly of making arrangements for level 1 and doesn’t need
much debugging attention.
§7.5 Debugging verbs which alter the game state in supernatural ways
‘‘purloin’’ hanythingi
You can ‘‘purloin’’ any item or items in your game at any time, wherever you
are, even if they wouldn’t normally be takeable. A typical use: ‘‘purloin all
keys’’. Purloining something automatically takes away the concealed attribute,
if necessary.
§
‘‘abstract’’ hanythingi ‘‘to’’ hanythingi
You can likewise ‘‘abstract" any item to any other item, meaning: move it
to the other item. This is unlikely to make sense unless the other item is a
container, supporter or animate object.
‘‘goto’’ hroom numberi
Teleports you to the numbered room. Because rooms don’t usually have
names, referring to them by number (as printed in the ‘‘tree’’ output) is the
best that can be done. . .
‘‘gonear’’ hanythingi
. . . unless you can instead name something which is in that room. So for
instance ‘‘gonear trident’’ teleports to the room containing the trident.
§7.6 The Infix verbs
Although Infix adds relatively few additional verbs to the stock, they are
immeasurably stronger. All of them begin with a semicolon ; and the
convention is that anything you type beginning with a semicolon is addressed
to Infix.
‘‘;’’ hexpressioni
This calculates the value of the expression and prints it out. At first sight, this
is no more than a pocket calculator, and indeed you can use it that way:
>; 1*2*3*4*5*6*7
; == 5040
But the hexpressioni can be almost any Inform expression, including variables,
constants, action, array, object and class names, routine calls, message-sending
and so on. It can be a condition, in which case the answer is 0 for false and 1
for true. It can even be an assignment.
>; score
; == 36
>; score = 1000
; == 1000
§
[Your score has just gone up by nine hundred and sixty-four points.]
>; brass_lantern has light
; false
>; lamp.power_remaining = 330
(brass lantern (39))
; == 330
>; child(wicker cage)
(wicker cage (55))
; == ‘‘nothing’’ (0)
>; children(me)
(yourself (20))
; == 4
In the dialogue above, from ‘Advent’ compiled with -X, the player called the
same item both brass_lantern, the name it has in the source code, and
‘‘lamp’’, the name it normally has in the game. When Infix is unable to
understand a term like ‘‘lamp’’ as referring to the source code, it tries to match
it to an object somewhere in the game, and prints up any guess it makes. This
is why it printed ‘‘(brass lantern (39))’’. (39 happens to be the object number
of the brass lantern.) Pronouns like ‘‘me’’ and ‘‘it’’ can also be used to refer to
objects.
>; StopDaemon(pirate)
; == 1
>; InformLibrary.begin_action(##Take, black_rod)
black rod with a rusty star on the end: Taken.
; == 0
The routine StopDaemon will appear later in §20: roughly speaking, ‘‘daemons’’
control random interventions, and stopping them is useful to keep (say) the
bearded pirate from appearing and disrupting the replay of a sequence of
commands. The second example shows a message being sent, though there is
a simpler way to cause actions:
‘‘;<’’ hactioni hnouni hsecondi
which generates any action of your choice, whether or not the given hnouni
and hsecondi (which are optional) are in your scope. Once generated, the
action is subject to all the usual rules:
>;< Take black_rod
; ;give real_location light
; give (the At ‘‘Y2’’) light
>wait
Time passes.
At ‘‘Y2’’
You are in a large room, with a passage to the south, a passage to the west,
and a wall of broken rock to the east. There is a large ‘‘Y2’’ on a rock in the
room’s center.
(The waiting was because Inform only checks light at the end of each turn, but
‘‘;give’’ and the other debugging verbs are set up to occupy no game time at all
-- which is often useful, even if it’s a slight nuisance here.) Infix also extends
the facilities for watching the changing state of the game:
‘‘;watch’’ or ‘‘;w’’ hnamed routinei
‘‘;watch’’ or ‘‘;w’’ hnamed routinei ‘‘off’’
‘‘;watch’’ or ‘‘;w’’ hobjecti
‘‘;watch’’ or ‘‘;w’’ hobjecti ‘‘off’’
When a named routine is being watched, text is printed each time it is called,
giving its name and the values of the arguments it started up with. For instance,
‘‘;watch StartTimer’’ will ensure that you’re told of any StartTimer(object,
time_to_run) call, and so of any timer that begins working. Watching an
object, say ‘‘;watch lamp’’, will notify you when:
(1)
(2)
(3)
(4)
any attribute of the lamp is given or taken away;
any property of the lamp is set;
any message is sent to a routine attached to the lamp;
the lamp is moved;
§
(5) anything is moved to the lamp.
You can also watch things in general:
‘‘;watch objects’’
watches every object
‘‘;watch timers’’
watches timers and daemons each turn
‘‘;watch messages’’ watches every message sent
‘‘;watch actions’’
watches all actions generated
The final two Infix verbs extend the facilities for looking at things.
‘‘;examine’’ or ‘‘;x’’ hsomethingi
‘‘;inventory’’ or ‘‘;i’’
‘‘;inventory’’ tells you the names known to Infix, which is a practical way to
find out which classes, routines, objects and so on are inside the story file.
‘‘;examine’’ looks at the hsomethingi and tells you whatever Infix knows about
it: (a) numbers are translated to hexadecimal and to ZSCII values, where
possible; (b) objects are ‘‘shown’’; (c) classes are listed; (d) constants have
their values given; (e) attributes produce a list of all objects currently having
them; (f) properties produce a list of all objects currently providing them; (g)
dictionary words are described; (h) verbs have their grammars listed; (i) arrays
have their definitions shown and their contents listed; (j) global variables have
their values given; (k) actions produce a list of all grammar known to the parser
which can produce them. For instance:
>;x ’silver’
; Dictionary word ’silver’ (address 27118): noun
>;x buffer
; Array buffer -> 120
; == 120 9 59 120 32 98 117 102 102 101 114 0 101 114 32 (then 105 zero
entries)
4
You can watch routines even without Infix, and the Inform language provides two
features for this. Firstly, you can declare a routine with an asterisk * immediately after
the name, which marks it for watching. For example, declaring a routine as follows
[ AnalyseObject * obj n m;
results in the game printing out lines like
[AnalyseObject, obj=26, n=0, m=0]
every time the routine is called. A more drastic measure is to compile the story file
with the -g set. The ordinary setting -g or -g1 marks every routine in your own source
code to be watched, but not routines in the library (more accurately, not to routines
defined in any ‘‘system file’’). The setting -g2 marks every routine from anywhere, but
be warned, this produces an enormous melée of output.
§
4
If you do have Infix present, then you can always type ‘‘;watch . . . off’’ to stop
watching any routine marked with a * in the source code. Without Infix, there’s no
stopping it.
44 At present, there is no source-level debugger for Inform. However, for the benefit
of any such tool which somebody might like to write, Inform has a switch -k which
makes it produce a file of ‘‘debugging information’’ to go with the story file. This
file mostly contains cross-references between positions in the game and lines of source
code. The details of its format are left to the Technical Manual.
• REFERENCES
Several exercises in this book are about defining new debugging verbs to test one thing
or another, and most games have invented a few of their own. Early versions of ‘Curses’,
for instance, allowed various supernatural actions beginning with ‘‘x’’ once the player
had typed the command ‘‘xallow 57’’ (the author was then living above a carpet shop
at 57 High Street, Oxford). But Paul David Doherty eventually disassembled the story
file and found the secret. Moral: place any new debugging verbs inside an Ifdef
DEBUG; directive, unless you plan on leaving them in as ‘‘Easter eggs’’ for people to
find. •A simple debugging verb called ‘‘xdeterm’’ is defined in the DEBUG version of
‘Advent’: it takes random events out of the game. •Marnie Parker’s library extension
"objlstr.h", which has contributions from Tony Lewis, provides a useful debugging
verb ‘‘list’’, allowing for instance ‘‘list has door lockable locked’’, which lists all objects
having that combination of attributes.
Chapter III: The Model World
A Model must be built which will get everything in without a clash;
and it can do this only by becoming intricate, by mediating its
unity through a great, and finely ordered, multiplicity.
— C. S. Lewis (1898–1963), The Discarded Image
§8
Places and scenery
In this longer chapter the model world of an Inform game will be
explored and examples will gradually complete the ‘Ruins’ begun
in Chapter II. So far, ‘Ruins’ contains just a location of rainforest
together with some rules about photography. The immediate need
is for a more substantial map, beginning with a sunken chamber. Like the
Forest, this too has light, however dim. If it didn’t, the player would never
see it: in Inform’s world darkness prevails unless the designer provides some
kind of lamp or, as in this case, ambient light.
Object Square_Chamber "Square Chamber"
with name ’lintelled’ ’lintel’ ’lintels’ ’east’ ’south’ ’doorways’,
description
"A sunken, gloomy stone chamber, ten yards across. A shaft
of sunlight cuts in from the steps above, giving the
chamber a diffuse light, but in the shadows low lintelled
doorways to east and south lead into the deeper darkness
of the Temple.",
has light;
This room has a name property even though rooms are not usually referred
to by players. The nouns given are words which Inform knows ‘‘you don’t
need to refer to’’, and it’s a convention of the genre that the designer should
signpost the game in this way. For the game to talk about something and later
deny all knowledge – ‘‘I can’t see any such thing’’ – is not merely rude but
harmful to the player’s illusion of holding a conversation about a real world.
Better to parry with:
>examine lintel
§
That’s not something you need to refer to in the course of this game.
·
·
·
·
·
Not all of the Square Chamber’s décor is so irrelevant:
Object -> "carved inscriptions"
with name ’carved’ ’inscriptions’ ’carvings’ ’marks’ ’markings’
’symbols’ ’moving’ ’scuttling’ ’crowd’ ’of’,
initial
"Carved inscriptions crowd the walls, floor and ceiling.",
description
"Each time you look at the carvings closely, they seem
to be still. But you have the uneasy feeling when you
look away that they’re scuttling, moving about. Two
glyphs are prominent: Arrow and Circle.",
has static pluralname;
The static attribute means that the inscriptions can’t be taken or moved. As
we went out of our way to describe a shaft of sunlight, we’ll include that as
well:
Object -> sunlight "shaft of sunlight"
with name ’shaft’ ’of’ ’sunlight’ ’sun’ ’light’ ’beam’ ’sunbeam’
’ray’ ’rays’ ’sun^s’ ’sunlit’ ’air’ ’motes’ ’dust’,
description
"Motes of dust glimmer in the shaft of sunlit air, so
that it seems almost solid.",
has scenery;
The ^ symbol in "sun^s" means an apostrophe, so the word is ‘‘sun’s’’. This
object has been given the constant name sunlight because other parts of the
‘Ruins’ source code will need to refer to it later on. Being scenery means that
the object is not only static but also not described by the game unless actually
examined by the player. A perfectionist might add a before rule:
before [;
Examine, Search: ;
default: "It’s only an insubstantial shaft of sunlight.";
],
so that the player can look at or through the sunlight, but any other request
involving them will be turned down. Note that a default rule, if given, means
‘‘any action except those already mentioned’’.
§
4 Objects having scenery are assumed to be mentioned in the description text of
the room, just as the ‘‘shaft of sunlight’’ is mentioned in that of the Square Chamber.
Giving an object concealed marks it as something which is present to a player who
knows about it, but hidden from the casual eye. It will not be cited in lists of objects
present in the room, and ‘‘take all’’ will not take it, but ‘‘take secret dossier’’, or
whatever, will work. (Designers seldom need concealed, but the library uses it all the
time, because the player-object is concealed.)
·
·
·
·
·
Some scenery must spread across several rooms. The ‘Ruins’, for instance,
are misty, and although we could design them with a different ‘‘mist’’ object in
every misty location, this would become tiresome. In ‘Advent’, for instance,
a stream runs through seven locations, while mist which (we are told) is
‘‘frequently a sign of a deep pit leading down to water’’ can be found in ten
different caves. Here is a better solution:
Object low_mist "low mist"
with name ’low’ ’swirling’ ’mist’,
description "The mist has an aroma reminiscent of tortilla.",
found_in Square_Chamber Forest,
before [;
Examine, Search: ;
Smell: <>;
default: "The mist is too insubstantial.";
],
has scenery;
The found_in property gives a list of places in which the mist is found: so far,
just the Square Chamber and the Forest.
4
This allows for up to 32 misty locations. If scenery has to be visible even more
widely than that, or if it has to change with circumstances (for instance, if the mist
drifts) then it is simpler to give a routine instead of a list. This can look at the current
location and say whether or not the object should be present, as in the following
example from a game taking place at night:
Object Procyon "Procyon",
with name ’procyon’ ’alpha’ ’canis’ ’minoris’ ’star’,
description "A double-star eleven light years distant.",
found_in [;
return (location ofclass OutsideRoom);
],
has scenery;
§
found_in is only consulted when the player’s location changes, and works by moving
objects around to give the illusion that they are in several places at once: thus, if the
player walks from a dark field to a hilltop, Procyon will be moved ahead to the hilltop
just in advance of the player’s move. This illusion is good enough for most practical
purposes, but sometimes needs a little extra work to maintain, for instance if the sky
must suddenly cloud over, concealing the stars. Since it often happens that an object
must be removed from all the places in which it would otherwise appear, an attribute
called absent is provided which overrides found_in and declares that the object is
found nowhere. Whatever change is made to found_in, or in giving or removing
absent, the Inform library needs also to be notified that changes have taken place. For
instance, if you need to occult Procyon behind the moon for a while, then:
give Procyon absent; MoveFloatingObjects();
The library routine MoveFloatingObjects keeps the books straight after a change of
state of found_in or absent.
·
·
·
·
·
Whereas Procyon is entirely visual, some scenery items may afflict the other
four senses. In ‘Ruins’, the Forest contains the rule:
before [;
Listen: "Howler monkeys, bats, parrots, macaw.";
],
Besides which, we have already said that the mist smells of tortilla, which
means that if the player types ‘‘smell’’ in a place where the mist is, there should
clearly be some reaction. For this, a react_before rule attached to the mist is
ideal:
react_before [;
Smell: if (noun == nothing) <>;
],
This is called a ‘‘react’’ rule because the mist is reacting to the fact that a
Smell action is taking place nearby. noun is compared with nothing to see if
the player has indeed just typed ‘‘smell’’ and not, say, ‘‘smell crocus’’. Thus,
when the action Smell takes place near the mist, it is converted into Smell
low_mist, whereas the action Smell crocus would be left alone.
The five senses all have actions in Inform: Look, Listen, Smell, Taste
and Touch. Of these, Look never has a noun attached (because Examine,
LookUnder and Search are provided for close-up views), Smell and Listen
may or may not have while Taste and Touch always have.
§
• EXERCISE 6
(Cf. ‘Spellbreaker’.) Make an orange cloud descend on the player, which can’t be seen
through or walked out of.
·
·
·
·
·
Rooms also react to actions that might occur in them and have their own
before and after rules. Here’s one for the Square Chamber:
before [;
Insert:
if (noun == eggsac && second == sunlight) {
remove eggsac; move stone_key to self;
"You drop the eggsac into the glare of the shaft of
sunlight. It bubbles obscenely, distends and then
bursts into a hundred tiny insects which run in all
directions into the darkness. Only spatters of slime
and a curious yellow-stone key remain on the chamber
floor.";
}
],
(The variables noun and second hold the first and second nouns supplied
with an action.) As it happens this rule could as easily have been part of the
definition of the eggsac or the sunlight, but before and after rules for rooms
are invaluable to code up geographical oddities.
• EXERCISE 7
Create a room for ‘Ruins’ called the Wormcast, which has the oddity that anything
dropped there ends up back in the Square Chamber.
4
Sometimes the room may be a different one after the action has taken place. The
Go action, for instance, is offered to the before routine of the room which is being left,
and the after routine of the room being arrived in. For example:
after [;
Go: if (noun == w_obj)
print "You feel an overwhelming sense of relief.^";
],
will print the message when its room is entered from the ‘‘west" direction. Note that
this routine returns false, in the absence of any code telling it to do otherwise, which
means that the usual game rules resume after the printing of the message.
§
• REFERENCES
‘A Scenic View’ by Richard Barnett demonstrates a system for providing examinable
scenery much more concisely (without defining so many objects).
•found_in can
allow a single object to represent many different but similar objects across a game, and a
good example is found in Martin Braun’s "elevator.inf" example game, where every
floor of a building has an up-arrow and a down-arrow button to summon an elevator.
§9
Directions and the map
I wisely started with a map, and made the story fit (generally with
meticulous care for distances). The other way about lands one in
confusions and impossibilities, and in any case it is weary work to
compose a map from a story – as I fear you have found.
— J. R. R. Tolkien (1892–1973), to Naomi Mitchison, 25 April 1954
‘Ruins’ so far contains two disconnected rooms. It is time to extend
it into a modest map in which, as promised, the Square Chamber
lies underneath the original Forest location. For the map of the
finished game, see §23 below, but here is the beginning of the
first level beneath ground, showing the Square Chamber and its two main
side-chambers:
Square Chamber ↔ Wormcast
l
Corridor
l
Shrine
To make these map connections, we need to add:
u_to Forest, e_to Wormcast, s_to Corridor,
to the Square Chamber. This seems a good point to add two more map
connections, or rather non-connections, to the Forest as well:
u_to "The trees are spiny and you’d cut your hands to ribbons
trying to climb them.",
cant_go "The rainforest is dense, and you haven’t hacked
through it for days to abandon your discovery now. Really,
you need a good few artifacts to take back to civilization
before you can justify giving up the expedition.",
The property cant_go contains what is printed when the player tries to go
in a nonexistent direction, and replaces ‘‘You can’t go that way’’. Instead of
giving an actual message you can give a routine to print one out, to vary what’s
printed with the circumstances. The Forest needs a cant_go because in real
life one could go in every direction from there: what we’re doing is explaining
the game rules to the player: go underground, find some ancient treasure, then
get out to win. The Forest’s u_to property is a string of text, not a room, and
this means that attempts to go up result only in that string being printed.
§
4
Here’s how this is done. When the library wants to go in a certain direction, let’s
say ‘‘north’’, it sends the message location.n_to() and looks at the reply: it takes
false to mean ‘‘Player can’t go that way’’ and says so; true means ‘‘Player can’t go
that way, and I’ve already said why’’; and any other value is taken as the destination.
• EXERCISE 8
Many early games have rooms with confused exits: ‘Advent’ has Bedquilt, ‘Acheton’
has a magnetic lodestone which throws the compass into confusion, ‘Zork II’ has a
spinning carousel room and so on. Make the Wormcast room in ‘Ruins’ similarly
bewildering.
·
·
·
·
·
For each of the twelve standard Inform directions there is a ‘‘direction
property’’:
n_to
ne_to
s_to
nw_to
e_to
se_to
w_to
sw_to
d_to
in_to
u_to
out_to
Each direction also has a ‘‘direction object’’ to represent it in the game. For
instance, n_obj is the object whose name is ‘‘north’’ and which the player
invokes by typing ‘‘go north’’ or just ‘‘n’’. So there are normally twelve of
these, too:
n_obj
ne_obj
s_obj
nw_obj
e_obj
se_obj
w_obj
sw_obj
d_obj
in_obj
u_obj
out_obj
Confusing the direction objects with the direction properties is easily done,
but they are quite different. When the player types ‘‘go north’’, the action is Go
n_obj, with noun being n_obj: only when this action has survived all possible
before rules is the n_to value of the current location looked at.
4
The set of direction objects is not fixed: the current direction objects are the
children of a special object called compass, and the game designer is free to add to or
take from the current stock. Here for instance is the definition of ‘‘north’’ made by the
library:
CompassDirection n_obj "north wall" compass
with name ’n’ ’north’ ’wall’, door_dir n_to;
CompassDirection is a class defined by the library for direction objects. door_dir is
a property more usually seen in the context of doors (see §13) and here tells Inform
which direction property corresponds to which direction object.
§
• 4EXERCISE 9
In the first millennium A.D., the Maya peoples of the Yucatán Peninsula had ‘world
colours’ white (sac), red (chac), yellow (kan) and black (chikin) for what we call the
compass bearings north, east, south, west (for instance west is associated with ‘sunset’,
hence black, the colour of night). Implement this.
• 4EXERCISE 10
In Level 9’s version of ‘Advent’, the magic word ‘‘xyzzy’’ was implemented as a
thirteenth direction. How can this be done?
• 4EXERCISE 11
(Cf. ‘Trinity’.) How can the entire game map be suddenly east–west reflected?
• 44EXERCISE 12
Even when the map is reflected, there may be many room descriptions referring to
‘‘east’’ and ‘‘west’’ by name. Reflect these too.
• 44EXERCISE 13
Some designers find it a nuisance to have to keep specifying all map connections twice:
once east from A to B, then a second time west from B to A, for instance. Write some
code to go in the Initialise routine making all connections automatically two-way.
• REFERENCES
‘Advent’ has a very tangled-up map in places (see the mazes) and a well-constructed
exterior of forest and valley giving an impression of space with remarkably few rooms.
The mist object uses found_in to the full, and see also the stream (a single object
representing every watercourse in the game). Bedquilt and the Swiss Cheese room
offer classic confused-exit puzzles. •For a simple movement rule using e_to, see the
Office in ‘Toyshop’. •The opening location of Infocom’s ‘Moonmist’ provides a good
example of cant_go used to guide the player in a tactful way: ‘‘(The castle is south
of here.)’’
•The library extension "smartcantgo.h" by David Wagner provides
a system for automatically printing out ‘‘You can only go east and north.’’-style
messages. •Ricardo Dague’s "cmap.h" constructs maps of American-style cities. The
same author’s "makemaze.inf" prints out Inform source code for random rectangular
mazes.
•Nicholas Daley and Gunther Schmidl have each independently written a
"dirs.h" providing a ‘‘dirs’’ or ‘‘exits’’ verb which lists available exits. Marnie Parker’s
"dirsmap.h" goes further by plotting up exits in map style or writing them out in
English, at the player’s discretion. •Brian D. Smith’s example program "spin.inf"
abolishes the convention that a player has an in-built ability to know which way is
north: it replaces conventional compass directions with ‘‘left’’, ‘‘right’’, ‘‘ahead’’ and
‘‘back’’.
§10
Food and drink
Any object with the attribute edible can be eaten by the player,
and the library provides the action Eat for this. If it succeeds, the
library removes whatever was eaten from the object tree, so that
it disappears from play (unless the designer has written any code
which later moves it back again). Two edible objects have already appeared in
‘Ruins’: the mushroom, §4, and the honeycomb, §5.
Because drinking is a less clear-cut matter than eating, there is no attribute
called drinkable. Although the library does provide the action Drink, you
have to write your own code to show what happens when the object has been
drunk. Here is an example where the object is entirely consumed:
Object -> "glass of milk"
with name ’glass’ ’of’ ’milk’,
before [;
Drink: remove self;
"Well, that’s the sixth stomach that hillside
of grass has been in.";
];
Other rules which might be needed, instead of simply removing the object,
would be to replace it with an ‘‘empty glass’’ object, or to change some counter
which records how many sips have been taken from a large supply of drink.
• REFERENCES
Players discover almost at once that ‘Advent’ contains ‘‘tasty food’’, actually tripe
although the text is too delicate to say so. (Type ‘‘get tripe ration’’ if you don’t believe
this.) Less well known is that the moss growing on the Soft Room’s walls is also edible.
•The product-placement parody ‘Coke Is It’ contains the occasional beverage. (‘‘You
are standing at the end of a road before a small brick building. Coca-Cola. . . Along
The Highway To Anywhere, Around The Corner From Everywhere, Coca-Cola is The
Best Friend Thirst Ever Had. A small stream flows out of the building and down a
gully.’’) •Inform doesn’t provide an automatic system for handling liquids because
it is difficult to find one that would satisfy enough people for enough of the time: for
more on the issues that liquids raise, see §50. In its handling of liquids the source code
for the alchemical mystery ‘Christminster’ is much borrowed from.
§11
Clothing
The player’s possessions are normally thought of as being held in
the hands, but there are two alternative possibilities: some items
might be being carried indirectly in a bag or other container (see
§12 next), while others might be worn as clothing. Wearing a hat is
evidently different from carrying it. Some consequences are left to the designer
to sort out, such as taking account of the hat keeping the rain off, but others
are automatically provided: for instance, a request to ‘‘drop all’’ will not be
taken as including the hat, and the hat will not count towards any maximum
number of items allowed to be held in the player’s hands. The library provides
two actions for clothing: Wear and Disrobe. There is already an action called
Remove for taking items out of containers, which is why the name Remove is
not used for taking clothes off.
‘Ruins’ contains only one item of clothing, found resting on an altar in
the Shrine: it summons a priest.
Treasure -> -> mask "jade mosaic face-mask"
with description "How exquisite it would look in the Museum.",
initial "Resting on the altar is a jade mosaic face-mask.",
name ’jade’ ’mosaic’ ’face-mask’ ’mask’ ’face’,
cultural_value 10,
after [;
Wear: move priest to Shrine;
if (location == Shrine)
"Looking through the obsidian eyeslits of the
mosaic mask, a ghostly presence reveals itself:
a mummified calendrical priest, attending your
word.";
Disrobe: remove priest;
],
has clothing;
The attribute clothing signifies that the mask can be worn. During the time
something is worn, it has the attribute worn. The library’s standard rules
ensure that:
An object can only have worn if it is in player, that is, if it is an immediate
possession of the player.
If you use move or remove to shift items of clothing in your own code, or give
or take away the worn attribute, then you too should follow this principle.
§
4
A risk of providing clothing for the player is that it’s hard to resist the lure of
realism. A tweed jacket would add some colour to ‘Ruins’. But if a jacket, why not
plus-four trousers, an old pair of army boots and a hat with a mosquito net? And if
trousers, why not underwear? What are the consequences if the player strips off? Too
much of this kind of realism can throw a game off balance, so: no tweed.
• 44EXERCISE 14
Design a pair of white silk gloves, left and right, which are a single object called ‘‘pair of
white gloves’’ until the player asks to do something which implies dividing them (typing
‘‘wear left glove’’, say, or ‘‘put right glove in drawer’’). They should automatically
rejoin into the pair as soon as they are together again. (By Richard Tucker. Hint:
react_before and before rules are all you need.)
• REFERENCES
For designers who do want to go down the ‘‘if trousers, why not underwear?’’ road,
Denis Moskowitz’s library extension "clothing.h" models clothing by layer and by
•For players who also want to, the road to take is ‘I-0’, by
area of body covered.
Adam Cadre.
§12
Containers, supporters and sub-objects
The year has been a good one for the Society (hear, hear). This year our
members have put more things on top of other things than ever before. But,
I should warn you, this is no time for complacency. No, there are still many
things, and I cannot emphasize this too strongly, not on top of other things.
— ‘The Royal Society For Putting Things On Top Of Other Things’ sketch,
Monty Python’s Flying Circus, programme 18 (1970)
In almost every game, certain objects need to be thought of as on
top of or inside other objects. The library provides actions Insert
and PutOn for placing things inside or on top of something, and
Remove for taking things out of or off the top of something. Many
objects, such as house-bricks, cannot sensibly contain things, and a designer
usually only wants certain specific items ever to have other things on top of
them. In the model world, then, only objects which the designer has given the
container attribute can contain things, and only those given the supporter
attribute can have items on top.
The packing case brought by our archaeologist hero to the scene of the
‘Ruins’ (found in the opening location, the Forest) is a thoroughly typical
container:
Object -> packing_case "packing case"
with name ’packing’ ’case’ ’box’ ’strongbox’,
initial
"Your packing case rests here, ready to hold any important
cultural finds you might make, for shipping back to
civilisation.",
before [;
Take, Remove, PushDir:
"The case is too heavy to bother moving, as long as
your expedition is still incomplete.";
],
has static container open openable;
A container can hold anything up to 100 items, but this limit can be modified
by giving the container a capacity property.
Note that the packing case is defined having an attribute called open. This
is essential, because the library will only allow the player to put things in a
container if it is currently open. (There is no attribute called closed, as any
§ , -
container lacking open is considered closed.) If a container has openable, the
player can open and close it at will, unless it also has locked. A locked object,
whether it be a door or a container, cannot be opened. But if it has lockable
then it can be locked or unlocked with the key object given in its with_key
property. If with_key is undeclared or equal to nothing, then no key will
fit, but this will not be told to the player. The actions Open, Close, Lock and
Unlock handle all of this.
• EXERCISE 15
Construct a musical box with a silver key.
· · · · ·
An object having supporter can have up to 100 items put on top of it, or,
once again, its capacity value if one is given. An object cannot be both a
container and a supporter at once, and there’s no concept of being ‘‘open’’
or ‘‘locked’’ for supporters. Here is an example from the Shrine:
Object -> stone_table "slab altar"
with name ’stone’ ’table’ ’slab’ ’altar’ ’great’,
initial "A great stone slab of a table, or altar, dominates
the Shrine.",
has enterable supporter static;
See §15 for enterable and its consequences.
·
·
·
·
·
Containers and supporters are able to react to things being put inside them,
or removed from them, by acting on the signal to Receive or LetGo. For
example, further down in the ‘Ruins’ is a chasm which, perhaps surprisingly,
is implemented as a container:
Object -> chasm "horrifying chasm"
with name ’blackness’ ’chasm’ ’pit’ ’horrifying’ ’bottomless’,
react_before [;
Jump: <>;
Go: if (noun == d_obj) <>;
],
before [;
Enter: deadflag = true;
"You plummet through the silent void of darkness!";
JumpOver: "It’s far too wide.";
],
after [;
Receive: remove noun;
§ , -
print_ret (The) noun, " tumbles silently into the
darkness of the chasm.";
Search: "The chasm is deep and murky.";
has
],
scenery open container;
(Actually the definition will grow in §23, so that the chasm reacts to an eightfoot pumice-stone ball being rolled into it.) Note the use of an after rule for
the Search action: this is because an attempt to ‘‘examine’’ or ‘‘look inside’’
the chasm will cause this action. Search means, in effect, ‘‘tell me what is
inside the container’’ and the after rule prevents a message like ‘‘There is
nothing inside the chasm.’’ from misleading the player. Note also that the
chasm ‘steals’ any stray Jump action in the vicinity using react_before and
converts it into an early death.
• EXERCISE 16
Make an acquisitive bag which will swallow things up but refuses to disgorge them.
4
Receive is sent to an object O when a player tries to put something in O, or on O.
In the rare event that O needs to react differently to these two attempts, it may consult
the library’s variable receive_action to find out whether ##PutOn or ##Insert is the
cause.
·
·
·
·
·
Not all containment is about carrying or supporting things. For instance,
suppose a machine has four levers. If the machine is fixed in place somewhere,
like a printing press, the levers could be provided as four further static
objects. But if it is portable, like a sewing machine, we need to make sure
that the levers always move whenever it moves, and vice versa. The natural
solution is to make the lever-objects children of the machine-object, as though
the machine were a container and the levers were its contents.
However, members of an object which isn’t a container or supporter
are normally assumed by the library to be hidden invisibly inside. In the
case of the levers, this would defeat the point. We can get around this by
giving the machine the transparent attribute, making the levers visible but
not removable.
Containers can also be transparent, making their contents visible even
when closed. The items on top of a supporter are of course always visible,
and it makes no sense for a supporter to be transparent. See §26 for further
details on when contents are listed in inventories and room descriptions.
§ , -
• EXERCISE 17
Make a glass box and a steel box, which behave differently when a lamp is shut up
inside them.
• EXERCISE 18
Make a television set with attached power button and screen.
• 4EXERCISE 19
Implement a macramé bag hanging from the ceiling, inside which objects are visible,
audible and so forth, but cannot be touched or manipulated in any way.
4
The most difficult case to handle is when an object needs to be portable, and to
have sub-objects like lamps and buttons, and also to be a container in its own right.
The solution to this will have to be left until the ‘‘scope addition’’ rules in §32, but
briefly: an object’s add_to_scope property may contain a list of sub-objects to be kept
attached to it but which are not its children.
• REFERENCES
Containers and supporters abound in the example games (except ‘Advent’, which is too
simple, though see the water-and-oil carrying bottle). Interesting containers include
the lottery-board and the podium sockets from ‘Balances’ and the ‘Adventureland’
bottle.
•For supporters, the hearth-rug, chessboard, armchair and mantelpiece of
‘Alice Through the Looking-Glass’ are typical examples; the mantelpiece and spirit
level of ‘Toyshop’ make a simple puzzle, and the pile of building blocks a complicated
one; see also the scales in ‘Balances’.
§13
Doors
The happiness of seizing one of these tall barriers to a room by the
porcelain knob of its belly; this quick hand-to-hand, during which
progress slows for a moment, your eye opens up and your whole
body adapts to its new apartment.
— Francis Ponge (1899–1988), Les plaisirs de la porte
When is a door not a door? It might be a rope-bridge or a ladder,
for instance. Inform provides doors for any situation in which some
game object is intermediate between one place and another, and
might on occasion become a barrier. Doors have a good deal in
common with containers, in that they need to be open to allow access and to
this end can also have openable, lockable or locked. Just as with containers,
any key they have should be stored in the with_key property. The same
actions Open, Close, Lock and Unlock all apply to doors just as they apply to
containers. There are four steps in creating a new door:
(1) give the object the door attribute;
(2) set its door_to property to the location on the other side;
(3) set its door_dir property to the direction which that would be, such as
n_to;
(4) make the location’s map connection in that direction point to the door
itself.
For example, here is a closed and locked door, blocking the way into the
‘Ruins’ Shrine:
Object Corridor "Stooped Corridor"
with description "A low, square-cut corridor, running north to south,
stooping you over.",
n_to Square_Chamber,
s_to StoneDoor;
Object -> StoneDoor "stone door"
with description "It’s just a big stone door.",
name ’door’ ’massive’ ’big’ ’stone’ ’yellow’,
when_closed "Passage south is barred by a massive door of
yellow stone.",
when_open "The great yellow stone door to the south is open.",
door_to Shrine,
door_dir s_to,
§
has
with_key stone_key
static door openable lockable locked;
Note that the door is static – otherwise the player could pick it up and walk
away with it. (Experienced play-testers of Inform games try this every time,
and usually come away with a door or two.) The properties when_closed and
when_open give descriptions appropriate for the door in these two states.
A door is ordinarily only present on one side of a map connection. If a
door needs to be accessible, say openable or lockable, from either side, then
the standard trick is to make it present in both locations using found_in and
to fix the door_to and door_dir to be the right way round for whichever side
the player is on. Here, then, is a two-way door:
Object -> StoneDoor "stone door"
with description "It’s just a big stone door.",
name ’door’ ’massive’ ’big’ ’stone’ ’yellow’,
when_closed "The passage is barred by a massive door
of yellow stone.",
when_open "The great yellow stone door is open.",
door_to [;
if (self in Corridor) return Shrine; return Corridor;
],
door_dir [;
if (self in Shrine) return n_to; return s_to;
],
with_key stone_key,
found_in Corridor Shrine,
has static door openable lockable locked;
where Corridor has s_to set to StoneDoor, and Shrine has n_to set to
StoneDoor. The door can now be opened, closed, entered, locked or unlocked
from either side. We could also make when_open and when_closed into
routines to print different descriptions of the door on each side.
·
·
·
·
·
Puzzles more interesting than lock-and-key involve writing some code to
intervene when the player tries to pass through. The interactive fiction
literature has no shortage of doors which only a player with no possessions can
pass through, for instance.
Care is required here because two different actions can make the player
pass through the door. In the Corridor above, the player might type ‘‘s’’ or
‘‘go south’’, causing the action Go s_obj. Or might ‘‘enter stone door’’ or ‘‘go
through door’’, causing Enter StoneDoor. Provided the door is actually open,
§
the Enter action then looks at the door’s door_dir property, finds that the
door faces south and generates the action Go s_obj. Thus, provided that the
door is open, the outcome is the same and you need only write code to trap the
Go action.
A neater alternative is to make the door_to property a routine. If a door_to
routine returns false instead of a room, then the player is told that the door
‘‘leads nowhere’’, like the broken bridge of Avignon. If door_to returns
true, then the library stops the action on the assumption that something has
happened and the player has been told already.
• EXERCISE 20
Create a plank bridge across a chasm, which collapses if the player walks across it while
carrying anything.
• EXERCISE 21
Create a locked door which turns out to be an immaterial illusion only when the player
tries to walk through it in blind faith.
• REFERENCES
‘Advent’ is especially rich in two-way doors: the steel grate in the streambed, two
bridges (one of crystal, the other of rickety wood) and a door with rusty hinges. See also
the iron gate in ‘Balances’. •The library extension "doors.h" by L. Ross Raszewski
defines a class called Connector of two-way doors, which are slotted automatically into
the map for convenience. Max Kalus’s further extension "doors2.h" enables such
doors to respond to, say, ‘‘the north door’’ from one side and ‘‘the south door’’ from
the other.
§14
Switchable objects
Steven: ‘Well, what does this do?’ Doctor: ‘That is the dematerialising
control. And that over yonder is the horizontal hold. Up there is the scanner,
these are the doors, that is a chair with a panda on it. Sheer poetry, dear boy.
Now please stop bothering me.’
— Dennis Spooner, The Time Meddler (a Doctor Who serial, 1965)
A switchable object is one which can be switched off or on, usually
because it has some obvious button, lever or switch on it. The object
has the attribute on if it’s on, and doesn’t have it if it’s off. (So
there’s no attribute called off, just as there’s no attribute called
closed.) The actions SwitchOn and SwitchOff allow the player to manipulate
anything which is switchable. For example:
Object searchlight "Gotham City searchlight" skyscraper
with name ’search’ ’light’ ’searchlight’ ’template’,
article "the",
description "It has some kind of template on it.",
when_on "The old city searchlight shines out a bat against
the feather-clouds of the darkening sky.",
when_off "The old city searchlight, neglected but still
functional, sits here."
has switchable static;
Something more portable would come in handy for the explorer of ‘Ruins’,
who would hardly have embarked on his expedition without a decent lamp:
Object sodium_lamp "sodium lamp"
with name ’sodium’ ’lamp’ ’heavy’,
describe [;
if (self has on)
"^The sodium lamp squats on the ground, burning away.";
"^The sodium lamp squats heavily on the ground.";
],
battery_power 100,
before [;
Examine: print "It is a heavy-duty archaeologist’s lamp, ";
if (self hasnt on) "currently off.";
if (self.battery_power < 10) "glowing a dim yellow.";
"blazing with brilliant yellow light.";
Burn: <>;
§
SwitchOn:
if (self.battery_power <= 0)
"Unfortunately, the battery seems to be dead.";
if (parent(self) hasnt supporter
&& self notin location)
"The lamp must be securely placed before being
lit.";
Take, Remove:
if (self has on)
"The bulb’s too delicate and the metal handle’s too
hot to lift the lamp while it’s switched on.";
has
],
after [;
SwitchOn: give self light;
SwitchOff: give self ~light;
],
switchable;
The ‘Ruins’ lamp will eventually be a little more complicated, with a daemon
to make the battery power run down and to extinguish the lamp when it runs
out; and it will be pushable from place to place, making it not quite as useless
as the player will hopefully think at first.
4 The reader may be wondering why the lamp needs to use a describe routine to
give itself a description varying with its condition: why not simply write the following?
when_off "The sodium lamp squats heavily on the ground.",
when_on "The sodium lamp squats on the ground, burning away.",
The answer is that when_on and when_off properties, like initial, only apply until an
object has been held by the player, after which it is normally given only a perfunctory
mention in room descriptions. ‘‘You can also see a sodium lamp here.’’ As the
describe property has priority over the whole business of how objects are described in
room descriptions, the above ensures that the full message always appears even if the
object has become old and familiar. For much more on room descriptions, see §26.
• REFERENCES
The original switchable object was the brass lamp from ‘Advent’, which even provides
verbs ‘‘on’’ and ‘‘off’’ to switch it.
•Jayson Smith’s library extension "links.h"
imitates a set of gadgets found in Andrew Plotkin’s game ‘Spider and Web’. In this
scheme, ‘‘linkable’’ machines only work when linked to ‘‘actuators’’, which are switches
of different kinds (remote controls, attachable push-buttons and so on).
§15
Things to enter, travel in and push around
Vehicles were objects that became, in effect, mobile rooms. . . The
code for the boat itself was not designed to function outside the
river section, but nothing kept the player from carrying the deflated
boat to the reservoir and trying to sail across. . .
— Tim Anderson, The History of Zork
Quite so. Even the case of an entirely static object which can be
climbed into or onto poses problems of realism. Sitting on a garden
roller, is one in the gardens, or not? Can one reasonably reach to
pick up a leaf on the ground? The Inform library leaves most of
these subtleties to the designer but has at least a general concept of ‘‘enterable
object’’. These have enterable and the Enter and Exit actions allow the
player to get in (or on) and out (or off) of them.
Enterable items might include, say, an open-topped car, a psychiatrist’s
couch or even a set of manacles attached to a dungeon wall. In practice,
though, manacles are an exceptional case, and one usually wants to make an
enterable thing also a container, or – as in the case of the altar from ‘Ruins’
which appeared in the previous section – a supporter:
Object -> stone_table "slab altar"
with name ’stone’ ’table’ ’slab’ ’altar’ ’great’,
initial "A great stone slab of a table, or altar, dominates the
Shrine.",
has enterable supporter static;
A chair to sit on, or a bed to lie down on, should also be a supporter.
Sitting on furniture, one is hardly in a different location altogether. But
suppose the player climbs into a container which is not transparent and
then closes it from the inside? To all intents and purposes this has become
another room. The interior may be dark, but if there’s light to see by, the player
will want to see some kind of room description. In any case, many enterable
objects ought to look different from inside or on top. Inside a vehicle, a player
might be able to see a steering wheel and a dashboard, for instance. On top of
a cupboard, it might be possible to see through a skylight window.
For this purpose, any enterable object can provide a property called
inside_description, which can hold a string of text or else a routine to print
some text, as usual. If the exterior location is still visible, then the ‘‘inside
description’’ is appended to the normal room description; if not, it replaces the
§ ,
room description altogether. As an extreme example, suppose that the player
gets into a huge cupboard, closes the door and then gets into a plastic cabinet
inside that. The resulting room description might read like so:
The huge cupboard (in the plastic cabinet)
It’s a snug little cupboard in here, almost a room in itself.
In the huge cupboard you can see a pile of clothes.
The plastic walls of the cabinet distort the view.
The second line is the inside_description for the huge cupboard, and the
fourth is that for the plastic cabinet.
• EXERCISE 22
(Also from ‘Ruins’.) Implement a cage which can be opened, closed and entered.
·
·
·
·
·
All the classic games have vehicles (like boats, or fork lift trucks, or hot air
balloons) which the player can journey in, and Inform makes this easy. Here
is a simple case:
Object car "little red car" cave
with name ’little’ ’red’ ’car’,
description "Large enough to sit inside. Among the controls is a
prominent on/off switch. The numberplate is KAR 1.",
when_on "The red car sits here, its engine still running.",
when_off "A little red car is parked here.",
before [;
Go: if (car has on) "Brmm! Brmm!";
print "(The ignition is off at the moment.)^";
],
has switchable enterable static container open;
Actually, this demonstrates a special rule. If a player is inside an enterable
object and tries to move, say ‘‘north’’, the before routine for the object is
called with the action Go n_obj. It may then return:
0
1
2
3
to disallow the movement, printing a refusal;
to allow the movement, moving vehicle and player;
to disallow but print and do nothing; or
to allow but print and do nothing.
If you want to move the vehicle in your own code, return 3, not 2: otherwise
the old location may be restored by subsequent workings. Notice that if you
write no code, the default value false will always be returned, so enterable
objects won’t become vehicular unless you write them that way.
§ ,
4
Because you might want to drive the car ‘‘out’’ of a garage, the ‘‘out’’ verb does not
make the player get out of the car. Instead the player generally has to type something
like ‘‘get out’’ or ‘‘exit’’ to make this happen.
• EXERCISE 23
Alter the car so that it will only drive along roads, and not through all map connections.
·
·
·
·
·
Objects like the car or, say, an antiquated wireless on casters, are too heavy
to pick up but the player should at least be able to push them from place to
place. When the player tries to do this, an action like PushDir wireless is
generated.
Now, if the before routine for the wireless returns false, the game will
just say that the player can’t move the wireless; and if it returns true, the game
will do nothing at all, assuming that the before routine has already printed
something more interesting. So how does one actually tell Inform that the
push should be allowed? The answer is: first call the AllowPushDir routine (a
library routine), and then return true. For example (‘Ruins’ again), here is a
ball on a north-south corridor which slopes upward at the northern end:
Object -> huge_ball "huge pumice-stone ball"
with name ’huge’ ’pumice’ ’pumice-stone’ ’stone’ ’ball’,
description
"A good eight feet across, though fairly lightweight.",
initial
"A huge pumice-stone ball rests here, eight feet wide.",
before [;
PushDir:
if (location == Junction && second == ne_obj)
"The Shrine entrance is far less than eight feet
wide.";
AllowPushDir(); rtrue;
Pull, Push, Turn:
"It wouldn’t be so very hard to get rolling.";
Take, Remove:
"There’s a lot of stone in an eight-foot sphere.";
],
after [;
PushDir:
if (second == s_obj)
"The ball is hard to stop once underway.";
if (second == n_obj)
"You strain to push the ball uphill.";
§ ,
has
],
static;
• 4EXERCISE 24
The library does not normally allow pushing objects up or down. How can the pumice
ball allow this?
• REFERENCES
For an enterable supporter puzzle, see the magic carpet in ‘Balances’ (and several
items in ‘Alice Through the Looking-Glass’). •When a vehicle has a sealed interior
large enough to be a location, it is probably best handled as a location with changing
map connections and not as a vehicle object moved from room to room. See for
instance Martin Braun’s "elevator.inf" example game, providing an elevator which
serves eight floors of a building.
§16
Reading matter and consultation
Making books is a skilled trade, like making clocks.
— Jean de la Bruyère (1645-1696)
‘‘Look up figure 18 in the engineering textbook’’ is a difficult line
for Inform to understand, because almost anything could appear in
the first part: even its format depends on what the second part is.
This kind of request, and more generally
>look up hany words herei in hthe objecti
>read about hany words herei in hthe objecti
>consult hthe objecti about hany words herei
cause the Consult action. In such cases, the noun is the book and there is no
second object. Instead, the object has to parse the hany words herei part itself.
The following variables are set up to make this possible:
consult_from holds the number of the first word in the hany. . .i clause;
consult_words holds the number of words in the hany. . .i clause.
The hany words herei clause must contain at least one word. The words
given can be parsed using library routines like NextWord(), TryNumber(wordnumber) and so on: see §28 for full details. As usual, the before routine
should return true if it has managed to deal with the action; returning false
will make the library print ‘‘You discover nothing of interest in. . .’’.
Little hints are placed here and there in the ‘Ruins’, written in the glyphs
of a not altogether authentic dialect of Mayan. Our explorer has, naturally,
come equipped with the latest and finest scholarship on the subject:
Object dictionary "Waldeck’s Mayan dictionary"
with name ’dictionary’ ’local’ ’guide’ ’book’ ’mayan’
’waldeck’ ’waldeck^s’,
description "Compiled from the unreliable lithographs of the
legendary raconteur and explorer ~Count~ Jean Frederic
Maximilien Waldeck (1766??-1875), this guide contains
what little is known of the glyphs used in the local
ancient dialect.",
correct false,
before [ w1 w2 glyph;
Consult:
§
wn = consult_from;
w1 = NextWord(); ! First word of subject
w2 = NextWord(); ! Second word (if any) of subject
if (consult_words==1 && w1~=’glyph’ or ’glyphs’) glyph = w1;
else if (consult_words==2 && w1==’glyph’) glyph = w2;
else if (consult_words==2 && w2==’glyph’) glyph = w1;
else "Try ~look up in book~.";
switch (glyph) {
’q1’: "(This is one glyph you have memorised!)^^
Q1: ~sacred site~.";
’crescent’: "Crescent: believed pronounced ~xibalba~,
though its meaning is unknown.";
’arrow’: "Arrow: ~journey; becoming~.";
’skull’: "Skull: ~death, doom; fate (not nec. bad)~.";
’circle’: "Circle: ~the Sun; also life, lifetime~.";
’jaguar’: "Jaguar: ~lord~.";
’monkey’: "Monkey: ~priest?~.";
’bird’: if (self.correct) "Bird: ~dead as a stone~.";
"Bird: ~rich, affluent?~.";
default: "That glyph is so far unrecorded.";
}
has
],
proper;
Note that this understands any of the forms ‘‘q1’’, ‘‘glyph q1’’ or ‘‘q1 glyph’’.
(These aren’t genuine Maya glyphs, but some of the real ones once had similar
names, dating from when their syllabic equivalents weren’t known.)
• 44EXERCISE 25
To mark the 505th anniversary of William Tyndale, the first English translator of the
New Testament (who was born some time around 1495 and burned as a heretic in
Vilvorde, Denmark, in 1535), prepare an Inform edition.
·
·
·
·
·
44 Ordinarily, a request by the player to ‘‘read’’ something is translated into an
Examine action. But the ‘‘read’’ verb is defined independently of the ‘‘examine’’ verb
in order to make it easy to separate the two requests. For instance:
Attribute legible;
...
Object textbook "textbook"
with name ’engineering’ ’textbook’ ’text’ ’book’,
description "What beautiful covers and spine!",
§
before [;
Consult, Read:
"The pages are full of senseless equations.";
],
has legible;
...
[ ReadSub; <>; ];
Extend ’read’ first * legible -> Read;
Note that ‘‘read’’ causes a Read action only for legible objects, and otherwise causes
Examine in the usual way. ReadSub is coded as a translation to Examine as well, so that
if a legible object doesn’t provide a Read rule then an Examine happens after all.
• REFERENCES
Another possibility for parsing commands like ‘‘look up hsomethingi in the catalogue’’,
where any object name might appear as the hsomethingi, would be to extend the
grammar for ‘‘look’’. See §30.
§17
People and animals
To know how to live is my trade and my art.
— Michel de Montaigne (1533–1592), Essays
Living creatures should be given the attribute animate so that the
library knows such an object can be talked to, given things, woken
from sleep and so on. When the player treats an animate object as
living in this way, the library calls upon that object’s life property.
This looks like before or after, but only applies to the following actions:
Attack
The player is making hostile advances. . .
Kiss
. . . or amorous ones. . .
WakeOther
. . . or simply trying to rouse the creature from sleep.
ThrowAt
The player asked to throw noun at the creature.
Give
The player asked to give noun to the creature. . .
Show
. . . or, tantalisingly, just to show it.
Ask
The player asked about something. Just as with a ‘‘consult’’ topic
(see §16 above), the variables consult_from and consult_words
are set up to indicate which words the object might like to think
about. (In addition, second holds the dictionary value for the
first word which isn’t ’the’, but this is much cruder.)
Tell
The player is trying to tell the creature about something. The
topic is set up just as for Ask (that is, consult_from and
consult_words are set, and second also holds the first interesting
word).
Answer
This can happen in two ways. One is if the player types ‘‘answer
hsome texti to troll" or ‘‘say hsome texti to troll’’; the other is if
an order is given which the parser can’t sort out, such as ‘‘troll, og
south", and which the orders property hasn’t handled already.
Once again, variables are set as if it were a ‘‘consult’’ topic. (In
addition, noun is set to the first word, and an attempt to read the
text as a number is stored in the variable special_number: for
instance, ‘‘computer, 143’’ will cause special_number to be set
to 143.)
§
Order
This catches any ‘orders’ which aren’t handled by the orders
property (see the next section); action, noun and second are set
up as usual.
If the life rule isn’t given, or returns false, events take their usual course.
life rules vary dramatically in size. The coiled snake from ‘Balances’ shows
that even the tiniest life routine can be adequate for an animal:
Object -> snake "hissing snake"
with name ’hissing’ ’snake’,
initial "Tightly coiled at the edge of the chasm is a
hissing snake.",
life "The snake hisses angrily!",
has animate;
It’s far from unknown for people in interactive fiction to be almost as simplistic
as that, but in most games even relatively passive characters have some ability
to speak or react. Here is the funerary priest standing in the ‘Ruins’ Shrine:
Object priest "mummified priest"
with name ’mummified’ ’priest’,
description
"He is desiccated and hangs together only by will-power.
Though his first language is presumably local Mayan,
you have the curious instinct that he will understand
your speech.",
initial "Behind the slab, a mummified priest stands waiting,
barely alive at best, impossibly venerable.",
life [;
Answer: "The priest coughs, and almost falls apart.";
Ask: switch (second) {
’dictionary’, ’book’:
if (dictionary.correct == false)
"~The ~bird~ glyph... very funny.~";
"~A dictionary? Really?~";
’glyph’, ’glyphs’, ’mayan’, ’dialect’:
"~In our culture, the Priests are ever
literate.~";
’lord’, ’tomb’, ’shrine’, ’temple’:
"~This is a private matter.~";
’ruins’: "~The ruins will ever defeat thieves.
In the underworld, looters are tortured
throughout eternity.~ A pause. ~As are
archaeologists.~";
§
’web’, ’wormcast’:
"~No man can pass the Wormcast.~";
’xibalba’: if (Shrine.sw_to == Junction)
"The priest shakes his bony finger.";
Shrine.sw_to = Junction;
"The priest extends one bony finger
southwest toward the icicles, which
vanish like frost as he speaks.
~Xibalb@’a, the Underworld.~";
}
"~You must find your own answer.~";
Tell: "The priest has no interest in your sordid life.";
Attack, Kiss: remove self;
"The priest desiccates away into dust until nothing
remains, not a breeze nor a bone.";
ThrowAt: move noun to location; <>;
Show, Give:
if (noun == dictionary && dictionary.correct == false) {
dictionary.correct = true;
"The priest reads a little of the book, laughing
in a hollow, whispering way. Unable to restrain
his mirth, he scratches in a correction somewhere
before returning the book.";
}
"The priest is not interested in earthly things.";
has
],
animate;
The Priest only stands and waits, but some characters need to move around,
or to appear and reappear throughout a game, changing in their responses
and what they know. This makes for a verbose object definition full of crossreferences to items and places scattered across the source code. An alternative
is to use different objects to represent the character at different times or places:
in ‘Jigsaw’, for instance, the person called ‘‘Black’’ is seven different objects.
·
·
·
·
·
Animate objects representing people with proper names, like ‘‘Mark Antony’’,
need to be given the proper attribute, and those with feminine names, such
as ‘‘Cleopatra’’, need to be both female and proper, though of course history
would have been very different if. . . Inanimate objects sometimes have proper
names, too: Waldeck’s Mayan dictionary in §16 was given proper. See §26 for
more on naming.
§
·
·
·
·
·
Some objects are not alive as such, but can still be spoken to: microphones,
tape recorders and so on. It would be a nuisance to implement these as
animate, since they have none of the other characteristics of life. Instead,
they can be given just the attribute talkable, making them responsive only to
conversation. They have a life property to handle Answer and so on, but it
will never be asked to deal with, for instance, Kiss. Talkable objects can also
receive orders: see the next section.
·
·
·
·
·
Designers often imagine animate objects as being altogether different from
things, so it’s worth noting that all the usual Inform rules apply equally well
to the living. An animate object still has before and after routines like any
other, so the short list of possible life rules is not as restrictive as it appears.
Animate objects can also react_before and react_after, and it’s here that
these properties really come into their own:
react_before [;
Drop: if (noun == satellite_gadget)
print "~I wouldn’t do that, Mr Bond,~ says Blofeld.^^";
Shoot: remove beretta;
"As you draw, Blofeld snaps his fingers and a giant
magnet snatches the gun from your hand. It hits the
ceiling with a clang. Blofeld silkily strokes his cat.";
];
If Blofeld moves from place to place, these rules usefully move with him.
Animate objects often have possessions as part of the game design. Two
examples, both from ‘The Lurking Horror’:
• an urchin with something bulging inside his jacket pocket;
• a hacker who has a bunch of keys hanging off his belt.
Recall from §12 that the child-objects of an object which isn’t a container
or supporter are outwardly visible only if the object has the transparent
attribute. Here, the hacker should have transparent and the urchin not. The
parser then prevents the player from referring to whatever the urchin is hiding,
even if the player has played the game before and knows what is in there.
• EXERCISE 26
Arrange for a bearded psychiatrist to place the player under observation, occasionally
mumbling insights such as ‘‘Subject puts green cone on table. Interesting.’’
§18
Making conversation
To listen is far harder than to speak. This section overlaps with
Chapter IV, the chapter on parsing text, and the later exercises are
among the hardest in the book. As the following summary table
shows, the simpler ways for the player to speak to people were
covered in the previous section: this section is about ‘‘orders’’.
Example command
‘‘say troll to orc’’
‘‘answer troll to orc’’
‘‘orc, tell me about coins’’
‘‘ask orc about the big troll’’
‘‘ask orc about wyvern’’
‘‘tell orc about lost troll’’
‘‘orc, take axe’’
‘‘orc, yes’’
‘‘ask orc for the shield’’
‘‘orc, troll’’
Rule
life
life
life
life
life
life
order
order
order
order
action
Answer
Answer
Ask
Ask
Ask
Tell
Take
Yes
Give
NotU...
noun
’troll’
’troll’
orc
orc
orc
orc
axe
0
shield
’troll’
second
orc
orc
’coins’
’big’
0
’lost’
0
0
player
orc
consult
2 1
2 1
6 1
4 3
4 1
4 2
3
1
Here we’re supposing that the game’s dictionary includes ‘‘troll’’, ‘‘orc’’ and
so forth, but not ‘‘wyvern’’, which is why ‘‘ask orc about wyvern’’ results
in the action Ask orc 0. The notation NotU... is an abbreviation for
NotUnderstood, of which more later. The two numbers in the ‘‘consult’’
column are the values of consult_from and consult_words, in cases where
they are set.
·
·
·
·
·
When the player types in something like ‘‘pilot, fly south’’, addressing an
object which has animate or at least talkable, the result is called an ‘order’.
The order is sent to the pilot’s orders property, which may if it wishes
comply or react in some other way. Otherwise, the standard game rules will
simply print something like ‘‘The pilot has better things to do.’’ The ‘Ruins’
priest is especially unhelpful:
orders [;
Go: "~I must not leave the Shrine.~";
NotUnderstood: "~You speak in riddles.~";
default: "~It is not your orders I serve.~";
],
The NotUnderstood clause of an orders rule is run when the parser couldn’t
understand what the player typed: e.g., ‘‘pilot, fly somersaults’’.
§
4
The Inform library regards the words ‘‘yes’’ and ‘‘no’’ as being verbs, so it parses
‘‘delores, yes’’ into a Yes order. This can be a slight nuisance, as ‘‘say yes to delores’’
is treated differently: it gets routed through the life routine as an Answer.
4 When a NotUnderstood order is being passed to orders, the library sets up
some variables to help you parse by hand if you need to. The actual order, say
‘‘fly somersaults’’, becomes a sort of consultation topic, with consult_from and
consult_words set to the first word number and the number of words. The variable
etype holds the parser error that would have been printed out, had it been a command
by the player himself. See §33: for instance, the value CANTSEE_PE would mean ‘‘the
pilot can’t see any such object’’.
4 If the orders property returns false or if there wasn’t an orders property in the
first place, the order is sent on either to the Order: part of the life property, if it was
understood, or to the Answer: part, if it wasn’t. (This is how all orders used to be
processed, and it’s retained to avoid making old Inform code go wrong.) If these also
return false, a message like ‘‘X has better things to do’’ (if understood) or ‘‘There is
no reply’’ (if not) is finally printed.
• EXERCISE 27
(Cf. ‘Starcross’.) Construct a computer responding to ‘‘computer, theta is 180’’.
• EXERCISE 28
For many designers, Answer and Tell are just too much trouble. How can you make
attempts to use these produce a message saying ‘‘To talk to someone, try ‘someone,
something’.’’?
·
·
·
·
·
When the player issues a request to an animate or talkable object, they’re
normally parsed in the standard way. ‘‘avon, take the bracelet’’ results in
the order Take bracelet being sent to Kerr Avon, just as typing ‘‘take the
bracelet’’ results in the action Take bracelet passing to the player. The range
of text understood is the same, whether or not the person addressed is Avon.
Sometimes, though, one would rather that different people understood entirely
different grammars.
For instance, consider Zen, the flight computer of an alien spacecraft. It’s
inappropriate to tell Zen to pick up a teleport bracelet and the crew tend to
give commands more like:
‘‘Zen, set course for Centauro’’
‘‘Zen, speed standard by six’’
‘‘Zen, scan 360 orbital’’
‘‘Zen, raise the force wall’’
§
‘‘Zen, clear the neutron blasters for firing’’
For such commands, an animate or talkable object can if it likes provide a
grammar property. This is called at a time when the parser has worked out the
object being addressed and has set the variables verb_wordnum and verb_word
to the word number of the ‘verb’ and its dictionary entry, respectively. For
example, in ‘‘orac, operate the teleport’’ verb_wordnum would be 3, because
the comma counts as a word on its own, and verb_word would be ’operate’.
Once called, the grammar routine can reply to the parser by returning:
false Meaning ‘‘carry on as usual’’.
true Meaning ‘‘you can stop parsing now because I have done it all, and put the
resulting order into the variables action, noun and second’’.
’verb’ Meaning ‘‘don’t use the standard game grammar: use the grammar lines for
this verb instead’’.
-’verb’ Meaning ‘‘use the grammar lines for this verb, and if none of them match,
use the standard game grammar as usual’’.
In addition, the grammar routine is free to do some partial parsing of the early
words provided it moves on verb_wordnum accordingly to show how much it’s
got through.
• 4EXERCISE 29
Implement Charlotte, a little girl who’s playing Simon Says (a game in which she only
follows your instructions if you remember to say ‘‘Simon says’’ in front of them: so
she’ll disobey ‘‘charlotte, wave’’ but obey ‘‘charlotte, simon says wave’’).
• 4EXERCISE 30
Another of Charlotte’s rules is that if you say a number, she has to clap that many
times. Can you play?
• 4EXERCISE 31
Regrettably, Dyslexic Dan has always mixed up the words ‘‘take’’ and ‘‘drop’’. Implement him anyway.
·
·
·
·
·
4 When devising unusual grammars, you sometimes want to define grammar lines
that the player can only use when talking to other people. The vile trick to achieve this
is to attach these grammar lines to an ‘‘untypeable verb’’, such as ’comp,’. This can
never match what the player typed because the parser automatically separates the text
‘‘comp,’’ into two words, ‘‘comp’’ and ‘‘,’’, with a space between them. The same will
happen with any word of up to 7 letters followed by a comma or full stop. For instance,
here’s one way to solve the ‘Starcross’ computer exercise, using an untypeable verb:
§
[ Control;
switch (NextWord()) {
’theta’: parsed_number = 1; return GPR_NUMBER;
’phi’:
parsed_number = 2; return GPR_NUMBER;
’range’: parsed_number = 3; return GPR_NUMBER;
default: return GPR_FAIL;
}
];
Verb ’comp,’ * Control ’is’ number -> SetTo;
(Here, Control is a ‘‘general parsing routine’’: see §31.) The computer itself then
needs these properties:
grammar [; return ’comp,’; ],
orders [;
SetTo:
switch (noun) {
1: print "~Theta"; 2: print "~Phi"; 3: print "~Range";
}
" set to ", second, ".~";
default: "~Does not compute!~";
];
This may not look easier, but it’s much more flexible, as the exercises below may
demonstrate.
• 44EXERCISE 32
How can you make a grammar extension to an ordinary verb that will apply only to
Dan?
• 4EXERCISE 33
Make an alarm clock responding to ‘‘alarm, off’’, ‘‘alarm, on’’ and ‘‘alarm, half past
seven’’ (the latter to set its alarm time).
• 4EXERCISE 34
Implement a tricorder (from Star Trek) which analyses nearby objects on a request like
‘‘tricorder, the quartz stratum’’.
• 4EXERCISE 35
And, for good measure, a replicator responding to commands like ‘‘replicator, tea earl
grey’’ and ‘‘replicator, aldebaran brandy’’.
• 44EXERCISE 36
And a communications badge in contact with the ship’s computer, which answers
questions like ‘‘computer, where is Admiral Blank’’. (This is best done with ‘‘scope
hacking’’, for which see §32.)
§
• 44EXERCISE 37
Finally, construct the formidable flight computer Zen. (Likewise.)
44 To trump one vile trick with another, untypeable verbs are also sometimes used
to create what might be called ‘fake fake actions’. Recall that a fake action is one which
is never generated by the parser, and has no action routine. For instance, there’s no
ThrownAtSub, because ThrownAt is a fake. A fake fake action is a half-measure: it’s a
full action in every respect, including having an action routine, except that it can never
be generated by the parser. The following grammar line creates three of them, called
Prepare, Simmer and Cook:
Verb ’fakes.’ * -> Prepare * -> Simmer * -> Cook;
The author is indebted for this terminology to an algebraic geometry seminar by Peter
Kronheimer on fake and fake fake K3 surfaces.
·
·
·
·
·
4 Difficult ‘‘someone on the other end of a phone’’ situations turn up quite often in
one form or another (see, for instance, the opening scene of ‘Seastalker’) and often a
quite simple solution is fine. If you just want to make something like ‘‘michael, tell me
about the crystals’’ work, when Michael is at the other end of the line, give the phone
the talkable attribute and make the word ’michael’ one of its names. If several
people are on the phone at different times, you can always give the phone a parse_name
property (see §28) to respond to different names at different times.
• 4EXERCISE 38
Via the main screen of the Starship Enterprise, Captain Jean-Luc Picard wants to see
and talk to Noslen Maharg, the notorious tyrant, who is down on the planet Mrofni.
Make it so.
• 44EXERCISE 39
Put the player in telepathic contact with Martha, who is in a sealed room some distance
away, but who has a talent for telekinesis. Martha should respond to ‘‘martha, look’’,
‘‘ask martha about. . .’’, ‘‘say yes to martha’’, ‘‘martha, give me the red ball’’ and the
like.
• REFERENCES
A much fuller example of a ‘non-player character’ is given in the example game ‘The
Thief’, by Gareth Rees (though it’s really an implementation of the gentleman in ‘Zork
I’, himself an imitation of the pirate in ‘Advent’). The thief is capable of walking around,
being followed, stealing things, picking locks, opening doors and so on. •Other good
definitions of animate objects to look at are Christopher in ‘Toyshop’, who will stack
up building blocks on request; the kittens in ‘Alice Through the Looking-Glass’;
•Following
the barker in ‘Balances’, and the animals and dwarves of ‘Advent’.
§
people means being able to refer to them after they’ve left the room: see the library
extension "follower.h" by Gareth Rees, Andrew Clover and Neil James Brown. •A
wandering character with a destination to aim for needs to be able to navigate from
room to room, and possibly through doors. Ideally, a designer should be able to
make a simple instruction like ‘‘head for the West Ballroom’’ without specifying any
route. Two independent library extensions allow this: "MoveClass.h", by Neil James
Brown and Alan Trewartha, is compatible with "follower.h" and is especially strong
on handling doors. Volker Lanz’s "NPCEngine" is designed for what might be called
detective-mystery situations, in which the people inside a country house are behaving
•Irene
independently in ways which must frequently be described to the player.
Callaci’s "AskTellOrder.h" library extension file automatically handles commands in
the form ‘‘ask/tell someone to do something’’.
§19
The light and the dark
>examine darkness
You can’t see the darkness without a light!
>let there be light
Okay, there is light.
>examine the light
It is good.
>divide the light from the darkness
It is so.
>call the light "day" then call the darkness "night"
Called.
Called.
– from a transcript of ‘The Creation’, a game never written but proposed
in some of Infocom’s surviving documents. (‘‘Estimated development time
8-10 months. . . shalts and begats and haths.’’)
Sighted people observe whether it’s light or dark so instantly that
the matter seems self-evident. The Inform library has to use reason
instead, and it rechecks this reasoning very frequently, because
almost any change in the world model can affect the light:
a total eclipse of the sun;
fusing all the lights in the house;
your lamp going out;
a dwarf stealing it and running away;
dropping a lit match which you were seeing by;
putting your lamp into an opaque box and shutting the lid;
black smoke filling up the glass jar that the lamp is in;
the dwarf with your lamp running back into your now-dark room.
The designer of an Inform game isn’t allowed to tell the library ‘‘the player
is now in darkness’’, because this would soon lead to inconsistencies. (If you
want it to be dark, ensure that there are no light sources nearby.) Because light
is automatically calculated, you can write statements like the following, and
leave the library to sort out the consequences:
give lamp light;
remove match;
give glass_jar ~transparent;
move dwarf to Dark_Room;
§
The light attribute means that an object is giving off light, or that a room is
currently lit, for instance because it is outdoors in day-time.
• EXERCISE 40
Abolish darkness altogether, without having to give every location light.
·
·
·
·
·
When the player is in darkness, the current location becomes thedark, a
special object which behaves like a room and has the short name ‘‘Darkness’’.
Instead, the variable real_location always contains the actual room occupied,
regardless of the light level.
The designer can ‘‘customise’’ the darkness in a game by altering its initial, description or short_name properties. For example, the Initialise
routine of the game might include:
thedark.short_name = "Creepy, nasty darkness";
See §20 for how ‘Ruins’ makes darkness menacing.
·
·
·
·
·
Light is reconsidered at the start of the game, after any movement of the
player, after any change of player, and at the end of each turn regardless. The
presence or absence of light affects the Look, Search, LookUnder and Examine
actions, and, since this is a common puzzle, also the Go action: you can provide
a routine called
DarkToDark()
and if you do then it will be called when the player goes from one dark place to
another. (It’s called just before the room description for the new dark room,
normally ‘‘Darkness’’, is printed). You could then take the opportunity to kill
the player off or extract some other forfeit. If you provide no such routine,
then the player can move about as freely in the darkness as in the light.
·
·
·
·
·
44 Darkness rules. Here is the full definition of ‘‘when there is light’’. Remember
that the parent of the player object may not be a room: it may be, say, a red car whose
parent is a large closed cardboard box whose parent is a room.
§
(1) There is light exactly when the parent of the player ‘offers light’.
(2) An object is see-through if:
(a) it is transparent, or
(b) it is a supporter, or
(c) it is a container which is open, or
(d) it is enterable but not a container.
(3) An object offers light if:
(a) it itself has the light attribute set, or
(b) any of its immediate possessions have light, or
(c) it is see-through and its parent offers light.
(4) An object has light if:
(a) it itself has the light attribute set, or
(b) it is see-through and any of its immediate possessions have light, or
(c) any object it places in scope using the property add_to_scope has light.
It may help to note that to ‘‘offer light’’ is to cast light inward, that is, down the object
tree, whereas to ‘‘have light’’ is to cast light outward, that is, up the object tree. The
library routines IsSeeThrough(obj), OffersLight(obj) and HasLightSource(obj)
check conditions (2) to (4), returning true or false as appropriate.
• EXERCISE 41
How would you design a troll who is afraid of the dark, and needs to be bribed with
a light source. . . so that the troll will be as happy with a goldfish bowl containing a
fluorescent jellyfish as he would be with a lamp?
• REFERENCES
For a DarkToDark routine which discourages wandering about caves in the dark, see
‘Advent’. •It is notoriously tricky to handle the gradual falling of night or a gradual
change of visibility. See §51.
§20
Daemons and the passing of time
Some, such as Sleep and Love, were never human. From this class an
individual daemon is allotted to each human being as his ‘witness and
guardian’ through life.
— C. S. Lewis (1898–1963), The Discarded Image
A great Daemon. . . Through him subsist all divination, and the science of
sacred things as it relates to sacrifices, and expiations, and disenchantments,
and prophecy, and magic. . . he who is wise in the science of this intercourse
is supremely happy. . .
— Plato (c.427–347 B.C.), The Symposium,
Bysshe Shelley (1792–1822)
in the translation by Percy
To medieval philosophers, daemons were the intermediaries of
God, hovering invisibly over the world and interfering with it. They
may be guardian spirits of places or people. So also with Inform:
a daemon is a meddling spirit, associated with a particular game
object, which gets a chance to interfere once per turn while it is ‘active’.
‘Advent’ has five: one to deplete the lamp’s batteries, three to move the bear,
the pirate and the threatening little dwarves and one to close the cave when
all the treasures have been collected. Though there isn’t much to say about
daemons, they are immensely useful, and there are some rule-based design
systems for interactive fiction in which the daemon is a more fundamental
concept than the object. (The early 1980s system by Scott Adams, for
instance.)
The daemon attached to an object is its daemon routine, if one is given.
However, a daemon is normally inactive, and must be explicitly activated and
deactivated using the library routines
StartDaemon(object);
StopDaemon(object);
Daemons are often started by a game’s Initialise routine and sometimes
remain active throughout. When active, the daemon property of the object is
called at the end of each turn, regardless of where that object is or what the
circumstances, provided only that the player is still alive. This makes daemons
useful for ‘tidying-up operations’, putting rooms back in order after the player
has moved on, or for the consequences of actions to catch up with the player.
§
• 4EXERCISE 42
Many games contain ‘‘wandering monsters’’, characters who walk around the map.
Use a daemon to implement one who wanders as freely as the player, like the gentleman
thief in ‘Zork’.
• 4EXERCISE 43
Use a background daemon to implement a system of weights, so that the player can
only carry a certain weight before strength gives out and something must be dropped.
It should allow for feathers to be lighter than lawn-mowers.
·
·
·
·
·
It’s also possible to attach a timer to an object. (In other design languages,
timers are called ‘‘fuses’’.) To set up a timer, you need to give an object two
properties: time_left and time_out. Like daemons, timers are inactive until
explicitly started:
StartTimer(object, time);
will set object.time_left to time. This value will be reduced by 1 each turn,
except that if this would make it negative, the Inform library instead sends the
message
object.time_out()
once and once only, after which the timer is deactivated again. You’re free
to alter time_left yourself: a value of 0 means ‘‘will go off at the end of the
present turn’’, so setting time_left to 0 triggers immediate activation. You
can also deactivate the timer, so that it never goes off, by calling
StopTimer(object);
• EXERCISE 44
Construct an egg-timer which runs for three turns.
4
At most 32 timers or daemons can be active at the same time, together with any
number of inactive ones. This limit of 32 is easily raised, though: just define the
constant MAX_TIMERS to some larger value, putting the definition in your code before
"Parser.h" is included.
·
·
·
·
·
§
There is yet a third form of timed event. If a room provides an each_turn
routine, then the library will send the message
location.each_turn()
at the end of every turn when the player is present. Similarly, for every object
O which is near the player and provides each_turn:
O.each_turn()
will be sent every turn. This would be one way to code the sword of ‘Zork’,
for instance, which begins to glow when monsters are nearby. each_turn is
also convenient to run creatures which stay in one place and are only active
when the player is nearby. An ogre with limited patience can therefore have
an each_turn routine which worries the player (‘‘The ogre stamps his feet
angrily!’’ and so forth) while also having a timer set to go off when patience
runs out.
4
‘‘Near the player’’ actually means ‘‘in scope’’, a term which will be properly
defined in §32 but which roughly translates as ‘‘in the same place and visible’’. You can
change the scope rules using an InScope routine, say to make the ‘Zork I’ thief audible
throughout the maze he is wandering around in. In case you want to tell whether scope
is being worked out for ordinary parsing reasons or instead for each_turn processing,
look to see whether the scope_reason variable has the value EACHTURN_REASON. (Again,
see §32 for more.)
4
It is safe to move an object when its own each_turn rule is running, but not to
move any other objects which are likely to be in scope.
• EXERCISE 45
(‘Ruins’.) Make ‘‘the sound of scuttling claws’’ approach in darkness and, after 4
consecutive turns in darkness, kill the player.
• 4EXERCISE 46
Now try implementing the scuttling claws in a single object definition, with no associated
code anywhere else in the program, not even a line in Initialise, and without running
its daemon all the time.
·
·
·
·
·
The library also has a limited ability to keep track of time of day as the game
goes on. The current time is held in the variable the_time and runs on a
24-hour clock: this variable holds the number of minutes since midnight, so it
takes values between 0 and 1439. The time can be set by
SetTime( 60×hhoursi+hminutesi, hratei );
§
The rate controls how rapidly time is moving: a rate of 0 means it is standing
still, that is, that the library doesn’t change it: your routines still can. A positive
rate means that that many minutes pass between each turn, while a negative
rate means that many turns pass between each minute. It’s usual for a timed
game to start off the clock by calling SetTime in its Initialise routine. The
time will appear on the game’s status line, replacing the usual listing of score
and turns, if you set
Statusline time;
as a directive at the start of your source code.
• EXERCISE 47
How could you make your game take notice of the time passing midnight, so that the
day of the week could be nudged on?
• 4EXERCISE 48
Make the lighting throughout the game change at sunrise and sunset.
·
·
·
·
·
4 Here is exactly what happens at the end of each turn. The sequence is abandoned
if at any stage the player dies or wins.
(1)
(2)
(3)
(4)
(5)
(6)
(7)
The turns counter is incremented.
The 24-hour clock is moved on.
Daemons and timers are run (in no guaranteed order).
each_turn takes place for the current room, and then for every object in scope.
An entry point called TimePasses is called, if the game provides such a routine.
Light is re-considered (see §19).
Any items the player now holds which have not previously been held are given the
moved attribute, and score is awarded if appropriate (see §22).
• 4EXERCISE 49
Suppose the player is magically suspended in mid-air, but that anything let go of will
fall out of sight. The natural way to code this is to use a daemon which gets rid of
anything it finds on the floor: this is better than trapping Drop actions because objects
might end up on the floor in many different ways. Why is each_turn better still?
• EXERCISE 50
How would a game work if it involved a month-long archaeological dig, where anything
from days to minutes pass between successive game turns?
§
• REFERENCES
Daemons abound in most games. Apart from ‘Advent’, see the flying tortoise from
‘Balances’ and the chiggers from ‘Adventureland’. For more ingenious uses of daemon,
see the helium balloon and the matchbook from ‘Toyshop’. •Typical timers include
the burning match and the hand grenade from ‘Toyshop’, the endgame timer from
‘Advent’ and the ‘Balances’ cyclops (also employing each_turn). •‘Adventureland’
makes much use of each_turn: see the golden fish, the mud, the dragon and the
bees. •The chapter of ‘Jigsaw’ set on the Moon runs the clock at rate −28, to allow
for the length of the lunar day.
•The library extension "timewait.h" by Andrew
Clover thoroughly implements time of day, allowing the player to ‘‘wait until quarter
past three’’. •Whereas Erik Hetzner’s "printtime.h" does just the reverse: it prints
out Inform’s numerical values of time in the form of text like ‘‘half past seven’’. Erik
is also author of "timepiece.h", which models watches and clocks, allowing them to
run slow or fast compared to the library’s absolute notion of time. (As yet nobody has
needed a relativistic world model.)
§21
Starting, moving, changing and killing the player
Life’s but a walking shadow, a poor player
That struts and frets his hour upon the stage
And then is heard no more; it is a tale
Told by an idiot, full of sound and fury,
Signifying nothing.
— William Shakespeare (1564–1616), Macbeth V v
To recap on §4, an ‘‘entry point routine’’ is one provided by your
own source code which the library may call from time to time.
There are about twenty of these, listed in §A5, and all of them
are optional but one: Initialise. This routine is called before
any text of the game is printed, and it can do many things: start timers and
daemons, set the time of day, equip the player with possessions, make any
random settings needed and so on. It usually prints out some welcoming text,
though not the name and author of the game, because that appears soon after
when the ‘‘game banner’’ is printed. The only thing it must do is to set the
location variable to where the player begins.
This is usually a room, possibly in darkness, but might instead be an
enterable object inside a room, such as a chair or a bed. Like medieval
romance epics, interactive fiction games often start by waking the player from
sleep, sometimes by way of a dream sequence. If your game begins with verbose
instructions before the first opportunity for a player to type a command, you
may want to offer the chance to restore a saved game at once:
print "Would you like to restore a game? >";
if (YesOrNo()) ;
To equip the player with possessions, simply move the relevant objects to
player.
The return value from Initialise is ordinarily ignored, whether true
or false, and the library goes on to print the game banner. If, however, you
return 2, the game banner is suppressed for now. This feature is provided for
games like ‘Sorcerer’ and ‘Seastalker’ which play out a short prelude first. If
you do suppress the banner from Initialise, you should print it no more
than a few turns later on by calling the library routine Banner. The banner is
familiar to players, reassuringly traditional and useful when testing, because it
identifies which version of a game is giving trouble. Like an imprint page with
an ISBN, it is invaluable to bibliographers and collectors of story files.
§ , ,
·
·
·
·
·
‘Ruins’ opens in classical fashion:
[ Initialise;
TitlePage();
location = Forest;
move map to player;
move sodium_lamp to player;
move dictionary to player;
thedark.description = "The darkness of ages presses in on you, and
you feel claustrophobic.";
"^^^Days of searching, days of thirsty hacking through the briars of
the forest, but at last your patience was rewarded. A discovery!^";
];
For the source code of the ‘Ruins’ TitlePage routine, see the exercises in §42.
·
·
·
·
·
The question ‘‘where is the player?’’ can be answered in three different ways.
Looking at parent(player) tells you the object immediately containing the
player, which can be a location but might instead be a chair or vehicle. So a
condition such as:
if (player in Bridleway) ...
would be false if the player were riding a horse through the Bridleway. The
safer alternative is:
if (location == Bridleway) ...
but even this would be false if the Bridleway were in darkness, because
then location would be the special object thedark (see §19). The definitive
location value is stored in real_location, so that:
if (real_location == Bridleway) ...
works in all cases. The condition for ‘‘is the player in a dark Bridleway?’’ is:
if (location == thedark && real_location == Bridleway) ...
Except for the one time in Initialise, you should not attempt to change
either of these variables, nor to move the player-object by hand. One safe way
to move the player in your own source code is to cause actions like
;
§ , ,
but for moments of teleportation it’s easier to use the library routine PlayerTo.
Calling PlayerTo(somewhere) makes the parent-object of the player somewhere and adjusts the location variables accordingly: it also runs through
a fair number of standard game rules, for instance checking the light level
and performing a Look action to print out the new room description. The
value somewhere can be a room, or an enterable object such as a cage or a
traction-engine, provided that the cardinal rule is always observed:
The parent of the player object must at all times be ‘‘location-like’’. An
object is ‘‘location-like’’ if either it is a location, or it has enterable and its
parent is location-like.
In other words, you can’t put the player in an enterable cardboard box if that
box is itself shut up in a free-standing safe which isn’t enterable. And you
can’t PlayerTo(nothing) or PlayerTo(thedark) because nothing is not an
object and thedark is not location-like.
4
Calling PlayerTo(somewhere,1) moves the player without printing any room
description. All other standard game rules are applied.
4
Calling PlayerTo(somewhere,2) is just like PlayerTo(somewhere) except that
the room description is in the form the player would expect from typing ‘‘go east’’
rather than from typing ‘‘look’’. The only difference is that in the former case the room
is (normally) given an abbreviated description if it has been visited before, whereas in
the latter case the description is always given in full.
·
·
·
·
·
44 It’s perhaps worth taking a moment to say what the standard rules upon changing
location are. The following rules are applied whenever a Look action or a call to
PlayerTo take place.
(0) If PlayerTo has been called then the parent of the player, location and
real_location are set.
(1) Any object providing found_in is checked. If it claims to be found_in the location,
it is moved to that location. If not, or if it has absent, it is removed from the
object tree. (See §8.)
(2) The availability of light is checked (see §19), and location is set to thedark if
necessary.
(3) The ‘‘visibility ceiling’’ of the player is determined. For instance, the VC for a
player in a closed wooden box is the box, but for a player in a closed glass box it’s
the location. To be more exact:
(a) The VC of a room is the value of location, i.e., either thedark or the room
object.
(b) If the parent of an object is a room or is ‘‘see-through’’ (see §19 for this
definition), the VC of the object is the VC of the parent.
§ , ,
(c) If not, the VC of the object is its parent.
(4) If the VC is thedark or (say) a box, skip this rule. Otherwise: if the VC has
changed since the previous time that rule (3) produced a VC which wasn’t an
enterable object, then:
(a) The message location.initial() is sent, if the location provides an initial rule. If the library finds that the player has been moved in the course of
running initial, it goes back to rule (3).
(b) The game’s entry point routine NewRoom is called, if it provides one.
(5) The room description is printed out, unless these rules are being gone through
by PlayerTo(somewhere,1). For exactly what happens in printing a room
description, see §26.
(6) If the location doesn’t have visited, give it this attribute and award the player
ROOM_SCORE points if the location has scored. (See §22.) Note that this rule
looks at location, not real_location, so no points unless the room is visible.
·
·
·
·
·
In the course of this chapter, rules to interfere with actions have been attached
to items, rooms and people, but not yet to the player. In §18 it was set
out that an order like ‘‘austin, eat tuna’’ would result in the action Eat tuna
being passed to austin.orders, and heavy hints were dropped that orders
and actions are more or less the same thing. This is indeed so, and the player’s
own object has an orders routine. This normally does nothing and always
returns false to mean ‘‘carry on as usual’’, but you can install a rule of your
own instead:
player.orders = MyNewRule;
where MyNewRule is a new orders rule. This rule is applied to every action or
order issued by the player. The variable actor holds the person asked to do
something, usually but not always player, and the variables action, noun and
second are set up as usual. For instance:
Example command
actor
action
noun
second
‘‘put tuna in dish’’
‘‘austin, eat tuna’’
player
Austin
Insert
Eat
tuna
tuna
dish
nothing
For instance, if a cannon goes off right next to the player, a period of partial
deafness might ensue:
[ MyNewRule;
if (actor ~= player) rfalse;
Listen: "Your hearing is still weak from all that cannon-fire.";
default: rfalse;
];
§ , ,
The if statement needs to be there to prevent commands like ‘‘helena, listen’’
from being ruled out – after all, the player can still speak.
• 4EXERCISE 51
Why not achieve the same effect by giving the player a react_before rule instead?
• EXERCISE 52
(Cf. ‘Curses’.) Write an orders routine for the player so that wearing a gas mask will
prevent speech.
·
·
·
·
·
The player object can not only be altered but switched altogether, allowing the
player to play from the perspective of someone or something else at any point in
the game. The player who tampers with Dr Frankenstein’s brain transference
machine may suddenly become the Monster strapped to the table. A player
who drinks too much wine could become a drunk player object to whom many
different rules apply. The ‘‘snavig’’ spell of ‘Spellbreaker’, which transforms
the player to an animal like the one cast upon, could be implemented thus.
Similarly the protagonist of ‘Suspended’, who telepathically runs a weathercontrol station by acting through six sensory robots, Iris, Waldo, Sensa, Auda,
Poet and Whiz. In a less original setting, a player might have a team of
four adventurers exploring a labyrinth, and be able to switch the one being
controlled by typing the name. In this case, an AfterLife routine (see below)
may be needed to switch the focus back to a still-living member of the team
after one has met a sticky end.
The library routine ChangePlayer(obj) transforms the player to obj. Any
object can be used for this. There’s no need to give it any name, as the parser
always understands pronouns like ‘‘me’’ and ‘‘myself’’ to refer to the current
player-object. You may want to set its description, as this is the text printed
if the player types ‘‘examine myself’’, or its capacity, the maximum number
of items which this form of the player can carry. Finally, this player-object can
have its own orders property and thus its own rules about what it can and
can’t do.
As ChangePlayer prints nothing, you may want to follow the call with a
<>; action.
4 You can call ChangePlayer as part of a game’s Initialise routine, but if so then
you should do this before setting location.
4 Calling ChangePlayer(obj,1); does the same except that it makes the game print
‘‘(as Whoever)’’ during subsequent room descriptions.
§ , ,
4 The body dispossessed remains where it was, in play, unless you move it away or
otherwise dispose of it. The player-object which the player begins with is a librarydefined object called selfobj, and is described in room descriptions as ‘‘your former
self’’.
• EXERCISE 53
In Central American legend, a sorceror can transform himself into a nagual, a familiar
such as a spider-monkey; indeed, each individual has an animal self or wayhel, living
in a volcanic land over which the king, as a jaguar, rules. Turn the player into wayhel
form.
• EXERCISE 54
Alter the Wormcast of ‘Ruins’ (previously defined in §9) so that when in wayhel form,
the player can pass through into a hidden burial shaft.
• EXERCISE 55
To complete the design of this sequence from ‘Ruins’, place a visible iron cage above
the hidden burial shaft. The cage contains skeletons and a warning written in glyphs,
but the player who enters it despite these (and they all will) passes into wayhel life.
(The transformed body is unable to move the sodium lamp, but has nocturnal vision,
so doesn’t need to.) Unfortunately the player is now outside a cage which has closed
around the human self which must be returned to, while the wayhel lacks the dexterity
to open the cage. The solution is to use the Wormcast to reach the Burial Chamber,
then bring its earthen roof down, opening a connection between the chamber below
and the cage above. Recovering human form, the player can take the grave goods,
climb up into the cage, open it from the inside and escape. Lara Croft would be proud.
·
·
·
·
·
44 The situation becomes a little complicated if the same orders routine has to do
service in two situations: once while its owner is a character met in the course of play,
and then a second time when the player has changed into it. This could be done simply
by changing the value of orders when the transformation takes place, but an alternative
is to arrange code for a single orders routine like so:
orders [;
if (player == self) {
if (actor == self) {
! I give myself an action
}
else {
! I give someone else an order
}
}
§ , ,
else {
! Someone else gives me an order
}
],
• 44EXERCISE 56
Write an orders routine for a Giant with a conscience, who will refuse to attack even a
mouse, but so that a player who becomes the Giant can be wantonly cruel.
·
·
·
·
·
‘‘There are only three events in a man’s life; birth, life and death; he is not
conscious of being born, he dies in pain and he forgets to live.’’ (Jean de la
Bruyère again.) Death is indeed the usual conclusion of an adventure game,
and occurs when the source code sets the library variable deadflag to true:
in normal play deadflag is always false. The ‘‘standard Inform rules’’ never
lead to the player’s death, so this is something the designer must explicitly do.
Unlike life, however, interactive fiction offers another way out: the player
can win. This happens if and when the variable deadflag is set to 2.
Any higher values of deadflag are considered to be more exotic forms of
death. The Inform library doesn’t know what to say if these arise, so it calls the
DeathMessage entry point routine, which is expected to look at deadflag and
can then print something suitable. For instance, ‘Ruins’ has a chasm which is
subject to the following before rule:
before [;
Enter: deadflag = 3;
"You plummet through the silent void of darkness, cracking
your skull against an outcrop of rock. Amid the pain and
redness, you dimly make out the God with the
Owl-Headdress...";
JumpOver: "It is far too wide.";
],
and this means that it needs a DeathMessage routine like so:
[ DeathMessage;
if (deadflag == 3) print "You have been captured";
];
Capture was about the worst fate that could befall you in the unspeakably
inhumane world of Maya strife.
§ , ,
‘Ruins’ doesn’t, but many games allow reincarnation or, as David M.
Baggett points out, in fact resurrection. You too can allow this, by providing
an AfterLife entry point routine. This gets the chance to do as it pleases
before any ‘‘death message’’ is printed, and it can even reset deadflag to
false, causing the game to resume as though nothing had happened. Such
AfterLife routines can be tricky to write, though, because the game often has
to be altered to reflect what has happened.
• REFERENCES
The magic words ‘‘xyzzy’’ and ‘‘plugh’’ in ‘Advent’ employ PlayerTo. •‘Advent’ has
an amusing AfterLife routine: for instance, try collapsing the bridge by leading the
bear across, then returning to the scene after resurrection. ‘Balances’ has one which
only slightly penalises death.
§22
Miscellaneous constants, scoring, quotations
For when the One Great Scorer comes
To write against your name,
He marks – not that you won or lost –
But how you played the game.
— Grantland Rice (1880–1954), Alumnus Football
There are some constants which, if defined in your code before the
library files are included, change the standard game rules or tell
the Inform library about your game. Two such constants appeared
back in §4: the strings of text Story and Headline.
Constant Story "ZORK II";
Constant Headline "^An Interactive Plagiarism^
Copyright (c) 1995 by Ivan O. Ideas.^";
·
·
·
·
·
The library won’t allow the player to carry an indefinite number of objects. As
was mentioned in §21, the limit is the value of capacity for the current playerobject, which you’re free to vary during play. The library sets up the capacity
of the usual player-object to be equal to a constant called MAX_CARRIED, which
is normally 100. But you can define it differently, and ‘Ruins’ does:
Constant MAX_CARRIED = 7;
For these purposes a container counts as only one object, even if it contains
hundreds of other objects.
Many games, perhaps too many, involve collecting vast miscellanies of
items until a use has been found for each. A small value of MAX_CARRIED
will then annoy the player unreasonably, whereas a large one will stretch
plausibility. The standard resolution is to give the player a sack for carrying
spare objects, and the Inform library provides a feature whereby the designer
can nominate a given container to be this ‘‘sack’’:
Object satchel "satchel"
with description "Big and with a smile painted on it.",
name ’satchel’, article ’your’,
when_closed "Your satchel lies on the floor.",
when_open "Your satchel lies open on the floor.",
has container open openable;
Constant SACK_OBJECT = satchel;
§ , ,
(This is from ‘Toyshop’: the ‘Ruins’ have been sacked too many times as it
is.) The convenience this offers is that the game will now automatically put
old, least-used objects away into the sack as the game progresses, provided the
sack is still being carried:
>get biscuit
(putting the old striped scarf into the canvas rucksack to make room)
Taken.
·
·
·
·
·
The ‘‘Invisiclues’’ hints of some of the reissued Infocom games sometimes
included a category called ‘‘For Your Amusement’’, listing some of the
improbable things the game can do. Only the victorious should read this, as
it might spoil surprises for anyone else. You can, optionally, provide such
‘‘amusing’’ information by defining the constant. . .
Constant AMUSING_PROVIDED;
. . . and also providing an entry point routine called Amusing. For a player who
has won the game, but not one who has merely died, the usual question
Would you like to RESTART, RESTORE a saved game or QUIT?
will then become
Would you like to RESTART, RESTORE a saved game, see some suggestions for AMUSING things to do or QUIT?
(The best way to provide such suggestions, if there are many, is to use a menu
system like that described in §44.) One of the worst-kept secrets of the Inform
library is that an option not mentioned by this question is to type ‘‘undo’’,
which will undo the last move and restore the player to life. If you feel that this
option should be mentioned, define the constant:
Constant DEATH_MENTION_UNDO;
Finally, this end-of-game question will also mention the possibility of typing
‘‘full’’ to see a full score breakdown, if tasks are provided (see below).
·
·
·
·
·
The other constants you are allowed to define help keep the score. There are
two scoring systems provided by the library, side by side: you can use both
or neither. You can always do what you like to the library’s score variable in
any case, though the ‘‘fullscore’’ verb might not then fully account for what’s
§ , ,
happened. Whatever scoring system you use, you should define MAX_SCORE, as
‘Ruins’ for instance does by declaring:
Constant MAX_SCORE = 30;
This is the value which the library tells to the player as the maximum score
attainable in text like:
You have so far scored 0 out of a possible 30, in 1 turn.
Note that the library does not check that this is the actual maximum score it’s
possible to clock up: and nor does it cause the game to be automatically won
if the maximum is achieved. The game is won when and only when deadflag
is set to 2 (see §21), regardless of score.
The simpler scoring system awards points for the first time certain objects
are picked up, and the first time certain places are entered. (As long as there
is light to see by: no points unless you can recognise that you’ve arrived
somewhere interesting.) To make an item or a place carry a points bonus,
give it the attribute scored. You may also want to vary the amounts of these
bonuses by defining two constants:
OBJECT_SCORE
ROOM_SCORE
points for picking up a scored object (normally 4);
points for entering a scored room (normally 5)
The more elaborate scoring system keeps track of which ‘‘tasks’’ the player
has accomplished. These are only present if the constant TASKS_PROVIDED
is defined, and then the further constant NUMBER_TASKS should indicate how
many tasks have to be accomplished. If this value is N , then the tasks are
numbered 0, 1, 2, . . ., N − 1. The number of points gained by solving each
task must be defined in a -> array with N entries called task_scores, like so:
Constant TASKS_PROVIDED;
Constant NUMBER_TASKS = 5;
Constant MAX_SCORE = 25;
Array task_scores -> 3 7 3 5 7;
Thus task 2 scores three points, task 3 scores five points. Since the entries in a
-> array have to be numbers between 0 and 255, no task can have a negative
score or a score higher than 255. Besides a points score, each task has a name,
and these are printed by an entry point routine called PrintTaskName. For
instance (‘Toyshop’):
§ , ,
[ PrintTaskName task_number;
switch (task_number) {
0: "eating a sweet";
1: "driving the car";
2: "shutting out the draught";
3: "building a tower of four";
4: "seeing which way the mantelpiece leans";
}
];
Finally, the game’s source code should call Achieved(task_number) to tell the
library that the given task has been completed. If this task has been completed
before, the library will do nothing: if not, the library will award the appropriate
number of points. The verb ‘‘full’’ will give a full score breakdown including
the achieved task in all future listings.
·
·
·
·
·
When points are awarded by a call to Achieved, or by the player picking up
a scored object, or visiting a scored place, or simply by the source code
itself altering the score variable, no text is printed at the time. Instead, the
library will normally notice at the end of the turn in question that the score has
changed, and will print a message like:
[Your score has gone up by three points.]
Not all players like this feature, so it can be turned on and off with the ‘‘notify’’
verb, but by default it is on. The designer can also turn the feature off and on:
it is off if the library’s variable notify_mode is false, on if it is true.
·
·
·
·
·
Another (optional) entry point routine, called PrintRank, gets the chance to
print text additional to the score. It’s called PrintRank because the traditional
‘‘something additional’’ is a ranking based on the current score. Here is
‘Ruins’:
[ PrintRank;
print ", earning you the rank of ";
if (score == 30) "Director of the Carnegie Institution.";
if (score >= 20) "Archaeologist.";
if (score >= 10) "Curiosity-seeker.";
if (score >= 5) "Explorer.";
"Tourist.";
];
§ , ,
·
·
·
·
·
Besides the score breakdown, two more verbs are usually provided to the
player: ‘‘objects’’ and ‘‘places’’. The former lists off all the objects handled by
the player and where they are now; the latter lists all the places visited by the
player. In some game designs, these verbs will cause problems: you can get rid
of them both by defining the constant NO_PLACES.
• 4EXERCISE 57
Suppose one single room object is used internally for the 64 squares of a gigantic
chessboard, each of which is a different location to the player. Then ‘‘places’’ is likely
to result in only the last-visited square being listed. Fix this.
·
·
·
·
·
The rest of this section runs through some simple ‘‘special effects’’ which are
often included in games. See Chapter VII for much more on this, and in
particular see §44 for using the "Menus.h" library extension.
The first effect is hardly special at all: to ask the player a yes/no question.
To do this, print up the question and then call the library routine YesOrNo,
which returns true/false accordingly.
The status line is perhaps the most distinctive feature of Infocom games in
play. This is the (usually highlighted) bar across the top of the screen. Usually,
the game automatically prints the current game location, and either the time
or the score and number of turns taken. It has the score/turns format unless
the directive
Statusline time;
has been written in the program, in which case the game’s 24-hour clock is
displayed. See §20 for more on time-keeping.
4 If you want to change this, you need to Replace the parser’s DrawStatusLine
routine. This requires some assembly language programming: there are several
examples of altered status lines in the exercises to §42.
·
·
·
·
·
Many games contain quotations, produced with box statements like so:
box "I might repeat to myself, slowly and soothingly,"
"a list of quotations beautiful from minds profound;"
"if I can remember any of the damn things."
""
"-- Dorothy Parker";
§ , ,
A snag with printing such boxes is that if you do it in the middle of a turn
then it will probably scroll half-off the screen by the time the game finishes
printing for the turn. The right time to do so is just after the prompt (usually
‘‘>’’) is printed, when the screen will definitely scroll no more. You could
use the Prompt: slot in LibraryMessages to achieve this (see §25), but a
more convenient way is to put your box-printing into the entry point routine
AfterPrompt, which is called at this time in every turn.
• EXERCISE 58
Devise a class Quotation, so that calling QuoteFrom(Q) for any quotation Q will cause
it to be displayed at the end of the current turn, provided it hasn’t been quoted before.
• REFERENCES
‘Advent’ contains ranks and an Amusing reward (but doesn’t use either of the scoring
systems provided by the library, instead working by hand). •‘Balances’ uses scored
objects (for its cubes). •‘Toyshop’ has tasks, as above. •‘Adventureland’ uses its
TimePasses entry point to recalculate the score every turn (and watch for victory).
§23
‘Ruins’ revisited
These fragments I have shored against my ruins
—T. S. Eliot (1888–1965), The Waste Land
Though ‘Ruins’ is a small world, and distorted in shape by the need
to have ‘‘one example of everything’’, it seems worth a few pages
to gather together the fragments scattered through the book so far
and complete the game.
To begin with, the stage set back in §4 was too generic, too plain. Chosen
at random, it may as well become La Milpa, a site rediscovered in dense
rainforest by Eric Thompson in 1938, towards the end of the glory days of
archaeological exploration. (La Milpa has been sadly looted since.) Though
this is something of a cliché of interactive fiction, ‘Ruins’ contains two objects
whose purpose is to anchor the player in time and place. Lining the packing
case, we find:
Object -> -> newspaper "month-old newspaper"
with name ’times’ ’newspaper’ ’paper’ ’month-old’ ’old’,
description
"~The Times~ for 26 February, 1938, at once damp and brittle
after a month’s exposure to the climate, which is much the
way you feel yourself. Perhaps there is fog in London.
Perhaps there are bombs.";
And among the player’s initial possessions:
Object map "sketch-map of Quintana Roo"
with name ’map’ ’sketch’ ’sketch-map’ ’quintana’ ’roo’,
description
"This map marks little more than the creek which brought you
here, off the south-east edge of Mexico and into deepest
rainforest, broken only by this raised plateau.";
To turn from the setting to the prologue, it is a little too easy to enter the
structure in the rainforest. And if the steps were always open, surely the rain
would sluice in? Recall that the Forest includes inward map connections to
the steps, which are a door, instead of to the Square_Chamber directly:
d_to steps, in_to steps,
§
The steps are, however, intentionally blocked by rubble, as happened in the
case of the hidden staircase found by Alberto Ruz beneath the Temple of the
Inscriptions at another site, Palenque:
Object -> steps "stone-cut steps"
with name ’steps’ ’stone’ ’stairs’ ’stone-cut’ ’pyramid’ ’burial’
’structure’ ’ten’ ’10’,
rubble_filled true,
description [;
if (self.rubble_filled)
"Rubble blocks the way after only a few steps.";
print "The cracked and worn steps descend into a dim
chamber. Yours might ";
if (Square_Chamber hasnt visited)
print "be the first feet to tread";
else print "have been the first feet to have trodden";
" them for five hundred years. On the top step is
inscribed the glyph Q1.";
],
door_to [;
if (self.rubble_filled)
"Rubble blocks the way after only a few steps.";
return Square_Chamber;
],
door_dir d_to
has scenery door open;
Next we must face the delicate issue of how to get from the mundane
1930s to a semi-magical Maya world. The stock device of Miguel Angel
Asturias’s Leyendas de Guatemala and other founding works of magic realism
(indeed, of Wuthering Heights come to think of it) is for the arriving, European
rationalist to become fascinated by a long tale told by local peasants. This
would take too much code to get right in so small a game, though, and
there also remains the unresolved question of what the mushroom is for. So
we delete the original before rule for the mushroom, which made eating it
potentially fatal, and instead give it an after:
Eat: steps.rubble_filled = false;
"You nibble at one corner, unable to trace the source of an
acrid taste, distracted by the flight of a macaw overhead
which seems to burst out of the sun, the sound of the beating
of its wings almost deafening, stone falling against stone.";
§
Great Plaza *
packing case
(newspaper, wet-plate camera)
mushroom
@
@
@rubble steps
@
@
@
@
@
Square Chamber
Wormcast
inscriptions
shaft of sunlight
eggsac (stone key)
burrowing
Stooped Corridor
pygmy statuette
locked stone door
Upper Canyon
pumice ball
Shrine
paintings
slab altar (mask, priest)
icicles
Xibalbá
stela
@
@
@
@
Antechamber
iron cage (skeletons)
@
@
@
earthen roof@
Lower Canyon
chasm
@
@ ?
@
@
Burial Shaft
honeycomb
standing on ball
Pumice Ledge
incised bone
* The player begins at the Great Plaza, carrying the map, the sodium lamp
and Waldeck’s Mayan dictionary.
§
This is fairly authentic, as a cult of hallucinogenic mushrooms seems to have
existed. Anyway, the player is getting off pretty lightly considering that Maya
lords also went in for narcotic enemas and ritual blood-letting from the tongue
and penis, an interactive fiction for which the world is not yet ready.
Descending underground, §8 alluded to an eggsac which burst on contact
with natural light. Naturally, this repellent object belongs in the Wormcast,
and here is its definition:
Object -> eggsac "glistening white eggsac",
with name ’egg’ ’sac’ ’eggs’ ’eggsac’,
initial "A glistening white eggsac, like a clump of frogspawn
the size of a beach ball, has adhered itself to something
in a crevice in one wall.",
after [;
Take: "Oh my.";
],
react_before [;
Go: if (location == Square_Chamber && noun == u_obj) {
deadflag = true;
"The moment that natural light falls upon the
eggsac, it bubbles obscenely and distends. Before
you can throw it away, it bursts into a hundred
tiny, birth-hungry insects...";
}
];
Note the clue that some object is within the egg sac: as it turned out in §8, a
stone key, released by putting the egg sac into the shaft of sunlight. The key
itself has a very short definition:
Object stone_key "stone key"
with name ’stone’ ’key’;
This is not an easy puzzle, but a further clue is provided by the carvings on
the Stone Chamber wall, which can be translated with Waldeck’s dictionary to
read ‘‘becoming the Sun/life’’.
Given the key, the player must next solve the problem of bringing light
to the Stooped Corridor, by pushing the burning sodium lamp south. This
means, as promised in §14, adding a before rule to the lamp:
PushDir:
if (location == Shrine && second == sw_obj)
"The nearest you can do is to push the sodium lamp to
the very lip of the Shrine, where the cave floor falls
§
away.";
AllowPushDir(); rtrue;
§14 also promised to run down the battery power: although since 100 turns
is plenty, this rule doesn’t play any real part in the game and is just windowdressing. We need to StartDaemon(sodium_lamp) in the Initialise routine,
and define the lamp’s daemon along the following lines:
daemon [;
if (self hasnt on) return;
if (--self.battery_power == 0)
give self ~light ~on;
if (self in location) {
switch (self.battery_power) {
10: "^The sodium lamp is getting dimmer!";
5: "^The sodium lamp can’t last much longer.";
0: "^The sodium lamp fades and suddenly dies.";
}
}
],
With the obligatory light puzzle solved, the Shrine can at last be opened:
Object Shrine "Shrine"
with description
"This magnificent Shrine shows signs of being hollowed out
from already-existing limestone caves, especially in the
western of the two long eaves to the south.",
n_to StoneDoor, se_to Antechamber,
sw_to
"The eaves taper out into a crevice which would wind
further if it weren’t jammed tight with icicles. The glyph
of the Crescent is not quite obscured by ice.";
Looking up the Crescent glyph in the dictionary (§16) reveals that it stands
for the word ‘‘xibalbá’’: asking the Priest (§17) brought into existence by
wearing the jade mosaic mask (§11) melts the icicles and makes the southwest
connection to Xibalbá, of which more later. No Maya game would be complete
without their religiously-observed cyclical countings of time:
Object -> paintings "paintings"
with name ’painting’ ’paintings’ ’lord’ ’captive’,
initial "Vividly busy paintings, of the armoured Lord trampling
on a captive, are almost too bright to look at, the
graffiti of an organised mob.",
§
has
description "The flesh on the bodies is blood-red. The markers
of the Long Count date the event to 10 baktun 4 katun 0 tun
0 uinal 0 kin, the sort of anniversary when one Lord would
finally decapitate a captured rival who had been ritually
tortured over a period of some years, in the Balkanised
insanity of the Maya city states.",
static;
Having called the priest ‘‘calendrical’’, here’s another topic to Ask the priest
about:
’paintings’: "The calendrical priest frowns.
~10 baktun, 4 katun, that makes 1,468,800 days
since the beginning of time: in your calendar
19 January 909.~";
And also, to make the point once more, and remind the player once again of
distant Europe:
Show, Give: ...
if (noun == newspaper)
"He looks at the date. ~12 baktun 16 katun 4 tun
1 uinal 12 kin~, he declares before browsing the
front page. ~Ah. Progress, I see.~";
Perhaps the player will never see either calculation: if so, it doesn’t matter, as
dates and calendars turn out to be this game’s red herring. (Every game should
have one.) The Antechamber of the Shrine is an undistinguished room. . .
Object Antechamber "Antechamber"
with description
"The southeastern eaves of the Shrine make a curious
antechamber.",
nw_to Shrine;
. . . except that this is where the iron cage (§15 and §21) is located, so that
the Burial Shaft lies below, with its complex puzzle in which the player is
transformed to a warthog and back again, opening the shaft. Lastly, then, in
the southwest eaves of the Shrine is a natural cave entrance, which in Mayan
mythology leads to the Underworld. There is supposed to be a crossroads
here, but in this modest game a three-way junction is all we have space for:
Object Junction "Xibalb@’a"
with description
"Fifty metres beneath rainforest, and the sound of water
is everywhere: these deep, eroded limestone caves
§
extend like tap roots. A slither northeast by a broad
collapsed column of ice-covered rock leads back to the
Shrine, while a kind of canyon floor extends uphill to
the north and downwards to south, pale white like shark’s
teeth in the diffused light from the sodium lamp above.",
ne_to Shrine, n_to Canyon_N, u_to Canyon_N,
s_to Canyon_S, d_to Canyon_S,
has light;
Treasure -> stela "stela"
with name ’stela’ ’boundary’ ’stone’ ’marker’,
initial
"A modest-sized stela, or boundary stone, rests on a
ledge at head height.",
description
"The carvings appear to warn that the boundary of
Xibalb@’a, Place of Fright, is near. The Bird glyph is
prominent.";
This canyon houses the eight-foot pumice stone ball (see §15) at the north
end, and the chasm (§12, §21) at the south:
Object Canyon_N "Upper End of Canyon"
with s_to Junction, d_to Junction,
description
"The higher, broader northern end of the canyon rises only
to an uneven wall of volcanic karst.",
has light;
Object Canyon_S "Lower End of Canyon"
with n_to Junction, u_to Junction,
s_to "Into the chasm?", d_to nothing,
description
"At the lower, and narrower, southern end, the canyon stops
dead at a chasm of vertiginous blackness. Nothing can be
seen or heard from below.",
has light;
As promised in §12, the chasm must react to having the stone ball pushed into
it, which means adding this to the chasm’s definition:
each_turn [;
if (huge_ball in parent(self)) {
remove huge_ball; Canyon_S.s_to = On_Ball;
Canyon_S.description = "The southern end of the canyon
now continues onto the pumice-stone ball, wedged into
the chasm.";
§
"^The pumice-stone ball rolls out of control down the
last few feet of the canyon before shuddering into the
jaws of the chasm, bouncing back a little and catching
you a blow on the side of the forehead. You slump
forward, bleeding, and... the pumice-stone shrinks,
or else your hand grows, because you seem now to be
holding it, staring at Alligator, son of seven-Macaw,
across the ball-court of the Plaza, the heads of his
last opponents impaled on spikes, a congregation baying
for your blood, and there is nothing to do but to throw
anyway, and... but this is all nonsense, and you have
a splitting headache.";
}
],
(Horribly violent, semi-religious ball-game rituals are common in early central
America, though nobody knows why: all substantial Maya cities have prominent ball-courts.) A fat paragraph of text in which fairly interesting things
happen, beyond the player’s control, is sometimes called a ‘‘cut-scene’’. Most
critics dislike the casual use of cut-scenes, and ‘Ruins’ would be a better game
if the confrontation with Alligator were an interactive scene. But this manual
hasn’t the space. Instead, here is the final location, which represents ‘‘standing
on the wedged ball’’:
Object On_Ball "Pumice-Stone Ledge"
with n_to Canyon_S, d_to Canyon_S, u_to Canyon_S,
description
"An impromptu ledge formed by the pumice-stone ball,
wedged into place in the chasm. The canyon nevertheless
ends here.",
has light;
Treasure -> "incised bone"
with name ’incised’ ’carved’ ’bone’,
initial
"Of all the sacrificial goods thrown into the chasm, perhaps
nothing will be reclaimed: nothing but an incised bone,
lighter than it looks, which projects from a pocket of wet
silt in the canyon wall.",
description
"A hand holding a brush pen appears from the jaws of
Itzamn@’a, inventor of writing, in his serpent form.";
§
And this is where Itzamná lays down his brush, for this is the fifth and last of
the cultural artifacts to collect. The game ends when they are all deposited in
the packing case, a rule which means a slight expansion of the definition of
Treasure:
after [;
Insert:
...
if (score == MAX_SCORE) {
deadflag = 2;
"As you carefully pack away ", (the) second,
" a red-tailed macaw flutters down from the tree-tops,
feathers heavy in the recent rain, the sound of its
beating wings almost deafening, stone falling against
stone... As the skies clear, a crescent moon rises above
a peaceful jungle. It is the end of March, 1938, and it
is time to go home.";
}
4
The following sequence of 111 moves tests ‘Ruins’ from beginning to end:
‘‘examine case / read newspaper / get newspaper / get camera / down / examine steps
/ east / up / enter structure 10 / eat mushroom / eat mushroom / down / examine
inscriptions / look up arrow in dictionary / east / get eggsac / west / put eggsac in
sunlight / get key / drop lamp / light lamp / look / push lamp s / get statuette / drop
all except camera / photograph statuette / get key / open door / unlock door with key /
open door / drop key / get pygmy / north / up / put pygmy in case / down / south / get
dictionary / get newspaper / south / north / push lamp south / examine paintings / drop
all but camera / photograph mask / get mask / get dictionary / get newspaper / wear
mask / show dictionary to priest / show newspaper to priest / drop newspaper / ask priest
about ruins / ask priest about paintings / se / nw / push lamp se / push lamp nw / sw /
look up crescent in dictionary / ask priest about xibalba / sw / north / push ball south /
push ball south / south / drop all but camera / remove mask / drop mask / photograph
bone / get all / north / north / drop all but camera / photograph stela / get all / ne / north
/ north / up / put bone in case / put stela in case / put mask in case / down / east / nw /
west / south / south / push lamp se / examine cage / enter cage / open cage / nw / north
/ north / east / down / east / up / drop all but camera / photograph honeycomb / get all /
up / open cage / out / push lamp nw / north / north / up / put honeycomb in case’’.
• REFERENCES
I am indebted to, which is to say I have roundly travestied, the following: ‘‘Mapping
La Milpa: a Maya city in northwestern Belize’’ (Tourtellot, Clarke and Hammond,
Antiquity 67 (1993), 96–108). All the same ‘Ruins’ favours old-fashioned ideas of Maya,
a good example being the ‘‘calendrical priests’’ fondly imagined by early archaeologists
before Maya writing was deciphered. •The standard all-in-one-book book is Michael
§
D. Coe’s The Maya (fourth edition). •The same author’s history of Breaking the Maya
Code offers pungently vivid portraits of Sir Eric Thompson and Maximilien Waldeck.
The British Museum guide Maya Glyphs, by S. D. Houston, is a trifle more reliable
than Waldeck’s work. •Numerous colour-postcard photographs by F. Monfort are
collected in Yucatan and the Maya Civilization (Crescent Books, 1978).
§24
The world model described
This section is a self-contained summary of the concepts and
systematic organising principles used by Inform to present the
illusion of describing a physically real environment, with which the
protagonist of a game interacts. All details of implementation are
ignored and Inform jargon is either avoided or explained. While many of the
rules are standard to all world models used for interactive fiction, some are
not, and the footnotes remark on some of the more interesting cases. The
next section, §25, buries itself back into implementation to discuss how to add
new rules or to change those rules below which you don’t agree with. The
description below is arranged as follows: ¶1. Substance; ¶2. Containment;
¶3. Space; ¶4. Sense; ¶5. Time; ¶6. Action.
1. Substance
1.1. Objects make up the substance of the world: its places and their
contents, abstract relations between these, transient states the world can be in,
actors and trends (such as the flowing of a river).
1.2. At any given time, every object in the world model is one and only
one of the following kinds: the player; a room; the darkness object; an item;
the compass object; a compass direction; or something which is out of play.1
1.2.1. The player object represents the protagonist of the game.
1.2.2. A room represents some region of space, not necessarily with walls
or indoors.
1.2.3. The darkness pseudo-room represents the experience of being in a
dark place, and has no specific location in space.
1
The compass pseudo-item and the darkness pseudo-room are anomalies. The
compass arises from a generic convention which is unrealistic but avoids making the
game needlessly tiresome: that the player has a perfect inherent sense of direction.
The representation of darkness is less defensible. Although vaguely justifiable from the
assumption that the experience of being in one entirely dark place is much like another,
it came about instead for reasons of implementation: partly to bundle up various
darkness-related texts as though they were room descriptions, and partly because
early versions of the Inform run-time format (version 3 of the Z-machine) imposed a
restriction that the status line displayed above the screen could only be the short name
of an object.
§
1.2.4. An item represents some body (or group of similar bodies) with a
definite spatial position at any given time. It is not necessarily solid but its
substance is indivisible.
1.2.5. The compass pseudo-item represents the frame of reference within
the protagonist’s head, and is not an actual compass with its attendant hazards
of being dropped, broken, stolen or becoming invisible in pitch darkness.
1.2.6. A compass direction represents a potential direction of movement,
such as ‘‘northeast’’, ‘‘down’’, ‘‘in’’ or ‘‘starboard’’.
1.2.7. Objects out of play represent nothing in the model world and the
protagonist does not interact with them. Out of play objects are mostly things
which once existed within the model world but which were destroyed, or which
have not yet been brought into being.
1.2.8. An object can change its kind as the game progresses: for instance a
compass direction can be taken out of play, and certain items can become the
player, making the object which was previously the player now merely an item.
1.3. Objects are indivisible even if the player may see internal structure to
them, such as the four legs which are part of a chair. Pieces of objects only
appear in the model if additional objects are provided for them.2
1.4. Objects have internal states and are therefore distinguishable from
each other by more than their position in the containment tree. Some are
‘‘open’’, some are ‘‘concealed’’ and so on, and they are given descriptions and
other specifications by the designer.
1.4.1. Some objects are ‘‘switchable’’ between two mutually exclusive
states, ‘‘on’’ and ‘‘off’’. These represent machines, trapdoors and the like,
which behave differently when set to when unset, and which generally have
rules about the process of setting and unsetting.
1.4.2. Some objects are ‘‘lockable’’ and someone with the specified ‘‘key’’
object can switch between mutually exclusive states, ‘‘locked’’ and ‘‘unlocked’’.
These objects represent containers and doors with locks.
1.4.3. Some objects are ‘‘openable’’ and therefore in one of two mutually
exclusive states, ‘‘open’’ and ‘‘closed’’. If such an object is closed and also
locked then it cannot be opened. These objects represent containers and doors.
2
The atomic theory of matter. Since there are a finite number of atoms each with a
finite range of possible states and positions, Heraclitus’ doctrine that one cannot stand
in the same river twice is false within the model world, and this can detract from its
realism. It is sometimes too easy for the protagonist to exactly undo his actions as
if they had never been and to return exactly to the world as it was before. Another
problem with ¶1.3 is that liquids need to be divisible (‘‘some water’’ becoming ‘‘some
water’’ and ‘‘some water’’).
§
2. Containment
2.1. Some objects are contained within other objects in what is sometimes
called a tree, meaning that: (i) an object can either be contained in one other
object (called its ‘‘parent’’), or not contained in any; (ii) there is no ‘‘loop’’ of
objects such that each is contained in the next and the last is contained in the
first. The pattern of containment changes frequently during play but (i) and
(ii) always hold.3
2.1.1. The objects contained within something are kept in order of how
long they have been there. The first possession (sometimes called the ‘‘child’’)
is the one most recently arrived, and the last is the one which has been
contained for longest.
2.1.2. In some games there are objects called ‘‘floating objects’’ which
represent something found in many locations, such as a stream flowing through
the map, or a pervasive cloud. These give the appearance of violating rule
¶2.1, but do not: the effect is an illusion brought about by making the floating
object belong at all times to the same room as the player.
2.2. The following rules remain true at all times:
2.2.1. A room is not contained.
2.2.2. The darkness object and the compass are not contained.
2.2.3. A compass direction is contained in the compass object but itself
contains nothing.
2.2.4. An item is always contained; either in the player, in another item
or a room.
2.2.5. The player is always contained in a visitable object. An object is
‘‘visitable’’ if either (a) it is a room, or (b) it is enterable and it has a visitable
parent.
2.2.6. An object out of play is either contained in another object out of
play, or else not contained at all.4
2.3. Containment models a number of subtly different kinds of belonging:
2.3.1. The contents of a room object are near each other (usually within
sight and touch) in the space represented by the room. For instance, the player,
3
Infocom world models all included rule (ii) but their implementations made no
systematic effort to enforce this, so that Infocom were perpetually fixing bugs arising
from putting two containers inside each other.
4
Without knowing the context, and looking at the object tree alone, it isn’t easy to
distinguish a room from an object out of play, which can make writing good debugging
features tricky.
§
a wooden door and a table might all be contained in a room representing the
inside of a small hut.5
2.3.2. The contents of the compass are the compass directions available
in principle to an actor. If ‘‘north’’ is removed from the compass, then north
becomes meaningless even if an actor is in a room with a north exit; if ‘‘aft’’ is
added, then it need not follow that any room actually has an exit leading aft.
2.3.3. The contents of the player fall into two categories: those which are
‘‘worn’’, and the rest.
2.3.3.1. Worn objects represent clothing or accessories held onto the body
without the need for hands, such as a belt or a rucksack.
2.3.3.2. The rest represent items being held in the player’s hands.
2.3.4. The contents of an item model different kinds of belonging, depending on the nature of the item:6
2.3.4.1. Some items are ‘‘containers’’: they represent boxes, bottles, bags,
holes in the wall and so on. The contents of a container are considered to be
physically inside it. At any given time a container can be ‘‘open’’ or ‘‘closed’’.
2.3.4.2. Some items are ‘‘supporters’’: they represent tables, plinths, beds
and so on. The contents of a supporter are considered to be physically on top
of it.
2.3.4.3. Some items are ‘‘animate’’: they represent people, sentient
creatures generally and higher animals. The contents of an animate object are
considered to be carried by it.
2.3.4.4. Some items are ‘‘enterable’’, meaning that it is possible for the
player to be contained within them. A large tea-chest might be an enterable
container; a bed might be an enterable supporter. In the case of an enterable
which is neither container nor supporter, and which contains the player, the
player is considered to be confined close to the enterable: for instance, by a
pair of manacles.
2.3.4.5. Failing this, the contents of an item represent pieces or components of it, such as a lever attached to machinery, or a slot cut into a slab of
masonry.
5
This part of the model was invented by the early mainframe ‘Zork’. The Crowther
and Woods ‘Advent’, and later the Scott Adams games, required all objects to belong
to a room but had a pseudo-room which represented ‘‘being carried by the player’’.
Objects were equated with potential possessions and no object represented the player.
6
The model in ¶2.3.4 has attracted criticism as being simplistic in three respects:
(a) an object is assumed not to be both a container and a supporter, so what about
an oven?; (b) while the player has a distinction between items worn and items carried,
animate objects do not; (c) being inside and being on top of are modelled, but being
underneath or behind are not.
§
3. Space
3.1. Spatial arrangement on the small scale (at ranges of a few moments’
walking distance or less) is modelled by considering some objects to lie close
together and others to lie far apart.
3.1.1. Objects ultimately contained in the same room are considered to be
close enough together that walking between the two is a largely unconscious
act.
3.1.1.1. The model takes no account of directions from one such object to
another, except as described in ¶2.3.4 above (e.g., that contents of a supporter
are on top of it).
3.1.1.2. All objects with the same parent are considered to be equidistant
from, and to have equal access to, each other.7
3.1.2. Objects ultimately contained in different rooms are considered to
be so far apart that will not ordinarily interact. Because of this the model takes
no account of one being further away than another.8
3.2. Spatial arrangement on the large scale (at ranges of an appreciable
walking distance or more) is modelled by joining rooms together at their edges,
much as a patchwork quilt is made.
3.2.1. Rooms joined together represent areas which are adjacent in that
they are separated by so short a walk that the walker does not have opportunity
to think twice and turn back, or to stop halfway and do something else.
3.2.2. Rooms are joined either by a map connection or a door.9
3.2.2.1. Map connections come in different kinds, representing different
directions in the geography of the world. These physical directions are north,
south, east, west, northeast, northwest, southeast, southwest, up, down, in
and out.
3.2.2.2. Each compass direction corresponds to a single physical direction
at any given moment, and this represents the actual direction which a player
will walk in if he tries to walk in a given direction within his own frame of
reference.10
7
In ¶4 it will become apparent that a measure of distance between two objects is
given by their distance apart in the containment tree.
8
Note that ¶3.1.2 says that objects far apart cannot interact, but ¶3.1.1 does not say
that objects close together can do. A honey bee in a sealed hive cannot interact with or
be aware of the delivery man carrying the hive to its new beekeeper. Rules on which
close objects can interact are the subject of ¶4.
9
There is no requirement for such a join to be usable from the other side.
10
For instance, on board a ship a player may try to walk ‘‘starboard’’, that being a
§
3.2.2.3. A ‘‘door’’ is an item representing something which comes between
two locations, which must be passed through or by in order to go from one to
the other, and which it requires some conscious decision to use.11
3.2.2.4. As with a compass direction, a door corresponds to a single
physical direction at any given moment.
4. Sense
4.1. The senses are used in the world model primarily to determine
whether the player can, or cannot, interact with a nearby object. Three
different kinds of accessibility are modelled: touch, sight and awareness.12
4.2. Awareness = sight + touch, that is, the player is aware of something
if it can be seen or touched.13
4.2.1. Awareness represents the scope of the player’s ability to interact
with the world in a single action. Although the player may pursue a grand
strategy, he must do so by a series of tactical moves each within the scope of
awareness.14
4.3. There are only two strengths of light: good enough to read by and
pitch blackness, which we shall call ‘‘light’’ and ‘‘dark’’.
4.3.1. Some containers and other items are specified as being transparent
to light, meaning that light can pass through from what contains them to what
they contain (for instance a glass box or a machine whose contents are the
compass direction, but will in fact move in some physical direction such as northeast.
Games have also been designed in which the player’s frame of reference consists only of
‘‘left’’, ‘‘right’’, ‘‘forward’’, ‘‘back’’, ‘‘up’’ and ‘‘down’’, whose assignment to physical
directions changes continuously in play.
11
Thus a vault door, a plank bridge or a ventilation duct high on one wall would
be represented by doors, but an open passageway or a never-locked and familiar door
within a house would instead be represented by map connections.
12
Hearing, taste and smell are not modelled: instead Inform’s implementation
provides convenient verbs and actions for designers to add their own ad-hoc rules.
13
Awareness = sense is a strongly restrictive position. Thanks to simplistic parsers,
in some early games the player is somehow aware of all objects everywhere, even those
not yet encountered. In some more modern games awareness = sense + recent memory.
For instance an item dropped in a dark room can be picked up again, since it is assumed
that the player can remember where it is.
14
The Inform parser enforces this by recognising only those typed commands which
request interaction with objects the player is aware of at a given time.
§
buttons on the its front panel), while others are opaque (for instance a wooden
box or a spy who keeps all her belongings concealed).
4.3.2. An object is called ‘‘see-through’’ if it is transparent, or if it is a
supporter, or if it is an open container, or if it is the player.
4.3.3. Some rooms are specified by the designer as having ambient light
(those representing outdoor locations, caves with fluorescent ore formations,
strip-lit office buildings and the like); some items are specified as giving off
light (those representing torches, lanterns and the like). The room or item is
said to be ‘‘lit’’.
4.3.4. There is light for the player to see by only if there is a lit object,
close to the player in the sense of ¶3, such that every object between them is
see-through. (For instance, if a player is in a sealed glass box in a cupboard
which also contains a key, the glass box comes between player and key, but the
cupboard does not.)15
4.4. What the player can touch depends on whether there is light.
4.4.1. In the light, the player can touch anything close to the player
provided that (a) every object between them is see-through and (b) none of
the objects between them is a closed container.
4.4.2. In the dark, the player can touch (a) anything contained in the
player and (b) the enterable object which the player is contained in (if any).16
4.5. What the player can see also depends on whether there is light,
but also on whether the designer has specified that any nearby items are
‘‘concealed’’, which models their being hidden from view. Concealed objects
remain touchable but you would need to know they were there, since sight
alone would not reveal this. On once being picked up, a concealed object
ceases to be concealed.
4.5.1. In the light, the player can see any non-concealed object close to
the player provided that (a) every object between them is see-through and (b)
none of the objects between them is concealed.
4.5.2. In the dark, the player can see nothing.
15
This definition is not as realistic as it looks. Translucency is equated with
transparency, presenting problems for glazed glass jars. ‘‘Close to the player’’ implies
that light never spills over from one room to another, for instance through an open
window on a sunny day.
16
Equivalently, in the dark you can touch exactly those objects adjacent to you in
the containment tree. Thus anything which can be touched in the dark can also be
touched in the light but not vice versa.
§
5. Time
5.1. The passage of time is represented by describing and changing the
model world at regular intervals, each cycle being called a ‘‘turn’’. The interval
from one such moment to the next is considered to be the time occupied by
carrying out these changes, so that all basic changes (i.e., actions: see ¶6)
consume the same unit of time.
5.2. Changes in the model world are carried out by a number of independent processes called ‘‘daemons’’. Some daemons are built in and others
added by the designer of a particular game, conventionally by associated them
with certain objects over which they have sway. A turn consists of the daemons
being invoked one at a time until each has been given the chance to intervene,
or to decline to intervene.
5.2.1. Daemons are invoked in the sequence: action daemon, any designed
daemons (in no particular order), each-turn daemon, scoring daemon, clock
daemon.
5.2.2. Daemons have the opportunity to carry out arbitrary changes to the
state of the objects, but should be designed to violate the rules of the model
world as little as possible. The built-in daemons do not violate them at all.
5.2.3. Certain designed daemons are ‘‘timers’’, meaning that they are set
to decline to intervene for a set number of turns and will then act once and
once only (unless or until reset).
5.3. The action daemon consults the player at the keyboard by asking
which action should be performed and then performing it. (See ¶6.) The
action daemon is invoked first in each turn.
5.4. The each-turn daemon polls any object of which the player is aware.
If it has been specified by the designer as having an each-turn rule, then that
rule is applied.
5.5. The scoring daemon keeps track of the length of the game so far by
keeping count of the number of turns. It also measures the player’s progress
by keeping score:
5.5.1. Points are awarded if the player is for the first time carrying an item
which the designer has marked as ‘‘scored’’.
5.5.2. Also if the player is for the first time inside a room which the
designer has marked as ‘‘scored’’, provided there is light to see by.
5.5.3. The library groups score ranges into ranks, with names such as
‘‘Beginner’’ or ‘‘Expert’’ specified by the designer. If this feature is used at all
then every possible score should correspond to one and only one rank.
§
5.6. The clock daemon records the time of day to the nearest minute.
(Day, month and year are not modelled.)
5.6.1. Between one change of state and the next, the clock is normally
advanced by one minute, but the designer (not the player) can arrange for this
to be varied in play either to several changes per minute, or several minutes
per change.
5.6.2. The designer can also change the time at any point.
5.6.3. Time passes at a constant rate for all objects, so that the player’s
measurement of the passage of time is the same as everybody else’s.17
5.7. Time stops immediately when any daemon declares the player’s death
or victory.
5.7.1. Only rules provided by the designer will do this: the model world’s
normal rules are set up so that, whatever the player asks to do, time will
continue indefinitely.18
6. Action
6.1. An action is a single impulse by the player to do something, which if
feasible would take sufficiently little time to carry out that there would be no
opportunity to change one’s mind half-way or leave it only partly carried out.
6.1.1. An action ‘‘succeeds’’ if the activity in question does take place
within the model world. If not, it ‘‘fails’’.19
6.1.2. Not all impulses are sensible or feasible. Some actions fail because
circumstances happen to frustrate them, but others could never have succeeded
in any circumstances.
6.1.3. Some actions (the so-called ‘‘group 3 actions’’) have a model in
which, once the impulse is verified as being feasible, all that happens is that a
message along the lines of ‘‘nothing much happens’’ is given.
17
This is a restriction, though not because it ignores relativistic effects (at walking
speeds these are of the order of 1 part in 1017 ). Suppose the player enters a room where
time runs slow and all actions take five times longer than they would elsewhere. This
means that daemons which handle changes far away from the player also run five times
slower than normal, relative to the model world’s clock.
18
Thus victory does not occur automatically when all scored objects are found and
all scored rooms visited; death does not occur automatically when the player crosses
from a dark room to another dark room, and so on.
19
It does not necessarily follow that any objects will change their states: an action
to look under something will result only in text being printed out, but this is successful
because the looking under has become part of the history of the model world.
§
6.1.4. Some actions imply the need for other actions to take place first. In
such cases the first action is tried, and only if this is successful will the second
action be tried.
6.1.4.1. An action which requires an object to be held (such as eating)
will cause a take action for that object.
6.1.4.2. A particular container, specified by the designer as the ‘‘sack
object’’, is such that when the player is carrying the maximum legal number
of items, any further take action causes the least recently taken item to be put
into the sack first.20
6.1.4.3. A drop action for a piece of clothing being worn will cause a
remove-clothing action first.
6.1.5. Other actions are recognised as being composites and so are split
into a sequence of simpler constituent actions.
6.1.5.1. Actions involving multiple objects specified by the player as a
collective batch (‘‘take six buttons’’, ‘‘drop all’’) are split into one action for
each object.
6.1.5.2. Emptying a container is split up into individual remove and drop
actions.
6.1.5.3. Entering something which would require intermediate objects to
be exited or entered is split into a sequence of exits and entrances.
6.2. An action can involve no objects other than the player, or else one
other object of which the player is aware, or else two other objects of which
the player is aware.21
6.2.1. The following actions fail if the player cannot touch the object(s)
acted on: taking, dropping, removing from a container, putting something on
or inside something, entering something, passing through a door, locking and
unlocking, switching on or off, opening, closing, wearing or removing clothing,
eating, touching, waving something, pulling, pushing or turning, squeezing,
throwing, attacking or kissing, searching something.
6.2.2. The following actions fail if the player cannot see the object(s) acted
on: examining, searching or looking under something.22
20
Casual assumptions, in this case that all games have a single rucksack-like
container, often make world models needlessly restrictive. Compare the Scott Adams
game engine, in which one object is designated as ‘‘the lamp’’.
21
The Inform parser will not generate actions concerning objects of which the player
is unaware.
22
Thus there is only one action requiring you to both see and touch the object acted
on: searching.
§
6.3. The actions modelled are grouped under five headings below: actions
of sense, alteration, arrangement, movement and communication. There is
also one inaction: waiting, in which the player chooses to do nothing for the
turn.
6.4. Actions of sense are those which seek information about the world
without changing it: inventory, examining, consulting, looking, looking under,
searching, listening, tasting, touching and smelling.23
6.4.1. ‘‘Look’’ describes only those parts of the room which can be seen.
(In particular, concealed objects are omitted.) In addition, certain objects
are ‘‘scenery’’ and are omitted from specific mention in room descriptions
because, although visible, they are either too obvious to mention (such as the
sky) or are mentioned already in the room-specific text.
6.5. Actions of alteration are those in which the player changes something
without moving it: opening, closing, locking, unlocking, switching on and off,
wearing and removing clothing, and eating.
6.5.1. A successful eating action results in the object being taken out of
play.24
6.6. Actions of arrangement are those in which the player rearranges the
spatial arrangement of things: taking, dropping, removing, inserting, putting
on top of, transferring, emptying, emptying onto or into, pulling, pushing,
turning and throwing.
6.6.1. Pulling, pushing and turning are only minimally provided: they are
checked to see if the player can indeed carry out the action, but then nothing
happens unless the designer writes code to make it happen, because the model
doesn’t include directions of pointing.
6.6.2. Actions of arrangement fail if the object is ‘‘static’’, meaning that it
is fixed in place.
6.7. Actions of movement are those in which the player moves about:
going, entering, exiting, getting off and pushing something from one room to
another.
6.7.1. An attempt to enter a container or door fails if it is not open.
6.7.2. Only an enterable object or a door can be entered.
6.7.3. The player can only get off or exit from the enterable object
currently holding him.
23
Reading is not distinguished from examining: instead the consult action provides
for looking things up in books.
24
This is the only circumstance in which the rules for the model world destroy an
object.
§
6.7.4. Certain enterable objects are ‘‘vehicles’’. Within a vehicle, movement actions are permitted as if the player were standing on the floor.
Otherwise, no movements are permitted if the player is within an enterable
object, except to enter or exit.
6.7.4.1. If the player travels in a vehicle, the vehicle object is also moved
to the new room, and the player remains within it.
6.7.5. Certain objects are ‘‘pushable’’ and accompany the player on an
otherwise normal movement. These, too, move to the new room.
6.7.6. An attempt to move through a concealed door fails as if the door
were not there at all.25
6.8. Actions of communication are those in which the player relates to
other people within the model world: giving, showing, waking somebody up,
attacking, kissing, answering, telling, asking about and asking for something.
6.8.1. Actions of communication fail unless the object is animate, except
that certain ‘‘talkable’’ objects can be addressed in conversation.
25
I have no idea what this is doing in the Inform world model, but it seems to be
there: perhaps the writing-room puzzle in the ‘Curses’ attic needed it. Andrew Plotkin:
‘‘I’ve always said that the definition of concealed was ad-hoc and not well understood
by anybody.’’
§25
Extending and redefining the world model
A circulating library in a town is as an ever-green tree of diabolical
knowledge! It blossoms through the year!
— R. B. Sheridan (1751–1816), The Rivals
In The History of Zork, Tim Anderson summed up the genesis of
the ‘Zork I’ world model – and perhaps also its exodus, that is, its
adaptation to other games – when he commented that ‘‘the general
problem always remained: anything that changes the world you’re
modelling changes practically everything in the world you’re modelling.’’
Substantial changes to the world model often have profound implications and
can lead to endless headaches in play-testing if not thought through. Even a
single object can upset the plausibility of the whole: a spray-can, for instance,
unless its use is carefully circumscribed. On the other hand, introducing whole
categories of objects often causes no difficulty at all, if they do not upset
existing ideas such as that of door, location, container and so on. For instance,
the set of collectable Tarot cards in the game ‘Curses’ have numerous rules
governing their behaviour, but never cause the basic rules of play to alter for
other items or places.
·
·
·
·
·
In making such an extension the natural strategy is simply to define a new
class of objects, and to take advantage of Inform’s message system to make
designing such objects as easy and flexible as possible. For example, suppose
we need a class of Egyptian magical amulets, small arrowhead-like totems worn
on the wrist and made of semi-precious minerals, with cartoon-like carvings.
(The Ashmolean Museum, Oxford, has an extensive collection.) Each amulet
is to have the power (but only if worn) to cast a different spell. Almost all of
the code for this will go into a class definition called Amulet. This means that
if (noun ofclass Amulet) ...
provides a convenient test to see if an object noun is an amulet, and so forth.
(This imposes a restriction that an object can’t start or stop being an amulet in
the course of play, because class membership is forever. If this restriction were
unacceptable, a new attribute would need to be created instead, in the same
way that the standard world model recognises any object with the attribute
container as a container, rather than having a Container class.)
§
Suppose the requirement is that the player should be able to type ‘‘cast
jasper amulet’’, which would work so long as the jasper amulet were being
worn. It seems sensible to create an action called Cast, and this necessitates
creating an action subroutine to deal with it:
[ CastSub;
"Nothing happens.";
];
Verb ’cast’ ’invoke’ * noun -> Cast;
Nothing happens here because the code is kept with the Amulet class instead:
Class Amulet
with amulet_spell "Nothing happens.",
before [ destination;
Cast:
if (self hasnt worn)
"The amulet rattles loosely in your hand.";
destination = self.amulet_spell();
switch (destination) {
false: "Nothing happens.";
true: ;
default: print "Osiris summons you to...^";
PlayerTo(destination);
}
rtrue;
],
has clothing;
Thus every Amulet provides an amulet_spell message, which answers the
question ‘‘you have been cast: what happens now?’’ The reply is either false,
meaning nothing has happened; true, meaning that something did happen; or
else an object or room to teleport the player to.
From the designer’s point of view, once the above extension has been
made, amulets are easy to create and have legible code. Here are four example
spells:
amulet_spell "The spell fizzles out with a dull phut! sound.",
amulet_spell [;
if (location == thedark) {
give real_location light;
"There is a burst of magical light!";
}
],
amulet_spell HiddenVault,
§
amulet_spell [;
return random(LeadRoom, SilverRoom, GoldRoom);
],
An elaborate library extension will end up defining many classes, grammar,
actions and verb definitions, and these may neatly be packaged up into an
Include file and to be placed among the library files.
44 Such a file should contain the directive System_file;, as then other designers
will be able to Replace routines from it, just as with the rest of the library.
·
·
·
·
·
So much for extending the Inform model with new classes: the rest of the
section is about modifying what’s ordinarily there. The simplest change, but
often all that’s needed, is to change a few of the standard responses called
‘‘library messages’’, such as the ‘‘Nothing is on sale.’’ which tends to be printed
when the player asks to buy something, or the ‘‘Taken.’’ when something is
picked up. (To change every message, and with it the language of the game,
see §34.)
To set new library messages, provide a special object called LibraryMessages, which must be defined between the inclusion of the "Parser.h" and
"Verblib.h" library files. This object should have just one property, a before
rule. For example:
Object LibraryMessages
with before [;
Jump: if (real_location ofclass ISS_Module)
"You jump and float helplessly for a while in zero
gravity here on the International Space Station.";
SwitchOn:
if (lm_n == 3) {
"You power up ", (the) lm_o, ".";
}
];
This object is never visible in the game, but its before rule is consulted before
any message is printed: if it returns false, the standard message is printed; if
true, then nothing is printed, as it’s assumed that this has already happened.
The Jump action only ever prints one message (usually ‘‘You jump on the
spot.’’), but more elaborate actions such as SwitchOn have several, and Take
has thirteen. The library’s variable lm_n holds the message number, which
counts upwards from 1. In some cases, the object being talked about is held
§
in lm_o. The messages and numbers are given in §A4. New message numbers
may possibly be added in future, but old ones will not be renumbered.
An especially useful library message to change is the prompt, normally
set to "^>" (new-line followed by >). This is printed under the action Prompt
(actually a fake action existing for this very purpose). You can use this to make
the game’s prompt context-sensitive, or to remove the new-line from before
the prompt.
• EXERCISE 59
Infocom’s game ‘The Witness’ has the prompt ‘‘What should you, the detective, do
next?’’ on turn one and ‘‘What next?’’ subsequently. Implement this.
4
LibraryMessages can also be used as a flexible way to alter the rules governing
individual actions. Here are two examples in the guise of exercises.
• 4EXERCISE 60
Under the standard world model (¶6.7.4 in §24 above), a player standing on top of
something is not allowed to type, say, ‘‘east’’ to leave the room: the message ‘‘You’ll
have to get off . . . first’’ is printed instead. Change this.
• 4EXERCISE 61
Under standard rules (¶6.6.1 in §24 above), a player trying to ‘‘push’’ something which
is not static or scenery or animate will find that ‘‘Nothing obvious happens’’. Add
the rule that an attempt to push a switchable item is to be considered as an attempt to
switch it on, if it’s off, and vice versa. (This might be useful if a game has many buttons
and levers.)
·
·
·
·
·
The Library is itself written in Inform, and with experience it’s not too hard
to alter it if need be. But to edit and change the library files themselves is
an inconvenience and an inelegant way to carry on, because it would lead to
needing a separate copy of all the files for each project you work on. Because
of this, Inform allows you to Replace any routine or routines of your choice
from the library, giving a definition of your own which is to be used instead of
the one in the standard files. For example, if the directive
Replace BurnSub;
is placed in your file before the library files are included, Inform ignores the
definition of BurnSub in the library files. You then have to define a routine
called BurnSub yourself: looking in the library file "Verblib.h", the original
turns out to be tiny:
[ BurnSub; L__M(##Burn,1,noun); ];
§
All this does is to print out library message number 1 for Burn, the somewhat
preachy ‘‘This dangerous act would achieve little.’’ You could instead write a
fuller BurnSub providing for a new concept of an object being ‘‘on fire’’.
44 Inform even allows you to Replace ‘‘hardware’’ functions like random or parent,
which would normally be translated directly to machine opcodes. This is even more
‘‘at your own risk’’ than ordinary usages of Replace.
·
·
·
·
·
What are the implications of fire likely to be? One way to find out is to read
through the world model (§24) and see how fire ought to affect each group
of rules. Evidently we have just created a new possible internal state for an
object, which means a new rule under ¶1, but it doesn’t stop there:
1.4.4. Some objects are ‘‘flammable’’ and therefore in one of two mutually
exclusive states, ‘‘on fire’’ and ‘‘not on fire’’.
2.4. If an object on fire is placed in a flammable container or on a
flammable supporter, that too catches fire.
2.5. If a container or supporter is on fire, any flammable object within or
on top of it catches fire.
4.3.3.1. Any object on fire provides light.
4.4.3. The player cannot touch any object on fire unless (say) wearing the
asbestos gloves.
5.4.1. All flammable objects have a ‘‘lifespan’’, a length of time for which
they can burn before being consumed. The each-turn daemon subtracts one
term from the lifespan of any object on fire, and removes it from play if the
lifespan reaches zero.
One could go further than this: arguably, certain rooms should also be
flammable, so that an object on fire which is dropped there would set the room
ablaze; and the player should not survive long in a burning room; and we have
not even provided a way to douse the flames, except by waiting for fires to
burn themselves out. But the above rules will maintain a reasonable level of
plausibility. ¶1.4.4 is provided by defining a new class of Flammable objects,
which contains an each_turn routine implementing ¶5.4.1, and an on_fire
attribute. The same each_turn can take care of ¶2.4 and ¶2.5, and can give
the object light if it’s currently on_fire, thus solving ¶4.3.3.1. But, while
it would be easy to add simple rules like ‘‘you can’t take an object which is
on fire’’, ¶4.4.3 in its fullest form is more problematic, and means replacing
the ObjectIsUntouchable routine. Giving any object on fire a before rule
preventing the player from using any of the ‘‘touchy’’ actions on it would go
some of the way, but wouldn’t handle subtler cases, like a player not being
§
allowed to reach for something through a burning hoop. Nor is this everything:
burning objects will need to be talked about differently when alight, and this
will call for using the powerful descriptive features in Chapter IV.
• REFERENCES
‘Balances’ implements the ‘Enchanter’ trilogy’s magic system by methods like the above.
•Approximately seventy library extensions have been contributed by members of the
Inform community and more are placed at ftp.gmd.de with each month that goes
by. Often short and legible, they make good examples of Inform coding even if you
don’t want to use them. Many are cited in ‘‘references’’ paragraphs throughout the
book: here are others which seem more appropriate here.
•"money.h", by Erik
Hetzner, is a textbook case of a class-based extension to Inform providing a new aspect
to the world model which doesn’t much overlap with what’s already there: notes and
•Conversely, Marnie Parker’s "OutOfRch.h" exemplifies a change that
coinage.
needs to permeate the existing world model to be effective: it defines which areas of a
location are within reach from which other areas, so that for instance a player sitting
on a chair might only be able to reach items on the adjacent table and not a window
on the far wall.
•In some graphical adventure games, interactivity sometimes cuts
out at a significant event and an unchangeable movie animation is shown instead: this
is sometimes called a ‘‘cut-scene’’. Such games sometimes allow the player to replay
any movies seen so far, reviewing them for clues missed previously. "movie.h", by
L. Ross Raszewski, projects the textual version of movies like this, thus providing a
framework for cut-scenes. •"infotake.h", by Joe Merical, shifts the Inform model
world back to the style of ‘Zork’: printing Zorkesque messages, providing a ‘‘diagnose’’
verb and so on.
•Anson Turner’s "animalib" retains the core algorithms of the
Inform library (principally the parser and list-writer) but redesigns the superstructure
of properties and attributes with the aim of a cleaner, more consistent world model.
Although this alternative library is in its early stages of development, its code makes
interesting reading. For instance, some child-objects represent items held inside their
parents and others represent items on top of their parents. The standard Inform library
distinguishes these cases by looking at the attributes of the parent-object – whether it is
a supporter or a container. Contrariwise, "animalib" distinguishes them by looking
at attributes of the child, so that the different children of a single parent can all be
contained in different ways.
Chapter IV: Describing and Parsing
Language disguises thought. . . The tacit conventions on which
the understanding of everyday language depends are enormously
complicated.
— Ludwig Wittgenstein (1889–1951), Tractatus
§26
Describing objects and rooms
Talking about the state of the world is much easier than listening
to the player’s intentions for it. Despite this, the business of
description takes up a fair part of this chapter since the designer of a
really complex game will eventually need to know almost every rule
involved. (Whereas nobody would want to know everything about the parser.)
The simplest description of an object is its ‘‘short name’’. For instance,
print (a) brass_lamp;
may result in ‘‘an old brass lamp’’ being printed. There are four such forms of
print:
print
print
print
print
(the) obj
(The) obj
(a) obj
(name) obj
Print the object with its definite article
The same, but capitalised
Print the object with indefinite article
Print the object’s short name alone
and these can be freely mixed into lists of things to print or print_ret, as for
example:
"The ogre declines to eat ", (the) noun, ".";
• EXERCISE 62
When referring to animate objects, you sometimes need to use pronouns such as
‘‘him’’. Define new printing routines so that print "You throw the book at ",
(PronounAcc) obj, "!"; will insert the right accusative pronouns.
§
44 There is also a special syntax print (object) for printing object names, but do
not use it without good reason: it doesn’t understand some of the features below and is
not protected against crashing if you mistakenly try to print the name for an object that
doesn’t exist.
·
·
·
·
·
Inform tries to work out the right indefinite article for any object automatically.
In English-language games, it uses ‘an’ when the short name starts with a vowel
and ‘a’ when it does not (unless the name is plural, when ‘some’ is used in
either case). You can override this by setting article yourself, either to some
text or a routine to print some. Here are some possibilities, arranged as ‘‘article
/ name’’:
‘‘a / platinum bar’’, ‘‘an / orange balloon’’, ‘‘your / Aunt Jemima’’,
‘‘some bundles of / reeds’’, ‘‘far too many / marbles’’,
‘‘The / London Planetarium’’
If the object is given the attribute proper then its name is treated as a
proper noun taking no article, so the value of article is ignored. Objects
representing named people usually have proper, and so might a book like
‘‘Whitaker’s Almanac’’.
Definite articles are always ‘‘the’’, except for proper nouns. Thus
‘‘the / platinum bar’’, ‘‘Benjamin Franklin’’, ‘‘Elbereth’’
are all printed by print (the) ..., the latter two objects being proper.
A single object whose name is plural, such as ‘‘grapes’’ or ‘‘marble pillars’’,
should be given the attribute pluralname. As a result the library might say,
e.g., ‘‘You can’t open those’’ instead of ‘‘You can’t open that’’. As mentioned
above, the indefinite article becomes ‘‘some’’, and the player can use the
pronoun ‘‘them’’ to refer to the object, so for instance ‘‘take them’’ to pick up
the grapes-object.
4 You can give animate objects the attributes male, female or neuter to help the
parser understand pronouns properly. animate objects are assumed to be male if you
set neither alternative.
4
There’s usually no need to worry about definite and indefinite articles for room
objects, as the standard Inform rules never print them.
·
·
·
·
·
§
The short name of an object is normally the text given in double-quotes at the
head of its definition. This is very inconvenient to change during play when,
for example, ‘‘blue liquid’’ becomes ‘‘purple liquid’’ as a result of a chemical
reaction. A more flexible way to specify an object’s short name is with the
short_name property. To print the name of such an object, Inform does the
following:
(1) If the short_name is a string, it’s printed and that’s all.
(2) If it is a routine, then it is called. If it returns true, that’s all.
(3) The text given in the header of the object definition is printed.
For example, the dye might be defined with:
short_name [;
switch(self.colour) {
1: print "blue ";
2: print "purple ";
3: print "horrid sludge"; rtrue;
}
],
with "liquid" as the short name in its header. According to whether its colour
property is 1, 2 or 3, the printed result is ‘‘blue liquid’’, ‘‘purple liquid’’ or
‘‘horrid sludge’’.
4
Alternatively, define the dye with short_name "blue liquid" and then simply
execute dye.short_name = "purple liquid"; when the time comes.
• EXERCISE 63
Design a chessboard of sixty-four locations with a map corresponding to an eight-byeight grid, so that White advances north towards Black, with pieces placed on the
board according to a game position. Hint: using flexible routines for short_name, this
can be done with just two objects, one representing all sixty-four squares, the other
representing all thirty-two pieces.
·
·
·
·
·
For many objects the indefinite article and short name will most often be seen
in inventory lists, such as:
>inventory
You are carrying:
a leaf of mint
a peculiar book
your satchel (which is open)
a green cube
§
Some objects, though, ought to have fuller entries in an inventory: a wine
bottle should say how much wine is left, for instance. The invent property is
designed for this. The simplest way to use invent is as a string. For instance,
declaring a peculiar book with
invent "that harmless old book of Geoffrey’s",
will make this the inventory line for the book. In the light of events, it could
later be changed with a statement like:
book.invent = "that lethal old book of Geoffrey’s";
4
Note that this string becomes the whole inventory entry: if the object were an
open container, its contents wouldn’t be listed, which might be unfortunate. In such
circumstances it’s better to write an invent routine, and that’s also the way to append
text like ‘‘(half-empty)’’.
4
Each line of an inventory is produced in two stages. First, the basic line:
(1a) The global variable inventory_stage is set to 1.
(1b) The invent routine is called (if there is one). If it returns true, stop here.
(1c) The object’s indefinite article and short-name are printed.
Second, little informative messages like ‘‘(which is open)" are printed, and inventories
are given for the contents of open containers:
(2a) The global variable inventory_stage is set to 2.
(2b) The invent routine is called (if there is one). If it returns true, stop here.
(2c) A message such as ‘‘(closed, empty and providing light)’’ is printed, as appropriate.
(2d) If it is an open container, or a supporter, or is transparent, then its contents
are inventoried.
After each line is printed, linking text such as a new-line or a comma is printed,
according to the current ‘‘list style’’.
For example, here is the invent routine used by the matchbook in ‘Toyshop’:
invent [ i;
if (inventory_stage == 2) {
i = self.number;
if (i == 0) print " (empty)";
if (i == 1) print " (1 match left)";
if (i > 1) print " (", i, " matches left)";
}
],
• 44EXERCISE 64
Suppose you want to change the whole inventory line for an ornate box but you can’t
use an invent string, or return true from stage 1, because you still want stage 2d to
happen properly (so that its contents will be listed). How can you achieve this?
§
·
·
·
·
·
The largest and most complicated messages the Inform library ever prints on
its own initiative are room descriptions, printed when the Look action is carried
out (for instance, when the statement ; triggers a room description).
What happens is: the room’s short name is printed, usually emphasised in
bold-face, then the description, followed by a list of the objects residing
there which aren’t concealed or scenery.
Chapter III mentioned many different properties – initial, when_on,
when_off and so on – giving descriptions of what an object looks like when in
the same room as the player: some apply to doors, others to switchable objects
and so on. All of them can be routines to print text, instead of being strings to
print. The precise rules are given below.
But the whole system can be bypassed using the describe property. If an
object gives a describe routine then this takes priority over everything: if it
returns true, the library assumes that the object has already been described,
and prints nothing further. For example:
describe [;
"^The platinum pyramid catches the light beautifully.";
],
Unlike an initial description, this is still seen even if the pyramid has moved,
i.e., been held by the player at some stage.
4
Note the initial ^ (new-line) character. The library doesn’t print a skipped line
itself before calling describe because it doesn’t know yet whether the routine will want
to say anything. A describe routine which prints nothing and returns true makes an
object invisible, as if it were concealed.
44 Here is exactly how a room description is printed. Recall from §21 that location
holds the player’s location if there is light, and thedark if not, and see §21 for the
definition of ‘‘visibility ceiling’’, which roughly means the outermost thing the player
can see: normally the location, but possibly a closed opaque container which the player
is inside. First the top line:
(1a) A new-line is printed. The short name of the visibility ceiling is printed, using
emphasised type, which on most models of computer appears as bold face.
(1b) If the player is on a supporter, then ‘‘ (on hsomethingi)’’ is printed; if inside
something (other than the visibility ceiling), then ‘‘ (in hsomethingi)’’.
(1c) ‘‘ (as hsomethingi)’’ is printed if this was requested by the game’s most recent
call to ChangePlayer: for instance, ‘‘ (as a werewolf)’’. (See §21 for details of
ChangePlayer.)
(1d) A new-line is printed.
§
Now the long description. This step is sometimes skipped, depending on which ‘‘look
mode’’ the player has chosen: in the normal mode, it is skipped if the player has just
moved by a Go action into a location already visited; in ‘‘superbrief’’ mode it is always
skipped, while in ‘‘verbose’’ mode it is never skipped.
(2a) Starting with the visibility ceiling and working down through ‘‘levels’’ of enterable
objects containing the player, a long description is printed, as follows:
(i) If the level is the current value of location, that is, the current room
or else thedark, then: if location provides describe then the message
location.describe() is sent. If not, then location.description() is
sent. Every room is required to provide one or the other. (This is now
redundant. In earlier Informs, description could only be a string, so
describe was there in case a routine was needed.)
(ii) If not, the level must be an enterable object E. If E provides it, the message
E.inside_description() is sent.
(2b) After the description of each visibility level, the objects contained in that level are
‘‘listed’’ (see below).
The library has now finished, but your game gets a chance to add a postscript by means
of an entry point routine:
(3) The entry point LookRoutine is called.
44 Besides printing room descriptions, the Look action has side-effects: it can award
the player some points, or mark a room with the attribute visited. For these rules in
full, see §21.
44 The visited attribute is only given to a room after its description has been printed
for the first time. This is convenient for making the description different after the first
time.
44 When ‘‘listing objects’’ (as in 3a and 3b above) some objects are given a paragraph
to themselves, while others are lumped together in a list at the end. The following
objects are not mentioned at all: the player; what the player is in or on (if anything),
because this has been taken care of in the short or long description already; and
anything which has the attributes scenery or concealed. The remaining objects are
looked through, eldest first, as follows:
(1) If the object has a describe routine, run it. If it returns true, stop here and don’t
mention the object at all.
(2) Work out the ‘‘description property’’ for the object:
(a) For a container, this is when_open or when_closed;
(b) Otherwise, for a switchable object this is when_on or when_off;
(c) Otherwise, for a door this is when_open or when_closed;
(d) Otherwise, it’s initial.
(3) If either the object doesn’t provide this property or the object has moved and the
property isn’t when_off or when_closed then the object will be listed at the end,
not given a paragraph of its own.
§
(4) Otherwise a new-line is printed and the property is printed (if it’s a string) or run
(if it’s a routine). If it is a routine, it had better print something, as otherwise there
will be a spurious blank line in the room description.
4
Note that although a supporter which is scenery won’t be mentioned, anything
on top of it may well be. If this is undesirable, set these objects on top to be concealed.
4
Objects which have just been pushed into a new room are not listed in that room’s
description on the turn in question. This is not because of any rule about room
descriptions, but because the pushed object is moved into the new room only after
the room description is made. This means that when a wheelbarrow is pushed for a
long distance, the player does not have to keep reading ‘‘You can see a wheelbarrow
here.’’ every move, as though that were a surprise.
4
You can use a library routine called Locale to perform object listing. See §A3 for
details, but suffice to say here that the process above is equivalent to executing
if (Locale(location, "You can see", "You can also see"))
print " here.^";
Locale is useful for describing areas of a room which are sub-divided off while remaining
part of the same location, such as the stage of a theatre.
• 44EXERCISE 65
As mentioned above, the library implements ‘‘superbrief’’ and ‘‘verbose’’ modes for
room description (one always omits long room descriptions, the other never does).
How can verbose mode automatically print room descriptions every turn? (Some of the
later Infocom games did this.)
• REFERENCES
‘Balances’ often uses short_name, especially for the white cubes (whose names change)
and lottery tickets (whose numbers are chosen by the player). ‘Adventureland’ uses
short_name in simpler ways: see the bear and the bottle, for instance. •The scroll
class of ‘Balances’ uses invent. •See the ScottRoom class of ‘Adventureland’ for a
radically different way to describe rooms (in pidgin English, like telegraphese, owing to
an extreme shortage of memory to store text – Scott Adams was obliged to write for
machines with under 16K of free memory).
§27
Listing and grouping objects
As some day it may happen that a victim must be found
I’ve got a little list – I’ve got a little list
Of society offenders who might well be underground,
And who never would be missed
Who never would be missed!
— W. S. Gilbert (1836–1911), The Mikado
Listing objects tidily in a grammatical sentence is more difficult
than it seems, especially when taking plurals and groups of similar
objects into account. Here, for instance, is a list of 23 items printed
by a room description in the demonstration game ‘List Property’:
You can see a plastic fork, knife and spoon, three hats (a fez, a Panama and
a sombrero), the letters X, Y, Z, P, Q and R from a Scrabble set, Punch
magazine, a recent issue of the Spectator, a die and eight coins (four silver,
one bronze and three gold) here.
Fortunately, the library’s list-maker is available to the public by calling:
WriteListFrom(object, style);
where the list will start from the given object and go along its siblings. Thus, to
list all the objects inside X, list from child(X). What the list looks like depends
on a ‘‘style’’ made by adding up some of the following:
ALWAYS_BIT
CONCEAL_BIT
DEFART_BIT
ENGLISH_BIT
FULLINV_BIT
INDENT_BIT
ISARE_BIT
NEWLINE_BIT
NOARTICLE_BIT
PARTINV_BIT
RECURSE_BIT
TERSE_BIT
WORKFLAG_BIT
Always recurse downwards
Misses out concealed or scenery objects
Uses the definite article in list
English sentence style, with commas and ‘and’
Gives full inventory entry for each object
Indents each entry according to depth
Prints ‘‘ is " or ‘‘ are " before list
Prints new-line after each entry
Prints no articles, definite or indefinite
Only brief inventory information after entry
Recurses downwards with usual rules
More terse English style
At top level (only), only lists objects
which have the workflag attribute
§
Recursing downwards means that if an object is listed, then its children are
also listed, and so on for their children. The ‘‘usual rules’’ of RECURSE_BIT
are that children are only listed if the parent is transparent, or a supporter,
or a container which is open – which is the definition of ‘‘see-through’’ used
throughout the Inform library. ‘‘Full inventory information’’ means listing
objects exactly as if in an inventory, according to the rigmarole described in
§26. ‘‘Brief inventory information’’ means listing as if in a room description:
that is, noting whether objects are open, closed, empty or providing light
(except that light is only mentioned when in a room which is normally dark).
The best way to decide which bits to set is to experiment. For example, a
‘tall’ inventory is produced by:
WriteListFrom(child(player),
FULLINV_BIT + INDENT_BIT + NEWLINE_BIT + RECURSE_BIT);
and a ‘wide’ one by:
WriteListFrom(child(player),
FULLINV_BIT + ENGLISH_BIT + RECURSE_BIT);
which produce effects like:
>inventory tall
You are carrying:
a bag (which is open)
three gold coins
two silver coins
a bronze coin
four featureless white cubes
a magic burin
a spell book
>inventory wide
You are carrying a bag (which is open), inside which are three gold coins, two
silver coins and a bronze coin, four featureless white cubes, a magic burin
and a spell book.
except that the ‘‘You are carrying’’ part is not done by the list-maker, and nor
is the final full stop in the second example.
4 The workflag is an attribute which the library scribbles over from time to time as
temporary storage, but you can use it for short periods with care. In this case it makes
it possible to specify any reasonable list.
44 WORKFLAG_BIT and CONCEAL_BIT specify conflicting rules. If they’re both given,
then what happens is: at the top level, but not below, everything with workflag is
included; on lower levels, but not at the top, everything without concealed or scenery
is included.
§
• EXERCISE 66
Write a DoubleInvSub action routine to produce an inventory like so:
You are carrying four featureless white cubes, a magic burin and a spell book.
In addition, you are wearing a purple cloak and a miner’s helmet.
·
·
·
·
·
Lists are shorter, neater and more elegantly phrased if similar objects are
grouped together. For instance, keys, books and torch batteries are all grouped
together in lists printed by the game ‘Curses’. To achieve this, the objects
belonging to such a group (all the keys for instance) provide a common value
of the property list_together. If this value is a number between 1 and 1000,
the library will unobtrusively group the objects with that value together so
that they appear consecutively in any list. For instance ‘Curses’ includes the
following definitions:
Constant KEY_GROUP = 1;
...
Object -> -> brass_key "small brass key"
with ...
list_together KEY_GROUP;
and similarly for the other keys. Alternatively, instead of being a small number
the common value can be a string such as "foodstuffs". If so, then it must
either be given in a class definition or else as a constant, like so:
Constant FOOD_GROUP = "foodstuffs";
(In particular, the actual text should only be written out in one place in the
source code. Otherwise two or more different strings will be made, which just
happen to have the same text as each other, and Inform will consider these to
be different values of list_together.) Lists will then cite, for instance,
three foodstuffs (a scarlet fish, some lembas wafer and an onion)
in running text, or
three foodstuffs:
a scarlet fish
some lembas wafer
an onion
§
in indented lists. This only happens when two or more are gathered together.
Finally, the common value of list_together can be a routine, such as:
list_together [;
if (inventory_stage == 1) {
print "heaps of food, notably";
if (c_style & INDENT_BIT == 0) print " ";
else print " --^";
} else if (c_style & INDENT_BIT == 0)
print " (which only reminds you how hungry you are)";
],
Typically this might be part of a class definition from which all the objects in
question inherit. Any list_together routine will be called twice: once, with
inventory_stage set to 1, as a preamble to the list of items, and once (with 2)
to print any postscript required. It is allowed to change c_style, the current
list style, without needing to restore the old value and may, by returning true
from stage 1, signal the list-maker not to print a list at all. The above example
would give a conversational sentence-like list as follows:
heaps of food, notably a scarlet fish, some lembas wafer and an onion (which
only reminds you how hungry you are)
and would also look suitable in sober tall-inventory-like columns:
heaps of food, notably -a scarlet fish
some lembas wafer
an onion
4 A list_together routine has the opportunity to look through the objects which
are about to be listed together, by looking at some of the list-maker’s variables.
parser_one holds the first object in the group and parser_two holds the depth of
recursion in the list, which might be needed to keep the indentation straight. Applying
x = NextEntry(x,parser_two) will move x on from one object to the next in the
group being listed.
4 The library variable listing_together is set to the first object of a group being
listed, when a group is being listed, or to nothing the rest of the time. This is useful
because an object’s short_name routine might need to behave differently during a
grouped listing to the rest of the time.
• 4EXERCISE 67
Implement the Scrabble pieces from the example list above.
§
• 44EXERCISE 68
Implement the three denominations of coin.
• 44EXERCISE 69
Implement the I Ching in the form of six coins, three gold (goat, deer and chicken),
three silver (robin, snake and bison) which can be thrown to reveal gold and silver
trigrams.
• 44EXERCISE 70
Design a class called AlphaSorted, members of which are always listed in alphabetical
order. Although this could be done with list_together, this exercise is here to draw
the reader’s attention to an ugly but sometimes useful alternative. The only actions
likely to produce lists are Look, Search, Open and Inv: so react to these actions by
rearranging the object tree into alphabetical order before the list-writer gets going.
• REFERENCES
A good example of WriteListFrom in action is the definition of CarryingClass from
the example game ‘The Thief’, by Gareth Rees. This alters the examine description of
a character by appending a list of what that person is carrying and wearing. •Andreas
Hoppler has written an alternative list-writing library extension called "Lister.h",
which defines a class Lister so that designers can customise their own listing engines
for different purposes in a single game. •Anson Turner’s example program "52.inf"
models a deck of cards, and an extensive list_together sorts out hands of cards.
§28
How nouns are parsed
The Naming of Cats is a difficult matter,
It isn’t just one of your holiday games;
You may think at first I’m as mad as a hatter
When I tell you, a cat must have THREE DIFFERENT NAMES.
— T. S. Eliot (1888–1965), The Naming of Cats
Suppose we have a tomato defined with
name ’fried’ ’green’ ’tomato’,
but which is going to redden later and need to be referred to as ‘‘red tomato’’.
The name property holds an array of dictionary words, so that
(tomato.#name)/2
tomato.&name-->0
tomato.&name-->1
tomato.&name-->2
==
==
==
==
3
’fried’
’green’
’tomato’
(Recall that X.#Y tells you the number of -> entries in such a property array,
in this case six, so that X.#Y/2 tells you the number of --> entries, in this case
three.) You are quite free to alter this array during play:
tomato.&name-->1 = ’red’;
The down side of this technique is that it’s clumsy, when all’s said and
done, and not so very flexible, because you can’t change the length of the
tomato.&name array during play. Of course you could define the tomato
with name ’fried’ ’green’ ’tomato’ ’blank.’ ’blank.’ ’blank.’
’blank.’ ’blank.’ ’blank.’ ’blank.’ ’blank.’ ’blank.’
’blank.’ ’blank.’ ’blank.’ ’blank.’ ’blank.’ ’blank.’,
or something similar, giving yourself another (say) fifteen ‘‘slots’’ to put new
names into, but this is inelegant even by Inform standards. Instead, an object
like the tomato can be given a parse_name routine, allowing complete flexibility
for the designer to specify just what names it does and doesn’t match. It is
time to begin looking into the parser and how it works.
§
·
·
·
·
·
The Inform parser has two cardinal principles: firstly, it is designed to be as
‘‘open-access’’ as possible, because a parser cannot ever be general enough for
every game without being highly modifiable. This means that there are many
levels on which you can augment or override what it does. Secondly, it tries
to be generous in what it accepts from the player, understanding the broadest
possible range of commands and making no effort to be strict in rejecting
ungrammatical requests. For instance, given a shallow pool nearby, ‘‘examine
shallow’’ has an adjective without a noun: but it’s clear what the player means.
In general, all sensible commands should be accepted but it is not important
whether or not nonsensical ones are rejected.
The first thing the parser does is to read in text from the keyboard and
break it up into a stream of words: so the text ‘‘wizened man, eat the grey
bread’’ becomes
wizened / man / , / eat / the / grey / bread
and these words are numbered from 1. At all times the parser keeps a ‘‘word
number’’ marker to keep its place along this line, and this is held in the variable
wn. The routine NextWord() returns the word at the current position of the
marker, and moves it forward, i.e., adds 1 to wn. For instance, the parser
may find itself at word 6 and trying to match ‘‘grey bread’’ as the name of an
object. Calling NextWord() returns the value ’grey’ and calling it again gives
’bread’.
Note that if the player had mistyped ‘‘grye bread’’, ‘‘grye’’ being a word
which isn’t mentioned anywhere in the program or created by the library,
then NextWord() returns 0 for ‘not in the dictionary’. Inform creates the
dictionary of a story file by taking all the name words of objects, all the verbs
and prepositions from grammar lines, and all the words used in constants like
’frog’ written in the source code, and then sorting these into alphabetical
order.
4
However, the story file’s dictionary only has 9-character resolution. (And only
6 if Inform has been told to compile an early-model story file: see §45.) Thus the
values of ’polyunsaturate’ and ’polyunsaturated’ are equal. Also, upper case and
lower case letters are considered the same. Although dictionary words are permitted
to contain numerals or typewriter symbols like -, : or /, these cost as much as two
ordinary letters, so ’catch-22’ looks the same as ’catch-2’ or ’catch-207’.
44 A dictionary word can even contain spaces, full stops or commas, but if so it is
‘untypeable’. For instance, ’in,out’ is an untypeable word because if the player were
to type something like ‘‘go in,out’’, the text would be broken up into four words, go /
§
in / , / out. Thus ’in,out’ may be in the story file’s dictionary but it will never match
against any word of what the player typed. Surprisingly, this can be useful, as it was at
the end of §18.
·
·
·
·
·
Since the story file’s dictionary isn’t always perfect, there is sometimes no
alternative but to actually look at the player’s text one character at a time: for
instance, to check that a 12-digit phone number has been typed correctly and
in full.
The routine WordAddress(wordnum) returns a byte array of the characters
in the word, and WordLength(wordnum) tells you how many characters there
are in it. Given the above example text of ‘‘wizened man, eat the grey bread’’:
WordLength(4) == 3
WordAddress(4)->0 == ’e’
WordAddress(4)->1 == ’a’
WordAddress(4)->2 == ’t’
because word number 4 is ‘‘eat’’. (Recall that the comma is considered as a
word in its own right.)
4
The parser provides a basic routine for comparing a word against the texts ’0’,
’1’, ’2’, . . ., ’9999’, ’10000’ or, in other words, against small numbers. This is
the library routine TryNumber(wordnum), which tries to parse the word at wordnum as
a number and returns that number, if it finds a match. Besides numbers written out
in digits, it also recognises the texts ’one’, ’two’, ’three’, . . ., ’twenty’. If it fails
to recognise the text as a number, it returns −1,000; if it finds a number greater than
10,000, it rounds down and returns 10,000.
·
·
·
·
·
To return to the naming of objects, the parser normally recognises any
arrangement of some or all of the name words of an object as a noun which
refers to it: and the more words, the better the match is considered to be.
Thus ‘‘fried green tomato’’ is a better match than ‘‘fried tomato’’ or ‘‘green
tomato’’ but all three are considered to match. On the other hand, so is ‘‘fried
green’’, and ‘‘green green tomato green fried green’’ is considered a very good
match indeed. The method is quick and good at understanding a wide variety
of sensible texts, though poor at throwing out foolish ones. (An example of
the parser’s strategy of being generous rather than strict.) To be more precise,
here is what happens when the parser wants to match some text against an
object:
§
(1) If the object provides a parse_name routine, ask this routine to determine how
good a match there is.
(2) If there was no parse_name routine, or if there was but it returned −1, ask the
entry point routine ParseNoun, if the game has one, to make the decision.
(3) If there was no ParseNoun entry point, or if there was but it returned −1, look at
the name of the object and match the longest possible sequence of words given in
the name.
So: a parse_name routine, if provided, is expected to try to match as many
words as possible starting from the current position of wn and reading them
in one at a time using the NextWord() routine. Thus it must not stop just
because the first word makes sense, but must keep reading and find out how
many words in a row make sense. It should return:
0
if the text didn’t make any sense at all,
k
if k words in a row of the text seem to refer to the object, or
−1 to tell the parser it doesn’t want to decide after all.
The word marker wn can be left anywhere afterwards. For example, here is the
fried tomato with which this section started:
parse_name [ n colour;
if (self.ripe) colour = ’red’; else colour = ’green’;
while (NextWord() == ’tomato’ or ’fried’ or colour) n++;
return n;
],
The effect of this is that if tomato.ripe is true then the tomato responds to
the names ‘‘tomato’’, ‘‘fried’’ and ‘‘red’’, and otherwise to ‘‘tomato’’, ‘‘fried’’
and ‘‘green’’.
As a second example of how parse_name can be useful, suppose you
define:
Object -> "fly in amber"
with name ’fly’ ’in’ ’amber’;
If the player then types ‘‘put fly in amber in hole’’, the parser will be thrown,
because it will think ‘‘fly in amber in’’ is all just naming the object and then it
won’t know what the word ‘‘hole’’ is doing at the end. However:
Object -> "fly in amber"
with parse_name [;
if (NextWord() ~= ’fly’ or ’amber’) return 0;
if (NextWord() == ’in’ && NextWord() == ’amber’)
return 3;
return 1;
];
§
Now the word ‘‘in’’ is only recognised as part of the fly’s name if it is followed
by the word ‘‘amber’’, and the ambiguity goes away. (‘‘amber in amber’’ is
also recognised, but then it’s not worth the bother of excluding.)
4
parse_name is also used to spot plurals: see §29.
• EXERCISE 71
Rewrite the tomato’s parse_name to insist that the adjectives must come before the
noun, which must be present.
• EXERCISE 72
Create a musician called Princess who, when kissed, is transformed into ‘‘/?%?/ (the
artiste formerly known as Princess)’’.
• EXERCISE 73
Construct a drinks machine capable of serving cola, coffee or tea, using only one object
for the buttons and one for the possible drinks.
• EXERCISE 74
Write a parse_name routine which looks through name in just the way that the parser
would have done anyway if there hadn’t been a parse_name in the first place.
• 4EXERCISE 75
Some adventure game parsers split object names into ‘adjectives’ and ‘nouns’, so that
only the pattern h0 or more adjectivesi h1 or more nounsi is recognised. Implement
this.
• EXERCISE 76
During debugging it sometimes helps to be able to refer to objects by their internal
numbers, so that ‘‘put object 31 on object 5’’ would work. Implement this.
• 4EXERCISE 77
How could the word ‘‘#’’ be made a wild-card, meaning ‘‘match any single object’’?
• 44EXERCISE 78
And how could ‘‘*’’ be a wild-card for ‘‘match any collection of objects’’? (Note: you
need to have read §29 to answer this.)
• REFERENCES
Straightforward parse_name examples are the chess pieces object and the kittens class of
‘Alice Through the Looking-Glass’. Lengthier ones are found in ‘Balances’, especially in
the white cubes class. •Miron Schmidt’s library extension "calyx_adjectives.h",
based on earlier work by Andrew Clover, provides for objects to have ‘‘adnames’’ as
well as ‘‘names’’: ‘‘adnames’’ are usually adjectives, and are regarded as being less good
§
matches for an object than ‘‘names’’. In this system ‘‘get string’’ would take either a
string bag or a ball of string, but if both were present would take the ball of string,
because ‘‘string’’ is in that case a noun rather than an adjective.
§29
Plural names for duplicated objects
A notorious challenge for adventure game parsers is to handle a
collection of, say, ten gold coins, allowing the player to use them
independently of each other, while gathering them together into
groups in descriptions and inventories. Two problems must be
overcome: firstly, the game has to be able to talk to the player in plurals, and
secondly vice versa. First, then, game to player:
Class GoldCoin
with name ’gold’ ’coin’,
short_name "gold coin",
plural "gold coins";
(and then similar silver and bronze coin classes)
Object bag "bag"
with name ’bag’,
has container open openable;
GoldCoin ->;
GoldCoin ->;
GoldCoin ->;
SilverCoin ->;
SilverCoin ->;
BronzeCoin ->;
Now we have a bag of six coins. The player looking inside the bag will get
>look inside bag
In the bag are three gold coins, two silver coins and a bronze coin.
How does the library know that the three gold coins are the same as each
other, but the others different? It doesn’t look at the classes but the names. It
will only group together things which:
(a) have a plural set, and
(b) are ‘‘indistinguishable’’ from each other.
‘‘Indistinguishable’’ means they have the same name words as each other,
possibly in a different order, so that nothing the player can type will separate
the two.
§
4
Actually, it’s a little more subtle than this. What it groups together depends
slightly on the context of the list being written. When it’s writing a list which prints out
details of which objects are providing light, for instance (as an inventory does), it won’t
group together two objects if one is lit but the other isn’t. Similarly for objects with
visible possessions or which can be worn.
44 This ramifies further when the objects have a parse_name routine supplied. If they
have different parse_name routines, the library decides that they are distinguishable.
But if they have the same parse_name routine, for instance by inheriting it from a class
definition, then the library has no alternative but to ask them. What happens is that:
(1) A variable called parser_action is set to the special value ##TheSame, a value it
never has at any other time;
(2) Two variables, called parser_one and parser_two are set to the two objects in
question;
(3) Their parse_name routine is called. If it returns:
−1 the objects are declared ‘‘indistinguishable";
−2 they are declared different.
(4) Otherwise, the usual rules apply and the library looks at the ordinary name fields
of the objects.
44 You may even want to provide a parse_name routine for objects which otherwise
don’t need one, just to speed up the process of the library telling if two objects are
distinguishable – if there were 30 gold coins in one place the parser would be doing a
lot of work comparing names, but you can make the decision much faster.
• 44EXERCISE 79
Perhaps the neatest trick of parsing in any Infocom game occurs in ‘Spellbreaker’,
which has a set of white cubes which are indistinguishable until the player writes words
onto them with a magic burin (a medieval kind of pen), after which it’s possible to tell
them apart. Imitate this in Inform.
·
·
·
·
·
Secondly, the player talking to the computer. Suppose a game involves
collecting a number of similar items, such as a set of nine crowns in different
colours. Then you’d want the parser to recognise things like:
>drop all of the crowns except green
>drop the three other crowns
Putting the word ’crowns’ in the name lists of the crown objects is not quite
right, because the parser will still think that ‘‘crowns’’ might refer to a single
specific item. Instead, put in the word ’crowns//p’. The suffix //p marks out
the dictionary word ‘‘crowns’’ as one that can refer to more than one game
object at once. (So that you shouldn’t set this for the word ‘‘grapes’’ if a bunch
§
of grapes is a single game object; you should give that object the pluralname
attribute instead, as in §26 back at the start of this chapter.) For example the
GoldCoin class would read:
Class GoldCoin
with name ’gold’ ’coin’ ’coins//p’,
short_name "gold coin",
plural "gold coins";
Now when the player types ‘‘take coins’’, the parser interprets this as ‘‘take all
the coins within reach’’.
44 The only snag is that now the word ’coins’ is marked as //p everywhere in the
game, in all circumstances. Here is a more complicated way to achieve the same result,
but strictly in context of these objects alone. We need to make the parse_name routine
tell the parser that yes, there was a match, but that it was a plural. The way to do this
is to set parser_action to ##PluralFound, another special value. So, for example:
Class Crown
with parse_name [ i j;
for (::) {
j = NextWord();
if (j == ’crown’ or self.name) i++;
else {
if (j == ’crowns’) {
parser_action = ##PluralFound; i++;
}
else return i;
}
}
];
This code assumes that the crown objects have just one name each, their colours.
• EXERCISE 80
Write a ‘cherub’ class so that if the player tries to call them ‘‘cherubs", a message like
‘‘I’ll let this go once, but the plural of cherub is cherubim" appears.
• REFERENCES
See the coinage of ‘Balances’.
§30
How verbs are parsed
‘‘. . .I can see that the grammar gets tucked into the tales and poetry
as one gives pills in jelly.’’
— Louisa May Alcott (1832–1888), Little Women
Here is how the parser reads in a whole command. Given a stream
of text like
saint / peter / , / take / the / keys / from / paul
it first breaks it into words, as shown, and then calls the entry point routine
BeforeParsing (which you can provide, if you want to, in order to meddle
with the text stream before parsing gets underway). The parser then works
out who is being addressed, if anyone, by looking for a comma, and trying out
the text up to there as a noun matching an animate or talkable object: in
this case St Peter. This person is called the ‘‘actor’’, since he or she is going
to perform the action, and is most often the player (thus, typing ‘‘myself, go
north’’ is equivalent to typing ‘‘go north’’). The next word, in this case ’take’,
is the ‘‘verb word’’. An Inform verb usually has several English verb words
attached, which are called synonyms of each other: for instance, the library is
set up with
‘‘take’’ = ‘‘carry’’ = ‘‘hold’’
all referring to the same Inform verb.
4
The parser sets up variables actor and verb_word while working. (In the example
above, their values would be the St Peter object and ’take’, respectively.)
4
This brief discussion is simplified in two ways. Firstly, it leaves out directions,
because Inform considers that the name of a direction-object implies ‘‘go’’: thus
‘‘north’’ means ‘‘go north’’. Secondly, it misses out the grammar property described in
§18, which can cause different actors to recognise different grammars.
• 4EXERCISE 81
Use BeforeParsing to implement a lamp which, when rubbed, produces a genie who
casts a spell to make the player confuse the words ‘‘white’’ and ‘‘black’’.
·
·
·
·
·
§
This section is about verbs, which are defined with ‘‘grammar’’, meaning usages
of the directives Verb and Extend. The library contains a substantial amount
of grammar as it is, and this forms (most of) the library file "Grammar.h".
Grammar defined in your own code can either build on this or selectively knock
it down, but either way it should be made after the inclusion of "Grammar.h".
For instance, making a new synonym for an existing verb is easy:
Verb ’steal’ ’acquire’ ’grab’ = ’take’;
Now ‘‘steal’’, ‘‘acquire’’ and ‘‘grab’’ are synonyms for ‘‘take’’.
4
One can also prise synonyms apart, as will appear later.
·
·
·
·
·
To return to the text above, the parser has now recognised the English word
‘‘take’’ as one of those which can refer to a particular Inform verb. It has
reached word 5 and still has ‘‘the keys from paul’’ left to understand.
Every Inform verb has a ‘‘grammar’’ which consists of a list of one or
more ‘‘grammar lines’’, each of them a pattern which the rest of the text might
match. The parser tries the first, then the second and so on, and accepts the
earliest one that matches, without ever considering later ones.
A line is a row of ‘‘tokens’’. Typical tokens might mean ‘the name of a
nearby object’, ‘the word ’from’’ or ‘somebody’s name’. To match a line, the
parser must match against each token in sequence. Continuing the example,
the parser accepts the line of three tokens
hone or more nounsi hthe word fromi ha nouni
as matching ‘‘the keys from paul’’.
Every grammar line has the name of an action attached, and in this case it
is Remove: so the parser has ground up the original text into just four quantities,
ending up with
actor = StPeter
action = Remove
noun = gold_keys
second = StPaul
The parser’s job is now complete, and the rest of the Inform library can get
on with processing the action or, as in this case, an order being addressed to
somebody other than the player.
4
The action for the line currently being worked through is stored in the variable
action_to_be; or, at earlier stages when the verb hasn’t been deciphered yet, it holds
the value NULL.
§
·
·
·
·
·
The Verb directive creates Inform verbs, giving them some English verb words
and a grammar. The library’s "Grammar.h" file consists almost exclusively of
Verb directives: here is an example simplified from one of them.
Verb ’take’ ’get’ ’carry’ ’hold’
* ’out’
->
* multi
->
* multiinside ’from’ noun ->
* ’in’ noun
->
* multiinside ’off’ noun ->
* ’off’ held
->
* ’inventory’
->
Exit
Take
Remove
Enter
Remove
Disrobe
Inv;
(You can look at the grammar being used in a game with the debugging verb
‘‘showverb’’: see §7.) Each line of grammar begins with a *, gives a list of
tokens as far as -> and then the action which the line produces. The first
line can only be matched by something like ‘‘get out’’, the second might be
matched by
‘‘take the banana’’
‘‘get all the fruit except the apple’’
and so on. A full list of tokens will be given later: briefly, ’out’ means the
literal word ‘‘out’’, multi means one or more objects nearby, noun means
just one and multiinside means one or more objects inside the second
noun. In this book, grammar tokens are written in the style noun to prevent
confusion (as there is also a variable called noun).
4 Some verbs are marked as meta – these are the verbs leading to Group 1 actions,
those which are not really part of the game’s world: for example, ‘‘save’’, ‘‘score’’ and
‘‘quit’’. For example:
Verb meta ’score’ * -> Score;
and any debugging verbs you create would probably work better this way, since
meta-verbs are protected from interference by the game and take up no game time.
·
·
·
·
·
After the -> in each line is the name of an action. Giving a name in this way is
what creates an action, and if you give the name of one which doesn’t already
§
exist then you must also write a routine to execute the action, even if it’s one
which doesn’t do very much. The name of the routine is always the name of
the action with Sub appended. For instance:
[ XyzzySub; "Nothing happens."; ];
Verb ’xyzzy’ * -> Xyzzy;
will make a new magic-word verb ‘‘xyzzy’’, which always says ‘‘Nothing
happens’’ – always, that is, unless some before rule gets there first, as it might
do in certain magic places. Xyzzy is now an action just as good as all the
standard ones: ##Xyzzy gives its action number, and you can write before
rules for it in Xyzzy: fields just as you would for, say, Take.
4 Finally, the line can end with the word reverse. This is only useful if there are
objects or numbers in the line which occur in the wrong order. An example from the
library’s grammar:
Verb ’show’ ’present’ ’display’
* creature held
-> Show reverse
* held ’to’ creature -> Show;
The point is that the Show action expects the first parameter to be an item, and the
second to be a person. When the text ‘‘show him the shield’’ is typed in, the parser
must reverse the two parameters ‘‘him’’ and ‘‘the shield’’ before causing a Show action.
On the other hand, in ‘‘show the shield to him’’ the parameters are in the right order
already.
·
·
·
·
·
The library defines grammars for the 100 or so English verbs most often used
by adventure games. However, in practice you quite often need to alter these,
usually to add extra lines of grammar but sometimes to remove existing ones.
For instance, suppose you would like ‘‘drop charges’’ to be a command in a
detection game (or a naval warfare game). This means adding a new grammar
line to the ‘‘drop’’ verb. The Extend directive is provided for exactly this
purpose:
Extend ’drop’ * ’charges’ -> DropCharges;
Normally, extra lines of grammar are added at the bottom of those already
there, so that this will be the very last grammar line tested by the parser. This
may not be what you want. For instance, ‘‘take’’ has a grammar line reading
* multi -> Take
§
quite early on. So if you want to add a grammar line diverting ‘‘take hfoodi" to
a different action, like so:
* edible -> Eat
( edible being a token matching anything which has the attribute edible)
then it’s no good adding this at the bottom of the Take grammar, because the
earlier line will always be matched first. What you need is for the new line to
go in at the top, not the bottom:
Extend ’take’ first
* edible -> Eat;
You might even want to throw away the old grammar completely, not just add
a line or two. For this, use
Extend ’press’ replace
* ’charges’ -> PressCharges;
and now the verb ‘‘press’’ has no other sense but this, and can’t be used in the
sense of pressing down on objects any more, because those grammar lines are
gone. To sum up, Extend can optionally take one of three keywords:
replace
first
last
replace the old grammar with this one;
insert the new grammar at the top;
insert the new grammar at the bottom;
with last being the default.
4
In library grammar, some verbs have many synonyms: for instance,
’attack’ ’break’ ’smash’ ’hit’ ’fight’ ’wreck’ ’crack’
’destroy’ ’murder’ ’kill’ ’torture’ ’punch’ ’thump’
are all treated as identical. But you might want to distinguish between murder and
lesser crimes. For this, try
Extend only ’murder’ ’kill’ replace
* animate -> Murder;
The keyword only tells Inform to extract the two verbs ‘‘murder" and ‘‘kill". These then
become a new verb which is initially an identical copy of the old one, but then replace
tells Inform to throw that away in favour of an entirely new grammar. Similarly,
Extend only ’run’ * ’program’ -> Compute;
makes ‘‘run" behave exactly like ‘‘go" and ‘‘walk’’, as these three words are ordinarily
synonymous to the library, except that it also recognises ‘‘program", so that ‘‘run
program" activates a computer but ‘‘walk program" doesn’t. Other good pairs to
separate might be ‘‘cross’’ and ‘‘enter’’, ‘‘drop’’ and ‘‘throw’’, ‘‘give’’ and ‘‘feed’’,
‘‘swim’’ and ‘‘dive’’, ‘‘kiss’’ and ‘‘hug’’, ‘‘cut’’ and ‘‘prune’’. Bear in mind that once a
pair has been split apart like this, any subsequent change to one will not also change
the other.
§
44 Occasionally verb definition commands are not enough. For example, in the
original ‘Advent’, the player could type the name of an adjacent place which had
previously been visited, and be taken there. (This feature isn’t included in the Inform
example version of the game in order to keep the source code as simple as possible.)
There are several laborious ways to code this, but here’s a concise way. The library
calls the UnknownVerb entry point routine (if you provide one) when the parser can’t
even get past the first word. This has two options: it can return false, in which case
the parser just goes on to complain as it would have done anyway. Otherwise, it can
return a verb word which is substituted for what the player actually typed. Here is one
way the ‘Advent’ room-naming might work. Suppose that every room has been given
a property called go_verb listing the words which refer to it, so for instance the well
house might be defined along these lines:
AboveGround Inside_Building "Inside Building"
with description
"You are inside a building, a well house for a
large spring.",
go_verb ’well’ ’house’ ’inside’ ’building’,
...
The UnknownVerb routine then looks through the possible compass directions for
already-visited rooms, checking against words stored in this new property:
Global go_verb_direction;
[ UnknownVerb word room direction adjacent;
room = real_location;
objectloop (direction in compass) {
adjacent = room.(direction.door_dir);
if (adjacent ofclass Object && adjacent has visited
&& adjacent provides go_verb
&& WordInProperty(word, adjacent, go_verb)) {
go_verb_direction = direction;
return ’go.verb’;
}
}
if (room provides go_verb
&& WordInProperty(word, room, go_verb)) {
go_verb_direction = "You’re already there!";
return ’go.verb’;
}
objectloop (room provides go_verb && room has visited
&& WordInProperty(word, room, go_verb)) {
go_verb_direction = "You can’t get there from here!";
return ’go.verb’;
}
§
objectloop (room provides go_verb && room hasnt visited
&& WordInProperty(word, room, go_verb)) {
go_verb_direction = "But you don’t know the way there!";
return ’go.verb’;
}
rfalse;
];
When successful, this routine stores either a compass direction (an object belonging to
the compass) in the variable go_verb_direction, or else a string to print. (Note that
an UnknownVerb routine shouldn’t print anything itself, as this might be inappropriate
in view of subsequent parsing, or if the actor isn’t the player.) The routine then tells the
parser to treat the verb as if it were ’go.verb’, and as this doesn’t exist yet, we must
define it:
[ Go_VerbSub;
if (go_verb_direction ofclass String)
print_ret (string) go_verb_direction;
<>;
];
Verb ’go.verb’ * -> Go_Verb;
• 44EXERCISE 82
A minor deficiency with the above system is that the parser may print out strange
responses like ‘‘I only understood you as far as wanting to go.verb.’’ if the player types
something odd like ‘‘bedquilt the nugget’’. How can we ensure that the parser will
always say something like ‘‘I only understood you as far as wanting to go to Bedquilt.’’?
• REFERENCES
‘Advent’ makes a string of simple Verb definitions; ‘Alice Through the LookingGlass’ uses Extend a little.
•‘Balances’ has a large extra grammar and also uses
the UnknownVerb and PrintVerb entry points. •Irene Callaci’s "AskTellOrder.h"
library extension file makes an elegant use of BeforeParsing to convert commands in
the form ‘‘ask mr darcy to dance’’ or ‘‘tell jack to go north’’ to Inform’s preferred form
‘‘mr darcy, dance’’ and ‘‘jack, go north’’.
§31
Tokens of grammar
The complete list of grammar tokens is given in the table below. These tokens are all described in this section except for
scope = hRoutinei , which is postponed to the next.
’hwordi’
that literal word only
noun
any object in scope
held
object held by the actor
multi
one or more objects in scope
multiheld
one or more held objects
multiexcept
one or more in scope, except the other object
multiinside
one or more in scope, inside the other object
hattributei
any object in scope which has the attribute
creature
an object in scope which is animate
noun = hRoutinei
any object in scope passing the given test
scope = hRoutinei
an object in this definition of scope
number
a number only
hRoutinei
any text accepted by the given routine
topic
any text at all
To recap, the parser goes through a line of grammar tokens trying to match
each against some text from the player’s input. Each token that matches must
produce one of the following five results:
(a)
(b)
(c)
(d)
a single object;
a ‘‘multiple object’’, that is, a set of objects;
a number;
a ‘‘consultation topic’’, that is, a collection of words left unparsed to be
looked through later;
(e) no information at all.
Ordinarily, a single line, though it may contain many tokens, can produce at
most two substantial results ((a) to (d)), at most one of which can be multiple
§
(b). (See the exercises below if this is a problem.) For instance, suppose the
text ‘‘green apple on the table’’ is parsed against the grammar line:
* multi ’on’ noun -> Insert
The multi token matches ‘‘green apple’’ (result: a single object, since
although multi can match a multiple object, it doesn’t have to), ’on’
matches ‘‘on’’ (result: nothing) and the second noun token matches ‘‘the
table’’ (result: a single object again). There are two substantial results, both
objects, so the action that comes out is . If the text
had been ‘‘all the fruit on the table’’, the multi token might have resulted in
a list: perhaps of an apple, an orange and a pear. The parser would then have
generated and run through three actions in turn: , then
and finally , printing out the
name of each item and a colon before running the action:
>put all the fruit on the table
Cox’s pippin: Done.
orange: Done.
Conference pear: Done.
The library’s routine InsertSub, which actually handles the action, only deals
with single objects at a time, and in each case it printed ‘‘Done.’’
·
·
·
·
·
’hwordi’
This matches only the literal word given, sometimes called a
preposition because it usually is one, and produces no resulting information.
(There can therefore be as many or as few of them on a grammar line as
desired.) It often happens that several prepositions really mean the same thing
for a given verb: for instance ‘‘in’’, ‘‘into’’ and ‘‘inside’’ are often synonymous.
As a convenient shorthand, then, you can write a series of prepositions (only)
with slashes / in between, to mean ‘‘one of these words’’. For example:
* noun ’in’/’into’/’inside’ noun -> Insert
noun
Matches any single object ‘‘in scope’’, a term defined in the next
section and which roughly means ‘‘visible to the player at the moment’’.
held
Matches any single object which is an immediate possession of the
actor. (Thus, if a key is inside a box being carried by the actor, the box
might match but the key cannot.) This is convenient for two reasons. Firstly,
§
many actions, such as Eat or Wear, only sensibly apply to things being held.
Secondly, suppose we have grammar
Verb ’eat’ * held -> Eat;
and the player types ‘‘eat the banana’’ while the banana is, say, in plain view
on a shelf. It would be petty of the game to refuse on the grounds that the
banana is not being held. So the parser will generate a Take action for the
banana and then, if the Take action succeeds, an Eat action. Notice that the
parser does not just pick up the object, but issues an action in the proper way –
so if the banana had rules making it too slippery to pick up, it won’t be picked
up. This is called ‘‘implicit taking’’, and happens only for the player, not for
other actors.
multi
Matches one or more objects in scope. The multi- tokens
indicate that a list of one or more objects can go here. The parser works out
all the things the player has asked for, sorting out plural nouns and words like
‘‘except" in the process. For instance, ‘‘all the apples’’ and ‘‘the duck and the
drake’’ could match a multi token but not a noun token.
multiexcept
Matches one or more objects in scope, except that it does
not match the other single object parsed in the same grammar line. This is
provided to make commands like ‘‘put everything in the rucksack’’ come out
right: the ‘‘everything’’ is matched by all of the player’s possessions except the
rucksack, which stops the parser from generating an action to put the rucksack
inside itself.
multiinside
Similarly, this matches anything inside the other single
object parsed on the same grammar line, which is good for parsing commands
like ‘‘remove everything from the cupboard’’.
hattributei
Matches any object in scope which has the given attribute.
This is useful for sorting out actions according to context, and perhaps the
ultimate example might be an old-fashioned ‘‘use’’ verb:
Verb ’use’ ’employ’ ’utilise’
* edible
-> Eat
* clothing -> Wear
...
* enterable -> Enter;
§
creature
Matches any object in scope which behaves as if living. This
normally means having animate: but, as an exceptional rule, if the action on
the grammar line is Ask, Answer, Tell or AskFor then having talkable is also
acceptable.
noun = hRoutinei
‘‘Any single object in scope satisfying some condition’’. When determining whether an object passes this test, the parser sets
the variable noun to the object in question and calls the routine. If it returns
true, the parser accepts the object, and otherwise it rejects it. For example,
the following should only apply to animals kept in a cage:
[ CagedCreature;
if (noun in wicker_cage) rtrue; rfalse;
];
Verb ’free’ ’release’
* noun=CagedCreature -> FreeAnimal;
So that only nouns which pass the CagedCreature test are allowed. The
CagedCreature routine can appear anywhere in the source code, though it’s
tidier to keep it nearby.
scope = hRoutinei
An even more powerful token, which means ‘‘an
object in scope’’ where scope is redefined specially. You can also choose
whether or not it can accept a multiple object. See §32.
number
Matches any decimal number from 0 upwards (though it rounds
off large numbers to 10,000), and also matches the numbers ‘‘one’’ to ‘‘twenty’’
written in English. For example:
Verb ’type’ * number -> TypeNum;
causes actions like when the player types ‘‘type 504’’. Note
that noun is set to 504, not to an object. (While inp1 is set to 1, indicating
that this ‘‘first input’’ is intended as a number: if the noun had been the object
which happened to have number 504, then inp1 would have been set to this
object, the same as noun.) If you need more exact number parsing, without
rounding off, and including negative numbers, see the exercise below.
§
• EXERCISE 83
Some games, such as David M. Baggett’s game ‘The Legend Lives!’ produce footnotes
every now and then. Arrange matters so that these are numbered [1], [2] and so on in
order of appearance, to be read by the player when ‘‘footnote 1’’ is typed.
4
The entry point ParseNumber allows you to provide your own number-parsing
routine, which opens up many sneaky possibilities – Roman numerals, coordinates like
‘‘J4’’, very long telephone numbers and so on. This takes the form
[ ParseNumber buffer length;
...returning false if no match is made, or the number otherwise...
];
and examines the supposed ‘number’ held at the byte address buffer, a row of
characters of the given length. If you provide a ParseNumber routine but return false
from it, then the parser falls back on its usual number-parsing mechanism to see if that
does any better.
44 Note that ParseNumber can’t return 0 to mean the number zero, because 0 is the
same as false. Probably ‘‘zero’’ won’t be needed too often, but if it is you can always
return some value like 1000 and code the verb in question to understand this as 0.
(Sorry: this was a poor design decision made too long ago to change now.)
topic
This token matches as much text as possible, regardless of what it
says, producing no result. As much text as possible means ‘‘until the end of the
typing, or, if the next token is a preposition, until that preposition is reached’’.
The only way this can fail is if it finds no text at all. Otherwise, the variable
consult_from is set to the number of the first word of the matched text and
consult_words to the number of words. See §16 and §18 for examples of
topics being used.
hRoutinei
The most flexible token is simply the name of a ‘‘general
parsing routine’’. As the name suggests, it is a routine to do some parsing
which can have any outcome you choose, and many of the interesting things
you can do with the parser involve writing one. A general parsing routine looks
at the word stream using NextWord and wn (see §28) to make its decisions, and
should return one of the following. Note that the values beginning GPR_ are
constants defined by the library.
GPR_FAIL
GPR_MULTIPLE
GPR_NUMBER
GPR_PREPOSITION
if there is no match;
if the result is a multiple object;
if the result is a number;
if there is a match but no result;
§
GPR_REPARSE
O
to reparse the whole command from scratch; or
if the result is a single object O.
On an unsuccessful match, returning GPR_FAIL, it doesn’t matter what the
final value of wn is. On a successful match it should be left pointing to the next
thing after what the routine understood. Since NextWord moves wn on by one
each time it is called, this happens automatically unless the routine has read
too far. For example:
[ OnAtorIn;
if (NextWord() == ’on’ or ’at’ or ’in’) return GPR_PREPOSITION;
return GPR_FAIL;
];
duplicates the effect of ’on’/’at’/’in’ , that is, it makes a token which
accepts any of the words ‘‘on", ‘‘at" or ‘‘in" as prepositions. Similarly,
[ Anything;
while (NextWordStopped() ~= -1) ; return GPR_PREPOSITION;
];
accepts the entire rest of the line (even an empty text, if there are no more
words on the line), ignoring it. NextWordStopped is a form of NextWord which
returns the special value −1 once the original word stream has run out.
If you return GPR_NUMBER, the number which you want to be the result
should be put into the library’s variable parsed_number.
If you return GPR_MULTIPLE, place your chosen objects in the table
multiple_object: that is, place the number of objects in multiple_object->0 and the objects themselves in -->1, . . .
The value GPR_REPARSE should only be returned if you have actually
altered the text you were supposed to be parsing. This is a feature used
internally by the parser when it asks ‘‘Which do you mean . . .?’’ questions, and
you can use it too, but be wary of loops in which the parser eternally changes
and reparses the same text.
·
·
·
·
·
4
To parse a token, the parser uses a routine called ParseToken. This behaves
almost exactly like a general parsing routine, and returns the same range of values. For
instance,
ParseToken(ELEMENTARY_TT, NUMBER_TOKEN)
§
parses exactly as number does: similarly for NOUN_TOKEN, HELD_TOKEN, MULTI_TOKEN,
MULTIHELD_TOKEN, MULTIEXCEPT_TOKEN, MULTIINSIDE_TOKEN and CREATURE_TOKEN.
The call
ParseToken(SCOPE_TT, MyRoutine)
does what scope=MyRoutine does. In fact ParseToken can parse any kind of token,
but these are the only cases which are both useful enough to mention and safe enough
to use. It means you can conveniently write a token which matches, say, either the word
‘‘kit’’ or any named set of items in scope:
[ KitOrStuff; if (NextWord() == ’kit’) return GPR_PREPOSITION;
wn--; return ParseToken(ELEMENTARY_TT, MULTI_TOKEN);
];
·
·
·
·
·
• EXERCISE 84
Write a token to detect small numbers in French, ‘‘un’’ to ‘‘cinq’’.
• EXERCISE 85
Write a token called Team, which matches only against the word ‘‘team’’ and results in
a multiple object containing each member of a team of adventurers in a game.
• 4EXERCISE 86
Write a token to detect non-negative floating-point numbers like ‘‘21’’, ‘‘5.4623’’, ‘‘two
point oh eight’’ or ‘‘0.01’’, rounding off to two decimal places.
• 4EXERCISE 87
Write a token to match a phone number, of any length from 1 to 30 digits, possibly
broken up with spaces or hyphens (such as ‘‘01245 666 737’’ or ‘‘123-4567’’).
• 44EXERCISE 88
(Adapted from code in "timewait.h": see the references below.) Write a token to
match any description of a time of day, such as ‘‘quarter past five’’, ‘‘12:13 pm’’,
‘‘14:03’’, ‘‘six fifteen’’ or ‘‘seven o’clock’’.
• 4EXERCISE 89
Code a spaceship control panel with five sliding controls, each set to a numerical value,
so that the game looks like:
§
>look
Machine Room
There is a control panel here, with five slides, each of which can be set to a
numerical value.
>push slide one to 5
You set slide one to the value 5.
>examine the first slide
Slide one currently stands at 5.
>set four to six
You set slide four to the value 6.
• 4EXERCISE 90
Write a general parsing routine accepting any amount of text, including spaces, full
stops and commas, between double-quotes as a single token.
• 4EXERCISE 91
On the face of it, the parser only allows two parameters to an action, noun and second.
Write a general parsing routine to accept a third. (This is easier than it looks: see the
specification of the NounDomain library routine in §A3.)
• EXERCISE 92
Write a token to match any legal Inform decimal, binary or hexadecimal constant (such
as -321, $4a7 or $$1011001), producing the correct numerical value in all cases, while
not matching any number which overflows or underflows the legal Inform range of
−32, 768 to 32,767.
• EXERCISE 93
Add the ability to match the names of the built-in Inform constants true, false,
nothing and NULL.
• EXERCISE 94
Now add the ability to match character constants like ’7’, producing the correct
character value (in this case 55, the ZSCII value for the character ‘7’).
• 44EXERCISE 95
Next add the ability to match the names of attributes, such as edible, or negated
attributes with a tilde in front, such as ~edible. An ordinary attribute should parse to
its number, a negated one should parse to its number plus 100. (Hint: the library has a
printing rule called DebugAttribute which prints the name of an attribute.)
• 44EXERCISE 96
And now add the names of properties.
§
• REFERENCES
Once upon a time, Andrew Clover wrote a neat library extension called "timewait.h"
for parsing times of day, and allowing commands such as ‘‘wait until quarter to three’’.
L. Ross Raszewski, Nicholas Daley and Kevin Forchione each tinkered with and
modernised this, so that there are now also "waittime.h" and "timesys.h". Each has
its merits.
§32
Scope and what you can see
He cannot see beyond his own nose. Even the fingers he outstretches from it to the world are (as I shall suggest) often invisible
to him.
— Max Beerbohm (1872–1956), of George Bernard Shaw
Time to say what ‘‘in scope’’ means. This definition is one of the
most important rules of play, because it decides what the player
is allowed to refer to. You can investigate this experimentally by
compiling any game with the debugging suite of verbs included (see
§7) and typing ‘‘scope’’ in interesting places. ‘‘In scope’’ roughly means ‘‘the
compass directions, what you’re carrying and what you can see’’. It exactly
means this:
(1) the compass directions;
(2) the player’s immediate possessions;
(3) if there is light, then the contents of the player’s visibility ceiling (see §21 for
definition, but roughly speaking the outermost object containing the player which
remains visible, which is usually the player’s location);
(4) if there is darkness, then the contents of the library’s object thedark (by default
there are no such contents, but some designers have been known to move objects
into thedark: see ‘Ruins’);
(5) if the player is inside a container, then that container;
(6) if O is in scope and is see-through (see §21), then the contents of O;
(7) if O is in scope, then any object which it ‘‘adds to scope’’ (see below).
with the proviso that the InScope entry point (see below) can add to or replace
these rules, if you write one.
It’s significant that rule (3) doesn’t just say ‘‘whatever is in the current
location’’. For instance, if the player is in a closed cupboard in the Stores
Room, then rule (3) means that the contents of the cupboard are in scope, but
other items in the Stores Room are not.
Even in darkness the player’s possessions are in scope, so the player can
still turn on a lamp being carried. On the other hand, a player who puts the
lamp on the ground and turns it off then loses the ability to turn it back on
again, because it is out of scope. This can be changed; see below.
4
Compass directions make sense as things as well as directions, and they respond
to names like ‘‘the south wall’’ and ‘‘the floor’’ as well as ‘‘south’’ and ‘‘down’’.
§
4
The concealed attribute only hides objects from room descriptions, and doesn’t
remove them from scope. If you want things to be unreferrable-to, put them somewhere
else!
4
The small print: 1. For ‘‘player’’, read ‘‘actor’’. Thus ‘‘dwarf, drop sword’’ will
be accepted if the dwarf can see the sword even if the player can’t. 2. Scope varies
depending on the token being parsed: for the multi- tokens, compass directions are
not in scope; for multiexcept the other object isn’t in scope; for multiinside only
the contents of the other object are in scope.
·
·
·
·
·
Two library routines enable you to see what’s in scope and what isn’t. The first,
TestScope(obj, actor), simply returns true or false according to whether
or not obj is in scope. The second is LoopOverScope(routine, actor) and
calls the given routine for each object in scope. In each case the actor given
is optional, and if it’s omitted, scope is worked out for the player as usual.
• EXERCISE 97
Implement the debugging suite’s ‘‘scope’’ verb, which lists all the objects currently in
scope.
• EXERCISE 98
Write a ‘‘megalook’’ verb, which looks around and examines everything in scope except
the walls, floor and ceiling.
·
·
·
·
·
Formally, scope determines what you can talk about, which usually means
what you can see or hear. But what can you touch? Suppose a locked chest
is inside a sealed glass cabinet. The Inform parser will allow the command
‘‘unlock chest with key’’ and generate the appropriate action, , because the chest is in scope, so the command at least makes sense.
But it’s impossible to carry out, because the player can’t reach through
the solid glass. So the library’s routine for handling the Unlock action needs to
enforce this. The library does this using a stricter rule called ‘‘touchability’’.
The rule is that you can touch anything in scope unless there’s a closed
container between you and it. This applies either if you’re in the container,
or if it is.
Some purely visual actions, such as Examine or LookUnder, don’t require
touchability. But most actions are tactile, and so are many actions created by
designers. If you want to make your own action routines enforce touchability,
you can call the library routine ObjectIsUntouchable(obj). This either
returns false and prints nothing if there’s no problem in touching obj, or
§
returns true and prints a suitable message, such as ‘‘The solid glass cabinet is
in the way.’’ Thus, the first line of many of the library’s action routines is:
if (ObjectIsUntouchable(noun)) return;
You can also call ObjectIsUntouchable(obj, true) to simply return true
or false, printing nothing, if you’d rather provide your own failure message.
·
·
·
·
·
The rest of this section is about how to change the scope rules. As usual with
Inform, you can change them globally, but it’s more efficient and safer to work
locally. To take a typical example: how do we allow the player to ask questions
like the traditional ‘‘what is a grue’’? The ‘‘grue’’ part ought to be parsed as
if it were a noun, so that we could distinguish between, say, a ‘‘garden grue’’
and a ‘‘wild grue’’. So it isn’t good enough to look only at a single word. Here
is one solution:
[ QuerySub; noun.description(); return;
];
[ QueryTopic;
switch (scope_stage) {
1: rfalse;
2: ScopeWithin(questions); rtrue;
3: "At the moment, even the simplest questions confuse you.";
}
];
Object questions;
where the actual questions at any time are the current children of the questions
object, like so:
Object -> "long count"
with name ’long’ ’count’,
description "The Long Count is the great Mayan cycle of
time, which began in 3114 BC and will finish with
the world’s end in 2012 AD.";
(which might be helpful in ‘Ruins’) and we also have a grammar line:
Verb ’what’ * ’is’/’was’ scope=QueryTopic -> Query;
The individual questions have short names so that the parser might be able to
say ‘‘Which do you mean, the long count or the short count?’’ if the player
asked ‘‘what is the count’’. (As it stands this won’t recognise ‘‘what is the
count?’’. Conventionally players are supposed not to type question marks or
quotes in their commands. To allow this one could always add ’count?’ as
one of the names above.)
§
·
·
·
·
·
Here is the specification. When the parser reaches scope=Whatever , it calls
the Whatever routine with the variable scope_stage set to 1. The routine
should return true if it is prepared to allow multiple objects to be accepted
here, and false otherwise. (In the example, as we don’t want ‘‘what is
everything’’ to list all the questions and answers in the game, we return false.)
A little later the parser calls Whatever again with scope_stage now set to
2. Whatever is then obliged to tell the parser which objects are to be in scope.
It can call two parser routines to do this:
ScopeWithin(obj)
which puts everything inside obj, but not obj itself, into scope, and then works
through rules (6) and (7) above, so that it may continue to add the contents of
the contents, and so on; and
PlaceInScope(obj)
which puts just obj into scope. It is perfectly legal to declare something in
scope that ‘‘would have been in scope anyway’’: or something which is in a
different room altogether from the actor concerned, say at the other end of a
telephone line. The scope routine Whatever should then return false if the
nominated items are additional to the usual scope, or true if they are the only
items in scope. (In the example, QueryTopic returns true because it wants
the only items in scope to be its question topics, not the usual miscellany near
the player.)
This is fine if the token is correctly parsed. If not, the parser may choose
to ask the token routine to print a suitable error message, by calling Whatever
with scope_stage set to 3. (In the example, this will happen if the player types
‘‘what is the lgon count’’, and QueryTopic replies ‘‘At the moment, even the
simplest questions confuse you.’’, because it comes from a faintly dream-like
game called ‘Balances’.)
• EXERCISE 99
Write a token which puts everything in scope, so that you could have a debugging
‘‘purloin’’ which could take anything, regardless of where it was and the rules applying
to it.
·
·
·
·
·
§
4
The global scope rules can be tampered with by providing an entry point routine
called InScope(actor), where actor is the actor whose scope is worked out. In effect,
this defines the ‘‘ordinary’’ scope by making calls to ScopeWithin and PlaceInScope,
then returning true or false, exactly as if it were a scope token at stage 2. For instance,
here as promised is how to change the rule that ‘‘things you’ve just dropped disappear
in the dark’’:
[ InScope person i;
if (person == player && location == thedark)
objectloop (i in parent(player))
if (i has moved) PlaceInScope(i);
rfalse;
];
With this routine added, objects near the player in a dark room are in scope only if
they have moved (that is, have been held by the player in the past), and even then are
in scope only to the player, not to anyone else.
4 The token scope=hRoutinei takes precedence over InScope, which will only be
reached if the routine returns false to signify ‘‘carry on’’.
44 There are seven reasons why InScope might be being called; the scope_reason
variable is set to the current one:
PARSING_REASON
TALKING_REASON
EACHTURN_REASON
REACT_BEFORE_REASON
REACT_AFTER_REASON
TESTSCOPE_REASON
LOOPOVERSCOPE_REASON
The usual reason. Note that action_to_be holds NULL in
the early stages (before the verb has been decided) and later
on the action which would result from a successful match.
Working out which objects are in scope for being spoken to
(see the end of §18 for exercises using this).
When running each_turn routines for anything nearby, at
the end of each turn.
When running react_before.
When running react_after.
When performing a TestScope.
When performing a LoopOverScope.
• 44EXERCISE 100
Construct a long room divided by a glass window. Room descriptions on either side
should describe what’s in view on the other; the window should be possible to look
through; objects on the far side should be in scope, but not manipulable.
• 44EXERCISE 101
Code the following puzzle. In an initially dark room there is a light switch. Provided
you’ve seen the switch at some time in the past, you can turn it on and off – but before
you’ve ever seen it, you can’t. Inside the room is nothing you can see, but you can hear
the distinctive sound of a dwarf breathing. If you tell this dwarf to turn the light on, he
will.
§
·
·
·
·
·
Each object has the ability to drag other objects into scope whenever it is
in scope. This is especially useful for giving objects component parts: e.g.,
giving a washing-machine a temperature dial. (The dial can’t be a child object
because that would throw it in with the clothes: and it ought to be attached
to the machine in case the machine is moved from place to place.) For this
purpose, the property add_to_scope may contain a list of objects to add.
4 Alternatively, it may contain a routine. This routine can then call AddToScope(x)
to add any object x to scope. It may not, however, call ScopeWithin or any other
scoping routines.
44 Scope addition does not occur for an object moved into scope by an explicit call
to PlaceInScope, since this must allow complete freedom in scope selections. But it
does happen when objects are moved in scope by calls to ScopeWithin(domain).
• EXERCISE 102
(From the tiny example game ‘A Nasal Twinge’.) Give the player a nose, which is
always in scope and can be held, reducing the player’s carrying capacity.
• EXERCISE 103
(Likewise.) Create a portable sterilising machine, with a ‘‘go’’ button, a top which
things can be put on and an inside to hold objects for sterilisation. Thus it is a container,
a supporter and a possessor of sub-objects all at once.
• 44EXERCISE 104
Create a red sticky label which the player can affix to any object in the game. (Hint:
use InScope, not add_to_scope.)
• REFERENCES
‘Balances’ uses scope = hroutinei tokens for legible spells and memorised spells.
•Jesse Burneko’s library extension "Info.h" is a helpful model to follow: using a simple
scope token, it allows for ‘‘consult’’ and ‘‘ask’’ commands to access topics which are
provided as objects. •See also the exercises at the end of §18 for further scope trickery.
•Similarly, Andrew C. Murie’s "whatis.h" and David Cornelson’s "whowhat.h" field
questions such as ‘‘what is. . .’’ and ‘‘who is. . .’’.
§33
Helping the parser out of trouble
Once you begin programming the parser on a large scale, you soon
reach the point where the parser’s ordinary error messages no longer
appear sensible. The ParserError entry point can change the rules
even at this last hurdle: it takes one argument, the error type, and
should return true to tell the parser to shut up, because a better error message
has already been printed, or false, to tell the parser to print its usual message.
The error types are defined as constants:
STUCK_PE
UPTO_PE
NUMBER_PE
CANTSEE_PE
TOOLIT_PE
NOTHELD_PE
MULTI_PE
MMULTI_PE
VAGUE_PE
EXCEPT_PE
ANIMA_PE
VERB_PE
SCENERY_PE
ITGONE_PE
JUNKAFTER_PE
TOOFEW_PE
NOTHING_PE
ASKSCOPE_PE
I didn’t understand that sentence.
I only understood you as far as. . .
I didn’t understand that number.
You can’t see any such thing.
You seem to have said too little!
You aren’t holding that!
You can’t use multiple objects with that verb.
You can only use multiple objects once on a line.
I’m not sure what ‘it’ refers to.
You excepted something not included anyway!
You can only do that to something animate.
That’s not a verb I recognise.
That’s not something you need to refer to. . .
You can’t see ‘it’ (the whatever) at the moment.
I didn’t understand the way that finished.
Only five of those are available.
Nothing to do!
whatever the scope routine prints
Each unsuccessful grammar line ends in one of these conditions. By the time
the parser wants to print an error, every one of the grammar lines in a verb will
have failed. The error message chosen it prints is the most ‘‘interesting’’ one:
meaning, lowest down this list.
If a general parsing routine you have written returns GPR_FAIL, then
the grammar line containing it normally ends in plain STUCK_PE, the least
interesting of all errors (unless you did something like calling the library’s
ParseToken routine before giving up, which might have set a more interesting
error like CANTSEE_PE). But you can choose to create a new error and put it in
the parser’s variable etype, as in the following example:
[ Degrees d;
d = TryNumber(wn++);
§
if (d == -1000) return GPR_FAIL;
if (d <= 360) { parsed_number = d; return GPR_NUMBER; }
etype = "There are only 360 degrees in a circle.";
return GPR_FAIL;
];
This parses a number of degrees between 0 and 360. Although etype normally
only holds values like VERB_PE, which are numbers lower than 100, here we’ve
set it equal to a string. As this will be a value that the parser doesn’t recognise,
we need to write a ParserError routine that will take care of it, by reacting to
a string in the obvious way – printing it out.
[ ParserError error_type;
if (error_type ofclass String) print_ret (string) error_type;
rfalse;
];
This will result in conversation like so:
>steer down
I didn’t understand that sentence.
>steer 385
There are only 360 degrees in a circle.
In the first case, Degrees failed without setting any special error message on
finding that the second word wasn’t a number; in the second case it gave the
new, specific error message.
·
·
·
·
·
The VAGUE_PE and ITGONE_PE errors apply to all pronouns (in English, ‘‘it’’,
‘‘him’’, ‘‘her’’ and ‘‘them’’). The variable vague_word contains the dictionary
address of whichever pronoun is involved (’it’, ’him’ and so on).
You can find out the current setting of a pronoun using the library’s
PronounValue routine: for instance, PronounValue(’it’) gives the object
which ‘‘it’’ currently refers to, possibly nothing. Similarly SetPronoun(’it’,
magic_ruby) would set ‘‘it’’ to mean the magic ruby object. You might want
this because, when something like a magic ruby suddenly appears in the
middle of a turn, players will habitually call it ‘‘it’’. A better way to adjust
the pronouns is to call PronounNotice(magic_ruby), which sets whatever
pronouns are appropriate. That is, it works out if the object is a thing or
a person, of what number and gender, which pronouns apply to it in the
parser’s current language, and so on. In code predating Inform 6.1 you may
see variables called itobj, himobj and herobj holding the English pronoun
values: these still work properly, but please use the modern system in new
games.
§
·
·
·
·
·
4
The Inform parser resolves ambiguous object names with a pragmatic algorithm
which has evolved over the years (see below). Experience also shows that no two
people ever quite agree on what the parser should ‘‘naturally’’ do. Designers have an
opportunity to influence this by providing an entry point routine called ChooseObjects:
ChooseObjects(obj, code)
is called in two circumstances. If code is false or true, the parser is considering
including the given obj in an ‘‘all’’: false means the parser has decided against, true
means it has decided in favour. The routine should reply
0
1
2
to accept the parser’s decision;
to force the object to be included; or
to force the object to be excluded.
It may want to decide using verb_word (the variable storing the current verb word, e.g.,
’take’) and action_to_be, which is the action which would happen if the current line
of grammar were successfully matched.
The other circumstance is when code is 2. This means the parser is choosing
between a list of items which made equally good matches against some text, and would
like a hint. ChooseObjects should then return a number from 0 to 9 to give obj a score
for how appropriate it is.
For instance, some designers would prefer ‘‘take all’’ not to attempt to take
scenery objects (which Inform, and the parsers in most of the Infocom games, will
do). Let us code this, and also teach the parser that edible things are more likely to be
eaten than inedible ones:
[ ChooseObjects obj code;
if (code < 2) { if (obj has scenery) return 2; rfalse; }
if (action_to_be == ##Eat && obj has edible) return 3;
if (obj hasnt scenery) return 2;
return 1;
];
Scenery is now excluded from ‘‘all’’ lists; and is further penalised in that non-scenery
objects are always preferred over scenery, all else being equal. Most objects score 2 but
edible things in the context of eating score 3, so ‘‘eat black’’ will now always choose a
Black Forest gateau in preference to a black rod with a rusty iron star on the end.
• 4EXERCISE 105
Allow ‘‘lock’’ and ‘‘unlock’’ to infer their second objects without being told, if there’s
an obvious choice (because the player’s only carrying one key), but to issue a disambiguation question otherwise. (Use Extend, not ChooseObjects.)
§
• 4EXERCISE 106
Joyce Haslam’s Inform edition of the classic Acornsoft game ‘Gateway to Karos’
requires a class called FaintlyLitRoom for rooms so dimly illuminated that ‘‘take all’’
is impossible. How might this work?
·
·
·
·
·
44 Suppose we have a set of objects which have all matched equally well against the
textual input, so that some knowledge of the game world is needed to resolve which
of the objects – possibly only one, possibly more – is or are intended. Deciding this is
called ‘‘disambiguation’’, and here in full are the rules used by library 6/10 to do it.
The reader is cautioned that after six years, these rules are still evolving.
(1) Call an object ‘‘good’’ according to a rule depending on what kind of token is
being matched:
held
Good if its parent is the actor.
multiheld
Good if its parent is the actor.
multiexcept Good if not also the second object, if that’s known yet.
multiinside Good if not inside the second object, if that’s known yet.
creature
Good if animate, or if the proposed action is Ask,
Answer, Tell or AskFor and the object is talkable.
other tokens
All objects are good.
If only a single object is good, this is immediately chosen.
(2) If the token is creature and no objects are good, fail the token altogether, as no
choice can make sense.
(3) Objects which don’t fit ‘‘descriptors’’ used by the player are removed:
if ‘‘my’’, an object whose parent isn’t the actor is discarded;
if ‘‘that’’, an object whose parent isn’t the actor’s location is discarded;
if ‘‘lit’’, an object which hasn’t light is discarded;
if ‘‘unlit’’, an object which has light is discarded;
if ‘‘his’’ or some similar possessive pronoun, an object not owned by the
person implied is discarded.
Thus ‘‘his lit torches’’ will invoke two of these rules at once.
(4) If there are no objects left, fail the token, as no choice can make sense.
(5) It is now certain that the token will not fail. The remaining objects are assigned a
score as follows:
(i) 1000 × C points, where C is the return value of ChooseObjects(object,2).
(0 ≤ C ≤ 9. If the designer doesn’t provide this entry point at all then
C = 0.)
(ii) 500 points for being ‘‘good’’ (see (1) above).
(iii) 100 points for not having concealed.
§
(iv) P points depending on the object’s position:
A
if object belongs to the actor,
L if object belongs to the actor’s visibility ceiling,
P =
20 if object belongs anywhere else except the compass,
0
if object belongs to the compass.
(Recall that ‘‘visibility ceiling’’ usually means ‘‘location’’ and that the objects
belonging to the compass are exactly the compass directions.) The values A
and L depend on the token being parsed:
n
A = 60 L = 40 for held or multiheld tokens,
A = 40 L = 60 otherwise.
(v) 10 points for not having scenery.
(vi) 5 points for not being the actor object.
(vii) 1 point if the object’s gender, number and animation (GNA) matches one
possibility implied by some pronoun typed by the player: for instance ‘‘them’’
in English implying plural, or ‘‘le’’ in French implying masculine singular.
(6d) In ‘‘definite mode’’, such as if the player has typed a definite article like ‘‘the’’, if
any single object has highest score, choose that object.
(7ip) The following rule applies only in indefinite mode and provided the player has
typed something definitely implying a plural, such as the words ‘‘all’’ or ‘‘three’’
or ‘‘coins’’. Here the parser already has a target number of objects to choose:
for instance 3 for ‘‘three’’, or the special value of 100, meaning ‘‘an unlimited
number’’, for ‘‘all’’ or ‘‘coins’’.
Go through the list of objects in ‘‘best guess’’ order (see below). Mark each as
‘‘accept’’ unless:
(i) it has worn or concealed;
(ii) or the action is Take or Remove and the object is held by the actor;
(iii) or the token is multiheld or multiexcept and the object isn’t held by
the actor;
(iv) or the target number is ‘‘unlimited’’ and S/20 (rounded down to the nearest
integer) has fallen below its maximum value, where S is the score of the
object.
The entry point ChooseObjects(object,accept_flag) is now called and can
overrule the ‘‘accept’’/‘‘reject’’ decision either way. We keep accepting objects like
this until the target is reached, or proves impossible to reach.
(8) The objects are now grouped so that any set of indistinguishable objects forms a
single group. ‘‘Indistinguishable’’ means that no further text typed by the player
could clarify which is meant (see §29). Note that there is no reason to suppose
that two indistinguishable objects have the same score, because they might be in
different places.
§
(9d) In definite mode, we know that there’s a tie for highest score, as otherwise a choice
would have been made at step (6d). If these highest-scoring objects belong to
more than one group, then ask the player to choose which group:
You can see a bronze coin and four gold coins here.
>get coin
Which do you mean, the bronze coin or a gold coin?
>gold
The player’s response is inserted textually into the original input and the parsing
begins again from scratch with ‘‘get gold coin’’ instead of ‘‘get coin’’.
(10) Only two possibilities remain: either (i) we are in indefinite but singular mode,
or (ii) we are in definite mode and there is a tie for highest-scoring object and
all of these equal-highest objects belong to the same group. Either way, choose
the ‘‘best guess’’ object (see below). Should this parsing attempt eventually prove
successful, print up an ‘‘inference’’ on screen, such as
>get key
(the copper key)
only if the number of groups found in (8) is more than 1.
(BG) It remains to define ‘‘best guess’’. From a set of objects, the best guess is the
highest-scoring one not yet guessed; and if several objects have equal highest
scores, it is the earliest one to have been matched by the parser. In practice this
means the one most recently taken or dropped, because the parser tries to match
against objects by traversing the object-tree, and the most recently moved object
tends to be the first in the list of children of its parent.
• REFERENCES
See ‘Balances’ for a usage of ParserError.
•Infocom’s parser typically produces
error messages like ‘‘I don’t know the word ‘tarantula’.’’ when the player types a word
not in the game’s dictionary, and some designers prefer this style to Inform’s givenothing-away approach (Inform tries not to let the player carry out experiments to see
what is, and what is not, in the dictionary). Neil Cerutti’s "dunno.h" library extension
restores the Infocom format. •The library extension "calyx_adjectives.h", which
resolves ambiguities in parsing by placing more weight on matches with nouns than
with adjectives, works by using ChooseObjects.
Chapter V: Natural Language
Westlich von Haus
Du stehst auf freiem Feld westlich von einem weißen Haus, dessen Haustür
mit Brettern vernagelt ist. Hier ist ein kleiner Briefkasten.
— ‘Zork I: Das Große Unterweltreich’
§34
Linguistics and the Inform parser
¡Bienvenido a Aventura! Despite the English-language bias of early
computers and their manuals, interactive fiction has a culture and
a history beyond English, not least in Germany. Like the Monty
Python team and the Beatles, Infocom made a German translation
of their defining work, when in early 1988 Jeff O’Neill coded up ‘Zork I:
Das Große Unterweltreich’. It came at a sorry time in Infocom’s fortunes
and remains officially unreleased, in part because the translator recruited had
rendered the text in a stilted, business-German manner, though a beta-test
of the story file circulates to this day. But O’Neill’s work was not in vain,
as it left another important legacy: an upgrading of the Z-machine format
to allow for accented characters, which opened the door to non-English IF
on the Z-machine. Jose Luiz Diaz’s translation of ‘Advent’ into Spanish,
as ‘Aventura’, stimulated much of the 1996 development of Inform as a
multilingual system, and Toni Arnold’s game ‘Rummelplatzgeschichte’ (1998)
also deserves mention, as does advice from Inform users across four continents,
among them Torbjörn Andersson, Joachim Baumann, Paul David Doherty,
Bjorn Gustavsson, Aapo Haapanen, Ralf Herrmann, J. P. Ikaheimonen, Ilario
Nardinocchi, Bob Newell, Giovanni Ricardi, Linards Ticmanis. If nothing
else, I am glad to have learned the palindromic Finnish word for soap dealer,
‘‘saippuakauppias’’.
The standard English-language release of the Inform library now consists
of eight files of code. Of these eight, only two need to be replaced to make
a translation to another language: "Grammar.h", which contains grammars
for English verbs like ‘‘take’’ and ‘‘drop’’; and a ‘‘language definition file’’
called "English.h". For instance, in Ilario Nardinocchi’s translation these
§
two files are replaced by "ItalianG.h" and "Italian.h", in Jose Luis Diaz’s
translation they become "SpanishG.h" and "Spanish.h" and so on. Language
definition files can be useful for more, or rather less, than just translation. ‘The
Tempest’ (1997), for instance, uses a language definition file to enable it to
speak in Early Modern English verse and to recognise pronouns like ‘‘thee’’
and ‘‘thy’’. A suitable language definition file could also change the persona
of an Inform game from second-person (‘‘You fall into a pit!’’) to first-person
(‘‘I have fallen into a pit!’’) or third (‘‘Bilbo falls into a pit!’’), or from present
to past tenses, as Jamie Murphy’s game ‘Leopold the Minstrel’ (1996) did.
This section goes into the linguistics of the Inform parser, and how to add
new grammatical concepts to it using grammar tokens. The next goes into
full-scale translation and how to write new language definition files.
·
·
·
·
·
Language is complex, computers are simple. Modern English is a mostly
non-inflected language, meaning that words tend not to alter their spelling
according to their usage, but even here the parser has to go to some trouble to
cope with one of its remaining inflections (‘‘take coin’’ but ‘‘take six coins’’:
see §29). The range of variation in human languages is large and as many are
heavily inflected the task at first seems hopeless.†
On the other hand, Inform is mainly used with Romance-family languages,
where commands are formed roughly as they are in English. The language
understood by the parser is a simple one, called Informese. It has three
genders, two numbers, a concept of animate versus inanimate nouns and a
clear understanding of articles and pronouns, but all verbs are imperative,
the only tense is the present, there are no cases of nouns (but see §35) and
adjectives are not distinguished from nouns (but see §26). Informese is based
on a small part of English, but the proposition of this chapter is that (with
some effort) you can find Informese at the core of many other languages as
well.
Changes of vocabulary are obviously needed: for instance, where an
English game recognises ‘‘other’’, a French one must recognise ‘‘autre’’. But,
as the following example shows, vocabulary changes are not enough:
jetez la boule dedans
throw the ball into it (French)
has no word-for-word translation into Informese, because ‘‘dedans’’ (into it)
is a pronominal adverb, and Informese doesn’t have pronominal adverbs.
† In fact the difficult languages to parse are not those with subtleties of spelling but
those where even word-recognition can be a matter of context and guesswork, such as
Hebrew, where all vowels are conventionally omitted.
§
Instead, a transformational rule like this one must be applied:
dedans
inside it
7→ dans lui
Transformational rules like this one bring new grammatical structures into the
Inform parser. The rest of this section is occupied with describing what is
present already.
·
·
·
·
·
The following is a short grammar of Informese. Both here and in the General Index,
grammatical concepts understood by the parser are written in angle brackets hlike soi.
(1) Commands
A command to an Inform game should be one of:
hoops-wordi hwordi
haction phrasei
hnoun phrasei, haction phrasei
An hoops-wordi corrects the last command by putting the hwordi in to replace whatever
seemed to be incorrect. In "English.h", the only words in the hoops-wordi category
are ‘‘oops’’ and its abbreviation ‘‘o’’. An haction phrasei instructs the player to perform
an action, unless it is preceded by a hnoun phrasei and a comma, in which case someone
else is instructed to perform an action.
An haction phrasei consists of a sequence of verb phrases, divided up by full stops
or then-words: a hthen-wordi is a word like the English ‘‘then’’ or a full stop. For
instance ‘‘take sword. east. put sword in stone’’ is broken into a sequence of three verb
phrases, each parsed and acted on in turn. (It’s important not to parse them all at once:
the meaning of the noun phrase ‘‘stone’’ depends on where the player is by then.)
(2) Verb phrases
A hverb phrasei is one of:
hagain-wordi
himperative verbi hgrammar linei
Again-words are another category: in "English.h" these are ‘‘again’’ and its abbreviation ‘‘g’’. An hagain-wordi is understood as ‘‘the hverb phrasei most recently typed in
which wasn’t an hagain-wordi’’.
The imperative is the form of the verb used for orders or instructions. In English
the imperative (‘‘open the window’’) looks the same as the infinitive (‘‘to open’’), but
in most languages they differ (French ‘‘ouvrez’’ is imperative, ‘‘ouvrir’’ infinitive). Even
in many languages where verbs usually follow objects, such as Latin, the imperative
comes at the start of a verb phrase, and Informese insists on this. Informese also
§
wants the himperative verbi to be a single word, but programming can get around both
requirements.
Grammar lines are sequences of tokens. Each token results in one of four
grammatically different outcomes:
hnoun phrasei
hprepositioni
hnumberi
hunparsed texti
For instance, a successful match for the tokens noun or multiheld would produce
a hnoun phrasei, whereas a match for ’into’ would produce a hprepositioni. Note
that a general parsing routine can produce any of these four outcomes.
(3) Prepositions
Any word written in quotes as a grammar token. This is normally also a preposition in
the ordinary grammatical sense, but not always, as the ‘‘press charges’’ example in §30
shows. In "English.h", ‘‘look under table’’ and ‘‘switch on radio’’ contain two words
considered to be prepositions in Informese: ‘‘under’’ and ‘‘on’’.
(4) Numbers
Include at least the numbers 1 to 20 written out in words. ‘‘At least’’ because a language
definition file is free to include more, but should not include less.
(5) Noun phrases
A string of words which refer to a single object or collection of objects, with more or
less exactness. Here are some typical examples of "English.h" noun phrases:
it
rucksack
brown bag, pepper
a box and the other compass
nine silver coins
everything except the rucksack
smooth stones
A noun phrase is a list of basic noun phrases:
hbasic npi hconnectivei hbasic npi hconnectivei . . . hconnectivei hbasic npi
and there are two kinds of connective: an hand-wordi (conjunction), and a hbut-wordi
(disjunction). The Inform parser always regards a comma in a hnoun phrasei (other
than one used at the start of a command: see (1) above) as an hand-wordi, and the
definition of "English.h" gives ‘‘and’’ as another. "English.h" has two hbut-wordsi:
‘‘but’’ and ‘‘except’’.
§
hNoun phrasesi being parsed are assigned several properties. They are declared
definite if they carry no article, or a definite article which is not qualified by an all-word
or a demanding number, and are otherwise indefinite. (Except that a noun-phrase
containing a dictionary word flagged as likely to be referring to plural objects, such
as ’crowns//p’, is always indefinite.) Definiteness affects disambiguation and the
parser’s willingness to make guesses, as the description of the parser’s disambiguation
algorithm at the end of §33 shows.
Indefinite noun phrases also have a target quantity of objects being referred to:
this is normally 1, but 7 for ‘‘seven stones’’ and 100, used internally to mean ‘‘as
many as possible’’, for ‘‘crowns’’ or ‘‘all the swords’’. Noun phrases also have a
gender-number-animation combination, or ‘‘GNA’’:
Gender: in most European languages, nouns divide up into masculine, feminine
or neuter, the three genders in Informese. Gender is important when parsing noun
phrases because it can distinguish otherwise identical nouns, as in French: ‘‘le faux’’,
the forgery, ‘‘la faux’’, the scythe. As in German, there may be no satisfactory way to
determine the gender of a noun by any automatic rules: see the next section for how
Inform assigns genders to nouns.
Number: singular (‘‘the hat’’) or plural (‘‘the grapes’’). Individual objects in Inform
games can have names of either number. Languages with more than two numbers are
rare, but Hebrew has a ‘‘pair of’’ number. This would have to be translated into a
demanding number (see (7d) below) for Informese.
Animation: Informese distinguishes between the animate (people and higher
animals) and the inanimate (objects, plants and lower animals).
With three genders, two numbers and two animations, Informese has twelve
possible GNA combinations, and these are internally represented by the numbers 0 to
11:
0 animate
singular
1
2
3
plural
4
5
6 inanimate singular
7
8
9
plural
10
11
masculine
feminine
neuter
masculine
feminine
neuter
masculine
feminine
neuter
masculine
feminine
neuter
Not all possible GNAs occur in all natural languages. In English, cases 6, 7, 9 and 10
never occur, except perhaps that ships are sometimes called ‘‘she’’ and ‘‘her’’ without
being animate (GNA 7). In French, 2, 5, 8 and 11 never occur. The parser actually
works by assigning sets of possible GNA values to each noun phrase: so, in French,
§
‘‘le faux’’ carries the set {6}, while the more ambiguous noun phrase ‘‘les’’ carries
{3, 4, 9, 10}.
(6) Basic noun phrases
These take the following form, in which both lists can have any number of words in,
including none, and in any order:
hlist of descriptorsi hlist of nounsi
For instance ‘‘the balloon’’ has one descriptor and one noun; ‘‘red balloon’’ has just
two nouns; ‘‘all’’ has just one descriptor.
(7) Descriptors
There are five kinds of hdescriptori, as follows:
(a) An harticlei is a word indicating whether a particular object is being referred to, or
merely one of a range. Thus there are two kinds of article, definite and indefinite.
"English.h" has four articles: ‘‘the’’ is definite, while ‘‘a’’, ‘‘an’’ and ‘‘some’’ are
indefinite.
(b) An hall-wordi is a word which behaves like the English word ‘‘all’’, that is, which
refers to a whole range of objects. Informese, like some natural languages (such as
Tagalog), handles this as a kind of article but which pluralises what follows it.
(c) An hother-wordi is a word behaving like ‘‘other’’, which Informese understands
as ‘‘other than the one I am holding’’. Thus, if the player is holding a sword in a
room where there’s also a sword on the floor, then ‘‘examine other sword’’ would
refer to the one on the floor.
(d) A hdemanding numberi is a word like ‘‘nine’’ in ‘‘nine bronze coins’’, which
demands that a certain number of items are needed.
(e) A hpossessive adjectivei is a word indicating ownership by someone or something
whose meaning is held in a pronoun. Among others "English.h" has ‘‘my’’
(belonging to ‘‘me’’); French has ‘‘son’’ (belonging to ‘‘lui’’). Informese also
counts hdemonstrative adjectivesi like ‘‘this’’, ‘‘these’’, ‘‘that’’ and ‘‘those’’ as
possessives, though demonstratives are hardly ever used by players and may not
be worth providing in other languages. In Spanish, for instance, there would have
to be twelve, for ‘‘this’’, ‘‘that’’ (nearby) and ‘‘that’’ (far away), each in masculine,
feminine, singular and plural forms; and the structure of ‘‘celui-ci’’ and ‘‘celui-la’’
in French is too complex to be worth the effort of parsing.
(8) Nouns
There are three kinds of hnouni, as follows:
(a) A hnamei is a word matched against particular objects. Unless an object has a
parse_name routine attached, which complicates matters, these will be the words
in its name array. For instance:
Object -> "blue box" with name ’blue’ ’box’;
§
has two possible names in Informese, ‘‘blue’’ and ‘‘box’’.
(b) A hme-wordi is a word which behaves like the English word ‘‘me’’, that is, which
refers to the player-object. Most languages would think these are just examples of
relative pronouns, but Informese considers them to be in a category of their own.
Note that they refer to the player, whoever is addressed: in ‘‘mark, give me the
bomb’’, ‘‘me’’ refers to the speaker, not to Mark.
(c) A hpronouni is a word which stands in the place of nouns and can only be
understood with reference back to what has previously been said. ‘‘it’’, ‘‘him’’,
‘‘her’’ and ‘‘them’’ are all pronouns in "English.h". (Though ‘‘her’’ can also be
a possessive adjective, as in (7e) above.)
·
·
·
·
·
It is worth mentioning a number of grammatical features which are not contained in
Informese, along with some ways to simulate them.
adverbs such as ‘‘quickly’’ in ‘‘run quickly east’’. These are not difficult to implement:
Verb ’run’
* noun=ADirection -> Go
* ’quickly’ noun=ADirection -> GoQuickly
* noun=ADirection ’quickly’ -> GoQuickly;
However, ‘‘The authors of Zork have thought about several possible extensions to the
Zork parser. One that has come up many times is to add adverbs. A player should be
able to do the following:
>go north quietly
You sneak past a sleeping lion who sniffs but doesn’t wake up.
The problem is to think of reasons why you would not do everything ‘quietly’,
‘carefully’ or whatever.’’ (P. David Lebling, ‘‘Zork and the Future of Computerized
Fantasy Simulations’’, Byte, December 1980.) A further problem is the impracticality
of modelling the game world closely enough to differentiate between ways to achieve
the same action. In Melbourne House’s ‘The Hobbit’ adverbs influence the probability
of success in randomised events, so for instance ‘‘throw rope vigorously across river’’
is more likely to succeed than ‘‘throw rope across river’’, but those few players who
discovered this were not pleased. Twenty years on from ‘Zork’, adverbs remain largely
unused in the medium.
adjectives are not distinguished from nouns, although it can be useful to do so when
resolving ambiguities. See §28 for remedies.
genitives: objects are not normally named by description of their circumstances, so ‘‘the
box on the floor’’ and ‘‘the priest’s hat’’ would not normally be understood. Designers
can still define objects like
Object -> "priest’s hat"
with name ’hat’ ’priest^s’;
§
command
order
..
.
@
‘‘,’’
@
actor
action
@
@
noun phrase: GNA {0}
definite, quantity 1
verb phrase
nouns
.
..
‘‘conan’’
verb
..
.
‘‘put’’
multiexcept
grammar line
’in’/’inside’/’into’
noun phrase: GNA {11}
indefinite, quantity ∞
descriptors
@
@
@
@
preposition
..
.
‘‘into’’
nouns
...
‘‘sword’’
all-word
..
.
‘‘every’’
noun
noun phrase: GNA {8}
definite, quantity 1
descriptors
@
@
nouns
..
.
‘‘box’’
article
..
.
‘‘the’’
An example of parsing Informese. This diagram shows the result of parsing the text
‘‘conan, put every sword into the box’’, assuming that the verb ‘‘put’’ has a grammar
line reading
* multiexcept ’in’/’inside’/’into’ noun -> Insert
as indeed it does have in the "English.h" grammar.
§
in which the genitive ‘‘priest’s’’ is a noun like any other.
pronouns of other kinds, notably: nominative pronouns (‘‘I’’ in ‘‘I am happy’’); interrogative pronouns (‘‘What’’ in ‘‘What are you doing?’’), although these are often
simulated by making ‘‘what’’ an Informese verb; demonstrative pronouns (‘‘that’’ in
‘‘eat that’’), although in "English.h" the parser gets this right because they look the
same as demonstrative adjectives with no noun attached; possessive pronouns (‘‘mine’’
in ‘‘take the troll’s sword.give him mine’’, which should expand ‘‘mine’’ to ‘‘my X’’,
where X is the current value of ‘‘it’’).
pronominal adverbs are not found in English, but are common in other languages: for
instance ‘‘dessous’’ (French: ‘‘under it’’). See the exercises below for one way to
achieve pronominal adverbs and the next section for another.
§35
Case and parsing noun phrases
As this section and the next use a variety of linguistic terms, here
are some definitions. ‘‘Flexion’’ is the changing of a word according
to its situation, and there are several kinds:
inflection: a variable ending for a word, such as ‘‘a’’ becoming ‘‘an’’.
agreement: when the inflection of one word is changed to match another word which it
goes with. Thus ‘‘grand maison’’ but ‘‘grande dame’’ (French), where the inflection on
‘‘grand’’ agrees with the gender of the noun it is being applied to.
affix: part of a word which is attached either at the beginning (prefix), the end (suffix)
or somewhere in the middle (infix) of the ordinary word (the stem) to indicate, for
instance, person or gender of the objects attached to a verb. The affix often plays a
part that an entirely separate word would play in English. For instance, ‘‘donnez-lui’’
(French: ‘‘give to him’’), where the suffix is ‘‘-lui’’, or ‘‘cogela’’ (Spanish: ‘‘take it’’),
where the suffix is ‘‘la’’.
enclitic: an affix, usually a suffix, meaning ‘‘too’’ or ‘‘and’’ in English. For instance,
‘‘que’’ (Latin).
agglutinization: the practice of composing many affixes to a single word, so that it may
even become an entire phrase. For instance:
kirjoitettuasi after you had written (Finnish)
· · · · ·
In most languages, noun phrases have different cases according to their situation
in a sentence. In the English sentence ‘‘Emily ate one bath bun and gave
Beatrice the other’’, the noun phrase ‘‘Emily’’ is nominative, ‘‘one bath bun’’
and ‘‘the other’’ are accusative and ‘‘Beatrice’’ is dative. These last two are the
cases most often occurring in Inform commands, as in the example
leg den frosch auf ihn put the frog on him (German)
nimm den frosch von ihm take the frog from him
where the noun phrase ‘‘den frosch’’ is accusative both times, but ‘‘ihn’’ and
‘‘ihm’’ are the same word (‘‘him’’) in its accusative and dative forms. In some
languages a vocative case would also be needed for the name of someone being
addressed:
domine, fiat lux
Lord, let there be light (Latin)
§
since ‘‘domine’’ is the vocative form of ‘‘dominus’’. Latin also has genitive and
ablative cases, making six in all, but this pales by comparison with Finnish,
which has about thirty. In effect, a wide range of English prepositional phrases
like ‘‘into the water’’ are written as just the noun phrase ‘‘water’’ with a suffix
meaning ‘‘into’’.
·
·
·
·
·
To parse any of these languages, and even in some circumstances to parse
special effects in English-language games, it’s useful to have further control
over the way that the parser recognises noun phrases.
The words entered into an object’s name property normally take the
accusative case, the one most often needed in commands, as for example in
the grammar line:
Verb ’take’ * noun -> Take;
On the other hand, the nouns in the following grammar lines aren’t all
accusative:
Verb ’give’
* noun ’to’ noun -> Give
* noun noun
-> Give reverse;
This matches ‘‘give biscuit to jemima’’ and ‘‘give jemima biscuit’’, ‘‘biscuit’’
being accusative in both cases and ‘‘to jemima’’ and ‘‘jemima’’ both dative. In
a language where the spelling of a word can tell a dative from an accusative,
such as German, we could instead use grammar like this:
Verb ’give’
* noun dativenoun -> Give
* dativenoun noun -> Give reverse;
where dativenoun is some token meaning ‘‘like noun , but in the dative case
instead of the accusative’’. This could be used as the definition of a German
verb ‘‘gib’’, in which case both of the following would be parsed correctly:
gib die blumen dem maedchen
gib dem maedchen die blumen
give the flowers to the girl
give the girl the flowers
Unfortunately Inform doesn’t come with a token called dativenoun built in,
so you have to write one, using a general parsing routine (see §31). For the sake
§
of an example closer to English, suppose a puzzle in which the Anglo-Saxon
hero Beowulf will do as he is asked to, but only if addressed in Old English:
beowulf, gief gold to cyning
beowulf, give gold to king (Old English)
The grammar would be much like that for German, and indeed English:
Verb ’gief’ * noun dativenoun -> OEGive;
and here is a simple version of dativenoun :
[ dativenoun;
if (NextWord() == ’to’)
return ParseToken(ELEMENTARY_TT, NOUN_TOKEN);
return GPR_FAIL;
];
Read this as: ‘‘if the next word is ‘‘to’’, try and match a noun following it;
otherwise it isn’t a dative’’. A more likely form of the command is however
beowulf, gief gold cyninge
beowulf, give gold to king (Old English)
where ‘‘cyninge’’ is the dative form of ‘‘cyning’’. The ending ‘‘-e’’ often
indicates a dative in Old English, but there are irregularities, such as ‘‘searo’’
(device), whose dative is ‘‘searwe’’, not ‘‘searoe’’. In the unlikely event of
Beowulf confronting a vending machine:
beowulf, gief gold to searo beowulf, give gold to device (Old English)
beowulf, gief gold searwe beowulf, give gold to device
How to manage all this? Here is a laborious way:
Object -> "searo"
with name ’searo’, dativename ’searwe’;
Object -> "Cyning"
with name ’cyning’, dativename ’cyninge’;
[ dativenoun;
if (NextWord() ~= ’to’) {
wn--; parser_inflection = dativename;
}
return ParseToken(ELEMENTARY_TT, NOUN_TOKEN);
];
§
The variable parser_inflection tells the parser where to find the name(s) of
an object. It must always be equal to either a property or a routine. Most of the
time it’s equal to the property name, the accusative case as normal. If it equals
another property, such as dativename, then the parser looks in that property
for name-words instead of in name.
The above solution is laborious because it makes the game designer write
out dative forms of every name, even though they are very often the same but
with ‘‘-e’’ suffixed. It’s for this kind of contingency that parser_inflection
can be set to a routine name. Such an ‘‘inflection routine’’ is called with two
arguments: an object and a dictionary word. It returns true if the dictionary
word can mean the object and false if not. The word number wn is always
set to the number of the next word along, and it should not be moved. Two
library routines may be particularly helpful:
DictionaryLookup(text, length)
returns 0 if the word held as a -> array of characters
text->0, text->1, . . ., text->(length-1)
is not in the game’s dictionary, or its dictionary entry if it is.
WordInProperty(word, object, property)
to see if this is one of the words listed in object.property. It may also be
useful to know that the variable indef_mode is always set to true when parsing
something known to be indefinite (e.g., because an indefinite article or a word
like ‘‘all’’ has just been typed), and false otherwise.
• 4EXERCISE 107
Rewrite the dativenoun token so that ‘‘-e’’ is recognised as a regular suffix indicating
the dative, while still making provision for some nouns to have irregular dative forms.
• 4EXERCISE 108
Now add an (imaginary, not Old English) dative pronominal adverb ‘‘toit’’, which is to
be understood as ‘‘to it’’.
• 44EXERCISE 109
Define a token swedishnoun to make nouns and adjectives agree with the article
(definite or indefinite) applied to them, so for instance:
en brun hund a brown dog (Swedish)
den bruna hunden the brown dog
ett brunt hus a brown house
det bruna huset the brown house
§
44 The use of grammar tokens is only one way of dealing with flexion and pronominal
adverbs. The alternative is to alter the text typed until it resembles normal Informese:
gief gold cyninge 7→ gief gold to cyning
gief gold toit 7→ gief gold to it
den bruna hunden 7→ den brun hund
det bruna huset 7→ det brun hus
See §36 below. In a heavily inflected language with many irregularities, a combination
of the two techniques may be needed.
§36
Parsing non-English languages
It, hell. She had Those.
— Dorothy Parker (1893–1967), reviewing the romantic novel ‘It’
Before embarking on a new language definition file, the translator
may want to consider what compromises are worth making, omitting tricky but not really necessary features of the language. For
instance, in German, adjectives take forms agreeing with whether
their noun takes a definite or indefinite article:
ein großer Mann a tall man (German)
der große Mann the tall man
This is an essential. But German also has a ‘‘neutral’’ form for adjectives, used
in sentences like
Der Mann ist groß
The man is tall
Now it could be argued that if the parser asks the German equivalent of
Whom do you mean, the tall man or the short man?
then the player ought to be able to reply ‘‘groß’’. But this is probably not worth
the effort.
As another example from German, is it essential for the parser to recognise
commands put in the polite form when addressed to somebody other than the
player? For instance,
freddy, öffne den ofen Freddy, open the oven
herr krüger, öffnen sie den ofen Mr Krueger, open the oven
indicate that Freddy is a friend but Mr Krueger a mere acquaintance. A
translator might go to the trouble of implementing this, but equally might not
bother, and simply tell players always to use the familiar form. It’s harder to
avoid the issue of whether the computer is familiar to the player. Does the
player address the computer or the main character of the game? In English it
makes no difference, but there are languages where an imperative verb agrees
§ -
with the gender of the person being addressed. Is the computer male? Is it still
male if the game’s main character is female?
Another choice is whether to require the player to use letters other than
‘a’ to ‘z’ from the keyboard. French readers are used to seeing words written
in capital letters without accents, so that there is no need to make the player
type accents. In Finnish, though, ‘ä’ and ‘ö’ are significantly different from ‘a’
and ‘o’: ‘‘vaara’’ means ‘‘danger’’, but ‘‘väärä’’ means ‘‘wrong’’.
Finally, there are also dialect forms. The number 80 is ‘‘quatre-vingt’’ in
metropolitan French, ‘‘octante’’ in Québecois and ‘‘huitante’’ in Swiss French.
In such cases, the translator may want to write the language definition file to
cope with all possible dialects. For example, something like
#ifdef DIALECT_FRANCOPHONE; print "septante";
#ifnot; print "soixante-dix";
#endif;
would enable the same definition file to be used by Québecois authors and
members of the Académie française alike. The standard "English.h" definition already has such a constant: DIALECT_US, which uses American spellings,
so that if an Inform game defines
Constant DIALECT_US;
before including Parser, then (for example) the number 106 would be printed
in words as ‘‘one hundred six’’ instead of ‘‘one hundred and six’’.
4
An alternative is to allow the player to change dialect during play, and to encode
all spelling variations inside variable strings. Ralf Herrmann’s "German.h" does this to
allow the player to choose traditional, reformed or Swiss German conventions on the
use of ‘‘ß’’. The low string variables @30 and @31 each hold either ‘‘ss’’ or ‘‘ß’’ for use
in words like "schlie@30t" and "mu@31t".
·
·
·
·
·
Organisation of language definition files
A language definition file is itself written in Inform, and fairly readable Inform at that:
you may want to have a copy of "English.h" to refer to while reading the rest of this
section. This is divided into four parts:
I. Preliminaries
II. Vocabulary
III. Translating to Informese
IV. Printing
§ -
It is helpful for all language definitions to follow the order and layout style of "English.h". The example used throughout the rest of the section is of developing a
definition of "French.h".
(I.1) Version number and alphabet
The file should begin as follows:
! ===========================================================
!
Inform Library Definition File: French
!
!
(c) Angela M. Horns 1996
! ----------------------------------------------------------System_file;
! ----------------------------------------------------------!
Part I.
Preliminaries
! ----------------------------------------------------------Constant LanguageVersion
= "Traduction fran@ccais 961205 par Angela M. Horns";
("English.h" defines a constant called EnglishNaturalLanguage here, but this is just
to help the library keep old code working with the new parser: don’t define a similar
constant yourself.) Note the c-cedilla written using escape characters, @cc not c, which
is a precaution to make absolutely certain that the file will be legible on anyone’s system,
even one whose character set doesn’t have accented characters in the normal way.
The next ingredient of Part I is declaring the accented letters which need to be
‘‘cheap’’ in the following sense. Inside story files, dictionary words are stored to a
‘‘resolution’’ of nine Z-characters: that is, only the first nine Z-characters are looked at,
so that
‘‘chrysanthemum’’ is stored as ’chrysanth’
‘‘chrysanthemums’’ is stored as ’chrysanth’
(This is one of the reasons why Informese doesn’t make linguistic use of word-endings.)
Normally no problem, but unfortunately Z-characters are not the same as letters. The
letters ‘A’ to ‘Z’ are ‘‘cheap’’ and take only one Z-character each, but accented letters
like ‘é’ normally take out four Z-characters. If your translation is going to ask the player
to type accented letters at the keyboard (which even a French translation need not do:
see above), the resolution may be unacceptably low:
‘‘télécarte’’ is stored as ’t@’el’
‘‘téléphone’’ is stored as ’t@’el’
as there are not even enough of the nine Z-characters left to encode the second ‘é’,
let alone the ‘c’ or the ‘p’ which would distinguish the two words. Inform therefore
§ -
provides a mechanism to make up to about 10 common accents cheaper to use, in that
they then take only two Z-characters each, not four. In the case of French, we might
write:
Zcharacter
Zcharacter
Zcharacter
Zcharacter
Zcharacter
Zcharacter
’@’e’;
’@‘e’;
’@‘a’;
’@‘u’;
’@^a’;
’@^e’;
!
!
!
!
!
!
E-acute
E-grave
A-grave
U-grave
A-circumflex
E-circumflex
(Note that since the Z-machine automatically reduces anything the player types into
lower case, we need only include lower-case accented letters here. Note also that there
are plenty of other French accented letters (ı̈, û and so forth) but these are uncommon
enough not to matter here.) With this change made,
‘‘télécarte’’ is stored as ’t@’el@’ecar’
‘‘téléphone’’ is stored as ’t@’el@’epho’
enabling a phone card and a phone to be correctly distinguished by the parser.
44 In any story file, 78 of the characters in the ZSCII set are designated as ‘‘cheap’’
by being placed into what’s called the ‘‘alphabet table’’. One of these is mandatorily
new-line, another is mandatorily double-quote and a third cannot be used, leaving 75.
Zcharacter moves a ZSCII character into the alphabet table, throwing out a character
which hasn’t yet been used to make way. Alternatively, and provided no characters
have so far been used at all, you can write a Zcharacter directive which sets the entire
alphabet table. The form required is to give three strings containing 26, 26 and 23
ZSCII characters respectively. For instance:
Zcharacter "abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789!$&*():;.,<>@{386}";
Characters in alphabet 1, the top row, take only one Z-character to print; characters in
alphabets 2 and 3 take two Z-characters to print; characters not in the table take four.
Note that this assumes that Unicode $0386 (Greek capital Alpha with tonos accent, as
it happens) is present in ZSCII. Ordinarily it would not be, but the block of ZSCII
character codes between 155 and 251 is configurable and can in principle contain any
Unicode characters of your choice. By default, if Inform reads ISO 8859-n (switch
setting -Cn) then this block is set up to contain all the non-ASCII letter characters in
ISO 8859-n. In the most common case, -C1 for ISO Latin-1, the ligatures ‘œ’ and ‘Œ’
are then added, but this still leaves 28 character codes vacant.
Zcharacter table + ’@{9a}’;
§ -
adds Unicode character $009a, a copyright symbol, to ZSCII. Alternatively, you can
instruct Inform to throw away all non-standard ZSCII characters and replace them
with a fresh stock. The effect of:
Zcharacter table ’@{9a}’ ’@{386}’ ’@^a’;
is that ZSCII 155 will be a copyright symbol, 156 will be a Greek capital alpha with
tonos, 157 will be an a-circumflex and 158 to 251 will be undefined; and all other
accented letters will be unavailable. Such Zcharacter directives must be made before
the characters in question are first used in game text. You don’t need to know the
ZSCII values, anyway: you can always write @{9a} when you want a copyright symbol.
(I.2) Compass objects
All that is left in Part I is to declare standard compass directions. The corresponding
part of "English.h", given below, should be imitated as closely as possible:
Class CompassDirection
with article "the", number
has scenery;
Object Compass "compass" has concealed;
Ifndef WITHOUT_DIRECTIONS;
CompassDirection -> n_obj "north wall"
with name ’n//’ ’north’ ’wall’,
CompassDirection -> s_obj "south wall"
with name ’s//’ ’south’ ’wall’,
CompassDirection -> e_obj "east wall"
with name ’e//’ ’east’ ’wall’,
CompassDirection -> w_obj "west wall"
with name ’w//’ ’west’ ’wall’,
CompassDirection -> ne_obj "northeast wall"
with name ’ne’ ’northeast’ ’wall’,
CompassDirection -> nw_obj "northwest wall"
with name ’nw’ ’northwest’ ’wall’,
CompassDirection -> se_obj "southeast wall"
with name ’se’ ’southeast’ ’wall’,
CompassDirection -> sw_obj "southwest wall"
with name ’sw’ ’southwest’ ’wall’,
CompassDirection -> u_obj "ceiling"
with name ’u//’ ’up’ ’ceiling’,
CompassDirection -> d_obj "floor"
with name ’d//’ ’down’ ’floor’,
Endif;
CompassDirection -> out_obj "outside"
with
door_dir n_to;
door_dir s_to;
door_dir e_to;
door_dir w_to;
door_dir ne_to;
door_dir nw_to;
door_dir se_to;
door_dir sw_to;
door_dir u_to;
door_dir d_to;
door_dir out_to;
§ -
CompassDirection -> in_obj "inside"
with
door_dir in_to;
For example, "French.h" would contain:
Class CompassDirection
with article "le", number
has scenery;
Object Compass "compas" has concealed;
...
CompassDirection -> n_obj "mur nord"
with name ’n//’ ’nord’ ’mur’,
door_dir n_to;
(II.1) Informese vocabulary: the small categories
This is where small grammatical categories like hagain-wordi are defined. The following
constants must be defined:
AGAIN*__WD
UNDO*__WD
OOPS*__WD
THEN*__WD
AND*__WD
BUT*__WD
ALL*__WD
OTHER*__WD
ME*__WD
OF*__WD
YES*__WD
NO*__WD
words of type hagain-wordi
words of type hundo-wordi
words of type hoops-wordi
words of type hthen-wordi
words of type hand-wordi
words of type hbut-wordi
words of type hall-wordi
words of type hother-wordi
words of type hme-wordi
words of type hof-wordi
words of type hyes-wordi
words of type hno-wordi
In each case * runs from 1 to 3, except for ALL*__WD where it runs 1 to 5 and OF*__WD
where it runs 1 to 4. hof-wordsi have not been mentioned before: these are used in the
sense of ‘‘three of the boxes’’, when parsing a reference to a given number of things.
They are redundant in English because the player could have typed simply ‘‘three
boxes’’, but Inform provides them anyway.
In French, we might begin with:
Constant AGAIN1__WD
Constant AGAIN2__WD
Constant AGAIN3__WD
= ’encore’;
= ’c//’;
= ’encore’;
Here we can’t actually think of a third synonymous word for ‘‘again’’, but we must
define AGAIN3__WD all the same, and must not allow it to be zero. And so on, through
to:
Constant YES1__WD
Constant YES2__WD
Constant YES3__WD
= ’o//’;
= ’oui’;
= ’oui’;
§ -
hyes-wordsi and hno-wordsi are used to parse the answers to yes-or-no questions
(oui-ou-non questions in French, of course). It causes no difficulty that the word ‘‘o’’
is also an abbreviation for ‘‘ouest’’ because they are used in different contexts. On the
other hand, hoops-wordsi, hagain-wordsi and hundo-wordsi should be different from
any verb or compass direction name.
After the above, a few further words have to be defined as possible replies to the
question asked when a game ends. Here the French example might be:
Constant
Constant
Constant
Constant
Constant
Constant
Constant
AMUSING__WD
FULLSCORE1__WD
FULLSCORE2__WD
QUIT1__WD
QUIT2__WD
RESTART__WD
RESTORE__WD
=
=
=
=
=
=
=
’amusant’;
’grandscore’;
’grand’;
’a//’;
’arret’;
’reprand’;
’restitue’;
(II.2) Informese vocabulary: pronouns
Part II continues with a table of pronouns, best explained by example. The following
table defines the standard English accusative pronouns:
Array LanguagePronouns table
! word
possible GNAs:
!
a
i
!
s p s p
!
mfnmfnmfnmfn
’it’
$$001000111000
’him’
$$100000100000
’her’
$$010000010000
’them’
$$000111000111
connected to:
NULL
NULL
NULL
NULL;
The ‘‘connected to’’ column should always be created with NULL entries. The pattern of
1 and 0 in the middle column indicates which types of hnoun phrasei might be referred
to with the given hpronouni. This is really a concise way of expressing a set of possible
GNA values, saying for instance that ‘‘them’’ can match against noun phrases with any
GNA in the set {3, 4, 5, 9, 10, 11}.
The accusative and dative pronouns in English are identical: for instance ‘‘her’’
in ‘‘give her the flowers’’ is dative and in ‘‘kiss her’’ is accusative. French is richer in
pronoun forms:
donne-le-lui give it to him/her
mange avec lui eat with him
Here ‘‘-lui’’ and ‘‘lui’’ are grammatically quite different, with one implying masculinity
where the other doesn’t. The table needed is:
Array LanguagePronouns table
§ -
!
!
!
!
word
’-le’
’-la’
’-les’
’-lui’
’-leur’
’lui’
’elle’
’eux’
’elles’
possible GNAs:
a
i
s p s p
mfnmfnmfnmfn
$$100000100000
$$010000010000
$$000110000110
$$110000110000
$$000110000110
$$100000100000
$$010000010000
$$000100000100
$$000010000010
connected to:
NULL
NULL
NULL
NULL
NULL
NULL
NULL
NULL
NULL;
This table assumes that ‘‘-le’’ can be treated as a free-standing word in its own right,
not as part of the word ‘‘donne-le-lui’’, and section (III.1) below will describe how to
bring this about. Note that ‘‘-les’’ and ‘‘-leur’’ are treated as synonymous: Informese
doesn’t (ordinarily) care that dative and accusative are different.
Using the ‘‘pronouns’’ verb in any game will print out current values, which may
be useful when debugging the above table. Here is the same game position, inside the
building at the end of the road, in parallel English, German and Spanish text:
English: ‘Advent’
At the moment, ‘‘it’’ means the small bottle, ‘‘him’’ is unset, ‘‘her’’ is unset
and ‘‘them’’ is unset.
German: ‘Abenteuer’
Im Augenblick, ‘‘er’’ heisst der Schlüsselbund, ‘‘sie’’ heisst die Flasche, ‘‘es’’
heisst das Essen, ‘‘ihn’’ heisst der Schlüsselbund, ‘‘ihm’’ heisst das Essen
und ‘‘ihnen’’ ist nicht gesetzt.
Spanish: ‘Aventura’
En este momento, ‘‘-lo’’ significa el grupo de llaves, ‘‘-los’’ no está definido,
‘‘-la’’ significa la pequeña botella, ‘‘-las’’ significa las par de tuberı́ as de unos
15 cm de diámetro, ‘‘-le’’ significa la pequeña botella, ‘‘-les’’ significa las par
de tuberí as de unos 15 cm de diámetro, ‘‘él’’ significa el grupo de llaves,
‘‘ella’’ significa la pequeña botella, ‘‘ellos’’ no está definido y ‘‘ellas’’ significa
las par de tuberí as de unos 15 cm de diámetro.
(II.3) Informese vocabulary: descriptors
Part II continues with a table of descriptors, in a similar format.
Array LanguageDescriptors table
! word
possible GNAs
descriptor
!
to follow:
type:
!
a
i
!
s p s p
connected
to:
§ -
!
’my’
’this’
’these’
’that’
’those’
’his’
’her’
’their’
’its’
’the’
’a//’
’an’
’some’
mfnmfnmfnmfn
$$111111111111
$$111000111000
$$000111000111
$$111111111111
$$000111000111
$$111111111111
$$111111111111
$$111111111111
$$111111111111
$$111111111111
$$111000111000
$$111000111000
$$000111000111
POSSESS_PK
POSSESS_PK
POSSESS_PK
POSSESS_PK
POSSESS_PK
POSSESS_PK
POSSESS_PK
POSSESS_PK
POSSESS_PK
DEFART_PK
INDEFART_PK
INDEFART_PK
INDEFART_PK
0
0
0
1
1
’him’
’her’
’them’
’it’
NULL
NULL
NULL
NULL;
This gives three of the four types of hdescriptori. The constant POSSESS_PK signifies
a hpossessive adjectivei, connected either to 0, meaning the player-object, or to 1,
meaning anything other than the player-object (used for ‘‘that’’ and similar words) or
to the object referred to by the given hpronouni, which must be one of those in the
pronoun table. DEFART_PK signifies a definite harticlei and INDEFART_PK an indefinite
harticlei: these should both give the connected-to value of NULL in all cases.
The fourth kind allows extra descriptors to be added which force the objects that
follow to have, or not have, a given attribute. For example, the following three lines
would implement ‘‘lit’’, ‘‘lighted’’ and ‘‘unlit’’ as adjectives automatically understood
by the English parser:
’lit’
$$111111111111
’lighted’ $$111111111111
’unlit’
$$111111111111
light
light
(-light)
NULL
NULL
NULL
An attribute name means ‘‘must have this attribute’’, while the negation of it means
‘‘must not have this attribute’’.
To continue the example, "French.h" needs the following descriptors table:
Array LanguageDescriptors table
! word
possible GNAs
!
to follow:
!
a
i
!
s p s p
!
mfnmfnmfnmfn
’le’
$$100000100000
’la’
$$010000010000
’l^’
$$110000110000
’les’
$$000110000110
’un’
$$100000100000
descriptor
type:
connected
to:
DEFART_PK
DEFART_PK
DEFART_PK
DEFART_PK
INDEFART_PK
NULL
NULL
NULL
NULL
NULL
§ -
’une’
’des’
’mon’
’ma’
’mes’
’son’
’sa’
’ses’
’leur’
’leurs’
$$010000010000
$$000110000110
$$100000100000
$$010000010000
$$000110000110
$$100000100000
$$010000010000
$$000110000110
$$110000110000
$$000110000110
INDEFART_PK
INDEFART_PK
POSSESS_PK
POSSESS_PK
POSSESS_PK
POSSESS_PK
POSSESS_PK
POSSESS_PK
POSSESS_PK
POSSESS_PK
NULL
NULL
0
0
0
’-lui’
’-lui’
’-lui’
’-les’
’-les’;
(recall that in dictionary words, the apostrophe is written ^). Thus, ‘‘son oiseau’’ means
‘‘his bird’’ or ‘‘her bird’’, according to the current meaning of ‘‘-lui’’, i.e., according to
the gender of the most recent singular noun referred to.
The parser automatically tries both meanings if the same word occurs in both
pronoun and descriptor tables. This happens in English, where ‘‘her’’ can mean either
a feminine singular possessive adjective (‘‘take her purse’’) or a feminine singular object
pronoun (‘‘wake her up’’).
(II.4) Informese vocabulary: numbers
An array should be given of words having type hnumberi. These should include enough
to express the numbers 1 to 20, as in the example:
Array LanguageNumbers table
’un’ 1 ’une’ 1 ’deux’ 2 ’trois’ 3 ’quatre’ 4 ’cinq’ 5
’six’ 6 ’sept’ 7 ’huit’ 8 ’neuf’ 9 ’dix’ 10
’onze’ 11 ’douze’ 12 ’treize’ 13 ’quatorze’ 14 ’quinze’ 15
’seize’ 16 ’dix-sept’ 17 ’dix-huit’ 18 ’dix-neuf’ 19 ’vingt’ 20;
In some languages, such as Russian, there are numbers larger than 1 which inflect
with gender: please recognise all possibilities here. If the same word appears in both
numbers and descriptors tables, its meaning as a descriptor takes priority, which is
useful in French as it means that the genders of ‘‘un’’ and ‘‘une’’ are recognised after
all.
(III.1) Translating natural language to Informese
Part III holds the routine to convert what the player has typed into Informese. In
"English.h" this does nothing at all:
[ LanguageToInformese; ];
This might just do for Dogg, the imaginary language in which Tom Stoppard’s play
Dogg’s Hamlet is written, where the words are more or less English words rearranged.
(It begins with someone tapping a microphone and saying ‘‘Breakfast, breakfast. . . sun,
dock, trog. . .’’, and ‘‘Bicycles!’’ is an expletive.) Other languages are structurally unlike
§ -
English and the task of LanguageToInformese is to rearrange or rewrite commands
to make them look more like English ones. Here are some typical procedures for
LanguageToInformese to follow:
(1) Strip out optional accents. For instance, "German.h" looks through the command
replacing ü with ue and so forth, and replacing ß with ss. This saves having to
recognise both spelling forms.
(2) Break up words at hyphens and apostrophes, so that:
donne-lui l’oiseau give the bird to him 7→ donne -lui l’ oiseau
(3) Remove inflections which don’t carry useful information. For instance, most
German imperatives can take two forms, one with an ‘e’ on the end: ‘‘lege’’ means
‘‘leg’’ (German: ‘‘put’’) and ‘‘schaue’’ means ‘‘schau’’ (German: ‘‘look’’). It
would be helpful to remove this ‘‘e’’, if only to avoid stuffing game dictionaries
full of essentially duplicate entries. (Toni Arnold’s "German.h" goes further and
strips all inflections from nouns, while Ralf Herrmann’s preserves inflections for
parsing later on.)
(4) Break affixes away from the words they’re glued to. For instance:
della of the (Italian) 7→ di la
cogela take it (Spanish) 7→ coge la
so that the affix part ‘‘la’’ becomes a separate word and can be treated as a
pronoun.
(5) Replace parts of speech not existing in Informese, such as pronominal adverbs,
with a suitable alternative. For instance:
dessus on top of it (French) 7→ sur lui
dedans inside it 7→ dans lui
(6) Alter word order. For instance, break off an enclitic and move it between two
nouns; or if the definite article is written as a suffix, cut it free and move it before
the noun:
arma virumque arms and the man (Latin) 7→ arma et virum
kakane the cakes (Norwegian) 7→ ne kake
When the call to LanguageToInformese is made, the text that the player typed is held
in a -> array called buffer, and some useful information about it is held in another
array called parse. The contents of these arrays are described in detail in §2.5.
The translation process has to be done by shifting characters about and altering
them in buffer. Of course the moment anything in buffer is changed, the information
§ -
in parse becomes out of date. You can update parse with the assembly-language
statement
@tokenise buffer parse;
(And the parser does just this when the LanguageToInformese routine has finished.)
The most commonly made alterations come down to inserting characters, often spaces,
in between words, and deleting other characters by overwriting them with spaces.
Inserting characters means moving the rest of buffer along by one character, altering
the length buffer->1, and making sure that no overflow occurs. The library therefore
provides a utility routine
LTI_Insert(position, character)
to insert the given character at the given position in buffer.
• EXERCISE 110
Write a LanguageToInformese routine to insert spaces before each hyphen and after
each apostrophe, so that:
donne-lui l’oiseau give the bird to him 7→
donne -lui l’ oiseau
• EXERCISE 111
Make further translations for French pronominal adverbs:
dessus on top of it 7→ sur lui
dedans inside it 7→ dans lui
• 4EXERCISE 112
Write a routine called LTI_Shift(from,chars), which shifts the tail of the buffer
array by chars positions to the right (so that it moves leftwards if chars is negative),
where the ‘‘tail’’ is the part of the buffer starting at from and continuing to the end.
• 4EXERCISE 113
Write a LanguageToInformese routine which sorts out German pronominal adverbs,
which are made by adding ‘‘da’’ or ‘‘dar’’ to any preposition. Beware of breaking a
name like ‘‘Darren’’ which might have a meaning within the game, though, so that:
7
von es
davon →
7
auf es
darauf →
ren es
darren 7→
6
§37
Names and messages in non-English languages
The fourth and final part of the language definition file is taken up with
rules on printing out messages and object names in the new language.
The gender-number-animation (GNA) combination is considerably more
important when printing nouns than when parsing them, because the
player is less forgiving of errors. Few errors are as conspicuous or as painful as ‘‘You
can see a gloves here.’’, in which the library’s list-writer has evidently used the wrong
GNA for the gloves. Here is a more substantial example:
Volière
Un jungle superb des bêtes et des arbres.
On peut voir trois oiseaux (une oie, un moineau et un cygne blanc), cinq
boı̂tes, un huı̂tre, Edith Piaf et des raisins ici.
To print this, the list-writer needs to know that ‘‘oie’’ is feminine singular, ‘‘cygne
blanc’’ is masculine singular and so on. In short, it must be told the GNA of every
object name it ever prints, or it will append all the wrong articles.
The translator will need first to decide how the genders are to be used. Inform
allows for three genders, called male, female and neuter because they are usually used
for masculine, feminine and neuter genders. Different natural languages will use these
differently. In English, all nouns are neuter except for those of people (and sometimes
higher animals), when they follow the gender of the person. Latin, German and Dutch
use all three genders without any very logical pattern, while French, Spanish and Italian
have no neuter. In Norwegian even the number of genders is a matter of dialect:
traditional Norwegian has two genders, ‘‘common’’ and ‘‘neuter’’, but more recently
Norwegian has absorbed a new feminine gender from its rural dialects. One way to
achieve this in Inform would be to use male for common, female for the rural feminine
and neuter for neuter. To avoid confusion it might be worth making the definition
Attribute common alias male;
which makes common equivalent to writing male. (The keyword alias is used, though
very seldom, for making alternative names for attributes and properties.)
Here’s how the library determines the GNA of an object’s short name. The A part
is easy: all objects having the animate attribute are animate and all others are inanimate.
Similarly for the N part: objects having pluralname are plural, all others singular. (An
object having pluralname is nevertheless only one object: for example an object called
‘‘doors’’ which represents a pair of doubled doors, or ‘‘grapes’’ representing a bunch of
grapes.) If the object has male, female or neuter then the short name has masculine,
feminine or neuter gender accordingly. If it has none of these, then it defaults to the
gender LanguageAnimateGender if animate and LanguageInanimateGender otherwise.
(These are constants set by the language definition file: see (IV.1) below.) You can
§
-
find the GNA associated with an object’s short name by calling the library routine
GetGNAOfObject(obj);
which returns the GNA number, 0 to 11.
• EXERCISE 114
Devise a verb so that typing ‘‘gna frog’’ results in ‘‘frog: animate singular neuter (GNA
2) / The frog / the frog / a frog’’, thus testing all possible articled and unarticled forms
of the short name.
·
·
·
·
·
In some languages, though not English, short names are inflected to make them agree
with the kind of article applied to them:
das rote Buch the red book (German)
ein rotes Buch a red book
In printing as in parsing, the library variable indef_mode holds true if an indefinite
article attaches to the noun phrase and false otherwise. So one rather clumsy solution
would be:
Object Buch
with ...
short_name [;
if (indef_mode) print "rotes Buch"; else print "rote Buch";
rtrue;
];
In fact, though, the library automatically looks for a short_name_indef property when
printing short names in indefinite cases, and uses this instead of short_name. So:
Object Buch
with short_name "rote Buch", short_name_indef "rotes Buch";
An automatic system for regular inflections of short names is possible but not easy to
get right.
In languages other than English, short names also inflect with case, and the best
way to handle this may be to provide new printing rules like dative_the, enabling the
designer to write code like so:
"You give ", (the) noun, " to ", (dative_the) second, ".";
§
-
(IV.1) Default genders and contraction forms
Part IV of a language definition file opens with declarations of the default gender
constants mentioned above. "English.h" has
Constant LanguageAnimateGender
= male;
Constant LanguageInanimateGender = neuter;
whereas French would define both to be male.
Inform uses the term contraction form to mean a textual feature of a noun which
causes any article in front of it to inflect. English has two contraction forms, ‘‘starting
with a vowel’’ and ‘‘starting with a consonant’’, affecting the indefinite article:
a + orange = an orange
a + banana = a banana
The first constant to define is the number of contraction forms in the language. In the
case of "French.h" there will be two:
Constant LanguageContractionForms = 2;
Of these, form 0 means ‘‘starting with a consonant’’ and 1 means ‘‘starting with a vowel
or mute h’’. (It’s up to you how you number these.) You also have to provide the
routine that decides which contraction form a piece of text has. Here is an abbreviated
version for French, abbreviated in that it omits to check accented vowels like ‘é’:
[ LanguageContraction text;
if (text->0 == ’a’ or ’e’ or ’i’ or ’o’ or ’u’ or ’h’ or
’A’ or ’E’ or ’I’ or ’O’ or ’U’ or ’H’) return 1;
return 0;
];
The text array holds the full text of the noun, though this routine would normally only
look at the first few letters at most. The routine is only ever called when it is necessary
to do so: for instance, when the library prints ‘‘the eagles’’, LanguageContraction is
not called because the article would be the same regardless of whether ‘‘eagles’’ has
contraction form 0 or 1.
• EXERCISE 115
Italian has three contraction forms: starting with a vowel, starting with a ‘z’ or
else ‘s’-followed-by-a-consonant, and starting with a consonant. Write a suitable
LanguageContraction routine.
§
-
(IV.2) How to print: articles
English needs two sets of articles: one set for singular nouns, which we shall call article
set 0, another for plurals, article set 1. We need to define an array to show which GNAs
result in which article set:
!
!
!
Array LanguageGNAsToArticles -->
a
s
p
m f n m f n
0 0 0 1 1 1
i
s
p
m f n m f n
0 0 0 1 1 1;
(The number of article sets is not defined as a constant, but instead by the contents
of this array: here the only values are 0 and 1, so there need to be two article sets.)
We also need to define the article sets themselves. There are three articles for each
combination of contraction form and article set. For example, "English.h" has two
contraction forms and two article sets, so we supply twelve articles:
Array LanguageArticles -->
!
Contraction form 0:
!
Cdef
Def
Indef
"The " "the " "a "
"The " "the " "some "
Contraction form 1:
Cdef
Def
Indef
"The " "the " "an "
"The " "the " "some ";
! Set 0
! Set 1
That defines the automatic rules used to apply articles to nouns, but there are two
ways to override this: the property article, if present, specifies an explicit indefinite
article for an object; and the property articles, if present, specifies an explicit set of
three articles. This is useful for nouns whose articles are irregular, such as the French
‘‘haricot’’: the regular definite article would be ‘‘l’haricot’’, but by an accident of history
‘‘le haricot’’ is correct, so:
Object "haricot"
with articles "Le " "le " "un ", ...
• EXERCISE 116
Construct suitable arrays for the regular French articles.
• EXERCISE 117
Likewise for Italian, where Inform needs to be able to print a wider selection: un, un’,
una, uno, i, il, gli, l’, la, le, lo.
• EXERCISE 118
At the other extreme, what if (like Latin: ‘‘vir’’ man or a man or the man) a language
has no articles?
§
-
(IV.3) How to print: direction names
Next is a routine called LanguageDirection to print names for direction properties
(not direction objects). Imitate the following, from "French.h":
[ LanguageDirection d;
switch (d) {
n_to:
print "nord";
e_to:
print "est";
ne_to:
print "nordest";
se_to:
print "sudest";
u_to:
print "haut";
in_to:
print "dans";
default: RunTimeError(9,d);
}
];
s_to:
w_to:
nw_to:
sw_to:
d_to:
out_to:
print
print
print
print
print
print
"sud";
"ouest";
"nordouest";
"sudouest";
"bas";
"dehors";
(IV.4) How to print: numbers
Next is a routine called LanguageNumber which takes a number N and prints it out in
textual form. N can be anything from -32767 to 32767 and the correct text should be
printed in every case. In most languages a recursive approach makes this routine less
enormous than it might sound.
• EXERCISE 119
Write LanguageNumber for French.
(IV.5) How to print: the time of day
Even mostly numeric representations of the time of day vary from language to language:
when it’s 1:23 pm in England, it’s 13h23 in France. A routine called LanguageTimeOfDay should print out the language’s preferred form of the time of day, like so:
[ LanguageTimeOfDay hours mins;
print hours/10, hours%10, "h", mins/10, mins%10;
];
• EXERCISE 120
Write the corresponding English version.
(IV.6) How to print: verbs
The parser sometimes needs to print verbs out, in messages like:
I only understood you as far as wanting to take the red box.
It normally does this by simply printing out the verb’s dictionary entry. However,
dictionary entries tend to be cut short (to the first 9 letters or so) or else to be
§
-
abbreviations (rather as ‘‘i’’ means ‘‘inventory’’). In your language, verbs might also
need to inflect in a sentence like the one above, which assumes that the infinitive and
imperative are the same. You might get around that by rewording the statement as:
I only understood you as far as ‘‘take the red box’’.
Even so, how to print out verbs depends on the language, so you need to give a routine
called LanguageVerb which looks at its argument and either prints a textual form and
returns true, or returns false to let the library carry on as normal. In English, only a few
of the more commonly-used abbreviations are glossed, and ‘‘x’’ for ‘‘examine’’ is the
only one that really matters:
[ LanguageVerb verb_word;
switch (verb_word) {
’l//’: print "look";
’z//’: print "wait";
’x//’: print "examine";
’i//’, ’inv’, ’inventory’: print "inventory";
default: rfalse;
}
rtrue;
];
(IV.7) How to print: menus
Next, a batch of definitions should be made to specify the look of menus and which
keys on the keyboard navigate through them. Imitate the following "English.h"
definitions, if possible keeping the strings the same length (padding out with spaces if
your translations are shorter than the English original):
Constant
Constant
Constant
Constant
Constant
Constant
Constant
Constant
Constant
Constant
Constant
NKEY__TX
PKEY__TX
QKEY1__TX
QKEY2__TX
RKEY__TX
NKEY1__KY
NKEY2__KY
PKEY1__KY
PKEY2__KY
QKEY1__KY
QKEY2__KY
=
=
=
=
=
=
=
=
=
=
=
"N = next subject";
"P = previous";
" Q = resume game";
"Q = previous menu";
"RETURN = read subject";
’N’;
’n’;
’P’;
’p’;
’Q’;
’q’;
§
-
(IV.8) How to print: miscellaneous short messages
These are phrases or words so short that the author decided they probably weren’t
worth putting in the LibraryMessages system (he now thinks otherwise: code in haste,
repent at leisure). Here are some French versions with notes.
Constant SCORE__TX
Constant MOVES__TX
Constant TIME__TX
= "Score: ";
= "Tours: ";
= "Heure: ";
which define the text printed on the status line: in English, ‘‘Score’’ and ‘‘Turns’’ or
‘‘Time’’;
Constant CANTGO__TX
= "On ne peut pas aller en cet direction.";
the default ‘‘You can’t go that way’’ message;
Constant FORMER__TX
= "votre m@^eme ancien";
the short name of the player’s former self, after the player has become somebody else
by use of the ChangePlayer routine;
Constant YOURSELF__TX = "votre m@^eme";
the short name of the player object;
Constant DARKNESS__TX = "Obscurit@’e";
the short name of a location in darkness;
Constant NOTHING__TX
= "rien";
the short name of the nothing object, caused by print (name) 0;, which is not strictly
speaking legal anyway;
Constant THAT__TX
Constant THOSET__TX
= "@cca";
= "ces choses";
(THOSET stands for ‘‘those things’’) used in command printing. There are three
circumstances in which all or part of a command can be printed by the parser: for an
incomplete command, a vague command or an overlong one. Thus
>take out
What do you want to take out?
>give frog
(to Professor Moriarty)
§
-
>take frog within cage
I only understood you as far as wanting to take the frog.
In such messages, the THOSET__TX text is printed in place of a multiple object like ‘‘all’’
while THAT__TX is printed in place of a number or of something not well understood by
the parser, like the result of a topic token.
Constant OR__TX
= " ou ";
appears in the list of objects being printed in a question asking you which thing you
mean: if you can’t find anything grammatical to go here, try using just ", ";
Constant AND__TX
= " et ";
used to divide up many kinds of list;
Constant
Constant
Constant
Constant
WHOM__TX
WHICH__TX
IS2__TX
ARE2__TX
=
=
=
=
"qui ";
"lequel ";
"est ";
"sont ";
used only to print text like ‘‘inside which is a duck’’, ‘‘on top of whom are two drakes’’;
Constant IS__TX
Constant ARE__TX
= " est";
= " sont";
used only by the list-maker and only when the ISARE_BIT is set; the library only does
this from within LibraryMessages, so you can avoid the need altogether.
(IV.9) How to print: the Library Messages
Finally, Part IV contains an extensive block of translated library messages, making up
at least half the bulk of the language definition file. Here is the entry for a typical verb
in "English.h":
SwitchOn:
switch (n) {
1: print_ret (ctheyreorthats) x1,
" not something you can switch.";
2: print_ret (ctheyreorthats) x1,
" already on.";
3: "You switch ", (the) x1, " on.";
}
§
-
You have to translate every one of these messages to at least a near equivalent. It may
be useful to define new printing rules, just as "English.h" does:
[ CTheyreorThats obj;
if (obj == player) { print "You’re"; return; }
if (obj has pluralname) { print "They’re"; return; }
if (obj has animate)
{
if (obj has female) { print "She’s"; return; }
else if (obj hasnt neuter) { print "He’s"; return; }
}
print "That’s";
];
• EXERCISE 121
Write a printing rule called FrenchNominativePronoun which prints the right one out
of il, elle, ils, elles.
• REFERENCES
Andreas Hoppler’s alternative list-writing library extension "Lister.h" is partly designed to make it easier for inflected languages to print out lists.
Chapter VI: Using the Compiler
I was promised a horse, but what I got instead
was a tail, with a horse hung from it almost dead.
— Palladas of Alexandria (319?–400?),
translated by Tony Harrison (1937–)
§38
Controlling compilation from within
Inform has a number of directives for controlling which pieces of
source code are compiled: for instance, you can divide your source
code into several files, compiled together or separately, or you can
write source code in which different passages will be compiled on
different occasions. Most of these directives are seldom seen, but almost every
game uses:
Include "filename";
which instructs Inform to glue the whole of that source code file into the
program right here. It is exactly equivalent to removing the Include directive
and replacing it with the whole file "filename". (The rules for how Inform
interprets "filename" vary from machine to machine: for instance, it may
automatically add an extension such as ‘‘.inf’’ if your operating system
normally uses filename extensions and it may look in some special directory.
Run Inform with the -h1 switch for further information.) Note that you can
write
Include ">shortname";
to mean ‘‘the file called "shortname" which is in the same directory that the
present file came from’’. This is convenient if all the files making up the source
code of your game are housed together.
Next, there are a number of ‘‘conditional compilation’’ directives. They
take the general form of a condition:
§
Is hnamei defined as having some meaning?
Ifdef hnamei;
Ifndef hnamei;
Is hnamei undefined?
Iftrue hconditioni;
Is this hconditioni true?
Iffalse hconditioni; Is this hconditioni false?
followed by a chunk of Inform and then, optionally,
Ifnot;
and another chunk of Inform; and finally
Endif;
At this point it is perhaps worth mentioning that (most) directives can also
be interspersed with statements in routine declarations, provided they are
preceded by a # sign. For example:
[ MyRoutine;
#Iftrue MAX_SCORE > 1000;
print "My, what a long game we’re in for!^";
#Ifnot;
print "Let’s have a quick game, then.^";
#Endif;
PlayTheGame();
];
which actually only compiles one of the two print statements, according to
what the value of the constant MAX_SCORE is.
4
One kind of ‘‘if-defined’’ manoeuvre is so useful that it has an abbreviation:
Default hnamei hvaluei;
defines hnamei as a constant if it wasn’t already the name of something: so it’s equivalent
to
Ifndef hnamei; Constant hnamei = hvaluei; Endif;
Similarly, though far less often used, Stub ; defines a do-nothing
routine with this name and number (0 to 3) of local variables, if it isn’t already the
name of something: it is equivalent to
Ifndef hnamei; [ hnamei x1 x2 . . . xhnumberi; ]; Endif;
§
· · · · ·
Large standard chunks of Inform source code are often bundled up into
‘‘libraries’’ which can be added to any Inform story file using the Include
directive. Almost all Inform adventure games include three library files called
‘‘Parser’’, ‘‘VerbLib’’ and ‘‘Grammar’’, and several dozen smaller libraries
have also been written. Sometimes, though, what you want to do is ‘‘include
all of this library file except for the definition of SomeRoutine’’. You can do
this by declaring:
Replace SomeRoutine;
before the relevant library file is included. You still have to define your own
SomeRoutine, hence the term ‘‘replace’’.
44 How does Inform know to ignore the SomeRoutine definition in the library file,
but to accept yours as valid? The answer is that a library file is marked out as having
routines which can be replaced, by containing the directive
System_file;
All eight of the standard Inform library files (the three you normally Include in games,
plus the five others which they Include for you) begin with this directive. It also has
the effect of suppressing all compilation warnings (but not errors) arising from the file.
· · · · ·
One way to follow what is being compiled is to use the Message directive. This
makes the compiler print messages as it compiles:
Message
Message
Message
Message
"An informational message";
error "An error message";
fatalerror "A fatal error message";
warning "A warning message";
Errors, fatal errors and warnings are treated as if they had arisen from faults
in the source code in the normal way. See §40 for more about the kinds
of error Inform can produce, but for now, note that an error or fatal error
will prevent any story file from being produced, and that messages issued by
Message warning will be suppressed if they occur in a ‘‘system file’’ (one that
you have marked with a System_file directive). Informational messages are
simply printed:
Message "Geometry library by Boris J. Parallelopiped";
prints this text, followed by a new-line.
§
4
One reason to use this might be to ensure that a library file fails gracefully if it
needs to use a feature which was only introduced on a later version of the Inform
compiler than the one it finds itself running through. For example:
Ifndef VN_1610;
Message fatalerror
"The geometry extension needs Inform 6.1 or later";
Endif;
By special rule, the condition ‘‘VN_1610 is defined’’ is true if and only if the compiler’s
release number is 6.10 or more; similarly for the previous releases 6.01, first to include
message-sending, 6.02, 6.03, 6.04, 6.05, 6.10, which expanded numerous previous
limits on grammar, 6.11, 6.12, which allowed Inform to read from non-English
character sets, 6.13, 6.15, which allowed parametrised object creation, 6.20, which
introduced strict error checking, and finally (so far) 6.21, the first to feature Infix. A
full history can be found in the Technical Manual.
·
·
·
·
·
Inform also has the ability to link together separately-compiled pieces of
story file into the current compilation. This feature is provided primarily for
users with slowish machines who would sooner not waste time compiling the
standard Inform library routines over and over again. Linking isn’t something
you can do entirely freely, though, and if you have a fast machine you may
prefer not to bother with it: the time taken to compile a story file is now often
dominated by disc access times, so little or no time will be saved.
The pieces of pre-compiled story file are called ‘‘modules’’ and they
cannot be interpreted or used for anything other than linking.
The process is as follows. A game being compiled (called the ‘‘external’’
program) may Link one or more pre-compiled sections of code called ‘‘modules’’. Suppose the game Jekyll has a subsection called Hyde. Then these two
methods of making Jekyll are, very nearly, equivalent:
(1) Putting Include "Hyde"; in the source for "Jekyll", and compiling
"Jekyll".
(2) Compiling "Hyde" with the -M (‘‘module’’) switch set, then putting Link
"Hyde"; into the same point in the source for "Jekyll", and compiling
"Jekyll".
Option (2) is faster as long as "Hyde" does not change very often, since
its ready-compiled module can be left lying around while "Jekyll" is being
developed.
Because ‘‘linking the library’’ is by far the most common use of the linker,
this is made simple. All you have to do is compile your game with the -U
§
switch set, or, equivalently, to begin your source code with
Constant USE_MODULES;
This assumes that you already have pre-compiled copies of the two library
modules: if not, you’ll need to make them with
inform -M library/parserm.h
inform -M library/verblibm.h
where library/parserm.h should be replaced with whatever filename you
keep the library file ‘‘parserm’’ in, and likewise for ‘‘verblibm’’. This sounds
good, but here are four caveats:
(1) You can only do this for a game compiled as a Version 5 story file. This is
the version Inform normally compiles to, but some authors of very large
games need to use Version 8. Such authors usually have relatively fast
machines and don’t need the marginal speed gain from linking rather than
including.
(2) It’s essential not to make any Attribute or Property declarations before
the Include "Parser" line in the source code, though after that point is
fine. Inform will warn you if you get this wrong.
(3) Infix debugging, -X, is not compatible with linking, and strict error
checking -S does not apply within modules.
(4) The precompiled library modules always include the -D debugging verbs,
so when you come to compile the final release version of a game, you’ll
have to compile it the slow way, i.e., without linking the library.
44 If you intend to write your own pre-compilable library modules, or intend to
subdivide a large game into many modular parts, you will need to know what the
limitations are on linking. (In the last recourse you may want to look at the Technical
Manual.) Here’s a brief list:
(1) The module must make the same Property and Attribute directives as the main
program and in the same order. Including the library file "linklpa.h" (‘‘link
library properties and attributes’’) declares the library’s own stock, so it might be
sensible to do this first, and then include a similar file defining any extra common
properties and attributes you need.
(2) The module cannot contain grammar (i.e., use Verb or Extend directives) or
create ‘‘fake actions’’.
(3) The module can only use global variables defined outside the module if they are
explicitly declared before use using the Import directive. For example, writing
Import global frog; allows the rest of the module’s source code to refer to the
§
variable frog (which must be defined in the outside program). Note that the
Include file "linklv.h" (‘‘link library variables’’) imports all the library variables,
so it would be sensible to include this.
(4) An object in the module can’t inherit from a class defined outside the module.
(But an object outside can inherit from a class inside.)
(5) Certain constant values in the module must be known at module-compile-time
(and must not, for instance, be a symbol only defined outside the module). For
instance: the size of an array must be known now, not later; the number of
duplicate members of a Class; and the quantities being compared in an Iftrue
or Iffalse.
(6) The module can’t: define the Main routine; use the Stub or Default directives; or
define an object whose parent object is not also in the same module.
These restrictions are mild in practice. As an example, here is a short module to play
with:
Include "linklpa";
! Make use of the properties, attributes
Include "linklv";
! and variables from the Library
[ LitThings x;
objectloop (x has light)
print (The) x, " is currently giving off light.^";
];
It should be possible to compile this -M and then to Link it into another game, making
the routine LitThings exist in that game.
·
·
·
·
·
Every story file has a release number and a serial code. Games compiled
with the Inform library print these numbers in one line of the ‘‘banner’’. For
instance, a game compiled in December 1998 might have the banner line:
Release 1 / Serial number 981218 / Inform v6.20 Library 6/8
The release number is 1 unless otherwise specified with the directive
Release ;
This can be any Inform number, but convention is for the first published copy
of a game to be numbered 1, and releases 2, 3, 4, . . . to be amended re-releases.
The serial number is set automatically to the date of compilation in the
form ‘‘yymmdd’’, so that 981218 means ‘‘18th December 1998’’ and 000101
means ‘‘1st January 2000’’. You can fix the date differently by setting
Serial "dddddd";
where the text must be a string of 6 (decimal) digits. Inform’s standard example
games do this, so that the serial number will be the date of last modification of
the source code, regardless of when the story file is eventually compiled.
§
4
The Inform release number is written into the story file by Inform itself, and you
can’t change it. But you can make the story file print out this number with the special
statement inversion;.
§39
Controlling compilation from without
The Inform compiler has the equivalent of a dashboard of ‘‘command line switches’’, controlling its behaviour. Most of these have
only two settings, off or on, and most of them are off most of the
time. Others can be set to a number between 0 and 9. In this
book switches are written preceded by a minus sign, just as they would be
typed if you were using Inform from the command line of (say) Unix or RISC
OS. Setting -x, for instance, causes Inform to print a row of hash signs as it
compiles:
inform -x shell
RISC OS Inform 6.20 (10th December 1998)
::##############################################################
#################
One hash sign is printed for every 100 textual lines of source code compiled, so
this row represents about eight thousand lines. (During the above compilation,
carried out by an Acorn Risc PC 700, hashes were printed at a rate of about
thirty per second.) -x is provided not so much for information as to indicate
that a slow compilation is continuing normally. Contrariwise, the -s switch
offers more statistics than you could possibly have wanted, as in the following
monster compilation (of ‘Curses’):
RISC OS Inform 6.20 (10th December 1998)
In: 25 source code files
17052 syntactic lines
22098 textual lines
860147 characters (ISO 8859-1 Latin1)
Allocated:
1925 symbols (maximum 10000)
1022182 bytes of memory
Out:
Version 8 "Extended" story file 17.981218 (343.5K long):
37 classes (maximum 64)
579 objects (maximum 639)
169 global vars (maximum 233)
4856 variable/array space (m. 8000)
153 verbs (maximum 200)
1363 dictionary entries (m. 2000)
318 grammar lines (version 2)
490 grammar tokens (unlimited)
167 actions (maximum 200)
34 attributes (maximum 48)
51 common props (maximum 62)
153 individual props (unlimited)
267892 characters used in text
195144 bytes compressed (rate 0.728)
0 abbreviations (maximum 64)
891 routines (unlimited)
25074 instructions of Z-code
10371 sequence points
52752 bytes readable memory used (maximum 65536)
351408 bytes used in Z-machine
172880 bytes free in Z-machine
Completed in 8 seconds
§
The complete list of switches is listed when you run Inform with -h2, and
also in Table 3 at the back of this book, where there are notes on some of the
odder-looking ones.
When the statistics listing claims that, for instance, the maximum space for
arrays is 10,000, this is so because Inform has only allocated enough memory
to keep track of 10,000 entries while compiling. This in turn is because
Inform’s ‘‘memory setting’’ $MAX_STATIC_DATA was set to 10,000 when the
compilation took place.
Between them, the ‘‘memory settings’’ control how much of your computer’s memory Inform uses while running. Too little and it may not be able to
compile games of the size you need, but too much and it may choke any other
programs in the computer for space. It’s left up to you to adjust the memory
settings to suit your own environment, but most people leave them alone until
an error message advises a specific increase.
Finally, Inform has ‘‘path variables’’, which contain filenames for the files
Inform uses or creates. Usage of these varies widely from one operating system
to another. Run Inform with the -h1 switch for further information.
·
·
·
·
·
Inform’s switches, path variables and memory settings are set using ‘‘Inform
Command Language’’ or ICL. The usual way to issue ICL commands, at
least on installations of Inform which don’t have windowed front-ends, is to
squeeze them onto the command line. The standard command line syntax is
inform hICL commandsi hsource filei houtput filei
where only the hsource filei is mandatory. By default, the full names to give the
source and output files are derived in a way suitable for the machine Inform
is running on: on a PC, for instance, advent may be understood as asking
to compile advent.inf to advent.z5. This is called ‘‘filename translation’’.
No detailed information on filenaming rules is given here, because it varies so
much from machine to machine: see the -h1 on-line documentation. Note
that a filename can contain spaces if it is written in double-quotes and if
the operating system being used can cope with spaces (as Mac OS can, for
instance).
To specify sprawling or peculiar projects may need more ICL commands
than can fit comfortably onto the command line. One possible ICL command,
however, is to give a filename in (round) brackets: e.g.,
inform -x (skyfall_setup) ...
§
which sets the -x switch, then runs through the text file skyfall_setup
executing each line as an ICL command. This file then look like this:
! Setup file for "Skyfall"
-d
! Contract double spaces
$max_objects=1000
! 500 of them snowflakes
(usual_setup)
! include my favourite settings, too
+module_path=mods
! keep modules in the "mods" directory
ICL can include comments if placed after !, just as in Inform. Otherwise, an
ICL file has only one command per line, with no dividing semicolons. Using
ICL allows you to compile whole batches of files as required, altering switches,
path variables and memory settings as you go. Notice that ICL files can call up
other ICL files, as in the example above: don’t allow a file to call up another
copy of itself, or the compilation will all end in tears.
4
When typing such a command into a shell under a Unix-like operating systems,
you may need to quote the parentheses:
inform -x ’(skyfall_setup)’ ...
This instructs the shell to pass on the command literally to Inform, and not to react
to unusual characters like $, ?, ( or ) in it. The same may be needed for other ICL
commands such as:
inform -x ’$MAX_OBJECTS=2000’ ...
4
Windowed front-ends for Inform sometimes work by letting the user select various
options and then, when the ‘‘Go’’ button is pressed, convert the state of the dialogue
box into an ICL file which is passed to Inform.
44 If you need to use Inform without benefit of either a command line or a fancy
front-end, or if you want your source code to specify its own switch settings, you can
still set (most) switches by placing the directive
Switches hsome settingsi;
at the very beginning of your source code. (So that you can still override such settings,
the switch -i tells Inform to ignore all Switches directives.)
·
·
·
·
·
§
The ICL commands are as follows:
-hswitchesi
Set these switches; or unset any switch preceded by a tilde ~. (For example,
-a~bc sets a, unsets b and sets c.)
$list
List current memory settings.
$?hnamei
Ask for information on what this memory setting is for.
$small
Set the whole collection of memory settings to suitable levels for a small game.
$large
Ditto, for a slightly larger game.
$huge
Ditto, for a reasonably big one.
$hnamei=hquantityi
Alter the named memory setting to the given level.
+hnamei=hfilenamei
Set the named pathname variable to the given filename, which should be one
or more filenames of directories, separated by commas.
compile hfilenamei hfilenamei
Compile the first-named file, containing source code, writing the output
program to the (optional) second-named file.
(hfilenamei)
Execute this ICL file (files may call each other in this way).
·
·
·
·
·
It’s a nuisance to have to move all the memory settings up or down to cope with
a big game or a small computer, so $small, $large and $huge are provided as
short cuts. Typically these might allocate 350K, 500K or 610K respectively.
Running
inform $list
will list the various settings which can be changed, and their current values.
Thus one can compare small and large with:
inform $small $list
inform $large $list
§
If Inform runs out of allocation for something, it will generally print an error
message like:
"Game", line 1320: Fatal error: The memory setting MAX_OBJECTS
(which is 200 at present) has been exceeded. Try running Inform
again with $MAX_OBJECTS= on the command line.
and it would then be sensible to try
inform $MAX_OBJECTS=250 game
which tells Inform to try again, reserving more memory for objects this time.
ICL commands are followed from left to right, so
inform $small $MAX_ACTIONS=200 ...
will work, but
inform $MAX_ACTIONS=200 $small ...
will not because the $small changes MAX_ACTIONS back again. Changing some
settings has hardly any effect on memory usage, whereas others are expensive
to increase. To find out about, say, MAX_VERBS, run
inform $?MAX_VERBS
(note the question mark) which will print some very brief comments.
§40
Error messages
Five kinds of error are reported by Inform: a fatal error is a
breakdown severe enough to make Inform stop working at once; an
error allows Inform to continue for the time being, but will normally
cause Inform not to output the story file (because it is suspected
of being damaged); and a warning means that Inform suspects you may have
made a mistake, but will not take any action itself. The fourth kind is an ICL
error, where a mistake has been made in a file of ICL commands for Inform to
follow: an error on the command line is called a ‘‘command line error’’ but is
just another way to get an ICL error. And the fifth is a compiler error, which
appears if Inform’s internal cross-checking shows that it is malfunctioning.
The text reporting a compiler error asks the user to report it the author of
Inform.
Fatal errors
1. Too many errors
Too many errors: giving up
After 100 errors, Inform stops, in case it has been given the wrong source file
altogether, such as a program written in a different language.
·
·
·
·
·
2. Input/output problems
Most commonly, Inform has the wrong filename:
Couldn’t open source file hfilenamei
Couldn’t open output file hfilenamei
(and so on). More seriously the whole process of file input/output (or ‘‘I/O’’)
may go wrong for some reason to do with the host computer: for instance, if it
runs out of disc space. Such errors are rare and look like this:
I/O failure: couldn’t read from source file
I/O failure: couldn’t backtrack on story file for checksum
and so forth. The explanation might be that two tasks are vying for control
of the same file (e.g., two independent Inform compilations trying to write a
debugging information file with the same name), or that the file has somehow
been left ‘‘open’’ by earlier, aborted compilation. Normally you can only have
§
at most 256 files of source code in a single compilation. If this limit is passed,
Inform generates the error
Program contains too many source files: increase #define
MAX_SOURCE_FILES
This might happen if file A includes file B which includes file C which includes
file A which. . . and so on. Finally, if a non-existent pathname variable is set in
ICL, the error
No such path setting as hnamei
is generated.
·
·
·
·
·
3. Running out of things
If there is not enough memory even to get started, the following appear:
Run out of memory allocating hni bytes for hsomethingi
Run out of memory allocating array of hnixhmi bytes for hsomethingi
More often memory will run out in the course of compilation, like so:
The memory setting hsettingi (which is hvaluei at present) has
been exceeded. Try running Inform again with $hsettingi=hlarger-valuei
on the command line.
(For details of memory settings, see §39 above.) In a really colossal game, it is
just conceivable that you might hit
One of the memory blocks has exceeded 640K
which would need Inform to be recompiled to get around (but I do not expect
anyone ever to have this trouble, because other limits would be reached first).
Much more likely is the error
The story file/module exceeds version hni limit (hmiK) by hbi bytes
If you’re already using version 8, then the story file is full: you might be able
to squeeze more game in using the Abbreviate directive, but basically you’re
near to the maximum game size possible. Otherwise, the error suggests that
you might want to change the version from 5 to 8, and the game will be able
to grow at least twice as large again. It’s also possible to run out not of story
file space but of byte-accessible memory:
This program has overflowed the maximum readable-memory size of the
Z-machine format. See the memory map below: the start of the
area marked "above readable memory" must be brought down to
$10000 or less.
§
Inform then prints out a memory map so that you can see what contributed to
the exhaustion: there is detailed advice on this vexatious error in §45. Finally,
you can also exhaust the number of classes:
Inform’s maximum possible number of classes (whatever amount of memory
is allocated) has been reached. If this causes serious
inconvenience, please contact the author.
At time of writing, this maximum is 256 and nobody has yet contacted the
author.
Errors
In the following, anything in double-quotes is a quotation from your source
code; other strings are in single-quotes. The most common error by far takes
the form:
Expected h. . .i but found h. . .i
There are 112 such errors, most of them straightforward to sort out, but a few
take some practice. One of the trickiest things to diagnose is a loop statement
having been misspelt. For example, the lines
pritn "Hello";
While (x == y) print "x is still y^";
produce one error each:
line 1: Error: Expected assignment or statement but found pritn
line 2: Error: Expected ’;’ but found print
The first is fine. The second is odd: a human immediately sees that While is
meant to be a while loop, but Inform is not able to make textual guesses like
this. Instead Inform decides that the code intended was
While (x == y); print "x is still y^";
with While assumed to be the name of a function which hasn’t been declared
yet. Thus, Inform thinks the mistake is that the ; has been missed out.
In that example, Inform repaired the situation and was able to carry on as
normal in subsequent lines. But it sometimes happens that a whole cascade of
errors is thrown up, in code which the user is fairly sure must be nearly right.
What has happened is that one syntax mistake threw Inform off the right track,
so that it continued not to know where it was for many lines in a row. Look at
the first error message, fix that and then try again.
§
·
·
·
·
·
1. Reading in the source-code
Unrecognised combination in source: htexti
Alphabetic character expected after htexti
Name exceeds the maximum length of hnumberi characters: hnamei
Binary number expected after ’$$’
Hexadecimal number expected after ’$’
Too much text for one pair of quotations ’...’ to hold
Too much text for one pair of quotations "..." to hold
No text between quotation marks ’’
Expected ’p’ after ’//’ to give number of dictionary word hwordi
In that last error message, ‘‘number’’ is used in the linguistic sense, of singular
versus plural.
·
·
·
·
·
2. Characters
Illegal character found in source: hchari
No such accented character as htexti
’@{’ without matching ’}’
At most four hexadecimal digits allowed in ’@{...}’
’@{...}’ may only contain hexadecimal digits
’@..’ must have two decimal digits
Character can be printed but not input: hchari
Character can be printed but not used as a value: hchari
Alphabet string must give exactly 23 [or 26] characters
Character can’t be used in alphabets unless entered into Zcharacter
table: hchari
Character must first be entered into Zcharacter table: hchari
Character can only be used if declared in advance as part of
’Zcharacter table’: hchari
Character duplicated in alphabet: hchari
No more room in the Zcharacter table
Characters are given by ISO Latin-1 code number if in this set, and otherwise
by Unicode number, and are also printed if this is possible. For instance, if
you try to use an accented character as part of an identifier name, you cause
an error like so:
Error: Illegal character found in source: (ISO Latin1) $e9, i.e., ’e’
> Object cafe
§
because identifiers may only use the letters A to Z, in upper and lower case,
the digits 0 to 9 and the underscore character _. The same kind of error is
produced if you try to use (say) the ^ character outside of quotation marks.
The characters legal in unquoted Inform source code are:
hnew-linei hform-feedi hspacei htabi
0123456789
abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
()[]{}<>"’,.:;?!+-*/%=&|~#@_
·
·
·
·
·
3. Variables and arrays
Variable must be defined before use: hnamei
’=’ applied to undeclared variable
Local variable defined twice: hnamei
All 233 global variables already declared
No array size or initial values given
Array sizes must be known now, not externally defined
An array must have a positive number of entries
A ’string’ array can have at most 256 entries
An array must have between 1 and 32767 entries
Entries in byte arrays and strings must be known constants
Missing ’;’ to end the initial array values before "[" or "]"
The limit of 233 global variables is absolute: a program even approaching
this limit should probably be making more use of object properties to store its
information. ‘‘Entries. . . must be known constants’’ is a restriction on what
byte or string arrays may contain: basically, numbers or characters; defined
constants (such as object names) may only be used if they have already been
defined. This restriction does not apply to the more normally used word and
table arrays.
·
·
·
·
·
4. Routines and function calls
No ’Main’ routine has been defined
It is illegal to nest routines using ’#[’
A routine can have at most 15 local variables
Argument to system function missing
System function given with too many arguments
Only constants can be used as possible ’random’ results
§
A function may be called with at most 7 arguments
Duplicate definition of label: hnamei
The following name is reserved by Inform for its own use as a
routine name; you can use it as a routine name yourself
(to override the standard definition) but cannot use it for
anything else: hnamei
Note that the system function random, when it takes more than one argument,
can only take constant arguments, as this enables the possibilities to be stored
efficiently within the program. Thus random(random(10), location) will
produce an error. To make a random choice between non-constant values,
write a switch statement instead.
5. Expressions and arithmetic
·
·
·
·
·
Missing operator: inserting ’+’
Evaluating this has no effect: hoperatori
’=’ applied to hoperatori
Brackets mandatory to clarify order of: hoperatori
Missing operand for hoperatori
Missing operand after hsomethingi
Found ’(’ without matching ’)’
No expression between brackets ’(’ and ’)’
’or’ not between values to the right of a condition
’has’/’hasnt’ applied to illegal attribute number
Division of constant by zero
Signed arithmetic on compile-time constants overflowed the range
-32768 to +32767: hcalculationi
Label name used as value: hnamei
System function name used as value: hnamei
No such constant as hnamei
The obsolete ’#w$word’ construct has been removed
‘‘Operators’’ include not only addition +, multiplication * and so on, but also
higher-level things like --> (‘‘array entry’’) and . (‘‘property value’’) and ::
(‘‘superclass’’). An example of an operator where ‘‘Evaluating this has no
effect’’ is in the statement
34 * score;
where the multiplication is a waste of time, since nothing is done with the
result. ‘‘= applied to hoperatori’’ means something like
(4 / fish) = 7;
§
which literally means ‘‘set 4/fish to 7’’ and gives the error ‘‘= applied to /’’.
‘‘Brackets mandatory to clarify order’’ means that an expression is unclear
as written, and this is often a caution that it would be wrong either way and
needs to be reconsidered. For instance:
if (parent(axe) == location == Hall_of_Mists) ...
This looks as if it might mean ‘‘if these three values are all equal’’, but does
not: the result of == is either true or false, so whichever comparison happens
first, the other one compares against one of these values.
·
·
·
·
·
6. Miscellaneous errors in statements
’do’ without matching ’until’
’default’ without matching ’switch’
’else’ without matching ’if’
’until’ without matching ’do’
’break’ can only be used in a loop or ’switch’ block
At most 32 values can be given in a single ’switch’ case
Multiple ’default’ clauses defined in same ’switch’
’default’ must be the last ’switch’ case
’continue’ can only be used in a loop block
A reserved word was used as a print specification: hnamei
No lines of text given for ’box’ display
In Version 3 no status-line drawing routine can be given
The ’style’ statement cannot be used for Version 3 games
For instance, print (bold) X gives the ‘‘reserved word in print specification’’
error because bold is a keyword that has some meaning already (the statement
style bold; changes the type face to bold). Anyway, call such a printing
routine something else.
·
·
·
·
·
7. Object and class declarations
Two textual short names given for only one object
The syntax ’->’ is only used as an alternative to ’Nearby’
Use of ’->’ (or ’Nearby’) clashes with giving a parent
’->’ (or ’Nearby’) fails because there is no previous object
’-> -> ...’ fails because no previous object is deep enough
Two commas ’,’ in a row in object/class definition
Object/class definition finishes with ’,’
§
hnamei is a name already in use (with type htypei) and may not be
used as a property name too
Property given twice in the same declaration, because the names
’hnamei’ and ’hnamei’ actually refer to the same property
Property given twice in the same declaration: hnamei
Property should be declared in ’with’, not ’private’: hnamei
Limit (of 32 values) exceeded for property hnamei
An additive property has inherited so many values that the list has
overflowed the maximum 32 entries
Duplicate-number not known at compile time
The number of duplicates must be 1 to 10000
Note that ‘‘common properties’’ (those provided by the library, or those
declared with Property) cannot be made private. All other properties are
called ‘‘individual’’. The ‘‘number of duplicates’’ referred to is the number
of duplicate instances to make for a new class, and it needs to be a number
Inform can determine now, not later on in the source code (or in another
module altogether). The limit 10,000 is arbitrary and imposed to help prevent
accidents: in practice available memory is very unlikely to permit anything like
this many instances.
·
·
·
·
·
8. Grammar
Two different verb definitions refer to hnamei
There is no previous grammar for the verb hnamei
There is no action routine called hnamei
No such grammar token as htexti
’=’ is only legal here as ’noun=Routine’
Not an action routine: hnamei
This is a fake action, not a real one: hnamei
Too many lines of grammar for verb: increase #define MAX_LINES_PER_VERB
’/’ can only be used with Library 6/3 or later
’/’ can only be applied to prepositions
The ’topic’ token is only available if you are using Library
6/3 or later
’reverse’ actions can only be used with Library 6/3 or later
At present verbs are limited to 20 grammar lines each, though this would be
easy to increase. (A grammar of this kind of length can probably be written
more efficiently using general parsing routines, however.)
·
·
·
·
·
§
9. Conditional compilation
’Ifnot’ without matching ’If...’
’Endif’ without matching ’If...’
Second ’Ifnot’ for the same ’If...’ condition
End of file reached in code ’If...’d out
This condition can’t be determined
‘‘Condition can’t be determined’’ only arises for Iftrue and Iffalse, which
make numerical or logical tests: for instance,
Iftrue #strings_offset == $4a50;
can’t be determined because, although both quantities are constants, the value
of #strings_offset will not be known until compilation is finished. On the
other hand, for example,
Iftrue #version_number > 5;
can be determined at any time, as the version number was set before compilation.
·
·
·
·
·
10. Miscellaneous errors in directives
You can’t ’Replace’ a system function already used
Must specify 0 to 3 local variables for ’Stub’ routine
A ’Switches’ directive must come before the first constant definition
All 62 properties already declared
’alias’ incompatible with ’additive’
The serial number must be a 6-digit date in double-quotes
A definite value must be given as release number
A definite value must be given as version number
Grammar__Version must be given an explicit constant value
Once a fake action has been defined it is too late to change the
grammar version. (If you are using the library, move any
Fake_Action directives to a point after the inclusion of "Parser".)
The version number must be in the range 3 to 8
All 64 abbreviations already declared
All abbreviations must be declared together
It’s not worth abbreviating htexti
’Default’ cannot be used in -M (Module) mode
’LowString’ cannot be used in -M (Module) mode
§
·
·
·
·
·
11. Linking and importing
File isn’t a module: hnamei
Link: action name clash with hnamei
Link: program and module give differing values of hnamei
Link: module (wrongly) declared this a variable: hnamei
Link: this attribute is undeclared within module: hnamei
Link: this property is undeclared within module: hnamei
Link: this was referred to as a constant, but isn’t: hnamei
Link: htypei hnamei in both program and module
Link: hnamei has type htypei in program but type htypei in module
Link: failed because too many extra global variables needed
Link: module (wrongly) declared this a variable: hnamei
Link: this attribute is undeclared within module: hnamei
Link: this property is undeclared within module: hnamei
Link: this was referred to as a constant, but isn’t: hnamei
Link: module and game use different character sets
Link: module and game both define non-standard character sets, but they
disagree
Link: module compiled as Version hnumberi (so it can’t link into this
Vhnumberi game
’Import’ cannot import things of this type: hnamei
’Import’ can only be used in -M (Module) mode
Note that the errors beginning ‘‘Link:’’ are exactly those occurring during the
process of linking a module into the current compilation. They mostly arise
when the same name is used for one purpose in the current program, and a
different one in the module.
·
·
·
·
·
12. Assembly language
Opcode specification should have form "VAR:102"
Unknown flag: options are B (branch), S (store),
T (text), I (indirect addressing), F** (set this Flags 2 bit)
Only one ’->’ store destination can be given
Only one ’?’ branch destination can be given
No assembly instruction may have more than 8 operands
This opcode does not use indirect addressing
Indirect addressing can only be used on the first operand
Store destination (the last operand) is not a variable
Opcode unavailable in this Z-machine version: hnamei
Assembly mistake: syntax is hsyntaxi
§
Routine contains no such label as hnamei
For this operand type, opcode number must be in range hrangei
·
·
·
·
·
13. Deliberate ‘‘errors’’
Finally, error messages can also be produced from within the program (deliberately) using Message. It may be that a mysterious message is being caused
by an included file written by someone other than yourself.
Warnings
1. Questionable practices
htypei hnamei declared but not used
For example, a Global directive was used to create a variable, which was then
never used in the program.
’=’ used as condition: ’==’ intended?
Although a line like
if (x = 5) print "My name is Alan Partridge.";
is legal, it’s probably a mistake: x = 5 sets x to 5 and results in 5, so the
condition is always true. Presumably it was a mistype for x == 5 meaning ‘‘test
x to see if it’s equal to 5’’.
Unlike C, Inform uses ’:’ to divide parts of a ’for’ loop
specification: replacing ’;’ with ’:’
Programmers used to the C language will now and then habitually type a for
loop in the form
for (i=0; i<10; i++) ...
but Inform needs colons, not semicolons: however, as it can see what was
intended, it makes the correction automatically and issues only a warning.
§
Missing ’,’? Property data seems to contain the property name
The following, part of an object declaration, is legal but unlikely:
with found_in MarbleHall
short_name "conch shell", name "conch" "shell",
As written, the found_in property has a list of three values: MarbleHall,
short_name and "conch shell". short_name throws up the warning because
Inform suspects that a comma was missed out and the programmer intended
with found_in MarbleHall,
short_name "conch shell", name "conch" "shell",
This is not a declared Attribute: hnamei
Similarly, suppose that a game contains a pen. Then the following give
statement is dubious but legal:
give MarbleDoorway pen;
The warning is caused because it’s far more likely to be a misprint for
give MarbleDoorway open;
Without bracketing, the minus sign ’-’ is ambiguous
For example,
Array Doubtful --> 50 10 -20 56;
because Inform is not sure whether this contains three entries (the middle one
being 10 − 20 = −10), or four. It guesses four, but suggests brackets to clarify
the situation.
Entry in ’->’ or ’string’ array not in range 0 to 255
Byte -> and string arrays can only hold numbers in the range 0 to 255. If
a larger entry is supplied, only the remainder mod 256 is stored, and this
warning is issued.
§
This statement can never be reached
There is no way that the statement being compiled can ever be executed when
the game is played. Here is an obvious example:
return; print "Goodbye!";
where the print statement can never be reached, because a return must just
have happened. Beginners often run into this example:
"You pick up the gauntlet."; score = score + 5; return;
Here the score = score + 5 statement is never reached because the text,
given on its own, means ‘‘print this, then print a new-line, then return from
the current routine’’. The intended behaviour needs something like
score = score + 5; "You pick up the gauntlet.";
Verb disagrees with previous verbs: hverbi
The Extend only directive is used to cleave off a set of synonymous English
verbs and make them into a new Inform verb. For instance, ordinarily ‘‘take’’,
‘‘get’’, ‘‘carry’’ and ‘‘hold’’ are one single Inform verb, but this directive could
split off ‘‘carry’’ and ‘‘get’’ from the other two. The warning would arise if
one tried to split off ‘‘take’’ and ‘‘drop’’ together, which come from different
original Inform verbs. (It’s still conceivably usable, which is why it’s a warning,
not an error.)
This does not set the final game’s statusline
An attempt to choose, e.g., Statusline time within a module, having no
effect on the program into which the module will one day be linked. Futile.
Finally a ragbag of unlikely and fairly self-explanatory contingencies:
This version of Inform is unable to produce the grammar table format
requested (producing number 2 format instead)
Grammar line cut short: you can only have up to 6 tokens in any line
(unless you’re compiling with library 6/3 or later)
Version 3 limit of 4 values per property exceeded (use -v5 to get 32),
so truncating property hnamei
The ’box’ statement has no effect in a version 3 game
Module has a more advanced format than this release of the
Inform 6 compiler knows about: it may not link in correctly
§
The last of these messages is to allow for different module formats to be
introduced in future releases of Inform, but so far there has only ever been
module format 1, so nobody has ever produced this error.
·
·
·
·
·
2. Obsolete usages
more modern to use ’Array’, not ’Global’
use ’->’ instead of ’data’
use ’->’ instead of ’initial’
use ’->’ instead of ’initstr’
ignoring ’print_paddr’: use ’print (string)’ instead
ignoring ’print_addr’: use ’print (address)’ instead
ignoring ’print_char’: use ’print (char)’ instead
use ’word’ as a constant dictionary address
’#a$Act’ is now superceded by ’##Act’
’#n$word’ is now superceded by ’’word’’
’#r$Routine’ can now be written just ’Routine’
all properties are now automatically ’long’
use the ^ character for the apostrophe in hdictionary wordi
These all occur if Inform compiles a syntax which was correct under Inform 5
(or earlier) but has now been withdrawn in favour of something better.
44 No Inform library file (or any other file marked System_file) produces warning
messages. It may contain many declared but unused routines, or may contain obsolete
usages for the sake of backward compatibility.
Chapter VII: The Z-Machine
§41
Architecture and assembly
Infocom’s games of 1979–89 were written in a language called
ZIL, the Zork Implementation Language. At first sight this is
outlandishly unlike Inform, but appearances are deceptive. The
following source code describes toy boats in Kensington Park, from
a game widely considered a masterpiece: ‘Trinity’ (1986), by Brian Moriarty.
Source Exif Data:
File Type : PDF File Type Extension : pdf MIME Type : application/pdf PDF Version : 1.3 Linearized : No Page Count : 571 Creator : TeX output 2001.05.01:2248 Author : Graham Nelson Title : The Inform Designer's Manual Subject : A design system for interactive fiction Producer : dvipdfm 0.12.7b, Copyright © 1998, by Mark A. Wicks Create Date : 2001:05:01 22:50:58+00:00EXIF Metadata provided by EXIF.tools