(IntFic = InteractiveFiction)
LucianSmith set this page up and IntFicInformFaq to try to address a need.
Use this page for three things:
- If you have an Inform question, whether or not you know the answer, post it here. Someone will answer it eventually.
- If you have an Inform answer, whether or not someone asked the question yet, put it up here.
- If you see an Inform answer that you feel could be worded better, feel free to change it! We may slip sometimes, but overall, it will improve.
(If you're having formatting problems, see TextFormattingRules)
I. Definitions
- What the heck is an Attribute?
- An Attribute, in Inform, is a sort of boolean flag attached to an object; an object either "has" the attribute, or it doesn't. Attributes are generally used to identify an object as participating in a binary state. Older inform games also used attributes to indicate that an object was a member of a class, but the ofClass operator introduced in Inform 6 is more efficient for this sort of thing. Examples of library-defined attributes include "light" (an object that "has" light provides illumination to its surroundings), "scenery" (the object is part of the background), "container" (the object can be used to contain things), "animate" (the object is alive), and others.
- Attributes may also be defined by the user by way of the "attribute" directive.
- The following syntax is often used when dealing with attributes:
- give (obj) (attribute); <- to set an attribute.
- give (obj) ~(attribute); <- to un-set an attribute.
- (obj) has (attribute) <- evaluates to "true" if the object has the attribute set;
- (obj) hasnt (attribute) <- evaluates to "true" if the object does not have the attribute set. Initial attributes are set by placing them in the "has" list for the object's definition. You might understand attributes a bit better if you think of "has" as "is". For example, (obj) has container tests if the object in question "is" a container or not. Nearly anything that can be expressed with "is" versus "isn't" in natural language is best done with an attribute in an Inform program.
- What on earth is a Global?
- A "global" variable is one which can be accessed from anywhere within the game. The use of global variables is generally discouraged in programming, as it can lead to unexpected side effects (i.e., the variable may be changed by any function) Global variables may be set to a value by any function, and that value remains stored in the global after the function terminates.
- Is this my Property or yours?
- Most properties are declared by the library. These properties generally contain either
- a number
- the name of an object
- a dictionary word
- a string of text
- a function
- an array of the first three options. These are used to "define" the object.
- The programmer can also define a property using the "property" directive. The number of such properties is strictly limited, but they are faster to access than the newer substitute,
- Personal properties can be attached to a class or object, simply by adding the name of the property to the property list of the object. A (theoretically) unlimited number of such properties can be made available.
- A property can also be declared to be "private", in which case it can only be accessed by routines attached to the object itself.
- A property is referenced as obj.prop; One useful operator for properties is "provides" obj provides property evaluates to true if the object has the property in question. Note, however, that to an outside function, obj provides prop will evaluate to false for a "private" property
II. Structure of an Inform file
- What does an Inform file look like when you begin coding?
- Completely blank. Well, actually, to compile, an Inform program must at least contain a Main() routine. Normally, the Main routine is handled by the library. Fairly early in the file (though not necessarily at the very top) you'll probably want to include the following three lines to link the standard library
include "parser";
include "verblib";
include "grammar";
.
- It may be a good idea to highly modularize large games, so that the actual .inf file for the game contains almost entirely Include statements. Note, however, that inform limits the number of included files to 65. It is possible to recompile Inform with an increased inclusion limit, if so desired.
- Where do I put the title information?
- At the beginning. Inform requires that the constants STORY and HEADLINE be defined before the inclusion of the parser. STORY holds the title of the game ( by convention, in all capital letters), and HEADLINE contains the game banner. In fact, the only command you're likely to put before the STORY and HEADLINE definitions is the "Switches" statement, which _must_ be the first statement in the file.
- Where do I put Global variables?
- Global variables may be declared at any point, prior to their first use. However, good programming style would suggest that global variables be defined all together.
- Where do I define Attributes?
- Attributes are the same as global variables; so long as their declaration comes before their use, it's all kosher to Inform. Again, style dictates placing all attribute definitions together near the top of the file.
- Ditto Properties!
- Ditto. Note, though, that only "global" properties need be declared at all. A property declared with a default value will be applied only to those objects whose definitions come after the property definition.
- So, what should a good Inform file look like?
- This example is a highly modularized inform file. The names of the include files should give you an idea of what they contain:
Switches rsv5;
Include "Constants";
Include "Globals";
Include "Attributes";
Include "Properties";
Include "Library";
Include "Rooms";
Include "NPCs";
Include "Misc";
Include "Initialise"; (Note the British "s" rather than the American "z.")
III. Mapping out your locations
- How do I create a location?
- It's dark in here...turn on the light!
- Inform does not consider a room to be significantly different from any other object, and objects don't emit light by default. Give the room the "light" attribute. Alternatively, define a class (called, say, Room) which gives the attribute of "light". If you don't want any darkness at all, one trick is to give the player-object the light attribute.
- Where's that bathroom that I built?
- You may have a very Euclidean sense of geometric relationships, but Inform doesn't. Rooms are connected by their properties. Each direction has an associated (dir)_to property (e_to, w_to, etc), which should either contain the room, or a function that returns the room, which the player reaches by traveling in that direction. Note that such transitions are not two-way! If the bedroom defines the bathroom as its e_to, the bathroom should define the bedroom as its w_to (unless the physics of the situation are more complicated).
- There's an awful lot of scenery here but I can't find it!
- Hey, how come I can pick up this four-poster iron bed?
- Inform does not infer anything about the size of an object. Giving an object "static" makes it non-portable. Alternatively, giving the object "scenery" both makes it static, and omits it from room descriptions.
I made something 'concealed' and now I'm having problems. What's going on?
- If the player put something inside a 'concealed' object, it will be concealed, too. Also, a player cannot go through a concealed door.
I'm trying to make a rope. What should I keep in mind?
- Stop now! Run away, run away! Seriously, ropes are extremely difficult to program correctly in IF, and you would be best advised, if just starting out, to scrap the idea and go on to something a little simpler, like natural language parsing (just kidding). If you absolutely must code up a rope, check out IntFicRope for some handy tips, or IntFicRopeTips for old news:rec.arts.int-fiction posts on the topic.
I'm trying to make a liquid. How should I do it?
I found the best way to give any liquids the attribute is_liquid and then also
create the attribute liquid_container. One can then create a liquid like, say,
water, and a container like a bottle or whatever, and it's quite comfortable to
code the reactions now, together with some default refure lines like "You cannot put the liquid on the slab of stone. The code for CHRISTMINSTER has a
some cool liquid code into it too, and is worth both reading and playing.
Björn Ludwig, 27.5.2000.
What's a good way to handle NPC conversation?
- Well, most of r.a.i-f seem to think that using the built in ask-tell model is the best possible way to handle conversation. However, there's still a lot to be said for menu-driven conversation. It manages to avoid Guess-the-word problems, and keeps the player from being distracted by an NPC "Not Knowing anything about" his own middle name. A library called Converse.h is available at the IfArchive, which handles basic menu-driven conversations. Author L RossRaszewski provides full support for this library, and is currently considering upgrades.
What exactly happens when a player inputs a command? What are the steps, and where are the places I can change things?
- [This question is probably best answered by a graphical map. Any takers?]
How do I "bypass" the Inform "Banner" at the beginning of a game? I'm trying to emulate the opening of Infocom's "Trinity," which starts with the introduction, and them moves directly to the first room without printing any title information. I see that the InformDesignersManual says, "...if [Initialise] returns 2, then no game banner will be printed at once" (p. 125). --How on earth do I do that? --JayGoemmer
The last line of your Initialise should read: return 2; -- L RossRaszewski
Here's the actual working source code excerpt (courtesy JayGoemmer):
[ Initialise;
Location = Palace_Gate;
print "^^ Sharp words between the superpowers. [snip]
...a contemplative stroll through the Kensington Gardens.^^^";
return 2;
];
Please note that you _must_ use the "print" directive. Otherwise,
the string will be printed and return a "1" (or "true"), and never
reach the "return 2." Also, Inform 6 is case-sensitive, and variant
capitalization as minimal as "Return 2" will cause a fatal error message
during compilation.
Q. What if I want to print the game banner later in the game, if I've already
bypassed it as above?
A. Simply include the following line (which "calls" the Banner routine) in the
appropriate routine:
Banner();
[Generalizing the previous question:]
What does it mean when something 'returns' a value?
- When something returns a value, this means that a number is being sent to whomever wanted to run the 'something' (usually a routine, but could also be a string) in the first place. Every time almost anything happens, some number is returned, but this is hard to see, because there are a lot of defaults. These are the different ways to return a value:
- The command 'return'. To use this, just type 'return ___' (as in the above question).
- Print something using the shortcut of just using quotes, instead of the command, 'print'. This will generate the print_ret action, which acts like print, but adds a new-line to the end of the statement, and returns the value of '1' to whoever called it. ('1' is also referred to as 'true')
- Do nothing. This will return *either* a 1 (true) or a zero (false), depending on the context. A stand-alone routine returns true be default. A routine which is part of an object definition returns "false" by default
How do I use returned values myself?
- Set a variable equal to the routine (or whatever) that you're calling. As in:
x = thing.routine();
- Then, x will contain the value returned by thing.routine.
Q. How do I implement location-sensitive hints that the player can read by
typing "HELP" at the prompt?
A. Let's say you have a room object named "Campsite." Insert the following code
after including "Grammar":
! Synonyms from L. RossRaszewski's "Hints.h"
Verb "help" "hint" "hints" "clue" "clues"
* -> Help;
[HelpSub?;
switch (location)
{
Campsite: CampsiteHelp?();
.
.
.
default: "Sorry, there are no hints available for this location.";
}
];
[ CampsiteHelp?;
print "^Appropriate ~HELP~ text.^";
];
Now, when the player types "HELP" in the Campsite room,
Inform will call the "CampsiteHelp?" routine and print the
"Appropriate ~Help~ text" message.
Q. What if I want situation-dependent hints, instead?
A. Wow, don't ask for much, do you? This is very tricky, and not simple to implement. First of all, a general tip: Do not start working on this until the game is *completely finished*! Too many things can change before you're done that can invalidate a lot of effort on your part if you try to do this too early.
There are two methods of doing this, both different from each other, and both appropriate for different systems. The first involves setting flags, and the second involves moving objects around.
In either method, you want to keep track of up to three events:
- When the player has access to a puzzle,
- When the player has the materials necessary or has visited the appropriate locations to enable them to solve the puzzle, and
- When the player has solved the puzzle.
At each stage, you need to either set a flag, or move an object.
If you are setting flags, probably the easiest method is to give a property to an object (say, 'Score'), which starts out with the value 0, like so:
Object Score
with tofupuzzle 0,
bobpuzzle 0,
vogonpuzzle 0;
Then, when the player encounters the tofu puzzle, set that value to 1, with the command:
score.tofupuzzle = 1;
When the puzzle is solvable, increment it to 2 in the same manner, and when the puzzle is solved, increment it again to 3. Be careful! You only want to set these values once per event! For example, if everything the player needs to solve the tofu puzzle is in the kitchen, you don't want the tofupuzzle value reverting to 2 every time the player enters the kitchen. Likewise, you want to be sure to set the value *at least* once--if there are multiple ways to solve the puzzle, all of them should set the flag to 3.
To use the moving objects method, the simplest way is to use menu objects. (menus.h, by GrahamNelson is one option, as is AltMenu?.h by L.RossRaszewski) When a puzzle is encountered, its question object is moved into the help menu. When it is solved, it is moved back out again. This is a particularly elegant method, because it means that you are accomplishing two tasks at once--tracking the player's progress, and providing them with hints. Keep in mind the same problems as before--you don't want items popping back up on the hint menu that have already been solved because the player tripped the same trigger more than once.
Q. I'm using the test 'parent(player)' to check for location. Is this OK?
A. It's better to look at the variable 'real_location' if you want to know the room the player is in. parent(player) will yield the direct parent of the player, which could be a chair or sofa or even 'thedark'. real_location will always contain the player's, uh, real location, as long as you follow the rules about moving the player from room to room. (Either use <Go [direction object]> or PlayerTo?()).
Q. How do I implement a flag, and how do I turn it "on" and "off" in a routine? Do I need to declare it before using it?
A. A 'flag' can be created in several different ways. One way is to create a global variable, at the beginning of your program:
Global flag1;
This value will default to '0', unless you declare it to be otherwise:
Global flag1 = 1;
This method is not generally recommended, however, due to its 'inelegance'. A better way is to attach a property to an object; preferably, the object the flag relates to. So, if your flag was telling you how full your coffee cup was:
Object coffeecup "coffee cup"
with filled 0;
coffeecup.filled now contains the value '0', and may be altered as needed.
A third method is to use attributes. These are the most efficient, spacewise, since they can only be 'true' or 'false' for any particular object.
The 'general' attribute can be used in this way. Say, in our prior example, we decide that the coffee cup will either be filled or empty--no gradations. When the cup is full, then, we will want to 'give coffecup general', and when it is emptied, we will 'give coffeecup ~general'. ('~general' takes away the attribute).
To test if something has an attribute, use 'has' and 'hasn't', as in:
Object coffeecup "coffee cup"
with description [;
if (self has general) "The cup is full.";
else "The cup is empty.";
];
The three methods might be used in the following ways:
With a global value:
Global filled 0;
Object coffeecup "coffee cup"
with description [;
if (filled) "The coffee cup is full.";
else "The coffee cup is empty.";
],
before [;
Fill: filled=1;
"You fill the cup.";
Empty: filled=0;
"You empty the cup.";
];
[Note that more work would have to be done to make this work realistically in an actual game--determining what, exactly, was filling the cup, for instance.]
Using the attribute method is almost identical:
Object coffeecup "coffee cup"
with description [;
if (self has general) "The coffee cup is full.";
else "The coffee cup is empty.";
],
before [;
Fill: give self general;
"You fill the cup.";
Empty: give self ~general;
"You empty the cup.";
];
[Note that since it was undeclared, the object coffecup starts off without the 'general' attribute. If we had added a 'has general' the opposite would be true.]
The property method would be used like this:
Object coffeecup "coffee cup"
with description [;
if (self.filled) "The coffee cup is full.";
else "The coffee cup is empty.";
],
filled 0;
before [;
Fill: self.filled = 1;
"You fill the cup.";
Empty: self.filled=0;
"You empty the cup.";
];
With both the global and the property-based methods, we could define finer gradations of the 'filled' concept. It could be 0 for empty, 1 for a few drops, 2 for half-full, and so on.
Q. How do I implement a timer and make it "count down?"
A. You'll need to include "time_left" and "time_out"
properties in an object, as follows:
Object Bomb "thermonuclear warhead" Missile_Silo
with name "bomb" "warhead"
time_left 0,
time_out
[;
deadflag = 1;
"^ ~It's the end of the world as we know it
^ And I feel fine . . .~
^^ --R.E.M.";
],
has static (etc., i.e., "attributes") ;
You can start a timer and set its initial number of turns
by including the following line in a routine:
StartTimer? (Bomb, 3);
The timer will count down to zero and run the "time_out"
routine. To test this, try including the line above in an
"Initialise" routine.
Q. How do I add or subtract points from a player's score?
A. score = score + 5; (read as "set 'score' to the present value of 'score' plus five") will increment a player's score by 5 points. However, this will happen whenever the line is encountered. If you want the score to be incremented only once per event, Inform has a task facility: define a constant called NUMBER_TASKS, equal to the number of scored-tasks, and another called TASKS_PROVIDED. Then define an array called TASK_SCORES, and place the score for eah task in it. whenever a task is completed, call Achieved(#), where # is the index in the array (they start at 0, not 1) of the task. On the first call, and only the first call, it will be incremented.
Q. How do I make an NPC that's present in a given room say something during each turn, e.g., making one of five random comments?
A. The each_turn property of an object contains code which is executed every turn that the object is "in scope". In the example above, the each_turn for such an NPC could run something like
each_turn [; switch (random (5) )
{
1: "~Nice day,~ Bob says.";
2: (etc)
5: . . .
}
],
! Define the rest of the object.
Q. How do you program a telephone conversation?
A. It's hard. You need a parsing routine, a scope routine, and verbs. Try IntFicTelephone for information.
If you've contributed to this page, go ahead and put your name here:
LucianSmith, DavidCornelson*, L RossRaszewski, JeffJohnson**,Linards Ticmanis, JayGoemmer, AnsonTurner (real_location).
*Made it wrap better and left out the commentary
**General proofreading fixes
Go back to the InteractiveFictionPatterns page.
CategoryIntFic
EditText of this page
(last edited September 15, 2001)
FindPage by searching (or browse LikePages or take a VisualTour)