1
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
4
<title>DM4 §44: Case study: a library file for menus</title>
5
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
6
<link rel="stylesheet" type="text/css" href="dm4.css">
10
<a href="index.html">home</a> /
11
<a href="contents.html">contents</a> /
12
<a href="ch7.html" title="Chapter VII: The Z-Machine">chapter VII</a> /
13
<a href="s43.html" title="§43: Pictures, sounds, blurbs and Blorb">prev</a> /
14
<a href="s45.html" title="§45: Limitations and getting around them">next</a> /
15
<a href="dm4index.html">index</a>
18
<a id="p336" name="p336"></a>
19
<h2>§44 Case study: a library file for menus</h2>
21
<blockquote>Yes, all right, I won't do the menu … I don't think
22
you realise how long it takes to do the menu, but no, it doesn't matter,
23
I'll hang the picture now. If the menus are late for lunch it doesn't
24
matter, the guests can all come and look at the picture till they
26
— John Cleese and Connie Booth, <i>Fawlty Towers</i></blockquote>
28
<p class="normal"><span class="atleft"><img src="dm4-336_1.jpg" alt=""></span>
29
Sometimes one would like to provide a menu of text options, offered
30
to the player as a list on screen which can be rummaged through
31
with the cursor keys. For instance, the hints display in the “solid
32
gold” edition of Infocom's ‘Zork I’ shows a list of
33
“Invisiclues”: “Above Ground”, “The Cellar
34
Area”, and so on. Moving a cursor to one of these options
35
and pressing RETURN brings up a sub-menu of questions on the general
36
topic chosen: for instance, “How do I cross the mountains?”
37
Besides hints, many modern games use menu displays for instructions,
38
background information, credits and release notes.</p>
40
<p class="indent">An optional library file called <tt>"Menus.h"</tt>
41
is provided to manage such menus. If you want its facilities then, where
42
you previously included <code>Verblib</code>, now write:</p>
44
<p class="lynxonly"></p>
46
Include "Verblib";
47
Include "Menus";
50
<p class="normal">And this will make the features of <tt>Menus.h</tt> available.
51
This section describes what these simple features are, and how they work,
52
as an extended example of Z-machine programming.</p>
54
<p class="indent">The designer of this system began by noticing that
55
menus and submenus and options fit together in a tree structure rather
56
like the object tree:</p>
58
<p class="output">Hints for ‘Zork I’ (menu)<br>
59
→ Above Ground (submenu)<br>
60
→ How do I cross the mountains? (option)<br>
61
→ <i>some text is revealed</i><br>
62
→ The Cellar Area (submenu)<br>
63
→ ...</p>
65
<a id="p337" name="p337"></a>
66
<p class="normal">The library file therefore defines two classes of object,
67
<code>Menu</code> and <code>Option</code>. The short name of a menu is
68
its title, while its children are the possible choices, which can be of
69
either class. (So you can have as many levels of submenu as needed.)
70
Since choosing an <code>Option</code> is supposed to produce some text,
71
which is vaguely like examining objects, the <code>description</code> property of
72
an <code>Option</code> holds the information revealed. So, for instance:</p>
74
<p class="lynxonly"></p>
76
Menu hints_menu "Hints for Zork I";
77
Menu -> "Above Ground";
78
Option -> -> "How do I cross the mountains?"
79
with description "By ...";
80
Menu -> "The Cellar Area";
83
<p class="normal">Note that such a structure can be rearranged in play
84
just as the rest of the object tree can, which is convenient for “adaptive
85
hints”, where the hints offered vary with the player's present travail.</p>
87
<p class="indent">How does this work? A menu or an option is chosen by
88
being sent the message <code>select</code>. So the designer will launch
89
the menu, perhaps in response to the player having typed “hints”,
92
<p class="lynxonly"></p>
99
<p class="normal">As the player browses through the menu, each menu sends
100
the <code>select</code> message to the next one chosen, and so on. This already suggests
101
that menus and options are basically similar, and in fact that's right:
102
<code>Menu</code> is actually a subclass of <code>Option</code>, which
103
is the more basic idea of the two.</p>
105
<p class="dotbreak">� � � � �</p>
107
<p class="normal">The actual code of <tt>Menus.h</tt> is slightly different
108
from that given below, but only to fuss with dealing with early copies
109
of the rest of the library, and to handle multiple languages. It begins
110
with the class definition of <code>Option</code>, as follows:</p>
112
<p class="lynxonly"></p>
116
self.emblazon(1, 1, 1);
117
@set_window 0; font on; style roman; new_line; new_line;
118
if (self provides description) return self.description();
119
"[No text written for this option.]^";
123
<a id="p338" name="p338"></a>
124
<p class="normal">The option sends itself the message <code>emblazon(1,1,1)</code>
125
to clear the screen an put a bar of height 1 line at the top, containing
126
the title of the option centred. The other two 1s declare that this
127
is “page 1 of 1”: see below. Window 0 (the ordinary, lower
128
window) is then selected; text reverts to its usual state of being
129
roman-style and using a variable-pitched font. The screen is now empty
130
and ready for use, and the option expects to have a <code>description</code>
131
property which actually does any printing that's required. To get back
132
to the emblazoning:</p>
134
<p class="lynxonly"></p>
136
emblazon [ bar_height page pages temp;
137
screen_width = 0->33;
140
@split_window bar_height;
141
! Black out top line in reverse video:
144
style reverse; spaces(screen_width);
145
if (standard_interpreter == 0)
148
ForUseByOptions-->0 = 128;
149
@output_stream 3 ForUseByOptions;
151
if (pages ~= 1) print " [", page, "/", pages, "]";
153
temp = (screen_width - ForUseByOptions-->0)/2;
157
if (pages ~= 1) print " [", page, "/", pages, "]";
158
return ForUseByOptions-->0;
162
<p class="normal">That completes <code>Option</code>. However, since
163
this code refers to a variable and an array, we had better write
164
definitions of them:</p>
166
<p class="lynxonly"></p>
169
Global screen_height;
170
Array ForUseByOptions -> 129;
173
<p class="normal">(The other global variable, <code>screen_height</code>,
174
will be used later. The variables are global because they will be needed
175
by all of the menu objects.) The <code>emblazon</code> code checks
176
to see if it's running on a standard interpreter. If so, it uses output
177
stream 3 into an array to measure the length of text like “The
178
<a id="p339" name="p339"></a>
179
Cellars [2/3]” in order to centre it on the top line. If not,
180
the text appears at the top left instead.</p>
182
<p class="indent">So much for <code>Option</code>. The definition of
183
<code>Menu</code> is, inevitably, longer. It inherits <code>emblazon</code>
184
from its superclass <code>Option</code>, but overrides the definition
185
of <code>select</code> with something more elaborate:</p>
187
<p class="lynxonly"></p>
189
Class Menu class Option
190
with select [ count j obj pkey line oldline top_line bottom_line
191
page pages options top_option;
192
screen_width = 0->33;
193
screen_height = 0->32;
194
if (screen_height == 0 or 255) screen_height = 18;
195
screen_height = screen_height - 7;
198
<p class="normal">The first task is to work out how much room the screen
199
has to display options. The width and height, in characters, are read
200
out of the story file's header area, where the interpreter has written
201
them. In case the interpreter is <em>really</em> poor, we guess at 18
202
if the height is claimed to be zero or 255; since this is a library
203
file and will be widely used, it errs on the side of extreme caution.
204
Finally, 7 is subtracted because seven of the screen lines are occupied
205
by the panel at the top and white space above and below the choices.
206
The upshot is that <code>screen_height</code> is the actual maximum number
207
of options to be offered per page of the menu. Next: how many options are
210
<p class="lynxonly"></p>
213
objectloop (obj in self && obj ofclass Option) options++;
214
if (options == 0) return 2;
217
<p class="normal">(Note that a <code>Menu</code> is also an <code>Option</code>.)
218
We can now work out how many pages will be needed.</p>
220
<p class="lynxonly"></p>
222
pages = options/screen_height;
223
if (options%screen_height ~= 0) pages++;
229
<p class="normal"><code>top_line</code> is the highest screen line used
230
to display an option: line 6. The local variables <code>page</code> and
231
<code>line</code> show which line on which page the current selection
232
arrow points to, so we're starting at the top line of page 1.</p>
234
<p class="lynxonly"></p>
237
top_option = (page - 1) * screen_height;
240
<a id="p340" name="p340"></a>
241
<p class="normal">This is the option number currently selected, counting
242
from zero. We display the three-line black strip at the top of the
243
screen, using <code>emblazon</code> to create the upper window:</p>
245
<p class="lynxonly"></p>
247
self.emblazon(7 + count, page, pages);
248
@set_cursor 2 1; spaces(screen_width);
249
@set_cursor 2 2; print "N = next subject";
250
j = screen_width-12; @set_cursor 2 j; print "P = previous";
251
@set_cursor 3 1; spaces(screen_width);
252
@set_cursor 3 2; print "RETURN = read subject";
253
j = screen_width-17; @set_cursor 3 j;
256
<p class="normal">The last part of the black strip to print is the
257
one offering Q to quit:</p>
259
<p class="lynxonly"></p>
261
if (sender ofclass Option) print "Q = previous menu";
262
else print " Q = resume game";
266
<p class="normal">The point of this is that pressing Q only takes us
267
back to the previous menu if we're inside the hierarchy, i.e., if the
268
message <code>select</code> was sent to this <code>Menu</code> by another
269
<code>Option</code>; whereas if not, Q takes us out of the menu altogether.
270
Next, we count through those options appearing on the current page
271
and print their names.</p>
273
<p class="lynxonly"></p>
275
count = top_line; j = 0;
276
objectloop (obj in self && obj ofclass Option) {
277
if (j >= top_option && j < (top_option+screen_height)) {
284
bottom_line = count - 1;
287
<p class="normal">Note that the name of the option begins on column 6
288
of each line. The player's current selection is shown with a cursor
289
<code>></code> appearing in column 4:</p>
291
<p class="lynxonly"></p>
295
! Move or create the > cursor:
296
if (line ~= oldline) {
298
@set_cursor oldline 4; print " ";
300
@set_cursor line 4; print ">";<a id="p341" name="p341"></a>
305
<p class="normal">Now we wait for a single key-press from the player:</p>
307
<p class="lynxonly"></p>
309
@read_char 1 -> pkey;
310
if (pkey == 'N' or 'n' or 130) {
313
if (line > bottom_line) {
316
if (page == pages) page = 1; else page++;
324
<p class="normal">130 is the ZSCII code for “cursor down key”.
325
Note that if the player tries to move the cursor off the bottom of the
326
list, and there's at least one more page, we jump right out of the loop
327
and back to <code>ReDisplay</code> to start again from the top of the
328
next page. Handling the “previous” option is very similar,
331
<p class="lynxonly"></p>
333
if (pkey == 'Q' or 'q' or 27 or 131) break;
336
<p class="normal">Thus pressing lower or upper case Q, escape (ZSCII 27)
337
or cursor left (ZSCII 131) all have the same effect: to break out of
338
the for loop. Otherwise, one can press RETURN or cursor right to select
341
<p class="lynxonly"></p>
343
if (pkey == 10 or 13 or 132) {
345
objectloop (obj in self && obj ofclass Option) {
346
if (count == top_option + line - top_line) break;
349
switch (obj.select()) {
353
print "[Please press SPACE to continue.]^";
354
@read_char 1 -> pkey;
360
<a id="p342" name="p342"></a>
361
<p class="normal">(No modern interpreter should ever give 10 for the
362
key-code of RETURN, which is ZSCII 13. Once again, the library file
363
is erring on the side of extreme caution.) An option's <code>select</code>
364
routine can return three different values for different effects:</p>
366
<p class="lynxonly"></p>
367
<div class="clump"><table border="1" align="center" style="text-align:center">
368
<tr><td>2</td><td>Redisplay the menu page that selected me</td></tr>
369
<tr><td>3</td><td>Exit from that menu page</td></tr>
370
<tr><td>anything else</td><td>Wait for SPACE, then redisplay that menu page</td></tr>
373
<p class="normal">Finally, the exit from the menu, either because the
374
player typed Q, escape, etc., or because the selected option returned 3:</p>
376
<p class="lynxonly"></p>
379
if (sender ofclass Option) return 2;
380
font on; @set_cursor 1 1;
381
@erase_window -1; @set_window 0;
382
new_line; new_line; new_line;
383
if (deadflag == 0) <<Look>>;
388
<p class="normal">And that's it. If this menu was the highest-level one, it
389
needs to resume the game politely, by clearing the screen and performing
390
a <code>Look</code> action. If not, then it needs only to return 2,
391
indicating “redisplay the menu page that selected me”:
392
that is, the menu one level above.</p>
394
<p class="indent">The only remaining code in <tt>"Menus.h"</tt>
395
shows some of the flexibility of the above design, by defining a special
398
<p class="lynxonly"></p>
400
Class SwitchOption class Option
402
print (object) self, " ";
403
if (self has on) print "(on)"; else print "(off)";
407
if (self has on) give self ~on; else give self on;
412
<p class="normal">Here is an example of <code>SwitchOptions</code> in
415
<p class="lynxonly"></p>
417
Menu settings "Game settings";
418
SwitchOption -> FullRoomD "full room descriptions" has on;
419
SwitchOption -> WordyP "wordier prompts";
420
SwitchOption -> AllowSavedG "allow saved games" has on;
423
<a id="p343" name="p343"></a>
424
<p class="normal">So each option has the attribute <code>on</code> only if
425
currently set. In the menu, the option <code>FullRoomD</code> is displayed
426
either as “full room descriptions (on)” or “full room
427
descriptions (off)”, and selecting it switches the state, like a
428
light switch. The rest of the code can then perform tests like so:</p>
430
<p class="lynxonly"></p>
432
if (AllowSavedG hasnt on) "That spell is forbidden.";
435
<p class="dotbreak">� � � � �</p>
437
<p class="lynxonly"></p>
439
<tr><td colspan="2"><small><i>Appearance of the final menu on a screen 64 characters wide:</i></small></td></tr>
440
<tr><td valign="top"><small><i>line 1</i></small></td><td valign="top"><tt> Hints for Zork I [1/2]</tt></td></tr>
441
<tr><td valign="top"><small><i>line 2</i></small></td><td valign="top"><tt>N = next subject P = previous</tt></td></tr>
442
<tr><td valign="top"><small><i>line 3</i></small></td><td valign="top"><tt>RETURN = read subject Q = resume game</tt></td></tr>
443
<tr><td valign="top"><small><i>line 4</i></small></td><td valign="top"><tt> </tt></td></tr>
444
<tr><td valign="top"><small><i>line 5</i></small></td><td valign="top"><tt> </tt></td></tr>
445
<tr><td valign="top"><small><i>line 6</i></small></td><td valign="top"><tt> Above Ground</tt></td></tr>
446
<tr><td valign="top"><small><i>line 7</i></small></td><td valign="top"><tt> > The Cellar Area</tt></td></tr>
447
<tr><td valign="top"><small><i>line 8</i></small></td><td valign="top"><tt> The Maze</tt></td></tr>
448
<tr><td valign="top"><small><i>line 9</i></small></td><td valign="top"><tt> The Round Room Area</tt></td></tr>
451
<p class="aside"><span class="warning"><b>•</b>
452
<b>REFERENCES</b></span><br>
453
Because there was a crying need for good menus in the early days of
454
Inform, there are now numerous library extensions to support menus
455
and interfaces built from them. The original such was L. Ross Raszewski's
456
<tt>"domenu.h"</tt>, which provides a core of basic routines.
457
<tt>"AltMenu.h"</tt> then uses these routines to emulate the
458
same menu structures coded up in this section. <tt>"Hints.h"</tt>
459
employs them for Invisiclues-style hints; <tt>"manual.h"</tt>
460
for browsing books and manuals; <tt>"converse.h"</tt> for
461
menu-based conversations with people, similar to those in graphical
462
adventure games. Or indeed to those in Adam Cadre's game ‘Photopia’,
463
and Adam has kindly extracted his menu-based conversational routines
464
into an example program called <tt>"phototalk.inf"</tt>.
465
For branching menus, such as a tree of questions and answers, try
466
Chris Klimas's <tt>"branch.h"</tt>. To put a menu of commands
467
at the status line of a typical game, try Adam Stark's <tt>"action.h"</tt>.</p>
471
<a href="index.html">home</a> /
472
<a href="contents.html">contents</a> /
473
<a href="ch7.html" title="Chapter VII: The Z-Machine">chapter VII</a> /
474
<a href="s43.html" title="§43: Pictures, sounds, blurbs and Blorb">prev</a> /
475
<a href="s45.html" title="§45: Limitations and getting around them">next</a> /
476
<a href="dm4index.html">index</a>