Object chair "chair"
has supporter enterable
with name "chair",
before
[; Take : "It is too big to carted around.";
];
If the player attempts to take the chair, "It is too big to be carted around.",
will be printed and the chair's before returns true (1). So the action stops
and the prompt appears again.
Object chair "chair"
has supporter enterable
with name "chair",
before
[; Take : print "It's pretty heavy. ";
];
While this prints, "It's pretty heavy. ", and returns 0. So Inform next calls
its
built-in library routine, Take, which checks to see if the chair is touchable,
if the player is carrying too much already, etc. If all conditions
are right, it moves the chair to the player (inventory) and print_rets,
"Taken", thus returning a 1, stopping further
processing. Put together, this is what will show on the screen, "It's pretty
heavy. Taken."
| Knowing what is/returns true or false, 1 or 0, is essential for understanding Inform. |
| Command |
|---|
|
];
(end of routine, returns automatically, value returned depends on contents, usually 1, but some routines have a default value) |
|
print "string";
(prints string and returns 0) |
|
print_ret "string";
(prints string, carriage return (new line), returns 1) |
|
"string";
(prints string, carriage return (new line), returns 1, same as above) |
|
return;
(returns from a routine, no value, sometimes handy) |
|
return #;
(returns a specific numeric value) |
|
rfalse;
(returns false) |
|
rtrue;
(returns true) |
| Processing | Actions Performed |
|---|---|
| start of each turn |
Look or New Room
PrintorRun room's initial NewRoom MoveFloatingObjects Look Steps: 1a. A new-line is printed. The location's short name is printed (in bold-face, if possible). 1b. If the player is on a supporter, then " (on <something>)" is printed; if inside anything else, then " (in <something>)". 1c. " (as 1d. A new-line is printed. Now the `long description'. This step is skipped if the player has just moved of his own will into a location already visited, unless the game is in "verbose" mode. 2. If the location has a describe property, then run it. If not, look at the location's description property: if it's a string, print it; if it's a routine, run it. All rooms must provide either a describe property or a description of themselves. Now for items nearby: 3a. List any objects on the floor. 3b. If the player is in or on something, list the other objects in that. The library has now finished, but your game gets a chance to add a postscript. 4. Call the entry point LookRoutine. |
|
Parsing
Parses the player's input (breaks sentence down into understandable parts). |
BeforeParsing
ChooseObjects parse_name ParseNoun animate/talkable's grammar routine |
|
Before
An opportunity for your code to interfere with or block altogether what might soon happen. |
GamePreRoutine
player's orders' property react_before of all objects in scope before of current room before (action) of first noun |
|
During
The library takes control and decides if the action makes sense according to its normal world model: for example, only an edible object may be eaten; only an object in the player's possession can be thrown at somebody, and so on. If the action is impossible, a complaint is printed and that's all. Otherwise the action is now carried out. |
LibraryMessages |
|
After
An opportunity for your code to react to what has happened, after it has happened but before any text announcing it has been printed. If it chooses, your code can print and cause an entirely different outcome. If your code doesn't interfere, the library reports back to the player (with such choice phrases as "Dropped."). |
react_after of all objects in scope
after of current room after of first noun GamePostRoutine |
| end of each turn |
1.The turns counter is incremented.
2.The 24-clock is moved on. 3.Daemons and timers are run (in no guaranteed order). 4. each_turn takes place for the current room, and then for everything nearby (that is, in scope). 5. The game's global TimePasses routine is called. 6. Light is re-considered (it may have changed as a result of events since this time last turn). |
[ Initialise;
location = field;
move lantern to player.
"You don't know how you got here. You were just strolling
along, minding your own business when this big troll...";
];
Of course, one puts the printed string last as that will return true.
player = newselfobj;Doe Tip:
| When changing the player in Initialise use the equal sign. Changing the player elsewhere in your game, use ChangePlayer(newselfobj);. Why? Well, ChangePlayer() performs all kinds of complicated maneuvers (MoveFloatingObjects(), OffersLight(), etc.) that Initialise also performs. So if you call ChangePlayer in Initialise, they are done twice. Anyway, Zarf recommends = in Initialise. Good enough for me ;-). |
location = office; location = chair;..instead of...
location = office; move player to chair;Doe Tip:
| I have never had a satisfactory explanation of why the first works and the second doesn't. But to start the player on or in an object, use location. It's also a good idea to set the player's location and/or change the player before doing anything else. |
[ Initialise k l;
location = field;
move lantern to player;
box ""
"Quote"
"Blah, blah..."
""
" by Blah blah"
"";
width = 0->33;
if (width==0) width = 80;
if (width > 25) l = (width - 25)/2;
else l = 1;
style bold;
spaces l;
print "[Press any key to begin.]";
style roman;
@read_char 1 i;
@erase_window $ffff;
"You don't know how you got here. You were just strolling
along, minding your own business when this big troll..."
];
width = 0->33; gets the default screen size (in most cases). The rest of
that calculation is used to center the prompt, "[Press any key to begin.]", (25
characters). @read_char waits for the player to press a key and @erase_window
clears the screen.
print "Do you want to restore a saved game? (y or n) ";
@read_char 1 i;
if (i == 'y' or 'Y')
{ print "Y^"; RestoreSub(); }
print "N^";
The print "Y^"; is to reflect what the player typed and provide a carriage
return without exiting (returning true) from the Initialise routine.
SetTime(hours, rateperturn);Hours is the current time, ratepersturn is how many minutes you want to allot to each turn. The only thing tricky about that is using a 24-hour clock.
general <- true ~general <- falseProperties are permanent variables, so their contents can vary, can hold any value (numbers, strings, routines). The ones already declared in the library (before, after, etc.) are all global.
Object chair "chair"
has supporter enterable
with name "chair" "plastic" "ordinary", <--- strings
description "It's an ordinary, plastic chair.", <--- string
number 0, <--- number
before <--- routine
[; Enter : if (man in chair)
"It seems to be occupied.";
];
Doe Tip:
| Familiarize yourself with the pre-declared attributes and properties in linklpa.h. |
General is a built-in all-purpose attribute that comes in handy when using
daemons and
timers. StartDaemon(object);, give object general;/StopDaemon(object);, give
object ~general; is a good way to keep track of when a daemon is still running.
if (object has general) daemon is still running |
Attribute foundordone;The following declares a global property (also at top of code, before including parser.h).
Property max_size 10;Global means "known to all". So every object is considered to have all the library and author declared attributes and global properties.
Object man "man"
has animate male
Global properties
have a default value of 0. For instance, every object
is considered to have a before routine. If no before routine for that object
actually exists, its value is 0. Thus when a before routine is included for a
object its value is usually 1 (true).
Object chair "chair"
with name "chair",
before
[; Enter : if ((child(self)~=0) && (child(self) has animate))
"It seems to be occupied.";
];
The advantage of a global property is all objects are assigned
that property with either zero or the initial value you give it. So testing
an object for that property, an object for which you did not include
a specific instant of that property, will never cause an error.
if (RunRoutines(chair,after)~=0) rtrue;For instance, since after is a global property, RunRoutines will always return true or false (if the object exists), regardless of whether the object has an after routine or not. If it doesn't have an after routine, it will return 0, not an error.
Object man "man" has animate male with name "man", intelligence 2;Local means "keep confined to the object in which it is declared". No other object knows about this property and if you test for it in another object which does not have it, Inform WILL return an error. You can, however, use the same (same is defined by having the same name) local property in several objects.
Object dog "dog" has animate neuter with name "dog" intelligence 1;Note the difference in value, remember properties are variables, so their content can vary.
if ((second~=0) && (second.intelligence==1)) "Smart enough to fetch a map, but not read it.";...without first testing if an object HAS that local property
if ((second~=0) && (second==man))
{ if (second.intelligence == 2)
"Smart enough to read a map, but not to ask for one when lost.";
}
...or, the much better...
if ((second~=0) && (second provides intelligence))
{ if (second.intelligence == 2) etc.;
Provides
is the way you test for the EXISTENCE of a local property. You need to
test if an object has a particular local property before testing it's value or,
as stated, an error will result.
| Since the maximum number of attributes can be reached fairly quickly, if (object provides localproperty) can make a nice substitute for if (object has attribute). |
Always test SECOND first to see it isn't zero before testing it for anything
else. Inadvertently condition checking second when it is zero...
if (second has general) etc;...is the biggest cause of vile zero errors from hell. |
if ((second~=0) && (second provides intelligence))relies on the fact that if the first part of an if statement is false...
if ((second~=0) - a false evaluation would mean it IS zerothe second half of the statement is never executed. Tedious, yes, but that way you will avoid most (99%) of the vile zero errors from hell. (I wish I always remembered to do it.)
| Command | Example |
|---|---|
|
has
(assigns an attribute to an object, only used once, even if multiple attributes are listed) |
Object dog "dog has animate neuter general with name "dog"; |
|
has
(tests if an attribute is true) |
if (dog has general) |
|
hasnt
(tests if an attribute is false) |
if (dog hasnt general) |
|
give object attribute
(turns on an attribute) |
give dog general; |
|
give object ~attribute
(turns off an attribute) |
give dog ~general; |
| Command | Example |
|---|---|
|
with
(assigns a property to an object, only used once, even if multiple properties are listed) |
Object dog "dog has animate neuter general with name "dog", intelligence 1; |
|
if (object.property == value)
(tests the value of a property) |
if (dog.intelligence == 1) |
|
object.property = value
(assigns a value to a property) |
dog.intelligence = 0; |
| Command | Example |
|---|---|
|
provides
(tests if an object has a local property) |
if (dog provides intelligence) |
| To anyone familiar with Pascal, using a period will not look strange. In fact, it might help to think of an object as a "record" and the properties as record "fields". |
An incredibly easy mistake to make
when testing properties (both global and local) is to accidentally leave
off the preceding object name.
if (intelligence == 1) should be if (object.intelligence == 1) |
| Become thoroughly conversant with grammar.h. |
Verb 'climb' 'scale'
* noun -> Climb
* 'up'/'over' noun -> Climb;
Verb defines a new verb, climb is the verb, scale the synonym, alternate
prepositions can be listed together separated by a forward slash, noun is
whatever noun the player enters and ClimbSub is the verb routine executed when
the climb command is entered by the player. Pretty straight forward.
Verb 'clamber' = 'climb';I don't think you will find creating new verbs difficult. However, creating new verbsubs (routines) is a tad more complicated.
| Verb routines are always followed by Sub in code, but when part of a grammar declaration, like in grammar.h, Sub is left off. Bit strange, but that is how it is. |
Replace BurnSub;Now a new Burnsub routine needs to be provided.
[ BurnSub;
if ((match notin player) || (match in player && match hasnt light))
"You have no source of flame.";
if (noun has animate) return L__M(##Take,3,noun);
if (noun has light)
print_ret (The) noun, " is already burning.";
if (noun has is_paper)
{ remove noun;
print_ret (The) noun, " catches fire and burns down to nothing.";
}
L__M(##Burn,1,noun);
];
Doe Tip:
| Actually including burn in a game is not recommended. It requires a LOT of coding. |
Object bonfire "bonfire"
with name "bonfire" "fire",
description
[; if (self has light) "The bonfire burns merrily away.";
"Someone has stacked a bonfire here, ready to be burned.";
],
before
[; Burn : if (self hasnt light)
{ if ((match in player) && (match has light))
"You light the bonfire and flames leap up.";
}
];
Of course, it would probably also use a timer to burn the bonfire down to
ashes.
Object man "man"
has animate male
with name "man",
life [; etc.;
No before routine
, so Burnsub is called and prints "I don't suppose the man
would care for that." (since man has animate, Burnsub uses the default Take
message, #3).
Object chair "chair"
has supporter enterable
with name "chair" "plastic" "ordinary",
description "It's an ordinary, plastic chair.",
number 0,
before
[; Enter : if (man in chair)
"It seems to be occupied.";
];
No response in the before routine for Burn
, so BurnSub is called and prints
"This dangerous act would achieve little." (Burnsub uses the default Burn
message, #1).
| Conditions you want specific to an object (in this case, bonfire already lit and lit match in player's possession), have to be tested in that object's before routine itself, because the corresponding verbsub will only be executed AFTERWARDS. |
| Command | Example |
|---|---|
|
Verb
(creates a new verb) |
Verb 'xyzzy'
* -> XYZZY;
|
|
=
(creates a synonym) |
Verb 'clamber' = 'climb'; |
|
Extend
(extends the definition of an existing verb, defaults to last place in list) |
Extend 'climb' * 'up'/'down' noun -> Climb; |
|
Extend verb last
(extends the definition of an existing verb, places at end of list, same as above) |
Extend 'climb' last * 'up'/'down' noun -> Climb; |
|
Extend verb first
(extends the definition of an existing verb, places at front of list) |
Extend 'climb' first * 'up'/'down' noun -> Climb; |
|
Extend verb replace
(extends the definition of an existing verb, completely replaces default definition in grammar.h) |
Extend 'climb' replace * 'up'/'down' noun -> Climb; |
Class button_class
has static switchable
with name "button",
before
[; Push : print "You push the ", (name) self;
if (self hasnt on){ <SwitchOn self>; " on"; }
<SwitchOff self>; " off";
];
No actual button exists yet. A class declaration just creates a category,
it does not create an object, an instance of that class.
Object TVButton "TV button" class button_class with name "tv" "television"; ...or... button_class TVButton "TV button" with name "tv" "television";Would create an instance of the button_class, an actual object. Class objects are created in the same way regular objects are. Using button_class on the first line is just short hand for Object etc., class button_class on two lines.
if (TVbutton ofclass button_class) true if a member of this class, false if not false if not a class object at all, no error will resultDoe Tip:
| Just as if (object provides localproperty) can make a nice substitute for if (object has attribute) [see attributes, #4], so can if (object ofclass class). Both help "get around" that maximum number of attributes limit. |
Class button_class
has static switchable
with name "button",
before
[; Push : print "You push the ", (name) self;
if (self hasnt on){ <SwitchOn self>; " on"; }
<SwitchOff self>; " off";
];
button_class TVButton "TV button"
with name "tv" "television";
Because TVButton object has no before, the before of the button_class is
called. But what happens when a class object has a property the class also has?
This differs depending on whether the property is
additive
or not.
Class button_class
has static switchable
with name "button",
before
[; Push : print "You push the ", (name) self;
if (self hasnt on){ <SwitchOn self>; " on"; }
<SwitchOff self>; " off";
react_before
[; Push : if (player in puddle)
"You get a shock and dance back.";
];
button_class TransButton "Transformer button"
with name "transformer",
react_before
[; Push : if (player in puddle)
"You might think twice about that.";
];
(I had the player dying when pushing the transformer button while standing in a
puddle, but that wouldn't illustrate the point). Since a react_before routine
is non-additive, once you
include
a react_before in the object, the class react_before will never be executed.
If the object has no react_before of its own...
button_class TransButton "Transformer button" with name "transformer";...then the class react_before WILL be executed. I don't know what Graham calls this, but I call it "overwriting". An object property will ALWAYS overwrite the class property when the property isn't additive. Also note that the whole property, not just parts of it (i.e., the whole react_before, not just the Push action in it), is non-additive.
| Using classes is a simple way to give things the same name distinguished only by different adjectives (television button, elevator button). This makes parsing easier and avoids the need to include more complicated parse_name routines. |
Object TVButton "TV button"
class button_class
with name "tv" "television",
before
[; Push : if (self hasnt on)
"You watch too much television already.";
];
Normally, in an ordinary object, the TVButton's before returns 1 (true) by
printing the string and processing stops there.
This also happens with an additive class property
. Since the TVButton's before is called first AND returns 1, the class before
will never be executed. If you want the class before routine to also execute,
the object before must return 0...
Object TVButton "TV button"
class button_class
with name "tv" "television",
before
[; Push : if (self hasnt on)
print "You watch too much television already. ";
];
(changing print_ret to print returns 0) OR use a "superclass" operator
Graham has provided. This will be a bit hard to explain, so let's
review first.
object.class::property;...because it calls a class property directly.
Class button_class
has static switchable
with name "button",
before
[; Push : print "You push the ", (name) self;
if (self hasnt on){ <SwitchOn self>; " on"; }
<SwitchOff self>; " off";
],
react_before
[; Push, Touch : if (player in puddle)
"You get a shock and dance back.";
];
button_class TransButton "Transformer button"
has on
with name "transformer",
react_before
[; Push : if ((player in puddle) && (self hasnt on))
"You might think twice about that.";
self.button_class::react_before();
default : if (action~=##Examine)
self.button_class::react_before();
];
Remember a whole property is non-additive, not just parts of it. So it doesn't
matter if a particular action returns 1 or 0, once the property is present in
the object it completely overwrites the class property. But including the
superclass operator forces a call of the class property. (At that point the
object property
return
value matters, as the superclass operator will only be used as long as the
object property doesn't return 1 before invoking it).
Class button_class
has static switchable
with name "button",
before
[; Push : if (self hasnt on) <SwitchOn self>;
<SwitchOff self>;
Touch : if (player in puddle)
"You get a shock and dance back.";
];
button_class TVButton "TV button"
has on
with name "tv" "television",
before
[; Push : self.button_class::before();
"You push ", (the) self, ".";
];
Not that good an example, but it does show another way to accomplish the same
thing often shown above. For Push, the superclass operator calls the class
before first and because it returns 0, the rest of the object before is executed
next. If the class before returned 1, the rest would not executed. This just
reverses the usual order of calling class properties.
Touch is unaffected, because before is an additive property and the object
before has no response for Touch. So the object automatically calls the
class before to get its Touch response.
Class button_class
has static switchable
with name "button",
before
[; Push : print "You push the ", (name) self;
if (self hasnt on){ <SwitchOn self> " on.";
else <SwitchOff self>; " off";
];
Object TVButton "TV button"
class button_class
with name "tv" "television",
description
[; print (The) self, " is ";
if (self has on) "on.";
"off.";
];
Object VCRButton "VCR button"
class button_class
with name "VCR";
The self in TVButton's description, because it is in the object itself,
refers to the TVButton. In the class declaration it refers to
whichever class object the class is CURRENTLY PROCESSING. So pushing the
TVButton prints, "You push the the TV button on." (or "off"), and turns it on
or off. Pushing the VCRButton prints, "You push the VCR button on." (or
"off"), and turns it on or off. It's another one of those neat
class features.
| Use (name) self in a class declaration instead of the usual (the) self, to get the current object's name to print correctly. A classy idiosyncrasy. |
| Command | Example |
|---|---|
|
class
(declares a class) |
Class button_class |
|
class
(in an object, declares that object as a member of that class) |
Object TVButton "TV Button" class button_class |
|
class name
(in an object, declares that object as a member of that class, same as above) |
button_class TVButton "TV Button" |
|
ofclass
(tests an object's class membership) |
if (TVButton ofclass button_class) |
|
self
(in a class declaration, refers to the class member currently being processed by the class) |
print "You push the ", (name) self, "."; |
|
object.class::property
(calls the class property directly) |
TVButton.button_class::before(); |
| additive properties |
after before describe each_turn life name |
| There is no capacity for double arrays in Inform, one of its biggest drawbacks and why creating a database in Inform is almost impossible. |
found_in secret_passage libraryThis is a two element array: secret_passage is element 0, library is element 1. The word, array, is not included in a property array, the only way you can tell a property is an array is by the multiple number of elements.
Trying to access non-existent array elements, going outside array bounds, is
probably the second biggest cause of vile zero errors from hell (see
Attributes, #4.)
Array test_array ->10; (declaration) test_array->10 = 1; (out of bounds, remember it would be 0-9) |
found_in secret_passage 0Now the door will only appear in the secret_passage and not in the library until the right conditions are met.
Object library "Library"
with etc.
e_to
[; if (hidden_door hasnt open) "You can't go that way.";
return hidden_door;
];
Object secret_passage
with etc.
w_to hidden_door;
Object hidden_door "hidden door"
has door openable static
with name "hidden" "door",
when_open
[; if (location==secret_passage) "The hidden door opens west.";
"The bookcase is open, revealing a secret passage east.";
],
when_closed "The hidden door is closed.",
door_dir
[; if (location==secret_passage) return w_to;
return e_to;
],
door_to
[; if (location==secret_passage) return library;
return secret_passage;
],
before
[; Close : if (location == library)
"It doesn't move, you'll have to close it some
other way.";
],
found_in secret_passage 0;
Object -> big_book "big book"
with name "book" "big",
description "An unusually large book, it appears as if the title
was painted on.",
before
[; Take : "It moves a little, but otherwise seems attached to
the bookcase.";
Push, Turn : "It doesn't budge.";
Pull : print "You pull the book. ";
if (hidden_door has open)
{ (hidden_door.&found_in)-->1 = 0;
give hidden_door ~open;
MoveFloatingObjects();
"^^~Whoosh!~ The bookcase swings closed.";
}
(hidden_door.&found_in)-->1 = library;
give hidden_door open;
MoveFloatingObjects();
"^^~Whoosh!~ The bookcase swings open revealing a
secret passage to the east.";
];
MoveFloatingObjects() is a built-in library routine automatically called at the
beginning of each turn to adjust objects that can be
found_in
in more than one location. But when one changes found_in directly,
MoveFloatingObjects() needs to be called immediately to make the necessary
adjustments.
Option Hint1 "How do I get the kitten out of the tree?"
class Hintobj
with the_hints "Try chopping down the tree."
"Too destructive? Try a garden hose."
"No hose? Read raif for other ways to get the
kitten down.";
the_hints is a string array. Notice, as with found_in, there is no comma
between elements.
Class Hintobj
with hints_seen 0,
hint_done 0,
the_hints "Put list of hints here (in the object of this class).",
description
[; self.showhints(); rtrue;
],
showhints
[ max i k;
max = (self.#the_hints / 2);
for (i = 1: (i <= max): i++)
{ if ((i==1) && ((max > 1) (self.hints_seen < max)))
print "There are ", (LanguageNumber) max, " increasingly
clear hints. Press H for another hint, any other
key to quit.^^^";
print "(", i, "/", max, ") ";
print (string) (self.&the_hints-->(i-1));
new_line; new_line;
! How many times through this loop? That is the number of the
! current hint. Starting with 1 = 0 hints_seen.
if (self.hints_seen == (i-1))
self.hints_seen = self.hints_seen + 1;
! Only call read_char if currently on the last hint seen and
! skip on the very last hint.
if ((i < max) && (i == self.hints_seen))
{ @read_char 1 0 0 k;
if (k ~= 'H' or 'h') return;
}
}
];
max uses the # operator (divided by two, that confusing byte/word thing again)
to find the number of strings in each hint object's the_hints array.
self.&the_hints--> uses (i-1) because the for loop starts at 1 instead of 0
(since I find it easier to think of 1-10 than 0-9, as many do). Each time a new
hint is read the hints_seen property is incremented by 1, so the next time
previously seen hints will automatically show on the screen.
| Command | Example |
|---|---|
|
Array
(declares an array) |
Array test_array-->5; Array test_array-->1 2 3 4 5; Array test_array string 5; Array test_array->"Name"; Array test_array->'N' 'a' 'm' 'e'; Array test_array table 5; Array test_array table 'name1' 'name2' 'name3' 'name4' 'name5'; Array test_array table [; "Name One"; "Name Two"; "Name Three"; "Name Four"; "Name Five"; ]; |
|
array types:
-> (byte) --> (word) string (array of characters, bytes) table (array of strings, words) |
see above |
|
#
(returns length of an array stored in a property) |
len = (obj.#property_array) / 2; |
|
&
(accesses an array stored in a property) |
if (obj.&property_array-->0==1); |