+-----------------------------------------------------------------------------+ | Plain text of the Inform Designer's Manual | +-----------------------------------------------------------------------------+ This plain text version was generated automatically from the TeX source, and is an approximate version of the typeset version. In case of garbled passages, consult that edition! +-----------------------------------------------------------------------------+ | Introduction | +-----------------------------------------------------------------------------+ I will build myself a copper tower With four ways out and no way in But mine the glory, mine the power... - Louis MacNeice (1907-1963), Flight of the Heart Inform is an Adventure-game compiler, and this is the book to read about it. Infocom format 'story files' (Adventure games, that is) can be played on almost any computer, ancient or modern, and interpreters which run them are widely available, from personal organisers to mainframes. They represent probably the most portable form in which games can ever be written, as no alteration whatever is required to move a game from one model of computer to another. Inform is not just a compiler but an 'operating system' too: its library (a suite of standard game routines) allows designers to begin coding at once. An Inform source file need not contain any of the parser code, or the running of the 'game universe', only descriptions and exceptions to the usual rules. This world is quite rich already, having over 80 verbs and an extensive grammar: the library understands rooms, objects, duplicates, containers, doors, things on top of other things, light, scoring, switching things on and off, opening, closing and locking things, entering things, travelling about in them and so forth. The parser it uses (which can be entirely invisible to the designer, but is programmable and very flexible) is sophisticated enough to handle ambiguities, clarify its input by asking questions and to cope properly with plurals, vagueness, conversation, pronouns and the player becoming someone else in mid-game. This manual makes occasional reference to the example games provided with Inform, at present 'Advent' (a full version of the original mainframe Adventure, which contains a good deal of 'everyday Inform'), 'Toyshop' (a collection of unusual objects to play with) and 'Balances' (a short story consisting of puzzles making great use of the parser's flexibility). Try to get hold of these programs and preferably print them out. There is also a 'Shell' game consisting of the minimum code to get going, but all 14 lines of it are given in section 1 anyway. The text of this book has evolved from five earlier editions. The old manual was in places rather technical, with a makeshift and sometimes defensive tone ('Inform is an easel, not a painting'). There were specifications of the run-time code format and literary critiques of games gone by: like an oven manual padded out with both a cookery book and a detailed plan of the gas mains. This book contains just the instructions for the oven. So there are three 'companion volumes'. The Craft of Adventure is an essay on the design of adventure games; The Specification of the Z-Machine covers the run-time format and Inform assembly language, its lowest level; and The Inform Technical Manual documents chiefly internals, for compiler maintenance and porting. In trying to be both a tutorial and reference work in a budget of about 100 pages of A4, 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.) I have tried to make every passage tell the truth, so that even early sections are reliable for reference purposes. Passages which divert the main story, usually to tell the unexpurgated truth when this may just confuse the newcomer, are marked with a warning triangle $**$ or two, and set in smaller type. Lengthy examples are left as exercises, with answers in the Appendix; which also contains a language specification, some reference lists, definitions of attributes and properties, and a large index. The copyright on Inform, the program and its source code, its example games and documentation (including this book) is retained by Graham Nelson, who asserts the moral right to be identified as its author. Having said this, I am happy for it to be freely distributed to anybody who wants a copy, 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. However, a story file produced with the Inform compiler (and libraries) then belongs to its author, and may be sold for profit if desired, provided that its game banner contains the information that it was compiled by Inform, and the Inform version number. At present, the best source for Inform material (executables of the compiler for different machines, source code, the library files and example games) is the anonymous ftp site ftp.gmd.de, and its home directory is: if-archive/infocom/compilers/inform Some of the ideas of Inform came from an incremental multi-player game called Tera, on the Cambridge University mainframe, written by Dilip Sequeira and the author in 1990 (whose compiler was called Teraform); in turn, this stole a little from David Seal and Jonathan Thackray's game assembler; which dates back to the late 1970s and was written for 'Acheton', perhaps the first worthwhile game written outside America. Still, much of the Inform kernel derives ultimately from the IEEE Computer article ''Zork: A Computerized Fantasy Simulation Game'' by P. David Lebling, Marc S. Blank and Timothy A. Anderson; and more was suggested by Richard Tucker, among others. With the advent of Inform 5 in mid-1994, I feel that the underlying language came to a satisfactory state. The library files, however, may well go on being improved, since users can update these much more easily than the compiler (changes to which mean work for many people). The present library files (release 5/4) are greatly enhanced from the Inform 5.2 files, but compatible with them (except in handling plural nouns and that the obselete attribute autosearch has been withdrawn). The list of those who have helped the project along is legion: I should like to thank them all, porters, users and critics alike, but especially Volker Blasius, Paul David Doherty, Mark Howell, Bob Newell, Robert Pelak, Gareth Rees, Joe rund Rian, Dilip Sequeira, Richard Tucker and Christopher Wichura. One final word. I should like to dedicate this book, impertinently perhaps, to our illustrious predecessors: Willie Crowther, Don Woods and the authors of Infocom, Inc. Graham Nelson Magdalen College, Oxford September 1994 +-----------------------------------------------------------------------------+ | 1 Getting started | +-----------------------------------------------------------------------------+ The first thing to try is to compile the 'Hello Cruel World' game, a very short test file supplied with Inform. If that compiles and runs properly (producing a short page of text, then finishing), try the following: Constant Story "SHELL"; Constant Headline "^An Interactive Skeleton^\ Copyright (c) 1994 by (your name here).^"; Include "Parser"; Include "VerbLib"; Object Blank_Room "Blank Room" with description "An empty room." has light; [ Initialise; location=Blank_Room; "^^^^^Welcome to the shell...^^"; ]; Include "Grammar"; end; If this compiles, Inform is probably set up and working properly. It takes a short while to compile, because it 'includes' three large standard files, containing a large amount of code. (Include lines are also sometimes written #include to make C programmers feel more at home.) The library files are: Parser The core of the game, and a full parser VerbLib Routines for many game verbs, like ''take'' Grammar A grammar table to decode the player's input from Together, they make up the 'library'. They can certainly be modified by designers, but great effort has gone into making sure the need hardly ever arises. Apart from that, the code contains: (a) strings giving the name of the game, and a copyright message, to be printed out at the appropriate moments; (b) a routine, called Initialise, which is run when the game begins, and simply sets where the player starts (in the obvious place!) and prints a welcoming message; (c) an object, to be the only room of the game. The 'Shell' game is very boring: there is nothing for the player to do but wait and quit. In Inform, everything is an object: rooms and things to be picked up, scenery, intangible things like mist and even some abstract ideas (like the direction 'north'). More about this later: for now, here's a new object, to go under the Blank_Room definition: Nearby cone "green cone" with name "green" "cone"; (Nearby just means it's an object inside the last thing declared as an Object, in this case the Blank Room.) A green cone now appears in the Blank Room. The player can call it either ''green cone", ''cone" or even ''green". It can be taken, dropped, looked at, looked under and so on. This is still rather plain. Examining the cone sees ''nothing special about the green cone", for instance. So we might extend the definition by: Nearby cone "green cone" with name "green" "cone" "emerald", initial "Nearby is an emerald green cone, one foot high."; The initial message now appears when we arrive in the Empty Room. Taking things a little further... Nearby cone "green cone" with name "green" "cone" "emerald" "marzipan", initial "Nearby is an emerald green cone, one foot high.", description "The cone seems to be made of emerald-coloured \ marzipan." has edible; (Note that the description is split across two lines: the \ makes the message come out as one sentence without a huge space.) Now if we examine the cone, we get its surprising description: and the player is allowed to eat the cone. ** name, description and initial are examples of 'properties', while edible is an 'attribute': the difference is that the former have values, whereas the latter are just on or off. So far, we're just filling in a form, and we could go much further doing this, but that wouldn't be much of an example. Instead, some honest programming: Nearby cone "green cone" with name "green" "cone" "emerald" "marzipan", initial "Nearby is an emerald green cone, one foot high.", description "The cone seems to be made of emerald-coloured \ marzipan.", after [; Take: "Taken. (Your hands are smeared with marzipan.)"; Drop: "The cone drops to the floor and sags a little."; ], has edible; The property after doesn't just have a string for a value: it has a routine of its own. Now what happens is that when an action happens to the cone, the after routine is called to apply any special rules about the cone. In this case, Take and Drop are the only actions tampered with: and the only effect is that the usual messages (''Taken.'' ''Dropped.'') are replaced. ** There's no real difference between routines like Initialise, which are defined outside objects at the 'top level', and routines inside objects like this after routine: except that outside routines have to have names, since they aren't identified by belonging to any particular object. Still, the cone doesn't actually do anything! So here it is with a (completely unfair) puzzle added: Nearby cone "green cone" with name "green" "cone" "emerald" "marzipan", initial "Nearby is an emerald green cone, one foot high.", description "The cone seems to be made of emerald-coloured \ marzipan.", before [; Eat: if (random(100) <= 30) { deadflag = 1; "Unfortunately, you seem to be allergic to almonds."; } "You nibble at a corner of the cone."; ], after [; Take: "Taken. (Your hands are smeared with marzipan.)"; Drop: "The cone drops to the floor and sags a little."; ], has edible; The before routine is called before the player's intended action takes place. So when the player tries typing, say, ''eat the cone", what happens is: in 30% of cases, she dies of almond poisoning; and in the other 70%, she simply nibbles a corner of the cone (without actually consuming it completely). ** Like many programming languages, Inform braces together blocks of code. deadflag is a global variable, whose value does not belong to any particular object (or routine). It is defined somewhere in the depths of the library: it's usually 0; setting it to 1 causes the game to be lost, and setting it to 2 causes a win. In either case, the usual rule for the Eat action is never applied. This is because, although it isn't obvious from the code, the routine actually returns a value, true or false. And the command "Unfortunately, you seem to be allergic to almonds."; not only prints the message (together with a carriage return), but also returns true from the before routine. Since the routine normally returns false, the library knows that something has happened to interrupt the usual rules of the game. Exercise: Extend the green cone so that it is described as sagging after it has been dropped back on the ground. (You need a few more properties for this.) Answer: Nearby cone "green cone" with name "green" "cone" "emerald" "marzipan", describe [; if (cone has moved) "A misshapen cone of green marzipan sits here."; "Nearby is an emerald green cone, one foot high."; ], description "The cone seems to be made of emerald-coloured \ marzipan.", before [; Eat: if (random(100) <= 30) { deadflag = 1; "Unfortunately, you seem to be allergic to almonds."; } "You nibble at a corner of the cone."; ], after [; Take: "Taken. (Your hands are smeared with marzipan.)"; Drop: cone.description = "The cone is a vague green mess."; "The cone drops to the floor and sags a little."; ], has edible; The old initial message has gone. Instead, we have provided a describe routine. Whenever the game has to describe the cone in the description of a place, it will call this routine. The moved attribute is held only by an object which has at some time in the past been taken. So the cone is now perfect and untouched until taken and dropped, whereupon it becomes misshapen. Also, the act of dropping the cone now changes the description which appears when a player examines it. +-----------------------------------------------------------------------------+ | 2 Ingredients and lexicon | +-----------------------------------------------------------------------------+ Properly speaking, 'Inform' means the compiler and its language: whereas this book is chiefly about the 'library' of code which comes with it. For most practical purposes this makes no difference - but it's worth remembering that in the last resort you can change almost anything about how the library works. The basic ingredients of an Inform game are: Objects, Routines, Actions and Grammar. We have already seen examples of Objects and Routines. Actions happen in the course of play. The main loop of a game looks like this: 1. Ask the player to type something 2. Parse this (i.e. decide what it means) and generate any actions it calls for 3. Work out the consequences of these actions and tell the player 4. Worry about time passing by, and other things happening a process which ends only in victory or death. (In this respect it is unlike life.) Probably the most complicated programming in any adventure game goes into the parser. Inform's parser is fairly advanced, and is designed with the intention of being highly programmable at all levels to suit your game: so this manual will be returning to features of the parser again and again. It isn't perfect by any means, but it does understand things like: throw three of the coins into the fountain write funny on the lighted cube take the sword and all the crowns what is a grue dwarf, give me the battleaxe It also asks questions when it needs to, makes inferences from partial requests and tries to make good guesses when faced with ambiguous requests. You can teach it new verbs and new forms of old ones: this is Grammar. (The library starts with about 85 verbs, not counting synonyms.) Until you need to start programming it, the parser sits in the background, and you need to know nothing about it. But the parser is where Actions come from. An Action can have up to two objects attached to it, and represents something which the player is trying to do. For instance, Inv Take sword Insert gold_coin cloth_bag are all actions. (By this stage the game has reduced them to three numbers each, the action number and two object numbers: there's no text floating about.) ** Actions are also sometimes produced as a knock-on effect by other actions, and they can also be produced by your own code: you can even create new actions altogether, which are treated no differently from the standard ones. Exercise: Compile one of the supplied example games, say 'Toyshop', with the DEBUG option defined (see the section on debugging) so that you can use the special ''actions'' verb to see exactly what all the actions are as they happen. Answer: You may be surprised how many actions take place: often more than one per turn. ** Inform can produce two kinds of game: ''Standard'' and ''Advanced''. Code is in almost every case portable between these two forms, so that you can easily make a version each way from the same program. The latter is better, but just in case you have a small computer (such as a personal organiser) or a very bad run-time interpreter, you might prefer the former. Occasionally this manual will mention a difference between these forms. ** ** At this point, we should describe the imaginary machine, sometimes called the Z-machine (Z is for 'Zork') which Inform compiles games for. An 'interpreter' is a program which simulates this imaginary machine, so that it can run the game files that Inform produces: the same files will work on any interpreter on any machine. Interpreters are available for very many machines indeed. The Z-machine is, except in a few corners, a beautiful design and is described in great detail in the Specification of the Z-Machine. For now, a few words about the memory map. The memory of this imaginary computer is 128K long for Standard games and 256K long for advanced ones: the format is extremely well compressed, so this is actually quite a large memory. ** ** Numbers are stored in 16-bit words (2 bytes long), and are signed in the usual way, so they hold values $$ -32768 <= n <= 32767 $$ with the hexadecimal value $ffff being the same as $-1$. The sign doesn't mean anything for addresses, of course, but there's a catch: given that a 2-byte number can only hold a value $0 <= n <= 65535$, how can an address in a 128K or 256K memory map be held? The answer is that there are two kinds of address: a 'packed address' and a 'byte address'. A byte address is just an address in the bottom 64K of memory, and the Z-machine is arranged so that everything which could ever change is there: so these are the only addresses which matter very much. Packed addresses are addresses divided by 2 (or by 4 in Advanced games), so they can only refer to even (divisible by four) locations in memory. They hold the addresses of long strings and routines, which Inform is careful always to put at even (divisible by four) locations. +-----------------------------------------------------------------------------+ | 3 Objects, properties and attributes | +-----------------------------------------------------------------------------+ Objects make up the substance of the world. That is why they cannot be composite. - Ludwig Wittgenstein (1889-1951), Tractatus ...making philosophical sense of change runs up against what seem to be impossible philosophical difficulties. Aristotle... focuses on the central case of an object coming to have a property that it formerly lacked. - Julia Annas, Classical Greek Philosophy The objects of the game form what is sometimes called a 'tree', though a better analogy would be a forest, and anyway one usually draws the whole thing upside down and uses the language of 'families' - calling them 'children' and 'parents' of each other. Anyway, here's an example: Meadow | Mailbox -> Player | | Note Sceptre -> Cucumber -> Torch -> Magic Rod | Battery This is a family tree, then: each object has a parent, a sibling and a child, though that object may be the special nothing object. (The Cucumber has child nothing, for instance.) As the game goes on, objects move around: when an object moves, all its possessions (that is, children) go with it. The Inform command to move an object is move object to new-owner; and it must be emphasized that this prints nothing on the screen, and indeed does nothing except to change the tree above. Inform provides special functions for reading this tree. parent, sibling and child all do the obvious things, and in addition there's a function called children which counts up how many children an object has (only children: grandchildren aren't counted). For instance, parent ( Mailbox ) = Meadow children ( Player ) = 4 child ( Sceptre ) = 0 sibling ( Torch ) = Magic Rod ** nothing isn't really an object: it's just a convenient name for the number 0, which is the object number meaning 'no such object'. It isn't a good idea to meddle with nothing, or to apply functions like parent to it, but then there's never any need. ** ** When an object is added to the possessions held by another, it appears at the end of the list. Thus, the eldest child is first in the list and the youngest is last. Inform also provides functions youngest end of list of possessions eldest same as child younger same as sibling, i.e. rightwards in the picture elder reverse of sibling, i.e. leftwards in the picture Objects have more to them than just where they are in the tree. They also have collections of variables attached to them. Firstly, there are flags, called ''attributes'', which can be either set or clear. These might be such conditions as ''giving light", ''currently worn" or ''is one of the featureless white cubes". Attributes all have names, which are a single word: like light, for instance, which indicates that something is giving off light. There are about 30 defined by the library. They can be tested with a condition like if (obj has locked) "But it's locked!"; and can be set with the give command. So, for instance, give brass_lantern light; give iron_door locked; and they can be taken away with give brass_lantern ~light; give fake_coin ~scored; the ~ sign (or tilde) standing for negation. In fact you can give or take many at one go, so for instance give wooden_door open openable ~locked; ** You can make your own attributes with a directive like Attribute offered_to_ogre; at the start of the program. (A 'directive' is a command to Inform directly, and happens at compile-time: not something in a routine which is to happen in the course of play.) ** ** In Standard games there are few spare attributes available because th library takes most of them. To get around this limit there's a convenient dodge. It sometimes happens that an attribute is only meaningful for a particular kind of object: for instance, ''spell has been read" might only be meaningful for a ''scroll". With care, therefore, one may re-use the same attribute to have different meanings for different kinds of object. The syntax to declare that an attribute is being reused is Attribute alias ; Thereafter Inform will treat the new and old attribute names as referring to the same attribute: it's up to the programmer to make sure this does not lead to inconsistencies. (The library already indulges in a certain amount of this chicanery.) Secondly, there are ''properties''. These are far more elaborate, and not every object has every property. For instance: not every object has the door_to property (it holds the place a door leads to, so things other than doors don't usually have it). The current value of a property is got at by constructions like: iron_door.door_to crystal_bridge.door_to green_cone.before diamond.initial You can read the value of door_to for something like the diamond, and you'll just get a dull value like nothing, but you can't write to it: that is, you can't change diamond.door_to unless you declared a door_to property for it. ** You can also define your own properties (again, subject to availability, because the library claims many of them). Property door_to; Property article "a"; Property blorple_routine $ffff; are all examples of the Property directive. In the case of article, we are saying that the value "a" should be the default value for any object which doesn't declare an article. ** ** Properties can also alias. They can also be declared as long or additive, which is a complicated matter: basically, we'll come to additive when discussing classes, and properties which might start as small numbers (less than 256) and be changed into large ones in play, ought to be declared as long. As will be seen from examples, a property value can be many things: a string like "frog", a number such as $ffff (this is the Inform way of writing numbers in hexadecimal), an object or a routine. You can change the current value by something like location.door_to = hall_of_mists; brass_lantern.short_name = "dimly lit brass lantern"; grenade.time_left = 45; !! The game may crash at run-time if you attempt to write to a property field which an object hasn't got. So although you can read an undeclared property (you just get the default value), you can't write to one. (Also, you can't extend a property beyond its length: see below.) ** The Inform language does not have types as such, and strings and routines are stored as numbers: as their addresses inside the virtual machine, in fact. This means that Inform thinks "Hello there" + 45 + 'a' is a perfectly sensible calculation. It's up to you to be careful. ** ** A property can hold more than just one number (be it interpreted as a string, a routine or whatever): it can hold a small array of numbers. In Standard games it can have four numbers (8 bytes' worth), and in Advanced games 32 (64 bytes). For instance, an entry in an object definition might read found_in Marble_Hall Arched_Passage Stone_Stairs, storing a sequence of three values (all objects) in the found_in property. To read or write to this array, you need to know its (byte) address and its length. The operators object.&property and object.#property tell you these. (Be warned: object.#property tells you the number of bytes, not the number of words.) If you give a property more than 8 bytes of data in a Standard game, Inform warns you and takes only the first 8, but does not cause an error: this is so that, say, Object ... with name "radio" "wireless" "transistor" "portable" "stereo" "tranny", ... will compile either way (but the last two synonyms for "radio" will not enter the dictionary if it's being compiled in Standard form, since a name takes two bytes). ** ** Exercise: Use the object.&property construction to find out whether the object in variable obj has the door_to property defined or not. Answer: if (obj.&door_to == 0) { ... } Time to make some object definitions. A typical object definition looks something like: Object trapdoor "hinged trapdoor" attic with name "hinged" "trap" "door" "trapdoor", when_open "A hinged trapdoor in the floor stands open, and light \ streams in from below.", when_closed "There is a closed trapdoor in the middle of the floor.", door_to house, door_dir d_to, has door static open light openable; This is the conventional way to lay out an Object declaration: with the header first, then with a list of properties and their starting values, finishing up with the attributes it initially has. trapdoor is the name given to the object in the program, and it becomes a constant from then on (whose value is the number of the object). The attic is the object which the trapdoor starts out belonging to (as any player of 'Curses' will know). Some objects start out not belonging to anything (rooms, for example, or treasures which only appear half-way through). You can declare these as belonging to nothing, but it's better just to miss this out altogether: Object trapdoor "hinged trapdoor" with ... If you do declare an object already belonging to another, as above, then the other object must already have been defined. (This is no real restriction, and ensures that you can't set up a 'loop' - one in another in a third in the first, for instance.) Objects can also be declared, in an identical way, by the Nearby directive. The only difference is that no initial-owner object can be given; it starts out belonging to the last thing declared as an Object. For example, in Object hillside "Panoramic Hillside" with ... Nearby scenery "scenery" with ... the hillside is a room to which the scenery will belong. Otherwise, Nearby is the same as Object, and this is just a convenience to make it easier to move things around in Inform code by cutting definitions out and pasting them in elsewhere. Some properties of objects should be routines. For instance, an object can have a describe property which is a routine to print out a description of it. These could just be listed as names of routines, but usually the actual routine is written out then and there as the property value. For instance, in the classic Adventure object Nearby tasty_food "tasty food" with description "Sure looks yummy!", initial "There is tasty food here.", name "food" "ration" "rations" "tripe" "yummy" "tasty" "delicious" "scrumptious", after [; Eat: "Delicious!"; ], article "some" has edible; the after property does not name a routine but instead defines it. No name is needed or given for the routine. (The semicolon after the [ is needed to show that the routine has no local variables.) The routine must end with either ], or ];. If ], the object definition can resume where it left off, with further properties. (If it ends with ];, then the object definition ends where the routine finishes.) ** The rules for embedded routines are not quite the same as those for ordimary routines. By default, embedded routines return ''false", or 0 (instead of ''true", or 1, which other routines return by default). Also, the handy shorthand: Action [, Action2 ...] : ...some code... is provided, which executes the code only if the action being considered is the one named. ** ** It actually does this by setting a special variable called sw__var to the action number: when it compiles Action1: it just compiles an if statement which sees whether sw__var has value Action1. ** Properly speaking, the full syntax of the header is Object ... "short name" [] or Nearby ... "short name" or Class and the parent object must have already been defined. A Class definition is very like an object definition, and will be discussed later on. The syntax for an object, then, is
[,] class ... [,] with ... , ... , ... ... [,] has ... Although it's conventional to write class, with and has in this order, actually they can be in any order and any or all can be omitted altogether: and the commas in square brackets [,] are optional in between these fields. (The classes listed under class are those which the object inherits from.) ** One property is treated differently from the others, and this is the special property name. Its data must be a list of English words in double-quotes, as in all the above examples. (Probably the most annoying restriction of Standard games is that this means only 4 names at most can be accommodated in that format: you get up to 32 in Advanced games.) It will probably confuse the parser if any of these names is something like "my", "the", "all", "except" or "this". Numbers can safely be used, however. ** ** In fact, name contains a list of byte addresses of dictionary words. A quick way to print out the first word in the name list is therefore print_addr obj.name; ** ** The attributes and so on can be taken away as well as added, thus: ... has light ~scored; which is sometimes useful to over-ride an inheritance from a class definition. ** ** A final curiosity of the Object syntax is that objects can be given more than one name. This is a hangover from much earlier days of Inform, to do with re-using the same physical object in different logical ways, something which the author does not commend to anyone. +-----------------------------------------------------------------------------+ | 4 Places, scenery and the map | +-----------------------------------------------------------------------------+ It was a long cylinder of parchment, which he unrolled and spread out on the floor, putting a stone on one end and holding the other. I saw a drawing on it, but it made no sense. - John Christopher, The White Mountains And if no piece of chronicle we prove, We'll build in sonnets pretty rooms; As well a well wrought urn becomes The greatest ashes, as half-acre tombs. - John Donne (1571?-1631), The Canonization Back to our example. Throw away the old ''blank room" and replace it by... Object Square_Room "Square Room" with description "A broad, square room, ten yards on a side, floored \ with black and white chequered tiles." has light; (We also have to change the Initialise routine to make this the place where the player begins, since the Blank Room no longer exists.) Like the blank room, this one has light. (If it didn't, the player would never see it, since it would be dark, and the player hasn't yet been given a lamp or torch of some kind.) So where is the light coming from? Nearby chandelier "crystal chandelier" with name "crystal" "chandelier", initial "A crystal chandelier hangs from far above, casting \ light in little rainbows across the floor.", description "The crystal is beautiful cut-glass." has static; This is part of the fittings, hence the static attribute (which means it can't be taken or moved). But what about the rainbows? Nearby rainbows "rainbows" with name "rainbow" "rainbows", description "Caused by diffraction, or something like that - \ you were never very good at physics." has scenery; Being scenery makes the object not only static but also not described by the game unless actually examined by the player. A true perfectionist might alter it to: Nearby rainbows "rainbows" with name "rainbow" "rainbows", description "Caused by diffraction, or something like that - \ you were never very good at physics.", before [; Take, Push, Pull, Turn: "But the rainbows are made of light."; ], has scenery; Let us now add a second room: Object Corridor "Sloping Corridor" with description "This corridor slopes upward and out of the square room.", d_to Square_Room, s_to Square_Room, u_to "The slope becomes impossibly steep, and you retreat.", cant_go "The corridor runs up and down." has light; and extend the Square Room to: Object Square_Room "Square Room" with description "A broad, square room, ten yards on a side, floored \ with black and white chequered tiles. A doorway in the \ centre of the north side opens onto a rising corridor.", u_to Corridor, n_to Corridor has light; The player can now go from one to the other. The properties u_to, d_to, n_to (and so on) declare what lies in the directions ''up", ''down", ''north" (and so on). If they aren't declared, one cannot go that way. Notice that they can be either a room or a message which is printed if the player tries to go in the given direction. In the Square Room, if the player tries to go, say, east, she gets a message along the lines of ''You can't go that way.", which is not very helpful. In the Corridor, the cant_go message is printed instead. (In fact, as is often the case with properties, instead of giving an actual message you can instead give a routine to print one out, so that what is printed varies with circumstances.) Map connections are all one-way, in themselves: they just happen to be defined here so that they appear two-way. Rooms also have rules of their own. We might write: Object Corridor "Sloping Corridor" with description "This corridor slopes upward and out of the square room: \ the floor underfoot is a little sticky.", d_to Square_Room, s_to Square_Room, u_to "The slope becomes impossibly steep, and you retreat.", cant_go "The corridor runs up and down.", before [; Take: if (noun == cone) "The cone seems to be stuck to the floor here."; ], has light; and now the cone (if dropped there) cannot be taken from the floor of the Sloping Corridor. The variables noun and second hold the first and second nouns supplied with an action. Rooms have before and after routines just as objects do. ** ** 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==in_obj) print "How grateful you are to get out of the rain...^"; ], will print the message when the room is entered via the ''in" direction. (Note that the message is printed with the print command. This means that it does not automatically return true: in fact, it returns false, so the game knows that the usual rules still apply. Also, no new-line is printed automatically: but the ^ symbol means ''print a new-line", so one is actually printed.) ** Directions (such as ''north") are objects called n_obj, s_obj and so on: in this case, in_obj. (They are not to be confused with the property names n_to and so on.) Moreover, you can change these directions (as far as Inform is concerned, only things in the special object compass can be directions). ** Exercise: In the first millenium A.D., the Mayan peoples of the Yucat'an 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. Answer: Define four objects along the lines of: Object white_obj "white wall" compass with name "white" "sac" "wall", article "the", door_dir n_to has direction scenery; and add the following line to Initialise: remove n_obj; remove e_obj; remove w_obj; remove s_obj; (We could even alias a new property white_to to be n_to, and then enter map directions in the source code using Mayan property names.) As a fine point of style, turquoise ( yax) is the world colour for 'here', so add a grammar line to make this cause a ''look'': Verb "turquoise" "yax" * -> Look; ** Exercise: (Cf. 'Trinity'.) How can the entire game map be suddenly east-west reflected? Answer: [ SwapDirs o1 o2 x; x=o1.door_dir; o1.door_dir=o2.door_dir; o2.door_dir=x; ]; [ ReflectWorld; SwapDirs(e_obj,w_obj); SwapDirs(ne_obj,nw_obj); SwapDirs(se_obj,sw_obj); ]; ** ** Exercise: Even when the map is reflected, there may be many room descriptions referring to ''east'' and ''west'' by name. Reflect these too. Answer: This is a prime candidate for using variable strings @nn, a topic properly covered in the Inform Technical Manual, but briefly: at the head of the source, define Lowstring east_str "east"; Lowstring west_str "west"; and then add two more routines to the game, [ NormalWorld; String 0 #east_str; String 1 #west_str; ]; [ ReversedWorld; String 0 #west_str; String 1 #east_str; ]; where NormalWorld is called in Initialise or to go back to normal, and ReversedWorld when the reflection happens. Write @00 in place of east in any double-quoted printable string, and similarly @01 for west. It will be printed as whichever is currently set. (Inform provides up to 32 such variable strings.) +-----------------------------------------------------------------------------+ | 5 Causing actions and making new ones | +-----------------------------------------------------------------------------+ Only the actions of the just Smell sweet and blossom in their dust. - James Shirley (1594-1666), The Contention of Ajax and Ulysses ...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 One often wants to simulate the effect of a player typing something. For instance, in the Pepper Room the air is full of pepper and every turn the player sneezes and drops something at random. If the code to do this simply removes an object and puts it on the floor, then it might accidentally provide a solution to a problem like ''the toffee apple sticks to your hands so you can't drop it". This can at least be coded like this: You sneeze convulsively, and lose your grip on the toffee apple... The toffee apple sticks to your hand! which (though not ideal) is much better. As an example, here is another piece of scenery to clutter up the tiny map so far: Object low_mist "low mist" with name "low" "swirling" "mist", article "the", initial "A low mist swirls about your feet.", description "It carries the unmistakable odour of cinnamon.", found_in Square_Room Corridor, before [; Smell: <>; ], has static; This mist is found in both the Square Room and the Corridor: note that the found_in property has a list of places as its value. The player will find that smelling the mist produces the same message as looking at it. The command <>; causes the game to behave exactly as if the player had typed ''examine the mist" at the keyboard: that is, the Examine action happens, applied to the low_mist object. (self always means the object whose routine this is. In this case, it's really a bit pointless since <>; does just the same thing.) After going through the business of examining the mist, the routine then returns 'true', or 1 (in this case, so that the normal rules for smelling something are stopped in their tracks). If instead ; had been used, the same would have happened, but the routine would not have been returned from. So the mist would be examined; and then the routine for Smell would resume, and since there's nothing more to it, it would return false; whereupon the library's usual rules would make it say something like ''You smell nothing unusual.". All actions can be written this way: ; <>; will, for instance, look around, then behave as if the player had asked to throw the cone at the chandelier, then return true. ** Internally, actions like Look, ThrowAt and Take are stored as numbers, and the number associated with an action can be got at by x = ##Take; for instance. The variable action holds the current action number and sometimes it's convenient to use this directly. For instance, imagine a mirror hung very far above the player. Given: before [; if (action==##Examine) rfalse; "The mirror is too high up, out of reach."; ] the player will only be able to examine the mirror, and nothing else. This prevents the library from ever saying something like ''You push the mirror but nothing happens.", which would be misleading. ** An action corresponds to a routine somewhere which actually carries out the looking, throwing at, taking and so forth. (Well, in fact there are also some special actions called ''fake actions" which don't correspond to such routines, as we shall come to later.) These have the same name as the action with Sub (short for ''subroutine") on the end: so, TakeSub for instance. ** The actions implemented by the library are in three groups. The useful ones are in groups 2 and 3: 1. Quit, Restart, Restore, Verify, Save, ScriptOn, ScriptOff, Pronouns, Score, Fullscore, LMode1, LMode2, LMode3, NotifyOn, NotifyOff; 2. Inv, InvTall, InvWide, Take, Drop, Remove, PutOn, Insert, Transfer, Empty, Enter, Exit, GetOff, Go, GoIn, Look, Examine, Give, Show, Unlock, Lock, SwitchOn, SwitchOff, Open, Close, Disrobe, Wear, Eat; 3. Yes, No, Burn, Pray, Wake, WakeOther [person], Kiss, Think, Smell, Listen, Taste, Touch, TouchThing, Dig, Cut, Jump [jump on the spot], JumpOver, Tie, Drink, Fill, Sorry, Strong [swear word], Mild [swear word], Attack, Swim, Swing [something], Blow, Rub, Set, WaveHands [ie, just "wave"], Wave [something], Pull, Push, PushDir [push something in a direction], Turn, Squeeze, LookUnder [look underneath something], Search, ThrowAt, Answer, Buy, Ask, Sing, Climb, Wait, Sleep Of course, the player can type all manner of things to get these. For instance, ''take off shirt" and ''remove the shirt" both cause the Disrobe action. Your code can ignore this complication. ** ** Group 1 actions are called meta - they are outside the game proper, and your code is unable to interfere with them. (If you want a room where the game can't be saved, as for instance 'Spellbreaker' cunningly does, you'll have to tamper with SaveSub directly, using a Replaced routine.) Although all actions call the before routines, not all of them bother to check after routines. For instance, since the built-in SmellSub routine just says ''You smell nothing out of the ordinary", there would be no point calling after routines - nothing, after all, has been done. (These are the group 3 actions above.) ** The ones which actually do something, and call after, are: Inv, Take, Drop, Remove, PutOn, Insert, Exit, Go, Look, Examine, Unlock, Lock, SwitchOn, SwitchOff, Open, Close, Disrobe, Wear, Eat, Search. ** ** Some other group 2 actions use these after routines indirectly - if the player empties a sack out onto the floor, this is deemed to be a sequence of Drop actions, for instance, and if a sack is emptied into a packing case this is considered a multiple insertion. ** ** Search (the searching or looking-inside-something action) is a slightly special case between groups 2 and 3. It never actually does anything beyond printing messages. What happens is that if it would be sensible to look inside the object (i.e. if it's an open or transparent container and there is light), after is called beforehand. In this way you can use before to trap searching of random scenery, and after to alter rules for listing contents of containers. The library's actions are easily added to. For instance, add the routine: [ BlorpleSub; "You speak the magic word ~Blorple~. Nothing happens."; ]; (somewhere after the Initialise routine, say, to be tidy). There is now an action Blorple (though it doesn't do anything very interesting). One can use the command ; to make it happen, and could change the before routine of, say, the Corridor, to make Blorple do something exciting in that one place. In other words, Blorple is now an action just like any other. But the player can't yet type ''blorple" and get this response, because although the action exists, it hasn't been written into the grammar of the game. The grammar is a large table (mostly written out in the Grammar library file). It can easily be added to, and in this case we simply add the lines Verb "blorple" * -> Blorple; immediately after the inclusion of the Grammar file. (The spacing is just a matter of convention.) This is about as simple as grammar lines come, and means that only the word ''blorple" can be used as a verb, and it can't apply to any noun or nouns. (Far more about grammar later.) ** It is time to be more precise about the exact sequence of events. Suppose the player is in the Bedquilt Room, and types ''drop oyster". Once it has checked that this is a reasonable command, the normal rules can be interrupted at eight different stages: 1. Call GamePreRoutine (if there is one). If this returns true, stop here. 2. Call the before of the player. If this returns true, stop here. 3. Call the before of Bedquilt Room. If this returns true, stop here. 4. Then the before of the oyster. If this returns true, stop here. 5. Actually drop the object. 6. Call the after of the player. If this returns true, stop here. 7. Call the after of Bedquilt Room. If this returns true, stop here. 8. Then the after of the oyster. If this returns true, stop here. 9. Call GamePostRoutine (if there is one). If this returns true, stop here. 10. Print ''Dropped.'' ** ** GamePreRoutine and GamePostRoutine are examples of 'entry points', like Initialise: routines you can provide to make global rule changes. Their use is a drastic measure to be avoided if possible. The player's own before and after will be discussed in section 11. ** ** ''Fake actions'' are just like real actions, except that they don't occur in any game grammar, so they're never generated by the parser. They're a neat way to pass 'messages' from one object to another one. Because they aren't defined implicitly in the grammar, they have to be explicitly declared before use, by the directive: Fake_Action ** ** Exercise: How can you make a medicine bottle, which can be opened in a variety of ways in the game, so that the opening-code only occurs in the bottle definition? Answer: Declare a fake action called, say, OpenUp. Then: Object medicine "guaranteed child-proof medicine bottle" cupboard with name "medicine" "bottle", description "~Antidote only: no preventative effect.~", before [; Open, Unlock: "It's adult-proof too."; Openup: give self open ~locked; "The bottle cracks open!"; ], has container openable locked; Any other code in the game can execute to crack open the bottle. +-----------------------------------------------------------------------------+ | 6 Containers, supporters and sub-objects | +-----------------------------------------------------------------------------+ The concept of a surface is implemented as a special kind of containment. Objects which have surfaces on which other objects may sit are actually containers with an additional property of ''surfaceness". - P. David Lebling, Zork and the Future Objects can be inside or on top of one another. An object which has the container attribute can contain things, like a box: one which has supporter can hold them up, like a table. (An object can't have both at once.) It can hold up to 100 items, by default: this is set by the capacity property. However, one can only put things inside a container when it has open. If it has openable, the player can open and close it at will. (Unless it also has locked.) To complicate matters, some containers are transparent (so that the player can see inside them even when they are closed) and some are not. Exercise: Make a glass box and a steel box, which behave differently when the lamp is shut up inside them. Answer: Nearby glass_box "glass box with a lid" with name "glass" "box" "with" "lid" has container transparent openable open; Nearby steel_box "steel box with a lid" with name "steel" "box" "with" "lid" has container openable open; Containers (and supporters) are able to react to things being put inside them, or removed from them, by acting on the Receive and LetGo actions. Exercise: Make the following, rather acquisitive bag: >put fish in bag The bag wriggles hideously as it swallows the fish. >get fish The bag defiantly bites itself shut on your hand until you desist. Answer: Object bag "toothed bag" room with name "toothed" "bag", description "A capacious bag with a toothed mouth.", before [; LetGo: "The bag defiantly bites itself \ shut on your hand until you desist."; Close: "The bag resists all attempts to close it."; ], after [; Receive: print "The bag wriggles hideously as it swallows "; DefArt(inp1); "."; ], has container open; ** LetGo and Receive are actually two of the fake actions: they are the actions Insert and Remove looked at from the container's point of view. Objects which have locked cannot be opened, be they doors or containers (or both). But objects which have lockable can be locked or unlocked with the appropriate key, which is declared in the with_key property. (If it is undeclared, then no key will fit.) As a final example of a container, this is a fairly typical locked cupboard: Nearby cupboard "bolted cupboard" with name "bolted" "cupboard", describe [; if (self hasnt open) "^A shut cupboard is bolted to one wall."; "^Bolted up on one wall is an open cupboard."; ], with_key key has locked container openable lockable static; Now suppose you want to make a portable television set which has four different buttons on it. Obviously when the television moves, its buttons should move with it, and the sensible way to arrange this is to make the four buttons possessions of the television object. However, the television isn't a container, and the player can't normally ''see'' (that is, refer to) the possessions of an object. So how do we bring the buttons into the player's ''view'' without making them removable, or allowing anyone to put extra things ''into'' the television? This is what the transparent attribute is for: it is an extremely useful device to allow the player to ''see'' (i.e. talk about) the contents of an object. Exercise: Implement a television set with attached power button and screen. Answer: Object television "portable television set" lounge with name "tv" "television" "set" "portable", before [; SwitchOn: <>; SwitchOff: <>; Examine: <>; ], has transparent; Nearby power_button "power button" with name "power" "button" "switch", after [; SwitchOn, SwitchOff: <>; ], has switchable; Nearby screen "television screen" with name "screen", before [; Examine: if (power_button hasnt on) "The screen is black."; "The screen writhes with a strange Japanese cartoon."; ]; +-----------------------------------------------------------------------------+ | 7 Doors | +-----------------------------------------------------------------------------+ Standing in front of you to the north, however, is a door surpassing anything you could have imagined. For starters, its massive lock is wrapped in a dozen six-inch thick iron chains. In addition, a certain five-headed monster... - Marc Blank and P. David Lebling, 'Enchanter' O for doors to be open and an invite with gilded edges To dine with Lord Lobcock and Count Asthma. - W. H. Auden (1907-1973), Song A useful kind of object is a door. This need not literally be a door: it might be a rope-bridge or a ladder, for instance. To set up a door: (a) give the object the door attribute; (b) set the door_to property to the destination; (c) set the door_dir property to the direction which that would be, such as n_to; (d) make the room's map connection in that direction point to the door itself. For example, here is a closed and locked door: Object In_Immense_N_S_Passage "Immense N/S Passage" with description "One end of an immense north/south passage.", s_to In_Giant_Room, n_to RustyDoor; Nearby RustyDoor "rusty door" with description "It's just a big iron door.", name "door" "hinge" "hinges" "massive" "rusty" "iron", when_closed "The way north is barred by a massive, rusty, iron door.", when_open "The way north leads through a massive, rusty, iron door.", door_to In_Cavern_With_Waterfall, door_dir n_to, with_key set_of_keys has static door openable lockable locked; (Note that the door is static - otherwise the player could pick it up and walk away with it!) The properties when_closed and when_open give descriptions appropriate for the door in these two states. Doors are rather one-way: they are only really present on one side. If a door needs to be accessible (openable and lockable from either side), a neat trick is to make it present in both locations and to fix the door_to and door_dir to the right way round for whichever side the player is on. Here, then, is a two-way door: Object Grate "steel grate" with name "grate" "lock" "gate" "grille" "metal" "strong" "steel" "grating", description "It just looks like an ordinary grate \ mounted in concrete.", with_key set_of_keys, door_dir [; if (location==Below_The_Grate) return u_to; return d_to; ], door_to [; if (location==Below_The_Grate) return Outside_Grate; return Below_The_Grate; ], describe [; if (self has open) "^The grate stands open."; if (self hasnt locked) "^The grate is unlocked but shut."; rtrue; ], found_in Below_The_Grate Outside_Grate has static door openable lockable locked; where Below_The_Grate has u_to set to Grate, and Outside_Grate has d_to set to Grate. The grate can now be opened, closed, entered and locked from either above or below. ** At first sight, it isn't obvious why doors have the door_dir property. Why does a door need to know which way it faces? The idea is that if there's an open door in the south wall, a player can go through it either by typing ''south'' or ''enter door". So what the Enter action does (provided the door is actually open) is to cause the Go action with the given direction. ** This has one practical consequence: if you put before and after routines on the Enter action for the Grate, they only apply to a player typing ''enter grate" and not to one just typing ''down". The way to trap both at once is to write a routine for the d_to property of Outside_Grate. +-----------------------------------------------------------------------------+ | 8 Switchable objects | +-----------------------------------------------------------------------------+ In one corner there is a machine which is reminiscent of a clothes dryer. On its face is a switch which is labelled ''START". The switch does not appear to be manipulable by any human hand (unless the fingers are about 1/16 by 1/4 inch)... - \sl Zork Objects can also be switchable. This means they can be turned off or on, as if they had some kind of switch on them. The object has the attribute on if it's on. For example: Object searchlight "Gotham City searchlight" skyscraper with name "search" "light" "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; Here is a lamp which provides for batteries which will some day run down, though the code to actually deplete these batteries (by decrementing time_left every turn it's switched on) is omitted: Nearby brass_lantern "brass lantern" with name "lamp" "lantern" "shiny" "brass", when_off "There is a shiny brass lamp nearby.", when_on "Your lamp is here, gleaming brightly.", time_left 330, before [; Examine: print "It is a shiny brass lamp"; if (brass_lantern hasnt on) ". It is not currently lit."; if (brass_lantern.time_left < 30) ", glowing dimly."; ", glowing brightly."; Burn: <>; Rub: "Rubbing the electric lamp is not particularly \ rewarding. Anyway, nothing exciting happens."; SwitchOn: if (brass_lantern.time_left <= 0) "Unfortunately, the batteries seem to be dead."; ], after [; SwitchOn: give brass_lantern light; SwitchOff: give brass_lantern ~light; ], has switchable; +-----------------------------------------------------------------------------+ | 9 Things to enter, travel in and push around | +-----------------------------------------------------------------------------+ ...the need to navigate a newly added river prompted the invention of vehicles (specifically, a boat). - P. David Lebling, Marc Blank and Timothy Anderson Some objects in a game are enterable, which means that a player can get inside them. The idea of ''inside" here is that the player is only half-in, as with a car or a psychiatrist's couch. (If it's more like a prison cell, then it should be a separate place.) In practice one often wants to make an enterable thing also a container, or, as in this example, a supporter: Object chair "dentist's chair" surgery with name "dentists" "chair" "couch", has enterable supporter; All the classic games have vehicles (like boats, or fork lift trucks, or hot air balloons) which the player can journey in, so Inform makes this easy. Here is a simple case: Object car "little red car" cave with name "little" "red" "car", longdesc "Large enough to sit inside. Among the controls is a \ prominent on/off switch. The numberplate is KAR 1.", initpos "The red car sits here, its engine still running.", closedpos "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, and n_obj as the noun. If it returns false, the game disallows the attempt to move (as usual). If it returns true, then the vehicle and player move together via the game's usual map. ** 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. Usually the player has to type something like ''get out'' to make this happen, though of course the rules can be changed. Exercise: Alter the car so that it won't go up or down stairs. Answer: Change the car's before to before [; Go: if (noun==d_obj or u_obj) { print "(The car will never get over those stairs.)^"; rfalse; } if (car has on) "Brmm! Brmm!"; print "(The ignition is off at the moment.)^"; ], ** Objects like the car (if the hand brake is off) or, say, an antiquated wireless on casters, are obviously 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, the PushDir action is generated. Now, if the before routine returns false, the game will just say that the player can't; and if it returns true, the game will do nothing at all, guessing 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 that one has to do two things: call the AllowPushDir() routine (a library routine), and then return true. So, for example, Nearby huge_ball "huge plaster-of-paris ball" with name "huge" "plaster" "ball", description "A good eight feet across, though fairly lightweight.", initial "A huge plaster-of-paris ball rests here, eight feet wide.", before [; PushDir: AllowPushDir(); rtrue; Take, Remove: "There's a lot of plaster in an eight-foot sphere."; ], after [; PushDir: "The ball rattles about and is hard to stop once underway."; ], has static; +-----------------------------------------------------------------------------+ | 10 Living creatures and conversation | +-----------------------------------------------------------------------------+ To know how to live is my trade and my art. - Michel de Montaigne (1533-1592), Essays Everything that can be said can be said clearly. - Ludwig Wittgenstein (1889-1951), Tractatus This rummage through special kinds of objects finishes up with the most sophisticated kind: living ones. Animate objects (such as sea monsters, mad aunts or nasty little dwarves) have a property called life, containing their rules. This behaves just like a before or after routine, but only the following actions apply: Attack The player is making hostile advances... Kiss ...or excessively friendly ones. ThrowAt The player asked to throw noun at the creature. Give The player asked to give noun to the creature... Show ...or just to show it. Ask The player asked the creature about something: noun holds the word. Answer The player tried either of: ''answer to troll" ''troll, " In either case the variable special_word is set to the dictionary entry of the first word, or 0 if it isn't in the dictionary, and special_number is set to an attempt to read it as a number. (For instance, ''computer, 143" will cause special_number to be set to 143.) Order On the other hand, an Order happens when the parser does understand what the player wants the creature to do: e.g., ''troll, go south". action, noun and second are set up as usual: in this case action=##Go and noun=s_obj, while second=0. If the routine doesn't exist, or returns false, events will take their usual course. Here is a full example: Object snake "sullen snake" mists with name "sullen" "snake", description "Perhaps a boa constrictor. Perhaps not.", life [; Order: if (action==##Go) "The snake wasn't born yesterday."; Attack: "Lazily, the snake dodges your attack."; Kiss: "What a repulsive idea."; ThrowAt: print "Effortlessly, the snake dodges "; DefArt(inp1); "."; Answer, Show: "The snake disdains to comment."; Ask: if (noun == 'mists') "~Healthy and good for the skin, mists.~"; "~I'm only the obligatory monster.~"; Give: if (noun has edible) { remove noun; "~Mmm! Thanks! I still hate you, though.~"; } "~Bleurghh! Are you trying to poison me?~"; ], has animate; Of course an animate still has before and after routines like any other, so you can trap many other kinds of behaviour. (The library understands that, for example, an animate creature cannot be taken.) ** DefArt is a routine which prints the short name of an object along with its definite article, usually ''the". Sometimes creatures should be transparent, sometimes not. Consider these two cases of animate characters, for instance: (o) an urchin with something bulging inside his jacket pocket; (o) a hacker who has a bunch of keys hanging off his belt. The hacker is transparent, the urchin not. That way the parser 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 and what it's called). But the player can look at and be tantalised by the hacker's keys. ** Another way to trap some of these actions (those which do not involve conversation, i.e. other than Order, Answer or Ask) is to use before in the ordinary way. This lets you change rules about, say, giving things to people in particular places. The ThrowAt action is also an ordinary one (for coconut shies, greenhouse windows and the like) but is given to life as well because most creatures react to it, and it's a convenience to have all the rules for a creature in one place. ** A footnote about Order: this is another ''fake action". The before and after routines of a room can't detect the player having given a request to another character. Also, if you want the snake to obey when the player tells it to take something, you have to write the code to do the actual taking yourself. This isn't any problem in practice. (Try looking at the code for Christopher in the ''Toyshop" example game.) +-----------------------------------------------------------------------------+ | 11 Starting, moving, changing and killing the player | +-----------------------------------------------------------------------------+ 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\'ere (1645-1696) That is the road we all have to take - over the Bridge of Sighs into eternity. - S\o ren Kierkegaard (1813-1855) The player normally begins in the room which location is set to, and setting location is the only absolute obligation on a game's Initialise routine. (The room may be initially dark if you so choose, or rather if you provide no light source.) In fact location could be set to a chair or bed just as easily if the player is to start seated or lying down. If you would like to give the player some items to begin with, Initialise should also move them to player. To move the player about (for teleportation of some kind), two things must be done: to move the player object, by move player to newroom; and also to change the location variable, which says which room to display on the status line: location = newroom; The cleanest way to move the player is call PlayerTo(place); which also sorts out things like only printing the new room's description if there's enough light there to see by. ** In general location can be different from parent(player) in two ways: it can be ''Darkness" (that is, the special object thedark, which behaves somewhat like a room), or it can be the actual room while parent(player) is something the player sits inside, like (say) a jeep. ** Calling PlayerTo(place, 1); will move the player without printing anything, and in particular without printing any room description. The player's whole persona can easily be changed, by setting player.before = #r$MyNewRule; where MyNewRule is a new before rule to be applied to every action of the player's (or similarly for an after rule). For instance, if a cannon goes right next to the player, a period of deafness might ensue, and this rule could stop the Listen action from taking its normal course. ** In fact a much more powerful trick is available: the player can actually become a different character in the game, allowing the real player at the keyboard to act through someone else. Calling ChangePlayer(obj) will transform the player to obj. There's no need for obj to have names like ''me'' or ''myself''; the parser understands these words automatically to refer to the currently-inhabited player object. However, it must provide a number property (which the library will use for workspace). The maximum number of items the player can carry as that object will be its capacity. Finally, since ChangePlayer prints nothing, you may want to conclude with a <>; ChangePlayer has many possible applications. 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. More ambitiously, a game could have a stock of half a dozen main characters, and the focus of play can switch between them. A player might have a team of four adventurers to explore a dungeon, 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.) ** Exercise: 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 his wayhel. Answer: The common man's wayhel was a lowly mouse. Since we think much more highly of the player: Object hog "Warthog" Caldera with name "wart" "hog" "warthog", description "Muddy and grunting.", number 0, initial "A warthog snuffles and grunts about in the ash.", before [; if (player==self && action~=##Go or ##Look or ##Examine) "Warthogs can't do anything as tricky as that!"; ], has animate proper; and we just ChangePlayer(warthog);. Note that this before rule is carefully written only to affect actions of the player-as-warthog. If the player-as-human should find and try to ''take warthog'', this before routine won't interfere. ** Calling ChangePlayer(object,1); will do the same but make the game print ''(as Whoever)'' during room descriptions. The player is still alive for as long as the variable deadflag is zero. When set to 1, the player dies; when set to 2, the player wins; and all higher values are taken as more exotic forms of death. Now Inform does not know what to call these exotica: so if they should arise, it calls the DeathMessage routine, which is expected to look at deadflag and can then print something like ''You have changed''. Many games allow reincarnation (or, as David M. Baggett points out, in fact resurrection). You too can allow this, by providing an AfterLife. This routine gets the chance to do as it pleases before any ''You are dead'' type message appears, including resetting deadflag back to 0 - which causes the game to proceed in the normal way, rather than end. AfterLife routines can be tricky to write, though, because the game has to be set to a state which convincingly reflects what has happened. (For instance, try collapsing the bridge in 'Advent' by leading the bear across it, then being reincarnated and returning to the scene.) +-----------------------------------------------------------------------------+ | 12 Printing out names of objects | +-----------------------------------------------------------------------------+ And we were angry and poor and happy, And proud of seeing our names in print. - G. K. Chesterton (1874-1936), A Song of Defeat Inform has a special form of print command for this, print object, but do not use it. Instead, call one of the following library routines: DefArt(obj) Print the object with its definite article CDefArt(obj) The same, but capitalised InDefArt(obj) Print the object with indefinite article PrintShortName(obj) Print the object's short name alone The indefinite article for an object is held in the property article and is assumed to be 'a' if none is declared. That means that if the short name starts with a vowel, you need to set it to 'an'. But article offers much more amusement: a platinum bar, an orange balloon, your Aunt Jemima, some bundles of reeds, far too many marbles are all examples of articles. Definite articles are always ''the'' unless an object is given the attribute proper, which makes it a proper noun and so not take a definite article at all. Thus the platinum bar, Aunt Jemima, Elbereth are all printed by DefArt, the latter two being proper. As we shall later see, changing the short name is easy. ** Exercise: Why does print CDefArt(obj), " falls to the floor.^"; seem to work, but mysteriously print the number 1 after the name of the object? Answer: Because CDefArt(obj) is a function call which, as it happens, returns the value true, or 1 (not that this signifies anything), and print thinks it is printing out a number. ** ** The reason print object is unsafe is that it prints the real, ''hardware and unchangeable'' short name, whereas we want everything to work nicely when the user changes the short name of an object in play: so the library routines almost all indirect through PrintShortName (except in two cases to do with ChangePlayer, since the current player's short name is always ''yourself''). +-----------------------------------------------------------------------------+ | 13 Classes of objects | +-----------------------------------------------------------------------------+ In most games there are groups of objects with certain rules in common. As well as individual objects, Inform allows one to define classes in almost exactly the same way. The only difference between the layout of a class and object definition is that a class has no short name or initial location (since it does not correspond to a single real item). For example: Class Treasure with depositpoints 10, after [; Take: if (location==Inside_Building) score=score-self.depositpoints; score=score+5; "Taken!"; Drop: score=score-5; if (location==Inside_Building) { score=score+self.depositpoints; "Safely deposited."; } ], has valuable; An object of this class inherits the properties and attributes it defines: in this case, an object of class Treasure picks up the given score and rules automatically. So Nearby bars_of_silver "bars of silver" class Treasure with description "They're probably worth a fortune!", initial "There are bars of silver here!", article "some", name "silver" "bars"; inherits the depositpoints value of 10 and the rules about taking and dropping. If the silver bars had themselves set depositpoints to 15, say, then the value would be 15: i.e., the class would be over-ridden. ** depositpoints isn't a library property: it's one defined in the game this example is drawn from, the 'Advent' example game. We could also, for instance, have: Nearby cake "valuable cake" class Treasure with description "Exquisite!", initial "There's a valuable cake here!", after [; Eat: "Your most expensive meal in ages, but worth it."; ], name "valuable" "cake" has edible; Now the cake has two after rules. Both apply, but the rule in the cake itself takes precedence, i.e., happens first. ** An object can inherit from several classes at once. Moreover, a class can itself inherit from other classes, so one can easily make a class for ''like Treasure but with only 8 depositpoints''. ** ** The class field of an object definition contains a list of classes, class $C_1$ ... $C_n$ in which case the object inherits first from $C_1$, then from $C_2$ and so on. $C_2$ over-rides $C_1$ and so on along the line. These classes may well disagree with each other, so the order matters. If $C_1$ says depositpoints is 5, $C_3$ says it is 10 but the object definition itself says 15 then the answer is 15. ** ** With some properties, the value is not replaced but added to: this is what happened with after above. These properties are those which were declared as additive, e.g. by Property additive before $ffff; For instance, the standard Inform properties name and before are both additive. So we could add name "treasure", to the properties in the class definition for Treasure, and then all objects of that class would respond to the word ''treasure'', as well as their own particular names. +-----------------------------------------------------------------------------+ | 14 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 Some daemon stole my pen (forgive th' offence) And once betrayed me into common sense. - Alexander Pope (1688-1744), The Dunciad By tradition, a daemon is an event which happens each turn while it is 'active'. The classic example is of a dwarf which appears in the cave: it has a daemon routine attached for moving about, throwing knives at the player and other pleasantries. Each object can have a daemon of its own. This is set going, and stopped again, by calling the (library) routines StartDaemon(the-object); StopDaemon(the-object); Once active, the daemon property of the object is called as a routine each turn. Daemons are often started by a game's Initialise routine, and active throughout. ** Be warned: this continues to happen even if the daemon is associated with a room or item which has been left behind by the player. Actually this is very useful, as it means daemons can be used for 'tidying-up operations', or for the consequences of the player's actions to catch up with him. Daemons often run a fair amount of code. (There are plenty of good examples in 'Toyshop' and 'Advent'.) They shouldn't be ridiculously slow if they will run more than once. And some daemons so sit in the background for enormously long times: for instance, the daemon in ''Advent'' which hides until the player has managed to get all the treasures, then pounces. Such daemons ought to check their condition and return as quickly as possible if it fails. Exercise: Many games contain 'wandering monsters', characters who walk around the map (usually hand-coded and not moving far abroad). Use a daemon to implement one who wanders as freely as the player, like the thief in 'Zork I'. Answer: This is a crude implementation, for brevity (the real Zork I thief has an enormous stock of attached messages). Object thief "thief" Danger_Zone with name "thief", each_turn "^The thief growls menacingly.", daemon [ i p j n k; if (random(3)~=1) rfalse; p=parent(thief); objectloop (i in compass) { j=p.(i.door_dir); if (j>player && j<=top_object && j hasnt door) n++; } if (n==0) rfalse; k=random(n); n=0; objectloop (i in compass) { j=p.(i.door_dir); if (j>player && j<=top_object && j hasnt door) n++; if (n==k) { move self to j; if (p==location) "^The thief stalks away!"; if (j==location) "^The thief stalks in!"; rfalse; } } ]; This thief walks at random and cannot pass through doors, bridges and the like (because these may be locked or have rules attached); it's only a first approximation, and in a good game one should occasionally see the thief do something surprising, such as open a secret door. Exercise: Use a background daemon to implement a system of weights, so that the player can only carry a certain weight before her strength gives out and she is obliged to drop something. It should allow for feathers to be lighter than lawn-mowers. Answer: First define a new property for object weight: Property weight 10; (10 being an average sort of weight). Containers weigh more when they hold things, so we will need: [ WeightOf obj t i; t = obj.weight; objectloop (i in obj) t=t+WeightOf(i); return t; ]; Now for the daemon which monitors the player's fatigue: Object weigher "weigher" with number 500, time_left 5, daemon [ w s b bw; w=WeightOf(player)-100-player.weight; s=self.number; s=s-w; if (s<0) s=0; if (s>500) s=500; self.number = s; if (s==0) { bw=-1; objectloop(b in player) if (WeightOf(b)>bw) { bw=WeightOf(b); w=b; } print "^Exhausted with carrying so much, you decide \ to discard "; DefArt(w); print ": "; <>; } w=s/100; if (w==self.time_left) rfalse; if (w==3) print "^You are feeling a little tired.^"; if (w==2) print "^You possessions are weighing you down.^"; if (w==1) print "^Carrying so much weight is wearing you out.^"; self.time_left = w; ]; Notice that items are actually dropped with Drop actions: one of them might be, say, a wild boar, which would bolt away into the forest when released. The daemon tries to drop the heaviest item. (Obviously a little improvement would be needed if the game contained, say, an un-droppable but very heavy ball and chain.) Now the daemon is going to run every turn forever, but needs to be started: so put StartDaemon(weigher); into the game's Initialise routine. A ''timer" (these are traditionally called ''fuses" but the author can stand only so much tradition) can alternatively be attached to an object. (An object can't have both a timer and a daemon going at the same time.) A timer is started with StartTimer(the-object, time); in which case it will ''go off" (alarm clock-style) in the given number of turns. This means that its time_out property will be called (as a routine), once and once only, when the time comes. It can be deactivated (so that it will never go off) by calling StopTimer(the-object); A timer is required to provide a time_left property, to hold the amount of time left. If it doesn't, the library will print an error message at run-time. You can alter time_left yourself, but setting it to 0 does not stop the timer: use the routine StopTimer for that. ** In early releases of the library, a daemon needed a time_left as well, which was illogical and a nuisance: anyway, it's no longer the case. ** Normally, you can only have 32 timers or daemons active at the same time as each other (there may be any number of inactive ones). But this limit is easily raised: just define the constant MAX_TIMERS to some larger value, putting the definition in your code before the Parser file is included. There is yet a third form of timed event. If a room provides an each_turn routine, then this will be called in each turn when the player is in it; if an object provides each_turn, this is called whenever the object is nearby. For instance, a radio might blare out music whenever it is nearby; a sword might glow whenever monsters are nearby; or a stream running through several forest locations might occasionally float objects by. 'Each turn' is entirely separate from daemons and timers. Although an object can't have both a timer and a daemon at the same time, it can have an each_turn at the same time, and this is quite useful, especially to run creatures. An ogre with limited patience can therefore have an each_turn routine which worries the player (''The ogre stamps his feet angrily!'', etc.) while also having a timer set to go off when his patience runs out. ** 'Nearby' actually means 'in scope', a term which will be properly explained later. The idea is based on line of sight, which works well in most cases. ** ** But it does mean that the radio will be inaudible when shut up inside most containers - which is arguably fair enough - yet audible when shut up inside transparent, say glass, ones. You can always change the scope rules using an InScope routine to get around this. In case you want to tell whether scope is being worked out for ordinary parsing reasons or instead for each_turn processing, look at the et_flag variable (0 in the former case, 1 in the latter). Powerful effects are available this way - you could put the radio in scope within all nearby rooms so as to allow sound to travel. Or you could make a thief audible throughout the maze he is wandering around in, as in 'Zork I'. The library also has the (limited) ability to keep track of time of day as the game goes on. If you're writing a game with the time instead of the score and turns on the status line, you can set the time by SetTime( 60$\times$$+$, rate ); The current time is held in the variable the_time and runs on a 24-hour clock. 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; and negative rate means that many turns pass between each minute. The time still won't appear on the game's status line unless you set Statusline time; as a directive somewhere in your code. And remember to start off the clock by calling SetTime in your Initialise routine, if you're going to use it. Exercise: How could you make your game take notice of the time passing midnight, so that the day of the week could be nudged on? Answer: Either set a daemon to watch for the_time suddenly dropping, or put such a watch in the game's TimePasses routine. ** Exactly what happens at the end of each turn is: 1. The turns counter is incremented. 2. The 24-clock is moved on. 3. Daemons and timers are run (in no guaranteed order). 4. each_turn takes place for the current room, and then for everything nearby (that is, in scope). 5. The game's global TimePasses() routine is called. 6. Light is re-considered (it may have changed as a result of events since this time last turn). The sequence is abandoned if at any stage the player dies or wins. ** Exercise: 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 just trapping Drop actions because objects might end up on the floor in many different ways). Why is using each_turn better? Answer: Because you don't know what order daemons will run in. A 'fatigue' daemon which makes the player drop something might come after the 'mid-air' daemon has run for this turn. Whereas each_turn happens after daemons and timers have run their course, and can fairly assume no further movements will take place this turn. Exercise: How would a game work if it involved a month-long archaeological dig, where anything from days to minutes pass between successive game turns? Answer: It would have to provide its own code to keep track of time, and it can do this by providing a TimePasses() routine. Providing ''time'' or even ''date'' verbs to tell the player would also be a good idea. +-----------------------------------------------------------------------------+ | 15 Adding verbs and grammar to the parser | +-----------------------------------------------------------------------------+ Grammar, which can govern even kings. - Moli\'ere (1622-1673), Les Femmes savantes Language disguises thought... The tacit conventions on which the understanding of everyday language depends are enormously complicated. - Ludwig Wittgenstein, Tractatus The next few sections will delve deep into the parser. Inform goes to a great deal of trouble to make the parser as ''open-access'' as possible, because a parser cannot ever be general enough for every game without being extremely modifiable. So there are very many ways to customise the Inform parser, hopefully without the user needing to understand much about how it works (because this is quite a long story). The first essential requirement of any parser is to accept the addition of the grammar for a new verb. In Inform code, grammar should appear at the end of the source code for a game, and of course most of it is written out in the Grammar header file, which all games using the library include. After this inclusion, you can add extras. The directive for this is called Verb, and here's a typical example: some of the library's grammar for ''take": Verb "take" "get" "pick" "lift" * "out" -> Exit * multi -> Take * multiinside "from" noun -> Remove * "in" noun -> Enter * multiinside "off" noun -> Remove * "off" held -> Disrobe * "inventory" -> Inv; This declares a verb, for which ''take", ''get" and so on are synonyms, and which can take seven different courses. ** It should be noted that these really are synonyms: the parser thinks ''take" and ''get" are exactly the same. Sometimes this has odd results, so that although ''get in bed" is correctly understood as a request to enter the bed, ''take in washing" is misunderstood as a request to enter the washing. You could either get around this by writing separate grammars for the two nearly-synonyms, or can cheat and see if the variable verb_word=='take' or 'get'. Mostly, though, you don't mind if a few odd things are accepted by the parser: what matters more is that sensible things are not rejected by it. When it ploughs through what the player has typed, the parser tries to match each line in turn, starting at the top. The first line will only be matched if the player has typed something like ''get out". The second line is more interesting: it allows the player to type a single object or a list of objects. So the second line could be matched by take the banana get all the fruit except the apple There need be no grammar at all after the verb: for example, a grammar for ''inventory" could be as simple as Verb "invent" "inv" "i" * -> Inv; After the -> in each line is the name of an action. This is how actions are defined: they are the names which appear in grammar lines like this one. Remember that if you do create an action this way, you also have to write a routine to execute the action, even if it's one which doesn't do very much. 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. (The name of the routine is always the name of the action with Sub appended.) The new action Xyzzy is treated exactly like all the standard Inform actions: ##Xyzzy gives its action number, and you can write before and after rules for it in Xyzzy: fields just as you would for, say, Take. The individual words in the grammar (after the * and before the ->) are called ''tokens". In increasing order of complexity, this is the complete list: "" that literal word only [noun] any object in scope [held] object held by the player [creature] an object in scope which is animate [multi] one or more objects in scope [multiheld] one or more held objects [multiexcept] one or more in scope, except the other [multiinside] one or more in scope, inside the other any object in scope which has the attribute noun = any object in scope passing the given test scope = an object in this definition of scope [special] any single word or number [number] a number only any text accepted by the given routine (Tokens like [noun] are so written here to avoid confusion: there is a variable called noun too, which is quite different.) In the case of noun = , the parser applies the following test: the variable noun is set to the object in question, and the routine is called. If it returns true, the parser accepts the object, and otherwise it rejects it. [number] matches any decimal number from 0 upwards (though it rounds off large numbers to 10000), and also matches the numbers ''one'' to ''twenty'' written in English. The token [special] is now obselete. For now, we shall take ''in scope" to mean ''visible to the player". It is quite important in some cases to know exactly what this means, so a better definition will be given later. The [held] token is convenient for two reasons. Firstly, many actions only sensibly apply to things being held (such as Eat or Wear), and using this token in the grammar you can make sure that the action is never generated by the parser unless the object is being held. That saves on always having to write ''You can't eat what you're not holding" code. Secondly, suppose we have grammar Verb "eat" * held -> Eat; and the player types eat the banana while the banana is plainly in view, on a shelf, say. It would be rather 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.) 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" by itself, and then generates actions for each one.) [multiexcept] is provided to make commands like put everything in the rucksack parsable: the ''everything" is matched by all of the player's possessions except the rucksack (this stops the parser from generating an action to put the rucksack inside itself). Similarly [multiinside] handles: remove everything from the cupboard A restriction here is that a single grammar line can only contain one [multi-] token: so ''hit everything with everything" can't be parsed (straightforwardly: you can parse anything with a little more effort). On the whole, this is no bad thing. The reason not all nouns can be multiple in the first place is that too helpful a parser makes too easy a game. You probably don't want to allow something like unlock the mystery door with all the keys - you want the player to suffer having to try them one at a time, or else to be thinking. (Of course if you do want to allow this it is easy enough to change the grammar: the point is that you have the option.) We can also sort out objects according to attributes that they have: Verb "use" "employ" "utilise" * edible -> Eat * clothing -> Wear ...and so on... * enterable -> Enter; and this is how attributes are used as tokens. (The library grammar does not contain such an appallingly convenient verb!) Since you can define your own attributes, it is therefore easy to make a grammar line which matches only your own class of object. ** A footnote: the [creature] token is thus equivalent to writing animate; but it dates back to the earliest days of Inform, and does no harm. Sometimes even that isn't flexible enough. Here is a verb, ''free", which should only apply to animal 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 code, though it's tidier to keep it nearby.) So far, all the tokens were to tell the parser which of the objects in scope were acceptable. Exactly what 'in scope' means will be gone into later, and so will the powerful scope=... token. Here we next want the parser to match things other than names of objects and prepositions like ''into". The simplest useful case is of numbers, for example: Verb "type" * number -> TypeNum; so that the TypeNum action will happen if the player types ''type 504'' or ''type seventeen''. Exercise: (A beautiful feature stolen from David M. Baggett's game 'The Legend Lives', which uses it to great effect.) Some games 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. Answer: Constant MAX_FOOTNOTES 10; global footnotes_seen data MAX_FOOTNOTES; global footnote_count; [ Note n i pn; for (i=0:ii) pn=i; if (footnote_count==MAX_FOOTNOTES) "** MAX_FOOTNOTES exceeded! **"; if (pn==0) { pn=footnote_count++; footnotes_seen->pn=n; } print " [",pn+1,"]"; ]; [ FootnoteSub n; if (noun>footnote_count) { print "No footnote [",noun,"] has been mentioned.^"; rtrue; } if (noun==0) "Footnotes count upward from 1."; n=footnotes_seen->(noun-1); print "[",noun,"] "; if (n==0) "This is a footnote."; if (n==1) "D.G.REG.F.D is inscribed around English coins."; if (n==2) "~Jackdaws love my big sphinx of quartz~, for example."; ]; Verb "footnote" "note" * number -> Footnote; And then call, for instance, Note(1); to refer in the game to the ''English coins'' footnote. ** In fact, you're allowed to provide your own number-parsing routine, so many sneaky possibilities are open here - Roman numerals, coordinates like ''J4", long telephone numbers (which would be too large to fit into integer variables in Inform), understanding English numbers and so on. This takes the form [ ParseNumber buffer length; ...returning 0 if no match is made, or the number otherwise... ]; and examines the supposed ''number" held at the byte address buffer, which is a row of ASCII characters of the given length. If you declare a ParseNumber routine, then the parser will always try this first when trying to decode something as a number (and if it fails will fall back on its own number-parsing routine). ** ** For not-very-good internal reasons, ParseNumber can't return 0 to mean the number zero. 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. ** ** You can give a routine to parse anything: [ French w n; w=NextWord(); if (w=='un' or 'une') n=1; if (w=='deux') n=2; if (w=='trois') n=3; if (w=='quatre') n=4; if (w=='cinq') n=5; if (n==0) return -1; parsed_number = n; return 1; ]; will detect low numbers in French, and could be used by something like: Verb "type" * French -> TypeFrenchNum * number -> TypeNum; The specification for such a routine is as follows: it is to use NextWord (possibly several times) to look at the appropriate words which the user has typed. The variable wn (current word number) may also be helpful. The routine must return -1 if the user's input isn't understood, 1 if there is a numerical value resulting, or $n$ if object $n$ is understood. In the case of a number, the actual value should be put into the variable parsed_number. On an unsuccessful match (returning -1) it doesn't matter what the final value of wn is. On a successful one 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.) That's all about tokens: back to verb definition commands, because some of the above examples are slightly contrived - they create wholly new and unlikely verbs. More often, one would want, say, a big array of labelled buttons, any of which could be pushed. So the verb for ''push" ought to be extended, and for this the Extend directive is provided: Extend "push" * Button -> PushButton; A routine called Button could then be written to accept things like ''button j16", ''d11", ''a5 button". The point of Extend is that it is against the spirit of the library to alter the standard library files - including the grammar table - unless absolutely necessary. ** ** Actually, there's a better way to provide these buttons, so that they work with any verb automatically, and that's to make a button object with a parse_name routine which cunningly extracts the button number as it parses: see the game 'Balances' for an example of this. Normally, extra lines of grammar are added at the bottom of those already there. This may not be what you want. For instance, ''take" has a grammar line * multi -> Take quite early on. So if you want to add a grammar line which diverts ''take something-edible" to a different action, like so: * edible -> Eat then it's no good adding this at the bottom of the Take grammar, because the earlier line will always be matched first. Thus, you really want to insert your line at the top, not the bottom, in this case. The right command is Extend "take" first * edible -> Eat; You might even want to actually replace the old grammar completely, not just add a line or two. For this, use Extend "push" replace * Button -> PushButton; and now ''push" can be used only in this way. To sum up, Extend can take three keywords: replace completely replace the old grammar with this one first insert the new grammar at the top of the old one last insert the new grammar at the bottom of the old one with last being the default (which doesn't need to be said explicitly). ** Another convenience is to define meta verbs - verbs which are not really part of the game, such as ''save'', ''score'' and ''quit''. (You probably want to add your own debugging commands as meta-verbs: if you make them ordinary verbs, then they may work most of the time, but might be interfered with by game rules, and will take up ''game time".) To declare a verb as meta, just add the word after the command. For instance, the library defines Verb meta "score" * -> Score; The parser will match the grammar exactly in the normal way, but treats the resulting action as outside the game - taking no time up, and possible at any moment. ** ** There are (a few) times when verb definition commands are not enough. For example, in the original 'Advent' (or 'Colossal Cave'), the player could type the name of a not-too-distant place which had previously been visited, and be taken there. There are several ways to code this - say, with 60 rather similar verb definitions, or with a single ''travel" verb which has 60 synonyms, whose action routine looks at the parser's verb_word variable to see which one was typed - but here's another. The library will call the UnknownVerb 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 a foolish example: [ UnknownVerb w; if (w=='shazam') { print "Shazam!^"; return 'inventory'; } rfalse; ]; which responds to the magic word ''shazam" by printing Shazam! and then, rather disappointingly, taking the player's inventory. But in the example above, it could be used to look for the word w through the locations of the game, store the place away in some global variable, and then return 'go'. The GoSub routine could then be fixed to look at this variable. ** ** Exercise: Why is it usually a bad idea to print text out in an UnknownVerb routine? Answer: Because the parser might go on to reject the line it's working on: for instance, if the player typed ''inventory splurge'' then the message ''Shazam!'' followed by a parser complaint will be somewhat unedifying. ** ** If you allow a flexible collection of verbs (say, names of spells or places) then you may want a single 'dummy' verb to stand for whichever is being typed. This may make the parser produce strange questions because it is unable to sensibly print the verb back at the player, but you can fix this using the PrintVerb entry point (see the Appendix). See the exercise below. ** Exercise: A tricky one: code a spaceship whose control panel has five sliding controls, each of which can be 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 number 1 to the value 5. >examine the first slide Slide number 1 currently stands at 5. >set four to six You set slide number 4 to the value 6. Answer: Here goes: we could implement the buttons with five separate objects, essentially duplicates of each other. (And by using a class definition, this wouldn't look too bad.) But if there were 500 slides this would be less reasonable. [ ASlide w n; if (location~=Machine_Room) ! Slides only make sense in return -1; ! the Machine Room w=NextWord(); if (w=='slide') w=NextWord(); n=0; if (w=='first' or 'one') n=1; if (w=='second' or 'two') n=2; if (w=='third' or 'three') n=3; if (w=='fourth' or 'four') n=4; if (w=='fifth' or 'five') n=5; if (n==0) return -1; ! Failure! w=NextWord(); if (w~='slide') wn--; ! Move word counter back to ! first misunderstood word parsed_number=n; return 1; ! Success! ]; Global slide_settings data 10; ! Ten bytes of data to hold ! five words for the settings ! (all initially zero) ! An interesting point here is that "noun" and "second" contain the ! appropriate numbers, and not objects: this all happens automatically [ SetSlideSub; slide_settings-->(noun-1) = second; print_ret "You set slide number ", noun, " to the value ", second, "."; ]; [ XSlideSub; print_ret "Slide number ", noun, " currently stands at ", slide_settings-->(noun-1), "."; ]; Extend "set" first * ASlide "to" number -> SetSlide; Extend "push" first * ASlide "to" number -> SetSlide; Extend "examine" first * ASlide -> XSlide; ** ** Exercise: An even trickier one: write a parsing routine which will accept any amount of text (including spaces, full stops and commas) between double-quotes as a single token. Answer: The blackboard code in 'Toyshop' contains just such a routine: Global from_char; Global to_char; [ QuotedText i j f; i = parse->((++wn)*4-3); if (buffer->i=='"') { for (j=i+1:j<=(buffer->1)+1:j++) if (buffer->j=='"') f=j; if (f==0) return -1; from_char = i+1; to_char=f-1; if (from_char>to_char) return -1; while (f> (parse->(wn*4-3))) wn++; wn++; return 1; } return -1; ]; Note that in the case of success, the word marker wn is moved beyond the last word accepted (since the Z-machine automatically tokenises a double-quote as a single word). The routine then tells the parser it has parsed a number, which is a white lie. What it actually does is to record the byte positions where the quoted text starts and finishes in the raw text buffer so that an action routine can easily extract the text and use it later. (Note that "" with no text inside is not matched by this routine but only because the last if statement throws out that one case.) ** ** Exercise: And another: implement the Crowther and Woods feature of moving from one room to another by typing its name, discussed above, using a dummy verb and without using Replace to change any library routines. Answer: Define two properties: Property place_name; Property long to_places; The scheme will work like this: a named room should have the place_name property set to a single dictionary word; say, the Bedquilt cave could be called 'bedquilt'. Then in any room, a list of those other rooms which can be moved to in this way should appear in the to_places entry. For instance, to_places Bedquilt Slab_Room Twopit_Room; Now the code: see if a not-understood verb is a place name of a nearby room, and if so store that room's object number in goto_room, converting the verb to a dummy. Global goto_room = 0; [ UnknownVerb word p i; p = location.&to_places; if (p==0) rfalse; for (i=0:(2*i)i).place_name) { goto_room = p-->i; return 'go#room'; } rfalse; ]; [ PrintVerb word; if (word=='go#room') { print "go to "; PrintShortName(goto_room); rtrue; } rfalse; ]; (The supplied PrintVerb is icing on the cake: so the parser can say something like ''I only understood you as far as wanting to go to Bedquilt.'' if need be.) It remains only to put in code and grammar for the dummy verb: [ GoRoomSub; if (goto_room hasnt visited) "But you have never been there."; PlayerTo(goto_room); ]; Verb "go#room" * -> GoRoom; Note that if you don't know the way, you can't go there! A purist might prefer instead to not recognise the name of an unvisited room, back at the UnknownVerb stage, to avoid the player being able to deduce names of nearby rooms from this 'error message'. +-----------------------------------------------------------------------------+ | 16 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 Wherefore are these things hid? - William Shakespeare (1564-1616), Twelfth Night Time to say what ''in scope" means. This definition is one of the most important algorithms in the game, although it is buried inside the parser, because it decides what the player is allowed to refer to: so here it is in full. The following are in scope: the player's immediate possessions; the 12 compass directions; if there is light, the objects in the same place as the player. \narrower In addition, if an object is in scope then its immediate possessions are in scope, if it is 'see-through', which means that: the object has supporter, or the object has transparent, or the object is an open container. The definition of when there is light will be gone into in the next section. ** Many things are still in scope in a dark room - so the player can still turn a lamp on if it's 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.) ** The compass direction objects make sense as objects. The player can always type something like attack the south wall (and a before rule for the room in question could make something unusual happen as a result). The compass directions are visible in the dark, as otherwise the player could not still walk around. ** The parser applies scope rules to other people too, not just the player. Thus ''dwarf, drop sword'' will be accepted even if the sword is invisible to the player provided it is visible to the dwarf. ** Items with the concealed attribute may be concealed from ordinary descriptions - but the parser still sees them: they are still in scope. (If you want things to be both concealed and unreferrable-to, put them somewhere else!) ** ** Actually, the above definition is not quite right, because the compass directions are not in scope when the player asks for a plural number of things, like ''take all the knives". (This makes some of the parser's plural algorithms run faster.) Also, for a [multiexcept] token, the other object is not in scope; and for a [multiinside] token, only objects in the other object are in scope. This makes ''take everything from the cupboard'' work in the natural way. 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 do so only locally. 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: Object questions "qs"; [ QuerySub; print_paddr noun.description; new_line; ]; [ Topic i; if (scope_stage==1) rfalse; if (scope_stage==2) { objectloop (i in questions) PlaceInScope(i); rtrue; } "At the moment, even the simplest questions confuse you."; ]; where the actual questions are themselves objects which belong to the questions object, like so: Object q1 "long count" questions 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."; and we also have a grammar line: Verb "what" * "is" scope=Topic -> Query * "was" scope=Topic -> Query; (Note that the questions and q1 objects are out of the game for every other purpose. The name ''qs'' doesn't matter; the individual questions are named so that the parser might be able to say ''Which do you mean, the long count or the short count?'' if the player just asked ''what is the count''.) Here's what happens: when the parser reaches scope=Topic, it calls the Topic routine with the variable scope_stage set to 1. The routine should return 1 (true) if it is prepared to allow multiple objects to be accepted here, and 0 (false) otherwise: as we don't want ''what is everything'' to list all the questions and answers in the game, we return false. A little later on in its machinations, the parser again calls Topic with scope_stage now set to 2. Topic is now under an obligation to tell the parser which objects are to be in scope. It can call two parser routines to do this. ScopeWithin(object) puts everything inside the object into scope, though not the object itself; PlaceInScope(object) puts just a single object into scope. It is perfectly legal to declare something in scope that ''would have been in scope anyway": or even something which is in a different room altogether from the actor concerned, say at the other end of a telephone line. Our scope routine Topic should then return 0 (false) to carry on with the usual scope rules, so that everything that would usually be in scope still is, and 1 (true) to tell the parser not to put any more objects into scope. (So at scope_stage 2 it is quite permissible to do nothing but return false, whereupon the usual rules apply.) Topic returns true because it wants only question topics to be in scope, not question topics together with everything near the player. If the player had typed ''what is the long count'', that would be all that happens and all would be well. On the other hand, if the player typed ''what is the lgon cnout'' owing to a bout of dyslexia, the error message which the parser would usually produce (''You can't see any such thing'') would be unsatisfactory. So if parsing failed at this token, then Topic is called at scope_stage 3 to print out a suitable error message. (It must provide one.) ** The scope= notation is available only under Inform 5.4 and later. ** Note that ScopeWithin(object) extends the scope down through its possessions according to the usual rules (i.e. depending on their transparency, whether they're containers and so in). The definition of Topic above shows how to put just the direct possessions into scope. Exercise: Write a token which puts everything in scope, so that you could have a debugging ''purloin'' verb which could take anything (regardless of where it was and the rules applying to it). Answer: A slight refinement of such a ''purloin'' verb is already defined in the library (if the constant DEBUG is defined), so there's no need. Here's how it could be done: [ Anything i; if (scope_stage==1) rfalse; if (scope_stage==2) { for (i=1:i<=top_object:i++) PlaceInScope(i); rtrue; } "No such in game."; ]; (This disallows multiple matches for efficiency reasons - the parser has enough work to do with such a huge scope definition as it is.) Now the token scope=Anything will match anything at all, even things like the abstract concept of 'east'. Changing the global definition of scope should be done cautiously (there may be unanticipated side effects); bear in mind that scope decisions need to be taken often - every time an object token is parsed, so perhaps five to ten times in every game turn - and hence moderately quickly. To do this one provides a routine called InScope(actor) where the actor is the person whom the parser is working out scope for; usually, then, actor=player, but this should not be assumed to be always the case. If the routine decides that a particular object should be in scope for the actor, it should execute InScope and ScopeWithin just as above, and return true or false (as if it were at scope_stage 2). Thus, it is vital to return false in circumstances when you don't want to intervene. ** In the case of a token scope=Routine, the Routine gets first crack at scope-changing, and InScope will only be reached if it returns false to signify 'carry on'. Here are some examples. Firstly, as promised above, 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 near player) if (i has moved) PlaceInScope(i); rfalse; ]; With this routine added, the objects in the dark room the player is in 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. ** Exercise: Code the following puzzle, which has nasty scope complications. 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 a dwarf breathing. If you tell the dwarf to turn the light on, he will. Answer: For good measure, we'll combine this with the previous rule about moved objects being in scope in the dark. The following can be inserted into the 'Shell' game: Object coal "dull coal" Blank_Room with name "dull" "coal"; Object Dark_Room "Dark Room" with description "An empty room with a west exit.", each_turn [; if (self has general) self.each_turn=0; else "^You hear the breathing of a dwarf."; ], w_to Blank_Room; Nearby light_switch "light switch" with name "light" "switch", initial "On one wall is the light switch.", after [; SwitchOn: give Dark_Room light; SwitchOff: give Dark_Room ~light; ], has switchable static; Nearby diamond "shiny diamond" with name "shiny" "diamond" has scored; Nearby dwarf "dwarf" with name "voice" "dwarf", life [; Order: if (action==##SwitchOn && noun==light_switch) { give Dark_Room light general; give light_switch on; "~Right you are, squire.~"; } ], has animate; [ InScope person i; if (parent(person)==Dark_Room) { if (person==dwarf Dark_Room has general) PlaceInScope(light_switch); } if (person==player && location==thedark) objectloop (i near player) if (i has moved i==dwarf) PlaceInScope(i); rfalse; ]; Note that the routine puts the light switch in scope for the dwarf - if it didn't, the dwarf would not be able to understand ''dwarf, turn light on", and that was the whole point. ** By combining an InScope routine with token-parsing routines in grammar, dramatic effects are possible. See the example game 'Balances', for instance. ** ** 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. So that you can change the rules even at this last hurdle, the parser calls your ParserError routine (if you provide one): the argument is the error type, and you return true to tell the parser to shut up (as you have already printed a better error message than it would have done). The error types are all defined as constants: STUCK_PE I didn't understand that sentence. UPTO_PE I only understood you as far as... CANTSEE_PE You can't see any such thing. TOOLIT_PE You seem to have said too little! NOTHELD_PE You aren't holding that! MULTI_PE You can't use multiple objects with that verb. MMULTI_PE You can only use multiple objects once on a line. VAGUE_PE I'm not sure what ~it~ refers to. EXCEPT_PE You excepted something not included anyway! ANIMA_PE You can only do that to something animate. VERB_PE That's not a verb I recognise. SCENERY_PE That's not something you need to refer to... ITGONE_PE You can't see ~it~ (the spell book) at the moment. JUNKAFTER_PE I didn't understand the way that finished. TOOFEW_PE Only five of those are available. NOTHING_PE Nothing to do! NUMBER_PE I didn't understand that number. ASKSCOPE_PE whatever the scope routine prints Each time the parser gives up on a line of grammar, it has hit one of these conditions. A verb may have many lines of grammar; so by the time the parser wants to print an error, all of them must have failed. The error message it prints is the most 'interesting' one (meaning, lowest down this list). +-----------------------------------------------------------------------------+ | 17 The light and the dark | +-----------------------------------------------------------------------------+ The library maintains light by itself, and copes with events like: 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 point of this list is to demonstrate that light v. darkness is tricky to get right, and that it is best left to the library. Your code needs only to do something like give lamp light; remove match; give glass_jar ~transparent; move dwarf to Dark_Room; and can leave the library to sort out the consequences. As the above suggests, the light attribute means that an object is giving off light, or that a room is currently lit (e.g. by being out of doors in day-time). If you simply never want to have darkness, and some games never do, a sneaky way of doing it is to put the line give player light; in Initialise. The game works as if the player herself were glowing enough to provide light to see by. So there's never darkness near the player. The definition of ''when there is light" is complicated, involving recursion both up and down. Remember that the parent of the player object may not be a room; it may be, say, a red car whose parent is a room. There is light exactly when the parent of the player 'offers light'. An object 'offers light' if: it itself has the light attribute set, or any of its immediate possessions 'have light', or it is see-through and its parent offers light, or it is enterable and its parent offers light; while an object 'has light' if: it currently has the light attribute set, or it is see-through and one of its immediate possessions has light. The process of checking this stops as soon as light is discovered. The routines OffersLight(object) and HasLightSource(object) which return true or false and have no side effects (that is, they merely report on the current state without changing anything), are available and might occasionally be useful. ** So light is cast up and down the tree of objects. In certain contrived circumstances this might be troublesome: perhaps an opaque box, whose outside is fluorescent but whose interior is dark, and which contains an actor who needs not to have other contents of the box in scope... 'contrived' being the word. The dilemma could be solved by putting an inner box in the outer one. Exercise: How would you code a troll who is afraid of the dark, and needs to be bribed but will only accept 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? Answer: Just test if HasLightSource(gift)==1. Each turn, light is reconsidered. 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 room into another dark one. If you want, you can 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 freely (subject to any rules which apply in the places concerned). +-----------------------------------------------------------------------------+ | 18 On inventories and lists | +-----------------------------------------------------------------------------+ 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 The library often needs to reel off a list of objects: when an Inv (inventory) action takes place, for instance, or when describing the contents of a container or a room. Lists are difficult to print out correctly 'by hand', because there are many cases to get right (especially when taking plurals into account). Fortunately, the library's list-maker is open to the public. The routine to call is: 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 the style, which is a bitmap you can make by adding up some of the following constants: NEWLINE_BIT New-line after each entry INDENT_BIT Indent each entry by depth FULLINV_BIT Full inventory information after entry ENGLISH_BIT English sentence style, with commas and 'and' RECURSE_BIT Recurse downwards with usual rules ALWAYS_BIT Always recurse downwards TERSE_BIT More terse English style PARTINV_BIT Only brief inventory information after entry DEFART_BIT Use the definite article in list WORKFLAG_BIT At top level (only), only list objects which have the workflag attribute The best way to use this 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). The workflag is an attribute which the library scribbles over from time to time as temporary storage, but you can use it with care. In this case it makes it possible to specify any reasonable list. Exercise: 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. Answer: [ DoubleInvSub i count1 count2; print "You are carrying "; objectloop (i in player) { if (i hasnt worn) { give i workflag; count1++; } else { give i ~workflag; count2++; } } if (count1==0) print "nothing."; else WriteListFrom(child(player), FULLINV_BIT + ENGLISH_BIT + RECURSE_BIT + WORKFLAG_BIT); if (count2==0) "."; print ". In addition, you are wearing "; objectloop (i in player) { if (i hasnt worn) give i ~workflag; else give i workflag; } WriteListFrom(child(player), ENGLISH_BIT + RECURSE_BIT + WORKFLAG_BIT); "."; ]; Some objects ought to print something different when they appear in inventory lists: a wine bottle should say how much wine is left, for instance. By giving an object an invent routine, you can alter the usual rules. A typical inventory list looks like: >i You are carrying: a green cone a pair of hiking boots your satchel (which is open) a green cube and each line goes through two stages. Firstly, the object's indefinite article and name are printed. Secondly, little informative messages like ''(which is open)" are printed, and inventories are given for the contents of open containers. An object's invent routine gets two chances to interfere with this, one before each stage. So what happens is this: 1. The global variable inventory_stage is set to 1. 2. The invent routine is called (if there is one). If it returns true, stop here. 3. The indefinite article and short name are printed. 4. The global variable inventory_stage is set to 2. 5. The invent routine is called (if there is one). If it returns true, go to step 8. 6. One or more of the following are printed, as appropriate: (providing light) (being worn) (which is open) (which is closed) 7. If it is an open container, its contents are inventoried. 8. Linking text (such as a new-line or a comma) is printed, according to the current style. Note that if an invent routine does return true, then the contents are never reached. For example, here is an invent routine for a matchbook: 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)"; } ], (The rest of the matchbook definition is given in the 'Toyshop' example game.) ** ** Changing the rules at inventory_stage 1 is kept on for more peculiar effects (and is not really recommended). +-----------------------------------------------------------------------------+ | 19 The naming of names | +-----------------------------------------------------------------------------+ 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 Bulldust, coolamon, dashiki, fizgig, grungy, jirble, pachinko, poodle-faker, sharny, taghairm - Catachrestic words from Chambers English Dictionary Providing an invent routine doesn't work quite so well for the match as for the matchbook. You might try: invent [; if (inventory_stage==2) { if (self has light) print " (burning away)"; rtrue; } ], ** Returning true here is a trick to stop ''(providing light)" from appearing as well - after all, that's redundant. This does work but there's a better way, which is to change the object's short name itself, from ''unlit match" to ''lit match" say. So another property, called short_name, is available. Normally, the short name of an object is given in the header of the object definition. But if a short_name routine is given, then whenever the game wants to write out the name of the object, the following happens: 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 'real' short name (the one in the object header) is printed. So, as promised, here's the better way to code the match: short_name [; if (self has light) print "burning match"; else print "unlit match"; rtrue; ], Note that rooms can also be given a short_name routine, which might be useful to code, say, a grid of four hundred almost exactly similar locations, called ''Area 1" up to ''Area 400". ** Now, though, the player is going to want to type ''drop the burning match", or ''light the unlit match". (Indeed, there might be two matches, only one of which can be called ''unlit".) It's perfectly straightforward to alter the name property of an object (provided one remembers what it is: a list of (machine) words, being the byte addresses in memory of words in the dictionary) but, again, there is a better and more powerful way. This is to give the match object a parse_name routine. A parse_name routine is expected to try to match the text which the user has typed (available one word at a time from the NextWord() routine), and to return how many words it managed to match. It is required to go as far as it can, i.e., not to stop if the first word makes sense, but to 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 make the parser just do what it would normally do. For example: Nearby thing "weird thing" with parse_name [ i; while (NextWord()=='weird' or 'thing') i++; return i; ]; This definition duplicates (very nearly) what the parser would have done if the weird thing had been defined as: Nearby thing "weird thing" with name "weird" "thing"; Which isn't very useful. But the match can now be coded up with parse_name [ i j; if (self has light) j='burning'; else j='unlit'; while (NextWord()=='match' or j) i++; return i; ], so that ''burning" only applies when it is, and similarly ''unlit". (Actually the parser automatically recognises ''unlit" and ''lit", so this last part was unnecessary.) +-----------------------------------------------------------------------------+ | 20 Plural names and groups of similar objects | +-----------------------------------------------------------------------------+ Abiit ad plures. - Petronius (?-c. 66), Cena Trimalchionis A notorious problem 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. This is relatively easy in Inform, and only in really hard cases do you have to provide code. There are two problems to 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 gold_coin_class with name "gold" "coin", plural "gold coins"; (and similar silver and bronze coin classes here) Object bag "bag" with name "bag" has container open openable; Nearby co1 "gold coin" class gold_coin_class; Nearby co2 "gold coin" class gold_coin_class; Nearby co3 "gold coin" class gold_coin_class; Nearby co4 "silver coin" class silver_coin_class; Nearby co5 "silver coin" class silver_coin_class; Nearby co6 "bronze coin" class bronze_coin_class; 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, (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. ** Actually, the library is cleverer than this. What it groups together depends slightly on the context of the list it's writing out. When it's writing a list which prints out details of which objects are providing light, for instance (like an inventory), it won't group together two objects if one is lit and the other isn't. Similarly for objects with visible possessions or which can be worn. ** ** This all gets even more complicated when the objects have a parse_name routine supplied, because then the library can't use the name fields to tell them apart. If they have different parse_name routines, it decides that they're different. But if they have the same parse_name routine, there is no alternative but to ask them. What happens is that 1. A variable called parser_action is set to ##TheSame; 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. (##TheSame is a fake action.) The implementation of the 'Spellbreaker cubes' in the 'Balances' game is an example of such a routine (so that if the player writes the same name on several of the cubes, they become grouped together). Note that this whole set-up is such that if the author of the parse_name routine has never read this paragraph, it doesn't matter and the usual rules take their course. ** ** You may even want to provide a parse_name routine just to speed up the process of telling two objects apart - if there were 30 gold coins the parser will be doing a lot of work comparing all their names, but you can make the decision much faster. Secondly, the player talking to the computer. This goes a little further than just copies of the same object: many games involve collecting a number of similar items, say 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 even though the crowns are not identical. The simple way to do this is just to put "crowns" in their name fields, and this works perfectly well most of the time. ** ** But it isn't ideal, because then the parser will think > drop crowns refers to a single object, and won't deduce that the player wants to pick up all the sensibly available crowns. So the complicated (but better way) is 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 fake action). So, for example: Class crown_class with parse_name [ i j; for (::) { j=NextWord(); if (j=='crown' or j==self.name) i++; else { if (j=='crowns') { parser_action=##PluralFound; i++; } else return i; } ]; Exercise: Write a 'cherub' class so that if the player tries to call them ''cherubs", a message like ''I'll let this go by for now, but the plural of cherub is cherubim" appears. Answer: Global c_warned = 0; Class cherub_class with parse_name [ i j flag; for (flag=1:flag==1:flag=0) { j=NextWord(); if (j=='cherub' or j==self.name) flag=1; if (j=='cherubs' && c_warned==0) { c_warned=1; parser_action=##PluralFound; flag=1; print "(I'll let this go once, but the plural of cherub is cherubim.)^"; } if (j=='cherubim') { parser_action=##PluralFound; flag=1; } i++; } return i-1; ]; Then again, Shakespeare even writes ''cherubins'' in Twelfth Night, so who are we to censure? +-----------------------------------------------------------------------------+ | 21 Miscellaneous constants and scoring | +-----------------------------------------------------------------------------+ Some game rules can be altered by defining 'constants' at the start of the program, before the library files are included. Two constants it must provide are the strings Story and Headline: Constant Story "ZORK II"; Constant Headline "^An Interactive Plagiarism^\ Copyright (c) 1993 by Ivan O. Ideas.^"; All the rest are optional. The library won't allow the player to carry an indefinite number of objects: the limit allowed is the constant MAX_CARRIED, which you may define if you wish. If you don't define it, it's 100, which roughly removes the rule. (In fact you can change this 'live', in that it is actually player.capacity which is consulted; the only use of MAX_CARRIED is to set this up to an initial value.) If you define SACK_OBJECT to be some container, then the player will automatically put old, least-used objects away in it as the game progresses, provided it is being carried. This is a feature which endears the designer greatly to players. Another constant is AMUSING_PROVIDED. If you define this, the library knows to put an ''amusing" option on the menu after the game is won. It will then call Amusing() from your code when needed. You can use this to roll closing credits, or tell the player various strange things about the game, now that there's no surprise left to spoil. The other constants you are allowed to define help the score routines along. 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 score variable in any case.) One scores points for getting certain items or reaching certain places; the other for completing certain actions. The constants are: MAX_SCORE The maximum game score (by default 0) NUMBER_TASKS Number of individual ''tasks" to perform (1) OBJECT_SCORE Bonus for first picking up a scored object (4) ROOM_SCORE Bonus for first entering a scored room (5) and then the individual tasks have scores, as follows: Global task_scores initial t1 t2 ... tn; Within your code, when a player achieves something, call Achieved(task) to mark that the task has been completed. It will only award points if this task has not been completed before. There do not have to be any ''tasks": there's no need to use the scoring system provided. Tasks (and the verb ''full" for full score) will only work at all if you define the constant TASKS_PROVIDED. A routine called PrintRank, which you can provide if you want, gets the chance to print something additional to the score, such as rankings. For instance: [ PrintRank; print ", earning you the rank of "; if (score >= 348) "Grandmaster Adventurer!"; if (score >= 330) "Master, first class."; if (score >= 300) "Master, second class."; if (score >= 200) "Junior Master."; if (score >= 130) "Seasoned Adventurer."; if (score >= 100) "Experienced Adventurer."; if (score >= 35) "Adventurer."; if (score >= 10) "Novice."; "Amateur."; ]; PrintTaskName prints the name of a game task (such as ''driving the car"). Of course, this is only ever called in a game with TASKS_PROVIDED defined. For instance, [ PrintTaskName ach; if (ach==0) "eating a sweet"; if (ach==1) "driving the car"; if (ach==2) "shutting out the draught"; if (ach==3) "building a tower of four"; if (ach==4) "seeing which way the mantelpiece leans"; ]; Normally, an Inform game will print messages like [Your score has gone up by three points.] when the score changes (by whatever means). The player can turn this on and off with the ''notify'' verb; by default it is on. You can alter the flag notify_mode yourself to control this. Sometimes you want to ask the player a yes/no question. If so, call YesOrNo(), a library routine: it returns true/false accordingly (and doesn't print any question, which is up to you). This saves a lot of bother programming the parser. ** ** Occasionally you need to rewrite one of the library routines. But the danger of doing so is that it is then necessary to keep a copy of the library for every game, which is clearly unsatisfactory. So: 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, or Inform will complain that the program refers to a routine which isn't there.) And this is the very last resort, and so the end of the manual proper. +-----------------------------------------------------------------------------+ | 22 Debugging verbs and tracing | +-----------------------------------------------------------------------------+ If builders built buildings the way programmers write programs, the first woodpecker that came along would destroy civilisation. - old computing adage The two problems with debugging a game are working out exactly what it's doing, and making things happen which wouldn't normally be allowed (such as giving yourself the trident now rather than playing for two hours to find it). Inform provides a small suite of debugging verbs to help with this, but only if the game defines the constant DEBUG before including the library files. (Just in case you forget to take this out again, the letter D appears in the game banner to indicate this.) You then get the following verbs, which can be used at any time in play: purloin abstract to tree tree goto actions actions on actions off routines routines on routines off timers timers on timers off trace trace on trace off trace <1 to 5> You can ''purloin" any item or items in your game at any time, wherever you are. You can likewise ''abstract" any item to any other item (meaning: move it to the other item). To get a listing of the objects in the game and how they contain each other, use ''tree", and to see the possessions of one of them alone, use ''tree ". Finally, you can go to any object: but since rooms don't have names understood by the parser, you have to give the object number of the place you want to go to. (The ''tree" listing will tell you these numbers.) Turning on ''actions" gives a trace of all the actions which take place in the game (the parser's, the library's or yours); turning on ''routines" traces every object routine (such as before or life) that is ever called, except for short_name (as this would look chaotic, especially on the status line). Turning on ''timers'' shows the state of all active timers and daemons each turn. Infix, Dilip Sequeira's source-level debugger for Inform, is currently in an advanced state of preparation: it is an enhanced form of Mark Howell's Zip interpreter providing for breakpoints, tracing and so forth. It should fairly soon be publically archived with the rest of the Inform project. ** For Infix's benefit, Inform (if compiling with the option set) produces a file of ''debugging information'' (cross-references of the game file with the source code), and anyone interested in writing an Inform utility program may want to know the format of this file: see the short C program Infact which prints out the debugging information file in English. On most interpreters, though, run-time crashes are mysterious (because they were written on the assumption that all existing Infocom game files were free from error). Zip is rather more generous and will usually tell you why and where the problem is; given a game file address you can work back to the problem point in the source either with Mark Howell's txd (disassembler) or by running Inform with the assembler trace option on. Here are all the ways I know to crash an interpreter at run-time (with high-level Inform code, that is; if you insist on using assembly language or the indirect function you're raising the stakes), arranged in decreasing order of likelihood: (o) Writing to a property of an object which it hasn't got; (o) Dividing by zero, possibly by calling random(0); (o) Giving a string or numerical value for a property which can only legally hold a routine, such as before, after or life; (o) Applying parent, child or children to the nothing object; (o) Using print object on the nothing object, or for some object which doesn't exist (always use the routines DefArt, CDefArt or PrintShortName as appropriate, which are safeguarded and anyway implement higher-level features); (o) Using print_addr or print_paddr to print from an address outside the memory map of the game file, or an address at which no string is present (this will result in random text appearing, possibly including unprintable characters, which may conceivably crash your terminal); (o) Setting the location variable to zero, or some non-existent object, since the interpreter prints the name of this object on the status line; in any case it is safer to use PlayerTo than to meddle directly with this; (o) Running out of stack space in a recursive loop (though this has never actually happened to anyone I know of). ** There are times when it's hard to work out what the parser is up to and why (actually, most times are like this). 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 Grammar lines Level 2 Individual tokens Level 3 Object list parsing 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 five" can produce reams of text when you try anything at all complicated: but you do sometimes want to see it, to get a list of exactly everything that is in scope and when. There are two levels lower than that but they're too busy doing dull spade-work to waste time on looking at parser_trace. (There's also a level 0, but it consists mostly of making arrangements for level 1, and isn't very interesting.) ** ** Finally, though this is a drastic measure, you can always compile your game -g ('debugging code') which gives a listing of every routine ever called and their parameters. This produces an enormous mel'ee of output. (In Inform 5.3, though not earlier versions, you can declare a routine with an asterisk * as its first local variable, which produces such tracing only for that one routine.) +-----------------------------------------------------------------------------+ | 23 Limitations on the run-time format | +-----------------------------------------------------------------------------+ How wide the limits stand Between a splendid and an happy land. - Oliver Goldsmith (1728-1774), The Deserted Village The Infocom run-time format is well-designed, and has three major advantages: it is compact, widely portable and can be quickly executed. Nevertheless, like any rigidly defined format (which it clearly must be), it imposes limitations. These are not by any means pressing. Inform itself has a flexible enough memory-management system not to impose artificial limits on numbers of objects and the like. There are two sizes of game available: Standard (or version 3) and Advanced (or version 5). Always use the latter if you can; it is very much to be preferred. (There are very slight risks associated with exotic assembly-language coding in Advanced games: but unless you deviate far from normal Inform practice, and code on a very low level, these will never trouble you.) There are still 8-bit computers in use which are too small for Advanced games, but most people should have no problem. Memory. The total size of a Standard game is at most 128K; of an Advanced game, 256K. Because games are encoded in a very compressed form, and because the centralised library of Inform makes the compiler fairly efficient in terms of not duplicating code, even 128K allows for a game at least half as large again as a typical old-style Infocom game. No game yet written under Inform has reached the 256K mark, not even the final version of 'Curses' (at 224K, already substantially bigger and denser than any Infocom game). Vocabulary. There is no theoretical limit, and vocabularies in excess of 2000 are quite sensibly possible. (A typical large game will muster 1000.) Dictionary resolution. In Standard games, dictionary words are truncated to their first 6 letters, which can be a nuisance; in Advanced, 9 letters, which never is. Attributes, properties, names. Standard games only provide 32 attributes, 30 properties and at most 8 bytes of data per property: the library consumes most of this ration, and the 8 bytes restriction means that an object can only have four words in its name. This is perfectly manageable, if unsatisfactory. Advanced games, which provide 48 attributes, 62 properties and 64 bytes of data (hence 32 names), effectively make none of these restrictions serious. Special effects. Standard games cannot have special effects such as bold face and underlining. (See the next two sections.) Objects. Standard games can have at most 255 objects, enough for a large Infocom-size game but not much more. Advanced games have no such restriction, the number being in principle unlimited. Objects cannot dynamically be created or destroyed, but this is imitated easily enough. Global variables. There can only be 240 of these, and the Inform compiler uses 5 as scratch space, while the library uses slightly over 100; but since a typical game uses only a dozen of its own, code being almost always object-oriented, the restriction is never even nearly felt. (Recall that arrays take only one global each.) ''Undo''. Standard games do not provide an ''undo'' verb, but Advanced ones (or rather, the Inform library when compiled on such) do. ** There is a yet larger Infocom format, version 6, which lifts even the mild restrictions of version 5 games, with memory maps of up to 600K or so: version 6 has only recently been fully deciphered, but Inform is already capable of producing such files, and Mark Howell's ''Zip'' interpreter is (at time of writing) making good progress in running them. A 600K Inform game would be gargantuan: by the time one comes along, there should be no problem in running it. ** ** Having said all this, if memory does become short, there is a standard mechanism for saving about 8-10% of the memory. Inform does not usually trouble to, since there's very seldom the need, and it makes the compiler run about 10% slower. What you need to do is define abbreviations. For instance, the directive Abbreviate " the "; (placed before any text appears) will cause the string '' the '' to be internally stored as a single 'letter', saving memory every time it occurs (about 2500 times in 'Curses', for instance). You can have up to 64 abbreviations. A good list of abbreviations can be found in the Technical Manual: basically, avoid proper nouns and instead pick on short combinations of a space and common two- or three-letter blocks. You can even get Inform to work out by itself what a good stock of abbreviations would be: but be warned, this makes the compiler run about 29000% slower. +-----------------------------------------------------------------------------+ | 24 A very few, not very special effects | +-----------------------------------------------------------------------------+ Yes, all right, I won't do the menu... I don't think you realise how long it takes to do the menu, but no, it doesn't matter, I'll hang the picture now. If the menus are late for lunch it doesn't matter, the guests can all come and look at the picture till they are ready, right? - John Cleese and Connie Booth, Fawlty Towers As the previous section suggested, some special effects are not to be encouraged: they reduce portability a little, and anyway Inform is for text games. 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; is present, in which case it shows the game's 24-hour clock. ** ** If you really want to change this, then Replace the parser's private DrawStatusLine routine. But be sure to test the result on more than one interpreter, in case you've made a mistake which doesn't show up on one quirky implementation. (In any case, you can only change it at all on Advanced games.) ** ** Exercise: Alter the 'Advent' example game to display the number of treasures found instead of the score and turns on the status line. Answer: First put the directive Replace DrawStatusLine; before including the library. Then add the following routine anywhere after treasures_found, an 'Advent' variable, is defined: [ DrawStatusLine; @split_window 1; @set_window 1; @set_cursor 1 1; style reverse; spaces (0->33)-1; @set_cursor 1 2; PrintShortName(location); if (treasures_found > 0) { @set_cursor 1 50; print "Treasure: ", treasures_found; } @set_cursor 1 1; style roman; @set_window 0; ]; About character graphic drawings: on some machines, text will by default be displayed in a proportional font (i.e. one in which the width of a letter depends on what it is, so that for example an 'i' will be narrower than an 'm'). If you want to display a diagram made up of letters, such as a map, you will have to turn this off, for which the font command is provided. Write font off before printing character graphics, and remember to write font on again afterwards. !! Don't turn the font on and off in the middle of a line; this doesn't look right on some machines. A distinctive feature of later Infocom games was their use of epigrams. Inform can do this, too, but only for Advanced games. The assembly language required is easy but a nuisance, so there is a command to do it, box. For example, box "Beware of the Dog"; or 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"; Note that a string of lines is given (without intervening commas) and that a blank line is given by a null string. Remember that the text cannot be too wide or it will look awful on a small screen. The author takes the view that this device is amusing for irrelevant quotations but irritating when it conveys vital information (such as, ''Beware of the Dog", for instance). Also, some people might be running your game on a laptop with a vertically challenged screen, so it is polite to provide a ''quotes off'' verb. Sometimes one would like to provide a menu of text options (for instance, when producing instructions which have several topics, or when giving clues). This can be done with the DoMenu routine, which imitates the traditional ''Invisiclues'' style. However, it only looks good for Advanced games; for Standard games, it's simply textual. (In an Advanced game, by setting pretty_flag=0 you can get this simple text version instead; which is a good idea for machines with very small screens.) Here is a typical call to DoMenu: DoMenu("There is information provided on the following:^\ ^ Instructions for playing\ ^ The history of this game\ ^ Credits^", #r$HelpMenu, #r$HelpInfo); Note the layout, and especially the carriage returns. The second and third arguments are themselves routines: the notation #r$, seldom seen in high-level Inform, allows routine names to become ordinary numerical values. The first routine, in this case called HelpMenu, is supposed to look at the variable menu_item. In the case when this is zero, it should return the number of entries in the menu (3 in the example). In any case it should set item_name to the title for the page of information for that item; and item_width to half its length in characters (this is used to centre titles on the screen: it is unfortunately tricky for the game to compute the length of a static string, so you have to tell it). In the case of item 0, the title should be that for the whole menu. The second routine, HelpInfo above, should simply look at menu_item (1 to 3 above) and print the text for that selection. After this returns, normally the game prints ''Press [Space] to return to menu'' but if the value 2 is returned it doesn't wait. Menu items can safely launch whole new menus, and it is easy to make a tree of these (which will be needed when it comes to providing hints across any size of game). Exercise: Code an ''Invisiclues''-style sequence of hints for a puzzle, revealed one at a time, as a menu item. Answer: Note the magic line of assembly code here, which only works for Advanced games: [ GiveHint hint keypress; print_paddr hint; new_line; new_line; @read_char 1 0 0 keypress; if (keypress == 'H' or 'h') rfalse; rtrue; ]; And a typical menu item using it: if (menu_item==1) { print "(Press ENTER to return to menu, or H for another hint.)^^"; if (GiveHint("(1/3) What kind of bird is it, exactly?")==1) return 2; if (GiveHint("(2/3) Magpies are attracted by shiny items.")==1) return 2; "(3/3) Wave at the magpie with the kitchen foil."; } Finally, though it should only be used sparingly (and again is restricted to Advanced games), one can change the text style. The command for this is style and its effects are loosely modelled on the VT100 (design of terminal). The style can be style roman, style bold, style reverse or style underline. Again, remember that poor terminals may not be able to display these, so you shouldn't hide crucial information in them. +-----------------------------------------------------------------------------+ | 25 Dirty tricks | +-----------------------------------------------------------------------------+ ** ** The dirtiest tricks of all are those which bypass Inform's higher levels and program the Z-machine directly. There is an element of danger in assembly language programming, in that some combinations of unusual opcodes can look ugly on some incomplete or wrongly-written interpreters: so if you're doing anything complicated, test it as widely as possible. Note that none of the interesting effects work at all on Standard games, only Advanced games. The best-researched and most reliable interpreter available by far is Mark Howell's Zip; as it's also the fastest, it will hopefully 'take over' entirely. Next comes the InfoTaskForce, which is thorough and should give no serious trouble, but was written when the format was a little less well understood, and so (in some ports) gets some (rare) screen effects wrong. (It also lacks an ''undo'' feature, so the ''undo'' verb automatically provided by the library routines won't work under ITF.) The other two publically-available interpreters are pinfocom and zterp, but these are unable to run Advanced games. In the last resort, sometimes it's possible to use one of Infocom's own supplied interpreters with a different game from that it came with; but only sometimes, as they may have inconvenient filenames 'wired into them'. The author recommends that anyone using assembly-language features get hold of both ITF and Zip, and test on both. This all sounds rather unportable, though actually the core described below is pretty reliable. But remember that the unportability does have some genuine cause. Your game may be running on a screen which is anything from a 64 by 9 pocket organiser up to a 132 by 48 X-window. Anyone wanting to really push the outer limits (say, by implementing Space Invaders or NetHack) will need to refer to The Specification of the Z-Machine, which also documents Inform assembly language format. Screen tricks are the commonest. An upper-level screen (which usually holds the status line) can be split off from the main screen: split_window n; creates one which is $n$ lines tall. (This doesn't change the display, and it can be resized at any time: but it needs to be tall enough to include all the lines you want to write to, as otherwise the interpreter may flounder: some will scroll the upper window, others won't.) The main screen is numbered 0, and the upper one 1; text output is switched between them by set_window n; The lower window is just a text stream whose cursor position cannot be set: on the other hand, when it is returned to, the cursor will be where it was before it was left. Within the upper window, the cursor can be moved by set_cursor line column; where $(1,1)$ is the top left character. Printing on the upper window overlies printing on the lower, is always done in a fixed-space font and does not appear in a printed transcript of the game. However, before printing to the upper window, it is essential to change the printing format - that is, the buffer_mode opcode. Before printing, execute buffer_mode 0; and when returning to the normal screen, buffer_mode 1; Otherwise, dodgy interpreters (like ITF) may continue trying to split lines at word breaks, and make a horrid mess. A convenient way to clear the screen is erase_window $ffff; but don't chance this in reverse video mode! (And don't assume that erase_window can erase individual windows - it should, but may not on bad interpreters.) Players can be gratuitously annoyed (on most machines, anyway) by the beep opcode. This is the only remotely portable sound effect. The keyboard can be read in remarkably flexible ways, using the aread and read_char opcodes. aread text parse time function; will read from the keyboard, putting text into the text buffer, tokenising it onto the end of the parse buffer (unless this is zero), and calling function(time); every time seconds that the user is thinking: the process ends if ever this function returns true. Thus (by Replaceing the Keyboard routine in the library files) you could, say, move around all the characters every ten seconds of real time. read_char 1 time function ; where is a variable, will store in that variable the ASCII value of a single keypress. Once again, the function is called every time seconds and may stop this process early. Function keys return special values from 129 onwards, in the order: cursor up, down, left, right, function key f1, ..., f12, keypad digit 0, ..., 9. Leafing through the dictionary of opcodes will reveal a few other interesting features. It's possible to change the input and output streams which, although only Zip gets this right, may be convenient for debugging purposes (creating scripts of all typed commands, for example). Finally, there are opcodes which tokenise (that is, simply compare dictionary entries against) arbitrary strings from arbitrary dictionaries, and which translate small doses of ASCII to internal Z-machine string format. Actually, one can avoid the need for this in many cases, by programming the parser correctly: see, for instance, the 'Balances' game which manages without these features. +-----------------------------------------------------------------------------+ | A1 The Inform language | +-----------------------------------------------------------------------------+ This is going to be a long appendix, full of lists and tables: but it has to appear somewhere, if only for reference. (Some technical commands for internal use only are skipped over: see the Technical Manual for details of these.) File format When Inform reads in a file, it treats a few characters in special ways. The character ! means the rest of the line (up to the next new-line) is a comment, and Inform throws it away. Tab characters are treated as spaces. Backslashes \ fold strings together, so that the new-line and all subsequent spaces are ignored. New-lines have no significance; statements (and directives) are separated by semicolons ;. Directives These are commands directly to the Inform compiler, like Object. They can, but need not, be prefaced by a hash character, #. The directives which Inform understands are: Abbreviate Declare an abbreviation Attribute Make a new attribute Class ... Define a new class Constant Define a named constant End End compilation here Endif End of conditional compilation Extend ... Extend the grammar for an existing verb Fake_action Make a new ''fake action'' Global ... Declare a global variable Ifdef Compile only if constant is defined Ifndef Compile only if constant is undefined Ifnot Compile only if previous If... failed Ifv3 Compile only for Standard games Ifv5 Compile only for Advanced games Include Include that file here Nearby Make an object inside the last Object Object Make an object Property ... Make a new property Replace Don't compile this library routine Release Set the game's Release Number Serial Set the game's Serial Number Statusline ... Make the status line show score or time Switches Set default compilation switches Verb ... Declare the grammar for a new verb The release number of a game (by default 1) is generally an edition number; the serial number is the compilation date in the form 940924, that is, yymmdd. Inform sets this automatically on machines where the date is accessible, so the Serial directive is provided only for use on machines without such an internal clock. ** Conditional compilation allows code for routines which need only exist in some ''versions" of your games. For instance, print "Welcome to the "; #IFV3; print "Standard"; #IFNOT; print "Advanced"; #ENDIF; print " version of Zork LVI."; (The #IFNOT clause is optional.) Note the trailing semicolon: Inform is not C! Such clauses may be nested up to 32 deep, and may contain whole routines. They may not, however, conditionally give part of a statement. Thus, for instance, print #IFV3; "Standard"; #IFNOT; "Advanced"; #ENDIF; is not legal. ** ** The following directives are recondite and not for public use: Default Dictionary Listsymbols Listdict Listverbs Lowstring Stub System_file Trace Btrace Etrace Ltrace Notrace Nobtrace Noetrace Noltrace Variables and arrays There are two kinds of variable, global and local (plus one special one). Variables are all two-byte integers, which are treated as signed when it makes sense to do so (e.g. in asking whether one is positive or not) but not when it doesn't (e.g. when it is used as an address). Global variables must be declared before use, by the Global directive: Global = data string initial ... initstr For instance: Global turns = 1; Global buffer string 120; ! text buffer holding 120 characters Global task_scores initial 4 5 9 1 2 3 0; ! a 7-byte array Global players_name initstr "Graham"; ! an array of 6 chars When you declare a variable as an array (by data, string, initial or initstr) what actually happens is that Inform allocates as much space as you ask for, somewhere inside the machine, and stores the address of this array in the variable. You can get at entries of the array by buffer->entry buffer-->entry which read (or write to) the entry-th byte (in the case of ->) or word (for -->). A data array is initially full of zeros, while an initial array contains the given (byte) values, which all have to be constants for obvious reasons. A string array is a special kind of data array: the first byte contains its length (in bytes), and the rest are initially zero. initstr is the same but initialised to the given string. The text here is plain ASCII, and is not encrypted as constant strings tend to be. (This string format is used by the parser.) In addition, a routine can have from none up to 15 local variables. !! There is also a stack, but it should be tampered with only carefully in times of dire need. Never call a variable sp, as this is the stack pointer, and if you must use the stack at all, be careful not to leave values on it: or your game may crash 1000 turns later, serving you right. Constants Inform constants can take many forms. The obvious ones are numbers, 123 $ee05 $$11011001 being examples in decimal, hexadecimal and binary respectively. There are also ##Action (The number of) the given action "some text" (The packed address of) the given string 'c' (The ASCII code for) the given character 'word' (The byte address of) its dictionary entry There is slight potential for confusion here: 'A' evaluates to 65, but 'an' to the dictionary address of the word 'an'. Note that the construction 'sponge' actually enters the word 'sponge' into the dictionary if it wasn't already there. These are all legal constants: 31415 -1 $ff $$1001001 'lantern' ##Look 'X' "an emerald the size of a plover's egg" "~Hello,~ said Peter.^~Hello, Peter,~ said Jane.^" Quoted strings Inside the text of a string, the character ^ is replaced by a new-line character, and the character ~ by a double-quote mark: these both make strings much easier to type. Inside a string (under Inform 5.3 or later) @@ produces the character whose ASCII value is $n$, and you can use this to get untypeable characters from foreign character sets. (Or, for example, a literal backslash, by @@92.) For the @ string escape and other obscure constant forms such as #r$, see the Technical Manual. Routines Routines start with a [ and end with a ]. That is, they open with [ ... ; giving the names of local variables for the routine ($0 <= n <= 15$). The routine ends with just ];. (Routines embedded in object definitions are the same, except that no routine-name is given, and they may end in ], if the object definition continues.) The first few local variables also hold the arguments passed to the routine when the function is called. That is, if you have a routine [ Look from i j; ...some code...; ]; and it is called by Look(attic); then the local variable from will initially have the value attic. The rest all start out at zero. From Inform 5.3, if the first variable is given as * then tracing code is compiled to print details each time the routine is called. Function calls (that is, calls to routines) are legal with between 0 and 3 arguments, and every routine always returns a value. If execution runs into the ]; at the bottom, that value is 'true' (or 1) for an ordinary routine, or 'false' (or 0) for an embedded one. Labels In case you want to jump around inside a routine, you can define labels with a statement starting with a full stop, like .labelname;. It is legal, though ill-advised, to jump out of one routine and into another. Label names are global, so the same label name can't be used in two different routines. Operators Arithmetic (and other) expressions can contain the following: + - plus, minus * / % & | times, divide, remainder, bitwise and, bitwise or -> --> byte array, word array entry . .& .# property, property address, property length - unary minus ++ -- incrementing and decrementing variables (as in C) The order of precedence is as shown: i.e. those on each line are equally potent, more potent than those above but less than those beneath. Expressions are not allowed to contain conditions, nor assignments: 2+(i=3/j) is not a legal expression. Some legal examples: 4*(x+3/y) Fish(x)+Fowl(y) lamp.time buffer->5 Note that ++ and -- can only be applied to variables, not to properties or array entries. Assignments There are four forms allowed are: = ; -> = ; --> = ; . = ; For example: i=-15-j; i=j-->1; albatross.weight = albatross.weight + 1; (paintpot.&roomlist)-->i = location; turns++; One can also inc or dec (increment or decrement) a variable, with inc score; being equivalent to the more modern score++; Although these look logical, they are not allowed: paintpot.#roomlist = 5; paintpot.&roomlist = array; because one cannot change the size or address of a property in play. !! A division by zero error (such as $n$/0 or $n$%0) may crash the game at run time. !! Attempting to write to a property which an object does not have may crash the game at run time. Conditions A simple condition is where the relation is one of == a equals b ~= a doesn't equal b < > >= <= numeric (signed) comparisons has object a has attribute b hasnt object a hasnt attribute b in object a is currently held by object b notin ...is not... With == (and ~=) only, one may also write the useful construction == [or [or ]] which is true if the first something is any of the values given. (An idiosyncracy of Inform, for 'hardware reasons', is that you can only have three). Conditions can be combined by the && and || operators: && || true if both, or either (respectively) are true. These are always tested left to right until the outcome is known. So, for instance, i==1 || Explode(2)==2 does not call Explode if i is 2. Examples of legal conditions are: i==1 or 2 or 3 door has open || (door has locked && key in player) Built-in functions A very few functions are built into the language of Inform itself (rather than written out longhand in the library files), but they behave like any other routines. They are: parent(obj) parent of object sibling(obj) sibling of object child(obj) eldest child of object children(obj) number of (direct) children of object eldest(obj) same as child youngest(obj) youngest child of object elder(obj) elder sibling of object younger(obj) same as sibling random(x) uniformly random number between 1 and $x$ indirect(addr) call routine with address addr !! random(0) may cause a division by zero error. Printing commands A string on its own, such as "The world explodes in a puff of garlic."; is printed, with a new-line, and the current routine is returned from with return value 'true', i.e., 1. In addition: new_line prints a new-line print ... prints the given things print_ret ... prints, new-lines and returns 1 spaces n prints $n$ spaces print_addr a print string at byte address a print_paddr a print string at packed address a font on/off turns proportional fonts on/off style ... in Advanced games, sets text style box "s1" ... "sn" in Advanced games, puts up quotation box inversion prints out the current Inform version number print and print_ret take a comma-separated list of things to print out, which can be: "strings", char n (print the character with this ASCII value) or variable (print out the variable as a signed number). The text style can be any of roman reverse bold underline Manipulating objects remove obj removes object from the tree move o1 to o2 moves o1 to become youngest child of o2 give obj a1 ... an gives attributes to obj Attributes beginning with a ~ are taken away rather than given. Returning from routines return Return true, i.e. 1 return x Return value $x$ rtrue Return true, i.e. 1 rfalse Return false, i.e. 0 Blocks of code A block of code may be a single instruction or a series of several, in which case it must be enclosed in braces { and }. Thus, for instance, in if (i==1) print "The water rises!"; if (i==2) { print "The water rises further..."; water++; } the if statements contain a block of code each. Blocks can be nested inside each other up to 32 deep. An if statement (for example) is a single statement even when it contains a great deal of code in its block: so, for example, if (i>1) if (water<10) "The water is beginning to worry you."; is legal. (One small exception: an if followed by an else counts as two statements, and this means Inform handles hanging elses rather poorly: so it's wise to brace so that else is clearly unambiguous.) Control constructs Inform provides: if [else ] while do until for (::) objectloop ( in ) objectloop ( from ) objectloop ( near ) break jump