Changed build system to Automake. Split Glk code off into a GTK widget.
[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 /* The glk_main() function is called by the Glk system; it's the main entry
66     point for your program. */
67 void glk_main(void)
68 {       
69     /* Open the main window. */
70     mainwin = glk_window_open(0, 0, 0, wintype_TextBuffer, 1);
71     if (!mainwin) {
72         /* It's possible that the main window failed to open. There's
73             nothing we can do without it, so exit. */
74         return; 
75     }
76     
77     /* Set the current output stream to print to it. */
78     glk_set_window(mainwin);
79     
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);
84
85     /* The third window, quotewin, isn't opened immediately. We'll do
86         that in verb_quote(). */
87
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");
91
92     current_room = 0; /* Set initial location. */
93     need_look = TRUE;
94     
95     while (1) {
96         char commandbuf[256];
97         char *cx, *cmd;
98         int gotline, len;
99         event_t ev;
100         
101         draw_statuswin();
102         
103         if (need_look) {
104             need_look = FALSE;
105             glk_put_string("\n");
106             glk_set_style(style_Subheader);
107             if (current_room == 0)
108                 glk_put_string("The Room\n");
109             else
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");
113         }
114         
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);
121         
122         gotline = FALSE;
123         while (!gotline) {
124         
125             /* Grab an event. */
126             glk_select(&ev);
127             
128             switch (ev.type) {
129             
130                 case evtype_LineInput:
131                     if (ev.win == mainwin) {
132                         gotline = TRUE;
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
139                             it. */
140                     }
141                     break;
142                     
143                 case evtype_Arrange:
144                     /* Windows have changed size, so we have to redraw the
145                         status window. */
146                     draw_statuswin();
147                     break;
148             }
149         }
150         
151         /* commandbuf now contains a line of input from the main window.
152             You would now run your parser and do something with it. */
153         
154         /* First, if there's a blockquote window open, let's close it. 
155             This ensures that quotes remain visible for exactly one
156             command. */
157         if (quotewin) {
158             glk_window_close(quotewin, NULL);
159             quotewin = 0;
160         }
161         
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';
166         
167         /* Then squash to lower-case. */
168         for (cx = commandbuf; *cx; cx++) { 
169             *cx = glk_char_to_lower(*cx);
170         }
171         
172         /* Then trim whitespace before and after. */
173         
174         for (cx = commandbuf; *cx == ' '; cx++) { };
175         
176         cmd = cx;
177         
178         for (cx = commandbuf+len-1; cx >= cmd && *cx == ' '; cx--) { };
179         *(cx+1) = '\0';
180         
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");
185         }
186         else if (str_eq(cmd, "help")) {
187             verb_help();
188         }
189         else if (str_eq(cmd, "move")) {
190             verb_move();
191         }
192         else if (str_eq(cmd, "jump")) {
193             verb_jump();
194         }
195         else if (str_eq(cmd, "yada")) {
196             verb_yada();
197         }
198         else if (str_eq(cmd, "quote")) {
199             verb_quote();
200         }
201         else if (str_eq(cmd, "quit")) {
202             verb_quit();
203         }
204         else if (str_eq(cmd, "save")) {
205             verb_save();
206         }
207         else if (str_eq(cmd, "restore")) {
208             verb_restore();
209         }
210         else if (str_eq(cmd, "script")) {
211             verb_script();
212         }
213         else if (str_eq(cmd, "unscript")) {
214             verb_unscript();
215         }
216         else {
217             glk_put_string("I don't understand the command \"");
218             glk_put_string(cmd);
219             glk_put_string("\".\n");
220         }
221     }
222 }
223
224 static void draw_statuswin(void)
225 {
226     char *roomname;
227     glui32 width, height;
228     
229     if (!statuswin) {
230         /* It is possible that the window was not successfully 
231             created. If that's the case, don't try to draw it. */
232         return;
233     }
234     
235     if (current_room == 0)
236         roomname = "The Room";
237     else
238         roomname = "A Different Room";
239     
240     glk_set_window(statuswin);
241     glk_window_clear(statuswin);
242     
243     glk_window_get_size(statuswin, &width, &height);
244     
245     /* Print the room name, centered. */
246     glk_window_move_cursor(statuswin, (width - str_len(roomname)) / 2, 1);
247     glk_put_string(roomname);
248     
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("/|\\");
256     
257     glk_set_window(mainwin);
258 }
259
260 static int yes_or_no(void)
261 {
262     char commandbuf[256];
263     char *cx;
264     int gotline, len;
265     event_t ev;
266     
267     draw_statuswin();
268     
269     /* This loop is identical to the main command loop in glk_main(). */
270     
271     while (1) {
272         glk_request_line_event(mainwin, commandbuf, 255, 0);
273         
274         gotline = FALSE;
275         while (!gotline) {
276         
277             glk_select(&ev);
278             
279             switch (ev.type) {
280                 case evtype_LineInput:
281                     if (ev.win == mainwin) {
282                         gotline = TRUE;
283                     }
284                     break;
285                     
286                 case evtype_Arrange:
287                     draw_statuswin();
288                     break;
289             }
290         }
291         
292         len = ev.val1;
293         commandbuf[len] = '\0';
294         for (cx = commandbuf; *cx == ' '; cx++) { };
295         
296         if (*cx == 'y' || *cx == 'Y')
297             return TRUE;
298         if (*cx == 'n' || *cx == 'N')
299             return FALSE;
300             
301         glk_put_string("Please enter \"yes\" or \"no\": ");
302     }
303     
304 }
305
306 static void verb_help(void)
307 {
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");
319 }
320
321 static void verb_jump(void)
322 {
323     glk_put_string("You jump on the fruit, spotlessly.\n");
324 }
325
326 static void verb_yada(void)
327 {
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",
333             "Ha", "Ni", "Na"
334     };
335     static char *wordlist[NUMWORDS] = {
336         "figgle", "wob", "shim", "fleb", "moobosh", "fonk", "wabble",
337             "gazoon", "ting", "floo", "zonk", "loof", "lob",
338     };
339     static int wcount1 = 0;
340     static int wcount2 = 0;
341     static int wstep = 1;
342     static int jx = 0;
343     int ix;
344     int first = TRUE;
345     
346     for (ix=0; ix<85; ix++) {
347         if (ix > 0) {
348             glk_put_string(" ");
349         }
350                 
351         if (first) {
352             glk_put_string(wordcaplist[(ix / 17) % NUMWORDS]);
353             first = FALSE;
354         }
355         
356         glk_put_string(wordlist[jx]);
357         jx = (jx + wstep) % NUMWORDS;
358         wcount1++;
359         if (wcount1 >= NUMWORDS) {
360             wcount1 = 0;
361             wstep++;
362             wcount2++;
363             if (wcount2 >= NUMWORDS-2) {
364                 wcount2 = 0;
365                 wstep = 1;
366             }
367         }
368         
369         if ((ix % 17) == 16) {
370             glk_put_string(".");
371             first = TRUE;
372         }
373     }
374     
375     glk_put_char('\n');
376 }
377
378 static void verb_quote(void)
379 {
380     glk_put_string("Someone quotes some poetry.\n");
381
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. */
385     if (!quotewin) {
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);
389         if (!quotewin) {
390             /* It's possible the quotewin couldn't be opened. In that
391                 case, just give up. */
392             return;
393         }
394     }
395     else {
396         glk_window_clear(quotewin);
397     }
398     
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");
407     
408     glk_set_window(mainwin);
409 }
410
411 static void verb_move(void)
412 {
413     current_room = (current_room+1) % 2;
414     need_look = TRUE;
415     
416     glk_put_string("You walk for a while.\n");
417 }
418
419 static void verb_quit(void)
420 {
421     glk_put_string("Are you sure you want to quit? ");
422     if (yes_or_no()) {
423         glk_put_string("Thanks for playing.\n");
424         glk_exit();
425         /* glk_exit() actually stops the process; it does not return. */
426     }
427 }
428
429 static void verb_script(void)
430 {
431     if (scriptstr) {
432         glk_put_string("Scripting is already on.\n");
433         return;
434     }
435     
436     /* If we've turned on scripting before, use the same file reference; 
437         otherwise, prompt the player for a file. */
438     if (!scriptref) {
439         scriptref = glk_fileref_create_by_prompt(
440             fileusage_Transcript | fileusage_TextMode, 
441             filemode_WriteAppend, 0);
442         if (!scriptref) {
443             glk_put_string("Unable to place script file.\n");
444             return;
445         }
446     }
447     
448     /* Open the file. */
449     scriptstr = glk_stream_open_file(scriptref, filemode_WriteAppend, 0);
450     if (!scriptstr) {
451         glk_put_string("Unable to write to script file.\n");
452         return;
453     }
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");
458 }
459
460 static void verb_unscript(void)
461 {
462     if (!scriptstr) {
463         glk_put_string("Scripting is already off.\n");
464         return;
465     }
466     
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");
472     scriptstr = NULL;
473 }
474
475 static void verb_save(void)
476 {
477     int ix;
478     frefid_t saveref;
479     strid_t savestr;
480     
481     saveref = glk_fileref_create_by_prompt(
482         fileusage_SavedGame | fileusage_BinaryMode, 
483         filemode_Write, 0);
484     if (!saveref) {
485         glk_put_string("Unable to place save file.\n");
486         return;
487     }
488     
489     savestr = glk_stream_open_file(saveref, filemode_Write, 0);
490     if (!savestr) {
491         glk_put_string("Unable to write to save file.\n");
492         glk_fileref_destroy(saveref);
493         return;
494     }
495
496     glk_fileref_destroy(saveref); /* We're done with the file ref now. */
497     
498     /* Write some binary data. */
499     for (ix=0; ix<256; ix++) {
500         glk_put_char_stream(savestr, (unsigned char)ix);
501     }
502     
503     glk_stream_close(savestr, NULL);
504     
505     glk_put_string("Game saved.\n");
506 }
507
508 static void verb_restore(void)
509 {
510     int ix;
511     int err;
512     glui32 ch;
513     frefid_t saveref;
514     strid_t savestr;
515     
516     saveref = glk_fileref_create_by_prompt(
517         fileusage_SavedGame | fileusage_BinaryMode, 
518         filemode_Read, 0);
519     if (!saveref) {
520         glk_put_string("Unable to find save file.\n");
521         return;
522     }
523     
524     savestr = glk_stream_open_file(saveref, filemode_Read, 0);
525     if (!savestr) {
526         glk_put_string("Unable to read from save file.\n");
527         glk_fileref_destroy(saveref);
528         return;
529     }
530
531     glk_fileref_destroy(saveref); /* We're done with the file ref now. */
532     
533     /* Read some binary data. */
534     err = FALSE;
535     
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");
540             err = TRUE;
541             break;
542         }
543         if (ch != (glui32)ix) {
544             glk_put_string("This does not appear to be a valid saved game.\n");
545             err = TRUE;
546             break;
547         }
548     }
549     
550     glk_stream_close(savestr, NULL);
551     
552     if (err) {
553         glk_put_string("Failed.\n");
554         return;
555     }
556     
557     glk_put_string("Game restored.\n");
558 }
559
560 /* simple string length test */
561 static int str_len(char *s1)
562 {
563     int len;
564     for (len = 0; *s1; s1++)
565         len++;
566     return len;
567 }
568
569 /* simple string comparison test */
570 static int str_eq(char *s1, char *s2)
571 {
572     for (; *s1 && *s2; s1++, s2++) {
573         if (*s1 != *s2)
574             return FALSE;
575     }
576     
577     if (*s1 || *s2)
578         return FALSE;
579     else
580         return TRUE;
581 }
582