#include #ifdef USE_OS_RANDOM #include #endif #include "glk.h" #include "miscfort.h" #include "commons.h" #include "funcs.h" /* This file contains various functions which help the Dungeon code work with Glk. */ /* Random number functions. If your OS has random() and srandom(), you can define USE_OS_RANDOM. Otherwise, substitutes are provided. */ #ifndef USE_OS_RANDOM static glui32 lo_random(void); static void lo_seed_random(glui32 seed); static glui32 rand_table[55]; /* State for the RNG. */ static int rand_index1, rand_index2; #endif /* USE_OS_RANDOM */ static winid_t mainwin = 0; /* The story window. */ static winid_t statuswin = 0; /* The status window. */ void glk_main() { /* Open the main window. */ mainwin = glk_window_open(0, 0, 0, wintype_TextBuffer, 1); if (!mainwin) { /* It's possible that the main window failed to open. There's nothing we can do without it, so exit. */ return; } /* Open the status window, a one-line textgrid. */ statuswin = glk_window_open(mainwin, winmethod_Above | winmethod_Fixed, 1, wintype_TextGrid, 0); /* It's possible that the status window failed to open. If so, statuswin is zero; we'll check that later, in redraw_statusline(). */ /* Set the current output stream to print to the main window. This will be true for the rest of the game, unless noted otherwise. */ glk_set_window(mainwin); /* Run the game. */ dungeon_main(); } /* These are the random-number functions called by the game source. If USE_OS_RANDOM is defined, they call random() and srandom(). If not, they call some substitutes supplied below. */ integer rnd_(integer *val) { int ix; #ifdef USE_OS_RANDOM ix = random(); #else /* USE_OS_RANDOM */ ix = lo_random(); #endif /* USE_OS_RANDOM */ if (ix < 0) ix = (-ix); return ix % (*val); } int inirnd_(integer *v1, integer *v2) { #ifdef USE_OS_RANDOM srandom(((*v1) << 16) | (*v2)); #else /* USE_OS_RANDOM */ lo_seed_random(((*v1) << 16) | (*v2)); #endif /* USE_OS_RANDOM */ return 0; } /* These functions are called by the game source to determine the date and time; they're used to seed the RNG, and also for the "time" command (which prints how long you've been playing.) They don't work very well at all, I'm afraid. I plan to add clock-time functions to Glk in the future, as an optional capability, but they're not there yet. */ int idate_(integer *year, integer *month, integer *day) { *year = 1998; *month = 1; *day = 1; return 0; } int itime_(integer *hour, integer *min, integer *sec) { *hour = 12; *min = 0; *sec = 0; return 0; } /* Print a string and wait for a key to be hit. */ void s_paus(char *prompt, int len) { glk_put_char('\n'); glk_put_buffer(prompt, len); getchar_(); glk_put_char('\n'); glk_put_char('\n'); } /* Redraw the status window. This is called before every key or line input, and again whenever there's an evtype_Arrange event. */ static void redraw_statusline(void) { integer ix, as; glui32 width; /* If there's no status window, forget it. */ if (!statuswin) return; glk_set_window(statuswin); glk_window_clear(statuswin); glk_window_get_size(statuswin, &width, NULL); /* We copy the code from the "rname" verb, which prints the room name. See verbs.c V67. */ ix = rooms_1.rdesc2 - play_1.here; if (ix > 0) { ix = rmsg_1.rtext[ix - 1]; } ix = abs(ix); if (ix) { glk_window_move_cursor(statuswin, 1, 0); printdb(ix - 1, -1, -1, FALSE_); } /* Now we print the score and number of moves. Allow 20 characters. If the screen is too narrow, this stomps on the room name -- sorry about that. */ glk_window_move_cursor(statuswin, (width - 20), 0); as = advs_1.ascore[play_1.winner - 1]; weeprintf("Score: %d / %d", as, play_1.moves); glk_set_window(mainwin); /* Set the output stream back to the main window. */ } /* Read a null-terminated line of text. */ int getline_null_(char *buf, int buflen) { int len = getline_(buf, buflen); if (len >= buflen) buf[buflen - 1] = '\0'; else buf[len] = '\0'; return len; } /* Read a line of text. This runs the usual Glk event loop. */ int getline_(char *buf, int buflen) { event_t ev; redraw_statusline(); /* Request input... */ glk_request_line_event(mainwin, buf, buflen, 0); while (1) { glk_select(&ev); switch (ev.type) { case evtype_LineInput: /* Got some. */ return ev.val1; case evtype_Arrange: /* The window changed size, so we have to redo the status line. */ redraw_statusline(); break; } } } /* Read a single keystroke. This runs the usual Glk event loop. */ int getchar_() { event_t ev; redraw_statusline(); /* Request input... */ glk_request_char_event(mainwin); while (1) { glk_select(&ev); switch (ev.type) { case evtype_CharInput: /* Got some. */ return ev.val1; case evtype_Arrange: /* The window changed size, so we have to redo the status line. */ redraw_statusline(); break; } } } /* Shut down the program. It's the library's job to put up a "hit any key to exit" message. (Which is important, because there may be some closing text printed just before this call, and the player ought to have time to read it.) */ int s_stop(char *s, ftnlen n) { glk_exit(); return 0; } /* Read an array of integers from a file, 4 bytes each, big-endian. */ int f_get_ints(strid_t fl, integer *ptr, integer num) { int ix, jx; int ch; glsi32 val; for (ix=0; ix> 24) & 0xff); glk_put_char_stream(fl, (val >> 16) & 0xff); glk_put_char_stream(fl, (val >> 8) & 0xff); glk_put_char_stream(fl, (val) & 0xff); } return TRUE_; } /* Write an array of logicals to a file, 1 byte each. */ int f_put_logicals(strid_t fl, logical *ptr, integer num) { int ix; for (ix=0; ix= '0' && *buf <= '9') { val = val * 10 + (*buf - '0'); } } if (negative) return -val; else return val; } /* A simple number printer. */ static void num_to_str(char *buf, int num) { int ix; int size = 0; char tmpc; if (num == 0) { buf[0] = '0'; buf[1] = '\0'; return; } if (num < 0) { buf[0] = '-'; buf++; num = -num; } while (num) { buf[size] = '0' + (num % 10); size++; num /= 10; } for (ix=0; ix= 0, it also reads those messages, and substitutes them for the first one (two) '#' characters in the text. If newline is true, it follows everything up with a newline. This allows a certain amount of formatting in the text it reads in, also. A tab character ('\011') is turned into four spaces. Ctrl-A ('\001') means to turn on fixed-width formatting. Ctrl-B ('\002') means to turn off fixed-width formatting. (It's okay to skip the closing ctrl-B; this code will automatically turn off the fixed-width after the final newline.) An escape character ('\033') means to skip the final newline, even if one was requested. This is used in a few places to glue messages together on the same line. No other control characters, except the newline (ctrl-J, '\012'), are legal. */ void printdb(int msgnum, int msgsub1, int msgsub2, int newline) { int ix, count; glsi32 fpos; char subbufa[80], subbufb[80]; char *sub1=NULL, *sub2=NULL; int suppressnewline = FALSE_; int insub; int fixedmode; if (msgsub1 >= 0) { fpos = datafile_strings + datafile_strindex[msgsub1]; if (fpos < 0) { weeprintf("Internal error: Tried to print nonexistent message %d.\n", msgsub1); return; } glk_stream_set_position(datafile, fpos, seekmode_Start); for (ix=0; ix<79; ix++) { subbufa[ix] = glk_get_char_stream(datafile); subbufa[ix] ^= (msgsub1 + ix + 3) & 0xff; if (subbufa[ix] == '\004') break; } subbufa[ix] = '\0'; sub1 = subbufa; } if (msgsub2 >= 0) { fpos = datafile_strings + datafile_strindex[msgsub2]; if (fpos < 0) { weeprintf("Internal error: Tried to print nonexistent message %d.\n", msgsub2); return; } glk_stream_set_position(datafile, fpos, seekmode_Start); for (ix=0; ix<79; ix++) { subbufb[ix] = glk_get_char_stream(datafile); subbufb[ix] ^= (msgsub2 + ix + 3) & 0xff; if (subbufb[ix] == '\004') break; } subbufb[ix] = '\0'; sub2 = subbufb; } fpos = datafile_strings + datafile_strindex[msgnum]; if (fpos < 0) { weeprintf("Internal error: Tried to print nonexistent message %d.\n", msgnum); return; } glk_stream_set_position(datafile, fpos, seekmode_Start); /* Print out. */ fixedmode = FALSE_; insub = FALSE_; count = 0; while (TRUE_) { unsigned char ch; if (!insub) { ch = glk_get_char_stream(datafile); ch ^= (msgnum + count + 3) & 0xff; count++; if (ch == '\004') break; } else { ch = *sub1; if (ch == '\0') { insub = FALSE_; sub1 = sub2; sub2 = NULL; continue; } sub1++; } switch (ch) { case '#': if (!insub && sub1) { insub = TRUE_; } else { glk_put_char(ch); } break; case '\011': glk_put_string(" "); break; case '\033': suppressnewline = TRUE_; break; case '\001': if (!fixedmode) { fixedmode = TRUE_; glk_set_style(style_Preformatted); } break; case '\002': if (fixedmode) { fixedmode = FALSE_; glk_set_style(style_Normal); } break; default: glk_put_char(ch); break; } } if (newline && !suppressnewline) glk_put_char('\n'); if (fixedmode) { fixedmode = FALSE_; glk_set_style(style_Normal); } } #ifndef USE_OS_RANDOM /* Here is a pretty standard random-number generator and seed function. */ static glui32 lo_random() { rand_index1 = (rand_index1 + 1) % 55; rand_index2 = (rand_index2 + 1) % 55; rand_table[rand_index1] = rand_table[rand_index1] - rand_table[rand_index2]; return rand_table[rand_index1]; } static void lo_seed_random(glui32 seed) { glui32 k = 1; int i, loop; rand_table[54] = seed; rand_index1 = 0; rand_index2 = 31; for (i = 0; i < 55; i++) { int ii = (21 * i) % 55; rand_table[ii] = k; k = seed - k; seed = rand_table[ii]; } for (loop = 0; loop < 4; loop++) { for (i = 0; i < 55; i++) rand_table[i] = rand_table[i] - rand_table[ (1 + i + 30) % 55]; } } #endif /* USE_OS_RANDOM */