Roger Firth's IF pages

Home

InFancy -- using Inform objects

Email
Back up

In previous pages we've developed a bottle and glass, placed liquid in the glass, and implemented a DRINK verb. What we need to finish things off is, of course, some way of starting with the liquid in the bottle, so that we can POUR it into the glass. To complicate matters, the bottle can hold more than the glass, so at times we'll have liquid in two places at once. There's any number of ways of tackling this situation: we'll use the technique of dynamic objects -- of creating and destroying instances of the Liquid class at run-time. For this to work, we must (a) set up the Liquid class appropriately, and (b) issue the necessary run-time messages. Happily, both of these are easy to do.

Extending the Liquid class

When we left our Liquid class on the previous page, its definition started out like this:

 
Class   Liquid
  with  name 'liquid',
        add_liquid [ qty;
            self.capacity = self.capacity + qty;
            if (self.capacity > 0)
                return self.capacity;
            else
                { remove self; return 0; }
            ],
        description [; print_ret "It looks rather like ", (name) self, "."; ],
        dots

To make it support dynamic object creation, we've changed it thus:

 
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;
            else
                { Liquid.destroy(self); return 0; }
            ],
        description [; print_ret "It looks rather like ", (name) self, "."; ],
        dots

We've made five changes:

  1. added a number in parentheses after the iname, to define the number of dynamic instances of this class which can exist at any one time while the game runs. Note that this doesn't include static instances, created at compile-time; there's no limit on those. Also, the phrase "at any one time" is important: our setting of (1) permits the sequence create() ... destroy() ... create() ... destroy(), but prevents create() ... create() ... destroy() ... destroy(), for which we'd need to have specified (2).
  2. extended the name property with five spare entries, for use by rename. There's nothing special about '.spare' other than that, since it contains a dot, it's an "untypeable" word, and so will never match anything the player types.
  3. supplied a short_name property, also for use by rename.
  4. created a rename property, as a convenient way of changing the name and short_name properties.
  5. modified the add_liquid property by changing remove self (which detaches the object from its parent, leaving it floating free and unreferenced but still in existence) to Liquid.destroy(self) (which completely deletes the object from the game).

Enhancing the bottle object

If we start the game with all of the liquid in the bottle, then it's natural to make the bottle responsible for creating Liquid instances in the glass. When we last saw it, the bottle looked something like this:

 
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 ";
            !!rfalse;
            ],
        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 [;
            Uncork: <<Remove cork self>>;
            Cork:   <<Insert cork self>>;
            ]
  has   transparent;

So now let's add the ability to pour its contents into a Vessel:

 
Object  bottle "bottle" kitchen_table
  with
        dots
        before [ liq qty liq2;
            liq = ChildOfClass(self,Liquid);
            if (liq ~= 0) qty = liq.add_liquid(0);
            Uncork: <<Remove cork self>>;
            Cork:   <<Insert cork self>>;
            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);
                    rtrue;
            ],
        capacity 10                     ! Liquid the bottle can contain
  has   transparent;

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

Verb	'pour'		= 'empty';

We've enhanced the bottle's before property to trap the EmptyT action; basically, that's EMPTY noun INTO noun. There's a lot happening here -- making sure the bottle is uncorked and not empty, that the destination is a Vessel which isn't already full, and so on -- but the most important lines are highlighted. We send a create() message to the Liquid class, and receive in reply the address of a newly-created instance of the class: a Liquid object. If the reply is zero, a new object couldn't be created, presumably because we're already at our specified limit of concurrent run-time instances. In this example this shouldn't happen, so if it does we just output a debugging message. Assuming that object creation is successful, we then call the new object's rename property to over-write its default short_name and name properties. Finally, more housekeeping: telling the player what's happened, moving the new Liquid object into the Vessel, and adjusting the quantities of Liquid in the bottle and the Vessel.

Once we've primed the bottle by giving it an initial Liquid content, it's time for a test drive:

 
Kitchen
Oddly, there are no exits.

In the centre of the room stands a battered pine table.

On the table are a corked bottle and an empty glass tumbler.

>EXAMINE THE BOTTLE
You see an ordinary wine bottle, of green glass, with a faded label
and a cork protruding from the slender neck. There is a quantity of
liquid inside.

>UNCORK IT
You pull the cork from the bottle.

>POUR IT INTO THE GLASS
You pour some liquid into the empty glass tumbler.

>TAKE THE GLASS
Taken.

>INV
You are carrying:
  a glass of red wine (full)
  a cork

>DRINK SOME WINE
You take a mouthful of red wine.

>POUR THE BOTTLE INTO THE GLASS
You add some more liquid to the glass of red wine.

>DRINK WINE
You take a mouthful of red wine.

>DRINK WINE
You finish the rest of the red wine.

>EMPTY THE BOTTLE INTO THE GLASS
You pour some liquid into the empty glass tumbler.

>EXAMINE BOTTLE
You see an ordinary wine bottle, of green glass, with a faded label
and a slender neck.

>EXAMINE GLASS
The glass tumbler is slightly chipped, but still usable with care.
It's currently partly full of red wine.

>DRINK WINE
You finish the rest of the red wine.

>INV
You are carrying:
  an empty glass tumbler
  a cork

OK, that's about as far as we can take things here; we've reached the point where the complexities of refining the objects' behaviour would outweigh their educational benefit. The classes and objects that we've created provide a simplistic but usable representation of liquid handling, and could potentially be developed into a full-fledged general package; we'll leave that as an exercise for the reader.


Finally, on the past page, you'll find a listing of the complete game in its 'final' state. Enjoy!