Roger Firth's IF pages | ![]() |
Inform 6: Frequently Asked Questions | ![]() |
![]() |
Operating |
These topics are about defining Inform objects:
Is a 'room' a special sort of object?
How does Inform distinguish nouns from adjectives?
When should I use scenery/static/concealed attributes?
How can I tell that one object is 'within' another?
The object tree
Moving objects around
How do I get rid of an object in mid-game?
What are the various 'description' properties for?
How are plural objects managed?
Plural types and examples:
Gunpowder; Instructions; String of pearls; Sticks of dynamite; Gold coins; Fuses; Matches; Cage of birds
Why do my pronouns keep changing?
The MANUAL_PRONOUNS constant
How do I define a new object property?
Common versus local properties
Turning properties into arrays
How do I use my new object property?
Routine or string? doesn't matter
Do I need to understand private properties?
How do I define a new object attribute?
How can I use individual properties as attributes?
What does class inheritance do for me?
When is a Dynamic class useful?
How can I reconfigure the Player Character (PC)?
Why does my game crash when I use move in an objectloop?
objectloop formats
Can I loop through all of an object's dependents?
No: a 'room' is simply an Inform object which the player can enter by some means, and which doesn't have a parent. There’s nothing more to it than that.
It doesn't. In an object's name property, you need to list all of the words that a player might think to use when referring to that object. For example, in this object, introduced when talking about apostrophes and quotes:
More information in the DM: |
Object old_man "grumpy old man"
with name 'man' 'grumpy' 'cross' 'old' 'elderly' 'ancient' 'gnome',
description "The grumpy old man stands silently in the corner.",
...
has animate;
you'll see there are two nouns -- 'man' and 'gnome' -- and five adjectives. The player could use these seven words (also THE which is always available) in every possible combination, both sensible (GRUMPY OLD MAN, THE GNOME, THE ELDERLY MAN, CROSS GNOME) and not so sensible (THE ANCIENT CROSS, GRUMPY GRUMPY, MAN OLD ELDERLY ANCIENT GNOME). In all of these cases, Inform will infer that the player is referring to the "grumpy old man" object. As it happens, players have become accustomed to this behaviour, and don't find it much of a problem.
Occasionally, you'll create a set of objects with similar names, in which circumstance having Inform react to nouns more strongly than to adjectives is helpful to players. (Without such special treatment, your players may have difficulty picking up, for example, a JEWEL which occurs in the same location as a JEWEL BOX.) Neil Cerutti's pname.h from the Archive offers a good solution in these circumstances.
You affect an object's behaviour using these attributes. The main differences are:
attribute |
Does LOOK mention it? |
Can you TAKE it? |
Does TAKE ALL attempt it? |
|---|---|---|---|
scenery |
no |
no -- "hardly portable" |
yes (annoyingly) |
static |
yes |
no -- "fixed in place" |
yes |
concealed |
no |
yes -- listed in your inventory, but disappears again if you drop it |
no |
none of these |
yes |
yes |
yes |
One of the main uses of concealed is for something that can be 'discovered' during the game, for example when you SEARCH another object. Using this technique, you might use an after property on the object being searched to remove the hidden object's concealed attribute, thereby bringing it into view. The alternative method is to use that same after property to bring the hidden object into the room from elsewhere; this has the disadvantage that second-time-around players still need to SEARCH even though they know that the hidden object is present. (On the other hand, if you allow them this shortcut, you need to remove the concealed attribute anyhow if they do TAKE without SEARCH.)
(An alternative to the concealed attribute, useful only rarely, is to give the object this property: describe [; rtrue; ], -- the object behaves in much the same way, except that it's included in a TAKE ALL attempt.)
On the subject of scenery and static, you occasionally run into trouble with the parser being too helpful. If the player types a command like TAKE without specifying an object, the parser usually asks "What do you want to take?", which is fine. If, however, there is only one object in scope, the parser guesses that that must be what you want, even if it's non-takeable. For example:
>TAKE (the writing desk) The writing desk is too heavy for that.
The best way to avoid this is by ensuring that there's always a choice of objects with the same attribute level -- two without static/scenery, or failing that two with static, or as a last resort two with scenery.
A typical Inform program defines dozens of objects: rooms, furniture, pieces of scenery, Non-Player Characters (NPCs), artefacts of all shapes and sizes. Rather than floating independently in some abstract space, most of those objects are linked by formal (but fluid) relationships. An important part of Inform programming is managing the changes in those relationships: keeping track of which objects are 'in' or 'on' other objects at each turn in the game.
For example, you might visualize the relationship between several objects in a bank thus.
In this diagram, the money, diamond and certificate are in the strongbox,
which is in the safe (along with the will), and the safe is in the same
room as the counter, on which is a key.
It's common to talk about an object's parent and its children:
thus, the safe's parent is the bank, and its children are the strongbox and the will.
If the player was to enter the bank and type TAKE THE KEY, the diagram would need redrawing
to show the player as a child of the bank, and the key as a child of the player.
The diagram above shows a conceptual representation in which each object has a single parent object (or nothing) above it, and zero, one or more child objects below it. However, this representation doesn't precisely reflect the way in which Inform stores its data. The relationship between Inform objects -- the object tree -- is actually defined by three pointers. Each object has an 'upwards' pointer towards its parent, a 'downwards' pointer towards its child, and a 'sideways' pointer towards its sibling; a pointer contains the constant nothing if there is no object above, below or to the side.
This system of pointers means that the set of objects in the bank is actually connected as shown alongside. You might notice that there is a small discrepancy between the conceptual model -- in which an object like the strongbox can have several children -- and the physical model, where an object has one (eldest) child, with any younger children being linked out to the side. The built-in functions parent(), sibling() and child() reflect this physical view:
parent(bank) == nothing sibling(bank) == nothing child(bank) == counter parent(counter) == bank sibling(counter) == safe child(counter) == key parent(safe) == bank sibling(safe) == nothing child(safe) == strongbox
and so on.
You can change object relationships with the move and remove statements, but it's relatively rare that you need to do so -- most of the time, the Library does everything necessary to move objects around the tree. What you frequently do need is the ability to discover where an object is currently located. You can test whether one object is a child of another object -- its parent -- using either of:
if (parent(c_obj) == p_obj) { ... }
if (c_obj in p_obj) { ... }
You can't reliably use this test:
if (c_obj == child(p_obj)) { ... }
because the child() function returns only an object's eldest child, rather than a list of all children; for example (safe == child(bank)) evaluates to false.
The first two tests, although reliable, are also limited in scope: they concern themselves only with an object's immediate children. So, (parent(safe) == bank) and (safe in bank) both evaluate to true, but (parent(strongbox) == bank) and (strongbox in bank) both evaluate to false -- the strongbox object is a grandchild of the bank object, not a direct child.
To get round this problem, you can use the Library routine IndirectlyContains():
if (IndirectlyContains(p_obj, c_obj)) { ... }
This routine returns true if p_obj is a parent, or grandparent, or great-grandparent, or great-great-grandparent..., of c_obj; that is, if c_obj lies anywhere below p_obj in the tree. So, IndirectlyContains(bank,strongbox) evaluates to true.
A related routine, CommonAncestor(c_obj1, c_obj2), returns the nearest object which has a parental relationship to both c_obj1 and c_obj2, or nothing. For example, CommonAncestor(diamond,will) would return the safe.
More information in the DM: |
You don't actually delete an unwanted object during the course of a game; you just reposition it out of the way so that the player won't ever encounter it. You usually do this by making its parent nothing, which detaches it from the object tree. However, you can't type move myObj to nothing; since nothing isn't an object; instead you use:
remove myObj;
The removed object now lurks out of sight, until either the game ends or you choose to resurrect it. To bring the object back into play, you simply need something like:
move myObj to location;
That's really all there is to it, though while we're here we'll mention a few related points. You can test whether an object has been removed by either of these:
if (myObj in nothing) { ... }
if (parent(myObj) == nothing) { ... }
but what you can't so easily do is identify all of the removed objects. This is a valid statement:
objectloop (x && x in nothing) { ... }
but it finds too much: several Library objects and all of the rooms, as well as any objects which you've shifted out of play. If you need to loop through out-of-play objects, a better approach is not to use the remove statement. Instead, create an inaccessible room -- commonly called "Limbo" -- and move unwanted objects into it. This is all you need:
Object limbo "Limbo"
with description "How the hell did you get in here?";
...
move myObj to limbo;
...
objectloop (x in limbo) { ... }
More information in the DM: |
If you've given myObj a found_in property -- so that it magically pops up in various rooms as the player visits them -- you've got a little more work to do. If you just move or remove such an object out of play, the Library will promptly transport it back again. You need to prevent this, by applying the absent attribute:
move myObj to limbo; ! or: remove myObj; give MyObj absent;
More information in the DM: |
Finally, the rather more complex business of creating and deleting objects during play. We'll skip most of the detail and just sketch the outline of dynamic object manipulation. First, you need a class from which to create your objects (the "10" specifies the maximum number which can be in play concurrently):
Class myObj(10)
with ... ;
and then you use statements like these to add a new object to the game, and later to get rid of it again:
x = myObj.create(); ... myObj.destroy(x);
Note that destroy() works only for objects which have previously been added using create(), not for normal objects which have existed throughout the game.
Two of the more popular commands during a game are LOOK (or L) and EXAMINE or (X). Here's a typical example:
>LOOK Dingy hall Steps lead down into darkness. You can see a trunk (which is closed) and a flashlight here. >EXAMINE THE TRUNK The trunk is large enough to get inside.
Here you can see the ubiquitous 'description' property doing its stuff: LOOK displays the current location's name, its description and a list of its notable contents, while EXAMINE displays the description of the specified object:
Object hallway "Dingy hall"
with description "Steps lead down into darkness.",
d_to cellar,
has light;
Object -> "trunk"
with name 'trunk' 'chest' 'box',
description "The trunk is large enough to get inside.",
has static container enterable openable ~open;
Object -> "flashlight"
with name 'flashlight' 'torch',
description "It's a battery-powered flashlight, of the sort that switches on and off.",
after [;
SwitchOn: give self light;
SwitchOff: give self ~light;
],
has switchable ~on ~light;
More information in the DM: |
The library provides a number of standard object properties which are used
in specific circumstances to display descriptive information about the object;
description is the one most commonly found, but the others all have their place.
This table summarises their usage:
Property |
For a room |
For an object |
|---|---|---|
description |
The first text (after the room's name) displayed by LOOK in that room, and on initial entry to the room. |
Displayed by EXAMINE of the object. This is the only property which is consulted by the EXAMINE command; all of the other entries in this table are associated with the LOOK command. |
inside_description |
Displayed after the room's description text. |
For an enterable object which the player is inside, displayed after the parent room's description text (if the room is still visible) or instead of it (if not). Over-rides initial and when_open/when_closed. |
describe |
The first text (after the room's name) displayed by LOOK in that room, and on initial entry to the room. Over-rides description (but provides no additional functionality and so is effectively redundant for a room). |
Displayed after the parent room's description text. If this property is a string or a routine returning true, any of the properties below are ignored. |
initial |
Displayed before the room's name when the player moves to that room. |
For an object which hasn't moved, displayed after the parent room's description text. Once the object has moved, this property is ignored and the object is included in the "You can [also] see..." list. |
when_on |
Ignored. |
For a switchable object which has on, displayed after the parent room's description text. Over-rides initial. |
when_off |
Ignored. |
For a switchable object which hasn't on, displayed after the parent room's description text. Over-rides initial. |
when_open |
Ignored. |
For a door or container object which has open, displayed after the parent room's description text. Over-rides initial and when_on/when_off. |
when_closed |
Ignored. |
For a door or container object which hasn't open, displayed after the parent room's description text. Over-rides initial and when_on/when_off. |
none of these |
- |
Displayed in the list of "You can [also] see..." objects at the end of the room description (except for concealed or scenery objects). |
It's hard to predict the precise presentation -- trial and error is your best bet -- but as a rule-of-thumb, the library automatically prints a newline before each property value (except for describe), and also prints one after each property whose value is a string (if a property value is a routine, the terminating newline is under your control).
Here's our previous example, now ornamented with a few of these properties:
Object hallway "Dingy hall"
with description "Steps lead down into darkness.",
d_to cellar,
has light;
Object -> "trunk"
with name 'trunk' 'chest' 'box',
description "The trunk is large enough to get inside.",
inside_description "From the inside, the trunk doesn't now seem so big.",
when_open "An open trunk sits in one corner.",
when_closed "The trunk in the corner looks like it can be opened.",
has static container enterable openable ~open;
Object -> "flashlight"
with name 'flashlight' 'torch',
description "It's a battery-powered flashlight, of the sort that you switch on and off.",
after [;
SwitchOn: give self light;
SwitchOff: give self ~light;
],
when_on "The flashlight provides a faint glimmer.",
has switchable ~on ~light;
And this is the result:
>LOOK Dingy hall Steps lead down into darkness. The trunk in the corner looks like it can be opened. You can also see a flashlight here. >SWITCH ON THE FLASHLIGHT You switch the flashlight on. >OPEN THE TRUNK You open the trunk. >LOOK Dingy hall Steps lead down into darkness. An open trunk sits in one corner. The flashlight provides a faint glimmer. >ENTER TRUNK You get into the trunk. >LOOK Dingy hall (in the trunk) Steps lead down into darkness. The flashlight provides a faint glimmer. From the inside, the trunk doesn't now seem so big.
While most of your game objects will be singular -- A SHINY SWORD, AN ANCIENT BRASS LANTERN, THE GRUMPY OLD MAN -- you will occasionally need to represent plurals: multiple objects such as A HANDFUL OF BEANS, THREE GOLD COINS, A BRACE OF PHEASANTS. The Library provides several standard properties and attributes which are useful when dealing with plural objects; here are some of the basic techniques:
Mass noun (singular)
|
Referenced by the player as: |
Referenced by the game as: |
More information in the DM: |
Object -> "gunpowder"
with name 'gunpowder' 'blasting' 'powder',
article "some";
|
Commentary: |
||
Mass noun (plural)
|
Referenced by the player as: |
Referenced by the game as: |
|
Object -> "instructions" with name 'instructions', has pluralname; |
Commentary: Object -> "instructions"
with name 'sheet' 'of' 'instructions',
article "a sheet of",
has pluralname;
Here the player could type, for example, OPEN SHEET (which is singular)
and get the mismatched plural response "They're not something you can open.".
For a way round this, see the next example.
|
||
Singular/plural noun
|
Referenced by the player as: |
Referenced by the game as: |
|
Object ->
with name 'string' 'of' 'pearls' 'pearl',
parse_name [ wd num singular;
for (wd=NextWord() :
WordInProperty(wd,self,name) :
wd=NextWord()) {
num++;
if (wd == 'string') singular = true;
}
if (num) {
if (singular) give self ~pluralname;
else give self pluralname;
}
return num;
],
short_name [;
if (self has pluralname)
print "pearls on a string";
else print "string of pearls";
rtrue;
],
has ~pluralname;
|
Commentary: name 'string' 'of' 'pearls',
parse_name [ wd num;
for (wd=NextWord() :
wd=='string' or 'of' or 'pearls' :
wd=NextWord()) num++;
return num; ],
name 'string' 'of' 'pearls',
parse_name [ wd num;
for (wd=NextWord() :
WordInProperty(wd,self,name) :
wd=NextWord()) num++;
return num; ],
The advantage of a parse_name property is the capability
of recognising certain trigger words, and then modifying or extending the standard behaviour.
Here, EXAMINE STRING makes the object singular, while EXAMINE PEARLS makes it plural.
|
||
Enumerated set of indistinguishable nouns #1
|
Referenced by the player as: |
Referenced by the game as: |
More information in the DM: |
Class TNT
with name 'stick' 'sticks//p' 'of' 'dynamite' 'tnt',
short_name "stick of dynamite",
plural "sticks of dynamite";
TNT ->;
TNT ->;
TNT ->;
|
Commentary: |
||
Enumerated set of indistinguishable nouns #2
|
Referenced by the player as: |
Referenced by the game as: |
More information in the DM: |
Class Money(10)
with name 'money' 'cash' 'gold',
sing_name 'coin' 'dollar' 'zorkmid',
plur_name 'coins' 'dollars' 'zorkmids',
parse_name [ wd qty b p s;
qty = TryNumber(wn);
if (qty >= 0) { b++; wn++; }
wd = NextWord();
while (wd) {
if (WordInProperty(wd,self,name)) b++;
else
if (WordInProperty(wd,self,plur_name)) p++;
else
if (WordInProperty(wd,self,sing_name)) s++;
else break;
wd = NextWord();
}
if (qty < 0) ! TryNumber() didn't find a number
if (s > p) qty = 1;
else qty = self.current_qty;
if (p+s) self.request_qty = qty;
return b+p+s;
],
current_qty 0, ! Number of coins in this object
request_qty 0, ! Number requested by player
article [;
if (self.current_qty == 1) print "a single";
else print (number) self.current_qty;
rtrue;
],
short_name [;
if (self.current_qty == 1) print "gold coin";
else print "gold coins";
rtrue;
],
before [ o; ! Before any cmd for these coins
if (self.request_qty == 0)
self.request_qty = self.current_qty;
if (self.request_qty > self.current_qty)
return L__M(##Miscellany,42,self.current_qty);
if (self.request_qty < self.current_qty) {
o = self.current_qty - self.request_qty;
o = Money.create(o, parent(self));
if (o == nothing) "BUG: Money creation failed!";
self.current_qty = self.request_qty;
self.request_qty = 0;
}
],
each_turn [ o;
self.request_qty = 0;
objectloop (o ofclass Money &&
parent(o) ~= Money &&
o ~= self)
if (parent(o) == parent(self)) {
self.current_qty =
self.current_qty + o.current_qty;
Money.destroy(o);
}
],
adjust [ qty;
if (qty >= 0)
self.current_qty = self.current_qty + qty;
else {
qty = -qty;
if (qty < self.current_qty)
self.current_qty = self.current_qty - qty;
else
Money.destroy(self);
}
],
create [ qty loc;
self.current_qty = qty;
self.request_qty = 0;
if (loc) move self to loc;
],
has pluralname;
|
Commentary: |
||
Grouped set of indistinguishable nouns
|
Referenced by the player as: |
Referenced by the game as: |
More information in the DM: |
Class Fuse
with name 'fuse' 'fuses//p',
short_name "fuse",
list_together [;
if (inventory_stage == 1) {
print "several fuses";
if (c_style & NEWLINE_BIT) new_line;
rtrue;
}
];
Fuse ->;
Fuse ->;
|
Commentary: |
||
Inexhaustible set, with ability to reference a single instance
|
Referenced by the player as: |
Referenced by the game as: |
|
Object -> "matches"
with name 'matches',
before [; Burn: <<Burn (child(self))>>; ],
has pluralname transparent;
Object -> -> "match"
with name 'match',
before [; PronounNotice(parent(self));
Burn: return burning_match.light_me();
default: <<(action) (parent(self)) second>>;
],
has pluralname;
Object burning_match "burning match"
with name 'burning' 'lit' 'match',
before [; Burn: "You've already lit one."; ],
light_me [;
if (parent(self)) <<Burn self>>;
PronounNotice(self); move self to player;
StartTimer(self, 4);
"You light a match.";
],
time_left 0,
time_out [;
remove self;
"^The match burns to nothing.";
];
|
Commentary: |
||
Virtual nouns
|
Referenced by the player as: |
Referenced by the game as: |
More information in the DM: |
Object ->
with name 'bird' 'birds' 'cage',
short_name [;
if (self.number) print "cage of birds";
else print "bird cage";
rtrue;
],
invent [;
if (inventory_stage == 1)
switch (self.number) {
0: print "an empty bird cage";
1: print "a cage containing a bird";
2: print "a pair of birds in a cage";
default: print "a cage full of birds";
}
rtrue;
],
number 2;
|
Commentary: |
||
Other examples of plural objects include: a pair of gloves (DM Exercise 14); the Scrabble pieces (DM Exercise 67); various coins (DM Exercises 68 and 69, also Balances.inf); and a book of matches, a box of candles and a pile of blocks (all from Toyshop.inf). |
Most of these examples are actually fairly simplistic, with even the relatively intricate Gold Coins object having some gaps in its implementation. There are often object-specific complexities -- typically related to manipulating them singly, in combination and as a group -- which mean that significant additional coding is needed. If you are trying to model real-world handling of a tricky subject like, for example, containers of liquid, you will need considerable patience to get things working properly; trial and error is usually the best approach.
The library manipulates four pronouns -- IT, HIM, HER and THEM -- on the player's behalf, so that the most recently-mentioned objects match the appropriate pronouns. So far, so good: if you EXAMINE THE GRUMPY OLD MAN, that assigns the HIM pronoun to mean the old_man object; GIVE AXE TO HIM makes IT refer to the axe, and so on. You can use the PRONOUNS verb to check this out; the game's behaviour is nicely predictable.
Where, arguably, things start to fall apart is having the library adjust the pronoun assignments without your knowledge. After you type INVENTORY, then typically IT now refers to the last item that the player is carrying; after you type LOOK then typically IT refers to the item listed last in the room's contents. The other pronouns are similarly adjusted if there are NPCs or plural objects present. This can be highly confusing, especially since a TAKEn object appears first in the inventory, and a DROPped one first in the room description:
You can see a rusty axe, a bomb, some cartridges, a dagger and an executioner here.
>X AXE
You see nothing special about the rusty axe.
>TAKE IT
Taken.
>INV
You are carrying:
a rusty axe
a shiny sword
some grenades
a nail-studded club
>X IT
You see nothing special about the nail-studded club.
To many players, this re-assignment of the settings can seem arbitrary and unpredictable, causing them to avoid the use of pronouns altogether. So, I recommend that you turn off the pronoun checking performed by INV and LOOK. It's easy to do; just define a MANUAL_PRONOUNS constant at the start of your game:
Constant Story "MYGAME"; Constant Headline "^My first Inform game.^"; Constant MANUAL_PRONOUNS;
Pronouns are now assigned only when the player refers directly to an object. Of course, this may mean that, as you move around, the object referenced by a pronoun is no longer in scope, but at least you can understand what's going on.
There are two ways: using common properties (rarely found), and using individual properties (very commonly found).
You can define a common property, one that is possessed by every object in the game, by adding a line like this near the start of the game:
Property weight 0;
There's an Inform limit of 62 common properties in total, and the Library already defines 50 of them. This isn't actually much of a restriction, because it's quite rare to need common properties; most of the time, you can do the job better using individual properties.
You can define an individual property which applies only to one object, or to a class of objects, simply by mentioning its name in the with segment when defining the object or class; you don't need to use the Property directive. For example, you might say:
Object old_man "grumpy old man"
with name 'man' 'grumpy' 'cross' 'old' 'elderly' 'ancient' 'gnome',
grumpiness 3,
...
has animate;
in order to vary the old man's behaviour according to how grumpy he was at that moment. Perhaps his description property then becomes:
description [;
print "The grumpy old man ";
switch(self.grumpiness) {
0: "smiles bashfully.";
1: "stares back without emotion.";
2: "frowns in your direction.";
3: "glowers at you ferociously.";
4: "seems incandescent with rage.";
default: "stands silently in the corner.";
} ],
In this example, the new grumpiness property contains a decimal value 0, 1, 2... etc. You can, however, assign other types of data to your property, as we explain next. Furthermore, you can assign more than one value to a property, thus turning it into an array. The syntax is exactly the same, except that you supply several values rather than just one. The familiar name property works just like this; here's another example, showing an alternative way of handling the grumpiness descriptions (though it would be sensible to include a check that grumpiness lay in the range 0..5):
More information in the DM: |
description [; print_ret "The grumpy old man ",
(string) self.&grumpydesc-->(self.grumpiness);
],
grumpydesc "smiles bashfully." "stares back without emotion."
"frowns in your direction." "glowers at you ferociously."
"seems incandescent with rage." "stands silently in the corner.",
You talk about it in the same way as the standard properties pre-defined by the library: that is, you'd refer to the previous topic's grumpiness property as self.grumpiness within the old_man object, or as old_man.grumpiness from within any other object. It's simple, consistent, and very powerful.
As for how you use your new property, that depends on what you store in it -- a true/false flag, a number, an object's address, a string or a routine (or an array of any of those, but we'll concentrate here on the basics). For example, you might create a flammable property, controlling what happens to an object when you set fire to it, as any of these formats:
More information in the DM: |
Property definition |
Reference to the property |
What's the outcome? |
|---|---|---|
flammable, |
X = self.flammable; |
X contains zero. |
flammable 20, |
X = self.flammable; |
X contains 20. |
flammable obj_name, |
X = self.flammable; |
X contains the address of the obj_name. |
flammable "string", |
X = self.flammable; |
X contains the address of the string. |
X = self.flammable(); |
the string is printed; X contains true. |
|
flammable [;
statement;
statement;
...
], |
X = self.flammable; |
X contains the address of the routine. |
X = self.flammable(); |
the routine is executed; X contains its return value. |
|
The really interesting -- and rather surprising -- information in that table relates to the self.flammable() format. Read the table again: you'll see that the same format does something useful both when the property value is a routine (which you'd expect), or a string (which you certainly wouldn't expect). This is a great feature (and is discussed in more detail later)!
It's often useful, especially when creating your own object classes, to construct properties which can be either a string or a routine. The library has several of these: article, cant_go, description, each_turn, initial, inside_description, plural, when_XXX and all of the direction properties. They're handy because they cater both for the simple case -- when you always want to display the same information -- and for more complex requirements where what you print depends on the object state, or the time of day, or some other factor.
Suppose that you've got an object, such as a flashlight, which can be pointed at other objects. Most objects don't react, but a few do... those that have a when_lit property, which could be a routine or string. You can choose one of three techniques.
Technique |
For example |
Commentary |
|---|---|---|
Testing the property type yourself |
if (otherObject.when_lit ofclass Routine)
otherObject.when_lit();
else
print (string) otherObject.when_lit; |
Not recommended -- it's too easy to make a mistake. |
Using a library routine |
PrintOrRun(otherObject, when_lit); PrintOrRun(otherObject, when_lit, true); |
Simpler and more reliable. |
Letting the system handle it automatically |
otherObject.when_lit(); otherObject.when_lit(p1,p2, ...); |
Simplest of all. |
To help clarify what's happening, here's some actual code:
Object -> "flashlight"
with name 'flashlight',
before [;
PointAt:
if (self has light && second provides when_lit) return second.when_lit();
],
after [;
SwitchOn: give self light;
SwitchOff: give self ~light;
],
has switchable ~on ~light;
Object -> "owl"
with name 'owl',
when_lit "The owl blinks in mild surprise.",
has animate;
Object -> "mouse"
with name 'mouse',
when_lit [;
remove self;
"The startled mouse disappears into the undergrowth.";
],
has animate;
[ PointAtSub; "Pointless."; ];
Verb 'aim' 'point'
* held 'at'/'on'/'towards' noun -> PointAt;
Extend only 'shine'
* held 'at'/'on'/'towards' noun -> PointAt;
So, if you're coding a property whose value can be a routine or a string -- and, given it's so easy, why not? -- my advice is to invoke it with the obj.prop() syntax at all times unless you especially don't want that automatic newline afterwards, when you should instead use PrintOrRun(obj, prop, true).
More information in the DM: |
No. They offer no meaningful advantage over regular properties, and you can forget all about them.
Unlike properties, you can't define an attribute which applies only to one object, or to a class of objects. Inform's attributes are globally-defined, and are always associated with all objects in the game. You would create a new attribute by saying, near the start of the game, something like:
More information in the DM: |
Attribute large;
and then including:
has large;
in the definition of objects which were, for example, too bulky to be pushed through a crack, or hidden in a rabbit hole, or whatever your game demanded.
There's an Inform limit of 48 attributes in total, and the Library already defines 31 of them. This, too, isn't actually much of a restriction, because it's quite rare to need new attributes; most of the time, you can do the job better using individual properties, as we explain next.
Attributes are variables which may have only two states: they're either present, or they're absent. These variables -- usually called 'flags' in programming jargon (the flag is either 'up' or 'down', meaning: set or clear, on or off, true or false, yes or no...) -- become really useful when you want to work with binary states: is the chest open, or closed? Has the banana been peeled, or not? etc.
More information in the DM: |
The use of attributes is very easy and straightforward. As we have seen in the previous topic, you can create a new attribute with the directive:
Attribute peeled;
And then you can check whether an object has the attribute set (or not set) with:
if (banana has peeled) ... if (banana hasnt peeled) ...
You can set the attribute with:
give banana peeled;
and clear it with:
give banana ~peeled;
Inform, however, has a limited stock of attributes; you can create only about 17 additional customised flags. Moreover, once you define an attribute, you do it for all the objects in the game; by default, the new attribute is not set for any of the objects, but nevertheless it feels a bit awkward to have the possibility of, for example, a peeled television set.
Individual properties are variables which may hold much more than just two states, so they can function as flags with room to spare. An individual property is defined within one object and it affects only that object: if you have a banana and an orange, you'll have to define a peeled property for each (or maybe create a Fruit class -- see below -- from which they both inherit such a property). The syntax is a little bit more complex, but hardly a nuisance. To define a new individual property to act as flag, simply write:
Object banana "banana" fruitbowl
with name 'banana',
peeled false,
...
Now you can test if the banana has been peeled (or not) with:
if (banana.peeled == true) ... if (banana.peeled == false) ...
and change the state of the banana with:
banana.peeled = true; banana.peeled = false;
Note the use of '==' to test the value of the property, and '=' to assign the value of the property.
If these tests or changes are made from within the banana object, we recommend the use of self instead of banana:
self.peeled = true; if (self.peeled == false) ...
So what's so hot about this slightly more verbose method for flags? Three main reasons.
To illustrate this last point (admittedly, we're not talking about simple flags any more), suppose that you want to create a set of 'flaming torch' objects which can be unlit, alight or burnt-out (and therefore useless). One way is to define two new attributes:
Attribute usable; Attribute burning;
and you could then test an object's state by:
if (myObject has usable && myObject hasnt burning) ... ! It's unlit if (myObject has usable && myObject has burning) ... ! It's lit if (myObject hasnt usable) ... ! It's burnt-out
But that's a clumsy approach; a better way is to use a single torch_state property:
if (myObject provides torch_state && myObject.torch_state == 0) ... ! It's unlit if (myObject provides torch_state && myObject.torch_state == 1) ... ! It's lit if (myObject provides torch_state && myObject.torch_state == 2) ... ! It's burnt-out
Note that we're testing whether myObject provides torch_state; this is unnecessary if we know that myObject is a torch and therefore will definitely have a torch_state property, but essential if myObject might be an ordinary object which didn't have such a property as part of its definition. Note also that the 'attribute' method doesn't enable you to deduce reliably whether any given object is in fact a torch or not, whereas with the 'local property' method it's easy:
if (myObject provides torch_state) ... ! It's a torch of some sort
An even better way, incidentally, would be to define a Class of torch objects (more about Classes in the next topic):
Class Torch
with torch_state;
Torch myObject
...
if (myObject ofclass Torch) ... ! It's a torch of some sort
if (myObject ofclass Torch && myObject.torch_state == 1) ... ! It's lit
In general, we advise you to define new attributes only where they will apply to a considerable number of objects in your game. When the same state settings apply to only one or two objects, the use of local properties is just as easy, and a lot more powerful.
If your game needs a lot of flags to control the state of play and you really can't afford the memory consumed by making each one a Global variable or an individual property, you might be interested in the newflags.h library contribution, Fredrik Ramsberg's revision of Adam Cadre's flags.h. (But if you do, be sure to define some meaningful constant names, for example
Constant FED_RACCOON = 0; ! Has Beauford eaten the corn and fed
! the raccoon in the proper order?
Constant BOOKED_PLANE = 1; ! Has Hildegard booked her plane tickets
! with the correct credit card?
...
Constant WEASEL_IN_PANTS = 53; ! Is the weasel hiding in the pantaloons?
rather than using raw numbers in your code.)
Defining your own classes, so that you can create objects which are instances of those classes, is an easy and yet powerful technique. Whenever you find yourself defining two or more objects which have some common characteristics, ask yourself whether they could possibly belong to the same class. The most obvious example is a class used for rooms. Almost every time that you add a new location to your game, you’ll want it to have light. So you can readily create a basic template:
More information in the DM: |
Class Room
with description "A bare and featureless room.",
has light;
and then take that as the starting point for your new room objects:
Room hallway "Dingy hall"
with description "Steps lead down into darkness.",
d_to cellar;
Room cellar "Cellar"
with description "Once the home of vintage wine; now only spiders remain."
u_to hallway,
has ~light;
For more information on objects, classes and the use of properties and attributes, see Roger Firth's InFancy pages |
Using classes like this makes a game easier to write and easier to read, so it's worth mastering the technique early on.
There's no reason why an object can't be a member of two classes. For example, here's a class whose only purpose is to be tested against by the "snow" object:
Class Snowy;
Object "snow"
with name 'snow' 'drift' 'drifts',
article "some",
description "Pure white billowy drifts.",
found_in [; return(location ofclass Snowy); ],
before [; Take: "It's too fine and powdery."; ];
Given those definitions, you can conjure up the start of an icy landscape:
Room northpole "The North Pole"
class Snowy
with description "Windy, and white, and very very cold.",
...
A class definition is a good way of handling similar objects, and an excellent way of dealing with identical objects. For example, this is all that's needed to put some eggs into a nest:
More information in the DM: |
Object -> "nest"
with name 'nest'
has container open;
Class Egg
with short_name "egg",
plural "eggs",
name 'egg' 'eggs//p',
description "Speckled blue and green.";
Egg -> ->;
Egg -> ->;
Egg -> ->;
The game reports this as "You can see a nest (in which are three eggs) here". The "three eggs" are grouped because they're 'indistinguishable' -- they provide a plural property, and their name properties are the same.
Usually, this technique is all you need -- there are three eggs at the start and hopefully still three at the end -- but just occasionally you'll come across a situation where similar objects appear and disappear during the course of a game. If you find yourself needing to create identical objects as the game progresses, you might consider using a Dynamic class. We'll illustrate this by blowing bubbles (you'll have to imagine the wire loop and the soap solution). Here's a basic class:
Class Bubble(10)
with short_name "bubble",
plural "bubbles",
name 'bubble' 'bubbles//p',
description "The bubble floats gently in the air.";
That's hardly any different from the definition of the Egg class, is it? Just the (10) after the class name, which you should read as specifying that "no more than ten bubbles can exist at any one time". What has changed is the way that the bubbles are brought into existence. We'll conjure them up by enabling the player to type BLOW BUBBLE, using this extension of the library's standard BLOW verb:
More information in the DM: |
[ BlowBubbleSub obj;
obj = Bubble.create();
if (obj == nothing)
"Sorry, you've run out of bubbles.";
move obj to location;
"You carefully blow a bubble.";
];
Extend 'blow' first
* 'bubble'/'bubbles' -> BlowBubble;
The statement obj = Bubble.create(); does the trick. Sending a create message to a Dynamic class definition brings a new object of that class into existence, and returns the number used to identify the object. The object is created in limbo -- without a parent -- so you'll usually need to move it into play; move obj to location; does that, and finally, you tell the player what's happened. If instead Bubble.create() returns nothing then the object couldn't be created (because you've already blown all of the specified 10), apologise to the player, and give up.
So far so good, but we can make a few improvements to our code. First, we simplify BlowBubbleSub() by making the newly-created object responsible for its own initialisation. We do this by adding a create property to the class:
Class Bubble(10)
with short_name "bubble",
plural "bubbles",
name 'bubble' 'bubbles//p',
description "The bubble floats gently in the air.",
create [;
move self to location;
"You carefully blow a bubble.";
];
[ BlowBubbleSub;
if (Bubble.create() == nothing)
"You've run out of bubbles.";
];
The use of Bubble.create() tells the Bubble class to create a new Bubble object; the library contains all the code to do that. Then, the library calls the create property of the new object -- if it provides one -- to enable the object to set itself up: here, by moving itself to the current location and announcing its own birth.
The bubbles that we're creating here aren't very realistic, since they can be taken and dropped like solid objects. Let's make them burst if touched...
Class Bubble(10)
with short_name "bubble",
plural "bubbles",
name 'bubble' 'bubbles//p',
description "The bubble floats gently in the air.",
create [;
move self to location;
"You carefully blow a bubble.";
],
before [;
Attack,Cut,Push,PushDir,Rub,Squeeze,Take,Taste,Tie,Touch,Turn,Wave:
Bubble.destroy(self);
"With a gentle 'pop', the bubble bursts.";
];
...or if deliberatly popped...
[ BurstSub;
"You can't burst that!";
];
Verb 'burst' 'deflate' 'pop' 'prick' 'puncture'
* multi -> Burst;
...or of their own accord after a few turns:
Class Bubble(10)
with short_name "bubble",
plural "bubbles",
name 'bubble' 'bubbles//p',
description "The bubble floats gently in the air.",
create [;
StartTimer(self, random(5));
move self to location;
"You carefully blow a bubble.";
],
before [ player_gone;
Attack,Burst,Cut,Push,PushDir,Rub,Squeeze,Take,Taste,Tie,Touch,Turn,Wave:
StopTimer(self);
player_gone = (self notin location);
Bubble.destroy(self);
if (player_gone) rtrue;
"With a gentle 'pop', the bubble bursts.";
],
time_left 0,
time_out [; <<Burst self>>; ];
[ BlowBubbleSub;
if (Bubble.create() == nothing)
"You pause for a moment to get your breath back.";
];
Note how we handle the destruction of a burst bubble. Rather than just moving it out of sight by remove self, we use Bubble.destroy(self) which has the effect making the destroyed object re-usable by a subsequent call to Bubble.create(). We suppress the message about the bubble bursting if the player is no longer in the room. Once we've got ten bubbles in the air at the same time and so can't blow any more, we have only to wait until one of them bursts... and we can blow another one. We therefore change the message from BlowBubbleSub() to suggest that the ban on creation is a temporary limitation.
One final flourish: we'll stop the bubbles being indistinguishable by giving them some size and colour characteristics, chosen at random.
Class Bubble(10)
with short_name [;
print (address) self.&name-->0, " ", (address) self.&name-->1, " bubble";
rtrue;
],
name '.size' '.colour' 'bubble' 'bubbles//p',
description [; print_ret (The) self, " floats gently in the air."; ],
create [;
self.&name-->0 = random('tiny','small','regular','large','enormous');
self.&name-->1 = random('red','green','blue');
PronounNotice(self);
move self to location;
StartTimer(self, random(5));
"You carefully blow ", (a) self, ".";
],
before [ player_gone s c;
Attack,Burst,Cut,Push,PushDir,Rub,Squeeze,Take,Taste,Tie,Touch,Turn,Wave:
StopTimer(self);
player_gone = (self notin location);
s = self.&name-->0; c = self.&name-->1;
Bubble.destroy(self);
self.&name-->0 = s; self.&name-->1 = c;
if (player_gone) rtrue;
"With a gentle 'pop', ", (the) self, " bursts.";
],
time_left 0,
time_out [; <<Burst self>>; ];
Several changes here. We've reserved two entries at the start of the name property to hold a pair of adjectives representing the bubble's size and colour; we write appropriate random values into these entries at the start of the create property. The short_name property uses the two adjectives to compose the object's name (for example, "tiny red bubble"). The various messages use print rules like "(The) self" in order to display that composite name. We've added PronounNotice(self) so that BLOW BUBBLE THEN BURST IT works as you'd imagine it should. And, somewhat obscurely, we remember the bubble's size and colour just before we destroy it, and reinstate them afterwards. Huh?
A bit of inside information may be helpful here. The declaration Class Bubble(10) ... ; actually compiles 11 identical bubble objects, and makes them all children of the Bubble class (which is also an object). The Bubble.create() call doesn't do much more than make one of the first ten of those child objects available to the game, and the Bubble.destroy() call just returns the 'destroyed' object to the pool of children. The eleventh object is never made available; it's there to provide a template so that, when an object is destroyed, its property values can be reset to their default state. Which means that the randomised size and colour values are lost, and you may see "You can't see 'it' (the .size .colour bubble) at the moment". That's why we reinstate the size and colour values after destroying a bubble. All of which means that our bubbles now behave something like this:
>BLOW BUBBLE You carefully blow a small blue bubble. >BLOW BUBBLE You carefully blow a large red bubble. >EXAMINE IT The large red bubble floats gently in the air. >BURST IT With a gentle 'pop', the large red bubble bursts. >EXAMINE IT You can't see "it" (the large red bubble) at the moment. >LOOK ... You can see a small blue bubble here.
There's one other thing you should know. You might imagine that a statement like objectloop (o ofclass Bubble) ... would loop through your 'created' bubbles. Wrong: it loops through all 11 of the compiled bubbles. The statement you want is probably objectloop (o ofclass Bubble && o notin Bubble) ...
In Parserm.h, the Library defines a standard object selfobj which represents the human person who is playing your game, and a variable player which normally contains the address of selfobj. The INVENTORY command lists the objects which are the children of player, while EXAMINE ME invokes its description property. By default, that outputs "As good-looking as ever.", but you may well wish to change it; here are a couple of methods. The standard selfobj looks rather like this:
More information in the DM: |
Object selfobj "(self object)"
with short_name [; return L__M(##Miscellany, 18); ],
description [; return L__M(##Miscellany, 19); ],
...
has animate concealed proper transparent;
So, one way to change the descriptive text is to define a LibraryMessages object to intercept Miscellany message 19:
More information in the DM: |
Include "Parser";
Object LibraryMessages
with before [;
Miscellany:
if (lm_n == 19) "Not much change since you last looked.";
];
Include "VerbLib";
However, it's easier just to change the description property itself. For example, you could add this line to your Initialise() routine:
[ Initialise;
player.description = "Not much change since you last looked.";
...
];
or this one, referencing the address of a suitable property routine:
[ PlayerDesc;
if (children(self) == 0) "Forlorn and empty-handed.";
"Your possessions don't inspire much confidence.";
];
[ Initialise;
player.description = PlayerDesc;
...
];
Or, yet another possibility, you could supply your own player object, a modified version of the one in Parserm.h:
Object mySelfobj "(self object)"
with short_name [; return L__M(##Miscellany, 18); ],
description [;
if (children(self) == 0) "Forlorn and empty-handed.";
"Your possessions don't inspire much confidence.";
],
...
has animate concealed proper transparent;
[ Initialise;
player = mySelfobj;
...
];
More information in the DM: |
In the Initialise() routine, it's safe to assign object addresses directly to the location and player variables. Everywhere else, you should use the Library routines PlayerTo() and ChangePlayer() respectively.
More information in the DM: |
Usually, it doesn't nowadays, because many errors are caught by Strict checking. However, you need to be aware of the special-case handling of objectloop. If you use one of the three special forms (especially #1; the others are rarely seen), then your loop runs a lot faster, because the number of objects being tested is much smaller. However, this optimisation is achieved by using the sibling() function to navigate the linked object tree, rather than simply iterating through all of the defined objects. And if you move or remove an object in the middle of an optimised loop, you'll screw up the linkage, and the results will be... interesting.
objectloop formats |
|
Generated code is equivalent to: |
|---|---|---|
Standard |
objectloop (condition) {
statement;
statement;
...
}
|
for (all objects in game) if (condition) {
statement;
statement;
...
}
|
Special case #1 |
objectloop (i in object) {
statement;
statement;
...
}
|
for (i = child(object) : i : i = sibling(i)) {
statement;
statement;
...
}
|
Special case #2 (rare) |
objectloop (i near object) {
statement;
statement;
...
}
|
for (i = child(parent(object)) : i : i = sibling(i)) {
statement;
statement;
...
}
|
Special case #3 (rare) |
objectloop (i from object) {
statement;
statement;
...
}
|
for (i = object : i : i = sibling(i)) {
statement;
statement;
...
}
|
Using move and remove in 'standard' objectloops is fine. Also, the (i in object) form is 'special' only in that exact form -- if you add another condition like (i in object && i has attribute) or (i && i in object), it becomes safely 'standard'. Note: you can't add extra conditions to the other two special forms, which is probably why they're not often encountered.
You'll see from the previous topic that objectloop (i in object) selects the direct children of object, but not any other dependent objects -- grandchildren, great-grandchildren and so on -- lower down the hierarchy. Since that's quite a useful thing to be able to do, we'll discuss some ways that you can go about it. First of all, for reasons that will hopefully become clear in a minute, we'll encapsulate the actual processing in a separate routine: this simple example merely displays the name of each object as we loop through it:
[ ProcessObject obj;
print (name) obj, "^";
];
With that in place, here are three alternative definitions of a routine which applies ProcessObject() to all of the dependents of a specified parent object par:
[ R1 par o;
objectloop (o ~= par && IndirectlyContains(par, o))
ProcessObject(o);
];
[ R2 par o;
for (o=child(par) : o : ) {
ProcessObject(o);
if (child(o)) o = child(o);
else
while (o) {
if (sibling(o)) { o = sibling(o); break; }
o = parent(o);
if (o == par) return;
}
}
];
[ R3 par o;
objectloop (o in par) {
ProcessObject(o);
if (child(o)) R3(o);
}
];
The R1() example is the easiest to write and understand: unfortunately it's also hopelessly inefficient because of the amount of unnecessary testing that it performs. The much better R2() example calls the low-level child(), sibling() and parent() functions to move directly between the various dependents of par. And R3() is a nice compromise: it uses recursion -- that is, it calls itself -- to navigate through par's dependents, which makes it appear simpler than it actually is.
So far, so good: you could use our little ProcessObject() routine with any of R1(player), R2(player) or R3(player) to print a list of the player's possessions. Or in fact, not so good, because ProcessObject() is being called for all of par's dependents, even if they're not visible to the outside world. For example, if the player is carrying a locked box, then you quite possibly want to bypass any objects in that box, rather than call ProcessObject() for each of them. This is quite tricky to do using the R1() technique, but is very straightforward for R2() and R3(). First of all, we extend ProcessObject() so that it returns true if its contents, if any, are also to be processed, or false if any contents are to be skipped. Here's one test that you might find useful:
[ ProcessObject obj;
print (name) obj, "^";
if ((obj has supporter or transparent) ||
(obj has container && obj has open)) rtrue;
rfalse;
];
And these are revised versions of R2() and R3() which test the value returned by ProcessObject() and act accordingly:
[ R2 par o;
for (o=child(par) : o : ) {
if (ProcessObject(o) && child(o)) o = child(o);
else
while (o) {
if (sibling(o)) { o = sibling(o); break; }
o = parent(o);
if (o == par) return;
}
}
];
[ R3 par o;
objectloop (o in par)
if (ProcessObject(o) && child(o)) R3(o);
];
Operating |