Doe's Inform 6 Primer



Erasers. Contents this "Lesson:"
  1. Attending to classes.
    Creating classes, testing class membership, non-additive/additive properties, the superclass operator, and "self."
  2. Skip a Grade to Advanced Inform Programming
    PlayerTo, parse_name, GamePreRoutine, ChooseObjects, etc.
Fourth Homework Assignment:
  1. Attending to Classes

    Fourth Homework Assignment Solutions

  1. Attending to classes.

    Creating Classes

    Classes have class. They also save on a lot of redundant coding, so they're fun and good for you. If you aren't familiar with classes from 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";
       ];
    
    Class - The word class declares a class. But a class declaration just creates a category, it does not create an object, an instance of that class. So in the above example, no actual button exists yet.
    Object TVButton "TV button"
      class button_class
      with name "tv" "television";
    
    ...or...
    
     button_class TVButton "TV button"
       with name "tv" "television";
    
    Class objects are created the same way regular objects are, except the class name is included. button_class on the first line is just shorthand for object, etc. class button_class on two lines. Now an actual button exists.

    Class Inheritance

    The classiest thing about classes is something called inheritance. 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 that when the player pushes the TVButton, it uses the before routine provided by the button_class to turn itself on or off.

    You could go crazy creating all kinds of button_class buttons, littering 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.

    Class Membership - Another classy thing about classes is the command, "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, and no error will result
    
    A Doe Crib Note:
    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 property inheritance, then things get a bit more complicated, because 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 is important for 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 the TVButton object has no before, the before of the button_class class is called. But what happens when an object has a property the class also has? This differs depending on whether the property is additive or not.

    Non-Additive Properties

    Non-additive properties are easier to understand, so we will start with those first. Let's give both the button class and a button object a react_before routine (which is 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)
                 {  if (self has on)
                       "You might think twice about that.";
                    deadflag = 1;
                    "Sparks arc from the button to the puddle at your feet..^^";
                 }
     ];
    
    button_class TransButton "transformer button"
       with name "transformer",
       react_before
       [; Push : if ((player in puddle) && (self has on))
                    "You get a shock and dance back.";
       ];
    
    
    When a property is non-additive, once it is included in the object, the class property will never be used/executed. So pushing the transformer button when it is on and the player is in a puddle will print, "You get a shock and dance back." But pushing the button when it isn't on and player is in a puddle and will just turn it on, because the class react_before will never be executed. However, if an object has no property of it own, then the class property will be used/executed. For instance, if the button object had no react_before...
    button_class TransButton "transformer button"
       with name "transformer";
    
    ...then the button class react_before would be executed and if the button is on and the player is in a puddle, it will print "You might think twice about that." And this time, if the button is off and the player is in a puddle, then the player will be killed. I don't know what Dr. Nelson calls this, but I call it "overwriting." When a property is non-additive, if it is included in an object it will always overwrite the class property. Also note the that whole property is non-additive, not just parts of it (i.e., the whole react_before, not Push).

    Additive Properites

    name
    describe
    before
    after
    each_turn
    life


    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";
    
    There are only a few additive properties. They add the class' property value to the object's property value. For instance, the TVButton inherits name from the button_class, so it can be referred to as:  "tv", "television", "button", "tv button", or "television button." Since the name property is not only inherited, but also additive, "tv" and "television" in the object is added to "button" in the class.

    A Doe Crib Note:
    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, with classes everything is inherited, including attributes. But 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?

    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";
       ];
    
    Object TVButton "TV button"
      class button_class
      with name "tv" "television",
      before
      [;  Push : if (self hasnt on)
                    "You watch too much television already.";
      ];
    
    In a regular object, if its before routine returns true (1), then processing stops there. This is also true for an additive property. When a property is additive, if it returns true from an object, then the class property will never be used/executed. Since the TVButton's before is called first and returns true by printing a string, the button_class' before is never executed. So "You watch too much television already." will be printed and the button will not be turned on or off. To have a class property also be used/execute, then object's property must return false (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. ";
      ];
    
    ...or the "superclass" operator that Dr. Nelson has provided can be used. Changing string to print, just prints the string and returns false, so the object message will be printed and the button will be turned on or off along with printing the class message. Taken all together it will appear on the screen as -- "You watch too much television already. You push the TV button on." (or "off").

    Review: Class declares a class category. Including the class name in an object creates an actual instance of that class. Class objects inherit everything from the class declaraction. Class properties are non-additive or additive. A non-additive property included in an object will overwrite that property in the class. An additive property will add the object's and class' property values together in the order of object first and class second, but only if the object property returns false (0) first.

    Superclass Operator

    object.class::property;
    
    The superclass operator calls a class property directly, so it can reverse the normal object/class property call order. Thus, it can be used to:  1. prevent a class non-additive property being overwritten 2. execute a class additive property first, only executing the object additive property next if some conditions in the class weren't met.

    Superclass Operator with 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)
                       {  if (self has on)
                            "You might think twice about that.";
                          deadflag = 1;
                          "Sparks arc from the button to the puddle at your feet..^^";
                       }
      ];
    
    button_class TransButton "transformer button"
       has on
       with name "transformer",
       react_before
       [; Push   :  if ((player in puddle) && (self has on))
                       "You get a shock and dance back.";
                   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 can force a call of a class non-additive property. When this is done then the object property return value matters, because 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 button on aren't met, the superclass operator calls the class react_before directly. For Touch, the superclass operator will always call the class react_before directly, because the default action/verb in the object react_before specifies Examine. Without that specification and, thus, the invocation of the superclass operator for all other verbs but Push and Examine, the class Touch response would always be skipped because the whole object react_before would overwrite the whole class react_before. So the standard library response, "You feel nothing unexpected.", would be printed instead.

    Superclass Operator with 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) && (self has on))
                     "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, ".";
      ];
    
    For Push, the superclass operator would call the class before first and because it returns 0, the rest of the object before would be executed next. If the class before returned 1, the rest of the object before would not executed. This just reverses the normal order of calling class properties. Touch is unaffected, because before is an additive property and the object before has no response for Touch. When an object has no response for an additive property it automatically calls the class property to get its response.

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

    Review:  A class object inherits everything from a class declaration. Only some properties are additive. If a non-additive property is present in both the class declaration and a class object, the object property will overwrite the class property. If an additive property is present in both class declaration and class object, their values will be added together in order of object first and class second, as long as the object property returns false (0) first. The superclass operator can reverse the object/class calling sequence for both non-additive and additive properties. Used with a non-additive property, the superclass operator can force a call of the class property instead of overwriting it. Used with an additive property, the superclass operator can call the class property first instead of second.

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

    Self

    Self refers the object it is used in. It does essentially the same thing in a class declaration.
    Class button_class
       has static switchable
       with name "button",
       description
       [; print "The ", (name) self, " is ";
          if (self has on) "on.";
          "off.";
       ],
       before
       [; Push : print "You push the ", (name) self;
                 if (self hasnt on){ <SwitchOn self> " on.";
                 <SwitchOff self>; " off";
       ];
    
    Object TVButton "TV button"
      class button_class
      with name "tv" "television";
    
    Object VCRButton "VCR button"
      class button_class
      with name "VCR";
    
    In an object, self refers to that particular object. In a class declaration, self refers to whichever class object the class is currently processing. So pushing the TVButton turns it on or off and prints, "You push the the TV button on." (or "off"). Pushing the VCRButton turns it on or off and prints, "You push the VCR button on." (or "off"). Examining the TVButton prints "The TV button is on." (or "off). Examining the VCRButton prints "The VCR button is on." (or "off'). It's another one of those classy features.

    A Doe Crib Note:
    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. But once you start doing things like that you have moved way beyond this class ;-).

    Class Commands
    Command Example
    class
    (declares a class)
    Class button_class
    
    class
    (in an object, makes that object a member of that class)
    Object TVButton "TV Button"
      class button_class
    
    class name
    (in an object, makes that object 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 object currently
    being processed by the class)
    print "You push the ", (name) self, ".";
    
    object.class::property
    (calls a class property directly -- superclass operator)
    TVButton.button_class::before();
    
    additive properties
    name
    describe
    after
    before
    each_turn
    life
    


    Contents

  2. Skip a Grade to Advanced Inform Programming



Previous Page - Sections 7 & 8 and Third Homework Assignment

Top of Page    Infotips    Interactive Fiction Main Page

Two sections of this primer and two homework assignments are unfinished. If you want me to finish them, email me and I will consider it.