!--------------------------------------------------------------------------
! FROBOZZICA: a demonstration by Gareth Rees
!
! The Inform library by default provides a system allowing "look up topic
! in book" and "consult book about topic", using grammar lines like
!
!     Verb "consult"
!         * noun "about" ConTopic -> Consult
!
! where the "ConTopic" routine just parses any old rubbish that the player
! might type, but sets the variable `consult_from' to the number of the
! first word in the rubbish.  An object can then parse these words itself
! to determine what the player asked.
!
! However, this can get a bit tedious if a topic has many words relating to
! it and you don't care about an exact match, or if several books need to
! refer to the same topic.  It would be better to get the parser to parse
! topics like objects.  This example game shows how it can be done, and how
! a similar system can be implemented allowing conversational topics to be
! parsed in the same way.
!--------------------------------------------------------------------------

Constant Story "ENCYCLOPEDIA FROBOZZICA";
Constant Headline "^An interactive demonstration^by Gareth Rees^";

Include "parser";
Include "verblib";
Include "grammar";


!--------------------------------------------------------------------------
! TOPICS
!
! Each topic that may be looked up in a book or asked of an NPC is given an
! object of class `TopicClass' inside the object `Topics' (the contents of
! which are put into scope at the right time).  The `TopicClass' makes sure
! that the topic's name is never printed, and so a correctly parsed topic
! cannot be distinguished from an unrecognised string of words.  For
! example, "look up zork" may produce the question "What do you want to
! look up that in?" (if there were two books nearby), rather than "What do
! you want to look up the Zork in?", which would have given away the
! existence of the `Zork' topic.
!
! CAVEAT: if two topics share a word in common, then an attempt to look up
! that word will result in "Which do you mean, that or that?"  Several
! solutions spring to mind: one is to avoid words in common wherever
! possible; another is to give the topics `parse_name' routines that don't
! allow the shared word on its own to refer to that topic.
!--------------------------------------------------------------------------

Class   TopicClass
has    proper
with   short_name "that";

Object  Topics "topics";

Nearby  TopicZork "Zork" class TopicClass
with   name "zork" "great" "underground" "empire" "gue";

Nearby  TopicWizard "Wizard of Frobozz" class TopicClass
with   name "wizard" "of" "frobozz";

Nearby  TopicFlathead "Lord Dimwit Flathead" class TopicClass
with   name "dimwit" "flathead" "excessive";

!--------------------------------------------------------------------------
! CONSULTING AND CONVERSATIONAL GRAMMAR
!
! We ensure that each possible pattern of input has two grammar lines
! associated with it, one involving a `scope=TopicScope' that parses topics
! as objects, and another using `ConTopic' or `ConTopicI' or so on, that
! reads any number of words (possibly stopping at a preposition).
!
! The idea is that if the topic is recognised, then the first grammar line
! will match, and generate a `NewConsult' or `Question' action.  But if the
! topic is not recognised, the second line will match, and generate a
! `Consult' or `NoQuestion action.  The second type of line acts as a
! `catch all' so that the player can't find out which words are valid
! topics except by looking them up in the correct book or asking them of
! the right person.
!
! There is a third type of grammar line, marked with (*), which can never
! be successfully parsed, but which is there to provide good error
! messages.  For example, given the grammar lines
!
!     Verb "read"
!         ...
!         * "about" ConTopicI "in" noun  -> Consult     ! (1)
!         * "about" ConTopic "in" noun   -> Consult;    ! (2)
!
! and the input
!
!     read about aardvark
!
! then the grammar line (1) can't match (because there was no word `in' in
! the input).  If line (2) weren't present the error message would be "I
! only understood you as far as wanting to read about."  But the ConTopic
! in line (2) matches the `aardvark', the preposition `in' is inferred by
! the parser, and it asks the question "What do you want to read about that
! in?", a much more acceptable error message.
!
! The grammar line (+) is necessary for "look" on its own to continue to
! work.
!--------------------------------------------------------------------------

[ TopicScope;
   if (scope_stage == 1) rfalse;
   if (scope_stage == 2) {
       ScopeWithin(Topics);
       rtrue;
   }
   "** Error: input should have matched a later line in grammar **";
];

[ NewConsultRSub; <<NewConsult second noun>>; ];
[ NewConsultSub; consult_words = 0; <<Consult noun>>; ];

Extend "look" first
   *                                    -> Look         ! (+)
   * "up" scope=TopicScope "in" noun    -> NewConsultR;

Extend "look" last
   * "up" ConTopic "in" noun            -> Consult;     ! (*)

Extend "consult" first
   * noun "about" scope=TopicScope      -> NewConsult
   * noun "on" scope=TopicScope         -> NewConsult;

Extend "read" first
   * "about" scope=TopicScope "in" noun -> NewConsultR
   * scope=TopicScope "in" noun         -> NewConsultR;

Extend "read" last
   * "about" ConTopic "in" noun         -> Consult      ! (*)
   * ConTopic "in" noun                 -> Consult;     ! (*)

[ QuestionSub; if (RunLife(noun,##Ask)~=0) rfalse; "No reply."; ];
[ RQuestionSub; <<Question second noun>>; ];
[ NoQuestionSub; <<Question noun 0>>; ];
[ SaySub; "Nothing happens."; ];

[ ConTopicPrep prep w; consult_from = wn;
 do w=NextWordStopped(); until (w==prep or -1); if (w==-1) return -1;
 wn--; consult_words = wn-consult_from;
 if (consult_words==0) return -1; return 0; ];
[ ConTopicTo; return ConTopicPrep('to'); ];
[ ConTopicAt; return ConTopicPrep('at'); ];

Extend "ask" replace
   * creature "about" scope=TopicScope  -> Question
   * creature "for" scope=TopicScope    -> Question
   * creature scope=TopicScope          -> Question
   * creature ConTopic                  -> NoQuestion;

Extend "say" replace
   * scope=TopicScope "to" creature     -> RQuestion
   * scope=TopicScope "at" creature     -> RQuestion
   * ConTopicTo "to" creature           -> NoQuestion
   * ConTopicAt "at" creature           -> NoQuestion
   * ConTopic "to" creature             -> NoQuestion     ! (*)
   * ConTopic                           -> Say;

Extend "tell" replace
   * creature "about" scope=TopicScope  -> Question
   * creature "about" ConTopic          -> NoQuestion
   * creature scope=TopicScope          -> Question
   * creature ConTopic                  -> NoQuestion;


!--------------------------------------------------------------------------
! THE GAME
!
! Some example objects with which to test the above definitions.  The
! extracts from the Encyclopedia Frobozzica have been lifted from the text
! written by Nino Ruffini, and available on the WWW at (for example)
! <http://www.spies.com/harrison/frobozz.html>.
!--------------------------------------------------------------------------

[ Initialise;
   location = Library;
   "^^^^^Everything you wanted to know about Zork but were afraid to \
   ask...^^";
];

Object  Library "Library of Zork"
has    light
with   description "A vast chamber, filled with the knowledge, legends \
           and lore of the Great Underground Empire.",
       cant_go "You wander for a while among the bookstacks, but can't \
           find a way out.";

Nearby  Encyclopedia "Encyclopedia Frobozzica"
with   name "encyclopedia" "encyclopaedia" "frobozzica" "book",
       description "The Encyclopedia is so packed full of amazing facts \
           that you'll have to look them up one at a time.",
       before [;
        NewConsult:
           switch(second) {
            TopicZork: "Formerly known as Quendor, the Great Underground \
               Empire reached its height under King Duncanthrax, began \
               declining under the excessive rule of Dimwit Flathead, \
               and finally fell in 883 GUE. The area is now called the \
               Land of Frobozz, after its largest province.";
            TopicWizard: "The Wizard of Frobozz was once a member of the \
               influential Accardi chapter of the Enchanters' Guild. \
               This Wizard was a strange little man, usually wearing a \
               long cloak, a high pointed hat with astrological signs, \
               and a long stringy beard. Once a court wizard, he was \
               exiled by Dimwit Flathead after accidentally turning \
               Flathead's castle into a mountain of fudge.";
            TopicFlathead: "Lord Dimwit Flathead the Excessive, the \
               great-great-grandson of King Duncanthrax, ruled the Great \
               Underground Empire from 770 GUE to 789 GUE. He was the \
               seventh king of the Flathead Dynasty, coming to the \
               throne after Mumberthrax, and before Loowit.";
           }
           "You can't find what you want in the Encyclopedia.";
       ];

Nearby  Librarian "librarian"
has    animate
with   name "librarian",
       description "His long beard, fiery eyes, and pointy black hat \
           with stars on suggests that he may be more than he seems.",
       life [;
        Ask:
           switch(second) {
            TopicZork: "~Zork doesn't really exist. It's just a bunch of \
                adventure games by a company called Infocom.~";
            TopicWizard: "~Fie to that flamboyant fellow in a fez! His \
               phantasms and fiends are but flippant fibs, foolish \
               fictions and flights of fancy.~";
            TopicFlathead: "~What a dimwit he was!~";
           }
       ];

End;