Added Nitfol and Frotz source code.
[projects/chimara/chimara.git] / interpreters / frotz / glkscreen.c
diff --git a/interpreters/frotz/glkscreen.c b/interpreters/frotz/glkscreen.c
new file mode 100644 (file)
index 0000000..4545124
--- /dev/null
@@ -0,0 +1,767 @@
+/* screen.c - Generic screen manipulation
+ *
+ *  Copyright (c) 2005 Tor Andersson -- Glk-ified and V6-disabled
+ *             Copyright (c) 1995-1997 Stefan Jokisch
+ *
+ * This file is part of Frotz.
+ *
+ * Frotz is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Frotz is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include "glkfrotz.h"
+
+static unsigned char statusline[256];
+static int oldstyle = 0;
+static int curstyle = 0;
+static int upperstyle = 0;
+static int lowerstyle = 0;
+static int cury = 1;
+static int curx = 1;
+
+/* To make the common code happy */
+
+int os_char_width (zchar z)
+{
+       return 1;
+}
+
+int os_string_width (const zchar *s)
+{
+       int width = 0;
+       zchar c;
+       while ((c = *s++) != 0)
+               if (c == ZC_NEW_STYLE || c == ZC_NEW_FONT)
+                       s++;
+               else
+                       width += os_char_width(c);
+       return width;
+}
+
+void os_prepare_sample (int a)
+{
+       glk_sound_load_hint(a, 1);
+}
+
+void os_finish_with_sample (int a)
+{
+       glk_sound_load_hint(a, 0);
+}
+
+/*
+ * os_start_sample
+ *
+ * Play the given sample at the given volume (ranging from 1 to 8 and
+ * 255 meaning a default volume). The sound is played once or several
+ * times in the background (255 meaning forever). In Z-code 3 the
+ * repeats value is always 0 and the number of repeats is taken from
+ * the sound file itself. The end_of_sound function is called as soon
+ * as the sound finishes.
+ *
+ */
+
+void os_start_sample (int number, int volume, int repeats, zword eos)
+{
+       int vol;
+
+       if (!gos_channel)
+       {
+               gos_channel = glk_schannel_create(0);
+               if (!gos_channel)
+                       return;
+       }
+
+       switch (volume)
+       {
+       case   1: vol = 0x02000; break;
+       case   2: vol = 0x04000; break;
+       case   3: vol = 0x06000; break;
+       case   4: vol = 0x08000; break;
+       case   5: vol = 0x0a000; break;
+       case   6: vol = 0x0c000; break;
+       case   7: vol = 0x0e000; break;
+       case   8: vol = 0x10000; break;
+       default:  vol = 0x20000; break;
+       }
+
+       /* we dont do repeating or eos-callback for now... */
+       glk_schannel_play_ext(gos_channel, number, 1, 0);
+       glk_schannel_set_volume(gos_channel, vol);
+}
+
+void os_stop_sample (int a)
+{
+       if (!gos_channel)
+               return;
+       glk_schannel_stop(gos_channel);
+}
+
+void os_beep (int volume)
+{
+}
+
+void gos_update_width(void)
+{
+       glui32 width;
+       if (gos_upper)
+       {
+               glk_window_get_size(gos_upper, &width, NULL);
+               h_screen_cols = width;
+               SET_BYTE(H_SCREEN_COLS, width);
+               if (curx > width)
+               {
+                       glk_window_move_cursor(gos_upper, 0, cury-1);
+                       curx = 1;
+               }
+       }
+}
+
+void gos_update_height(void)
+{
+       glui32 height_upper;
+       glui32 height_lower;
+       if (gos_curwin)
+       {
+               glk_window_get_size(gos_upper, NULL, &height_upper);
+               glk_window_get_size(gos_lower, NULL, &height_lower);
+               h_screen_rows = height_upper + height_lower + 1;
+               SET_BYTE(H_SCREEN_ROWS, h_screen_rows);
+       }
+}
+
+void reset_status_ht(void)
+{
+       glui32 height;
+       if (gos_upper)
+       {
+               glk_window_get_size(gos_upper, NULL, &height);
+               if (mach_status_ht != height)
+                       glk_window_set_arrangement(
+                               glk_window_get_parent(gos_upper),
+                               winmethod_Above | winmethod_Fixed,
+                               mach_status_ht, NULL);
+       }
+}
+
+void erase_window (int w)
+{
+       if (w == 0)
+               glk_window_clear(gos_lower);
+       else if (gos_upper)
+       {
+               memset(statusline, ' ', sizeof statusline);
+               glk_window_clear(gos_upper);
+               reset_status_ht();
+               curr_status_ht = 0;
+       }
+}
+
+void split_window (int lines)
+{
+       if (!gos_upper)
+               return;
+       /* The top line is always set for V1 to V3 games */
+       if (h_version < V4)
+               lines++;
+
+       if (lines > curr_status_ht)
+       {
+               glui32 height;
+
+               glk_window_get_size(gos_upper, NULL, &height);
+               if (lines != height)
+                       glk_window_set_arrangement(
+                               glk_window_get_parent(gos_upper),
+                               winmethod_Above | winmethod_Fixed,
+                               lines, NULL);
+               curr_status_ht = lines;
+       }
+       mach_status_ht = lines;
+       if (cury > lines)
+       {
+               glk_window_move_cursor(gos_upper, 0, 0);
+               curx = cury = 1;
+       }
+       gos_update_width();
+
+       if (h_version == V3)
+               glk_window_clear(gos_upper);
+}
+
+void restart_screen (void)
+{
+       erase_window(0);
+       erase_window(1);
+       split_window(0);
+}
+
+/*
+ * statusline overflowed the window size ... bad game!
+ * so ... split status text into regions, reformat and print anew.
+ */
+
+void packspaces(unsigned char *src, unsigned char *dst)
+{
+       int killing = 0;
+       while (*src)
+       {
+               if (*src == ' ')
+                       killing++;
+               else
+                       killing = 0;
+               if (killing > 2)
+                       src++;
+               else
+                       *dst++ = *src++;
+       }
+       *dst = 0;
+}
+
+void smartstatusline (void)
+{
+       unsigned char packed[256];
+       unsigned char buf[256];
+       unsigned char *a, *b, *c, *d;
+       int roomlen, scorelen, scoreofs;
+       int len;
+
+       statusline[curx - 1] = 0; /* terminate! */
+
+       packspaces(statusline, packed);
+       //strcpy(packed, statusline);
+       len = strlen(packed);
+
+       a = packed;
+       while (a[0] == ' ')
+               a ++;
+
+       b = a;
+       while (b[0] != 0 && !(b[0] == ' ' && b[1] == ' '))
+               b ++;
+
+       c = b;
+       while (c[0] == ' ')
+               c ++;
+
+       d = packed + len - 1;
+       while (d[0] == ' ' && d > c)
+               d --;
+       if (d[0] != ' ' && d[0] != 0)
+               d ++;
+       if (d < c)
+               d = c;
+
+       //printf("smart '%s'\n", packed);
+       //printf("smart %d %d %d %d\n",a-packed,b-packed,c-packed,d-packed);
+
+       roomlen = b - a;
+       scorelen = d - c;
+       scoreofs = h_screen_cols - scorelen - 2;
+
+       memset(buf, ' ', h_screen_cols);
+       memcpy(buf + 1 + scoreofs, c, scorelen);
+       memcpy(buf + 1, a, roomlen);
+       if (roomlen >= scoreofs)
+               buf[roomlen + 1] = '|';
+
+       glk_window_move_cursor(gos_upper, 0, 0);
+       glk_set_style(style_User1);
+       glk_put_buffer(buf, h_screen_cols);
+       glk_window_move_cursor(gos_upper, cury - 1, curx - 1);
+}
+
+void screen_char (zchar c)
+{
+       if (gos_linepending && (gos_curwin == gos_linewin))
+       {
+               gos_cancel_pending_line();
+               if (gos_curwin == gos_upper)
+               {
+                       curx = 1;
+                       cury ++;
+               }
+               if (c == '\n')
+                       return;
+       }
+
+       if (gos_upper && gos_curwin == gos_upper) {
+               if (cury > mach_status_ht) {
+                       mach_status_ht = cury;
+                       reset_status_ht();
+               }
+       }
+
+       /* check fixed flag in header, game can change it at whim */
+       if (gos_curwin == gos_lower)
+       {
+               static int forcefix = -1;
+               int curfix = h_flags & FIXED_FONT_FLAG;
+               if (forcefix != curfix)
+               {
+                       forcefix = curfix;
+                       zargs[0] = 0xf000;      /* tickle tickle! */
+                       z_set_text_style();
+               }
+       }
+
+       if (gos_upper && gos_curwin == gos_upper)
+       {
+               if (c == '\n' || c == ZC_RETURN) {
+                       glk_put_char('\n');
+                       curx = 1;
+                       cury ++;
+               }
+               else {
+                       if (cury == 1)
+                       {
+                               if (curx < sizeof statusline)
+                                       statusline[curx - 1] = c;
+                               curx++;
+                               if (curx <= h_screen_cols)
+                                       glk_put_char(c);
+                               else
+                                       smartstatusline();
+                       }
+                       else
+                       {
+                               glk_put_char(c);
+                               curx++;
+                               if (curx > h_screen_cols) {
+                                       curx = 1;
+                                       cury++;
+                               }
+                       }
+               }
+       }
+       else if (gos_curwin == gos_lower)
+       {
+               if (c == ZC_RETURN)
+                       glk_put_char('\n');
+               else glk_put_char(c);
+       }
+}
+
+void screen_new_line (void)
+{
+       screen_char('\n');
+}
+
+void screen_word (const zchar *s)
+{
+       zchar c;
+       while ((c = *s++) != 0)
+               if (c == ZC_NEW_FONT)
+                       s++;
+               else if (c == ZC_NEW_STYLE)
+                       s++;
+               else
+                       screen_char (c); 
+}
+
+void screen_mssg_on (void)
+{
+       if (gos_curwin == gos_lower)
+       {
+               oldstyle = curstyle;
+               glk_set_style(style_Preformatted);
+               glk_put_string("\n    ");
+       }
+}
+
+void screen_mssg_off (void)
+{
+       if (gos_curwin == gos_lower)
+       {
+               glk_put_char('\n');
+               zargs[0] = 0;
+               z_set_text_style();
+               zargs[0] = oldstyle;
+               z_set_text_style();
+       }
+}
+
+/*
+ * z_buffer_mode, turn text buffering on/off.
+ *
+ *             zargs[0] = new text buffering flag (0 or 1)
+ *
+ */
+
+void z_buffer_mode (void)
+{
+}
+
+/*
+ * z_erase_line, erase the line starting at the cursor position.
+ *
+ *             zargs[0] = 1 + #units to erase (1 clears to the end of the line)
+ *
+ */
+
+void z_erase_line (void)
+{
+       int i;
+
+       if (gos_upper && gos_curwin == gos_upper)
+       {
+               for (i = 0; i < h_screen_cols + 1 - curx; i++)
+                       glk_put_char(' ');
+               glk_window_move_cursor(gos_curwin, curx - 1, cury - 1);
+       }
+}
+
+/*
+ * z_erase_window, erase a window or the screen to background colour.
+ *
+ *             zargs[0] = window (-3 current, -2 screen, -1 screen & unsplit)
+ *
+ */
+
+void z_erase_window (void)
+{
+       short w = zargs[0];
+       if (w == -2)
+       {
+               if (gos_upper)
+                       glk_window_clear(gos_upper);
+               glk_window_clear(gos_lower);
+       }
+       if (w == -1)
+       {
+               if (gos_upper)
+                       glk_window_clear(gos_upper);
+               glk_window_clear(gos_lower);
+               split_window(0);
+       }
+       if (w == 0)
+               glk_window_clear(gos_lower);
+       if (w == 1 && gos_upper)
+               glk_window_clear(gos_upper);
+}
+
+/*
+ * z_get_cursor, write the cursor coordinates into a table.
+ *
+ *             zargs[0] = address to write information to
+ *
+ */
+
+void z_get_cursor (void)
+{
+       storew ((zword) (zargs[0] + 0), cury);
+       storew ((zword) (zargs[0] + 2), curx);
+}
+
+/*
+ * z_print_table, print ASCII text in a rectangular area.
+ *
+ *             zargs[0] = address of text to be printed
+ *             zargs[1] = width of rectangular area
+ *             zargs[2] = height of rectangular area (optional)
+ *             zargs[3] = number of char's to skip between lines (optional)
+ *
+ */
+
+void z_print_table (void)
+{
+       zword addr = zargs[0];
+       zword x;
+       int i, j;
+
+       /* Supply default arguments */
+
+       if (zargc < 3)
+               zargs[2] = 1;
+       if (zargc < 4)
+               zargs[3] = 0;
+
+       /* Write text in width x height rectangle */
+
+       x = curx;
+
+       for (i = 0; i < zargs[2]; i++) {
+
+               if (i != 0) {
+                       cury += 1;
+                       curx = x;
+               }
+
+               for (j = 0; j < zargs[1]; j++) {
+
+                       zbyte c;
+
+                       LOW_BYTE (addr, c)
+                       addr++;
+
+                       print_char (c);
+               }
+
+               addr += zargs[3];
+       }
+}
+
+/*
+ * z_set_colour, set the foreground and background colours.
+ *
+ *             zargs[0] = foreground colour
+ *             zargs[1] = background colour
+ *             zargs[2] = window (-3 is the current one, optional)
+ *
+ */
+
+void z_set_colour (void)
+{
+       int zfore = zargs[0];
+       int zback = zargs[1];
+
+       if (!(zfore == 0 && zback == 0))
+               garglk_set_zcolors(zfore, zback);
+}
+
+/*
+ * z_set_font, set the font for text output and store the previous font.
+ *
+ *              zargs[0] = number of font or 0 to keep current font
+ *
+ */
+
+void z_set_font (void)
+{
+}
+
+/*
+ * z_set_cursor, set the cursor position or turn the cursor on/off.
+ *
+ *             zargs[0] = y-coordinate or -2/-1 for cursor on/off
+ *             zargs[1] = x-coordinate
+ *             zargs[2] = window (-3 is the current one, optional)
+ *
+ */
+
+void z_set_cursor (void)
+{
+       cury = zargs[0];
+       curx = zargs[1];
+       if (gos_upper)
+               glk_window_move_cursor(gos_upper, curx - 1, cury - 1);
+}
+
+/*
+ * z_set_text_style, set the style for text output.
+ *
+ *              zargs[0] = style flags to set or 0 to reset text style
+ *
+ */
+
+void z_set_text_style (void)
+{
+       int style;
+
+       if (zargs[0] == 0)
+               curstyle = 0;
+       else if (zargs[0] != 0xf000) /* not tickle time */
+               curstyle |= zargs[0];
+
+       if (h_flags & FIXED_FONT_FLAG)
+               style = curstyle | FIXED_WIDTH_STYLE;
+       else
+               style = curstyle;
+
+       if (gos_linepending && gos_curwin == gos_linewin)
+               return;
+
+       if (style & REVERSE_STYLE)
+       {
+               if (gos_curwin == gos_upper && gos_upper) {
+                       glk_set_style(style_User1);
+               }
+               garglk_set_reversevideo(TRUE);
+       }
+       else if (style & FIXED_WIDTH_STYLE)
+               glk_set_style(style_Preformatted);
+       else if (style & BOLDFACE_STYLE && style & EMPHASIS_STYLE)
+               glk_set_style(style_Alert);
+       else if (style & BOLDFACE_STYLE)
+               glk_set_style(style_Subheader);
+       else if (style & EMPHASIS_STYLE)
+               glk_set_style(style_Emphasized);
+       else
+               glk_set_style(style_Normal);
+
+       if (curstyle == 0) {
+               garglk_set_reversevideo(FALSE);
+       }
+}
+
+/*
+ * z_set_window, select the current window.
+ *
+ *             zargs[0] = window to be selected (-3 is the current one)
+ *
+ */
+
+void z_set_window (void)
+{
+       int win = zargs[0];
+
+       if (gos_curwin == gos_lower)
+               lowerstyle = curstyle;
+       else
+               upperstyle = curstyle;
+
+       if (win == 0)
+       {
+               glk_set_window(gos_lower);
+               gos_curwin = gos_lower;
+               curstyle = lowerstyle;
+       }
+       else
+       {
+               if (gos_upper)
+                       glk_set_window(gos_upper);
+               gos_curwin = gos_upper;
+               curstyle = upperstyle;
+       }
+
+        if (win == 0)
+            enable_scripting = TRUE;
+        else
+            enable_scripting = FALSE;
+}
+
+/*
+ * z_show_status, display the status line for V1 to V3 games.
+ *
+ *             no zargs used
+ *
+ */
+
+static void pad_status_line (int column)
+{
+       int spaces;
+       spaces = (h_screen_cols + 1 - curx) - column;
+       while (spaces-- > 0)
+               print_char(' ');
+}
+
+void z_show_status (void)
+{
+       zword global0;
+       zword global1;
+       zword global2;
+       zword addr;
+
+       bool brief = FALSE;
+
+       if (!gos_upper)
+               return;
+
+       /* One V5 game (Wishbringer Solid Gold) contains this opcode by
+          accident, so just return if the version number does not fit */
+
+       if (h_version >= V4)
+               return;
+
+       /* Read all relevant global variables from the memory of the
+          Z-machine into local variables */
+
+       addr = h_globals;
+       LOW_WORD (addr, global0)
+       addr += 2;
+       LOW_WORD (addr, global1)
+       addr += 2;
+       LOW_WORD (addr, global2)
+
+       /* Move to top of the status window, and print in reverse style. */
+
+       glk_set_window(gos_upper);
+       gos_curwin = gos_upper;
+
+       curx = cury = 1;
+       glk_window_move_cursor(gos_upper, 0, 0);
+       glk_set_style(style_User1);
+
+       /* If the screen width is below 55 characters then we have to use
+          the brief status line format */
+
+       if (h_screen_cols < 55)
+               brief = TRUE;
+
+       /* Print the object description for the global variable 0 */
+
+       print_char (' ');
+       print_object (global0);
+
+       /* A header flag tells us whether we have to display the current
+          time or the score/moves information */
+
+       if (h_config & CONFIG_TIME) {           /* print hours and minutes */
+
+               zword hours = (global1 + 11) % 12 + 1;
+
+               pad_status_line (brief ? 15 : 20);
+
+               print_string ("Time: ");
+
+               if (hours < 10)
+                       print_char (' ');
+               print_num (hours);
+
+               print_char (':');
+
+               if (global2 < 10)
+                       print_char ('0');
+               print_num (global2);
+
+               print_char (' ');
+
+               print_char ((global1 >= 12) ? 'p' : 'a');
+               print_char ('m');
+
+       } else {                                                                /* print score and moves */
+
+               pad_status_line (brief ? 15 : 30);
+
+               print_string (brief ? "S: " : "Score: ");
+               print_num (global1);
+
+               pad_status_line (brief ? 8 : 14);
+
+               print_string (brief ? "M: " : "Moves: ");
+               print_num (global2);
+
+       }
+
+       /* Pad the end of the status line with spaces */
+
+       pad_status_line (0);
+
+       /* Return to the lower window */
+
+       glk_set_window(gos_lower);
+       gos_curwin = gos_lower;
+}
+
+/*
+ * z_split_window, split the screen into an upper (1) and lower (0) window.
+ *
+ *             zargs[0] = height of upper window in screen units (V6) or #lines
+ *
+ */
+
+void z_split_window (void)
+{
+       split_window(zargs[0]);
+}
+