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);
67 fprintf(stderr, "I'm the interrupt handler!\n");
70 /* The glk_main() function is called by the Glk system; it's the main entry
71 point for your program. */
74 glk_set_interrupt_handler(&handler);
76 /* Open the main window. */
77 mainwin = glk_window_open(0, 0, 0, wintype_TextBuffer, 1);
79 /* It's possible that the main window failed to open. There's
80 nothing we can do without it, so exit. */
84 /* Set the current output stream to print to it. */
85 glk_set_window(mainwin);
87 /* Open a second window: a text grid, above the main window, three lines
88 high. It is possible that this will fail also, but we accept that. */
89 statuswin = glk_window_open(mainwin, winmethod_Above | winmethod_Fixed,
90 3, wintype_TextGrid, 0);
92 /* The third window, quotewin, isn't opened immediately. We'll do
93 that in verb_quote(). */
95 glk_put_string("Model Glk Program\nAn Interactive Model Glk Program\n");
96 glk_put_string("By Andrew Plotkin.\nRelease 7.\n");
97 glk_put_string("Type \"help\" for a list of commands.\n");
99 current_room = 0; /* Set initial location. */
103 char commandbuf[256];
112 glk_put_string("\n");
113 glk_set_style(style_Subheader);
114 if (current_room == 0)
115 glk_put_string("The Room\n");
117 glk_put_string("A Different Room\n");
118 glk_set_style(style_Normal);
119 glk_put_string("You're in a room of some sort.\n");
122 glk_put_string("\n>");
123 /* We request up to 255 characters. The buffer can hold 256, but we
124 are going to stick a null character at the end, so we have to
125 leave room for that. Note that the Glk library does *not*
126 put on that null character. */
127 glk_request_line_event(mainwin, commandbuf, 255, 0);
137 case evtype_LineInput:
138 if (ev.win == mainwin) {
140 /* Really the event can *only* be from mainwin,
141 because we never request line input from the
142 status window. But we do a paranoia test,
143 because commandbuf is only filled if the line
144 event comes from the mainwin request. If the
145 line event comes from anywhere else, we ignore
151 /* Windows have changed size, so we have to redraw the
158 /* commandbuf now contains a line of input from the main window.
159 You would now run your parser and do something with it. */
161 /* First, if there's a blockquote window open, let's close it.
162 This ensures that quotes remain visible for exactly one
165 glk_window_close(quotewin, NULL);
169 /* The line we have received in commandbuf is not null-terminated.
170 We handle that first. */
171 len = ev.val1; /* Will be between 0 and 255, inclusive. */
172 commandbuf[len] = '\0';
174 /* Then squash to lower-case. */
175 for (cx = commandbuf; *cx; cx++) {
176 *cx = glk_char_to_lower(*cx);
179 /* Then trim whitespace before and after. */
181 for (cx = commandbuf; *cx == ' '; cx++) { };
185 for (cx = commandbuf+len-1; cx >= cmd && *cx == ' '; cx--) { };
188 /* cmd now points to a nice null-terminated string. We'll do the
189 simplest possible parsing. */
190 if (str_eq(cmd, "")) {
191 glk_put_string("Excuse me?\n");
193 else if (str_eq(cmd, "help")) {
196 else if (str_eq(cmd, "move")) {
199 else if (str_eq(cmd, "jump")) {
202 else if (str_eq(cmd, "yada")) {
205 else if (str_eq(cmd, "quote")) {
208 else if (str_eq(cmd, "quit")) {
211 else if (str_eq(cmd, "save")) {
214 else if (str_eq(cmd, "restore")) {
217 else if (str_eq(cmd, "script")) {
220 else if (str_eq(cmd, "unscript")) {
224 glk_put_string("I don't understand the command \"");
226 glk_put_string("\".\n");
231 static void draw_statuswin(void)
234 glui32 width, height;
237 /* It is possible that the window was not successfully
238 created. If that's the case, don't try to draw it. */
242 if (current_room == 0)
243 roomname = "The Room";
245 roomname = "A Different Room";
247 glk_set_window(statuswin);
248 glk_window_clear(statuswin);
250 glk_window_get_size(statuswin, &width, &height);
252 /* Print the room name, centered. */
253 glk_window_move_cursor(statuswin, (width - str_len(roomname)) / 2, 1);
254 glk_put_string(roomname);
256 /* Draw a decorative compass rose in the upper right. */
257 glk_window_move_cursor(statuswin, width - 3, 0);
258 glk_put_string("\\|/");
259 glk_window_move_cursor(statuswin, width - 3, 1);
260 glk_put_string("-*-");
261 glk_window_move_cursor(statuswin, width - 3, 2);
262 glk_put_string("/|\\");
264 glk_set_window(mainwin);
267 static int yes_or_no(void)
269 char commandbuf[256];
276 /* This loop is identical to the main command loop in glk_main(). */
279 glk_request_line_event(mainwin, commandbuf, 255, 0);
287 case evtype_LineInput:
288 if (ev.win == mainwin) {
300 commandbuf[len] = '\0';
301 for (cx = commandbuf; *cx == ' '; cx++) { };
303 if (*cx == 'y' || *cx == 'Y')
305 if (*cx == 'n' || *cx == 'N')
308 glk_put_string("Please enter \"yes\" or \"no\": ");
313 static void verb_help(void)
315 glk_put_string("This model only understands the following commands:\n");
316 glk_put_string("HELP: Display this list.\n");
317 glk_put_string("JUMP: A verb which just prints some text.\n");
318 glk_put_string("YADA: A verb which prints a very long stream of text.\n");
319 glk_put_string("MOVE: A verb which prints some text, and also changes the status line display.\n");
320 glk_put_string("QUOTE: A verb which displays a block quote in a temporary third window.\n");
321 glk_put_string("SCRIPT: Turn on transcripting, so that output will be echoed to a text file.\n");
322 glk_put_string("UNSCRIPT: Turn off transcripting.\n");
323 glk_put_string("SAVE: Write fake data to a save file.\n");
324 glk_put_string("RESTORE: Read it back in.\n");
325 glk_put_string("QUIT: Quit and exit.\n");
328 static void verb_jump(void)
330 glk_put_string("You jump on the fruit, spotlessly.\n");
333 static void verb_yada(void)
335 /* This is a goofy (and overly ornate) way to print a long paragraph.
336 It just shows off line wrapping in the Glk implementation. */
337 #define NUMWORDS (13)
338 static char *wordcaplist[NUMWORDS] = {
339 "Ga", "Bo", "Wa", "Mu", "Bi", "Fo", "Za", "Mo", "Ra", "Po",
342 static char *wordlist[NUMWORDS] = {
343 "figgle", "wob", "shim", "fleb", "moobosh", "fonk", "wabble",
344 "gazoon", "ting", "floo", "zonk", "loof", "lob",
346 static int wcount1 = 0;
347 static int wcount2 = 0;
348 static int wstep = 1;
353 for (ix=0; ix<85; ix++) {
359 glk_put_string(wordcaplist[(ix / 17) % NUMWORDS]);
363 glk_put_string(wordlist[jx]);
364 jx = (jx + wstep) % NUMWORDS;
366 if (wcount1 >= NUMWORDS) {
370 if (wcount2 >= NUMWORDS-2) {
376 if ((ix % 17) == 16) {
385 static void verb_quote(void)
387 glk_put_string("Someone quotes some poetry.\n");
389 /* Open a third window, or clear it if it's already open. Actually,
390 since quotewin is closed right after line input, we know it
391 can't be open. But better safe, etc. */
393 /* A five-line window above the main window, fixed size. */
394 quotewin = glk_window_open(mainwin, winmethod_Above | winmethod_Fixed,
395 5, wintype_TextBuffer, 0);
397 /* It's possible the quotewin couldn't be opened. In that
398 case, just give up. */
403 glk_window_clear(quotewin);
406 /* Print some quote. */
407 glk_set_window(quotewin);
408 glk_set_style(style_BlockQuote);
409 glk_put_string("Tomorrow probably never rose or set\n"
410 "Or went out and bought cheese, or anything like that\n"
411 "And anyway, what light through yonder quote box breaks\n"
412 "Handle to my hand?\n");
413 glk_put_string(" -- Fred\n");
415 glk_set_window(mainwin);
418 static void verb_move(void)
420 current_room = (current_room+1) % 2;
423 glk_put_string("You walk for a while.\n");
426 static void verb_quit(void)
428 glk_put_string("Are you sure you want to quit? ");
430 glk_put_string("Thanks for playing.\n");
432 /* glk_exit() actually stops the process; it does not return. */
436 static void verb_script(void)
439 glk_put_string("Scripting is already on.\n");
443 /* If we've turned on scripting before, use the same file reference;
444 otherwise, prompt the player for a file. */
446 scriptref = glk_fileref_create_by_prompt(
447 fileusage_Transcript | fileusage_TextMode,
448 filemode_WriteAppend, 0);
450 glk_put_string("Unable to place script file.\n");
456 scriptstr = glk_stream_open_file(scriptref, filemode_WriteAppend, 0);
458 glk_put_string("Unable to write to script file.\n");
461 glk_put_string("Scripting on.\n");
462 glk_window_set_echo_stream(mainwin, scriptstr);
463 glk_put_string_stream(scriptstr,
464 "This is the beginning of a transcript.\n");
467 static void verb_unscript(void)
470 glk_put_string("Scripting is already off.\n");
474 /* Close the file. */
475 glk_put_string_stream(scriptstr,
476 "This is the end of a transcript.\n\n");
477 glk_stream_close(scriptstr, NULL);
478 glk_put_string("Scripting off.\n");
482 static void verb_save(void)
488 saveref = glk_fileref_create_by_prompt(
489 fileusage_SavedGame | fileusage_BinaryMode,
492 glk_put_string("Unable to place save file.\n");
496 savestr = glk_stream_open_file(saveref, filemode_Write, 0);
498 glk_put_string("Unable to write to save file.\n");
499 glk_fileref_destroy(saveref);
503 glk_fileref_destroy(saveref); /* We're done with the file ref now. */
505 /* Write some binary data. */
506 for (ix=0; ix<256; ix++) {
507 glk_put_char_stream(savestr, (unsigned char)ix);
510 glk_stream_close(savestr, NULL);
512 glk_put_string("Game saved.\n");
515 static void verb_restore(void)
523 saveref = glk_fileref_create_by_prompt(
524 fileusage_SavedGame | fileusage_BinaryMode,
527 glk_put_string("Unable to find save file.\n");
531 savestr = glk_stream_open_file(saveref, filemode_Read, 0);
533 glk_put_string("Unable to read from save file.\n");
534 glk_fileref_destroy(saveref);
538 glk_fileref_destroy(saveref); /* We're done with the file ref now. */
540 /* Read some binary data. */
543 for (ix=0; ix<256; ix++) {
544 ch = glk_get_char_stream(savestr);
545 if (ch == (glui32)(-1)) {
546 glk_put_string("Unexpected end of file.\n");
550 if (ch != (glui32)ix) {
551 glk_put_string("This does not appear to be a valid saved game.\n");
557 glk_stream_close(savestr, NULL);
560 glk_put_string("Failed.\n");
564 glk_put_string("Game restored.\n");
567 /* simple string length test */
568 static int str_len(char *s1)
571 for (len = 0; *s1; s1++)
576 /* simple string comparison test */
577 static int str_eq(char *s1, char *s2)
579 for (; *s1 && *s2; s1++, s2++) {