1 #include <stdarg.h>
2 #ifdef USE_OS_RANDOM
3 #include <stdlib.h>
4 #endif
5 
6 #include "glk.h"
7 
8 #include "miscfort.h"
9 #include "commons.h"
10 #include "funcs.h"
11 
12 /* This file contains various functions which help the Dungeon code work
13     with Glk. */
14 
15 /* Random number functions. If your OS has random() and srandom(), you can
16     define USE_OS_RANDOM. Otherwise, substitutes are provided. */
17 #ifndef USE_OS_RANDOM
18     static glui32 lo_random(void);
19     static void lo_seed_random(glui32 seed);
20     static glui32 rand_table[55]; /* State for the RNG. */
21     static int rand_index1, rand_index2;
22 #endif /* USE_OS_RANDOM */
23 
24 static winid_t mainwin = 0;    /* The story window. */
25 static winid_t statuswin = 0;  /* The status window. */
26 
glk_main()27 void glk_main()
28 {
29     /* Open the main window. */
30     mainwin = glk_window_open(0, 0, 0, wintype_TextBuffer, 1);
31     if (!mainwin) {
32         /* It's possible that the main window failed to open. There's
33             nothing we can do without it, so exit. */
34         return;
35     }
36 
37     /* Open the status window, a one-line textgrid. */
38     statuswin = glk_window_open(mainwin, winmethod_Above | winmethod_Fixed,
39         1, wintype_TextGrid, 0);
40     /* It's possible that the status window failed to open. If so, statuswin
41         is zero; we'll check that later, in redraw_statusline(). */
42 
43     /* Set the current output stream to print to the main window. This will
44         be true for the rest of the game, unless noted otherwise. */
45     glk_set_window(mainwin);
46 
47     /* Run the game. */
48     dungeon_main();
49 }
50 
51 /* These are the random-number functions called by the game source. If
52     USE_OS_RANDOM is defined, they call random() and srandom(). If not,
53     they call some substitutes supplied below. */
54 
rnd_(integer * val)55 integer rnd_(integer *val)
56 {
57     int ix;
58 #ifdef USE_OS_RANDOM
59     ix = random();
60 #else /* USE_OS_RANDOM */
61     ix = lo_random();
62 #endif /* USE_OS_RANDOM */
63     if (ix < 0)
64         ix = (-ix);
65     return ix % (*val);
66 }
67 
inirnd_(integer * v1,integer * v2)68 int inirnd_(integer *v1, integer *v2)
69 {
70 #ifdef USE_OS_RANDOM
71     srandom(((*v1) << 16) | (*v2));
72 #else /* USE_OS_RANDOM */
73     lo_seed_random(((*v1) << 16) | (*v2));
74 #endif /* USE_OS_RANDOM */
75     return 0;
76 }
77 
78 /* These functions are called by the game source to determine the date and
79     time; they're used to seed the RNG, and also for the "time" command
80     (which prints how long you've been playing.) They don't work very
81     well at all, I'm afraid. I plan to add clock-time functions to Glk in
82     the future, as an optional capability, but they're not there yet. */
83 
idate_(integer * year,integer * month,integer * day)84 int idate_(integer *year, integer *month, integer *day)
85 {
86     *year = 1998;
87     *month = 1;
88     *day = 1;
89     return 0;
90 }
91 
itime_(integer * hour,integer * min,integer * sec)92 int itime_(integer *hour, integer *min, integer *sec)
93 {
94     *hour = 12;
95     *min = 0;
96     *sec = 0;
97     return 0;
98 }
99 
100 /* Print a string and wait for a key to be hit. */
s_paus(char * prompt,int len)101 void s_paus(char *prompt, int len)
102 {
103     glk_put_char('\n');
104     glk_put_buffer(prompt, len);
105     getchar_();
106     glk_put_char('\n');
107     glk_put_char('\n');
108 }
109 
110 /* Redraw the status window. This is called before every key or line input,
111     and again whenever there's an evtype_Arrange event. */
redraw_statusline(void)112 static void redraw_statusline(void)
113 {
114     integer ix, as;
115     glui32 width;
116 
117     /* If there's no status window, forget it. */
118     if (!statuswin)
119         return;
120 
121     glk_set_window(statuswin);
122     glk_window_clear(statuswin);
123 
124     glk_window_get_size(statuswin, &width, NULL);
125 
126     /* We copy the code from the "rname" verb, which prints the room name.
127         See verbs.c V67. */
128     ix = rooms_1.rdesc2 - play_1.here;
129     if (ix > 0) {
130         ix = rmsg_1.rtext[ix - 1];
131     }
132     ix = abs(ix);
133     if (ix) {
134         glk_window_move_cursor(statuswin, 1, 0);
135         printdb(ix - 1, -1, -1, FALSE_);
136     }
137 
138     /* Now we print the score and number of moves. Allow 20 characters. If
139         the screen is too narrow, this stomps on the room name -- sorry about
140         that. */
141     glk_window_move_cursor(statuswin, (width - 20), 0);
142 
143     as = advs_1.ascore[play_1.winner - 1];
144     weeprintf("Score: %d / %d",
145         as, play_1.moves);
146 
147     glk_set_window(mainwin); /* Set the output stream back to the main window. */
148 }
149 
150 /* Read a null-terminated line of text. */
getline_null_(char * buf,int buflen)151 int getline_null_(char *buf, int buflen)
152 {
153     int len = getline_(buf, buflen);
154     if (len >= buflen)
155         buf[buflen - 1] = '\0';
156     else
157         buf[len] = '\0';
158     return len;
159 }
160 
161 /* Read a line of text. This runs the usual Glk event loop. */
getline_(char * buf,int buflen)162 int getline_(char *buf, int buflen)
163 {
164     event_t ev;
165 
166     redraw_statusline();
167 
168     /* Request input... */
169     glk_request_line_event(mainwin, buf, buflen, 0);
170 
171     while (1) {
172         glk_select(&ev);
173         switch (ev.type) {
174             case evtype_LineInput:
175                 /* Got some. */
176                 return ev.val1;
177             case evtype_Arrange:
178                 /* The window changed size, so we have to redo the
179                     status line. */
180                 redraw_statusline();
181                 break;
182         }
183     }
184 }
185 
186 /* Read a single keystroke. This runs the usual Glk event loop. */
getchar_()187 int getchar_()
188 {
189     event_t ev;
190 
191     redraw_statusline();
192 
193     /* Request input... */
194     glk_request_char_event(mainwin);
195 
196     while (1) {
197         glk_select(&ev);
198         switch (ev.type) {
199             case evtype_CharInput:
200                 /* Got some. */
201                 return ev.val1;
202             case evtype_Arrange:
203                 /* The window changed size, so we have to redo the
204                     status line. */
205                 redraw_statusline();
206                 break;
207         }
208     }
209 }
210 
211 /* Shut down the program. It's the library's job to put up a "hit any key
212     to exit" message. (Which is important, because there may be some closing
213     text printed just before this call, and the player ought to have time
214     to read it.) */
s_stop(char * s,ftnlen n)215 int s_stop(char *s, ftnlen n)
216 {
217     glk_exit();
218     return 0;
219 }
220 
221 /* Read an array of integers from a file, 4 bytes each, big-endian. */
f_get_ints(strid_t fl,integer * ptr,integer num)222 int f_get_ints(strid_t fl, integer *ptr, integer num)
223 {
224   int ix, jx;
225   int ch;
226   glsi32 val;
227 
228   for (ix=0; ix<num; ix++, ptr++) {
229     val = 0;
230     for (jx=0; jx<4; jx++) {
231       ch = glk_get_char_stream(fl);
232       if (ch == -1) {
233         glk_exit();
234       }
235       val = (val << 8) | (ch & 0xff);
236     }
237     *ptr = val;
238   }
239 
240   return TRUE_;
241 }
242 
243 /* Read an array of logicals from a file, 4 bytes each, big-endian. */
f_get_llogicals(strid_t fl,logical * ptr,integer num)244 int f_get_llogicals(strid_t fl, logical *ptr, integer num)
245 {
246   int ix, jx;
247   int ch;
248   glsi32 val;
249 
250   for (ix=0; ix<num; ix++, ptr++) {
251     val = 0;
252     for (jx=0; jx<4; jx++) {
253       ch = glk_get_char_stream(fl);
254       if (ch == -1) {
255         glk_exit();
256       }
257       val = (val << 8) | (ch & 0xff);
258     }
259     *ptr = val;
260   }
261 
262   return TRUE_;
263 }
264 
265 /* Read an array of logicals from a file, 1 byte each. */
f_get_logicals(strid_t fl,logical * ptr,integer num)266 int f_get_logicals(strid_t fl, logical *ptr, integer num)
267 {
268   int ix;
269   int ch;
270 
271   for (ix=0; ix<num; ix++, ptr++) {
272     ch = glk_get_char_stream(fl);
273     if (ch == -1) {
274       glk_exit();
275     }
276     if (ch)
277       *ptr = TRUE_;
278     else
279       *ptr = FALSE_;
280   }
281 
282   return TRUE_;
283 }
284 
285 /* Write an array of integers to a file, 4 bytes each, big-endian. */
f_put_ints(strid_t fl,integer * ptr,integer num)286 int f_put_ints(strid_t fl, integer *ptr, integer num)
287 {
288   int ix;
289   glsi32 val;
290 
291   for (ix=0; ix<num; ix++, ptr++) {
292     val = *ptr;
293     glk_put_char_stream(fl, (val >> 24) & 0xff);
294     glk_put_char_stream(fl, (val >> 16) & 0xff);
295     glk_put_char_stream(fl, (val >> 8) & 0xff);
296     glk_put_char_stream(fl, (val) & 0xff);
297   }
298 
299   return TRUE_;
300 }
301 
302 /* Write an array of logicals to a file, 1 byte each. */
f_put_logicals(strid_t fl,logical * ptr,integer num)303 int f_put_logicals(strid_t fl, logical *ptr, integer num)
304 {
305   int ix;
306 
307   for (ix=0; ix<num; ix++, ptr++) {
308     if (*ptr) {
309       glk_put_char_stream(fl, 1);
310     }
311     else {
312       glk_put_char_stream(fl, 0);
313     }
314   }
315 
316   return TRUE_;
317 }
318 
319 /* A simple number reader. */
str_to_num(char * buf)320 int str_to_num(char *buf)
321 {
322     int negative = 0;
323     int val = 0;
324 
325     for (; *buf; buf++) {
326         if (*buf == '-') {
327             negative = 1;
328         }
329         else if (*buf >= '0' && *buf <= '9') {
330             val = val * 10 + (*buf - '0');
331         }
332     }
333 
334     if (negative)
335         return -val;
336     else
337         return val;
338 }
339 
340 /* A simple number printer. */
num_to_str(char * buf,int num)341 static void num_to_str(char *buf, int num)
342 {
343     int ix;
344     int size = 0;
345     char tmpc;
346 
347     if (num == 0) {
348         buf[0] = '0';
349         buf[1] = '\0';
350         return;
351     }
352 
353     if (num < 0) {
354         buf[0] = '-';
355         buf++;
356         num = -num;
357     }
358 
359     while (num) {
360         buf[size] = '0' + (num % 10);
361         size++;
362         num /= 10;
363     }
364     for (ix=0; ix<size/2; ix++) {
365         tmpc = buf[ix];
366         buf[ix] = buf[size-ix-1];
367         buf[size-ix-1] = tmpc;
368     }
369     buf[size] = '\0';
370 }
371 
372 /* A simplified printf() substitute, which prints to the current Glk
373     output stream. The only format codes it understands are %c, %d, %s. */
weeprintf(char * fmt,...)374 void weeprintf(char *fmt, ...)
375 {
376     va_list argptr;
377     glsi32 val;
378     char numbuf[32];
379     char *cx;
380 
381     va_start(argptr, fmt);
382     for (; *fmt; fmt++) {
383         if (*fmt == '%' && *(fmt+1)) {
384             fmt++;
385             switch (*fmt) {
386                 case 'c':
387                     val = va_arg(argptr, glsi32);
388                     glk_put_char(val & 0xff);
389                     break;
390                 case 'd':
391                     val = va_arg(argptr, glsi32);
392                     num_to_str(numbuf, val);
393                     for (cx=numbuf; *cx; cx++)
394                         glk_put_char(*cx);
395                     break;
396                 case 's':
397                     cx = va_arg(argptr, char *);
398                     for (; *cx; cx++)
399                         glk_put_char(*cx);
400                     break;
401                 default:
402                     glk_put_char('%');
403                     glk_put_char(*fmt);
404                     break;
405             }
406         }
407         else {
408             glk_put_char(*fmt);
409         }
410     }
411     va_end(argptr);
412 }
413 
414 /* Read some text from the Dungeon data file and print it. This prints message
415     number msgnum. If msgsub1 (and possibly msgsub2) are >= 0, it also reads
416     those messages, and substitutes them for the first one (two) '#' characters
417     in the text. If newline is true, it follows everything up with a newline.
418    This allows a certain amount of formatting in the text it reads in, also.
419     A tab character ('\011') is turned into four spaces.
420     Ctrl-A ('\001') means to turn on fixed-width formatting.
421     Ctrl-B ('\002') means to turn off fixed-width formatting. (It's okay
422         to skip the closing ctrl-B; this code will automatically turn off the
423         fixed-width after the final newline.)
424     An escape character ('\033') means to skip the final newline, even if one
425         was requested. This is used in a few places to glue messages together
426         on the same line.
427     No other control characters, except the newline (ctrl-J, '\012'), are legal.
428     */
printdb(int msgnum,int msgsub1,int msgsub2,int newline)429 void printdb(int msgnum, int msgsub1, int msgsub2, int newline)
430 {
431     int ix, count;
432     glsi32 fpos;
433     char subbufa[80], subbufb[80];
434     char *sub1=NULL, *sub2=NULL;
435     int suppressnewline = FALSE_;
436     int insub;
437     int fixedmode;
438 
439     if (msgsub1 >= 0) {
440         fpos = datafile_strings + datafile_strindex[msgsub1];
441         if (fpos < 0) {
442             weeprintf("Internal error: Tried to print nonexistent message %d.\n",
443                 msgsub1);
444             return;
445         }
446         glk_stream_set_position(datafile, fpos, seekmode_Start);
447         for (ix=0; ix<79; ix++) {
448             subbufa[ix] = glk_get_char_stream(datafile);
449             subbufa[ix] ^= (msgsub1 + ix + 3) & 0xff;
450             if (subbufa[ix] == '\004')
451                 break;
452         }
453         subbufa[ix] = '\0';
454         sub1 = subbufa;
455     }
456 
457     if (msgsub2 >= 0) {
458         fpos = datafile_strings + datafile_strindex[msgsub2];
459         if (fpos < 0) {
460             weeprintf("Internal error: Tried to print nonexistent message %d.\n",
461                 msgsub2);
462             return;
463         }
464         glk_stream_set_position(datafile, fpos, seekmode_Start);
465         for (ix=0; ix<79; ix++) {
466             subbufb[ix] = glk_get_char_stream(datafile);
467             subbufb[ix] ^= (msgsub2 + ix + 3) & 0xff;
468             if (subbufb[ix] == '\004')
469                 break;
470         }
471         subbufb[ix] = '\0';
472         sub2 = subbufb;
473     }
474 
475     fpos = datafile_strings + datafile_strindex[msgnum];
476     if (fpos < 0) {
477         weeprintf("Internal error: Tried to print nonexistent message %d.\n",
478             msgnum);
479         return;
480     }
481     glk_stream_set_position(datafile, fpos, seekmode_Start);
482 
483     /* Print out. */
484     fixedmode = FALSE_;
485     insub = FALSE_;
486     count = 0;
487     while (TRUE_) {
488         unsigned char ch;
489         if (!insub) {
490             ch = glk_get_char_stream(datafile);
491             ch ^= (msgnum + count + 3) & 0xff;
492             count++;
493             if (ch == '\004')
494                 break;
495         }
496         else {
497             ch = *sub1;
498             if (ch == '\0') {
499                 insub = FALSE_;
500                 sub1 = sub2;
501                 sub2 = NULL;
502                 continue;
503             }
504             sub1++;
505         }
506 
507         switch (ch) {
508             case '#':
509                 if (!insub && sub1) {
510                     insub = TRUE_;
511                 }
512                 else {
513                     glk_put_char(ch);
514                 }
515                 break;
516             case '\011':
517                 glk_put_string("    ");
518                 break;
519             case '\033':
520                 suppressnewline = TRUE_;
521                 break;
522             case '\001':
523                 if (!fixedmode) {
524                     fixedmode = TRUE_;
525                     glk_set_style(style_Preformatted);
526                 }
527                 break;
528             case '\002':
529                 if (fixedmode) {
530                     fixedmode = FALSE_;
531                     glk_set_style(style_Normal);
532                 }
533                 break;
534             default:
535                 glk_put_char(ch);
536                 break;
537         }
538     }
539 
540     if (newline && !suppressnewline)
541         glk_put_char('\n');
542 
543     if (fixedmode) {
544         fixedmode = FALSE_;
545         glk_set_style(style_Normal);
546     }
547 }
548 
549 #ifndef USE_OS_RANDOM
550 /* Here is a pretty standard random-number generator and seed function. */
551 
lo_random()552 static glui32 lo_random()
553 {
554     rand_index1 = (rand_index1 + 1) % 55;
555     rand_index2 = (rand_index2 + 1) % 55;
556     rand_table[rand_index1] = rand_table[rand_index1] - rand_table[rand_index2];
557     return rand_table[rand_index1];
558 }
559 
lo_seed_random(glui32 seed)560 static void lo_seed_random(glui32 seed)
561 {
562     glui32 k = 1;
563     int i, loop;
564 
565     rand_table[54] = seed;
566     rand_index1 = 0;
567     rand_index2 = 31;
568 
569     for (i = 0; i < 55; i++) {
570         int ii = (21 * i) % 55;
571         rand_table[ii] = k;
572         k = seed - k;
573         seed = rand_table[ii];
574     }
575     for (loop = 0; loop < 4; loop++) {
576         for (i = 0; i < 55; i++)
577             rand_table[i] = rand_table[i] - rand_table[ (1 + i + 30) % 55];
578     }
579 }
580 
581 #endif /* USE_OS_RANDOM */
582 
583