This section covers the only opcodes which designers are likely to have occasional need of: those which drive powerful and otherwise inaccessible features of the Z-machine's “hardware”, such as sound, graphics, menus and the mouse. There's no need to be fluent in assembly language to use these opcodes, which work just as well if used as incantations from a unfamiliar tongue.
▲
WARNING
Some of these incantations may not work well if a story file is played
on old interpreters which do not adhere to the Z-Machine Standard.
Standard interpreters are very widely available, but if seriously worried
you can test in an Initialise
routine whether your game
is running on a good interpreter, as in the following code.
if (standard_interpreter == 0) { print "This game must be played on an interpreter obeying the Z-Machine Standard.^"; @quit; }
The library variable standard_interpreter
holds the version number of the standard obeyed, with the upper byte
holding the major and the lower byte the minor version number, or
else zero if the interpreter isn't standard-compliant. Thus $002
means 0.2 and $100
means 1.0. Any standard interpreter will
carry out the opcodes in this chapter correctly, or else provide fair
warning that they cannot. (For instance, an interpreter running on a
palm-top personal organiser without a loudspeaker cannot provide sound
effects.) Here is how to tell whether a standard interpreter can or
can't provide the feature you need.
Feature | Versions | Available if |
auxiliary files | 5,6,8 | (true) |
coloured text | 5,6,8 | ((0->1) & 1 ~= 0) |
input streams | 5,6,8 | (true) |
menus | 6 | (($10-->0) & 256 ~= 0) |
mouse | 5,6 | (($10-->0) & 32 ~= 0) |
output streams | 5,6,8 | (true) |
pictures | 6 | (($10-->0) & 8 ~= 0) |
sounds | 5,6,8 | (($10-->0) & 128 ~= 0) |
throw/catch stack frames | 5,6,8 | (true) |
timed keyboard interrupts | 5,6,8 | ((0->1) & 128 ~= 0) |
For instance, if coloured text is essential (for instance
if red and black letters have to look different because it's a vital clue
to some puzzle), you may want to add a test like the following to your
Initialise
routine:
if ((0->1) & 1 == 0) print "*** This game is best appreciated on an interpreter capable of displaying colours, unlike the present one. Proceed at your own risk! ***^";
· · · · ·
▲ Text flows in and out of the Z-machine continuously: the player's commands flow in, responses flow out. Commands can come in from two different “input streams”, only one of which is selected at any given time: stream 0 is the keyboard and stream 1 is a file on the host computer. The stream is selected with:
@input_stream number
The Inform debugging verb “replay” basically does no more than switch input to stream 1.
▲
There are four output streams for text, numbered 1 to 4. These are:
(1) the screen, (2) the transcript file, (3) an array in memory and
(4) a file of commands on the host computer. These can be active in
any combination, except that at all times either stream 1 or stream 3
is active and not both. Inform uses stream 3 when the message
print_to_array
is sent to a string, and streams 2 and 4
in response to commands typed by the player: “script on”
switches stream 2 on, “script on” switches it off;
“recording on” and “off” switch stream 4 on and
off. The relevant opcode is:
@output_stream number arr
If number
is 0 this does nothing.
+
n switches stream n on,
-
n switches it off. The arr
operand is
omitted except for stream 3, when it's a table array holding the text
printed: that is, arr-->0
contains the number of characters
printed and the text printed is stored as ZSCII characters in
arr->2
, arr->3
, …
▲ As the designer, you cannot choose the filename of the file of commands used by input stream 1 or output stream 4. Whoever is playing the story file will choose this: perhaps after being prompted by the interpreter, perhaps through a configuration setting on that interpreter.
•▲▲
EXERCISE 122
Implement an Inform version of the standard ‘C’ routine
printf
, taking the form
printf(format, arg1, ...)
to print out the format string but with escape sequences
like %d
replaced by the arguments (printed in various ways).
For example,
printf("The score is %e out of %e.", score, MAX_SCORE);
should print something like “The score is five out of ten.”
▲
In Version 6 story files, only, @output_stream
can take
an optional third operand when output stream 3 is being enabled. That is:
@output_stream 3 arr width
If width
is positive, the text streamed
into the array will be word-wrapped as if it were on a screen width
characters wide; if width
is negative, then as if on
a screen -width
pixels wide. The text going into arr
is in the form of a sequence of lines, each consisting of a word containing
the number of characters and then the ZSCII characters themselves in
bytes. The sequence of lines finishes with a zero word. Such an array
is exactly what is printed out by the opcode @print_form arr
.
· · · · ·
▲ The Z-machine has two kinds of “screen model”, or specification for what can and can't be done to the contents of the screen. Version 6 has an advanced graphical model, whereas other versions have much simpler textual arrangements. Early versions of the Z-machine are generally less capable here, so this section will document only the Version 5 and Version 6 models. (Versions 7 and 8 have the same model as Version 5.)
The version 5 screen model. The screen is divided into an upper window, normally used for a status line and sometimes also for quotations or menus, and a lower window, used for ordinary text. At any given time the upper window has a height H, which is a whole number of lines: and H can be zero, making the upper window invisible. (The story file can vary H from time to time and many do.) When text in the upper and lower windows occupy the same screen area, it's the upper window text that's visible. This often happens when quotation boxes are displayed.
@split_window H
Splits off an upper-level window of the given number of lines H in height from the main screen. Be warned that the upper window doesn't scroll, so you need to make H large enough for all the text you need to fit at once.
@set_window window
Selects which window text is to be printed into: (0) the lower one or (1) the upper one. Printing on the upper window overlies printing on the lower, is always done in a fixed-pitch font and does not appear in a printed transcript of the game.
@set_cursor line column
Places the cursor inside the upper window, where (1, 1) is the top left character.
@buffer_mode flag
This turns on (flag==true)
or off
(flag==false)
word-breaking for the current window: that is,
the practice of printing new-lines only at the ends of words, so that
text is neatly formatted.
@erase_window window
Blanks out window 0 (lower), window 1 (upper) or the
whole screen (if window=-1
).
Using fixed-pitch measurements, the screen has dimensions
X characters across by Y characters down, where
X and Y are stored in bytes $21
and $20
of the header respectively. It's sometimes useful to know this when formatting
tables:
print "My screen has ", 0->$20, " rows and ", 0->$21, " columns.^";
Be warned: it might be 80 × 210 or then again it might be 7 × 40. Text printing has a given foreground and background colour at all times. The standard stock of colours is:
0 | current colour | 5 | yellow |
1 | default colour | 6 | blue |
2 | black | 7 | magenta |
3 | red | 8 | cyan |
4 | green | 9 | white |
@set_colour foreground background
If coloured text is available, this opcode sets text
to be foreground
against background
.
(But bear in mind that not all interpreters can display coloured text,
and not all players enjoy reading it.) Even in a monochrome game, text
can be set to print in “reverse colours”: background on
foreground rather than vice versa. Status lines are almost always printed
in reverse-colour, but this is only a convention and is not required
by the Z-machine. Reverse is one of five possible text styles: roman,
bold, underline (which many interpreters will render with italic),
reverse and fixed-pitch. (Inform's style
statement chooses
between these.)
•▲
EXERCISE 123
Design a title page for ‘Ruins’, displaying a more or
less apposite quotation and waiting for a key to be pressed. (For
this last part, see below.)
•▲
EXERCISE 124
Change the status line so that it has the usual score/moves appearance
except when a variable invisible_status
is set to
true
, when it's invisible.
•▲
EXERCISE 125
Alter the ‘Advent’ example game to display the number of
treasures found instead of the score and turns on the status line.
•▲
EXERCISE 126
(From code by Joachim Baumann.) Put a compass rose on the status line,
displaying the directions in which the room can be left.
•▲▲
EXERCISE 127
(Cf. ‘Trinity’.) Make the status line consist only of
the name of the current location, centred in the top line of the screen.
The version 6 screen model. We are now in the realm of graphics, and the screen is considered to be a grid of pixels: coordinates are usually given in the form (y,x), with (1,1) at the top left. y and x are measured in units known, helpfully enough, as “units”. The interpreter decides how large “1 unit” is, and it's not safe to assume that 1 unit equals 1 pixel. All you can tell is what the screen dimensions are, in units:
print "The screen measures ", $22-->0, " units across and ", $22-->1, " units down.^";
There are eight windows, numbered 0 to 7, which text and pictures can currently be printing to: what actually appears on the screen is whatever shows through the boundaries of the window at the time the printing or plotting happens. Window number −3 means “the current one”. Windows have no visible borders and usually lie on top of each other. Subsequent movements of the window do not move what was printed and there is no sense in which characters or graphics “belong” to any particular window once printed. Each window has a position (in units), a size (in units), a cursor position within it (in units, relative to its own origin), a number of flags called “attributes” and a number of variables called “properties”. If you move a window so that the cursor is left outside, the interpreter automatically moves the cursor back to the window's new top left. If you only move the cursor, it's your responsibility to make sure it doesn't leave the window.
The attributes are (0) “wrapping”, (1) “scrolling”, (2) “copy text to output stream 2 if active” and (3) “buffer printing”. Wrapping means that when text reaches the right-hand edge it continues from the left of the next line down. Scrolling means scrolling the window upwards when text printing reaches the bottom right corner, to make room for more. Output stream 2 is the transcript file, so the question here is whether you want text in the given window to appear in a transcript: for instance, for a status line the answer is probably “no”, but for normal conversation it would be “yes”. Finally, buffering is a more sophisticated form of wrapping, which breaks lines of text in between words, but which (roughly speaking) means that no line is printed until complete. Note that ordinary printing in the lower window has all four of these attributes.
@window_style window attrs operation
Changes window attributes. attrs
is a
bitmap in which bit 0 means “wrapping”, bit 1 means “scrolling”,
etc. operation
is 0 to set to these settings, 1 to set only
those attributes which you specify in the bitmap, 2 to clear only those
and 3 to reverse them. For instance,
@window_style 2 $$1011 0
sets window 2 to have wrapping, scrolling and buffering but not to be copied to output stream 2, and
@window_style 1 $$1000 2
clears the buffer printing attribute of window 1.
Windows have 16 properties, numbered as follows:
0 | y coordinate | 8 | newline interrupt routine |
1 | x coordinate | 9 | interrupt countdown |
2 | y size | 10 | text style |
3 | x size | 11 | colour data |
4 | y cursor | 12 | font number |
5 | x cursor | 13 | font size |
6 | left margin size | 14 | attributes |
7 | right margin size | 15 | line count |
The x and y values are all in units,
but the margin sizes are in pixels. The font size data is
256*h+w
, where h
is the height and w
the width in pixels. The colour data is 256*b+f
, where
f
and b
are foreground and background colour
numbers. The text style is a bitmap set by the Inform style
statement: bit 0 means Roman, 1 is reverse video, 2 is bold, 3 is italic,
4 is fixed-pitch. The current value of any property can be read with:
@get_wind_prop window prop -> r
Those few window properties which are not italicised in the table (and only those few) can be set using:
@put_wind_prop window prop value
Most window properties, the ones with italicised text in the table above, are set using specially-provided opcodes:
@move_window window y x
Moves to the given position on screen. Nothing visible happens, but all future plotting to the given window will happen in the new place.
@window_size window y x
Changes window size in pixels. Again, nothing visible happens.
@set_colour foreground background window
Sets the foreground and background colours for the given window.
@set_cursor line column window
Moves the window's cursor to this position, in units,
relative to (1,1) in the top left of the window. If this would lie
outside the margin positions, the cursor moves to the left margin
of its current line. In addition, @set_cursor -1
turns the
cursor off, and @set_cursor -2
turns it back on again.
@get_cursor arr
Writes the cursor row of the current window into
arr-->0
and the column into arr-->1
,
in units.
@set_font font -> r
(This opcode is available in Versions 5 and 8 as
well as 6.) Selects the numbered font for the current window, and stores
a positive number into r
(actually, the previous font number)
if available, or zero if not available. Font support is only minimal,
for the sake of portability. The possible font numbers are: 1 (the normal
font), 3 (a character graphics font: see §16 of The Z-Machine
Standards Document), 4 (a fixed-pitch Courier-like font). Owing
to a historical accident there is no font 2.
@set_margins left right window
Sets margin widths, in pixels, for the given window. If the cursor is overtaken in the process and now lies outside these margins, it is moved back to the left margin of the current line.
▲ “Interrupt countdowns” are a fancy system to allow text to flow gracefully around obstructions such as pictures. If property 9 is set to a non-zero value, then it'll be decremented on each new-line, and when it hits zero the routine in property 8 will be called. This routine should not attempt to print any text, and is usually used to change margin settings.
•
EXERCISE 128
(Version 6 games only.) Set up wavy margins, which advance inwards for
a while and then back outwards, over and over, so that the game's text
ends up looking like a concertina.
Here are two useful tricks with windows:
@erase_window window
Erases the window's whole area to its background colour.
@scroll_window window pixels
Scrolls the given window by the given number of pixels. A negative value scrolls backwards, i.e., moving the body of the window down rather than up. Blank (background colour) pixels are plotted onto the new lines. This can be done to any window and is not related to the “scrolling” attribute of a window.
▲ Finally, Version 6 story files (but no others) are normally able to display images, or “pictures”. To the Z-machine these are referred to only by number, and a story file does not “know” how pictures are provided to it, or in what format they are stored. For the mechanics of how to attach resources like pictures to a story file, see §43. Fouropcodes are concerned with pictures:
@draw_picture pn y x
Draws picture number pn
so that its
top left corner appears at coordinates (y
, x
)
in units on the screen. If y
or x
are omitted,
the coordinate of the cursor in the current window is used.
@erase_picture pn y x
Exactly as @draw_picture
, but erases
the corresponding screen area to the current background colour.
@picture_data pn arr ?Label
Asks for information about picture number pn
.
If the given picture exists, a branch occurs to the given Label
,
and the height and width in pixels of the image are written to the given
array arr
, with arr-->0
being the height
and arr-->1
the width. If the given picture doesn't
exist, no branch occurs and nothing is written, except that
if pn
is zero, then arr-->0
is the number
of pictures available to the story file and arr-->1
the “release number” of the collection of pictures. (Or of
the Blorb file attached to the story file, if that's how pictures have
been provided to it.)
@picture_table tarr
Given a table
array tarr
of
picture numbers, this warns the Z-machine that the story file will
want to plot these pictures often, soon and in quick succession. Providing
such a warning is optional and enables some interpreters to plot more
quickly, because they can cache images in memory somewhere.
· · · · ·
▲ Sound effects are available to story files of any Version from 5 upwards. Once again, to the Z-machine these are referred to only by number, and a story file does not “know” how sounds are provided to it, or in what format they are stored. For the mechanics of how to attach resources like sound effects to a story file, see §43. There is only one sound opcode, but it does a good deal. The simplest form is:
@sound_effect number
which emits a high-pitched bleep if number
is 1 and a low-pitched bleep if 2. No other values are allowed.
@sound_effect number effect volrep routine
The given effect
happens to the given sound
number
, which must be 3 or higher and correspond to a
sound effect provided to the story file by the designer. Volume is measured
from 1 (quiet) to 8 (loud), with the special value 255 meaning “loudest
possible”, and you can also specify between 0 and 254 repeats,
or 255 to mean “repeat forever”. These two parameters are
combined in volrep
:
volrep = 256*repeats + volume;
The effect
can be: 1 (prepare), 2 (start),
3 (stop), 4 (finish with). You may want to “warn” the
Z-machine that a sound effect will soon be needed by using the
“prepare” effect, but this is optional: similarly you may
want to warn it that you've finished with the sound effect for the
time being, but this too is optional. “Start” and “stop”
are self-explanatory except to say that sound effects can be playing in
the background while the player gets on with play: i.e., the Z-machine
doesn't simply halt until the sound is complete. The “stop”
effect makes the sound cease at once, even if there is more still
to be played. Otherwise, unless set to repeat indefinitely, it will
end by itself in due course. If a routine
has been provided
(this operand is optional, and takes effect only on effect
2), this routine will then be called as an interrupt. Such routines generally
do something like play the sound again but at a different volume level,
giving a fading-away effect.
· · · · ·
▲ In addition to reading entire lines of text from the keyboard, which games normally do once per turn, you can read a single press of a key. Moreover, on most interpreters you can set either kind of keyboard-reading to wait for at most a certain time before giving up.
@aread text parse time function -> result
This opcode reads a line of text from the keyboard, writing
it into the text
string array and ‘tokenising’
it into a word stream, with details stored in the parse
string array (unless this is zero, in which case no tokenisation happens).
(See §2.5 for the format of text
and parse
.) While it is doing this it calls function()
every time
tenths of a second: the process ends if ever this
function returns true. The value written into result is the “terminating
character” which finished the input, or else 0 if a time-out ended
the input.
@read_char 1 time function -> result
Results in the ZSCII value of a single keypress. Once
again, function(time)
is called every time
tenths of a second and may stop this process early. (The first operand
is always 1, meaning “from the keyboard”.)
@tokenise text parse dictionary
This takes the text in the text
buffer
(in the format produced by @aread
) and tokenises it,
i.e., breaks it up into words and finds their addresses in the given
dictionary. The result is written into the parse
buffer
in the usual way.
@encode_text zscii-text length from coded-text
Translates a ZSCII word to the internal, Z-encoded,
text format suitable for use in a @tokenise
dictionary.
The text begins at from in the zscii-text
and is
length
characters long, which should contain the right
length value (though in fact the interpreter translates the word
as far as a zero terminator). The result is 6 bytes long and usually
represents between 1 and 9 letters.
It's also possible to specify which ZSCII character codes are “terminating characters”, meaning that they terminate a line of input. Normally, the return key is the only terminating character, but others can be added, and this is how games like ‘Beyond Zork’ make function keys act as shorthand commands. For instance, the following directive makes ZSCII 132 (cursor right) and 136 (function key f4) terminating:
Zcharacter terminating 132 136;
The legal values to include are those for the cursor,
function and keypad keys, plus mouse and menu clicks (see
Table 2 for values). The special value
255 makes all of these characters terminating. (For other uses of
Zcharacter
, see §36.)
•
EXERCISE 129
Write a “press any key to continue” routine.
•
EXERCISE 130
And another routine which determines if any key is being held down,
returning either its ZSCII code or zero to indicate that no key is
being held down.
•
EXERCISE 131
Write a game in which a player taking more than ten seconds to consider
a command is hurried along.
•
EXERCISE 132
And if thirty seconds are taken, make the player's mind up for her.
•
EXERCISE 133
Design an hourglass fixed to a pivot on one room's wall, which (when
turned upright) runs sand through in real time, turning itself over
automatically every forty seconds.
· · · · ·
▲ Besides the keyboard, Version 6 normally supports the use of a mouse. In theory this can have any number of buttons, but since some widely-used computers have single-button mice (e.g., the Apple Macintosh) it's safest not to rely on more than one.
The mouse must be attached to one of the eight windows
for it to register properly. (Initially, it isn't attached to any window
and so is entirely inert.) To attach it to the window numbered wnum
,
use the opcode:
@mouse_window wnum
Once attached, a click within the window will register
as if it were a key-press to @read_char
with ZSCII value
254, unless it is a second click in quick succession to a previous click
in the same position, in which case it has ZSCII value 253. Thus, a
double-clicking registers twice, once as click (254) and then as double-click
(253).
At any time, the mouse position, measured in units, and the state of its buttons, i.e., pressed or not pressed, can be read off with the opcode:
@read_mouse mouse_array
places (x, y) coordinates of the click
in mouse_array-->0
and mouse_array-->1
and the state of the buttons as a bitmap in mouse_array-->2
,
with bit 0 representing the rightmost button, bit 1 the next and so
on. In practice, it's safest simply to test whether this value is zero
(no click) or not (click). The array mouse_array
should
have room for 4 entries, however: the fourth relates to menus (see below).
•
EXERCISE 134
Write a test program to wait for mouse clicks and then print out the
state of the mouse.
▲ The mouse also allows access to menus, on some interpreters, though in Version 6 only. The model here is of a Mac OS-style desktop, with one or more menus added to the menu bar at the top of the screen: games are free to add or remove menus at any time. They are added with:
@make_menu number mtable ?IfAbleTo;
Such menus are numbered from 3 upwards. mtable
is
a table
array of menu entries, each of which must be itself
a string
array giving the text of that option. IfAbleTo
is a label, to which the interpreter will jump if the menu is successfully
created. If mtable
is zero, the opcode instead removes
an already-existing menu. During play, the selection of a menu item
by the player is signalled to the Z-machine as a key-press with ZSCII
value 252, and the game receiving this can then look up which item on
which menu was selected by looking at entry -->3
in an array given to read_mouse
. The value in this entry
will be the menu number times 256, plus the item number, where items
are numbered from 0. If the game has 252 listed as a “terminating
character” (see above), then menu selection can take the place
of typing a command.
•
EXERCISE 135
Provide a game with a menu of common commands like “inventory”
and “look” to save on typing.
· · · · ·
▲ The Z-machine can also load and save “auxiliary files” to or from the host machine. These should have names adhering to the “8 + 3” convention, that is, one to eight alphanumeric characters optionally followed by a full stop and one to three further alphanumeric characters. Where no such extension is given, it is assumed to be .AUX. Designers are asked to avoid using the extensions .INF, .H, .SAV or .Z5 or similar, to prevent confusion. Note that auxiliary files from different games may be sharing a common directory on the host machine, so that a filename should be as distinctive as possible. The two opcodes are:
@save buffer length filename -> R
Saves the byte array buffer
(of size
length) to a file, whose (default) name is given in the filename
(a
string
array). Afterwards, R
holds true
on success, false
on failure.
@restore buffer length filename -> R
Loads in the byte array buffer
(of size
length
) from a file, whose (default) name is given in the
filename
(a string
array). Afterwards,
R
holds the number of bytes successfully read.
•
EXERCISE 136
How might this assist a “role-playing game campaign” with
several scenarios, each implemented as a separate Inform game but sharing
a player-character who takes objects and experience from one scenario
to the next?
•
EXERCISE 137
Design catacombs in which the ghosts of former, dead players from
previous games linger.
· · · · ·
▲ Finally, the Z-machine supports a very simple form of exception-handling like that of the C language's long jump feature. This is very occasionally useful to get the program out of large recursive tangles in a hurry.
@catch -> result
The opposite of @throw
, @catch
preserves the “stack frame” of the current routine in the
variable result
: roughly speaking, the stack frame is
the current position of which routine is being run and which ones
have called it so far.
@throw value stack-frame
This causes the program to execute a return with
value
, but as if it were returning from the routine which
was running when the stack-frame
was “caught”, that
is, set up by a corresponding @catch
opcode. Note that
you can only @throw
back to a routine which is still
running, i.e., to which control will eventually return anyway.
•▲▲
EXERCISE 138
Use @throw
and @catch
to make an exception
handler for actions, so that any action subroutine getting into recursive
trouble can throw an exception and escape.
•
REFERENCES
The assembly-language connoisseur will appreciate ‘Freefall’
by Andrew Plotkin and ‘Robots’ by Torbjörn Andersson,
although the present lack of on-line hints make these difficult games
to win.
•Gevan Dutton has made an amazing
port of the classic character-graphic maze adventure ‘Rogue’
to Inform, taking place entirely in the upper window.
•Similarly, ‘Zugzwang’
by Magnus Olsson plots up a chess position.
•The function library
"text_functions.h", by Patrick Kellum, offers text
styling and colouring. These routines are entirely written in assembler.
Similar facilities are available from Chris Klimas's "style.h"
and L. Ross Raszewski's "utility.h".
•Jason Penney's "V6Lib.h"
is a coherent extension to the Inform library for Version 6 games
(only), offering support for multiple text windows, images and sounds
by means of class definitions and high-level Inform code.
•More modestly, but applicably to
Version 5 and 8 games, L. Ross Raszewski's "sound.h"
function library handles sound effects.