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 Å-machine backend copies all style definitions verbatim into the story file, whiche makes them available to interpreters at runtime. The official javascript Å-machine interpreter in turn hands them over to the web browser. Thus, with this particular setup, the full set of CSS properties are supported.

Nevertheless, authors are advised to think of styling as optional icing on the cake, and to avoid encoding critical information in the style hints. Be considerate of the people who choose to engage with your story using a screen reader, or who prefer to play on vintage hardware. Never change the text colour without also changing the background colour, as the player may be using an interpreter with a dark or light default background.

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.

font-family

The value is checked for the presence or absence of the keyword monospace.

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.

The Å-machine backend treats status bar styling like it does div styling in general: All style properties are passed to the interpreter, which is then free to interpret them according to the CSS standard or ignore them. The official javascript interpreter simply hands them over to the web browser. The same advice applies as for general divs: Don't assume that the reader will use an interpreter that obeys all style hints.

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.

Hyperlinks

The Å-machine backend supports a form of hyperlinks, for the purpose of simplifying text entry on mobile devices. The syntax is:

(link $Target) statement
[Copy to clipboard]

The following example creates a piece of text, “clickable”. Clicking on the text has the same effect as typing the words THE LINKED TEXT and pressing return.

Here is some (link [the linked text]) { clickable } text.
[Copy to clipboard]

Note that the words are appended to the end of the current contents of the input buffer, so that the player might type a verb, and then complete the sentence by clicking on e.g. a noun from a room description.

The link target must be a flat list of words and/or integers, like the ones obtained from (get input $). It can be computed at runtime.

Å-machine interpreters are not required to support hyperlinks at all, and some may provide an option to turn them off for players who find them distracting. The built-in predicate (interpreter supports links) can be used to check whether hyperlinks are supported and enabled. When they are not, the would-be-clickable text shows up as normal text.

Hyperlinks are always disabled on the Z-machine backend.

Resources

The Å-machine backend supports embedded graphics, as well as links to external web sites, using a common mechanism. A resource is defined with the following syntax:

(define resource id) location
[Copy to clipboard]

The id is any bound Dialog-value, such as an integer, a dictionary word, an object, or even a list.

The location is either a URL with one of the schemes http, https, or mailto, or a local filename. Local files are copied into the .aastory file, and this is the recommended way to work with feelies and embedded graphics.

Links to resources

When a resource has been defined, it's possible to link to it using the following syntax:

(link resource $Id) statement
[Copy to clipboard]

Here is an example:

(define resource @manual)
feelies/manual.pdf
(define resource @web)
https://example.com/
(understand command [about])
(perform [about])
Please make sure to check out the (link resource @manual) {printed
manual} that was bundled with the game.
(par)
For more works by the same author, head over to
(link resource @web) {example.com}.
[Copy to clipboard]

Not every interpreter or backend supports links. Use (interpreter supports links) to check for this feature at runtime. Alternatively, make sure that all of your sentences with links also make sense as plain text.

It is up to the interpreter to decide what happens when the player clicks on a link to a resource. The Å-machine web interpreter opens the file or web site in a new browser tab.

Embedded resources

It is also possible to embed a resource, such as a picture, into the story text. This is done with the built-in predicate (embed resource $).

In general, interpreters won't be able to embed every conceivable kind of resource. When defining a resource, it is possible to add an alt-text that can be displayed instead of the resource. The alt-text appears at the end of the resource definition, separated from the location by a semicolon:

(define resource id) location; alt-text
[Copy to clipboard]

If no alt-text is specified, the filename (i.e. location) is used as a default value. Only the actual filename is used as alt-text, not the full path.

The built-in predicate (interpreter can embed $) checks whether the current interpreter is capable of displaying a given resource. If not, (embed resource $) will display the alt-text instead.

Example:

(define resource #lighthouse)
media/lighthouse.png; A small model of a lighthouse.
(style class @center)
margin-top: 1em;
margin-bottom: 1em;
text-align: center;
#lighthouse
(name *)lighthouse
(dict *)small tiny model silver
(descr *)
It's a tiny model of a lighthouse, made of silver.
(if) (interpreter can embed *) (then)
(div @center) (embed resource *)
(endif)
The lighthouse glistens in the moonlight.
[Copy to clipboard]

In the above example, the (div $) splits the text into two paragraphs if the interpreter is able to embed png files. Otherwise, there will be no paragraph break, and no alt-text.

With an eye towards future extensibility, this language feature has been designed to be open-ended. Resources could conceivably be any kind of multimedia, including sound and animation. Interpreters are supposed to restrict this vast space of possibilities to a manageable set of supported file formats. The current version of the Å-machine web interpreter, for instance, only embeds graphics in png or jpeg format. Other backends (Z-machine, debugger) just print the alt-text.

About local filenames

Local path names are interpreted relative to the resource directory, which defaults to the current working directory. A different resource directory can be specified with the -r option to dialogc.

All resources bundled into an .aastory file must have unique filenames, regardless of their path. Thus, you can't define one resource with the filename “hero/face.png” and another with the filename “heroine/face.png”. This restriction might be relaxed in future versions.

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.