Unified Glulx Input by Andrew Plotkin


Unified Glulx Input is an attempt to tidy up all the messy I6 APIs that you need to customize your game's input system.

The Glulx Entry Points extension does this already, but that exposes all the mess -- you have to understand how Glk works to use to correctly. Unified Glulx Input tries to offer you a simple model which handles common cases easily.

(This extension was written and tested with Inform 7 releases 6L38 and 6M62.)

Chapter: Basic concepts

Section: Input-contexts: why are we awaiting input?

A game can stop and await input for many reasons. It can be waiting for a command, or for a yes-or-no answer (an "if the player consents..." test). It can be waiting for the player to hit any key to continue.

In Unified Glulx Input, all of these inputs invoke the same mechanism. The mechanism can be customized to behave in different ways -- printing different prompts, waiting for different sorts of input. But it's always the same mechanism underneath.

You customize it by writing rules. The extension has five rulebooks which cover various aspects of the problem. The default rules cover the normal input situations of an IF game -- the ones mentioned above. Of course, you can modify these or add more.

We distinguish these situations with a value called an "input-context". The rulebooks we mentioned are input-context based rulebooks. The UGI extension comes with seven:

primary context, disambiguation context (the command input-contexts)
yes-no question context, extended yes-no question context, repeat yes-no question context (the yes-no input-contexts)
final question context
keystroke-wait context

The first two (primary and disambiguation) are used by the main game loop -- the core cycle of fetching and performing commands. The others are special-purpose, ad-hoc inputs.

These are more finely divided than you usually need to bother with. For example, the game might be waiting for a normal command (primary context) or for a disambiguation reply (disambiguation context). In most games these will appear the same. So you can handle them together. For example:

Prompt displaying rule for a command input-context:
     instead say ">>>";

This rule changes the command prompt for all main-game-loop inputs (both primary and disambiguation).

Section: G-events: what kind of input are we awaiting?

Most IF input comes as lines of text. But a game can also accept keystroke input, as in a "hit any key" prompt or a menu controlled with arrow keys.

Glulx currently supports eight types of input, described as "g-event" values:

char-event - a keystroke
line-event - a line of text
hyperlink-event - selection of a hyperlink
timer-event - event repeated at fixed intervals
mouse-event - a mouse click
arrange-event - window sizes have changed
redraw-event - graphics windows need redrawing
sound-notify-event - sound finished playing

Section: Glk-windows: what window is awaiting input?

This version of UGI is only concerned with one window, the "story-window". You can set various properties of the story-window object to customize its behavior.

There is also a "status-window" object, but it is not yet functional. Future versions of UGI will support this. Future versions will also integrate with the Multiple Windows extension to support multi-window games.

Chapter: High-level tasks

These phrases can be used with no deeper knowledge of UGI.

Section: Yes-no questions

if the player consents:...

This works just as it always has (WWI 11.5); it waits for the player to type "yes" or "no". You are expected to print the question first.

if the player consents asking (T1 - text):...
if the player consents asking (T1 - text) and (T2 - text):...

Similar, except UGI uses your text as the prompt (through the magic of the prompt displaying rulebook). T1 should be the initial question; T2 is a followup if the player fails to answer. If you don't supply T2, it defaults to "Please answer yes or no."

Section: Waiting for a key

These phrases are copied from the Basic Screen Effects extension; they have been updated here to work with UGI.

wait for any key
wait for the SPACE key

By default no prompt is printed. You can print your own beforehand, or write a prompt displaying rule:

Prompt displaying rule for keystroke-wait context:
     say ">>>";

If you want to cause a wait and then get the key that was hit:

the key waited for -- Unicode character

Include the Unicode Character Names extension for a complete list of characters, or my ASCII Character Names extension for the basic ones. The result may also be a special value representing a control key:

special keycode left, special keycode right, special keycode up, special keycode down, special keycode return, special keycode delete, special keycode escape, special keycode tab, special keycode pageup, special keycode pagedown, special keycode home, special keycode end, special keycode func1,... special keycode func12

These are not part of the Unicode spec, but I7 represents them as Unicode character values. (They are outside the official Unicode range of $0 to $10FFFF.) Don't try to print them or insert them into normal texts -- they have no normal printed representation. If you need to print a special character, use this phrase:

say extended (C - Unicode character)

This will print special characters as "left", "right", and so on. Normal Unicode characters will be printed directly.

Chapter: The five rulebooks

We've talked about the five rulebooks, and now it's time to introduce them:

setting up input rules - decide what kinds of input are desired
prompt displaying rules - display a prompt, traditionally ">"
accepting input rules - accept, reject, or alter individual input events
checking undo input rules - check whether an input event is an UNDO command
handling input rules - convert an input event into an action

These rulebooks are based on an input-context value. So you might write

Prompt displaying rule for yes-or-no context:...
Accepting input rule for a command input-context:...

Section: Setting up input rules

This rulebook decides what sort of input the game desires for player commands. It also decides whether the command will be an undo step. By default it requests only line input, and says that all line inputs are undo steps.

This rulebook applies only to the command contexts (primary and disambiguation). Other questions (yes-or-no, keystroke-wait, etc) do their own setup (and are not undo steps).

To customize this, set the input-request, hyperlink-input-request, and mouse-input-request properties of the story-window object. For example:

Setting up input rule:
     now the input-request of the story-window is char-input;
     now the story-window is hyperlink-input-request;

The input-request property can be char-input, line-input, or no-input. (These are mutually exclusive.) You can set hyperlink-input-request and mouse-input-request independently.

(Note that mouse-input-request does not apply to the story-window -- buffer windows have no fixed coordinates -- so it cannot currently be used. There is a status-window object, but UGI does not yet support input requests for it.)

To indicate whether this command is an undo step, use one of these phrases:

set input undoable
set input non-undoable

When you set input undoable, you're telling the parser that the player *can* perform an UNDO command (whether by typing "UNDO" or some other way) and therefore this is a turn that should be added to the undo chain. If you set input non-undoable, you're telling the parser that UNDO is not supported for this command, and future UNDO commands should skip back over it.

To make this more clear: when the player does an UNDO, they don't want to jump back to the previous *input*; they want to jump back to the previous *turn*. Yes-or-no commands are inputs but never turns. When the setting-up-input rulebook is called, it's *usually* a turn, but you might want to decide otherwise.

(Note: you can suppress UNDO for your game this way, but it's better to use the "undo prevention" option. That way, UNDO commands will say "The use of 'undo' is forbidden", rather than just throwing a "not a verb I recognize" error.)

We'd better mention how to display hyperlinks in your game text. A hyperlink can contain a number or an object reference. (Or, really, any value that can be cast to an I6 integer, which is any value at all. But stick with the easy cases.) (It's best not to mix numbers and objects in the same game. Pick one.)

say hyperlink (O - object)
say hyperlink (N - number)
say /hyperlink

Use these phrases in text as markup:

say "You see a [hyperlink sword]ancient elvish sword[/hyperlink]."

Section: Prompt displaying rules

This rulebook displays a prompt before input. It applies to all input contexts.

The default for keystroke input is no prompt. For yes-no input it is ">", unless you've specified prompts by invoking the "player consents asking..." phrase. For the final question, the prompt is "> [run paragraph on]" (as defined by the old print the final prompt rule).

For other cases, including all command contexts, the prompt is ">". You can customize this by changing the old command prompt global variable, or by writing a rule:

Prompt displaying rule for a command input-context:...

(See example: "Changing the Prompt".)

Section: Accepting input rules

This rulebook decides whether to accept or reject an incoming event, or convert the event to a different event. It applies to all input contexts.

This is the fussiest rulebook in UGI. It's also the one you will use least often! For many games, the default rules are all you need. So don't be overwhelmed by the description here.

Every time an event arrives, this rulebook is invoked. You can get the event type with this phrase:

the current input event type -- g-event

So you can write a rule like:

Handling input rule when the current input event type is hyperlink-event:

Or, as a handy shortcut:

Handling input rule when handling hyperlink-event:

Depending on the current event type, you can check its contents with one of these phrases:

the current input event character -- Unicode character
the current input event hyperlink number -- number
the current input event hyperlink object -- object
the current input event line text -- text
the current input event line word count -- number

An important note: in the accepting input rulebook, the "player's command" snippet has not yet been set up. You cannot refer to this or any other snippet variable; you will get errors. Use the "current input event line text" phrase.

The job of the accepting input rulebook is to reject the event (keep waiting) or accept it (stop waiting and allow event to be processed). These are available as phrases (really just aliases for "rule succeeds" and "rule fails"):

accept the input event
reject the input event

The default rules accept character, line, and hyperlink events (assuming they were requested by the setting up input rules). Other event types are rejected by default, although a rearrange event will trigger a status-line refresh first.

You can also convert the event to a different one:

replace the current input event with the line (T - text)
replace the current input event with the character (C - Unicode character)
replace the current input event with the hyperlink object (O - object)
replace the current input event with the hyperlink number (N - number)

This is handy for simple cases -- perhaps you want to convert hyperlink clicks into a line of text and run that through the parser in the usual way. (See example: "Maze of Keys".) However, it's cleaner to handle hyperlinks in the handling input rulebook. See next section.

Here's the messy part. If you've requested character or line input, and a *different* input event arrives, the character or line input is still in progress. *You may not print text as long as character or line input is in progress. *

To be clear, this is not a problem when you *receive* a character or line event. That signals that the input is complete and printing is safe. But if you get another event type (say a timer or hyperlink event) while char/line input is in progress, you must either

(a) take background action that does not print to the story window, or

(b) accept the event, printing nothing, or

(c) interrupt text input before printing.

The standard redraw status line on arrange rule is an example of policy (a). It redraws the status line (and then rejects the event to continue waiting). See example: "Tick Tick Tick Button".

Policy (b) is fine if you plan to handle the event in the handling input rulebook. See next section.

For policy (c), you'll have to call this phrase:

interrupt text input for (W - glk-window)

This stops line or character input. If you then reject the event, input will be re-requested automatically.

interrupt text input for (W - glk-window), preserving input

This variation stores the player's in-progress input as the preload-input-text property of the story-window. The next time line input starts, the string will be pre-loaded into the input buffer. (See example: "Master Blaster".)

The interrupt text input phrase has an additional use: it tells UGI that you're about to print text, so the prompt might have to be redisplayed. This is sometimes helpful if you're rejecting an input event with an error message (rather than silently).

Finally, I'll say again: this is the least commonly used of the five rulebooks! Forget the mess above and read on.

Section: Checking undo input rules

At this point an input event has been accepted. This rulebook decides whether it's an UNDO command.

(UNDO commands are special; they never reach the handling-input stage, and they never become an "undoing" action. There is no such action, in fact. This is awkward but it's just the way the parser is. So we need this special rulebook to detect them.)

The default rule in this rulebook checks for line input containing just the word "UNDO". That's the expected behavior for games that accept line input.

If your game works by keystroke or hyperlink input, you might still want to support UNDO. You could write an accepting input rule that replaces the current input event with the line "undo". (See example: "Maze of Keys".) However, it's cleaner to write a checking undo rule which checks for whatever UNDO input your game wants. (See example: "Maze of Keys 2".)

This is important: if you set input undoable at setting-up-input time, you *must* accept at least one input as UNDO at checking-undo-input time! If you don't, you'll break multiple undo for the player. (They won't be able to UNDO back through this command.)

Contrariwise, if you forget to set input undoable at setting-up-input time, then UNDO will not work for this command. The checking-undo-input rulebook will be skipped.

Section: Handling input rules

At this point an input event has been accepted (and it wasn't UNDO). This rulebook decides how to convert it into an action.

This rulebook applies only to the command contexts (primary and disambiguation). Other questions (yes-or-no, keystroke-wait, etc) do their own setup.

The phrases mentioned above, for examining the input event, still apply:

the current input event type -- g-event
if handling (G - g-event):...
the current input event character -- Unicode character
the current input event hyperlink number -- number
the current input event hyperlink object -- object
the current input event line text -- text
the current input event line word count -- number
accept the input event
reject the input event

If your rule rejects the event, the command does nothing. The game prompts for another command.

If your rule accepts the event, it is processed. Line input is parsed in the classic way. Any other input type causes an "I beg your pardon?" error.

So what do we do with other input types? Usually you'll invoke this phrase:

handle the current input event as (act - stored action)

This bypasses the parser entirely. The game will proceed as if the given action had been parsed; the player will carry it out (or try to). For example, if you had a hyperlink labelled "INVENTORY", you might write:

Handling input rule when handling hyperlink-event:
     handle the current input event as the action of taking inventory;
     accept the input event.

(Note that a stored action is always phrased "the action of...".)

See example: "A Study In Memoriam".

By the way, at the handling-input stage, all inputs have been completed or cancelled. You don't have to worry about manually interrupting text input.

Section: Which rulebook again?

The accepting-input and handling-input rulebooks both operate on input events. They both can accept and reject events. When you're building a game, you may find yourself unsure which to use.

The rules of thumb:

- If you want to generate an action, use a handling input rule.

- If you want to convert some event into a line input event, you can use either, but handling input will probably be simpler.

- If you want to respond to an event without interrupting the player's input, use an accepting input rule.

- If you want to reject an event without even printing a new prompt, use an accepting input rule.

- If you want to respond to events in every input context (including yes-or-no, keystroke-wait, etc) use an accepting input rule.

Section: What about the reading a command activity?

The old "reading a command" activity still exists under UGI. It's called by the same code that runs the handling input rulebook. It's not the same thing, though; we must be careful of the differences.

- Reading a command is an activity. You can write before rules and after rules for it. The accepting input and handling input rulebooks run "inside" reading a command -- between the before and after stages of the activity.

- In an after reading a command rule, you can refer to (or replace) the "player's command" snippet. That's not allowed in a handling input rule; the player's command is not yet set up during that rulebook.

- In a handling input rule, you can say "handle the current input event as the action of...". That's not available at after reading a command time.

- For a disambiguation input, the handling input rule sees what the player typed. In contrast, the player's command (in an after reading a command rule) is the *entire* disambiguated command! An example:

>GET
(handling input: text="GET".)
(after reading: player's command="GET".)
What do you want to get?
>RED ROCK
(handling input: text="RED ROCK".)
(after reading: player's command="GET RED ROCK".)
Taken.

In short, the reading a command activity is the most sensible place to examine or replace text. The handling input rulebook is primarily meant for translating *non-textual* input events into actions.

Chapter: The flow of the machinery

It may be helpful to diagram the whole input machine and describe exactly when each rulebook runs.

(top of parser loop:)
do before reading a command activity
repeat until a command is accepted:
    ...
     (ParserInput function:)
     repeat until an event is accepted:
         follow setting up input rules
        ...
         (AwaitInput function:)
         repeat until an event is accepted:
             follow prompt displaying rules
             redraw the status line
             make all necessary Glk input requests
             wait for an input event to arrive
             follow the accepting input rules
         cancel any remaining Glk input requests
         (end of AwaitInput function)
        ...
         reject blank lines ("I beg your pardon")
         if the setting-up rules said that this is an undoable turn:
             follow checking undo input rules
             if the input was an UNDO command:
                 perform UNDO
             save an undo point
     (end of ParserInput function)
    ...
     follow handling input rules
(at this point, the "player's command" is set)
do after reading a command activity
if no action is recognized, say "I beg your pardon"
otherwise, carry out the action

Requests such as "player consents" and "wait for any key" only invoke the AwaitInput function. They bypass ParserInput, and thus ignore the setting up input rules and the handling input rules.

The inner blank-line rejection is a bit of an anomaly. It happens before the handling input rules (and the after reading a command activity), which may cause problems; you cannot handle blank line input the same way you handle other line input.

UGI offers a use option to resolve this:

Use pass blank input lines.

If you set this, the reject blank lines step is omitted. A blank line will pass through all the same rulebooks as other line input. It will be rejected at the *last* step -- no action is recognized, say "I beg your pardon". So the player will see the same response, really.

(One difference: we will have passed through the save-undo step. That is, when this option is set, blank lines count as undoable commands. That's not great, but it's not a big nuisance either. A future version of UGI may address this.)

Chapter: Low-level invocations

UGI offers phrases to stop and wait for a particular input (a keystroke, a yes-or-no question). It's quite easy to write your own such phrase.

Say we want to stop and wait for one hyperlink click. First we'll want to define a new input-context:

Link-wait context is an input-context.

Then we need a rule to accept hyperlink input, storing the link value in a global:

The found object is an object that varies.
Rule for accepting input for link-wait context when handling hyperlink-event:
     now the found object is the current input event hyperlink object;
     accept the input event.

Now we can write a phrase:

To decide what object is the hyperlink waited for:
     now the found object is nothing;
     clear all input requests;
     now the story-window is hyperlink-input-request;
     await input in link-wait context;
     decide on the found object.

The core phrase here is

await input in (C - input-context)

Before calling this, we *must* set the desired request properties. (Remember, the setting up input rulebook will not be run) It is best to clear all requests and then set exactly the requests desired.

If we want line input, we must use one of the alternate forms:

await input in (C - input-context) with primary buffer
await input in (C - input-context) with secondary buffer

Generally you will want to use the secondary buffer. That will avoid stomping on the player's command (which is always stored in the primary buffer).

For another case, see example: "Secret Number Request"

Chapter: TEST ME

Inform's standard TEST ME facility has been upgraded to support more kinds of events. You can now say

Test me with "$char x / $char 65 / $char escape / $char".
Test me with "$link 123 / $link sword".
Test me with "$timer".

Any test line beginning with a dollar sign defines a special event. You can escape dollar signs and slashes in line input by writing "[$]" or "[/]".

Character (keystroke) events are defined with "$char"; they can be single characters, ASCII codes, or special names such as "escape", "left", "right", "space", etc. "$char" by itself is taken as a space keystroke.

(Note that Inform automatically lower-cases test lines. So to define an upper-case keystroke, you must use a numeric ASCII code.)

Hyperlink events are defined with "$link"; they can be a numeric value or an object name. Note that the object name parsing is extremely simplistic. Only one word is recognized after "$link", and it must be unconditionally defined for the object (not a property adjective, e.g.). There is no disambiguation; if several objects match the word, the test just picks the first one.

Timer events are defined with "$timer". There is no other information to supply.

If you generate a test event that the game isn't expecting, it will be ignored and the game will move on to the next test.

Chapter: Under the hood

This chapter describes what UGI has changed in the deep reaches of the Parser.i6t template. It is not very interesting.

The low-level functions VM_ReadKeyboard and VM_KeyDelay are gone. Instead, AwaitInput handles the bottom-level glk_select loop. Where VM_ReadKeyboard took two arguments (buffer and table), AwaitInput takes four (input-context, event, buffer, table).

Responsibility for redrawing the status line and printing the prompt has been moved into AwaitInput. (In the old model, these had to be done before calling an input function, meaning they were required in a lot of places. Bugs resulted.)

The old Keyboard routine has been renamed ParserInput. It too now takes input-context, event, buffer, and table arguments. ParserInput is a wrapper around AwaitInput which adds OOPS and UNDO support.

Blank line rejection is still handled in ParserInput (nee Keyboard), but only for the sake of backwards compatibility. By using the "pass blank input lines" option, the game can omit this check from ParserInput. Blank lines are then handled the same way as all other line input (and rejected by the parser at a later stage).

TestKeyboardPrimitive (in Tests.i6t) is now named CheckTestInput. It now takes event and buffer arguments. (Not table, as tokenization is now handled by the caller.)

Responsibility for getting TEST ME input (the CheckTestInput call) has been moved into AwaitInput, right next to the code that gets REPLAY stream input. CheckTestInput returns a flag indicating whether it generated a test event. (This is different from TestKeyboardPrimitive, which was called from the KeyboardPrimitive wrapper, and called through to VM_ReadKeyboard when no test event was available. KeyboardPrimitive is not needed in the new model.)

The parser itself used to call Keyboard in three places (Parser Letter A and NounDomain). It now calls ParserInput, and understands that it could receive any kind of input event (not just line input).

The parser invokes the handling input rulebook after each ParserInput call. If that rulebook generates an action, Parser__parse returns immediately (or NounDomain returns REPARSE_CODE, which leads to the same outcome.) Then the parser rejects blank lines and non-text inputs with an "I beg your pardon" error. After that, we know we have non-blank text input, so parsing proceeds in the original path.

Example: * Changing the Prompt - Changing the command prompt in various contexts.

The old "command prompt" global variable still works, but we'd like a more rule-based approach. Here we use "What now?" for the command prompt (both primary and disambiguation) and "Answer now!" for the final question.

We also use an extended form of the "player consents" phrase, in which we supply the prompt question to use.

"Changing the Prompt"

Include Unified Glulx Input by Andrew Plotkin.

Prompt displaying rule for a command input-context:
     instead say "What now? ".

Prompt displaying rule for the final question context:
     instead say "Answer now! ".

The Kitchen is a room. "You are in a kitchen."
The Outdoors is outside from the Kitchen.

Check going outside:
     if the player consents asking "Outdoors is scary. Are you sure?" and "That was a yes/no question.":
         instead end the story finally;
     else:
         instead say "You fail to overcome your agoraphobia.";

Test me with "out / maybe / no / out / yes".

Example: ** Tick Tick Tick Button - A timer in the status window.

This timer updates a clock in the status window. It never interrupts input or writes to the story window, so we only need an accepting input rule. The rule does its work and then rejects the event. (Timer events are rejected by default, so that last line isn't necessary, but it keeps the flow clear.)

The rule doesn't specify an input-context; timer input operates in all contexts. Thus, the clock keeps running during the yes-or-no question. It would keep running even during the game's final question if we didn't switch it off.

"Tick Tick Tick Button"

Include Unified Glulx Input by Andrew Plotkin.

The Observation Lounge is a room. "This room overlooks the test chamber. A single button is ready to launch the experiment."

A digital clock is fixed in place in the Lounge. "A digital clock blinks impassively overhead."
The description is "The clock reads [readout]."

A button is scenery in the Lounge.

Check pushing the button:
     if the player consents asking "Are you very very sure?":
         say "[line break]It is now [readout]. The experiment [one of]succeeds[or]fails[purely at random].";
         set the timer off;
         instead end the story finally;
     else:
         instead say "You demur for now.";

When play begins:
     now the right hand status line is "[readout]";
     set the timer to 1 second.

Rule for accepting input when handling timer-event:
     increment the counter;
     redraw the status line;
     reject input event.

The counter is initially 60.

To say readout:
     let M be the remainder after dividing the counter by 60;
     let H be the counter divided by 60;
     if H < 10:
         say "0";
     say H;
     say ":";
     if M < 10:
         say "0";
     say M;

Example: *** Master Blaster - A timer that triggers a game action.

In this game, the BLAST command causes an explosion exactly two seconds later.

We want the timer to interrupt player input, so we accept the timer event. The interrupt text input line is not required; UGI always interrupts all input when an event is accepted. But by invoking it explicitly with the "preserving input" option, we ensure that the player's interrupted command will be preloaded into the next input line. It also allows us to print a quick "Interrupting..." message before the rule finishes.

Note that we only accept timer events when in a command input-context. In any other context (yes-or-no, etc) we would want to ignore the event -- it wouldn't make sense to trigger one game action in the middle of another. (In fact, the handling input rules don't even run in non-command contexts, so the event would be wasted anyhow.)

Once the timer event is accepted, it proceeds to the next rulebook, the handling input rules. The rule here generates an explosioning action. (This is a special action which has no understand lines. It cannot be invoked directly by the player; the handling input rule is the only way the explosioning can occur.)

"The Master Blaster"

Include Unified Glulx Input by Andrew Plotkin.

The Repository is a room. "You are in an immense room, even larger than the giant room. A sign reads, 'Say BLAST to wrap it all up!'"

The gap is a thing. "A ragged gap in the south wall shows the way to victory!"

Check entering the gap:
     say "You march through in triumph.";
     turn the timer off;
     instead end the story finally.

Check going south in the Repository:
     if the gap is in the Repository:
         instead try entering the gap.

Blasting is an action applying to nothing.

Understand "blast" as blasting.

Carry out blasting:
     if the timer is active:
         instead say "The explosion is already on its way.";
     set the timer to 2 seconds;
     say "You say the magic word. The air prickles. Wait for it..."

Check answering someone that "blast":
     instead try blasting.

Explosioning is an action applying to nothing.

Carry out explosioning:
     if the gap is not in the Repository:
         now the gap is in the Repository;
         now the gap is fixed in place;
         instead say "A tremendous explosion rocks the Repository! When the dust clears, you see a gap to the south.";
     say "Additional fireworks go off! No new gaps though."

Rule for accepting input for a command input-context when handling timer-event:
     interrupt text input for the story-window, preserving input;
     say "(Interrupting...)";
     accept input event.

Rule for handling input for a command input-context when handling timer-event:
     turn the timer off;
     handle the current input event as the action of explosioning.

Test me with "blast / $timer / blast / blast / $timer / south".

Example: ** Maze of Keys - Controlling the game with single keystrokes.

In this example, the underworld uses a different input mechanism: single keystrokes. Character events are translated into line input for the parser. (This is a crude approach. See the "Maze of Keys 2" example for a tidier model.)

"Maze of Keys"

Include Unified Glulx Input by Andrew Plotkin.
Include Unicode Character Names by Graham Nelson.

The Kitchen is a room. "You are in a kitchen. An open trap door beckons you downward."

Aboveground is a region. The Kitchen is in Aboveground.

Maze10 is a room. "You are in a maze of twisty passages, basically all alike."
Maze20 is a room. "You are in a maze of twisty passages, all pretty much alike."
Maze01 is a room. "You are in a maze of twisty passages, all basically alike."
Maze11 is a room. "You are in a maze of twisty passages, all kind of alike."
Maze21 is a room. "You are in a maze of twisty passages, more or less all alike."
Maze02 is a room. "You are in a maze of twisty passages, pretty much all alike."
Maze12 is a room. "You are in a maze of twisty passages, all alike."
Maze22 is a room. "You are in a maze of twisty passages, all more or less alike."
Maze32 is a room. "You are in a maze of twisty passages, all sort of alike."
Maze03 is a room. "You are in a maze of twisty passages, kind of all alike."
Maze13 is a room. "You are in a maze of twisty passages, all quite alike."
Maze23 is a room. "You are in a maze of twisty passages, quite all alike."
Maze33 is a room. "You are in a maze of twisty passages, sort of all alike."

Maze12 is below the Kitchen.
Maze10 is west of Maze20.
Maze10 is north of Maze11. Maze20 is north of Maze21.
Maze01 is west of Maze11. Maze11 is west of Maze21.
Maze01 is north of Maze02. Maze11 is north of Maze12.
Maze02 is west of Maze12. Maze12 is west of Maze22. Maze22 is west of Maze32.
Maze12 is north of Maze13. Maze22 is north of Maze23. Maze32 is north of Maze33.
Maze03 is west of Maze13. Maze23 is west of Maze33.

Rule for printing the name of a room (called R) when R is not in Aboveground:
     say "Maze".

Check going down from the Kitchen:
     say "(Down here, single-keystroke commands rule. Use the arrow keys or NSEW to move around; Z to undo; U or escape to return upstairs.)";
     continue the action.

Check going up when the location is not in Aboveground:
     say "You fumble your way back to the light.";
     now the player is in the Kitchen;
     stop the action.

Prompt displaying rule when the location is not in Aboveground:
     instead say "==>".

Setting up input rule when the location is not in Aboveground:
     now the input-request of the story-window is char-input;
     set input undoable;
     rule succeeds.

Accepting input rule when the location is not in Aboveground and handling char-event:
     let C be the current input event character;
     if C is special keycode left or C is Unicode Latin small letter w or C is Unicode Latin capital letter W:
         say "GO WEST[line break]";
         replace the current input event with the line "go west";
         rule succeeds;
     if C is special keycode right or C is Unicode Latin small letter e or C is Unicode Latin capital letter E:
         say "GO EAST[line break]";
         replace the current input event with the line "go east";
         rule succeeds;
     if C is special keycode up or C is Unicode Latin small letter n or C is Unicode Latin capital letter N:
         say "GO NORTH[line break]";
         replace the current input event with the line "go north";
         rule succeeds;
     if C is special keycode down or C is Unicode Latin small letter s or C is Unicode Latin capital letter S:
         say "GO SOUTH[line break]";
         replace the current input event with the line "go south";
         rule succeeds;
     if C is special keycode escape or C is Unicode Latin small letter u or C is Unicode Latin capital letter U:
         say "GO UP[line break]";
         replace the current input event with the line "go up";
         rule succeeds;
     if C is Unicode Latin small letter l or C is Unicode Latin capital letter L:
         say "LOOK[line break]";
         replace the current input event with the line "look";
         rule succeeds;
     if C is Unicode Latin small letter z or C is Unicode Latin capital letter Z:
         say "UNDO[line break]";
         replace the current input event with the line "undo";
         rule succeeds;
     interrupt text input for the story-window;
     say "('[extended C]' is not a valid key.)";
     reject the input event.

Test me with "down / $char s / $char w / $char right / $char up / $char escape / look".

Example: *** Maze of Keys 2 - Controlling the game with single keystrokes.

This is nearly the same as the previous example. But now, instead of changing char input events to line input events, we handle char events directly as going actions.

The work is now done in the accepting input rulebook. We no longer pretend that we're entering line input, and the parser is entirely bypassed.

"Maze of Keys 2"

Include Unified Glulx Input by Andrew Plotkin.
Include Unicode Character Names by Graham Nelson.

The Kitchen is a room. "You are in a kitchen. An open trap door beckons you downward."

Aboveground is a region. The Kitchen is in Aboveground.

Maze10 is a room. "You are in a maze of twisty passages, basically all alike."
Maze20 is a room. "You are in a maze of twisty passages, all pretty much alike."
Maze01 is a room. "You are in a maze of twisty passages, all basically alike."
Maze11 is a room. "You are in a maze of twisty passages, all kind of alike."
Maze21 is a room. "You are in a maze of twisty passages, more or less all alike."
Maze02 is a room. "You are in a maze of twisty passages, pretty much all alike."
Maze12 is a room. "You are in a maze of twisty passages, all alike."
Maze22 is a room. "You are in a maze of twisty passages, all more or less alike."
Maze32 is a room. "You are in a maze of twisty passages, all sort of alike."
Maze03 is a room. "You are in a maze of twisty passages, kind of all alike."
Maze13 is a room. "You are in a maze of twisty passages, all quite alike."
Maze23 is a room. "You are in a maze of twisty passages, quite all alike."
Maze33 is a room. "You are in a maze of twisty passages, sort of all alike."

Maze12 is below the Kitchen.
Maze10 is west of Maze20.
Maze10 is north of Maze11. Maze20 is north of Maze21.
Maze01 is west of Maze11. Maze11 is west of Maze21.
Maze01 is north of Maze02. Maze11 is north of Maze12.
Maze02 is west of Maze12. Maze12 is west of Maze22. Maze22 is west of Maze32.
Maze12 is north of Maze13. Maze22 is north of Maze23. Maze32 is north of Maze33.
Maze03 is west of Maze13. Maze23 is west of Maze33.

Rule for printing the name of a room (called R) when R is not in Aboveground:
     say "Maze".

Check going down from the Kitchen:
     say "(Down here, single-keystroke commands rule. Use the arrow keys or NSEW to move around; Z to undo; U or escape to return upstairs.)";
     continue the action.

Check going up when the location is not in Aboveground:
     say "You fumble your way back to the light.";
     now the player is in the Kitchen;
     stop the action.

Prompt displaying rule when the location is not in Aboveground:
     instead say "==>".

Setting up input rule when the location is not in Aboveground:
     now the input-request of the story-window is char-input;
     set input undoable;
     rule succeeds.

Checking undo input rule when the location is not in Aboveground:
     let C be the current input event character;
     if C is Unicode Latin small letter z or C is Unicode Latin capital letter Z:
         say "(Undoing one turn...)";
         rule succeeds.

Handling input rule when the location is not in Aboveground and handling char-event:
     let C be the current input event character;
     if C is special keycode left or C is Unicode Latin small letter w or C is Unicode Latin capital letter W:
         say "(You try going west...)";
         handle the current input event as the action of going west;
         rule succeeds;
     if C is special keycode right or C is Unicode Latin small letter e or C is Unicode Latin capital letter E:
         say "(You try going east...)";
         handle the current input event as the action of going east;
         rule succeeds;
     if C is special keycode up or C is Unicode Latin small letter n or C is Unicode Latin capital letter N:
         say "(You try going north...)";
         handle the current input event as the action of going north;
         rule succeeds;
     if C is special keycode down or C is Unicode Latin small letter s or C is Unicode Latin capital letter S:
         say "(You try going south...)";
         handle the current input event as the action of going south;
         rule succeeds;
     if C is special keycode escape or C is Unicode Latin small letter u or C is Unicode Latin capital letter U:
         say "(You try going up...)";
         handle the current input event as the action of going up;
         rule succeeds;
     if C is Unicode Latin small letter l or C is Unicode Latin capital letter L:
         say "(You look around...)";
         handle the current input event as the action of looking;
         rule succeeds;
     say "('[extended C]' is not a valid key.)";
     reject the input event.

Test me with "down / $char s / $char w / $char right / $char up / $char escape / look".

Example: ** A Study In Memoriam - A pure-hyperlink game.

Here we drop text input entirely. (Dropping the standard parser input line request rule accomplishes this.) Instead we set up input to be hyperlink-based. Hyperlink events are translated into the examining action, which happily works for both on-stage objects and off-stage memories.

"A Study In Memoriam"

Include Unified Glulx Input by Andrew Plotkin.

The Study is a room. "You are in your cluttered study."

The messy desk is a supporter in the Study. The description is "You found this battered antique in a battered [antique shop] in Chapel Hill. As you recall, it was sitting behind [a microscope]."

A fossil is on the desk. The description is "Some species of [ammonite]."

A copper fulgurite is on the desk. The description is "It's a chunk of melted copper that you picked up from the base of a telephone pole. It's not really fulgurite, minerallogically speaking. But it [italic type]was[roman type] formed by [lightning]!"

A memory is a kind of thing.

The ammonite is a memory. The description is "You've dreamed of drifting through antediluvian seas, the master of all you survey... Then the bony fishes come. The damned bony fishes."

The microscope is a memory. The printed name is "defunct electron microscope". The description is "Yes, you once discovered an electron microsope in [an antique shop]. It was a console device, like a kitchen counter with switches all over the top and vacuum pumps hanging out underneath. You wonder if anyone ever purchased it."

The antique shop is a memory. The description is "You discovered a mad antique shop on a years-ago trip to North Carolina. The prize of its collection was [a microscope], though you doubt anyone else would think of it that way. You bought [hyperlink desk]an old desk[/hyperlink] instead; the very one in this room."

The lightning is a memory. The description is "One can never remember lightning properly."

Rule for printing the name of an object (called O):
     say "[hyperlink O][printed name of O][/hyperlink]";

The standard parser input line request rule does nothing.

Prompt displaying rule for a command input-context:
     say "[first time](Touch a link.)[only]";
     rule succeeds.

Setting up input rule:
     now the story-window is hyperlink-input-request.

Handling input rule for a command input-context when handling hyperlink-event:
     let O be current input event hyperlink object;
     if O is nothing:
         reject the input event;
     if O is a room:
         handle the current input event as the action of looking;
     else:
         handle the current input event as the action of examining O;
     rule succeeds.

Before examining something:
     say "--- [The noun] ---[paragraph break]";

Section - not for release

The test-me is a memory.

When play begins:
     say "(Hit [test-me] to run the tests.)";

Instead of examining test-me:
     run test-me.

To run test-me: (- special_word = 'me//'; TestScriptSub(); -).

Test me with "$link fossil / $link ammonite / $link study".

Example: **** Secret Number Request - A phrase to query the player for a number.

In this example we ask the player for a number. It's very much like "if the player consents..." except that we get back a number rather than a yes-or-no decision.

We'll want a new input-context for this operation, so we invent one: numeric context. Our prompt displaying rule relies on this to give the player a special prompt for the number-typing operation.

The "number waited for" phrase demonstrates doing a low-level input operation. First we must clear all input requests for the story window. (We don't want a request left over from regular command input.) Then we set the input request we want -- line input only. Then we "await input", using our special numeric context.

Then it gets a little bit messy. There's no easy I7 access to the input buffer. We can't use "the player's command" because that's a parser variable. We're doing this behind the parser's back, so no player's command is ever set. Nor can we match against an I7 token like "[number]".

Instead, we rely on an existing I6 function called TryNumber. This returns the parsed number, or -1000 if no number was typed. Loop until we get something valid.

Unfortunately, TryNumber is set up to use the parser's input buffer. That's why the input phrase is

await input in numeric context with primary buffer;

This winds up stomping on "the player's command"; that snippet will be invalid for the rest of the turn. Too bad!

A better implementation would rely on the secondary buffer. We couldn't use TryNumber, though, so it would be a longer and messier example.

"Secret Number Request"

Include Unified Glulx Input by Andrew Plotkin.

The Alley is a room. "You are in that alley from that spy game. A steel door is to the east; next to it is a button and a speaker grille."

The steel door is scenery in the Alley. The description is "The door is closed and locked."

Check entering the steel door:
     instead say "The door isn't open."
Check going east in the Alley:
     instead try entering the steel door.
Check going south in the Alley:
     instead say "You can't quit now."

The secret number is initially 0.

The button is scenery in the Alley.

Check pushing the button:
     if the secret number is 0:
         now the secret number is a random number between 100 and 500;
     say "A crackly voice asks, 'What's the secret number?'";
     let N be the number waited for;
     if N is the secret number:
         say "'Did you say [N]? You're right!' The door pops open and someone on the other side tasers you.";
         end the story finally;
     else:
         say "'Did you say [N]? That's not right. The secret number today is [secret number].'[paragraph break]The door does not open.";
     stop the action.

Numeric context is an input-context.

Prompt displaying rule for numeric context:
     instead say "==>".

To decide what number is the number waited for:
     while 1 is 1:
         clear all input requests;
         now the input-request of the story-window is line-input;
         await input in numeric context with primary buffer;
         let N be the hacky low-level number check;
         if N is not -1000:
             decide on N;
         say "Please enter a number.";

To decide what number is the hacky low-level number check: (- TryNumber(1) -).