home *** CD-ROM | disk | FTP | other *** search
/ rtsi.com / 2014.01.www.rtsi.com.tar / www.rtsi.com / OS9 / OSK / GAMES / informosk.lha / parser.h < prev    next >
Text File  |  1993-12-08  |  69KB  |  2,076 lines

  1. ! ----------------------------------------------------------------------------
  2. !  Include file "PARSER":  for standard definitions, and a Z-code parser
  3. !
  4. !  (c) Graham Nelson, 1993, but freely usable
  5. ! ----------------------------------------------------------------------------
  6. !  Bug in ambiguity-resolution to do with compass objects fixed - 3/12/93
  7. ! ----------------------------------------------------------------------------
  8.  
  9. ! ----------------------------------------------------------------------------
  10. ! Declare the attributes and properties.  Note that properties "preroutine"
  11. ! and "postroutine" default to $ffff which forces them to be two bytes long:
  12. ! similarly, "timeleft" sometimes needs to be over 256, so it's flagged long
  13. ! ----------------------------------------------------------------------------
  14.  
  15. Attribute light;
  16. Attribute concealed;
  17. Attribute worn;
  18. Attribute clothing;
  19. Attribute animate;
  20. Attribute female;
  21. Attribute proper;
  22. Attribute moved;
  23. Attribute portal;
  24. Attribute container;
  25. Attribute supporter;
  26. Attribute interior;
  27. Attribute open;
  28. Attribute openable;
  29. Attribute workflag;
  30. Attribute enterable;
  31. Attribute scenery;
  32. Attribute static;
  33. Attribute direction;
  34. Attribute visited;
  35. Attribute lockable;
  36. Attribute locked;
  37. Attribute switchable;
  38. Attribute on;
  39. Attribute general;
  40. Attribute edible;
  41. Attribute autosearch;
  42. Attribute scored;
  43. Attribute talkable;
  44.  
  45. Property longdesc 0;
  46. Property article "a";
  47. Property initpos 0;
  48.  
  49. Property preroutine $ffff;
  50. Property postroutine $ffff;
  51. Property liferoutine $ffff;
  52.  
  53. Property dirprop;
  54. Property n_to;  Property s_to;
  55. Property e_to;  Property w_to;
  56. Property ne_to; Property se_to;
  57. Property nw_to; Property sw_to;
  58. Property u_to;  Property d_to;
  59.  
  60. Property portalto;
  61. Property closedpos;
  62. Property long timeleft;
  63. Property with_key;
  64. Property cantgo "You can't go that way.";
  65. Property capacity 100;
  66.  
  67. ! ----------------------------------------------------------------------------
  68. ! Construct the compass - a dummy object containing the directions, which also
  69. ! represent the walls in whatever room the player is in
  70. ! ----------------------------------------------------------------------------
  71.  
  72. Object compass "compass" nothing has concealed;
  73. Object n_obj "north wall" compass      
  74.   with name "n" "north" "wall",           article "the", dirprop n_to
  75.   has  direction scenery;
  76. Object s_obj "south wall" compass      
  77.   with name "s" "south" "wall",           article "the", dirprop s_to
  78.   has  direction scenery;
  79. Object e_obj "east wall" compass      
  80.   with name "e" "east" "wall",            article "the", dirprop e_to
  81.    has  direction scenery;
  82. Object w_obj "west wall" compass       
  83.   with name "w" "west" "wall",            article "the", dirprop w_to
  84.    has  direction scenery;
  85. Object ne_obj "northeast wall" compass 
  86.   with name "ne" "northe" "wall",         article "the", dirprop ne_to
  87.   has  direction scenery;
  88. Object nw_obj "northwest wall" compass
  89.   with name "nw" "northw" "wall",         article "the", dirprop nw_to
  90.   has  direction scenery;
  91. Object se_obj "southeast wall" compass
  92.   with name "se" "southe" "wall",         article "the", dirprop se_to
  93.   has  direction scenery;
  94. Object sw_obj "southwest wall" compass
  95.   with name "sw" "southw" "wall",         article "the", dirprop sw_to
  96.   has  direction scenery;
  97. Object u_obj "ceiling" compass         
  98.   with name "u" "up" "ceiling",           article "the", dirprop u_to
  99.    has  direction scenery;
  100. Object d_obj "floor" compass
  101.   with name "d" "down" "floor",           article "the", dirprop d_to
  102.    has  direction scenery;
  103.  
  104. ! ----------------------------------------------------------------------------
  105. ! The other dummy object is "Darkness", not really a place but it has to be
  106. ! an object so that the name on the status line can be "Darkness":
  107. ! we also create the player object
  108. ! ----------------------------------------------------------------------------
  109.  
  110. Object thedark "Darkness" nothing
  111.   with longdesc "It is pitch dark, and you can't see a thing.";
  112.  
  113. Object selfobj "yourself" thedark
  114.   with name "me" "myself" "self", article "the"
  115.   has  concealed animate proper;
  116.  
  117. ! ----------------------------------------------------------------------------
  118. ! Globals: note that the first one defined gives the status line place, the
  119. ! next two the score/turns
  120. ! ----------------------------------------------------------------------------
  121.  
  122. Global location = 1;
  123. Global sline1 = 0;
  124. Global sline2 = 0;
  125.  
  126. Global the_time = $ffff;
  127. Global time_rate = 1;
  128. Global time_step = 0;
  129.  
  130. Global score = 0;
  131. Global turns = 1;
  132. Global player;
  133.  
  134. Global lightflag = 1;
  135. Global deadflag = 0;
  136.  
  137. Global transcript_mode = 0;
  138. Global places_score = 0;
  139. Global things_score = 0;
  140. Global lookmode = 1;
  141. Global lastdesc = 0;
  142.  
  143. ! ----------------------------------------------------------------------------
  144. ! Parser variables accessible to the rest of the game
  145. ! ----------------------------------------------------------------------------
  146.  
  147. Global buffer          string 120;   ! Text buffer
  148. Global parse           string 64;    ! List of parsed addresses of words
  149. Global inputobjs       data 16;      ! To hold parameters
  150. Global toomany_flag    = 0;          ! Flag for "take all made too many"
  151. Global actor           = 0;          ! Person asked to do something
  152. Global action          = 0;          ! Thing he is asked to do
  153. Global inp1            = 0;          ! First parameter
  154. Global inp2            = 0;          ! Second parameter
  155. Global multiple_object data 64;      ! List of multiple parameters
  156. Global special_word    = 0;          ! Dictionary address of "special"
  157. Global special_number  = 0;          ! The number, if a number was typed
  158. global multiflag;                    ! Multiple-object flag
  159. global notheld_mode  = 0;            ! To do with implicit taking
  160. global onotheld_mode = 0;            !
  161. global meta;                         ! Verb is a meta-command (such as "save")
  162. global reason_code;                  ! Reason for calling a liferoutine
  163.  
  164. ! ----------------------------------------------------------------------------
  165. ! Main (putting it here ensures it is the first routine, as it must be)
  166. ! ----------------------------------------------------------------------------
  167.  
  168. [ Main;
  169.   player=selfobj;
  170.   PlayTheGame();
  171. ];
  172.  
  173. ! ----------------------------------------------------------------------------
  174. ! The parser, beginning with variables private to itself:
  175. ! ----------------------------------------------------------------------------
  176.  
  177. global buffer2  string 120;     ! Buffers for supplementary questions
  178. global parse2   string 64;      !
  179. global parse3   string 64;      !
  180.  
  181. global wn;                      ! Word number
  182. global num_words;               ! Number of words typed
  183. global verb_word;               ! Verb word (eg, take in "take all" or
  184.                                 ! "dwarf, take all") - address in dictionary
  185. global verb_wordnum;            ! - and the number in typing order (eg, 1 or 3)
  186.  
  187. global multi_mode;              ! Multiple mode
  188. global multi_filter;            ! Multiple mode filter
  189. global all_mode;                ! All mode
  190.  
  191. global pattern data 16;         ! For the current pattern match
  192. global pcount;                  ! and a marker within it
  193. global pattern2 data 16;        ! And another, which stores the best match
  194. global pcount2;                 ! so far
  195.  
  196. global parameters;              ! Parameters (objects) entered so far
  197.  
  198. global inferfrom;               ! The point from which the rest of the
  199.                                 ! command must be inferred
  200. global inferword;               ! And the preposition inferred
  201.  
  202. global oops_from = 0;           ! The "first mistake" point, where oops acts
  203. global saved_oops = 0;          ! Used in working this out
  204. global oops_heap data 10;       ! Used temporarily by "oops" routine
  205.  
  206. global match_list data 64;      ! An array of matched objects so far
  207. global number_matched;          ! How many items in it?  (0 means none)
  208. global match_length;            ! How many typed words long are these matches?
  209. global match_from;              ! At what word of the input do they begin?
  210.  
  211. global vague_word;              ! Records which vague word ("it", "them", ...)
  212.                                 ! caused an error
  213. global vague_obj;               ! And what it was thought to refer to
  214.  
  215. global itobj=0;                 ! The object which is currently "it"
  216. global himobj=0;                ! The object which is currently "him"
  217. global herobj=0;                ! The object which is currently "her"
  218.  
  219. global lookahead;               ! The token after the object now being matched
  220. global indef_mode;              ! "Indefinite" mode - ie, "take a brick" is in
  221.                                 ! this mode
  222. global not_holding;             ! Object to be automatically taken as an
  223.                                 ! implicit command
  224. global kept_results data 16;    ! The delayed command (while the take happens)
  225.  
  226. global saved_wn;                ! These are temporary variables for Parser()
  227. global saved_token;             ! (which hasn't enough spare local variables)
  228.  
  229. global plural_word1 = 1;        ! For plural nouns
  230. global plural_word2 = 1;        ! (a dictionary address can never be 1,
  231. global plural_word3 = 1;        ! so this is a safe "undefined" value)
  232. global plural_filter1;          ! And the attributes that define them
  233. global plural_filter2;          !
  234. global plural_filter3;          !
  235.  
  236. global etype;                   ! Error number used within parser
  237.  
  238. ! ----------------------------------------------------------------------------
  239. !  Put useful words into the dictionary...
  240. ! ----------------------------------------------------------------------------
  241.  
  242. Dictionary again_word   "again";
  243. Dictionary g_word       "g";
  244. Dictionary it_word      "it";
  245. Dictionary them_word    "them";
  246. Dictionary him_word     "him";
  247. Dictionary her_word     "her";
  248. Dictionary and_word     "and";
  249. Dictionary comma_word   "xcomma";
  250. Dictionary but_word     "but";
  251. Dictionary except_word  "except";
  252. Dictionary all_word     "all";
  253. Dictionary both_word    "both";
  254. Dictionary everyt_word  "everyt";
  255. Dictionary of_word      "of";
  256. Dictionary the_word     "the";
  257. Dictionary a_word       "a";
  258. Dictionary an_word      "an";
  259. Dictionary any_word     "any";
  260. Dictionary either_word  "either";
  261. Dictionary o_word       "o";
  262. Dictionary oops_word    "oops";
  263.  
  264. ! ----------------------------------------------------------------------------
  265. !  The Keyboard routine actually receives the player's words,
  266. !  putting the words in "a_buffer" and their dictionary addresses in
  267. !  "a_table".  It is assumed that the table is the same one on each
  268. !  (standard) call.
  269. !
  270. !  It can also be used by miscellaneous routines in the game to ask
  271. !  yes-no questions and the like, without invoking the rest of the parser.
  272. !
  273. !  Return the number of words typed
  274. ! ----------------------------------------------------------------------------
  275.  
  276. [ Keyboard  a_buffer a_table  nw i w x1 x2;
  277.  
  278.     DisplayStatus();
  279.     .FreshInput;
  280.  
  281. !  Save the start of the table, in case "oops" needs to restore it
  282. !  to the previous time's table
  283.  
  284.     for i 0 to 9 { put oops_heap byte i a_table->i; }
  285.  
  286. !  Print the prompt, and read in the words and dictionary addresses
  287.  
  288.     new_line; print_char '>'; read a_buffer a_table;
  289.  
  290.     nw=a_table->1;
  291.  
  292. !  If the line was blank, get a fresh line
  293.     if nw == 0
  294.     { print "I beg your pardon?^"; jump FreshInput; }
  295.  
  296. !  Unless the opening word was "oops" or its abbreviation "o", return
  297.  
  298.     w=a_table-->1;
  299.     if w == o_word or oops_word { jump DoOops; }
  300.     return nw;
  301.  
  302.     .DoOops;
  303.     if oops_from == 0
  304.     {   print "Sorry, that can't be corrected.^"; jump FreshInput; }
  305.     if nw == 1
  306.     {   print "Think nothing of it.^"; jump FreshInput; }
  307.     if nw > 2
  308.     {   print "~Oops~ can only correct a single word.^"; jump FreshInput; }
  309.  
  310. !  So now we know: there was a previous mistake, and the player has
  311. !  attempted to correct a single word of it.
  312. !
  313. !  Oops is very primitive: it gets the text buffer wrong, for instance.
  314. !
  315. !  Take out the 4-byte table entry for the supplied correction:
  316. !  restore the 10 bytes at the front of the table, which were over-written
  317. !  by what the user just typed: and then replace the oops_from word entry
  318. !  with the correction one.
  319. !
  320.     x1=a_table-->3; x2=a_table-->4;
  321.     for i 0 to 9 { put a_table byte i oops_heap->i; }
  322.     w=2*oops_from - 1;
  323.     put a_table word w x1;
  324.     inc w;
  325.     put a_table word w x1;
  326.  
  327.     return nw;
  328. ];
  329.  
  330. ! ----------------------------------------------------------------------------
  331. !  The Parser routine is the heart of the parser.
  332. !
  333. !  It returns only when a sensible request has been made, and puts into the
  334. !  "results" buffer:
  335. !
  336. !  Byte 0 = The action number
  337. !  Byte 1 = Number of parameters
  338. !  Bytes 2, 3, ... = The parameters (object numbers), but
  339. !                    00 means "multiple object list goes here"
  340. !                    01 means "special word goes here"
  341. !
  342. !  (Some of the global variables above are really local variables for this
  343. !  routine, because the Z-machine only allows up to 15 local variables per
  344. !  routine, and Parser runs out.)
  345. !
  346. !  To simplify the picture a little, a rough map of this routine is:
  347. !
  348. !  (A)    Get the input, do "oops" and "again"
  349. !  (B)    Is it a direction, and so an implicit "go"?  If so return
  350. !  (C)    Is anyone being addressed?
  351. !  (D)    Get the verb: try all the syntax lines for that verb
  352. !  (E)        Go through each token in the syntax line
  353. !  (F)           Check (or infer) an adjective
  354. !  (G)            Check to see if the syntax is finished, and if so return
  355. !  (H)    Cheaply parse otherwise unrecognised conversation and return
  356. !  (I)    Print best possible error message
  357. !  (J)    Retry the whole lot
  358. !
  359. !  The strategic points (A) to (J) are marked in the commentary.
  360. !
  361. !  Note that there are three different places where a return can happen.
  362. !
  363. ! ----------------------------------------------------------------------------
  364.  
  365. [ Parser  results   syntax line num_lines line_address i j
  366.                     token l m;
  367.  
  368. !  **** (A) ****
  369.  
  370. !  Firstly, in "not held" mode, we still have a command left over from last
  371. !  time (eg, the user typed "eat biscuit", which was parsed as "take biscuit"
  372. !  last time, with "eat biscuit" tucked away until now).  So we return that.
  373.  
  374.     if notheld_mode==1
  375.     {   for i 0 to 7 { put results byte i kept_results->i; }
  376.         notheld_mode=0; rtrue;
  377.     }
  378.  
  379.   .ReType;
  380.  
  381.     Keyboard(buffer,parse);
  382.  
  383.   .ReParse;
  384.  
  385. !  "etype" is the current failure-to-match error - it is by default
  386. !  the least informative one, "don't understand that sentence"
  387.  
  388.     etype=1;
  389.  
  390. !  Initially assume the command is aimed at the player, and the verb
  391. !  is the first word
  392.  
  393.     num_words=parse->1;
  394.     verb_wordnum=1;
  395.     actor=player;
  396.  
  397. !  Begin from what we currently think is the verb word
  398.  
  399.   .BeginCommand;
  400.     wn=verb_wordnum;
  401.     verb_word = NextWord();
  402.  
  403. !  Now try for "again" or "g", which are special cases:
  404. !  don't allow "again" if nothing has previously been typed;
  405. !  simply copy the previous parse table and ReParse with that
  406.  
  407.     if verb_word==g_word { verb_word=again_word; }
  408.     if verb_word==again_word
  409.     {   j=parse3->1;
  410.         if j==0 { print "You can hardly repeat that.^"; jump ReType; }
  411.         for i 0 to 63 { j=parse3->i; put parse byte i j; }
  412.         jump ReParse;
  413.     }
  414.  
  415. !  Save the present parse table in case of an "again" next time
  416.  
  417.     if verb_word~=again_word
  418.     {   for i 0 to 63 { j=parse->i; put parse3 byte i j; }
  419.     }
  420.  
  421. !  If the first word is not recognised, it can't be either the name of
  422. !  an animate creature or a verb, so give an error at once.
  423.  
  424.     if verb_word==0
  425.     {   etype=11; jump GiveError;
  426.     }
  427.  
  428. !  **** (B) ****
  429.  
  430. !  If the first word is not listed as a verb, it must be a direction
  431. !  or the name of someone to talk to
  432. !  (NB: better avoid having a Mr Take or Mrs Inventory around...)
  433.  
  434.     i=(verb_word->4) & 1;
  435.     if i==0
  436.     {   
  437.  
  438. !  So is the first word an object contained in the special object "compass"
  439. !  (i.e., a direction)?  This needs use of NounDomain, a routine which
  440. !  does the object matching, returning the object number, or 0 if none found,
  441. !  or 1000 if it has restructured the parse table so that the whole parse
  442. !  must be begun again...
  443.  
  444.         wn=verb_wordnum;
  445.         l=NounDomain(compass,0,0); if l==1000 { jump ReParse; }
  446.  
  447. !  If it is a direction, send back the results:
  448. !  action=GoSub, no of arguments=1, argument 1=the direction.
  449.  
  450.         if l~=0 
  451.         {   put results byte 0 #a$GoSub;
  452.             put results byte 1 1;
  453.             put results byte 2 l;
  454.             rtrue;
  455.         }
  456.  
  457. !  **** (C) ****
  458.  
  459. !  Only check for a comma (a "someone, do something" command) if we are
  460. !  not already in the middle of one.  (This simplification stops us from
  461. !  worrying about "robot, wizard, you are an idiot", telling the robot to
  462. !  tell the wizard that she is an idiot.)
  463.  
  464.         if actor==player
  465.         {   for j 2 to num_words
  466.             {   i=NextWord(); if i==comma_word { jump Conversation; }
  467.             }
  468.         }
  469.         etype=11; jump GiveError;
  470.  
  471. !  NextWord nudges the word number wn on by one each time, so we've now
  472. !  advanced past a comma.  (A comma is a word all on its own in the table.)
  473.  
  474.       .Conversation;
  475.         j=wn-1;
  476.         if j==1 { print "You can't begin with a comma.^"; jump ReType; }
  477.  
  478. !  Use NounDomain (in the context of "animate creature") to see if the
  479. !  words make sense as the name of someone held or nearby
  480.  
  481.         wn=1; lookahead=1;
  482.         l=NounDomain(player,location,6); if l==1000 { jump ReParse; }
  483.  
  484.         if l==0 { print "You seem to want to talk to someone, \
  485.                          but I can't see whom.^"; jump ReType; }
  486.  
  487. !  The object addressed must at least be "talkable" if not actually "animate"
  488. !  (the distinction allows, for instance, a microphone to be spoken to,
  489. !  without the parser thinking that the microphone is human).
  490.  
  491.         if l hasnt animate { if l hasnt talkable {
  492.             print "You can't talk to "; DefArt(l); print ".^"; jump ReType;
  493.         } }
  494.  
  495. !  Check that there aren't any mystery words between the end of the person's
  496. !  name and the comma (eg, throw out "dwarf sdfgsdgs, go north").
  497.  
  498.         if wn~=j
  499.         {   print "To talk to someone, try ~someone, hello~ or some such.^";
  500.             jump ReType;
  501.         }
  502.  
  503. !  The player has now successfully named someone.  Adjust "him", "her", "it":
  504.  
  505.         ResetVagueWords(l);
  506.  
  507. !  Set the global variable "actor", adjust the number of the first word,
  508. !  and begin parsing again from there.
  509.  
  510.         verb_wordnum=j+1; actor=l;
  511.         jump BeginCommand;
  512.     }
  513.  
  514. !  **** (D) ****
  515.  
  516. !  We now definitely have a verb, not a direction, whether we got here by the
  517. !  "take ..." or "person, take ..." method.  Get the meta flag for this verb:
  518.  
  519.     meta=(verb_word->4) & 2;
  520.  
  521. !  Now let i be the corresponding verb number, stored in the dictionary entry
  522. !  (in a peculiar 255-n fashion for traditional Infocom reasons)...
  523.  
  524.     i=$ff-(verb_word->5);
  525.  
  526. !  ...then look up the i-th entry in the verb table, whose address is at word
  527. !  7 in the Z-machine (in the header), so as to get the address of the syntax
  528. !  table for the given verb...
  529.  
  530.     syntax=(0-->7)-->i;
  531.  
  532. !  ...and then see how many lines (ie, different patterns corresponding to the
  533. !  same verb) are stored in the parse table...
  534.  
  535.     num_lines=(syntax->0)-1;
  536.  
  537. !  ...and now go through them all, one by one.
  538. !  To prevent vague_word 0 being misunderstood,
  539.  
  540.    vague_word=it_word; vague_obj=itobj;
  541.  
  542. !  **** (E) ****
  543.  
  544.     for line 0 to num_lines;
  545.     {   line_address = syntax+1+line*8;
  546.  
  547. !  We aren't in "not holding", "all" or inferring modes, and haven't entered
  548. !  any parameters on the line yet...
  549.  
  550.         not_holding=0;
  551.         inferfrom=0;
  552.         parameters=0; all_mode=0;
  553.  
  554. !  Put the word marker back to just after the verb
  555.  
  556.         wn=verb_wordnum+1;
  557.  
  558. !  An individual "line" contains six tokens...  "Pattern" gradually accumulates
  559. !  what has been recognised so far, so that it may be reprinted by the parser
  560. !  later on
  561.  
  562.         for pcount 1 to 6;
  563.         {   put pattern word pcount 0;
  564.  
  565.             token=line_address->pcount;
  566.  
  567. !  Lookahead is set to the token after this one, or 8 if there isn't one.
  568. !  (Complicated because the line is padded with 0's.)
  569.  
  570.             m=pcount+1; lookahead=8;
  571.             if m<=6 { lookahead=line_address->m; }
  572.             if lookahead==0
  573.             {   m=parameters; if token<=7 { inc m; }
  574.                 if m>=line_address->0 { lookahead=8; }
  575.             }
  576.  
  577. !  **** (F) ****
  578.  
  579. !  A token is either an "adjective" (Infocom-speak for preposition) number,
  580. !  which count downwards from 255 and are therefore large numbers, or else
  581. !  0 through to 7 for kinds of object
  582.  
  583. !  Anyway, first the case when it is an adjective: remember the adjective
  584. !  number in the "pattern".
  585.  
  586.             if token>7
  587.             {   put pattern word pcount 1000+token;
  588.  
  589. !  If we've run out of the player's input, but still have parameters to
  590. !  specify, we go into "infer" mode, remembering where we are and the
  591. !  adjective we are inferring...
  592.  
  593.                 if wn > num_words
  594.                 {   if inferfrom==0
  595.                     {   if parameters< line_address->0
  596.                         { inferfrom=pcount; inferword=token; }
  597.                     }
  598.  
  599. !  Otherwise, this line must be wrong.
  600.  
  601.                     if inferfrom==0 { break; }
  602.                 }
  603.  
  604. !  Whereas, if the player has typed something here, see if it is the
  605. !  required adjective... if it's wrong, the line must be wrong,
  606. !  but if it's right, the token is passed (jump to Back to finish this
  607. !  round of the loop).
  608.  
  609.                 if wn <= num_words
  610.                 {   if token~=Adjective() { break; } }
  611.                 jump Back;
  612.             }
  613.  
  614. !  **** (G) ****
  615. !  Check now to see if the player has entered enough parameters...
  616. !  (since line_address->0 is the number of them)
  617.  
  618.             if parameters == line_address->0
  619.             {  
  620.  
  621. !  If the player has entered enough parameters already but there's still
  622. !  text to wade through: store the pattern away so as to be able to produce
  623. !  a decent error message if this turns out to be the best we ever manage,
  624. !  and in the mean time give up on this line
  625.  
  626.                 if wn <= num_words
  627.                 {   for m 0 to 7 { put pattern2 word m pattern-->m; }
  628.                     pcount2=pcount;
  629.                     etype=2; break;
  630.                 }
  631.  
  632. !  Now, if we've only processed the "all" list once, we need to make a second
  633. !  pass (see below for the complicated explanation as to why).
  634.  
  635.                 if all_mode==1
  636.                 {   wn=saved_wn; token=saved_token;
  637.                     l=ParseObjectList(results,token);
  638.                     if l==1000 { jump ReParse; }
  639.                     if l==0 { break; }
  640.                 }
  641.  
  642. !  At this point the line has worked out perfectly, and it's a matter of
  643. !  sending the results back...
  644. !  ...pausing to explain any inferences made (using the pattern)...
  645.  
  646.                 if inferfrom~=0
  647.                 {   print "("; PrintCommand(inferfrom,1); print ")^";
  648.                 }
  649.  
  650. !  ...and to copy the action number, and the number of parameters...
  651.  
  652.                 put results byte 1 line_address->0;
  653.                 put results byte 0 line_address->7;
  654.  
  655. !  ...and to reset "it"-style objects to the first of these parameters, if
  656. !  there is one (and it really is an object)...
  657.  
  658.                 if parameters > 0
  659.                 {   i=results->2;
  660.                     if i>=2
  661.                     {   ResetVagueWords(i);
  662.                     }
  663.                 }
  664.  
  665. !  ...and declare the user's input to be error free...
  666.  
  667.                 oops_from = 0;
  668.  
  669. !  ...and worry about the case where an object was allowed as a parameter
  670. !  even though the player wasn't holding it and should have been: in this
  671. !  event, keep the results for next time round, go into "not holding" mode,
  672. !  and for now tell the player what's happening and return a "take" request
  673. !  instead...
  674.  
  675.                 if not_holding~=0
  676.                 {   notheld_mode=1;
  677.                     for i 0 to 7 { put kept_results byte i results->i; }
  678.                     put results byte 1 1;
  679.                     put results byte 0 #a$TakeSub;
  680.                     put results byte 2 not_holding;
  681.                     print "(first taking "; DefArt(not_holding); print ")^";
  682.                     rtrue;
  683.                 }
  684.  
  685. !  ...and finish.                
  686.                 rtrue;
  687.             }
  688.  
  689. !  Otherwise, the player still has at least one parameter to specify: an
  690. !  object of some kind is expected, and this we hand over to:
  691.  
  692.             l=ParseObjectList(results,token);
  693.             if l==1000 { jump ReParse; }
  694.             if l==0 { break; }
  695.  
  696. !  Finally, if we came up with a multiple object, then no parameter got recorded,
  697. !  so the value 0 is entered as parameter to signify that "the list goes here".
  698.  
  699.             if multi_mode==1
  700.             {   put results byte parameters+2 0;
  701.                 put pattern word pcount 0;
  702.                 parameters=parameters+1;
  703.             }
  704.  
  705. !  The token has been successfully parsed.  We have thus finished and can go on
  706. !  to the next token...
  707.  
  708.             .Back;
  709.         }
  710.  
  711. !  And if we get here it means that the line failed somewhere, so we continue
  712. !  the outer for loop and try the next line...
  713.  
  714.    }
  715.  
  716. !  So that if we get here, each line for the specified verb has failed.
  717.  
  718. !  **** (H) ****
  719.  
  720.   .GiveError;
  721.  
  722. !  Errors are handled differently depending on who was talking.
  723.  
  724. !  If the command was addressed to somebody else (eg, "dwarf, sfgh") then
  725. !  it is taken as conversation which the parser has no business in disallowing.
  726. !  In order to make it easier for the host game to work out what was said, the
  727. !  "verb" word (eg, "sfgh") is parsed as a number and as a dictionary entry, and
  728. !  the parser returns as if the player had typed
  729. !
  730. !     answer sfgh to dwarf   
  731. !
  732. !  with the globals special_word and special_number set accordingly.
  733.  
  734. !  (This is convenient for, say, "computer, 2451" or "guard, blue").
  735.  
  736.     if actor~=player
  737.     {   special_number=TryNumber(verb_wordnum);
  738.         wn=verb_wordnum;
  739.         special_word=NextWord();
  740.         action=#a$AnswerSub;
  741.         inp1=1; inp2=actor; actor=player;
  742.         rtrue;
  743.     }
  744.  
  745. !  **** (I) ****
  746.  
  747. !  If the player was the actor (eg, in "take dfghh") the error must be printed, and
  748. !  fresh input called for.  In three cases the oops word must be jiggled.
  749.  
  750.     if etype==1 { print "I didn't understand that sentence.^"; oops_from=1; }
  751.  
  752. !  In this case, we need to reconstruct the command to show how much was
  753. !  understood:
  754.  
  755.     if etype==2 { print "I only understood you as far as wanting to ";
  756.                   for m 0 to 7 { put pattern word m pattern2-->m; }
  757.                   pcount=pcount2;
  758.                   PrintCommand(0,1);
  759.                   print ".^";
  760.                 }
  761.     if etype==3 { print "You can't see any such thing.^"; oops_from=saved_oops; }
  762.     if etype==4 { print "You seem to have said too little!^"; }
  763.     if etype==5 { print "You aren't holding that!^"; oops_from=saved_oops; }
  764.     if etype==6 { print "You can't use multiple objects with that verb.^"; }
  765.     if etype==7 { print "You can only use multiple objects once on a line.^"; }
  766.     if etype==8 { print "I'm not sure what ~"; print_addr vague_word;
  767.                   print "~ refers to.^"; }
  768.     if etype==9 { print "You excepted something not included anyway!^"; }
  769.     if etype==10 { print "You can only do that to something animate.^"; }
  770.     if etype==11 { print "That's not a verb I recognise.^"; }
  771.     if etype==12 { print "That's not something you need to refer to \
  772.                           in the course of this game.^"; }
  773.     if etype==13 { print "You can't see ~"; print_addr vague_word;
  774.                    print "~ ("; DefArt(vague_obj); print ") at the moment.^"; }
  775.  
  776. !  **** (J) ****
  777.  
  778. !  And go (almost) right back to square one...
  779.  
  780.     jump ReType;
  781.  
  782. !  ...being careful not to go all the way back, to avoid infinite repetition
  783. !  of a deferred command causing an error.
  784.  
  785. ];
  786.  
  787. ! ----------------------------------------------------------------------------
  788. !  ParseObjectList
  789. !
  790. !  Returns:
  791. !    1000 for "reconstructed input"
  792. !    1    for "token accepted"
  793. !    0    for "token failed"
  794. !
  795. !  (A)            Go through a list of objects
  796. !  (B)              try plural nouns, "all", "except"
  797. !  (C)              try an explicit object
  798. !  (D)              cope with "and"s
  799. !
  800. ! ----------------------------------------------------------------------------
  801.  
  802. [ ParseObjectList results token l m o and_mode;
  803.  
  804. !  Well, we aren't in "multiple" or "and mode" yet...
  805.  
  806.     multi_mode=0; and_mode=0;
  807.  
  808. !  **** (A) ****
  809. !  We expect to find a list of objects next in what the player's typed.
  810.  
  811.   .ObjectList;
  812.  
  813. !  Take a look at the next word: if it's "it" or "them", and these are
  814. !  unset, set the appropriate error number and give up on the line
  815. !  (if not, these are still parsed in the usual way - it is not assumed
  816. !  that they still refer to something in context)
  817.  
  818.     o=NextWord(); dec wn;
  819.             
  820.     if o==it_word or them_word
  821.     {   vague_word=o; vague_obj=itobj;
  822.         if itobj==0 { etype=8; return 0; }
  823.     }
  824.     if o==him_word
  825.     {   vague_word=o; vague_obj=himobj;
  826.         if himobj==0 { etype=8; return 0; }
  827.     }
  828.     if o==her_word
  829.     {   vague_word=o; vague_obj=herobj;
  830.         if herobj==0 { etype=8; return 0; }
  831.     }
  832.  
  833. !  Skip over (and ignore) the word "the" before an item in the list
  834.  
  835.     if o==the_word { inc wn; jump ObjectList; }
  836.  
  837. !  The next item in the list is to be taken in "definite" mode (rather
  838. !  than "indefinite" mode) unless "a" or "an" or ... is hit: in which case
  839. !  they are skipped over.
  840.  
  841.     indef_mode=0;
  842.     if o==a_word or an_word or any_word { inc wn; indef_mode=1; }
  843.     if o==either_word { inc wn; indef_mode=1; }
  844.  
  845. !  Recall that "token" is set to the kind of objects expected
  846. !  here.  The value 7, or "special", means that a single word is expected,
  847. !  which is allowed to be anything at all (whether in the dictionary or not)
  848. !  but should also be tried as a decimal number:
  849. !  the global variables "special_number" and "special_word" hold what the
  850. !  parser thinks this word is.
  851. !
  852. !  The parameter is entered into the pattern and results as $ff.
  853.  
  854.     if token==7
  855.     {   special_number=TryNumber(wn);
  856.         special_word=NextWord();
  857.         put results byte parameters+2 1;
  858.         parameters=parameters+1;
  859.         put pattern word pcount 1;
  860.         jump NextInList;
  861.     }
  862.  
  863. !  Otherwise, we have one of the tokens 0 to 6, all of which really do mean
  864. !  that objects are expected.
  865.  
  866. !  **** (B) ****
  867. !  The code to handle plural nouns goes here.
  868. !  For instance, in Deja-Vu, "take cubes" sets the filter to cubes-only,
  869. !  (so that a "take all" would only take objects with the is_cube attribute
  870. !  set) and then replaces "cubes" by "all".
  871.  
  872.     multi_filter=0;
  873.     if o==plural_word1 { o=all_word; multi_filter=plural_filter1; }
  874.     if o==plural_word2 { o=all_word; multi_filter=plural_filter2; }
  875.     if o==plural_word3 { o=all_word; multi_filter=plural_filter3; }
  876.  
  877. !  "everything", "both" and "all" are synonymous here:
  878.             
  879.     if o==everyt_word or both_word or all_word
  880.     {   
  881.  
  882. !  Only tokens 2, 3, 4 and 5 can accept multiple objects, so the use of "all"
  883. !  throws the line out immediately if the token was anything else...
  884.  
  885.         if token<2 { etype=6; return 0; }
  886.         if token>=6 { etype=6; return 0; }
  887.  
  888. !  "all" mode is rather complicated.  all_mode is expected to count from 0
  889. !  to 2, because this code expects to be run _twice_.  (Thus if it ever reaches
  890. !  3, the player must have tried using "all" twice in different places, which
  891. !  we throw out as beyond our abilities.)
  892.  
  893.         inc all_mode;
  894.         if all_mode>2 { etype=7; return 0; }
  895.  
  896. !  The first pass through is now, in the usual way.  The second pass happens
  897. !  just before the command is finally accepted (see above).
  898.  
  899. !  In order to be able to come back and do it all again, on the first time
  900. !  out we have to save away the token number and the word marker;
  901. !  and we also call DoAll(location) to work out which of the objects in
  902. !  "location" are in context
  903.  
  904.         if all_mode==1
  905.         {   saved_wn=wn; saved_token=token;
  906.             DoAll(location);
  907.         }
  908.  
  909. !  The point of doing it twice is that on the second time through, parameters
  910. !  which will not be specified until later can be known.  Consequently, e.g.,
  911. !
  912. !     ~put everything in the rucksack~
  913. !
  914. !  can understand on the second attempt that the rucksack is to be excluded
  915. !  from the list of items.  On the first attempt it couldn't, because the
  916. !  rucksack hadn't been reached yet.
  917.  
  918. !  The token tells the parser how to interpret "all".
  919.  
  920. !  2 means: all items on the floor.  (But being careful to make sure they
  921. !           really do come from the floor, not from the "darkness" place,
  922. !           if the player is currently in the dark.)
  923. !  3 means: all items carried by the actor.
  924. !  4 means: all items carried, except the other parameter given, if that's
  925. !           also carried (this is the "put all in sack" case)
  926. !  5 means: all items inside the other parameter given
  927. !           (this is to handle things like "get all from cupboard")
  928.   
  929.         if all_mode==2
  930.         {   if token==2
  931.             {   if location==thedark { DoAll(location); }
  932.                 else { DoAll(parent(player)); }
  933.             }
  934.             if token==3 { DoAll(actor); }
  935.             if token==4
  936.             {   DoAll(actor); o=parent(results->3);
  937.                 if o==actor { MultiSub(results->3); }
  938.             }
  939.             if token==5 { DoAll(results->3); }
  940.  
  941. !  Note that MultiSub(thing) removed thing from the list.
  942. !  MultiFilter weeds out everything without the right attribute, if needed:
  943.  
  944.             if multi_filter~=0 { MultiFilter(multi_filter); }
  945.         }
  946.  
  947. !  Finally, skip over the actual "all" word, and pass on to any exceptions.
  948.  
  949.         inc wn;
  950.  
  951.         .Exceptions;
  952.         o=NextWord();
  953.  
  954. !  Once again, skip "the" and allow for plural nouns... in case of expressions
  955. !  like "drop all the cubes", where the filtering for cubes only doesn't happen
  956. !  until after the "all" has been processed.
  957.  
  958.         if o==the_word or of_word { jump Exceptions; }
  959.         if o==plural_word1 { MultiFilter(plural_filter1); jump Exceptions; }
  960.         if o==plural_word2 { MultiFilter(plural_filter2); jump Exceptions; }
  961.         if o==plural_word3 { MultiFilter(plural_filter3); jump Exceptions; }
  962.  
  963. !  Now check for "except" and "but", which we treat as synonyms, and if so
  964. !  go back round to the object list again but excluding (and_mode=2) rather
  965. !  than including (and_mode=1) things.
  966.  
  967.         if o==except_word { o=but_word; }
  968.         if o==but_word { and_mode=2; jump ObjectList; }
  969.  
  970. !  No exceptions... so the word marker is too far on, and we move it back;
  971. !  and the list must be complete.
  972.  
  973.         wn=wn-1;
  974.         jump EndOfList;
  975.     }
  976.  
  977. !  **** (C) ****
  978.  
  979. !  Right, so the item in the object list was not a plural object.  That means
  980. !  it will be an actual specified object, and is therefore where a typing error
  981. !  is most likely to occur, so we set:
  982.  
  983.     oops_from=wn;
  984.  
  985. !  Two cases now.  What really matters now is whether the token is 1 (meaning
  986. !  "single item held by the actor") or not, as this affects whether an
  987. !  implicit take ought to happen or not.
  988.  
  989. !  In either case we use NounDomain, giving it the token number as the current
  990. !  context, and two places to look: among the actor's possessions, and in the
  991. !  present location.  (Note that the order we look at these in depends on which
  992. !  we expect as more likely.)
  993.  
  994. !  (The reason we still bother to check the floor, even when an object in the
  995. !  actor's hands is expected, is to be able to do implicit taking, and to
  996. !  produce messages like "Already on the floor." if the player tries to drop
  997. !  something already dropped.)
  998.  
  999. !  A devious addition is that it is possible for NounDomain to ask the player
  1000. !  to clarify a single named object, and get a multiple one back.
  1001.  
  1002. !  Case One:  token not equal to 1
  1003.  
  1004.     if token~=1
  1005.     {   
  1006.         l=NounDomain(location, actor, token);
  1007.         if l==1000 { return l; }
  1008.         if l==0 { etype=CantSee(); return 0; }
  1009.         if l==1
  1010.         {   put results byte parameters+2 0;
  1011.             parameters=parameters+1;
  1012.             put pattern word pcount 0;
  1013.             return 1;
  1014.         }
  1015.  
  1016. !  Note that when the line has to be thrown out, it can be tricky working out
  1017. !á which error message is most appropriate, and CantSee() does the job for us.
  1018.  
  1019. !  Suppose the token was 6, which means "animate creature" - dwarf, sea
  1020. !  monster, etc.  Then throw the line away if the resulting object wasn't.
  1021.  
  1022.         if token==6
  1023.         {   if l hasnt animate
  1024.             {   etype=10; return 0; }
  1025.         }
  1026.  
  1027. !  We might have tokens 2 to 5 here, so multiple objects are still allowed,
  1028. !  and we have to cater for additions and subtractions.  If this is the
  1029. !  first object in the list so far (i.e. if we aren't in "and mode" at all)
  1030. !  then just store it as a parameter in the results, as usual.
  1031.  
  1032.         if and_mode==0
  1033.         {   put results byte parameters+2 l;
  1034.             parameters=parameters+1;
  1035.             put pattern word pcount l;
  1036.         }
  1037.  
  1038. !  But if we're already in "and mode", so that this is a subsequent object
  1039. !  in a list, add it into the multiple object collected so far...
  1040.  
  1041.         if and_mode==1
  1042.         {   MultiAdd(l); }
  1043.  
  1044. !  ...unless, of course, an "except" has happened, in which case remove it from
  1045. !  the list so far:
  1046.  
  1047.         if and_mode==2
  1048.         {   m=MultiSub(l);
  1049.             if all_mode~=1 { if m~=0 { etype=m; return 0; } }
  1050.         }
  1051.  
  1052. !  (and let MultiSub feed an error in the case when the player has tried
  1053. !  something like ~drop all except fish~ when not actually holding the fish,
  1054. !  in other words tried to except something not already there.)
  1055.  
  1056.     }
  1057.  
  1058. !  Case Two:  token equal to 1
  1059. !
  1060. !  Which is pretty similar...
  1061.  
  1062.     if token==1
  1063.     {   l=NounDomain(actor,location,token);
  1064.         if l==1000 { return l; }
  1065.         if l==0 { etype=CantSee(); return l; }
  1066.  
  1067. !  ...until it produces something not held by the actor.  Then an implicit
  1068. !  take must be tried.  If this is already happening anyway, things are too
  1069. !  confused and we have to give up (but saving the oops marker so as to get
  1070. !  it on the right word afterwards).
  1071. !
  1072. !  The point of this last rule is that a sequence like
  1073. !
  1074. !      > read newspaper
  1075. !      (taking the newspaper first)
  1076. !      The dwarf unexpectedly prevents you from taking the newspaper!
  1077. !
  1078. !  should not be allowed to go into an infinite repeat - read becomes
  1079. !  take then read, but take has no effect, so read becomes take then read...
  1080.  
  1081. !  Anyway for now all we do is record the number of the object to take.
  1082.  
  1083.         o=parent(l);
  1084.         if o~=actor
  1085.         {   if notheld_mode==1
  1086.             { saved_oops=oops_from; etype=5; return 0; }
  1087.             not_holding=l;
  1088.         }
  1089.         put results byte parameters+2 l;
  1090.         parameters=parameters+1;
  1091.         put pattern word pcount l;
  1092.     }
  1093.  
  1094. !  **** (D) ****
  1095.  
  1096. !  In either Case One or Two, if we got through all that, we've now come to
  1097. !  the end of the object specified, and might have reached the end of the list
  1098. !  or might have come to a comma or the word "and"...
  1099.  
  1100.     .NextInList;
  1101.  
  1102. !  Refers is a routine to work out which words refer to the object that the
  1103. !  parser decided the player meant, so the following skips the word marker
  1104. !  past the named object...
  1105.  
  1106.     o=NextWord();
  1107.     if 0~=Refers(l,o) { jump NextInList; }
  1108.  
  1109. !  And now we see if there's a comma or an "and" (which are equivalent here).
  1110.  
  1111.     if o==and_word or comma_word
  1112.     {   
  1113.  
  1114. !  If so, then once again they can only be used with tokens 2, 3, 4 and 5.
  1115.  
  1116.         if token<2 { etype=6; return 0; }
  1117.         if token>=6 { etype=6; return 0; }
  1118.  
  1119. !  Note that the following innocent line hides a subtlety: it would not be
  1120. !  right just to write "and_mode=1;" because that would convert the exception
  1121. !  mode (when and_mode=2) into the inclusion mode (and_mode=1).  In other words,
  1122. !  we are being careful about "drop all but the fish and the bread".
  1123.  
  1124.         if and_mode==0 { and_mode=1; }
  1125.  
  1126. !  "Multiple" mode keeps track of whether or not we've begun a multiple object
  1127. !  yet.  If we haven't, then we take the parameter back and begin a multiple
  1128. !  object list with it.
  1129.  
  1130.         if multi_mode==0 { put multiple_object byte 0 1;
  1131.         put multiple_object byte 1 l;
  1132.         parameters=parameters-1; multi_mode=1; }
  1133.  
  1134. !  And now go and find out what's next in the list.
  1135.  
  1136.         jump ObjectList;
  1137.     }
  1138.  
  1139. !  If none of that happened, then we finished the object list: we hit a word which
  1140. !  wasn't an "and" or a comma.  As the word counter was moved on by one in
  1141. !  checking this, bring it back by one:
  1142.  
  1143.     wn=wn-1;
  1144.  
  1145. !  The object list is now finished.
  1146.  
  1147.     .EndOfList;
  1148.  
  1149.     return 1;
  1150. ];
  1151.  
  1152. ! ----------------------------------------------------------------------------
  1153. !  PrintCommand reconstructs the command as it presently reads, from
  1154. !  the pattern which has been built up
  1155. !
  1156. !  If from is 0, it starts with the verb: then it goes through the pattern.
  1157. !  The other parameter is "emptyf" - a flag: if 0, it goes up to pcount:
  1158. !  if 1, it goes up to pcount-1.
  1159. !
  1160. !  Note that verbs and prepositions are printed out of the dictionary:
  1161. !  and that since the dictionary only preserves the first six characters
  1162. !  of a word, we have to hand-code the longer words needed.
  1163. !
  1164. !  (Recall that pattern entries are 0 for "multiple object", 1 for "special
  1165. !  word", 2 to 255 are object numbers and 1000+n means the preposition n)
  1166. ! ----------------------------------------------------------------------------
  1167.  
  1168. [ PrintCommand from emptyf i j k f;
  1169.   if from==0
  1170.   {   i=verb_word; from=1; f=1;
  1171.       if i==#w$invent { print "take an inventory"; jump VerbPrinted; }
  1172.       if i==#w$examin { print "examine";           jump VerbPrinted; }
  1173.       if i==#w$discar { print "discard";           jump VerbPrinted; }
  1174.       if i==#w$swallo { print "swallow";           jump VerbPrinted; }
  1175.       if i==#w$embrac { print "embrace";           jump VerbPrinted; }
  1176.       if i==#w$squeez { print "squeeze";           jump VerbPrinted; }
  1177.       if i==#w$purcha { print "purchase";          jump VerbPrinted; }
  1178.       if i==#w$unscre { print "unscrew";           jump VerbPrinted; }
  1179.       if i==#w$descri { print "describe";          jump VerbPrinted; }
  1180.       if i==#w$uncove { print "uncover";           jump VerbPrinted; }
  1181.       if i==#w$discar { print "discard";           jump VerbPrinted; }
  1182.       if i==#w$transf { print "transfer";          jump VerbPrinted; }
  1183.       print_addr i;
  1184.   }
  1185.   .VerbPrinted;
  1186.   j=pcount-emptyf;
  1187.   for k from to j
  1188.   {   if f==1 { print_char ' '; }
  1189.       i=pattern-->k;
  1190.       if i==0 { print "those things"; jump TokenPrinted; }
  1191.       if i==1 { print "that"; jump TokenPrinted; }
  1192.       if i<256 { DefArt(i); }
  1193.       if i>=1000
  1194.       {   i=AdjectiveAddress(i-1000);
  1195.           if i==#w$agains { print "against";       jump TokenPrinted; }
  1196.           print_addr i;
  1197.       }
  1198.       .TokenPrinted;
  1199.       f=1;
  1200.   }
  1201. ];
  1202.  
  1203. ! ----------------------------------------------------------------------------
  1204. !  The CantSee routine returns a good error number for the situation where
  1205. !  the last word looked at didn't seem to refer to any object in context.
  1206. !
  1207. !  The idea is that: if the actor is in a location (but not inside something
  1208. !  like, for instance, a tank which is in that location) then an attempt to
  1209. !  refer to one of the words listed as meaningful-but-irrelevant there
  1210. !  will cause an error 12 ("you don't need to refer to that in this game")
  1211. !  in preference to an error 3 ("no such thing"), or error 13 ("what's 'it'?").
  1212. !
  1213. !  (The advantage of not having looked at "irrelevant" local nouns until now
  1214. !  is that it stops them from clogging up the ambiguity-resolving process.
  1215. !  Thus game objects always triumph over scenery.)
  1216. ! ----------------------------------------------------------------------------
  1217.  
  1218. [ CantSee  i w e;
  1219.     saved_oops=oops_from;
  1220.     wn=wn-1; w=NextWord();
  1221.  
  1222.     e=3;
  1223.     if w==vague_word { e=13; }
  1224.     i=parent(actor);
  1225.     if i has visited { if 0~=Refers(i,w) { e=12; } }
  1226.     return e;
  1227. ];
  1228.  
  1229. ! ----------------------------------------------------------------------------
  1230. !  The DoAll routine works through everything in context inside
  1231. !  the "domain" (but does not descend the object tree), putting what's there
  1232. !  in the multiple object list.
  1233. !
  1234. !  Here "in context" means "neither concealed nor worn".  Some people might
  1235. !  also like to add "nor scenery".
  1236. ! ----------------------------------------------------------------------------
  1237.  
  1238. [ DoAll domain;
  1239.   multi_mode=1; put multiple_object byte 0 0;
  1240.   domain=child(domain);
  1241.   while domain~=0
  1242.   {   if domain hasnt concealed { if domain hasnt worn { MultiAdd(domain); } }
  1243.       domain=sibling(domain);
  1244.   }
  1245. ];
  1246.  
  1247. ! ----------------------------------------------------------------------------
  1248. !  The MultiAdd routine adds object "o" to the multiple-object-list.
  1249. !
  1250. !  This is only allowed to hold 63 objects at most, at which point it ignores
  1251. !  any new entries (and sets a global flag so that a warning may later be
  1252. !  printed if need be).
  1253. ! ----------------------------------------------------------------------------
  1254.  
  1255. [ MultiAdd o i j;
  1256.   i=multiple_object->0;
  1257.   if i==63 { toomany_flag=1; rtrue; }
  1258.   for j 1 to i
  1259.   {   if o==multiple_object->j { rtrue; }
  1260.   }
  1261.   i=i+1;
  1262.   put multiple_object byte i o;
  1263.   put multiple_object byte 0 i;
  1264. ];
  1265.  
  1266. ! ----------------------------------------------------------------------------
  1267. !  The MultiSub routine deletes object "o" from the multiple-object-list.
  1268. !
  1269. !  It returns 0 if the object was there in the first place, and 9 (because
  1270. !  this is the appropriate error number in Parser()) if it wasn't.
  1271. ! ----------------------------------------------------------------------------
  1272.  
  1273. [ MultiSub o i j k et;
  1274.   i=multiple_object->0; et=0;
  1275.   for j 1 to i
  1276.   {   if o==multiple_object->j
  1277.       {   for k j to i { put multiple_object byte k multiple_object->(k+1); }
  1278.           i=i-1;
  1279.           put multiple_object byte 0 i;
  1280.           return et;
  1281.       }
  1282.   }
  1283.   et=9; return et;
  1284. ];
  1285.  
  1286. ! ----------------------------------------------------------------------------
  1287. !  The MultiFilter routine goes through the multiple-object-list and throws
  1288. !  out anything without the given attribute "attr" set.
  1289. ! ----------------------------------------------------------------------------
  1290.  
  1291. [ MultiFilter attr  i j o;
  1292.   .MFiltl;
  1293.   i=multiple_object->0;
  1294.   for j 1 to i
  1295.   {   o=multiple_object->j;
  1296.       if o hasnt attr { MultiSub(o); jump Mfiltl; }
  1297.   }
  1298. ];
  1299.  
  1300. ! ----------------------------------------------------------------------------
  1301. !  NounDomain does the most substantial part of parsing an object name.
  1302. !
  1303. !  It is given two "domains" - places to look for the objects in question -
  1304. !  and a context (a token type, such as "on the floor"), and returns:
  1305. !
  1306. !   0    if no match at all could be made,
  1307. !   1    if a multiple object was made,
  1308. !   k    if object k was the one decided upon,
  1309. !   1000 if it asked a question of the player and consequently rewrote all
  1310. !        the player's input, so that the whole parser should start again
  1311. !        on the rewritten input.
  1312. ! ----------------------------------------------------------------------------
  1313.  
  1314. [ NounDomain domain1 domain2 context  first_word i j k oldw answer_words;
  1315.   match_length=0; number_matched=0; match_from=wn;
  1316.  
  1317. !  Use NounWithin to put together the match list - the list of "equally
  1318. !  good" (from a purely word-matching point of view) objects
  1319. !  (and put the word marker to the first word after the words matched)
  1320.  
  1321.   NounWithin(domain1); NounWithin(domain2);
  1322.   wn=match_from+match_length;
  1323.  
  1324. !  If nothing worked at all, leave with the word marker skipped past the
  1325. !  first unmatched word...
  1326.  
  1327.   if number_matched==0 { inc wn; rfalse; }
  1328.  
  1329. !  Suppose that there really were some words being parsed (i.e., we did
  1330. !  not just infer).  If so, and if there was only one match, it must be
  1331. !  right and we return it...
  1332.  
  1333.  
  1334.   if match_from <= num_words
  1335.   {   if number_matched==1 { i=match_list->0; return i; }
  1336.  
  1337. !  ...now suppose that there was more typing to come, i.e. suppose that
  1338. !  the user entered something beyond this noun.  Use the lookahead token
  1339. !  to check that if an adjective comes next, it is the right one.  (If
  1340. !  not then there must be a mistake like "press red buttno" where "red"
  1341. !  has been taken for the noun in the mistaken belief that "buttno" is
  1342. !  some preposition or other.)
  1343. !
  1344. !  If nothing ought to follow, then similarly there must be a mistake.
  1345.  
  1346.       if wn<=num_words
  1347.       {   if lookahead==8 { rfalse; }
  1348.           if lookahead>8
  1349.           {   if lookahead~=Adjective() { dec wn; rfalse; }
  1350.               dec wn;
  1351.           }
  1352.       }
  1353.   }
  1354.  
  1355. !  Now look for a good choice, if there's more than one choice...
  1356.   
  1357.   if number_matched==1 { i=match_list->0; }
  1358.   if number_matched>1  { i=Adjudicate(context); }
  1359.  
  1360. !  If i is non-zero here, one of two things is happening: either
  1361. !  (a) an inference has been successfully made that object i is
  1362. !      the intended one from the user's specification, or
  1363. !  (b) the user finished typing some time ago, but we've decided
  1364. !      on i because it's the only possible choice.
  1365. !  In either case we have to keep the pattern up to date,
  1366. !  note that an inference has been made, and return.
  1367.  
  1368.   if i~=0
  1369.   {   if inferfrom==0 { inferfrom=pcount; }
  1370.       put pattern word pcount i;
  1371.       return i;
  1372.   }
  1373.  
  1374. !  If we get here, there was no obvious choice of object to make.  If in
  1375. !  fact we've already gone past the end of the player's typing (which
  1376. !  means the match list must contain every object in context, regardless
  1377. !  of its name), then it's foolish to give an enormous list to choose
  1378. !  from - instead we go and ask a more suitable question...
  1379.  
  1380.   if match_from > num_words  {   jump Incomplete;  }
  1381.  
  1382. !  Now we print up the question...
  1383.  
  1384.   if context==6 { print "Who"; } else { print "Which"; }
  1385.   print " do you mean, ";
  1386.   j=number_matched-1;
  1387.   for i 0 to j
  1388.   {   k=match_list->i; Defart(k);
  1389.       if i<j-1 { print ", "; }
  1390.       if i==j-1 { print " or "; }
  1391.   }
  1392.   print "?^";
  1393.  
  1394. !  ...and get an answer:
  1395.  
  1396.   .WhichOne;
  1397.   answer_words=Keyboard(buffer2, parse2);
  1398.  
  1399.   first_word=(parse2-->1);
  1400.  
  1401. !  Take care of "all":
  1402.  
  1403.   if first_word==all_word or both_word or everyt_word
  1404.   {   
  1405.       if context>=2 { if context<=5
  1406.       {   for i 0 to j
  1407.           {   k=match_list->i;
  1408.               put multiple_object byte i+1 k;
  1409.           }
  1410.           put multiple_object byte 0 j+1;
  1411.           return 1;
  1412.       } }
  1413.       print "Sorry, you can only have one item here.  Which one exactly?^";
  1414.       jump WhichOne;
  1415.   }
  1416.  
  1417. !  If the first word of the reply can be interpreted as a verb, then
  1418. !  assume that the player has ignored the question and given a new
  1419. !  command altogether.
  1420. !  (This is one time when it's convenient that the directions are
  1421. !  not themselves verbs - thus, "north" as a reply to "Which, the north
  1422. !  or south door" is not treated as a fresh command but as an answer.)
  1423.  
  1424.   j=first_word->4;
  1425.   if 0~=j&1
  1426.   {   Copy(buffer, buffer2);
  1427.       Copy(parse, parse2);
  1428.       k=1000; return k;
  1429.   }
  1430.  
  1431. !  Now we insert the answer into the original typed command, as
  1432. !  words additionally describing the same object
  1433. !  (eg, > take red button
  1434. !       Which one, ...
  1435. !       > music
  1436. !  becomes "take music red button".  The parser will thus have three
  1437. !  words to work from next time, not two.)
  1438. !
  1439. !  To do this we use MoveWord which copies in a word.
  1440.  
  1441.   oldw=parse->1;
  1442.   put parse byte 1 answer_words+oldw;
  1443.  
  1444.   k=oldw+answer_words;
  1445.   while k>match_from
  1446.   {   i=k-answer_words; MoveWord(k, parse, i);
  1447.       dec k;
  1448.   }
  1449.   for k 1 to answer_words
  1450.   {   i=match_from+k-1; MoveWord(i, parse2, k);
  1451.   }
  1452.  
  1453. !  Having reconstructed the input, we warn the parser accordingly
  1454. !  and get out.
  1455.  
  1456.   k=1000; return k;
  1457.  
  1458. !  Now we come to the question asked when the input has run out
  1459. !  and can't easily be guessed (eg, the player typed "take" and there
  1460. !  were plenty of things which might have been meant).
  1461.  
  1462.   .Incomplete;
  1463.  
  1464.   if context==6 { print "Whom"; } else { print "What"; }
  1465.   print " do you want";
  1466.   if actor~=player { print " "; DefArt(actor); }
  1467.   print " to "; PrintCommand(0,1); print "?^";
  1468.  
  1469.   answer_words=Keyboard(buffer2, parse2);
  1470.  
  1471.   first_word=(parse2-->1);
  1472.  
  1473. !  Once again, if the reply looks like a command, give it to the
  1474. !  parser to get on with and forget about the question...
  1475.  
  1476.   j=first_word->4;
  1477.   if 0~=j&1
  1478.   {   Copy(buffer, buffer2);
  1479.       Copy(parse, parse2);
  1480.       k=1000; return k;
  1481.   }
  1482.  
  1483. !  ...but if we have a genuine answer, then we adjoin the words
  1484. !  typed onto the expression.  But if we've just inferred a
  1485. !  preposition which wasn't actually there, then we need to
  1486. !  adjoin that as well.  (NB: two consecutive prepositions will
  1487. !  cause trouble here!)
  1488.  
  1489.   oldw=parse->1;
  1490.  
  1491.   if inferfrom==0
  1492.   {   for k 1 to answer_words
  1493.       {   i=match_from+k-1; MoveWord(i, parse2, k);
  1494.       }
  1495.   }
  1496.   if inferfrom~=0
  1497.   {   for k 1 to answer_words
  1498.       {   i=match_from+k; MoveWord(i, parse2, k);
  1499.       }
  1500.       put parse2 word 1 AdjectiveAddress(inferword);
  1501.       MoveWord(match_from, parse2, 1);
  1502.       inc answer_words; 
  1503.   }
  1504.   put parse byte 1 answer_words+oldw;
  1505.  
  1506. !  And go back to the parser.
  1507.  
  1508.   k=1000; return k;
  1509. ];
  1510.  
  1511. ! ----------------------------------------------------------------------------
  1512. !  The Adjudicate routine tries to see if there is an obvious choice, when
  1513. !  faced with a list of objects (the match_list) each of which matches the
  1514. !  player's specification equally well.
  1515. !
  1516. !  To do this it makes use of the context (the token type being worked on).
  1517. !  It counts up the number of obvious choices for the given context
  1518. !  (all to do with where a candidate is, except for 6 (animate) which is to
  1519. !  do with whether it is animate or not);
  1520. !
  1521. !  if only one obvious choice is found, that is returned;
  1522. !
  1523. !  if we are in indefinite mode (don't care which) one of the obvious choices
  1524. !    is returned, or if there is no obvious choice then an unobvious one is
  1525. !    made;
  1526. !
  1527. !  otherwise, 0 (meaning, unable to decide) is returned.
  1528. ! ----------------------------------------------------------------------------
  1529.  
  1530. [ Adjudicate context i j good_ones last n ultimate;
  1531.  
  1532.   j=number_matched-1; good_ones=0; last=match_list->0;
  1533.   for i 0 to j
  1534.   {   n=match_list->i;
  1535.       if n hasnt concealed
  1536.       {   ultimate=n;
  1537.           .AdjLoop; ultimate=parent(ultimate);
  1538.           if ultimate~=location { if ultimate~=actor { if ultimate~=0 {
  1539.               jump Adjloop; } } }
  1540.           if context==0 { if ultimate==location { inc good_ones; last=n; } }
  1541.           if context==1 { if parent(n)==actor    { inc good_ones; last=n; } }
  1542.           if context==2 { if ultimate==location { inc good_ones; last=n; } }
  1543.           if context==3 { if parent(n)==actor    { inc good_ones; last=n; } }
  1544.           if context==4 { if parent(n)==actor    { inc good_ones; last=n; } }
  1545.           if context==5 { if parent(n)==actor    { inc good_ones; last=n; } }
  1546.           if context==6 { if n has animate       { inc good_ones; last=n; } }
  1547.       }
  1548.   }
  1549.   j=0;
  1550.   if good_ones==1  { j=last; }
  1551.   if indef_mode==1 { j=last; }
  1552.   return j;
  1553. ];
  1554.  
  1555. ! ----------------------------------------------------------------------------
  1556. !  MoveWord copies word at2 from parse buffer b2 to word at1 in "parse"
  1557. !  (the main parse buffer)
  1558. ! ----------------------------------------------------------------------------
  1559.  
  1560. [ MoveWord at1 b2 at2 x y;
  1561.   x=at1*2-1; y=at2*2-1;
  1562.   put parse word x b2-->y;
  1563.   inc x; inc y;
  1564.   put parse word x b2-->y;
  1565. ];
  1566.  
  1567. ! ----------------------------------------------------------------------------
  1568. !  NounWithin looks for objects in the domain which make textual sense
  1569. !  and puts them in the match list.
  1570. ! ----------------------------------------------------------------------------
  1571.  
  1572. [ NounWithin domain i threshold;
  1573.  
  1574. !  Special rule: the directions (interpreted as the ten walls of a room) are
  1575. !  always in context.  (So, e.g., "examine north wall" is always legal.)
  1576.  
  1577.   if domain==location { NounWithin(compass); }
  1578.  
  1579. !  Look through the objects in the domain
  1580.  
  1581.   domain=child(domain);
  1582.   while domain~=0
  1583.   { 
  1584.  
  1585. !  If we're beyond the end of the user's typing, accept everything
  1586. !  (NounDomain will sort things out)
  1587.  
  1588.     if match_from > num_words { MakeMatch(domain,1); jump DontAccept; }
  1589.  
  1590. !  "it" or "them" matches to the it-object only.  (Note that (1) this means
  1591. !  that "it" will only be understood if the object in question is still
  1592. !  in context, and (2) only one match can ever be made in this case.)
  1593.  
  1594.     wn=match_from;
  1595.     i=Noun();
  1596.     if i==1 { if itobj==domain { MakeMatch(itobj,1); } }
  1597.     if i==2 { if himobj==domain { MakeMatch(himobj,1); } }
  1598.     if i==3 { if herobj==domain { MakeMatch(herobj,1); } }
  1599.  
  1600. !  Construing the current word as a noun, can it refer to the object?
  1601.  
  1602.     if 0 == Refers(domain, i) { jump DontAccept; }
  1603.  
  1604. !  If it can, count up how many words in a row can refer to this object,
  1605. !  and send it to the match list with this number
  1606.  
  1607.     threshold=0;
  1608.     while 0~=Refers(domain,i) { i=NextWord(); inc threshold; }
  1609.     MakeMatch(domain,threshold);
  1610.  
  1611.   .DontAccept;
  1612.  
  1613. !  Shall we consider the possessions of the current object, as well?
  1614. !  Only if it's a container (so, for instance, if a dwarf carries a
  1615. !  sword, then "drop sword" will not be accepted, but "dwarf, drop sword"
  1616. !  will).
  1617. !  Also, only if there are such possessions.
  1618. !
  1619. !  Then the rules are: if it is open, or has the "interior" flag unset.
  1620. !
  1621. !  (The idea is that a steel box has an "interior", but a table hasn't.)
  1622.  
  1623.     if domain has supporter { jump DoRecurse; }
  1624.     if domain has container
  1625.     {   if domain has open { jump DoRecurse; }
  1626.         if domain has interior { jump DontRecurse; }
  1627.  
  1628.         .DoRecurse;
  1629.             if child(domain)~=0 { NounWithin(domain); }
  1630.         .DontRecurse;
  1631.     }
  1632.     domain=sibling(domain);
  1633.   }
  1634. ];
  1635.  
  1636. ! ----------------------------------------------------------------------------
  1637. !  MakeMatch looks at how good a match is.  If it's the best so far, then
  1638. !  wipe out all the previous matches and start a new list with this one.
  1639. !  If it's only as good as the best so far, add it to the list.
  1640. !  If it's worse, ignore it altogether.
  1641. !
  1642. !  The idea is that "red panic button" is better than "red button" or "panic".
  1643. !
  1644. !  number_matched (the number of words matched) is set to the current level
  1645. !  of quality.
  1646. ! ----------------------------------------------------------------------------
  1647.  
  1648. [ MakeMatch obj quality;
  1649.   if quality < match_length { rtrue; }
  1650.   if quality > match_length { match_length=quality; number_matched=0; }
  1651.   put match_list byte number_matched obj;
  1652.   inc number_matched;
  1653. ];
  1654.  
  1655. ! ----------------------------------------------------------------------------
  1656. !  Refers works out whether the word with dictionary address wd can refer to
  1657. !  the object obj, by seeing if wd is listed in the "names" property of obj.
  1658. ! ----------------------------------------------------------------------------
  1659.  
  1660. [ Refers obj wd   k l m;
  1661.     k=prop_addr(obj,1); l=(prop_len(k)/2)-1;
  1662.     for m 0 to l
  1663.     {   if wd==k-->m { rtrue; }
  1664.     }
  1665.     rfalse;
  1666. ];
  1667.  
  1668. ! ----------------------------------------------------------------------------
  1669. !  Noun (which takes no arguments) returns:
  1670. !
  1671. !   1  if the next word is "it" or "them",
  1672. !   2  if the next word is "him",
  1673. !   3  if the next word is "her",
  1674. !   0  if the next word is unrecognised or does not carry the "noun" bit in
  1675. !      its dictionary entry,
  1676. !   or the address in the dictionary if it is a recognised noun.
  1677. !
  1678. !  The "current word" marker moves on one.
  1679. ! ----------------------------------------------------------------------------
  1680.  
  1681. [ Noun i j;
  1682.   i=NextWord();
  1683.   if i==it_word or them_word { j=1; return j; }
  1684.   if i==him_word { j=2; return j; }
  1685.   if i==her_word { j=3; return j; }
  1686.   if i==0 { rfalse; }
  1687.   j=i-->4;
  1688.   if j&128 == 0 { rfalse; }
  1689.  
  1690.   return i;
  1691. ];
  1692.  
  1693. ! ----------------------------------------------------------------------------
  1694. !  Adjective (which takes no arguments) returns:
  1695. !
  1696. !   0  if the next word is listed in the dictionary as possibly an adjective,
  1697. !   or its adjective number if it is.
  1698. !
  1699. !  The "current word" marker moves on one.
  1700. ! ----------------------------------------------------------------------------
  1701.  
  1702. [ Adjective i j;
  1703.   j=NextWord();
  1704.   if j==0 { rfalse; }
  1705.   i=j->4;
  1706.   if i&8 == 0 { rfalse; }
  1707.   return(j->6);
  1708. ];
  1709.  
  1710. ! ----------------------------------------------------------------------------
  1711. !  AdjectiveAddress works out the address in the dictionary of the word
  1712. !  corresponding to the given adjective number.
  1713. !
  1714. !  It should never produce the given error (which would mean that Inform
  1715. !  had set up the adjectives table incorrectly).
  1716. ! ----------------------------------------------------------------------------
  1717.  
  1718. [ AdjectiveAddress number m;
  1719.   m=#adjectives_table;
  1720.   while 1==1
  1721.   {   if number==m-->1 { m=m-->0; return m; }
  1722.       m=m+4;
  1723.   }
  1724.   m=#adjectives_table;
  1725.   print "<Adjective not found>";
  1726.   return m;
  1727. ];
  1728.  
  1729. ! ----------------------------------------------------------------------------
  1730. !  NextWord (which takes no arguments) returns:
  1731. !
  1732. !  0            if the next word is unrecognised,
  1733. !  comma_word   if it is a comma character
  1734. !               (which is treated oddly by the Z-machine, hence the code)
  1735. !  or the dictionary address if it is recognised.
  1736. !
  1737. !  The "current word" marker is moved on.
  1738. ! ----------------------------------------------------------------------------
  1739.  
  1740. [ NextWord i j k;
  1741.   if wn > (parse->1) { inc wn; rfalse; }
  1742.   i=wn*2-1; inc wn;
  1743.   j=parse-->i;
  1744.   if j==0
  1745.   {   k=wn*4-3; i=buffer->(parse->k);
  1746.       if i==',' { j=comma_word; }
  1747.   }
  1748.   return j;
  1749. ];
  1750.  
  1751. ! ----------------------------------------------------------------------------
  1752. !  TryNumber is the only routine which really does any character-level
  1753. !  parsing, since that's normally left to the Z-machine.
  1754. !  It takes word number "wordnum" and tries to parse it as an (unsigned)
  1755. !  decimal number, returning
  1756. !
  1757. !  0                    if it is 0 or not a number
  1758. !  the number           if it has between 1 and 4 digits
  1759. !  10000                if it has 5 or more digits.
  1760. !
  1761. !  (The danger of allowing 5 digits is that Z-machine integers are only
  1762. !  16 bits long, and anyway this isn't meant to be perfect.)
  1763. ! ----------------------------------------------------------------------------
  1764.  
  1765. [ TryNumber wordnum   i j c num len mul tot d digit;
  1766.   i=wordnum*4+1; j=parse->i; num=j+buffer;
  1767.   len=parse->(i-1);
  1768.   if len>=4 { mul=1000; }
  1769.   if len==3 { mul=100; }
  1770.   if len==2 { mul=10; }
  1771.   if len==1 { mul=1; }
  1772.   tot=0; c=0; len=len-1;
  1773.   for c 0 to len
  1774.   {   digit=num->c;
  1775.       if digit=='0' { d=0; jump digok; }
  1776.       if digit=='1' { d=1; jump digok; }
  1777.       if digit=='2' { d=2; jump digok; }
  1778.       if digit=='3' { d=3; jump digok; }
  1779.       if digit=='4' { d=4; jump digok; }
  1780.       if digit=='5' { d=5; jump digok; }
  1781.       if digit=='6' { d=6; jump digok; }
  1782.       if digit=='7' { d=7; jump digok; }
  1783.       if digit=='8' { d=8; jump digok; }
  1784.       if digit=='9' { d=9; jump digok; }
  1785.       rfalse;
  1786.     .digok;
  1787.       tot=tot+mul*d;
  1788.       mul=mul/10;
  1789.   }
  1790.   if len>3 { tot=10000; }
  1791.   return tot;
  1792. ];
  1793.  
  1794. ! ----------------------------------------------------------------------------
  1795. !  ResetVagueWords does, assuming that i was the object last referred to
  1796. ! ----------------------------------------------------------------------------
  1797.  
  1798. [ ResetVagueWords i;
  1799.   if i has animate
  1800.   {   if GetGender(i)==1 { himobj=i; }
  1801.       else { herobj=i; }
  1802.   }
  1803.   else { itobj=i; }
  1804. ];
  1805.  
  1806. ! ----------------------------------------------------------------------------
  1807. !  GetGender returns 0 if the given animate object is female, and 1 if male
  1808. !  (not all games will want such a simple decision function!)
  1809. ! ----------------------------------------------------------------------------
  1810.  
  1811. [ GetGender person i;
  1812.   if person hasnt female { i=1; }
  1813.   return i;
  1814. ];
  1815.  
  1816. ! ----------------------------------------------------------------------------
  1817. !  For copying buffers
  1818. ! ----------------------------------------------------------------------------
  1819.  
  1820. [ Copy bto bfrom i size;
  1821.   size=bto->0;
  1822.   for i 1 to size { put bto byte i bfrom->i; }
  1823. ];
  1824.  
  1825. ! ----------------------------------------------------------------------------
  1826. !  End of the parser proper: the remaining routines are its front end.
  1827. ! ----------------------------------------------------------------------------
  1828.  
  1829. [ DisplayStatus;
  1830.       if the_time==$ffff
  1831.       {   sline1=score; sline2=turns; }
  1832.       else
  1833.       {   sline1=the_time/60; sline2=the_time%60; }
  1834. ];
  1835.  
  1836. [ SetTime t s;
  1837.   the_time=t; time_rate=s; time_step=0;
  1838.   if s<0 { time_step=0-s; }
  1839. ];
  1840.  
  1841. [ PlayTheGame i j k l aflag;
  1842.  
  1843.   Initialise();
  1844.  
  1845.   move player to location;
  1846.  
  1847.   Banner();
  1848.  
  1849.   LookSub();
  1850.  
  1851.   for i 1 to 100 { j=random(i); }
  1852.  
  1853.   while deadflag==0
  1854.   {   .Error;
  1855.       inp1=0; inp2=0; action=0;
  1856.       Parser(inputobjs);
  1857.       onotheld_mode=notheld_mode; notheld_mode=0;
  1858.  
  1859.       if actor~=player
  1860.       {   action=inputobjs->0;
  1861.           if Life(actor,0)==0
  1862.           {   CDefArt(actor); print " has better things to do.^"; }
  1863.           jump timeslice;
  1864.       }
  1865.  
  1866.       if toomany_flag==1
  1867.       {   toomany_flag=0;
  1868.           print "(taking the first sixteen objects only)^";
  1869.       }
  1870.       aflag=0;
  1871.       if action~=0 { aflag=1; }
  1872.       if action==0 { action=inputobjs->0; }
  1873.  
  1874.       if aflag==0
  1875.       {   i=inputobjs->1;
  1876.           inp1=inputobjs->2;
  1877.           inp2=inputobjs->3;
  1878.       }
  1879.       if aflag~=0
  1880.       {   i=2;
  1881.       }
  1882.       multiflag=0;
  1883.       if i==0 { Process(0,0,action); }
  1884.       if i>0
  1885.       {   if inp1~=0 { Process(inp1,inp2,action); }
  1886.           if inp1==0
  1887.           {   multiflag=1;
  1888.               j=multiple_object->0;
  1889.               if j==0 { print "Nothing to do!^"; jump Error; }
  1890.               for k 1 to j
  1891.               {   l=multiple_object->k; print_obj l; print ": ";
  1892.                   Process(l,inp2,action);
  1893.               }
  1894.           }
  1895.       }
  1896.       .timeslice;
  1897.       if notheld_mode==1 { meta=1; }
  1898.       if deadflag==0 { if meta==0 { Time(); } }
  1899.   }
  1900.   print "^^    ***";
  1901.   if deadflag==1 { print " You have died "; }
  1902.   if deadflag==2 { print " You have won "; }
  1903.   if deadflag>2 { print " "; DeathMessage(); print " "; }
  1904.   print "***^^^";
  1905.   ScoreSub();
  1906.  
  1907.   .RRQPL;
  1908.   print "^Would you like to RESTART, RESTORE a saved game, give the FULL score for that game";
  1909.   if deadflag==2 { print ", see some suggestions for AMUSING things to do"; }
  1910.   print " or QUIT?^";
  1911.   .RRQL;
  1912.   print_char '?'; print_char ' ';
  1913.   read buffer parse;
  1914.   i=parse-->1;
  1915.   if i==#w$quit   { quit; }
  1916.   if i==#w$q      { quit; }
  1917.   if i==#w$restar { restart; }
  1918.   if i==#w$restor { RestoreSub(); jump RRQPL; }
  1919.   if i==#w$fullsc { FullScoreSub(); jump RRQPL; }
  1920.   if deadflag==2 { if i==#n$amusin { new_line; Amusing(); jump RRQPL; } }
  1921.   if i==#w$full { new_line; FullScoreSub(); jump RRQPL; }
  1922.   print "Please answer RESTART, RESTORE, FULL";
  1923.   if deadflag==2 { print ", AMUSING"; }
  1924.   print " or QUIT.^";
  1925.   jump RRQL;
  1926. ];
  1927.  
  1928. [ Process i j acti;
  1929.  
  1930.   inp1 = i; inp2 = j; action=acti;
  1931.   if meta==1 { jump metap; }
  1932.   if GamePreRoutine()~=0 { rtrue; }
  1933.   if location~=0
  1934.   {   i=prop(location,preroutine);
  1935.       if i~=$ffff
  1936.       {   if indirect(i)~=0 { rtrue; }
  1937.       }
  1938.   }
  1939.   if inp1>1
  1940.   {   i=prop(inp1,preroutine);
  1941.       if i~=$ffff
  1942.       {   if indirect(i)~=0 { rtrue; }
  1943.       }
  1944.   }
  1945.   .metap;
  1946.   j=#actions_table-->action;
  1947.   indirect(j);
  1948. ];
  1949.  
  1950. [ Life a j i;
  1951.   reason_code = j;
  1952.   i=prop(a,liferoutine);
  1953.   if i~=$ffff
  1954.   {   if indirect(i)~=0 { rtrue; }
  1955.   }
  1956.   rfalse;
  1957. ];
  1958.  
  1959. [ PostAct i k;
  1960.  
  1961.   k=location; i=prop(k,postroutine);
  1962.   if i==$ffff { jump NoLocPost; }
  1963.   if indirect(i)~=0 { rtrue; }
  1964.  
  1965.   .NoLocPost;
  1966.   k=0; i=inputobjs->1;
  1967.   if i>0 { k=inp1; }
  1968.   if k<2 { rfalse; }
  1969.   i=prop(k,postroutine);
  1970.   if i==$ffff { rfalse; }
  1971.   if indirect(i)~=0 { rtrue; }
  1972.   return GamePostRoutine();
  1973. ];
  1974.  
  1975. [ LPostAct i k;
  1976.   k=location;
  1977.   if k==0 { rfalse; }
  1978.   i=prop(k,postroutine);
  1979.   if i==$ffff { rfalse; }
  1980.   if indirect(i)~=0 { rtrue; }
  1981.   return GamePostRoutine();
  1982. ];
  1983.  
  1984. [ Banner i;
  1985.   print_paddr #Story;
  1986.   print_paddr #Headline;
  1987.   print "Release ";
  1988.   print_num (0-->1) & $03ff;
  1989.   print " / Serial number ";
  1990.   for i 18 to 23
  1991.   {   print_char 0->i;
  1992.   }
  1993.   print "  (Compiled by Inform v"; inversion; print ")^";
  1994. ];
  1995.  
  1996. [ Time i;
  1997.  
  1998.   inc turns;
  1999.   if the_time~=$ffff
  2000.   {   if time_rate>=0 { the_time=the_time+time_rate; }
  2001.       else { dec time_step;
  2002.              if time_step==0
  2003.              { inc the_time; time_step = 0-time_rate; }
  2004.            }
  2005.   }
  2006.  
  2007.   TimePasses();
  2008.   
  2009.   i=lightflag;
  2010.   lightflag=OffersLight(parent(player));
  2011.  
  2012.   if i==0
  2013.   {   if lightflag==1 { new_line; location=parent(player); LookSub(1); }
  2014.   }
  2015.   if i==1
  2016.   {   if lightflag==0
  2017.       { new_line; print "It is now pitch dark in here!^"; location=thedark; }
  2018.   }
  2019. ];
  2020.  
  2021. [ TimeUp i j;
  2022.   j=prop(i,timeleft);
  2023.   if j>turns { rfalse; }
  2024.   if j==0 { rfalse; }
  2025.   put_prop i timeleft 0;
  2026.   rtrue;
  2027. ];
  2028.  
  2029. [ DecTimeUp i j;
  2030.   j=prop(i,timeleft);
  2031.   if j==0 { rfalse; }
  2032.   dec j; put_prop i timeleft j;
  2033.   if j>0 { rfalse; }
  2034.   rtrue;
  2035. ];
  2036.  
  2037. [ SetTimer i j;
  2038.   j=turns+j;
  2039.   put_prop i timeleft j;
  2040. ];
  2041.  
  2042. [ Clock i j;
  2043.   j=prop(i,timeleft)-turns;
  2044.   return j;
  2045. ];
  2046.  
  2047. [ OffersLight i;
  2048.   if i has light { rtrue; }
  2049.   if i has container
  2050.   {   if i has interior
  2051.       {   if i hasnt open { rfalse; }
  2052.       }
  2053.   }
  2054.   i=child(i);
  2055.   while i~=0
  2056.   {   if 1==OffersLight(i) { rtrue; }
  2057.       i=sibling(i);
  2058.   }
  2059.   rfalse;
  2060. ];
  2061.  
  2062. [ Indefart o;
  2063.   if o hasnt proper { print_paddr prop(o,article); print " "; }
  2064.   print_obj(o);
  2065. ];
  2066.  
  2067. [ Defart o;
  2068.   if o hasnt proper { print "the "; }
  2069.   print_obj(o);
  2070. ];
  2071.  
  2072. [ CDefart o;
  2073.   if o hasnt proper { print "The "; }
  2074.   print_obj(o);
  2075. ];
  2076.