1 #include <libchimara/glk.h>
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);
64 static void verb_alert(void);
65 static void verb_notice(void);
67 /* The glk_main() function is called by the Glk system; it's the main entry
68 point for your program. */
71 /* Open the main window. */
72 mainwin = glk_window_open(0, 0, 0, wintype_TextBuffer, 1);
74 /* It's possible that the main window failed to open. There's
75 nothing we can do without it, so exit. */
79 /* Set the current output stream to print to it. */
80 glk_set_window(mainwin);
82 /* Open a second window: a text grid, above the main window, three lines
83 high. It is possible that this will fail also, but we accept that. */
84 statuswin = glk_window_open(mainwin, winmethod_Above | winmethod_Fixed,
85 3, wintype_TextGrid, 0);
87 /* The third window, quotewin, isn't opened immediately. We'll do
88 that in verb_quote(). */
90 glk_put_string("Model Glk Program\nAn Interactive Model Glk Program\n");
91 glk_put_string("By Andrew Plotkin.\nRelease 7.\n");
92 glk_put_string("Type \"help\" for a list of commands.\n");
94 current_room = 0; /* Set initial location. */
107 glk_put_string("\n");
108 glk_set_style(style_Subheader);
109 if (current_room == 0)
110 glk_put_string("The Room\n");
112 glk_put_string("A Different Room\n");
113 glk_set_style(style_Normal);
114 glk_put_string("You're in a room of some sort.\n");
117 glk_put_string("\n>");
118 /* We request up to 255 characters. The buffer can hold 256, but we
119 are going to stick a null character at the end, so we have to
120 leave room for that. Note that the Glk library does *not*
121 put on that null character. */
122 glk_request_line_event(mainwin, commandbuf, 255, 0);
132 case evtype_LineInput:
133 if (ev.win == mainwin) {
135 /* Really the event can *only* be from mainwin,
136 because we never request line input from the
137 status window. But we do a paranoia test,
138 because commandbuf is only filled if the line
139 event comes from the mainwin request. If the
140 line event comes from anywhere else, we ignore
146 /* Windows have changed size, so we have to redraw the
153 /* commandbuf now contains a line of input from the main window.
154 You would now run your parser and do something with it. */
156 /* First, if there's a blockquote window open, let's close it.
157 This ensures that quotes remain visible for exactly one
160 glk_window_close(quotewin, NULL);
164 /* The line we have received in commandbuf is not null-terminated.
165 We handle that first. */
166 len = ev.val1; /* Will be between 0 and 255, inclusive. */
167 commandbuf[len] = '\0';
169 /* Then squash to lower-case. */
170 for (cx = commandbuf; *cx; cx++) {
171 *cx = glk_char_to_lower(*cx);
174 /* Then trim whitespace before and after. */
176 for (cx = commandbuf; *cx == ' '; cx++) { };
180 for (cx = commandbuf+len-1; cx >= cmd && *cx == ' '; cx--) { };
183 /* cmd now points to a nice null-terminated string. We'll do the
184 simplest possible parsing. */
185 if (str_eq(cmd, "")) {
186 glk_put_string("Excuse me?\n");
188 else if (str_eq(cmd, "help")) {
191 else if (str_eq(cmd, "move")) {
194 else if (str_eq(cmd, "jump")) {
197 else if (str_eq(cmd, "yada")) {
200 else if (str_eq(cmd, "quote")) {
203 else if (str_eq(cmd, "quit")) {
206 else if (str_eq(cmd, "save")) {
209 else if (str_eq(cmd, "restore")) {
212 else if (str_eq(cmd, "script")) {
215 else if (str_eq(cmd, "unscript")) {
218 else if (str_eq(cmd, "alert")) {
221 else if (str_eq(cmd, "notice")) {
225 glk_put_string("I don't understand the command \"");
227 glk_put_string("\".\n");
232 static void draw_statuswin(void)
235 glui32 width, height;
238 /* It is possible that the window was not successfully
239 created. If that's the case, don't try to draw it. */
243 if (current_room == 0)
244 roomname = "The Room";
246 roomname = "A Different Room";
248 glk_set_window(statuswin);
249 glk_window_clear(statuswin);
251 glk_window_get_size(statuswin, &width, &height);
253 /* Print the room name, centered. */
254 glk_window_move_cursor(statuswin, (width - str_len(roomname)) / 2, 1);
255 glk_put_string(roomname);
257 /* Draw a decorative compass rose in the upper right. */
258 glk_window_move_cursor(statuswin, width - 3, 0);
259 glk_put_string("\\|/");
260 glk_window_move_cursor(statuswin, width - 3, 1);
261 glk_put_string("-*-");
262 glk_window_move_cursor(statuswin, width - 3, 2);
263 glk_put_string("/|\\");
265 glk_set_window(mainwin);
268 static int yes_or_no(void)
270 char commandbuf[256];
277 /* This loop is identical to the main command loop in glk_main(). */
280 glk_request_line_event(mainwin, commandbuf, 255, 0);
288 case evtype_LineInput:
289 if (ev.win == mainwin) {
301 commandbuf[len] = '\0';
302 for (cx = commandbuf; *cx == ' '; cx++) { };
304 if (*cx == 'y' || *cx == 'Y')
306 if (*cx == 'n' || *cx == 'N')
309 glk_put_string("Please enter \"yes\" or \"no\": ");
314 static void verb_help(void)
316 glk_put_string("This model only understands the following commands:\n");
317 glk_put_string("HELP: Display this list.\n");
318 glk_put_string("JUMP: A verb which just prints some text.\n");
319 glk_put_string("YADA: A verb which prints a very long stream of text.\n");
320 glk_put_string("MOVE: A verb which prints some text, and also changes the status line display.\n");
321 glk_put_string("QUOTE: A verb which displays a block quote in a temporary third window.\n");
322 glk_put_string("SCRIPT: Turn on transcripting, so that output will be echoed to a text file.\n");
323 glk_put_string("UNSCRIPT: Turn off transcripting.\n");
324 glk_put_string("SAVE: Write fake data to a save file.\n");
325 glk_put_string("RESTORE: Read it back in.\n");
326 glk_put_string("ALERT: Print a frightful message.\n");
327 glk_put_string("NOTICE: Print an interesting message.\n");
328 glk_put_string("QUIT: Quit and exit.\n");
331 static void verb_jump(void)
333 glk_put_string("You jump on the fruit, spotlessly.\n");
336 static void verb_yada(void)
338 /* This is a goofy (and overly ornate) way to print a long paragraph.
339 It just shows off line wrapping in the Glk implementation. */
340 #define NUMWORDS (13)
341 static char *wordcaplist[NUMWORDS] = {
342 "Ga", "Bo", "Wa", "Mu", "Bi", "Fo", "Za", "Mo", "Ra", "Po",
345 static char *wordlist[NUMWORDS] = {
346 "figgle", "wob", "shim", "fleb", "moobosh", "fonk", "wabble",
347 "gazoon", "ting", "floo", "zonk", "loof", "lob",
349 static int wcount1 = 0;
350 static int wcount2 = 0;
351 static int wstep = 1;
356 for (ix=0; ix<85; ix++) {
362 glk_put_string(wordcaplist[(ix / 17) % NUMWORDS]);
366 glk_put_string(wordlist[jx]);
367 jx = (jx + wstep) % NUMWORDS;
369 if (wcount1 >= NUMWORDS) {
373 if (wcount2 >= NUMWORDS-2) {
379 if ((ix % 17) == 16) {
388 static void verb_quote(void)
390 glk_put_string("Someone quotes some poetry.\n");
392 /* Open a third window, or clear it if it's already open. Actually,
393 since quotewin is closed right after line input, we know it
394 can't be open. But better safe, etc. */
396 /* A five-line window above the main window, fixed size. */
397 quotewin = glk_window_open(mainwin, winmethod_Above | winmethod_Fixed,
398 5, wintype_TextBuffer, 0);
400 /* It's possible the quotewin couldn't be opened. In that
401 case, just give up. */
406 glk_window_clear(quotewin);
409 /* Print some quote. */
410 glk_set_window(quotewin);
411 glk_set_style(style_BlockQuote);
412 glk_put_string("Tomorrow probably never rose ");
413 glk_put_string("or set\n Or went out and bought cheese, or anything like that\n"
414 "And anyway, what light through yonder quote box breaks\n"
415 "Handle to my hand?\n");
416 glk_put_string(" -- Fred\n");
418 glk_set_window(mainwin);
421 static void verb_move(void)
423 current_room = (current_room+1) % 2;
426 glk_put_string("You ");
427 glk_set_style(style_Emphasized);
428 glk_put_string("walk");
429 glk_set_style(style_Normal);
430 glk_put_string(" for a while.\n");
433 static void verb_quit(void)
435 glk_put_string("Are you sure you want to quit? ");
437 glk_put_string("Thanks for playing.\n");
439 /* glk_exit() actually stops the process; it does not return. */
443 static void verb_script(void)
446 glk_put_string("Scripting is already on.\n");
450 /* If we've turned on scripting before, use the same file reference;
451 otherwise, prompt the player for a file. */
453 scriptref = glk_fileref_create_by_prompt(
454 fileusage_Transcript | fileusage_TextMode,
455 filemode_WriteAppend, 0);
457 glk_put_string("Unable to place script file.\n");
463 scriptstr = glk_stream_open_file(scriptref, filemode_WriteAppend, 0);
465 glk_put_string("Unable to write to script file.\n");
468 glk_put_string("Scripting on.\n");
469 glk_window_set_echo_stream(mainwin, scriptstr);
470 glk_put_string_stream(scriptstr,
471 "This is the beginning of a transcript.\n");
474 static void verb_unscript(void)
477 glk_put_string("Scripting is already off.\n");
481 /* Close the file. */
482 glk_put_string_stream(scriptstr,
483 "This is the end of a transcript.\n\n");
484 glk_stream_close(scriptstr, NULL);
485 glk_put_string("Scripting off.\n");
489 static void verb_save(void)
495 saveref = glk_fileref_create_by_prompt(
496 fileusage_SavedGame | fileusage_BinaryMode,
499 glk_put_string("Unable to place save file.\n");
503 savestr = glk_stream_open_file(saveref, filemode_Write, 0);
505 glk_put_string("Unable to write to save file.\n");
506 glk_fileref_destroy(saveref);
510 glk_fileref_destroy(saveref); /* We're done with the file ref now. */
512 /* Write some binary data. */
513 for (ix=0; ix<256; ix++) {
514 glk_put_char_stream(savestr, (unsigned char)ix);
517 glk_stream_close(savestr, NULL);
519 glk_put_string("Game saved.\n");
522 static void verb_restore(void)
530 saveref = glk_fileref_create_by_prompt(
531 fileusage_SavedGame | fileusage_BinaryMode,
534 glk_put_string("Unable to find save file.\n");
538 savestr = glk_stream_open_file(saveref, filemode_Read, 0);
540 glk_put_string("Unable to read from save file.\n");
541 glk_fileref_destroy(saveref);
545 glk_fileref_destroy(saveref); /* We're done with the file ref now. */
547 /* Read some binary data. */
550 for (ix=0; ix<256; ix++) {
551 ch = glk_get_char_stream(savestr);
552 if (ch == (glui32)(-1)) {
553 glk_put_string("Unexpected end of file.\n");
557 if (ch != (glui32)ix) {
558 glk_put_string("This does not appear to be a valid saved game.\n");
564 glk_stream_close(savestr, NULL);
567 glk_put_string("Failed.\n");
571 glk_put_string("Game restored.\n");
576 glk_set_style(style_Alert);
577 glk_put_string("O noes!\n");
578 glk_set_style(style_Normal);
583 glk_set_style(style_Note);
584 glk_put_string("The answer... is 42!\n");
585 glk_set_style(style_Normal);
589 /* simple string length test */
590 static int str_len(char *s1)
593 for (len = 0; *s1; s1++)
598 /* simple string comparison test */
599 static int str_eq(char *s1, char *s2)
601 for (; *s1 && *s2; s1++, s2++) {