3 /* model.c: Model program for Glk API, version 0.5.
4 Designed by Andrew Plotkin <erkyrath@eblong.com>
5 http://www.eblong.com/zarf/glk/index.html
6 This program is in the public domain.
9 /* This is a simple model of a text adventure which uses the Glk API.
10 It shows how to input a line of text, display results, maintain a
11 status window, write to a transcript file, and so on. */
13 /* This is the cleanest possible form of a Glk program. It includes only
14 "glk.h", and doesn't call any functions outside Glk at all. We even
15 define our own str_eq() and str_len(), rather than relying on the
16 standard libraries. */
18 /* We also define our own TRUE and FALSE and NULL. */
29 /* The story, status, and quote windows. */
30 static winid_t mainwin = NULL;
31 static winid_t statuswin = NULL;
32 static winid_t quotewin = NULL;
34 /* A file reference for the transcript file. */
35 static frefid_t scriptref = NULL;
36 /* A stream for the transcript file, when it's open. */
37 static strid_t scriptstr = NULL;
39 /* Your location. This determines what appears in the status line. */
40 static int current_room;
42 /* A flag indicating whether you should look around. */
45 /* Forward declarations */
48 static void draw_statuswin(void);
49 static int yes_or_no(void);
51 static int str_eq(char *s1, char *s2);
52 static int str_len(char *s1);
54 static void verb_help(void);
55 static void verb_jump(void);
56 static void verb_yada(void);
57 static void verb_quote(void);
58 static void verb_move(void);
59 static void verb_quit(void);
60 static void verb_script(void);
61 static void verb_unscript(void);
62 static void verb_save(void);
63 static void verb_restore(void);
65 /* The glk_main() function is called by the Glk system; it's the main entry
66 point for your program. */
69 /* Open the main window. */
70 mainwin = glk_window_open(0, 0, 0, wintype_TextBuffer, 1);
72 /* It's possible that the main window failed to open. There's
73 nothing we can do without it, so exit. */
77 /* Set the current output stream to print to it. */
78 glk_set_window(mainwin);
80 /* Open a second window: a text grid, above the main window, three lines
81 high. It is possible that this will fail also, but we accept that. */
82 statuswin = glk_window_open(mainwin, winmethod_Above | winmethod_Fixed,
83 3, wintype_TextGrid, 0);
85 /* The third window, quotewin, isn't opened immediately. We'll do
86 that in verb_quote(). */
88 glk_put_string("Model Glk Program\nAn Interactive Model Glk Program\n");
89 glk_put_string("By Andrew Plotkin.\nRelease 7.\n");
90 glk_put_string("Type \"help\" for a list of commands.\n");
92 current_room = 0; /* Set initial location. */
105 glk_put_string("\n");
106 glk_set_style(style_Subheader);
107 if (current_room == 0)
108 glk_put_string("The Room\n");
110 glk_put_string("A Different Room\n");
111 glk_set_style(style_Normal);
112 glk_put_string("You're in a room of some sort.\n");
115 glk_put_string("\n>");
116 /* We request up to 255 characters. The buffer can hold 256, but we
117 are going to stick a null character at the end, so we have to
118 leave room for that. Note that the Glk library does *not*
119 put on that null character. */
120 glk_request_line_event(mainwin, commandbuf, 255, 0);
130 case evtype_LineInput:
131 if (ev.win == mainwin) {
133 /* Really the event can *only* be from mainwin,
134 because we never request line input from the
135 status window. But we do a paranoia test,
136 because commandbuf is only filled if the line
137 event comes from the mainwin request. If the
138 line event comes from anywhere else, we ignore
144 /* Windows have changed size, so we have to redraw the
151 /* commandbuf now contains a line of input from the main window.
152 You would now run your parser and do something with it. */
154 /* First, if there's a blockquote window open, let's close it.
155 This ensures that quotes remain visible for exactly one
158 glk_window_close(quotewin, NULL);
162 /* The line we have received in commandbuf is not null-terminated.
163 We handle that first. */
164 len = ev.val1; /* Will be between 0 and 255, inclusive. */
165 commandbuf[len] = '\0';
167 /* Then squash to lower-case. */
168 for (cx = commandbuf; *cx; cx++) {
169 *cx = glk_char_to_lower(*cx);
172 /* Then trim whitespace before and after. */
174 for (cx = commandbuf; *cx == ' '; cx++) { };
178 for (cx = commandbuf+len-1; cx >= cmd && *cx == ' '; cx--) { };
181 /* cmd now points to a nice null-terminated string. We'll do the
182 simplest possible parsing. */
183 if (str_eq(cmd, "")) {
184 glk_put_string("Excuse me?\n");
186 else if (str_eq(cmd, "help")) {
189 else if (str_eq(cmd, "move")) {
192 else if (str_eq(cmd, "jump")) {
195 else if (str_eq(cmd, "yada")) {
198 else if (str_eq(cmd, "quote")) {
201 else if (str_eq(cmd, "quit")) {
204 else if (str_eq(cmd, "save")) {
207 else if (str_eq(cmd, "restore")) {
210 else if (str_eq(cmd, "script")) {
213 else if (str_eq(cmd, "unscript")) {
217 glk_put_string("I don't understand the command \"");
219 glk_put_string("\".\n");
224 static void draw_statuswin(void)
227 glui32 width, height;
230 /* It is possible that the window was not successfully
231 created. If that's the case, don't try to draw it. */
235 if (current_room == 0)
236 roomname = "The Room";
238 roomname = "A Different Room";
240 glk_set_window(statuswin);
241 glk_window_clear(statuswin);
243 glk_window_get_size(statuswin, &width, &height);
245 /* Print the room name, centered. */
246 glk_window_move_cursor(statuswin, (width - str_len(roomname)) / 2, 1);
247 glk_put_string(roomname);
249 /* Draw a decorative compass rose in the upper right. */
250 glk_window_move_cursor(statuswin, width - 3, 0);
251 glk_put_string("\\|/");
252 glk_window_move_cursor(statuswin, width - 3, 1);
253 glk_put_string("-*-");
254 glk_window_move_cursor(statuswin, width - 3, 2);
255 glk_put_string("/|\\");
257 glk_set_window(mainwin);
260 static int yes_or_no(void)
262 char commandbuf[256];
269 /* This loop is identical to the main command loop in glk_main(). */
272 glk_request_line_event(mainwin, commandbuf, 255, 0);
280 case evtype_LineInput:
281 if (ev.win == mainwin) {
293 commandbuf[len] = '\0';
294 for (cx = commandbuf; *cx == ' '; cx++) { };
296 if (*cx == 'y' || *cx == 'Y')
298 if (*cx == 'n' || *cx == 'N')
301 glk_put_string("Please enter \"yes\" or \"no\": ");
306 static void verb_help(void)
308 glk_put_string("This model only understands the following commands:\n");
309 glk_put_string("HELP: Display this list.\n");
310 glk_put_string("JUMP: A verb which just prints some text.\n");
311 glk_put_string("YADA: A verb which prints a very long stream of text.\n");
312 glk_put_string("MOVE: A verb which prints some text, and also changes the status line display.\n");
313 glk_put_string("QUOTE: A verb which displays a block quote in a temporary third window.\n");
314 glk_put_string("SCRIPT: Turn on transcripting, so that output will be echoed to a text file.\n");
315 glk_put_string("UNSCRIPT: Turn off transcripting.\n");
316 glk_put_string("SAVE: Write fake data to a save file.\n");
317 glk_put_string("RESTORE: Read it back in.\n");
318 glk_put_string("QUIT: Quit and exit.\n");
321 static void verb_jump(void)
323 glk_put_string("You jump on the fruit, spotlessly.\n");
326 static void verb_yada(void)
328 /* This is a goofy (and overly ornate) way to print a long paragraph.
329 It just shows off line wrapping in the Glk implementation. */
330 #define NUMWORDS (13)
331 static char *wordcaplist[NUMWORDS] = {
332 "Ga", "Bo", "Wa", "Mu", "Bi", "Fo", "Za", "Mo", "Ra", "Po",
335 static char *wordlist[NUMWORDS] = {
336 "figgle", "wob", "shim", "fleb", "moobosh", "fonk", "wabble",
337 "gazoon", "ting", "floo", "zonk", "loof", "lob",
339 static int wcount1 = 0;
340 static int wcount2 = 0;
341 static int wstep = 1;
346 for (ix=0; ix<85; ix++) {
352 glk_put_string(wordcaplist[(ix / 17) % NUMWORDS]);
356 glk_put_string(wordlist[jx]);
357 jx = (jx + wstep) % NUMWORDS;
359 if (wcount1 >= NUMWORDS) {
363 if (wcount2 >= NUMWORDS-2) {
369 if ((ix % 17) == 16) {
378 static void verb_quote(void)
380 glk_put_string("Someone quotes some poetry.\n");
382 /* Open a third window, or clear it if it's already open. Actually,
383 since quotewin is closed right after line input, we know it
384 can't be open. But better safe, etc. */
386 /* A five-line window above the main window, fixed size. */
387 quotewin = glk_window_open(mainwin, winmethod_Above | winmethod_Fixed,
388 5, wintype_TextBuffer, 0);
390 /* It's possible the quotewin couldn't be opened. In that
391 case, just give up. */
396 glk_window_clear(quotewin);
399 /* Print some quote. */
400 glk_set_window(quotewin);
401 glk_set_style(style_BlockQuote);
402 glk_put_string("Tomorrow probably never rose or set\n"
403 "Or went out and bought cheese, or anything like that\n"
404 "And anyway, what light through yonder quote box breaks\n"
405 "Handle to my hand?\n");
406 glk_put_string(" -- Fred\n");
408 glk_set_window(mainwin);
411 static void verb_move(void)
413 current_room = (current_room+1) % 2;
416 glk_put_string("You walk for a while.\n");
419 static void verb_quit(void)
421 glk_put_string("Are you sure you want to quit? ");
423 glk_put_string("Thanks for playing.\n");
425 /* glk_exit() actually stops the process; it does not return. */
429 static void verb_script(void)
432 glk_put_string("Scripting is already on.\n");
436 /* If we've turned on scripting before, use the same file reference;
437 otherwise, prompt the player for a file. */
439 scriptref = glk_fileref_create_by_prompt(
440 fileusage_Transcript | fileusage_TextMode,
441 filemode_WriteAppend, 0);
443 glk_put_string("Unable to place script file.\n");
449 scriptstr = glk_stream_open_file(scriptref, filemode_WriteAppend, 0);
451 glk_put_string("Unable to write to script file.\n");
454 glk_put_string("Scripting on.\n");
455 glk_window_set_echo_stream(mainwin, scriptstr);
456 glk_put_string_stream(scriptstr,
457 "This is the beginning of a transcript.\n");
460 static void verb_unscript(void)
463 glk_put_string("Scripting is already off.\n");
467 /* Close the file. */
468 glk_put_string_stream(scriptstr,
469 "This is the end of a transcript.\n\n");
470 glk_stream_close(scriptstr, NULL);
471 glk_put_string("Scripting off.\n");
475 static void verb_save(void)
481 saveref = glk_fileref_create_by_prompt(
482 fileusage_SavedGame | fileusage_BinaryMode,
485 glk_put_string("Unable to place save file.\n");
489 savestr = glk_stream_open_file(saveref, filemode_Write, 0);
491 glk_put_string("Unable to write to save file.\n");
492 glk_fileref_destroy(saveref);
496 glk_fileref_destroy(saveref); /* We're done with the file ref now. */
498 /* Write some binary data. */
499 for (ix=0; ix<256; ix++) {
500 glk_put_char_stream(savestr, (unsigned char)ix);
503 glk_stream_close(savestr, NULL);
505 glk_put_string("Game saved.\n");
508 static void verb_restore(void)
516 saveref = glk_fileref_create_by_prompt(
517 fileusage_SavedGame | fileusage_BinaryMode,
520 glk_put_string("Unable to find save file.\n");
524 savestr = glk_stream_open_file(saveref, filemode_Read, 0);
526 glk_put_string("Unable to read from save file.\n");
527 glk_fileref_destroy(saveref);
531 glk_fileref_destroy(saveref); /* We're done with the file ref now. */
533 /* Read some binary data. */
536 for (ix=0; ix<256; ix++) {
537 ch = glk_get_char_stream(savestr);
538 if (ch == (glui32)(-1)) {
539 glk_put_string("Unexpected end of file.\n");
543 if (ch != (glui32)ix) {
544 glk_put_string("This does not appear to be a valid saved game.\n");
550 glk_stream_close(savestr, NULL);
553 glk_put_string("Failed.\n");
557 glk_put_string("Game restored.\n");
560 /* simple string length test */
561 static int str_len(char *s1)
564 for (len = 0; *s1; s1++)
569 /* simple string comparison test */
570 static int str_eq(char *s1, char *s2)
572 for (; *s1 && *s2; s1++, s2++) {