Doe's Inform Primer

I'd call it a faq except no one asked me frequently enough. ;-)





Note: This primer has been rewritten, although the rewrite is not complete yet. However, Doe's Inform Primer Version II is still quite useable and probably better than this primer. Doe :-) 8/3/03

In this "primer" I chose to focus on what I found "hardest" when learning Inform programming. Based on the premise that what I found tricky, others might too. However, I had to really cast my mind back, because I know it fairly well now. Also, when I started learning Inform I already knew how to program, so I didn't start from ground zero. In other words, this doesn't cover how to program and/or the very basics of Inform, but newbies may still get something out of it.



Contents:
  1. True or false?
  2. Big "bad" table.
    The order in which Inform routines are processed.
  3. Stupid Initialise tricks.
    Changing the player, starting the player on something, skipping the banner, etc.
  4. Attributes and properties, apples and oranges.
    The difference between the two, global/local properties and avoiding vile zero errors from hell.
  5. Verbalizing skillfully.
    Creating/changing verbs and verbsub routines.
  6. Getting classy with classes.
    Using, testing class membership, non-additive/additive properties, superclass operator and "self".
  7. Using the before, after and react_before routines to affect actions.
  8. Multiply the power, multiply the fun.
    Arrays, array properties and found_in.

  1. True or false?

    I wasn't really going to cover any real basics, but I refer to this so often in this primer, I thought I better summarize.

    Inform relies very heavily on true or false. False is actually the same as 0, true is the same as 1 (or any number higher than zero). Like a computer's bit, 0 or 1, off or on.

    Attributes can be true or false. Properties routines, verbsubs and other routines return values of 0 or 1 (or higher).

    When a routine returns a non-zero value it means, "Stop here, this is the end, proceed no further." Processing stops before other routines can be called. The prompt appears again.

    The most common way a routine can return 1 is by printing message. This relies on the fact that print_ret "string"; prints the string, provides a carriage return (new line) and returns 1. "String"; is the same as print_ret "string";, so it also provides a carriage return and returns 1. But print "string"; just prints the string and returns 0. Note print_ret (or print), not just string, must be used if you want to call an additional specialized print routine: (the), (name), etc., from within the print statement.
    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."

    When a routine returns 0, Inform's built-in routines take over next until they, in turn, stop (usually by using "string";). So an author's coding is essentially all exception handling; interrupting or stopping Inform's default processing.

    Doe Tip:
    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)


    Contents


  2. Big "bad" table.

    This comes straight from the Designer's Manual, pages 94, 115, 122, 124, 137, 148 & 163. I have just it all into one table. You might look this over, but it is really for reference after you already know a little Inform.

    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 )" is printed if this was requested by the game's most recent call to ChangePlayer (for instance, " (as a werewolf)").
    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).


    Contents


  3. Stupid Initialise tricks.

    Initialise prints any introduction that appears before the game banner. It also sets the starting location for the player and their inventory. (Also, note the British spelling.)

       [ 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.

    That's pretty simple. But suppose you want to change the player? You want a customized player with body parts.

    Copy selfobj in parserm.h to your code, name the copy something like newselfobj, customize the player how you want, then in Initialise:

    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 ;-).

    As far as how to add body parts, use add_to_scope hands; or put Object -> hands "hands" (or whatever body part you are adding) directly under the newselfobj.

    Another tricky thing is having a game start with the player SITTING on something. Use...
    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.

    How about a quote box at the beginning of your game? Or having it start by asking if you want to restore a saved game? Or skipping the banner until later? Or having a timed game? These are all things that are done in Initialise as well.

    1. Quote Box
      The ; at the start of any routine (Initialise, before, after) is where you can put local variables (discardable variables that start at zero each time the routine is called). Also, Inform has a nice built-in quote box routine.

      [ 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.

    2. Restoring a Saved Game
      The same thing can be done to ask if the player wants to restore a saved game.
      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.

    3. Skipping the Banner
      Initialise usually returns 1 (see True or False, #1). But to skip the banner, have the last line be return 2; so it returns 2 instead (also make sure to use print "string" instead of "string", so Initialise doesn't return before you get to that line). To call the banner later in your code, use Banner();

    4. Creating a Timed Game
      Making a timed game uses a default Inform routine as well and is also set in Initialise:
      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.


    Actually, none of it is that tricky once you know how.

    Contents


  4. Attributes and properties, apples and oranges.

    Attributes are boolean flags, so they can only be either true or false.

     general   <- true
    
     ~general  <- false
    
    Properties 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.

    You can, of course, add your own attributes and properties.

    Doe Tip:
    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
    

    All attributes are, by default, global. To declare one, include it at the top of your code before including parser.h:

    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.

    Attributes are negative by default. To turn on (set true) a particular attribute for an object, you have to specifically assign it to that object.

    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.

    But all properties don't need to be global. Local properties are declared within the object.

    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.

    The advantage of a local property is it is object-oriented and keeps information from being globally accessed (I also suspect they use less memory, but can't swear to it). Also, while there is limit on the number of global properties (maximum depending on game size), there is none on local properties. HOWEVER, you must remember NOT to test other objects for the value of that local property...

    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.

    Doe Tip:
    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).

    Doe Tip:
    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.

    The simple error check shown above
    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 zero
    
    the 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.)

    I didn't really cover the commands concerning attributes and properties, I figured you'd pick that up somewhere else. But a brief summary...

    Attributes:

    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;
    


    Give object attribute assigns that attribute to that object. Therefore, "has" does not always need to be included in the object itself.

    Properties:

    Global
    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;
    


    Local

    The same as above, except also includes...

    Command Example
    provides
    (tests if an object has a local property)
    if (dog provides intelligence)
    


    Note when testing the value of a property outside of the object it is assigned to, the property name is preceded by a period and the object name. Inside the object it is self.property.

    Doe Tip:
    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".

    Doe Tip:
    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)
    

    You can probably see why instead of creating new attributes and/or global properties, it is often a good idea, maximum limit-wise (and possibly memory-wise), to create new local properties instead. However, sometimes you really NEED globals. So your decision of which to create depends on how you plan to use it.

    Testing and assigning properties new values are not done that frequently. 90% of the time most Inform programmers just use the default property routines. See before, etc., #7.

    Contents


  5. Verbalizing skillfully.

    Are very easy to do in Inform, one of its many strengths.

    Doe Tip:
    Become thoroughly conversant with grammar.h.

    Looking at 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.

    So creating another synonym is as simple as:
    Verb 'clamber' = 'climb';
    
    I don't think you will find creating new verbs difficult. However, creating new verbsubs (routines) is a tad more complicated.

    All the default verbs have a corresponding verbsub. But only a few actually do something. Most just print a default library message. Graham calls these group 3 verbs, see page 92 of the DM. These verbs also have no after routine, only a before.

    But suppose, for instance, you want burn to DO something? By default it just prints, "This dangerous act would achieve little."

    Doe Tip:
    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.

    Well, first BurnSub needs to be replaced. Replace by putting (before parser.h):
    Replace BurnSub;
    
    Now a new Burnsub routine needs to be provided.

    Let's create a paper attribute, is_paper (could be a class, but we haven't gotten that far yet). So the routine would be:
    [ 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.

    Okay, the new Burnsub covers these defaults: the player trying to light something without having a match, has a match but it is unlit, trying to burn a creature, trying to burn something already burning and burning up something made of paper. If none of these conditions are met, the default message, "This dangerous act would achieve little.", is still printed.

    Doesn't look like this accomplishes much, does it? Let's say there is a bonfire in the game. Do the lighting IN THE BEFORE ROUTINE of the bonfire:

    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.

    THE MOST IMPORTANT THING TO REMEMBER IS:  a verbsub is only executed if the object to be affected has no (a zero) response for that verb. No before routine for the object (value 0), no response for that verb in the object's before routine (returns 0) or if the object's before routine response for that verb returns false (0).
    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).

    Back to the bonfire. It returns true (1), only if the it isn't already burning AND the player has a lit match. If it is already lit, its before routine returns 0, so BurnSub is called and prints "The bonfire is already burning." If the player doesn't have a match or has a match that isn't lit, its before routine also returns 0, so BurnSub is called and prints "You have no source of flame."

    Doe Tip:
    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.

    Based on the fact that a verbsub is only executed when an object's before response for that verb is 0, all complicated verb (action) coding should be in before routines. Remember:  Inform's built-in routines handle defaults so an author's coding is basically just exception handling (see True, #1). When creating/changing verbs one should follow this same reasoning. THIS WAS ONE OF THE HARDEST THINGS FOR ME TO LEARN.

    Creating a new verb from scratch works exactly the same. But I'll leave it to you to figure out how and when to write an after routine for a new verb.

    To reemphasize , don't put all the ramifications of an action in its verbsub, only put in what you want to happen when there is no response for that verb in a before routine. Only put in defaults. Handle all exceptions in before routines. Graham has already gone to a lot of trouble to create default responses for most verbs. Rely on that. Also, a verbsub can quickly become spaghetti code trying to anticipate all the possible exceptions , that is the underlying reason why they should be done on a case by case basis in before routines. It really will save you a lot of wasted time and additional coding. Believe me, I know from experience ;-).

    Which leads naturally into #4... but first a brief summary of the commands to create verbs...

    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;
    


    Contents


  6. Getting classy with classes.

    Classes have class. They save on a lot of redundant coding, so they're fun AND good for you. If you aren't familiar with classes from some previous programming experience, don't be intimidated, they're also fairly simple.

    A class is basically just a way of grouping like objects together under one heading.

    Let's create a button class.
    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.

    One neat thing about classes is something called inheritance. Basically, an instance of a class, a class object, "inherits" everything declared in the class. For instance, the before routine of the button_class is inherited by the TVButton. This means when the player pushes it, the TVButton uses the before routine provided by the button_class to turn itself on or off (it does this by essentially making a local copy of everything it inherits from the class).

    You could go crazy creating all kinds of button_class buttons, enough until they litter the playing landscape, but you would only have to write the before routine once, in the class declaration. So you can easily see why classes can save on code.

    Another really neat about classes is "ofclass", which determines the class membership of an object. Skillful use of this command can also save immensely on code.

    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 result
    
    Doe 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.

    Simple, huh? Well, except when it comes to properties, then things get a bit more complicated. In classes, some properties are non-additive and some are additive.

    THE MOST IMPORTANT THING TO REMEMBER IS:   properties are called in the order of object property FIRST, class property SECOND. This order becomes important when using both non-additive and additive properties.

    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.

    Non-Additive Properties:

    Let's give both the class and object a react_before routine (non-additive).
    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.

    Additive Properties:
    after
    before
    describe
    each_turn
    life
    name

    Additive properties add the class property value to the object property value. For instance, the TVButton inherits name from the button_class, so the TVButton can be referred to as "tv" & "television", "button" and "tv button" & "television button". This is because the name property is not only inherited, but also additive, i.e., the name, "button", from the class declaration is added to the name, "television", in the object.

    Doe Tip:
    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.

    What is the difference between inheritance and additive? Well, everything is inherited including attributes. While the additive condition only applies to inherited properties.

    So name works fine and dandy as an additive property because it is a string(s). But what if the additive property is a routine that returns a value? For example, before is an additive property, so a class before should be added to the object before, right?

    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.

    Review: Class declares a category. Using the class name in an object creates an actual instance of that class. Class objects inherit everything from the class. Class properties are non-additive or additive. A non-additive object property also included in the class overwrites the class property. While an additive property adds the class and object property values in the order of object first, class second, but only if the object property returns 0 first.

    Superclass Operator

    However, sometimes you may not want a class non-additive property to be overwritten and/or you may want a class additive property to be executed first and only have the object additive property executed if some conditions in the class weren't met.

    The superclass operator :: can accomplish either or both of the above...
    object.class::property;
    
    ...because it calls a class property directly.

    The superclass operator with a non-additive property:
    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).

    In this case, for Push, if the conditions of player in puddle and transformer button on aren't met, the class react_before is then directly called. For Touch, it will always be called because it is used as a default in the object react_before. Without that, the class Touch response would always be skipped because the whole object react_before overwrites the class react_before when the superoperator is not specifically invoked. So the standard library response, "You feel nothing unexpected.", would be printed instead.

    The superclass operator with an additive property:

    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.

    As you can see, the superclass operator is yet another neat feature of classes.

    Summary:  A class object inherits everything from a class declaration. Only some properties are additive. If a non-additive property is present in both class declaration and class object, the object property will completely overwrite the class property. But using the superclass operator, object.class::property, can force a non-additive object property to call the class property instead of overwriting it. If an additive property is present in both class declaration and class property, its value will be added in order of object first, class second, as long as the object property returns 0. But using the superclass operator, object.class::property, can reverse this object/class sequence for both additive and non-additive properties.

    It really is simple, huh? Sure ;-).

    Which leads to self. Self refers the object it is used in. It also does in a class declaration.
    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.

    Doe Tip:
    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.

    You can also have multiple inheritance, since a class can inherit from another class and/or an object can inherit from more than one class. Once you start doing things like that you are really cooking with class ;-).

    That sums up everything I know about classes and it has definitely been hard-won information. Now a list of commands...

    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
    


    Contents


  7. Using the before, after and react_before routines to affect actions.

  8. Multiply the power, multiply the fun.

    An array is a sequential collection of items, each item is an element. I always have problems with arrays, whether they start with 1 or 0, because it depends on the language (I also have problems with bytes and words, but that is my problem.)

    In Inform, arrays start with 0 and array elements are accessed with an >. Byte arrays use a short arrow, ->, word a long arrow, -->. Most Inform arrays are word arrays, unless one is accessing characters (char) in a string, which is automatically an array of characters. There are also tables, arrays of strings. The 0 element in a string (array of bytes) and table (essentially an array of pointers to strings) holds the length of the array, which can come in very handy.

    -> (byte) --> (word) string (array of characters) table (array of strings)

    The word, Array , declares an array and because the first element is 0, an array declared with 10 elements is actually 0-9, not 1-10. There are good examples of using arrays in the Designer's Manual and arrays.inf, also by Dr. Nelson, at the archive.

    Doe Tip
    There is no capacity for double arrays in Inform, one of its biggest drawbacks and why creating a database in Inform is almost impossible.

    Where arrays really become interesting is when they are used as object properties. When I discovered how to create new properties, especially array properties, my Inform programming really "took off".

    Found_in is the property most commonly used as an array, although it can be a routine instead. (Most other array properties you create yourself.) It is often used with doors:

    found_in secret_passage library
    
    This 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.

    But let's say, instead of a regular door, we want a hidden one that will only appear in the library when a special book on the shelves is pulled (trite, but a good example).

    THE MOST IMPORTANT THING TO REMEMBER IS:  in Inform arrays cannot be dynamically allocated. This means an array must be declared with its maximum potential size.

    Doe Tip
    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)
    

    In property arrays, space for future elements can be allocated with 0.
    found_in secret_passage 0
    
    Now the door will only appear in the secret_passage and not in the library until the right conditions are met.

    Property arrays also involve two other important operators, .& and .#. # works the same as the 0 element for strings and tables, giving the length of the array, and & accesses the actual array stored in the property.

    Putting it altogether, here is the hidden door in action:
    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.

    hidden_door.&found_in-->1 is the array element the hidden door is added to and removed from. Pulling the book adds the hidden_door when the door is open (present) or removes it. Otherwise, the player could try to touch/open/etc. the door when it is supposed to be hidden.

    One can create array properties with elements of: objects (like found_in often uses), numbers or strings. However, array elements can't be routines.

    This comes from my infotips page under the Invisiclues section.

    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.

    For the rest, see my infotips page.

    As you can see, using array properties can lead to some fancy Inform programming!

    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);
    

    Contents


Top of Page    Infotips    Interactive Fiction Main Page

If this primer has been of any use to you I wouldn't mind hearing about it.