! ----------------------------------------------------------------------------
!   Balances:  An exercise in parsing                                 25/ 9/94
!                                                             updated  6/10/94
!                                                          modernised 11/12/95
!
!   This short story was written to demonstrate large-scale programming of
!   the parser, and features multiple objects, complicated plurals, variable
!   verbs, objects named by the player and questions.  The spell-casting
!   system is written in a "safe" way so that it could easily be transplanted,
!   and is as "object-oriented" as seemed sensible to the author.
!
!   Release 3 contains many small improvements, especially in replying well
!   to incorrect input.
!
!   Advert: Michael Phillips has written an ingenious and elegant "adaptive
!     hints" library for Inform, and used "Balances" as an example game to
!     show how to program such hints.  I recommend taking a look.
!
!   Needs Inform 5.5, library 5/12 or later to compile.
! ----------------------------------------------------------------------------

Release 3;
Serial "951220";
Switches dv5x;

Constant Story "BALANCES";
Constant Headline "^An Interactive Short Story^\
            Copyright (c) 1994, 1995 by Graham Nelson.^";

Constant OBJECT_SCORE 5;
Constant MAX_SCORE 51;


Replace LowKey_Menu;
Replace DoMenu;

Include "Parser";
Include "VerbLib";

! ----------------------------------------------------------------------------
!   The white featureless cubes from "Spellbreaker", which can be identified
!   by being written on with the magic burin, so that their names are given
!   by the player in the course of play
!
!   A particularly witty thing to do is to give several of them the same name,
!   or to frotz some of them to distinguish them from the others...
!   And the game will have no problem with this.
! ----------------------------------------------------------------------------

Attribute is_cube;

Array cube_text_buffer -> 8;
Global the_named_word = 0;
Global from_char; Global to_char;

Fake_Action Baptise;

Class  cube_class
 with number 0 0 0 0,
      description "A perfect white cube, four inches on a side.",
      parse_name
      [ i j flag;
           if (parser_action==##TheSame)
           {   for (i=0:i<8:i++)
                   if ((parser_one.&number)->i
                       ~= (parser_two.&number)->i) return -2;
               return -1;
           }
           for (::i++)
           {   j=NextWord(); flag=0;
               if (j=='cube' or 'white' ||
                   (j=='featureless' or 'blank' &&
                          ((self.&number)->0) == 0)) flag=1;
               if (j=='cubes')
               {   flag=1; parser_action=##PluralFound; }
               if (flag==0 && ((self.&number)->0) ~= 0)
               {   wn--;
                   if (TextReader(0)==0) return i;
                   for (j=0: j<8: j++)
                       if ((self.&number)->j ~= cube_text_buffer->j)
                           return i;
                   flag=1;
               }
               if (flag==0) return i;
           }
      ],
      article "a",
      short_name
      [ i; if (((self.&number)->0) == 0) print "featureless white cube";
           else
           {   print "~";
               while (((self.&number)->i) ~= 0)
                   print char (self.&number)->i++;
               print "~ cube";
           }
           rtrue;
      ],
      plural
      [;   RunRoutines(self, short_name); print "s";
      ],
      before
      [ i; Baptise: wn = the_named_word;
                    if (TextReader(1)==0) return i;
                    for (i=0: i<8: i++)
                        (self.&number)->i = cube_text_buffer->i;
                    self.article="the";
                    print_ret "It is now called ", (the) self, ".";
      ],
 has  is_cube scored;

!  Copies word "wn" from what the player most recently typed, putting it as
!  plain text into cube_text_buffer, returning false if no such word is there

[ TextReader flag point i j len;

  if (flag==1 && from_char~=to_char)
  {   for (i=from_char, j=0:i<=to_char && j<7:i++)
      {   cube_text_buffer->j = buffer->i;
          if (buffer->i ~= ' ' or ',' or '.') j++;
      }
      for (:j<8:j++) cube_text_buffer->j = 0;
      from_char=0; to_char=0;
      rtrue;
  }

  for (i=0:i<8:i++) cube_text_buffer->i = 0;
  if (wn > parse->1) { wn++; rfalse; }
  i=wn*4+1; j=parse->i; point=j+buffer; len=parse->(i-1);

  for (i=0:i<len && i<7:i++) cube_text_buffer->i = point->i;

  wn++; rtrue;
];

Object burin "magic burin"
 with name "magic" "magical" "burin" "pen",
      description
         "This is a magical burin, used for inscribing objects with words \
          or runes of magical import. Such a burin also gives you the \
          ability to write spell scrolls.",
      before
      [; WriteOn:
            if (second has is_cube)
            {   if (second notin player)
                    "Writing on a cube is such a fiddly process that you \
                     need to be holding it in your hand first.";
                if (burin notin player)
                    "You would need some powerful implement for that.";
                <<Baptise second>>;
            }
            if (second has is_spell_book)
                "If a burin could write in a spell book, you wouldn't need \
                 the gnusto spell!";
            if (second has is_scroll)
                "You cannot write just anything on the magic parchment of \
                 a scroll: you can only ~copy~ a spell to it.";
      ];

[ WriteOnSub; "Graffiti is banned."; ];

[ CopyToSub;
 if (burin notin player) "You need to be holding the burin to copy a spell.";
 if (second has is_spell_book)
     "If a burin could write in a spell book, you wouldn't need \
      the gnusto spell!";
 if (second hasnt is_scroll) "You can only copy spells to scrolls.";
 if (child(second)~=0)
     "The scroll is already full of incantation.";
 "The scroll is not blank, only illegible.";
];

! ----------------------------------------------------------------------------
!   Now the whole spell-casting system
! ----------------------------------------------------------------------------

Attribute is_spell;
Attribute known_about;
Attribute is_scroll;
Attribute is_spell_book;
Property  magic;
Fake_Action SayName;

[ SpellName obj; print (address) (obj.&name)-->0; ];

Class  spell_class
 with name "spell" "spells", article "the", number 0,
      short_name
      [; SpellName(self); print " spell"; give self known_about; rtrue;
      ],
      before
      [; SayName: SpellName(self); print " spell:  ", object self;
             give self known_about; rtrue;
         Examine: <SayName self>; ".";
      ],
 has  is_spell;

Object memory "memory"
 with capacity 5,
      number 1,
      before
      [ i j k;
        Examine:
          objectloop (i in self) if (i.number==100) j++;
          if (j>0)
          {   print "The ";
              objectloop (i in self)
                  if (i.number==100)
                  {   k++; SpellName(i);
                      if (k==j-1) print " and ";
                      if (k<j-1) print ", ";
                  }
              if (j==1) print " spell is"; else print " spells are";
              print " yours forever.  Other than that, y";
          }
          else print "Y";
          print "ou have ";
          j=0; k=0;
          objectloop (i in self) if (i.number<100) j++;
          if (j>0)
          {   print "the ";
              objectloop (i in self)
                  if (i.number<100)
                  {   k++;
                      PrintShortName(i);
                      if (i.number==2) print " (twice)";
                      if (i.number==3) print " (thrice)";
                      if (i.number==4) print " (four times)";
                      if (i.number>=5) print " (many times)";
                      if (k==j-1) print " and ";
                      if (k<j-1) print ", ";
                  }
          }
          else print "no spells";
          " memorised.";
        Insert:
          if (second.number==100) "You always know that spell.";
          self.number=self.number+1;
          print "Using your best study habits, you commit the ";
          SpellName(second);
          print " spell to memory";
          if (second notin self) second.number=0;
          move second to self;
          second.number=second.number+1;
          if (second.number==1) print ".";
          if (second.number==2) print " once again.";
          if (second.number==3) print " a third time.";
          if (second.number>3) print " yet another time.";
          if (self.number <= self.capacity) { new_line; rtrue; }
          i=youngest(self); <Remove self i>;
          "  You have so much buzzing around in your head, though, \
             that it's likely something may have been forgotten \
             in the shuffle.";
        Remove:
          if (second notin self || second.number==100) rtrue;
          if (self.number>0) self.number=self.number-1;
          second.number=second.number-1;
          if (second.number==0) remove second;
          rtrue;
      ];

Object gnusto_spell "copy a scroll into your spell book" memory
class spell_class
 with name "gnusto",
      number 100,
      magic
      [ i a_book;
           if (second has is_spell_book)
              "Unlike scrolls, spell books are magically guarded against \
               the 'theft' of their lore.";
           if (second==0 || second hasnt is_scroll)
              "Your spell fizzles vaguely out.";
           if (second notin player)
               "A gnusto spell would require close scrutiny of the scroll \
                it is to copy: which you do not seem to be holding.";
           objectloop (i in player)
               if (i has is_spell_book) a_book=i;
           if (a_book==0)
               "Your spell fails, as you have no spell book.";
           i=child(second);
           if (i==0 || i hasnt is_spell)
           {   print_ret "Your spell fails, as ", (the) second,
                  " is illegible.";
           }
           <Learn a_book i>; remove second;
           print_ret
              "Your spell book begins to glow softly.  Slowly, ornately, \
               the words of ", (the) i, " are inscribed, \
               glowing even more brightly then the book itself.  \
               The book's brightness fades, but the spell remains!  \
               However, the scroll on which it was written vanishes as \
               the last word is copied.";
      ];

Class  spell_book_class
 with magic 0,
      capacity 16,
      before
      [ p i; Open, Close:
             print_ret
             (The) self, " is always open to the right place, but it \
             is also always closed. This eliminates tedious leafing and \
             hunting for spells.  Many lives have been saved by this \
             magical innovation.";
         Attack:
             print_ret "When you are done, ", (the) self, " remains unmarred.";
         Learn:
             if (self.magic==0) "(This spell book has no pages.)";
             p = self.magic;
             for (i=0:i<self.capacity && (p-->i)~=0:i++) ;
             if (i==self.capacity) rtrue;
             p-->i = second;
             rtrue;
      ],
      after
      [ p i j; Examine:
             if (self.magic==0) "(This spell book has no pages.)";
             p = self.magic;
             for (i=0:i<self.capacity && (p-->i)~=0:i++)
             {   j=p-->i; <Examine j>;
             }
             rtrue;
      ],
 has  is_spell_book;

Class  scroll_class
 with parse_name
      [ i j k; j=-1;
             if (self has general)
             {   if (child(self)~=0 && child(self) has is_spell)
                     j=(child(self).&name)-->0; else j='illegible';
             }
             for (::)
             {   k=NextWord();
                 if (k=='scrolls') parser_action=##PluralFound;
                 if ((k=='scrolls' or 'scroll' or j) || k==(self.&name)-->0)
                     i++;
                 else return i;
             }
      ],
      before
      [ i; Examine:
           i=child(self);
           give self general;
           if (i==0 || i hasnt is_spell)
               "The scroll has faded, and you cannot read it.";
           print "The scroll reads ~"; <SayName i>; "~.";
      ],
      invent
      [;   if (inventory_stage==2 && self has general)
           {   if (child(self)==0 || child(self) hasnt is_spell)
                   print " (which is illegible)";
               else
               {   print " (of ", (the) child(self), ")"; }
           }
      ],
 has  is_scroll;

[ ReadableSpell i j k;
 if (scope_stage==1)
 {   if (action_to_be==##Examine) rfalse;
     rtrue;
 }
 if (scope_stage==2)
 {   objectloop (i in player)
         if (i has is_spell_book)
         {   for (k=0:k<i.capacity && (i.magic)-->k~=0:k++)
             {   j=(i.magic)-->k; PlaceInScope(j);
             }
         }
     rtrue;
 }
 ! No need for scope_stage 3 (the error stage), because our
 ! ParserError routine handles that case instead
];

[ CopyableSpell i j k;
 if (scope_stage==1) return 1;
 if (scope_stage==2)
 {   objectloop (i in player)
         if (i has is_spell_book)
         {   for (k=0:k<i.capacity && (i.magic)-->k~=0:k++)
             {   j=(i.magic)-->k; PlaceInScope(j);
             }
         }
     rfalse;
 }
 ! No need for scope_stage 3 (the error stage), because our
 ! ParserError routine handles that case instead
];

[ SpellsSub; <Examine memory>; ];

[ LearnSub; if (location==thedark)
               print "(The magic writing of the spells casts enough light \
                       that you can read them.)^";
           <Insert memory noun>;
];

Global the_spell_was = gnusto_spell;

[ CastOneSub; <Cast the_spell_was noun>; ];

Property unmagic;

[ CastSub;
 the_spell_was = noun; <Remove memory noun>;

 if (noun has general)
 {   give noun ~general;
     if (RunRoutines(noun,unmagic)~=0) rfalse;
     "Nothing happens.";
 }

 if (second~=0)
 {   ResetVagueWords(second);                     ! Set "it", "him", "her"
     if (RunRoutines(second,before)~=0) rfalse;   ! Run before routine(s)
 }
 if (RunRoutines(noun,magic)~=0) rfalse;
 "Nothing happens.";
];

[ InScope i;
 if (verb_word=='c,cast' or 'cast')
     objectloop (i in memory) PlaceInScope(i);
 rfalse;
];

[ ParserError x i flag vb;
 if (etype==VERB_PE or ASKSCOPE_PE)
 {   if (etype==ASKSCOPE_PE)
     {   if (verb_word=='cast') vb=1;
         if (verb_word=='learn' or 'memorise' or 'memorize') vb=2;
         if (verb_word=='copy') vb=3;
         if (vb==0) { etype=CANTSEE_PE; rfalse; }
     }
     wn=verb_wordnum; if (vb~=0) wn++;
     x=NextWordStopped();
     for (i=player+1:i<=top_object:i++)
         if (i has is_spell && Refers(i,x)==1
             && i has known_about) flag=1;
     if (flag==1)
     {   if (vb==0 or 1)
            "You haven't got that spell committed to memory.  [Type ~spells~ \
             to see what you do remember.]";
         if (vb==2)
            "Your training is such that you can only memorise such a spell \
             with the aid of a spell book containing it.";
         if (vb==3)
            "You have no text of that spell to copy.";
     }
     if (vb==1)
        "You haven't learned that spell, if indeed it is a spell.";
     if (vb==2 or 3)
        "You haven't access to that spell, if indeed it is a spell.";
 }
 rfalse;
];

[ ChooseObjects obj code;
 if (code<2) rfalse;
 if (action_to_be==##WriteOn && obj in player) return 9;
 return 0;
];

[ UnknownVerb word i;
 objectloop (i in memory)
     if (word==(i.&name)-->0) { the_spell_was = i; return 'c,cast'; }
 rfalse;
];

[ PrintVerb v;
 if (v=='c,cast') { print "cast a spell at"; rtrue; }
 rfalse;
];

! ----------------------------------------------------------------------------
!   And now, on with the story.  First, some global variables:
! ----------------------------------------------------------------------------

Global prepared_flag = 0;          !  Prepared for resurrection?
Global hearing_good = 0;           !  Sharp hearing?
Global number_filled = 0;          !  Sockets in the temple filled
Global all_my_spells -> 32;        !  Array for contents of your spell book
Global helistars_spells -> 32;     !  ...and Helistar's

! ----------------------------------------------------------------------------
!   A "questions" verb.  Thus,
!      "who is my friend helistar"
!      "what was the great change"
!   and so on are recognised.
! ----------------------------------------------------------------------------

Object questions "qs";
[ QuerySub;
 print_ret (string) noun.description;
];
[ Topic i;
 if (scope_stage==1) return 0;
 if (scope_stage==2)
 {   objectloop (i in questions) PlaceInScope(i);
     rtrue;
 }
 "At the moment, even the simplest questions confuse you.";
];

Object q1 "q1" questions
 with name "helistar" "my" "friend" "colleague",
      description
     "Helistar is your colleague, an Enchanter like you who has been much \
      on your mind lately.  She has been investigating some very dark \
      magic indeed, and seems not to be around any more.  You feel rather \
      vague about the details.";
Object q2 "q2" questions
 with name "magical" "magic" "burin",
      description "A burin is an engraving and writing tool.";
Object q3 "q3" questions
 with name "change" "great",
      description
     "Something you had a lot to do with, but what exactly?  No, it's gone.";
Object q4 "q4" questions
 with name "cyclops",
      description
     "A one-eyed giant, usually hostile.  (Don't they teach anything at \
      adventurer school these days?)";
Object q5 "q5" questions
 with name "grue",
      description
     "The grue is a sinister, lurking presence in the dark places of the \
      earth. Its favorite diet is adventurers, but its insatiable appetite \
      is tempered by its fear of light. No grue has ever been seen by the \
      light of day, and few have survived its fearsome jaws to \
      tell the tale.";
Object q6 "q6" questions
 with name "grimoire",
      description
     "According to Chambers English Dictionary, a grimoire is ~a magician's \
      book for calling up spirits~.";

! ----------------------------------------------------------------------------
!   Some multiple objects, coins in fact, coded in deluxe fashion:
! ----------------------------------------------------------------------------

Attribute is_coin;

Class  coin_class
 with name "coin",
      description "A round unstamped disc, presumably part of the local \
          currency.",
      parse_name
      [ i j w;
        if (parser_action==##TheSame)
        {   if ((parser_one.&name)-->0 == (parser_two.&name)-->0) return -1;
            return -2;
        }
        w=(self.&name)-->0;
        for (::i++)
        {   j=NextWord();
            if (j=='coins') parser_action=##PluralFound;
            else if (j~='coin' or w) return i;
        }
      ],
 has  is_coin;

Class  gold_coin_class
class coin_class,
 with name "gold",
      plural "gold coins";
Class  silver_coin_class
class coin_class,
 with name "silver",
      plural "silver coins";
Class  bronze_coin_class
class coin_class,
 with name "bronze",
      plural "bronze coins";

Object coin1 "silver coin"
class silver_coin_class;

[ TossCoinSub; if (noun notin player) "You need to be holding the coin first.";
 move noun to parent(player);
 if (location==thedark) "You throw it away into the darkness.";
 if (random(20)==1) "You toss the coin, and it lands... on its edge, \
     amazingly.";
 "You toss the coin, and it comes up... blank, since neither side is \
  marked.";
];

! ----------------------------------------------------------------------------
!   The player's spell book, and three initial spells (to go with gnusto):
! ----------------------------------------------------------------------------

Object spell_book "spell book"
class spell_book_class,
 with name "spell" "book" "my" "spellbook",
      description "My Spell Book^";

Object frotz_spell "cause an object to give off light"
class spell_class,
 with name "frotz",
      magic
      [;  if (second==0) "There is a brief, blinding flash of light.";
          if (second has animate)
              "The spell, not designed for living creatures, goes sour.";
          if (parent(second)==compass)
              "The spell dissipates vaguely.";
          give second light;
          print_ret
           "There is an almost blinding flash of light as ", (the) second,
           " begins to glow!  It slowly fades to a less painful level, but ",
           (the) second, " is now quite usable as a light source.";
      ],
      unmagic
      [;  if (second==0) "There is a brief moment of deep darkness.";
          if (second has animate)
              "The spell, not designed for living creatures, goes sour.";
          if (parent(second)==compass)
              "The spell dissipates vaguely.";
          if (second hasnt light)
              print_ret (The) second, " isn't producing light as it is.";
          give second ~light;
          print_ret "A pool of darkness coagulates around ", (the) second,
                " but slowly fades back to normality.  Still, ",
                (the) second, " is no longer any kind of light source.";
      ];

Object rezrov_spell "open even locked or enchanted objects"
class spell_class,
 with name "rezrov",
      magic
      [;  if (second==0) "The world is open already.";
          if (second has animate)
              "It might be a boon to surgeons if it worked, but it doesn't.";
          if (second has open || second hasnt openable)
              "It doesn't need opening.";
          if (second hasnt locked)
          {   give second open;
              print_ret (The) second, " opens obediently.  \
              Like swatting a fly with a sledge hammer, if you ask me.";
          }
          give second open ~locked;
          print "Silently, ", (the) second, " swings open. ";
          if (second has container) <<Search second>>; new_line; rtrue;
      ],
      unmagic
      [;  if (second==0) "The world is closed already.";
          if (second has animate)
              "Happily, that is unnecessary.";
          if (second has locked || second hasnt lockable)
              "It doesn't need locking.";
          give second ~open locked;
          print "Silently, ", (the) second, " swings shut and locks.";
      ];

Object yomin_spell "mind probe"
class spell_class,
 with name "yomin",
      magic
      [;  if (second==0 || second hasnt animate)
              "That must be either vegetable or mineral.";
          if (second==player) "You give yourself a mild headache.";
          print_ret "You look into the rather ordinary thoughts of ",
                    (the) second, ".";
      ],
      unmagic
      [;  if (second==0 || second hasnt animate)
              "That must be either vegetable or mineral.";
          if (second==player) "You give yourself a mild headache.";
          print_ret (The) second, " is rather shocked, for some reason.";
      ];

! ----------------------------------------------------------------------------
!   The first scene: the Hut and its (rather easy) secret
! ----------------------------------------------------------------------------

Object Hut "Ramshackle Hut"
 with description
         "Until quite recently, someone lived here, you feel sure.  \
          Now the furniture is matchwood and \
          the windows are glassless.  Outside, it is a warm, sunny day, \
          and grasslands extend to the low hills on the horizon.",
      out_to Grasslands, w_to Grasslands,
      cant_go "There's only the one room: better go ~out~.",
      name "windows" "grasslands" "grass" "hills",
 has  light;

Nearby furniture "wooden furniture"
 with name "furniture" "broken" "wood" "wooden",
      before
      [;  Examine, Search, LookUnder:
              self.before=0; score=score+5;
              move h_box to player;
              "Searching through the furniture, which is good for nothing \
               but firewood now, you come across an old cedarwood box, \
               which you pick up for a closer look.";
      ],
 has  scenery;

Object h_box "cedarwood box"
 with name "cedar" "cedarwood" "wooden" "box",
      description "The box bears the calligraphed initial H."
 has  container openable lockable locked;

Nearby helistars_book "Helistar's grimoire"
class spell_book_class,
 with name "grimoire" "helistar" "helistars",
      description "This must be the grimoire of dangerous spells kept by \
                   your irresponsible friend Helistar.  Many pages are \
                   missing, but a few spells remain:^",
 has  proper;

! ----------------------------------------------------------------------------
!   Grasslands and the valley
! ----------------------------------------------------------------------------

Object Grasslands "Grasslands, near Hut"
 with name "grasslands" "grass" "hut" "path",
      description
         "The grasslands sway over low hills in all directions: it is a \
          peaceful wilderness, broken only by this hut and a faint path \
          to the north.",
      in_to Hut, e_to Hut,
      n_to Valley,
      cant_go "You wander around for a while but end up back at the hut."
 has  light;

Object Valley "Pocket Valley"
 with name "valley" "trail",
      description
         "A pleasant pocket valley in the grassy hills, through which a \
          trail runs north-to-south.",
      n_to "The trail runs out to nothing, and you retreat for fear of \
            getting so lost you couldn't find the hut again by nightfall.",
      cant_go "You wander around the pleasant valley, but are afraid to \
               lose sight of the trail.",
      s_to Grasslands
 has  light;

[ RideSub; print_ret "You can hardly ride ", (a) noun, "."; ];

Nearby horse "horse"
 with short_name
      [; if (self has general) print "winged horse";
         else print "chestnut horse";
         rtrue;
      ],
      parse_name
      [ i j; if (self has general) j='winged'; else j=-1;
         while (NextWord()==j or 'horse' or 'chestnut') i++;
         return i;
      ],
      describe
      [;  print_ret
             "There is ", (a) self, " here, munching on a pile of oats.";
      ],
      before
      [;  Cast: if (the_spell_was == bozbar_spell)
                {   give self general;
                   "A pair of handsome brown wings suddenly appears on \
                    the horse's powerful shoulders.  The horse turns in a \
                    complete circle, a look of puzzlement on his face.";
                }
                if (the_spell_was == yomin_spell)
                   "He is mainly thinking about oats.  Partly who you are \
                    and what you're up to, but mainly oats.";
          Enter: <<Ride self>>;
          Ride: if (horse hasnt general)
                   "You ride around for a while, exercising the horse, but \
                    soon enough he tires of this and pointedly brings you \
                    back to the oats.  Obligingly you dismount and he \
                    begins grazing again.";

          print "You begin to ride north.  Then, slowly at first but with \
                 increasing sureness, the horse begins beating its powerful \
                 wings.  You rise majestically through the air, sailing \
                 gracefully across a chasm where the hills fall away.  \
                 The horse lands gently on the far side and deposits you, \
                 taking to the skies again.^";
          PlayerTo(Edge); rtrue;
      ],
 has  animate;

Nearby oats "pile of oats"
 with name "oats" "pile" "of",
      before
      [;  Examine, Search, LookUnder:
              self.before=NULL;
              move shiny_scroll to player; score=score+5;
              itobj=shiny_scroll;
              "Sifting through the oats, you find a shiny scroll!  Lucky \
               you got to it before the horse did.  As you turn it over \
               in your hands, it seems undamaged.";
          Take:  "What would you want with all those oats?";
      ],
 has  scenery;

Object shiny_scroll "shiny scroll"
class scroll_class,
 with name "shiny";

Object bozbar_spell "cause an animal to sprout wings" shiny_scroll
class spell_class,
 with name "bozbar",
      magic
      [;  if (second==0 || second hasnt animate)
              "The spell dies away in vain.";
          if (second==player)
              "Your elbows twitch, but there is no other effect.";
          print_ret "For a moment, ", (the) second,
              " looks highly discomforted, but the moment passes.";
      ],
      unmagic
      [;  if (second==0 || second hasnt animate)
              "The spell dies away in vain.";
          if (second==player) "What wings?";
          if (second==horse && horse has general)
          {   give horse ~general;
              "The Enchanter giveth, and the Enchanter taketh away.  \
               The horse looks disconsolate but returns to the oats.";
          }
          print_ret (The) second, " has no wings to lose.";
      ];

! ----------------------------------------------------------------------------
!   The Chasm and the snake
! ----------------------------------------------------------------------------

Object Edge "Edge of Chasm"
 with name "wide" "chasm" "road" "daffodils" "clump",
      description
         "The road ends suddenly at a wide chasm.  The road leads upward \
          to the north, and you can see it continuing on the southern side \
          of the chasm.",
      u_to Up_Road, n_to Up_Road,
      cant_go "The chasm is too perilous to approach.  The only safe way is \
              up and to the north.",
      before
      [;  Jump: deadflag=1; "You jump bravely into the chasm, and plunge... \
              gracefully through the air.  (It gets a bit less noble and \
              airy after that.)";
      ],
 has  light;

Nearby snake "hissing snake"
 with name "hissing" "snake",
      initial
        "Lying in a tight coil at the edge of the chasm is a hissing snake.",
      description
        "It has some V-markings, some scaly parts, colours from grey to \
         reddish-brown. Is that any help?",
      life
      [; "The snake hisses angrily!"; ],
      before
      [;  Cast:
              switch(the_spell_was)
              {   urbzig_spell:
                      remove self;
                      snakes_cube.initial =
                   "Beside a clump of daffodils is a featureless white cube.";
                      "The snake is replaced by a clump of daffodils.";
                  bozbar_spell:
                      deadflag=1; remove self;
                      snakes_cube.initial =
                     "A featureless cube rests where the snake took off from.";
                     "The snake is transformed into a huge, winged serpent, \
                      a dragon which bellows and leaps out into the chasm, \
                      backwinging furiously... and knocking you over the \
                      edge quite by accident.";
                  yomin_spell:
                     "Horrid reptilian thoughts insinuate their way into you.";
              }
          Take, Remove:
             "The slipperiness of its skin is only one of many reasons \
              why this is ill-advised.";
      ],
 has  animate;

Nearby snakes_cube "cube"
class cube_class,
 with initial
        "The snake appears to be curled around a featureless white cube.",
      before
      [; if (snake notin nothing) "The snake won't let you near that cube!";
      ];

! ----------------------------------------------------------------------------
!   The crest of the hill; Icarus the tortoise; the chewed scroll
! ----------------------------------------------------------------------------

Object Up_Road "Crest of Hill"
 with description
         "The road crosses the top of a ridge here, sloping downwards to \
          the south and the northwest.  A track diverges to east.",
      nw_to Cave_Mouth, s_to Edge, d_to Edge, e_to Track,
 has  light;

Nearby tortoise "tortoise"
 with name "tortoise" "turtle",
      initial "A tortoise ambles along the road, extremely slowly.",
      life
      [; "The tortoise (slowly) turns its neck to look at you (stupidly).";
      ],
      before
      [; Cast: switch(the_spell_was)
               {   urbzig_spell:
                      "Just how safe do you want your surroundings to be?";
                   bozbar_spell:
                       move chewed_scroll to parent(self); remove self;
                       StartDaemon(self); score=score+5;
                      "The tortoise seems to be incapable of expressing \
                       surprise, but is now soaring away high in the sky.  \
                       Something rather grubby is left behind.";
                   yomin_spell:
                      "For a moment you think there is nothing there, as you \
                       chew absentmindedly on a leaf.  But somewhere inside \
                       the tortoise is a sense of wonder at the amazing blue \
                       canopy of the sky.";
               }
         Take, Remove:
              "Your parents always warned you not to pick up casual \
               acquaintances met on the road.";
      ],
      daemon
      [ i; if (location ~= Up_Road or Track || random(6)~=1) rfalse;
         if (random(4)==1 && self hasnt general)
         {   move feather to location; give self general;
             "^A tortoise-feather flutters to the ground before you!";
         }
         i=random(3);
         switch(i)
         {   1: print "^High in the sky,";
             2: print "^Far above you,";
             3: print "^Tiny in the blue sky,";
         }
         " a tortoise flaps across the sun.";
      ],
 has  animate;

Object torn_scroll "torn scroll"
class scroll_class,
 with name "torn";

Nearby lobal_spell "sharpen hearing"
class spell_class,
 with magic
      [;  if (second==0 || second hasnt animate)
              "There is a loud bang in your ear, but no other effect.";
          if (second==player)
          {   if (hearing_good==1) "There is no further effect.";
              hearing_good=1; StartTimer(self, 5);
              "Nothing happens, possibly because those butterflies on the \
               other side of the hill keep distracting you.";
          }
          print_ret (The) second,
             " is no doubt grateful for the gift of sharper hearing.";
      ],
      unmagic
      [;  if (second==0 || second hasnt animate)
              "There is a brief silence, but no other effect.";
          if (second==player) { StopTimer(self); hearing_good=0; "Pardon?"; }
          print_ret (The) second,
             " is no doubt grateful not to have to listen to you.";
      ],
      time_left 0,
      time_out
      [;  if (hearing_good==0) rfalse;
          hearing_good=0;
          "^Those wretched butterflies finally shut up.";
      ],
      name "lobal";

Object chewed_scroll "chewed scroll"
class scroll_class,
 with initial "It looks as if the tortoise was chewing something - once \
               it might have been a scroll, but now it lies there, \
               chewed up like a lettuce leaf.",
      before
      [;  Cast: if (the_spell_was == caskly_spell)
                {   move torn_scroll to parent(self);
                    remove self; score=score+5;
                    "Before your eyes, the scroll begins to repair itself, \
                     failing only at the very last tear.  Not quite perfect \
                     perhaps, but certainly a readable, if torn scroll.";
                }
      ],
 with name "chewed";

Object feather "tortoise feather"
 with name "tortoise" "feather",
      description
         "Possibly your rarest, and also least valuable, possession.";

! ----------------------------------------------------------------------------
!   The cave mouth and the perfect sapphire
! ----------------------------------------------------------------------------

Object Cave_Mouth "Cave Mouth"
 with name "gorse" "footpath" "cave" "mouth",
      description
         "This is a cave mouth, at one end of a road which winds southeast \
          over rising ground.  The entrance west to the caves is a dark \
          tunnel, and only a footpath runs further north, into gorse.",
      u_to Up_Road, se_to Up_Road, in_to Iron_Door, w_to Iron_Door,
      n_to Footpath
 has  light;

Nearby Iron_Door "iron door"
 with name "iron" "door" "heavy",
      description "It just looks like an ordinary heavy iron door.",
      door_dir
      [; if (location==Cave_Mouth) return w_to; return e_to;
      ],
      door_to
      [; if (location==Cave_Mouth) return In_Cave;
         return Cave_Mouth;
      ],
      describe
      [; if (self has open) "^The iron door stands open.";
         if (self hasnt locked) "^The iron door is unlocked but shut.";
         "A heavy iron door bars the cave mouth.";
      ],
      found_in  In_Cave  Cave_Mouth
 has  static door openable locked lockable;

!  Cf. T. S. Eliot, "Burnt Norton" II:
!  (but see also Mallarme's sonnet from which Eliot borrowed the image)

Nearby sapphire "perfect sapphire"
 with name "perfect" "sapphire" "gemstone" "gem",
      initial "Clotted in the mud beside the door is a perfect sapphire.",
      before
      [;  Examine: remove self; move caskly_spell to memory;
                   <Learn spell_book caskly_spell>;
                   caskly_spell.number=100;
                   "As you gaze into the perfect blue of the sapphire, \
                    you feel your mind begin to reel.  Unable to bear \
                    the naked sight of perfection, you look away, ashamed.  \
                    As you do so, the sapphire cracks and wastes away to \
                    thin hot dust.  But something remains, something in your \
                    mind...";
      ];

Object caskly_spell "cause perfection"
class spell_class,
 with name "caskly",
      magic
      [;  if (second==0) "Trying to make everything perfect was a little \
                          too ambitious.";
          if (second==player) "Oh, don't be too hard on yourself.";
          if (second==helistars_book)
              "Your spell is not powerful enough to restore the lost pages.";
          print_ret (The) second, " looks pretty perfect as is.";
      ];

! ----------------------------------------------------------------------------
!   Inside the Cave, the powerful urbzig spell and its consequences
! ----------------------------------------------------------------------------

Object In_Cave "Inside Cave"
 with description
         "A wide but shallow cave not far inside the hill.  There is no \
          obvious exit, except for the way you came in.",
      out_to
      [;  if (CoinsIn(left_pan)+CoinsIn(right_pan) < 6)
              "Something bars your way, and you hear \
               the scales jangling militantly.  You were trying to \
               steal its coins!";
          if (scales.number~=0) "Something bars your way, and you hear \
              the scales jangle slightly with energy.";
          return Iron_Door;
      ],
      e_to
      [;  return RunRoutines(self,out_to);
      ],
      cant_go "The only way is back ~out~ through the iron door.",
      after
      [;  Take: if (parent(noun)==left_pan or right_pan)
                    print_ret "Taken from ", (the) parent(noun), ".";
      ];

Nearby cave_cube "cube"
class cube_class,
 with initial "Balanced on a rock formation is a featureless white cube.";

Nearby scales "pair of scales"
 with name "pair" "of" "scales" "pans", number 0,
      describe
      [;  print "^A fair-sized pair of scales hangs from a bracket in the \
                 cave wall.  ";
          if (self.number==0)  "The scales are balanced.";
          if (self.number==1)  "The left-hand side is higher.";
          "The right-hand side is higher.";
      ],
      before
      [;  "There are left and right hand pans, which you should refer to \
           individually.";
      ],
 has  static supporter;

Class  pan_class
 with name "pan" "side" "tray",
      before
      [;  Receive: if (noun has is_scroll || noun has is_coin) rfalse;
          if (noun==feather) rfalse;
          "The pans gleam with what almost seems greed, and somehow they \
           contrive to nudge your hand past them with your worthless and \
           boring item.";
      ],
      after
      [ i j d w1 w2;  Receive, LetGo: i=scales.number;
          objectloop (j in left_pan) w1=w1 + WeightOf(j);
          objectloop (j in right_pan) w2=w2 + WeightOf(j);
          if (w1==w2) scales.number=0;
          if (w1 > w2) scales.number=-1;
          if (w1 < w2) scales.number=1;
          j=scales.number; d=(w2-w1)*(scales.number);
          if (j==i) rfalse;
          if (j==0) "The scales come into balance.";
          if (j==1) print "The left pan "; else print "The right pan ";
          if (d==1) "very slowly rises up.";
          "rises up.";
      ],
 has  supporter scenery;

[ WeightOf obj;
 if (obj==bronze_coin) return 2;
 if (obj has is_scroll || obj==feather) return 1;
 return 3;
];

[ CoinsIn obj i c;
 objectloop (i in obj) if (i has is_coin) c++;
 return c;
];

Nearby left_pan "left pan"
class pan_class,
 with name "left";
Nearby right_pan "right pan"
class pan_class,
 with name "right";
Object bronze_coin "bronze coin" left_pan
class bronze_coin_class;
Object coin3 "gold coin" left_pan
class gold_coin_class;
Object coin4 "gold coin" left_pan
class gold_coin_class;
Object coin5 "gold coin" right_pan
class gold_coin_class;
Object coin6 "silver coin" right_pan
class silver_coin_class;
Object coin7 "silver coin" right_pan
class silver_coin_class;
Object crumpled_scroll "crumpled scroll" left_pan
class scroll_class,
 with name "crumpled";

Object urbzig_spell "turn a dangerous object into a harmless one"
      crumpled_scroll
class spell_class,
 with name "urbzig",
      magic
      [;  if (second==0) "The spell fizzles away.";
          if (second==player) "It's a matter of opinion, isn't it?";
          if (second==helistars_book or mace || second has is_cube)
          {   CDefArt(second); remove second;
              if (second==mace && cyclops in location)
              {   remove cyclops; move eye_cube to location;
                  " turns into a featureless white cube just as the cyclops \
                   was about to hit you with it.  Mightily embarrassed \
                   by this, he drops the cube and runs off!";
              }
              print " turns into a moth and flutters away.^";
              rtrue;
          }
          print_ret "Nothing obvious happens.  Perhaps ", (the) second,
                    " isn't so very dangerous after all.";
      ],
      unmagic
      [;  if (second==0) "The spell fizzles away.";
          if (second==player) "It's a matter of opinion, isn't it?";
          if (second has static || second has scenery)
          {   print_ret "Your spell is too weak for something quite as \
                         monumentally harmless as ", (the) second, ".";
          }
          if (second==helistars_book or snake || second has is_cube
              || second==cyclops or mace)
              "Nothing obvious happens.";
          if (second in player)
          {   remove second; deadflag=1;
              "Suddenly, a tarantula races up your arm to your throat!  \
               Perhaps it was unwise to gizbru something you were \
               actually holding.";
          }
          if (cyclops has general)
              "Nothing happens.  Perhaps that's just as well, \
               after the last time.";
          move cyclops to location;
          remove second; give cyclops general; StartTimer(cyclops, 5);
          print_ret (The) second, " is replaced by a buck-toothed cyclops \
               wielding a mace!";
      ];

Object cyclops "buck-toothed cyclops"
 with name "buck" "toothed" "buck-toothed" "cyclops",
      initial "A huge buck-toothed cyclops menaces you, armed with a \
               heavy mace!",
      before
      [;  Cast: if (the_spell_was == bozbar_spell)
                    "Does the term ~death wish~ mean anything to you?";
                if (the_spell_was == urbzig_spell)
                    "The cyclops bellows with glee as your spell has \
                     no effect.  (After all, he wouldn't be ~dangerous~ if \
                     an urbzig spell worked on him, would he?)";
      ],
      life [; "He roars incoherently, swinging the mace!"; ],
      time_left 0,
      time_out
      [;  if (self notin location)
          {   remove self; rtrue;
          }
          deadflag=1; remove mace; remove cyclops;
          "Feeling that he's given you quite long enough to explain why \
           you made such a mess of his life, he swings the great mace \
           maniacally down on you!";
      ],
      each_turn
      [ i; i=random(4); if (i==1) "^The cyclops leaps and bellows!";
           if (i==2)
             "^Whirling the mace, the cyclops jabbers at you incoherently.";
           if (i==3)
             "^The cyclops is losing patience (the appropriate cyclops \
               word is untranslatable into English, but approximately means \
               ~forbearance in not smashing all nearby skulls~).";
           "^The cyclops jabs you with the mace, almost breaking your rib.";
      ],
 has  animate transparent;

Nearby mace "mace"
 with name "heavy" "mace" "axe",
      description "It looks much too heavy for you to even lift.";

Nearby eye_cube "cube"
class cube_class,
 with initial
        "A featureless white cube lies where the cyclops dropped it.";

! ----------------------------------------------------------------------------
!   The Footpath and the carpet
! ----------------------------------------------------------------------------

Object Footpath "Gorse Bushes"
 with description
          "The footpath from the cave mouth runs into dense, impenetrable \
           gorse bushes.  Perhaps it wasn't so much a footpath as a rill \
           in the earth where roots wouldn't take; anyway, there's no way \
           but back south.",
      s_to Cave_Mouth
 has  light;

Nearby carpet "beautiful red carpet"
 with name "beautiful" "magic" "red" "carpet",
      initial
         "Slung over one of the gorse bushes is a beautiful red carpet.",
      description
         "This is a carpet of unusual design. It is red, beautifully woven \
          and bears a pattern of cubes.",
      before
      [ i;  Receive:
                if (self notin location || self hasnt moved)
                    "Not until the carpet's on the ground, you can't.";
          Ride: <<Enter self>>;
          Enter:
                if (self notin location || self hasnt moved)
                    "Not until the carpet's on the ground, you can't.";
                if (location==Balance_Room)
                    "Mysteriously, the carpet rucks and pulls until you're \
                     thrown off.  It settles back on the white floor with a \
                     contented sigh.";
                if (location==In_Cave)
                    "The carpet rises suddenly, crashing into the roof of \
                     the cave and throwing you back off again.  Painfully.";
                if (location==Bazaar) i=Up_Road; else i=Bazaar;
                print "The carpet rises suddenly into the fluffy white \
                       clouds, and after a headlong journey deposits you...^";
                move self to i;
                PlayerTo(i,1); move player to self; <<Look>>;
          Take: if (player in self) "Not while you're on it!";
                for (i=child(self):i~=0:i=child(self))
                {   move i to location;
                    print "(Dislodging ", (the) i, ")^";
                }
      ],
 has  supporter enterable;

! ----------------------------------------------------------------------------
!   A Bazaar Lottery
! ----------------------------------------------------------------------------

Attribute is_ticket;

Object Bazaar "Crowded Bazaar"
 with description
          "This is a crowded, noisy bazaar.  Directly in front of you is \
           a lottery!  But the contemptuous-looking barker is doing a \
           very poor trade: hardly anyone wants his first prize, the \
           big cuddly toy elephant, or even his nineteenth prize, a \
           featureless white cube.",
      each_turn
      [;   switch(random(4))
           {   1: "^~Roll up!  Roll up!  One silver piece for three goes!~";
               2: "^~Come on, then!  Just a silver coin gets you three!~";
               3: "^~Think what you could win, all for one silver coin!~";
               4: "^~This could be your lucky day!~";
           }
      ],
      before
      [;   Learn:
               "~None of that!~ snaps the barker angrily, putting you off \
                your study habits.  He mutters about ~Enchanter cheats~, \
                but under the circumstances you decide to let the insult \
                pass.";
      ],
      cant_go "Everywhere, the crowds of jabbering natives block your way \
          to all the good stalls.  In fact, the only one you can get at is \
          this dismal lottery.",
 has  light;

Global last_called = 1;
Global explicit_flag = 0;
Global tickets_taken = 0;

Nearby board "lottery board"
 with number 0,
      name "board" "lottery" "holes",
      description
          "There are a hundred holes each way, making, um, let's see, yes, \
           ten thousand tickets in all.  Still, there are nineteen prizes, \
           so your odds must be, oh, well, not too awful anyway.",
      before
      [ i; LetGo:
               if (self.number==0) "The barker stabs you in the chest with \
                   his finger.  ~That's a silver coin to you, bub!~";

               tickets_taken++;

               .RandomTicket;
               if (explicit_flag==0) last_called=random(10000);

               for (i=taken_t1: i<=taken_t6: i++)
                   if (last_called == i.number)
                   {   if (explicit_flag==0) jump RandomTicket;
                      "That ticket's already taken.";
                   }

               self.number=self.number - 1;
               for (i=taken_t1: i<=taken_t6: i++)
                   if (i hasnt moved)
                   {   i.number = last_called; itobj = i;
                       move i to player; give i moved proper;
                       if (explicit_flag==0)
                           print "Randomly picking from the ",
                               10001-tickets_taken,
                              " numbered holes with tickets in, you ";
                       else print "You ";
                       print_ret "take ", (the) i, " out of the board.";
                   }
           Examine: ;
           Receive:
               if (noun has is_ticket)
                  "~No changes of mind, that's your ticket now!  Give it to \
                    me if you want to play it.~";
               <<Push self>>;
           default:
               "The barker is burly, and won't let you \
                tamper with the board.";
      ],
      initial
          "Behind the barker is a huge drilled board, and inside each little \
           numbered hole is a rolled-up lottery ticket."
 has  static container open;

Class  ticket_class
 with number -1, name "ticket",
      description
      [;  if (self.number==2306) "It is labelled ~First Prize~!";
          if (self.number==5802) "It is labelled ~Nineteenth Prize~.";
          "~You lose,~ says the ticket, with a smily face.  ~Try again!~";
      ],
      short_name
      [;  if (self.number==-1) rfalse;
          print "lottery ticket ", self.number; rtrue;
      ],
      parse_name
      [ i j w;
          i=0;
          if (NextWord()=='lottery') i++; else wn--;
          if (NextWord()=='tickets')
          {   parser_action=##PluralFound; return i+1; } else wn--;
          if (NextWord()~='ticket') return 0;
          if (self==ticket_in_board) explicit_flag=0;
          i++;
          w=TryNumber(wn);
          if (w==-1000) return i;
          if (w==0) return 0;
          if (self.number==-1)
          {   for (j=taken_t1: j<=taken_t6: j++)
                  if (w == j.number && TestScope(j) ~= 0) rfalse;
          }
          else
          {   if (self.number~=w) return 0;
          }
        if (self==ticket_in_board)
          {   explicit_flag=1; last_called = w;
          }
          i++; return i;
      ],
      before
      [;  Examine:
              if (self in board)
                 "It would be cheating to see what's written on the curled up \
                  tickets still in the board.";
          Cast: "~Get outta here, bub!~, the barker says, disgusted.";
      ],
 has  is_ticket;

Object ticket_in_board "rolled-up ticket from the board" board
class ticket_class
 with article "a";
Object taken_t1 "t1" class ticket_class;
Object taken_t2 "t2" class ticket_class;
Object taken_t3 "t3" class ticket_class;
Object taken_t4 "t4" class ticket_class;
Object taken_t5 "t5" class ticket_class;
Object taken_t6 "t6" class ticket_class;

Object barker "barker" Bazaar
 with name "barker" "burly" "man",
      number 0,
      description
          "A boxer gone to seed who failed as a magician all down the \
           coast, that'd be your guess.",
      life
      [;  Attack, Kiss: "No way.  He must weigh twice what you do.";
          Ask:  switch(second)
                {   'prize', 'prizes':
                        "~Just one silver coin and a prize could be yours!~";
                    'white', 'featureless', 'cube':
                        "He blows the dust off it.  ~Genuine antique, that.~";
                    'elephant', 'toy', 'cuddly':
                        "~Good quality merchandise,~ he says, in a way that \
                         suggests he can only spell one of those three words.";
                    'ticket', 'tickets', 'lottery':
                        "~Three tickets for one silver coin!~";
                    default: "~Just play the game, bub.~";
                }
          Order, Answer: "The barker glowers at you.";
          Give: if (noun has is_ticket)
                {   remove noun;
                    if (noun.number==2306)
                    {   move elephant to player; give elephant moved;
                        remove pelephant;
                        Bazaar.description =
          "This is a crowded, noisy bazaar.  Directly in front of you is \
           the lottery!";
                        "With very bad grace, the barker shoves the \
                         cuddly toy elephant into your arms.";
                    }
                    if (noun.number==5802)
                    {   move barker_cube to player; give barker_cube moved;
                        remove pcube;
                        Bazaar.description =
          "This is a crowded, noisy bazaar.  Directly in front of you is \
           the lottery!";
                        score=score+5;
                        "With concealed relief, the barker shoves the \
                         featureless white cube into your hands.";
                    }
                    "~Bad luck!  You lose!~";
                }
                if (self.number==2) "~You've had enough goes already!~ he \
                    growls.  No wonder trade is bad.";
                if (noun hasnt is_coin) "~What do you call that?  One silver \
                    coin to play!~";
                if ((noun.&name)-->0 == 'bronze')
                    "~Bronze!  Not a chance, sunshine.~";
                remove noun;
                board.number = board.number + 3; self.number=self.number+1;
                if ((noun.&name)-->0 == 'gold')
                    "Gleefully the barker snatches the gold coin.  ~Sorry \
                     bub, no change.  Business is slack today!~";
                "Grudgingly the barker takes the silver coin and stands \
                 back to let you at the board, arms folded.";
      ],
      before
      [;  Cast: switch(the_spell_was)
                {   bozbar_spell:
                       "He's not that much of an animal.";
                    lobal_spell:
                       "His problem is listening, not hearing.";
                    caskly_spell:
                       "For a moment his hair seems to comb itself.  \
                        Irritated, he ruffles it again, and the spell dies \
                        an ignominious death.";
                    yomin_spell:
                        if (elephant has moved || barker_cube has moved)
                        "The barker's mind is a heap of grumbles about lost \
                         prizes and scrawny Enchanters.";
                        if (self hasnt general)
                        {   give self general;
                           "~Hope that scrawny Enchanter doesn't pick 2306!~ \
                            thinks the barker (slowly).";
                        }
                        "~If that mark does win, hope it's only worthless \
                         old 5802,~ ponders the barker.";
                }
      ],
 has  animate scenery;

Object prizes "prizes" Bazaar
 with name "prize" "prizes",
      before [; "~Hands off those prizes!~"; ],
 has  scenery;

Object pelephant "prize elephant" Bazaar
 with name "prize" "elephant" "cuddly" "toy",
      description "Pink, cuddly, toy, elephant.  Says it all, really.",
      before [; Examine: ; default: "~Hands off those prizes!~"; ],
 has  scenery;

Object pcube "prize cube" Bazaar
 with name "prize" "featureless" "white" "cube",
      description "Wouldn't you like to win it?",
      before [; Examine: ; default: "~Hands off those prizes!~"; ],
 has  scenery;

Object elephant "cuddly toy elephant"
 with name "cuddly" "toy" "elephant",
      description "Pink, cuddly, toy, elephant.  Says it all, really.",
      before
      [;  Cast: if (the_spell_was == bozbar_spell)
                    "Let me get this straight.  You, the enchanter who \
                     defeated Krill, the head of the Borphee Guild \
                     himself...  are attempting to grow wings on a pink \
                     cuddly elephant?";
                if (the_spell_was == yomin_spell) "Woolly.";
      ];

Object barker_cube "cube"
class cube_class;

! ----------------------------------------------------------------------------
!   The spells in Helistar's grimoire
! ----------------------------------------------------------------------------

Object lleps_spell "reverse effect of memorised spell"
class spell_class,
 with name "lleps",
      magic
      [;   if (second==0 || parent(second)~=memory)
               "The spell backfires, painfully.";
           if (second.number==100)
               "You know that spell too well for your mind to be able \
                to accept the change.";
           if (second has general) give second ~general;
           else give second general;
           if (second==lleps_spell)
           {    <Remove memory second>;
               "Your mind wrenches as the two lleps spells \
                cancel each other out, leaving only a sensation \
                quite like a hangover.";
           }
           print_ret "Your mind wrenches as ", (the) second,
                     " reverses itself.";
      ],
      unmagic
      [;   RunRoutines(self,magic); rtrue;
      ];

Object mortin_spell "cause immediate death of caster"
class spell_class,
 with name "mortin",
      magic
      [;   deadflag=1;
           "You really can't fault Helistar on this one.  Death is \
            absolutely immediate, like a sudden blackout curtain...";
      ],
      unmagic
      [;   prepared_flag=1;
           "Nothing quite happens... and yet you feel enormously more \
            confident as you go about this dangerous world.";
      ];

! ----------------------------------------------------------------------------
!   Death and the Boneyard
! ----------------------------------------------------------------------------

[ AfterLife i;
 if (prepared_flag==0) rfalse;

 if (parent(player)==Balance_Room)
     "^^Your foresight in preparing a resurrection was wasted.  The \
        tangled magic of the Balance Room coiled around your puny \
        enchantment like a constricting serpent.";

 prepared_flag=0; deadflag=0; hearing_good=0;
 i=memory.capacity; if (i>1) memory.capacity = i-1;
 i=parent(player);
 while (child(player)~=0) move child(player) to i;
 move spell_book to player;

 print "^^With great foresight you prepared yourself for resurrection...  \
          Your mind feels a little weaker, but at least you're alive.^";
 PlayerTo(Boneyard);
];

Object Boneyard "Boneyard"
 with name "bones" "blades" "shoulder" "skulls",
      description
         "This is a room of bones.  Shoulder blades make up the floor, \
          skulls the walls and leg-bones the door frames. The west exit \
          leads into darkness, but the doorway to the north opens onto a \
          seemingly normal grassy scene.",
      n_to Grasslands,
      w_to "Some magical force blocks your way, as though that doorway \
            led into adventures from your past which you cannot rejoin now.",
      before
      [;  Examine, Search:
              if (noun==w_obj) "You can make out nothing to the west.";
      ],
 has  light;

Nearby worthless_scroll "worthless scroll"
class scroll_class,
 with initial "You are almost treading on a worthless scroll.",
      name "worthless";

Object filfre_spell "produce gratuitous fireworks" worthless_scroll
class spell_class,
 with name "filfre",
      magic
      [;    if (self hasnt scored) { score++; give self scored; }
           "A brief shower of gratuitous fireworks spells out:^^\
            The masterly Enchanter trilogy was written by Marc Blank, \
            Dave Lebling and Steve Meretzky.";
      ],
      unmagic
      [;   "A lengthy shower of artistically justified fireworks spells out:^^\
            The masterly Enchanter trilogy was written by Jane Austen, \
            Emily Bronte and Edgar Allen Poe.";
      ];

! ----------------------------------------------------------------------------
!   The Cubical Temple
! ----------------------------------------------------------------------------

Object Track "Track, outside Temple"
 with description
         "This is the end of a long track winding through desolate hills, \
          which runs back west up to the ridge.",
      before
      [;   Listen:
               if (hearing_good==0) "The chanting is too quiet to make out.";
               "The endlessly repeating threnody of the monks tells of \
                the legend of one who will some day enlighten their order, \
                and so be taken up to a higher plane.  He (or she, \
                presumably) is known as The Four-Cubed One.";
      ],
      w_to Up_Road, u_to Up_Road
 has  light;

Nearby Temple "cubical Temple"
 with name "temple" "cubical" "cube" "enormous",
      before
      [ i j;
        Enter: "The Temple is featureless and unbroken.  Perhaps the top \
                is open, because the sound must come from somewhere... \
                but you wouldn't bet on it.";
        Cast:   switch(the_spell_was)
                {   rezrov_spell:
                       "The huge temple remains impassive at your relatively \
                        puny enchantment.";
                    frotz_spell:
                        objectloop (i in player) if (i has is_cube) j++;
                        if (j==0)
                            "The temple shakes, but then is still again.";
                        if (j<4) "The temple shakes!  White light plays \
                            over your hands and possessions, but then all is \
                            still again.";
                        print "The temple shakes and white light bathes you.  \
                            Smoothly it unfolds itself in a four-dimensional \
                            way your senses can barely comprehend.  All you \
                            know is that when it is over, \
                            you find yourself in...^";
                        hearing_good=0; score=score+5;
                        PlayerTo(Balance_Room); rtrue;
                }
      ],
      describe
      [;   print "^You stand outside an enormous temple in the shape of a \
                  perfect, featureless white cube, four hundred feet on a \
                  side.  From somewhere within you hear the ";
           if (hearing_good==1) print "bellowing noise";
           else print "tiny sound";
           " of the monks chanting.";
      ],
      description
          "It's much like every other gigantic temple in the shape of a \
           featureless white cube you've ever seen.  No obvious way in.",
 has  static;

! ----------------------------------------------------------------------------
!   Inside the Temple
! ----------------------------------------------------------------------------

Object Balance_Room "Balance Room"
 with description
          "This seems to be the inside of a featureless white cube, forty \
           feet on a side.  The air is stale and there is no exit.",
 has  light;

Nearby balance_meter "image of the scales"
 with name "image" "scales" "of" "pair", article "the",
      initial "The image of a pair of scales hangs high in the air.  One \
               pan is much lower than the other.",
      before [; "It's only an image."; ],
 has  static;

Nearby dusty_podium "dusty podium"
 with name "podium" "dusty" "cobwebs" "cobwebbed",
      initial "Far below the scales, in the centre of the ~floor~, is a \
               predictably-shaped podium, but it is so dusty and \
               cobwebbed that you can't see what it once was.",
      before
      [;  Cast: if (the_spell_was == caskly_spell)
                   "Nice try, but it is protected from enchantment.";
               "However dusty it is, the podium is still protected from \
                casual enchantment.";
          Rub:  remove self; move balance_key to Balance_Room;
                itobj = balance_key;
               "No substitute for old-fashioned hard work, sometimes, \
                and after much patient (sneezy) scrubbing, the podium \
                appears in its true white glory.  Set into it are four \
                sockets, arranged in a two by two square.";
      ],
 has  static;

Object balance_key "podium"
 with name "podium" "pedestal" "platform" "cubical",
      description "As predicted, it is cubical.",
      initial "Far below the scales, in the centre of the ~floor~, is a \
               predictably-shaped podium.  Set into it are four sockets, \
               arranged in a two by two square.",
 has  static supporter;

Nearby sockets "two by two square"
 with name "square" "two" "by" "two",
      before
      [ i;  if (action~=##Examine || number_filled==0)
             "You'll have to say which socket you mean.  \
              (Let's call them ~top left~, ~bottom right~ and so on.)";
          objectloop (i in self)
          {   print (The) i;
              if (child(i)==0) print " is empty.^";
              else { print " contains ", (a) child(i), ".^"; }
          }
          rtrue;
      ],
 has  static;

Class  socket_class
 with name "socket", article "the",
      before
      [;  Cast:  "The sockets are proof against magic.";
          Examine: print (The) self, ", cubical and slightly more \
                   than four inches on a side, is decorated with ",
                   (string) self.description;
                   if (child(self)==0) ".";
                   print_ret ", and contains ", (a) child(self), ".";
          Receive: if (noun hasnt is_cube) "The socket rejects that.";
                   if (child(self)~=0)
                      "There is already a cube in that socket.";
      ],
      after
      [;  LetGo:  number_filled--;
                  "With much struggle, you manage to pull the cube away.";
          Receive: number_filled++;
          if (number_filled==4)
          {   if (snakes_cube in bl_socket
                  && barker_cube in ul_socket
                  && cave_cube in br_socket
                  && eye_cube in ur_socket)
              {   deadflag=2; score=score+5;
                 "As you place the final cube into the sockets, you feel \
                  imbued with celestial wisdom (more so than usually).  \
                  You find yourself growing to the height of the cube, so \
                  that you pull the balances back level by hand, and then \
                  you grow still further, out of the temple until it is but \
                  a cube in your hand, and you are a giant towering over \
                  the land.^^\
                  Then, of course, you wake up, glumly realising it's time \
                  to go to your job at the new Borphee Laboratories and \
                  all those Wheatstone bridge experiments.  But at least \
                  you can dream about the old days.";
              }
             "The sockets are all full now, but that doesn't mean \
              anything's happened.";
          }
         "The cube is a predictably perfect fit in the socket.";
      ],
 has  static container open;

Nearby bl_socket "bottom left socket"
class socket_class
 with name "bottom" "left" "serpent",
      description "a serpent";

Nearby ul_socket "top left socket"
class socket_class
 with name "top" "left" "bazaar",
      description "a scene in a bazaar";

Nearby br_socket "bottom right socket"
class socket_class
 with name "bottom" "right" "cave",
      description "an engraving of a rocky cave";

Nearby ur_socket "top right socket"
class socket_class
 with name "top" "right" "eye",
      description "an eye";

! ----------------------------------------------------------------------------
!   That's all of the object definitions: just a little code and grammar left
! ----------------------------------------------------------------------------

[ Initialise;

 location = Hut;
 move burin to player; move coin1 to player; move spell_book to player;

 thedark.description =
     "It is pitch black.  You are likely to be eaten by a grue.";
 ! (In fact you are stone-cold certain not to be, but never mind.)

 spell_book.magic = all_my_spells;
 <Learn spell_book gnusto_spell>;
 <Learn spell_book frotz_spell>;
 <Learn spell_book yomin_spell>;
 <Learn spell_book rezrov_spell>;
 give gnusto_spell known_about;

 helistars_book.magic = helistars_spells;
 <Learn helistars_book frotz_spell>;
 <Learn helistars_book lleps_spell>;
 <Learn helistars_book mortin_spell>;

 print "^^^^^This transcript is not from the Enchanter trilogy, but it \
        does show most of the usual things you can do in those stories...^^";
 print "You feel a little confused as to how you got here.  Something \
        to do with Helistar!  That's right, and how the world is so far \
        off balance nowadays, after the Great Change.^^";

];

[ PrintRank;
 print ", earning you the rank of ";
 if (score >= 50)  "Scientist.";
 if (score >= 40)  "Spellbreaker.";
 if (score >= 30)  "Sorcerer.";
 if (score >= 20)  "Enchanter.";
 if (score >= 10)  "novice Enchanter.";
 "lost dreamer.";
];

[ DiagnoseSub i;
 i=memory.capacity;
 if (i==5) "You feel fine, and your memory is unimpaired.";
 if (i==4) "You feel shaky after your brush with death, but your mental \
            faculties seem sound.";
 if (i==3) "For someone who has died twice, you're in reasonable shape.";
 "How many times have you died now?  Your memory isn't what it was.";
];

! ----------------------------------------------------------------------------
!   Grammar extensions needed by the spell-casting and cube-writing rules:
! ----------------------------------------------------------------------------

Include "NewMenu";

Constant SHOWSOLVEDTAG;
Constant HINTDEBUGVERBS;
Include "AdHints";
Include "ah_bal";

Include "Grammar";

[ AnyWord; from_char=0; to_char=0; the_named_word=wn++; return burin; ];

[ QuotedText i j f;
  i = WordAddress(wn++); i=i-buffer;
  if (buffer->i=='"')
  {   for (j=i+1:j<=(buffer->1)+1:j++)
          if (buffer->j=='"') f=j;
      if (f==0) return -1;
      from_char = i+1; to_char=f-1;
      if (from_char>to_char) return -1;
      while (buffer+f > WordAddress(wn)) wn++; wn++;
      return burin;
  }
  return -1;
];

Verb "write" "scribe"
               * AnyWord "on" held              -> WriteOn
               * QuotedText "on" held           -> WriteOn;
Verb "copy"     * scope=CopyableSpell "to" noun  -> CopyTo;
Verb "who" "what"
               * "is"  scope=Topic              -> Query
               * "was" scope=Topic              -> Query;
Verb "spells" "memory"
               *                                -> Spells;
Verb "learn" "memorise" "memorize"
               * scope=ReadableSpell            -> Learn;
Extend "examine" first
               * scope=ReadableSpell            -> Examine;
Verb "c,cast"
               *                                -> CastOne
               * noun                           -> CastOne;
Verb "cast"
               * is_spell                       -> Cast
               * is_spell "at" noun             -> Cast
               * is_spell "on" noun             -> Cast;
Verb "diagnose" "health"
               *                                -> Diagnose;

! ----------------------------------------------------------------------------
!   And one for the game itself.
! ----------------------------------------------------------------------------

Verb "ride" "mount" "straddle"
               * creature                       -> Ride
               * noun                           -> Enter;
Verb "flip" "toss" * is_coin                     -> TossCoin;

end;