The sequence of events in play: the Main routine which runs the startup rulebook, the turn sequence rulebook and the shutdown rulebook; and most of the I6 definitions of primitive rules in those rulebooks.


§1. Main. This is where every I6 story file begins execution: it can end either by returning, or by a quit statement or equivalent opcode. (In I7 this does indeed happen when the quitting the game action is carried out, or when QUIT is typed as a reply to the final question; it's only if the user has altered the shutdown rulebook that we might ever actually return from Main.) The return value from Main is not meaningful.

The EarlyInTurnSequence flag is used to enforce the requirement that the "parse command rule" and "generate action rule" do nothing unless the turn sequence rulebook is being followed directly from Main, an anomaly explained in the Standard Rules.

Global EarlyInTurnSequence;
Global IterationsOfTurnSequence;

+replacing(from BasicInformKit) [ Main;
    rulebook_without_variables = ACTION_PROCESSING_RB;
    Startup();
    while (true) {
        while (deadflag == false) {
            EarlyInTurnSequence = true;
            action = ##Wait; meta = false; noun = nothing; second = nothing;
            actor = player;
            FollowRulebook(TURN_SEQUENCE_RB);
            IterationsOfTurnSequence++;
        }
        if (FollowRulebook(SHUTDOWN_RB) == false) return;
    }
];

§2. Initial Whitespace Rule. The printing of three blank lines at the start of play is traditional: on early Z-machine interpreters such as InfoTaskForce and Zip it was a necessity because of the way they buffered output. On modern windowed ones it still helps to space the opening text better.

[ INITIAL_WHITESPACE_R;
    It is now safe for the paragraph breaking between rules mechanism to work
    if (say__pc & PARA_NORULEBOOKBREAKS) say__pc = say__pc - PARA_NORULEBOOKBREAKS;
    print "^^^";
    rfalse;
];

§3. Initial Situation. The array InitialSituation is compiled by Inform and contains:

The start object and start room are meaningful only if the player's object is compiled outside of the object tree (as can happen if the source text reads, say, "Mrs Bridges is a woman. The player is Mrs Bridges."): in other circumstances they are often correct, but this must not be relied on.

§4. Initialise Memory Rule. In addition to BasicInformKit's memory initialisation, this rule sets up the initial situation:

A handful of variables are filled in. I7_LOOKMODE is a constant created by the use options "use full-length room descriptions" or "use abbreviated room descriptions", but otherwise not existing. It is particularly important that player have the correct value, as the process of initialising the memory heap uses the player as the presumed actor when creating memory representations of literal stored actions where no actor was specified; this is why player is initialised here and not in the "position player in model world rule" below. The other interesting point here is that we explicitly set location and real_location to nothing, which is certainly incorrect, even though we know better. We do this so that the "update chronological records rule" cannot see where the player is: see the Standard Rules for an explanation of why this is, albeit perhaps dubiously, a good thing.

The not_yet_in_play flag, which is cleared when the first command is about to be read from the keyboard, suppresses the standard status line text: thus, if there is some long text to read before the player finds out where he is, the surprise will not be spoiled.

+replacing(keeping)(from BasicInformKit) [ INITIALISE_MEMORY_R;
    not_yet_in_play = true;
    lookmode = WorldModelKit`ROOM_DESC_DETAIL_CFGV;
    player = InitialSituation-->PLAYER_OBJECT_INIS;
    the_time = InitialSituation-->START_TIME_INIS;
    real_location = nothing;
    location = nothing;

    replaced`INITIALISE_MEMORY_R();

    rfalse;
];

§5. Position Player In Model World Rule. This seems as good a place as any to write down the invariant we attempt to maintain for the player's position variables:

These invariants are usually all false before the following rule is executed; they are all true once it has completed. In addition, because the global action variables usually hold details of the action most recently carried out, we initialise these as if the most recent action had been the player waiting. (Nobody ought to use these variables at this point, but in case they do use them by accident in a "when play begins" rule, we want Inform to behave predictably and without type-unsafe values entering code.)

[ POSITION_PLAYER_IN_MODEL_R player_to_be;

    player = selfobj;
    player_to_be = InitialSituation-->PLAYER_OBJECT_INIS;

    location = LocationOf(player_to_be);
    if (location == 0) {
        location = InitialSituation-->START_ROOM_INIS;
        if (InitialSituation-->START_OBJECT_INIS)
            move player_to_be to InitialSituation-->START_OBJECT_INIS;
        else move player_to_be to location;
    }

    if (player_to_be ~= player) { remove selfobj; ChangePlayer(player_to_be); }
    else { real_location = location; SilentlyConsiderLight(); }
    NOTE_OBJECT_ACQUISITIONS_R();
    MoveFloatingObjects();

    actor = player; act_requester = nothing; actors_location = real_location; action = ##Wait;

    InitialSituation-->DONE_INIS = true;
    rfalse;
];

§6. Parse Command Rule. This section contains only two primitive rules from the turn sequence rulebook, the matched pair of the "parse command rule" and the "generate action rule"; the others are found in the sections on Light and Time.

We use almost identically the same parser as that in the I6 library, since it is a well-proven and understood algorithm. The I6 parser returns some of its results in a supplied array (here parser_results, though the I6 library used to call this inputobjs), but others are in global variables:

Some of these conventions are a little odd-looking now: why not simply have a larger results array, rather than this pile of occasionally used variables? The reasons are purely historical: the I6 parser developed gradually over about a decade.

Constant ACTION_PRES = 0;
Constant NO_INPS_PRES = 1;
Constant INP1_PRES = 2;
Constant INP2_PRES = 3; Parser.i6t code assumes this is INP1_PRES + 1

[ PARSE_COMMAND_R;
    if (EarlyInTurnSequence == false) rfalse; Prevent use outside top level
    not_yet_in_play = false;

    Parser__parse();
    TreatParserResults();
    rfalse;
];

§7. Treat Parser Results. We don't quite use the results exactly as they are returned by the parser: we make modifications in a few special cases.

[ TreatParserResults;
    if (parser_results-->ACTION_PRES == ##MistakeAction) meta = true;

    if (parser_results-->ACTION_PRES == ##Tell &&
        parser_results-->INP1_PRES == player && actor ~= player) {
        parser_results-->ACTION_PRES = ##Ask;
        parser_results-->INP1_PRES = actor; actor = player;
    }
];

§8. Generate Action Rule. For what are, again, historical reasons to do with the development of I6, the current action is recorded in a slate of global variables:

In the following rule, we create this set of variables for the action or multiple action(s) suggested by the parser: each action is sent on to BeginAction for processing. Once done, we reset the above variables in what might seem an odd way: we allow straightforward actions by the player to remain in the variables, but convert requests to other people to the neutral "waiting" action carried out by the player (which is the zero value for actions). Now, in a better world, we would always erase the action like this, because an action once completed ought to be forgotten. The value of noun ought to be visible only during the action's processing.

But in practice many I7 users write "every turn" rules which are predicated on what the turn's main action was: say, "Every turn when going: ..." The every turn stage is not until later in the turn sequence, so such rules can only work if we keep the main parser-generated action of the turn in the action variables when we finish up here: so that's what we do. (Note that BeginAction preserves the values of the action variables, storing copies on the stack, so whatever may have happened during processing, we finish this routine with the same action variable values that we set at the beginning.)

Finally, note that an out of world action stops the turn sequence early, at the end of action generation: this is what prevents the time of day advancing, every turn rules from firing, and so forth — see the Standard Rules.

[ GENERATE_ACTION_R i;
    if (EarlyInTurnSequence == false) rfalse; Prevent use outside top level
    EarlyInTurnSequence = false;

    action = parser_results-->ACTION_PRES;
    act_requester = nothing; if (actor ~= player) act_requester = player;

    inp1 = 0; inp2 = 0; multiflag = false;
    if (parser_results-->NO_INPS_PRES >= 1) {
        inp1 = parser_results-->INP1_PRES; if (inp1 == 0) multiflag = true;
    }
    if (parser_results-->NO_INPS_PRES >= 2) {
        inp2 = parser_results-->INP2_PRES; if (inp2 == 0) multiflag = true;
    }

    if (inp1 == 1) noun = nothing; else noun = inp1;
    if (inp2 == 1) second = nothing; else second = inp2;

    if (multiflag) {
        if (multiple_object-->0 == 0) {
            if (actor == player) { GENERATE_ACTION_RM('B'); new_line; }
            return;
        }
        if (toomany_flag) {
            toomany_flag = false;
            if (actor == player) { GENERATE_ACTION_RM('A'); }
        }
        i = multiple_object-->0;
        FollowRulebook(MULTIPLE_ACTION_PROCESSING_RB);
        if ((multiple_object-->0 == 1) && (i > 1)) {
            multiflag = false;
            if (inp1 == 0) noun = multiple_object-->1;
            if ((inp2 == 0) && (parser_results-->NO_INPS_PRES >= 2))
                second = multiple_object-->1;
        }
        if (multiple_object-->0 == 0) rfalse;
    }

    if (multiflag) {
        GenerateMultipleActions();
        multiflag = false;
    } else BeginAction(action, noun, second);

    if ((actor ~= player) || (act_requester)) action = ##Wait;
    actor = player; act_requester = 0;

    if (meta) { RulebookSucceeds(); rtrue; }
    rfalse;
];

§9. Generate Multiple Actions. So this routine is used to issue the individual actions necessary when a multiple object list has been supplied as either the noun or second noun part of an action generated by the parser. Note that we stop processing the list in the event of the game ending, or of the location variable changing its value, which can happen either through movement of the player, or through passage from darkness to light or vice versa.

We use RunParagraphOn to omit skipped lines as paragraph breaks between the results from any item in the list: this is both more condensed on screen in ordinary lists, and might allow the user to play tricks such as gathering up reports from a list and delivering them later in some processed way.

[ GenerateMultipleActions initial_location k item;
    initial_location = location;
    for (k=1: k<=multiple_object-->0: k++) {
        item = multiple_object-->k;
        RunParagraphOn();
        if (inp1 == 0) { inp1 = item; BeginAction(action, item, second, item); inp1 = 0; }
        else { inp2 = item; BeginAction(action, noun, item, item); inp2 = 0; }
        if (deadflag) return;
        if (location ~= initial_location) {
            if (player == actor) { ACTION_PROCESSING_INTERNAL_RM('J'); new_line; }
            return;
        }
    }
];

§10. Timed Events Rule. A timed event is a rule stored in the TimedEventsTable, an I6 table array: zero entries in this table are ignored, and the sequence is significant only if more than one event goes off at the same moment, in which case earlier entries go off first. Each rule in the table has a corresponding timer value in TimedEventTimesTable. If this is negative, it represents a number of turns to go before the event happens — or properly speaking, the number of times the timed events rule is invoked. Otherwise the timer value must be a valid time of day at which the event happens (note that valid times are all non-negative integers). We allow a bracket of 30 minutes after the event time proper; this is designed to cope with situations in which the user sets some timed events, then advances the clock by hand (or uses a long step time, say in which each turn equates to 20 minutes).

Because an event is struck out of the table just before it is fired, it will not continue to go off the rest of the half-hour. Moreover, because the striking out happens {\it before} rather than after the rule fires, a rule can re-time itself to go off again later, somewhat like the snooze feature on an alarm clock, without the risk of it going off again immediately in the same use of the timed events rule: there is guaranteed to be a blank slot in the timer array at or before the current position because we have just blanked one.

[ TIMED_EVENTS_R i d event_timer fire rule;
    for (i=1: i<=(TimedEventsTable-->0): i++)
        if ((rule=TimedEventsTable-->i) ~= 0) {
            event_timer = TimedEventTimesTable-->i; fire = false;
            if (event_timer<0) {
                (TimedEventTimesTable-->i)++;
                if (TimedEventTimesTable-->i == 0) fire = true;
            } else {
                d = (the_time - event_timer + TWENTY_FOUR_HOURS) % TWENTY_FOUR_HOURS;
                if ((d >= 0) && (d < 30)) fire = true;
            }
            if (fire) {
                TimedEventsTable-->i = 0;
                FollowRulebook(rule);
            }
        }
    rfalse;
];

§11. Setting Timed Events. This is the corresponding routine which adds events to the timer tables, and is used to define phrases like "the cuckoo clock explodes in 7 turns from now" or "the cuckoo clock explodes at 4 PM". Here the rule would be "cuckoo clock explodes", and the event_time would either be 4 PM with absolute_time set, or simply 7 with absolute_time clear.

Note that the same event can occur only once in the timer tables: a new setting for its firing overwrites an old one. (This ensures that the table does not slowly balloon in size if the user has not been careful to ensure that events always fire.)

[ SetTimedEvent rule event_time absolute_time i b;
    for (i=1: i<=(TimedEventsTable-->0): i++) {
        if (rule == TimedEventsTable-->i) { b=i; break; }
        if ((b==0) && (TimedEventsTable-->i == 0)) b=i;
    }
    if (b==0) return
        IssueRTP("TooManyTimedEvents",
            "Too many timed events are going on at once.", WorldModelKitRTPs);
    TimedEventsTable-->b = rule;
    if (absolute_time) TimedEventTimesTable-->b = event_time;
    else TimedEventTimesTable-->b = -event_time;
];

§12. Setting Time Of Day. This is the old I6 library routine SetTime, which is no longer used in I7 at present; but might be, some day.

Global time_step;

[ SetTime t s;
    the_time = t; time_rate = s; time_step = 0;
    if (s < 0) time_step = 0-s;
];

§13. Advance Time Rule. This rule advances the two measures of the passing of time: the number of turns of play, and the_time of day.

[ ADVANCE_TIME_R;
    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 % TWENTY_FOUR_HOURS;
    }
    rfalse;
];

§14. Note Object Acquisitions Rule. See the Standard Rules for comment on this.

[ NOTE_OBJECT_ACQUISITIONS_R obj;
    objectloop (obj in player) give obj moved;
    objectloop (obj has concealed)
        if (IndirectlyContains(player, obj)) give obj ~concealed;
    if (RUCKSACK_CLASS) {
        objectloop (obj in player)
            if (obj ofclass RUCKSACK_CLASS)
                SACK_OBJECT = obj;
        objectloop (obj ofclass RUCKSACK_CLASS && obj provides component_parent
            && obj.component_parent == player)
            SACK_OBJECT = obj;
    }
    rfalse;
];

§15. Resurrect Player If Asked Rule. If a rule in the "when play ends" rulebook set resurrect_please, by executing the "resume the game" phrase, then this is where we notice that: making the shutdown rulebook succeed then tells Main to fall back into the turn sequence.

[ RESURRECT_PLAYER_IF_ASKED_R;
    if (resurrect_please) {
        RulebookSucceeds(); resurrect_please = false;
        deadflag = 0; story_complete = false; rtrue;
    }
    rfalse;
];

§16. Ask The Final Question Rule. And so we come to the bittersweet end: we ask the final question endlessly, until the player gives a reply which takes drastic enough action to destroy the current execution context in the VM, for instance by typing QUIT, RESTART, UNDO or RESTORE. The question and answer are all managed by the activity, which is defined in I7 source text in the Standard Rules.

[ ASK_FINAL_QUESTION_R;
    print "^";
    while (true) {
        CarryOutActivity(DEALING_WITH_FINAL_QUESTION_ACT);
        DivideParagraphPoint();
        if (resurrect_please) rtrue;
    }
];

§17. Read The Final Answer Rule. This erases the current command, so is a technique we couldn't use during actual play, but here commands are but a distant memory. So we can use the same buffers for the final question as for game commands.

[ READ_FINAL_ANSWER_R;
    KeyboardPrimitive(buffer, parse, DrawStatusLine);
    players_command = 100 + WordCount();
    num_words = WordCount();
    wn = 1;
    rfalse;
];

§18. Immediately Restart VM Rule. Now for four rules acting on typical responses to the final question.

[ IMMEDIATELY_RESTART_VM_R; @restart; ];

§19. Immediately Restore Saved Game Rule. It is almost certainly unnecessary to set actor to player here, but we do so just in case, because RESTORE_THE_GAME_R is protected against doing anything when it thinks it might have been called erroneously through a command like "DAPHNE, RESTORE". (Out of world actions should never be carried out that way, but again, it's a precaution.)

[ IMMEDIATELY_RESTORE_SAVED_R; actor = player; RESTORE_THE_GAME_R(); ];

§20. Immediately Quit Rule.

[ IMMEDIATELY_QUIT_R; @quit; ];

§21. Immediately Undo Rule.

[ IMMEDIATELY_UNDO_R; Perform_Undo(); ];

§22. Print Obituary Headline Rule. Finally, definitions of three primitive rules for the "printing the player's obituary" activity.

[ PRINT_OBITUARY_HEADLINE_R;
    print "^^    ";
    VM_Style(ALERT_VMSTY);
    print "***";
    if (deadflag == 1) PRINT_OBITUARY_HEADLINE_RM('A');
    if (deadflag == 2) PRINT_OBITUARY_HEADLINE_RM('B');
    if (deadflag == 3) PRINT_OBITUARY_HEADLINE_RM('C');
    if (deadflag ~= 0 or 1 or 2 or 3)  {
        print " ";
        TEXT_TY_Say(deadflag);
        print " ";
    }
    print "***";
    VM_Style(NORMAL_VMSTY);
    print "^^^";
    rfalse;
];

§23. Print Final Score Rule.

[ PRINT_FINAL_SCORE_R;
    if (WorldModelKit`SCORING_CFGV) ANNOUNCE_SCORE_R();
    rfalse;
];

§24. Display Final Status Line Rule.

[ DISPLAY_FINAL_STATUS_LINE_R;
    sline1 = score; sline2 = turns;
    rfalse;
];