Dialog contains a set of built-in predicates for checking if a value is of a particular type. They are:
(unbound $X)
Succeeds if and only if $X is currently an unbound variable.
(number $X)
Succeeds if and only if $X is bound to a number.
(word $X)
Succeeds if and only if $X is bound to a dictionary word.
(empty $X)
Succeeds if and only if $X is bound to an empty list.
(non-empty $X)
Succeeds if and only if $X is bound to a non-empty list.
(list $X)
Succeeds if and only if $X is bound to a list (empty or non-empty).
The last one comes with an extra feature:
(object $X)
If $X is bound to an object, the query succeeds. If it is bound to anything else, the query fails. But if $X is unbound, *(object $X) backtracks over every object in the game.
The Dialog language is designed for symbolic manipulation, predicate logic, and storytelling. Arithmetic is possible, but the syntax is rather clunky.
($A plus $B into $C)
A and B must be bound to numbers; C is unified with their sum. If the result is outside the valid range of numbers, the query fails.
($A minus $B into $C)
A and B must be bound to numbers; C is unified with their difference. If the result is outside the valid range of numbers, the query fails.
($A times $B into $C)
A and B must be bound to numbers; C is unified with their product. If the product is outside the valid range of numbers, the query succeeds, but the numeric result is unpredictable (i.e. it depends on the interpreter).
($A divided by $B into $C)
A and B must be bound to numbers; C is unified with the (integer) quotient after dividing A by B. The query fails if B is zero.
($A modulo $B into $C)
A and B must be bound to numbers; C is unified with the remainder after dividing A by B. The query fails if B is zero.
(random from $A to $B into $C)
A and B must be bound to numbers, such that B is greater than or equal to A. A random number in the range A to B (inclusive) is picked, and then unified with C.
($A < $B)
This predicate succeeds if and only if A is numerically less than B.
($A > $B)
This predicate succeeds if and only if A is numerically greater than B.
Common to all of the above predicates is that they fail if A or B is unbound, or bound to a non-number. C may be bound or unbound; it is unified with the result of the computation.
To check for numerical equality, use regular unification, i.e. ($ = $).
All numbers in Dialog are restricted to the range 0–16383 (inclusive). This range directly supports four-digit numbers such as years and PIN codes. Pocket money should be fairly straightforward to implement by counting in cents; story authors (or library developers) that require more sophisticated number crunching will have to get creative.
To change the current text style, use the built-in predicates (bold), (italic), (reverse), and (fixed pitch) to enable each respective style, and (roman) to disable all four.
The built-in predicate (uppercase) forces the next character to be printed in uppercase. The standard library uses this to define a number of convenient predicates such as:
(The $Obj) | (uppercase) (the $Obj) |
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.
The built-in predicate (space $) prints a given number of spaces in succession. Its vertical counterpart, (par $), produces a given number of blank lines in succession.
(program entry point) | |
(clear all) | |
(par 10) | |
(space 10) | |
(bold) | |
This text is printed in bold, starting at row 11, column 11. |
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) expression. In addition, the system makes sure to provide a single-letter dictionary word for every character supported by the underlying platform.
On a side note, don't worry about the Z-machine game dictionary getting bloated with every possible one-character word. It may appear so at the Dialog level, but the underlying runtime representation is different.
There are three built-in predicates for obtaining input from the player. One waits for a single keypress, while the other two read a full line of input.
(get key $Char) |
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 on the Z-machine, dictionary words are case-insensitive, so at least for the 26 letters of the English 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) |
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. |
(get input $WordList) |
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 |
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 that dictionary word is returned.
If all else fails, the word of input will be represented by a list of characters, each character itself a dictionary word.
For instance, the input TAKE 02 UNKNOWNWORD,X BALL may, depending on the contents of the dictionary, be represented by the list: [take 2 [u n k n o w n w o r d] , x ball]
In practice, unknown words are ignored by most rules dealing with player input. The reason for providing such words as lists of characters is to allow programs to print them back, e.g. to complain that a particular word wasn't understood.
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.
(get raw input $CharList) |
This query reads a line of input just like (get input $), but does not try to interpret it as a series of dictionary words or numbers.
This is useful, for instance, for asking the player what their name is. It makes sense to treat the player's name as an opaque list of characters, just in case their first or last name is over nine letters long and happens to match a dictionary word: Recall that long dictionary words are truncated—and we generally do not want to truncate the player's name when we print it back.
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) |
$Keywords must be a list of simple values (i.e. no unbound variables or 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) |
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 $) |
Two built-in predicates control low-level tracing of queries:
(trace on)
Enables tracing. Following this, debugging information will be printed when queries are made, and when rule bodies are entered.
(trace off)
Disables tracing.
If your program source code contains a query to (trace on) anywhere, the compiler will insert extra instructions all over the generated code, to deal with tracing. This is known as instrumenting the code, and it makes the program slower and larger. Thus, you'll only want to use these predicates temporarily, during debugging. The compiler prints a warning when it adds the extra instructions.
Please be aware that the Dialog compiler optimizes your program, and you will be tracing the optimized code. This means that certain queries and rules will be missing from the debug printouts. And occasionally it may seem like a rule body is being entered even though it shouldn't (in which case the rule will typically fail right after the printout). Admittedly, tracing at this low level can make debugging harder than it needs to be, but it is highly useful when trying to speed up a program. In the future, an interactive debugger might be developed, for stepping through the program statements at a more intuitive level. Until then, tracing nevertheless offers a powerful complement to manually coded debug printouts.
The following built-in predicates offer low-level control over the interpreter and the Dialog runtime. This is decidedly in the domain of library code, so story authors rarely need to worry about these predicates.
(quit)
Immediately terminates the program. This predicate neither fails nor succeeds.
(restart)
Resets the program to its initial state. The only part of the game state that may survive a restart is the state of the output machinery (including the current style and on-screen contents, and whether the transcript feature is on or off). If the operation succeeds, execution resumes from the start of the program. If there is an error, or the interpreter doesn't support restarting, execution continues normally, i.e. the query succeeds.
(save $ComingBack)
Attempts to save the current game state to a file. The interpreter takes care of asking the player for a filename. In the event of a save error, or if the operation was cancelled, the query fails. On success, the parameter is unified with 0 if we just saved the state, and with 1 if we just restored the state from a file saved by this query.
(restore)
Attempts to restore the current game state from a file. The interpreter takes care of asking the player for a filename. The only part of the game state that may survive a restore is the state of the output machinery (including the current style and on-screen contents, and whether the transcript feature is on or off). If the operation succeeds, execution resumes after the query from which the save file was written. Otherwise, in the event of a load error or if the operation was cancelled, execution continues normally, i.e. the query succeeds.
(save undo $ComingBack)
Works just like (save $), but stores the game state in a buffer in memory. This operation is typically invoked once per move.
(undo)
Works like (restore), but restores the game state from the undo buffer. If there is no saved undo state, the predicate fails. If there's some other problem—such as the interpreter imposing a limit on the number of undo states that are retained in memory—the predicate succeeds, and execution continues normally.
(interpreter supports undo)
Succeeds if and only if the current interpreter declares that it supports undo functionality.
(script on)
Enables the transcript feature. The interpreter takes care of asking the player for a filename. If the operation succeeds, the query suceeds. In case of an error, or if the operation was cancelled, the query fails.
(script off)
Disables the transcript feature. This predicate always succeeds.
(display memory statistics)
Prints a line of information specific to the compiler backend (there is currently only one), about the peak memory usage in the heap and auxiliary heap areas. The size of these areas can be adjusted by passing commandline options to the compiler. During debugging and testing, you may wish to invoke this predicate just before quitting, as it will tell you how close you are to the limits.