~ubuntu-branches/ubuntu/wily/gargoyle-free/wily-proposed

« back to all changes in this revision

Viewing changes to terps/magnetic/Glk/glk.c

  • Committer: Bazaar Package Importer
  • Author(s): Sylvain Beucler
  • Date: 2009-09-11 20:09:43 UTC
  • Revision ID: james.westby@ubuntu.com-20090911200943-idgzoyupq6650zpn
Tags: upstream-2009-08-25
ImportĀ upstreamĀ versionĀ 2009-08-25

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* vi: set ts=2 shiftwidth=2 expandtab:
 
2
 *
 
3
 * Copyright (C) 2002-2006  Simon Baldwin, simon_baldwin@yahoo.com
 
4
 * Mac portions Copyright (C) 2002  Ben Hines
 
5
 *
 
6
 * This program is free software; you can redistribute it and/or modify
 
7
 * it under the terms of version 2 of the GNU General Public License
 
8
 * as published by the Free Software Foundation.
 
9
 *
 
10
 * This program is distributed in the hope that it will be useful,
 
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
 * GNU General Public License for more details.
 
14
 *
 
15
 * You should have received a copy of the GNU General Public License
 
16
 * along with this program; if not, write to the Free Software
 
17
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 
18
 * USA
 
19
 */
 
20
 
 
21
/*
 
22
 * Glk interface for Magnetic Scrolls 2.3
 
23
 * --------------------------------------
 
24
 *
 
25
 * This module contains the the Glk porting layer for the Magnetic
 
26
 * Scrolls interpreter.  It defines the Glk arguments list structure,
 
27
 * the entry points for the Glk library framework to use, and all
 
28
 * platform-abstracted I/O to link to Glk's I/O.
 
29
 *
 
30
 * The following items are omitted from this Glk port:
 
31
 *
 
32
 *  o Glk tries to assert control over _all_ file I/O.  It's just too
 
33
 *    disruptive to add it to existing code, so for now, the Magnetic
 
34
 *    Scrolls interpreter is still dependent on stdio and the like.
 
35
 *
 
36
 */
 
37
 
 
38
#include <assert.h>
 
39
#include <errno.h>
 
40
#include <stdio.h>
 
41
#include <ctype.h>
 
42
#include <time.h>
 
43
#include <stdlib.h>
 
44
#include <string.h>
 
45
#include <limits.h>
 
46
#include <stddef.h>
 
47
 
 
48
#include "defs.h"
 
49
 
 
50
#include "glk.h"
 
51
 
 
52
/*
 
53
 * True and false definitions -- usually defined in glkstart.h, but we need
 
54
 * them early, so we'll define them here too.  We also need NULL, but that's
 
55
 * normally from stdio.h or one of it's cousins.
 
56
 */
 
57
#ifndef FALSE
 
58
# define FALSE 0
 
59
#endif
 
60
#ifndef TRUE
 
61
# define TRUE (!FALSE)
 
62
#endif
 
63
 
 
64
 
 
65
/*---------------------------------------------------------------------*/
 
66
/*  Module variables, miscellaneous other stuff                        */
 
67
/*---------------------------------------------------------------------*/
 
68
 
 
69
/* Glk Magnetic Scrolls port version number. */
 
70
static const glui32 GMS_PORT_VERSION = 0x00010601;
 
71
 
 
72
/*
 
73
 * We use a maximum of five Glk windows, one for status, one for pictures,
 
74
 * two for hints, and one for everything else.  The status and pictures
 
75
 * windows may be NULL, depending on user selections and the capabilities
 
76
 * of the Glk library.  The hints windows will normally be NULL, except
 
77
 * when in the hints subsystem.
 
78
 */
 
79
static winid_t gms_main_window = NULL,
 
80
               gms_status_window = NULL,
 
81
               gms_graphics_window = NULL,
 
82
               gms_hint_menu_window = NULL,
 
83
               gms_hint_text_window = NULL;
 
84
 
 
85
/*
 
86
 * Transcript stream and input log.  These are NULL if there is no current
 
87
 * collection of these strings.
 
88
 */
 
89
static strid_t gms_transcript_stream = NULL,
 
90
               gms_inputlog_stream = NULL;
 
91
 
 
92
/* Input read log stream, for reading back an input log. */
 
93
static strid_t gms_readlog_stream = NULL;
 
94
 
 
95
/* Note about whether graphics is possible, or not. */
 
96
static int gms_graphics_possible = TRUE;
 
97
 
 
98
/* Options that may be turned off or set by command line flags. */
 
99
static int gms_graphics_enabled = TRUE;
 
100
static enum {
 
101
  GAMMA_OFF, GAMMA_NORMAL, GAMMA_HIGH
 
102
}
 
103
gms_gamma_mode = GAMMA_NORMAL;
 
104
static int gms_animation_enabled = TRUE,
 
105
           gms_prompt_enabled = TRUE,
 
106
           gms_abbreviations_enabled = TRUE,
 
107
           gms_commands_enabled = TRUE;
 
108
 
 
109
/* Magnetic Scrolls standard input prompt string. */
 
110
static const char * const GMS_INPUT_PROMPT = ">";
 
111
 
 
112
/* Forward declaration of event wait function. */
 
113
static void gms_event_wait (glui32 wait_type, event_t * event);
 
114
 
 
115
 
 
116
/*---------------------------------------------------------------------*/
 
117
/*  Glk port utility functions                                         */
 
118
/*---------------------------------------------------------------------*/
 
119
 
 
120
/*
 
121
 * gms_fatal()
 
122
 *
 
123
 * Fatal error handler.  The function returns, expecting the caller to
 
124
 * abort() or otherwise handle the error.
 
125
 */
 
126
static void
 
127
gms_fatal (const char *string)
 
128
{
 
129
  /*
 
130
   * If the failure happens too early for us to have a window, print
 
131
   * the message to stderr.
 
132
   */
 
133
  if (!gms_main_window)
 
134
    {
 
135
      fprintf (stderr, "\n\nINTERNAL ERROR: %s\n", string);
 
136
 
 
137
      fprintf (stderr, "\nPlease record the details of this error, try to"
 
138
                       " note down everything you did to cause it, and email"
 
139
                       " this information to simon_baldwin@yahoo.com.\n\n");
 
140
      return;
 
141
    }
 
142
 
 
143
  /* Cancel all possible pending window input events. */
 
144
  glk_cancel_line_event (gms_main_window, NULL);
 
145
  glk_cancel_char_event (gms_main_window);
 
146
  if (gms_hint_menu_window)
 
147
    {
 
148
      glk_cancel_char_event (gms_hint_menu_window);
 
149
      glk_window_close (gms_hint_menu_window, NULL);
 
150
    }
 
151
  if (gms_hint_text_window)
 
152
    {
 
153
      glk_cancel_char_event (gms_hint_text_window);
 
154
      glk_window_close (gms_hint_text_window, NULL);
 
155
    }
 
156
 
 
157
  /* Print a message indicating the error. */
 
158
  glk_set_window (gms_main_window);
 
159
  glk_set_style (style_Normal);
 
160
  glk_put_string ("\n\nINTERNAL ERROR: ");
 
161
  glk_put_string ((char *) string);
 
162
 
 
163
  glk_put_string ("\n\nPlease record the details of this error, try to"
 
164
                  " note down everything you did to cause it, and email"
 
165
                  " this information to simon_baldwin@yahoo.com.\n\n");
 
166
}
 
167
 
 
168
 
 
169
/*
 
170
 * gms_malloc()
 
171
 * gms_realloc()
 
172
 *
 
173
 * Non-failing malloc and realloc; call gms_fatal and exit if memory
 
174
 * allocation fails.
 
175
 */
 
176
static void *
 
177
gms_malloc (size_t size)
 
178
{
 
179
  void *pointer;
 
180
 
 
181
  pointer = malloc (size);
 
182
  if (!pointer)
 
183
    {
 
184
      gms_fatal ("GLK: Out of system memory");
 
185
      glk_exit ();
 
186
    }
 
187
 
 
188
  return pointer;
 
189
}
 
190
 
 
191
static void *
 
192
gms_realloc (void *ptr, size_t size)
 
193
{
 
194
  void *pointer;
 
195
 
 
196
  pointer = realloc (ptr, size);
 
197
  if (!pointer)
 
198
    {
 
199
      gms_fatal ("GLK: Out of system memory");
 
200
      glk_exit ();
 
201
    }
 
202
 
 
203
  return pointer;
 
204
}
 
205
 
 
206
 
 
207
/*
 
208
 * gms_strncasecmp()
 
209
 * gms_strcasecmp()
 
210
 *
 
211
 * Strncasecmp and strcasecmp are not ANSI functions, so here are local
 
212
 * definitions to do the same jobs.
 
213
 */
 
214
static int
 
215
gms_strncasecmp (const char *s1, const char *s2, size_t n)
 
216
{
 
217
  size_t index;
 
218
 
 
219
  for (index = 0; index < n; index++)
 
220
    {
 
221
      int diff;
 
222
 
 
223
      diff = glk_char_to_lower (s1[index]) - glk_char_to_lower (s2[index]);
 
224
      if (diff < 0 || diff > 0)
 
225
        return diff < 0 ? -1 : 1;
 
226
    }
 
227
 
 
228
  return 0;
 
229
}
 
230
 
 
231
static int
 
232
gms_strcasecmp (const char *s1, const char *s2)
 
233
{
 
234
  size_t s1len, s2len;
 
235
  int result;
 
236
 
 
237
  s1len = strlen (s1);
 
238
  s2len = strlen (s2);
 
239
 
 
240
  result = gms_strncasecmp (s1, s2, s1len < s2len ? s1len : s2len);
 
241
  if (result < 0 || result > 0)
 
242
    return result;
 
243
  else
 
244
    return s1len < s2len ? -1 : s1len > s2len ? 1 : 0;
 
245
}
 
246
 
 
247
 
 
248
/*---------------------------------------------------------------------*/
 
249
/*  Glk port stub graphics functions                                   */
 
250
/*---------------------------------------------------------------------*/
 
251
 
 
252
/*
 
253
 * If we're working with a very stripped down, old, or lazy Glk library
 
254
 * that neither offers Glk graphics nor graphics stubs functions, here
 
255
 * we define our own stubs, to avoid link-time errors.
 
256
 */
 
257
#ifndef GLK_MODULE_IMAGE
 
258
static glui32
 
259
glk_image_draw (winid_t win, glui32 image, glsi32 val1, glsi32 val2)
 
260
{
 
261
  return FALSE;
 
262
}
 
263
static glui32
 
264
glk_image_draw_scaled (winid_t win, glui32 image, glsi32 val1, glsi32 val2,
 
265
                       glui32 width, glui32 height)
 
266
{
 
267
  return FALSE;
 
268
}
 
269
static glui32
 
270
glk_image_get_info (glui32 image, glui32 * width, glui32 * height)
 
271
{
 
272
  return FALSE;
 
273
}
 
274
static void
 
275
glk_window_flow_break (winid_t win)
 
276
{
 
277
}
 
278
static void
 
279
glk_window_erase_rect (winid_t win, glsi32 left, glsi32 top,
 
280
                       glui32 width, glui32 height)
 
281
{
 
282
}
 
283
static void
 
284
glk_window_fill_rect (winid_t win, glui32 color, glsi32 left, glsi32 top,
 
285
                      glui32 width, glui32 height)
 
286
{
 
287
}
 
288
static void
 
289
glk_window_set_background_color (winid_t win, glui32 color)
 
290
{
 
291
}
 
292
#endif
 
293
 
 
294
 
 
295
/*---------------------------------------------------------------------*/
 
296
/*  Glk port CRC functions                                             */
 
297
/*---------------------------------------------------------------------*/
 
298
 
 
299
/* CRC table initialization polynomial. */
 
300
static const glui32 GMS_CRC_POLYNOMIAL = 0xedb88320;
 
301
 
 
302
 
 
303
/*
 
304
 * gms_get_buffer_crc()
 
305
 *
 
306
 * Return the CRC of the bytes in buffer[0..length-1].
 
307
 *
 
308
 * This algorithm is taken from the PNG specification, version 1.0.
 
309
 */
 
310
static glui32
 
311
gms_get_buffer_crc (const void *void_buffer, size_t length)
 
312
{
 
313
  static int is_initialized = FALSE;
 
314
  static glui32 crc_table[UCHAR_MAX + 1];
 
315
 
 
316
  const char *buffer = (const char *) void_buffer;
 
317
  glui32 crc;
 
318
  size_t index;
 
319
 
 
320
  /* Build the static CRC lookup table on first call. */
 
321
  if (!is_initialized)
 
322
    {
 
323
      for (index = 0; index < UCHAR_MAX + 1; index++)
 
324
        {
 
325
          int bit;
 
326
 
 
327
          crc = (glui32) index;
 
328
          for (bit = 0; bit < CHAR_BIT; bit++)
 
329
            crc = crc & 1 ? GMS_CRC_POLYNOMIAL ^ (crc >> 1) : crc >> 1;
 
330
 
 
331
          crc_table[index] = crc;
 
332
        }
 
333
 
 
334
      is_initialized = TRUE;
 
335
 
 
336
      /* CRC lookup table self-test, after is_initialized set -- recursion. */
 
337
      assert (gms_get_buffer_crc ("123456789", 9) == 0xcbf43926);
 
338
    }
 
339
 
 
340
  /*
 
341
   * Start with all ones in the crc, then update using table entries.  Xor
 
342
   * with all ones again, finally, before returning.
 
343
   */
 
344
  crc = 0xffffffff;
 
345
  for (index = 0; index < length; index++)
 
346
    crc = crc_table[(crc ^ buffer[index]) & UCHAR_MAX] ^ (crc >> CHAR_BIT);
 
347
  return crc ^ 0xffffffff;
 
348
}
 
349
 
 
350
 
 
351
/*---------------------------------------------------------------------*/
 
352
/*  Glk port game identification data and identification functions     */
 
353
/*---------------------------------------------------------------------*/
 
354
 
 
355
/*
 
356
 * The game's name, suitable for printing out on a status line, or other
 
357
 * location where game information is relevant.  Set on game startup, by
 
358
 * identifying the game from its text file header.
 
359
 */
 
360
static const char *gms_gameid_game_name = NULL;
 
361
 
 
362
 
 
363
/*
 
364
 * The following game database is built from Generic/games.txt, and is used
 
365
 * to identify the game being run.  Magnetic Scrolls games don't generally
 
366
 * supply a status line, so this data can be used instead.
 
367
 */
 
368
typedef const struct {
 
369
  const type32 undo_size;   /* Header word at offset 0x22 */
 
370
  const type32 undo_pc;     /* Header word at offset 0x26 */
 
371
  const char * const name;  /* Game title and platform */
 
372
} gms_game_table_t;
 
373
typedef gms_game_table_t *gms_game_tableref_t;
 
374
 
 
375
static gms_game_table_t GMS_GAME_TABLE[] = {
 
376
  {0x2100, 0x427e, "Corruption v1.11 (Amstrad CPC)"},
 
377
  {0x2100, 0x43a0, "Corruption v1.11 (Archimedes)"},
 
378
  {0x2100, 0x43a0, "Corruption v1.11 (DOS)"},
 
379
  {0x2100, 0x4336, "Corruption v1.11 (Commodore 64)"},
 
380
  {0x2100, 0x4222, "Corruption v1.11 (Spectrum +3)"},
 
381
  {0x2100, 0x4350, "Corruption v1.12 (Archimedes)"},
 
382
  {0x2500, 0x6624, "Corruption v1.12 (DOS, Magnetic Windows)"},
 
383
 
 
384
  {0x2300, 0x3fa0, "Fish v1.02 (DOS)"},
 
385
  {0x2400, 0x4364, "Fish v1.03 (Spectrum +3)"},
 
386
  {0x2300, 0x3f72, "Fish v1.07 (Commodore 64)"},
 
387
  {0x2200, 0x3f9c, "Fish v1.08 (Archimedes)"},
 
388
  {0x2a00, 0x583a, "Fish v1.10 (DOS, Magnetic Windows)"},
 
389
 
 
390
  {0x5000, 0x6c30, "Guild v1.0 (Amstrad CPC)"},
 
391
  {0x5000, 0x6cac, "Guild v1.0 (Commodore 64)"},
 
392
  {0x5000, 0x6d5c, "Guild v1.1 (DOS)"},
 
393
  {0x3300, 0x698a, "Guild v1.3 (Archimedes)"},
 
394
  {0x3200, 0x6772, "Guild v1.3 (Spectrum +3)"},
 
395
  {0x3400, 0x6528, "Guild v1.3 (DOS, Magnetic Windows)"},
 
396
 
 
397
  {0x2b00, 0x488c, "Jinxter v1.05 (Commodore 64)"},
 
398
  {0x2c00, 0x4a08, "Jinxter v1.05 (DOS)"},
 
399
  {0x2c00, 0x487a, "Jinxter v1.05 (Spectrum +3)"},
 
400
  {0x2c00, 0x4a56, "Jinxter v1.10 (DOS)"},
 
401
  {0x2b00, 0x4924, "Jinxter v1.22 (Amstrad CPC)"},
 
402
  {0x2c00, 0x4960, "Jinxter v1.30 (Archimedes)"},
 
403
 
 
404
  {0x1600, 0x3940, "Myth v1.0 (Commodore 64)"},
 
405
  {0x1500, 0x3a0a, "Myth v1.0 (DOS)"},
 
406
 
 
407
  {0x3600, 0x42cc, "Pawn v2.3 (Amstrad CPC)"},
 
408
  {0x3600, 0x4420, "Pawn v2.3 (Archimedes)"},
 
409
  {0x3600, 0x3fb0, "Pawn v2.3 (Commodore 64)"},
 
410
  {0x3600, 0x4420, "Pawn v2.3 (DOS)"},
 
411
  {0x3900, 0x42e4, "Pawn v2.3 (Spectrum 128)"},
 
412
  {0x3900, 0x42f4, "Pawn v2.4 (Spectrum +3)"},
 
413
 
 
414
  {0x3900, 0x75f2, "Wonderland v1.21 (DOS, Magnetic Windows)"},
 
415
  {0x3900, 0x75f8, "Wonderland v1.27 (Archimedes)"},
 
416
  {0, 0, NULL}
 
417
};
 
418
 
 
419
 
 
420
/*
 
421
 * gms_gameid_lookup_game()
 
422
 *
 
423
 * Look up and return the game table entry given a game's undo size and
 
424
 * undo pc values.  Returns the entry, or NULL if not found.
 
425
 */
 
426
static gms_game_tableref_t
 
427
gms_gameid_lookup_game (type32 undo_size, type32 undo_pc)
 
428
{
 
429
  gms_game_tableref_t game;
 
430
 
 
431
  for (game = GMS_GAME_TABLE; game->name; game++)
 
432
    {
 
433
      if (game->undo_size == undo_size && game->undo_pc == undo_pc)
 
434
        break;
 
435
    }
 
436
 
 
437
  return game->name ? game : NULL;
 
438
}
 
439
 
 
440
 
 
441
/*
 
442
 * gms_gameid_read_uint32()
 
443
 *
 
444
 * Endian-safe unsigned 32 bit integer read from game text file.  Returns
 
445
 * 0 on error, a known unused table value.
 
446
 */
 
447
static type32
 
448
gms_gameid_read_uint32 (int offset, FILE *stream)
 
449
{
 
450
  unsigned char buffer[4];
 
451
 
 
452
  if (fseek (stream, offset, SEEK_SET) != 0)
 
453
    return 0;
 
454
  if (fread (buffer, 1, sizeof (buffer), stream) != sizeof (buffer))
 
455
    return 0;
 
456
 
 
457
  return buffer[0] << 24 | buffer[1] << 16 | buffer[2] << 8 | buffer[3];
 
458
}
 
459
 
 
460
 
 
461
/*
 
462
 * gms_gameid_identify_game()
 
463
 *
 
464
 * Identify a game from its text file header, and cache the game's name for
 
465
 * later queries.  Sets the cache to NULL if not found.
 
466
 */
 
467
static void
 
468
gms_gameid_identify_game (const char *text_file)
 
469
{
 
470
  FILE *stream;
 
471
  assert (text_file);
 
472
 
 
473
  stream = fopen (text_file, "r");
 
474
  if (stream)
 
475
    {
 
476
      type32 undo_size, undo_pc;
 
477
      gms_game_tableref_t game;
 
478
 
 
479
      /* Read the game's signature undo size and undo pc values. */
 
480
      undo_size = gms_gameid_read_uint32 (0x22, stream);
 
481
      undo_pc = gms_gameid_read_uint32 (0x26, stream);
 
482
      fclose (stream);
 
483
 
 
484
      /* Search for these values in the table, and set game name if found. */
 
485
      game = gms_gameid_lookup_game (undo_size, undo_pc);
 
486
      gms_gameid_game_name = game ? game->name : NULL;
 
487
    }
 
488
  else
 
489
    gms_gameid_game_name = NULL;
 
490
}
 
491
 
 
492
 
 
493
/*
 
494
 * gms_gameid_get_game_name()
 
495
 *
 
496
 * Return the name of the game, or NULL if not identifiable.
 
497
 */
 
498
static const char *
 
499
gms_gameid_get_game_name (void)
 
500
{
 
501
  return gms_gameid_game_name;
 
502
}
 
503
 
 
504
 
 
505
/*---------------------------------------------------------------------*/
 
506
/*  Glk port picture functions                                         */
 
507
/*---------------------------------------------------------------------*/
 
508
 
 
509
/*
 
510
 * Color conversions lookup tables, and a word about gamma corrections.
 
511
 *
 
512
 * When uncorrected, some game pictures can look dark (Corruption, Won-
 
513
 * derland), whereas others look just fine (Guild Of Thieves, Jinxter).
 
514
 *
 
515
 * The standard general-purpose gamma correction is around 2.1, with
 
516
 * specific values, normally, of 2.5-2.7 for IBM PC systems, and 1.8 for
 
517
 * Macintosh.  However, applying even the low end of this range can make
 
518
 * some pictures look washed out, yet improve others nicely.
 
519
 *
 
520
 * To try to solve this, here we'll set up a precalculated table with
 
521
 * discrete gamma values.  On displaying a picture, we'll try to find a
 
522
 * gamma correction that seems to offer a reasonable level of contrast
 
523
 * for the picture.
 
524
 *
 
525
 * Here's an AWK script to create the gamma table:
 
526
 *
 
527
 * BEGIN { max=255.0; step=max/7.0
 
528
 *         for (gamma=0.9; gamma<=2.7; gamma+=0.05) {
 
529
 *             printf "  {\"%2.2f\", {0, ", gamma
 
530
 *             for (i=1; i<8; i++) {
 
531
 *                 printf "%3.0f", (((step*i / max) ^ (1.0/gamma)) * max)
 
532
 *                 printf "%s", (i<7) ? ", " : ""
 
533
 *             }
 
534
 *             printf "}, "
 
535
 *             printf "%s },\n", (gamma>0.99 && gamma<1.01) ? "FALSE" : "TRUE "
 
536
 *         } }
 
537
 *
 
538
 */
 
539
typedef const struct
 
540
{
 
541
  const char * const level;      /* Gamma correction level. */
 
542
  const unsigned char table[8];  /* Color lookup table. */
 
543
  const int is_corrected;        /* Flag if non-linear. */
 
544
} gms_gamma_t;
 
545
typedef gms_gamma_t *gms_gammaref_t;
 
546
 
 
547
static gms_gamma_t GMS_GAMMA_TABLE[] = {
 
548
  {"0.90", {0,  29,  63,  99, 137, 175, 215, 255}, TRUE},
 
549
  {"0.95", {0,  33,  68, 105, 141, 179, 217, 255}, TRUE},
 
550
  {"1.00", {0,  36,  73, 109, 146, 182, 219, 255}, FALSE},
 
551
  {"1.05", {0,  40,  77, 114, 150, 185, 220, 255}, TRUE},
 
552
  {"1.10", {0,  43,  82, 118, 153, 188, 222, 255}, TRUE},
 
553
  {"1.15", {0,  47,  86, 122, 157, 190, 223, 255}, TRUE},
 
554
  {"1.20", {0,  50,  90, 126, 160, 193, 224, 255}, TRUE},
 
555
  {"1.25", {0,  54,  94, 129, 163, 195, 225, 255}, TRUE},
 
556
  {"1.30", {0,  57,  97, 133, 166, 197, 226, 255}, TRUE},
 
557
  {"1.35", {0,  60, 101, 136, 168, 199, 227, 255}, TRUE},
 
558
  {"1.40", {0,  64, 104, 139, 171, 201, 228, 255}, TRUE},
 
559
  {"1.45", {0,  67, 107, 142, 173, 202, 229, 255}, TRUE},
 
560
  {"1.50", {0,  70, 111, 145, 176, 204, 230, 255}, TRUE},
 
561
  {"1.55", {0,  73, 114, 148, 178, 205, 231, 255}, TRUE},
 
562
  {"1.60", {0,  76, 117, 150, 180, 207, 232, 255}, TRUE},
 
563
  {"1.65", {0,  78, 119, 153, 182, 208, 232, 255}, TRUE},
 
564
  {"1.70", {0,  81, 122, 155, 183, 209, 233, 255}, TRUE},
 
565
  {"1.75", {0,  84, 125, 157, 185, 210, 233, 255}, TRUE},
 
566
  {"1.80", {0,  87, 127, 159, 187, 212, 234, 255}, TRUE},
 
567
  {"1.85", {0,  89, 130, 161, 188, 213, 235, 255}, TRUE},
 
568
  {"1.90", {0,  92, 132, 163, 190, 214, 235, 255}, TRUE},
 
569
  {"1.95", {0,  94, 134, 165, 191, 215, 236, 255}, TRUE},
 
570
  {"2.00", {0,  96, 136, 167, 193, 216, 236, 255}, TRUE},
 
571
  {"2.05", {0,  99, 138, 169, 194, 216, 237, 255}, TRUE},
 
572
  {"2.10", {0, 101, 140, 170, 195, 217, 237, 255}, TRUE},
 
573
  {"2.15", {0, 103, 142, 172, 197, 218, 237, 255}, TRUE},
 
574
  {"2.20", {0, 105, 144, 173, 198, 219, 238, 255}, TRUE},
 
575
  {"2.25", {0, 107, 146, 175, 199, 220, 238, 255}, TRUE},
 
576
  {"2.30", {0, 109, 148, 176, 200, 220, 238, 255}, TRUE},
 
577
  {"2.35", {0, 111, 150, 178, 201, 221, 239, 255}, TRUE},
 
578
  {"2.40", {0, 113, 151, 179, 202, 222, 239, 255}, TRUE},
 
579
  {"2.45", {0, 115, 153, 180, 203, 222, 239, 255}, TRUE},
 
580
  {"2.50", {0, 117, 154, 182, 204, 223, 240, 255}, TRUE},
 
581
  {"2.55", {0, 119, 156, 183, 205, 223, 240, 255}, TRUE},
 
582
  {"2.60", {0, 121, 158, 184, 206, 224, 240, 255}, TRUE},
 
583
  {"2.65", {0, 122, 159, 185, 206, 225, 241, 255}, TRUE},
 
584
  {"2.70", {0, 124, 160, 186, 207, 225, 241, 255}, TRUE},
 
585
  {NULL,   {0,   0,   0,   0,   0,   0,   0,   0}, FALSE}
 
586
};
 
587
 
 
588
/* R,G,B color triple definition. */
 
589
typedef struct
 
590
{
 
591
  int red, green, blue;
 
592
} gms_rgb_t;
 
593
typedef gms_rgb_t *gms_rgbref_t;
 
594
 
 
595
/*
 
596
 * Weighting values for calculating the luminance of a color.  There are
 
597
 * two commonly used sets of values for these -- 299,587,114, taken from
 
598
 * NTSC (Never The Same Color) 1953 standards, and 212,716,72, which is the
 
599
 * set that modern CRTs tend to match.  The NTSC ones seem to give the best
 
600
 * subjective results.
 
601
 */
 
602
static const gms_rgb_t GMS_LUMINANCE_WEIGHTS = { 299, 587, 114 };
 
603
 
 
604
/*
 
605
 * Maximum number of regions to consider in a single repaint pass.  A
 
606
 * couple of hundred seems to strike the right balance between not too
 
607
 * sluggardly picture updates, and responsiveness to input during graphics
 
608
 * rendering, when combined with short timeouts.
 
609
 */
 
610
static const int GMS_REPAINT_LIMIT = 256;
 
611
 
 
612
/*
 
613
 * Graphics timeout; we like an update call after this period (ms).  In
 
614
 * practice, this timeout may actually be shorter than the time taken
 
615
 * to reach the limit on repaint regions, but because Glk guarantees that
 
616
 * user interactions (in this case, line events) take precedence over
 
617
 * timeouts, this should be okay; we'll still see a game that responds to
 
618
 * input each time the background repaint function yields.
 
619
 *
 
620
 * Setting this value is tricky.  We'd like it to be the shortest possible
 
621
 * consistent with getting other stuff done, say 10ms.  However, Xglk has
 
622
 * a granularity of 50ms on checking for timeouts, as it uses a 1/20s
 
623
 * timeout on X select.  This means that the shortest timeout we'll ever
 
624
 * get from Xglk will be 50ms, so there's no point in setting this shorter
 
625
 * than that.  With luck, other Glk libraries will be more efficient than
 
626
 * this, and can give us higher timer resolution; we'll set 50ms here, and
 
627
 * hope that no other Glk library is worse.
 
628
 */
 
629
static const glui32 GMS_GRAPHICS_TIMEOUT = 50;
 
630
 
 
631
/*
 
632
 * Count of timeouts to wait in between animation paints, and to wait on
 
633
 * repaint request.  Waiting for 2 timeouts of around 50ms, gets us to the
 
634
 * 100ms recommended animation frame rate.  Waiting after a repaint smooths
 
635
 * the display where the frame is being resized, by helping to avoid
 
636
 * graphics output while more resize events are received; around 1/2 second
 
637
 * seems okay.
 
638
 */
 
639
static const int GMS_GRAPHICS_ANIMATION_WAIT = 2,
 
640
                 GMS_GRAPHICS_REPAINT_WAIT = 10;
 
641
 
 
642
/* Pixel size multiplier for image size scaling. */
 
643
static const int GMS_GRAPHICS_PIXEL = 2;
 
644
 
 
645
/* Proportion of the display to use for graphics. */
 
646
static const glui32 GMS_GRAPHICS_PROPORTION = 60;
 
647
 
 
648
/*
 
649
 * Border and shading control.  For cases where we can't detect the back-
 
650
 * ground color of the main window, there's a default, white, background.
 
651
 * Bordering is black, with a 1 pixel border, 2 pixel shading, and 8 steps
 
652
 * of shading fade.
 
653
 */
 
654
static const glui32 GMS_GRAPHICS_DEFAULT_BACKGROUND = 0x00ffffff,
 
655
                    GMS_GRAPHICS_BORDER_COLOR = 0x00000000;
 
656
static const int GMS_GRAPHICS_BORDER = 1,
 
657
                 GMS_GRAPHICS_SHADING = 2,
 
658
                 GMS_GRAPHICS_SHADE_STEPS = 8;
 
659
 
 
660
/*
 
661
 * Guaranteed unused pixel value.  This value is used to fill the on-screen
 
662
 * buffer on new pictures or repaints, resulting in a full paint of all
 
663
 * pixels since no off-screen, real picture, pixel will match it.
 
664
 */
 
665
static const int GMS_GRAPHICS_UNUSED_PIXEL = 0xff;
 
666
 
 
667
/*
 
668
 * The current picture bitmap being displayed, its width, height, palette,
 
669
 * animation flag, and picture id.
 
670
 */
 
671
enum { GMS_PALETTE_SIZE = 16 };
 
672
static type8 *gms_graphics_bitmap = NULL;
 
673
static type16 gms_graphics_width = 0,
 
674
              gms_graphics_height = 0,
 
675
              gms_graphics_palette[GMS_PALETTE_SIZE]; /* = { 0, ... }; */
 
676
static type8 gms_graphics_animated = FALSE;
 
677
static type32 gms_graphics_picture = 0;
 
678
 
 
679
/*
 
680
 * Flags set on new picture, and on resize or arrange events, and a flag
 
681
 * to indicate whether background repaint is stopped or active.
 
682
 */
 
683
static int gms_graphics_new_picture = FALSE,
 
684
           gms_graphics_repaint = FALSE,
 
685
           gms_graphics_active = FALSE;
 
686
 
 
687
/* Flag to try to monitor the state of interpreter graphics. */
 
688
static int gms_graphics_interpreter = FALSE;
 
689
 
 
690
/*
 
691
 * Pointer to the two graphics buffers, one the off-screen representation
 
692
 * of pixels, and the other tracking on-screen data.  These are temporary
 
693
 * graphics malloc'ed memory, and should be free'd on exit.
 
694
 */
 
695
static type8 *gms_graphics_off_screen = NULL,
 
696
             *gms_graphics_on_screen = NULL;
 
697
 
 
698
/*
 
699
 * Pointer to the current active gamma table entry.  Because of the way
 
700
 * it's queried, this may not be NULL, otherwise we risk a race, with
 
701
 * admittedly a very low probability, with the updater.  So, it's init-
 
702
 * ialized instead to the gamma table.  The real value in use is inserted
 
703
 * on the first picture update timeout call for a new picture.
 
704
 */
 
705
static gms_gammaref_t gms_graphics_current_gamma = GMS_GAMMA_TABLE;
 
706
 
 
707
/*
 
708
 * The number of colors used in the palette by the current picture.  This
 
709
 * value is also at risk of a race with the updater, so it too has a mild
 
710
 * lie for a default value.
 
711
 */
 
712
static int gms_graphics_color_count = GMS_PALETTE_SIZE;
 
713
 
 
714
 
 
715
/*
 
716
 * gms_graphics_open()
 
717
 *
 
718
 * If it's not open, open the graphics window.  Returns TRUE if graphics
 
719
 * was successfully started, or already on.
 
720
 */
 
721
static int
 
722
gms_graphics_open (void)
 
723
{
 
724
  if (!gms_graphics_window)
 
725
    {
 
726
      gms_graphics_window = glk_window_open (gms_main_window,
 
727
                                             winmethod_Above
 
728
                                             | winmethod_Proportional,
 
729
                                             GMS_GRAPHICS_PROPORTION,
 
730
                                             wintype_Graphics, 0);
 
731
    }
 
732
 
 
733
  return gms_graphics_window != NULL;
 
734
}
 
735
 
 
736
 
 
737
/*
 
738
 * gms_graphics_close()
 
739
 *
 
740
 * If open, close the graphics window and set back to NULL.
 
741
 */
 
742
static void
 
743
gms_graphics_close (void)
 
744
{
 
745
  if (gms_graphics_window)
 
746
    {
 
747
      glk_window_close (gms_graphics_window, NULL);
 
748
      gms_graphics_window = NULL;
 
749
    }
 
750
}
 
751
 
 
752
 
 
753
/*
 
754
 * gms_graphics_start()
 
755
 *
 
756
 * If graphics enabled, start any background picture update processing.
 
757
 */
 
758
static void
 
759
gms_graphics_start (void)
 
760
{
 
761
  if (gms_graphics_enabled)
 
762
    {
 
763
      /* If not running, start the updating "thread". */
 
764
      if (!gms_graphics_active)
 
765
        {
 
766
          glk_request_timer_events (GMS_GRAPHICS_TIMEOUT);
 
767
          gms_graphics_active = TRUE;
 
768
        }
 
769
    }
 
770
}
 
771
 
 
772
 
 
773
/*
 
774
 * gms_graphics_stop()
 
775
 *
 
776
 * Stop any background picture update processing.
 
777
 */
 
778
static void
 
779
gms_graphics_stop (void)
 
780
{
 
781
  /* If running, stop the updating "thread". */
 
782
  if (gms_graphics_active)
 
783
    {
 
784
      glk_request_timer_events (0);
 
785
      gms_graphics_active = FALSE;
 
786
    }
 
787
}
 
788
 
 
789
 
 
790
/*
 
791
 * gms_graphics_are_displayed()
 
792
 *
 
793
 * Return TRUE if graphics are currently being displayed, FALSE otherwise.
 
794
 */
 
795
static int
 
796
gms_graphics_are_displayed (void)
 
797
{
 
798
  return gms_graphics_window != NULL;
 
799
}
 
800
 
 
801
 
 
802
/*
 
803
 * gms_graphics_paint()
 
804
 *
 
805
 * Set up a complete repaint of the current picture in the graphics window.
 
806
 * This function should be called on the appropriate Glk window resize and
 
807
 * arrange events.
 
808
 */
 
809
static void
 
810
gms_graphics_paint (void)
 
811
{
 
812
  if (gms_graphics_enabled && gms_graphics_are_displayed ())
 
813
    {
 
814
      /* Set the repaint flag, and start graphics. */
 
815
      gms_graphics_repaint = TRUE;
 
816
      gms_graphics_start ();
 
817
    }
 
818
}
 
819
 
 
820
 
 
821
/*
 
822
 * gms_graphics_restart()
 
823
 *
 
824
 * Restart graphics as if the current picture is a new picture.  This
 
825
 * function should be called whenever graphics is re-enabled after being
 
826
 * disabled, on change of gamma color correction policy, and on change
 
827
 * of animation policy.
 
828
 */
 
829
static void
 
830
gms_graphics_restart (void)
 
831
{
 
832
  if (gms_graphics_enabled && gms_graphics_are_displayed ())
 
833
    {
 
834
      /*
 
835
       * If the picture is animated, we'll need to be able to re-get the
 
836
       * first animation frame so that the picture can be treated as if
 
837
       * it is a new one.  So here, we'll try to re-extract the current
 
838
       * picture to do this.  Calling ms_extract() is safe because we
 
839
       * don't get here unless graphics are displayed, and graphics aren't
 
840
       * displayed until there's a valid picture loaded, and ms_showpic
 
841
       * only loads a picture after it's called ms_extract and set the
 
842
       * picture id into gms_graphics_picture.
 
843
       *
 
844
       * The bitmap and other picture stuff can be ignored because it's
 
845
       * the precise same stuff as we already have in picture details
 
846
       * variables.  If the ms_extract() fails, we'll carry on regardless,
 
847
       * which may, or may not, result in the ideal picture display.
 
848
       *
 
849
       * One or two non-animated pictures return NULL from ms_extract()
 
850
       * being re-called, so we'll restrict calls to animations only.
 
851
       * And just to be safe, we'll also call only if we're already
 
852
       * holding a bitmap (and we should be; how else could the graphics
 
853
       * animation flag be set?...).
 
854
       */
 
855
      if (gms_graphics_animated && gms_graphics_bitmap)
 
856
        {
 
857
          type8 *bitmap, animated;
 
858
          type16 width, height, palette[GMS_PALETTE_SIZE];
 
859
 
 
860
          /* Extract the bitmap into dummy variables. */
 
861
          bitmap = ms_extract (gms_graphics_picture, &width, &height,
 
862
                               palette, &animated);
 
863
        }
 
864
 
 
865
      /* Set the new picture flag, and start graphics. */
 
866
      gms_graphics_new_picture = TRUE;
 
867
      gms_graphics_start ();
 
868
    }
 
869
}
 
870
 
 
871
 
 
872
/*
 
873
 * gms_graphics_count_colors()
 
874
 *
 
875
 * Analyze an image, and return the usage count of each palette color, and
 
876
 * an overall count of how many colors out of the palette are used.  NULL
 
877
 * arguments indicate no interest in the return value.
 
878
 */
 
879
static void
 
880
gms_graphics_count_colors (type8 bitmap[], type16 width, type16 height,
 
881
                           int *color_count, long color_usage[])
 
882
{
 
883
  int x, y, count;
 
884
  long usage[GMS_PALETTE_SIZE], index_row;
 
885
  assert (bitmap);
 
886
 
 
887
  /*
 
888
   * Traverse the image, counting each pixel usage.  For the y iterator,
 
889
   * maintain an index row as an optimization to avoid multiplications in
 
890
   * the loop.
 
891
   */
 
892
  count = 0;
 
893
  memset (usage, 0, sizeof (usage));
 
894
  for (y = 0, index_row = 0; y < height; y++, index_row += width)
 
895
    {
 
896
      for (x = 0; x < width; x++)
 
897
        {
 
898
          long index;
 
899
 
 
900
          /* Get the pixel index, and update the count for this color. */
 
901
          index = index_row + x;
 
902
          usage[bitmap[index]]++;
 
903
 
 
904
          /* If color usage is now 1, note new color encountered. */
 
905
          if (usage[bitmap[index]] == 1)
 
906
            count++;
 
907
        }
 
908
    }
 
909
 
 
910
  if (color_count)
 
911
    *color_count = count;
 
912
 
 
913
  if (color_usage)
 
914
    memcpy (color_usage, usage, sizeof (usage));
 
915
}
 
916
 
 
917
 
 
918
/*
 
919
 * gms_graphics_game_to_rgb_color()
 
920
 * gms_graphics_split_color()
 
921
 * gms_graphics_combine_color()
 
922
 * gms_graphics_color_luminance()
 
923
 *
 
924
 * General graphics helper functions, to convert between Magnetic Scrolls
 
925
 * and RGB color representations, and between RGB and Glk glui32 color
 
926
 * representations, and to calculate color luminance.
 
927
 */
 
928
static void
 
929
gms_graphics_game_to_rgb_color (type16 color, gms_gammaref_t gamma,
 
930
                                gms_rgbref_t rgb_color)
 
931
{
 
932
  assert (gamma && rgb_color);
 
933
 
 
934
  /*
 
935
   * Convert Magnetic Scrolls color, through gamma, into RGB.  This splits
 
936
   * the color into components based on the 3-bits used in the game palette,
 
937
   * and gamma-corrects and rescales each to the range 0-255, using the given
 
938
   * correction.
 
939
   */
 
940
  rgb_color->red   = gamma->table[(color & 0x700) >> 8];
 
941
  rgb_color->green = gamma->table[(color & 0x070) >> 4];
 
942
  rgb_color->blue  = gamma->table[(color & 0x007)];
 
943
}
 
944
 
 
945
static void
 
946
gms_graphics_split_color (glui32 color, gms_rgbref_t rgb_color)
 
947
{
 
948
  assert (rgb_color);
 
949
 
 
950
  rgb_color->red   = (color >> 16) & 0xff;
 
951
  rgb_color->green = (color >> 8) & 0xff;
 
952
  rgb_color->blue  = color & 0xff;
 
953
}
 
954
 
 
955
static glui32
 
956
gms_graphics_combine_color (gms_rgbref_t rgb_color)
 
957
{
 
958
  glui32 color;
 
959
  assert (rgb_color);
 
960
 
 
961
  color = (rgb_color->red << 16) | (rgb_color->green << 8) | rgb_color->blue;
 
962
  return color;
 
963
}
 
964
 
 
965
static int
 
966
gms_graphics_color_luminance (gms_rgbref_t rgb_color)
 
967
{
 
968
  static int is_initialized = FALSE;
 
969
  static int weighting = 0;
 
970
 
 
971
  long luminance;
 
972
 
 
973
  /* On the first call, calculate the overall weighting. */
 
974
  if (!is_initialized)
 
975
    {
 
976
      weighting = GMS_LUMINANCE_WEIGHTS.red + GMS_LUMINANCE_WEIGHTS.green
 
977
                  + GMS_LUMINANCE_WEIGHTS.blue;
 
978
 
 
979
      is_initialized = TRUE;
 
980
    }
 
981
 
 
982
  /* Calculate the luminance and scale back by 1000 to 0-255 before return. */
 
983
  luminance = ((long) rgb_color->red   * (long) GMS_LUMINANCE_WEIGHTS.red
 
984
             + (long) rgb_color->green * (long) GMS_LUMINANCE_WEIGHTS.green
 
985
             + (long) rgb_color->blue  * (long) GMS_LUMINANCE_WEIGHTS.blue);
 
986
 
 
987
  assert (weighting > 0);
 
988
  return (int) (luminance / weighting);
 
989
}
 
990
 
 
991
 
 
992
/*
 
993
 * gms_graphics_compare_luminance()
 
994
 * gms_graphics_constrast_variance()
 
995
 *
 
996
 * Calculate the contrast variance of the given palette and color usage, at
 
997
 * the given gamma correction level.  Helper functions for automatic gamma
 
998
 * correction.
 
999
 */
 
1000
static int
 
1001
gms_graphics_compare_luminance (const void *void_first,
 
1002
                                const void *void_second)
 
1003
{
 
1004
  long first = *(long *) void_first;
 
1005
  long second = *(long *) void_second;
 
1006
 
 
1007
  return first > second ? 1 : second > first ? -1 : 0;
 
1008
}
 
1009
 
 
1010
static long
 
1011
gms_graphics_contrast_variance (type16 palette[],
 
1012
                                long color_usage[], gms_gammaref_t gamma)
 
1013
{
 
1014
  int index, count, has_black, mean;
 
1015
  long sum;
 
1016
  int contrast[GMS_PALETTE_SIZE];
 
1017
  int luminance[GMS_PALETTE_SIZE + 1];  /* Luminance for each color,
 
1018
                                           plus one extra for black */
 
1019
 
 
1020
  /* Calculate the luminance energy of each palette color at this gamma. */
 
1021
  has_black = FALSE;
 
1022
  for (index = 0, count = 0; index < GMS_PALETTE_SIZE; index++)
 
1023
    {
 
1024
      if (color_usage[index] > 0)
 
1025
        {
 
1026
          gms_rgb_t rgb_color;
 
1027
 
 
1028
          /*
 
1029
           * Convert the 16-bit base picture color to RGB using the gamma
 
1030
           * currently under consideration.  Calculate luminance for this
 
1031
           * color and store in the next available luminance array entry.
 
1032
           */
 
1033
          gms_graphics_game_to_rgb_color (palette[index], gamma, &rgb_color);
 
1034
          luminance[count++] = gms_graphics_color_luminance (&rgb_color);
 
1035
 
 
1036
          /* Note if black is present in the palette. */
 
1037
          has_black |= luminance[count - 1] == 0;
 
1038
        }
 
1039
    }
 
1040
 
 
1041
  /*
 
1042
   * For best results, we want to anchor contrast calculations to black, so
 
1043
   * if black is not represented in the palette, add it as an extra luminance.
 
1044
   */
 
1045
  if (!has_black)
 
1046
    luminance[count++] = 0;
 
1047
 
 
1048
  /* Sort luminance values so that the darkest color is at index 0. */
 
1049
  qsort (luminance, count,
 
1050
         sizeof (*luminance), gms_graphics_compare_luminance);
 
1051
 
 
1052
  /*
 
1053
   * Calculate the difference in luminance between adjacent luminances in
 
1054
   * the sorted array, as contrast, and at the same time sum contrasts to
 
1055
   * calculate the mean.
 
1056
   */
 
1057
  sum = 0;
 
1058
  for (index = 0; index < count - 1; index++)
 
1059
    {
 
1060
      contrast[index] = luminance[index + 1] - luminance[index];
 
1061
      sum += contrast[index];
 
1062
    }
 
1063
  mean = sum / (count - 1);
 
1064
 
 
1065
  /* Calculate and return the variance in contrasts. */
 
1066
  sum = 0;
 
1067
  for (index = 0; index < count - 1; index++)
 
1068
    sum += (contrast[index] - mean) * (contrast[index] - mean);
 
1069
 
 
1070
  return sum / (count - 1);
 
1071
}
 
1072
 
 
1073
 
 
1074
/*
 
1075
 * gms_graphics_equal_contrast_gamma()
 
1076
 *
 
1077
 * Try to find a gamma correction for the given palette and color usage that
 
1078
 * gives relatively equal contrast among the displayed colors.
 
1079
 *
 
1080
 * To do this, we search the gamma tables, computing color luminance for each
 
1081
 * color in the palette given this gamma.  From luminances, we then compute
 
1082
 * the contrasts between the colors, and settle on the gamma correction that
 
1083
 * gives the most even and well-distributed picture contrast.  We ignore
 
1084
 * colors not used in the palette.
 
1085
 *
 
1086
 * Note that we don't consider how often a palette color is used, only whether
 
1087
 * it's represented, or not.  Some weighting might improve things, but the
 
1088
 * simple method seems to work adequately.  In practice, as there are only 16
 
1089
 * colors in a palette, most pictures use most colors in a relatively well
 
1090
 * distributed manner.  This algorithm probably wouldn't work well on real
 
1091
 * photographs, though.
 
1092
 */
 
1093
static gms_gammaref_t
 
1094
gms_graphics_equal_contrast_gamma (type16 palette[], long color_usage[])
 
1095
{
 
1096
  gms_gammaref_t gamma, result;
 
1097
  long lowest_variance;
 
1098
  assert (palette && color_usage);
 
1099
 
 
1100
  result = NULL;
 
1101
  lowest_variance = LONG_MAX;
 
1102
 
 
1103
  /* Search the gamma table for the entry with the lowest contrast variance. */
 
1104
  for (gamma = GMS_GAMMA_TABLE; gamma->level; gamma++)
 
1105
    {
 
1106
      long variance;
 
1107
 
 
1108
      /* Find the color contrast variance of the palette at this gamma. */
 
1109
      variance = gms_graphics_contrast_variance (palette, color_usage, gamma);
 
1110
 
 
1111
      /*
 
1112
       * Compare the variance to the lowest so far, and if it is lower, note
 
1113
       * the gamma entry that produced it as being the current best found.
 
1114
       */
 
1115
      if (variance < lowest_variance)
 
1116
        {
 
1117
          result = gamma;
 
1118
          lowest_variance = variance;
 
1119
        }
 
1120
    }
 
1121
 
 
1122
  assert (result);
 
1123
  return result;
 
1124
}
 
1125
 
 
1126
 
 
1127
/*
 
1128
 * gms_graphics_select_gamma()
 
1129
 *
 
1130
 * Select a suitable gamma for the picture, based on the current gamma mode.
 
1131
 *
 
1132
 * The function returns either the linear gamma, a gamma value half way
 
1133
 * between linear and the gamma that gives the most even contrast, or just
 
1134
 * the gamma that gives the most even contrast.
 
1135
 *
 
1136
 * In the normal case, a value half way to the extreme case of making color
 
1137
 * contrast equal for all colors is, subjectively, a reasonable value to use.
 
1138
 * The problem cases are the darkest pictures, and selecting this value
 
1139
 * brightens them while at the same time not making them look overbright or
 
1140
 * too "sunny".
 
1141
 */
 
1142
static gms_gammaref_t
 
1143
gms_graphics_select_gamma (type8 bitmap[], type16 width, type16 height,
 
1144
                           type16 palette[])
 
1145
{
 
1146
  static int is_initialized = FALSE;
 
1147
  static gms_gammaref_t linear_gamma = NULL;
 
1148
 
 
1149
  long color_usage[GMS_PALETTE_SIZE];
 
1150
  int color_count;
 
1151
  gms_gammaref_t contrast_gamma;
 
1152
 
 
1153
  /* On first call, find and cache the uncorrected gamma table entry. */
 
1154
  if (!is_initialized)
 
1155
    {
 
1156
      gms_gammaref_t gamma;
 
1157
 
 
1158
      for (gamma = GMS_GAMMA_TABLE; gamma->level; gamma++)
 
1159
        {
 
1160
          if (!gamma->is_corrected)
 
1161
            {
 
1162
              linear_gamma = gamma;
 
1163
              break;
 
1164
            }
 
1165
        }
 
1166
 
 
1167
      is_initialized = TRUE;
 
1168
    }
 
1169
  assert (linear_gamma);
 
1170
 
 
1171
  /*
 
1172
   * Check to see if automated correction is turned off; if it is, return
 
1173
   * the linear gamma.
 
1174
   */
 
1175
  if (gms_gamma_mode == GAMMA_OFF)
 
1176
    return linear_gamma;
 
1177
 
 
1178
  /*
 
1179
   * Get the color usage and count of total colors represented.  For a
 
1180
   * degenerate picture with one color or less, return the linear gamma.
 
1181
   */
 
1182
  gms_graphics_count_colors (bitmap, width, height, &color_count, color_usage);
 
1183
  if (color_count <= 1)
 
1184
    return linear_gamma;
 
1185
 
 
1186
  /*
 
1187
   * Now calculate a gamma setting to give the most equal contrast across the
 
1188
   * picture colors.  We'll return either half this gamma, or all of it.
 
1189
   */
 
1190
  contrast_gamma = gms_graphics_equal_contrast_gamma (palette, color_usage);
 
1191
 
 
1192
  /*
 
1193
   * For normal automated correction, return a gamma value half way between
 
1194
   * the linear gamma and the equal contrast gamma.
 
1195
   */
 
1196
  if (gms_gamma_mode == GAMMA_NORMAL)
 
1197
    return linear_gamma + (contrast_gamma - linear_gamma) / 2;
 
1198
 
 
1199
  /* Correction must be high; return the equal contrast gamma. */
 
1200
  assert (gms_gamma_mode == GAMMA_HIGH);
 
1201
  return contrast_gamma;
 
1202
}
 
1203
 
 
1204
 
 
1205
/*
 
1206
 * gms_graphics_clear_and_border()
 
1207
 *
 
1208
 * Clear the graphics window, and border and shade the area where the
 
1209
 * picture is going to be rendered.  This attempts a small raised effect
 
1210
 * for the picture, in keeping with modern trends.
 
1211
 */
 
1212
static void
 
1213
gms_graphics_clear_and_border (winid_t glk_window, int x_offset, int y_offset,
 
1214
                               int pixel_size, type16 width, type16 height)
 
1215
{
 
1216
  glui32 background, fade_color, shading_color;
 
1217
  gms_rgb_t rgb_background, rgb_border, rgb_fade;
 
1218
  int index;
 
1219
  assert (glk_window);
 
1220
 
 
1221
  /*
 
1222
   * Try to detect the background color of the main window, by getting the
 
1223
   * background for Normal style (Glk offers no way to directly get a window's
 
1224
   * background color).  If we can get it, we'll match the graphics window
 
1225
   * background to it.  If we can't, we'll default the color to white.
 
1226
   */
 
1227
  if (!glk_style_measure (gms_main_window,
 
1228
                          style_Normal, stylehint_BackColor, &background))
 
1229
    {
 
1230
      /*
 
1231
       * Unable to get the main window background, so assume, and default
 
1232
       * graphics to white.
 
1233
       */
 
1234
      background = GMS_GRAPHICS_DEFAULT_BACKGROUND;
 
1235
    }
 
1236
 
 
1237
  /*
 
1238
   * Set the graphics window background to match the main window background,
 
1239
   * as best as we can tell, and clear the window.
 
1240
   */
 
1241
  glk_window_set_background_color (glk_window, background);
 
1242
  glk_window_clear (glk_window);
 
1243
 
 
1244
  /*
 
1245
   * For very small pictures, just border them, but don't try and do any
 
1246
   * shading.  Failing this check is probably highly unlikely.
 
1247
   */
 
1248
  if (width < 2 * GMS_GRAPHICS_SHADE_STEPS
 
1249
      || height < 2 * GMS_GRAPHICS_SHADE_STEPS)
 
1250
    {
 
1251
      /* Paint a rectangle bigger than the picture by border pixels. */
 
1252
      glk_window_fill_rect (glk_window,
 
1253
                            GMS_GRAPHICS_BORDER_COLOR,
 
1254
                            x_offset - GMS_GRAPHICS_BORDER,
 
1255
                            y_offset - GMS_GRAPHICS_BORDER,
 
1256
                            width * pixel_size + GMS_GRAPHICS_BORDER * 2,
 
1257
                            height * pixel_size + GMS_GRAPHICS_BORDER * 2);
 
1258
      return;
 
1259
    }
 
1260
 
 
1261
  /*
 
1262
   * Paint a rectangle bigger than the picture by border pixels all round,
 
1263
   * and with additional shading pixels right and below.  Some of these
 
1264
   * shading pixels are later overwritten by the fading loop below.  The
 
1265
   * picture will sit over this rectangle.
 
1266
   */
 
1267
  glk_window_fill_rect (glk_window,
 
1268
                        GMS_GRAPHICS_BORDER_COLOR,
 
1269
                        x_offset - GMS_GRAPHICS_BORDER,
 
1270
                        y_offset - GMS_GRAPHICS_BORDER,
 
1271
                        width * pixel_size + GMS_GRAPHICS_BORDER * 2
 
1272
                          + GMS_GRAPHICS_SHADING,
 
1273
                        height * pixel_size + GMS_GRAPHICS_BORDER * 2
 
1274
                          + GMS_GRAPHICS_SHADING);
 
1275
 
 
1276
  /*
 
1277
   * Split the main window background color and the border color into
 
1278
   * components.
 
1279
   */
 
1280
  gms_graphics_split_color (background, &rgb_background);
 
1281
  gms_graphics_split_color (GMS_GRAPHICS_BORDER_COLOR, &rgb_border);
 
1282
 
 
1283
  /*
 
1284
   * Generate the incremental color to use in fade steps.  Here we're
 
1285
   * assuming that the border is always darker than the main window
 
1286
   * background (currently valid, as we're using black).
 
1287
   */
 
1288
  rgb_fade.red = (rgb_background.red - rgb_border.red)
 
1289
                 / GMS_GRAPHICS_SHADE_STEPS;
 
1290
  rgb_fade.green = (rgb_background.green - rgb_border.green)
 
1291
                 / GMS_GRAPHICS_SHADE_STEPS;
 
1292
  rgb_fade.blue = (rgb_background.blue - rgb_border.blue)
 
1293
                 / GMS_GRAPHICS_SHADE_STEPS;
 
1294
 
 
1295
  /* Combine RGB fade into a single incremental Glk color. */
 
1296
  fade_color = gms_graphics_combine_color (&rgb_fade);
 
1297
 
 
1298
  /* Fade in edge, from background to border, shading in stages. */
 
1299
  shading_color = background;
 
1300
  for (index = 0; index < GMS_GRAPHICS_SHADE_STEPS; index++)
 
1301
    {
 
1302
      /* Shade the two border areas with this color. */
 
1303
      glk_window_fill_rect (glk_window, shading_color,
 
1304
                            x_offset + width * pixel_size
 
1305
                              + GMS_GRAPHICS_BORDER,
 
1306
                            y_offset + index - GMS_GRAPHICS_BORDER,
 
1307
                            GMS_GRAPHICS_SHADING, 1);
 
1308
      glk_window_fill_rect (glk_window, shading_color,
 
1309
                            x_offset + index - GMS_GRAPHICS_BORDER,
 
1310
                            y_offset + height * pixel_size
 
1311
                              + GMS_GRAPHICS_BORDER,
 
1312
                            1, GMS_GRAPHICS_SHADING);
 
1313
 
 
1314
      /* Update the shading color for the fade next iteration. */
 
1315
      shading_color -= fade_color;
 
1316
    }
 
1317
}
 
1318
 
 
1319
 
 
1320
/*
 
1321
 * gms_graphics_convert_palette()
 
1322
 *
 
1323
 * Convert a Magnetic Scrolls color palette to a Glk one, using the given
 
1324
 * gamma corrections.
 
1325
 */
 
1326
static void
 
1327
gms_graphics_convert_palette (type16 ms_palette[], gms_gammaref_t gamma,
 
1328
                              glui32 glk_palette[])
 
1329
{
 
1330
  int index;
 
1331
  assert (ms_palette && gamma && glk_palette);
 
1332
 
 
1333
  for (index = 0; index < GMS_PALETTE_SIZE; index++)
 
1334
    {
 
1335
      gms_rgb_t rgb_color;
 
1336
 
 
1337
      /*
 
1338
       * Convert the 16-bit base picture color through gamma to a 32-bit
 
1339
       * RGB color, and combine into a Glk color and store in the Glk palette.
 
1340
       */
 
1341
      gms_graphics_game_to_rgb_color (ms_palette[index], gamma, &rgb_color);
 
1342
      glk_palette[index] = gms_graphics_combine_color (&rgb_color);
 
1343
    }
 
1344
}
 
1345
 
 
1346
 
 
1347
/*
 
1348
 * gms_graphics_position_picture()
 
1349
 *
 
1350
 * Given a picture width and height, return the x and y offsets to center
 
1351
 * this picture in the current graphics window.
 
1352
 */
 
1353
static void
 
1354
gms_graphics_position_picture (winid_t glk_window,
 
1355
                               int pixel_size, type16 width, type16 height,
 
1356
                               int *x_offset, int *y_offset)
 
1357
{
 
1358
  glui32 window_width, window_height;
 
1359
  assert (glk_window && x_offset && y_offset);
 
1360
 
 
1361
  /* Measure the current graphics window dimensions. */
 
1362
  glk_window_get_size (glk_window, &window_width, &window_height);
 
1363
 
 
1364
  /*
 
1365
   * Calculate and return an x and y offset to use on point plotting, so that
 
1366
   * the image centers inside the graphical window.
 
1367
   */
 
1368
  *x_offset = ((int) window_width - width * pixel_size) / 2;
 
1369
  *y_offset = ((int) window_height - height * pixel_size) / 2;
 
1370
}
 
1371
 
 
1372
 
 
1373
/*
 
1374
 * gms_graphics_apply_animation_frame()
 
1375
 *
 
1376
 * Apply a single animation frame to the given off-screen image buffer, using
 
1377
 * the frame bitmap, width, height and mask, the off-screen buffer, and the
 
1378
 * width and height of the main picture.
 
1379
 *
 
1380
 * Note that 'mask' may be NULL, implying that no frame pixel is transparent.
 
1381
 */
 
1382
static void
 
1383
gms_graphics_apply_animation_frame (type8 bitmap[],
 
1384
                                    type16 frame_width, type16 frame_height,
 
1385
                                    type8 mask[], int frame_x, int frame_y,
 
1386
                                    type8 off_screen[], type16 width,
 
1387
                                    type16 height)
 
1388
{
 
1389
  int mask_width, x, y;
 
1390
  type8 mask_hibit;
 
1391
  long frame_row, buffer_row, mask_row;
 
1392
  assert (bitmap && off_screen);
 
1393
 
 
1394
  /*
 
1395
   * It turns out that the mask isn't quite as described in defs.h, and thanks
 
1396
   * to Torbjorn Andersson and his Gtk port of Magnetic for illuminating this.
 
1397
   * The mask is made up of lines of 16-bit words, so the mask width is always
 
1398
   * even.  Here we'll calculate the real width of a mask, and also set a high
 
1399
   * bit for later on.
 
1400
   */
 
1401
  mask_width = (((frame_width - 1) / CHAR_BIT) + 2) & (~1);
 
1402
  mask_hibit = 1 << (CHAR_BIT - 1);
 
1403
 
 
1404
  /*
 
1405
   * Initialize row index components; these are optimizations to avoid the
 
1406
   * need for multiplications in the frame iteration loop.
 
1407
   */
 
1408
  frame_row = 0;
 
1409
  buffer_row = frame_y * width;
 
1410
  mask_row = 0;
 
1411
 
 
1412
  /*
 
1413
   * Iterate over each frame row, clipping where y lies outside the main
 
1414
   * picture area.
 
1415
   */
 
1416
  for (y = 0; y < frame_height; y++)
 
1417
    {
 
1418
      /* Clip if y is outside the main picture area. */
 
1419
      if (y + frame_y < 0 || y + frame_y >= height)
 
1420
        {
 
1421
          /* Update optimization variables as if not clipped. */
 
1422
          frame_row += frame_width;
 
1423
          buffer_row += width;
 
1424
          mask_row += mask_width;
 
1425
          continue;
 
1426
        }
 
1427
 
 
1428
      /* Iterate over each frame column, clipping again. */
 
1429
      for (x = 0; x < frame_width; x++)
 
1430
        {
 
1431
          long frame_index, buffer_index;
 
1432
 
 
1433
          /* Clip if x is outside the main picture area. */
 
1434
          if (x + frame_x < 0 || x + frame_x >= width)
 
1435
            continue;
 
1436
 
 
1437
          /*
 
1438
           * If there's a mask, check the bit associated with this x,y, and
 
1439
           * ignore any transparent pixels.
 
1440
           */
 
1441
          if (mask)
 
1442
            {
 
1443
              type8 mask_byte;
 
1444
 
 
1445
              /* Isolate the mask byte, and test the transparency bit. */
 
1446
              mask_byte = mask[mask_row + (x / CHAR_BIT)];
 
1447
              if ((mask_byte & (mask_hibit >> (x % CHAR_BIT))) != 0)
 
1448
                continue;
 
1449
            }
 
1450
 
 
1451
          /*
 
1452
           * Calculate indexes for this pixel into the frame, and into the
 
1453
           * main off-screen buffer, and transfer the frame pixel into the
 
1454
           * off-screen buffer.
 
1455
           */
 
1456
          frame_index = frame_row + x;
 
1457
          buffer_index = buffer_row + x + frame_x;
 
1458
          off_screen[buffer_index] = bitmap[frame_index];
 
1459
        }
 
1460
 
 
1461
      /* Update row index components on change of y. */
 
1462
      frame_row += frame_width;
 
1463
      buffer_row += width;
 
1464
      mask_row += mask_width;
 
1465
    }
 
1466
}
 
1467
 
 
1468
 
 
1469
/*
 
1470
 * gms_graphics_animate()
 
1471
 *
 
1472
 * This function finds and applies the next set of animation frames to the
 
1473
 * given off-screen image buffer.  It's handed the width and height of the
 
1474
 * main picture, and the off-screen buffer.
 
1475
 *
 
1476
 * It returns FALSE if at the end of animations, TRUE if more animations
 
1477
 * remain.
 
1478
 */
 
1479
static int
 
1480
gms_graphics_animate (type8 off_screen[], type16 width, type16 height)
 
1481
{
 
1482
  struct ms_position *positions;
 
1483
  type16 count;
 
1484
  type8 status;
 
1485
  int frame;
 
1486
  assert (off_screen);
 
1487
 
 
1488
  /* Search for more animation frames, and return zero if none. */
 
1489
  status = ms_animate (&positions, &count);
 
1490
  if (status == 0)
 
1491
    return FALSE;
 
1492
 
 
1493
  /* Apply each animation frame to the off-screen buffer. */
 
1494
  for (frame = 0; frame < count; frame++)
 
1495
    {
 
1496
      type8 *bitmap, *mask;
 
1497
      type16 frame_width, frame_height;
 
1498
 
 
1499
      /*
 
1500
       * Get the bitmap and other details for this frame.  If we can't get
 
1501
       * this animation frame, skip it and see if any others are available.
 
1502
       */
 
1503
      bitmap = ms_get_anim_frame (positions[frame].number,
 
1504
                                  &frame_width, &frame_height, &mask);
 
1505
      if (bitmap)
 
1506
        {
 
1507
          gms_graphics_apply_animation_frame (bitmap,
 
1508
                                              frame_width, frame_height, mask,
 
1509
                                              positions[frame].x,
 
1510
                                              positions[frame].y,
 
1511
                                              off_screen, width, height);
 
1512
        }
 
1513
    }
 
1514
 
 
1515
  /* Return TRUE since more animation frames remain. */
 
1516
  return TRUE;
 
1517
}
 
1518
 
 
1519
 
 
1520
/*
 
1521
 * gms_graphics_is_vertex()
 
1522
 *
 
1523
 * Given a point, return TRUE if that point is the vertex of a fillable
 
1524
 * region.  This is a helper function for layering pictures.  When assign-
 
1525
 * ing layers, we want to weight the colors that have the most complex
 
1526
 * shapes, or the largest count of isolated areas, heavier than simpler
 
1527
 * areas.
 
1528
 *
 
1529
 * By painting the colors with the largest number of isolated areas or
 
1530
 * the most complex shapes first, we help to minimize the number of fill
 
1531
 * regions needed to render the complete picture.
 
1532
 */
 
1533
static int
 
1534
gms_graphics_is_vertex (type8 off_screen[], type16 width, type16 height,
 
1535
                        int x, int y)
 
1536
{
 
1537
  type8 pixel;
 
1538
  int above, below, left, right;
 
1539
  long index_row;
 
1540
  assert (off_screen);
 
1541
 
 
1542
  /* Use an index row to cut down on multiplications. */
 
1543
  index_row = y * width;
 
1544
 
 
1545
  /* Find the color of the reference pixel. */
 
1546
  pixel = off_screen[index_row + x];
 
1547
  assert (pixel < GMS_PALETTE_SIZE);
 
1548
 
 
1549
  /*
 
1550
   * Detect differences between the reference pixel and its upper, lower, left
 
1551
   * and right neighbors.  Mark as different if the neighbor doesn't exist,
 
1552
   * that is, at the edge of the picture.
 
1553
   */
 
1554
  above = (y == 0 || off_screen[index_row - width + x] != pixel);
 
1555
  below = (y == height - 1 || off_screen[index_row + width + x] != pixel);
 
1556
  left  = (x == 0 || off_screen[index_row + x - 1] != pixel);
 
1557
  right = (x == width - 1 || off_screen[index_row + x + 1] != pixel);
 
1558
 
 
1559
  /*
 
1560
   * Return TRUE if this pixel lies at the vertex of a rectangular, fillable,
 
1561
   * area.  That is, if two adjacent neighbors aren't the same color (or if
 
1562
   * absent -- at the edge of the picture).
 
1563
   */
 
1564
  return ((above || below) && (left || right));
 
1565
}
 
1566
 
 
1567
 
 
1568
/*
 
1569
 * gms_graphics_compare_layering_inverted()
 
1570
 * gms_graphics_assign_layers()
 
1571
 *
 
1572
 * Given two sets of image bitmaps, and a palette, this function will
 
1573
 * assign layers palette colors.
 
1574
 *
 
1575
 * Layers are assigned by first counting the number of vertices in the
 
1576
 * color plane, to get a measure of the complexity of shapes displayed in
 
1577
 * this color, and also the raw number of times each palette color is
 
1578
 * used.  This is then sorted, so that layers are assigned to colors, with
 
1579
 * the lowest layer being the color with the most complex shapes, and
 
1580
 * within this (or where the count of vertices is zero, as it could be
 
1581
 * in some animation frames) the most used color.
 
1582
 *
 
1583
 * The function compares pixels in the two image bitmaps given, these
 
1584
 * being the off-screen and on-screen buffers, and generates counts only
 
1585
 * where these bitmaps differ.  This ensures that only pixels not yet
 
1586
 * painted are included in layering.
 
1587
 *
 
1588
 * As well as assigning layers, this function returns a set of layer usage
 
1589
 * flags, to help the rendering loop to terminate as early as possible.
 
1590
 *
 
1591
 * By painting lower layers first, the paint can take in larger areas if
 
1592
 * it's permitted to include not-yet-validated higher levels.  This helps
 
1593
 * minimize the amount of Glk areas fills needed to render a picture.
 
1594
 */
 
1595
typedef struct {
 
1596
  long complexity;  /* Count of vertices for this color. */
 
1597
  long usage;       /* Color usage count. */
 
1598
  int color;        /* Color index into palette. */
 
1599
} gms_layering_t;
 
1600
 
 
1601
static int
 
1602
gms_graphics_compare_layering_inverted (const void *void_first,
 
1603
                                        const void *void_second)
 
1604
{
 
1605
  gms_layering_t * first = (gms_layering_t *) void_first;
 
1606
  gms_layering_t * second = (gms_layering_t *) void_second;
 
1607
 
 
1608
  /*
 
1609
   * Order by complexity first, then by usage, putting largest first.  Some
 
1610
   * colors may have no vertices at all when doing animation frames, but
 
1611
   * rendering optimization relies on the first layer that contains no areas
 
1612
   * to fill halting the rendering loop.  So it's important here that we order
 
1613
   * indexes so that colors that render complex shapes come first, non-empty,
 
1614
   * but simpler shaped colors next, and finally all genuinely empty layers.
 
1615
   */
 
1616
  return second->complexity > first->complexity ? 1 :
 
1617
         first->complexity > second->complexity ? -1 :
 
1618
         second->usage > first->usage ? 1 :
 
1619
         first->usage > second->usage ? -1 : 0;
 
1620
}
 
1621
 
 
1622
static void
 
1623
gms_graphics_assign_layers (type8 off_screen[], type8 on_screen[],
 
1624
                            type16 width, type16 height,
 
1625
                            int layers[], long layer_usage[])
 
1626
{
 
1627
  int index, x, y;
 
1628
  long index_row;
 
1629
  gms_layering_t layering[GMS_PALETTE_SIZE];
 
1630
  assert (off_screen && on_screen && layers && layer_usage);
 
1631
 
 
1632
  /* Clear initial complexity and usage counts, and set initial colors. */
 
1633
  for (index = 0; index < GMS_PALETTE_SIZE; index++)
 
1634
    {
 
1635
      layering[index].complexity = 0;
 
1636
      layering[index].usage = 0;
 
1637
      layering[index].color = index;
 
1638
    }
 
1639
 
 
1640
  /*
 
1641
   * Traverse the image, counting vertices and pixel usage where the pixels
 
1642
   * differ between the off-screen and on-screen buffers.  Optimize by
 
1643
   * maintaining an index row to avoid multiplications.
 
1644
   */
 
1645
  for (y = 0, index_row = 0; y < height; y++, index_row += width)
 
1646
    {
 
1647
      for (x = 0; x < width; x++)
 
1648
        {
 
1649
          long index;
 
1650
 
 
1651
          /*
 
1652
           * Get the index for this pixel, and update complexity and usage
 
1653
           * if off-screen and on-screen pixels differ.
 
1654
           */
 
1655
          index = index_row + x;
 
1656
          if (on_screen[index] != off_screen[index])
 
1657
            {
 
1658
              if (gms_graphics_is_vertex (off_screen, width, height, x, y))
 
1659
                layering[off_screen[index]].complexity++;
 
1660
 
 
1661
              layering[off_screen[index]].usage++;
 
1662
            }
 
1663
        }
 
1664
    }
 
1665
 
 
1666
  /*
 
1667
   * Sort counts to form color indexes.  The primary sort is on the shape
 
1668
   * complexity, and within this, on color usage.
 
1669
   */
 
1670
  qsort (layering, GMS_PALETTE_SIZE,
 
1671
         sizeof (*layering), gms_graphics_compare_layering_inverted);
 
1672
 
 
1673
  /*
 
1674
   * Assign a layer to each palette color, and also return the layer usage
 
1675
   * for each layer.
 
1676
   */
 
1677
  for (index = 0; index < GMS_PALETTE_SIZE; index++)
 
1678
    {
 
1679
      layers[layering[index].color] = index;
 
1680
      layer_usage[index] = layering[index].usage;
 
1681
    }
 
1682
}
 
1683
 
 
1684
 
 
1685
/*
 
1686
 * gms_graphics_paint_region()
 
1687
 *
 
1688
 * This is a partially optimized point plot.  Given a point in the graphics
 
1689
 * bitmap, it tries to extend the point to a color region, and fill a number
 
1690
 * of pixels in a single Glk rectangle fill.  The goal here is to reduce the
 
1691
 * number of Glk rectangle fills, which tend to be extremely inefficient
 
1692
 * operations for generalized point plotting.
 
1693
 *
 
1694
 * The extension works in image layers; each palette color is assigned a
 
1695
 * layer, and we paint each layer individually, starting at the lowest.  So,
 
1696
 * the region is free to fill any invalidated pixel in a higher layer, and
 
1697
 * all pixels, invalidated or already validated, in the same layer.  In
 
1698
 * practice, it is good enough to look for either invalidated pixels or pixels
 
1699
 * in the same layer, and construct a region as large as possible from these,
 
1700
 * then on marking points as validated, mark only those in the same layer as
 
1701
 * the initial point.
 
1702
 *
 
1703
 * The optimization here is not the best possible, but is reasonable.  What
 
1704
 * we do is to try and stretch the region horizontally first, then vertically.
 
1705
 * In practice, we might find larger areas by stretching vertically and then
 
1706
 * horizontally, or by stretching both dimensions at the same time.  In
 
1707
 * mitigation, the number of colors in a picture is small (16), and the
 
1708
 * aspect ratio of pictures makes them generally wider than they are tall.
 
1709
 *
 
1710
 * Once we've found the region, we render it with a single Glk rectangle fill,
 
1711
 * and mark all the pixels in this region that match the layer of the initial
 
1712
 * given point as validated.
 
1713
 */
 
1714
static void
 
1715
gms_graphics_paint_region (winid_t glk_window, glui32 palette[], int layers[],
 
1716
                           type8 off_screen[], type8 on_screen[],
 
1717
                           int x, int y, int x_offset, int y_offset,
 
1718
                           int pixel_size, type16 width, type16 height)
 
1719
{
 
1720
  type8 pixel;
 
1721
  int layer, x_min, x_max, y_min, y_max, x_index, y_index;
 
1722
  long index_row;
 
1723
  assert (glk_window && palette && layers && off_screen && on_screen);
 
1724
 
 
1725
  /* Find the color and layer for the initial pixel. */
 
1726
  pixel = off_screen[y * width + x];
 
1727
  layer = layers[pixel];
 
1728
  assert (pixel < GMS_PALETTE_SIZE);
 
1729
 
 
1730
  /*
 
1731
   * Start by finding the extent to which we can pull the x coordinate and
 
1732
   * still find either invalidated pixels, or pixels in this layer.
 
1733
   *
 
1734
   * Use an index row to remove multiplications from the loops.
 
1735
   */
 
1736
  index_row = y * width;
 
1737
  for (x_min = x; x_min - 1 >= 0; x_min--)
 
1738
    {
 
1739
      long index = index_row + x_min - 1;
 
1740
 
 
1741
      if (on_screen[index] == off_screen[index]
 
1742
          && layers[off_screen[index]] != layer)
 
1743
        break;
 
1744
    }
 
1745
  for (x_max = x; x_max + 1 < width; x_max++)
 
1746
    {
 
1747
      long index = index_row + x_max + 1;
 
1748
 
 
1749
      if (on_screen[index] == off_screen[index]
 
1750
          && layers[off_screen[index]] != layer)
 
1751
        break;
 
1752
    }
 
1753
 
 
1754
  /*
 
1755
   * Now try to stretch the height of the region, by extending the y
 
1756
   * coordinate as much as possible too.  Again, we're looking for pixels
 
1757
   * that are invalidated or ones in the same layer.  We need to check
 
1758
   * across the full width of the current region.
 
1759
   *
 
1760
   * As above, an index row removes multiplications from the loops.
 
1761
   */
 
1762
  for (y_min = y, index_row = (y - 1) * width;
 
1763
       y_min - 1 >= 0; y_min--, index_row -= width)
 
1764
    {
 
1765
      for (x_index = x_min; x_index <= x_max; x_index++)
 
1766
        {
 
1767
          long index = index_row + x_index;
 
1768
 
 
1769
          if (on_screen[index] == off_screen[index]
 
1770
              && layers[off_screen[index]] != layer)
 
1771
            goto break_y_min;
 
1772
        }
 
1773
    }
 
1774
break_y_min:
 
1775
 
 
1776
  for (y_max = y, index_row = (y + 1) * width;
 
1777
       y_max + 1 < height; y_max++, index_row += width)
 
1778
    {
 
1779
      for (x_index = x_min; x_index <= x_max; x_index++)
 
1780
        {
 
1781
          long index = index_row + x_index;
 
1782
 
 
1783
          if (on_screen[index] == off_screen[index]
 
1784
              && layers[off_screen[index]] != layer)
 
1785
            goto break_y_max;
 
1786
        }
 
1787
    }
 
1788
break_y_max:
 
1789
 
 
1790
  /* Fill the region using Glk's rectangle fill. */
 
1791
  glk_window_fill_rect (glk_window, palette[pixel],
 
1792
                        x_min * pixel_size + x_offset,
 
1793
                        y_min * pixel_size + y_offset,
 
1794
                        (x_max - x_min + 1) * pixel_size,
 
1795
                        (y_max - y_min + 1) * pixel_size);
 
1796
 
 
1797
  /*
 
1798
   * Validate each pixel in the reference layer that was rendered by the
 
1799
   * rectangle fill.  We don't validate pixels that are not in this layer
 
1800
   * (and are by definition in higher layers, as we've validated all lower
 
1801
   * layers), since although we colored them, we did it for optimization
 
1802
   * reasons, and they're not yet colored correctly.
 
1803
   *
 
1804
   * Maintain an index row as an optimization to avoid multiplication.
 
1805
   */
 
1806
  index_row = y_min * width;
 
1807
  for (y_index = y_min; y_index <= y_max; y_index++)
 
1808
    {
 
1809
      for (x_index = x_min; x_index <= x_max; x_index++)
 
1810
        {
 
1811
          long index;
 
1812
 
 
1813
          /*
 
1814
           * Get the index for x_index,y_index.  If the layers match, update
 
1815
           * the on-screen buffer.
 
1816
           */
 
1817
          index = index_row + x_index;
 
1818
          if (layers[off_screen[index]] == layer)
 
1819
            {
 
1820
              assert (off_screen[index] == pixel);
 
1821
              on_screen[index] = off_screen[index];
 
1822
            }
 
1823
        }
 
1824
 
 
1825
      /* Update row index component on change of y. */
 
1826
      index_row += width;
 
1827
    }
 
1828
}
 
1829
 
 
1830
static void
 
1831
gms_graphics_paint_everything (winid_t glk_window,
 
1832
                        glui32 palette[],
 
1833
                        type8 off_screen[],
 
1834
                        int x_offset, int y_offset,
 
1835
                        type16 width, type16 height)
 
1836
{
 
1837
        type8           pixel;                  /* Reference pixel color */
 
1838
        int             x, y;
 
1839
 
 
1840
        for (y = 0; y < height; y++)
 
1841
        {
 
1842
            for (x = 0; x < width; x ++)
 
1843
            {
 
1844
                pixel = off_screen[ y * width + x ];
 
1845
                glk_window_fill_rect (glk_window,
 
1846
                        palette[ pixel ],
 
1847
                        x * GMS_GRAPHICS_PIXEL + x_offset,
 
1848
                        y * GMS_GRAPHICS_PIXEL + y_offset,
 
1849
                        GMS_GRAPHICS_PIXEL, GMS_GRAPHICS_PIXEL);
 
1850
            }
 
1851
        }
 
1852
}
 
1853
 
 
1854
/*
 
1855
 * gms_graphics_timeout()
 
1856
 *
 
1857
 * This is a background function, called on Glk timeouts.  Its job is to
 
1858
 * repaint some of the current graphics image.  On successive calls, it
 
1859
 * does a part of the repaint, then yields to other processing.  This is
 
1860
 * useful since the Glk primitive to plot points in graphical windows is
 
1861
 * extremely slow; this way, the repaint doesn't block game play.
 
1862
 *
 
1863
 * The function should be called on Glk timeout events.  When the repaint
 
1864
 * is complete, the function will turn off Glk timers.
 
1865
 *
 
1866
 * The function uses double-buffering to track how much of the graphics
 
1867
 * buffer has been rendered.  This helps to minimize the amount of point
 
1868
 * plots required, as only the differences between the two buffers need
 
1869
 * to be rendered.
 
1870
 */
 
1871
static void
 
1872
gms_graphics_timeout (void)
 
1873
{
 
1874
  static glui32 palette[GMS_PALETTE_SIZE];   /* Precomputed Glk palette */
 
1875
  static int layers[GMS_PALETTE_SIZE];       /* Assigned image layers */
 
1876
  static long layer_usage[GMS_PALETTE_SIZE]; /* Image layer occupancies */
 
1877
 
 
1878
  static int deferred_repaint = FALSE;       /* Local delayed repaint flag */
 
1879
  static int ignore_counter;                 /* Count of calls ignored */
 
1880
 
 
1881
  static int x_offset, y_offset;             /* Point plot offsets */
 
1882
  static int yield_counter;                  /* Yields in rendering */
 
1883
  static int saved_layer;                    /* Saved current layer */
 
1884
  static int saved_x, saved_y;               /* Saved x,y coord */
 
1885
 
 
1886
  static int total_regions;                  /* Debug statistic */
 
1887
 
 
1888
  type8 *on_screen;                          /* On-screen image buffer */
 
1889
  type8 *off_screen;                         /* Off-screen image buffer */
 
1890
  long picture_size;                         /* Picture size in pixels */
 
1891
  int layer;                                 /* Image layer iterator */
 
1892
  int x, y;                                  /* Image iterators */
 
1893
  int regions;                               /* Count of regions painted */
 
1894
 
 
1895
  /* Ignore the call if the current graphics state is inactive. */
 
1896
  if (!gms_graphics_active)
 
1897
    return;
 
1898
  assert (gms_graphics_window);
 
1899
 
 
1900
  /*
 
1901
   * On detecting a repaint request, note the flag in a local static variable,
 
1902
   * then set up a graphics delay to wait until, hopefully, the resize, if
 
1903
   * that's what caused it, is complete, and return.  This makes resizing the
 
1904
   * window a lot smoother, since it prevents unnecessary region paints where
 
1905
   * we are receiving consecutive Glk arrange or redraw events.
 
1906
   */
 
1907
  if (gms_graphics_repaint)
 
1908
    {
 
1909
      deferred_repaint = TRUE;
 
1910
      gms_graphics_repaint = FALSE;
 
1911
      ignore_counter = GMS_GRAPHICS_REPAINT_WAIT - 1;
 
1912
      return;
 
1913
    }
 
1914
 
 
1915
  /*
 
1916
   * If asked to ignore a given number of calls, decrement the ignore counter
 
1917
   * and return having done nothing more.  This lets us delay graphics
 
1918
   * operations by a number of timeouts, providing animation timing and
 
1919
   * partial protection from resize event "storms".
 
1920
   *
 
1921
   * Note -- to wait for N timeouts, set the count of timeouts to be ignored
 
1922
   * to N-1.
 
1923
   */
 
1924
  assert (ignore_counter >= 0);
 
1925
  if (ignore_counter > 0)
 
1926
    {
 
1927
      ignore_counter--;
 
1928
      return;
 
1929
    }
 
1930
 
 
1931
  /* Calculate the picture size, and synchronize screen buffer pointers. */
 
1932
  picture_size = gms_graphics_width * gms_graphics_height;
 
1933
  off_screen = gms_graphics_off_screen;
 
1934
  on_screen = gms_graphics_on_screen;
 
1935
 
 
1936
  /*
 
1937
   * If we received a new picture, set up the local static variables for that
 
1938
   * picture -- decide on gamma correction, convert the color palette, and
 
1939
   * initialize the off_screen buffer to be the base picture.
 
1940
   */
 
1941
  if (gms_graphics_new_picture)
 
1942
    {
 
1943
      /* Initialize the off_screen buffer to be a copy of the base picture. */
 
1944
      free (off_screen);
 
1945
      off_screen = gms_malloc (picture_size * sizeof (*off_screen));
 
1946
      memcpy (off_screen, gms_graphics_bitmap,
 
1947
              picture_size * sizeof (*off_screen));
 
1948
 
 
1949
      /* Note the buffer for freeing on cleanup. */
 
1950
      gms_graphics_off_screen = off_screen;
 
1951
 
 
1952
      /*
 
1953
       * If the picture is animated, apply the first animation frames now.
 
1954
       * This is important, since they form an intrinsic part of the first
 
1955
       * displayed image (in type2 animation cases, perhaps _all_ of the
 
1956
       * first displayed image).
 
1957
       */
 
1958
      if (gms_graphics_animated)
 
1959
        {
 
1960
          gms_graphics_animate (off_screen,
 
1961
                                gms_graphics_width, gms_graphics_height);
 
1962
        }
 
1963
 
 
1964
      /*
 
1965
       * Select a suitable gamma for the picture, taking care to use the
 
1966
       * off-screen buffer.
 
1967
       */
 
1968
      gms_graphics_current_gamma =
 
1969
          gms_graphics_select_gamma (off_screen,
 
1970
                                     gms_graphics_width,
 
1971
                                     gms_graphics_height,
 
1972
                                     gms_graphics_palette);
 
1973
 
 
1974
      /*
 
1975
       * Pre-convert all the picture palette colors into their corresponding
 
1976
       * Glk colors.
 
1977
       */
 
1978
      gms_graphics_convert_palette (gms_graphics_palette,
 
1979
                                    gms_graphics_current_gamma, palette);
 
1980
 
 
1981
      /* Save the color count for possible queries later. */
 
1982
      gms_graphics_count_colors (off_screen,
 
1983
                                 gms_graphics_width, gms_graphics_height,
 
1984
                                 &gms_graphics_color_count, NULL);
 
1985
    }
 
1986
 
 
1987
  /*
 
1988
   * For a new picture, or a repaint of a prior one, calculate new values for
 
1989
   * the x and y offsets used to draw image points, and set the on-screen
 
1990
   * buffer to an unused pixel value, in effect invalidating all on-screen
 
1991
   * data.  Also, reset the saved image scan coordinates so that we scan for
 
1992
   * unpainted pixels from top left starting at layer zero, and clear the
 
1993
   * graphics window.
 
1994
   */
 
1995
  if (gms_graphics_new_picture || deferred_repaint)
 
1996
    {
 
1997
      /*
 
1998
       * Calculate the x and y offset to center the picture in the graphics
 
1999
       * window.
 
2000
       */
 
2001
      gms_graphics_position_picture (gms_graphics_window,
 
2002
                                     GMS_GRAPHICS_PIXEL,
 
2003
                                     gms_graphics_width, gms_graphics_height,
 
2004
                                     &x_offset, &y_offset);
 
2005
 
 
2006
      /*
 
2007
       * Reset all on-screen pixels to an unused value, guaranteed not to
 
2008
       * match any in a real picture.  This forces all pixels to be repainted
 
2009
       * on a buffer/on-screen comparison.
 
2010
       */
 
2011
      free (on_screen);
 
2012
      on_screen = gms_malloc (picture_size * sizeof (*on_screen));
 
2013
      memset (on_screen, GMS_GRAPHICS_UNUSED_PIXEL,
 
2014
              picture_size * sizeof (*on_screen));
 
2015
 
 
2016
      /* Note the buffer for freeing on cleanup. */
 
2017
      gms_graphics_on_screen = on_screen;
 
2018
 
 
2019
      /*
 
2020
       * Assign new layers to the current image.  This sorts colors by usage
 
2021
       * and puts the most used colors in the lower layers.  It also hands us
 
2022
       * a count of pixels in each layer, useful for knowing when to stop
 
2023
       * scanning for layers in the rendering loop.
 
2024
       */
 
2025
#ifndef GARGLK
 
2026
      gms_graphics_assign_layers (off_screen, on_screen,
 
2027
                                  gms_graphics_width, gms_graphics_height,
 
2028
                                  layers, layer_usage);
 
2029
#endif
 
2030
 
 
2031
      /* Clear the graphics window. */
 
2032
      gms_graphics_clear_and_border (gms_graphics_window,
 
2033
                                     x_offset, y_offset,
 
2034
                                     GMS_GRAPHICS_PIXEL,
 
2035
                                     gms_graphics_width, gms_graphics_height);
 
2036
 
 
2037
      /* Start a fresh picture rendering pass. */
 
2038
      yield_counter = 0;
 
2039
      saved_layer = 0;
 
2040
      saved_x = 0;
 
2041
      saved_y = 0;
 
2042
      total_regions = 0;
 
2043
 
 
2044
      /* Clear the new picture and deferred repaint flags. */
 
2045
      gms_graphics_new_picture = FALSE;
 
2046
      deferred_repaint = FALSE;
 
2047
    }
 
2048
 
 
2049
#ifndef GARGLK
 
2050
  /*
 
2051
   * Make a portion of an image pass, from lower to higher image layers,
 
2052
   * scanning for invalidated pixels that are in the current image layer we
 
2053
   * are painting.  Each invalidated pixel gives rise to a region paint,
 
2054
   * which equates to one Glk rectangle fill.
 
2055
   *
 
2056
   * When the limit on regions is reached, save the current image pass layer
 
2057
   * and coordinates, and yield control to the main game playing code by
 
2058
   * returning.  On the next call, pick up where we left off.
 
2059
   *
 
2060
   * As an optimization, we can leave the loop on the first empty layer we
 
2061
   * encounter.  Since layers are ordered by complexity and color usage, all
 
2062
   * layers higher than the first unused one will also be empty, so we don't
 
2063
   * need to scan them.
 
2064
   */
 
2065
  regions = 0;
 
2066
  for (layer = saved_layer;
 
2067
       layer < GMS_PALETTE_SIZE && layer_usage[layer] > 0; layer++)
 
2068
    {
 
2069
      long index_row;
 
2070
 
 
2071
      /*
 
2072
       * As an optimization to avoid multiplications in the loop, maintain a
 
2073
       * separate index row.
 
2074
       */
 
2075
      index_row = saved_y * gms_graphics_width;
 
2076
      for (y = saved_y; y < gms_graphics_height; y++)
 
2077
        {
 
2078
          for (x = saved_x; x < gms_graphics_width; x++)
 
2079
            {
 
2080
              long index;
 
2081
 
 
2082
              /* Get the index for this pixel. */
 
2083
              index = index_row + x;
 
2084
              assert (index < picture_size * sizeof (*off_screen));
 
2085
 
 
2086
              /*
 
2087
               * Ignore pixels not in the current layer, and pixels not
 
2088
               * currently invalid (that is, ones whose on-screen represen-
 
2089
               * tation matches the off-screen buffer).
 
2090
               */
 
2091
              if (layers[off_screen[index]] == layer
 
2092
                  && on_screen[index] != off_screen[index])
 
2093
                {
 
2094
                  /*
 
2095
                   * Rather than painting just one pixel, here we try to
 
2096
                   * paint the maximal region we can for the layer of the
 
2097
                   * given pixel.
 
2098
                   */
 
2099
                  gms_graphics_paint_region (gms_graphics_window,
 
2100
                                             palette, layers,
 
2101
                                             off_screen, on_screen,
 
2102
                                             x, y, x_offset, y_offset,
 
2103
                                             GMS_GRAPHICS_PIXEL,
 
2104
                                             gms_graphics_width,
 
2105
                                             gms_graphics_height);
 
2106
 
 
2107
                  /*
 
2108
                   * Increment count of regions handled, and yield, by
 
2109
                   * returning, if the limit on paint regions is reached.
 
2110
                   * Before returning, save the current layer and scan
 
2111
                   * coordinates, so we can pick up here on the next call.
 
2112
                   */
 
2113
                  regions++;
 
2114
                  if (regions >= GMS_REPAINT_LIMIT)
 
2115
                    {
 
2116
                      yield_counter++;
 
2117
                      saved_layer = layer;
 
2118
                      saved_x = x;
 
2119
                      saved_y = y;
 
2120
                      total_regions += regions;
 
2121
                      return;
 
2122
                    }
 
2123
                }
 
2124
            }
 
2125
 
 
2126
          /* Reset the saved x coordinate on y increment. */
 
2127
          saved_x = 0;
 
2128
 
 
2129
          /* Update the index row on change of y. */
 
2130
          index_row += gms_graphics_width;
 
2131
        }
 
2132
 
 
2133
      /* Reset the saved y coordinate on layer change. */
 
2134
      saved_y = 0;
 
2135
    }
 
2136
 
 
2137
  /*
 
2138
   * If we reach this point, then we didn't get to the limit on regions
 
2139
   * painted on this pass.  In that case, we've finished rendering the
 
2140
   * image.
 
2141
   */
 
2142
  assert (regions < GMS_REPAINT_LIMIT);
 
2143
  total_regions += regions;
 
2144
 
 
2145
#else
 
2146
        gms_graphics_paint_everything
 
2147
            (gms_graphics_window,
 
2148
             palette, off_screen,
 
2149
             x_offset, y_offset,
 
2150
             gms_graphics_width,
 
2151
             gms_graphics_height);
 
2152
#endif
 
2153
 
 
2154
  /*
 
2155
   * If animated, and if animations are enabled, handle further animation
 
2156
   * frames, if any.
 
2157
   */
 
2158
  if (gms_animation_enabled && gms_graphics_animated)
 
2159
    {
 
2160
      int more_animation;
 
2161
 
 
2162
      /*
 
2163
       * Reset the off-screen buffer to a copy of the base picture.  This is
 
2164
       * the correct state for applying animation frames.
 
2165
       */
 
2166
      memcpy (off_screen, gms_graphics_bitmap,
 
2167
              picture_size * sizeof (*off_screen));
 
2168
 
 
2169
      /*
 
2170
       * Apply any further animations.  If none, then stop the graphics
 
2171
       * "thread" and return.  There's no more to be done until something
 
2172
       * restarts us.
 
2173
       */
 
2174
      more_animation = gms_graphics_animate (off_screen,
 
2175
                                             gms_graphics_width,
 
2176
                                             gms_graphics_height);
 
2177
      if (!more_animation)
 
2178
        {
 
2179
          /*
 
2180
           * There's one extra wrinkle here.  The base picture we've just put
 
2181
           * into the off-screen buffer isn't really complete (and for type2
 
2182
           * animations, might be pure garbage), so if we happen to get a
 
2183
           * repaint after an animation has ended, the off-screen data we'll
 
2184
           * be painting could well look wrong.
 
2185
           *
 
2186
           * So... here we want to set the off-screen buffer to contain the
 
2187
           * final animation frame.  Fortunately, we still have it in the
 
2188
           * on-screen buffer.
 
2189
           */
 
2190
          memcpy (off_screen, on_screen, picture_size * sizeof (*off_screen));
 
2191
          gms_graphics_stop ();
 
2192
          return;
 
2193
        }
 
2194
 
 
2195
      /*
 
2196
       * Re-assign layers based on animation changes to the off-screen
 
2197
       * buffer.
 
2198
       */
 
2199
#ifndef GARGLK
 
2200
      gms_graphics_assign_layers (off_screen, on_screen,
 
2201
                                  gms_graphics_width, gms_graphics_height,
 
2202
                                  layers, layer_usage);
 
2203
#endif
 
2204
 
 
2205
      /*
 
2206
       * Set up an animation wait, adjusted here by the number of times we
 
2207
       * had to yield while rendering, as we're now that late with animations,
 
2208
       * and capped at zero, as we can't do anything to compensate for being
 
2209
       * too late.  In practice, we're running too close to the edge to have
 
2210
       * much of an effect here, but nevertheless...
 
2211
       */
 
2212
      ignore_counter = GMS_GRAPHICS_ANIMATION_WAIT - 1;
 
2213
      if (yield_counter > ignore_counter)
 
2214
        ignore_counter = 0;
 
2215
      else
 
2216
        ignore_counter -= yield_counter;
 
2217
 
 
2218
      /* Start a fresh picture rendering pass. */
 
2219
      yield_counter = 0;
 
2220
      saved_layer = 0;
 
2221
      saved_x = 0;
 
2222
      saved_y = 0;
 
2223
      total_regions = 0;
 
2224
    }
 
2225
  else
 
2226
    {
 
2227
      /*
 
2228
       * Not an animated picture, so just stop graphics, as again, there's
 
2229
       * no more to be done until something restarts us.
 
2230
       */
 
2231
      gms_graphics_stop ();
 
2232
    }
 
2233
}
 
2234
 
 
2235
 
 
2236
/*
 
2237
 * ms_showpic()
 
2238
 *
 
2239
 * Called by the main interpreter when it wants us to display a picture.
 
2240
 * The function gets the picture bitmap, palette, and dimensions, and
 
2241
 * saves them, and the picture id, in module variables for the background
 
2242
 * rendering function.
 
2243
 *
 
2244
 * The graphics window is opened if required, or closed if mode is zero.
 
2245
 *
 
2246
 * The function checks for changes of actual picture by calculating the
 
2247
 * CRC for picture data; this helps to prevent unnecessary repaints in
 
2248
 * cases where the interpreter passes us the same picture as we're already
 
2249
 * displaying.  There is a less than 1 in 4,294,967,296 chance that a new
 
2250
 * picture will be missed.  We'll live with that.
 
2251
 *
 
2252
 * Why use CRCs, rather than simply storing the values of picture passed in
 
2253
 * a static variable?  Because some games, typically Magnetic Windows, use
 
2254
 * the picture argument as a form of string pointer, and can pass in the
 
2255
 * same value for several, perhaps all, game pictures.  If we just checked
 
2256
 * for a change in the picture argument, we'd never see one.  So we must
 
2257
 * instead look for changes in the real picture data.
 
2258
 */
 
2259
void
 
2260
ms_showpic (type32 picture, type8 mode)
 
2261
{
 
2262
  static glui32 current_crc = 0;  /* CRC of the current picture */
 
2263
 
 
2264
  type8 *bitmap, animated;
 
2265
  type16 width, height, palette[GMS_PALETTE_SIZE];
 
2266
  long picture_bytes;
 
2267
  glui32 crc;
 
2268
 
 
2269
  /* See if the mode indicates no graphics. */
 
2270
  if (mode == 0)
 
2271
    {
 
2272
      /* Note that the interpreter turned graphics off. */
 
2273
      gms_graphics_interpreter = FALSE;
 
2274
 
 
2275
      /*
 
2276
       * If we are currently displaying the graphics window, stop any update
 
2277
       * "thread" and turn off graphics.
 
2278
       */
 
2279
      if (gms_graphics_enabled && gms_graphics_are_displayed ())
 
2280
        {
 
2281
          gms_graphics_stop ();
 
2282
          gms_graphics_close ();
 
2283
        }
 
2284
 
 
2285
      /* Nothing more to do now graphics are off. */
 
2286
      return;
 
2287
    }
 
2288
 
 
2289
  /* Note that the interpreter turned graphics on. */
 
2290
  gms_graphics_interpreter = TRUE;
 
2291
 
 
2292
  /*
 
2293
   * Obtain the image details for the requested picture.  The call returns
 
2294
   * NULL if there's a problem with the picture.
 
2295
   */
 
2296
  bitmap = ms_extract (picture, &width, &height, palette, &animated);
 
2297
  if (!bitmap)
 
2298
    return;
 
2299
 
 
2300
  /* Note the last thing passed to ms_extract, in case of graphics restarts. */
 
2301
  gms_graphics_picture = picture;
 
2302
 
 
2303
  /* Calculate the picture size, and the CRC for the bitmap data. */
 
2304
  picture_bytes = width * height * sizeof (*bitmap);
 
2305
  crc = gms_get_buffer_crc (bitmap, picture_bytes);
 
2306
 
 
2307
  /*
 
2308
   * If there is no change of picture, we might be able to largely ignore the
 
2309
   * call.  Check for a change, and if we don't see one, and if graphics are
 
2310
   * enabled and being displayed, we can safely ignore the call.
 
2311
   */
 
2312
  if (width == gms_graphics_width
 
2313
      && height == gms_graphics_height
 
2314
      && crc == current_crc
 
2315
      && gms_graphics_enabled && gms_graphics_are_displayed ())
 
2316
    return;
 
2317
 
 
2318
  /*
 
2319
   * We know now that this is either a genuine change of picture, or graphics
 
2320
   * were off and have been turned on.  So, record picture details, ensure
 
2321
   * graphics is on, set the flags, and start the background graphics update.
 
2322
   */
 
2323
 
 
2324
  /*
 
2325
   * Save the picture details for the update code.  Here we take a complete
 
2326
   * local copy of the bitmap, since the interpreter core may reuse part of
 
2327
   * its memory for animations.
 
2328
   */
 
2329
  free (gms_graphics_bitmap);
 
2330
  gms_graphics_bitmap = gms_malloc (picture_bytes);
 
2331
  memcpy (gms_graphics_bitmap, bitmap, picture_bytes);
 
2332
  gms_graphics_width = width;
 
2333
  gms_graphics_height = height;
 
2334
  memcpy (gms_graphics_palette, palette, sizeof (palette));
 
2335
  gms_graphics_animated = animated;
 
2336
 
 
2337
  /* Retain the new picture CRC. */
 
2338
  current_crc = crc;
 
2339
 
 
2340
  /*
 
2341
   * If graphics are enabled, ensure the window is displayed, set the
 
2342
   * appropriate flags, and start graphics update.  If they're not enabled,
 
2343
   * the picture details will simply stick around in module variables until
 
2344
   * they are required.
 
2345
   */
 
2346
  if (gms_graphics_enabled)
 
2347
    {
 
2348
      /* 
 
2349
       * Ensure graphics on, then set the new picture flag and start the
 
2350
       * updating "thread".
 
2351
       */
 
2352
      if (gms_graphics_open ())
 
2353
        {
 
2354
          gms_graphics_new_picture = TRUE;
 
2355
          gms_graphics_start ();
 
2356
        }
 
2357
    }
 
2358
}
 
2359
 
 
2360
 
 
2361
/*
 
2362
 * gms_graphics_picture_is_available()
 
2363
 *
 
2364
 * Return TRUE if the graphics module data is loaded with a usable picture,
 
2365
 * FALSE if there is no picture available to display.
 
2366
 */
 
2367
static int
 
2368
gms_graphics_picture_is_available (void)
 
2369
{
 
2370
  return gms_graphics_bitmap != NULL;
 
2371
}
 
2372
 
 
2373
 
 
2374
/*
 
2375
 * gms_graphics_get_picture_details()
 
2376
 *
 
2377
 * Return the width, height, and animation flag of the currently loaded
 
2378
 * picture.  The function returns FALSE if no picture is loaded, otherwise
 
2379
 * TRUE, with picture details in the return arguments.
 
2380
 */
 
2381
static int
 
2382
gms_graphics_get_picture_details (int *width, int *height, int *is_animated)
 
2383
{
 
2384
  if (gms_graphics_picture_is_available ())
 
2385
    {
 
2386
      if (width)
 
2387
        *width = gms_graphics_width;
 
2388
      if (height)
 
2389
        *height = gms_graphics_height;
 
2390
      if (is_animated)
 
2391
        *is_animated = gms_graphics_animated;
 
2392
 
 
2393
      return TRUE;
 
2394
    }
 
2395
 
 
2396
  return FALSE;
 
2397
}
 
2398
 
 
2399
 
 
2400
/*
 
2401
 * gms_graphics_get_rendering_details()
 
2402
 *
 
2403
 * Returns the current level of applied gamma correction, as a string, the
 
2404
 * count of colors in the picture, and a flag indicating if graphics is
 
2405
 * active (busy).  The function return FALSE if graphics is not enabled or
 
2406
 * if not being displayed, otherwise TRUE with the gamma, color count, and
 
2407
 * active flag in the return arguments.
 
2408
 *
 
2409
 * This function races with the graphics timeout, as it returns information
 
2410
 * set up by the first timeout following a new picture.  There's a very,
 
2411
 * very small chance that it might win the race, in which case out-of-date
 
2412
 * gamma and color count values are returned.
 
2413
 */
 
2414
static int
 
2415
gms_graphics_get_rendering_details (const char **gamma, int *color_count,
 
2416
                                    int *is_active)
 
2417
{
 
2418
  if (gms_graphics_enabled && gms_graphics_are_displayed ())
 
2419
    {
 
2420
      /*
 
2421
       * Return the string representing the gamma correction.  If racing
 
2422
       * with timeouts, we might return the gamma for the last picture.
 
2423
       */
 
2424
      if (gamma)
 
2425
        {
 
2426
          assert (gms_graphics_current_gamma);
 
2427
          *gamma = gms_graphics_current_gamma->level;
 
2428
        }
 
2429
 
 
2430
      /*
 
2431
       * Return the color count noted by timeouts on the first timeout
 
2432
       * following a new picture.  Again, we might return the one for
 
2433
       * the prior picture.
 
2434
       */
 
2435
      if (color_count)
 
2436
        *color_count = gms_graphics_color_count;
 
2437
 
 
2438
      /* Return graphics active flag. */
 
2439
      if (is_active)
 
2440
        *is_active = gms_graphics_active;
 
2441
 
 
2442
      return TRUE;
 
2443
    }
 
2444
 
 
2445
  return FALSE;
 
2446
}
 
2447
 
 
2448
 
 
2449
/*
 
2450
 * gms_graphics_interpreter_enabled()
 
2451
 *
 
2452
 * Return TRUE if it looks like interpreter graphics are turned on, FALSE
 
2453
 * otherwise.
 
2454
 */
 
2455
static int
 
2456
gms_graphics_interpreter_enabled (void)
 
2457
{
 
2458
  return gms_graphics_interpreter;
 
2459
}
 
2460
 
 
2461
 
 
2462
/*
 
2463
 * gms_graphics_cleanup()
 
2464
 *
 
2465
 * Free memory resources allocated by graphics functions.  Called on game
 
2466
 * end.
 
2467
 */
 
2468
static void
 
2469
gms_graphics_cleanup (void)
 
2470
{
 
2471
  free (gms_graphics_bitmap);
 
2472
  gms_graphics_bitmap = NULL;
 
2473
  free (gms_graphics_off_screen);
 
2474
  gms_graphics_off_screen = NULL;
 
2475
  free (gms_graphics_on_screen);
 
2476
  gms_graphics_on_screen = NULL;
 
2477
 
 
2478
  gms_graphics_animated = FALSE;
 
2479
  gms_graphics_picture = 0;
 
2480
}
 
2481
 
 
2482
 
 
2483
/*---------------------------------------------------------------------*/
 
2484
/*  Glk port status line functions                                     */
 
2485
/*---------------------------------------------------------------------*/
 
2486
 
 
2487
/*
 
2488
 * The interpreter feeds us status line characters one at a time, with Tab
 
2489
 * indicating right justify, and CR indicating the line is complete.  To get
 
2490
 * this to fit with the Glk event and redraw model, here we'll buffer each
 
2491
 * completed status line, so we have a stable string to output when needed.
 
2492
 * It's also handy to have this buffer for Glk libraries that don't support
 
2493
 * separate windows.
 
2494
 */
 
2495
enum { GMS_STATBUFFER_LENGTH = 1024 };
 
2496
static char gms_status_buffer[GMS_STATBUFFER_LENGTH];
 
2497
static int gms_status_length = 0;
 
2498
 
 
2499
/* Default width used for non-windowing Glk status lines. */
 
2500
static const int GMS_DEFAULT_STATUS_WIDTH = 74;
 
2501
 
 
2502
 
 
2503
/*
 
2504
 * ms_statuschar()
 
2505
 *
 
2506
 * Receive one status character from the interpreter.  Characters are
 
2507
 * buffered internally, and on CR, the buffer is copied to the main static
 
2508
 * status buffer for use by the status line printing function.
 
2509
 */
 
2510
void
 
2511
ms_statuschar (type8 c)
 
2512
{
 
2513
  static char buffer[GMS_STATBUFFER_LENGTH];
 
2514
  static int length = 0;
 
2515
 
 
2516
  /*
 
2517
   * If the status character is newline, transfer locally buffered data to
 
2518
   * the common buffer, empty the local buffer; otherwise, if space permits,
 
2519
   * buffer the character.
 
2520
   */
 
2521
  if (c == '\n')
 
2522
    {
 
2523
      memcpy (gms_status_buffer, buffer, length);
 
2524
      gms_status_length = length;
 
2525
 
 
2526
      length = 0;
 
2527
    }
 
2528
  else
 
2529
    {
 
2530
      if (length < sizeof (buffer))
 
2531
        buffer[length++] = c;
 
2532
    }
 
2533
}
 
2534
 
 
2535
 
 
2536
/*
 
2537
 * gms_status_update()
 
2538
 *
 
2539
 * Update the information in the status window with the current contents of
 
2540
 * the completed status line buffer, or a default string if no completed
 
2541
 * status line.
 
2542
 */
 
2543
static void
 
2544
gms_status_update (void)
 
2545
{
 
2546
  glui32 width, height;
 
2547
  assert (gms_status_window);
 
2548
 
 
2549
  glk_window_get_size (gms_status_window, &width, &height);
 
2550
  if (height > 0)
 
2551
    {
 
2552
      glk_window_clear (gms_status_window);
 
2553
      glk_window_move_cursor (gms_status_window, 0, 0);
 
2554
      glk_set_window (gms_status_window);
 
2555
 
 
2556
      if (gms_status_length > 0)
 
2557
        {
 
2558
          int index;
 
2559
 
 
2560
          /*
 
2561
           * Output each character from the status line buffer.  If the
 
2562
           * character is Tab, position the cursor to eleven characters shy
 
2563
           * of the status window right.
 
2564
           */
 
2565
          for (index = 0; index < gms_status_length; index++)
 
2566
            {
 
2567
              if (gms_status_buffer[index] == '\t')
 
2568
                glk_window_move_cursor (gms_status_window, width - 11, 0);
 
2569
              else
 
2570
                glk_put_char (gms_status_buffer[index]);
 
2571
            }
 
2572
        }
 
2573
      else
 
2574
        {
 
2575
          const char *game_name;
 
2576
 
 
2577
          /*
 
2578
           * We have no status line to display, so print the game's name, or
 
2579
           * a standard message if unable to identify the game.  Having no
 
2580
           * status line is common with Magnetic Windows games, which don't,
 
2581
           * in general, seem to use one.
 
2582
           */
 
2583
          game_name = gms_gameid_get_game_name ();
 
2584
          glk_put_string (game_name ? (char *) game_name
 
2585
                                    : "Glk Magnetic version 2.3");
 
2586
        }
 
2587
 
 
2588
      glk_set_window (gms_main_window);
 
2589
    }
 
2590
}
 
2591
 
 
2592
 
 
2593
/*
 
2594
 * gms_status_print()
 
2595
 *
 
2596
 * Print the current contents of the completed status line buffer out in the
 
2597
 * main window, if it has changed since the last call.  This is for non-
 
2598
 * windowing Glk libraries.
 
2599
 */
 
2600
static void
 
2601
gms_status_print (void)
 
2602
{
 
2603
  static char buffer[GMS_STATBUFFER_LENGTH];
 
2604
  static int length = 0;
 
2605
 
 
2606
  int index, column;
 
2607
 
 
2608
  /*
 
2609
   * Do nothing if there is no status line to print, or if the status
 
2610
   * line hasn't changed since last printed.
 
2611
   */
 
2612
  if (gms_status_length == 0
 
2613
      || (gms_status_length == length
 
2614
          && strncmp (buffer, gms_status_buffer, length)) == 0)
 
2615
    return;
 
2616
 
 
2617
  /* Set fixed width font to try to preserve status line formatting. */
 
2618
  glk_set_style (style_Preformatted);
 
2619
 
 
2620
  /* Bracket, and output the status line buffer. */
 
2621
  glk_put_string ("[ ");
 
2622
  column = 1;
 
2623
  for (index = 0; index < gms_status_length; index++)
 
2624
    {
 
2625
      /*
 
2626
       * If the character is Tab, position the cursor to eleven characters
 
2627
       * shy of the right edge.  In the absence of the real window dimensions,
 
2628
       * we'll select 74 characters, which gives us a 78 character status
 
2629
       * line; pretty standard.
 
2630
       */
 
2631
      if (gms_status_buffer[index] == '\t')
 
2632
        {
 
2633
          while (column <= GMS_DEFAULT_STATUS_WIDTH - 11)
 
2634
            {
 
2635
              glk_put_char (' ');
 
2636
              column++;
 
2637
            }
 
2638
        }
 
2639
      else
 
2640
        {
 
2641
          glk_put_char (gms_status_buffer[index]);
 
2642
          column++;
 
2643
        }
 
2644
    }
 
2645
 
 
2646
  while (column <= GMS_DEFAULT_STATUS_WIDTH)
 
2647
    {
 
2648
      glk_put_char (' ');
 
2649
      column++;
 
2650
    }
 
2651
  glk_put_string (" ]\n");
 
2652
 
 
2653
  /* Save the details of the printed status buffer. */
 
2654
  memcpy (buffer, gms_status_buffer, gms_status_length);
 
2655
  length = gms_status_length;
 
2656
}
 
2657
 
 
2658
 
 
2659
/*
 
2660
 * gms_status_notify()
 
2661
 *
 
2662
 * Front end function for updating status.  Either updates the status window
 
2663
 * or prints the status line to the main window.
 
2664
 */
 
2665
static void
 
2666
gms_status_notify (void)
 
2667
{
 
2668
  if (gms_status_window)
 
2669
    gms_status_update ();
 
2670
  else
 
2671
    gms_status_print ();
 
2672
}
 
2673
 
 
2674
 
 
2675
/*
 
2676
 * gms_status_redraw()
 
2677
 *
 
2678
 * Redraw the contents of any status window with the buffered status string.
 
2679
 * This function should be called on the appropriate Glk window resize and
 
2680
 * arrange events.
 
2681
 */
 
2682
static void
 
2683
gms_status_redraw (void)
 
2684
{
 
2685
  if (gms_status_window)
 
2686
    {
 
2687
      winid_t parent;
 
2688
 
 
2689
      /*
 
2690
       * Rearrange the status window, without changing its actual arrangement
 
2691
       * in any way.  This is a hack to work round incorrect window repainting
 
2692
       * in Xglk; it forces a complete repaint of affected windows on Glk
 
2693
       * window resize and arrange events, and works in part because Xglk
 
2694
       * doesn't check for actual arrangement changes in any way before
 
2695
       * invalidating its windows.  The hack should be harmless to Glk
 
2696
       * libraries other than Xglk, moreover, we're careful to activate it
 
2697
       * only on resize and arrange events.
 
2698
       */
 
2699
      parent = glk_window_get_parent (gms_status_window);
 
2700
      glk_window_set_arrangement (parent,
 
2701
                                  winmethod_Above | winmethod_Fixed, 1, NULL);
 
2702
 
 
2703
      gms_status_update ();
 
2704
    }
 
2705
}
 
2706
 
 
2707
 
 
2708
/*---------------------------------------------------------------------*/
 
2709
/*  Glk port output functions                                          */
 
2710
/*---------------------------------------------------------------------*/
 
2711
 
 
2712
/*
 
2713
 * Flag for if the user entered "help" as their last input, or if hints have
 
2714
 * been silenced as a result of already using a Glk command.
 
2715
 */
 
2716
static int gms_help_requested = FALSE,
 
2717
           gms_help_hints_silenced = FALSE;
 
2718
 
 
2719
/*
 
2720
 * Output buffer.  We receive characters one at a time, and it's a bit
 
2721
 * more efficient for everyone if we buffer them, and output a complete
 
2722
 * string on a flush call.
 
2723
 */
 
2724
static char *gms_output_buffer = NULL;
 
2725
static int gms_output_allocation = 0,
 
2726
           gms_output_length = 0;
 
2727
 
 
2728
/*
 
2729
 * Flag to indicate if the last buffer flushed looked like it ended in a
 
2730
 * ">" prompt.
 
2731
 */
 
2732
static int gms_output_prompt = FALSE;
 
2733
 
 
2734
 
 
2735
/*
 
2736
 * gms_output_register_help_request()
 
2737
 * gms_output_silence_help_hints()
 
2738
 * gms_output_provide_help_hint()
 
2739
 *
 
2740
 * Register a request for help, and print a note of how to get Glk command
 
2741
 * help from the interpreter unless silenced.
 
2742
 */
 
2743
static void
 
2744
gms_output_register_help_request (void)
 
2745
{
 
2746
  gms_help_requested = TRUE;
 
2747
}
 
2748
 
 
2749
static void
 
2750
gms_output_silence_help_hints (void)
 
2751
{
 
2752
  gms_help_hints_silenced = TRUE;
 
2753
}
 
2754
 
 
2755
static void
 
2756
gms_output_provide_help_hint (void)
 
2757
{
 
2758
  if (gms_help_requested && !gms_help_hints_silenced)
 
2759
    {
 
2760
      glk_set_style (style_Emphasized);
 
2761
      glk_put_string ("[Try 'glk help' for help on special interpreter"
 
2762
                      " commands]\n");
 
2763
 
 
2764
      gms_help_requested = FALSE;
 
2765
      glk_set_style (style_Normal);
 
2766
    }
 
2767
}
 
2768
 
 
2769
 
 
2770
/*
 
2771
 * gms_game_prompted()
 
2772
 *
 
2773
 * Return TRUE if the last game output appears to have been a ">" prompt.
 
2774
 * Once called, the flag is reset to FALSE, and requires more game output
 
2775
 * to set it again.
 
2776
 */
 
2777
static int
 
2778
gms_game_prompted (void)
 
2779
{
 
2780
  int result;
 
2781
 
 
2782
  result = gms_output_prompt;
 
2783
  gms_output_prompt = FALSE;
 
2784
 
 
2785
  return result;
 
2786
}
 
2787
 
 
2788
 
 
2789
/*
 
2790
 * gms_detect_game_prompt()
 
2791
 *
 
2792
 * See if the last non-newline-terminated line in the output buffer seems
 
2793
 * to be a prompt, and set the game prompted flag if it does, otherwise
 
2794
 * clear it.
 
2795
 */
 
2796
static void
 
2797
gms_detect_game_prompt (void)
 
2798
{
 
2799
  int index;
 
2800
 
 
2801
  gms_output_prompt = FALSE;
 
2802
 
 
2803
  /*
 
2804
   * Search for a prompt across any last unterminated buffered line; a prompt
 
2805
   * is any non-space character on that line.
 
2806
   */
 
2807
  for (index = gms_output_length - 1;
 
2808
       index >= 0 && gms_output_buffer[index] != '\n'; index--)
 
2809
    {
 
2810
      if (gms_output_buffer[index] != ' ')
 
2811
        {
 
2812
          gms_output_prompt = TRUE;
 
2813
          break;
 
2814
        }
 
2815
    }
 
2816
}
 
2817
 
 
2818
 
 
2819
/*
 
2820
 * gms_output_delete()
 
2821
 *
 
2822
 * Delete all buffered output text.  Free all malloc'ed buffer memory, and
 
2823
 * return the buffer variables to their initial values.
 
2824
 */
 
2825
static void
 
2826
gms_output_delete (void)
 
2827
{
 
2828
  free (gms_output_buffer);
 
2829
  gms_output_buffer = NULL;
 
2830
  gms_output_allocation = gms_output_length = 0;
 
2831
}
 
2832
 
 
2833
 
 
2834
/*
 
2835
 * gms_output_flush()
 
2836
 *
 
2837
 * Flush any buffered output text to the Glk main window, and clear the
 
2838
 * buffer.
 
2839
 */
 
2840
static void
 
2841
gms_output_flush (void)
 
2842
{
 
2843
  assert (glk_stream_get_current ());
 
2844
 
 
2845
  if (gms_output_length > 0)
 
2846
    {
 
2847
      /*
 
2848
       * See if the game issued a standard prompt, then print the buffer to
 
2849
       * the main window.  If providing a help hint, position that before
 
2850
       * the game's prompt (if any).
 
2851
       */
 
2852
      gms_detect_game_prompt ();
 
2853
      glk_set_style (style_Normal);
 
2854
 
 
2855
      if (gms_output_prompt)
 
2856
        {
 
2857
          int index;
 
2858
 
 
2859
          for (index = gms_output_length - 1;
 
2860
               index >= 0 && gms_output_buffer[index] != '\n'; )
 
2861
            index--;
 
2862
 
 
2863
          glk_put_buffer (gms_output_buffer, index + 1);
 
2864
          gms_output_provide_help_hint ();
 
2865
          glk_put_buffer (gms_output_buffer + index + 1,
 
2866
                          gms_output_length - index - 1);
 
2867
        }
 
2868
      else
 
2869
        {
 
2870
          glk_put_buffer (gms_output_buffer, gms_output_length);
 
2871
          gms_output_provide_help_hint ();
 
2872
        }
 
2873
 
 
2874
      gms_output_delete ();
 
2875
    }
 
2876
}
 
2877
 
 
2878
 
 
2879
/*
 
2880
 * ms_putchar()
 
2881
 *
 
2882
 * Buffer a character for eventual printing to the main window.
 
2883
 */
 
2884
void
 
2885
ms_putchar (type8 c)
 
2886
{
 
2887
  int bytes;
 
2888
  assert (gms_output_length <= gms_output_allocation);
 
2889
 
 
2890
  /*
 
2891
   * See if the character is a backspace.  Magnetic Scrolls games can send
 
2892
   * backspace characters to the display.  We'll need to handle such
 
2893
   * characters specially, by taking the last character out of the buffer.
 
2894
   */
 
2895
  if (c == '\b')
 
2896
    {
 
2897
      if (gms_output_length > 0)
 
2898
        gms_output_length--;
 
2899
 
 
2900
      return;
 
2901
    }
 
2902
 
 
2903
  /* Grow the output buffer if necessary, then add the character. */
 
2904
  for (bytes = gms_output_allocation; bytes < gms_output_length + 1; )
 
2905
    bytes = bytes == 0 ? 1 : bytes << 1;
 
2906
 
 
2907
  if (bytes > gms_output_allocation)
 
2908
    {
 
2909
      gms_output_buffer = gms_realloc (gms_output_buffer, bytes);
 
2910
      gms_output_allocation = bytes;
 
2911
    }
 
2912
 
 
2913
  gms_output_buffer[gms_output_length++] = c;
 
2914
}
 
2915
 
 
2916
 
 
2917
/*
 
2918
 * gms_styled_string()
 
2919
 * gms_styled_char()
 
2920
 * gms_standout_string()
 
2921
 * gms_standout_char()
 
2922
 * gms_normal_string()
 
2923
 * gms_normal_char()
 
2924
 * gms_header_string()
 
2925
 * gms_banner_string()
 
2926
 *
 
2927
 * Convenience functions to print strings in assorted styles.  A standout
 
2928
 * string is one that hints that it's from the interpreter, not the game.
 
2929
 */
 
2930
static void
 
2931
gms_styled_string (glui32 style, const char *message)
 
2932
{
 
2933
  assert (message);
 
2934
 
 
2935
  glk_set_style (style);
 
2936
  glk_put_string ((char *) message);
 
2937
  glk_set_style (style_Normal);
 
2938
}
 
2939
 
 
2940
static void
 
2941
gms_styled_char (glui32 style, char c)
 
2942
{
 
2943
  char buffer[2];
 
2944
 
 
2945
  buffer[0] = c;
 
2946
  buffer[1] = '\0';
 
2947
  gms_styled_string (style, buffer);
 
2948
}
 
2949
 
 
2950
static void
 
2951
gms_standout_string (const char *message)
 
2952
{
 
2953
  gms_styled_string (style_Emphasized, message);
 
2954
}
 
2955
 
 
2956
static void
 
2957
gms_standout_char (char c)
 
2958
{
 
2959
  gms_styled_char (style_Emphasized, c);
 
2960
}
 
2961
 
 
2962
static void
 
2963
gms_normal_string (const char *message)
 
2964
{
 
2965
  gms_styled_string (style_Normal, message);
 
2966
}
 
2967
 
 
2968
static void
 
2969
gms_normal_char (char c)
 
2970
{
 
2971
  gms_styled_char (style_Normal, c);
 
2972
}
 
2973
 
 
2974
static void
 
2975
gms_header_string (const char *message)
 
2976
{
 
2977
  gms_styled_string (style_Header, message);
 
2978
}
 
2979
 
 
2980
static void
 
2981
gms_banner_string (const char *message)
 
2982
{
 
2983
  gms_styled_string (style_Subheader, message);
 
2984
}
 
2985
 
 
2986
 
 
2987
/*
 
2988
 * ms_fatal()
 
2989
 *
 
2990
 * Handle fatal interpreter error message.
 
2991
 */
 
2992
void
 
2993
ms_fatal (type8s * string)
 
2994
{
 
2995
  gms_status_notify ();
 
2996
  gms_output_flush ();
 
2997
 
 
2998
  gms_fatal (string);
 
2999
  glk_exit ();
 
3000
}
 
3001
 
 
3002
 
 
3003
/*
 
3004
 * ms_flush()
 
3005
 *
 
3006
 * Handle a core interpreter call to flush the output buffer.  Because Glk
 
3007
 * only flushes its buffers and displays text on glk_select(), we can ignore
 
3008
 * these calls as long as we call gms_output_flush() when reading line input.
 
3009
 *
 
3010
 * Taking ms_flush() at face value can cause game text to appear before status
 
3011
 * line text where we are working with a non-windowing Glk, so it's best
 
3012
 * ignored where we can.
 
3013
 */
 
3014
void
 
3015
ms_flush (void)
 
3016
{
 
3017
}
 
3018
 
 
3019
 
 
3020
/*---------------------------------------------------------------------*/
 
3021
/*  Glk port hint functions                                            */
 
3022
/*---------------------------------------------------------------------*/
 
3023
 
 
3024
/* Hint type definitions. */
 
3025
enum {
 
3026
  GMS_HINT_TYPE_FOLDER = 1,
 
3027
  GMS_HINT_TYPE_TEXT = 2
 
3028
};
 
3029
 
 
3030
/* Success and fail return codes from hint functions. */
 
3031
static const type8 GMS_HINT_SUCCESS = 1,
 
3032
                   GMS_HINT_ERROR = 0;
 
3033
 
 
3034
/* Default window sizes for non-windowing Glk libraries. */
 
3035
static const glui32 GMS_HINT_DEFAULT_WIDTH = 72,
 
3036
                    GMS_HINT_DEFAULT_HEIGHT = 25;
 
3037
 
 
3038
/*
 
3039
 * Special hint nodes indicating the root hint node, and a value to signal
 
3040
 * quit from hints subsystem.
 
3041
 */
 
3042
static const type16 GMS_HINT_ROOT_NODE = 0,
 
3043
                    GMS_HINTS_DONE = USHRT_MAX;
 
3044
 
 
3045
/* Generic hint topic for the root hints node. */
 
3046
static const char * const GMS_GENERIC_TOPIC = "Hints Menu";
 
3047
 
 
3048
/*
 
3049
 * Note of the interpreter's hints array.  Note that keeping its address
 
3050
 * like this assumes that it's either static or heap in the interpreter.
 
3051
 */
 
3052
static struct ms_hint *gms_hints = NULL;
 
3053
 
 
3054
/* Details of the current hint node on display from the hints array. */
 
3055
static type16 gms_current_hint_node = 0;
 
3056
 
 
3057
/*
 
3058
 * Array of cursors for each hint.  The cursor indicates the current hint
 
3059
 * position in a folder, and the last hint shown in text hints.  Space
 
3060
 * is allocated as needed for a given set of hints, and needs to be freed
 
3061
 * on interpreter exit.
 
3062
 */
 
3063
static int *gms_hint_cursor = NULL;
 
3064
 
 
3065
 
 
3066
/*
 
3067
 * gms_get_hint_max_node()
 
3068
 *
 
3069
 * Return the maximum hint node referred to by the tree under the given node.
 
3070
 * The result is the largest index found, or node, if greater.  Because the
 
3071
 * interpreter doesn't supply it, we need to uncover it the hard way.  The
 
3072
 * function is recursive, and since it is a tree search, assumes that hints
 
3073
 * is a tree, not a graph.
 
3074
 */
 
3075
static type16
 
3076
gms_get_hint_max_node (const struct ms_hint hints[], type16 node)
 
3077
{
 
3078
  const struct ms_hint *hint;
 
3079
  int index;
 
3080
  type16 max_node;
 
3081
  assert (hints);
 
3082
 
 
3083
  hint = hints + node;
 
3084
  max_node = node;
 
3085
 
 
3086
  switch (hint->nodetype)
 
3087
    {
 
3088
    case GMS_HINT_TYPE_TEXT:
 
3089
      break;
 
3090
 
 
3091
    case GMS_HINT_TYPE_FOLDER:
 
3092
      /*
 
3093
       * Recursively find the maximum node reference for each link, and keep
 
3094
       * the largest value found.
 
3095
       */
 
3096
      for (index = 0; index < hint->elcount; index++)
 
3097
        {
 
3098
          type16 link_max;
 
3099
 
 
3100
          link_max = gms_get_hint_max_node (hints, hint->links[index]);
 
3101
          if (link_max > max_node)
 
3102
            max_node = link_max;
 
3103
        }
 
3104
      break;
 
3105
 
 
3106
    default:
 
3107
      gms_fatal ("GLK: Invalid hints node type encountered");
 
3108
      glk_exit ();
 
3109
    }
 
3110
 
 
3111
  /*
 
3112
   * Return the largest node reference found, capped to avoid overlapping the
 
3113
   * special end-hints value.
 
3114
   */
 
3115
  return max_node < GMS_HINTS_DONE ? max_node : GMS_HINTS_DONE - 1;
 
3116
}
 
3117
 
 
3118
 
 
3119
/*
 
3120
 * gms_get_hint_content()
 
3121
 *
 
3122
 * Return the content string for a given hint number within a given node.
 
3123
 * This counts over 'number' ASCII NULs in the node's content, returning
 
3124
 * the address of the string located this way.
 
3125
 */
 
3126
static const char *
 
3127
gms_get_hint_content (const struct ms_hint hints[], type16 node, int number)
 
3128
{
 
3129
  const struct ms_hint *hint;
 
3130
  int offset, index;
 
3131
  assert (hints);
 
3132
 
 
3133
  hint = hints + node;
 
3134
 
 
3135
  /* Run through content until 'number' strings found. */
 
3136
  offset = 0;
 
3137
  for (index = 0; index < number; index++)
 
3138
    offset += strlen (hint->content + offset) + 1;
 
3139
 
 
3140
  /* Return the start of the number'th string encountered. */
 
3141
  return hint->content + offset;
 
3142
}
 
3143
 
 
3144
 
 
3145
/*
 
3146
 * gms_get_hint_topic()
 
3147
 *
 
3148
 * Return the topic string for a given hint node.  This is found by searching
 
3149
 * the parent node for a link to the node handed in.  For the root node, the
 
3150
 * string is defaulted, since the root node has no parent.
 
3151
 */
 
3152
static const char *
 
3153
gms_get_hint_topic (const struct ms_hint hints[], type16 node)
 
3154
{
 
3155
  assert (hints);
 
3156
 
 
3157
  if (node == GMS_HINT_ROOT_NODE)
 
3158
    {
 
3159
      /* If the node is the root node, return a generic string. */
 
3160
      return GMS_GENERIC_TOPIC;
 
3161
    }
 
3162
  else
 
3163
    {
 
3164
      type16 parent;
 
3165
      int index;
 
3166
      const char *topic;
 
3167
 
 
3168
      /*
 
3169
       * Search the parent for a link to node, and use that as the hint topic;
 
3170
       * NULL if none found.
 
3171
       */
 
3172
      parent = hints[node].parent;
 
3173
 
 
3174
      topic = NULL;
 
3175
      for (index = 0; index < hints[parent].elcount; index++)
 
3176
        {
 
3177
          if (hints[parent].links[index] == node)
 
3178
            {
 
3179
              topic = gms_get_hint_content (hints, parent, index);
 
3180
              break;
 
3181
            }
 
3182
        }
 
3183
 
 
3184
      return topic ? topic : GMS_GENERIC_TOPIC;
 
3185
    }
 
3186
}
 
3187
 
 
3188
 
 
3189
/*
 
3190
 * gms_hint_open()
 
3191
 *
 
3192
 * If not already open, open the hints windows.  Returns TRUE if the windows
 
3193
 * opened, or were already open.
 
3194
 *
 
3195
 * The function creates two hints windows -- a text grid on top, for menus,
 
3196
 * and a text buffer below for hints.
 
3197
 */
 
3198
static int
 
3199
gms_hint_open (void)
 
3200
{
 
3201
  if (!gms_hint_menu_window)
 
3202
    {
 
3203
      assert (!gms_hint_text_window);
 
3204
 
 
3205
      /*
 
3206
       * Open the hint menu window.  The initial size is two lines, but we'll
 
3207
       * change this later to suit the hint.
 
3208
       */
 
3209
      gms_hint_menu_window = glk_window_open (gms_main_window,
 
3210
                                              winmethod_Above | winmethod_Fixed,
 
3211
                                              2, wintype_TextGrid, 0);
 
3212
      if (!gms_hint_menu_window)
 
3213
        return FALSE;
 
3214
 
 
3215
      /*
 
3216
       * Now open the hints text window.  This is set to be 100% of the size
 
3217
       * of the main window, so should cover what remains of it completely.
 
3218
       */
 
3219
      gms_hint_text_window = glk_window_open (gms_main_window,
 
3220
                                              winmethod_Above
 
3221
                                                | winmethod_Proportional,
 
3222
                                              100, wintype_TextBuffer, 0);
 
3223
      if (!gms_hint_text_window)
 
3224
        {
 
3225
          glk_window_close (gms_hint_menu_window, NULL);
 
3226
          gms_hint_menu_window = NULL;
 
3227
          return FALSE;
 
3228
        }
 
3229
    }
 
3230
 
 
3231
  return TRUE;
 
3232
}
 
3233
 
 
3234
 
 
3235
/*
 
3236
 * gms_hint_close()
 
3237
 *
 
3238
 * If open, close the hints windows.
 
3239
 */
 
3240
static void
 
3241
gms_hint_close (void)
 
3242
{
 
3243
  if (gms_hint_menu_window)
 
3244
    {
 
3245
      assert (gms_hint_text_window);
 
3246
 
 
3247
      glk_window_close (gms_hint_menu_window, NULL);
 
3248
      gms_hint_menu_window = NULL;
 
3249
      glk_window_close (gms_hint_text_window, NULL);
 
3250
      gms_hint_text_window = NULL;
 
3251
    }
 
3252
}
 
3253
 
 
3254
 
 
3255
/*
 
3256
 * gms_hint_windows_available()
 
3257
 *
 
3258
 * Return TRUE if hints windows are available.  If they're not, the hints
 
3259
 * system will need to use alternative output methods.
 
3260
 */
 
3261
static int
 
3262
gms_hint_windows_available (void)
 
3263
{
 
3264
  return (gms_hint_menu_window && gms_hint_text_window);
 
3265
}
 
3266
 
 
3267
 
 
3268
/*
 
3269
 * gms_hint_menu_print()
 
3270
 * gms_hint_menu_header()
 
3271
 * gms_hint_menu_justify()
 
3272
 * gms_hint_text_print()
 
3273
 * gms_hint_menutext_done()
 
3274
 * gms_hint_menutext_start()
 
3275
 *
 
3276
 * Output functions for writing hints.  These functions will write to hints
 
3277
 * windows where available, and to the main window where not.  When writing
 
3278
 * to hints windows, they also take care not to line wrap in the menu window.
 
3279
 * Limited formatting is available.
 
3280
 */
 
3281
static void
 
3282
gms_hint_menu_print (int line, int column, const char *string,
 
3283
                     glui32 width, glui32 height)
 
3284
{
 
3285
  assert (string);
 
3286
 
 
3287
  /* Ignore the call if the text position is outside the window. */
 
3288
  if (!(line > height || column > width))
 
3289
    {
 
3290
      if (gms_hint_windows_available ())
 
3291
        {
 
3292
          int posn, index;
 
3293
 
 
3294
          glk_window_move_cursor (gms_hint_menu_window, column, line);
 
3295
          glk_set_window (gms_hint_menu_window);
 
3296
 
 
3297
          /* Write until the end of the string, or the end of the window. */
 
3298
          for (posn = column, index = 0;
 
3299
               posn < width && index < strlen (string); posn++, index++)
 
3300
            {
 
3301
              glk_put_char (string[index]);
 
3302
            }
 
3303
 
 
3304
          glk_set_window (gms_main_window);
 
3305
        }
 
3306
      else
 
3307
        {
 
3308
          static int current_line = 0;    /* Retained line number */
 
3309
          static int current_column = 0;  /* Retained col number */
 
3310
 
 
3311
          int index;
 
3312
 
 
3313
          /*
 
3314
           * Check the line number against the last one output.  If it is less,
 
3315
           * assume the start of a new block.  In this case, perform a hokey
 
3316
           * type of screen clear.
 
3317
           */
 
3318
          if (line < current_line)
 
3319
            {
 
3320
              for (index = 0; index < height; index++)
 
3321
                gms_normal_char ('\n');
 
3322
 
 
3323
              current_line = 0;
 
3324
              current_column = 0;
 
3325
            }
 
3326
 
 
3327
          /* Print blank lines until the target line is reached. */
 
3328
          for (; current_line < line; current_line++)
 
3329
            {
 
3330
              gms_normal_char ('\n');
 
3331
              current_column = 0;
 
3332
            }
 
3333
 
 
3334
          /* Now print spaces until the target column is reached. */
 
3335
          for (; current_column < column; current_column++)
 
3336
            gms_normal_char (' ');
 
3337
 
 
3338
          /*
 
3339
           * Write characters until the end of the string, or the end of the
 
3340
           * (self-imposed not-really-there) window.
 
3341
           */
 
3342
          for (index = 0;
 
3343
               current_column < width && index < strlen (string);
 
3344
               current_column++, index++)
 
3345
            {
 
3346
              gms_normal_char (string[index]);
 
3347
            }
 
3348
        }
 
3349
    }
 
3350
}
 
3351
 
 
3352
static void
 
3353
gms_hint_menu_header (int line, const char *string,
 
3354
                      glui32 width, glui32 height)
 
3355
{
 
3356
  int posn, length;
 
3357
  assert (string);
 
3358
 
 
3359
  /* Output the text in the approximate line center. */
 
3360
  length = strlen (string);
 
3361
  posn = length < width ? (width - length) / 2 : 0;
 
3362
  gms_hint_menu_print (line, posn, string, width, height);
 
3363
}
 
3364
 
 
3365
static void
 
3366
gms_hint_menu_justify (int line,
 
3367
                       const char *left_string, const char *right_string,
 
3368
                       glui32 width, glui32 height)
 
3369
{
 
3370
  int posn, length;
 
3371
  assert (left_string && right_string);
 
3372
 
 
3373
  /* Write left text normally to window left. */
 
3374
  gms_hint_menu_print (line, 0, left_string, width, height);
 
3375
 
 
3376
  /* Output the right text flush with the right of the window. */
 
3377
  length = strlen (right_string);
 
3378
  posn = length < width ? width - length : 0;
 
3379
  gms_hint_menu_print (line, posn, right_string, width, height);
 
3380
}
 
3381
 
 
3382
static void
 
3383
gms_hint_text_print (const char *string)
 
3384
{
 
3385
  assert (string);
 
3386
 
 
3387
  if (gms_hint_windows_available ())
 
3388
    {
 
3389
      glk_set_window (gms_hint_text_window);
 
3390
      glk_put_string ((char *) string);
 
3391
      glk_set_window (gms_main_window);
 
3392
    }
 
3393
  else
 
3394
    gms_normal_string (string);
 
3395
}
 
3396
 
 
3397
static void
 
3398
gms_hint_menutext_start (void)
 
3399
{
 
3400
  /*
 
3401
   * Twiddle for non-windowing libraries; 'clear' the main window by writing
 
3402
   * a null string at line 1, then a null string at line 0.  This works
 
3403
   * because we know the current output line in gms_hint_menu_print() is zero,
 
3404
   * since we set it that way with gms_hint_menutext_done(), or if this is
 
3405
   * the first call, then that's its initial value.
 
3406
   */
 
3407
  if (!gms_hint_windows_available ())
 
3408
    {
 
3409
      gms_hint_menu_print (1, 0, "",
 
3410
                           GMS_HINT_DEFAULT_WIDTH, GMS_HINT_DEFAULT_HEIGHT);
 
3411
      gms_hint_menu_print (0, 0, "",
 
3412
                           GMS_HINT_DEFAULT_WIDTH, GMS_HINT_DEFAULT_HEIGHT);
 
3413
    }
 
3414
}
 
3415
 
 
3416
static void
 
3417
gms_hint_menutext_done (void)
 
3418
{
 
3419
  /*
 
3420
   * Twiddle for non-windowing libraries; 'clear' the main window by writing
 
3421
   * an empty string to line zero.  For windowing Glk libraries, this function
 
3422
   * does nothing.
 
3423
   */
 
3424
  if (!gms_hint_windows_available ())
 
3425
    {
 
3426
      gms_hint_menu_print (0, 0, "",
 
3427
                           GMS_HINT_DEFAULT_WIDTH, GMS_HINT_DEFAULT_HEIGHT);
 
3428
    }
 
3429
}
 
3430
 
 
3431
 
 
3432
/*
 
3433
 * gms_hint_menutext_char_event()
 
3434
 *
 
3435
 * Request and return a character event from the hints windows.  In practice,
 
3436
 * this means either of the hints windows if available, or the main window
 
3437
 * if not.
 
3438
 */
 
3439
static void
 
3440
gms_hint_menutext_char_event (event_t * event)
 
3441
{
 
3442
  assert (event);
 
3443
 
 
3444
  if (gms_hint_windows_available ())
 
3445
    {
 
3446
      glk_request_char_event (gms_hint_menu_window);
 
3447
      glk_request_char_event (gms_hint_text_window);
 
3448
 
 
3449
      gms_event_wait (evtype_CharInput, event);
 
3450
      assert (event->win == gms_hint_menu_window
 
3451
              || event->win == gms_hint_text_window);
 
3452
 
 
3453
      glk_cancel_char_event (gms_hint_menu_window);
 
3454
      glk_cancel_char_event (gms_hint_text_window);
 
3455
    }
 
3456
  else
 
3457
    {
 
3458
      glk_request_char_event (gms_main_window);
 
3459
      gms_event_wait (evtype_CharInput, event);
 
3460
    }
 
3461
}
 
3462
 
 
3463
 
 
3464
/*
 
3465
 * gms_hint_arrange_windows()
 
3466
 *
 
3467
 * Arrange the hints windows so that the hint menu window has the requested
 
3468
 * number of lines.  Returns the actual hint menu window width and height,
 
3469
 * or defaults if no hints windows are available.
 
3470
 */
 
3471
static void
 
3472
gms_hint_arrange_windows (int requested_lines, glui32 * width, glui32 * height)
 
3473
{
 
3474
  if (gms_hint_windows_available ())
 
3475
    {
 
3476
      winid_t parent;
 
3477
 
 
3478
      /* Resize the hint menu window to fit the current hint. */
 
3479
      parent = glk_window_get_parent (gms_hint_menu_window);
 
3480
      glk_window_set_arrangement (parent,
 
3481
                                  winmethod_Above | winmethod_Fixed,
 
3482
                                  requested_lines, NULL);
 
3483
 
 
3484
      /* Measure, and return the size of the hint menu window. */
 
3485
      glk_window_get_size (gms_hint_menu_window, width, height);
 
3486
 
 
3487
      /* Clear both the hint menu and the hint text window. */
 
3488
      glk_window_clear (gms_hint_menu_window);
 
3489
      glk_window_clear (gms_hint_text_window);
 
3490
    }
 
3491
  else
 
3492
    {
 
3493
      /*
 
3494
       * No hints windows, so default width and height.  The hints output
 
3495
       * functions will cope with this.
 
3496
       */
 
3497
      if (width)
 
3498
        *width = GMS_HINT_DEFAULT_WIDTH;
 
3499
      if (height)
 
3500
        *height = GMS_HINT_DEFAULT_HEIGHT;
 
3501
    }
 
3502
}
 
3503
 
 
3504
 
 
3505
/*
 
3506
 * gms_hint_display_folder()
 
3507
 *
 
3508
 * Update the hints windows for the given folder hint node.
 
3509
 */
 
3510
static void
 
3511
gms_hint_display_folder (const struct ms_hint hints[],
 
3512
                         const int cursor[], type16 node)
 
3513
{
 
3514
  glui32 width, height;
 
3515
  int line, index;
 
3516
  assert (hints && cursor);
 
3517
 
 
3518
  /*
 
3519
   * Arrange windows to suit the hint folder.  For a folder menu window we
 
3520
   * use one line for each element, three for the controls, and two spacers,
 
3521
   * making a total of five additional lines.  Width and height receive the
 
3522
   * actual menu window dimensions.
 
3523
   */
 
3524
  gms_hint_arrange_windows (hints[node].elcount + 5, &width, &height);
 
3525
 
 
3526
  /* Paint in the menu header. */
 
3527
  line = 0;
 
3528
  gms_hint_menu_header (line++,
 
3529
                        gms_get_hint_topic (hints, node),
 
3530
                        width, height);
 
3531
  gms_hint_menu_justify (line++,
 
3532
                         " N = next subject  ", "  P = previous ",
 
3533
                         width, height);
 
3534
  gms_hint_menu_justify (line++,
 
3535
                         " RETURN = read subject  ",
 
3536
                         node == GMS_HINT_ROOT_NODE
 
3537
                           ? "  Q = resume game " : "  Q = previous menu ",
 
3538
                         width, height);
 
3539
 
 
3540
  /*
 
3541
   * Output a blank line, then the menu for the node's folder hint.  The folder
 
3542
   * text for the selected hint is preceded by a '>' pointer.
 
3543
   */
 
3544
  line++;
 
3545
  for (index = 0; index < hints[node].elcount; index++)
 
3546
    {
 
3547
      gms_hint_menu_print (line, 3,
 
3548
                           index == cursor[node] ? ">" : " ",
 
3549
                           width, height);
 
3550
      gms_hint_menu_print (line++, 5,
 
3551
                           gms_get_hint_content (hints, node, index),
 
3552
                           width, height);
 
3553
    }
 
3554
 
 
3555
  /*
 
3556
   * Terminate with a blank line; using a single space here improves cursor
 
3557
   * positioning for optimized output libraries (for example, without it,
 
3558
   * curses output will leave the cursor at the end of the previous line).
 
3559
   */
 
3560
  gms_hint_menu_print (line, 0, " ", width, height);
 
3561
}
 
3562
 
 
3563
 
 
3564
/*
 
3565
 * gms_hint_display_text()
 
3566
 *
 
3567
 * Update the hints windows for the given text hint node.
 
3568
 */
 
3569
static void
 
3570
gms_hint_display_text (const struct ms_hint hints[],
 
3571
                       const int cursor[], type16 node)
 
3572
{
 
3573
  glui32 width, height;
 
3574
  int line, index;
 
3575
  assert (hints && cursor);
 
3576
 
 
3577
  /*
 
3578
   * Arrange windows to suit the hint text.  For a hint menu, we use a simple
 
3579
   * two-line set of controls; everything else is in the hints text window.
 
3580
   * Width and height receive the actual menu window dimensions.
 
3581
   */
 
3582
  gms_hint_arrange_windows (2, &width, &height);
 
3583
 
 
3584
  /* Paint in a short menu header. */
 
3585
  line = 0;
 
3586
  gms_hint_menu_header (line++,
 
3587
                        gms_get_hint_topic (hints, node),
 
3588
                        width, height);
 
3589
  gms_hint_menu_justify (line++,
 
3590
                         " RETURN = read hint  ", "  Q = previous menu ",
 
3591
                         width, height);
 
3592
 
 
3593
  /*
 
3594
   * Output hints to the hints text window.  Hints not yet exposed are
 
3595
   * indicated by the cursor for the hint, and are displayed as a dash.
 
3596
   */
 
3597
  gms_hint_text_print ("\n");
 
3598
  for (index = 0; index < hints[node].elcount; index++)
 
3599
    {
 
3600
      char buffer[16];
 
3601
 
 
3602
      sprintf (buffer, "%3d.  ", index + 1);
 
3603
      gms_hint_text_print (buffer);
 
3604
 
 
3605
      gms_hint_text_print (index < cursor[node]
 
3606
                           ? gms_get_hint_content (hints, node, index) : "-");
 
3607
      gms_hint_text_print ("\n");
 
3608
    }
 
3609
}
 
3610
 
 
3611
 
 
3612
/*
 
3613
 * gms_hint_display()
 
3614
 *
 
3615
 * Display the given hint using the appropriate display function.
 
3616
 */
 
3617
static void
 
3618
gms_hint_display (const struct ms_hint hints[],
 
3619
                  const int cursor[], type16 node)
 
3620
{
 
3621
  assert (hints && cursor);
 
3622
 
 
3623
  switch (hints[node].nodetype)
 
3624
    {
 
3625
    case GMS_HINT_TYPE_TEXT:
 
3626
      gms_hint_display_text (hints, cursor, node);
 
3627
      break;
 
3628
 
 
3629
    case GMS_HINT_TYPE_FOLDER:
 
3630
      gms_hint_display_folder (hints, cursor, node);
 
3631
      break;
 
3632
 
 
3633
    default:
 
3634
      gms_fatal ("GLK: Invalid hints node type encountered");
 
3635
      glk_exit ();
 
3636
    }
 
3637
}
 
3638
 
 
3639
 
 
3640
/*
 
3641
 * gms_hint_handle_folder()
 
3642
 *
 
3643
 * Handle a Glk keycode for the given folder hint.  Return the next node to
 
3644
 * handle, or the special end-hints on Quit at the root node.
 
3645
 */
 
3646
static type16
 
3647
gms_hint_handle_folder (const struct ms_hint hints[],
 
3648
                        int cursor[], type16 node, glui32 keycode)
 
3649
{
 
3650
  unsigned char response;
 
3651
  type16 next_node;
 
3652
  assert (hints && cursor);
 
3653
 
 
3654
  /* Convert key code into a single response character. */
 
3655
  switch (keycode)
 
3656
    {
 
3657
    case keycode_Down:
 
3658
      response = 'N';
 
3659
      break;
 
3660
    case keycode_Up:
 
3661
      response = 'P';
 
3662
      break;
 
3663
    case keycode_Right:
 
3664
    case keycode_Return:
 
3665
      response = '\n';
 
3666
      break;
 
3667
    case keycode_Left:
 
3668
    case keycode_Escape:
 
3669
      response = 'Q';
 
3670
      break;
 
3671
    default:
 
3672
      response = keycode <= UCHAR_MAX ? glk_char_to_upper (keycode) : 0;
 
3673
      break;
 
3674
    }
 
3675
 
 
3676
  /*
 
3677
   * Now handle the response character.  We'll default the next node to be
 
3678
   * this node, but a response case can change it.
 
3679
   */
 
3680
  next_node = node;
 
3681
  switch (response)
 
3682
    {
 
3683
    case 'N':
 
3684
      /* Advance the hint cursor, wrapping at the folder end. */
 
3685
      if (cursor[node] < hints[node].elcount - 1)
 
3686
        cursor[node]++;
 
3687
      else
 
3688
        cursor[node] = 0;
 
3689
      break;
 
3690
 
 
3691
    case 'P':
 
3692
      /* Regress the hint cursor, wrapping at the folder start. */
 
3693
      if (cursor[node] > 0)
 
3694
        cursor[node]--;
 
3695
      else
 
3696
        cursor[node] = hints[node].elcount - 1;
 
3697
      break;
 
3698
 
 
3699
    case '\n':
 
3700
      /* The next node is the hint node at the cursor position. */
 
3701
      next_node = hints[node].links[cursor[node]];
 
3702
      break;
 
3703
 
 
3704
    case 'Q':
 
3705
      /* If root, we're done; if not, next node is node's parent. */
 
3706
      next_node = node == GMS_HINT_ROOT_NODE
 
3707
                  ? GMS_HINTS_DONE : hints[node].parent;
 
3708
      break;
 
3709
 
 
3710
    default:
 
3711
      break;
 
3712
    }
 
3713
 
 
3714
  return next_node;
 
3715
}
 
3716
 
 
3717
 
 
3718
/*
 
3719
 * gms_hint_handle_text()
 
3720
 *
 
3721
 * Handle a Glk keycode for the given text hint.  Return the next node to
 
3722
 * handle.
 
3723
 */
 
3724
static type16
 
3725
gms_hint_handle_text (const struct ms_hint hints[],
 
3726
                      int cursor[], type16 node, glui32 keycode)
 
3727
{
 
3728
  unsigned char response;
 
3729
  type16 next_node;
 
3730
  assert (hints && cursor);
 
3731
 
 
3732
  /* Convert key code into a single response character. */
 
3733
  switch (keycode)
 
3734
    {
 
3735
    case keycode_Right:
 
3736
    case keycode_Return:
 
3737
      response = '\n';
 
3738
      break;
 
3739
    case keycode_Left:
 
3740
    case keycode_Escape:
 
3741
      response = 'Q';
 
3742
      break;
 
3743
    default:
 
3744
      response = keycode <= UCHAR_MAX ? glk_char_to_upper (keycode) : 0;
 
3745
      break;
 
3746
    }
 
3747
 
 
3748
  /*
 
3749
   * Now handle the response character.  We'll default the next node to be
 
3750
   * this node, but a response case can change it.
 
3751
   */
 
3752
  next_node = node;
 
3753
  switch (response)
 
3754
    {
 
3755
    case '\n':
 
3756
      /* If not at end of the hint, advance the hint cursor. */
 
3757
      if (cursor[node] < hints[node].elcount)
 
3758
        cursor[node]++;
 
3759
      break;
 
3760
 
 
3761
    case 'Q':
 
3762
      /* Done with this hint node, so next node is its parent. */
 
3763
      next_node = hints[node].parent;
 
3764
      break;
 
3765
 
 
3766
    default:
 
3767
      break;
 
3768
    }
 
3769
 
 
3770
  return next_node;
 
3771
}
 
3772
 
 
3773
 
 
3774
/*
 
3775
 * gms_hint_handle()
 
3776
 *
 
3777
 * Handle a Glk keycode for the given hint using the appropriate handler
 
3778
 * function.  Return the next node to handle.
 
3779
 */
 
3780
static type16
 
3781
gms_hint_handle (const struct ms_hint hints[],
 
3782
                 int cursor[], type16 node, glui32 keycode)
 
3783
{
 
3784
  type16 next_node;
 
3785
  assert (hints && cursor);
 
3786
 
 
3787
  next_node = GMS_HINT_ROOT_NODE;
 
3788
  switch (hints[node].nodetype)
 
3789
    {
 
3790
    case GMS_HINT_TYPE_TEXT:
 
3791
      next_node = gms_hint_handle_text (hints, cursor, node, keycode);
 
3792
      break;
 
3793
 
 
3794
    case GMS_HINT_TYPE_FOLDER:
 
3795
      next_node = gms_hint_handle_folder (hints, cursor, node, keycode);
 
3796
      break;
 
3797
 
 
3798
    default:
 
3799
      gms_fatal ("GLK: Invalid hints node type encountered");
 
3800
      glk_exit ();
 
3801
    }
 
3802
 
 
3803
  return next_node;
 
3804
}
 
3805
 
 
3806
 
 
3807
/*
 
3808
 * ms_showhints()
 
3809
 *
 
3810
 * Start game hints.  These are modal, though there's no overriding Glk
 
3811
 * reason why.  It's just that this matches the way they're implemented by
 
3812
 * most Inform games.  This may not be the best way of doing help, but at
 
3813
 * least it's likely to be familiar, and anything more ambitious may be
 
3814
 * beyond the current Glk capabilities.
 
3815
 *
 
3816
 * This function uses CRCs to detect any change of hints data.  Normally,
 
3817
 * we'd expect none, at least within a given game run, but we can probably
 
3818
 * handle it okay if it happens.
 
3819
 */
 
3820
type8
 
3821
ms_showhints (struct ms_hint * hints)
 
3822
{
 
3823
  static int is_initialized = FALSE;
 
3824
  static glui32 current_crc = 0;
 
3825
 
 
3826
  type16 hint_count;
 
3827
  glui32 crc;
 
3828
  assert (hints);
 
3829
 
 
3830
  /*
 
3831
   * Find the number of hints in the array.  To do this, we'll visit every
 
3832
   * node in a tree search, starting at the root, to locate the maximum node
 
3833
   * number found, then add one to that.  It's a pity that the interpreter
 
3834
   * doesn't hand us this information directly.
 
3835
   */
 
3836
  hint_count = gms_get_hint_max_node (hints, GMS_HINT_ROOT_NODE) + 1;
 
3837
 
 
3838
  /*
 
3839
   * Calculate a CRC for the hints array data.  If the CRC has changed, or
 
3840
   * this is the first call, assign a new cursor array.
 
3841
   */
 
3842
  crc = gms_get_buffer_crc (hints, hint_count * sizeof (*hints));
 
3843
  if (crc != current_crc || !is_initialized)
 
3844
    {
 
3845
      int bytes;
 
3846
 
 
3847
      /* Allocate new cursors, and set all to zero initial state. */
 
3848
      free (gms_hint_cursor);
 
3849
      bytes = hint_count * sizeof (*gms_hint_cursor);
 
3850
      gms_hint_cursor = gms_malloc (bytes);
 
3851
      memset (gms_hint_cursor, 0, bytes);
 
3852
 
 
3853
      /*
 
3854
       * Retain the hints CRC, for later comparisons, and set is_initialized
 
3855
       * flag.
 
3856
       */
 
3857
      current_crc = crc;
 
3858
      is_initialized = TRUE;
 
3859
    }
 
3860
 
 
3861
  /*
 
3862
   * Save the hints array passed in.  This is done here since even if the data
 
3863
   * remains the same (found by the CRC check above), the pointer to it might
 
3864
   * have changed.
 
3865
   */
 
3866
  gms_hints = hints;
 
3867
 
 
3868
  /*
 
3869
   * Try to create the hints windows.  If they can't be created, perhaps
 
3870
   * because the Glk library doesn't support it, the output functions will
 
3871
   * work around this.
 
3872
   */
 
3873
  gms_hint_open ();
 
3874
  gms_hint_menutext_start ();
 
3875
 
 
3876
  /*
 
3877
   * Begin hints display at the root node, and navigate until the user exits
 
3878
   * hints.
 
3879
   */
 
3880
  gms_current_hint_node = GMS_HINT_ROOT_NODE;
 
3881
  while (gms_current_hint_node != GMS_HINTS_DONE)
 
3882
    {
 
3883
      event_t event;
 
3884
 
 
3885
      assert (gms_current_hint_node < hint_count);
 
3886
      gms_hint_display (gms_hints, gms_hint_cursor, gms_current_hint_node);
 
3887
 
 
3888
      /* Get and handle a character key event for hint navigation. */
 
3889
      gms_hint_menutext_char_event (&event);
 
3890
      assert (event.type == evtype_CharInput);
 
3891
      gms_current_hint_node = gms_hint_handle (gms_hints,
 
3892
                                               gms_hint_cursor,
 
3893
                                               gms_current_hint_node,
 
3894
                                               event.val1);
 
3895
    }
 
3896
 
 
3897
  /* Done with hint windows. */
 
3898
  gms_hint_menutext_done ();
 
3899
  gms_hint_close ();
 
3900
 
 
3901
  return GMS_HINT_SUCCESS;
 
3902
}
 
3903
 
 
3904
 
 
3905
/*
 
3906
 * gms_hint_redraw()
 
3907
 *
 
3908
 * Update the hints windows for the current hint.  This function should be
 
3909
 * called from the event handler on resize events, to repaint the hints
 
3910
 * display.  It does nothing if no hints windows have been opened, since
 
3911
 * in this case, there's no resize action required -- either we're not in
 
3912
 * the hints subsystem, or hints are being displayed in the main game
 
3913
 * window, for whatever reason.
 
3914
 */
 
3915
static void
 
3916
gms_hint_redraw (void)
 
3917
{
 
3918
  if (gms_hint_windows_available ())
 
3919
    {
 
3920
      assert (gms_hints && gms_hint_cursor);
 
3921
      gms_hint_display (gms_hints, gms_hint_cursor, gms_current_hint_node);
 
3922
    }
 
3923
}
 
3924
 
 
3925
 
 
3926
/*
 
3927
 * gms_hints_cleanup()
 
3928
 *
 
3929
 * Free memory resources allocated by hints functions.  Called on game
 
3930
 * end.
 
3931
 */
 
3932
static void
 
3933
gms_hints_cleanup (void)
 
3934
{
 
3935
  free (gms_hint_cursor);
 
3936
  gms_hint_cursor = NULL;
 
3937
 
 
3938
  gms_hints = NULL;
 
3939
  gms_current_hint_node = 0;
 
3940
}
 
3941
 
 
3942
 
 
3943
void ms_playmusic(type8 * midi_data, type32 length, type16 tempo)
 
3944
{
 
3945
}
 
3946
 
 
3947
 
 
3948
/*---------------------------------------------------------------------*/
 
3949
/*  Glk command escape functions                                       */
 
3950
/*---------------------------------------------------------------------*/
 
3951
 
 
3952
/*
 
3953
 * gms_command_undo()
 
3954
 *
 
3955
 * Stub function for the undo command.  The real work is to return the
 
3956
 * undo code to the input functions.
 
3957
 */
 
3958
static void
 
3959
gms_command_undo (const char *argument)
 
3960
{
 
3961
  assert (argument);
 
3962
}
 
3963
 
 
3964
 
 
3965
/*
 
3966
 * gms_command_script()
 
3967
 *
 
3968
 * Turn game output scripting (logging) on and off.
 
3969
 */
 
3970
static void
 
3971
gms_command_script (const char *argument)
 
3972
{
 
3973
  assert (argument);
 
3974
 
 
3975
  if (gms_strcasecmp (argument, "on") == 0)
 
3976
    {
 
3977
      frefid_t fileref;
 
3978
 
 
3979
      if (gms_transcript_stream)
 
3980
        {
 
3981
          gms_normal_string ("Glk transcript is already on.\n");
 
3982
          return;
 
3983
        }
 
3984
 
 
3985
      fileref = glk_fileref_create_by_prompt (fileusage_Transcript
 
3986
                                              | fileusage_TextMode,
 
3987
                                              filemode_WriteAppend, 0);
 
3988
      if (!fileref)
 
3989
        {
 
3990
          gms_standout_string ("Glk transcript failed.\n");
 
3991
          return;
 
3992
        }
 
3993
 
 
3994
      gms_transcript_stream = glk_stream_open_file (fileref,
 
3995
                                                     filemode_WriteAppend, 0);
 
3996
      glk_fileref_destroy (fileref);
 
3997
      if (!gms_transcript_stream)
 
3998
        {
 
3999
          gms_standout_string ("Glk transcript failed.\n");
 
4000
          return;
 
4001
        }
 
4002
 
 
4003
      glk_window_set_echo_stream (gms_main_window, gms_transcript_stream);
 
4004
 
 
4005
      gms_normal_string ("Glk transcript is now on.\n");
 
4006
    }
 
4007
 
 
4008
  else if (gms_strcasecmp (argument, "off") == 0)
 
4009
    {
 
4010
      if (!gms_transcript_stream)
 
4011
        {
 
4012
          gms_normal_string ("Glk transcript is already off.\n");
 
4013
          return;
 
4014
        }
 
4015
 
 
4016
      glk_stream_close (gms_transcript_stream, NULL);
 
4017
      gms_transcript_stream = NULL;
 
4018
 
 
4019
      glk_window_set_echo_stream (gms_main_window, NULL);
 
4020
 
 
4021
      gms_normal_string ("Glk transcript is now off.\n");
 
4022
    }
 
4023
 
 
4024
  else if (strlen (argument) == 0)
 
4025
    {
 
4026
      gms_normal_string ("Glk transcript is ");
 
4027
      gms_normal_string (gms_transcript_stream ? "on" : "off");
 
4028
      gms_normal_string (".\n");
 
4029
    }
 
4030
 
 
4031
  else
 
4032
    {
 
4033
      gms_normal_string ("Glk transcript can be ");
 
4034
      gms_standout_string ("on");
 
4035
      gms_normal_string (", or ");
 
4036
      gms_standout_string ("off");
 
4037
      gms_normal_string (".\n");
 
4038
    }
 
4039
}
 
4040
 
 
4041
 
 
4042
/*
 
4043
 * gms_command_inputlog()
 
4044
 *
 
4045
 * Turn game input logging on and off.
 
4046
 */
 
4047
static void
 
4048
gms_command_inputlog (const char *argument)
 
4049
{
 
4050
  assert (argument);
 
4051
 
 
4052
  if (gms_strcasecmp (argument, "on") == 0)
 
4053
    {
 
4054
      frefid_t fileref;
 
4055
 
 
4056
      if (gms_inputlog_stream)
 
4057
        {
 
4058
          gms_normal_string ("Glk input logging is already on.\n");
 
4059
          return;
 
4060
        }
 
4061
 
 
4062
      fileref = glk_fileref_create_by_prompt (fileusage_InputRecord
 
4063
                                              | fileusage_BinaryMode,
 
4064
                                              filemode_WriteAppend, 0);
 
4065
      if (!fileref)
 
4066
        {
 
4067
          gms_standout_string ("Glk input logging failed.\n");
 
4068
          return;
 
4069
        }
 
4070
 
 
4071
      gms_inputlog_stream = glk_stream_open_file (fileref,
 
4072
                                                   filemode_WriteAppend, 0);
 
4073
      glk_fileref_destroy (fileref);
 
4074
      if (!gms_inputlog_stream)
 
4075
        {
 
4076
          gms_standout_string ("Glk input logging failed.\n");
 
4077
          return;
 
4078
        }
 
4079
 
 
4080
      gms_normal_string ("Glk input logging is now on.\n");
 
4081
    }
 
4082
 
 
4083
  else if (gms_strcasecmp (argument, "off") == 0)
 
4084
    {
 
4085
      if (!gms_inputlog_stream)
 
4086
        {
 
4087
          gms_normal_string ("Glk input logging is already off.\n");
 
4088
          return;
 
4089
        }
 
4090
 
 
4091
      glk_stream_close (gms_inputlog_stream, NULL);
 
4092
      gms_inputlog_stream = NULL;
 
4093
 
 
4094
      gms_normal_string ("Glk input log is now off.\n");
 
4095
    }
 
4096
 
 
4097
  else if (strlen (argument) == 0)
 
4098
    {
 
4099
      gms_normal_string ("Glk input logging is ");
 
4100
      gms_normal_string (gms_inputlog_stream ? "on" : "off");
 
4101
      gms_normal_string (".\n");
 
4102
    }
 
4103
 
 
4104
  else
 
4105
    {
 
4106
      gms_normal_string ("Glk input logging can be ");
 
4107
      gms_standout_string ("on");
 
4108
      gms_normal_string (", or ");
 
4109
      gms_standout_string ("off");
 
4110
      gms_normal_string (".\n");
 
4111
    }
 
4112
}
 
4113
 
 
4114
 
 
4115
/*
 
4116
 * gms_command_readlog()
 
4117
 *
 
4118
 * Set the game input log, to read input from a file.
 
4119
 */
 
4120
static void
 
4121
gms_command_readlog (const char *argument)
 
4122
{
 
4123
  assert (argument);
 
4124
 
 
4125
  if (gms_strcasecmp (argument, "on") == 0)
 
4126
    {
 
4127
      frefid_t fileref;
 
4128
 
 
4129
      if (gms_readlog_stream)
 
4130
        {
 
4131
          gms_normal_string ("Glk read log is already on.\n");
 
4132
          return;
 
4133
        }
 
4134
 
 
4135
      fileref = glk_fileref_create_by_prompt (fileusage_InputRecord
 
4136
                                              | fileusage_BinaryMode,
 
4137
                                              filemode_Read, 0);
 
4138
      if (!fileref)
 
4139
        {
 
4140
          gms_standout_string ("Glk read log failed.\n");
 
4141
          return;
 
4142
        }
 
4143
 
 
4144
      if (!glk_fileref_does_file_exist (fileref))
 
4145
        {
 
4146
          glk_fileref_destroy (fileref);
 
4147
          gms_standout_string ("Glk read log failed.\n");
 
4148
          return;
 
4149
        }
 
4150
 
 
4151
      gms_readlog_stream = glk_stream_open_file (fileref, filemode_Read, 0);
 
4152
      glk_fileref_destroy (fileref);
 
4153
      if (!gms_readlog_stream)
 
4154
        {
 
4155
          gms_standout_string ("Glk read log failed.\n");
 
4156
          return;
 
4157
        }
 
4158
 
 
4159
      gms_normal_string ("Glk read log is now on.\n");
 
4160
    }
 
4161
 
 
4162
  else if (gms_strcasecmp (argument, "off") == 0)
 
4163
    {
 
4164
      if (!gms_readlog_stream)
 
4165
        {
 
4166
          gms_normal_string ("Glk read log is already off.\n");
 
4167
          return;
 
4168
        }
 
4169
 
 
4170
      glk_stream_close (gms_readlog_stream, NULL);
 
4171
      gms_readlog_stream = NULL;
 
4172
 
 
4173
      gms_normal_string ("Glk read log is now off.\n");
 
4174
    }
 
4175
 
 
4176
  else if (strlen (argument) == 0)
 
4177
    {
 
4178
      gms_normal_string ("Glk read log is ");
 
4179
      gms_normal_string (gms_readlog_stream ? "on" : "off");
 
4180
      gms_normal_string (".\n");
 
4181
    }
 
4182
 
 
4183
  else
 
4184
    {
 
4185
      gms_normal_string ("Glk read log can be ");
 
4186
      gms_standout_string ("on");
 
4187
      gms_normal_string (", or ");
 
4188
      gms_standout_string ("off");
 
4189
      gms_normal_string (".\n");
 
4190
    }
 
4191
}
 
4192
 
 
4193
 
 
4194
/*
 
4195
 * gms_command_abbreviations()
 
4196
 *
 
4197
 * Turn abbreviation expansions on and off.
 
4198
 */
 
4199
static void
 
4200
gms_command_abbreviations (const char *argument)
 
4201
{
 
4202
  assert (argument);
 
4203
 
 
4204
  if (gms_strcasecmp (argument, "on") == 0)
 
4205
    {
 
4206
      if (gms_abbreviations_enabled)
 
4207
        {
 
4208
          gms_normal_string ("Glk abbreviation expansions are already on.\n");
 
4209
          return;
 
4210
        }
 
4211
 
 
4212
      gms_abbreviations_enabled = TRUE;
 
4213
      gms_normal_string ("Glk abbreviation expansions are now on.\n");
 
4214
    }
 
4215
 
 
4216
  else if (gms_strcasecmp (argument, "off") == 0)
 
4217
    {
 
4218
      if (!gms_abbreviations_enabled)
 
4219
        {
 
4220
          gms_normal_string ("Glk abbreviation expansions are already off.\n");
 
4221
          return;
 
4222
        }
 
4223
 
 
4224
      gms_abbreviations_enabled = FALSE;
 
4225
      gms_normal_string ("Glk abbreviation expansions are now off.\n");
 
4226
    }
 
4227
 
 
4228
  else if (strlen (argument) == 0)
 
4229
    {
 
4230
      gms_normal_string ("Glk abbreviation expansions are ");
 
4231
      gms_normal_string (gms_abbreviations_enabled ? "on" : "off");
 
4232
      gms_normal_string (".\n");
 
4233
    }
 
4234
 
 
4235
  else
 
4236
    {
 
4237
      gms_normal_string ("Glk abbreviation expansions can be ");
 
4238
      gms_standout_string ("on");
 
4239
      gms_normal_string (", or ");
 
4240
      gms_standout_string ("off");
 
4241
      gms_normal_string (".\n");
 
4242
    }
 
4243
}
 
4244
 
 
4245
 
 
4246
/*
 
4247
 * gms_command_graphics()
 
4248
 *
 
4249
 * Enable or disable graphics more permanently than is done by the main
 
4250
 * interpreter.  Also, print out a few brief details about the graphics
 
4251
 * state of the program.
 
4252
 */
 
4253
static void
 
4254
gms_command_graphics (const char *argument)
 
4255
{
 
4256
  assert (argument);
 
4257
 
 
4258
  if (!gms_graphics_possible)
 
4259
    {
 
4260
      gms_normal_string ("Glk graphics are not available.\n");
 
4261
      return;
 
4262
    }
 
4263
 
 
4264
  if (gms_strcasecmp (argument, "on") == 0)
 
4265
    {
 
4266
      if (gms_graphics_enabled)
 
4267
        {
 
4268
          gms_normal_string ("Glk graphics are already on.\n");
 
4269
          return;
 
4270
        }
 
4271
 
 
4272
      gms_graphics_enabled = TRUE;
 
4273
 
 
4274
      /* If a picture is loaded, call the restart function to repaint it. */
 
4275
      if (gms_graphics_picture_is_available ())
 
4276
        {
 
4277
          if (!gms_graphics_open ())
 
4278
            {
 
4279
              gms_normal_string ("Glk graphics error.\n");
 
4280
              return;
 
4281
            }
 
4282
          gms_graphics_restart ();
 
4283
        }
 
4284
 
 
4285
      gms_normal_string ("Glk graphics are now on.\n");
 
4286
    }
 
4287
 
 
4288
  else if (gms_strcasecmp (argument, "off") == 0)
 
4289
    {
 
4290
      if (!gms_graphics_enabled)
 
4291
        {
 
4292
          gms_normal_string ("Glk graphics are already off.\n");
 
4293
          return;
 
4294
        }
 
4295
 
 
4296
      /*
 
4297
       * Set graphics to disabled, and stop any graphics processing.  Close
 
4298
       * the graphics window.
 
4299
       */
 
4300
      gms_graphics_enabled = FALSE;
 
4301
      gms_graphics_stop ();
 
4302
      gms_graphics_close ();
 
4303
 
 
4304
      gms_normal_string ("Glk graphics are now off.\n");
 
4305
    }
 
4306
 
 
4307
  else if (strlen (argument) == 0)
 
4308
    {
 
4309
      gms_normal_string ("Glk graphics are available,");
 
4310
      gms_normal_string (gms_graphics_enabled
 
4311
                         ? " and enabled.\n" : " but disabled.\n");
 
4312
 
 
4313
      if (gms_graphics_picture_is_available ())
 
4314
        {
 
4315
          int width, height, is_animated;
 
4316
 
 
4317
          if (gms_graphics_get_picture_details (&width, &height, &is_animated))
 
4318
            {
 
4319
              char buffer[16];
 
4320
 
 
4321
              gms_normal_string ("There is ");
 
4322
              gms_normal_string (is_animated ? "an animated" : "a");
 
4323
              gms_normal_string (" picture loaded, ");
 
4324
 
 
4325
              sprintf (buffer, "%d", width);
 
4326
              gms_normal_string (buffer);
 
4327
              gms_normal_string (" by ");
 
4328
 
 
4329
              sprintf (buffer, "%d", height);
 
4330
              gms_normal_string (buffer);
 
4331
 
 
4332
              gms_normal_string (" pixels.\n");
 
4333
            }
 
4334
        }
 
4335
 
 
4336
      if (!gms_graphics_interpreter_enabled ())
 
4337
        gms_normal_string ("Interpreter graphics are disabled.\n");
 
4338
 
 
4339
      if (gms_graphics_enabled && gms_graphics_are_displayed ())
 
4340
        {
 
4341
          int color_count, is_active;
 
4342
          const char *gamma;
 
4343
 
 
4344
          if (gms_graphics_get_rendering_details (&gamma, &color_count,
 
4345
                                                  &is_active))
 
4346
            {
 
4347
              char buffer[16];
 
4348
 
 
4349
              gms_normal_string ("Graphics are ");
 
4350
              gms_normal_string (is_active ? "active, " : "displayed, ");
 
4351
 
 
4352
              sprintf (buffer, "%d", color_count);
 
4353
              gms_normal_string (buffer);
 
4354
              gms_normal_string (" colours");
 
4355
 
 
4356
              if (gms_gamma_mode == GAMMA_OFF)
 
4357
                gms_normal_string (", without gamma correction");
 
4358
              else
 
4359
                {
 
4360
                  gms_normal_string (", with gamma ");
 
4361
                  gms_normal_string (gamma);
 
4362
                  gms_normal_string (" correction");
 
4363
                }
 
4364
              gms_normal_string (".\n");
 
4365
            }
 
4366
          else
 
4367
            gms_normal_string ("Graphics are being displayed.\n");
 
4368
        }
 
4369
 
 
4370
      if (gms_graphics_enabled && !gms_graphics_are_displayed ())
 
4371
        gms_normal_string ("Graphics are not being displayed.\n");
 
4372
    }
 
4373
 
 
4374
  else
 
4375
    {
 
4376
      gms_normal_string ("Glk graphics can be ");
 
4377
      gms_standout_string ("on");
 
4378
      gms_normal_string (", or ");
 
4379
      gms_standout_string ("off");
 
4380
      gms_normal_string (".\n");
 
4381
    }
 
4382
}
 
4383
 
 
4384
 
 
4385
/*
 
4386
 * gms_command_gamma()
 
4387
 *
 
4388
 * Enable or disable picture gamma corrections.
 
4389
 */
 
4390
static void
 
4391
gms_command_gamma (const char *argument)
 
4392
{
 
4393
  assert (argument);
 
4394
 
 
4395
  if (!gms_graphics_possible)
 
4396
    {
 
4397
      gms_normal_string ("Glk automatic gamma correction is not available.\n");
 
4398
      return;
 
4399
    }
 
4400
 
 
4401
  if (gms_strcasecmp (argument, "high") == 0)
 
4402
    {
 
4403
      if (gms_gamma_mode == GAMMA_HIGH)
 
4404
        {
 
4405
          gms_normal_string ("Glk automatic gamma correction mode is"
 
4406
                             " already 'high'.\n");
 
4407
          return;
 
4408
        }
 
4409
 
 
4410
      gms_gamma_mode = GAMMA_HIGH;
 
4411
      gms_graphics_restart ();
 
4412
 
 
4413
      gms_normal_string ("Glk automatic gamma correction mode is"
 
4414
                         " now 'high'.\n");
 
4415
    }
 
4416
 
 
4417
  else if (gms_strcasecmp (argument, "normal") == 0
 
4418
           || gms_strcasecmp (argument, "on") == 0)
 
4419
    {
 
4420
      if (gms_gamma_mode == GAMMA_NORMAL)
 
4421
        {
 
4422
          gms_normal_string ("Glk automatic gamma correction mode is"
 
4423
                             " already 'normal'.\n");
 
4424
          return;
 
4425
        }
 
4426
 
 
4427
      gms_gamma_mode = GAMMA_NORMAL;
 
4428
      gms_graphics_restart ();
 
4429
 
 
4430
      gms_normal_string ("Glk automatic gamma correction mode is"
 
4431
                         " now 'normal'.\n");
 
4432
    }
 
4433
 
 
4434
  else if (gms_strcasecmp (argument, "none") == 0
 
4435
           || gms_strcasecmp (argument, "off") == 0)
 
4436
    {
 
4437
      if (gms_gamma_mode == GAMMA_OFF)
 
4438
        {
 
4439
          gms_normal_string ("Glk automatic gamma correction mode is"
 
4440
                             " already 'off'.\n");
 
4441
          return;
 
4442
        }
 
4443
 
 
4444
      gms_gamma_mode = GAMMA_OFF;
 
4445
      gms_graphics_restart ();
 
4446
 
 
4447
      gms_normal_string ("Glk automatic gamma correction mode is"
 
4448
                         " now 'off'.\n");
 
4449
    }
 
4450
 
 
4451
  else if (strlen (argument) == 0)
 
4452
    {
 
4453
      gms_normal_string ("Glk automatic gamma correction mode is '");
 
4454
      switch (gms_gamma_mode)
 
4455
        {
 
4456
        case GAMMA_OFF:
 
4457
          gms_normal_string ("off");
 
4458
          break;
 
4459
        case GAMMA_NORMAL:
 
4460
          gms_normal_string ("normal");
 
4461
          break;
 
4462
        case GAMMA_HIGH:
 
4463
          gms_normal_string ("high");
 
4464
          break;
 
4465
        }
 
4466
      gms_normal_string ("'.\n");
 
4467
    }
 
4468
 
 
4469
  else
 
4470
    {
 
4471
      gms_normal_string ("Glk automatic gamma correction mode can be ");
 
4472
      gms_standout_string ("high");
 
4473
      gms_normal_string (", ");
 
4474
      gms_standout_string ("normal");
 
4475
      gms_normal_string (", or ");
 
4476
      gms_standout_string ("off");
 
4477
      gms_normal_string (".\n");
 
4478
    }
 
4479
}
 
4480
 
 
4481
 
 
4482
/*
 
4483
 * gms_command_animations()
 
4484
 *
 
4485
 * Enable or disable picture animations.
 
4486
 */
 
4487
static void
 
4488
gms_command_animations (const char *argument)
 
4489
{
 
4490
  assert (argument);
 
4491
 
 
4492
  if (!gms_graphics_possible)
 
4493
    {
 
4494
      gms_normal_string ("Glk graphics animations are not available.\n");
 
4495
      return;
 
4496
    }
 
4497
 
 
4498
  if (gms_strcasecmp (argument, "on") == 0)
 
4499
    {
 
4500
      int is_animated;
 
4501
 
 
4502
      if (gms_animation_enabled)
 
4503
        {
 
4504
          gms_normal_string ("Glk graphics animations are already on.\n");
 
4505
          return;
 
4506
        }
 
4507
 
 
4508
      /*
 
4509
       * Set animation to on, and restart graphics if the current picture
 
4510
       * is animated; if it isn't, we can leave it displayed as is, since
 
4511
       * changing animation mode doesn't affect this picture.
 
4512
       */
 
4513
      gms_animation_enabled = TRUE;
 
4514
      if (gms_graphics_get_picture_details (NULL, NULL, &is_animated))
 
4515
        {
 
4516
          if (is_animated)
 
4517
            gms_graphics_restart ();
 
4518
        }
 
4519
 
 
4520
      gms_normal_string ("Glk graphics animations are now on.\n");
 
4521
    }
 
4522
 
 
4523
  else if (gms_strcasecmp (argument, "off") == 0)
 
4524
    {
 
4525
      int is_animated;
 
4526
 
 
4527
      if (!gms_animation_enabled)
 
4528
        {
 
4529
          gms_normal_string ("Glk graphics animations are already off.\n");
 
4530
          return;
 
4531
        }
 
4532
 
 
4533
      gms_animation_enabled = FALSE;
 
4534
      if (gms_graphics_get_picture_details (NULL, NULL, &is_animated))
 
4535
        {
 
4536
          if (is_animated)
 
4537
            gms_graphics_restart ();
 
4538
        }
 
4539
 
 
4540
      gms_normal_string ("Glk graphics animations are now off.\n");
 
4541
    }
 
4542
 
 
4543
  else if (strlen (argument) == 0)
 
4544
    {
 
4545
      gms_normal_string ("Glk graphics animations are ");
 
4546
      gms_normal_string (gms_animation_enabled ? "on" : "off");
 
4547
      gms_normal_string (".\n");
 
4548
    }
 
4549
 
 
4550
  else
 
4551
    {
 
4552
      gms_normal_string ("Glk graphics animations can be ");
 
4553
      gms_standout_string ("on");
 
4554
      gms_normal_string (", or ");
 
4555
      gms_standout_string ("off");
 
4556
      gms_normal_string (".\n");
 
4557
    }
 
4558
}
 
4559
 
 
4560
 
 
4561
/*
 
4562
 * gms_command_prompts()
 
4563
 *
 
4564
 * Turn the extra "> " prompt output on and off.
 
4565
 */
 
4566
static void
 
4567
gms_command_prompts (const char *argument)
 
4568
{
 
4569
  assert (argument);
 
4570
 
 
4571
  if (gms_strcasecmp (argument, "on") == 0)
 
4572
    {
 
4573
      if (gms_prompt_enabled)
 
4574
        {
 
4575
          gms_normal_string ("Glk extra prompts are already on.\n");
 
4576
          return;
 
4577
        }
 
4578
 
 
4579
      gms_prompt_enabled = TRUE;
 
4580
      gms_normal_string ("Glk extra prompts are now on.\n");
 
4581
 
 
4582
      /* Check for a game prompt to clear the flag. */
 
4583
      gms_game_prompted ();
 
4584
    }
 
4585
 
 
4586
  else if (gms_strcasecmp (argument, "off") == 0)
 
4587
    {
 
4588
      if (!gms_prompt_enabled)
 
4589
        {
 
4590
          gms_normal_string ("Glk extra prompts are already off.\n");
 
4591
          return;
 
4592
        }
 
4593
 
 
4594
      gms_prompt_enabled = FALSE;
 
4595
      gms_normal_string ("Glk extra prompts are now off.\n");
 
4596
    }
 
4597
 
 
4598
  else if (strlen (argument) == 0)
 
4599
    {
 
4600
      gms_normal_string ("Glk extra prompts are ");
 
4601
      gms_normal_string (gms_prompt_enabled ? "on" : "off");
 
4602
      gms_normal_string (".\n");
 
4603
    }
 
4604
 
 
4605
  else
 
4606
    {
 
4607
      gms_normal_string ("Glk extra prompts can be ");
 
4608
      gms_standout_string ("on");
 
4609
      gms_normal_string (", or ");
 
4610
      gms_standout_string ("off");
 
4611
      gms_normal_string (".\n");
 
4612
    }
 
4613
}
 
4614
 
 
4615
 
 
4616
/*
 
4617
 * gms_command_print_version_number()
 
4618
 * gms_command_version()
 
4619
 *
 
4620
 * Print out the Glk library version number.
 
4621
 */
 
4622
static void
 
4623
gms_command_print_version_number (glui32 version)
 
4624
{
 
4625
  char buffer[64];
 
4626
 
 
4627
  sprintf (buffer, "%lu.%lu.%lu",
 
4628
          (unsigned long) version >> 16,
 
4629
          (unsigned long) (version >> 8) & 0xff,
 
4630
          (unsigned long) version & 0xff);
 
4631
  gms_normal_string (buffer);
 
4632
}
 
4633
 
 
4634
static void
 
4635
gms_command_version (const char *argument)
 
4636
{
 
4637
  glui32 version;
 
4638
  assert (argument);
 
4639
 
 
4640
  gms_normal_string ("This is version ");
 
4641
  gms_command_print_version_number (GMS_PORT_VERSION);
 
4642
  gms_normal_string (" of the Glk Magnetic port.\n");
 
4643
 
 
4644
  version = glk_gestalt (gestalt_Version, 0);
 
4645
  gms_normal_string ("The Glk library version is ");
 
4646
  gms_command_print_version_number (version);
 
4647
  gms_normal_string (".\n");
 
4648
}
 
4649
 
 
4650
 
 
4651
/*
 
4652
 * gms_command_commands()
 
4653
 *
 
4654
 * Turn command escapes off.  Once off, there's no way to turn them back on.
 
4655
 * Commands must be on already to enter this function.
 
4656
 */
 
4657
static void
 
4658
gms_command_commands (const char *argument)
 
4659
{
 
4660
  assert (argument);
 
4661
 
 
4662
  if (gms_strcasecmp (argument, "on") == 0)
 
4663
    {
 
4664
      gms_normal_string ("Glk commands are already on.\n");
 
4665
    }
 
4666
 
 
4667
  else if (gms_strcasecmp (argument, "off") == 0)
 
4668
    {
 
4669
      gms_commands_enabled = FALSE;
 
4670
      gms_normal_string ("Glk commands are now off.\n");
 
4671
    }
 
4672
 
 
4673
  else if (strlen (argument) == 0)
 
4674
    {
 
4675
      gms_normal_string ("Glk commands are ");
 
4676
      gms_normal_string (gms_commands_enabled ? "on" : "off");
 
4677
      gms_normal_string (".\n");
 
4678
    }
 
4679
 
 
4680
  else
 
4681
    {
 
4682
      gms_normal_string ("Glk commands can be ");
 
4683
      gms_standout_string ("on");
 
4684
      gms_normal_string (", or ");
 
4685
      gms_standout_string ("off");
 
4686
      gms_normal_string (".\n");
 
4687
    }
 
4688
}
 
4689
 
 
4690
 
 
4691
/*
 
4692
 * gms_command_license()
 
4693
 *
 
4694
 * Print licensing terms.
 
4695
 */
 
4696
static void
 
4697
gms_command_license (const char *argument)
 
4698
{
 
4699
  assert (argument);
 
4700
 
 
4701
  gms_normal_string ("This program is free software; you can redistribute it"
 
4702
                      " and/or modify it under the terms of version 2 of the"
 
4703
                      " GNU General Public License as published by the Free"
 
4704
                      " Software Foundation.\n\n");
 
4705
 
 
4706
  gms_normal_string ("This program is distributed in the hope that it will be"
 
4707
                      " useful, but ");
 
4708
  gms_standout_string ("WITHOUT ANY WARRANTY");
 
4709
  gms_normal_string ("; without even the implied warranty of ");
 
4710
  gms_standout_string ("MERCHANTABILITY");
 
4711
  gms_normal_string (" or ");
 
4712
  gms_standout_string ("FITNESS FOR A PARTICULAR PURPOSE");
 
4713
  gms_normal_string (".  See the GNU General Public License for more"
 
4714
                      " details.\n\n");
 
4715
 
 
4716
  gms_normal_string ("You should have received a copy of the GNU General"
 
4717
                      " Public License along with this program; if not, write"
 
4718
                      " to the Free Software Foundation, Inc., 59 Temple"
 
4719
                      " Place, Suite 330, Boston, MA  02111-1307 USA\n\n");
 
4720
 
 
4721
  gms_normal_string ("Please report any bugs, omissions, or misfeatures to ");
 
4722
  gms_standout_string ("simon_baldwin@yahoo.com");
 
4723
  gms_normal_string (".\n");
 
4724
}
 
4725
 
 
4726
 
 
4727
/* Glk subcommands and handler functions. */
 
4728
typedef const struct
 
4729
{
 
4730
  const char * const command;                     /* Glk subcommand. */
 
4731
  void (* const handler) (const char *argument);  /* Subcommand handler. */
 
4732
  const int takes_argument;                       /* Argument flag. */
 
4733
  const int undo_return;                          /* "Undo" return value. */
 
4734
} gms_command_t;
 
4735
typedef gms_command_t *gms_commandref_t;
 
4736
 
 
4737
static void gms_command_summary (const char *argument);
 
4738
static void gms_command_help (const char *argument);
 
4739
 
 
4740
static gms_command_t GMS_COMMAND_TABLE[] = {
 
4741
  {"summary",        gms_command_summary,        FALSE, FALSE},
 
4742
  {"undo",           gms_command_undo,           FALSE, TRUE},
 
4743
  {"script",         gms_command_script,         TRUE,  FALSE},
 
4744
  {"inputlog",       gms_command_inputlog,       TRUE,  FALSE},
 
4745
  {"readlog",        gms_command_readlog,        TRUE,  FALSE},
 
4746
  {"abbreviations",  gms_command_abbreviations,  TRUE,  FALSE},
 
4747
  {"graphics",       gms_command_graphics,       TRUE,  FALSE},
 
4748
  {"gamma",          gms_command_gamma,          TRUE,  FALSE},
 
4749
  {"animations",     gms_command_animations,     TRUE,  FALSE},
 
4750
  {"prompts",        gms_command_prompts,        TRUE,  FALSE},
 
4751
  {"version",        gms_command_version,        FALSE, FALSE},
 
4752
  {"commands",       gms_command_commands,       TRUE,  FALSE},
 
4753
  {"license",        gms_command_license,        FALSE, FALSE},
 
4754
  {"help",           gms_command_help,           TRUE,  FALSE},
 
4755
  {NULL, NULL, FALSE, FALSE}
 
4756
};
 
4757
 
 
4758
 
 
4759
/*
 
4760
 * gms_command_summary()
 
4761
 *
 
4762
 * Report all current Glk settings.
 
4763
 */
 
4764
static void
 
4765
gms_command_summary (const char *argument)
 
4766
{
 
4767
  gms_commandref_t entry;
 
4768
  assert (argument);
 
4769
 
 
4770
  /*
 
4771
   * Call handlers that have status to report with an empty argument,
 
4772
   * prompting each to print its current setting.
 
4773
   */
 
4774
  for (entry = GMS_COMMAND_TABLE; entry->command; entry++)
 
4775
    {
 
4776
      if (entry->handler == gms_command_summary
 
4777
            || entry->handler == gms_command_undo
 
4778
            || entry->handler == gms_command_license
 
4779
            || entry->handler == gms_command_help)
 
4780
        continue;
 
4781
 
 
4782
      entry->handler ("");
 
4783
    }
 
4784
}
 
4785
 
 
4786
 
 
4787
/*
 
4788
 * gms_command_help()
 
4789
 *
 
4790
 * Document the available Glk commands.
 
4791
 */
 
4792
static void
 
4793
gms_command_help (const char *command)
 
4794
{
 
4795
  gms_commandref_t entry, matched;
 
4796
  assert (command);
 
4797
 
 
4798
  if (strlen (command) == 0)
 
4799
    {
 
4800
      gms_normal_string ("Glk commands are");
 
4801
      for (entry = GMS_COMMAND_TABLE; entry->command; entry++)
 
4802
        {
 
4803
          gms_commandref_t next;
 
4804
 
 
4805
          next = entry + 1;
 
4806
          gms_normal_string (next->command ? " " : " and ");
 
4807
          gms_standout_string (entry->command);
 
4808
          gms_normal_string (next->command ? "," : ".\n\n");
 
4809
        }
 
4810
 
 
4811
      gms_normal_string ("Glk commands may be abbreviated, as long as"
 
4812
                          " the abbreviation is unambiguous.  Use ");
 
4813
      gms_standout_string ("glk help");
 
4814
      gms_normal_string (" followed by a Glk command name for help on that"
 
4815
                          " command.\n");
 
4816
      return;
 
4817
    }
 
4818
 
 
4819
  matched = NULL;
 
4820
  for (entry = GMS_COMMAND_TABLE; entry->command; entry++)
 
4821
    {
 
4822
      if (gms_strncasecmp (command, entry->command, strlen (command)) == 0)
 
4823
        {
 
4824
          if (matched)
 
4825
            {
 
4826
              gms_normal_string ("The Glk command ");
 
4827
              gms_standout_string (command);
 
4828
              gms_normal_string (" is ambiguous.  Try ");
 
4829
              gms_standout_string ("glk help");
 
4830
              gms_normal_string (" for more information.\n");
 
4831
              return;
 
4832
            }
 
4833
          matched = entry;
 
4834
        }
 
4835
    }
 
4836
  if (!matched)
 
4837
    {
 
4838
      gms_normal_string ("The Glk command ");
 
4839
      gms_standout_string (command);
 
4840
      gms_normal_string (" is not valid.  Try ");
 
4841
      gms_standout_string ("glk help");
 
4842
      gms_normal_string (" for more information.\n");
 
4843
      return;
 
4844
    }
 
4845
 
 
4846
  if (matched->handler == gms_command_summary)
 
4847
    {
 
4848
      gms_normal_string ("Prints a summary of all the current Glk Magnetic"
 
4849
                          " settings.\n");
 
4850
    }
 
4851
 
 
4852
  else if (matched->handler == gms_command_undo)
 
4853
    {
 
4854
      gms_normal_string ("Undoes a single game turn.\n\nEquivalent to the"
 
4855
                         " standalone game 'undo' command.\n");
 
4856
    }
 
4857
 
 
4858
  else if (matched->handler == gms_command_script)
 
4859
    {
 
4860
      gms_normal_string ("Logs the game's output to a file.\n\nUse ");
 
4861
      gms_standout_string ("glk script on");
 
4862
      gms_normal_string (" to begin logging game output, and ");
 
4863
      gms_standout_string ("glk script off");
 
4864
      gms_normal_string (" to end it.  Glk Magnetic will ask you for a file"
 
4865
                          " when you turn scripts on.\n");
 
4866
    }
 
4867
 
 
4868
  else if (matched->handler == gms_command_inputlog)
 
4869
    {
 
4870
      gms_normal_string ("Records the commands you type into a game.\n\nUse ");
 
4871
      gms_standout_string ("glk inputlog on");
 
4872
      gms_normal_string (", to begin recording your commands, and ");
 
4873
      gms_standout_string ("glk inputlog off");
 
4874
      gms_normal_string (" to turn off input logs.  You can play back"
 
4875
                          " recorded commands into a game with the ");
 
4876
      gms_standout_string ("glk readlog");
 
4877
      gms_normal_string (" command.\n");
 
4878
    }
 
4879
 
 
4880
  else if (matched->handler == gms_command_readlog)
 
4881
    {
 
4882
      gms_normal_string ("Plays back commands recorded with ");
 
4883
      gms_standout_string ("glk inputlog on");
 
4884
      gms_normal_string (".\n\nUse ");
 
4885
      gms_standout_string ("glk readlog on");
 
4886
      gms_normal_string (".  Command play back stops at the end of the"
 
4887
                          " file.  You can also play back commands from a"
 
4888
                          " text file created using any standard editor.\n");
 
4889
    }
 
4890
 
 
4891
  else if (matched->handler == gms_command_abbreviations)
 
4892
    {
 
4893
      gms_normal_string ("Controls abbreviation expansion.\n\nGlk Magnetic"
 
4894
                          " automatically expands several standard single"
 
4895
                          " letter abbreviations for you; for example, \"x\""
 
4896
                          " becomes \"examine\".  Use ");
 
4897
      gms_standout_string ("glk abbreviations on");
 
4898
      gms_normal_string (" to turn this feature on, and ");
 
4899
      gms_standout_string ("glk abbreviations off");
 
4900
      gms_normal_string (" to turn it off.  While the feature is on, you"
 
4901
                          " can bypass abbreviation expansion for an"
 
4902
                          " individual game command by prefixing it with a"
 
4903
                          " single quote.\n");
 
4904
    }
 
4905
 
 
4906
  else if (matched->handler == gms_command_graphics)
 
4907
    {
 
4908
      gms_normal_string ("Turns interpreter graphics on and off.\n\nUse ");
 
4909
      gms_standout_string ("glk graphics on");
 
4910
      gms_normal_string (" to enable interpreter graphics, and ");
 
4911
      gms_standout_string ("glk graphics off");
 
4912
      gms_normal_string (" to turn graphics off and close the graphics window."
 
4913
                         "  This control works slightly differently to the"
 
4914
                         " 'graphics' command in Magnetic Windows and Magnetic"
 
4915
                         " Scrolls games themselves; the game's 'graphics'"
 
4916
                         " command may disable new images, but leave old ones"
 
4917
                         " displayed.  For graphics to be displayed, they"
 
4918
                         " must be turned on in both the game and the"
 
4919
                         " interpreter.\n");
 
4920
    }
 
4921
 
 
4922
  else if (matched->handler == gms_command_gamma)
 
4923
    {
 
4924
      gms_normal_string ("Sets the level of automatic gamma correction applied"
 
4925
                         " to game graphics.\n\nUse ");
 
4926
      gms_standout_string ("glk gamma normal");
 
4927
      gms_normal_string (" to set moderate automatic colour contrast"
 
4928
                         " correction, ");
 
4929
      gms_standout_string ("glk gamma high");
 
4930
      gms_normal_string (" to set high automatic colour contrast correction,"
 
4931
                         " or ");
 
4932
      gms_standout_string ("glk gamma off");
 
4933
      gms_normal_string (" to turn off all automatic gamma correction.\n");
 
4934
    }
 
4935
 
 
4936
  else if (matched->handler == gms_command_animations)
 
4937
    {
 
4938
      gms_normal_string ("Turns graphic animations on and off.\n\nUse ");
 
4939
      gms_standout_string ("glk animation on");
 
4940
      gms_normal_string (" to enable animations, or ");
 
4941
      gms_standout_string ("glk animation off");
 
4942
      gms_normal_string (" to turn animations off.  Not all game graphics are"
 
4943
                         " animated, so this control works only on graphics"
 
4944
                         " that are animated.  When animation is off, Glk"
 
4945
                         " Magnetic displays only the static portions of a"
 
4946
                         " game's pictures.\n");
 
4947
    }
 
4948
 
 
4949
  else if (matched->handler == gms_command_prompts)
 
4950
    {
 
4951
      gms_normal_string ("Controls extra input prompting.\n\n"
 
4952
                          "Glk Magnetic can issue a replacement '>' input"
 
4953
                          " prompt if it detects that the game hasn't prompted"
 
4954
                          " after, say, an empty input line.  Use ");
 
4955
      gms_standout_string ("glk prompts on");
 
4956
      gms_normal_string (" to turn this feature on, and ");
 
4957
      gms_standout_string ("glk prompts off");
 
4958
      gms_normal_string (" to turn it off.\n");
 
4959
    }
 
4960
 
 
4961
  else if (matched->handler == gms_command_version)
 
4962
    {
 
4963
      gms_normal_string ("Prints the version numbers of the Glk library"
 
4964
                          " and the Glk Magnetic port.\n");
 
4965
    }
 
4966
 
 
4967
  else if (matched->handler == gms_command_commands)
 
4968
    {
 
4969
      gms_normal_string ("Turn off Glk commands.\n\nUse ");
 
4970
      gms_standout_string ("glk commands off");
 
4971
      gms_normal_string (" to disable all Glk commands, including this one."
 
4972
                          "  Once turned off, there is no way to turn Glk"
 
4973
                          " commands back on while inside the game.\n");
 
4974
    }
 
4975
 
 
4976
  else if (matched->handler == gms_command_license)
 
4977
    {
 
4978
      gms_normal_string ("Prints Glk Magnetic's software license.\n");
 
4979
    }
 
4980
 
 
4981
  else if (matched->handler == gms_command_help)
 
4982
    gms_command_help ("");
 
4983
 
 
4984
  else
 
4985
    gms_normal_string ("There is no help available on that Glk command."
 
4986
                        "  Sorry.\n");
 
4987
}
 
4988
 
 
4989
 
 
4990
/*
 
4991
 * gms_command_escape()
 
4992
 *
 
4993
 * This function is handed each input line.  If the line contains a specific
 
4994
 * Glk port command, handle it and return TRUE, otherwise return FALSE.
 
4995
 *
 
4996
 * On unambiguous returns, it will also set the value for undo_command to the
 
4997
 * table undo return value.
 
4998
 */
 
4999
static int
 
5000
gms_command_escape (const char *string, int *undo_command)
 
5001
{
 
5002
  int posn;
 
5003
  char *string_copy, *command, *argument;
 
5004
  assert (string && undo_command);
 
5005
 
 
5006
  /*
 
5007
   * Return FALSE if the string doesn't begin with the Glk command escape
 
5008
   * introducer.
 
5009
   */
 
5010
  posn = strspn (string, "\t ");
 
5011
  if (gms_strncasecmp (string + posn, "glk", strlen ("glk")) != 0)
 
5012
    return FALSE;
 
5013
 
 
5014
  /* Take a copy of the string, without any leading space or introducer. */
 
5015
  string_copy = gms_malloc (strlen (string + posn) + 1 - strlen ("glk"));
 
5016
  strcpy (string_copy, string + posn + strlen ("glk"));
 
5017
 
 
5018
  /*
 
5019
   * Find the subcommand; the first word in the string copy.  Find its end,
 
5020
   * and ensure it terminates with a NUL.
 
5021
   */
 
5022
  posn = strspn (string_copy, "\t ");
 
5023
  command = string_copy + posn;
 
5024
  posn += strcspn (string_copy + posn, "\t ");
 
5025
  if (string_copy[posn] != '\0')
 
5026
    string_copy[posn++] = '\0';
 
5027
 
 
5028
  /*
 
5029
   * Now find any argument data for the command, ensuring it too terminates
 
5030
   * with a NUL.
 
5031
   */
 
5032
  posn += strspn (string_copy + posn, "\t ");
 
5033
  argument = string_copy + posn;
 
5034
  posn += strcspn (string_copy + posn, "\t ");
 
5035
  string_copy[posn] = '\0';
 
5036
 
 
5037
  /*
 
5038
   * Try to handle the command and argument as a Glk subcommand.  If it
 
5039
   * doesn't run unambiguously, print command usage.  Treat an empty command
 
5040
   * as "help".
 
5041
   */
 
5042
  if (strlen (command) > 0)
 
5043
    {
 
5044
      gms_commandref_t entry, matched;
 
5045
      int matches;
 
5046
 
 
5047
      /*
 
5048
       * Search for the first unambiguous table command string matching
 
5049
       * the command passed in.
 
5050
       */
 
5051
      matches = 0;
 
5052
      matched = NULL;
 
5053
      for (entry = GMS_COMMAND_TABLE; entry->command; entry++)
 
5054
        {
 
5055
          if (gms_strncasecmp (command, entry->command, strlen (command)) == 0)
 
5056
            {
 
5057
              matches++;
 
5058
              matched = entry;
 
5059
            }
 
5060
        }
 
5061
 
 
5062
      /* If the match was unambiguous, call the command handler. */
 
5063
      if (matches == 1)
 
5064
        {
 
5065
          if (!matched->undo_return)
 
5066
            gms_normal_char ('\n');
 
5067
          matched->handler (argument);
 
5068
 
 
5069
          if (!matched->takes_argument && strlen (argument) > 0)
 
5070
            {
 
5071
              gms_normal_string ("[The ");
 
5072
              gms_standout_string (matched->command);
 
5073
              gms_normal_string (" command ignores arguments.]\n");
 
5074
            }
 
5075
 
 
5076
          *undo_command = matched->undo_return;
 
5077
        }
 
5078
 
 
5079
      /* No match, or the command was ambiguous. */
 
5080
      else
 
5081
        {
 
5082
          gms_normal_string ("\nThe Glk command ");
 
5083
          gms_standout_string (command);
 
5084
          gms_normal_string (" is ");
 
5085
          gms_normal_string (matches == 0 ? "not valid" : "ambiguous");
 
5086
          gms_normal_string (".  Try ");
 
5087
          gms_standout_string ("glk help");
 
5088
          gms_normal_string (" for more information.\n");
 
5089
        }
 
5090
    }
 
5091
  else
 
5092
    {
 
5093
      gms_normal_char ('\n');
 
5094
      gms_command_help ("");
 
5095
    }
 
5096
 
 
5097
  /* The string contained a Glk command; return TRUE. */
 
5098
  free (string_copy);
 
5099
  return TRUE;
 
5100
}
 
5101
 
 
5102
 
 
5103
/*
 
5104
 * gms_command_undo_special()
 
5105
 *
 
5106
 * This function makes a special case of the input line containing the single
 
5107
 * word "undo", treating it as if it is "glk undo".  This makes life a bit
 
5108
 * more convenient for the player, since it's the same behavior that most
 
5109
 * other IF systems have.  It returns TRUE if "undo" found, FALSE otherwise.
 
5110
 */
 
5111
static int
 
5112
gms_command_undo_special (const char *string)
 
5113
{
 
5114
  int posn, end;
 
5115
  assert (string);
 
5116
 
 
5117
  /* Find the start and end of the first string word. */
 
5118
  posn = strspn (string, "\t ");
 
5119
  end = posn + strcspn (string + posn, "\t ");
 
5120
 
 
5121
  /* See if string contains an "undo" request, with nothing following. */
 
5122
  if (end - posn == strlen ("undo")
 
5123
      && gms_strncasecmp (string + posn, "undo", end - posn) == 0)
 
5124
    {
 
5125
      posn = end + strspn (string + end, "\t ");
 
5126
      if (string[posn] == '\0')
 
5127
        return TRUE;
 
5128
    }
 
5129
 
 
5130
  return FALSE;
 
5131
}
 
5132
 
 
5133
 
 
5134
/*---------------------------------------------------------------------*/
 
5135
/*  Glk port input functions                                           */
 
5136
/*---------------------------------------------------------------------*/
 
5137
 
 
5138
/*
 
5139
 * Input buffer allocated for reading input lines.  The buffer is filled
 
5140
 * from either an input log, if one is currently being read, or from Glk
 
5141
 * line input.  We also need an "undo" notification flag.
 
5142
 */
 
5143
enum { GMS_INPUTBUFFER_LENGTH = 256 };
 
5144
static char gms_input_buffer[GMS_INPUTBUFFER_LENGTH];
 
5145
static int gms_input_length = 0,
 
5146
           gms_input_cursor = 0,
 
5147
           gms_undo_notification = FALSE;
 
5148
 
 
5149
/* Table of single-character command abbreviations. */
 
5150
typedef const struct
 
5151
{
 
5152
  const char abbreviation;       /* Abbreviation character. */
 
5153
  const char * const expansion;  /* Expansion string. */
 
5154
} gms_abbreviation_t;
 
5155
typedef gms_abbreviation_t *gms_abbreviationref_t;
 
5156
 
 
5157
static gms_abbreviation_t GMS_ABBREVIATIONS[] = {
 
5158
  {'c', "close"},    {'g', "again"},  {'i', "inventory"},
 
5159
  {'k', "attack"},   {'l', "look"},   {'p', "open"},
 
5160
  {'q', "quit"},     {'r', "drop"},   {'t', "take"},
 
5161
  {'x', "examine"},  {'y', "yes"},    {'z', "wait"},
 
5162
  {'\0', NULL}
 
5163
};
 
5164
 
 
5165
 
 
5166
/*
 
5167
 * gms_expand_abbreviations()
 
5168
 *
 
5169
 * Expand a few common one-character abbreviations commonly found in other
 
5170
 * game systems, but not always normal in Magnetic Scrolls games.
 
5171
 */
 
5172
static void
 
5173
gms_expand_abbreviations (char *buffer, int size)
 
5174
{
 
5175
  char *command, abbreviation;
 
5176
  const char *expansion;
 
5177
  gms_abbreviationref_t entry;
 
5178
  assert (buffer);
 
5179
 
 
5180
  /* Ignore anything that isn't a single letter command. */
 
5181
  command = buffer + strspn (buffer, "\t ");
 
5182
  if (!(strlen (command) == 1
 
5183
        || (strlen (command) > 1 && isspace (command[1]))))
 
5184
    return;
 
5185
 
 
5186
  /* Scan the abbreviations table for a match. */
 
5187
  abbreviation = glk_char_to_lower ((unsigned char) command[0]);
 
5188
  expansion = NULL;
 
5189
  for (entry = GMS_ABBREVIATIONS; entry->expansion; entry++)
 
5190
    {
 
5191
      if (entry->abbreviation == abbreviation)
 
5192
        {
 
5193
          expansion = entry->expansion;
 
5194
          break;
 
5195
        }
 
5196
    }
 
5197
 
 
5198
  /*
 
5199
   * If a match found, check for a fit, then replace the character with the
 
5200
   * expansion string.
 
5201
   */
 
5202
  if (expansion)
 
5203
    {
 
5204
      if (strlen (buffer) + strlen (expansion) - 1 >= size)
 
5205
        return;
 
5206
 
 
5207
      memmove (command + strlen (expansion) - 1, command, strlen (command) + 1);
 
5208
      memcpy (command, expansion, strlen (expansion));
 
5209
 
 
5210
#if 0
 
5211
      gms_standout_string ("[");
 
5212
      gms_standout_char (abbreviation);
 
5213
      gms_standout_string (" -> ");
 
5214
      gms_standout_string (expansion);
 
5215
      gms_standout_string ("]\n");
 
5216
#endif
 
5217
    }
 
5218
}
 
5219
 
 
5220
 
 
5221
/*
 
5222
 * gms_buffer_input
 
5223
 *
 
5224
 * Read and buffer a line of input.  If there is an input log active, then
 
5225
 * data is taken by reading this first.  Otherwise, the function gets a
 
5226
 * line from Glk.
 
5227
 *
 
5228
 * It also makes special cases of some lines read from the user, either
 
5229
 * handling commands inside them directly, or expanding abbreviations as
 
5230
 * appropriate.  This is not reflected in the buffer, which is adjusted as
 
5231
 * required before returning.
 
5232
 */
 
5233
static void
 
5234
gms_buffer_input (void)
 
5235
{
 
5236
  event_t event;
 
5237
 
 
5238
  /*
 
5239
   * Update the current status line display, and flush any pending buffered
 
5240
   * output.
 
5241
   */
 
5242
  gms_status_notify ();
 
5243
  gms_output_flush ();
 
5244
 
 
5245
  /*
 
5246
   * Magnetic Windows games tend not to issue a prompt after reading an empty
 
5247
   * line of input.  This can make for a very blank looking screen.
 
5248
   *
 
5249
   * To slightly improve things, if it looks like we didn't get a prompt from
 
5250
   * the game, do our own.
 
5251
   */
 
5252
  if (gms_prompt_enabled && !gms_game_prompted ())
 
5253
    {
 
5254
      gms_normal_char ('\n');
 
5255
      gms_normal_string (GMS_INPUT_PROMPT);
 
5256
    }
 
5257
 
 
5258
  /*
 
5259
   * If we have an input log to read from, use that until it is exhausted.  On
 
5260
   * end of file, close the stream and resume input from line requests.
 
5261
   */
 
5262
  if (gms_readlog_stream)
 
5263
    {
 
5264
      glui32 chars;
 
5265
 
 
5266
      /* Get the next line from the log stream. */
 
5267
      chars = glk_get_line_stream (gms_readlog_stream,
 
5268
                                   gms_input_buffer, sizeof (gms_input_buffer));
 
5269
      if (chars > 0)
 
5270
        {
 
5271
          /* Echo the line just read in input style. */
 
5272
          glk_set_style (style_Input);
 
5273
          glk_put_buffer (gms_input_buffer, chars);
 
5274
          glk_set_style (style_Normal);
 
5275
 
 
5276
          /* Note how many characters buffered, and return. */
 
5277
          gms_input_length = chars;
 
5278
          return;
 
5279
        }
 
5280
 
 
5281
      /*
 
5282
       * We're at the end of the log stream.  Close it, and then continue
 
5283
       * on to request a line from Glk.
 
5284
       */
 
5285
      glk_stream_close (gms_readlog_stream, NULL);
 
5286
      gms_readlog_stream = NULL;
 
5287
    }
 
5288
 
 
5289
  /*
 
5290
   * No input log being read, or we just hit the end of file on one.  Revert
 
5291
   * to normal line input; start by getting a new line from Glk.
 
5292
   */
 
5293
  glk_request_line_event (gms_main_window,
 
5294
                          gms_input_buffer, sizeof (gms_input_buffer) - 1, 0);
 
5295
  gms_event_wait (evtype_LineInput, &event);
 
5296
 
 
5297
  /* Terminate the input line with a NUL. */
 
5298
  assert (event.val1 <= sizeof (gms_input_buffer) - 1);
 
5299
  gms_input_buffer[event.val1] = '\0';
 
5300
 
 
5301
  /* Special handling for "undo" commands. */
 
5302
  if (gms_command_undo_special (gms_input_buffer))
 
5303
    {
 
5304
      /* Write the "undo" to any input log. */
 
5305
      if (gms_inputlog_stream)
 
5306
        {
 
5307
          glk_put_string_stream (gms_inputlog_stream, gms_input_buffer);
 
5308
          glk_put_char_stream (gms_inputlog_stream, '\n');
 
5309
        }
 
5310
 
 
5311
      /* Overwrite buffer with an empty line if we saw "undo". */
 
5312
      gms_input_buffer[0] = '\n';
 
5313
      gms_input_length = 1;
 
5314
 
 
5315
      gms_undo_notification = TRUE;
 
5316
      return;
 
5317
    }
 
5318
 
 
5319
  /*
 
5320
   * If neither abbreviations nor local commands are enabled, use the data
 
5321
   * read above without further massaging.
 
5322
   */
 
5323
  if (gms_abbreviations_enabled || gms_commands_enabled)
 
5324
    {
 
5325
      char *command;
 
5326
 
 
5327
      /*
 
5328
       * If the first non-space input character is a quote, bypass all
 
5329
       * abbreviation expansion and local command recognition, and use the
 
5330
       * unadulterated input, less introductory quote.
 
5331
       */
 
5332
      command = gms_input_buffer + strspn (gms_input_buffer, "\t ");
 
5333
      if (command[0] == '\'')
 
5334
        {
 
5335
          /* Delete the quote with memmove(). */
 
5336
          memmove (command, command + 1, strlen (command));
 
5337
        }
 
5338
      else
 
5339
        {
 
5340
          /* Check for, and expand, any abbreviated commands. */
 
5341
          if (gms_abbreviations_enabled)
 
5342
            {
 
5343
              gms_expand_abbreviations (gms_input_buffer,
 
5344
                                        sizeof (gms_input_buffer));
 
5345
            }
 
5346
 
 
5347
          /*
 
5348
           * Check for standalone "help", then for Glk port special commands;
 
5349
           * suppress the interpreter's use of this input for Glk commands
 
5350
           * by overwriting the line with a single newline character.
 
5351
           */
 
5352
          if (gms_commands_enabled)
 
5353
            {
 
5354
              int posn;
 
5355
 
 
5356
              posn = strspn (gms_input_buffer, "\t ");
 
5357
              if (gms_strncasecmp (gms_input_buffer + posn,
 
5358
                                   "help", strlen ("help"))== 0)
 
5359
                {
 
5360
                  if (strspn (gms_input_buffer + posn + strlen ("help"), "\t ")
 
5361
                      == strlen (gms_input_buffer + posn + strlen ("help")))
 
5362
                    {
 
5363
                      gms_output_register_help_request ();
 
5364
                    }
 
5365
                }
 
5366
 
 
5367
              if (gms_command_escape (gms_input_buffer,
 
5368
                                      &gms_undo_notification))
 
5369
                {
 
5370
                  gms_output_silence_help_hints ();
 
5371
                  gms_input_buffer[0] = '\n';
 
5372
                  gms_input_length = 1;
 
5373
                  return;
 
5374
                }
 
5375
            }
 
5376
        }
 
5377
    }
 
5378
 
 
5379
  /*
 
5380
   * If there is an input log active, log this input string to it.  Note that
 
5381
   * by logging here we get any abbreviation expansions but we won't log glk
 
5382
   * special commands, nor any input read from a current open input log.
 
5383
   */
 
5384
  if (gms_inputlog_stream)
 
5385
    {
 
5386
      glk_put_string_stream (gms_inputlog_stream, gms_input_buffer);
 
5387
      glk_put_char_stream (gms_inputlog_stream, '\n');
 
5388
    }
 
5389
 
 
5390
  /*
 
5391
   * Now append a newline to the buffer, since Glk line input doesn't provide
 
5392
   * one, and in any case, abbreviation expansion may have edited the buffer
 
5393
   * contents (and in particular, changed the length).
 
5394
   */
 
5395
  gms_input_buffer[strlen (gms_input_buffer) + 1] = '\0';
 
5396
  gms_input_buffer[strlen (gms_input_buffer)] = '\n';
 
5397
 
 
5398
  /* Note how many characters are buffered after all of the above. */
 
5399
  gms_input_length = strlen (gms_input_buffer);
 
5400
}
 
5401
 
 
5402
 
 
5403
/*
 
5404
 * ms_getchar()
 
5405
 *
 
5406
 * Return the single next character to the interpreter.  This function
 
5407
 * extracts characters from the input buffer until empty, when it then
 
5408
 * tries to buffer more data.
 
5409
 */
 
5410
type8
 
5411
ms_getchar (type8 trans)
 
5412
{
 
5413
  /* See if we are at the end of the input buffer. */
 
5414
  if (gms_input_cursor == gms_input_length)
 
5415
    {
 
5416
      /*
 
5417
       * Try to read in more data, and rewind buffer cursor.  As well as
 
5418
       * reading input, this may set an undo notification.
 
5419
       */
 
5420
      gms_buffer_input ();
 
5421
      gms_input_cursor = 0;
 
5422
 
 
5423
      if (gms_undo_notification)
 
5424
        {
 
5425
          /*
 
5426
           * Clear the undo notification, and discard buffered input (usually
 
5427
           * just the '\n' placed there when the undo command was recognized).
 
5428
           */
 
5429
          gms_undo_notification = FALSE;
 
5430
          gms_input_length = 0;
 
5431
 
 
5432
          /*
 
5433
           * Return the special 0, or a blank line if no undo is allowed at
 
5434
           * this point.
 
5435
           */
 
5436
          return trans ? 0 : '\n';
 
5437
        }
 
5438
    }
 
5439
 
 
5440
  /* Return the next character from the input buffer. */
 
5441
  assert (gms_input_cursor < gms_input_length);
 
5442
  return gms_input_buffer[gms_input_cursor++];
 
5443
}
 
5444
 
 
5445
 
 
5446
/*
 
5447
 * gms_confirm()
 
5448
 *
 
5449
 * Print a confirmation prompt, and read a single input character, taking
 
5450
 * only [YyNn] input.  If the character is 'Y' or 'y', return TRUE.
 
5451
 */
 
5452
static int
 
5453
gms_confirm (const char *prompt)
 
5454
{
 
5455
  event_t event;
 
5456
  unsigned char response;
 
5457
  assert (prompt);
 
5458
 
 
5459
  /*
 
5460
   * Print the confirmation prompt, in a style that hints that it's from the
 
5461
   * interpreter, not the game.
 
5462
   */
 
5463
  gms_standout_string (prompt);
 
5464
 
 
5465
  /* Wait for a single 'Y' or 'N' character response. */
 
5466
  response = ' ';
 
5467
  do
 
5468
    {
 
5469
      glk_request_char_event (gms_main_window);
 
5470
      gms_event_wait (evtype_CharInput, &event);
 
5471
 
 
5472
      if (event.val1 <= UCHAR_MAX)
 
5473
        response = glk_char_to_upper (event.val1);
 
5474
    }
 
5475
  while (!(response == 'Y' || response == 'N'));
 
5476
 
 
5477
  /* Echo the confirmation response, and a blank line. */
 
5478
  glk_set_style (style_Input);
 
5479
  glk_put_string (response == 'Y' ? "Yes" : "No");
 
5480
  glk_set_style (style_Normal);
 
5481
  glk_put_string ("\n\n");
 
5482
 
 
5483
  return response == 'Y';
 
5484
}
 
5485
 
 
5486
 
 
5487
/*---------------------------------------------------------------------*/
 
5488
/*  Glk port event functions                                           */
 
5489
/*---------------------------------------------------------------------*/
 
5490
 
 
5491
/*
 
5492
 * gms_event_wait()
 
5493
 *
 
5494
 * Process Glk events until one of the expected type arrives.  Return
 
5495
 * the event of that type.
 
5496
 */
 
5497
static void
 
5498
gms_event_wait (glui32 wait_type, event_t * event)
 
5499
{
 
5500
  assert (event);
 
5501
 
 
5502
  do
 
5503
    {
 
5504
      glk_select (event);
 
5505
 
 
5506
      switch (event->type)
 
5507
        {
 
5508
        case evtype_Arrange:
 
5509
        case evtype_Redraw:
 
5510
          /* Refresh any sensitive windows on size events. */
 
5511
          gms_status_redraw ();
 
5512
          gms_hint_redraw ();
 
5513
          gms_graphics_paint ();
 
5514
          break;
 
5515
 
 
5516
        case evtype_Timer:
 
5517
          /* Do background graphics updates on timeout. */
 
5518
          gms_graphics_timeout ();
 
5519
          break;
 
5520
        }
 
5521
    }
 
5522
  while (event->type != wait_type);
 
5523
}
 
5524
 
 
5525
 
 
5526
/*---------------------------------------------------------------------*/
 
5527
/*  Glk port file functions                                            */
 
5528
/*---------------------------------------------------------------------*/
 
5529
 
 
5530
/* Success and fail return codes from file functions. */
 
5531
static const type8 GMS_FILE_SUCCESS = 0,
 
5532
                   GMS_FILE_ERROR = 1;
 
5533
 
 
5534
 
 
5535
/*
 
5536
 * ms_save_file ()
 
5537
 * ms_load_file ()
 
5538
 *
 
5539
 * Save the current game state to a file, and load a game state.
 
5540
 */
 
5541
type8
 
5542
ms_save_file (type8s * name, type8 * ptr, type16 size)
 
5543
{
 
5544
  assert (ptr);
 
5545
 
 
5546
  /* Flush any pending buffered output. */
 
5547
  gms_output_flush ();
 
5548
 
 
5549
  /* If there is no name, use Glk to prompt for one, and save. */
 
5550
  if (!name)
 
5551
    {
 
5552
      frefid_t fileref;
 
5553
      strid_t stream;
 
5554
 
 
5555
      fileref = glk_fileref_create_by_prompt (fileusage_SavedGame,
 
5556
                                              filemode_Write, 0);
 
5557
      if (!fileref)
 
5558
        return GMS_FILE_ERROR;
 
5559
 
 
5560
      stream = glk_stream_open_file (fileref, filemode_Write, 0);
 
5561
      if (!stream)
 
5562
        {
 
5563
          glk_fileref_destroy (fileref);
 
5564
          return GMS_FILE_ERROR;
 
5565
        }
 
5566
 
 
5567
      /* Write game state. */
 
5568
      glk_put_buffer_stream (stream, ptr, size);
 
5569
 
 
5570
      glk_stream_close (stream, NULL);
 
5571
      glk_fileref_destroy (fileref);
 
5572
    }
 
5573
 
 
5574
  else
 
5575
    {
 
5576
      FILE *stream;
 
5577
 
 
5578
      /*
 
5579
       * If openable for read, assume the file already exists and confirm
 
5580
       * overwrite.
 
5581
       */
 
5582
      stream = fopen (name, "r");
 
5583
      if (stream)
 
5584
        {
 
5585
          fclose (stream);
 
5586
 
 
5587
          /*
 
5588
           * Confirm overwrite, clearing the game prompted flag beforehand
 
5589
           * so we're sure to issue a new prompt on the next line input.
 
5590
           */
 
5591
          gms_game_prompted ();
 
5592
          if (!gms_confirm ("Overwrite existing file? [y/n] "))
 
5593
            return GMS_FILE_ERROR;
 
5594
        }
 
5595
 
 
5596
      /* Open output file directly.  There's no Glk way to achieve this. */
 
5597
      stream = fopen (name, "wb");
 
5598
      if (!stream)
 
5599
        return GMS_FILE_ERROR;
 
5600
 
 
5601
      /* Write game state. */
 
5602
      if (fwrite (ptr, 1, size, stream) != size)
 
5603
        {
 
5604
          fclose (stream);
 
5605
          return GMS_FILE_ERROR;
 
5606
        }
 
5607
 
 
5608
      fclose (stream);
 
5609
    }
 
5610
 
 
5611
  return GMS_FILE_SUCCESS;
 
5612
}
 
5613
 
 
5614
type8
 
5615
ms_load_file (type8s * name, type8 * ptr, type16 size)
 
5616
{
 
5617
  assert (ptr);
 
5618
 
 
5619
  /* Flush any pending buffered output. */
 
5620
  gms_output_flush ();
 
5621
 
 
5622
  /* If there is no name, use Glk to prompt for one, and load. */
 
5623
  if (!name)
 
5624
    {
 
5625
      frefid_t fileref;
 
5626
      strid_t stream;
 
5627
 
 
5628
      fileref = glk_fileref_create_by_prompt (fileusage_SavedGame,
 
5629
                                              filemode_Read, 0);
 
5630
      if (!fileref)
 
5631
        return GMS_FILE_ERROR;
 
5632
 
 
5633
      /*
 
5634
       * Reject the file reference if we're expecting to read from it, and
 
5635
       * the referenced file doesn't exist.
 
5636
       */
 
5637
      if (!glk_fileref_does_file_exist (fileref))
 
5638
        {
 
5639
          glk_fileref_destroy (fileref);
 
5640
          return GMS_FILE_ERROR;
 
5641
        }
 
5642
 
 
5643
      stream = glk_stream_open_file (fileref, filemode_Read, 0);
 
5644
      if (!stream)
 
5645
        {
 
5646
          glk_fileref_destroy (fileref);
 
5647
          return GMS_FILE_ERROR;
 
5648
        }
 
5649
 
 
5650
      /* Restore saved game data. */
 
5651
      glk_get_buffer_stream (stream, ptr, size);
 
5652
 
 
5653
      glk_stream_close (stream, NULL);
 
5654
      glk_fileref_destroy (fileref);
 
5655
    }
 
5656
 
 
5657
  else
 
5658
    {
 
5659
      FILE *stream;
 
5660
 
 
5661
      /*
 
5662
       * Open the input file directly.  As above, there's no Glk way to
 
5663
       * achieve this.
 
5664
       */
 
5665
      stream = fopen (name, "rb");
 
5666
      if (!stream)
 
5667
        return GMS_FILE_ERROR;
 
5668
 
 
5669
      /* Restore saved game data. */
 
5670
      if (fread (ptr, 1, size, stream) != size)
 
5671
        {
 
5672
          fclose (stream);
 
5673
          return GMS_FILE_ERROR;
 
5674
        }
 
5675
 
 
5676
      fclose (stream);
 
5677
    }
 
5678
 
 
5679
  return GMS_FILE_SUCCESS;
 
5680
}
 
5681
 
 
5682
 
 
5683
/*---------------------------------------------------------------------*/
 
5684
/*  Functions intercepted by link-time wrappers                        */
 
5685
/*---------------------------------------------------------------------*/
 
5686
 
 
5687
/*
 
5688
 * __wrap_toupper()
 
5689
 * __wrap_tolower()
 
5690
 *
 
5691
 * Wrapper functions around toupper() and tolower().  The Linux linker's
 
5692
 * --wrap option will convert calls to mumble() to __wrap_mumble() if we
 
5693
 * give it the right options.  We'll use this feature to translate all
 
5694
 * toupper() and tolower() calls in the interpreter code into calls to
 
5695
 * Glk's versions of these functions.
 
5696
 *
 
5697
 * It's not critical that we do this.  If a linker, say a non-Linux one,
 
5698
 * won't do --wrap, then just do without it.  It's unlikely that there
 
5699
 * will be much noticeable difference.
 
5700
 */
 
5701
int
 
5702
__wrap_toupper (int ch)
 
5703
{
 
5704
  unsigned char uch;
 
5705
 
 
5706
  uch = glk_char_to_upper ((unsigned char) ch);
 
5707
  return (int) uch;
 
5708
}
 
5709
 
 
5710
int
 
5711
__wrap_tolower (int ch)
 
5712
{
 
5713
  unsigned char lch;
 
5714
 
 
5715
  lch = glk_char_to_lower ((unsigned char) ch);
 
5716
  return (int) lch;
 
5717
}
 
5718
 
 
5719
 
 
5720
/*---------------------------------------------------------------------*/
 
5721
/*  main() and options parsing                                         */
 
5722
/*---------------------------------------------------------------------*/
 
5723
 
 
5724
/*
 
5725
 * The following values need to be passed between the startup_code and main
 
5726
 * functions.
 
5727
 */
 
5728
static char *gms_gamefile = NULL,      /* Name of game file. */
 
5729
            *gms_game_message = NULL;  /* Error message. */
 
5730
 
 
5731
 
 
5732
/*
 
5733
 * gms_establish_filenames()
 
5734
 *
 
5735
 * Given a game name, try to establish three filenames from it - the main game
 
5736
 * text file, the (optional) graphics data file, and the (optional) hints
 
5737
 * file.  Given an input "file" X, the function looks for X.MAG or X.mag for
 
5738
 * game data, X.GFX or X.gfx for graphics, and X.HNT or X.hnt for hints.
 
5739
 * If the input file already ends with .MAG, .GFX, or .HNT, the extension
 
5740
 * is stripped first.
 
5741
 *
 
5742
 * The function returns NULL for filenames not available.  It's not fatal if
 
5743
 * the graphics filename or hints filename is NULL, but it is if the main game
 
5744
 * filename is NULL.  Filenames are malloc'ed, and need to be freed by the
 
5745
 * caller.
 
5746
 */
 
5747
static void
 
5748
gms_establish_filenames (char *name, char **text, char **graphics, char **hints)
 
5749
{
 
5750
  char *base, *text_file, *graphics_file, *hints_file;
 
5751
  FILE *stream;
 
5752
  assert (name && text && graphics && hints);
 
5753
 
 
5754
  /* Take a destroyable copy of the input filename. */
 
5755
  base = gms_malloc (strlen (name) + 1);
 
5756
  strcpy (base, name);
 
5757
 
 
5758
  /* If base has an extension .MAG, .GFX, or .HNT, remove it. */
 
5759
  if (strlen (base) > strlen (".XXX"))
 
5760
    {
 
5761
      if (gms_strcasecmp (base + strlen (base) - strlen (".MAG"), ".MAG") == 0
 
5762
       || gms_strcasecmp (base + strlen (base) - strlen (".GFX"), ".GFX") == 0
 
5763
       || gms_strcasecmp (base + strlen (base) - strlen (".HNT"), ".HNT") == 0)
 
5764
        base[strlen (base) - strlen (".XXX")] = '\0';
 
5765
    }
 
5766
 
 
5767
  /* Allocate space for the return text file. */
 
5768
  text_file = gms_malloc (strlen (base) + strlen (".MAG") + 1);
 
5769
 
 
5770
  /* Form a candidate text file, by adding a .MAG extension. */
 
5771
  strcpy (text_file, base);
 
5772
  strcat (text_file, ".MAG");
 
5773
  stream = fopen (text_file, "rb");
 
5774
  if (!stream)
 
5775
    {
 
5776
      /* Retry, using a .mag extension instead. */
 
5777
      strcpy (text_file, base);
 
5778
      strcat (text_file, ".mag");
 
5779
      stream = fopen (text_file, "rb");
 
5780
      if (!stream)
 
5781
        {
 
5782
          /*
 
5783
           * No access to a usable game text file.  Return immediately,
 
5784
           * without looking for any associated graphics or hints files.
 
5785
           */
 
5786
          *text = NULL;
 
5787
          *graphics = NULL;
 
5788
          *hints = NULL;
 
5789
 
 
5790
          free (text_file);
 
5791
          free (base);
 
5792
          return;
 
5793
        }
 
5794
    }
 
5795
  if (stream)
 
5796
    fclose (stream);
 
5797
 
 
5798
  /* Now allocate space for the return graphics file. */
 
5799
  graphics_file = gms_malloc (strlen (base) + strlen (".GFX") + 1);
 
5800
 
 
5801
  /* As above, form a candidate graphics file, using a .GFX extension. */
 
5802
  strcpy (graphics_file, base);
 
5803
  strcat (graphics_file, ".GFX");
 
5804
  stream = fopen (graphics_file, "rb");
 
5805
  if (!stream)
 
5806
    {
 
5807
      /* Retry, using a .gfx extension instead. */
 
5808
      strcpy (graphics_file, base);
 
5809
      strcat (graphics_file, ".gfx");
 
5810
      stream = fopen (graphics_file, "rb");
 
5811
      if (!stream)
 
5812
        {
 
5813
          /*
 
5814
           * No access to any graphics file.  In this case, free memory and
 
5815
           * reset graphics file to NULL.
 
5816
           */
 
5817
          free (graphics_file);
 
5818
          graphics_file = NULL;
 
5819
        }
 
5820
    }
 
5821
  if (stream)
 
5822
    fclose (stream);
 
5823
 
 
5824
  /* Now allocate space for the return hints file. */
 
5825
  hints_file = gms_malloc (strlen (base) + strlen (".HNT") + 1);
 
5826
 
 
5827
  /* As above, form a candidate graphics file, using a .HNT extension. */
 
5828
  strcpy (hints_file, base);
 
5829
  strcat (hints_file, ".HNT");
 
5830
  stream = fopen (hints_file, "rb");
 
5831
  if (!stream)
 
5832
    {
 
5833
      /* Retry, using a .hnt extension instead. */
 
5834
      strcpy (hints_file, base);
 
5835
      strcat (hints_file, ".hnt");
 
5836
      stream = fopen (hints_file, "rb");
 
5837
      if (!stream)
 
5838
        {
 
5839
          /*
 
5840
           * No access to any hints file.  In this case, free memory and
 
5841
           * reset hints file to NULL.
 
5842
           */
 
5843
          free (hints_file);
 
5844
          hints_file = NULL;
 
5845
        }
 
5846
    }
 
5847
  if (stream)
 
5848
    fclose (stream);
 
5849
 
 
5850
  /* Return the text file, and graphics and hints, which may be NULL. */
 
5851
  *text = text_file;
 
5852
  *graphics = graphics_file;
 
5853
  *hints = hints_file;
 
5854
 
 
5855
  free (base);
 
5856
}
 
5857
 
 
5858
 
 
5859
/*
 
5860
 * gms_startup_code()
 
5861
 * gms_main()
 
5862
 *
 
5863
 * Together, these functions take the place of the original main().  The
 
5864
 * first one is called from glkunix_startup_code(), to parse and generally
 
5865
 * handle options.  The second is called from glk_main(), and does the real
 
5866
 * work of running the game.
 
5867
 */
 
5868
static int
 
5869
gms_startup_code (int argc, char *argv[])
 
5870
{
 
5871
  int argv_index;
 
5872
 
 
5873
  /* Handle command line arguments. */
 
5874
  for (argv_index = 1;
 
5875
       argv_index < argc && argv[argv_index][0] == '-'; argv_index++)
 
5876
    {
 
5877
      if (strcmp (argv[argv_index], "-nc") == 0)
 
5878
        {
 
5879
          gms_commands_enabled = FALSE;
 
5880
          continue;
 
5881
        }
 
5882
      if (strcmp (argv[argv_index], "-na") == 0)
 
5883
        {
 
5884
          gms_abbreviations_enabled = FALSE;
 
5885
          continue;
 
5886
        }
 
5887
      if (strcmp (argv[argv_index], "-np") == 0)
 
5888
        {
 
5889
          gms_graphics_enabled = FALSE;
 
5890
          continue;
 
5891
        }
 
5892
      if (strcmp (argv[argv_index], "-ng") == 0)
 
5893
        {
 
5894
          gms_gamma_mode = GAMMA_OFF;
 
5895
          continue;
 
5896
        }
 
5897
      if (strcmp (argv[argv_index], "-nx") == 0)
 
5898
        {
 
5899
          gms_animation_enabled = FALSE;
 
5900
          continue;
 
5901
        }
 
5902
      if (strcmp (argv[argv_index], "-ne") == 0)
 
5903
        {
 
5904
          gms_prompt_enabled = FALSE;
 
5905
          continue;
 
5906
        }
 
5907
      return FALSE;
 
5908
    }
 
5909
 
 
5910
  /*
 
5911
   * Get the name of the game file.  Since we need this in our call from
 
5912
   * glk_main, we need to keep it in a module static variable.  If the game
 
5913
   * file name is omitted, then here we'll set the pointer to NULL, and
 
5914
   * complain about it later in main.  Passing the message string around
 
5915
   * like this is a nuisance...
 
5916
   */
 
5917
  if (argv_index == argc - 1)
 
5918
    {
 
5919
      gms_gamefile = argv[argv_index];
 
5920
      gms_game_message = NULL;
 
5921
#ifdef GARGLK
 
5922
    {
 
5923
      char *s;
 
5924
      s = strrchr(gms_gamefile, '\\');
 
5925
      if (s) garglk_set_story_name(s+1);
 
5926
      s = strrchr(gms_gamefile, '/');
 
5927
      if (s) garglk_set_story_name(s+1);
 
5928
    }
 
5929
#endif
 
5930
    }
 
5931
  else
 
5932
    {
 
5933
      gms_gamefile = NULL;
 
5934
      if (argv_index < argc - 1)
 
5935
        gms_game_message = "More than one game file was given"
 
5936
                           " on the command line.";
 
5937
      else
 
5938
        gms_game_message = "No game file was given on the command line.";
 
5939
    }
 
5940
 
 
5941
  /* All startup options were handled successfully. */
 
5942
  return TRUE;
 
5943
}
 
5944
 
 
5945
static void
 
5946
gms_main (void)
 
5947
{
 
5948
  char *text_file = NULL, *graphics_file = NULL, *hints_file = NULL;
 
5949
  int ms_init_status, is_running;
 
5950
 
 
5951
  /* Ensure Magnetic Scrolls internal types have the right sizes. */
 
5952
  if (!(sizeof (type8) == 1 && sizeof (type8s) == 1
 
5953
        && sizeof (type16) == 2 && sizeof (type16s) == 2
 
5954
        && sizeof (type32) == 4 && sizeof (type32s) == 4))
 
5955
    {
 
5956
      gms_fatal ("GLK: Types sized incorrectly, recompilation is needed");
 
5957
      glk_exit ();
 
5958
    }
 
5959
 
 
5960
  /* Create the main Glk window, and set its stream as current. */
 
5961
  gms_main_window = glk_window_open (0, 0, 0, wintype_TextBuffer, 0);
 
5962
  if (!gms_main_window)
 
5963
    {
 
5964
      gms_fatal ("GLK: Can't open main window");
 
5965
      glk_exit ();
 
5966
    }
 
5967
  glk_window_clear (gms_main_window);
 
5968
  glk_set_window (gms_main_window);
 
5969
  glk_set_style (style_Normal);
 
5970
 
 
5971
  /* If there's a problem with the game file, complain now. */
 
5972
  if (!gms_gamefile)
 
5973
    {
 
5974
      assert (gms_game_message);
 
5975
      gms_header_string ("Glk Magnetic Error\n\n");
 
5976
      gms_normal_string (gms_game_message);
 
5977
      gms_normal_char ('\n');
 
5978
      glk_exit ();
 
5979
    }
 
5980
 
 
5981
  /*
 
5982
   * Given the basic game name, try to come up with usable text, graphics,
 
5983
   * and hints filenames.  The graphics and hints files may be null, but the
 
5984
   * text file may not.
 
5985
   */
 
5986
  errno = 0;
 
5987
  gms_establish_filenames (gms_gamefile,
 
5988
                           &text_file, &graphics_file, &hints_file);
 
5989
  if (!text_file)
 
5990
    {
 
5991
      assert (!graphics_file && !hints_file);
 
5992
      gms_header_string ("Glk Magnetic Error\n\n");
 
5993
      gms_normal_string ("Can't find or open game '");
 
5994
      gms_normal_string (gms_gamefile);
 
5995
      gms_normal_string ("[.mag|.MAG]'");
 
5996
      if (errno != 0)
 
5997
        {
 
5998
          gms_normal_string (": ");
 
5999
          gms_normal_string (strerror (errno));
 
6000
        }
 
6001
      gms_normal_char ('\n');
 
6002
      glk_exit ();
 
6003
    }
 
6004
 
 
6005
  /* Set the possibility of pictures depending on graphics file. */
 
6006
  if (graphics_file)
 
6007
    {
 
6008
      /*
 
6009
       * Check Glk library capabilities, and note pictures are impossible if
 
6010
       * the library can't offer both graphics and timers.  We need timers to
 
6011
       * create the background "thread" for picture updates.
 
6012
       */
 
6013
      gms_graphics_possible = glk_gestalt (gestalt_Graphics, 0)
 
6014
                              && glk_gestalt (gestalt_Timer, 0);
 
6015
    }
 
6016
  else
 
6017
    gms_graphics_possible = FALSE;
 
6018
 
 
6019
 
 
6020
  /*
 
6021
   * If pictures are impossible, clear pictures enabled flag.  That is, act
 
6022
   * as if -np was given on the command line, even though it may not have
 
6023
   * been.  If pictures are impossible, they can never be enabled.
 
6024
   */
 
6025
  if (!gms_graphics_possible)
 
6026
    gms_graphics_enabled = FALSE;
 
6027
 
 
6028
  /* Try to create a one-line status window.  We can live without it. */
 
6029
  gms_status_window = glk_window_open (gms_main_window,
 
6030
                                       winmethod_Above | winmethod_Fixed,
 
6031
                                       1, wintype_TextGrid, 0);
 
6032
 
 
6033
  /* Seed the random number generator. */
 
6034
  ms_seed (time (NULL));
 
6035
 
 
6036
  /*
 
6037
   * Load the game.  If no graphics are possible, then passing the NULL to
 
6038
   * ms_init() runs a game without graphics.
 
6039
   */
 
6040
  errno = 0;
 
6041
  if (gms_graphics_possible)
 
6042
    {
 
6043
      assert (graphics_file);
 
6044
      ms_init_status = ms_init (text_file, graphics_file, hints_file, NULL);
 
6045
    }
 
6046
  else
 
6047
    ms_init_status = ms_init (text_file, NULL, hints_file, NULL);
 
6048
 
 
6049
  /* Look for a complete failure to load the game. */
 
6050
  if (ms_init_status == 0)
 
6051
    {
 
6052
      if (gms_status_window)
 
6053
        glk_window_close (gms_status_window, NULL);
 
6054
      gms_header_string ("Glk Magnetic Error\n\n");
 
6055
      gms_normal_string ("Can't load game '");
 
6056
      gms_normal_string (gms_gamefile);
 
6057
      gms_normal_char ('\'');
 
6058
      if (errno != 0)
 
6059
        {
 
6060
          gms_normal_string (": ");
 
6061
          gms_normal_string (strerror (errno));
 
6062
        }
 
6063
      gms_normal_char ('\n');
 
6064
 
 
6065
      /*
 
6066
       * Free the text file path, any graphics/hints file path, and
 
6067
       * interpreter allocated memory.
 
6068
       */
 
6069
      free (text_file);
 
6070
      free (graphics_file);
 
6071
      free (hints_file);
 
6072
      ms_freemem ();
 
6073
      glk_exit ();
 
6074
    }
 
6075
 
 
6076
  /* Try to identify the game from its text file header. */
 
6077
  gms_gameid_identify_game (text_file);
 
6078
 
 
6079
  /* Print out a short banner. */
 
6080
  gms_header_string ("\nMagnetic Scrolls Interpreter, version 2.3\n");
 
6081
  gms_banner_string ("Written by Niclas Karlsson\n"
 
6082
                     "Glk interface by Simon Baldwin\n\n");
 
6083
 
 
6084
  /* Look for failure to load just game graphics. */
 
6085
  if (gms_graphics_possible && ms_init_status == 1)
 
6086
    {
 
6087
      /*
 
6088
       * Output a warning if graphics failed, but the main game text
 
6089
       * initialized okay.
 
6090
       */
 
6091
      gms_standout_string ("Error: Unable to open graphics file\n"
 
6092
                           "Continuing without pictures...\n\n");
 
6093
 
 
6094
      gms_graphics_possible = FALSE;
 
6095
    }
 
6096
 
 
6097
  /* Run the game opcodes -- ms_rungame() returns FALSE on game end. */
 
6098
  do
 
6099
    {
 
6100
      is_running = ms_rungame ();
 
6101
      glk_tick ();
 
6102
    }
 
6103
  while (is_running);
 
6104
 
 
6105
  /* Handle any updated status and pending buffered output. */
 
6106
  gms_status_notify ();
 
6107
  gms_output_flush ();
 
6108
 
 
6109
  /* Turn off any background graphics "thread". */
 
6110
  gms_graphics_stop ();
 
6111
 
 
6112
  /* Free interpreter allocated memory. */
 
6113
  ms_freemem ();
 
6114
 
 
6115
  /*
 
6116
   * Free any temporary memory that may have been used by graphics and hints.
 
6117
   */
 
6118
  gms_graphics_cleanup ();
 
6119
  gms_hints_cleanup ();
 
6120
 
 
6121
  /* Close any open transcript, input log, and/or read log. */
 
6122
  if (gms_transcript_stream)
 
6123
    {
 
6124
      glk_stream_close (gms_transcript_stream, NULL);
 
6125
      gms_transcript_stream = NULL;
 
6126
    }
 
6127
  if (gms_inputlog_stream)
 
6128
    {
 
6129
      glk_stream_close (gms_inputlog_stream, NULL);
 
6130
      gms_inputlog_stream = NULL;
 
6131
    }
 
6132
  if (gms_readlog_stream)
 
6133
    {
 
6134
      glk_stream_close (gms_readlog_stream, NULL);
 
6135
      gms_readlog_stream = NULL;
 
6136
    }
 
6137
 
 
6138
  /* Free the text file path, and any graphics/hints file path. */
 
6139
  free (text_file);
 
6140
  free (graphics_file);
 
6141
  free (hints_file);
 
6142
}
 
6143
 
 
6144
 
 
6145
/*---------------------------------------------------------------------*/
 
6146
/*  Linkage between Glk entry/exit calls and the Magnetic interpreter  */
 
6147
/*---------------------------------------------------------------------*/
 
6148
 
 
6149
/*
 
6150
 * Safety flags, to ensure we always get startup before main, and that
 
6151
 * we only get a call to main once.
 
6152
 */
 
6153
static int gms_startup_called = FALSE,
 
6154
           gms_main_called = FALSE;
 
6155
 
 
6156
/*
 
6157
 * glk_main()
 
6158
 *
 
6159
 * Main entry point for Glk.  Here, all startup is done, and we call our
 
6160
 * function to run the game.
 
6161
 */
 
6162
void
 
6163
glk_main (void)
 
6164
{
 
6165
  assert (gms_startup_called && !gms_main_called);
 
6166
  gms_main_called = TRUE;
 
6167
 
 
6168
  /* Call the interpreter main function. */
 
6169
  gms_main ();
 
6170
}
 
6171
 
 
6172
 
 
6173
/*---------------------------------------------------------------------*/
 
6174
/*  Glk linkage relevant only to the UNIX platform                     */
 
6175
/*---------------------------------------------------------------------*/
 
6176
#ifdef TRUE
 
6177
 
 
6178
#include "glkstart.h"
 
6179
 
 
6180
/*
 
6181
 * Glk arguments for UNIX versions of the Glk interpreter.
 
6182
 */
 
6183
glkunix_argumentlist_t glkunix_arguments[] = {
 
6184
  {(char *) "-nc", glkunix_arg_NoValue,
 
6185
   (char *) "-nc        No local handling for Glk special commands"},
 
6186
  {(char *) "-na", glkunix_arg_NoValue,
 
6187
   (char *) "-na        Turn off abbreviation expansions"},
 
6188
  {(char *) "-np", glkunix_arg_NoValue,
 
6189
   (char *) "-np        Turn off pictures"},
 
6190
  {(char *) "-ng", glkunix_arg_NoValue,
 
6191
   (char *) "-ng        Turn off automatic gamma correction on pictures"},
 
6192
  {(char *) "-nx", glkunix_arg_NoValue,
 
6193
   (char *) "-nx        Turn off picture animations"},
 
6194
  {(char *) "-ne", glkunix_arg_NoValue,
 
6195
   (char *) "-ne        Turn off additional interpreter prompt"},
 
6196
  {(char *) "", glkunix_arg_ValueCanFollow,
 
6197
   (char *) "filename   game to run"},
 
6198
  {NULL, glkunix_arg_End, NULL}
 
6199
};
 
6200
 
 
6201
 
 
6202
/*
 
6203
 * glkunix_startup_code()
 
6204
 *
 
6205
 * Startup entry point for UNIX versions of Glk interpreter.  Glk will
 
6206
 * call glkunix_startup_code() to pass in arguments.  On startup, we call
 
6207
 * our function to parse arguments and generally set stuff up.
 
6208
 */
 
6209
int
 
6210
glkunix_startup_code (glkunix_startup_t * data)
 
6211
{
 
6212
  assert (!gms_startup_called);
 
6213
  gms_startup_called = TRUE;
 
6214
 
 
6215
#ifdef GARGLK
 
6216
  garglk_set_program_name("Magnetic 2.3");
 
6217
  garglk_set_program_info(
 
6218
      "Magnetic 2.3 by Niclas Karlsson, David Kinder,\n"
 
6219
      "Stefan Meier, and Paul David Doherty.\n"
 
6220
      "Glk port by Simon Baldwin.\n"
 
6221
      "Gargoyle tweaks by Tor Andersson.\n");
 
6222
#endif
 
6223
 
 
6224
  return gms_startup_code (data->argc, data->argv);
 
6225
}
 
6226
#endif /* _unix */
 
6227
 
 
6228
 
 
6229
/*---------------------------------------------------------------------*/
 
6230
/*  Glk linkage relevant only to the Mac platform                      */
 
6231
/*---------------------------------------------------------------------*/
 
6232
#if TARGET_OS_MAC
 
6233
 
 
6234
#include "macglk_startup.h"
 
6235
 
 
6236
static strid_t gms_mac_gamefile = NULL;
 
6237
static short gms_savedVRefNum = 0;
 
6238
static long gms_savedDirID = 0;
 
6239
 
 
6240
 
 
6241
/*
 
6242
 * gms_mac_whenselected()
 
6243
 * gms_mac_whenbuiltin()
 
6244
 * macglk_startup_code()
 
6245
 *
 
6246
 * Startup entry points for Mac versions of Glk interpreter.  Glk will call
 
6247
 * macglk_startup_code() for details on what to do when the application is
 
6248
 * selected.  On selection, an argv[] vector is built, and passed to the
 
6249
 * normal interpreter startup code, after which, Glk will call glk_main().
 
6250
 */
 
6251
static Boolean
 
6252
gms_mac_whenselected (FSSpec * file, OSType filetype)
 
6253
{
 
6254
  static char *argv[2];
 
6255
  assert (!gms_startup_called);
 
6256
  gms_startup_called = TRUE;
 
6257
 
 
6258
  /* Set the WD to where the file is, so later fopens work. */
 
6259
  if (HGetVol (0, &gms_savedVRefNum, &gms_savedDirID) != 0)
 
6260
    {
 
6261
      gms_fatal ("GLK: HGetVol failed");
 
6262
      return FALSE;
 
6263
    }
 
6264
  if (HSetVol (0, file->vRefNum, file->parID) != 0);
 
6265
    {
 
6266
      gms_fatal ("GLK: HSetVol failed");
 
6267
      return FALSE;
 
6268
    }
 
6269
 
 
6270
  /* Put a CString version of the PString name into argv[1]. */
 
6271
  argv[1] = gms_malloc (file->name[0] + 1);
 
6272
  BlockMoveData (file->name + 1, argv[1], file->name[0]);
 
6273
  argv[1][file->name[0]] = '\0';
 
6274
  argv[2] = NULL;
 
6275
 
 
6276
  return gms_startup_code (2, argv);
 
6277
}
 
6278
 
 
6279
static Boolean
 
6280
gms_mac_whenbuiltin (void)
 
6281
{
 
6282
  /* Not implemented yet. */
 
6283
  return TRUE;
 
6284
}
 
6285
 
 
6286
Boolean
 
6287
macglk_startup_code (macglk_startup_t * data)
 
6288
{
 
6289
  static OSType gms_mac_gamefile_types[] = { 'MaSc' };
 
6290
 
 
6291
  data->startup_model = macglk_model_ChooseOrBuiltIn;
 
6292
  data->app_creator = 'cAGL';
 
6293
  data->gamefile_types = gms_mac_gamefile_types;
 
6294
  data->num_gamefile_types = sizeof (gms_mac_gamefile_types)
 
6295
                             / sizeof (*gms_mac_gamefile_types);
 
6296
  data->savefile_type = 'BINA';
 
6297
  data->datafile_type = 0x3f3f3f3f;
 
6298
  data->gamefile = &gms_mac_gamefile;
 
6299
  data->when_selected = gms_mac_whenselected;
 
6300
  data->when_builtin = gms_mac_whenbuiltin;
 
6301
  /* macglk_setprefs(); */
 
6302
  return TRUE;
 
6303
}
 
6304
#endif /* TARGET_OS_MAC */