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