Added Nitfol and Frotz source code.
[rodin/chimara.git] / interpreters / nitfol / io.c
diff --git a/interpreters/nitfol/io.c b/interpreters/nitfol/io.c
new file mode 100644 (file)
index 0000000..ee29799
--- /dev/null
@@ -0,0 +1,1546 @@
+/*  Nitfol - z-machine interpreter using Glk for output.
+    Copyright (C) 1999  Evin Robertson
+
+    This program 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.
+
+    This program 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, USA.
+
+    The author can be reached at nitfol@deja.com
+*/
+#include "nitfol.h"
+#include "nio.h"
+
+#ifdef HEADER
+
+typedef struct z_window *zwinid;
+
+#endif
+
+BOOL is_fixed;       /* If we are forcing output to be fixed-width */
+
+
+/* descriptions of z-machine colors, in glk 0x00rrggbb style.
+   -1 means to call glk_stylehint_clear instead of glk_stylehint_set.
+   The 'current color' will be overwritten on calls to set_colour.
+
+   Go ahead and customize these (making background colors lighter than
+   foreground colors might be interesting)
+*/
+
+glsi32 bgcolortable[] = {
+  -1L,          /* current color */
+  -1L,          /* defualt setting */
+  0x00000000L,  /* black */
+  0x00ff0000L,  /* red */
+  0x00008000L,  /* green */
+  0x00ffff00L,  /* yellow */
+  0x000000ffL,  /* blue */
+  0x00ff00ffL,  /* magenta */
+  0x0000ffffL,  /* cyan */
+  0x00ffffffL,  /* white */
+  0x00c0c0c0L,  /* light grey */
+  0x00808080L,  /* medium grey */
+  0x00404040L   /* dark grey */
+};
+
+glsi32 fgcolortable[] = {
+  -1L,          /* current color */
+  -1L,          /* defualt setting */
+  0x00000000L,  /* black */
+  0x00ff0000L,  /* red */
+  0x00008000L,  /* green */
+  0x00ffff00L,  /* yellow */
+  0x000000ffL,  /* blue */
+  0x00ff00ffL,  /* magenta */
+  0x0000ffffL,  /* cyan */
+  0x00ffffffL,  /* white */
+  0x00c0c0c0L,  /* light grey */
+  0x00808080L,  /* medium grey */
+  0x00404040L   /* dark grey */
+};
+
+
+static void killglkwithcolor(glui32 styl, int fore, int back)
+{
+  if(fgcolortable[fore] == -1)
+    glk_stylehint_clear(wintype_AllTypes, styl,
+                       stylehint_TextColor);
+  else
+    glk_stylehint_set(wintype_AllTypes, styl,
+                     stylehint_TextColor, fgcolortable[fore]);
+
+  if(bgcolortable[back] == -1)
+    glk_stylehint_clear(wintype_AllTypes, styl,
+                       stylehint_BackColor);
+  else
+    glk_stylehint_set(wintype_AllTypes, styl,
+                     stylehint_BackColor, bgcolortable[back]);
+}
+
+
+static void set_stylehints(char fore, char back)
+{
+  glui32 n;
+  for(n = 0; n < style_NUMSTYLES; n++)
+    killglkwithcolor(n, fore, back);
+
+  /* Subheader will be used for bold */
+  glk_stylehint_set(wintype_TextBuffer, style_Subheader,
+                   stylehint_Weight, 1);
+
+  /* BlockQuote will be used for reverse proportional text */
+  glk_stylehint_set(wintype_TextBuffer, style_BlockQuote,
+                   stylehint_Proportional, 0);
+  glk_stylehint_set(wintype_TextBuffer, style_BlockQuote,
+                   stylehint_Justification, stylehint_just_Centered);
+#ifdef stylehint_ReverseColor
+  glk_stylehint_set(wintype_TextBuffer, style_BlockQuote,
+                   stylehint_ReverseColor, 1);
+#endif
+
+  /* User1 will be used for bold italics */
+  glk_stylehint_set(wintype_TextBuffer, style_User1,
+                   stylehint_Weight, 1);
+  glk_stylehint_set(wintype_TextBuffer, style_User1,
+                   stylehint_Oblique, 1);
+
+  /* User2 will be used for proportional bold/italic */
+  glk_stylehint_set(wintype_TextBuffer, style_User2,
+                   stylehint_Proportional, 0);
+  glk_stylehint_set(wintype_TextBuffer, style_User2,
+                   stylehint_Weight, 1);
+}
+
+#define sBOLD 1
+#define sITAL 2
+#define sFIXE 4
+#define sREVE 8
+
+#if 0
+
+static glui32 bitmap_to_style[16] = {
+  style_Normal,
+  style_Subheader,   /* sBOLD */
+  style_Emphasized,  /* sITAL */
+  style_User1,       /* sBOLD | sITAL */
+  style_Preformatted,/* sFIXE */
+  style_User2,       /* sFIXE | sBOLD */
+  style_User2,       /* sFIXE | sITAL */
+  style_User2,       /* sFIXE | sBOLD | sITAL*/
+  style_BlockQuote,  /* sREVE */
+  style_BlockQuote,  /* sREVE | sBOLD */
+  style_BlockQuote,  /* sREVE | sITAL */
+  style_BlockQuote,  /* sREVE | sBOLD | sITAL */
+  style_BlockQuote,  /* sFIXE | sREVE */
+  style_BlockQuote,  /* sFIXE | sREVE | sBOLD */
+  style_BlockQuote,  /* sFIXE | sREVE | sITAL */
+  style_BlockQuote   /* sFIXE | sREVE | sBOLD | sITAL */
+};
+
+#else
+
+static glui32 bitmap_to_style[16] = {
+  style_Normal,
+  style_Subheader,   /* sBOLD */
+  style_Emphasized,  /* sITAL */
+  style_Subheader,   /* sBOLD | sITAL */
+  style_Preformatted,/* sFIXE */
+  style_Subheader,   /* sFIXE | sBOLD */
+  style_Emphasized,  /* sFIXE | sITAL */
+  style_Subheader,   /* sFIXE | sBOLD | sITAL*/
+  style_Normal,
+  style_Subheader,   /* sBOLD */
+  style_Emphasized,  /* sITAL */
+  style_Subheader,   /* sBOLD | sITAL */
+  style_Preformatted,/* sFIXE */
+  style_Subheader,   /* sFIXE | sBOLD */
+  style_Emphasized,  /* sFIXE | sITAL */
+  style_Subheader    /* sFIXE | sBOLD | sITAL*/
+};
+
+#endif
+
+typedef struct {
+  char fore;
+  char back;
+  unsigned char style;
+} colorstyle;
+
+typedef struct {
+  glui32 image_num;
+  glui32 x, y;
+  glui32 width, height;
+} z_image;
+
+
+struct z_window {
+  winid_t win;
+  strid_t str;
+  glui32 wintype;
+  glui32 method;
+
+  strid_t transcript;
+
+  BOOL glk_input_pending;
+  glui32 pending_input_type;
+  glui32 pending_input_length;
+
+  /* for upper window of v3 - returns # of lines drawn */
+  glui32 (*draw_callback)(winid_t win, glui32 width, glui32 height);
+  BOOL (*mouse_callback)(BOOL is_char_event, winid_t win, glui32 x, glui32 y);
+  
+  glui32 width, height;
+  glui32 x1, y1, x2, y2;
+
+  glui32 last_height;   /* What the height was last time we got input */
+  glui32 biggest_height;/* The biggest it's been since */
+
+  glui32 curr_offset;   /* offset into text_buffer/color_buffer */
+  glui32 max_offset;    /* curr_offset must stay < max_offset */
+  glui32 buffer_size;   /* max_offset must stay < buffer_size */
+
+  BOOL dirty;           /* Has window been changed since last redraw? */
+  BOOL defined;         /* Is our location well defined? */
+
+  unsigned char *text_buffer;  /* whole window for grid, current line for buffer */
+  colorstyle *color_buffer;
+
+  z_image images[12];
+
+  colorstyle current;
+  colorstyle actual;
+};
+
+
+#define num_z_windows 16
+
+static struct z_window game_windows[num_z_windows];
+
+static glui32 upper_width, upper_height;
+
+
+static int waitforinput(zwinid window, glui32 *val,
+                       BOOL (*timer_callback)(zword), zword timer_arg);
+
+
+void set_glk_stream_current(void)
+{
+  z_flush_text(&game_windows[0]);
+  glk_stream_set_current(game_windows[0].str);
+}
+
+void draw_intext_picture(zwinid window, glui32 picture, glui32 alignment)
+{
+  z_flush_text(window);
+  wrap_glk_image_draw(window->win, picture, alignment, 0);
+}
+
+void draw_picture(zwinid window, glui32 picture, glui32 x, glui32 y)
+{
+  int i;
+  int useimage = -1;
+  glui32 width, height;
+
+  wrap_glk_image_get_info(operand[0], &width, &height);
+
+  for(i = 0; i < 12; i++) {
+    if(is_in_bounds(window->images[i].x, window->images[i].y,
+                   window->images[i].width, window->images[i].height,
+                   x, y, width, height))
+      useimage = i;
+  }
+  if(useimage == -1)
+    for(i = 0; i < 12; i++)
+      if(window->images[i].image_num == 0)
+       useimage = i;
+  if(useimage == -1)
+    return;
+
+  window->images[useimage].image_num = picture;
+  window->images[useimage].x = x;
+  window->images[useimage].y = y;
+  window->images[useimage].width = width;
+  window->images[useimage].height = height;
+}
+
+
+static int showstuffcount = 0;
+
+/* Show an interpreter message */
+void showstuff(const char *title, const char *type, const char *message, offset number)
+{
+  static BOOL loopy = FALSE;
+  if(loopy) {
+    loopy = FALSE;
+    n_show_fatal(E_SYSTEM, "loopy message reporting", 0);
+  }
+  loopy = TRUE;
+
+  z_pause_timed_input(&game_windows[0]);
+  z_flush_text(&game_windows[0]);
+  glk_stream_set_current(game_windows[0].str);
+
+  glk_set_style(style_Alert);
+  w_glk_put_string("\n[");
+  w_glk_put_string(title);
+  w_glk_put_string(": ");
+  w_glk_put_string(type);
+  w_glk_put_string("]: ");
+  w_glk_put_string(message);
+  w_glk_put_string(" (");
+  g_print_snumber(number);
+  w_glk_put_string(") ");
+
+#ifdef DEBUGGING
+  infix_gprint_loc(stack_get_depth(), 0);
+#else
+  w_glk_put_string("PC=");
+  g_print_number(oldPC);
+  glk_put_char('\n');
+#endif
+
+  if(++showstuffcount == 100) {
+    w_glk_put_string("[pausing every 100 errors]\n");
+    z_wait_for_key(&game_windows[0]);
+  }
+
+  glk_set_style(style_Normal);
+
+  loopy = FALSE;
+}
+
+
+void init_lower(zwinid *lower)
+{
+  zwinid lower_win = &game_windows[0];
+  glui32 i;
+
+  if(lower)
+    *lower = lower_win;
+
+  if(lower_win->win) {
+    z_pause_timed_input(lower_win);
+    glk_window_close(lower_win->win, NULL);
+  }
+
+  set_stylehints(lower_win->current.fore,
+                lower_win->current.back);
+
+
+  lower_win->dirty = TRUE;
+  lower_win->wintype = wintype_TextBuffer;
+  lower_win->method = winmethod_Below;
+  lower_win->curr_offset = 0;
+  lower_win->max_offset = 80 * 24;
+  lower_win->buffer_size = lower_win->max_offset;
+
+  if(!lower_win->text_buffer)
+    lower_win->text_buffer = (unsigned char *) n_malloc(lower_win->buffer_size);
+  if(!lower_win->color_buffer)
+    lower_win->color_buffer = (colorstyle *) n_malloc(lower_win->buffer_size * sizeof(colorstyle));
+
+  for(i = 0; i < lower_win->buffer_size; i++) {
+    lower_win->text_buffer[i] = ' ';
+    lower_win->color_buffer[i] = lower_win->current;
+  }
+
+  lower_win->actual = lower_win->current;
+
+  lower_win->win = glk_window_open(game_windows[1].win,
+                                   winmethod_Below | winmethod_Proportional,
+                                   50,  /* Percent doesn't matter */
+                                   wintype_TextBuffer, 0);
+
+
+  if(lower_win->win == 0) {
+    n_show_fatal(E_OUTPUT, "cannot open lower window", 0);
+    return;
+  }
+
+  lower_win->str = glk_window_get_stream(lower_win->win);
+
+  if(lower_win->transcript)
+    glk_window_set_echo_stream(lower_win->win, lower_win->transcript);
+}
+
+
+void init_upper(zwinid *upper)
+{
+  zwinid upper_win = &game_windows[1];
+  glui32 i;
+
+  if(upper)
+    *upper = upper_win;
+
+  if(upper_win->win) {
+    z_pause_timed_input(upper_win);
+    glk_window_close(upper_win->win, NULL);
+  }
+
+  upper_win->dirty = TRUE;
+  upper_win->wintype = wintype_TextGrid;
+  upper_win->method = winmethod_Above | winmethod_Fixed;
+  upper_win->height = 0;
+  upper_win->width = upper_width;
+  upper_win->curr_offset = 0;
+  upper_win->max_offset = upper_height * upper_width;
+  upper_win->buffer_size = upper_win->max_offset;
+
+  if(!upper_win->text_buffer)
+    upper_win->text_buffer = (unsigned char *) n_malloc(upper_win->buffer_size);
+  if(!upper_win->color_buffer)
+    upper_win->color_buffer = (colorstyle *) n_malloc(upper_win->buffer_size * sizeof(colorstyle));
+
+  for(i = 0; i < upper_win->buffer_size; i++) {
+    upper_win->text_buffer[i] = ' ';
+    upper_win->color_buffer[i] = upper_win->current;
+  }
+
+  upper_win->actual = upper_win->current;
+
+  upper_win->win = glk_window_open(game_windows[0].win,
+                                   winmethod_Above | winmethod_Fixed,
+                                   1, /* XXX huh? upper_height, */
+                                   wintype_TextGrid, 1);
+
+  if(upper_win->win == 0) {
+    upper_win->str = 0;
+    return;
+  }
+
+  upper_win->str = glk_window_get_stream(upper_win->win);
+
+  if(upper_win->str == 0) {
+    glk_window_close(upper_win->win, NULL);
+    upper_win->win = 0;
+    return;
+  }
+}
+
+
+void z_init_windows(BOOL dofixed,
+                   glui32 (*draw_callback)(winid_t, glui32, glui32),
+                   BOOL (*mouse_callback)(BOOL, winid_t, glui32, glui32),
+                   glui32 maxwidth, glui32 maxheight,
+                   zwinid *upper, zwinid *lower)
+{
+  colorstyle defaultstyle;
+  defaultstyle.fore = 1; defaultstyle.back = 1; defaultstyle.style = 0;
+
+  is_fixed = dofixed;
+
+  kill_windows();
+
+  upper_width = maxwidth; upper_height = maxheight;
+
+  game_windows[0].current = game_windows[1].current = defaultstyle;
+  game_windows[1].draw_callback  = draw_callback;
+  game_windows[1].mouse_callback = mouse_callback;
+
+  init_lower(lower);
+  init_upper(upper);
+}
+
+
+zwinid z_split_screen(glui32 wintype, glui32 method,
+                     glui32 (*draw_callback)(winid_t, glui32, glui32),
+                     BOOL (*mouse_callback)(BOOL, winid_t, glui32, glui32))
+{
+  int i;
+  for(i = 0; i < num_z_windows; i++) {
+    if(!game_windows[i].win) {
+      winid_t root = glk_window_get_root();
+      game_windows[i].win = glk_window_open(root, method, 0, wintype, 0);
+      game_windows[i].str = glk_window_get_stream(game_windows[i].win);
+      game_windows[i].wintype = wintype;
+      game_windows[i].method = method;
+      game_windows[i].transcript = NULL;
+      game_windows[i].glk_input_pending = FALSE;
+      game_windows[i].draw_callback = draw_callback;
+      game_windows[i].mouse_callback = mouse_callback;
+      game_windows[i].width = 0;
+      game_windows[i].height = 0;
+      game_windows[i].curr_offset = 0;
+      game_windows[i].max_offset = 1;
+      game_windows[i].buffer_size = 2;
+      game_windows[i].text_buffer = n_malloc(2);
+      game_windows[i].color_buffer = n_malloc(2);
+      game_windows[i].dirty = TRUE;
+      return &game_windows[i];
+    }
+  }
+  return NULL;
+}
+
+
+void z_kill_window(zwinid win)
+{
+  if(!win)
+    return;
+  n_free(win->text_buffer);
+  win->text_buffer = NULL;
+  n_free(win->color_buffer);
+  win->color_buffer = NULL;
+  win->transcript = NULL;
+  glk_window_close(win->win, NULL);
+  win->win = NULL;
+  win->str = NULL;
+}
+
+
+/* close any open windows */
+void kill_windows(void)
+{
+  int i;
+
+  for(i = 0; i < num_z_windows; i++)
+    z_clear_window(&game_windows[i]);
+
+  free_windows();
+  for(i = 0; i < num_z_windows; i++) {
+    if(game_windows[i].win) {
+      game_windows[i].transcript = NULL;
+
+      glk_window_close(game_windows[i].win, NULL);
+      game_windows[i].win = NULL;
+      game_windows[i].str = NULL;
+    }
+  }
+  showstuffcount = 0;
+}
+
+
+/* free memory space used by windows, but don't close them */
+void free_windows(void)
+{
+  int i;
+
+  z_flush_all_windows();
+
+  for(i = 0; i < num_z_windows; i++) {
+    if(game_windows[i].win) {
+      n_free(game_windows[i].text_buffer);
+      game_windows[i].text_buffer = NULL;
+
+      n_free(game_windows[i].color_buffer);
+      game_windows[i].color_buffer = NULL;
+    }
+  }
+}
+
+zwinid z_find_win(winid_t win)
+{
+  int i;
+  for(i = 0; i < num_z_windows; i++) {
+    if(game_windows[i].win == win)
+      return &game_windows[i];
+  }
+  return NULL;
+}
+
+
+static BOOL coloreq(colorstyle a, colorstyle b) /* return true if colors are equivalent */
+{
+  return (fgcolortable[(int) a.fore] == fgcolortable[(int) b.fore]) &&
+         (bgcolortable[(int) a.back] == bgcolortable[(int) b.back]);
+}
+
+
+static void checkforblockquote(zwinid window, zwinid dest_win)
+{
+  if(window->biggest_height > window->last_height &&
+     window->biggest_height > window->height) {
+    /* find borders of the blockquote */
+    unsigned leftx = window->width, rightx = 0;
+    unsigned topy = window->biggest_height;
+    unsigned bottomy = window->height;
+    unsigned x, y, i;
+
+    i = window->height * window->width;
+    for(y = window->height; y < window->biggest_height; y++)
+      for(x = 0; x < window->width; x++)
+       if(window->text_buffer[i++] != ' ') {
+         if(x < leftx)
+           leftx = x;
+         if(x > rightx)
+           rightx = x;
+         if(y < topy)
+           topy = y;
+         if(y > bottomy)
+           bottomy = y;
+       }
+    
+    z_pause_timed_input(dest_win);
+    glk_stream_set_current(game_windows[1].str);
+    glk_put_char(10);
+    glk_set_style(style_BlockQuote);
+    
+    /* draw the blockquote */
+    for(y = topy; y <= bottomy; y++) {
+      i = y * window->width + leftx;
+      for(x = leftx; x <= rightx; x++)
+       glk_put_char(window->text_buffer[i++]);
+      glk_put_char(10);
+    }
+  }
+}
+
+
+void z_pause_timed_input(zwinid window)
+{
+  event_t eep;
+  if(window->glk_input_pending) {
+    window->glk_input_pending = FALSE;
+
+    switch(window->pending_input_type) {
+    case evtype_CharInput:
+      glk_cancel_char_event(window->win);
+      break;
+    case evtype_LineInput:
+      glk_cancel_line_event(window->win, &eep);
+      window->pending_input_length = eep.val1;
+    }
+  }
+}
+
+
+void z_flush_all_windows(void)
+{
+  int window;
+  for(window = 0; window < num_z_windows; window++) {
+    if(game_windows[window].dirty) {
+      z_pause_timed_input(&game_windows[window]);
+      
+      switch(game_windows[window].wintype) {
+      case wintype_TextBuffer:
+       z_flush_text(&game_windows[window]);
+       break;
+      case wintype_TextGrid:
+       z_flush_fixed(&game_windows[window]);
+       break;
+      case wintype_Graphics:
+       z_flush_graphics(&game_windows[window]);
+       break;
+      }
+    }
+  }
+}
+
+void z_draw_all_windows(void)
+{
+  int window;
+  for(window = 0; window < num_z_windows; window++) {
+    if(game_windows[window].wintype == wintype_TextGrid) {
+      game_windows[window].dirty = TRUE;
+      z_flush_fixed(&game_windows[window]);
+    }
+  }
+}
+
+
+static void z_put_styled_string(zwinid window, unsigned char *text,
+                               colorstyle *color, glui32 length)
+{
+  glui32 n;
+  colorstyle laststyle = color[0];
+
+  if(!length)
+    return;
+
+  glk_set_style_stream(window->str, bitmap_to_style[laststyle.style]);
+
+  for(n = 0; n < length; n++) {
+    if(color[n].style != laststyle.style)
+      glk_set_style_stream(window->str, bitmap_to_style[color[n].style]);
+    glk_put_char_stream(window->str, text[n]);
+    laststyle = color[n];
+  }
+}
+
+void z_flush_fixed(zwinid window)
+{
+  glui32 winx, winy;
+  winid_t o;
+  glui32 start_line, end_line;
+
+  /* If there's no such window, give up */
+  if(!window->win || !window->str ||
+     !window->text_buffer || !window->color_buffer)
+    return;
+
+  /* glk doesn't allow writing to a window while input is pending */
+  z_pause_timed_input(window);
+
+  end_line = window->height;
+
+  /* Has the window grown and shrunk?  If so, probably because someone wants
+     to draw a box quote - don't let them shrink the window quite so fast */
+  if(window->biggest_height > window->last_height &&
+     window->biggest_height > window->height)
+    end_line = window->biggest_height;
+
+  /* For v3 games, there's a callback function to draw the room name and
+     score; if this is present, we start drawing at a lower position */
+  start_line = 0;
+  if(window->draw_callback)
+    start_line = window->draw_callback(NULL, 0, 0);
+  end_line += start_line;
+
+  o = glk_window_get_parent(window->win);
+#if 0
+  glk_window_get_size(window->win, &winx, &winy);
+  if (!(window->method & winmethod_Above || window->method & winmethod_Below)
+      || winy != end_line)
+    glk_window_set_arrangement(o, window->method,
+                              end_line, window->win);
+#else
+  glk_window_set_arrangement(o, window->method, end_line, window->win);
+#endif
+  glk_window_get_size(window->win, &winx, &winy);
+
+  if(window->draw_callback) {
+    glk_stream_set_current(window->str);
+    glk_window_clear(window->win);
+    glk_window_move_cursor(window->win, 0, 0);
+    window->draw_callback(window->win, winx, winy);
+  }
+
+  if(end_line > start_line && window->dirty) {
+
+    unsigned padleft = 0, padmiddle = 0, padright = 0;
+    unsigned skipleft = 0, skipmiddle = 0, skipright = 0;
+    unsigned width;
+    unsigned firstwidth, lastwidth;
+
+    unsigned x, y, i;
+
+    /* Calculate how much space is used for margins */
+
+    unsigned left_margin = window->width, right_margin = window->width;
+
+    i = 0;
+    for(y = start_line; y < end_line; y++) {
+      
+      for(x = 0; x < window->width; x++)
+       if(window->text_buffer[i + x] != ' ') {
+         if(x < left_margin)
+           left_margin = x;
+         break;
+       }
+      
+      for(x = 0; x < window->width; x++)
+       if(window->text_buffer[i + window->width - x - 1] != ' ') {
+         if(x < right_margin)
+           right_margin = x;
+         break;
+       }
+      
+      i += window->width;
+    }
+    
+    firstwidth = window->width; lastwidth = 0;
+    
+    if(start_line + 1 == end_line) {
+      unsigned longestx = 0;
+      unsigned longestlen = 0;
+      unsigned thisx = 0;
+      unsigned thislen = 0;
+      colorstyle lastcolor;
+      width = window->width;
+      
+      for(x = skipleft; x < width + skipleft; x++) {
+       if(window->text_buffer[x] == ' '
+          && (!thislen || coloreq(window->color_buffer[x], lastcolor))) {
+         if(!thislen)
+           thisx = x;
+         thislen++;
+         lastcolor = window->color_buffer[x];
+       } else {
+         if(thislen > longestlen) {
+           longestx = thisx;
+           longestlen = thislen;
+         }
+         thislen = 0;
+       }
+      }
+      if(longestlen > 4) {
+       firstwidth = longestx - skipleft;
+       skipmiddle = longestlen - 1;
+       lastwidth = width - firstwidth - skipmiddle;
+      }
+    }
+    
+    if(skipmiddle && winx < firstwidth + 2 + lastwidth)
+      padmiddle = 2;
+    
+    if(lastwidth && winx >= firstwidth + padmiddle + lastwidth) {
+      padmiddle = winx - firstwidth - lastwidth;
+    } else {
+      if(winx >= window->width)
+       width = window->width;
+      else
+       width = winx;
+      
+      if(right_margin + left_margin) {
+       if(winx > window->width)
+         padleft = (unsigned) ((winx - window->width) * (((float) left_margin) / (right_margin + left_margin)));
+       else
+         skipleft = (unsigned) ((window->width - winx) * (((float) left_margin) / (right_margin + left_margin)));
+      }
+      else {
+       padleft = winx - window->width;
+      }
+      
+      if(skipleft > left_margin)
+       skipleft = left_margin;
+      
+      if(winx > window->width)
+       padright = winx - window->width - padleft;
+      else
+       skipright = window->width - winx - skipleft;
+      
+      if(width < firstwidth + padmiddle) {
+       firstwidth = width;
+       padmiddle = 0;
+       lastwidth = 0;
+      } else if(width < firstwidth + padmiddle + lastwidth) {
+       lastwidth = width - firstwidth - padmiddle;
+      }
+    }
+
+    
+    glk_stream_set_current(window->str);
+    glk_window_move_cursor(window->win, 0, start_line);
+
+    /* draw to the upper window */
+    i = 0;
+    for(y = start_line; y < end_line; y++) {
+
+      for(x = 0; x < padleft; x++)
+       glk_put_char(' ');
+
+      i += skipleft;
+
+      z_put_styled_string(window, window->text_buffer + i,
+                         window->color_buffer + i, firstwidth);
+      i += firstwidth;
+
+      for(x = 0; x < padmiddle; x++)
+       glk_put_char(' ');
+      i += skipmiddle;
+      
+      z_put_styled_string(window, window->text_buffer + i,
+                         window->color_buffer + i, lastwidth);
+      i += lastwidth;
+
+      for(x = 0; x < padright; x++)
+       glk_put_char(' ');
+
+      i += skipright;
+    }
+
+    /* Bureaucracy needs the cursor positioned and visible in upper window. */
+    glk_window_move_cursor(window->win,
+                          window->curr_offset % window->width,
+                          window->curr_offset / window->width);
+    window->dirty = FALSE;
+  }
+}
+
+
+void z_flush_text(zwinid window)
+{
+  z_pause_timed_input(window);
+
+  if(!window->win || !window->str
+     || !window->text_buffer || !window->color_buffer
+     || window->curr_offset == 0) {
+    window->curr_offset = 0;
+    return;
+  }
+  
+  z_put_styled_string(window, window->text_buffer, window->color_buffer,
+                     window->curr_offset);
+
+  window->curr_offset = 0;
+  window->dirty = FALSE;
+}
+
+
+void z_flush_graphics(zwinid window)
+{
+  int i;
+  glui32 winx, winy;
+  float xratio, yratio;
+  winid_t parent;
+
+  if(!window->win)
+    return;
+
+  glk_window_get_size(window->win, &winx, &winy);
+  xratio = ((float) winx) / window->width;
+  yratio = ((float) winy) / window->height;
+
+  parent = glk_window_get_parent(window->win);
+
+  /* We want the window to maintain its original height/width ratio */
+  switch(window->method & winmethod_DirMask) {
+  case winmethod_Left:   /* Left and right splits mean height is fixed - */
+  case winmethod_Right:  /* adjust width to the yratio */
+    glk_window_set_arrangement(parent, window->method,
+                              (glui32) (window->width * yratio), 0);
+    break;
+  case winmethod_Above:  /* Above and below splits mean width is fixed - */
+  case winmethod_Below:  /* adjust height to the xratio */
+    glk_window_set_arrangement(parent, window->method,
+                              (glui32) (window->height * xratio), 0);
+    break;
+  }
+
+  /* Check to see what it became, and if it's still off, don't worry */
+  glk_window_get_size(window->win, &winx, &winy);
+  xratio = ((float) winx) / window->width;
+  yratio = ((float) winy) / window->height;
+
+  for(i = 0; i < 12; i++) {
+    if(window->images[i].image_num) {
+      wrap_glk_image_draw_scaled(window->win, window->images[i].image_num,
+                                (glui32) (window->images[i].x * xratio),
+                                (glui32) (window->images[i].y * yratio),
+                                (glui32) (window->images[i].width * xratio),
+                                (glui32) (window->images[i].height * yratio));
+    }
+  }
+}
+
+void z_print_number(zwinid window, int number)
+{
+  int i;
+  char buffer[12];
+  int length = n_to_decimal(buffer, number);
+
+  for(i = length - 1; i >= 0; i--)
+    z_put_char(window, buffer[i]);
+}
+
+void z_put_char(zwinid window, unsigned c)
+{
+  colorstyle color = window->current;
+  if(is_fixed)
+    color.style |= sFIXE;
+
+  if(c == 0)     /* Section 3.8.2.1 */
+    return;
+
+  window->dirty = TRUE;
+
+  if((c < 32 && c != 13) || (c >= 127 && c <= 159)) {  /*Undefined in latin-1*/
+    switch(window->wintype) {
+    case wintype_TextBuffer:
+      z_put_char(window, '[');
+      z_print_number(window, c);
+      z_put_char(window, ']');
+      return;
+    case wintype_TextGrid:
+      c = '?';
+    }
+  }
+
+  switch(c) {
+  case 0x152:
+    z_put_char(window, 'O');
+    c = 'E';
+    break;
+  case 0x153:
+    z_put_char(window, 'o');
+    c = 'e';
+    break;
+  }
+
+
+  if(c > 255)    /* Section 3.8.5.4.3 */
+    c = '?';
+
+  if(c == 13) {  /* Section 7.1.2.2.1 */
+    switch(window->wintype) {
+    case wintype_TextBuffer:
+      window->text_buffer[window->curr_offset] = 10;
+      window->curr_offset++;
+      z_flush_text(window);
+      break;
+    case wintype_TextGrid:
+      window->curr_offset += window->width;
+      window->curr_offset -= window->curr_offset % window->width;
+    }
+  } else {
+    window->text_buffer[window->curr_offset] = c;
+    window->color_buffer[window->curr_offset] = color;
+    window->curr_offset++;
+  }
+
+  if(window->curr_offset >= window->max_offset) {
+    switch(window->wintype) {
+    case wintype_TextBuffer:
+      z_flush_text(window);
+      break;
+    case wintype_TextGrid:
+      if(!window->defined)                  /* Section 8.6.2 */
+       n_show_port(E_OUTPUT, "writing past end of window", c);
+      
+      if(window->max_offset)
+       window->curr_offset = window->max_offset - 1;
+      else
+       window->curr_offset = 0;
+
+      window->defined = FALSE;
+    }
+  }
+}
+
+void z_setxy(zwinid window, zword x, zword y)
+{
+  window->curr_offset = (y - 1) * window->width + (x - 1);
+  window->defined = TRUE;
+}
+
+void z_getxy(zwinid window, zword *x, zword *y)
+{
+  if(window->width) {
+    *x = (window->curr_offset % window->width) + 1;
+    *y = (window->curr_offset / window->width) + 1;
+  } else {
+    *x = window->curr_offset + 1;
+    *y = 1;
+  }
+}
+
+void z_getsize(zwinid window, unsigned *width, unsigned *height)
+{
+  *width = window->width;
+  *height = window->height;
+}
+
+void z_find_size(glui32 *wid, glui32 *hei)
+{
+  glui32 oldwid, oldhei;
+  zwinid upper = &game_windows[1];
+  winid_t o = glk_window_get_parent(upper->win);
+  glk_window_get_size(upper->win, &oldwid, &oldhei);
+  glk_window_set_arrangement(o, (upper->method & ~winmethod_Fixed) |
+                            winmethod_Proportional, 100, upper->win);
+  glk_window_get_size(upper->win, wid, hei);
+  glk_window_set_arrangement(o, upper->method, oldhei, upper->win);
+
+  upper_width = *wid; upper_height = *hei;
+  init_upper(&upper);
+}
+
+void z_set_height(zwinid window, unsigned height)
+{
+  unsigned x;
+  if(height * window->width > window->buffer_size) {
+    n_show_error(E_OUTPUT, "height too large", height);
+    return;
+  }
+
+  window->height = height;
+  if(height > window->biggest_height)
+    window->biggest_height = height;
+
+  x = window->max_offset;
+  window->max_offset = height * window->width;
+
+  for(; x < window->max_offset; x++) {
+    window->text_buffer[x] = ' ';
+    window->color_buffer[x] = window->current;
+  }
+
+  window->dirty = TRUE;
+}
+
+void z_set_color(zwinid window, unsigned fore, unsigned back)
+{
+  if(fore >= sizeof(fgcolortable) / sizeof(*fgcolortable)) {
+    n_show_error(E_OUTPUT, "illegal foreground color", fore);
+    return;
+  }
+  if(back >= sizeof(bgcolortable) / sizeof(*bgcolortable)) {
+    n_show_error(E_OUTPUT, "illegal background color", back);
+    return;
+  }
+
+  fgcolortable[0] = fgcolortable[fore];
+  bgcolortable[0] = bgcolortable[back];
+  
+  window->current.fore = fore;
+  window->current.back = back;
+}
+
+void z_set_style(zwinid window, int style)
+{
+  switch(style) {
+  case 0: window->current.style = 0; break;
+  case 1: window->current.style |= sREVE; break;
+  case 2: window->current.style |= sBOLD; break;
+  case 4: window->current.style |= sITAL; break;
+  case 8: window->current.style |= sFIXE; break;
+  default: n_show_error(E_OUTPUT, "undefined style", style);
+  }
+}
+
+void set_fixed(BOOL p)
+{
+  if(!p) {
+    is_fixed = FALSE;
+  } else {
+    is_fixed = TRUE;
+  }
+}
+
+void z_set_transcript(zwinid window, strid_t stream)
+{
+  window->transcript = stream;
+  glk_window_set_echo_stream(window->win, stream);
+}
+
+void z_clear_window(zwinid window)
+{
+  glui32 i;
+
+  if(window == &game_windows[0] && showstuffcount) {
+    z_pause_timed_input(&game_windows[0]);
+    z_flush_text(&game_windows[0]);
+    glk_stream_set_current(game_windows[0].str);
+    w_glk_put_string("[pausing to show unread error message]\n");
+    z_wait_for_key(&game_windows[0]);
+  }
+
+  window->dirty = TRUE;
+  window->curr_offset = 0;
+
+  if(window->win && window->text_buffer && window->color_buffer) {
+    switch(window->wintype) {
+    case wintype_TextGrid:
+      for(i = 0; i < window->max_offset; i++) {
+       window->text_buffer[i] = ' ';
+       window->color_buffer[i] = window->current;
+      }
+      window->curr_offset = 0;
+      window->dirty = TRUE;
+      break;
+    case wintype_TextBuffer:
+      z_pause_timed_input(window);
+      z_flush_text(window);
+      if(coloreq(window->actual, window->current)) {
+       glk_window_clear(window->win);
+      } else {
+       init_lower(NULL); /* **FIXME** This is wrong, but deal with it later */
+      }
+    }
+  }
+}
+
+void z_erase_line(zwinid window)
+{
+  if(window->wintype == wintype_TextGrid) {
+    int i;
+    int x = window->curr_offset % window->width;
+    int endoffset = window->curr_offset + (window->width - x);
+    
+    window->dirty = TRUE;
+    for(i = window->curr_offset; i < endoffset; i++) {
+      window->text_buffer[i] = ' ';
+      window->color_buffer[i] = window->current;
+    }
+  }
+}
+
+
+/* Waits for input or timeout
+ * Returns:
+ *   0 - output during wait; may need to redraw or somesuch
+ *  -1 - callback routine said to stop
+ *  10 - read input
+ * 254 - mouse input
+ * char and line events will be canceled by the time it exits
+ */
+static int waitforinput(zwinid window, glui32 *val,
+                       BOOL (*timer_callback)(zword), zword timer_arg)
+{
+  int i;
+  event_t moo;
+  zwinid t;
+  
+  showstuffcount = 0;
+
+  for(i = 0; i < num_z_windows; i++)
+    if(game_windows[i].mouse_callback && game_windows[i].win)
+      glk_request_mouse_event(game_windows[i].win);
+  
+  window->glk_input_pending = TRUE;
+
+  while(window->glk_input_pending) {
+    glk_select(&moo);
+
+    check_sound(moo);
+
+    switch(moo.type) {
+    case evtype_Timer:
+      if(timer_callback && timer_callback(timer_arg)) {
+       if(window->pending_input_type == evtype_CharInput) {
+         glk_cancel_char_event(window->win);
+         *val = 0;
+       } else {
+         glk_cancel_line_event(window->win, &moo);
+         *val = moo.val1;
+       }
+       window->glk_input_pending = FALSE;
+       return -1;
+      }
+      break;
+
+    case evtype_CharInput:
+      *val = moo.val1;
+      window->glk_input_pending = FALSE;
+      return 10;
+
+    case evtype_LineInput:
+      *val = moo.val1;
+      window->glk_input_pending = FALSE;
+      return 10;
+
+    case evtype_MouseInput:
+      t = z_find_win(moo.win);
+      if(t && t->mouse_callback &&
+        t->mouse_callback(window->pending_input_type == evtype_CharInput,
+                          moo.win, moo.val1, moo.val2)) {
+       if(window->pending_input_type == evtype_CharInput) {
+         glk_cancel_char_event(window->win);
+         *val = 254;
+       } else {
+         glk_cancel_line_event(window->win, &moo);
+         *val = moo.val1;
+       }
+       window->glk_input_pending = FALSE;
+       return 254;
+      }
+      glk_request_mouse_event(moo.win);
+      break;
+    
+    case evtype_Arrange:
+      z_draw_all_windows();
+    }
+
+    z_flush_all_windows();
+  }
+
+  if(window->pending_input_type == evtype_LineInput)
+    *val = window->pending_input_length;
+  else
+    *val = 0;
+
+  return 0;
+}
+
+
+void z_wait_for_key(zwinid window)
+{
+  glui32 ch;
+  do {
+    z_draw_all_windows();
+    glk_request_char_event(window->win);
+    window->pending_input_type = evtype_CharInput;
+  } while(waitforinput(window, &ch, NULL, 0) == 0);
+  window->pending_input_type = 0;
+}
+
+
+zwinid check_valid_for_input(zwinid window)
+{  
+  glui32 y, i;
+  if(!window->win) {
+    zwinid newwin = NULL;
+    for(i = 0; i < num_z_windows; i++) {
+      if(game_windows[i].win) {
+       newwin = &game_windows[i];
+       break;
+      }
+    }
+    if(!newwin)
+      return NULL;
+
+    if(window->wintype == wintype_TextGrid) {
+      i = 0;
+      for(y = 0; y < window->height; y++) {
+       z_put_char(newwin, 13);
+       z_put_styled_string(newwin, window->text_buffer + i,
+                           window->color_buffer + i, window->width);
+       i += window->width;
+      }
+      z_put_char(newwin, 13);
+    }
+
+    window = newwin;
+  }
+  return window;
+}
+
+
+/* returns number of characters read */
+int z_read(zwinid window, char *dest, unsigned maxlen, unsigned initlen,
+          zword timer, BOOL (*timer_callback)(zword), zword timer_arg,
+          unsigned char *terminator)
+{
+  /* FIXME: support terminating characters when (if) glk gets support for
+     them */
+  unsigned i;
+  unsigned ux, uy;
+  glui32 length;
+  BOOL done;
+
+  if(automap_unexplore()) {
+    read_abort = TRUE;
+    return 0;
+  }
+  
+  read_abort = FALSE;
+
+  if(initlen > maxlen) {
+    n_show_error(E_OUTPUT, "initlen > maxlen", initlen);
+    return 0;
+  }
+
+  if(window == 0)
+    window = &game_windows[0];
+  
+  if(window->pending_input_type != 0) {
+    n_show_error(E_OUTPUT, "nested input attempted", 0);
+    return 0;
+  }
+
+#ifdef DEBUGGING
+
+  if(do_automap) {
+    const char *dir = automap_explore();
+    if(dir) {
+      length = n_strlen(dir);
+      if(length > maxlen)
+       length = maxlen;
+      n_strncpy(dest, dir, length);
+      return length;
+    }
+  }
+#endif
+
+  glk_request_timer_events(timer * 100);  /* if time is zero, does nothing */
+    
+  if(initlen != 0 && window->wintype == wintype_TextBuffer) {
+    BOOL good = FALSE;
+    if(initlen <= window->curr_offset) {
+      good = TRUE;
+      for(i = 0; i < initlen; i++)  /* check the end of the linebuffer */
+       if(window->text_buffer[window->curr_offset - initlen + i] != dest[i]) {
+         good = FALSE;
+         break;
+       }
+    }
+    if(!good) {
+      /* bleah */
+      /* argh */
+      /* oof */
+    } else {
+      window->curr_offset -= initlen; /* Remove initial text from linebuffer */
+    }
+  }
+  
+  if(window->wintype == wintype_TextGrid) {
+    ux = window->curr_offset % window->width;
+    uy = window->curr_offset / window->width;
+  }
+
+  z_flush_all_windows();
+  window = check_valid_for_input(window);
+
+  done = FALSE;
+  length = initlen;
+  while(!done) {
+    int t;
+
+    if(window->wintype == wintype_TextGrid)
+      glk_window_move_cursor(window->win, ux, uy);
+
+    if(input_stream1) {
+      glui32 len = maxlen;
+      *terminator = transcript_getline(dest, &len);
+      length = len;
+    }
+    if(input_stream1) { /* If didn't EOF, input_stream1 will be non-zero */
+      glk_stream_set_current(window->str);
+      set_glk_stream_current();
+      glk_set_style(style_Input);
+      glk_put_buffer(dest, length);
+      glk_put_char(10);
+      done = TRUE;
+    } else {
+      glk_request_line_event(window->win, dest, maxlen, length);
+      window->pending_input_type = evtype_LineInput;
+    
+      t = waitforinput(window, &length, timer_callback, timer_arg);
+      if(t != 0) {
+       if(t == -1)
+         *terminator = 0;
+       else
+         *terminator = t;
+       done = TRUE;
+      }
+    }
+
+    if(done)
+      stream4line(dest, length, *terminator);
+
+#ifdef DEBUGGING
+    if(done && length >= 2 && dest[0] == '/') {
+      if(dest[1] == '/') {  /* "//" means no command, but start with "/" */
+       for(i = 1; i < length; i++)
+         dest[i-1] = dest[i];
+       length--;
+      } else {
+       done = FALSE;
+       dest[length] = 0;
+       
+       process_debug_command(dest+1);
+
+       if(read_abort)
+         done = TRUE;
+
+       length = 0;
+      }
+    }
+#endif
+  }
+  glk_request_timer_events(0);  /* stop timer */
+
+  window->pending_input_type = 0;
+
+  for(i = 0; i < num_z_windows; i++)
+    game_windows[i].biggest_height = game_windows[i].last_height = game_windows[i].height;
+
+  return length;
+}
+
+zword z_read_char(zwinid window,
+                 zword timer, BOOL (*timer_callback)(zword), zword timer_arg)
+{
+  unsigned i;
+  glui32 ch;
+  zword validch = 0;
+
+  if(automap_unexplore()) {
+    read_abort = TRUE;
+    return 0;
+  }
+
+  if(input_stream1) {
+    unsigned num;
+    validch = transcript_getchar(&num);
+    if(!validch)
+      validch = num;
+  }
+  if(input_stream1) {
+    return validch;
+  }
+  
+  read_abort = FALSE;
+
+  glk_request_timer_events(timer * 100);
+
+  z_flush_all_windows();
+  window = check_valid_for_input(window);
+
+  do {
+    do {
+      z_draw_all_windows();
+      glk_request_char_event(window->win);
+      window->pending_input_type = evtype_CharInput;
+    } while(waitforinput(window, &ch, timer_callback, timer_arg) == 0);
+    
+    if(' ' <= ch && ch <= '~')
+      validch = ch;
+    
+    switch(ch) {
+    case 8:
+    case keycode_Delete: validch = 8; break;
+    case 9:
+    case keycode_Tab:    validch = 9; break;
+    case 13:
+    case keycode_Return: validch = 13; break;
+/*  case 21:
+      if(restoreundo()) {
+       read_abort = TRUE;
+       return 0;
+      }
+      break; */
+    case 27:
+    case keycode_Escape: validch = 27; break;
+    case 16:
+    case keycode_Up:     validch = 129; break;
+    case 14:
+    case keycode_Down:   validch = 130; break;
+    case 2:
+    case keycode_Left:   validch = 131; break;
+    case 6:
+    case keycode_Right:  validch = 132; break;
+    case keycode_Func1:  validch = 133; break;
+    case keycode_Func2:  validch = 134; break;
+    case keycode_Func3:  validch = 135; break;
+    case keycode_Func4:  validch = 136; break;
+    case keycode_Func5:  validch = 137; break;
+    case keycode_Func6:  validch = 138; break;
+    case keycode_Func7:  validch = 139; break;
+    case keycode_Func8:  validch = 140; break;
+    case keycode_Func9:  validch = 141; break;
+    case keycode_Func10: validch = 142; break;
+    case keycode_Func11: validch = 143; break;
+    case keycode_Func12: validch = 144; break;
+    }
+  } while(!(validch || ch == 0));
+
+  glk_request_timer_events(0);     /* stop timer */
+
+  window->pending_input_type = 0;
+
+  for(i = 0; i < num_z_windows; i++)
+    game_windows[i].biggest_height = game_windows[i].last_height = game_windows[i].height;
+
+  return validch;
+}
+
+
+/*
+void zwin_init(int number, glui32 wintype,
+              glui32 x_coord, glui32 y_coord, glui32 x_size, glui32 y_size)
+{
+  zwinid self = game_windows + number;
+
+  if(x_coord == self->x1) {
+    if(y_coord == self->y1) {
+      
+
+  if(game_windows[number].win) {
+    z_pause_timed_input(game_windows[number].win);
+    glk_window_close(game_windows[number].win, NULL);
+  }
+  set_style_hints();
+  game_windows[number].win = glk_window_open(
+}
+*/