/*
 * os2iob.c
 *
 * specific thread-1 screen I/O routines for OS/2.
 *
 */

/* Created by John W. Kennedy, May, 1994 */
/* Based on pre-existing mscio.c */
/* Functional improvements over mscio.c: */
/*   Keyboard reading is done in a separate thread, with a timed-out wait,*/
/*     so that the CPU is not uselessly devoured */
/*   Accomodation is made for screens in color, black-and-white, and */
/*     monochrome modes.  The modes are by discovery, where possible */
/*   Option -b forces black-and-white mode; when determination is impossible, */
/*     either in a window or on a display that doesn't feed back color, */
/*     and -b is not specified, then the question is asked outright */
/*   Screen size is handled by discovery */

/* Second release additions, June, 1994: */
/*   Screen information is correctly fed back to Z machine */
/*     In V4+, Config byte 0x04 is always set */
/*       (per Graham Nelson, it is "Boldface available") */
/*     CONFIG_EMPHASIS is set for PALETTE_MONOCHROME only */
/*       (per Graham Nelson, it is "Underlining available") */
/*   ^D launches a window displaying, in chronological order, all files */
/*     in the current directory containing exactly the right number of */
/*     bytes to be a save file in the current game */
/*   Bug corrected that made Border Zone loop on input */
/*   Options -o for color and -m for monochrome added */
/*   Options -o, -m, and -b invoke MODE command */

/* Third release additions, December, 1995 */
/*   Code forced to test __STDC__ throughout, because VisualAge C++ */
/*     only uses __STDC__ with -Sa */

/* Fourth release, July, 1997 */
/*   Version-8 support */
/*   __STDC__ removed from the two OS/2 modules and -Sa used on the rest */
/*   Corrected error in keyboard timeout */

#include "ztypes.h"

#define INCL_NOCOMMON
#define INCL_DOSPROCESS
#define INCL_DOSSEMAPHORES
#define INCL_DOSSESMGR
#define INCL_VIO
#define INCL_KBD
#define INCL_DOSERRORS
#define INCL_NOPMAPI
#include <os2.h>

#include <stddef.h>
#include <stdlib.h>
#include <string.h>

/* Communications with the keyboard thread */
HEV                     hevKbdReq, hevKbdRsp;
volatile int            fKbdOpen = FALSE;
volatile int            iKbdChar;
static TID              tidKbd = 0;
static int              fKbdRequested = FALSE;

#define                 BLACK       0
#define                 BLUE        1
#define                 UNDERSCORE  1
#define                 GREEN       2
#define                 CYAN        3
#define                 RED         4
#define                 MAGENTA     5
#define                 BROWN       6
#define                 WHITE       7

#define                 BRIGHT      0x08
#define                 FLASH       0x10

#define                 PALETTE_NIL             -1
#define                 PALETTE_COLOUR          0
#define                 PALETTE_BLACK_AND_WHITE 1
#define                 PALETTE_MONOCHROME      2
int                     iPalette = PALETTE_NIL;

static int              screen_started = FALSE;
static int              cursor_saved = OFF;
static int              saved_row = 0;
static int              saved_col = 0;
static int              current_fg = WHITE;
static int              current_bg = BLACK;

/* These items are maintained here instead of by the system */
static char             achBlankAttribute[2] = {' ', (BLACK << 4) | WHITE};
static int              current_row, current_col;


void                    os2_read_key_thread (
   void                    *p);
int                     os2_read_key_byte (void);
int                     os2_read_key_byte_timed (
   int                     timeout);
int                     convert_key (
   int                     c);


void                    initialize_screen (void) {

   VIOMODEINFO             vmi;
   VIOSETULINELOC          vsull;
   KBDINFO                 ki;
   APIRET                  rc;

   if (iPalette != PALETTE_NIL) {
      static char /* const */ * const apszMode [3] =
                                                  {"Mode\0CO80\0",
                                                   "Mode\0BW80\0",
                                                   "Mode\0Mono\0"};
      RESULTCODES          rc;

      DosExecPgm (NULL, 0, EXEC_SYNC, apszMode [iPalette], NULL, &rc, "MODE.COM");
   } /* endif */

   /* Get the underline location; if < 31, use monochrome palette */
   vsull.cb = sizeof vsull; vsull.type = 5;
   rc = VioGetState (&vsull, NULLHANDLE);
   if (!rc && vsull.scanline < 31) {
      iPalette = PALETTE_MONOCHROME;
   } else {
      /* Otherwise, get color information */
      /* If the 16 main colors have R==G==B, use black-and-white palette */
      /* If either VioGetState call fails, we still don't know */
      PVIOPALSTATE            pvps = _alloca (38);
      pvps->cb = 38; pvps->type = 0; pvps->iFirst = 0;
      if (!VioGetState (pvps, NULLHANDLE)) {
         VIOCOLORREG             vcr;
         int                     iLimit;
         iLimit = pvps->iFirst + (pvps->cb - sizeof (VIOPALSTATE)) / sizeof (USHORT) + 1;
         vcr.cb = sizeof (VIOCOLORREG); vcr.type = 3;
         vcr.firstcolorreg = 0; vcr.numcolorregs = 256;
         vcr.colorregaddr = _alloca (3 * 256);
         if (!VioGetState (&vcr, NULLHANDLE)) {
            int                     i;
            for (i = pvps->iFirst; i < iLimit; ++i) {
               typedef struct {
                  UCHAR                   uchRed, uchGreen, uchBlue;
               }                       COLOURS;
               COLOURS                  *vcrx = &(((COLOURS *)vcr.colorregaddr)[pvps->acolor[i-pvps->iFirst]]);
               if (vcrx->uchRed != vcrx->uchGreen || vcrx->uchGreen != vcrx->uchBlue) {
                  break;
               } /* endif */
            } /* endfor */
            if (i >= iLimit) {
               iPalette = PALETTE_BLACK_AND_WHITE;
            } /* endif */
         } /* endif */
      } /* endif */
   } /* endif */
   /* If we are still undetermined, it is possible that we are in a */
   /*   window or that we have a dumb CRT; either way, we could still be */
   /*   on black and white hardware, so give up and ask the user */
   if (iPalette == PALETTE_NIL) {
      char                    ch;
      do {
         static /* const */ char sz[] = "Do you want color? ";
         KBDKEYINFO              kki;
         VioScrollUp (0, 0, 0xFFFF, 0xFFFF, 0xFFFF, achBlankAttribute, NULLHANDLE);
         VioWrtCharStrAtt (sz, sizeof sz - 1, 0, 0,
                           &achBlankAttribute[1], NULLHANDLE);
         VioSetCurPos (0, sizeof sz - 1, NULLHANDLE);
         KbdCharIn (&kki, IO_WAIT, NULLHANDLE);
         ch = kki.chChar;
      } while (ch != 'y' && ch != 'Y' && ch != 'n' && ch != 'N'); /* enddo */
      if (ch == 'n' || ch == 'N') {
         iPalette = PALETTE_BLACK_AND_WHITE;
      } else {
         iPalette = PALETTE_COLOUR;
      } /* endif */
   } /* endif */

   /* If we're still in color after all that, set up for color */
   if (iPalette == PALETTE_COLOUR) {
      current_bg = BLUE; achBlankAttribute[1] = (BLUE << 4) | WHITE;
   } /* endif */

   /* Now get the existing rows and columns */
   vmi.cb = sizeof vmi;
   VioGetMode (&vmi, NULLHANDLE);
   if (screen_rows == 0) {
      screen_rows = vmi.row;
   } /* endif */
   if (screen_cols == 0) {
      screen_cols = vmi.col;
   } /* endif */

   /* Now set up the user's desired rows and columns */
   /* If it fails, go to the default */
   vmi.cb = sizeof vmi.cb + sizeof vmi.fbType + sizeof vmi.color +
            sizeof vmi.col + sizeof vmi.row;
   vmi.col = screen_cols; vmi.row = screen_rows;
   if (VioSetMode (&vmi, NULLHANDLE)) {
      vmi.row = screen_rows = 25;
      vmi.col = screen_cols = DEFAULT_COLS;
      VioSetMode (&vmi, NULLHANDLE);
   } /* endif */

   move_cursor (1, 1);
   set_attribute (NORMAL);
   clear_screen ();

   {

      static /* const */ char sz [] = "The story is loading...";
      static const char       cb = sizeof sz - 1;
      move_cursor (screen_rows / 2, (screen_cols - cb) / 2);

      VioWrtCharStrAtt (sz, cb, current_row - 1, current_col - 1,
                        &achBlankAttribute[1], NULLHANDLE);
      current_col += cb;
      move_cursor (current_row, current_col);

   }

   ki.cb = sizeof ki;
   KbdGetStatus (&ki, NULLHANDLE);
   ki.fsMask = KEYBOARD_ECHO_OFF | KEYBOARD_BINARY_MODE;
   KbdSetStatus (&ki, NULLHANDLE);

   /* Set up a semaphore for this thread to unblock the other, */
   /*   and one for the other to respond.  Then start the thread */
   DosCreateEventSem (NULL, &hevKbdReq, 0, FALSE);
   DosCreateEventSem (NULL, &hevKbdRsp, 0, FALSE);
   tidKbd = _beginthread (&os2_read_key_thread, NULL, 12288, NULL);
   fKbdOpen = TRUE;

   screen_started = TRUE;

   h_interpreter = INTERP_MSDOS;

}/* initialize_screen */

void                    restart_screen (void) {

   cursor_saved = OFF;

   set_byte (H_CONFIG, (get_byte (H_CONFIG) | CONFIG_WINDOWS));

   if (h_type > V3) {
      set_byte (H_CONFIG, (get_byte (H_CONFIG) | 0x04 |
                           (iPalette == PALETTE_MONOCHROME ? CONFIG_EMPHASIS : 0) |
                           CONFIG_COLOUR));
   } /* endif */
   set_word (H_FLAGS, get_word (H_FLAGS) & (~GRAPHICS_FLAG));

}/* restart_screen */


void                    reset_screen (void) {

   if (screen_started == TRUE) {
      output_new_line ();
      output_string ("[Hit any key to exit.]");
      (void) input_character (0);
      output_new_line ();

      current_bg = BLACK; current_fg = WHITE;
      achBlankAttribute[1] = (BLACK << 4) | WHITE;
      clear_screen ();
      /* We know that the last input-character was not timed, so set */
      /*   fKbdOpen to false and unblock the other thread, which should end */
      fKbdOpen = FALSE;
      DosPostEventSem (hevKbdReq);
      if (tidKbd) {
         DosWaitThread (&tidKbd, DCWW_WAIT);
      } /* endif */
   } /* endif */

   screen_started = FALSE;

}/* reset_screen */


void                    clear_screen (void) {

   VioScrollUp (0, 0, 0xFFFF, 0xFFFF, 0xFFFF, achBlankAttribute, NULLHANDLE);

}/* clear_screen */


void                    create_status_window (void) {

}/* create_status_window */


void                    delete_status_window (void) {

}/* delete_status_window */


void                    select_status_window (void) {

   VIOCURSORINFO           vci;

   save_cursor_position ();
   VioGetCurType (&vci, NULLHANDLE);
   vci.attr = -1;
   VioSetCurType (&vci, NULLHANDLE);

}/* select_status_window */


void                    select_text_window (void) {

   VIOCURSORINFO           vci;

   VioGetCurType (&vci, NULLHANDLE);
   vci.attr = 0;
   VioSetCurType (&vci, NULLHANDLE);
   restore_cursor_position ();

}/* select_text_window */


void                    clear_line (void) {

   VioScrollRt (current_row - 1, current_col - 1, current_row - 1, screen_cols - 1,
                0xFFFF, achBlankAttribute, NULLHANDLE);

}/* clear_line */


void                    clear_text_window (void) {

   VioScrollUp (status_size, 0, screen_rows - 1, screen_cols - 1, 0xFFFF,
                achBlankAttribute, NULLHANDLE);

}/* clear_text_window */


void                    clear_status_window (void) {

   VioScrollUp (0, 0, status_size - 1, screen_cols - 1, 0xFFFF,
                achBlankAttribute, NULLHANDLE);

}/* clear_status_window */


void                    move_cursor (
   int                     row,
   int                     col) {

   current_row = row; current_col = col;
   VioSetCurPos (current_row - 1, current_col - 1, NULLHANDLE);

}/* move_cursor */


void                    get_cursor_position (
   int                     *row,
   int                     *col) {

   *row = current_row;
   *col = current_col;

}/* get_cursor_position */


void                    save_cursor_position (void) {

   if (cursor_saved == OFF) {
      saved_row = current_row; saved_col = current_col;
      cursor_saved = ON;
   } /* endif */

}/* save_cursor_position */


void                    restore_cursor_position (void) {

   if (cursor_saved == ON) {
      move_cursor (saved_row, saved_col);
      cursor_saved = OFF;
   } /* endif */

}/* restore_cursor_position */


void                    set_attribute (
   int                     attribute) {

   int                     fg, bg, new_fg, new_bg;

   fg = achBlankAttribute[1] & 0x0F;
   bg = achBlankAttribute[1] >> 4;

   if (attribute == NORMAL) {
      new_fg = current_fg;
      new_bg = current_bg;
   } /* endif */

   if (attribute & REVERSE) {
      if (iPalette == PALETTE_MONOCHROME) {
         if (fg == UNDERSCORE) {
            fg == 0;
         } /* endif */
      } /* endif */
      new_fg = bg;
      new_bg = fg & WHITE;
   } /* endif */

   if (attribute & BOLD) {
      new_fg = fg | BRIGHT;
      new_bg = bg;
   } /* endif */

   if (attribute & EMPHASIS) {
      switch (iPalette) {
      case PALETTE_COLOUR:
         new_fg = RED | BRIGHT;
         break;
      case PALETTE_BLACK_AND_WHITE:
         new_fg = WHITE | BRIGHT;
         break;
      case PALETTE_MONOCHROME:
         new_fg = UNDERSCORE;
         break;
      } /* endswitch */
      new_bg = bg;
   } /* endif */

   if (attribute & FIXED_FONT) {
      new_fg = fg;
      new_bg = bg;
   } /* endif */

   achBlankAttribute[1] = (new_bg << 4) | new_fg;

}/* set_attribute */


void                    display_char (
   int                     c) {

   char                    ch = c;

   switch (ch) {
   case '\x0A':
      scroll_line ();
      break;
   case '\x07':
      DosBeep (880, 200);
      break;
   default:
      VioWrtCharStrAtt (&ch, 1, current_row - 1, current_col - 1,
                        &achBlankAttribute[1], NULLHANDLE);
      ++current_col;
      break;
   } /* endswitch */

}/* display_char */


static int              timed_read_key (
   int                     timeout) {

   int                     c;

   do {
      c = os2_read_key_byte_timed(timeout);
      if (c != '\0' && c != (unsigned char) '\xE0') {
         if (c == '\x7F') {
             c = '\b';
         } else {
            if (c >= (unsigned char) '\x80') {
                DosBeep (880,200);
                c = '\0';
            } /* endif */
         } /* endif */
      } else {
         c = convert_key (os2_read_key_byte_timed(timeout));
      } /* endif */
   } while (c == 0); /* enddo */
   return c;

}/* timed_read_key */


static int              read_key (void) {

   int                     c;

   do {
      c = os2_read_key_byte();
      if (c != '\0' && c != (unsigned char) '\xE0') {
         if (c == '\x07F') {
            c = '\b';
         } else {
            if (c >= (unsigned char) '\x80') {
                DosBeep (880,200);
                c = '\0';
            } /* endif */
         } /* endif */
      } else {
         c = convert_key (os2_read_key_byte());
      } /* endif */
   } while (c == 0); /* enddo */
   return c;

}/* read_key */


int                     input_line (
   int                     buflen,
   char                    *buffer,
   int                     timeout,
   int                     *read_size) {

   int                     c, row, col;

   for ( ; ; ) {

      /* Read a single keystroke */
      do {
         if (timeout == 0) {
            c = read_key ();
         } else {
            c = timed_read_key (timeout);
            if (c == -1)
               return c;
         } /* endif */
      } while (c == 0); /* enddo */

      switch (c) {

      case '\x04': {

         STARTDATA            sd;
         ULONG                ulSessID;
         PID                  pid;
         char                 *pszPath = getenv ("COMSPEC");
         static char          szTitle[] = "Save files";
         static char          sz1[] = "/C Dir /Od | Find \" ";
         char                 sz2[10];
         int                  cch2;
         static char          sz3[] = " \"| More && Pause && Exit";
         char                 *psz;

         cch2 = sprintf (sz2, "%u", (unsigned) (h_restart_size + sizeof stack));

         psz = _alloca (sizeof sz1 - 1 + cch2 + sizeof sz3);

         strcat (strcat (strcpy (psz, sz1), sz2), sz3);

         sd.Length =      offsetof (STARTDATA, IconFile);
         sd.Related =     SSF_RELATED_CHILD;
         sd.FgBg =        SSF_FGBG_FORE;
         sd.TraceOpt =    SSF_TRACEOPT_NONE;
         sd.PgmTitle =    szTitle;
         sd.PgmName =     pszPath;
         sd.PgmInputs =   psz;
         sd.TermQ =       NULL;
         sd.Environment = NULL;
         sd.InheritOpt =  SSF_INHERTOPT_PARENT;
         sd.SessionType = SSF_TYPE_DEFAULT;
         DosStartSession (&sd, &ulSessID, &pid);

         break;

      }

      case '\b':

         /* Delete key action */

         if (*read_size == 0) {

            /* Ring bell if line is empty */
            DosBeep (880, 200);

         } else {

            char                    ch = ' ';

            /* Decrement read count */
            (*read_size)--; --current_col;

            /* Erase last character typed */
            VioWrtCharStrAtt (&ch, 1, current_row - 1, current_col - 1,
                              &achBlankAttribute[1], NULLHANDLE);
            move_cursor (current_row, current_col);

         } /* endif */

         break;

      default:

         /* Normal key action */

         if (*read_size == (buflen - 1)) {

            /* Ring bell if buffer is full */

            DosBeep (880, 200);

         } else {

            /* Scroll line if return key pressed */

            if (c == '\r' || c == '\n') {
               c = '\n';
               scroll_line ();
            } /* endif */

            if (c == '\n' || c >= (unsigned char) '\x80') {

               /* Return key if it is a line terminator */

               /* Beyond Zork wants different keys for the arrow keys (as used */
               /*   to choose menu options) and for cardinal navigation.  Since */
               /*   arrows applies only when reading single characters, and */
               /*   navigation applies only when reading input lines, we can */
               /*   disambiguate them here */

               switch (c) {
               case '\x81': /* Up arrow */
                  return '\x99'; /* North */
               case '\x82': /* Down arrow */
                  return '\x93'; /* South */
               case '\x83': /* Left arrow */
                  return '\x95'; /* West */
               case '\x84': /* Right arrow */
                  return '\x97'; /* East */
               default:
                  return (unsigned char) c;
               } /* endswitch */


            } else {

               /* Put key in buffer and display it */

               buffer[(*read_size)++] = (char) c;
               display_char (c);

            } /* endif */

         } /* endif */

      } /* endswitch */

   } /* endfor */

}/* input_line */


int                     input_character (
   int                     timeout) {

   int                     c;

   if (timeout == 0) {
      c = read_key ();
   } else {
      c = timed_read_key (timeout);
   } /* endif */

   return c;

}/* input_character */


static int              os2_read_key_byte (void) {

   ULONG                   ulPostCt;

   /* Ensure that the cursor is where it belongs */
   VioSetCurPos (current_row - 1, current_col - 1, NULLHANDLE);

   /* If we have not alredy requested the other thread to read, do it now */
   if (!fKbdRequested) {
      DosPostEventSem (hevKbdReq);
      fKbdRequested = TRUE;
   } /* endif */

   /* Either way, it's requested now; wait for it */
   DosWaitEventSem (hevKbdRsp, SEM_INDEFINITE_WAIT);
   DosResetEventSem (hevKbdRsp, &ulPostCt);

   /* We are clear; reset, and return the key */
   fKbdRequested = FALSE;
   return iKbdChar;

}/* os2_read_key_byte */


static int              os2_read_key_byte_timed (
   int                     timeout) {

   ULONG                   ulPostCt;
   APIRET                  rc;

   /* Ensure that the cursor is where it belongs */
   VioSetCurPos (current_row - 1, current_col - 1, NULLHANDLE);

   /* If we have not alredy requested the other thread to read, do it now */
   if (!fKbdRequested) {
      DosPostEventSem (hevKbdReq);
      fKbdRequested = TRUE;
   } /* endif */

   /* Either way, it's requested now; wait for it, or for a timeout */
   rc = DosWaitEventSem (hevKbdRsp, timeout * 100);
   if (rc == ERROR_TIMEOUT) {
      return -1;
   } else {
      DosResetEventSem (hevKbdRsp, &ulPostCt);
      fKbdRequested = FALSE;
      return iKbdChar;
   } /* endif */

} /* os2_read_key_byte_timed */


static int              convert_key (
   int                     c) {

   /* Translate extended keys to Infocom-determined single bytes above 127 */
   switch (c) {
   case 'H': /* Up arrow */
      c = (unsigned char) '\x81';
      break;
   case 'P': /* Down arrow */
      c = (unsigned char) '\x82';
      break;
   case 'K': /* Left arrow */
      c = (unsigned char) '\x83';
      break;
   case 'M': /* Right arrow */
      c = (unsigned char) '\x84';
      break;
   case ';': /* Function keys F1 to F10 */
   case '<':
   case '=':
   case '>':
   case '?':
   case '@':
   case 'A':
   case 'B':
   case 'C':
   case 'D':
      c = (c - ';') + (unsigned char) '\x85';
      break;
   case 'O': /* End (SW) */
      c = (unsigned char) '\x92';
      break;
   case 'Q': /* PgDn (SE) */
      c = (unsigned char) '\x94';
      break;
   case 'G': /* Home (NW) */
      c = (unsigned char) '\x98';
      break;
   case 'I': /* PgUp (NE) */
      c = (unsigned char) '\x9A';
      break;
   case 'L': /* Unnamed (Walk Around) */
      c = (unsigned char) '\x96';
      break;
   default:
      DosBeep (880, 200);
      c =  0;
   } /* endswitch */
   return c;

}


void                    scroll_line (void) {

   if (current_row == screen_rows) {
      VioScrollUp (status_size, 0, screen_rows - 1, screen_cols - 1, 1,
                   achBlankAttribute, NULLHANDLE);
   } else {
      current_row++;
   } /* endif */
   move_cursor (current_row, 1);

}/* scroll_line */


/*
 * set_colours
 *
 * Sets the screen foreground and background colours.
 *
 */

void                    set_colours (
   int                     foreground,
   int                     background) {

   int                     fg, bg;

   /* Translate from Z-code colour values to natural colour values */

   if (iPalette != PALETTE_COLOUR) {

      int                     colour_map[] = { BLACK, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE };

      fg = (foreground == 1) ? WHITE : colour_map[foreground - 2];
      bg = (fg == BLACK) ? WHITE : BLACK;

   } else {

      int                     colour_map[] = { BLACK, RED, GREEN, BROWN, BLUE, MAGENTA, CYAN, WHITE };

      fg = (foreground == 1) ? WHITE : colour_map[foreground - 2];
      bg = (background == 1) ? BLUE : colour_map[background - 2];

   } /* endif */

   /* Set foreground and background colour */

   achBlankAttribute[1] = (bg << 4) | fg;

   /* Save new foreground and background colours for restoring colour */

   current_fg = (int) fg;
   current_bg = (int) bg;;

}/* set_colours */


/*
 * codes_to_text
 *
 * Translate Z-code characters to machine specific characters. These characters
 * include line drawing characters and international characters.
 *
 * The routine takes one of the Z-code characters from the following table and
 * writes the machine specific text replacement. The target replacement buffer
 * is defined by MAX_TEXT_SIZE in ztypes.h. The replacement text should be in a
 * normal C, zero terminated, string.
 *
 * Return 0 if a translation was available, otherwise 1.
 *
 *  Arrow characters (0x18 - 0x1b):
 *
 *  0x18 Up arrow
 *  0x19 Down arrow
 *  0x1a Right arrow
 *  0x1b Left arrow
 *
 *  International characters (0x9b - 0xa3):
 *
 *  0x9b a umlaut (ae)
 *  0x9c o umlaut (oe)
 *  0x9d u umlaut (ue)
 *  0x9e A umlaut (Ae)
 *  0x9f O umlaut (Oe)
 *  0xa0 U umlaut (Ue)
 *  0xa1 sz (ss)
 *  0xa2 open quote (>>)
 *  0xa3 close quota (<<)
 *
 *  Line drawing characters (0xb3 - 0xda):
 *
 *  0xb3 vertical line (|)
 *  0xba double vertical line (#)
 *  0xc4 horizontal line (-)
 *  0xcd double horizontal line (=)
 *  all other are corner pieces (+)
 *
 */

int                     codes_to_text (
   int                     c,
   char                    *s) {

   /* Characters 24 to 27 and 179 to 218 need no translation */

   if ((c > 23 && c < 28) || (c > 178 && c < 219)) {

      s[0] = (char) c;
      s[1] = '\0';

      return 0;

   }

   /* German characters need translation */

   if (c > 154 && c < 164) {

      char                    xlat[9] = { 0x84, 0x94, 0x81, 0x8e, 0x99, 0x9a, 0xe1, 0xaf, 0xae };

      s[0] = xlat[c - 155];
      s[1] = '\0';

      return 0;
   } /* endif */

   return 1;

}/* codes_to_text */
