a80314941e8f8370e24ae9d9ee5ab95cc97a4cac
[rodin/chimara.git] / src / first.c
1 #include "glk.h"
2
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.
7 */
8
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. */
12
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. */
17
18 /* We also define our own TRUE and FALSE and NULL. */
19 #ifndef TRUE
20 #define TRUE 1
21 #endif
22 #ifndef FALSE
23 #define FALSE 0
24 #endif
25 #ifndef NULL
26 #define NULL 0
27 #endif
28
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;
33
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;
38
39 /* Your location. This determines what appears in the status line. */
40 static int current_room; 
41
42 /* A flag indicating whether you should look around. */
43 static int need_look; 
44
45 /* Forward declarations */
46 void glk_main(void);
47
48 static void draw_statuswin(void);
49 static int yes_or_no(void);
50
51 static int str_eq(char *s1, char *s2);
52 static int str_len(char *s1);
53
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
65 void handler(void)
66 {
67         fprintf(stderr, "I'm the interrupt handler!\n");
68 }
69
70 /* The glk_main() function is called by the Glk system; it's the main entry
71     point for your program. */
72 void glk_main(void)
73 {
74         glk_set_interrupt_handler(&handler);
75         
76     /* Open the main window. */
77     mainwin = glk_window_open(0, 0, 0, wintype_TextBuffer, 1);
78     if (!mainwin) {
79         /* It's possible that the main window failed to open. There's
80             nothing we can do without it, so exit. */
81         return; 
82     }
83     
84     /* Set the current output stream to print to it. */
85     glk_set_window(mainwin);
86     
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);
91
92     /* The third window, quotewin, isn't opened immediately. We'll do
93         that in verb_quote(). */
94
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");
98
99     current_room = 0; /* Set initial location. */
100     need_look = TRUE;
101     
102     while (1) {
103         char commandbuf[256];
104         char *cx, *cmd;
105         int gotline, len;
106         event_t ev;
107         
108         draw_statuswin();
109         
110         if (need_look) {
111             need_look = FALSE;
112             glk_put_string("\n");
113             glk_set_style(style_Subheader);
114             if (current_room == 0)
115                 glk_put_string("The Room\n");
116             else
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");
120         }
121         
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);
128         
129         gotline = FALSE;
130         while (!gotline) {
131         
132             /* Grab an event. */
133             glk_select(&ev);
134             
135             switch (ev.type) {
136             
137                 case evtype_LineInput:
138                     if (ev.win == mainwin) {
139                         gotline = TRUE;
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
146                             it. */
147                     }
148                     break;
149                     
150                 case evtype_Arrange:
151                     /* Windows have changed size, so we have to redraw the
152                         status window. */
153                     draw_statuswin();
154                     break;
155             }
156         }
157         
158         /* commandbuf now contains a line of input from the main window.
159             You would now run your parser and do something with it. */
160         
161         /* First, if there's a blockquote window open, let's close it. 
162             This ensures that quotes remain visible for exactly one
163             command. */
164         if (quotewin) {
165             glk_window_close(quotewin, NULL);
166             quotewin = 0;
167         }
168         
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';
173         
174         /* Then squash to lower-case. */
175         for (cx = commandbuf; *cx; cx++) { 
176             *cx = glk_char_to_lower(*cx);
177         }
178         
179         /* Then trim whitespace before and after. */
180         
181         for (cx = commandbuf; *cx == ' '; cx++) { };
182         
183         cmd = cx;
184         
185         for (cx = commandbuf+len-1; cx >= cmd && *cx == ' '; cx--) { };
186         *(cx+1) = '\0';
187         
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");
192         }
193         else if (str_eq(cmd, "help")) {
194             verb_help();
195         }
196         else if (str_eq(cmd, "move")) {
197             verb_move();
198         }
199         else if (str_eq(cmd, "jump")) {
200             verb_jump();
201         }
202         else if (str_eq(cmd, "yada")) {
203             verb_yada();
204         }
205         else if (str_eq(cmd, "quote")) {
206             verb_quote();
207         }
208         else if (str_eq(cmd, "quit")) {
209             verb_quit();
210         }
211         else if (str_eq(cmd, "save")) {
212             verb_save();
213         }
214         else if (str_eq(cmd, "restore")) {
215             verb_restore();
216         }
217         else if (str_eq(cmd, "script")) {
218             verb_script();
219         }
220         else if (str_eq(cmd, "unscript")) {
221             verb_unscript();
222         }
223         else {
224             glk_put_string("I don't understand the command \"");
225             glk_put_string(cmd);
226             glk_put_string("\".\n");
227         }
228     }
229 }
230
231 static void draw_statuswin(void)
232 {
233     char *roomname;
234     glui32 width, height;
235     
236     if (!statuswin) {
237         /* It is possible that the window was not successfully 
238             created. If that's the case, don't try to draw it. */
239         return;
240     }
241     
242     if (current_room == 0)
243         roomname = "The Room";
244     else
245         roomname = "A Different Room";
246     
247     glk_set_window(statuswin);
248     glk_window_clear(statuswin);
249     
250     glk_window_get_size(statuswin, &width, &height);
251     
252     /* Print the room name, centered. */
253     glk_window_move_cursor(statuswin, (width - str_len(roomname)) / 2, 1);
254     glk_put_string(roomname);
255     
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("/|\\");
263     
264     glk_set_window(mainwin);
265 }
266
267 static int yes_or_no(void)
268 {
269     char commandbuf[256];
270     char *cx;
271     int gotline, len;
272     event_t ev;
273     
274     draw_statuswin();
275     
276     /* This loop is identical to the main command loop in glk_main(). */
277     
278     while (1) {
279         glk_request_line_event(mainwin, commandbuf, 255, 0);
280         
281         gotline = FALSE;
282         while (!gotline) {
283         
284             glk_select(&ev);
285             
286             switch (ev.type) {
287                 case evtype_LineInput:
288                     if (ev.win == mainwin) {
289                         gotline = TRUE;
290                     }
291                     break;
292                     
293                 case evtype_Arrange:
294                     draw_statuswin();
295                     break;
296             }
297         }
298         
299         len = ev.val1;
300         commandbuf[len] = '\0';
301         for (cx = commandbuf; *cx == ' '; cx++) { };
302         
303         if (*cx == 'y' || *cx == 'Y')
304             return TRUE;
305         if (*cx == 'n' || *cx == 'N')
306             return FALSE;
307             
308         glk_put_string("Please enter \"yes\" or \"no\": ");
309     }
310     
311 }
312
313 static void verb_help(void)
314 {
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");
326 }
327
328 static void verb_jump(void)
329 {
330     glk_put_string("You jump on the fruit, spotlessly.\n");
331 }
332
333 static void verb_yada(void)
334 {
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",
340             "Ha", "Ni", "Na"
341     };
342     static char *wordlist[NUMWORDS] = {
343         "figgle", "wob", "shim", "fleb", "moobosh", "fonk", "wabble",
344             "gazoon", "ting", "floo", "zonk", "loof", "lob",
345     };
346     static int wcount1 = 0;
347     static int wcount2 = 0;
348     static int wstep = 1;
349     static int jx = 0;
350     int ix;
351     int first = TRUE;
352     
353     for (ix=0; ix<85; ix++) {
354         if (ix > 0) {
355             glk_put_string(" ");
356         }
357                 
358         if (first) {
359             glk_put_string(wordcaplist[(ix / 17) % NUMWORDS]);
360             first = FALSE;
361         }
362         
363         glk_put_string(wordlist[jx]);
364         jx = (jx + wstep) % NUMWORDS;
365         wcount1++;
366         if (wcount1 >= NUMWORDS) {
367             wcount1 = 0;
368             wstep++;
369             wcount2++;
370             if (wcount2 >= NUMWORDS-2) {
371                 wcount2 = 0;
372                 wstep = 1;
373             }
374         }
375         
376         if ((ix % 17) == 16) {
377             glk_put_string(".");
378             first = TRUE;
379         }
380     }
381     
382     glk_put_char('\n');
383 }
384
385 static void verb_quote(void)
386 {
387     glk_put_string("Someone quotes some poetry.\n");
388
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. */
392     if (!quotewin) {
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);
396         if (!quotewin) {
397             /* It's possible the quotewin couldn't be opened. In that
398                 case, just give up. */
399             return;
400         }
401     }
402     else {
403         glk_window_clear(quotewin);
404     }
405     
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");
414     
415     glk_set_window(mainwin);
416 }
417
418 static void verb_move(void)
419 {
420     current_room = (current_room+1) % 2;
421     need_look = TRUE;
422     
423     glk_put_string("You walk for a while.\n");
424 }
425
426 static void verb_quit(void)
427 {
428     glk_put_string("Are you sure you want to quit? ");
429     if (yes_or_no()) {
430         glk_put_string("Thanks for playing.\n");
431         glk_exit();
432         /* glk_exit() actually stops the process; it does not return. */
433     }
434 }
435
436 static void verb_script(void)
437 {
438     if (scriptstr) {
439         glk_put_string("Scripting is already on.\n");
440         return;
441     }
442     
443     /* If we've turned on scripting before, use the same file reference; 
444         otherwise, prompt the player for a file. */
445     if (!scriptref) {
446         scriptref = glk_fileref_create_by_prompt(
447             fileusage_Transcript | fileusage_TextMode, 
448             filemode_WriteAppend, 0);
449         if (!scriptref) {
450             glk_put_string("Unable to place script file.\n");
451             return;
452         }
453     }
454     
455     /* Open the file. */
456     scriptstr = glk_stream_open_file(scriptref, filemode_WriteAppend, 0);
457     if (!scriptstr) {
458         glk_put_string("Unable to write to script file.\n");
459         return;
460     }
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");
465 }
466
467 static void verb_unscript(void)
468 {
469     if (!scriptstr) {
470         glk_put_string("Scripting is already off.\n");
471         return;
472     }
473     
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");
479     scriptstr = NULL;
480 }
481
482 static void verb_save(void)
483 {
484     int ix;
485     frefid_t saveref;
486     strid_t savestr;
487     
488     saveref = glk_fileref_create_by_prompt(
489         fileusage_SavedGame | fileusage_BinaryMode, 
490         filemode_Write, 0);
491     if (!saveref) {
492         glk_put_string("Unable to place save file.\n");
493         return;
494     }
495     
496     savestr = glk_stream_open_file(saveref, filemode_Write, 0);
497     if (!savestr) {
498         glk_put_string("Unable to write to save file.\n");
499         glk_fileref_destroy(saveref);
500         return;
501     }
502
503     glk_fileref_destroy(saveref); /* We're done with the file ref now. */
504     
505     /* Write some binary data. */
506     for (ix=0; ix<256; ix++) {
507         glk_put_char_stream(savestr, (unsigned char)ix);
508     }
509     
510     glk_stream_close(savestr, NULL);
511     
512     glk_put_string("Game saved.\n");
513 }
514
515 static void verb_restore(void)
516 {
517     int ix;
518     int err;
519     glui32 ch;
520     frefid_t saveref;
521     strid_t savestr;
522     
523     saveref = glk_fileref_create_by_prompt(
524         fileusage_SavedGame | fileusage_BinaryMode, 
525         filemode_Read, 0);
526     if (!saveref) {
527         glk_put_string("Unable to find save file.\n");
528         return;
529     }
530     
531     savestr = glk_stream_open_file(saveref, filemode_Read, 0);
532     if (!savestr) {
533         glk_put_string("Unable to read from save file.\n");
534         glk_fileref_destroy(saveref);
535         return;
536     }
537
538     glk_fileref_destroy(saveref); /* We're done with the file ref now. */
539     
540     /* Read some binary data. */
541     err = FALSE;
542     
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");
547             err = TRUE;
548             break;
549         }
550         if (ch != (glui32)ix) {
551             glk_put_string("This does not appear to be a valid saved game.\n");
552             err = TRUE;
553             break;
554         }
555     }
556     
557     glk_stream_close(savestr, NULL);
558     
559     if (err) {
560         glk_put_string("Failed.\n");
561         return;
562     }
563     
564     glk_put_string("Game restored.\n");
565 }
566
567 /* simple string length test */
568 static int str_len(char *s1)
569 {
570     int len;
571     for (len = 0; *s1; s1++)
572         len++;
573     return len;
574 }
575
576 /* simple string comparison test */
577 static int str_eq(char *s1, char *s2)
578 {
579     for (; *s1 && *s2; s1++, s2++) {
580         if (*s1 != *s2)
581             return FALSE;
582     }
583     
584     if (*s1 || *s2)
585         return FALSE;
586     else
587         return TRUE;
588 }
589