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 >
Wrap
Text File
|
1993-12-08
|
69KB
|
2,076 lines
! ----------------------------------------------------------------------------
! Include file "PARSER": for standard definitions, and a Z-code parser
!
! (c) Graham Nelson, 1993, but freely usable
! ----------------------------------------------------------------------------
! Bug in ambiguity-resolution to do with compass objects fixed - 3/12/93
! ----------------------------------------------------------------------------
! ----------------------------------------------------------------------------
! Declare the attributes and properties. Note that properties "preroutine"
! and "postroutine" default to $ffff which forces them to be two bytes long:
! similarly, "timeleft" sometimes needs to be over 256, so it's flagged long
! ----------------------------------------------------------------------------
Attribute light;
Attribute concealed;
Attribute worn;
Attribute clothing;
Attribute animate;
Attribute female;
Attribute proper;
Attribute moved;
Attribute portal;
Attribute container;
Attribute supporter;
Attribute interior;
Attribute open;
Attribute openable;
Attribute workflag;
Attribute enterable;
Attribute scenery;
Attribute static;
Attribute direction;
Attribute visited;
Attribute lockable;
Attribute locked;
Attribute switchable;
Attribute on;
Attribute general;
Attribute edible;
Attribute autosearch;
Attribute scored;
Attribute talkable;
Property longdesc 0;
Property article "a";
Property initpos 0;
Property preroutine $ffff;
Property postroutine $ffff;
Property liferoutine $ffff;
Property dirprop;
Property n_to; Property s_to;
Property e_to; Property w_to;
Property ne_to; Property se_to;
Property nw_to; Property sw_to;
Property u_to; Property d_to;
Property portalto;
Property closedpos;
Property long timeleft;
Property with_key;
Property cantgo "You can't go that way.";
Property capacity 100;
! ----------------------------------------------------------------------------
! Construct the compass - a dummy object containing the directions, which also
! represent the walls in whatever room the player is in
! ----------------------------------------------------------------------------
Object compass "compass" nothing has concealed;
Object n_obj "north wall" compass
with name "n" "north" "wall", article "the", dirprop n_to
has direction scenery;
Object s_obj "south wall" compass
with name "s" "south" "wall", article "the", dirprop s_to
has direction scenery;
Object e_obj "east wall" compass
with name "e" "east" "wall", article "the", dirprop e_to
has direction scenery;
Object w_obj "west wall" compass
with name "w" "west" "wall", article "the", dirprop w_to
has direction scenery;
Object ne_obj "northeast wall" compass
with name "ne" "northe" "wall", article "the", dirprop ne_to
has direction scenery;
Object nw_obj "northwest wall" compass
with name "nw" "northw" "wall", article "the", dirprop nw_to
has direction scenery;
Object se_obj "southeast wall" compass
with name "se" "southe" "wall", article "the", dirprop se_to
has direction scenery;
Object sw_obj "southwest wall" compass
with name "sw" "southw" "wall", article "the", dirprop sw_to
has direction scenery;
Object u_obj "ceiling" compass
with name "u" "up" "ceiling", article "the", dirprop u_to
has direction scenery;
Object d_obj "floor" compass
with name "d" "down" "floor", article "the", dirprop d_to
has direction scenery;
! ----------------------------------------------------------------------------
! The other dummy object is "Darkness", not really a place but it has to be
! an object so that the name on the status line can be "Darkness":
! we also create the player object
! ----------------------------------------------------------------------------
Object thedark "Darkness" nothing
with longdesc "It is pitch dark, and you can't see a thing.";
Object selfobj "yourself" thedark
with name "me" "myself" "self", article "the"
has concealed animate proper;
! ----------------------------------------------------------------------------
! Globals: note that the first one defined gives the status line place, the
! next two the score/turns
! ----------------------------------------------------------------------------
Global location = 1;
Global sline1 = 0;
Global sline2 = 0;
Global the_time = $ffff;
Global time_rate = 1;
Global time_step = 0;
Global score = 0;
Global turns = 1;
Global player;
Global lightflag = 1;
Global deadflag = 0;
Global transcript_mode = 0;
Global places_score = 0;
Global things_score = 0;
Global lookmode = 1;
Global lastdesc = 0;
! ----------------------------------------------------------------------------
! Parser variables accessible to the rest of the game
! ----------------------------------------------------------------------------
Global buffer string 120; ! Text buffer
Global parse string 64; ! List of parsed addresses of words
Global inputobjs data 16; ! To hold parameters
Global toomany_flag = 0; ! Flag for "take all made too many"
Global actor = 0; ! Person asked to do something
Global action = 0; ! Thing he is asked to do
Global inp1 = 0; ! First parameter
Global inp2 = 0; ! Second parameter
Global multiple_object data 64; ! List of multiple parameters
Global special_word = 0; ! Dictionary address of "special"
Global special_number = 0; ! The number, if a number was typed
global multiflag; ! Multiple-object flag
global notheld_mode = 0; ! To do with implicit taking
global onotheld_mode = 0; !
global meta; ! Verb is a meta-command (such as "save")
global reason_code; ! Reason for calling a liferoutine
! ----------------------------------------------------------------------------
! Main (putting it here ensures it is the first routine, as it must be)
! ----------------------------------------------------------------------------
[ Main;
player=selfobj;
PlayTheGame();
];
! ----------------------------------------------------------------------------
! The parser, beginning with variables private to itself:
! ----------------------------------------------------------------------------
global buffer2 string 120; ! Buffers for supplementary questions
global parse2 string 64; !
global parse3 string 64; !
global wn; ! Word number
global num_words; ! Number of words typed
global verb_word; ! Verb word (eg, take in "take all" or
! "dwarf, take all") - address in dictionary
global verb_wordnum; ! - and the number in typing order (eg, 1 or 3)
global multi_mode; ! Multiple mode
global multi_filter; ! Multiple mode filter
global all_mode; ! All mode
global pattern data 16; ! For the current pattern match
global pcount; ! and a marker within it
global pattern2 data 16; ! And another, which stores the best match
global pcount2; ! so far
global parameters; ! Parameters (objects) entered so far
global inferfrom; ! The point from which the rest of the
! command must be inferred
global inferword; ! And the preposition inferred
global oops_from = 0; ! The "first mistake" point, where oops acts
global saved_oops = 0; ! Used in working this out
global oops_heap data 10; ! Used temporarily by "oops" routine
global match_list data 64; ! An array of matched objects so far
global number_matched; ! How many items in it? (0 means none)
global match_length; ! How many typed words long are these matches?
global match_from; ! At what word of the input do they begin?
global vague_word; ! Records which vague word ("it", "them", ...)
! caused an error
global vague_obj; ! And what it was thought to refer to
global itobj=0; ! The object which is currently "it"
global himobj=0; ! The object which is currently "him"
global herobj=0; ! The object which is currently "her"
global lookahead; ! The token after the object now being matched
global indef_mode; ! "Indefinite" mode - ie, "take a brick" is in
! this mode
global not_holding; ! Object to be automatically taken as an
! implicit command
global kept_results data 16; ! The delayed command (while the take happens)
global saved_wn; ! These are temporary variables for Parser()
global saved_token; ! (which hasn't enough spare local variables)
global plural_word1 = 1; ! For plural nouns
global plural_word2 = 1; ! (a dictionary address can never be 1,
global plural_word3 = 1; ! so this is a safe "undefined" value)
global plural_filter1; ! And the attributes that define them
global plural_filter2; !
global plural_filter3; !
global etype; ! Error number used within parser
! ----------------------------------------------------------------------------
! Put useful words into the dictionary...
! ----------------------------------------------------------------------------
Dictionary again_word "again";
Dictionary g_word "g";
Dictionary it_word "it";
Dictionary them_word "them";
Dictionary him_word "him";
Dictionary her_word "her";
Dictionary and_word "and";
Dictionary comma_word "xcomma";
Dictionary but_word "but";
Dictionary except_word "except";
Dictionary all_word "all";
Dictionary both_word "both";
Dictionary everyt_word "everyt";
Dictionary of_word "of";
Dictionary the_word "the";
Dictionary a_word "a";
Dictionary an_word "an";
Dictionary any_word "any";
Dictionary either_word "either";
Dictionary o_word "o";
Dictionary oops_word "oops";
! ----------------------------------------------------------------------------
! The Keyboard routine actually receives the player's words,
! putting the words in "a_buffer" and their dictionary addresses in
! "a_table". It is assumed that the table is the same one on each
! (standard) call.
!
! It can also be used by miscellaneous routines in the game to ask
! yes-no questions and the like, without invoking the rest of the parser.
!
! Return the number of words typed
! ----------------------------------------------------------------------------
[ Keyboard a_buffer a_table nw i w x1 x2;
DisplayStatus();
.FreshInput;
! Save the start of the table, in case "oops" needs to restore it
! to the previous time's table
for i 0 to 9 { put oops_heap byte i a_table->i; }
! Print the prompt, and read in the words and dictionary addresses
new_line; print_char '>'; read a_buffer a_table;
nw=a_table->1;
! If the line was blank, get a fresh line
if nw == 0
{ print "I beg your pardon?^"; jump FreshInput; }
! Unless the opening word was "oops" or its abbreviation "o", return
w=a_table-->1;
if w == o_word or oops_word { jump DoOops; }
return nw;
.DoOops;
if oops_from == 0
{ print "Sorry, that can't be corrected.^"; jump FreshInput; }
if nw == 1
{ print "Think nothing of it.^"; jump FreshInput; }
if nw > 2
{ print "~Oops~ can only correct a single word.^"; jump FreshInput; }
! So now we know: there was a previous mistake, and the player has
! attempted to correct a single word of it.
!
! Oops is very primitive: it gets the text buffer wrong, for instance.
!
! Take out the 4-byte table entry for the supplied correction:
! restore the 10 bytes at the front of the table, which were over-written
! by what the user just typed: and then replace the oops_from word entry
! with the correction one.
!
x1=a_table-->3; x2=a_table-->4;
for i 0 to 9 { put a_table byte i oops_heap->i; }
w=2*oops_from - 1;
put a_table word w x1;
inc w;
put a_table word w x1;
return nw;
];
! ----------------------------------------------------------------------------
! The Parser routine is the heart of the parser.
!
! It returns only when a sensible request has been made, and puts into the
! "results" buffer:
!
! Byte 0 = The action number
! Byte 1 = Number of parameters
! Bytes 2, 3, ... = The parameters (object numbers), but
! 00 means "multiple object list goes here"
! 01 means "special word goes here"
!
! (Some of the global variables above are really local variables for this
! routine, because the Z-machine only allows up to 15 local variables per
! routine, and Parser runs out.)
!
! To simplify the picture a little, a rough map of this routine is:
!
! (A) Get the input, do "oops" and "again"
! (B) Is it a direction, and so an implicit "go"? If so return
! (C) Is anyone being addressed?
! (D) Get the verb: try all the syntax lines for that verb
! (E) Go through each token in the syntax line
! (F) Check (or infer) an adjective
! (G) Check to see if the syntax is finished, and if so return
! (H) Cheaply parse otherwise unrecognised conversation and return
! (I) Print best possible error message
! (J) Retry the whole lot
!
! The strategic points (A) to (J) are marked in the commentary.
!
! Note that there are three different places where a return can happen.
!
! ----------------------------------------------------------------------------
[ Parser results syntax line num_lines line_address i j
token l m;
! **** (A) ****
! Firstly, in "not held" mode, we still have a command left over from last
! time (eg, the user typed "eat biscuit", which was parsed as "take biscuit"
! last time, with "eat biscuit" tucked away until now). So we return that.
if notheld_mode==1
{ for i 0 to 7 { put results byte i kept_results->i; }
notheld_mode=0; rtrue;
}
.ReType;
Keyboard(buffer,parse);
.ReParse;
! "etype" is the current failure-to-match error - it is by default
! the least informative one, "don't understand that sentence"
etype=1;
! Initially assume the command is aimed at the player, and the verb
! is the first word
num_words=parse->1;
verb_wordnum=1;
actor=player;
! Begin from what we currently think is the verb word
.BeginCommand;
wn=verb_wordnum;
verb_word = NextWord();
! Now try for "again" or "g", which are special cases:
! don't allow "again" if nothing has previously been typed;
! simply copy the previous parse table and ReParse with that
if verb_word==g_word { verb_word=again_word; }
if verb_word==again_word
{ j=parse3->1;
if j==0 { print "You can hardly repeat that.^"; jump ReType; }
for i 0 to 63 { j=parse3->i; put parse byte i j; }
jump ReParse;
}
! Save the present parse table in case of an "again" next time
if verb_word~=again_word
{ for i 0 to 63 { j=parse->i; put parse3 byte i j; }
}
! If the first word is not recognised, it can't be either the name of
! an animate creature or a verb, so give an error at once.
if verb_word==0
{ etype=11; jump GiveError;
}
! **** (B) ****
! If the first word is not listed as a verb, it must be a direction
! or the name of someone to talk to
! (NB: better avoid having a Mr Take or Mrs Inventory around...)
i=(verb_word->4) & 1;
if i==0
{
! So is the first word an object contained in the special object "compass"
! (i.e., a direction)? This needs use of NounDomain, a routine which
! does the object matching, returning the object number, or 0 if none found,
! or 1000 if it has restructured the parse table so that the whole parse
! must be begun again...
wn=verb_wordnum;
l=NounDomain(compass,0,0); if l==1000 { jump ReParse; }
! If it is a direction, send back the results:
! action=GoSub, no of arguments=1, argument 1=the direction.
if l~=0
{ put results byte 0 #a$GoSub;
put results byte 1 1;
put results byte 2 l;
rtrue;
}
! **** (C) ****
! Only check for a comma (a "someone, do something" command) if we are
! not already in the middle of one. (This simplification stops us from
! worrying about "robot, wizard, you are an idiot", telling the robot to
! tell the wizard that she is an idiot.)
if actor==player
{ for j 2 to num_words
{ i=NextWord(); if i==comma_word { jump Conversation; }
}
}
etype=11; jump GiveError;
! NextWord nudges the word number wn on by one each time, so we've now
! advanced past a comma. (A comma is a word all on its own in the table.)
.Conversation;
j=wn-1;
if j==1 { print "You can't begin with a comma.^"; jump ReType; }
! Use NounDomain (in the context of "animate creature") to see if the
! words make sense as the name of someone held or nearby
wn=1; lookahead=1;
l=NounDomain(player,location,6); if l==1000 { jump ReParse; }
if l==0 { print "You seem to want to talk to someone, \
but I can't see whom.^"; jump ReType; }
! The object addressed must at least be "talkable" if not actually "animate"
! (the distinction allows, for instance, a microphone to be spoken to,
! without the parser thinking that the microphone is human).
if l hasnt animate { if l hasnt talkable {
print "You can't talk to "; DefArt(l); print ".^"; jump ReType;
} }
! Check that there aren't any mystery words between the end of the person's
! name and the comma (eg, throw out "dwarf sdfgsdgs, go north").
if wn~=j
{ print "To talk to someone, try ~someone, hello~ or some such.^";
jump ReType;
}
! The player has now successfully named someone. Adjust "him", "her", "it":
ResetVagueWords(l);
! Set the global variable "actor", adjust the number of the first word,
! and begin parsing again from there.
verb_wordnum=j+1; actor=l;
jump BeginCommand;
}
! **** (D) ****
! We now definitely have a verb, not a direction, whether we got here by the
! "take ..." or "person, take ..." method. Get the meta flag for this verb:
meta=(verb_word->4) & 2;
! Now let i be the corresponding verb number, stored in the dictionary entry
! (in a peculiar 255-n fashion for traditional Infocom reasons)...
i=$ff-(verb_word->5);
! ...then look up the i-th entry in the verb table, whose address is at word
! 7 in the Z-machine (in the header), so as to get the address of the syntax
! table for the given verb...
syntax=(0-->7)-->i;
! ...and then see how many lines (ie, different patterns corresponding to the
! same verb) are stored in the parse table...
num_lines=(syntax->0)-1;
! ...and now go through them all, one by one.
! To prevent vague_word 0 being misunderstood,
vague_word=it_word; vague_obj=itobj;
! **** (E) ****
for line 0 to num_lines;
{ line_address = syntax+1+line*8;
! We aren't in "not holding", "all" or inferring modes, and haven't entered
! any parameters on the line yet...
not_holding=0;
inferfrom=0;
parameters=0; all_mode=0;
! Put the word marker back to just after the verb
wn=verb_wordnum+1;
! An individual "line" contains six tokens... "Pattern" gradually accumulates
! what has been recognised so far, so that it may be reprinted by the parser
! later on
for pcount 1 to 6;
{ put pattern word pcount 0;
token=line_address->pcount;
! Lookahead is set to the token after this one, or 8 if there isn't one.
! (Complicated because the line is padded with 0's.)
m=pcount+1; lookahead=8;
if m<=6 { lookahead=line_address->m; }
if lookahead==0
{ m=parameters; if token<=7 { inc m; }
if m>=line_address->0 { lookahead=8; }
}
! **** (F) ****
! A token is either an "adjective" (Infocom-speak for preposition) number,
! which count downwards from 255 and are therefore large numbers, or else
! 0 through to 7 for kinds of object
! Anyway, first the case when it is an adjective: remember the adjective
! number in the "pattern".
if token>7
{ put pattern word pcount 1000+token;
! If we've run out of the player's input, but still have parameters to
! specify, we go into "infer" mode, remembering where we are and the
! adjective we are inferring...
if wn > num_words
{ if inferfrom==0
{ if parameters< line_address->0
{ inferfrom=pcount; inferword=token; }
}
! Otherwise, this line must be wrong.
if inferfrom==0 { break; }
}
! Whereas, if the player has typed something here, see if it is the
! required adjective... if it's wrong, the line must be wrong,
! but if it's right, the token is passed (jump to Back to finish this
! round of the loop).
if wn <= num_words
{ if token~=Adjective() { break; } }
jump Back;
}
! **** (G) ****
! Check now to see if the player has entered enough parameters...
! (since line_address->0 is the number of them)
if parameters == line_address->0
{
! If the player has entered enough parameters already but there's still
! text to wade through: store the pattern away so as to be able to produce
! a decent error message if this turns out to be the best we ever manage,
! and in the mean time give up on this line
if wn <= num_words
{ for m 0 to 7 { put pattern2 word m pattern-->m; }
pcount2=pcount;
etype=2; break;
}
! Now, if we've only processed the "all" list once, we need to make a second
! pass (see below for the complicated explanation as to why).
if all_mode==1
{ wn=saved_wn; token=saved_token;
l=ParseObjectList(results,token);
if l==1000 { jump ReParse; }
if l==0 { break; }
}
! At this point the line has worked out perfectly, and it's a matter of
! sending the results back...
! ...pausing to explain any inferences made (using the pattern)...
if inferfrom~=0
{ print "("; PrintCommand(inferfrom,1); print ")^";
}
! ...and to copy the action number, and the number of parameters...
put results byte 1 line_address->0;
put results byte 0 line_address->7;
! ...and to reset "it"-style objects to the first of these parameters, if
! there is one (and it really is an object)...
if parameters > 0
{ i=results->2;
if i>=2
{ ResetVagueWords(i);
}
}
! ...and declare the user's input to be error free...
oops_from = 0;
! ...and worry about the case where an object was allowed as a parameter
! even though the player wasn't holding it and should have been: in this
! event, keep the results for next time round, go into "not holding" mode,
! and for now tell the player what's happening and return a "take" request
! instead...
if not_holding~=0
{ notheld_mode=1;
for i 0 to 7 { put kept_results byte i results->i; }
put results byte 1 1;
put results byte 0 #a$TakeSub;
put results byte 2 not_holding;
print "(first taking "; DefArt(not_holding); print ")^";
rtrue;
}
! ...and finish.
rtrue;
}
! Otherwise, the player still has at least one parameter to specify: an
! object of some kind is expected, and this we hand over to:
l=ParseObjectList(results,token);
if l==1000 { jump ReParse; }
if l==0 { break; }
! Finally, if we came up with a multiple object, then no parameter got recorded,
! so the value 0 is entered as parameter to signify that "the list goes here".
if multi_mode==1
{ put results byte parameters+2 0;
put pattern word pcount 0;
parameters=parameters+1;
}
! The token has been successfully parsed. We have thus finished and can go on
! to the next token...
.Back;
}
! And if we get here it means that the line failed somewhere, so we continue
! the outer for loop and try the next line...
}
! So that if we get here, each line for the specified verb has failed.
! **** (H) ****
.GiveError;
! Errors are handled differently depending on who was talking.
! If the command was addressed to somebody else (eg, "dwarf, sfgh") then
! it is taken as conversation which the parser has no business in disallowing.
! In order to make it easier for the host game to work out what was said, the
! "verb" word (eg, "sfgh") is parsed as a number and as a dictionary entry, and
! the parser returns as if the player had typed
!
! answer sfgh to dwarf
!
! with the globals special_word and special_number set accordingly.
! (This is convenient for, say, "computer, 2451" or "guard, blue").
if actor~=player
{ special_number=TryNumber(verb_wordnum);
wn=verb_wordnum;
special_word=NextWord();
action=#a$AnswerSub;
inp1=1; inp2=actor; actor=player;
rtrue;
}
! **** (I) ****
! If the player was the actor (eg, in "take dfghh") the error must be printed, and
! fresh input called for. In three cases the oops word must be jiggled.
if etype==1 { print "I didn't understand that sentence.^"; oops_from=1; }
! In this case, we need to reconstruct the command to show how much was
! understood:
if etype==2 { print "I only understood you as far as wanting to ";
for m 0 to 7 { put pattern word m pattern2-->m; }
pcount=pcount2;
PrintCommand(0,1);
print ".^";
}
if etype==3 { print "You can't see any such thing.^"; oops_from=saved_oops; }
if etype==4 { print "You seem to have said too little!^"; }
if etype==5 { print "You aren't holding that!^"; oops_from=saved_oops; }
if etype==6 { print "You can't use multiple objects with that verb.^"; }
if etype==7 { print "You can only use multiple objects once on a line.^"; }
if etype==8 { print "I'm not sure what ~"; print_addr vague_word;
print "~ refers to.^"; }
if etype==9 { print "You excepted something not included anyway!^"; }
if etype==10 { print "You can only do that to something animate.^"; }
if etype==11 { print "That's not a verb I recognise.^"; }
if etype==12 { print "That's not something you need to refer to \
in the course of this game.^"; }
if etype==13 { print "You can't see ~"; print_addr vague_word;
print "~ ("; DefArt(vague_obj); print ") at the moment.^"; }
! **** (J) ****
! And go (almost) right back to square one...
jump ReType;
! ...being careful not to go all the way back, to avoid infinite repetition
! of a deferred command causing an error.
];
! ----------------------------------------------------------------------------
! ParseObjectList
!
! Returns:
! 1000 for "reconstructed input"
! 1 for "token accepted"
! 0 for "token failed"
!
! (A) Go through a list of objects
! (B) try plural nouns, "all", "except"
! (C) try an explicit object
! (D) cope with "and"s
!
! ----------------------------------------------------------------------------
[ ParseObjectList results token l m o and_mode;
! Well, we aren't in "multiple" or "and mode" yet...
multi_mode=0; and_mode=0;
! **** (A) ****
! We expect to find a list of objects next in what the player's typed.
.ObjectList;
! Take a look at the next word: if it's "it" or "them", and these are
! unset, set the appropriate error number and give up on the line
! (if not, these are still parsed in the usual way - it is not assumed
! that they still refer to something in context)
o=NextWord(); dec wn;
if o==it_word or them_word
{ vague_word=o; vague_obj=itobj;
if itobj==0 { etype=8; return 0; }
}
if o==him_word
{ vague_word=o; vague_obj=himobj;
if himobj==0 { etype=8; return 0; }
}
if o==her_word
{ vague_word=o; vague_obj=herobj;
if herobj==0 { etype=8; return 0; }
}
! Skip over (and ignore) the word "the" before an item in the list
if o==the_word { inc wn; jump ObjectList; }
! The next item in the list is to be taken in "definite" mode (rather
! than "indefinite" mode) unless "a" or "an" or ... is hit: in which case
! they are skipped over.
indef_mode=0;
if o==a_word or an_word or any_word { inc wn; indef_mode=1; }
if o==either_word { inc wn; indef_mode=1; }
! Recall that "token" is set to the kind of objects expected
! here. The value 7, or "special", means that a single word is expected,
! which is allowed to be anything at all (whether in the dictionary or not)
! but should also be tried as a decimal number:
! the global variables "special_number" and "special_word" hold what the
! parser thinks this word is.
!
! The parameter is entered into the pattern and results as $ff.
if token==7
{ special_number=TryNumber(wn);
special_word=NextWord();
put results byte parameters+2 1;
parameters=parameters+1;
put pattern word pcount 1;
jump NextInList;
}
! Otherwise, we have one of the tokens 0 to 6, all of which really do mean
! that objects are expected.
! **** (B) ****
! The code to handle plural nouns goes here.
! For instance, in Deja-Vu, "take cubes" sets the filter to cubes-only,
! (so that a "take all" would only take objects with the is_cube attribute
! set) and then replaces "cubes" by "all".
multi_filter=0;
if o==plural_word1 { o=all_word; multi_filter=plural_filter1; }
if o==plural_word2 { o=all_word; multi_filter=plural_filter2; }
if o==plural_word3 { o=all_word; multi_filter=plural_filter3; }
! "everything", "both" and "all" are synonymous here:
if o==everyt_word or both_word or all_word
{
! Only tokens 2, 3, 4 and 5 can accept multiple objects, so the use of "all"
! throws the line out immediately if the token was anything else...
if token<2 { etype=6; return 0; }
if token>=6 { etype=6; return 0; }
! "all" mode is rather complicated. all_mode is expected to count from 0
! to 2, because this code expects to be run _twice_. (Thus if it ever reaches
! 3, the player must have tried using "all" twice in different places, which
! we throw out as beyond our abilities.)
inc all_mode;
if all_mode>2 { etype=7; return 0; }
! The first pass through is now, in the usual way. The second pass happens
! just before the command is finally accepted (see above).
! In order to be able to come back and do it all again, on the first time
! out we have to save away the token number and the word marker;
! and we also call DoAll(location) to work out which of the objects in
! "location" are in context
if all_mode==1
{ saved_wn=wn; saved_token=token;
DoAll(location);
}
! The point of doing it twice is that on the second time through, parameters
! which will not be specified until later can be known. Consequently, e.g.,
!
! ~put everything in the rucksack~
!
! can understand on the second attempt that the rucksack is to be excluded
! from the list of items. On the first attempt it couldn't, because the
! rucksack hadn't been reached yet.
! The token tells the parser how to interpret "all".
! 2 means: all items on the floor. (But being careful to make sure they
! really do come from the floor, not from the "darkness" place,
! if the player is currently in the dark.)
! 3 means: all items carried by the actor.
! 4 means: all items carried, except the other parameter given, if that's
! also carried (this is the "put all in sack" case)
! 5 means: all items inside the other parameter given
! (this is to handle things like "get all from cupboard")
if all_mode==2
{ if token==2
{ if location==thedark { DoAll(location); }
else { DoAll(parent(player)); }
}
if token==3 { DoAll(actor); }
if token==4
{ DoAll(actor); o=parent(results->3);
if o==actor { MultiSub(results->3); }
}
if token==5 { DoAll(results->3); }
! Note that MultiSub(thing) removed thing from the list.
! MultiFilter weeds out everything without the right attribute, if needed:
if multi_filter~=0 { MultiFilter(multi_filter); }
}
! Finally, skip over the actual "all" word, and pass on to any exceptions.
inc wn;
.Exceptions;
o=NextWord();
! Once again, skip "the" and allow for plural nouns... in case of expressions
! like "drop all the cubes", where the filtering for cubes only doesn't happen
! until after the "all" has been processed.
if o==the_word or of_word { jump Exceptions; }
if o==plural_word1 { MultiFilter(plural_filter1); jump Exceptions; }
if o==plural_word2 { MultiFilter(plural_filter2); jump Exceptions; }
if o==plural_word3 { MultiFilter(plural_filter3); jump Exceptions; }
! Now check for "except" and "but", which we treat as synonyms, and if so
! go back round to the object list again but excluding (and_mode=2) rather
! than including (and_mode=1) things.
if o==except_word { o=but_word; }
if o==but_word { and_mode=2; jump ObjectList; }
! No exceptions... so the word marker is too far on, and we move it back;
! and the list must be complete.
wn=wn-1;
jump EndOfList;
}
! **** (C) ****
! Right, so the item in the object list was not a plural object. That means
! it will be an actual specified object, and is therefore where a typing error
! is most likely to occur, so we set:
oops_from=wn;
! Two cases now. What really matters now is whether the token is 1 (meaning
! "single item held by the actor") or not, as this affects whether an
! implicit take ought to happen or not.
! In either case we use NounDomain, giving it the token number as the current
! context, and two places to look: among the actor's possessions, and in the
! present location. (Note that the order we look at these in depends on which
! we expect as more likely.)
! (The reason we still bother to check the floor, even when an object in the
! actor's hands is expected, is to be able to do implicit taking, and to
! produce messages like "Already on the floor." if the player tries to drop
! something already dropped.)
! A devious addition is that it is possible for NounDomain to ask the player
! to clarify a single named object, and get a multiple one back.
! Case One: token not equal to 1
if token~=1
{
l=NounDomain(location, actor, token);
if l==1000 { return l; }
if l==0 { etype=CantSee(); return 0; }
if l==1
{ put results byte parameters+2 0;
parameters=parameters+1;
put pattern word pcount 0;
return 1;
}
! Note that when the line has to be thrown out, it can be tricky working out
!á which error message is most appropriate, and CantSee() does the job for us.
! Suppose the token was 6, which means "animate creature" - dwarf, sea
! monster, etc. Then throw the line away if the resulting object wasn't.
if token==6
{ if l hasnt animate
{ etype=10; return 0; }
}
! We might have tokens 2 to 5 here, so multiple objects are still allowed,
! and we have to cater for additions and subtractions. If this is the
! first object in the list so far (i.e. if we aren't in "and mode" at all)
! then just store it as a parameter in the results, as usual.
if and_mode==0
{ put results byte parameters+2 l;
parameters=parameters+1;
put pattern word pcount l;
}
! But if we're already in "and mode", so that this is a subsequent object
! in a list, add it into the multiple object collected so far...
if and_mode==1
{ MultiAdd(l); }
! ...unless, of course, an "except" has happened, in which case remove it from
! the list so far:
if and_mode==2
{ m=MultiSub(l);
if all_mode~=1 { if m~=0 { etype=m; return 0; } }
}
! (and let MultiSub feed an error in the case when the player has tried
! something like ~drop all except fish~ when not actually holding the fish,
! in other words tried to except something not already there.)
}
! Case Two: token equal to 1
!
! Which is pretty similar...
if token==1
{ l=NounDomain(actor,location,token);
if l==1000 { return l; }
if l==0 { etype=CantSee(); return l; }
! ...until it produces something not held by the actor. Then an implicit
! take must be tried. If this is already happening anyway, things are too
! confused and we have to give up (but saving the oops marker so as to get
! it on the right word afterwards).
!
! The point of this last rule is that a sequence like
!
! > read newspaper
! (taking the newspaper first)
! The dwarf unexpectedly prevents you from taking the newspaper!
!
! should not be allowed to go into an infinite repeat - read becomes
! take then read, but take has no effect, so read becomes take then read...
! Anyway for now all we do is record the number of the object to take.
o=parent(l);
if o~=actor
{ if notheld_mode==1
{ saved_oops=oops_from; etype=5; return 0; }
not_holding=l;
}
put results byte parameters+2 l;
parameters=parameters+1;
put pattern word pcount l;
}
! **** (D) ****
! In either Case One or Two, if we got through all that, we've now come to
! the end of the object specified, and might have reached the end of the list
! or might have come to a comma or the word "and"...
.NextInList;
! Refers is a routine to work out which words refer to the object that the
! parser decided the player meant, so the following skips the word marker
! past the named object...
o=NextWord();
if 0~=Refers(l,o) { jump NextInList; }
! And now we see if there's a comma or an "and" (which are equivalent here).
if o==and_word or comma_word
{
! If so, then once again they can only be used with tokens 2, 3, 4 and 5.
if token<2 { etype=6; return 0; }
if token>=6 { etype=6; return 0; }
! Note that the following innocent line hides a subtlety: it would not be
! right just to write "and_mode=1;" because that would convert the exception
! mode (when and_mode=2) into the inclusion mode (and_mode=1). In other words,
! we are being careful about "drop all but the fish and the bread".
if and_mode==0 { and_mode=1; }
! "Multiple" mode keeps track of whether or not we've begun a multiple object
! yet. If we haven't, then we take the parameter back and begin a multiple
! object list with it.
if multi_mode==0 { put multiple_object byte 0 1;
put multiple_object byte 1 l;
parameters=parameters-1; multi_mode=1; }
! And now go and find out what's next in the list.
jump ObjectList;
}
! If none of that happened, then we finished the object list: we hit a word which
! wasn't an "and" or a comma. As the word counter was moved on by one in
! checking this, bring it back by one:
wn=wn-1;
! The object list is now finished.
.EndOfList;
return 1;
];
! ----------------------------------------------------------------------------
! PrintCommand reconstructs the command as it presently reads, from
! the pattern which has been built up
!
! If from is 0, it starts with the verb: then it goes through the pattern.
! The other parameter is "emptyf" - a flag: if 0, it goes up to pcount:
! if 1, it goes up to pcount-1.
!
! Note that verbs and prepositions are printed out of the dictionary:
! and that since the dictionary only preserves the first six characters
! of a word, we have to hand-code the longer words needed.
!
! (Recall that pattern entries are 0 for "multiple object", 1 for "special
! word", 2 to 255 are object numbers and 1000+n means the preposition n)
! ----------------------------------------------------------------------------
[ PrintCommand from emptyf i j k f;
if from==0
{ i=verb_word; from=1; f=1;
if i==#w$invent { print "take an inventory"; jump VerbPrinted; }
if i==#w$examin { print "examine"; jump VerbPrinted; }
if i==#w$discar { print "discard"; jump VerbPrinted; }
if i==#w$swallo { print "swallow"; jump VerbPrinted; }
if i==#w$embrac { print "embrace"; jump VerbPrinted; }
if i==#w$squeez { print "squeeze"; jump VerbPrinted; }
if i==#w$purcha { print "purchase"; jump VerbPrinted; }
if i==#w$unscre { print "unscrew"; jump VerbPrinted; }
if i==#w$descri { print "describe"; jump VerbPrinted; }
if i==#w$uncove { print "uncover"; jump VerbPrinted; }
if i==#w$discar { print "discard"; jump VerbPrinted; }
if i==#w$transf { print "transfer"; jump VerbPrinted; }
print_addr i;
}
.VerbPrinted;
j=pcount-emptyf;
for k from to j
{ if f==1 { print_char ' '; }
i=pattern-->k;
if i==0 { print "those things"; jump TokenPrinted; }
if i==1 { print "that"; jump TokenPrinted; }
if i<256 { DefArt(i); }
if i>=1000
{ i=AdjectiveAddress(i-1000);
if i==#w$agains { print "against"; jump TokenPrinted; }
print_addr i;
}
.TokenPrinted;
f=1;
}
];
! ----------------------------------------------------------------------------
! The CantSee routine returns a good error number for the situation where
! the last word looked at didn't seem to refer to any object in context.
!
! The idea is that: if the actor is in a location (but not inside something
! like, for instance, a tank which is in that location) then an attempt to
! refer to one of the words listed as meaningful-but-irrelevant there
! will cause an error 12 ("you don't need to refer to that in this game")
! in preference to an error 3 ("no such thing"), or error 13 ("what's 'it'?").
!
! (The advantage of not having looked at "irrelevant" local nouns until now
! is that it stops them from clogging up the ambiguity-resolving process.
! Thus game objects always triumph over scenery.)
! ----------------------------------------------------------------------------
[ CantSee i w e;
saved_oops=oops_from;
wn=wn-1; w=NextWord();
e=3;
if w==vague_word { e=13; }
i=parent(actor);
if i has visited { if 0~=Refers(i,w) { e=12; } }
return e;
];
! ----------------------------------------------------------------------------
! The DoAll routine works through everything in context inside
! the "domain" (but does not descend the object tree), putting what's there
! in the multiple object list.
!
! Here "in context" means "neither concealed nor worn". Some people might
! also like to add "nor scenery".
! ----------------------------------------------------------------------------
[ DoAll domain;
multi_mode=1; put multiple_object byte 0 0;
domain=child(domain);
while domain~=0
{ if domain hasnt concealed { if domain hasnt worn { MultiAdd(domain); } }
domain=sibling(domain);
}
];
! ----------------------------------------------------------------------------
! The MultiAdd routine adds object "o" to the multiple-object-list.
!
! This is only allowed to hold 63 objects at most, at which point it ignores
! any new entries (and sets a global flag so that a warning may later be
! printed if need be).
! ----------------------------------------------------------------------------
[ MultiAdd o i j;
i=multiple_object->0;
if i==63 { toomany_flag=1; rtrue; }
for j 1 to i
{ if o==multiple_object->j { rtrue; }
}
i=i+1;
put multiple_object byte i o;
put multiple_object byte 0 i;
];
! ----------------------------------------------------------------------------
! The MultiSub routine deletes object "o" from the multiple-object-list.
!
! It returns 0 if the object was there in the first place, and 9 (because
! this is the appropriate error number in Parser()) if it wasn't.
! ----------------------------------------------------------------------------
[ MultiSub o i j k et;
i=multiple_object->0; et=0;
for j 1 to i
{ if o==multiple_object->j
{ for k j to i { put multiple_object byte k multiple_object->(k+1); }
i=i-1;
put multiple_object byte 0 i;
return et;
}
}
et=9; return et;
];
! ----------------------------------------------------------------------------
! The MultiFilter routine goes through the multiple-object-list and throws
! out anything without the given attribute "attr" set.
! ----------------------------------------------------------------------------
[ MultiFilter attr i j o;
.MFiltl;
i=multiple_object->0;
for j 1 to i
{ o=multiple_object->j;
if o hasnt attr { MultiSub(o); jump Mfiltl; }
}
];
! ----------------------------------------------------------------------------
! NounDomain does the most substantial part of parsing an object name.
!
! It is given two "domains" - places to look for the objects in question -
! and a context (a token type, such as "on the floor"), and returns:
!
! 0 if no match at all could be made,
! 1 if a multiple object was made,
! k if object k was the one decided upon,
! 1000 if it asked a question of the player and consequently rewrote all
! the player's input, so that the whole parser should start again
! on the rewritten input.
! ----------------------------------------------------------------------------
[ NounDomain domain1 domain2 context first_word i j k oldw answer_words;
match_length=0; number_matched=0; match_from=wn;
! Use NounWithin to put together the match list - the list of "equally
! good" (from a purely word-matching point of view) objects
! (and put the word marker to the first word after the words matched)
NounWithin(domain1); NounWithin(domain2);
wn=match_from+match_length;
! If nothing worked at all, leave with the word marker skipped past the
! first unmatched word...
if number_matched==0 { inc wn; rfalse; }
! Suppose that there really were some words being parsed (i.e., we did
! not just infer). If so, and if there was only one match, it must be
! right and we return it...
if match_from <= num_words
{ if number_matched==1 { i=match_list->0; return i; }
! ...now suppose that there was more typing to come, i.e. suppose that
! the user entered something beyond this noun. Use the lookahead token
! to check that if an adjective comes next, it is the right one. (If
! not then there must be a mistake like "press red buttno" where "red"
! has been taken for the noun in the mistaken belief that "buttno" is
! some preposition or other.)
!
! If nothing ought to follow, then similarly there must be a mistake.
if wn<=num_words
{ if lookahead==8 { rfalse; }
if lookahead>8
{ if lookahead~=Adjective() { dec wn; rfalse; }
dec wn;
}
}
}
! Now look for a good choice, if there's more than one choice...
if number_matched==1 { i=match_list->0; }
if number_matched>1 { i=Adjudicate(context); }
! If i is non-zero here, one of two things is happening: either
! (a) an inference has been successfully made that object i is
! the intended one from the user's specification, or
! (b) the user finished typing some time ago, but we've decided
! on i because it's the only possible choice.
! In either case we have to keep the pattern up to date,
! note that an inference has been made, and return.
if i~=0
{ if inferfrom==0 { inferfrom=pcount; }
put pattern word pcount i;
return i;
}
! If we get here, there was no obvious choice of object to make. If in
! fact we've already gone past the end of the player's typing (which
! means the match list must contain every object in context, regardless
! of its name), then it's foolish to give an enormous list to choose
! from - instead we go and ask a more suitable question...
if match_from > num_words { jump Incomplete; }
! Now we print up the question...
if context==6 { print "Who"; } else { print "Which"; }
print " do you mean, ";
j=number_matched-1;
for i 0 to j
{ k=match_list->i; Defart(k);
if i<j-1 { print ", "; }
if i==j-1 { print " or "; }
}
print "?^";
! ...and get an answer:
.WhichOne;
answer_words=Keyboard(buffer2, parse2);
first_word=(parse2-->1);
! Take care of "all":
if first_word==all_word or both_word or everyt_word
{
if context>=2 { if context<=5
{ for i 0 to j
{ k=match_list->i;
put multiple_object byte i+1 k;
}
put multiple_object byte 0 j+1;
return 1;
} }
print "Sorry, you can only have one item here. Which one exactly?^";
jump WhichOne;
}
! If the first word of the reply can be interpreted as a verb, then
! assume that the player has ignored the question and given a new
! command altogether.
! (This is one time when it's convenient that the directions are
! not themselves verbs - thus, "north" as a reply to "Which, the north
! or south door" is not treated as a fresh command but as an answer.)
j=first_word->4;
if 0~=j&1
{ Copy(buffer, buffer2);
Copy(parse, parse2);
k=1000; return k;
}
! Now we insert the answer into the original typed command, as
! words additionally describing the same object
! (eg, > take red button
! Which one, ...
! > music
! becomes "take music red button". The parser will thus have three
! words to work from next time, not two.)
!
! To do this we use MoveWord which copies in a word.
oldw=parse->1;
put parse byte 1 answer_words+oldw;
k=oldw+answer_words;
while k>match_from
{ i=k-answer_words; MoveWord(k, parse, i);
dec k;
}
for k 1 to answer_words
{ i=match_from+k-1; MoveWord(i, parse2, k);
}
! Having reconstructed the input, we warn the parser accordingly
! and get out.
k=1000; return k;
! Now we come to the question asked when the input has run out
! and can't easily be guessed (eg, the player typed "take" and there
! were plenty of things which might have been meant).
.Incomplete;
if context==6 { print "Whom"; } else { print "What"; }
print " do you want";
if actor~=player { print " "; DefArt(actor); }
print " to "; PrintCommand(0,1); print "?^";
answer_words=Keyboard(buffer2, parse2);
first_word=(parse2-->1);
! Once again, if the reply looks like a command, give it to the
! parser to get on with and forget about the question...
j=first_word->4;
if 0~=j&1
{ Copy(buffer, buffer2);
Copy(parse, parse2);
k=1000; return k;
}
! ...but if we have a genuine answer, then we adjoin the words
! typed onto the expression. But if we've just inferred a
! preposition which wasn't actually there, then we need to
! adjoin that as well. (NB: two consecutive prepositions will
! cause trouble here!)
oldw=parse->1;
if inferfrom==0
{ for k 1 to answer_words
{ i=match_from+k-1; MoveWord(i, parse2, k);
}
}
if inferfrom~=0
{ for k 1 to answer_words
{ i=match_from+k; MoveWord(i, parse2, k);
}
put parse2 word 1 AdjectiveAddress(inferword);
MoveWord(match_from, parse2, 1);
inc answer_words;
}
put parse byte 1 answer_words+oldw;
! And go back to the parser.
k=1000; return k;
];
! ----------------------------------------------------------------------------
! The Adjudicate routine tries to see if there is an obvious choice, when
! faced with a list of objects (the match_list) each of which matches the
! player's specification equally well.
!
! To do this it makes use of the context (the token type being worked on).
! It counts up the number of obvious choices for the given context
! (all to do with where a candidate is, except for 6 (animate) which is to
! do with whether it is animate or not);
!
! if only one obvious choice is found, that is returned;
!
! if we are in indefinite mode (don't care which) one of the obvious choices
! is returned, or if there is no obvious choice then an unobvious one is
! made;
!
! otherwise, 0 (meaning, unable to decide) is returned.
! ----------------------------------------------------------------------------
[ Adjudicate context i j good_ones last n ultimate;
j=number_matched-1; good_ones=0; last=match_list->0;
for i 0 to j
{ n=match_list->i;
if n hasnt concealed
{ ultimate=n;
.AdjLoop; ultimate=parent(ultimate);
if ultimate~=location { if ultimate~=actor { if ultimate~=0 {
jump Adjloop; } } }
if context==0 { if ultimate==location { inc good_ones; last=n; } }
if context==1 { if parent(n)==actor { inc good_ones; last=n; } }
if context==2 { if ultimate==location { inc good_ones; last=n; } }
if context==3 { if parent(n)==actor { inc good_ones; last=n; } }
if context==4 { if parent(n)==actor { inc good_ones; last=n; } }
if context==5 { if parent(n)==actor { inc good_ones; last=n; } }
if context==6 { if n has animate { inc good_ones; last=n; } }
}
}
j=0;
if good_ones==1 { j=last; }
if indef_mode==1 { j=last; }
return j;
];
! ----------------------------------------------------------------------------
! MoveWord copies word at2 from parse buffer b2 to word at1 in "parse"
! (the main parse buffer)
! ----------------------------------------------------------------------------
[ MoveWord at1 b2 at2 x y;
x=at1*2-1; y=at2*2-1;
put parse word x b2-->y;
inc x; inc y;
put parse word x b2-->y;
];
! ----------------------------------------------------------------------------
! NounWithin looks for objects in the domain which make textual sense
! and puts them in the match list.
! ----------------------------------------------------------------------------
[ NounWithin domain i threshold;
! Special rule: the directions (interpreted as the ten walls of a room) are
! always in context. (So, e.g., "examine north wall" is always legal.)
if domain==location { NounWithin(compass); }
! Look through the objects in the domain
domain=child(domain);
while domain~=0
{
! If we're beyond the end of the user's typing, accept everything
! (NounDomain will sort things out)
if match_from > num_words { MakeMatch(domain,1); jump DontAccept; }
! "it" or "them" matches to the it-object only. (Note that (1) this means
! that "it" will only be understood if the object in question is still
! in context, and (2) only one match can ever be made in this case.)
wn=match_from;
i=Noun();
if i==1 { if itobj==domain { MakeMatch(itobj,1); } }
if i==2 { if himobj==domain { MakeMatch(himobj,1); } }
if i==3 { if herobj==domain { MakeMatch(herobj,1); } }
! Construing the current word as a noun, can it refer to the object?
if 0 == Refers(domain, i) { jump DontAccept; }
! If it can, count up how many words in a row can refer to this object,
! and send it to the match list with this number
threshold=0;
while 0~=Refers(domain,i) { i=NextWord(); inc threshold; }
MakeMatch(domain,threshold);
.DontAccept;
! Shall we consider the possessions of the current object, as well?
! Only if it's a container (so, for instance, if a dwarf carries a
! sword, then "drop sword" will not be accepted, but "dwarf, drop sword"
! will).
! Also, only if there are such possessions.
!
! Then the rules are: if it is open, or has the "interior" flag unset.
!
! (The idea is that a steel box has an "interior", but a table hasn't.)
if domain has supporter { jump DoRecurse; }
if domain has container
{ if domain has open { jump DoRecurse; }
if domain has interior { jump DontRecurse; }
.DoRecurse;
if child(domain)~=0 { NounWithin(domain); }
.DontRecurse;
}
domain=sibling(domain);
}
];
! ----------------------------------------------------------------------------
! MakeMatch looks at how good a match is. If it's the best so far, then
! wipe out all the previous matches and start a new list with this one.
! If it's only as good as the best so far, add it to the list.
! If it's worse, ignore it altogether.
!
! The idea is that "red panic button" is better than "red button" or "panic".
!
! number_matched (the number of words matched) is set to the current level
! of quality.
! ----------------------------------------------------------------------------
[ MakeMatch obj quality;
if quality < match_length { rtrue; }
if quality > match_length { match_length=quality; number_matched=0; }
put match_list byte number_matched obj;
inc number_matched;
];
! ----------------------------------------------------------------------------
! Refers works out whether the word with dictionary address wd can refer to
! the object obj, by seeing if wd is listed in the "names" property of obj.
! ----------------------------------------------------------------------------
[ Refers obj wd k l m;
k=prop_addr(obj,1); l=(prop_len(k)/2)-1;
for m 0 to l
{ if wd==k-->m { rtrue; }
}
rfalse;
];
! ----------------------------------------------------------------------------
! Noun (which takes no arguments) returns:
!
! 1 if the next word is "it" or "them",
! 2 if the next word is "him",
! 3 if the next word is "her",
! 0 if the next word is unrecognised or does not carry the "noun" bit in
! its dictionary entry,
! or the address in the dictionary if it is a recognised noun.
!
! The "current word" marker moves on one.
! ----------------------------------------------------------------------------
[ Noun i j;
i=NextWord();
if i==it_word or them_word { j=1; return j; }
if i==him_word { j=2; return j; }
if i==her_word { j=3; return j; }
if i==0 { rfalse; }
j=i-->4;
if j&128 == 0 { rfalse; }
return i;
];
! ----------------------------------------------------------------------------
! Adjective (which takes no arguments) returns:
!
! 0 if the next word is listed in the dictionary as possibly an adjective,
! or its adjective number if it is.
!
! The "current word" marker moves on one.
! ----------------------------------------------------------------------------
[ Adjective i j;
j=NextWord();
if j==0 { rfalse; }
i=j->4;
if i&8 == 0 { rfalse; }
return(j->6);
];
! ----------------------------------------------------------------------------
! AdjectiveAddress works out the address in the dictionary of the word
! corresponding to the given adjective number.
!
! It should never produce the given error (which would mean that Inform
! had set up the adjectives table incorrectly).
! ----------------------------------------------------------------------------
[ AdjectiveAddress number m;
m=#adjectives_table;
while 1==1
{ if number==m-->1 { m=m-->0; return m; }
m=m+4;
}
m=#adjectives_table;
print "<Adjective not found>";
return m;
];
! ----------------------------------------------------------------------------
! NextWord (which takes no arguments) returns:
!
! 0 if the next word is unrecognised,
! comma_word if it is a comma character
! (which is treated oddly by the Z-machine, hence the code)
! or the dictionary address if it is recognised.
!
! The "current word" marker is moved on.
! ----------------------------------------------------------------------------
[ NextWord i j k;
if wn > (parse->1) { inc wn; rfalse; }
i=wn*2-1; inc wn;
j=parse-->i;
if j==0
{ k=wn*4-3; i=buffer->(parse->k);
if i==',' { j=comma_word; }
}
return j;
];
! ----------------------------------------------------------------------------
! TryNumber is the only routine which really does any character-level
! parsing, since that's normally left to the Z-machine.
! It takes word number "wordnum" and tries to parse it as an (unsigned)
! decimal number, returning
!
! 0 if it is 0 or not a number
! the number if it has between 1 and 4 digits
! 10000 if it has 5 or more digits.
!
! (The danger of allowing 5 digits is that Z-machine integers are only
! 16 bits long, and anyway this isn't meant to be perfect.)
! ----------------------------------------------------------------------------
[ TryNumber wordnum i j c num len mul tot d digit;
i=wordnum*4+1; j=parse->i; num=j+buffer;
len=parse->(i-1);
if len>=4 { mul=1000; }
if len==3 { mul=100; }
if len==2 { mul=10; }
if len==1 { mul=1; }
tot=0; c=0; len=len-1;
for c 0 to len
{ digit=num->c;
if digit=='0' { d=0; jump digok; }
if digit=='1' { d=1; jump digok; }
if digit=='2' { d=2; jump digok; }
if digit=='3' { d=3; jump digok; }
if digit=='4' { d=4; jump digok; }
if digit=='5' { d=5; jump digok; }
if digit=='6' { d=6; jump digok; }
if digit=='7' { d=7; jump digok; }
if digit=='8' { d=8; jump digok; }
if digit=='9' { d=9; jump digok; }
rfalse;
.digok;
tot=tot+mul*d;
mul=mul/10;
}
if len>3 { tot=10000; }
return tot;
];
! ----------------------------------------------------------------------------
! ResetVagueWords does, assuming that i was the object last referred to
! ----------------------------------------------------------------------------
[ ResetVagueWords i;
if i has animate
{ if GetGender(i)==1 { himobj=i; }
else { herobj=i; }
}
else { itobj=i; }
];
! ----------------------------------------------------------------------------
! GetGender returns 0 if the given animate object is female, and 1 if male
! (not all games will want such a simple decision function!)
! ----------------------------------------------------------------------------
[ GetGender person i;
if person hasnt female { i=1; }
return i;
];
! ----------------------------------------------------------------------------
! For copying buffers
! ----------------------------------------------------------------------------
[ Copy bto bfrom i size;
size=bto->0;
for i 1 to size { put bto byte i bfrom->i; }
];
! ----------------------------------------------------------------------------
! End of the parser proper: the remaining routines are its front end.
! ----------------------------------------------------------------------------
[ DisplayStatus;
if the_time==$ffff
{ sline1=score; sline2=turns; }
else
{ sline1=the_time/60; sline2=the_time%60; }
];
[ SetTime t s;
the_time=t; time_rate=s; time_step=0;
if s<0 { time_step=0-s; }
];
[ PlayTheGame i j k l aflag;
Initialise();
move player to location;
Banner();
LookSub();
for i 1 to 100 { j=random(i); }
while deadflag==0
{ .Error;
inp1=0; inp2=0; action=0;
Parser(inputobjs);
onotheld_mode=notheld_mode; notheld_mode=0;
if actor~=player
{ action=inputobjs->0;
if Life(actor,0)==0
{ CDefArt(actor); print " has better things to do.^"; }
jump timeslice;
}
if toomany_flag==1
{ toomany_flag=0;
print "(taking the first sixteen objects only)^";
}
aflag=0;
if action~=0 { aflag=1; }
if action==0 { action=inputobjs->0; }
if aflag==0
{ i=inputobjs->1;
inp1=inputobjs->2;
inp2=inputobjs->3;
}
if aflag~=0
{ i=2;
}
multiflag=0;
if i==0 { Process(0,0,action); }
if i>0
{ if inp1~=0 { Process(inp1,inp2,action); }
if inp1==0
{ multiflag=1;
j=multiple_object->0;
if j==0 { print "Nothing to do!^"; jump Error; }
for k 1 to j
{ l=multiple_object->k; print_obj l; print ": ";
Process(l,inp2,action);
}
}
}
.timeslice;
if notheld_mode==1 { meta=1; }
if deadflag==0 { if meta==0 { Time(); } }
}
print "^^ ***";
if deadflag==1 { print " You have died "; }
if deadflag==2 { print " You have won "; }
if deadflag>2 { print " "; DeathMessage(); print " "; }
print "***^^^";
ScoreSub();
.RRQPL;
print "^Would you like to RESTART, RESTORE a saved game, give the FULL score for that game";
if deadflag==2 { print ", see some suggestions for AMUSING things to do"; }
print " or QUIT?^";
.RRQL;
print_char '?'; print_char ' ';
read buffer parse;
i=parse-->1;
if i==#w$quit { quit; }
if i==#w$q { quit; }
if i==#w$restar { restart; }
if i==#w$restor { RestoreSub(); jump RRQPL; }
if i==#w$fullsc { FullScoreSub(); jump RRQPL; }
if deadflag==2 { if i==#n$amusin { new_line; Amusing(); jump RRQPL; } }
if i==#w$full { new_line; FullScoreSub(); jump RRQPL; }
print "Please answer RESTART, RESTORE, FULL";
if deadflag==2 { print ", AMUSING"; }
print " or QUIT.^";
jump RRQL;
];
[ Process i j acti;
inp1 = i; inp2 = j; action=acti;
if meta==1 { jump metap; }
if GamePreRoutine()~=0 { rtrue; }
if location~=0
{ i=prop(location,preroutine);
if i~=$ffff
{ if indirect(i)~=0 { rtrue; }
}
}
if inp1>1
{ i=prop(inp1,preroutine);
if i~=$ffff
{ if indirect(i)~=0 { rtrue; }
}
}
.metap;
j=#actions_table-->action;
indirect(j);
];
[ Life a j i;
reason_code = j;
i=prop(a,liferoutine);
if i~=$ffff
{ if indirect(i)~=0 { rtrue; }
}
rfalse;
];
[ PostAct i k;
k=location; i=prop(k,postroutine);
if i==$ffff { jump NoLocPost; }
if indirect(i)~=0 { rtrue; }
.NoLocPost;
k=0; i=inputobjs->1;
if i>0 { k=inp1; }
if k<2 { rfalse; }
i=prop(k,postroutine);
if i==$ffff { rfalse; }
if indirect(i)~=0 { rtrue; }
return GamePostRoutine();
];
[ LPostAct i k;
k=location;
if k==0 { rfalse; }
i=prop(k,postroutine);
if i==$ffff { rfalse; }
if indirect(i)~=0 { rtrue; }
return GamePostRoutine();
];
[ Banner i;
print_paddr #Story;
print_paddr #Headline;
print "Release ";
print_num (0-->1) & $03ff;
print " / Serial number ";
for i 18 to 23
{ print_char 0->i;
}
print " (Compiled by Inform v"; inversion; print ")^";
];
[ Time i;
inc turns;
if the_time~=$ffff
{ if time_rate>=0 { the_time=the_time+time_rate; }
else { dec time_step;
if time_step==0
{ inc the_time; time_step = 0-time_rate; }
}
}
TimePasses();
i=lightflag;
lightflag=OffersLight(parent(player));
if i==0
{ if lightflag==1 { new_line; location=parent(player); LookSub(1); }
}
if i==1
{ if lightflag==0
{ new_line; print "It is now pitch dark in here!^"; location=thedark; }
}
];
[ TimeUp i j;
j=prop(i,timeleft);
if j>turns { rfalse; }
if j==0 { rfalse; }
put_prop i timeleft 0;
rtrue;
];
[ DecTimeUp i j;
j=prop(i,timeleft);
if j==0 { rfalse; }
dec j; put_prop i timeleft j;
if j>0 { rfalse; }
rtrue;
];
[ SetTimer i j;
j=turns+j;
put_prop i timeleft j;
];
[ Clock i j;
j=prop(i,timeleft)-turns;
return j;
];
[ OffersLight i;
if i has light { rtrue; }
if i has container
{ if i has interior
{ if i hasnt open { rfalse; }
}
}
i=child(i);
while i~=0
{ if 1==OffersLight(i) { rtrue; }
i=sibling(i);
}
rfalse;
];
[ Indefart o;
if o hasnt proper { print_paddr prop(o,article); print " "; }
print_obj(o);
];
[ Defart o;
if o hasnt proper { print "the "; }
print_obj(o);
];
[ CDefart o;
if o hasnt proper { print "The "; }
print_obj(o);
];