Chapter 5: Input and output

Divisions and style classes

As we saw in the introductory chapter, text inside rule bodies gets printed. The text is treated as a sequence of words and punctuation. Furthermore, the (par) built-in predicate produces paragraph breaks, and (line) produces line breaks.

Output can be further organized into divisions, or divs for short. Divisions are rectangular areas of text, often spanning the full width of the containing window, to which style hints can be applied.

The purpose of divisions and style hints is to allow the same story to run on many different platforms, with varying support for advanced presentation techniques. Furthermore, divisions promote a clean source code structure, by isolating the presentation aspects from the text and logic of the story.

The syntax for creating a division is (div $) followed by a statement. The parameter is a style class, and the statement is usually a block. Example:

(div @quote) {
This could be displayed in italics, for instance, if
the corresponding style hint is defined for the @quote
class.
}
[Copy to clipboard]

Style classes are identified by words, but these words don't end up in the game dictionary (unless they're also used elsewhere, of course). This is particularly relevant if removable word endings are used (see the input section below).

What a given class is supposed to look like is determined by compile-time queries to the (style class $) predicate, with the name of the class as parameter. Style attributes are specified using a small subset of CSS, the widely known Cascading Style Sheets syntax of the web. All style attributes are regarded as optional hints, so a backend or interpreter can pick and choose among them, or ignore them altogether.

Here is an example style definition:

(style class @quote)
font-style: italic;
margin-top: 2em;
margin-bottom: 2em;
[Copy to clipboard]

The Z-machine backend only supports a limited number of style attributes. For divs in the main text area, they are:

font-style

Supported values are italic and inherit.

font-weight

Supported values are bold and inherit.

margin-top

The value must be an integer followed by the keyword em, with no whitespace in between. The size of the resulting margin is the given number of blank lines.

margin-bottom

The value must be an integer followed by the keyword em, with no whitespace in between. The size of the resulting margin is the given number of blank lines.

There is currently no support for the combined margin attribute.

Divs and the flow of execution

Divisions can be nested, but only in a strict tree-like structure. There are three ways to leave a div during normal program execution:

Regardless of how the inner statement terminates, the div rectangle is concluded, its bottom margin takes effect, and the style attributes of the surrounding div are restored on the fly.

Inline styles and formatting

To change the current text style without entering a new division, use the built-in predicates (bold), (italic), (reverse), and (fixed pitch) to enable each respective style, and (roman) to disable all four. The predicate (unstyle) reverts to the default style of the current division, which is usually roman.

The built-in predicate (uppercase) forces the next character to be printed in uppercase. The standard library uses this to define the following convenient predicates:

(A full $Obj)(uppercase) (a full $Obj)
(A $Obj)(uppercase) (a $Obj)
(It $Obj)(uppercase) (it $Obj)
(Name $Obj)(uppercase) (name $Obj)
(That $Obj)(uppercase) (that $Obj)
(The full $Obj)(uppercase) (the full $Obj)
(The $Obj is)(uppercase) (the $Obj is)
(The $Obj)(uppercase) (the $Obj)
[Copy to clipboard]

The built-in predicate (space $) prints a given number of space characters in succession.

The status bar

The special syntax (status bar $Class), followed by a statement, creates a special kind of division located at the top of the screen. The appearance of this status bar area is controlled using style hints.

The status bar itself should have a height attribute, specified in em units. The Z-machine backend will reserve this many lines at the top of the screen.

(style class @status)
height: 1em;
(program entry point)
(status bar @status) {
Look at my status bar!
}
[Copy to clipboard]

On the Z-machine, text inside the status bar is always rendered in a fixed-pitch font. When entering the status bar environment, Dialog fills the status bar area with reverse space characters, enables reverse video, and positions the cursor in the top left corner.

The status bar area can be split into multiple segments horizontally. These segments are described using nested so called floating divs. These have a float attribute that is set to either left or right to determine where inside the status bar the segment should be located. The width of the segment can be specified in absolute numbers (using the ch unit) or as a percentage of the width of the surrounding div, which is normally the status bar itself.

Floating divs can be further subdivided, either horizontally using floating divs, or vertically using ordinary divs (or simply with line breaks).

Let's extend our simple status bar with a score display in the upper right corner:

(current score 0)
(style class @status)
height: 1em;
(style class @score)
width: 20ch;
float: right;
(program entry point)
(status bar @status) {
(div @score) {
(current score $S)
Score: $S
}
Look at my status bar!
}
[Copy to clipboard]

In CSS, em represents the height of a capital M, while ch represents the width of the digit zero. On the Z-machine, they both refer to the width or height of a character, depending on context, and are interchangeable. But it is good practice to specify widths in ch and heights in em.

The Z-machine obeys the following style attributes in the status bar area:

height

For the top-level (status bar $) div only: The desired height, expressed as an integer followed by the word em.

width

For divs nested inside the status bar area: The desired width, as an integer followed by a unit. Supported units are em and %. An em is interpreted as the width of a character in the fixed-pitch font.

float

For divs nested inside the status bar area: The desired location within the surrounding div. Must be either left or right.

margin-top

Works as it does for ordinary divs, but is ignored for the top-level (status bar $) itself.

margin-bottom

Works as it does for ordinary divs, but is ignored for the top-level (status bar $) itself.

It is possible to invoke (status bar $) with different style classes at different times, in order to vary the look of the status bar area during gameplay. When reducing the size of the status bar area (e.g. drawing a status bar of height 1em after having drawn one of height 2em), be aware that some interpreters hide the extraneous lines, while others regard them as being part of the main window.

It is not possible to save the game state (to a file or undo buffer) from within the status bar area, or to enter a nested (status bar $) from within the status bar area. Such operations will fail.

Visualizing progress

The built-in predicate (progress bar $ of $) draws a progress bar scaled to fit the width of the current div. It is rendered with character graphics on the Z-machine backend.

Clearing the screen

To clear the screen below the status bar area, use (clear). To clear the entire screen and disable the status bar, use (clear all). Be aware that on some interpreters, clearing interferes with the player's ability to scroll back and review earlier parts of the play session.

Input

User input is represented by dictionary words.

The Dialog compiler collects all dictionary words mentioned explicitly in the source code (with the @-prefix or as bare words inside lists), as well as every literal word that can come out of a (collect words) or (determine object $) expression. In addition, the system makes sure to provide a single-letter dictionary word for every character supported by the underlying platform. Together, these words make up what's called the game-wide dictionary.

It may be helpful to know that there's a difference between dictionary words at the Dialog level, and the native, low-level words of the Z-machine. Dialog dictionary words are an abstraction over several different kinds of internal representation. That being said, it is the specific constraints of the low-level Z-machine dictionary that determine where the split occurs between the essential and optional parts of a given dictionary word.

There are two built-in predicates for obtaining input from the player. One waits for a single keypress, while the other reads a full line of input.

Get key

(get key $Char)
[Copy to clipboard]

This predicate waits for the player to type a character.

Some interpreters indicate that the game is waiting for input by displaying a flashing cursor. Others don't, so story authors may wish to prompt the reader explicitly.

The parameter, $Char, is unified with a dictionary word representing the character that was typed, e.g. @e if the E key was pressed. Note that dictionary words are case-insensitive, so for letters of the alphabet there is no way to tell whether the player was holding shift or not.

Some special keys correspond to dictionary words that can't be represented directly in Dialog source code. The dictionary words for these keys can be obtained at runtime, by querying the following built-in predicates:

(word representing return $Char)
(word representing space $Char)
(word representing backspace $Char)
(word representing up $Char)
(word representing down $Char)
(word representing left $Char)
(word representing right $Char)
[Copy to clipboard]

A simple keypress dispatcher might look like this:

(program entry point)
(get key $Key)
(handle keypress $Key)
(handle keypress @a)
'A' was pressed.
(handle keypress @b)
'B' was pressed.
(handle keypress (word representing return $))
RETURN was pressed.
[Copy to clipboard]

Get input

(get input $WordList)
[Copy to clipboard]

This query blocks execution until the player types a line of input, followed by return. Different interpreters provide different levels of line-editing facilities, ranging from simple backspace handling all the way up to input history and spell checking.

The parameter, $WordList, is unified with a list where each element represents a word typed by the player. The punctuation characters full stop, comma, double quote, semicolon, and asterisk are treated as individual words; the remaining text is separated into words by whitespace. If a word is recognized as one that appears in the program-wide dictionary, then the element will be that dictionary word. Else, if the word is a decimal number in the range 0–16383, the element will be that number.

If the word was neither recognized, nor found to be a decimal number, then Dialog will attempt to remove certain word endings, and check whether the remaining part of the word exists in the dictionary. This procedure is necessary for games written in e.g. German, whereas English games generally do not require it.

To specify removable endings, add one or more rule definitions to the predicate (removable word endings). Each rule body should consist of one or more word endings:

(removable word endings)
%% German adjective endings
en es em e
(removable word endings)
%% German noun endings
e en s es
[Copy to clipboard]

The part that remains after removing the ending is referred to as the stem of the word. If the stem consists of at least two letters, and exists in the program-wide dictionary, then the resulting dictionary word will have the stem as its essential part, and the ending as its optional part. During comparison (unification with another bound value), only the essential part is considered. During printing, both the essential part and the optional part are printed.

During tracing, dictionary words are displayed with a plus sign (+) separating the essential and optional parts. Thus, if the German word “klein” is part of the game-wide dictionary, and the player enters KLEINES, that word appears as @klein+es in the trace logs, and unifies successfully with @klein.

If a word of input isn't recognized at all, even after considering the removable word endings, then it's an unrecognized dictionary word. It can still be stored in a variable, retrieved, and printed back, and it will unify successfully with other instances of the same unrecognized word. When tracing is enabled, unrecognized dictionary words are displayed with a plus sign at the end.

For instance, the input TAKE 02 UNKNOWNWORD,X BALL may, depending on the contents of the dictionary, be represented by the list: [take 2 unknownword , x ball]. As part of a trace, it might be displayed as [take 2 unknownword+ , x ball].

Special gotcha: Recall that zero-prefixed numbers in the source code, as well as numbers that are out of range, are treated as words. If 007 appears in the program in such a way that it becomes part of the program-wide dictionary, then it will show up as a dictionary word in the list returned by (get input $). Otherwise, it will be represented by the numerical value 7.

Splitting input by keywords

During parsing, it is often necessary to scan a list for certain keywords, and then split it into two sublists, representing the elements on either side of the matched keyword. It is straightforward to implement this using ordinary rules in Dialog. However, for performance reasons the language also provides a built-in predicate:

(split $Input by $Keywords into $Left and $Right)
[Copy to clipboard]

$Input and $Keywords must be lists of simple values, i.e. they mustn't contain sublists.

The $Input list will be scanned, starting at its head, until the first element that also appears in $Keywords is found. A list of the elements that came before the keyword is unified with $Left, and a list of the elements that follow it is unified with $Right. That is, neither $Left nor $Right includes the keyword itself.

When invoked as a multi-query, the predicate backtracks over each matching position. Thus:

*(split [the good , the bad and the ugly]
by [and ,]
into $Left and $Right)
[Copy to clipboard]

will succeed twice: First, binding $Left to [the good] and $Right to [the bad and the ugly], and then a second time binding $Left to [the good , the bad] and $Right to [the ugly].

The split-by predicate can also be used to check whether a list contains one or more of a set of keywords. The standard library uses it that way in the following rule definition:

($X contains one of $Y)
(split $X by $Y into $ and $)
[Copy to clipboard]

Determining objects from words

This section is mainly of concern for library programmers, so story authors may safely skip it.

Dialog has a special construct for matching player input against in-world object names in a very efficient way:

(determine object $Obj)
object generator
(from words)
word generator
(matching all of $Input)
[Copy to clipboard]

This statement will backtrack over every object $Obj for which:

object generator succeeds, and
word generator, when exhausted, emits at least every word in the $Input list.

The variable $Obj should appear both in the object generator and in the word generator, and the object generator should contain a multi-query to backtrack over a set of objects. A canonical example is:

(determine object $Obj)
*($Obj is in scope)
(from words)
*(dict $Obj)
(matching all of $Input)
[Copy to clipboard]

A non-optimizing compiler could deal with this construct as follows: First, compile normal code for the object generator. Then, exhaust every branch of the word generator, collecting all its output into a temporary list of words. Finally, check that every word of $Input appears in the temporary list.

However, the Dialog compiler and debugger both perform the following optimization: At compile-time, they analyze the word generator statically, and construct a reverse lookup table, mapping words of input to objects. At runtime, this table is consulted first, based on $Input, to determine what objects the player might be referring to. So, for instance, if the input is LARGE HAT, and there are only two game objects for which (dict $Obj) can produce both of those words, then $Obj will now be bound to each of those two objects in turn. But if there are dozens of large hats, $Obj may instead be left unbound; the compiler is allowed to make a trade-off between speed and memory footprint. Either way, after this step, the operation proceeds as in the unoptimized case.