| ! ---------------------------------------------------------------------------- | |
| ! "PARSER": The core of the Inform library | |
| ! | |
| ! Supplied for use with Inform 5.5 | |
| ! | |
| ! (c) Graham Nelson 1993, 1994, 1995, but freely usable (see documentation) | |
| ! ---------------------------------------------------------------------------- | |
| Constant LibSerial "951024"; | |
| Constant LibRelease "5/12"; | |
| System_file; | |
| Constant MAX_TIMERS 64; | |
| Array the_timers -> MAX_TIMERS; | |
| Array timer_flags -> MAX_TIMERS; | |
| Array buffer string 120; | |
| Array parse string 64; | |
| Attribute animate; | |
| Attribute clothing; | |
| Attribute concealed; | |
| Attribute container; | |
| Attribute door; | |
| Attribute edible; | |
| Attribute enterable; | |
| Attribute female; | |
| Attribute general; | |
| Attribute light; | |
| Attribute lockable; | |
| Attribute locked; | |
| Attribute moved; | |
| Attribute on; | |
| Attribute open; | |
| Attribute openable; | |
| Attribute proper; | |
| Attribute scenery; | |
| Attribute scored; | |
| Attribute static; | |
| Attribute supporter; | |
| Attribute switchable; | |
| Attribute talkable; | |
| Attribute transparent; | |
| Attribute visited; | |
| Attribute workflag; | |
| Attribute worn; | |
| Attribute absent alias female; ! Please, no psychoanalysis | |
| Property additive before $ffff; | |
| Property additive after $ffff; | |
| Property additive life $ffff; | |
| Property long n_to; Property long s_to; ! Slightly wastefully, these are | |
| Property long e_to; Property long w_to; ! long (they might be routines) | |
| Property long ne_to; Property long se_to; | |
| Property long nw_to; Property long sw_to; | |
| Property long u_to; Property long d_to; | |
| Property long in_to; Property long out_to; | |
| Property door_to alias n_to; ! For economy: these properties are | |
| Property when_closed alias s_to; ! used only by objects which | |
| Property with_key alias e_to; ! aren't rooms | |
| Property door_dir alias w_to; | |
| Property invent alias u_to; | |
| Property plural alias d_to; | |
| Property add_to_scope alias se_to; | |
| Property list_together alias sw_to; | |
| Property react_before alias ne_to; | |
| Property react_after alias nw_to; | |
| Property grammar alias in_to; | |
| Property orders alias out_to; | |
| Property long initial; | |
| Property when_open alias initial; | |
| Property when_on alias initial; | |
| Property when_off alias when_closed; | |
| Property long description; | |
| Property additive describe $ffff; | |
| Property article "a"; | |
| Property cant_go "You can't go that way."; | |
| Property long found_in; ! For fiddly reasons this can't alias | |
| Property long time_left; | |
| Property long number; | |
| Property additive time_out $ffff; | |
| Property daemon alias time_out; | |
| Property additive each_turn $ffff; | |
| Property capacity 100; | |
| Property long short_name 0; | |
| Property long parse_name 0; | |
| ! The following definitions, commented out, define pre-Inform 5.2 and now | |
| ! obselete names as aliases for the standard names: | |
| ! Property preroutine alias before; Property desc alias description; | |
| ! Property postroutine alias after; Property longdesc alias description; | |
| ! Property liferoutine alias life; Property timeleft alias time_left; | |
| ! Property initpos alias initial; | |
| ! Property portalto alias door_to; | |
| ! Property closedpos alias when_closed; | |
| ! Property dirprop alias door_dir; | |
| ! Property cantgo alias cant_go; | |
| ! Attribute portal alias door; | |
| Fake_Action LetGo; | |
| Fake_Action Receive; | |
| Fake_Action ThrownAt; | |
| Fake_Action Order; | |
| Fake_Action TheSame; | |
| Fake_Action PluralFound; | |
| Fake_Action Miscellany; | |
| Fake_Action Prompt; | |
| Fake_Action NotUnderstood; | |
| [ Main; PlayTheGame(); ]; | |
| Constant NULL $ffff; | |
| ! ---------------------------------------------------------------------------- | |
| ! The compass, directions, darkness and player objects | |
| ! Definitions of fake actions | |
| ! Library global variables | |
| ! Private parser variables | |
| ! Keyboard reading | |
| ! Parser, level 0: outer shell, conversation, errors | |
| ! 1: grammar lines | |
| ! 2: tokens | |
| ! 3: object lists | |
| ! 4: scope and ambiguity resolving | |
| ! 5: object comparisons | |
| ! 6: word comparisons | |
| ! 7: reading words and moving tables about | |
| ! Main game loop | |
| ! Action processing | |
| ! Menus | |
| ! Time: timers and daemons | |
| ! Considering light | |
| ! Changing player personality | |
| ! Printing short names | |
| ! ---------------------------------------------------------------------------- | |
| ! ---------------------------------------------------------------------------- | |
| ! Construct the compass - a dummy object containing the directions, which also | |
| ! represent the walls in whatever room the player is in (these are given the | |
| ! general-purpose "number" property for the programmer's convenience) | |
| ! ---------------------------------------------------------------------------- | |
| Object compass "compass" has concealed; | |
| #IFNDEF WITHOUT_DIRECTIONS; | |
| Object n_obj "north wall" compass | |
| with name "n" "north" "wall", article "the", door_dir n_to, number 0 | |
| has scenery; | |
| Object s_obj "south wall" compass | |
| with name "s" "south" "wall", article "the", door_dir s_to, number 0 | |
| has scenery; | |
| Object e_obj "east wall" compass | |
| with name "e" "east" "wall", article "the", door_dir e_to, number 0 | |
| has scenery; | |
| Object w_obj "west wall" compass | |
| with name "w" "west" "wall", article "the", door_dir w_to, number 0 | |
| has scenery; | |
| Object ne_obj "northeast wall" compass | |
| with name "ne" "northeast" "wall", article "the", door_dir ne_to, number 0 | |
| has scenery; | |
| Object se_obj "southeast wall" compass | |
| with name "se" "southeast" "wall", article "the", door_dir se_to, number 0 | |
| has scenery; | |
| Object nw_obj "northwest wall" compass | |
| with name "nw" "northwest" "wall", article "the", door_dir nw_to, number 0 | |
| has scenery; | |
| Object sw_obj "southwest wall" compass | |
| with name "sw" "southwest" "wall", article "the", door_dir sw_to, number 0 | |
| has scenery; | |
| Object u_obj "ceiling" compass | |
| with name "u" "up" "ceiling", article "the", door_dir u_to, number 0 | |
| has scenery; | |
| Object d_obj "floor" compass | |
| with name "d" "down" "floor", article "the", door_dir d_to, number 0 | |
| has scenery; | |
| #ENDIF; | |
| Object out_obj "outside" compass | |
| with article "the", door_dir out_to, number 0 | |
| has scenery; | |
| Object in_obj "inside" compass | |
| with article "the", door_dir in_to, number 0 | |
| has 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" | |
| with initial 0, | |
| short_name "Darkness", | |
| description "It is pitch dark, and you can't see a thing."; | |
| Object selfobj "yourself" | |
| with description "As good-looking as ever.", number 0, | |
| before $ffff, after $ffff, life $ffff, each_turn $ffff, | |
| time_out $ffff, describe $ffff, capacity 100, | |
| parse_name 0, short_name 0, orders 0, | |
| has concealed animate proper transparent; | |
| ! ---------------------------------------------------------------------------- | |
| ! 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 = NULL; | |
| Global time_rate = 1; | |
| Global time_step = 0; | |
| Global score = 0; | |
| Global turns = 1; | |
| Global player; | |
| Global lightflag = 1; | |
| Global real_location = thedark; | |
| Global print_player_flag = 0; | |
| Global deadflag = 0; | |
| Global transcript_mode = 0; | |
| Global last_score = 0; | |
| Global notify_mode = 1; ! Score notification | |
| Global places_score = 0; | |
| Global things_score = 0; | |
| Global lookmode = 1; | |
| Global lastdesc = 0; | |
| Global top_object = 0; | |
| Global standard_interpreter = 0; | |
| ! ---------------------------------------------------------------------------- | |
| ! Parser variables accessible to the rest of the game | |
| ! ---------------------------------------------------------------------------- | |
| Array inputobjs --> 16; ! To hold parameters | |
| Global toomany_flag = 0; ! Flag for "take all made too many" | |
| Global actor = 0; ! Person asked to do something | |
| Global actors_location = 0; ! Like location, but for the actor | |
| Global action = 0; ! Thing he is asked to do | |
| Global inp1 = 0; ! First parameter | |
| Global inp2 = 0; ! Second parameter | |
| Global special_number1 = 0; ! First number, if one was typed | |
| Global special_number2 = 0; ! Second number, if two were typed | |
| Global self = 0; ! Object whose routines are being run | |
| Global noun = 0; ! First noun | |
| Global second = 0; ! Second noun | |
| Array multiple_object --> 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 parsed_number = 0; ! For user-supplied parsing routines | |
| 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 life | |
| global sw__var = 0; ! Switch variable (used for embeddeds) | |
| global consult_from; ! Word that "consult"�topic starts on | |
| global consult_words; ! ...and number of words in topic | |
| #IFV5; | |
| global undo_flag = 0; ! Can the interpreter provide "undo"? | |
| #ENDIF; | |
| global parser_trace = 0; ! Set this to 1 to make the parser trace | |
| ! tokens and lines | |
| global debug_flag = 0; ! For debugging information | |
| global lm_n; ! Parameters for LibraryMessages | |
| global lm_o; | |
| Constant REPARSE_CODE 10000; | |
| Constant PARSING_REASON 0; | |
| Constant TALKING_REASON 1; | |
| Constant EACH_TURN_REASON 2; | |
| Constant REACT_BEFORE_REASON 3; | |
| Constant REACT_AFTER_REASON 4; | |
| Constant LOOPOVERSCOPE_REASON 5; | |
| Constant TESTSCOPE_REASON 6; | |
| ! ---------------------------------------------------------------------------- | |
| ! The parser, beginning with variables private to itself: | |
| ! ---------------------------------------------------------------------------- | |
| Array buffer2 string 120; ! Buffers for supplementary questions | |
| Array parse2 string 64; ! | |
| Array parse3 string 64; ! | |
| global wn; ! Word number (counts from 1) | |
| 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_wanted; ! Number of things needed in multitude | |
| global multi_had; ! Number of things actually found | |
| global multi_context; ! What token the multi-object was accepted for | |
| Array pattern --> 8; ! For the current pattern match | |
| global pcount; ! and a marker within it | |
| Array pattern2 --> 8; ! And another, which stores the best match | |
| global pcount2; ! so far | |
| global parameters; ! Parameters (objects) entered so far | |
| global params_wanted; ! Number needed (may change in parsing) | |
| global nsns; ! Number of special_numbers 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 | |
| Array oops_heap --> 5; ! Used temporarily by "oops" routine | |
| Constant MATCH_LIST_SIZE 128; | |
| Array match_list -> 128; | |
| ! An array of matched objects so far | |
| Array match_classes -> 128; | |
| ! An array of equivalence classes for them | |
| global number_matched; ! How many items in it? (0 means none) | |
| global number_of_classes; ! How many equivalence classes? | |
| global match_length; ! How many typed words long are these matches? | |
| global match_from; ! At what word of the input do they begin? | |
| global parser_action; ! For the use of the parser when calling | |
| global parser_one; ! user-supplied routines | |
| global parser_two; ! | |
| 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 indef_type; ! Bit-map holding types of specification | |
| global indef_wanted; ! Number of items wanted (100 for all) | |
| global indef_guess_p; ! Plural-guessing flag | |
| global allow_plurals; ! Whether they are presently allowed or not | |
| global not_holding; ! Object to be automatically taken as an | |
| ! implicit command | |
| Array kept_results --> 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 held_back_mode = 0; ! Flag: is there some input from last time | |
| global hb_wn = 0; ! left over? (And a save value for wn) | |
| global best_etype; ! Error number used within parser | |
| global etype; ! Error number used for individual lines | |
| global last_command_from; ! For sorting out "then again" | |
| global last_command_to; ! | |
| global token_was; ! For noun filtering by user routines | |
| global advance_warning; ! What a later-named thing will be | |
| global placed_in_flag; ! To do with PlaceInScope | |
| global length_of_noun; ! Set by NounDomain to number of words in noun | |
| global action_to_be; ! So the parser can "cheat" in one case | |
| global dont_infer; ! Another dull flag | |
| global scope_reason = PARSING_REASON; ! For "each_turn" and reactions | |
| global scope_token; ! For scope:Routine tokens | |
| global scope_error; | |
| global scope_stage; | |
| global ats_flag = 0; ! For AddToScope routines | |
| global ats_hls; ! | |
| global take_all_rule = 1; | |
| global usual_grammar_after = 0; | |
| #IFV5; | |
| global just_undone = 0; ! Can't have two successive UNDOs | |
| #ENDIF; | |
| Global pretty_flag=1; | |
| Global menu_nesting = 0; | |
| Global item_width=8; | |
| Global item_name="Nameless item"; | |
| Global menu_item=0; | |
| Global active_timers = 0; | |
| ! ---------------------------------------------------------------------------- | |
| ! Variables for Verblib | |
| ! ---------------------------------------------------------------------------- | |
| global inventory_stage = 1; | |
| global c_style; | |
| global lt_value; | |
| global listing_together; | |
| global listing_size; | |
| global wlf_indent; | |
| global inventory_style; | |
| global keep_silent; | |
| global receive_action; | |
| #ifdef DEBUG; | |
| global xcommsdir; | |
| Global x_scope_count; | |
| #endif; | |
| ! ---------------------------------------------------------------------------- | |
| ! The comma_word is a special word, used to substitute commas in the input | |
| ! ---------------------------------------------------------------------------- | |
| Constant comma_word 'xcomma'; | |
| ! ---------------------------------------------------------------------------- | |
| ! In Advanced games only, the DrawStatusLine routine does just that: this is | |
| ! provided explicitly so that it can be Replace'd to change the style, and | |
| ! as written it emulates the ordinary Standard game status line, which is | |
| ! drawn in hardware | |
| ! ---------------------------------------------------------------------------- | |
| #IFV5; | |
| [ DrawStatusLine i width posa posb; | |
| @split_window 1; @set_window 1; @set_cursor 1 1; style reverse; | |
| width = 0->33; posa = width-26; posb = width-13; | |
| spaces (width-1); | |
| @set_cursor 1 2; PrintShortName(location); | |
| if ((0->1)&2 == 0) | |
| { if (width > 76) | |
| { @set_cursor 1 posa; print "Score: ", sline1; | |
| @set_cursor 1 posb; print "Moves: ", sline2; | |
| } | |
| if (width > 63 && width <= 76) | |
| { @set_cursor 1 posb; print sline1, "/", sline2; | |
| } | |
| } | |
| else | |
| { @set_cursor 1 posa; print "Time: "; | |
| i=sline1%12; if (i<10) print " "; | |
| if (i==0) i=12; | |
| print i, ":"; | |
| if (sline2<10) print "0"; | |
| print sline2; | |
| if ((sline1/12) > 0) print " pm"; else print " am"; | |
| } | |
| @set_cursor 1 1; style roman; @set_window 0; | |
| ]; | |
| #ENDIF; | |
| ! ---------------------------------------------------------------------------- | |
| ! 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:i<10:i++) oops_heap->i = a_table->i; | |
| ! In case of an array entry corruption that shouldn't happen, but would be | |
| ! disastrous if it did: | |
| a_buffer->0 = 120; | |
| a_table->0 = 64; | |
| ! Print the prompt, and read in the words and dictionary addresses | |
| L__M(##Prompt); | |
| AfterPrompt(); | |
| #IFV3; read a_buffer a_table; #ENDIF; | |
| temp_global = 0; | |
| #IFV5; read a_buffer a_table DrawStatusLine; #ENDIF; | |
| nw=a_table->1; | |
| ! If the line was blank, get a fresh line | |
| if (nw == 0) | |
| { L__M(##Miscellany,10); jump FreshInput; } | |
| ! Unless the opening word was "oops" or its abbreviation "o", return | |
| w=a_table-->1; | |
| if (w == #n$o or 'oops') jump DoOops; | |
| #IFV5; | |
| ! Undo handling | |
| if ((w == 'undo')&&(parse->1==1)) | |
| { if (turns==1) | |
| { L__M(##Miscellany,11); jump FreshInput; | |
| } | |
| if (undo_flag==0) | |
| { L__M(##Miscellany,6); jump FreshInput; | |
| } | |
| if (undo_flag==1) jump UndoFailed; | |
| if (just_undone==1) | |
| { L__M(##Miscellany,12); jump FreshInput; | |
| } | |
| @restore_undo i; | |
| if (i==0) | |
| { .UndoFailed; | |
| L__M(##Miscellany,7); | |
| } | |
| jump FreshInput; | |
| } | |
| @save_undo i; | |
| just_undone=0; | |
| undo_flag=2; | |
| if (i==-1) undo_flag=0; | |
| if (i==0) undo_flag=1; | |
| if (i==2) | |
| { style bold; | |
| print (name) location, "^"; | |
| style roman; | |
| L__M(##Miscellany,13); | |
| just_undone=1; | |
| jump FreshInput; | |
| } | |
| #ENDIF; | |
| return nw; | |
| .DoOops; | |
| if (oops_from == 0) | |
| { L__M(##Miscellany,14); jump FreshInput; } | |
| if (nw == 1) | |
| { L__M(##Miscellany,15); jump FreshInput; } | |
| if (nw > 2) | |
| { L__M(##Miscellany,16); 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:i<10:i++) a_table->i = oops_heap->i; | |
| w=2*oops_from - 1; | |
| a_table-->w = x1; | |
| a_table-->(w+1) = x2; | |
| return nw; | |
| ]; | |
| Constant STUCK_PE 1; | |
| Constant UPTO_PE 2; | |
| Constant NUMBER_PE 3; | |
| Constant CANTSEE_PE 4; | |
| Constant TOOLIT_PE 5; | |
| Constant NOTHELD_PE 6; | |
| Constant MULTI_PE 7; | |
| Constant MMULTI_PE 8; | |
| Constant VAGUE_PE 9; | |
| Constant EXCEPT_PE 10; | |
| Constant ANIMA_PE 11; | |
| Constant VERB_PE 12; | |
| Constant SCENERY_PE 13; | |
| Constant ITGONE_PE 14; | |
| Constant JUNKAFTER_PE 15; | |
| Constant TOOFEW_PE 16; | |
| Constant NOTHING_PE 17; | |
| Constant ASKSCOPE_PE 18; | |
| ! ---------------------------------------------------------------------------- | |
| ! 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: | |
| ! | |
| ! Word 0 = The action number | |
| ! Word 1 = Number of parameters | |
| ! Words 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 go to (K) | |
| ! (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 | |
| ! (K) Last thing: check for "then" and further instructions(s), return. | |
| ! | |
| ! The strategic points (A) to (K) 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:i<8:i++) results-->i=kept_results-->i; | |
| notheld_mode=0; rtrue; | |
| } | |
| if (held_back_mode==1) | |
| { held_back_mode=0; | |
| for (i=0:i<64:i++) parse->i=parse2->i; | |
| new_line; | |
| jump ReParse; | |
| } | |
| .ReType; | |
| Keyboard(buffer,parse); | |
| .ReParse; | |
| ! Initially assume the command is aimed at the player, and the verb | |
| ! is the first word | |
| num_words=parse->1; | |
| wn=1; | |
| BeforeParsing(); | |
| num_words=parse->1; | |
| if (parser_trace>=4) | |
| { print "[ ", num_words, " to parse: "; | |
| for (i=1:i<=num_words:i++) | |
| { j=parse-->((i-1)*2+1); | |
| if (j == 0) print "? "; | |
| else | |
| { if (UnsignedCompare(j, 0-->4)>=0 | |
| && UnsignedCompare(j, 0-->2)<0) print_addr j; | |
| else print j; print " "; | |
| } | |
| } | |
| print "]^"; | |
| } | |
| verb_wordnum=1; | |
| actor=player; actors_location=location; | |
| usual_grammar_after = 0; | |
| .AlmostReParse; | |
| token_was = 0; ! In case we're still in "user-filter" mode from last round | |
| scope_token = 0; | |
| action_to_be = NULL; | |
| ! Begin from what we currently think is the verb word | |
| .BeginCommand; | |
| wn=verb_wordnum; | |
| verb_word = NextWordStopped(); | |
| ! If there's no input here, we must have something like | |
| ! "person,". | |
| if (verb_word==-1) | |
| { best_etype = STUCK_PE; jump GiveError; } | |
| ! 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==#n$g) verb_word='again'; | |
| if (verb_word=='again') | |
| { if (actor~=player) | |
| { print "To repeat a command like ~frog, jump~, just say \ | |
| ~again~, not ~frog, again~.^"; jump ReType; } | |
| if (parse3->1==0) | |
| { print "You can hardly repeat that.^"; jump ReType; } | |
| for (i=0:i<64:i++) parse->i=parse3->i; | |
| jump ReParse; | |
| } | |
| ! Save the present parse table in case of an "again" next time | |
| if (verb_word~='again') | |
| for (i=0:i<64:i++) | |
| parse3->i=parse->i; | |
| if (usual_grammar_after==0) | |
| { i = RunRoutines(actor, grammar); | |
| if (parser_trace>=2 && actor.grammar~=0 or NULL) | |
| print " [Grammar property returned ", i, "]^"; | |
| if (i<0) { usual_grammar_after = verb_wordnum; i=-i; } | |
| if (i==1) | |
| { results-->0 = action; | |
| results-->1 = noun; | |
| results-->2 = second; | |
| rtrue; | |
| } | |
| if (i~=0) { verb_word = i; wn--; verb_wordnum--; } | |
| else | |
| { wn = verb_wordnum; verb_word=NextWord(); | |
| } | |
| } | |
| else usual_grammar_after=0; | |
| ! **** (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...) | |
| if (verb_word==0 || ((verb_word->#dict_par1) & 1) == 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 REPARSE_CODE 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==REPARSE_CODE) jump ReParse; | |
| ! If it is a direction, send back the results: | |
| ! action=GoSub, no of arguments=1, argument 1=the direction. | |
| if (l~=0) | |
| { results-->0 = ##Go; | |
| results-->1 = 1; | |
| results-->2 = l; | |
| jump LookForMore; | |
| } | |
| ! **** (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:j<=num_words:j++) | |
| { i=NextWord(); if (i==comma_word) jump Conversation; | |
| } | |
| verb_word=UnknownVerb(verb_word); | |
| if (verb_word~=0) jump VerbAccepted; | |
| } | |
| best_etype=VERB_PE; 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; | |
| scope_reason = TALKING_REASON; | |
| l=NounDomain(player,actors_location,6); | |
| scope_reason = PARSING_REASON; | |
| if (l==REPARSE_CODE) 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 && 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; | |
| actors_location=l; | |
| while (parent(actors_location)~=0) | |
| actors_location=parent(actors_location); | |
| if (parser_trace>=1) | |
| print "[Actor is ", (the) actor, " in ", | |
| (name) actors_location, "]^"; | |
| jump BeginCommand; | |
| } | |
| ! **** (D) **** | |
| .VerbAccepted; | |
| ! 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->#dict_par1) & 2)/2; | |
| ! You can't order other people to "full score" for you, and so on... | |
| if (meta==1 && actor~=player) | |
| { best_etype=VERB_PE; meta=0; jump GiveError; } | |
| ! 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->#dict_par2); | |
| ! ...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'; vague_obj=itobj; | |
| if (parser_trace>=1) | |
| { print "[Parsing for the verb '"; print_addr verb_word; | |
| print "' (", num_lines+1, " lines)]^"; | |
| } | |
| best_etype=STUCK_PE; | |
| ! "best_etype" is the current failure-to-match error - it is by default | |
| ! the least informative one, "don't understand that sentence" | |
| ! **** (E) **** | |
| for (line=0:line<=num_lines:line++) | |
| { line_address = syntax+1+line*8; | |
| if (parser_trace>=1) | |
| { print "[Line ", line, ": ", line_address->0, " parameters: "; | |
| for (pcount=1:pcount<=6:pcount++) | |
| { token=line_address->pcount; | |
| print token, " "; | |
| } | |
| print " -> action ", line_address->7, "]^"; | |
| } | |
| ! We aren't in "not holding" or inferring modes, and haven't entered | |
| ! any parameters on the line yet, or any special numbers; the multiple | |
| ! object is still empty. | |
| not_holding=0; | |
| inferfrom=0; | |
| parameters=0; | |
| params_wanted = line_address->0; | |
| nsns=0; special_word=0; special_number=0; | |
| multiple_object-->0 = 0; | |
| multi_context = 0; | |
| etype=STUCK_PE; | |
| action_to_be = line_address->7; | |
| ! Put the word marker back to just after the verb | |
| wn=verb_wordnum+1; | |
| ! An individual "line" contains six tokens... There's a preliminary pass | |
| ! first, to parse late tokens early if necessary (because of mi or me). | |
| ! We also check to see whether the line contains any "multi"s. | |
| advance_warning=-1; indef_mode=0; | |
| for (i=0,m=0,pcount=1:pcount<=6:pcount++) | |
| { scope_token=0; | |
| token=line_address->pcount; | |
| if (token==2) m++; | |
| if (token<180) i++; | |
| if (token==4 or 5 && i==1) | |
| { if (parser_trace>=2) print " [Trying look-ahead]^"; | |
| pcount++; | |
| while (pcount<=6 && line_address->pcount>=180) pcount++; | |
| token=line_address->(pcount-1); | |
| if (token>=180) | |
| { j=AdjectiveAddress(token); | |
| ! Now look for word with j, move wn, parse next | |
| ! token... | |
| while (wn <= num_words) | |
| { if (NextWord()==j) | |
| { l = NounDomain(actors_location,actor,token); | |
| if (parser_trace>=2) | |
| { print " [Forward token parsed: "; | |
| if (l==REPARSE_CODE) print "re-parse request]^"; | |
| if (l==1) print "but multiple found]^"; | |
| if (l==0) print "hit error ", etype, "]^"; | |
| } | |
| if (l==REPARSE_CODE) jump ReParse; | |
| if (l>=2) | |
| { advance_warning = l; | |
| if (parser_trace>=3) | |
| { DefArt(l); print "]^"; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| ! Slightly different line-parsing rules will apply to "take multi", to | |
| ! prevent "take all" behaving correctly but misleadingly when there's | |
| ! nothing to take. | |
| take_all_rule = 0; | |
| if (m==1 && params_wanted==1 && action_to_be==##Take) | |
| take_all_rule = 1; | |
| ! And now start again, properly, forearmed or not as the case may be. | |
| not_holding=0; | |
| inferfrom=0; | |
| parameters=0; | |
| nsns=0; special_word=0; special_number=0; | |
| multiple_object-->0 = 0; | |
| etype=STUCK_PE; | |
| action_to_be = line_address->7; | |
| wn=verb_wordnum+1; | |
| ! "Pattern" gradually accumulates what has been recognised so far, | |
| ! so that it may be reprinted by the parser later on | |
| for (pcount=1:pcount<=6:pcount++) | |
| { pattern-->pcount=0; scope_token=0; | |
| token=line_address->pcount; | |
| if (parser_trace>=2) | |
| { print " [Token ",pcount, " is ", token, ": "; | |
| if (token<16) | |
| { if (token==0) print "<noun> or null"; | |
| if (token==1) print "<held>"; | |
| if (token==2) print "<multi>"; | |
| if (token==3) print "<multiheld>"; | |
| if (token==4) print "<multiexcept>"; | |
| if (token==5) print "<multiinside>"; | |
| if (token==6) print "<creature>"; | |
| if (token==7) print "<special>"; | |
| if (token==8) print "<number>"; | |
| } | |
| if (token>=16 && token<48) | |
| print "<noun filter by routine ",token-16, ">"; | |
| if (token>=48 && token<80) | |
| print "<general parse by routine ",token-48, ">"; | |
| if (token>=80 && token<128) | |
| print "<scope parse by routine ",token-80, ">"; | |
| if (token>=128 && token<180) | |
| print "<noun filter by attribute ",token-128, ">"; | |
| if (token>180) | |
| { print "<adjective ",255-token, " '"; | |
| print_addr AdjectiveAddress(token); print "'>"; | |
| } | |
| print " at word number ", wn, "]^"; | |
| } | |
| ! 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) m++; | |
| if (m>=params_wanted) lookahead=8; | |
| } | |
| ! **** (F) **** | |
| ! When the token is a large number, it must be an adjective: | |
| ! remember the adjective number in the "pattern". | |
| if (token>180) | |
| { pattern-->pcount = REPARSE_CODE+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 && parameters<params_wanted) | |
| { 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 finish this token). | |
| if (wn <= num_words && token~=AdjectiveWord()) break; | |
| jump TokenPassed; | |
| } | |
| ! **** (G) **** | |
| ! Check now to see if the player has entered enough parameters... | |
| ! (since params_wanted is the number of them) | |
| if (parameters == params_wanted) | |
| { | |
| ! 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 | |
| ! However, if the superfluous text begins with a comma, "and" or "then" then | |
| ! take that to be the start of another instruction | |
| if (wn <= num_words) | |
| { l=NextWord(); | |
| if (l=='then' or comma_word) | |
| { held_back_mode=1; hb_wn=wn-1; } | |
| else | |
| { for (m=0:m<8:m++) pattern2-->m=pattern-->m; | |
| pcount2=pcount; | |
| etype=UPTO_PE; break; | |
| } | |
| } | |
| ! Now, we may need to revise the multiple object because of the single one | |
| ! we now know (but didn't when the list was drawn up). | |
| if (parameters>=1 && results-->2 == 0) | |
| { l=ReviseMulti(results-->3); | |
| if (l~=0) { etype=l; break; } | |
| } | |
| if (parameters>=2 && results-->3 == 0) | |
| { l=ReviseMulti(results-->2); | |
| if (l~=0) { etype=l; break; } | |
| } | |
| ! To trap the case of "take all" inferring only "yourself" when absolutely | |
| ! nothing else is in the vicinity... | |
| if (take_all_rule==2 && results-->2 == actor) | |
| { best_etype = NOTHING_PE; jump GiveError; | |
| } | |
| if (parser_trace>=1) | |
| print "[Line successfully parsed]^"; | |
| ! 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... | |
| results-->1 = params_wanted; | |
| results-->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 && results-->2 >= 2) | |
| ResetVagueWords(results-->2); | |
| ! ...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 && actor==player) | |
| { notheld_mode=1; | |
| for (i=0:i<8:i++) kept_results-->i = results-->i; | |
| results-->0 = ##Take; | |
| results-->1 = 1; | |
| results-->2 = not_holding; | |
| print "(first taking "; DefArt(not_holding); print ")^"; | |
| } | |
| ! (Notice that implicit takes are only generated for the player, and not | |
| ! for other actors. This avoids entirely logical, but misleading, text | |
| ! being printed.) | |
| ! ...and finish. | |
| if (held_back_mode==1) { wn=hb_wn; jump LookForMore; } | |
| 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 POL. | |
| if (token==6 && (action_to_be==##Answer or ##Ask or ##AskFor | |
| || action_to_be==##Tell)) | |
| scope_reason=TALKING_REASON; | |
| l=ParseObjectList(results,token); | |
| scope_reason=PARSING_REASON; | |
| if (parser_trace>=3) | |
| { print " [Parse object list replied with"; | |
| if (l==REPARSE_CODE) print " re-parse request]^"; | |
| if (l==0) print " token failed, error type ", etype, "]^"; | |
| if (l==1) print " token accepted]^"; | |
| } | |
| if (l==REPARSE_CODE) jump ReParse; | |
| if (l==0) break; | |
| ! The token has been successfully passed; we are ready for the next. | |
| .TokenPassed; | |
| } | |
| ! But if we get here it means that the line failed somewhere, so we continue | |
| ! the outer for loop and try the next line... | |
| if (etype>best_etype) best_etype=etype; | |
| ! ...unless the line was something like "take all" which failed because | |
| ! nothing matched the "all", in which case we stop and give an error now. | |
| if (take_all_rule == 2 && etype==NOTHING_PE) break; | |
| } | |
| ! So that if we get here, each line for the specified verb has failed. | |
| ! **** (H) **** | |
| .GiveError; | |
| etype=best_etype; | |
| ! 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. | |
| if (actor~=player) | |
| { if (usual_grammar_after>0) | |
| { verb_wordnum = usual_grammar_after; | |
| jump AlmostReParse; | |
| } | |
| wn=verb_wordnum; | |
| special_word=NextWord(); | |
| if (special_word=='xcomma') | |
| { special_word=NextWord(); | |
| verb_wordnum++; | |
| } | |
| special_number=TryNumber(verb_wordnum); | |
| results-->0=##NotUnderstood; | |
| results-->1=2; | |
| results-->2=1; special_number1=special_word; | |
| results-->3=actor; | |
| consult_from = verb_wordnum; consult_words = num_words-consult_from+1; | |
| 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 (ParserError(etype)~=0) jump ReType; | |
| if (etype==STUCK_PE) | |
| { print "I didn't understand that sentence.^"; oops_from=1; } | |
| if (etype==UPTO_PE) | |
| { print "I only understood you as far as wanting to "; | |
| for (m=0:m<8:m++) pattern-->m = pattern2-->m; | |
| pcount=pcount2; PrintCommand(0,1); print ".^"; | |
| } | |
| if (etype==NUMBER_PE) | |
| print "I didn't understand that number.^"; | |
| if (etype==CANTSEE_PE) | |
| { print "You can't see any such thing.^"; | |
| oops_from=saved_oops; } | |
| if (etype==TOOLIT_PE) | |
| print "You seem to have said too little!^"; | |
| if (etype==NOTHELD_PE) | |
| { print "You aren't holding that!^"; | |
| oops_from=saved_oops; } | |
| if (etype==MULTI_PE) | |
| print "You can't use multiple objects with that verb.^"; | |
| if (etype==MMULTI_PE) | |
| print "You can only use multiple objects once on a line.^"; | |
| if (etype==VAGUE_PE) | |
| { print "I'm not sure what ~"; print_addr vague_word; | |
| print "~ refers to.^"; } | |
| if (etype==EXCEPT_PE) | |
| print "You excepted something not included anyway!^"; | |
| if (etype==ANIMA_PE) | |
| print "You can only do that to something animate.^"; | |
| if (etype==VERB_PE) | |
| print "That's not a verb I recognise.^"; | |
| if (etype==SCENERY_PE) | |
| print "That's not something you need to refer to \ | |
| in the course of this game.^"; | |
| if (etype==ITGONE_PE) | |
| { print "You can't see ~"; print_addr vague_word; | |
| print "~ ("; DefArt(vague_obj); print ") at the moment.^"; } | |
| if (etype==JUNKAFTER_PE) | |
| print "I didn't understand the way that finished.^"; | |
| if (etype==TOOFEW_PE) | |
| { if (multi_had==0) print "None"; | |
| else { print "Only "; EnglishNumber(multi_had); } | |
| print " of those "; | |
| if (multi_had==1) print "is"; else print "are"; | |
| print " available.^"; } | |
| if (etype==NOTHING_PE) | |
| { if (multi_wanted==100) print "Nothing to do!^"; | |
| else print "There are none at all available!^"; } | |
| if (etype==ASKSCOPE_PE) | |
| { scope_stage=3; indirect(scope_error); } | |
| ! **** (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. | |
| ! **** (K) **** | |
| ! At this point, the return value is all prepared, and we are only looking | |
| ! to see if there is a "then" followed by subsequent instruction(s). | |
| .LookForMore; | |
| if (wn>num_words) rtrue; | |
| i=NextWord(); | |
| if (i=='then' || i==comma_word) | |
| { if (wn>num_words) | |
| { parse2->1=(parse2->1)-1; held_back_mode = 0; rtrue; } | |
| if (actor==player) j=0; else j=verb_wordnum-1; | |
| last_command_from = j+1; last_command_to = wn-2; | |
| i=NextWord(); | |
| if (i=='again' or #n$g) | |
| { for (i=0: i<j: i++) | |
| { parse2-->(2*i+1) = parse-->(2*i+1); | |
| parse2-->(2*i+2) = parse-->(2*i+2); | |
| } | |
| for (i=last_command_from:i<=last_command_to:i++, j++) | |
| { parse2-->(2+2*j) = parse-->(2*i); | |
| parse2-->(1+2*j) = parse-->(2*i-1); | |
| } | |
| for (i=wn:i<=num_words:i++, j++) | |
| { parse2-->(2+2*j) = parse-->(2*i); | |
| parse2-->(1+2*j) = parse-->(2*i-1); | |
| } | |
| parse2->1=j; held_back_mode = 1; rtrue; | |
| } | |
| else wn--; | |
| for (i=0: i<j: i++) | |
| { parse2-->(2*i+1) = parse-->(2*i+1); | |
| parse2-->(2*i+2) = parse-->(2*i+2); | |
| } | |
| for (i=wn:i<=num_words:i++, j++) | |
| { parse2-->(2+2*j) = parse-->(2*i); | |
| parse2-->(1+2*j) = parse-->(2*i-1); | |
| } | |
| parse2->1=j; held_back_mode = 1; rtrue; | |
| } | |
| best_etype=UPTO_PE; jump GiveError; | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! NumberWord - fairly self-explanatory | |
| ! ---------------------------------------------------------------------------- | |
| [ NumberWord o; | |
| if (o=='one') return 1; | |
| if (o=='two') return 2; | |
| if (o=='three') return 3; | |
| if (o=='four') return 4; | |
| if (o=='five') return 5; | |
| if (o=='six') return 6; | |
| if (o=='seven') return 7; | |
| if (o=='eight') return 8; | |
| if (o=='nine') return 9; | |
| if (o=='ten') return 10; | |
| if (o=='eleven') return 11; | |
| if (o=='twelve') return 12; | |
| if (o=='thirteen') return 13; | |
| if (o=='fourteen') return 14; | |
| if (o=='fifteen') return 15; | |
| if (o=='sixteen') return 16; | |
| if (o=='seventeen') return 17; | |
| if (o=='eighteen') return 18; | |
| if (o=='nineteen') return 19; | |
| if (o=='twenty') return 20; | |
| return 0; | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! Descriptors() | |
| ! | |
| ! Handles descriptive words like "my", "his", "another" and so on. | |
| ! Skips "the", and leaves wn pointing to the first misunderstood word. | |
| ! | |
| ! Allowed to set up for a plural only if allow_p is set | |
| ! | |
| ! Returns error number, or 0 if no error occurred | |
| ! ---------------------------------------------------------------------------- | |
| Constant OTHER_BIT 1; ! These will be used in Adjudicate() | |
| Constant MY_BIT 2; ! to disambiguate choices | |
| Constant THAT_BIT 4; | |
| Constant PLURAL_BIT 8; | |
| Constant ITS_BIT 16; | |
| Constant HIS_BIT 32; | |
| Constant LIT_BIT 64; | |
| Constant UNLIT_BIT 128; | |
| [ Descriptors context o flag n; | |
| indef_mode=0; indef_type=0; indef_wanted=0; indef_guess_p=0; | |
| for (flag=1:flag==1:) | |
| { o=NextWord(); flag=0; | |
| if (o=='the') flag=1; | |
| if (o==#n$a or 'an' or 'any' || o=='either' or 'anything') | |
| { indef_mode=1; flag=1; } | |
| if (o=='another' or 'other') | |
| { indef_mode=1; flag=1; | |
| indef_type = indef_type | OTHER_BIT; } | |
| if (o=='my' or 'this' or 'these') | |
| { indef_mode=1; flag=1; | |
| indef_type = indef_type | MY_BIT; } | |
| if (o=='that' or 'those') | |
| { indef_mode=1; flag=1; | |
| indef_type = indef_type | THAT_BIT; } | |
| if (o=='its') | |
| { indef_mode=1; flag=1; | |
| indef_type = indef_type | ITS_BIT; } | |
| if (o=='his' or 'your') | |
| { indef_mode=1; flag=1; | |
| indef_type = indef_type | HIS_BIT; } | |
| if (o=='lit' or 'lighted') | |
| { indef_mode=1; flag=1; | |
| indef_type = indef_type | LIT_BIT; } | |
| if (o=='unlit') | |
| { indef_mode=1; flag=1; | |
| indef_type = indef_type | UNLIT_BIT; } | |
| if (o=='all' or 'each' or 'every' || o=='everything') | |
| { indef_mode=1; flag=1; indef_wanted=100; | |
| if (take_all_rule == 1) | |
| take_all_rule = 2; | |
| indef_type = indef_type | PLURAL_BIT; } | |
| if (allow_plurals==1) | |
| { n=NumberWord(o); | |
| if (n>1) { indef_guess_p=1; | |
| indef_mode=1; flag=1; indef_wanted=n; | |
| indef_type = indef_type | PLURAL_BIT; } | |
| } | |
| if (flag==1 && NextWord() ~= 'of') wn--; ! Skip 'of' after these | |
| } | |
| wn--; | |
| if ((indef_wanted > 0) && (context<2 || context>5)) return MULTI_PE; | |
| return 0; | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! CreatureTest: Will this person do for a "creature" token? | |
| ! ---------------------------------------------------------------------------- | |
| [ CreatureTest obj; | |
| if (obj has animate) rtrue; | |
| if (obj hasnt talkable) rfalse; | |
| if (action_to_be==##Ask or ##Answer or ##Tell | |
| || action_to_be==##AskFor) rtrue; | |
| rfalse; | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! ParseObjectList: Parses tokens 0 to 179, from the current word number wn | |
| ! | |
| ! Returns: | |
| ! REPARSE_CODE for "reconstructed input, please re-parse from scratch" | |
| ! 1 for "token accepted" | |
| ! 0 for "token failed" | |
| ! | |
| ! (A) Preliminaries and special/number tokens | |
| ! (B) Actual object names (mostly subcontracted!) | |
| ! (C) and/but and so on | |
| ! (D) Returning an accepted token | |
| ! | |
| ! ---------------------------------------------------------------------------- | |
| [ ParseObjectList results token l o i j k | |
| and_parity single_object desc_wn many_flag; | |
| many_flag=0; and_parity=1; dont_infer=0; | |
| ! **** (A) **** | |
| ! We expect to find a list of objects next in what the player's typed. | |
| .ObjectList; | |
| if (parser_trace>=3) print " [Object list from word ", wn, "]^"; | |
| ! Take an advance 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 scope) | |
| o=NextWord(); wn--; | |
| if (o=='it' or 'them') | |
| { vague_word=o; vague_obj=itobj; | |
| if (itobj==0) { etype=VAGUE_PE; return 0; } | |
| } | |
| if (o=='him') | |
| { vague_word=o; vague_obj=himobj; | |
| if (himobj==0) { etype=VAGUE_PE; return 0; } | |
| } | |
| if (o=='her') | |
| { vague_word=o; vague_obj=herobj; | |
| if (herobj==0) { etype=VAGUE_PE; return 0; } | |
| } | |
| if (o=='me' or 'myself' or 'self') | |
| { vague_word=o; vague_obj=player; | |
| } | |
| ! Firstly, get rid of tokens 7 and 8 ("special" and "number"), and | |
| ! tokens which are entirely handed out to outside routines | |
| if (token==7) | |
| { l=TryNumber(wn); | |
| if (l~=-1000) | |
| { if (nsns==0) special_number1=l; else special_number2=l; | |
| special_number=l; | |
| nsns++; | |
| if (parser_trace>=3) | |
| print " [Read special as the number ", l, "]^"; | |
| } | |
| if (parser_trace>=3) | |
| print " [Read special word at word number ", wn, "]^"; | |
| special_word=NextWord(); single_object=1; jump PassToken; | |
| } | |
| if (token==8) | |
| { l=TryNumber(wn++); | |
| if (l==-1000) { etype=NUMBER_PE; rfalse; } | |
| if (parser_trace>=3) print " [Read number as ", l, "]^"; | |
| if (nsns++==0) special_number1=l; else special_number2=l; | |
| single_object=1; jump PassToken; | |
| } | |
| if (token>=48 && token<80) | |
| { l=indirect(#preactions_table-->(token-48)); | |
| if (parser_trace>=3) | |
| print " [Outside parsing routine returned ", l, "]^"; | |
| if (l<0) rfalse; | |
| if (l==0) { params_wanted--; rtrue; } ! An adjective after all... | |
| if (l==1) | |
| { if (nsns==0) special_number1=parsed_number; | |
| else special_number2=parsed_number; | |
| nsns++; | |
| } | |
| if (l==REPARSE_CODE) return l; | |
| single_object=l; jump PassToken; | |
| } | |
| if (token>=80 && token<128) | |
| { scope_token = #preactions_table-->(token-80); | |
| scope_stage = 1; | |
| l=indirect(scope_token); | |
| if (parser_trace>=3) | |
| print " [Scope routine returned multiple-flag of ", l, "]^"; | |
| if (l==1) token=2; else token=0; | |
| } | |
| token_was=0; | |
| if (token>=16) | |
| { token_was = token; | |
| token=0; | |
| } | |
| ! Otherwise, we have one of the tokens 0 to 6, all of which really do mean | |
| ! that objects are expected. | |
| ! So now we parse any descriptive words | |
| allow_plurals = 1; desc_wn = wn; | |
| .TryAgain; | |
| l=Descriptors(token); if (l~=0) { etype=l; return 0; } | |
| ! **** (B) **** | |
| ! This is an actual specified object, and is therefore where a typing error | |
| ! is most likely to occur, so we set: | |
| oops_from=wn; | |
| ! In either case below we use NounDomain, giving it the token number as | |
| ! context, and two places to look: among the actor's possessions, and in the | |
| ! present location. (Note that the order depends on which is likeliest.) | |
| ! So, two cases. Case 1: token not equal to "held" (so, no implicit takes) | |
| ! but we may well be dealing with multiple objects | |
| if (token~=1) | |
| { i=multiple_object-->0; | |
| if (parser_trace>=3) | |
| print " [Calling NounDomain on location and actor]^"; | |
| l=NounDomain(actors_location, actor, token); | |
| if (l==REPARSE_CODE) return l; ! Reparse after Q&A | |
| if (l==0) { etype=CantSee(); jump FailToken; } ! Choose best error | |
| if (parser_trace>=3) | |
| { if (l>1) | |
| { print " [ND returned "; DefArt(l); print "]^"; } | |
| else | |
| { print " [ND appended to the multiple object list:^"; | |
| k=multiple_object-->0; | |
| for (j=i+1:j<=k:j++) | |
| { print " Entry ", j, ": "; CDefArt(multiple_object-->j); | |
| print " (", multiple_object-->j, ")^"; | |
| } | |
| print " List now has size ", k, "]^"; | |
| } | |
| } | |
| if (l==1) | |
| { if (many_flag==0) | |
| { many_flag=1; | |
| } | |
| else ! Merge with earlier ones | |
| { k=multiple_object-->0; ! (with either parity) | |
| multiple_object-->0 = i; | |
| for (j=i+1:j<=k:j++) | |
| { if (and_parity==1) MultiAdd(multiple_object-->j); | |
| else MultiSub(multiple_object-->j); | |
| } | |
| if (parser_trace>=3) | |
| print " [Merging ", k-i, " new objects to the ", i, " old ones]^"; | |
| } | |
| } | |
| else | |
| { if (token==6 && CreatureTest(l)==0) ! Animation is required | |
| { etype=ANIMA_PE; jump FailToken; } ! for token 6 | |
| if (many_flag==0) | |
| single_object = l; | |
| else | |
| { if (and_parity==1) MultiAdd(l); else MultiSub(l); | |
| if (parser_trace>=3) | |
| { print " [Combining "; DefArt(l); print " with list]^"; | |
| } | |
| } | |
| } | |
| } | |
| ! Case 2: token is "held" (which fortunately can't take multiple objects) | |
| ! and may generate an implicit take | |
| if (token==1) | |
| { l=NounDomain(actor,actors_location,token); ! Same as above... | |
| if (l==REPARSE_CODE) 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=NOTHELD_PE; jump FailToken; | |
| } | |
| not_holding = l; | |
| if (parser_trace>=3) | |
| { print " [Allowing object "; DefArt(l); print " for now]^"; | |
| } | |
| } | |
| single_object = l; | |
| } | |
| ! The following moves the word marker to just past the named object... | |
| wn = oops_from + match_length; | |
| ! **** (C) **** | |
| ! Object(s) specified now: is that the end of the list, or have we reached | |
| ! "and", "but" and so on? If so, create a multiple-object list if we | |
| ! haven't already (and are allowed to). | |
| .NextInList; | |
| o=NextWord(); | |
| if (o=='and' or 'but' or 'except' || o==comma_word) | |
| { | |
| if (parser_trace>=3) | |
| { print " [Read '"; print_addr o; print "']^"; | |
| } | |
| if (token<2 || token>=6) { etype=MULTI_PE; jump FailToken; } | |
| if (o=='but' or 'except') and_parity = 1-and_parity; | |
| if (many_flag==0) | |
| { multiple_object-->0 = 1; | |
| multiple_object-->1 = single_object; | |
| many_flag=1; | |
| if (parser_trace>=3) | |
| { print " [Making new list from "; | |
| DefArt(single_object); print "]^"; | |
| } | |
| } | |
| dont_infer = 1; inferfrom=0; ! Don't print (inferences) | |
| jump ObjectList; ! And back around | |
| } | |
| wn--; ! Word marker back to first not-understood word | |
| ! **** (D) **** | |
| ! Happy or unhappy endings: | |
| .PassToken; | |
| if (many_flag==1) | |
| { single_object = 0; | |
| multi_context = token; | |
| } | |
| else | |
| { if (indef_mode==1 && indef_type & PLURAL_BIT ~= 0) | |
| { if (indef_wanted<100 && indef_wanted>1) | |
| { multi_had=1; multi_wanted=indef_wanted; | |
| etype=TOOFEW_PE; | |
| jump FailToken; | |
| } | |
| } | |
| } | |
| results-->(parameters+2) = single_object; | |
| parameters++; | |
| pattern-->pcount = single_object; | |
| return 1; | |
| .FailToken; | |
| ! If we were only guessing about it being a plural, try again but only | |
| ! allowing singulars (so that words like "six" are not swallowed up as | |
| ! Descriptors) | |
| if (allow_plurals==1 && indef_guess_p==1) | |
| { allow_plurals=0; wn=desc_wn; jump TryAgain; | |
| } | |
| return 0; | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! NounDomain does the most substantial part of parsing an object name. | |
| ! | |
| ! It is given two "domains" - usually a location and then the actor who is | |
| ! looking - and a context (i.e. token type), 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, | |
| ! REPARSE_CODE 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. | |
| ! | |
| ! In the case when it returns 1<k<REPARSE_CODE, it also sets the variable | |
| ! length_of_noun to the number of words in the input text matched to the | |
| ! noun. | |
| ! In the case k=1, the multiple objects are added to multiple_object by | |
| ! hand (not by MultiAdd, because we want to allow duplicates). | |
| ! ---------------------------------------------------------------------------- | |
| [ NounDomain domain1 domain2 context first_word i j k l oldw | |
| answer_words marker; | |
| if (parser_trace>=4) print " [NounDomain called at word ", wn, "^"; | |
| match_length=0; number_matched=0; match_from=wn; placed_in_flag=0; | |
| SearchScope(domain1, domain2, context); | |
| if (parser_trace>=4) print " [ND made ", number_matched, " matches]^"; | |
| 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) { 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, | |
| ! (unless what does follow is just a full stop, and or comma) | |
| if (wn<=num_words) | |
| { i=NextWord(); wn--; | |
| if ((i~='and' or comma_word or 'then') | |
| && (i~='but' or 'except')) | |
| { if (lookahead==8) rfalse; | |
| if (lookahead>8) | |
| { if (lookahead~=AdjectiveWord()) | |
| { wn--; if (parser_trace>=3) | |
| print " [ND failed at lookahead at word ", wn, "]^"; | |
| rfalse; | |
| } | |
| wn--; | |
| } | |
| } | |
| } | |
| } | |
| ! Now look for a good choice, if there's more than one choice... | |
| number_of_classes=0; | |
| if (number_matched==1) i=match_list-->0; | |
| if (number_matched>1) | |
| { i=Adjudicate(context); | |
| if (i==-1) rfalse; | |
| if (i==1) rtrue; ! Adjudicate has made a multiple | |
| ! object, and we pass it on | |
| } | |
| ! 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. | |
| ! (Except, we don't note which of a pile of identical objects.) | |
| if (i~=0) | |
| { if (dont_infer==1) return i; | |
| if (inferfrom==0) inferfrom=pcount; | |
| pattern-->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 scope, 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, using the equivalence classes as worked | |
| ! out by Adjudicate() so as not to repeat ourselves on plural objects... | |
| if (context==6) print "Who"; else print "Which"; | |
| print " do you mean, "; | |
| j=number_of_classes; marker=0; | |
| for (i=1:i<=number_of_classes:i++) | |
| { | |
| while (((match_classes-->marker) ~= i) | |
| && ((match_classes-->marker) ~= -i)) marker++; | |
| k=match_list-->marker; | |
| if (match_classes-->marker > 0) DefArt(k); else InDefArt(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", because that does something too clever here to do | |
| ! later on: | |
| if ((first_word=='all' or 'both' or 'everything') | |
| || (first_word=='every' or 'each')) | |
| { | |
| if (context>=2 && context<=5) | |
| { l=multiple_object-->0; | |
| for (i=0:i<number_matched && l+i<63:i++) | |
| { k=match_list-->i; | |
| multiple_object-->(i+1+l) = k; | |
| } | |
| multiple_object-->0 = i+l; | |
| rtrue; | |
| } | |
| 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->#dict_par1; | |
| if (0~=j&1) | |
| { Copy(buffer, buffer2); | |
| Copy(parse, parse2); | |
| return REPARSE_CODE; | |
| } | |
| ! 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; | |
| parse->1 = answer_words+oldw; | |
| for (k=oldw+answer_words : k>match_from : k--) | |
| MoveWord(k, parse, k-answer_words); | |
| for (k=1:k<=answer_words:k++) | |
| MoveWord(match_from+k-1, parse2, k); | |
| ! Having reconstructed the input, we warn the parser accordingly | |
| ! and get out. | |
| return REPARSE_CODE; | |
| ! 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->#dict_par1; | |
| if (0~=j&1) | |
| { Copy(buffer, buffer2); | |
| Copy(parse, parse2); | |
| return REPARSE_CODE; | |
| } | |
| ! ...but if we have a genuine answer, then we adjoin the words | |
| ! typed onto the expression. But if we've just inferred something | |
| ! which wasn't actually there, we must adjoin that as well. (Note | |
| ! the sneaky use of "it" to match an object inferred this time round.) | |
| oldw=parse->1; | |
| if (inferfrom==0) | |
| for (k=1:k<=answer_words:k++) | |
| MoveWord(oldw+k, parse2, k); | |
| else | |
| { j=pcount-inferfrom; | |
| for (k=1:k<=answer_words:k++) | |
| MoveWord(oldw+k+j, parse2, k); | |
| for (j=inferfrom:j<pcount:j++) | |
| { if (pattern-->j >= 2 && pattern-->j < REPARSE_CODE) | |
| { parse2-->1 = 'it'; itobj = pattern-->j; | |
| } | |
| else parse2-->1 = AdjectiveAddress((pattern-->j) - REPARSE_CODE); | |
| MoveWord(oldw+1+j-inferfrom, parse2, 1); | |
| answer_words++; | |
| } | |
| } | |
| parse->1 = answer_words+oldw; | |
| ! And go back to the parser. | |
| return REPARSE_CODE; | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! 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; | |
| ! | |
| ! at this stage, we work out whether the objects are distinguishable from | |
| ! each other or not: if they are all indistinguishable from each other, | |
| ! then choose one, it doesn't matter which; | |
| ! | |
| ! otherwise, 0 (meaning, unable to decide) is returned (but remember that | |
| ! the equivalence classes we've just worked out will be needed by other | |
| ! routines to clear up this mess, so we can't economise on working them | |
| ! out). | |
| ! | |
| ! Returns -1 if an error occurred | |
| ! ---------------------------------------------------------------------------- | |
| [ Adjudicate context i j k good_ones last n ultimate flag offset; | |
| if (parser_trace>=4) | |
| print " [Adjudicating match list of size ", number_matched, "^"; | |
| j=number_matched-1; good_ones=0; last=match_list-->0; | |
| for (i=0:i<=j:i++) | |
| { n=match_list-->i; | |
| if (n hasnt concealed) | |
| { ultimate=n; | |
| do | |
| ultimate=parent(ultimate); | |
| until (ultimate==actors_location or actor or 0); | |
| if (context==0 && ultimate==actors_location && | |
| (token_was==0 || UserFilter(n)==1)) { good_ones++; last=n; } | |
| if (context==1 && parent(n)==actor) { good_ones++; last=n; } | |
| if (context==2 && ultimate==actors_location) | |
| { good_ones++; last=n; } | |
| if (context==3 && parent(n)==actor) { good_ones++; last=n; } | |
| if (context==4 or 5) | |
| { if (advance_warning==-1) | |
| { if (parent(n)==actor) { good_ones++; last=n; } | |
| } | |
| else | |
| { if (context==4 && parent(n)==actor && n~=advance_warning) | |
| { good_ones++; last=n; } | |
| if (context==5 && parent(n)==actor && n in advance_warning) | |
| { good_ones++; last=n; } | |
| } | |
| } | |
| if (context==6 && CreatureTest(n)==1) { good_ones++; last=n; } | |
| } | |
| } | |
| if (good_ones==1) return last; | |
| ! If there is ambiguity about what was typed, but it definitely wasn't | |
| ! animate as required, then return anything; higher up in the parser | |
| ! a suitable error will be given. (This prevents a question being asked.) | |
| ! | |
| if (context==6 && good_ones==0) return match_list-->0; | |
| if (indef_mode==1 && indef_type & PLURAL_BIT ~= 0) | |
| { if (context<2 || context>5) { etype=MULTI_PE; return -1; } | |
| i=0; number_of_classes=1; offset=multiple_object-->0; | |
| for (j=BestGuess():j~=-1 && i<indef_wanted | |
| && i+offset<63:j=BestGuess()) | |
| { flag=0; | |
| if (j hasnt concealed && j hasnt worn) flag=1; | |
| if (context==3 or 4 && parent(j)~=actor) flag=0; | |
| k=ChooseObjects(j,flag); | |
| if (k==1) flag=1; else { if (k==2) flag=0; } | |
| if (flag==1) | |
| { i++; multiple_object-->(i+offset) = j; | |
| if (parser_trace>=4) | |
| print " Accepting it^"; | |
| } | |
| else | |
| { if (parser_trace>=4) | |
| print " Rejecting it^"; | |
| } | |
| } | |
| if (i<indef_wanted && indef_wanted<100) | |
| { etype=TOOFEW_PE; multi_wanted=indef_wanted; | |
| multi_had=multiple_object-->0; | |
| return -1; | |
| } | |
| multiple_object-->0 = i+offset; | |
| multi_context=context; | |
| if (parser_trace>=4) | |
| print " Made multiple object of size ", i, "]^"; | |
| return 1; | |
| } | |
| for (i=0:i<number_matched:i++) match_classes-->i=0; | |
| n=1; | |
| for (i=0:i<number_matched:i++) | |
| if (match_classes-->i==0) | |
| { match_classes-->i=n++; flag=0; | |
| for (j=i+1:j<number_matched:j++) | |
| if (match_classes-->j==0 | |
| && Identical(match_list-->i, match_list-->j)==1) | |
| { flag=1; | |
| match_classes-->j=match_classes-->i; | |
| } | |
| if (flag==1) match_classes-->i = 1-n; | |
| } | |
| n--; | |
| if (parser_trace>=4) | |
| { print " Difficult adjudication with ", n, " equivalence classes:^"; | |
| for (i=0:i<number_matched:i++) | |
| { print " "; CDefArt(match_list-->i); | |
| print " (", match_list-->i, ") --- ",match_classes-->i, "^"; | |
| } | |
| } | |
| number_of_classes = n; | |
| if (n>1 && indef_mode==0) | |
| { j=0; good_ones=0; | |
| for (i=0:i<number_matched:i++) | |
| { k=ChooseObjects(match_list-->i,2); | |
| if (k==j) good_ones++; | |
| if (k>j) { j=k; good_ones=1; last=match_list-->i; } | |
| } | |
| if (good_ones==1) | |
| { if (parser_trace>=4) | |
| print " ChooseObjects picked a best.]^"; | |
| return last; | |
| } | |
| if (parser_trace>=4) | |
| print " Unable to decide: it's a draw.]^"; | |
| return 0; | |
| } | |
| ! When the player is really vague, or there's a single collection of | |
| ! indistinguishable objects to choose from, choose the one the player | |
| ! most recently acquired, or if the player has none of them, then | |
| ! the one most recently put where it is. | |
| if (indef_mode==0) indef_type=0; | |
| if (n==1) dont_infer = 1; | |
| return BestGuess(); | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! ReviseMulti revises the multiple object which already exists, in the | |
| ! light of information which has come along since then (i.e., the second | |
| ! parameter). It returns a parser error number, or else 0 if all is well. | |
| ! This only ever throws things out, never adds new ones. | |
| ! ---------------------------------------------------------------------------- | |
| [ ReviseMulti second_p i low; | |
| if (parser_trace>=4) | |
| print " Revising multiple object list of size ", multiple_object-->0, | |
| " with 2nd ", object second_p, "^"; | |
| if (multi_context==4 or 5) | |
| { for (i=1, low=0:i<=multiple_object-->0:i++) | |
| { if ( (multi_context==4 && multiple_object-->i ~= second_p) | |
| || (multi_context==5 && multiple_object-->i in second_p)) | |
| { low++; multiple_object-->low = multiple_object-->i; | |
| } | |
| } | |
| multiple_object-->0 = low; | |
| } | |
| if (multi_context==2) | |
| { for (i=1, low=0:i<=multiple_object-->0:i++) | |
| if (parent(multiple_object-->i)==parent(actor)) low++; | |
| if (parser_trace>=4) | |
| print " Token 2 plural case: number with actor ", low, "^"; | |
| if (take_all_rule==2 || low>0) | |
| { for (i=1, low=0:i<=multiple_object-->0:i++) | |
| { if (parent(multiple_object-->i)==parent(actor)) | |
| { low++; multiple_object-->low = multiple_object-->i; | |
| } | |
| } | |
| multiple_object-->0 = low; | |
| } | |
| } | |
| i=multiple_object-->0; | |
| if (parser_trace>=4) | |
| print " Done: new size ", i, "^"; | |
| if (i==0) return NOTHING_PE; | |
| return 0; | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! ScoreMatchL scores the match list for quality in terms of what the | |
| ! player has vaguely asked for. Points are awarded for conforming with | |
| ! requirements like "my", and so on. If the score is less than the | |
| ! threshold, block out the entry to -1. | |
| ! The scores are put in the match_classes array, which we can safely | |
| ! reuse by now. | |
| ! ---------------------------------------------------------------------------- | |
| [ ScoreMatchL its_owner its_score obj i threshold a_s l_s; | |
| if (indef_type & OTHER_BIT ~= 0) threshold=40; | |
| if (indef_type & MY_BIT ~= 0) threshold=threshold+40; | |
| if (indef_type & THAT_BIT ~= 0) threshold=threshold+40; | |
| if (indef_type & ITS_BIT ~= 0) threshold=threshold+40; | |
| if (indef_type & HIS_BIT ~= 0) threshold=threshold+40; | |
| if (indef_type & LIT_BIT ~= 0) threshold=threshold+40; | |
| if (indef_type & UNLIT_BIT ~= 0) threshold=threshold+40; | |
| if (parser_trace>=4) print " Scoring match list with type ", indef_type, | |
| ", threshold ", threshold, ":^"; | |
| a_s = 30; l_s = 20; | |
| if (action_to_be == ##Take or ##Remove) { a_s=20; l_s=30; } | |
| for (i=0:i<number_matched:i++) | |
| { obj = match_list-->i; its_owner = parent(obj); its_score=0; | |
| if (its_owner==actor) its_score=a_s; | |
| if (its_owner==actors_location) its_score=l_s; | |
| if (its_score==0 && its_owner~=compass) its_score=10; | |
| if (indef_type & OTHER_BIT ~=0 | |
| && obj~=itobj or himobj or herobj) | |
| its_score=its_score+40; | |
| if (indef_type & MY_BIT ~=0 && its_owner==actor) | |
| its_score=its_score+40; | |
| if (indef_type & THAT_BIT ~=0 && its_owner==actors_location) | |
| its_score=its_score+40; | |
| if (indef_type & LIT_BIT ~=0 && obj has light) | |
| its_score=its_score+40; | |
| if (indef_type & UNLIT_BIT ~=0 && obj hasnt light) | |
| its_score=its_score+40; | |
| if (indef_type & ITS_BIT ~=0 && its_owner==itobj) | |
| its_score=its_score+40; | |
| if (indef_type & HIS_BIT ~=0 && its_owner has animate | |
| && GetGender(its_owner)==1) | |
| its_score=its_score+40; | |
| its_score=its_score + ChooseObjects(obj,2); | |
| if (its_score < threshold) match_list-->i=-1; | |
| else | |
| { match_classes-->i=its_score; | |
| if (parser_trace >= 4) | |
| { print " "; CDefArt(match_list-->i); | |
| print " (", match_list-->i, ") in "; DefArt(its_owner); | |
| print " scores ",its_score, "^"; | |
| } | |
| } | |
| } | |
| number_of_classes=2; | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! BestGuess makes the best guess it can out of the match list, assuming that | |
| ! everything in the match list is textually as good as everything else; | |
| ! however it ignores items marked as -1, and so marks anything it chooses. | |
| ! It returns -1 if there are no possible choices. | |
| ! ---------------------------------------------------------------------------- | |
| [ BestGuess earliest its_score best i; | |
| if (number_of_classes~=1) ScoreMatchL(); | |
| earliest=0; best=-1; | |
| for (i=0:i<number_matched:i++) | |
| { if (match_list-->i >= 0) | |
| { its_score=match_classes-->i; | |
| if (its_score>best) { best=its_score; earliest=i; } | |
| } | |
| } | |
| if (parser_trace>=4) | |
| { if (best<0) | |
| print " Best guess ran out of choices^"; | |
| else | |
| { print " Best guess "; DefArt(match_list-->earliest); | |
| print " (", match_list-->earliest, ")^"; | |
| } | |
| } | |
| if (best<0) return -1; | |
| i=match_list-->earliest; | |
| match_list-->earliest=-1; | |
| return i; | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! Identical decides whether or not two objects can be distinguished from | |
| ! each other by anything the player can type. If not, it returns true. | |
| ! ---------------------------------------------------------------------------- | |
| [ Identical o1 o2 p1 p2 n1 n2 i j flag; | |
| ! print "Id on ", o1, " (", object o1, ") and ", o2, " (", object o2, ")^"; | |
| if (o1==o2) rtrue; ! This should never happen, but to be on the safe side | |
| if (o1==0 || o2==0) rfalse; ! Similarly | |
| if (parent(o1)==compass || parent(o2)==compass) rfalse; ! Saves time | |
| ! What complicates things is that o1 or o2 might have a parsing routine, | |
| ! so the parser can't know from here whether they are or aren't the same. | |
| ! If they have different parsing routines, we simply assume they're | |
| ! different. If they have the same routine (which they probably got from | |
| ! a class definition) then the decision process is as follows: | |
| ! | |
| ! the routine is called (with self being o1, not that it matters) | |
| ! with noun and second being set to o1 and o2, and action being set | |
| ! to the fake action TheSame. If it returns -1, they are found | |
| ! identical; if -2, different; and if >=0, then the usual method | |
| ! is used instead. | |
| if (o1.parse_name~=0 || o2.parse_name~=0) | |
| { if (o1.parse_name ~= o2.parse_name) rfalse; | |
| parser_action=##TheSame; parser_one=o1; parser_two=o2; | |
| j=wn; i=RunRoutines(o1,parse_name); wn=j; | |
| if (i==-1) rtrue; if (i==-2) rfalse; | |
| } | |
| ! This is the default algorithm: do they have the same words in their | |
| ! "name" (i.e. property no. 1) properties. (Note that the following allows | |
| ! for repeated words and words in different orders.) | |
| p1 = o1.&1; n1 = (o1.#1)/2; | |
| p2 = o2.&1; n2 = (o2.#1)/2; | |
| ! for (i=0:i<n1:i++) { print_addr p1-->i; print " "; } new_line; | |
| ! for (i=0:i<n2:i++) { print_addr p2-->i; print " "; } new_line; | |
| for (i=0:i<n1:i++) | |
| { flag=0; | |
| for (j=0:j<n2:j++) | |
| if (p1-->i == p2-->j) flag=1; | |
| if (flag==0) rfalse; | |
| } | |
| for (j=0:j<n2:j++) | |
| { flag=0; | |
| for (i=0:i<n1:i++) | |
| if (p1-->i == p2-->j) flag=1; | |
| if (flag==0) rfalse; | |
| } | |
| ! print "Which are identical!^"; | |
| rtrue; | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! 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 may only preserve the first six characters | |
| ! of a word (in a V3 game), we have to hand-code the longer words needed. | |
| ! | |
| ! (Recall that pattern entries are 0 for "multiple object", 1 for "special | |
| ! word", 2 to 999 are object numbers and REPARSE_CODE+n means the preposition n) | |
| ! ---------------------------------------------------------------------------- | |
| [ PrintCommand from emptyf i j k f; | |
| if (from==0) | |
| { i=verb_word; from=1; f=1; | |
| #IFV3; | |
| if (i=='inventory') { print "take an inventory"; jump VerbPrinted; } | |
| if (i=='examine') { print "examine"; jump VerbPrinted; } | |
| if (i=='discard') { print "discard"; jump VerbPrinted; } | |
| if (i=='swallow') { print "swallow"; jump VerbPrinted; } | |
| if (i=='embrace') { print "embrace"; jump VerbPrinted; } | |
| if (i=='squeeze') { print "squeeze"; jump VerbPrinted; } | |
| if (i=='purchase') { print "purchase"; jump VerbPrinted; } | |
| if (i=='unscrew') { print "unscrew"; jump VerbPrinted; } | |
| if (i=='describe') { print "describe"; jump VerbPrinted; } | |
| if (i=='uncover') { print "uncover"; jump VerbPrinted; } | |
| if (i=='discard') { print "discard"; jump VerbPrinted; } | |
| if (i=='transfer') { print "transfer"; jump VerbPrinted; } | |
| #ENDIF; | |
| if (i==#n$l) { print "look"; jump VerbPrinted; } | |
| if (i==#n$z) { print "wait"; jump VerbPrinted; } | |
| if (i==#n$x) { print "examine"; jump VerbPrinted; } | |
| if (i==#n$i or 'inv') { print "inventory"; jump VerbPrinted; } | |
| if (PrintVerb(i)==0) print_addr i; | |
| } | |
| .VerbPrinted; | |
| j=pcount-emptyf; | |
| for (k=from:k<=j:k++) | |
| { 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>=REPARSE_CODE) | |
| { i=AdjectiveAddress(i-REPARSE_CODE); | |
| #IFV3; | |
| if (i=='against') { print "against"; jump TokenPrinted; } | |
| #ENDIF; | |
| print_addr i; | |
| } | |
| else DefArt(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 "you don't need to refer to that in this game" rather than | |
| ! "no such thing" or "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; | |
| if (scope_token~=0) { scope_error = scope_token; return ASKSCOPE_PE; } | |
| wn--; w=NextWord(); | |
| e=CANTSEE_PE; | |
| if (w==vague_word) e=ITGONE_PE; | |
| i=parent(actor); | |
| if (i has visited && Refers(i,w)==1) e=SCENERY_PE; | |
| if (etype>e) return etype; | |
| return e; | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! 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:j<=i:j++) | |
| if (o==multiple_object-->j) | |
| rtrue; | |
| i++; | |
| multiple_object-->i = o; | |
| multiple_object-->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:j<=i:j++) | |
| if (o==multiple_object-->j) | |
| { for (k=j:k<=i:k++) | |
| multiple_object-->k = multiple_object-->(k+1); | |
| multiple_object-->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:j<=i:j++) | |
| { o=multiple_object-->j; | |
| if (o hasnt attr) { MultiSub(o); jump Mfiltl; } | |
| } | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! The UserFilter routine consults the user's filter (or checks on attribute) | |
| ! to see what already-accepted nouns are acceptable | |
| ! ---------------------------------------------------------------------------- | |
| [ UserFilter obj; | |
| if (token_was>=128) | |
| { if (obj has (token_was-128)) rtrue; | |
| rfalse; | |
| } | |
| noun=obj; | |
| return (indirect(#preactions_table-->(token_was-16))); | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! 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; | |
| parse-->x++ = b2-->y++; | |
| parse-->x = b2-->y; | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! SearchScope domain1 domain2 context | |
| ! | |
| ! Works out what objects are in scope (possibly asking an outside routine), | |
| ! but does not look at anything the player has typed. | |
| ! ---------------------------------------------------------------------------- | |
| [ SearchScope domain1 domain2 context i; | |
| i=0; | |
| ! Everything is in scope to the debugging commands | |
| #ifdef DEBUG; | |
| if (scope_reason==PARSING_REASON | |
| && (verb_word == 'purloin' or 'tree' or 'abstract' | |
| || verb_word == 'gonear' or 'scope')) | |
| { for (i=selfobj+1:i<=top_object:i++) PlaceInScope(i); | |
| rtrue; | |
| } | |
| #endif; | |
| ! First, a scope token gets priority here: | |
| if (scope_token ~= 0) | |
| { scope_stage=2; | |
| if (indirect(scope_token)~=0) rtrue; | |
| } | |
| ! Next, call any user-supplied routine adding things to the scope, | |
| ! which may circumvent the usual routines altogether if they return true: | |
| if (actor==domain1 or domain2 && InScope(actor)~=0) rtrue; | |
| ! Pick up everything in the location except the actor's possessions; | |
| ! then go through those. (This ensures the actor's possessions are in | |
| ! scope even in Darkness.) | |
| if (context==5 && advance_warning ~= -1) | |
| { if (IsSeeThrough(advance_warning)==1) | |
| ScopeWithin(advance_warning, 0, context); | |
| } | |
| else | |
| { ScopeWithin(domain1, domain2, context); | |
| ScopeWithin(domain2,0,context); | |
| } | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! IsSeeThrough is used at various places: roughly speaking, it determines | |
| ! whether o being in scope means that the contents of o are in scope. | |
| ! ---------------------------------------------------------------------------- | |
| [ IsSeeThrough o; | |
| if (o has supporter | |
| || (o has transparent) | |
| || (o has container && o has open)) | |
| rtrue; | |
| rfalse; | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! PlaceInScope is provided for routines outside the library, and is not | |
| ! called within the parser (except for debugging purposes). | |
| ! ---------------------------------------------------------------------------- | |
| [ PlaceInScope thing; | |
| if (scope_reason~=PARSING_REASON or TALKING_REASON) | |
| { DoScopeAction(thing); rtrue; } | |
| wn=match_from; TryGivenObject(thing); placed_in_flag=1; | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! DoScopeAction | |
| ! ---------------------------------------------------------------------------- | |
| [ DoScopeAction thing s p1; | |
| s = scope_reason; p1=parser_one; | |
| if (parser_trace>=5) | |
| { print "[DSA on ", (the) thing, " with reason = ", scope_reason, | |
| " p1 = ", parser_one, " p2 = ", parser_two, "]^"; | |
| } | |
| switch(scope_reason) | |
| { REACT_BEFORE_REASON: | |
| if (thing.react_before==0 or NULL) return; | |
| if (parser_trace>=2) | |
| { print "[Considering react_before for ", (the) thing, "]^"; } | |
| if (parser_one==0) parser_one = RunRoutines(thing,react_before); | |
| REACT_AFTER_REASON: | |
| if (thing.react_after==0 or NULL) return; | |
| if (parser_trace>=2) | |
| { print "[Considering react_after for ", (the) thing, "]^"; } | |
| if (parser_one==0) parser_one = RunRoutines(thing,react_after); | |
| EACH_TURN_REASON: | |
| if (thing.&each_turn==0) return; | |
| if (parser_trace>=2) | |
| { print "[Considering each_turn for ", (the) thing, "]^"; } | |
| PrintOrRun(thing, each_turn); | |
| TESTSCOPE_REASON: | |
| if (thing==parser_one) parser_two = 1; | |
| LOOPOVERSCOPE_REASON: | |
| indirect(parser_one,thing); parser_one=p1; | |
| } | |
| scope_reason = s; | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! ScopeWithin looks for objects in the domain which make textual sense | |
| ! and puts them in the match list. (However, it does not recurse through | |
| ! the second argument.) | |
| ! ---------------------------------------------------------------------------- | |
| [ ScopeWithin domain nosearch context; | |
| if (domain==0) rtrue; | |
| ! multiexcept doesn't have second parameter in scope | |
| if (context==4 && domain==advance_warning) rtrue; | |
| ! Special rule: the directions (interpreted as the 12 walls of a room) are | |
| ! always in context. (So, e.g., "examine north wall" is always legal.) | |
| ! (Unless we're parsing something like "all", because it would just slow | |
| ! things down then, or unless the context is "creature".) | |
| if (indef_mode==0 && domain==actors_location | |
| && scope_reason==PARSING_REASON && context~=6) ScopeWithin(compass); | |
| ! Look through the objects in the domain | |
| objectloop (domain in domain) ScopeWithin_O(domain, nosearch, context); | |
| ]; | |
| [ ScopeWithin_O domain nosearch context i ad n; | |
| ! If the scope reason is unusual, don't parse. | |
| if (scope_reason~=PARSING_REASON or TALKING_REASON) | |
| { DoScopeAction(domain); jump DontAccept; } | |
| ! If we're beyond the end of the user's typing, accept everything | |
| ! (NounDomain will sort things out) | |
| if (match_from > num_words) | |
| { i=parser_trace; parser_trace=0; | |
| if (i>=5) { print " Out of text: matching "; DefArt(domain); | |
| new_line; } | |
| MakeMatch(domain,1); | |
| parser_trace=i; 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=NounWord(); | |
| if (i==1 && itobj==domain) MakeMatch(itobj,1); | |
| if (i==2 && himobj==domain) MakeMatch(himobj,1); | |
| if (i==3 && herobj==domain) MakeMatch(herobj,1); | |
| if (i==4 && player==domain) MakeMatch(player,1); | |
| ! Construing the current word as the start of a noun, can it refer to the | |
| ! object? | |
| wn--; TryGivenObject(domain); | |
| .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. | |
| ! | |
| ! Notice that the parser can see "into" anything flagged as | |
| ! transparent - such as a dwarf whose sword you can get at. | |
| if (child(domain)~=0 && domain ~= nosearch && IsSeeThrough(domain)==1) | |
| ScopeWithin(domain,0,context); | |
| ! Drag any extras into context | |
| ad = domain.&add_to_scope; | |
| if (ad ~= 0) | |
| { if (UnsignedCompare(ad-->0,top_object) > 0) | |
| { ats_flag = 2+context; | |
| RunRoutines(domain, add_to_scope); | |
| ats_flag = 0; | |
| } | |
| else | |
| { n=domain.#add_to_scope; | |
| for (i=0:(2*i)<n:i++) | |
| ScopeWithin_O(ad-->i,0,context); | |
| } | |
| } | |
| ]; | |
| [ AddToScope obj; | |
| if (ats_flag>=2) | |
| ScopeWithin_O(obj,0,ats_flag-2); | |
| if (ats_flag==1) | |
| { if (HasLightSource(obj)==1) ats_hls = 1; | |
| } | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! 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. | |
| ! | |
| ! We never match anything twice, and keep at most 64 equally good items. | |
| ! ---------------------------------------------------------------------------- | |
| [ MakeMatch obj quality i; | |
| if (parser_trace>=5) print " Match with quality ",quality,"^"; | |
| if (token_was~=0 && UserFilter(obj)==0) | |
| { if (parser_trace>=5) print " Match filtered out^"; | |
| rtrue; | |
| } | |
| if (quality < match_length) rtrue; | |
| if (quality > match_length) { match_length=quality; number_matched=0; } | |
| else | |
| { if (2*number_matched>=MATCH_LIST_SIZE) rtrue; | |
| for (i=0:i<number_matched:i++) | |
| if (match_list-->i==obj) rtrue; | |
| } | |
| match_list-->number_matched++ = obj; | |
| if (parser_trace>=5) print " Match added to list^"; | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! TryGivenObject tries to match as many words as possible in what has been | |
| ! typed to the given object, obj. If it manages any words matched at all, | |
| ! it calls MakeMatch to say so. There is no return value. | |
| ! ---------------------------------------------------------------------------- | |
| [ TryGivenObject obj threshold k w j; | |
| if (parser_trace>=5) | |
| { print " Trying "; DefArt(obj); | |
| print " (", obj, ") at word ", wn, "^"; | |
| } | |
| ! If input has run out and we're in indefinite mode, then always match, | |
| ! with only quality 0 (this saves time). | |
| if (indef_mode ~=0 && wn > parse->1) { MakeMatch(obj,0); rfalse; } | |
| ! Ask the object to parse itself if necessary, sitting up and taking notice | |
| ! if it says the plural was used: | |
| if (obj.parse_name~=0) | |
| { parser_action=-1; j=wn; | |
| k=RunRoutines(obj,parse_name); | |
| if (k>0) | |
| { wn=j+k; | |
| .MMbyPN; | |
| if (parser_action == ##PluralFound) | |
| { if (allow_plurals == 0) jump NoWordsMatch; | |
| if (indef_mode==0) | |
| { indef_mode=1; indef_type=0; indef_wanted=0; } | |
| indef_type=indef_type | PLURAL_BIT; | |
| if (indef_wanted==0) indef_wanted=100; | |
| } | |
| MakeMatch(obj,k); rfalse; | |
| } | |
| if (k==0) jump NoWordsMatch; | |
| } | |
| ! The default algorithm is simply to count up how many words pass the | |
| ! Refers test: | |
| w = NounWord(); | |
| if ((w==1 && obj==itobj) | |
| || (w==2 && obj==himobj) | |
| || (w==3 && obj==herobj) | |
| || (w==4 && obj==player)) { MakeMatch(obj,1); rfalse; } | |
| j=--wn; | |
| threshold = ParseNoun(obj); | |
| if (threshold>=0 && parser_trace>=5) | |
| print " ParseNoun returned ", threshold, "^"; | |
| if (threshold<0) wn++; | |
| if (threshold>0) { k=threshold; jump MMbyPN; } | |
| if (threshold==0 || Refers(obj,w)==0) | |
| { .NoWordsMatch; | |
| if (indef_mode~=0) MakeMatch(obj,0); | |
| rfalse; | |
| } | |
| if (threshold<0) | |
| { threshold=1; while (0~=Refers(obj,NextWord())) threshold++; | |
| } | |
| MakeMatch(obj,threshold); | |
| if (parser_trace>=5) print " 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; | |
| if (obj==0) rfalse; | |
| k=obj.&1; l=(obj.#1)/2-1; | |
| for (m=0:m<=l:m++) | |
| if (wd==k-->m) rtrue; | |
| rfalse; | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! NounWord (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", | |
| ! 4 if "me", "myself", "self" | |
| ! 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. | |
| ! ---------------------------------------------------------------------------- | |
| [ NounWord i; | |
| i=NextWord(); | |
| if (i=='it' or 'them') return 1; | |
| if (i=='him') return 2; | |
| if (i=='her') return 3; | |
| if (i=='me' or 'myself' or 'self') return 4; | |
| if (i==0) rfalse; | |
| if ((i->#dict_par1)&128 == 0) rfalse; | |
| return i; | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! AdjectiveWord (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. | |
| ! ---------------------------------------------------------------------------- | |
| [ AdjectiveWord i j; | |
| j=NextWord(); | |
| if (j==0) rfalse; | |
| i=j->#dict_par1; | |
| if (i&8 == 0) rfalse; | |
| return(j->#dict_par3); | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! 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; | |
| for (::) | |
| { if (number==m-->1) return m-->0; | |
| 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. | |
| ! | |
| ! NextWordStopped does the same, but returns -1 when input has run out | |
| ! ---------------------------------------------------------------------------- | |
| [ NextWord i j k; | |
| if (wn > parse->1) { wn++; rfalse; } | |
| i=wn*2-1; wn++; | |
| j=parse-->i; | |
| if (j==0) | |
| { k=wn*4-3; i=buffer->(parse->k); | |
| if (i==',') j=comma_word; | |
| if (i=='.') j='then'; | |
| } | |
| return j; | |
| ]; | |
| [ NextWordStopped; | |
| if (wn > parse->1) { wn++; return -1; } | |
| return NextWord(); | |
| ]; | |
| [ WordAddress wordnum; | |
| return buffer + parse->(wordnum*4+1); | |
| ]; | |
| [ WordLength wordnum; | |
| return parse->(wordnum*4); | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! 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 | |
| ! | |
| ! -1000 if it is 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.) | |
| ! | |
| ! Using NumberWord, it also catches "one" up to "twenty". | |
| ! | |
| ! Note that a game can provide a ParseNumber routine which takes priority, | |
| ! to enable parsing of odder numbers ("x45y12", say). | |
| ! ---------------------------------------------------------------------------- | |
| [ TryNumber wordnum i j c num len mul tot d digit; | |
| i=wn; wn=wordnum; j=NextWord(); wn=i; | |
| j=NumberWord(j); if (j>=1) return j; | |
| i=wordnum*4+1; j=parse->i; num=j+buffer; len=parse->(i-1); | |
| tot=ParseNumber(num, len); if (tot~=0) return tot; | |
| 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:c<=len:c++) | |
| { 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; } | |
| return -1000; | |
| .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 && i~=player) | |
| { 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; | |
| if (person hasnt female) rtrue; | |
| rfalse; | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! For copying buffers | |
| ! ---------------------------------------------------------------------------- | |
| [ Copy bto bfrom i size; | |
| size=bto->0; | |
| for (i=1:i<=size:i++) bto->i=bfrom->i; | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! Useful routine: unsigned comparison (for addresses in Z-machine) | |
| ! Returns 1 if x>y, 0 if x=y, -1 if x<y | |
| ! ZRegion(addr) returns 1 if object num, 2 if in code area, 3 if in strings | |
| ! ---------------------------------------------------------------------------- | |
| [ UnsignedCompare x y u v; | |
| if (x==y) return 0; | |
| if (x<0 && y>=0) return 1; | |
| if (x>=0 && y<0) return -1; | |
| u = x&$7fff; v= y&$7fff; | |
| if (u>v) return 1; | |
| return -1; | |
| ]; | |
| [ ZRegion addr; | |
| if (addr==0) return 0; | |
| if (addr>=1 && addr<=top_object) return 1; | |
| if (UnsignedCompare(addr, #strings_offset)>=0) return 3; | |
| if (UnsignedCompare(addr, #code_offset)>=0) return 2; | |
| return 0; | |
| ]; | |
| [ PrintOrRun obj prop flag a; | |
| if (obj. | |
| if (obj.prop==NULL) rfalse; | |
| a=ZRegion(obj.prop); | |
| if (a==0 or 1) "** Expected string/routine as prop value! **"; | |
| if (a==3) { print_paddr obj.prop; if (flag==0) new_line; rtrue; } | |
| return RunRoutines(obj,prop); | |
| ]; | |
| [ ValueOrRun obj prop a; | |
| a=ZRegion(obj.prop); | |
| if (a==2) return RunRoutines(obj,prop); | |
| return obj.prop; | |
| ]; | |
| [ RunRoutines obj prop i j k l m ssv; | |
| if (obj==thedark && prop~=initial) obj=real_location; | |
| if (obj.prop==NULL or 0) rfalse; | |
| #IFDEF DEBUG; | |
| if (debug_flag & 1 ~= 0 && prop~=short_name) | |
| { print "[Running "; | |
| if (prop==before) { print "before"; jump DebugPrt; } | |
| if (prop==after) { print "after"; jump DebugPrt; } | |
| if (prop==life) { print "life"; jump DebugPrt; } | |
| if (prop==each_turn) { print "each_turn"; jump DebugPrt; } | |
| if (prop==describe) { print "describe"; jump DebugPrt; } | |
| if (prop==initial) { print "initial"; jump DebugPrt; } | |
| if (prop==n_to) { print "n_to/door_to"; jump DebugPrt; } | |
| if (prop==s_to) { print "s_to/when_closed"; jump DebugPrt; } | |
| if (prop==e_to) { print "e_to/with_key"; jump DebugPrt; } | |
| if (prop==w_to) { print "w_to/door_dir"; jump DebugPrt; } | |
| if (prop==ne_to) { print "ne_to/react_before"; jump DebugPrt; } | |
| if (prop==nw_to) { print "nw_to/react_after"; jump DebugPrt; } | |
| if (prop==se_to) { print "se_to/add_to_scope"; jump DebugPrt; } | |
| if (prop==sw_to) { print "sw_to/list_together"; jump DebugPrt; } | |
| if (prop==u_to) { print "u_to/invent"; jump DebugPrt; } | |
| if (prop==d_to) { print "d_to/plural"; jump DebugPrt; } | |
| if (prop==in_to) { print "in_to/grammar"; jump DebugPrt; } | |
| if (prop==out_to) { print "out_to/orders"; jump DebugPrt; } | |
| if (prop==time_out) { print "daemon/time_out"; jump DebugPrt; } | |
| if (prop==parse_name) { print "parse_name"; jump DebugPrt; } | |
| print "property ",prop; | |
| .DebugPrt; | |
| print " for ", object obj,"]^"; | |
| } | |
| #ENDIF; | |
| j=obj.∝ k=obj. | |
| ssv=sw__var; | |
| if (prop==life) sw__var=reason_code; | |
| else sw__var=action; | |
| ! if (prop~=life or orders) | |
| ! { noun=inp1; second=inp2; | |
| ! } | |
| for (i=0:i<k/2:i++) | |
| { if (j-->i == NULL) { self=m; sw__var=ssv; rfalse; } | |
| l=ZRegion(j-->i); | |
| if (l==2) | |
| { l=indirect(j-->i); | |
| if (l~=0) { self=m; sw__var=ssv; return l; } | |
| } | |
| else | |
| { if (l==3) { print_paddr j-->i; new_line; } | |
| else print "** Entry in property list not routine or string **^"; | |
| } | |
| } | |
| self=m; sw__var=ssv; | |
| rfalse; | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
| ! End of the parser proper: the remaining routines are its front end. | |
| ! ---------------------------------------------------------------------------- | |
| [ DisplayStatus; | |
| if (the_time==NULL) | |
| { 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; | |
| ]; | |
| [ NotifyTheScore i; | |
| print "^[Your score has just gone "; | |
| if (last_score > score) { i=last_score-score; print "down"; } | |
| else { i=score-last_score; print "up"; } | |
| print " by "; EnglishNumber(i); print " point"; | |
| if (i>1) print "s"; print ".]^"; | |
| ]; | |
| [ PlayTheGame i j k l; | |
| standard_interpreter = $32-->0; | |
| player = selfobj; | |
| top_object = #largest_object-255; | |
| selfobj.capacity = MAX_CARRIED; | |
| j=Initialise(); | |
| last_score = score; | |
| move player to location; | |
| while (parent(location)~=0) location=parent(location); | |
| objectloop (i in player) give i moved ~concealed; | |
| if (j~=2) Banner(); | |
| lightflag=OffersLight(parent(player)); | |
| if (lightflag==0) { real_location=location; location=thedark; } | |
| <Look>; | |
| for (i=1:i<=100:i++) j=random(i); | |
| while (deadflag==0) | |
| { if (score ~= last_score) | |
| { if (notify_mode==1) NotifyTheScore(); last_score=score; } | |
| .late__error; | |
| inputobjs-->0 = 0; inputobjs-->1 = 0; | |
| inputobjs-->2 = 0; inputobjs-->3 = 0; meta=0; | |
| ! The Parser writes its results into inputobjs and meta, | |
| ! a flag indicating a "meta-verb". This can only be set for | |
| ! commands by the player, not for orders to others. | |
| Parser(inputobjs); | |
| noun=0; second=0; action=0; multiflag=0; | |
| onotheld_mode=notheld_mode; notheld_mode=0; | |
| ! The notheld_mode variables are for implicit taking. | |
| action=inputobjs-->0; | |
| ! Reverse "give fred biscuit" into "give biscuit to fred" | |
| if (action==##GiveR or ##ShowR) | |
| { i=inputobjs-->2; inputobjs-->2=inputobjs-->3; inputobjs-->3=i; | |
| if (action==##GiveR) action=##Give; else action=##Show; | |
| } | |
| ! Convert "P, tell me about X" to "ask P about X" | |
| if (action==##Tell && inputobjs-->2==player && actor~=player) | |
| { inputobjs-->2=actor; actor=player; action=##Ask; | |
| } | |
| ! Convert "ask P for X" to "P, give X to me" | |
| if (action==##AskFor && inputobjs-->2~=player && actor==player) | |
| { actor=inputobjs-->2; inputobjs-->2=inputobjs-->3; | |
| inputobjs-->3=player; action=##Give; | |
| } | |
| ! For old, obsolete code: special_word contains the topic word | |
| ! in conversation | |
| if (action==##Ask or ##Tell or ##Answer) | |
| special_word = special_number1; | |
| .begin__action; | |
| inp1=0; inp2=0; i=inputobjs-->1; | |
| if (i>=1) inp1=inputobjs-->2; | |
| if (i>=2) inp2=inputobjs-->3; | |
| ! inp1 and inp2 hold: object numbers, or 0 for "multiple object", | |
| ! or 1 for "a number or dictionary address" | |
| noun=inp1; second=inp2; | |
| if (inp1==1) noun=special_number1; | |
| if (inp2==1) | |
| { if (inp1==1) second=special_number2; | |
| else second=special_number1; | |
| } | |
| ! noun and second equal inp1 and inp2, except that in place of 1 | |
| ! they substitute the actual number or dictionary address | |
| if (actor~=player) | |
| { | |
| ! The player's "orders" property can refuse to allow conversation | |
| ! here, by returning true. If not, the order is sent to the | |
| ! other person's "orders" property. If that also returns false, | |
| ! then: if it was a misunderstood command anyway, it is converted | |
| ! to an Answer action (thus "floyd, grrr" ends up as | |
| ! "say grrr to floyd"). If it was a good command, it is finally | |
| ! offered to the Order: part of the other person's "life" | |
| ! property, the old-fashioned way of dealing with conversation. | |
| j=RunRoutines(player,orders); | |
| if (j==0) | |
| { j=RunRoutines(actor,orders); | |
| if (j==0) | |
| { if (action==##NotUnderstood) | |
| { inputobjs-->3=actor; actor=player; action=##Answer; | |
| jump begin__action; | |
| } | |
| if (RunLife(actor,##Order)==0) L__M(##Order,1,actor); | |
| } | |
| } | |
| jump turn__end; | |
| } | |
| ! So now we must have an ordinary parser-generated action, unless | |
| ! inp1 is a "multiple object", in which case we: (a) check the | |
| ! multiple list isn't empty; (b) warn the player if it has been | |
| ! cut short because of excessive size, and (c) generate a sequence | |
| ! of actions from the list (stopping on death or movement away). | |
| if (i==0 || inp1~=0) Process(); | |
| else | |
| { multiflag=1; | |
| j=multiple_object-->0; | |
| if (j==0) { L__M(##Miscellany,2); jump late__error; } | |
| if (toomany_flag==1) | |
| { toomany_flag=0; L__M(##Miscellany,1); } | |
| i=location; | |
| for (k=1:k<=j:k++) | |
| { if (deadflag~=0) break; | |
| if (location~=i) | |
| { print "(Since something dramatic has happened, \ | |
| your list of commands has been cut short.)^"; | |
| break; | |
| } | |
| l=multiple_object-->k; ResetVagueWords(l); | |
| PrintShortName(l); print ": "; | |
| inp1=l; noun=l; Process(); inp1=0; noun=0; | |
| } | |
| } | |
| .turn__end; | |
| ! No time passes if either (i) the verb was meta, or | |
| ! (ii) we've only had the implicit take before the "real" | |
| ! action to follow. | |
| if (notheld_mode==1) meta=1; | |
| if (deadflag==0 && meta==0) EndTurnSequence(); | |
| } | |
| if (deadflag~=2) AfterLife(); | |
| if (deadflag==0) jump late__error; | |
| print "^^ "; | |
| #IFV5; style bold; #ENDIF; | |
| print "***"; | |
| if (deadflag==1) L__M(##Miscellany,3); | |
| if (deadflag==2) L__M(##Miscellany,4); | |
| if (deadflag>2) { print " "; DeathMessage(); print " "; } | |
| print "***"; | |
| #IFV5; style roman; #ENDIF; | |
| print "^^^"; | |
| ScoreSub(); | |
| DisplayStatus(); | |
| .RRQPL; | |
| L__M(##Miscellany,5); | |
| .RRQL; | |
| print "> "; | |
| #IFV3; read buffer parse; #ENDIF; | |
| temp_global=0; | |
| #IFV5; read buffer parse DrawStatusLine; #ENDIF; | |
| i=parse-->1; | |
| if (i=='quit' or | |
| if (i=='restart') @restart; | |
| if (i=='restore') { RestoreSub(); jump RRQPL; } | |
| if (i=='fullscore' or 'full' && TASKS_PROVIDED==0) | |
| { new_line; FullScoreSub(); jump RRQPL; } | |
| if (deadflag==2 && i=='amusing' && AMUSING_PROVIDED==0) | |
| { new_line; Amusing(); jump RRQPL; } | |
| #IFV5; | |
| if (i=='undo') | |
| { if (undo_flag==0) | |
| { L__M(##Miscellany,6); | |
| jump RRQPL; | |
| } | |
| if (undo_flag==1) jump UndoFailed2; | |
| @restore_undo i; | |
| if (i==0) | |
| { .UndoFailed2; L__M(##Miscellany,7); | |
| } | |
| jump RRQPL; | |
| } | |
| #ENDIF; | |
| L__M(##Miscellany,8); | |
| jump RRQL; | |
| ]; | |
| [ Process; | |
| #IFDEF DEBUG; | |
| if (debug_flag & 2 ~= 0) TraceAction(0); | |
| #ENDIF; | |
| if (meta==1 || BeforeRoutines()==0) | |
| indirect(#actions_table-->action); | |
| ]; | |
| #IFDEF DEBUG; | |
| Array debug_anames table | |
| [;##Inv "Inv"; | |
| ##InvTall "InvTall"; | |
| ##InvWide "InvWide"; | |
| ##Take "Take"; | |
| ##Drop "Drop"; | |
| ##Remove "Remove"; | |
| ##PutOn "PutOn"; | |
| ##Insert "Insert"; | |
| ##Transfer "Transfer"; | |
| ##Empty "Empty"; | |
| ##Enter "Enter"; | |
| ##Exit "Exit"; | |
| ##GetOff "GetOff"; | |
| ##Go "Go"; | |
| ##GoIn "GoIn"; | |
| ##Look "Look"; | |
| ##Examine "Examine"; | |
| ##Search "Search"; | |
| ##Give "Give"; | |
| ##Show "Show"; | |
| ##Unlock "Unlock"; | |
| ##Lock "Lock"; | |
| ##SwitchOn "SwitchOn"; | |
| ##SwitchOff "SwitchOff"; | |
| ##Open "Open"; | |
| ##Close "Close"; | |
| ##Disrobe "Disrobe"; | |
| ##Wear "Wear"; | |
| ##Eat "Eat"; | |
| ##Yes "Yes"; | |
| ##No "No"; | |
| ##Burn "Burn"; | |
| ##Pray "Pray"; | |
| ##Wake "Wake"; | |
| ##WakeOther "WakeOther"; | |
| ##Consult "Consult"; | |
| ##Kiss "Kiss"; | |
| ##Think "Think"; | |
| ##Smell "Smell"; | |
| ##Listen "Listen"; | |
| ##Taste "Taste"; | |
| ##Touch "Touch"; | |
| ##Dig "Dig"; | |
| ##Cut "Cut"; | |
| ##Jump "Jump"; | |
| ##JumpOver "JumpOver"; | |
| ##Tie "Tie"; | |
| ##Drink "Drink"; | |
| ##Fill "Fill"; | |
| ##Sorry "Sorry"; | |
| ##Strong "Strong"; | |
| ##Mild "Mild"; | |
| ##Attack "Attack"; | |
| ##Swim "Swim"; | |
| ##Swing "Swing"; | |
| ##Blow "Blow"; | |
| ##Rub "Rub"; | |
| ##Set "Set"; | |
| ##SetTo "SetTo"; | |
| ##WaveHands "WaveHands"; | |
| ##Wave "Wave"; | |
| ##Pull "Pull"; | |
| ##Push "Push"; | |
| ##PushDir "PushDir"; | |
| ##Turn "Turn"; | |
| ##Squeeze "Squeeze"; | |
| ##LookUnder "LookUnder"; | |
| ##ThrowAt "ThrowAt"; | |
| ##Answer "Answer"; | |
| ##Buy "Buy"; | |
| ##Ask "Ask"; | |
| ##Tell "Tell"; | |
| ##AskFor "AskFor"; | |
| ##Sing "Sing"; | |
| ##Climb "Climb"; | |
| ##Wait "Wait"; | |
| ##Sleep "Sleep"; | |
| ##Order "Order"; | |
| ##NotUnderstood "NotUnderstood"; | |
| ]; | |
| [ DebugParameter w x n l; | |
| x=0-->4; x=x+(x->0)+1; l=x->0; n=(x+1)-->0; x=w-(x+3); | |
| print w; | |
| if (w>=1 && w<=top_object) print " (", (name) w, ")"; | |
| if (x%l==0 && (x/l)<n) print " ('", (address) w, "')"; | |
| ]; | |
| [ DebugAction a i; | |
| for (i=1:i<=debug_anames-->0:i=i+2) | |
| { if (debug_anames-->i==a) | |
| { print (string) debug_anames-->(i+1); rfalse; } | |
| } | |
| print a; | |
| ]; | |
| [ TraceAction source ar; | |
| if (source<2) { print "[Action "; DebugAction(action); } | |
| else | |
| { if (ar==##Order) | |
| { print "[Order to "; PrintShortName(actor); print ": "; | |
| DebugAction(action); | |
| } | |
| else | |
| { print "[Life rule "; DebugAction(ar); } | |
| } | |
| if (noun~=0) { print " with noun "; DebugParameter(noun); } | |
| if (second~=0) { print " and second "; DebugParameter(second); } | |
| if (source==0) print " (from parser)"; | |
| if (source==1) print " (from outside)"; | |
| print "]^"; | |
| ]; | |
| #ENDIF; | |
| [ TestScope obj act a al sr x y; | |
| x=parser_one; y=parser_two; | |
| parser_one=obj; parser_two=0; a=actor; al=actors_location; | |
| sr=scope_reason; scope_reason=TESTSCOPE_REASON; | |
| if (act==0) actor=player; else actor=act; | |
| actors_location=actor; | |
| while (parent(actors_location)~=0) | |
| actors_location=parent(actors_location); | |
| SearchScope(location,player,0); scope_reason=sr; actor=a; | |
| actors_location=al; parser_one=x; x=parser_two; parser_two=y; | |
| return x; | |
| ]; | |
| [ LoopOverScope routine act x y a al; | |
| x = parser_one; y=scope_reason; a=actor; al=actors_location; | |
| parser_one=routine; if (act==0) actor=player; else actor=act; | |
| actors_location=actor; | |
| while (parent(actors_location)~=0) | |
| actors_location=parent(actors_location); | |
| scope_reason=LOOPOVERSCOPE_REASON; | |
| SearchScope(actors_location,actor,0); | |
| parser_one=x; scope_reason=y; actor=a; actors_location=al; | |
| ]; | |
| [ BeforeRoutines; | |
| if (GamePreRoutine()~=0) rtrue; | |
| if (RunRoutines(player,orders)~=0) rtrue; | |
| if (location~=0 && RunRoutines(location,before)~=0) rtrue; | |
| scope_reason=REACT_BEFORE_REASON; parser_one=0; | |
| SearchScope(location,player,0); scope_reason=PARSING_REASON; | |
| if (parser_one~=0) rtrue; | |
| if (inp1>1 && RunRoutines(inp1,before)~=0) rtrue; | |
| rfalse; | |
| ]; | |
| [ AfterRoutines; | |
| scope_reason=REACT_AFTER_REASON; parser_one=0; | |
| SearchScope(location,player,0); scope_reason=PARSING_REASON; | |
| if (parser_one~=0) rtrue; | |
| if (location~=0 && RunRoutines(location,after)~=0) rtrue; | |
| if (inp1>1 && RunRoutines(inp1,after)~=0) rtrue; | |
| return GamePostRoutine(); | |
| ]; | |
| [ R_Process acti i j sn ss sa sse; | |
| sn=inp1; ss=inp2; sa=action; sse=self; | |
| inp1 = i; inp2 = j; noun=i; second=j; action=acti; | |
| #IFDEF DEBUG; | |
| if (debug_flag & 2 ~= 0) TraceAction(1); | |
| #ENDIF; | |
| if ((meta==1 || BeforeRoutines()==0) && action<256) | |
| { indirect(#actions_table-->action); | |
| self=sse; inp1=sn; noun=sn; inp2=ss; second=ss; action=sa; rfalse; | |
| } | |
| self=sse; inp1=sn; noun=sn; inp2=ss; second=ss; action=sa; rtrue; | |
| ]; | |
| [ RunLife a j; | |
| #IFDEF DEBUG; | |
| if (debug_flag & 2 ~= 0) TraceAction(2, j); | |
| #ENDIF; | |
| reason_code = j; return RunRoutines(a,life); | |
| ]; | |
| [ LowKey_Menu menu_choices EntryR ChoiceR lines main_title i j; | |
| menu_nesting++; | |
| .LKRD; | |
| menu_item=0; | |
| lines=indirect(EntryR); | |
| main_title=item_name; | |
| print "--- "; print_paddr main_title; print " ---^^"; | |
| if (ZRegion(menu_choices)==3) print_paddr menu_choices; | |
| else indirect(menu_choices); | |
| .LKML; | |
| print "^Type a number from 1 to ", lines, | |
| ", 0 to redisplay or press ENTER.^> "; | |
| #IFV3; read buffer parse; #ENDIF; | |
| temp_global=0; | |
| #IFV5; read buffer parse DrawStatusLine; #ENDIF; | |
| i=parse-->1; | |
| if (i=='quit' or | |
| { menu_nesting--; if (menu_nesting>0) rfalse; | |
| if (deadflag==0) <<Look>>; | |
| rfalse; | |
| } | |
| i=TryNumber(1); | |
| if (i<1 || i>lines) jump LKML; | |
| menu_item=i; | |
| j=indirect(ChoiceR); | |
| if (j==2) jump LKRD; | |
| if (j==3) rfalse; | |
| jump LKML; | |
| ]; | |
| #IFV3; | |
| [ DoMenu menu_choices EntryR ChoiceR; | |
| LowKey_Menu(menu_choices,EntryR,ChoiceR); | |
| ]; | |
| #ENDIF; | |
| #IFV5; | |
| [ DoMenu menu_choices EntryR ChoiceR | |
| lines main_title main_wid cl i j oldcl pkey; | |
| if (pretty_flag==0) | |
| { LowKey_Menu(menu_choices,EntryR,ChoiceR); | |
| rfalse; | |
| } | |
| menu_nesting++; | |
| menu_item=0; | |
| lines=indirect(EntryR); | |
| main_title=item_name; main_wid=item_width; | |
| cl=7; | |
| .ReDisplay; | |
| oldcl=0; | |
| @erase_window $ffff; | |
| i=lines+7; | |
| @split_window i; | |
| i = 0->33; | |
| if (i==0) i=80; | |
| @set_window 1; | |
| @set_cursor 1 1; | |
| style reverse; | |
| spaces(i); j=i/2-main_wid; | |
| @set_cursor 1 j; | |
| print_paddr main_title; | |
| @set_cursor 2 1; spaces(i); | |
| @set_cursor 2 2; print "N = next subject"; | |
| j=i-12; @set_cursor 2 j; print "P = previous"; | |
| @set_cursor 3 1; spaces(i); | |
| @set_cursor 3 2; print "RETURN = read subject"; | |
| j=i-17; @set_cursor 3 j; | |
| if (menu_nesting==1) | |
| print " Q = resume game"; | |
| else | |
| print "Q = previous menu"; | |
| style roman; | |
| @set_cursor 5 2; font off; | |
| if (ZRegion(menu_choices)==3) print_paddr menu_choices; | |
| else indirect(menu_choices); | |
| .KeyLoop; | |
| if (cl~=oldcl) | |
| { if (oldcl>0) { @set_cursor oldcl 4; print " "; } | |
| @set_cursor cl 4; print ">"; | |
| } | |
| oldcl=cl; | |
| @read_char 1 0 0 pkey; | |
| if (pkey=='N' or 'n' or 130) | |
| { cl++; if (cl==7+lines) cl=7; jump KeyLoop; } | |
| if (pkey=='P' or 'p' or 129) | |
| { cl--; if (cl==6) cl=6+lines; jump KeyLoop; } | |
| if (pkey=='Q' or 'q' or 27) { jump QuitHelp; } | |
| if (pkey==10 or 13) | |
| { @set_window 0; font on; | |
| new_line; new_line; new_line; | |
| menu_item=cl-6; | |
| indirect(EntryR); | |
| @erase_window $ffff; | |
| @split_window 1; | |
| i = 0->33; if (i==0) { i=80; } | |
| @set_window 1; @set_cursor 1 1; style reverse; spaces(i); | |
| j=i/2-item_width; | |
| @set_cursor 1 j; | |
| print_paddr item_name; | |
| style roman; @set_window 0; new_line; | |
| i = indirect(ChoiceR); | |
| if (i==2) jump ReDisplay; | |
| if (i==3) jump QuitHelp; | |
| print "^[Please press SPACE.]^"; | |
| @read_char 1 0 0 pkey; jump ReDisplay; | |
| } | |
| jump KeyLoop; | |
| .QuitHelp; | |
| menu_nesting--; if (menu_nesting>0) rfalse; | |
| font on; @set_cursor 1 1; | |
| @erase_window $ffff; @set_window 0; | |
| new_line; new_line; new_line; | |
| if (deadflag==0) <<Look>>; | |
| ]; | |
| #ENDIF; | |
| [ TimerE; "** Too many timers/daemons! Increase MAX_TIMERS **"; ]; | |
| [ TimerE2 obj; print "** Object "; PrintShortName(obj); | |
| " has no time_left property! **"; ]; | |
| [ TimerE3 obj; print "** Object "; PrintShortName(obj); | |
| " both timer and daemon! **"; ]; | |
| [ StartTimer obj timer i; | |
| for (i=0:i<active_timers:i++) | |
| if (the_timers-->i==obj) | |
| { if (timer_flags->i==2) TimerE3(obj); | |
| rfalse; | |
| } | |
| for (i=0:i<active_timers:i++) | |
| if (the_timers-->i==0) jump FoundTSlot; | |
| i=active_timers++; | |
| if (i*2>=MAX_TIMERS) TimerE(); | |
| .FoundTSlot; | |
| if (obj.&time_left==0) TimerE2(obj); | |
| the_timers-->i=obj; timer_flags->i=1; obj.time_left=timer; | |
| ]; | |
| [ StopTimer obj i; | |
| for (i=0:i<active_timers:i++) | |
| if (the_timers-->i==obj) jump FoundTSlot2; | |
| rfalse; | |
| .FoundTSlot2; | |
| if (obj.&time_left==0) TimerE2(obj); | |
| the_timers-->i=0; obj.time_left=0; | |
| ]; | |
| [ StartDaemon obj i; | |
| for (i=0:i<active_timers:i++) | |
| if (the_timers-->i==obj) | |
| { if (timer_flags->i==1) TimerE3(obj); | |
| rfalse; | |
| } | |
| for (i=0:i<active_timers:i++) | |
| if (the_timers-->i==0) jump FoundTSlot3; | |
| i=active_timers++; | |
| if (i*2>=MAX_TIMERS) TimerE(); | |
| .FoundTSlot3; | |
| the_timers-->i=obj; timer_flags->i=2; | |
| ]; | |
| [ StopDaemon obj i; | |
| for (i=0:i<active_timers:i++) | |
| if (the_timers-->i==obj) jump FoundTSlot4; | |
| rfalse; | |
| .FoundTSlot4; | |
| the_timers-->i=0; | |
| ]; | |
| [ EndTurnSequence i j; | |
| turns++; | |
| if (the_time~=NULL) | |
| { if (time_rate>=0) the_time=the_time+time_rate; | |
| else | |
| { time_step--; | |
| if (time_step==0) | |
| { the_time++; | |
| time_step = -time_rate; | |
| } | |
| } | |
| the_time=the_time % 1440; | |
| } | |
| #IFDEF DEBUG; | |
| if (debug_flag & 4 ~= 0) | |
| { for (i=0: i<active_timers: i++) | |
| { j=the_timers-->i; | |
| if (j~=0) | |
| { PrintShortName(j); | |
| if (timer_flags->i==2) print ": daemon"; | |
| else | |
| { print ": timer with ", j.time_left, " turns to go"; } | |
| new_line; | |
| } | |
| } | |
| } | |
| #ENDIF; | |
| for (i=0: deadflag==0 && i<active_timers: i++) | |
| { j=the_timers-->i; | |
| if (j~=0) | |
| { if (timer_flags->i==2) RunRoutines(j,daemon); | |
| else | |
| { if (j.time_left==0) | |
| { StopTimer(j); | |
| RunRoutines(j,time_out); | |
| } | |
| else | |
| j.time_left=j.time_left-1; | |
| } | |
| } | |
| } | |
| if (deadflag==0) | |
| { scope_reason=EACH_TURN_REASON; verb_word=0; | |
| DoScopeAction(location); SearchScope(location,player,0); | |
| scope_reason=PARSING_REASON; | |
| } | |
| if (deadflag==0) TimePasses(); | |
| if (deadflag==0) | |
| { AdjustLight(); | |
| objectloop (i in player) | |
| if (i hasnt moved) | |
| { give i moved; | |
| if (i has scored) | |
| { score=score+OBJECT_SCORE; | |
| things_score=things_score+OBJECT_SCORE; | |
| } | |
| } | |
| } | |
| ]; | |
| [ AdjustLight flag i; | |
| i=lightflag; | |
| lightflag=OffersLight(parent(player)); | |
| if (i==0 && lightflag==1) | |
| { location=real_location; if (flag==0) <Look>; | |
| } | |
| if (i==1 && lightflag==0) | |
| { real_location=location; location=thedark; | |
| if (flag==0) { NoteArrival(); | |
| return L__M(##Miscellany, 9); } | |
| } | |
| if (i==0 && lightflag==0) location=thedark; | |
| ]; | |
| [ OffersLight i j; | |
| if (i==0) rfalse; | |
| if (i has light) rtrue; | |
| objectloop (j in i) | |
| if (HasLightSource(j)==1) rtrue; | |
| if (i has enterable || IsSeeThrough(i)==1) | |
| return OffersLight(parent(i)); | |
| rfalse; | |
| ]; | |
| [ HasLightSource i j ad; | |
| if (i==0) rfalse; | |
| if (i has light) rtrue; | |
| if (i has enterable || IsSeeThrough(i)==1) | |
| { objectloop (i in i) | |
| if (HasLightSource(i)==1) rtrue; | |
| } | |
| ad = i.&add_to_scope; | |
| if (parent(i)~=0 && ad ~= 0) | |
| { if (ad-->0 > top_object) | |
| { ats_hls = 0; ats_flag = 1; | |
| RunRoutines(i, add_to_scope); | |
| ats_flag = 0; if (ats_hls == 1) rtrue; | |
| } | |
| else | |
| { for (j=0:(2*j)<i.#add_to_scope:j++) | |
| if (HasLightSource(ad-->j)==1) rtrue; | |
| } | |
| } | |
| rfalse; | |
| ]; | |
| [ SayProS x; | |
| if (x==0) print "is unset"; | |
| else { print "means "; DefArt(x); } | |
| ]; | |
| [ PronounsSub; | |
| print "At the moment, ~it~ "; SayProS(itobj); | |
| print ", ~him~ "; SayProS(himobj); | |
| if (player==selfobj) print " and"; else print ","; | |
| print " ~her~ "; SayProS(herobj); | |
| if (player==selfobj) "."; | |
| print " and ~me~ means ", object player; "."; | |
| ]; | |
| [ ChangePlayer obj flag i; | |
| if (obj.&number==0) "** Player objects must have ~number~ prop **"; | |
| if (actor==player) actor=obj; | |
| give player ~transparent ~concealed; | |
| i=obj; while(parent(i)~=0) { if (i has animate) give i transparent; | |
| i=parent(i); } | |
| if (player==selfobj) player.short_name="your former self"; | |
| player.number=real_location; player=obj; | |
| if (player==selfobj) player.short_name=NULL; | |
| give player transparent concealed animate proper; | |
| i=player; while(parent(i)~=0) i=parent(i); location=i; | |
| real_location=player.number; | |
| if (real_location==0) real_location=location; | |
| lightflag=OffersLight(parent(player)); | |
| if (lightflag==0) location=thedark; | |
| print_player_flag=flag; | |
| ]; | |
| [ ChangeDefault prop val; | |
| (0-->5)-->(prop-1) = val; | |
| ]; | |
| [ RandomEntry tab; | |
| if (tab-->0==0) "** Table size 0 **"; | |
| return tab-->(random(tab-->0)); | |
| ]; | |
| [ Indefart o; | |
| if (o hasnt proper) { PrintOrRun(o,article,1); print " "; } | |
| PrintShortName(o); | |
| ]; | |
| [ Defart o; | |
| if (o hasnt proper) print "the "; PrintShortName(o); | |
| ]; | |
| [ CDefart o; | |
| if (o hasnt proper) print "The "; PrintShortName(o); | |
| ]; | |
| [ PrintShortName o; | |
| if (o==0) { print "nothing"; rtrue; } | |
| if (o>top_object || o<0) { print "<no such object>"; rtrue; } | |
| if (o==player) { print "yourself"; rtrue; } | |
| if (o.&short_name~=0 && PrintOrRun(o,short_name,1)~=0) rtrue; | |
| print_obj o; | |
| ]; | |
| ! Provided for, e.g., print (DirectionName) obj.door_dir; | |
| [ DirectionName d; | |
| switch(d) | |
| { n_to: print "north"; | |
| s_to: print "south"; | |
| e_to: print "east"; | |
| w_to: print "west"; | |
| ne_to: print "northeast"; | |
| nw_to: print "northwest"; | |
| se_to: print "southeast"; | |
| sw_to: print "southwest"; | |
| u_to: print "up"; | |
| d_to: print "down"; | |
| in_to: print "in"; | |
| out_to: print "out"; | |
| default: "** No such direction **"; | |
| } | |
| ]; | |
| [ Banner i; | |
| #IFV5; style bold; #ENDIF; | |
| print (string) Story; | |
| #IFV5; style roman; #ENDIF; | |
| print (string) Headline; | |
| print "Release ", (0-->1) & $03ff, " / Serial number "; | |
| for (i=18:i<24:i++) print_char 0->i; | |
| print " / Inform v"; inversion; | |
| print " Library ", (string) LibRelease; | |
| print " D"; | |
| new_line; | |
| if (standard_interpreter > 0) | |
| print "Standard interpreter ", | |
| standard_interpreter/256, ".", standard_interpreter%256, "^"; | |
| ]; | |
| [ VersionSub; | |
| Banner(); | |
| #IFV5; | |
| print "Interpreter ", 0->$1e, " Version ", char 0->$1f, " / "; | |
| #ENDIF; | |
| print "Library serial number ", (string) LibSerial, "^"; | |
| ]; | |
| ! ---------------------------------------------------------------------------- | |
Xet Storage Details
- Size:
- 130 kB
- Xet hash:
- f83c16d2f3942eb4533e709b95e6fdc5ae5c29c77e4a329e042e8d6dbfa085ee
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.