1 /* Nitfol - z-machine interpreter using Glk for output.
2 Copyright (C) 1999 Evin Robertson
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
18 The author can be reached at nitfol@deja.com
22 #define UPPER_WINDOW 1
23 #define LOWER_WINDOW 0
25 static zwinid lower_win, upper_win;
26 static zwinid current_window;
34 static strid_t stream2, stream4;
36 static int output_stream;
37 static zword stream3_table_starts[16];
38 static zword stream3_table_locations[16];
39 static int stream3_nesting_depth;
44 static BOOL abort_output = FALSE; /* quickly stop outputting */
47 BOOL is_transcripting(void)
49 return (output_stream & STREAM2) != 0;
52 void set_transcript(strid_t stream)
56 zword flags2 = LOWORD(HD_FLAGS2) | b00000001;
57 LOWORDwrite(HD_FLAGS2, flags2);
60 output_stream |= STREAM2;
62 z_set_transcript(lower_win, stream2);
65 zword flags2 = LOWORD(HD_FLAGS2) & b11111110;
66 LOWORDwrite(HD_FLAGS2, flags2);
68 output_stream &= ~STREAM2;
70 z_set_transcript(lower_win, 0);
75 /* initialize the windowing environment */
76 void init_windows(BOOL dofixed, glui32 maxwidth, glui32 maxheight)
78 z_init_windows(dofixed, draw_upper_callback, upper_mouse_callback,
79 maxwidth, maxheight, &upper_win, &lower_win);
81 current_window = lower_win;
82 output_stream = STREAM1 | (output_stream & STREAM2);
83 stream3_nesting_depth = 0;
86 if(output_stream & STREAM2) {
87 set_transcript(stream2);
93 v6_main_window_is(lower_win);
98 static int upper_roomname_length;
100 static void counting_glk_put_char(int ch)
102 upper_roomname_length++;
106 glui32 draw_upper_callback(winid_t win, glui32 width, glui32 height)
108 glui32 curx = 0, cury = 0;
112 if(win == NULL || height == 0) {
119 zword location = get_var(16);
120 offset short_name_off = object_name(location);
122 glk_window_move_cursor(win, 0, cury);
124 if(location && short_name_off) {
125 glk_put_char(' '); curx++;
126 upper_roomname_length = 0;
127 decodezscii(short_name_off, counting_glk_put_char);
128 curx += upper_roomname_length;
131 glk_window_move_cursor(win, width - 8, cury);
132 if((zversion <= 2) || ((LOBYTE(HD_FLAGS1) & 2) == 0)) {
133 if(width > curx + 26) {
134 glk_window_move_cursor(win, width - 24, cury);
135 w_glk_put_string("Score: ");
136 g_print_znumber(get_var(17));
138 glk_window_move_cursor(win, width - 12, cury);
139 w_glk_put_string("Moves: ");
140 g_print_znumber(get_var(18));
142 g_print_znumber(get_var(17)); /* score */
144 g_print_znumber(get_var(18)); /* turns */
147 const char *ampmstr[8] = { " AM", " PM" };
149 zword hours = get_var(17);
150 zword minutes = get_var(18);
159 g_print_number(hours);
163 g_print_number(minutes);
164 w_glk_put_string(ampmstr[ampm]);
168 glk_window_move_cursor(win, 0, cury);
174 void output_string(const char *s)
180 void output_char(int c)
182 static int starlength = 0;
184 if(output_stream & STREAM3) { /* Table output */
185 zword num_chars = LOWORD(stream3_table_starts[stream3_nesting_depth-1]) +1;
186 if((c < 32 && c != 13) || (c >= 127 && c <= 159) || (c > 255))
187 c = '?'; /* Section 7.5.3 */
188 LOBYTEwrite(stream3_table_locations[stream3_nesting_depth-1], c);
189 stream3_table_locations[stream3_nesting_depth-1] += 1;
190 LOWORDwrite(stream3_table_starts[stream3_nesting_depth-1], num_chars);
192 if(output_stream & STREAM1) { /* Normal screen output */
193 if(c >= 155 && c <= 251) { /* "extra characters" */
194 zword game_unicode_table = header_extension_read(3);
195 if(game_unicode_table && LOBYTE(game_unicode_table) >= (zbyte) (c - 155)) {
196 zword address = game_unicode_table + 1 + (c - 155) * 2;
199 const unsigned default_unicode_translation[] = {
200 0xe4, 0xf6, 0xfc, 0xc4, 0xd6, 0xdc, 0xdf, 0xbb,
201 0xab, 0xeb, 0xef, 0xff, 0xcb, 0xcf, 0xe1, 0xe9,
202 0xed, 0xf3, 0xfa, 0xfd, 0xc1, 0xc9, 0xcd, 0xd3,
203 0xda, 0xdd, 0xe0, 0xe8, 0xec, 0xf2, 0xf9, 0xc0,
204 0xc8, 0xcc, 0xd2, 0xd9, 0xe2, 0xea, 0xee, 0xf4,
205 0xfb, 0xc2, 0xca, 0xce, 0xd4, 0xdb, 0xe5, 0xc5,
206 0xf8, 0xd8, 0xe3, 0xf1, 0xf5, 0xc3, 0xd1, 0xd5,
207 0xe6, 0xc6, 0xe7, 0xc7, 0xfe, 0xf0, 0xde, 0xd0,
208 0xa3, 0x153, 0x152, 0xa1, 0xbf
210 c = default_unicode_translation[c - 155];
214 if(++starlength == 3) /* Three asterisks usually means win or death */
215 if(automap_unexplore())
222 const char font3trans[] =
223 " <>/" "\\ --" "||||" "--\\/" /* 32-47 */
224 "\\//\\/\\@ " " |" "|-- " /* 48-63 */
225 " /" "\\/\\ " " " " " /* 64-79 */
226 " " "####" " X+" "udb*" /* 80-95 */
227 "?abc" "defg" "hijk" "lmno" /* 96-111 */
228 "pqrs" "tuvw" "xyzU" "DB?"; /* 112-126 */
229 if(c >= 32 && c <= 126)
230 c = font3trans[c - 32];
234 z_put_char(current_window, c);
239 void n_print_number(unsigned n)
243 int length = n_to_decimal(buffer, n);
245 for(i = length - 1; i >= 0; i--)
246 output_char(buffer[i]);
250 void g_print_number(unsigned n)
254 int length = n_to_decimal(buffer, n);
256 for(i = length - 1; i >= 0; i--)
257 glk_put_char(buffer[i]);
260 void g_print_snumber(int n)
269 void g_print_znumber(zword n)
273 g_print_number(neg(n));
279 void n_print_znumber(zword n)
283 n_print_number(neg(n));
290 void stream4number(unsigned c)
292 if(output_stream & STREAM4) {
293 glk_stream_set_current(stream4);
302 void op_buffer_mode(void)
304 /* FIXME: Glk can't really do this.
305 * I could rely on the Plotkin Bug to do it, but that's ugly and would
306 * break 20 years from now when somebody fixes it. I could also print
307 * spaces between each letter, which isn't the intended effect
309 * For now, do nothing. Doubt this opcode is used often anyway...
314 void op_check_unicode(void)
317 if(operand[0] <= 255 &&
318 (glk_gestalt(gestalt_CharOutput, (unsigned char) operand[0]) !=
319 gestalt_CharOutput_CannotPrint))
321 if(operand[0] <= 255 &&
322 (glk_gestalt(gestalt_LineInput, (unsigned char) operand[0]) !=
325 mop_store_result(result);
329 void op_erase_line(void)
334 if(operand[0] == 1 && current_window == upper_win) {
335 z_erase_line(current_window);
340 void op_erase_window(void)
346 n_show_debug(E_OUTPUT, "erase_window", operand[0]);
352 operand[0] = 0; op_split_window();
353 current_window = lower_win;
356 z_clear_window(upper_win);
357 if(operand[0] == UPPER_WINDOW) break; /* Ok, this is evil, but it works. */
359 z_clear_window(lower_win);
365 void op_get_cursor(void)
368 z_getxy(upper_win, &x, &y);
369 LOWORDwrite(operand[0], x);
370 LOWORDwrite(operand[0] + ZWORD_SIZE, y);
374 void op_new_line(void)
380 void op_output_stream(void)
384 if(is_neg(operand[0])) {
385 switch(neg(operand[0])) {
389 output_stream &= ~STREAM1;
399 if(stream3_nesting_depth)
400 stream3_nesting_depth--;
402 n_show_error(E_OUTPUT, "stream3 unnested too many times", 0);
403 if(!stream3_nesting_depth)
404 output_stream &= ~STREAM3;
410 glk_stream_close(stream4, NULL);
412 output_stream &= ~STREAM4;
416 n_show_error(E_OUTPUT, "unknown stream deselected", neg(operand[0]));
423 output_stream |= STREAM1;
430 stream2 = n_file_prompt(fileusage_Transcript | fileusage_TextMode,
431 filemode_WriteAppend);
434 set_transcript(stream2);
438 if(stream3_nesting_depth >= 16) {
439 n_show_error(E_OUTPUT, "nesting stream 3 too deeply",
440 stream3_nesting_depth);
443 LOWORDwrite(operand[1], 0);
444 stream3_table_starts[stream3_nesting_depth] = operand[1];
445 stream3_table_locations[stream3_nesting_depth] = operand[1] + 2;
447 output_stream |= STREAM3;
448 stream3_nesting_depth++;
454 stream4 = n_file_prompt(fileusage_InputRecord | fileusage_TextMode,
455 filemode_WriteAppend);
457 output_stream |= STREAM4;
460 n_show_error(E_OUTPUT, "unknown stream selected", operand[0]);
469 abort_output = FALSE;
470 length = decodezscii(PC, output_char);
476 void op_print_ret(void)
479 abort_output = FALSE;
480 length = decodezscii(PC, output_char);
491 void op_print_addr(void)
493 decodezscii(operand[0], output_char);
497 void op_print_paddr(void)
499 getstring(operand[0]);
503 void op_print_char(void)
505 if(operand[0] > 1023) {
506 n_show_error(E_INSTR, "attempt to print character > 1023", operand[0]);
509 output_char(operand[0]);
513 void op_print_num(void)
515 n_print_znumber(operand[0]);
519 void op_print_table(void)
522 zword text = operand[0];
523 zword width = operand[1];
524 zword height = operand[2];
525 zword skips = operand[3];
527 zword startx, starty;
528 unsigned win_width, win_height;
530 z_getxy(current_window, &startx, &starty);
531 z_getsize(current_window, &win_width, &win_height);
538 if(current_window == upper_win) {
539 if(startx + width - 1 > win_width) {
541 n_show_warn(E_OUTPUT, "table too wide; trimming", width);
542 diff = startx + width - 1 - win_width;
546 if(starty + height - 1 > win_height) {
547 n_show_warn(E_OUTPUT, "table too tall; trimming", height);
548 height = win_height - starty + 1;
552 for(y = 0; y < height; y++) {
553 if(current_window == upper_win && allow_output)
554 z_setxy(upper_win, startx, y+starty);
556 for(x = 0; x < width; x++) {
557 output_char(LOBYTE(text));
562 if(current_window != upper_win && y+1 < height)
568 void op_set_colour(void)
573 z_set_color(current_window, operand[0], operand[1]);
577 void op_set_cursor(void)
579 unsigned width, height;
580 zword x = operand[1];
581 zword y = operand[0];
587 n_show_debug(E_OUTPUT, "set_cursor y=", operand[0]);
588 n_show_debug(E_OUTPUT, "set_cursor x=", operand[1]);
591 if(current_window != upper_win) {
595 z_getsize(current_window, &width, &height);
597 if(y == 0 || y > height) { /* section 8.7.2.3 */
598 n_show_error(E_OUTPUT, "illegal line for set_cursor", y);
599 if(y == 0 || y > 512)
601 z_set_height(upper_win, y); /* Resize to allow broken games to work */
603 if(x == 0 || x > width) {
604 n_show_error(E_OUTPUT, "illegal column for set_cursor", x);
608 z_setxy(current_window, x, y);
612 void op_set_text_style(void)
617 z_set_style(current_window, operand[0]);
621 void op_set_window(void)
627 n_show_debug(E_OUTPUT, "set_window", operand[0]);
633 current_window = upper_win;
634 z_setxy(upper_win, 1, 1);
637 current_window = lower_win;
640 n_show_error(E_OUTPUT, "invalid window selected", operand[0]);
645 void op_split_window(void)
651 n_show_debug(E_OUTPUT, "split_window", operand[0]);
658 if(operand[0] > 512) {
659 n_show_error(E_OUTPUT, "game is being ridiculous", operand[0]);
664 z_set_height(upper_win, 0); /* clear the whole upper window first */
666 z_set_height(upper_win, operand[0]);
670 static BOOL timer_callback(zword routine)
672 zword dummylocals[16];
674 mop_call(routine, 0, dummylocals, -2); /* add a special stack frame */
675 decode(); /* start interpreting the routine */
677 exit_decoder = FALSE;
682 BOOL upper_mouse_callback(BOOL is_char_event, winid_t win, glui32 x, glui32 y)
686 if(!(LOBYTE(HD_FLAGS2) & b00100000))
689 header_extension_write(1, x + 1);
690 header_extension_write(2, y + 1);
693 stream4number(x + 1);
694 stream4number(y + 1);
699 for(i = z_terminators; LOBYTE(i) != 0; i++)
700 if(LOBYTE(i) == 255 || LOBYTE(i) == 254)
703 /* @read will not receive mouse input inputs if they're not
704 * terminating characters, but I'm not sure how to reasonably do
705 * that and it shouldn't matter to most things */
711 typedef struct alias_entry alias_entry;
718 BOOL in_use, is_recursive;
721 static alias_entry *alias_list = NULL;
724 void parse_new_alias(const char *aliascommand, BOOL is_recursive)
727 char *stringcopy = n_strdup(aliascommand);
728 char *pcommand = stringcopy;
729 while(isspace(*pcommand))
732 while(isgraph(*pcommand))
738 while(isspace(*pcommand))
745 if(*to == 0) /* Expand blank aliases to a single space */
746 add_alias(from, " ", is_recursive);
748 add_alias(from, to, is_recursive);
752 void add_alias(const char *from, const char *to, BOOL is_recursive)
754 alias_entry newalias;
756 newalias.next = NULL;
757 newalias.from = n_strdup(from);
758 newalias.to = n_strdup(to);
759 newalias.in_use = FALSE;
760 newalias.is_recursive = is_recursive;
761 LEadd(alias_list, newalias);
765 BOOL remove_alias(const char *from)
770 LEsearchremove(alias_list, p, t, n_strcmp(p->from, from) == 0, (n_free(p->from), n_free(p->to)));
775 static alias_entry *find_alias(const char *text, int length)
778 LEsearch(alias_list, p, n_strmatch(p->from, text, length));
783 int search_for_aliases(char *text, int length, int maxlen)
789 for(i = 0; i <= length; i++) {
790 if(i == length || isspace(text[i]) || ispunct(text[i])) {
791 int word_length = i - word_start;
793 alias_entry *p = find_alias(text + word_start, word_length);
794 if(p && !(p->in_use)) {
795 int newlen = strlen(p->to);
796 if(length - word_length + newlen > maxlen)
797 newlen = maxlen - length + word_length;
798 n_memmove(text + word_start + newlen,
799 text + word_start + word_length,
800 maxlen - word_start - MAX(newlen, word_length));
801 n_memcpy(text + word_start, p->to, newlen);
803 if(p->is_recursive) {
805 newlen = search_for_aliases(text + word_start, newlen, maxlen - word_start);
809 length += newlen - word_length;
810 i = word_start + newlen;
820 int n_read(zword dest, unsigned maxlen, zword parse, unsigned initlen,
821 zword timer, zword routine, unsigned char *terminator)
825 char *buffer = (char *) n_malloc(maxlen + 1);
827 #ifdef SMART_TOKENISER
828 forget_corrections();
836 n_show_warn(E_OUTPUT, "small text buffer", maxlen);
838 if(dest > dynamic_size || dest < 64) {
839 n_show_error(E_OUTPUT, "input buffer in invalid location", dest);
843 if(dest + maxlen > dynamic_size) {
844 n_show_error(E_OUTPUT, "input buffer exceeds dynamic memory", dest + maxlen);
845 maxlen = dynamic_size - dest;
848 if(parse >= dest && dest + maxlen > parse) {
849 n_show_warn(E_OUTPUT, "input buffer overlaps parse", dest + maxlen - parse);
850 maxlen = parse - dest;
853 for(i = 0; i < maxlen; i++)
854 buffer[i] = LOBYTE(dest + i);
856 length = z_read(current_window, buffer, maxlen, initlen, timer, timer_callback, routine, terminator);
863 length = search_for_aliases(buffer, length, maxlen);
865 for(i = 0; i < length; i++) {
866 buffer[i] = glk_char_to_lower(buffer[i]);
867 LOBYTEwrite(dest + i, buffer[i]);
871 z_tokenise(buffer, length, parse, z_dictionary, TRUE);
878 void stream4line(const char *buffer, int length, char terminator)
880 if(output_stream & STREAM4) {
881 w_glk_put_buffer_stream(stream4, buffer, length);
883 stream4number(terminator);
884 glk_put_char_stream(stream4, 10);
891 unsigned maxlen, length;
893 zword text = operand[0];
895 maxlen = LOBYTE(text) - 1;
901 length = n_read(text + 1, maxlen, operand[1], 0,
902 operand[2], operand[3], &term);
904 LOBYTEwrite(text + 1 + length, 0); /* zero terminator */
907 if(!has_done_save_undo && auto_save_undo)
909 has_done_save_undo = FALSE;
916 int maxlen, length, initlen;
918 zword text = operand[0];
920 maxlen = LOBYTE(text);
921 initlen = LOBYTE(text + 1);
927 length = n_read(text + 2, maxlen, operand[1], initlen,
928 operand[2], operand[3], &term);
930 LOBYTEwrite(text + 1, length);
931 mop_store_result(term);
934 if(!has_done_save_undo && auto_save_undo)
936 has_done_save_undo = FALSE;
941 void op_read_char(void)
946 n_show_error(E_OUTPUT, "input attempted during time routine", 0);
951 if(operand[0] != 1) {
952 n_show_warn(E_OUTPUT, "read_char with non-one first operand", operand[0]);
962 validch = z_read_char(current_window, operand[1], timer_callback, operand[2]);
967 mop_store_result(validch);
970 if(!has_done_save_undo && auto_save_undo_char) {
972 has_done_save_undo = FALSE;
976 stream4number(validch);
980 void op_show_status(void)
983 z_flush_fixed(upper_win);
986 /* Returns a character, or returns 0 if it found a number, which it stores
988 unsigned char transcript_getchar(unsigned *num)
995 c = glk_get_char_stream(input_stream1);
998 glk_stream_close(input_stream1, NULL);
1004 while((c = glk_get_char_stream(input_stream1)) != GLK_EOF) {
1007 if(c >= '0' && c <= '9') {
1008 *num = (*num * 10) + (c - '0');
1011 c = glk_get_char_stream(input_stream1);
1013 n_show_error(E_OUTPUT, "input script not understood", c);
1020 /* Returns line terminator. Writes up to *len bytes to dest, writing in the
1021 actual number of characters read in *len. */
1022 unsigned char transcript_getline(char *dest, glui32 *length)
1024 unsigned char term = 10;
1028 if(!input_stream1) {
1032 for(len = 0; len < *length; len++) {
1033 c = transcript_getchar(&num);
1048 void op_input_stream(void)
1050 /* *FIXME*: 10.2.4 says we shouldn't wait for [MORE]. Glk won't allow this */
1052 glk_stream_close(input_stream1, NULL);
1055 switch(operand[0]) {
1059 input_stream1 = n_file_prompt(fileusage_InputRecord | fileusage_TextMode,
1063 n_show_error(E_OUTPUT, "unknown input stream selected", operand[0]);
1068 void op_set_font(void)
1073 mop_store_result(0);
1080 n_show_debug(E_OUTPUT, "set_font", operand[0]);
1084 switch(operand[0]) {
1085 case 1: font = 1; break;
1086 case 4: font = 4; break;
1087 case 3: if(enablefont3) font = 3;
1088 default: mop_store_result(0); return;
1090 set_fixed(font == 4);
1092 mop_store_result(lastfont);
1096 void op_print_unicode(void)
1100 if(operand[0] >= 256 || (operand[0] > 127 && operand[0] < 160)) {
1104 if(output_stream & STREAM3) {
1105 if(operand[0] >= 160) {
1106 const unsigned char default_unicode_zscii_translation[] = {
1107 0x00, 0xde, 0x00, 0xdb, 0x00, 0x00, 0x00, 0x00,
1108 0x00, 0x00, 0x00, 0xa3, 0x00, 0x00, 0x00, 0x00,
1109 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1110 0x00, 0x00, 0x00, 0xa2, 0x00, 0x00, 0x00, 0xdf,
1111 0xba, 0xaf, 0xc4, 0xd0, 0x9e, 0xca, 0xd4, 0xd6,
1112 0xbb, 0xb0, 0xc5, 0xa7, 0xbc, 0xb1, 0xc6, 0xa8,
1113 0xda, 0xd1, 0xbd, 0xb2, 0xc7, 0xd2, 0x9f, 0x00,
1114 0xcc, 0xbe, 0xb3, 0xc8, 0xa0, 0xb4, 0xd9, 0xa1,
1115 0xb5, 0xa9, 0xbf, 0xcd, 0x9b, 0xc9, 0xd3, 0xd5,
1116 0xb6, 0xaa, 0xc0, 0xa4, 0xb7, 0xab, 0xc1, 0xa5,
1117 0xd8, 0xce, 0xb8, 0xac, 0xc2, 0xcf, 0x9c, 0x00,
1118 0xcb, 0xb9, 0xad, 0xc3, 0x9d, 0xae, 0xd7, 0xa6
1120 unsigned char c = default_unicode_zscii_translation[operand[0] - 160];
1121 output_char(c == 0 ? '?' : c);
1122 } else if(operand[0] == 10) {
1125 output_char(operand[0]);
1128 if(output_stream & STREAM1) {
1129 z_put_char(current_window, operand[0]);