Int Fic Inform Tips

(IntFic = InteractiveFiction)

LucianSmith set this page up and IntFicInformFaq to try to address a need. Use this page for three things: (If you're having formatting problems, see TextFormattingRules)
I. Definitions II. Structure of an Inform file
            include "parser";
            include "verblib";
            include "grammar";
.
    1. 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.
               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
I made something 'concealed' and now I'm having problems. What's going on?
I'm trying to make a rope. What should I keep in mind?
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?
What exactly happens when a player inputs a command? What are the steps, and where are the places I can change things?
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?
How do I use returned values myself?
   x = 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: 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)