Implement empty browser window
[projects/chimara/chimara.git] / tests / first.c
1 #include <libchimara/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 static void verb_alert(void);
65 static void verb_notice(void);
66
67 /* The glk_main() function is called by the Glk system; it's the main entry
68     point for your program. */
69 void glk_main(void)
70 {       
71     /* Open the main window. */
72     mainwin = glk_window_open(0, 0, 0, wintype_TextBuffer, 1);
73     if (!mainwin) {
74         /* It's possible that the main window failed to open. There's
75             nothing we can do without it, so exit. */
76         return; 
77     }
78     
79     /* Set the current output stream to print to it. */
80     glk_set_window(mainwin);
81     
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);
86
87     /* The third window, quotewin, isn't opened immediately. We'll do
88         that in verb_quote(). */
89
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");
93
94     current_room = 0; /* Set initial location. */
95     need_look = TRUE;
96     
97     while (1) {
98         char commandbuf[256];
99         char *cx, *cmd;
100         int gotline, len;
101         event_t ev;
102         
103         draw_statuswin();
104         
105         if (need_look) {
106             need_look = FALSE;
107             glk_put_string("\n");
108             glk_set_style(style_Subheader);
109             if (current_room == 0)
110                 glk_put_string("The Room\n");
111             else
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");
115         }
116         
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);
123         
124         gotline = FALSE;
125         while (!gotline) {
126         
127             /* Grab an event. */
128             glk_select(&ev);
129             
130             switch (ev.type) {
131             
132                 case evtype_LineInput:
133                     if (ev.win == mainwin) {
134                         gotline = TRUE;
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
141                             it. */
142                     }
143                     break;
144                     
145                 case evtype_Arrange:
146                     /* Windows have changed size, so we have to redraw the
147                         status window. */
148                     draw_statuswin();
149                     break;
150             }
151         }
152         
153         /* commandbuf now contains a line of input from the main window.
154             You would now run your parser and do something with it. */
155         
156         /* First, if there's a blockquote window open, let's close it. 
157             This ensures that quotes remain visible for exactly one
158             command. */
159         if (quotewin) {
160             glk_window_close(quotewin, NULL);
161             quotewin = 0;
162         }
163         
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';
168         
169         /* Then squash to lower-case. */
170         for (cx = commandbuf; *cx; cx++) { 
171             *cx = glk_char_to_lower(*cx);
172         }
173         
174         /* Then trim whitespace before and after. */
175         
176         for (cx = commandbuf; *cx == ' '; cx++) { };
177         
178         cmd = cx;
179         
180         for (cx = commandbuf+len-1; cx >= cmd && *cx == ' '; cx--) { };
181         *(cx+1) = '\0';
182         
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");
187         }
188         else if (str_eq(cmd, "help")) {
189             verb_help();
190         }
191         else if (str_eq(cmd, "move")) {
192             verb_move();
193         }
194         else if (str_eq(cmd, "jump")) {
195             verb_jump();
196         }
197         else if (str_eq(cmd, "yada")) {
198             verb_yada();
199         }
200         else if (str_eq(cmd, "quote")) {
201             verb_quote();
202         }
203         else if (str_eq(cmd, "quit")) {
204             verb_quit();
205         }
206         else if (str_eq(cmd, "save")) {
207             verb_save();
208         }
209         else if (str_eq(cmd, "restore")) {
210             verb_restore();
211         }
212         else if (str_eq(cmd, "script")) {
213             verb_script();
214         }
215         else if (str_eq(cmd, "unscript")) {
216             verb_unscript();
217         }
218         else if (str_eq(cmd, "alert")) {
219             verb_alert();
220         }
221         else if (str_eq(cmd, "notice")) {
222             verb_notice();
223         }
224         else {
225             glk_put_string("I don't understand the command \"");
226             glk_put_string(cmd);
227             glk_put_string("\".\n");
228         }
229     }
230 }
231
232 static void draw_statuswin(void)
233 {
234     char *roomname;
235     glui32 width, height;
236     
237     if (!statuswin) {
238         /* It is possible that the window was not successfully 
239             created. If that's the case, don't try to draw it. */
240         return;
241     }
242     
243     if (current_room == 0)
244         roomname = "The Room";
245     else
246         roomname = "A Different Room";
247     
248     glk_set_window(statuswin);
249     glk_window_clear(statuswin);
250     
251     glk_window_get_size(statuswin, &width, &height);
252     
253     /* Print the room name, centered. */
254     glk_window_move_cursor(statuswin, (width - str_len(roomname)) / 2, 1);
255     glk_put_string(roomname);
256     
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("/|\\");
264     
265     glk_set_window(mainwin);
266 }
267
268 static int yes_or_no(void)
269 {
270     char commandbuf[256];
271     char *cx;
272     int gotline, len;
273     event_t ev;
274     
275     draw_statuswin();
276     
277     /* This loop is identical to the main command loop in glk_main(). */
278     
279     while (1) {
280         glk_request_line_event(mainwin, commandbuf, 255, 0);
281         
282         gotline = FALSE;
283         while (!gotline) {
284         
285             glk_select(&ev);
286             
287             switch (ev.type) {
288                 case evtype_LineInput:
289                     if (ev.win == mainwin) {
290                         gotline = TRUE;
291                     }
292                     break;
293                     
294                 case evtype_Arrange:
295                     draw_statuswin();
296                     break;
297             }
298         }
299         
300         len = ev.val1;
301         commandbuf[len] = '\0';
302         for (cx = commandbuf; *cx == ' '; cx++) { };
303         
304         if (*cx == 'y' || *cx == 'Y')
305             return TRUE;
306         if (*cx == 'n' || *cx == 'N')
307             return FALSE;
308             
309         glk_put_string("Please enter \"yes\" or \"no\": ");
310     }
311     
312 }
313
314 static void verb_help(void)
315 {
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");
329 }
330
331 static void verb_jump(void)
332 {
333     glk_put_string("You jump on the fruit, spotlessly.\n");
334 }
335
336 static void verb_yada(void)
337 {
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",
343             "Ha", "Ni", "Na"
344     };
345     static char *wordlist[NUMWORDS] = {
346         "figgle", "wob", "shim", "fleb", "moobosh", "fonk", "wabble",
347             "gazoon", "ting", "floo", "zonk", "loof", "lob",
348     };
349     static int wcount1 = 0;
350     static int wcount2 = 0;
351     static int wstep = 1;
352     static int jx = 0;
353     int ix;
354     int first = TRUE;
355     
356     for (ix=0; ix<85; ix++) {
357         if (ix > 0) {
358             glk_put_string(" ");
359         }
360                 
361         if (first) {
362             glk_put_string(wordcaplist[(ix / 17) % NUMWORDS]);
363             first = FALSE;
364         }
365         
366         glk_put_string(wordlist[jx]);
367         jx = (jx + wstep) % NUMWORDS;
368         wcount1++;
369         if (wcount1 >= NUMWORDS) {
370             wcount1 = 0;
371             wstep++;
372             wcount2++;
373             if (wcount2 >= NUMWORDS-2) {
374                 wcount2 = 0;
375                 wstep = 1;
376             }
377         }
378         
379         if ((ix % 17) == 16) {
380             glk_put_string(".");
381             first = TRUE;
382         }
383     }
384     
385     glk_put_char('\n');
386 }
387
388 static void verb_quote(void)
389 {
390     glk_put_string("Someone quotes some poetry.\n");
391
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. */
395     if (!quotewin) {
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);
399         if (!quotewin) {
400             /* It's possible the quotewin couldn't be opened. In that
401                 case, just give up. */
402             return;
403         }
404     }
405     else {
406         glk_window_clear(quotewin);
407     }
408     
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");
417     
418     glk_set_window(mainwin);
419 }
420
421 static void verb_move(void)
422 {
423     current_room = (current_room+1) % 2;
424     need_look = TRUE;
425     
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");
431 }
432
433 static void verb_quit(void)
434 {
435     glk_put_string("Are you sure you want to quit? ");
436     if (yes_or_no()) {
437         glk_put_string("Thanks for playing.\n");
438         glk_exit();
439         /* glk_exit() actually stops the process; it does not return. */
440     }
441 }
442
443 static void verb_script(void)
444 {
445     if (scriptstr) {
446         glk_put_string("Scripting is already on.\n");
447         return;
448     }
449     
450     /* If we've turned on scripting before, use the same file reference; 
451         otherwise, prompt the player for a file. */
452     if (!scriptref) {
453         scriptref = glk_fileref_create_by_prompt(
454             fileusage_Transcript | fileusage_TextMode, 
455             filemode_WriteAppend, 0);
456         if (!scriptref) {
457             glk_put_string("Unable to place script file.\n");
458             return;
459         }
460     }
461     
462     /* Open the file. */
463     scriptstr = glk_stream_open_file(scriptref, filemode_WriteAppend, 0);
464     if (!scriptstr) {
465         glk_put_string("Unable to write to script file.\n");
466         return;
467     }
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");
472 }
473
474 static void verb_unscript(void)
475 {
476     if (!scriptstr) {
477         glk_put_string("Scripting is already off.\n");
478         return;
479     }
480     
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");
486     scriptstr = NULL;
487 }
488
489 static void verb_save(void)
490 {
491     int ix;
492     frefid_t saveref;
493     strid_t savestr;
494     
495     saveref = glk_fileref_create_by_prompt(
496         fileusage_SavedGame | fileusage_BinaryMode, 
497         filemode_Write, 0);
498     if (!saveref) {
499         glk_put_string("Unable to place save file.\n");
500         return;
501     }
502     
503     savestr = glk_stream_open_file(saveref, filemode_Write, 0);
504     if (!savestr) {
505         glk_put_string("Unable to write to save file.\n");
506         glk_fileref_destroy(saveref);
507         return;
508     }
509
510     glk_fileref_destroy(saveref); /* We're done with the file ref now. */
511     
512     /* Write some binary data. */
513     for (ix=0; ix<256; ix++) {
514         glk_put_char_stream(savestr, (unsigned char)ix);
515     }
516     
517     glk_stream_close(savestr, NULL);
518     
519     glk_put_string("Game saved.\n");
520 }
521
522 static void verb_restore(void)
523 {
524     int ix;
525     int err;
526     glui32 ch;
527     frefid_t saveref;
528     strid_t savestr;
529     
530     saveref = glk_fileref_create_by_prompt(
531         fileusage_SavedGame | fileusage_BinaryMode, 
532         filemode_Read, 0);
533     if (!saveref) {
534         glk_put_string("Unable to find save file.\n");
535         return;
536     }
537     
538     savestr = glk_stream_open_file(saveref, filemode_Read, 0);
539     if (!savestr) {
540         glk_put_string("Unable to read from save file.\n");
541         glk_fileref_destroy(saveref);
542         return;
543     }
544
545     glk_fileref_destroy(saveref); /* We're done with the file ref now. */
546     
547     /* Read some binary data. */
548     err = FALSE;
549     
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");
554             err = TRUE;
555             break;
556         }
557         if (ch != (glui32)ix) {
558             glk_put_string("This does not appear to be a valid saved game.\n");
559             err = TRUE;
560             break;
561         }
562     }
563     
564     glk_stream_close(savestr, NULL);
565     
566     if (err) {
567         glk_put_string("Failed.\n");
568         return;
569     }
570     
571     glk_put_string("Game restored.\n");
572 }
573
574 void verb_alert()
575 {
576         glk_set_style(style_Alert);
577         glk_put_string("O noes!\n");
578         glk_set_style(style_Normal);
579 }
580
581 void verb_notice()
582 {
583         glk_set_style(style_Note);
584         glk_put_string("The answer... is 42!\n");
585         glk_set_style(style_Normal);
586 }
587
588
589 /* simple string length test */
590 static int str_len(char *s1)
591 {
592     int len;
593     for (len = 0; *s1; s1++)
594         len++;
595     return len;
596 }
597
598 /* simple string comparison test */
599 static int str_eq(char *s1, char *s2)
600 {
601     for (; *s1 && *s2; s1++, s2++) {
602         if (*s1 != *s2)
603             return FALSE;
604     }
605     
606     if (*s1 || *s2)
607         return FALSE;
608     else
609         return TRUE;
610 }
611