Roger Firth's IF pages


InFancy -- using Inform objects

Back up

This is the final form of the code, represented here for ease of reference. As ever, we start with a standard preamble:

Constant Story "INFANCY";
Constant Headline "^A juvenile INFORM program.^";

Include "Parser";
Include "VerbLib";

! ---------------------------------------------------------------------------- ! 

These are the standalone routines:

[ Initialise; location = kitchen; ];

!       Locate the (first) child object of a given Class.
[ ChildOfClass obj class
        objectloop (k in obj) if (k ofclass class) return k;
        return 0;

!       Ensure compatibility with glulx, which uses four-byte words.
#ifndef WORDSIZE;
    Constant TARGET_ZCODE 0;
    Constant WORDSIZE 2+TARGET_ZCODE;   ! avoid compiler warning

!       Test if a given value occurs in a given property array of an object.
[ IsAWordIn wd obj prop
        k l m;
        k = obj.∝ l = (obj.#prop)/WORDSIZE;
        for (m=0 : m<l : m++) if (wd == k-->m) rtrue;

! ---------------------------------------------------------------------------- ! 

The classes:

Class   Room
  with  description "A bare room."
  has   light;

Class   Furniture
  with  before [; Take,Pull,Push,PushDir:
            print_ret (The) self, " is too heavy for that.";
  has   static supporter;

Class   Vessel
  with  parse_name [ liq qty wd container_count contents_count;
            if (parser_action == ##TheSame) return -2;
            liq = ChildOfClass(self,Liquid);
            if (liq ~= 0) qty = liq.add_liquid(0);
            for (wd=NextWord() : wd~=0 : wd=NextWord()) switch (wd) {
                    if (container_count > 0) container_count++;
                    if (qty == 0) contents_count++;
                    if (IsAWordIn(wd,self,name)) container_count++;
                        if (qty > 0 && IsAWordIn(wd,liq,name)) contents_count++;
                        else jump exitVessel;
            if (container_count > 0) return container_count + contents_count;
            return 0;
        invent [ liq qty;
            liq = ChildOfClass(self,Liquid);
            if (liq ~= 0) qty = liq.add_liquid(0);
            if (inventory_stage == 2) {
                if (qty == self.capacity) print " (full)";
                    if (qty > 0) print " (partly full)";
        description [ liq qty;
            liq = ChildOfClass(self,Liquid);
            if (liq ~= 0) qty = liq.add_liquid(0);
            print "It's currently ";
            if (qty == self.capacity) print_ret "full of ", (name) liq, ".";
                if (qty > 0) print_ret "partly full of ", (name) liq, ".";
                else "empty.";
        before [ liq qty;
            liq = ChildOfClass(self,Liquid);
            if (liq ~= 0) qty = liq.add_liquid(0);
            Drink, Empty:
                if (self notin player)
                    print_ret "You need to be holding ", (the) self, ".";
                if (qty > 1)
                    print "You take a mouthful of ", (name) liq, ".^";
                    if (qty == 1)
                        print "You finish the rest of ", (the) liq, ".^";
                    else print_ret "There's nothing in ", (the) self, ".";
            Receive: print_ret (The) self, " is meant for holding liquids.";
        capacity 0              ! Liquid the vessel can contain
  has   transparent;

Class   Liquid(1)
  with  name 'liquid' '.spare' '.spare' '.spare' '.spare' '.spare',
        short_name "liquid",
        rename [ sn n0 n1 n2 n3 n4 n5;
            if (sn) self.short_name = sn;
            if (n0) self.&name-->0 = n0;
            if (n1) self.&name-->1 = n1;
            if (n2) self.&name-->2 = n2;
            if (n3) self.&name-->3 = n3;
            if (n4) self.&name-->4 = n4;
            if (n5) self.&name-->5 = n5;
        add_liquid [ qty;
            self.capacity = self.capacity + qty;
            if (self.capacity > 0)
                return self.capacity;
                { Liquid.destroy(self); return 0; }
        description [; print_ret "It looks rather like ", (name) self, "."; ],
        article "some",
        before [ container; container = parent(self);
                if (container ofclass Vessel) <<Take container>>;
                print_ret "You can't take a puddle of ", (name) self, ".";
                if (container ofclass Vessel) <<Drink container>>;
                print_ret "You can't drink a puddle of ", (name) self, ".";
                if (container ofclass Vessel) <<Empty container>>;
                print_ret "You can't do that.";
                if (container ofclass Vessel) <<EmptyT container second>>;
                print_ret "You can't do that.";
        capacity 0;             ! Liquid currently in the vessel

! ---------------------------------------------------------------------------- ! 

The instances of those classes -- the actual game objects:

Room    kitchen "Kitchen"
  with  description "Oddly, there are no exits.";

Furniture   kitchen_table "table" kitchen
  with  name 'battered' 'pine' 'table',
            "In the centre of the room stands a battered pine table.",
            "The table is scuffed and stained with indeterminate substances.";

Object  bottle "bottle" kitchen_table
  with  parse_name [ wd adj_count noun_count;
            if (parser_action == ##TheSame) return -2;
            wd = NextWord();
            while (wd == 'green' or 'glass' or 'wine' or 'corked')
                { wd = NextWord(); adj_count++; }
            while (wd == 'bottle' or 'flask' or 'flagon')
                { wd = NextWord(); noun_count++; }
            if (noun_count > 0) return noun_count + adj_count;
            return 0;
        short_name [;
            if (cork in self) print "corked ";
        description [ liq;
            liq = ChildOfClass(self,Liquid);
            print "You see an ordinary wine bottle, of green glass,
                with a faded label and a ";
            if (cork in self) print "cork protruding from the ";
            print "slender neck";
            if (liq ~= 0) print ". There is a quantity of liquid inside";
        before [ liq qty liq2;
            liq = ChildOfClass(self,Liquid);
            if (liq ~= 0) qty = liq.add_liquid(0);
            Uncork: <<Remove cork self>>;
            Cork:   <<Insert cork self>>;
            Drink:  "Please don't drink from the bottle!";
            Empty:  if (cork in self)
                        "Fortunately, the cork prevents you making a mess.";
                    if (qty == 0) print_ret (The) self, " is empty.";
                    liq.rename("red wine",'red','wine','plonk');
                    print "You pour ", (the) liq, " onto the floor.^";
                    move liq to location;
            EmptyT: if (cork in self)
                        print_ret "You tip ", (the) self,
                            ", but nothing happens.";
                    if (qty == 0) print_ret (The) self, " is empty.";
                    if (~~(second ofclass Vessel))
                        "That would just make a mess.";
                    liq2 = ChildOfClass(second,Liquid);
                    if (liq2 == 0) {    ! no liquid in Vessel
                        if (qty > second.capacity) qty = second.capacity;
                        liq2 = Liquid.create();
                        if (liq2 == 0) "*** Can't create Liquid object! ***";
                        liq2.rename("red wine",'red','wine','plonk');
                        print "You pour ", (a) liq, " into ", (the) second, ".^";
                        move liq2 to second;
                    else {              ! already some liquid in Vessel
                        qty = second.capacity - liq2.add_liquid(0);
                        if (qty == 0)
                            print_ret (The) second, " is already full.";
                        print "You add some more ", (name) liq,
                            " to ", (the) second, ".^";
                    liq2.add_liquid(qty); liq.add_liquid(-qty);
        capacity 10             ! Liquid the bottle can contain
  has   transparent;

Liquid  "liquid" bottle
  with  capacity 4;             ! Liquid currently in the bottle

Object  label "label" bottle
  with  name 'faded' 'label' 'lable',
            "Though somewhat faded and difficult to make out,
            the label seems to read ~Chat Eau~.";

Object  cork "cork" bottle
  with  name 'cork' 'stopper',
        before [;
            Remove, Take, Pull:
                if ((self notin bottle) || (second ~= nothing or bottle))
                move self to player;
                "You pull the cork from the bottle.";
                if ((self notin player) || (second ~= bottle)) rfalse;
                move self to bottle;
                "You put the cork in the bottle.";
            "It's a small cork, of the type associated with wine bottles.";

Vessel  glass kitchen_table
  with  name 'chipped' 'glass' 'tumbler',
        short_name [ liq qty;
            liq = ChildOfClass(self,Liquid);
            if (liq ~= 0) qty = liq.add_liquid(0);
            if (qty > 0)
                print "glass of ", (name) liq;
                print "empty glass tumbler";
        description [;
            print "The glass tumbler is slightly chipped,
                but still usable with care. ";
        capacity 2;             ! Liquid the glass can contain

! ---------------------------------------------------------------------------- ! 

Finishing up with the standard Grammar and our small extensions to it:

Include "Grammar";

[ UncorkSub;    "You can't uncork that."; ];
[ CorkSub;      "You can't cork that."; ];

Verb    'uncork'    * noun          -> Uncork;
Verb    'cork'      * noun          -> Cork;
Verb    'pour'      = 'empty';

! ---------------------------------------------------------------------------- ! 

The techniques that we've talked about here are certainly not the only ways of producing the required behaviour; they're not even necessarily the best ones. As ever, comments are welcome; corrections even more so. I'd hate to be misleading those in most need, so if any of you gurus spot an error, please let me know.