§3   Objects and classes

Objects make up the substance of the world.
—Ludwig Wittgenstein (1889–1951), Tractatus

§3.1   Objects, classes, metaclasses and nothing

In Inform, objects are little bundles of routines and variables tied up together. Dividing up the source code into objects is a good way to organise any large, complicated program, and makes particular sense for an adventure game, based as it usually is on simulated items and places. One item in the simulated world corresponds to one “object” in the source code. Each of these pieces of the story file should take responsibility for its own behaviour, so for instance a brass lamp in an adventure game might be coded with an Inform object called brass_lamp, containing all of the game rules which affect the lamp. Then again, objects do have to interact.

In West Pit
You are at the bottom of the western pit in the twopit room. There is a large hole in the wall about 25 feet above you.
There is a tiny little plant in the pit, murmuring “Water, water,…”

In this moment from ‘Advent’, the player is more or less openly invited to water the plant. There might be many ways to bring water to it, or indeed to bring liquids other than water, and the rules for what happens will obviously affect the bottle carrying the water, the water itself and the plant. Where should the rules appear in the source code? Ideally, the plant object should know about growing and the bottle about being filled up and emptied. Many people feel that the most elegant approach is for the bottle, or any other flask, not to interfere with the plant directly but instead to send a “message” to the plant to say “you have been watered”. It's then easy to add other solutions to the same puzzle: a successful rain dance, for instance, could also result in a “you have been watered” message being sent to plant. The whole behaviour of the plant could be altered without needing even to look at the rain-dance or the bottle source code. Objects like this can frequently be cut out of the source code for one game and placed into another, still working.

This traffic of messages between objects goes on continuously in Inform-compiled adventure games. When the player tries to pick up a wicker cage, the Inform library sends a message to the cage object asking if it minds being picked up. When the player tries to go north from the Hall of Mists, the library sends a message to an object called Hall_of_Mists asking where that would lead, and so on.

· · · · ·

Typical large story files have so many objects (‘Curses’, for instance, has 550) that it is convenient to group similar objects together into “classes”. For instance, ‘Advent’ began life as a simulation of part of the Mammoth and Flint Ridge cave system of Kentucky, caves which contain many, many dead ends. The Inform source code could become very repetitive without a class like so:

Class DeadEndRoom
 with short_name "Dead End",
      description "You have reached a dead end.",
      cant_go "You'll have to go back the way you came.";

Leaving the exact syntax for later, this code lays out some common features of dead ends. All kinds of elegant things can be done with classes if you like, or not if you don't.

Objects can belong to several different classes at once, and it is sometimes convenient to be able to check whether or not a given object belongs to a given class. For instance, in adventures compiled with the Inform library, a variable called location always holds the player's current position, so it might be useful to do this:

if (location ofclass DeadEndRoom) "Perhaps you should go back.";

· · · · ·

Items and places in an Inform game always belong to at least one class, whatever you define, because they always belong to the “metaclass” Object. (“Meta” from the Greek for “beyond”.) As we shall see, all of the objects explicitly written out in Inform source code always belong to Object. Though you seldom need to know this, there are three other metaclasses. Classes turn out to be a kind of object in themselves, and belong to Class. (So that Class belongs to itself. If you enjoy object-oriented programming, this will give you a warm glow inside). And although you almost never need to know or care, routines as in §1 are internally considered as a kind of object, of class Routine; while strings in double-quotes are likewise of class String. That's all, though. If in doubt, you can always find out what kind of object obj is, using the built-in function metaclass(obj). Here are some example values:

metaclass("Violin Concerto no. 1") == String
metaclass(Main) == Routine
metaclass(DeadEndRoom) == Class
metaclass(silver_bars) == Object

Classes are useful and important, but for most game-designing purposes it's now safe to forget all about metaclasses.

It turns out to be useful to have a constant called nothing and meaning “no object at all”. It really does mean that: nothing is not an object. If you try to treat it as one, many programming errors will be printed up when the story file is played.

If X is not an object at all, then metaclass(X) is nothing, and in particular metaclass(nothing) is nothing.

§3.2   The object tree

Objects declared in the source code are joined together in an “object tree” which grows through every Inform story file. Adventure games use this to represent which items are contained inside which other items.

It's conventional to think of this as a sort of family tree without marriages. Each object has a parent, a child and a sibling. Such a relation is always either another object in the tree, or else nothing, so for instance the parent of an orphan would be nothing. Here is a sample object tree:

Meadow
child
Mailbox sibling Player
child child
Note Sceptre sibling Bottle sibling Torch sibling Stone
child
Battery

The Mailbox and Player are both children of the Meadow, which is their parent, but only the Mailbox is the child of the Meadow. The Stone is the sibling of the Torch, which is the sibling of the Bottle, and so on.

Inform provides special functions for reading off positions in the tree: parent, sibling do the obvious things, and child gives the first child: in addition there's a function called children which counts up how many children an object has (note that grandchildren don't count as children). Here are some sample values:

parent ( Mailbox )  == Meadow
children ( Player ) == 4
child ( Player )    == Sceptre
child ( Sceptre )   == nothing
sibling ( Torch )   == Stone

WARNING
It is incorrect to apply these functions to the value nothing, since it is not an object. If you write a statement like print children(x); when the value of x happens to be nothing, the interpreter will print up the message:

[** Programming error: tried to find the “children” of nothing **]

You get a similar error message if you try to apply these tree functions to a routine, string or class.

§3.3   Declaring objects 1: setting up the tree

Objects are made with the directive Object. Here is a portion of source code, with the bulk of the definitions abbreviated to “...”:

Object Bucket ...
Object -> Starfish ...
Object -> Oyster ...
Object -> -> Pearl ...
Object -> Sand ...

The resulting tree looks a little like the source code turned on its side:

Bucket
child
Starfish sibling Oyster sibling Sand
child
Pearl

The idea is that if no arrows -> are given in the Object definition, then the object has no parent. If one -> is given, then the object is made a child of the last object defined with no arrows; if two are given, it's made a child of the last object defined with only one arrow; and so on.

An object definition consists of a “head” followed by a “body”, itself divided into “segments”, though there the similarity with caterpillars ends. The head takes the form:

Objectarrows› ‹identifier"textual name"parent

  1. The ‹arrows› are as described above. Note that if one or more arrows are given, that automatically specifies what object this is the child of, so a ‹parent› cannot be given as well.
  2. The ‹identifier› is what the object can be called inside the program, in the same way that a variable or a routine has a name.
  3. The "textual name" can be given if the object's name ever needs to be printed by the program when it is running.
  4. The ‹parent› is an object which this new object is to be a child of. This is an alternative to supplying arrows.

All four parts are optional, so that even this bare directive is legal:

Object;

though it makes a nameless and featureless object which is unlikely to be useful.

§3.4   Tree statements: move, remove, objectloop

The positions of objects in the tree are by no means fixed: objects are created in a particular formation but then shuffled around extensively during the story file's execution. (In an adventure game, where the objects represent items and rooms, objects are moved across the tree whenever the player picks something up or moves around.) The statement

moveobjecttoobject

moves the first-named object to become a child of the second-named one. All of the first object's own children “move along with it”, i.e., remain its own children.

For instance, starting from the tree as shown in the diagram of §3.2 above,

move Bottle to Mailbox;

results in the tree

Meadow
child
Mailbox sibling Player
child child
Bottle sibling Note       Sceptre sibling Torch sibling Stone
child
Battery

When an object becomes the child of another in this way, it always becomes the “eldest” child: that is, it is the new child() of its parent, pushing the previous children over into being its siblings. In the tree above, Bottle has displaced Note just so.

You can only move one object in the tree to another: you can't

move Torch to nothing;

because nothing is not an object. Instead, you can detach the Torch branch from the tree with

remove Torch;

and this would result in:

Meadow Torch
child child
Mailbox sibling Player Battery
child child
Bottle sibling Note       Sceptre sibling Stone

The “object tree” is often fragmented like this into many little trees, and is not so much a tree as a forest.

WARNING
It would make no sense to have a circle of objects each containing the next, so if you try to move Meadow to Note; then you'll only move the interpreter to print up:

[** Programming error: tried to move the Meadow to the note, which would make a loop: Meadow in note in mailbox in Meadow **]

· · · · ·

Since objects move around a good deal, it's useful to be able to test where an object currently is, and the condition in is provided for this. For example,

Bottle in Mailbox

is true if and only if the Bottle is one of the direct children of the Mailbox. (Bottle in Mailbox is true, but Bottle in Meadow is false.) Note that

X in Y

is only an abbreviation for parent(X)==Y but it occurs so often that it's worth having. Similarly, X notin Y means parent(X)~=Y. X has to be a bona-fide member of the object tree, but Y is allowed to be nothing, and testing X in nothing reveals whether or not X has been removed from the rest of the tree.

· · · · ·

The remaining loop statement left over from §1 is objectloop.

objectloop(variable-name)statement

runs through the ‹statement› once for each object in the game, putting each object in turn into the variable. For example,

objectloop(x) print (name) x, "^";

prints out a list of the textual names of every object in the game. More powerfully, any condition can be written in the brackets, as long as it begins with a variable name.

objectloop (x in Mailbox) print (name) x, "^";

prints the names only of those objects which are direct children of the Mailbox object.

The simple case where the condition reads “‹variableinobject›” is handled in a faster and more predictable way than other kinds of objectloop: the loop variable is guaranteed to run through the children of the object in sibling order, eldest down to youngest. (This is faster because it doesn't waste time considering every object in the game, only the children.) If the condition is not in this form then no guarantee is made as to the order in which the objects are considered.

WARNING
When looping over objects with in, it's not safe to move these same objects around: this is like trying to cut a branch off an elm tree while sitting on it. Code like this:

objectloop(x in Meadow) move x to Sandy_Beach;

looks plausible but is not a safe way to move everything in the Meadow, and will instead cause the interpreter to print up

[** Programming error: objectloop broken because the object mailbox was moved while the loop passed through it **]

Here is a safer way to move the meadow's contents to the beach:

while (child(Meadow)) move child(Meadow) to Sandy_Beach;

This works because when the Meadow has no more children, its child is then nothing, which is the same as false.

But it moves the eldest child first, with the possibly undesirable result that the children arrive in reverse order (Mailbox and then Player, say, become Player and then Mailbox). Here is an alternative, moving the youngest instead of the eldest child each time, which keeps them in the same order:

while (child(Meadow)) {
    x = child(Meadow); while (sibling(x)) x = sibling(x);
    move x to Sandy_Beach;
}

Keeping children in order can be worth some thought when game designing. For instance, suppose a tractor is to be moved to a farmyard in which there is already a barn. The experienced game designer might do this like so:

move tractor to Farmyard;
move barn to Farmyard;

Although the barn was in the farmyard already, the second statement wasn't redundant: because a moved object becomes the eldest child, the statement does this:

Farmyard Farmyard
child   =>   child
tractor sibling barn barn sibling tractor

And this is desirable because the ordering of paragraphs in room descriptions tends to follow the ordering of items in the object tree, and the designer wants the barn mentioned before the tractor.

An objectloop range can be any condition so long as a named local or global variable appears immediately after the open bracket. This means that

objectloop (child(x) == nothing) ...

isn't allowed, because the first thing after the bracket is child, but a dodge to get around this is:

objectloop (x && child(x) == nothing) ...

The loop variable of an objectloop can never equal false, because that's the same as nothing, which isn't an object.

▲▲ The objectloop statement runs through all objects of metaclass Object or Class, but skips any Routine or String.

§3.5   Declaring objects 2: with and provides

So far Objects are just tokens with names attached which can be shuffled around in a tree. They become interesting when data and routines are attached to them, and this is what the body of an object definition is for. The body contains four different kinds of segments, introduced by the keywords:

with    has    class    private

These are all optional and can be given in any order.

They can even be given more than once: that is, there can be two or more of a given kind, which Inform will combine together as if they had been defined in one go. (This is only likely to be useful for automated Inform-writing programs.)

· · · · ·

The most important segment is with, which specifies variables and, as we shall see, routines and even arrays, to be attached to the object. For example,

Object magpie "black-striped bird"
  with wingspan, worms_eaten;

attaches two variables to the bird, one called wingspan, the other called worms_eaten. Commas are used to separate them and the object definition as a whole ends with a semicolon, as always. Variables of this kind are called properties, and are referred to in the source code thus:

magpie.wingspan
magpie.worms_eaten

Properties are just like global variables: any value you can store in a variable can be stored in a property. But note that

crested_grebe.wingspan
magpie.wingspan

are different and may well have different values, which is why the object whose wingspan it is (the magpie or the grebe) has to be named.

The property wingspan is said to be provided by both the magpie and crested_grebe objects, whereas an object whose with segment didn't name wingspan would not provide it. The dot . operator can only be used to set the value of a property which is provided by the object on the left of the dot: if not a programming error will be printed up when the story file is played.

The presence of a property can be tested using the provides condition. For example,

objectloop (x provides wingspan) ...

executes the code ... for each object x in the program which is defined with a wingspan property.

Although the provision of a property can be tested, it can't be changed while the program is running. The value of magpie.wingspan may change, but not the fact that the magpie provides a wingspan.

▲▲ Some special properties, known as “common properties”, can have their values read (but not changed) even for an object which doesn't provide them. All of the properties built into the Inform library are common properties. See §3.14.

· · · · ·

When the magpie is created as above, the initial values of

magpie.wingspan
magpie.worms_eaten

are both 0. To create the magpie with a given wingspan, we have to specify an initial value, which we do by giving it after the name, e.g.:

Object magpie "black-striped bird"
  with wingspan 5, worms_eaten;

The story file now begins with magpie.wingspan equal to 5, though magpie.worms_eaten still equal to 0.

· · · · ·

A property can contain a routine instead of a value. In the definition

Object magpie "black-striped bird"
  with name 'magpie' 'bird' 'black-striped' 'black' 'striped',
       wingspan 5,
       flying_strength [;
           return magpie.wingspan + magpie.worms_eaten;
       ],
       worms_eaten;

The value of magpie.flying_strength is given as a routine, in square brackets as usual. Note that the Object continues where it left off after the routine-end marker, ]. Routines which are written in as property values are called “embedded” and are the way objects receive messages, as we shall see.

If, during play, you want to change the way a magpie's flying strength is calculated, you can simply change the value of its property:

magpie.flying_strength = ExhaustedBirdFS;

where ExhaustedBirdFS is the name of a routine to perform the new calculation.

Embedded routines are just like ordinary ones, with two exceptions:

  1. An embedded routine has no name of its own, since it is referred to as a property such as magpie.flying_strength instead.
  2. If execution reaches the ] end-marker of an embedded routine, then it returns false, not true (as a non-embedded routine would).

▲▲ Properties can be arrays instead of variables. If two or more consecutive values are given for the same property, it becomes an array. Thus,

Object magpie "black-striped bird"
  with name 'magpie' 'bird' 'black-striped' 'black' 'striped',
       wingspan 5, worms_eaten;

You can't write magpie.name because there is no single value: rather, there is an --> array (see §2.4). This array must be accessed using two special operators, .& and .#, for the array and its length, as follows.

magpie.&name

means “the array held in magpie's name property”, so that the actual name values are in the entries

magpie.&name-->0, magpie.&name-->1, ..., magpie.&name-->4

The size of this array can be discovered with

magpie.#name

which evaluates to the twice the number of entries, in this case, to 10. Twice the number of entries because that is the number of bytes in the array: people fairly often use property arrays as byte arrays to save on memory.

name is a special property created by Inform, intended to hold dictionary words which can refer to an object.

§3.6   Declaring objects 3: private properties

A system is provided for “encapsulating” certain properties so that only the object itself has access to them. These are defined by giving them in a segment of the object declaration called private. For instance,

Object sentry "sentry"
  private pass_number 16339,
  with challenge [ attempt;
           if (attempt == sentry.pass_number)
               "Approach, friend!";
           "Stand off, stranger.";
  ];

provides for two properties: challenge, which is public, and pass_number, which can be used only by the sentry's own embedded routines.

▲▲ This makes the provides condition slightly more interesting than it appeared in the previous section. The answer to the question of whether or not

sentry provides pass_number

depends on who's asking: this condition is true if it is tested in one of the sentry's own routines, and elsewhere false. A private property is so well hidden that nobody else can even know whether or not it exists.

§3.7   Declaring objects 4: has and give

In addition to properties, objects have flags attached, called “attributes”. Recall that flags are a limited form of variable which can only have two values, sometimes called set and clear.) Unlike property names, attribute names have to be declared before use with a directive like:

Attribute hungry;

Once this declaration is made, every object in the tree has a hungry flag attached, which is either true or false at any given time. The state can be tested with the has condition:

magpie has hungry

is true if and only if the magpie's hungry flag is currently set. You can also test if magpie hasnt hungry. There's no apostrophe in hasnt.

The magpie can now be born hungry, using the has segment in its declaration:

Object magpie "black-striped bird"
  with wingspan, worms_eaten
  has  hungry;

The has segment contains a list (without commas in between) of the attributes which are initially set: for instance, the steel grate in the Inform example game ‘Advent’ includes the line

has  static door openable lockable locked;

The state of an attribute can be changed during play using the give statement:

give magpie hungry;

sets the magpie's hungry attribute, and

give magpie ~hungry;

clears it again. The give statement can take more than one attribute at a time, too:

give double_doors_of_the_horizon ~locked openable open;

means “clear locked and set openable and open”.†


† The refrain from the prelude to Act I of Philip Glass's opera Akhnaten is “Open are the double doors of the horizon/ Unlocked are its bolts”.

▲▲ An attribute can also have a tilde ~ placed in front in the has part of an object declaration, indicating “this is definitely not held”. This is usually what would have happened anyway, except that class inheritance (see below) might have passed on an attribute: if so, this is how to get rid of it again. Suppose there is a whole class of steel grates like the one in ‘Advent’ mentioned above, providing for a dozen grates scattered through a game, but you also want a loose grate L whose lock has been smashed. If L belongs to the class, it will start the game with attributes making it locked like the others, because the class sets these automatically: but if you include has ~lockable ~locked; in its declaration, these two attributes go away again.

§3.8   Declaring objects 5: class inheritance

A class is a prototype design from which other objects are manufactured. These resulting objects are sometimes called instances or members of the class, and are said to inherit from it.

Classes are useful when a group of objects are to have common features. In the definition of the magpie above, a zoologically doubtful formula was laid out for flying strength:

flying_strength [;
    return magpie.wingspan + magpie.worms_eaten;
],

This formula ought to apply to birds in general, not just to magpies, and in the following definition it does:

Attribute flightless;
Class Bird
  with wingspan 7,
       flying_strength [;
           if (self has flightless) return 0;
           return self.wingspan + self.worms_eaten;
       ],
       worms_eaten;
Bird "ostrich" with wingspan 3, has flightless;
Bird "magpie" with wingspan 5;
Bird "crested grebe";
Bird "Great Auk" with wingspan 15;
Bird "early bird" with worms_eaten 1;

Facts about birds in general are now located in a class called Bird. Every example of a Bird automatically provides wingspan, a flying_strength routine and a count of worms_eaten. Notice that the Great Auk is not content with the average avian wingspan of 7, and insists on measuring 15 across. This is an example of inheritance from a class being over-ridden by a definition inside the object. The actual values set up are as follows:

BB.wingspan B.worms_eaten
ostrich30
magpie50
crested grebe70
Great Auk150
early bird71

Note also the use of the special value self in the definition of Bird. It means “whatever bird I am”: if the flying_strength routine is being run for the ostrich, then self means the ostrich, and so on.

The example also demonstrates a general rule: to create something, begin its declaration with the name of the class you want it to belong to: a plain Object, a Class or now a Bird.

Sometimes you need to specify that an object belongs to many classes, not just one. You can do this with the class segment of the definition, like so:

Object "goose that lays the golden eggs"
  class Bird Treasure;

This goose belongs to three classes: Object of course, as all declared objects do, but also Bird and Treasure. (It inherits from Object first and then Bird and then Treasure, attribute settings and property values from later-mentioned classes overriding earlier ones, so if these classes should give contradictory instructions then Treasure gets the last word.) You can also make class definitions have classes, or rather, pass on membership of other classes:

Class BirdOfPrey
class Bird
 with wingspan 15,
      people_eaten;
BirdOfPrey kestrel;

makes kestrel a member of both BirdOfPrey and of Bird. Dutiful apostles of object-oriented programming may want to call BirdOfPrey a “subclass” of Bird. Indeed, they may want to call Inform a “weakly-typed language with multiple-inheritance”, or more probably a “shambles”.

▲▲ For certain “additive” common properties, clashes between what classes say and what an instance says are resolved differently: see §5. Inform's built-in property name is one of these.

§3.9   Messages

Objects communicate with each other by means of messages. A message has a sender, a receiver and some parameter values attached, and it always produces a reply, which is just a single value. For instance,

x = plant.pour_over(cold_spring_water);

sends the message pour_over with a single parameter, cold_spring_water, to the object plant, and puts the reply value into x.

In order to receive this message, plant has to provide a pour_over property. If it doesn't, then the interpreter will print something like

[** Programming error: the plant (object number 21) has no property pour over to send message **]

when the story file is played. The pour_over property will normally be a routine, perhaps this one:

pour_over [ liquid;
    remove liquid;
    switch(liquid) {
        oil: "The plant indignantly shakes the oil off its
              leaves and asks, ~Water?~";
        ...
    }
];

Inside such a routine, self means the object receiving the message and sender means the object which sent it. In a typical Inform game situation, sender will often be the object InformLibrary, which organises play and sends out many messages to items and places in the game, consulting them about what should happen next. Much of any Inform game designer's time is spent writing properties which receive messages from InformLibrary: before, after, each_turn and n_to among many others.

You can see all the messages being sent in a game as it runs using the debugging verb “messages”: see §7 for details. This is the Inform version of listening in on police-radio traffic.

It was assumed above that the receiving property value would be a routine. But this needn't always be true. It can instead be: nothing, in which case the reply value is also nothing (which is the same as zero and the same as false). Or it can be an Object or a Class, in which case nothing happens and the object or class is sent back as the reply value. Or it can be a string in double-quotes, in which case the string is printed out, then a new-line is printed, and the reply value is true.

This can be useful. Here is approximately what happens when the Inform library tries to move the player northeast from the current room (the location) in an adventure game (leaving out some complications to do with doors):

if (location provides ne_to) {
    x = location.ne_to();
    if (x == nothing) "You can't go that way.";
    if (x ofclass Object) move player to x;
} else "You can't go that way.";

This neatly deals with all of the following cases:

Object Octagonal_Room "Octagonal Room"
  with ...
       ne_to "The way north-east is barred by an invisible wall!",
       w_to Courtyard,
       e_to [;
           if (Amulet has worn) {
               print "A section of the eastern wall suddenly parts
                      before you, allowing you into...^";
               return HiddenShrine;
           }
       ],
       s_to [;
           if (random(5) ~= 1) return Gateway;
           print "The floor unexpectedly gives way, dropping you
                  through an open hole in the plaster...^";
           return random(Maze1, Maze2, Maze3, Maze4);
       ];

Noteworthy here is that the e_to routine, being an embedded routine, returns false which is the same as nothing if the ] end-marker is reached, so if the Amulet isn't being worn then there is no map connection east.

▲▲ The receiving property can even hold an array of values, in which case the message is sent to each entry in the array in turn. The process stops as soon as one of these entries replies with a value other than nothing or false. If every entry is tried and they all replied nothing, then the reply value sent back is nothing. (This is useful to the Inform library because it allows before rules to be accumulated from the different classes an object belongs to.)

§3.10   Passing messages up to the superclass

It fairly often happens that an instance of a class needs to behave almost, but not quite, as the class would suggest. For instance, suppose the following Treasure class:

Class Treasure
  with deposit [;
      if (self provides deposit_points)
          score = score + self.deposit_points;
      else score = score + 5;
      move self to trophy_case;
      "You feel a sense of increased esteem and worth.";
  ];

and we want to create an instance called Bat_Idol which flutters away, resisting deposition, but only if the room is dark:

Treasure Bat_Idol "jewelled bat idol"
  with deposit [;
           if (location == thedark) {
               remove self;
               "There is a clinking, fluttering sound!";
           }
           ...
       ];

In place of ..., what we want is all of the previous source code about depositing treasures. We could just copy it out again, but a much neater trick is to write:

self.Treasure::deposit();

Instead of sending the message deposit, we send the message Treasure::deposit, which means “what deposit would do if it used the value defined by Treasure”. The double-colon :: is called the “superclass operator”. (The word “superclass”, in this context, is borrowed from the Smalltalk-80 language.)

▲▲ object.class::property is the value of property which the given object would normally inherit from the given class. (Or it gives an error if the class doesn't provide that property or if the object isn't a member of that class).

▲▲ It's perfectly legal to write something like x = Treasure::deposit; and then to send Bat_Idol.x();.

§3.11   Creating and deleting objects during play

In an adventure-game setting, object creation is useful for something like a beach full of stones: if the player wants to pick up more and more stones, the game needs to create a new object for each stone brought into play.

Besides that, it is often elegant to grow structures organically. A maze of caves being generated during play, for example, should have new caves gradually added onto the map as and when needed.

The trouble with this is that since resources cannot be infinite, the cave-objects have to come from somewhere, and at last they come no more. The program must be able to cope with this, and it can present the programmer with real difficulties, especially if the conditions that will prevail when the supply runs out are hard to predict.

Inform does allow object creation during play, but it insists that the programmer must specify in advance the maximum resources which will ever be needed. (For example, the maximum number of beach stones which can ever be in play.) This is a nuisance, but means that the resulting story file will always work, or always fail, identically on every machine running it. It won't do one thing on the designer's 256-megabyte Sun workstation in Venice and then quite another on a player's personal organiser in a commuter train in New Jersey.

· · · · ·

If you want to create objects, you need to define a class for them and to specify N, the maximum number ever needed at once. Objects can be deleted once created, so if all N are in play then deleting one will allow another to be created.

Suppose the beach is to contain up to fifty pebbles. Then:

Class Pebble(50)
 with name 'smooth' 'pebble',
      short_name "smooth pebble from the beach";

Pebble is an ordinary class in every respect, except that it has the ability to create up to N = 50 instances of itself.

Creating and destroying objects is done by sending messages to the class Pebble itself, so for instance sending the message Pebble.create() will bring another pebble into existence. Classes can receive five different messages, as follows:

remaining()
How many more instances of this class can be created?

create(parameters)
Replies with a newly created object of this class, or else with nothing if no more can be created. If given, the parameters are passed on to the object so that it can use them to configure itself (see below).

destroy(I)
Destroys the instance I, which must previously have been created. You can't destroy an object which you defined by hand in the source code. (This is quite unlike remove, which only takes an object out of the tree for a while but keeps it in existence, ready to be moved back again later.)

recreate(I, ‹parameters)
Re-initialises the instance I, as if it had been destroyed and then created again.

copy(I, J)
Copies the property and attribute values from I to be equal to those of J, where both have to be instances of the class. (If a property holds an array, this is copied over as well.)

It's rather useful that recreate and copy can be sent for any instances, not just instances which have previously been created. For example,

Plant.copy(Gilded_Branch, Poison_Ivy)

copies over all the features of a Plant from Poison_Ivy to Gilded_Branch, but leaves any other properties and attributes of the gilded branch alone. Likewise,

Treasure.recreate(Gilded_Branch)

only resets the properties to do with Treasure, leaving the Plant properties alone.

If you didn't give a number like (50) in the class definition, then you'll find that N is zero. copy will work as normal, but remaining will return zero and create will always return nothing. There is nothing to destroy and since this isn't a class which can create objects, recreate will not work either. (Oh, and don't try to send these messages to the class Class: creating and destroying classes is called “programming”, and it's far too late when the game is already being played.)

▲▲ You can even give the number as (0). You might do this either so that Class Gadget(MAX_GADGETS) in some library file will work even if the constant MAX_GADGETS happens to be zero. Or so that you can at least recreate existing members of the class even if you cannot create new ones.

· · · · ·

The following example shows object creation used in a tiny game, dramatising a remark attributed to Isaac Newton (though it appears only in Brewster's Memoirs of Newton).

Constant Story "ISAAC NEWTON'S BEACH";
Constant Headline "^An Interactive Metaphor^";
Include "Parser";
Include "VerbLib";
Class Pebble(50)
 with name 'smooth' 'pebble' 'stone' 'pebbles//p' 'stones//p',
      short_name "smooth pebble from the beach",
      plural "smooth pebbles from the beach";
Object Shingle "Shingle"
 with description
         "You seem to be only a boy playing on a sea-shore, and
          diverting yourself in finding a smoother pebble or a
          prettier shell than ordinary, whilst the great ocean of
          truth lies all undiscovered before you.",
 has  light;
Object -> "pebble"
  with name 'smoother' 'pebble' 'stone' 'stones' 'shingle',
       initial "The breakers drain ceaselessly through the shingle,
               spilling momentary rock-pools.",
       before [ new_stone;
           Take:
               new_stone = Pebble.create();
               if (new_stone == nothing)
                  "You look in vain for a stone smoother than
                   the fifty ever-smoother stones you have
                   gathered so far.";
               move new_stone to Shingle;
               <<Take new_stone>>;
       ],
  has  static;
[ Initialise;
  location = Shingle; 
  "^^^^^Welcome to...^";
];
Include "Grammar";

In this very small adventure game, if the player types “take a pebble”, he will get one: more surprisingly, if he types “take a smoother pebble” he will get another one, and so on until his inventory listing reads “fifty smooth pebbles from the beach”. (See §29 for how to make sure identical objects are described well in Inform adventure games.) Notice that a newly-created object is in nothing, that is, is outside the object tree, so it must be moved to Shingle in order to come to the player's hand.

· · · · ·

However smooth, one pebble is much like another. More complicated objects sometimes need some setting-up when they are created, and of course in good object-oriented style they ought to do this setting-up for themselves. Here is a class which does:

Class Ghost(7)
 with haunting,
      create [;
          self.haunting = random(Library, Ballroom, Summer_House);
          move self to self.haunting;
          if (self in location)
              "^The air ripples as if parted like curtains.";
      ];

What happens is that when the program sends the message

Ghost.create();

the class Ghost creates a new ghost G, if there aren't already seven, and then sends a further message

G.create();

This new object G chooses its own place to haunt and moves itself into place. Only then does the class Ghost reply to the outside program. A class can also give a destroy routine to take care of the consequences of destruction, as in the following example:

Class Axe(30);
Class Dwarf(7)
 with create [ x;
          x = Axe.create(); if (x ~= nothing) move x to self;
      ],
      destroy [ x;
          objectloop (x in self && x ofclass Axe) Axe.destroy(x);
      ];

A new axe is created whenever a new dwarf is created, while stocks last, and when a dwarf is destroyed, any axes it carries are also destroyed.

Finally, you can supply create with up to 3 parameters. Here is a case with only one:

Class GoldIngot(10)
 with weight, value,
      create [ carats;
          self.value = 10*carats;
          self.weight = 20 + carats;
      ];

and now GoldIngot.create(24) will create a 24-carat gold ingot.

§3.12   Sending messages to routines and strings

§3.9 was about sending messages to Objects, and then in §3.11 it turned out that there are five messages which can be sent to a Class. That's two of the four metaclasses, and it turns out that you can send messages to a Routine and a String too.

The only message you can send to a Routine is call, and all this does is to call it. So if Explore is the name of a routine,

Explore.call(2, 4);   and   Explore(2, 4);

do exactly the same as each other. This looks redundant, except that it allows a little more flexibility: for instance

(random(Hello, Goodbye)).call(7);

has a 50% chance of calling Hello(7) and a 50% chance of calling Goodbye(7). As you might expect, the call message replies with whatever value was returned by the routine.

Two different messages can be sent to a String. The first is print, which is provided because it logically ought to be, rather than because it's useful. So, for example,

("You can see an advancing tide of bison!").print();

prints out the string, followed by a new-line, and evaluates as true.

The second is print_to_array. This copies out the text of the string into entries 2, 3, 4,… of the supplied byte array, and writes the number of characters as a word into entries 0 and 1. (Make sure that the byte array is large enough to hold the text of the string.) For instance:

Array Petals->30;
...
    ("A rose is a rose is a rose").print_to_array(Petals);

will leave Petals-->0 set to 26 and place the letters 'A', ' ', 'r', 'o', …, 'e' into the entries Petals->2, Petals->3, …, Petals->27. For convenience, the reply value of the message print_to_array is also 26. You can use this message to find the length of a string, copying the text into some temporary array and only making use of this return value.

§3.13   Common properties and Property

▲▲ Many classes, the Bird class for example, pass on properties to their members. Properties coming from the class Object are called “common properties”. Every item and place in an adventure game belongs to class Object, so a property inherited from Object will be not just common but well-nigh universal. Properties which aren't common are sometimes called “individual”.

The Inform library sets up the class Object with about fifty common properties. Story files would be huge if all of the objects in a game actually used all of these common properties, so a special rule applies: you can read a common property for any Object, but you can only write to it if you've written it into the object's declaration yourself.

For instance, the library contains the directive

Property cant_go "You can't go that way.";

This tells Inform to add cant_go to the class definition for Object. The practical upshot is that you can perform

print_ret (string) location.cant_go;

whatever the location is, and the resulting text will be “You can't go that way.” if the location doesn't define a cant_go value of its own. On the other hand

location.cant_go = "Please stop trying these blank walls.";

will only work if location actually provides a cant_go value of its own, which you can test with the condition location provides cant_go.

▲▲ Using the superclass operator you can read and even alter the default values of common properties at run-time: for instance,

location.Object::cant_go = "Another blank wall. Tsk!";

will substitute for “You can't go that way.”

▲▲ The Inform library uses common properties because they're marginally faster to access and marginally cheaper on memory. Only 62 are available, of which the compiler uses 3 and the library a further 47. On the other hand, you can have up to 16,320 individual properties, which in practical terms is as good as saying they are unlimited.

§3.14   Philosophy

▲▲ “Socialism is all very well in practice, but does it work in theory?” (Stephen Fry). While the chapter is drizzling out into small type, this last section is aimed at those readers who might feel happier with Inform's ideas about classes and objects if only there were some logic to it all. Other readers may feel that it's about as relevant to actual Inform programming as socialist dialectic is to shopping for groceries. Here are the rules anyway:

  1. Story files are made up of objects, which may have variables attached of various different kinds, which we shall here call “properties”.
  2. Source code contains definitions of both objects and classes. Classes are abstract descriptions of common features which might be held by groups of objects.
  3. Any given object in the program either is, or is not, a member of any given class.
  4. For every object definition in the source code, an object is made in the story file. The definition specifies which classes this object is a member of.
  5. If an object X is declared as a member of class C, then X “inherits” property values as given in the class definition of C.

Exact rules of inheritance aren't relevant here, except perhaps to note that one of the things inherited from class C might be the membership of some other class, D.

  1. For every class definition, an object is made in the story file to represent it, called its “class-object”. For example, suppose we have a class definition like:

Class Shrub
 with species;

The class Shrub will generate a class-object in the final program, also called Shrub. This class-object exists to receive messages like create and destroy and, more philosophically, to represent the concept of “being a shrub” within the simulated world.

The class-object of a class is not normally a member of that class. The concept of being a shrub is not itself a shrub, and the condition Shrub ofclass Shrub is false. Individual shrubs provide a property called species, but the class-object of Shrub does not: the concept of being a shrub has no single species.

  1. Classes which are automatically defined by Inform are called “metaclasses”. There are four of these: Class, Object, Routine and String.

It follows by rule (6) that every Inform program contains the class-objects of these four, also called Class, Object, Routine and String.

  1. Every object is a member of one, and only one, metaclass:
    1. The class-objects are members of Class, and no other class.
    2. Routines in the program, including those given as property values, are members of Routine and no other class.
    3. Constant strings in the program, including those given as property values, are members of String, and of no other class.
    4. The objects defined in the source code are members of Object, and possibly also of other classes defined in the source code.

It follows from (8.1) that Class is the unique class whose class-object is one of its own members: so Class ofclass Class is true.

  1. Contrary to rules (5) and (8.1), the class-objects of the four metaclasses do not inherit from Class.
  2. Properties inherited from the class-object of the metaclass Object are read-only and cannot be set.

· · · · ·

To see what the rules entail means knowing the definitions of the four metaclasses. These definitions are never written out in any textual form inside Inform, as it happens, but this is what they would look like if they were. (Metaclass is an imaginary directive, as the programmer isn't allowed to create new metaclasses.)

Metaclass Object
 with name,
      ...;

A class from which the common properties defined by Property are inherited, albeit (by rule (10), an economy measure) in read-only form.

Metaclass Class
with create    [ ...; ... ],
     recreate  [ instance ...; ... ],
     destroy   [ instance; ... ],
     copy      [ instance1 instance2; ... ],
     remaining [; ... ];

So class-objects respond only to these five messages and provide no other properties: except that by rule (9), the class-objects Class, Object, Routine and String provide no properties at all. The point is that these five messages are concerned with object creation and deletion at run time. But Inform is a compiler and not, like Smalltalk-80 or other highly object-oriented languages, an interpreter. Rule (9) expresses our inability to create the program while it is actually running.

Metaclass Routine
with call [ parameters...; ... ];

Routines therefore provide only call.

Metaclass String
with print          [; print_ret (string) self; ],
     print_to_array [ array; ... ];

Strings therefore provide only print and print_to_array.

REFERENCES
L. Ross Raszewski's library extension "imem.h" manages genuinely dynamic memory allocation for objects, and is most often used when memory is running terribly short.