/* vi: set ts=2 shiftwidth=2 expandtab: * * Copyright (C) 2003-2007 Simon Baldwin and Mark J. Tilford * * This program is free software; you can redistribute it and/or modify * it under the terms of version 2 of the GNU General Public License * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA */ /* * Module notes: * * o The Glk interface makes no effort to set text colors, background colors, * and so forth, and minimal effort to set fonts and other style effects. */ #include #include #include #include #include #include #include #include "scare.h" #include "glk.h" /* * True and false definitions -- usually defined in glkstart.h, but we need * them early, so we'll define them here too. We also need NULL, but that's * normally from stdio.h or one of it's cousins. */ #ifndef FALSE # define FALSE 0 #endif #ifndef TRUE # define TRUE (!FALSE) #endif /*---------------------------------------------------------------------*/ /* Module variables, miscellaneous other stuff */ /*---------------------------------------------------------------------*/ /* Glk SCARE interface version number. */ static const glui32 GSC_PORT_VERSION = 0x00010306; /* Two windows, one for the main text, and one for a status line. */ static winid_t gsc_main_window = NULL, gsc_status_window = NULL; /* * Transcript stream and input log. These are NULL if there is no current * collection of these strings. */ static strid_t gsc_transcript_stream = NULL, gsc_inputlog_stream = NULL; /* Input read log stream, for reading back an input log. */ static strid_t gsc_readlog_stream = NULL; /* Options that may be turned off or set by command line flags. */ static int gsc_commands_enabled = TRUE, gsc_abbreviations_enabled = TRUE; /* Adrift game to interpret. */ static sc_game gsc_game = NULL; /* Special out-of-band os_confirm() options used locally with os_glk. */ static const sc_int GSC_CONF_SUBTLE_HINT = INT_MAX, GSC_CONF_UNSUBTLE_HINT = INT_MAX - 1, GSC_CONF_CONTINUE_HINTS = INT_MAX - 2; /* Forward declaration of event wait function, and a short delay. */ static void gsc_event_wait (glui32 wait_type, event_t * event); static void gsc_short_delay (void); /*---------------------------------------------------------------------*/ /* Glk port utility functions */ /*---------------------------------------------------------------------*/ /* * gsc_fatal() * * Fatal error handler. The function returns, expecting the caller to * abort() or otherwise handle the error. */ static void gsc_fatal (const char *string) { /* * If the failure happens too early for us to have a window, print * the message to stderr. */ if (!gsc_main_window) { fprintf (stderr, "\n\nINTERNAL ERROR: %s\n", string); fprintf (stderr, "\nPlease record the details of this error, try to" " note down everything you did to cause it, and email" " this information to simon_baldwin@yahoo.com.\n\n"); return; } /* Cancel all possible pending window input events. */ glk_cancel_line_event (gsc_main_window, NULL); glk_cancel_char_event (gsc_main_window); /* Print a message indicating the error, and exit. */ glk_set_window (gsc_main_window); glk_set_style (style_Normal); glk_put_string ("\n\nINTERNAL ERROR: "); glk_put_string ((char *) string); glk_put_string ("\n\nPlease record the details of this error, try to" " note down everything you did to cause it, and email" " this information to simon_baldwin@yahoo.com.\n\n"); } /* * gsc_malloc() * * Non-failing malloc; call gsc_fatal and exit if memory allocation fails. */ static void * gsc_malloc (size_t size) { void *pointer; pointer = malloc (size); if (!pointer) { gsc_fatal ("GLK: Out of system memory"); glk_exit (); } return pointer; } /*---------------------------------------------------------------------*/ /* Glk port status line functions */ /*---------------------------------------------------------------------*/ /* Default width used for non-windowing Glk status lines. */ enum { GSC_DEFAULT_STATUS_WIDTH = 74 }; /* * gsc_status_update() * * Update the status line from the current game state. This is for windowing * Glk libraries. */ static void gsc_status_update (void) { glui32 width, height; assert (gsc_status_window); glk_window_get_size (gsc_status_window, &width, &height); if (height > 0) { glk_window_clear (gsc_status_window); glk_window_move_cursor (gsc_status_window, 0, 0); glk_set_window (gsc_status_window); /* See if the game is indicating any current player room. */ if (!sc_get_game_room (gsc_game)) { /* * Player location is indeterminate, so print out a generic status, * showing the game name and author. */ glk_put_string ((char *) sc_get_game_name (gsc_game)); glk_put_string (" | "); glk_put_string ((char *) sc_get_game_author (gsc_game)); } else { const sc_char *status; /* Print the player location. */ glk_put_string ((char *) sc_get_game_room (gsc_game)); /* See if the game offers a specialized status line. */ status = sc_get_game_status_line (gsc_game); if (status) { /* Print the game's status line at window right. */ glk_window_move_cursor (gsc_status_window, width - strlen (status), 0); glk_put_string ((char *) status); } else { char buffer[64]; /* Print the score at window right. */ sprintf (buffer, "Score: %ld", sc_get_game_score (gsc_game)); glk_window_move_cursor (gsc_status_window, width - strlen (buffer), 0); glk_put_string (buffer); } } glk_set_window (gsc_main_window); } } /* * gsc_status_safe_strcat() * * Helper for gsc_status_print(), concatenates strings only up to the * available length. */ static void gsc_status_safe_strcat (char *dest, size_t length, const char *src) { size_t avail, src_len; /* Append only as many characters as will fit. */ src_len = strlen (src); avail = length - strlen (dest) - 1; if (avail > 0) strncat (dest, src, src_len < avail ? src_len : avail); } /* * gsc_status_print() * * Print the current contents of the completed status line buffer out in the * main window, if it has changed since the last call. This is for non- * windowing Glk libraries. */ static void gsc_status_print (void) { static char current_status[GSC_DEFAULT_STATUS_WIDTH + 1]; /* Saved buffer. */ const sc_char *game_room; /* Do nothing if the game isn't indicating any current player room. */ game_room = sc_get_game_room (gsc_game); if (game_room) { char buffer[GSC_DEFAULT_STATUS_WIDTH + 1]; const sc_char *status_line; char score[64]; size_t index_; /* Format a reasonable attempt at a status line, room at left... */ strcpy (buffer, ""); gsc_status_safe_strcat (buffer, sizeof (buffer), sc_get_game_room (gsc_game)); /* ... and either game status or score at right. */ status_line = sc_get_game_status_line (gsc_game); if (!status_line) { sprintf (score, "Score: %ld", sc_get_game_score (gsc_game)); status_line = score; } /* Pad status line, then append status or score. */ for (index_ = strlen (buffer); index_ < sizeof (buffer) - strlen (status_line) - 1; index_++) gsc_status_safe_strcat (buffer, sizeof (buffer), " "); gsc_status_safe_strcat (buffer, sizeof (buffer), status_line); /* If this matches the current saved status line, do nothing more. */ if (strcmp (buffer, current_status) != 0) { /* Set fixed width font to try to preserve status line formatting. */ glk_set_style (style_Preformatted); /* Bracket, and output the status line buffer. */ glk_put_string ("[ "); glk_put_string (buffer); glk_put_string (" ]\n"); /* Save the details of the printed status buffer. */ strcpy (current_status, buffer); } } } /* * gsc_status_notify() * * Front end function for updating status. Either updates the status window * or prints the status line to the main window. */ static void gsc_status_notify (void) { if (gsc_status_window) gsc_status_update (); else gsc_status_print (); } /* * gsc_status_redraw() * * Redraw the contents of any status window with the constructed status string. * This function should be called on the appropriate Glk window resize and * arrange events. */ static void gsc_status_redraw (void) { if (gsc_status_window) { winid_t parent; /* * Rearrange the status window, without changing its actual arrangement * in any way. This is a hack to work round incorrect window repainting * in Xglk; it forces a complete repaint of affected windows on Glk * window resize and arrange events, and works in part because Xglk * doesn't check for actual arrangement changes in any way before * invalidating its windows. The hack should be harmless to Glk * libraries other than Xglk, moreover, we're careful to activate it * only on resize and arrange events. */ parent = glk_window_get_parent (gsc_status_window); glk_window_set_arrangement (parent, winmethod_Above | winmethod_Fixed, 1, NULL); gsc_status_update (); } } /*---------------------------------------------------------------------*/ /* Glk port output functions */ /*---------------------------------------------------------------------*/ /* * Flag for if the user entered "help" as their last input, or if hints have * been silenced as a result of already using a Glk command. */ static int gsc_help_requested = FALSE, gsc_help_hints_silenced = FALSE; /* Font descriptor type, encapsulating size and monospaced boolean. */ typedef struct { int is_monospaced; glui32 size; } gsc_font_size_t; /* Font stack and attributes for nesting tags. */ enum { GSC_MAX_STYLE_NESTING = 32 }; static gsc_font_size_t gsc_font_stack[GSC_MAX_STYLE_NESTING]; static glui32 gsc_font_index = 0; static glui32 gsc_attribute_bold = 0, gsc_attribute_italic = 0, gsc_attribute_underline = 0, gsc_attribute_secondary_color = 0; /* Notional default font size, and limit font sizes. */ static const sc_int GSC_DEFAULT_FONT_SIZE = 12, GSC_MEDIUM_FONT_SIZE = 14, GSC_LARGE_FONT_SIZE = 16; /* Milliseconds per second and timeouts count for delay tags. */ static const glui32 GSC_MILLISECONDS_PER_SECOND = 1000; static const glui32 GSC_TIMEOUTS_COUNT = 10; /* Known valid character printing range. */ static const char GSC_MIN_PRINTABLE = ' ', GSC_MAX_PRINTABLE = 126; /* Number of hints to refuse before offering to end hint display. */ static const sc_int GSC_HINT_REFUSAL_LIMIT = 5; /* * gsc_output_register_help_request() * gsc_output_silence_help_hints() * gsc_output_provide_help_hint() * * Register a request for help, and print a note of how to get Glk command * help from the interpreter unless silenced. */ static void gsc_output_register_help_request (void) { gsc_help_requested = TRUE; } static void gsc_output_silence_help_hints (void) { gsc_help_hints_silenced = TRUE; } static void gsc_output_provide_help_hint (void) { if (gsc_help_requested && !gsc_help_hints_silenced) { glk_set_style (style_Emphasized); glk_put_string ("[Try 'glk help' for help on special interpreter" " commands]\n"); gsc_help_requested = FALSE; glk_set_style (style_Normal); } } /* * gsc_set_glk_style() * * Set a Glk style based on the top of the font stack and attributes. */ static void gsc_set_glk_style (void) { sc_bool is_monospaced; sc_int font_size; /* Get the current font stack top, or default value. */ if (gsc_font_index > 0) { is_monospaced = gsc_font_stack[gsc_font_index - 1].is_monospaced; font_size = gsc_font_stack[gsc_font_index - 1].size; } else { is_monospaced = FALSE; font_size = GSC_DEFAULT_FONT_SIZE; } /* * Map the font and current attributes into a Glk style. Because Glk styles * aren't cumulative this has to be done by precedences. */ if (is_monospaced) { /* * No matter the size or attributes, if monospaced use Preformatted * style, as it's all we have. */ glk_set_style (style_Preformatted); } else { /* * For large and medium point sizes, use Header or Subheader styles * respectively. */ if (font_size >= GSC_LARGE_FONT_SIZE) glk_set_style (style_Header); else if (font_size >= GSC_MEDIUM_FONT_SIZE) glk_set_style (style_Subheader); else { /* * For bold, use Subheader; for italics, underline, or secondary * color, use Emphasized. */ if (gsc_attribute_bold > 0) glk_set_style (style_Subheader); else if (gsc_attribute_italic > 0 || gsc_attribute_underline > 0 || gsc_attribute_secondary_color > 0) glk_set_style (style_Emphasized); else { /* * There's nothing special about this text, so drop down to * Normal style. */ glk_set_style (style_Normal); } } } } /* * gsc_handle_font_tag() * gsc_handle_endfont_tag() * * Push the settings of a font tag onto the font stack, and pop on end of * font tag. Set the appropriate Glk style. */ static void gsc_handle_font_tag (const sc_char *argument) { /* Ignore the call on stack overrun. */ if (gsc_font_index < GSC_MAX_STYLE_NESTING) { sc_char *lower, *face, *size; sc_bool is_monospaced; sc_int index_, font_size; /* Get the current top of stack, or default on empty stack. */ if (gsc_font_index > 0) { is_monospaced = gsc_font_stack[gsc_font_index - 1].is_monospaced; font_size = gsc_font_stack[gsc_font_index - 1].size; } else { is_monospaced = FALSE; font_size = GSC_DEFAULT_FONT_SIZE; } /* Copy and convert argument to all lowercase. */ lower = gsc_malloc (strlen (argument) + 1); strcpy (lower, argument); for (index_ = 0; lower[index_] != '\0'; index_++) lower[index_] = glk_char_to_lower (lower[index_]); /* Find any face= portion of the tag argument. */ face = strstr (lower, "face="); if (face) { /* * There may be plenty of monospaced fonts, but we do only courier * and terminal. */ is_monospaced = strncmp (face, "face=\"courier\"", 14) == 0 || strncmp (face, "face=\"terminal\"", 15) == 0; } /* Find the size= portion of the tag argument. */ size = strstr (lower, "size="); if (size) { sc_uint value; /* Deal with incremental and absolute sizes. */ if (strncmp (size, "size=+", 6) == 0 && sscanf (size, "size=+%lu", &value) == 1) font_size += value; else if (strncmp (size, "size=-", 6) == 0 && sscanf (size, "size=-%lu", &value) == 1) font_size -= value; else if (sscanf (size, "size=%lu", &value) == 1) font_size = value; } /* Done with tag argument copy. */ free (lower); /* * Push the new font setting onto the font stack, and set Glk style. */ gsc_font_stack[gsc_font_index].is_monospaced = is_monospaced; gsc_font_stack[gsc_font_index++].size = font_size; gsc_set_glk_style (); } } static void gsc_handle_endfont_tag (void) { /* Unless underrun, pop the font stack and set Glk style. */ if (gsc_font_index > 0) { gsc_font_index--; gsc_set_glk_style (); } } /* * gsc_handle_attribute_tag() * * Increment the required attribute nesting counter, or decrement on end * tag. Set the appropriate Glk style. */ static void gsc_handle_attribute_tag (sc_int tag) { /* * Increment the required attribute nesting counter, and set Glk style. */ switch (tag) { case SC_TAG_BOLD: gsc_attribute_bold++; break; case SC_TAG_ITALICS: gsc_attribute_italic++; break; case SC_TAG_UNDERLINE: gsc_attribute_underline++; break; case SC_TAG_COLOR: gsc_attribute_secondary_color++; break; default: break; } gsc_set_glk_style (); } static void gsc_handle_endattribute_tag (sc_int tag) { /* * Decrement the required attribute nesting counter, unless underrun, and * set Glk style. */ switch (tag) { case SC_TAG_ENDBOLD: if (gsc_attribute_bold > 0) gsc_attribute_bold--; break; case SC_TAG_ENDITALICS: if (gsc_attribute_italic > 0) gsc_attribute_italic--; break; case SC_TAG_ENDUNDERLINE: if (gsc_attribute_underline > 0) gsc_attribute_underline--; break; case SC_TAG_ENDCOLOR: if (gsc_attribute_secondary_color > 0) gsc_attribute_secondary_color--; break; default: break; } gsc_set_glk_style (); } /* * gsc_handle_wait_tag() * * If Glk offers timers, delay for the requested period. Otherwise, this * function does nothing. */ static void gsc_handle_wait_tag (const sc_char *argument) { /* Ignore the wait tag if the Glk doesn't have timers. */ if (glk_gestalt (gestalt_Timer, 0)) { double delay = 0.0; /* Determine the delay time, and convert to milliseconds. */ if (sscanf (argument, "%lf", &delay) == 1 && delay > 0.0) { glui32 milliseconds; milliseconds = (glui32) (delay * GSC_MILLISECONDS_PER_SECOND); if (milliseconds > 0) { glui32 timeout; event_t event; /* * Request timeouts at 1/10 of the wait period, to minimize Glk * timer jitter, then wait for 10 timeouts. Cancel Glk timeouts * when done. */ glk_request_timer_events (milliseconds / GSC_TIMEOUTS_COUNT); for (timeout = 0; timeout < GSC_TIMEOUTS_COUNT; timeout++) gsc_event_wait (evtype_Timer, &event); glk_request_timer_events (0); } } } } /* * gsc_reset_glk_style() * * Drop all stacked fonts and nested attributes, and return to normal Glk * style. */ static void gsc_reset_glk_style (void) { /* Reset the font stack and attributes, and set a normal style. */ gsc_font_index = 0; gsc_attribute_bold = 0; gsc_attribute_italic = 0; gsc_attribute_underline = 0; gsc_attribute_secondary_color = 0; gsc_set_glk_style (); } /* * os_print_tag() * * Interpret selected Adrift output control tags. Not all are implemented * here; several are ignored. */ void os_print_tag (sc_int tag, const sc_char *argument) { event_t event; assert (argument); switch (tag) { case SC_TAG_CLS: /* Clear the main text display window. */ glk_window_clear (gsc_main_window); break; case SC_TAG_FONT: /* Handle with specific tag handler function. */ gsc_handle_font_tag (argument); break; case SC_TAG_ENDFONT: /* Handle with specific endtag handler function. */ gsc_handle_endfont_tag (); break; case SC_TAG_BOLD: case SC_TAG_ITALICS: case SC_TAG_UNDERLINE: case SC_TAG_COLOR: /* Handle with common attribute tag handler function. */ gsc_handle_attribute_tag (tag); break; case SC_TAG_ENDBOLD: case SC_TAG_ENDITALICS: case SC_TAG_ENDUNDERLINE: case SC_TAG_ENDCOLOR: /* Handle with common attribute endtag handler function. */ gsc_handle_endattribute_tag (tag); break; case SC_TAG_CENTER: case SC_TAG_RIGHT: case SC_TAG_ENDCENTER: case SC_TAG_ENDRIGHT: /* * We don't center or justify text, but so that things look right we do * want a newline on starting or ending such a section. */ glk_put_char ('\n'); break; case SC_TAG_WAIT: /* * Update the status line now only if it has its own window, then * handle with a specialized handler. */ if (gsc_status_window) gsc_status_notify (); gsc_handle_wait_tag (argument); break; case SC_TAG_WAITKEY: /* * If reading an input log, ignore; it disrupts replay. Write a newline * to separate off any unterminated game output instead. */ if (!gsc_readlog_stream) { /* Update the status line now only if it has its own window. */ if (gsc_status_window) gsc_status_notify (); /* Request a character event, and wait for it to be filled. */ glk_request_char_event (gsc_main_window); gsc_event_wait (evtype_CharInput, &event); } else glk_put_char ('\n'); break; default: /* Ignore unimplemented and unknown tags. */ break; } } /* * os_print_string() * * Print a text string to the main output window. Adrift games tend to * use assorted Microsoft CP-1252 characters, so output conversion may * be needed. */ void os_print_string (const sc_char *string) { int index_, marker; assert (string); assert (glk_stream_get_current ()); /* * Iterate each character in the string, scanning for unprintable characters * that need special handling. */ marker = 0; for (index_ = 0; string[index_] != '\0'; index_++) { unsigned char character; /* * Extract and check for Glk printability, and loop if printable; special * check for newline which Xglk denies will print, though we know it will. */ character = (unsigned char) string[index_]; if (character == '\n' || (character >= GSC_MIN_PRINTABLE && character <= GSC_MAX_PRINTABLE) || glk_gestalt (gestalt_CharOutput, character)) continue; /* Flush data up to this point. */ if (index_ > marker) glk_put_buffer ((char *) string + marker, index_ - marker); /* Handle unprintable character, advance marker. */ switch (character) { case '\t': glk_put_string (" "); break; case '\r': glk_put_char ('\n'); break; case 0x80: /* Euro */ glk_put_char ('E'); break; case 0x83: /* Florin */ glk_put_char ('f'); break; case 0x85: /* Ellipsis */ glk_put_string ("..."); break; case 0x86: /* Dagger */ glk_put_char ('+'); break; case 0x87: /* Double dagger */ glk_put_char ('#'); break; case 0x88: /* Circumflex */ glk_put_char ('^'); break; case 0x8B: /* Left angle quote */ glk_put_char ('<'); break; case 0x8c: /* OE ligature */ glk_put_string ("OE"); break; case 0x91: case 0x92: /* Left/right single quote */ glk_put_char ('\''); break; case 0x93: case 0x94: /* Left/right double quote */ glk_put_char ('"'); break; case 0x95: /* Bullet */ glk_put_char (0xb7); break; case 0x96: case 0x97: /* En/em dash */ glk_put_char ('-'); break; case 0x98: /* Small tilde */ glk_put_char ('~'); break; case 0x99: /* Trade mark */ glk_put_string ("[TM]"); break; case 0x9b: /* Right angle quote */ glk_put_char ('>'); break; case 0x9c: /* oe ligature */ glk_put_string ("oe"); break; default: /* Unsupported */ glk_put_char ('?'); break; } marker = index_ + 1; } /* Flush remaining unprinted data. */ if (index_ > marker) glk_put_buffer ((char *) string + marker, index_ - marker); } /* * os_print_string_debug() * * Debugging output goes to the main Glk window -- no special effects or * dedicated debugging window attempted. */ void os_print_string_debug (const sc_char *string) { os_print_string (string); } /* * gsc_styled_string() * gsc_styled_char() * gsc_standout_string() * gsc_standout_char() * gsc_normal_string() * gsc_normal_char() * gsc_header_string() * * Convenience functions to print strings in assorted styles. A standout * string is one that hints that it's from the interpreter, not the game. */ static void gsc_styled_string (glui32 style, const char *message) { assert (message); glk_set_style (style); glk_put_string ((char *) message); glk_set_style (style_Normal); } static void gsc_styled_char (glui32 style, char c) { char buffer[2]; buffer[0] = c; buffer[1] = '\0'; gsc_styled_string (style, buffer); } static void gsc_standout_string (const char *message) { gsc_styled_string (style_Emphasized, message); } static void gsc_standout_char (char c) { gsc_styled_char (style_Emphasized, c); } static void gsc_normal_string (const char *message) { gsc_styled_string (style_Normal, message); } static void gsc_normal_char (char c) { gsc_styled_char (style_Normal, c); } static void gsc_header_string (const char *message) { gsc_styled_string (style_Header, message); } /* * os_display_hints() * * This is a very basic hints display. In mitigation, very few games use * hints at all, and those that do are usually sparse in what they hint at, so * it's sort of good enough for the moment. */ void os_display_hints (sc_game game) { sc_game_hint hint; sc_int refused; /* For each hint, print the question, and confirm hint display. */ refused = 0; for (hint = sc_iterate_game_hints (game, NULL); hint; hint = sc_iterate_game_hints (game, hint)) { const sc_char *hint_question, *hint_text; /* If enough refusals, offer a way out of the loop. */ if (refused >= GSC_HINT_REFUSAL_LIMIT) { if (!os_confirm (GSC_CONF_CONTINUE_HINTS)) break; refused = 0; } /* Pop the question. */ hint_question = sc_get_game_hint_question (game, hint); gsc_normal_char ('\n'); gsc_standout_string (hint_question); gsc_normal_char ('\n'); /* Print the subtle hint, or on to the next hint. */ hint_text = sc_get_game_subtle_hint (game, hint); if (hint_text) { if (!os_confirm (GSC_CONF_SUBTLE_HINT)) { refused++; continue; } gsc_normal_char ('\n'); gsc_standout_string (hint_text); gsc_normal_string ("\n\n"); } /* Print the less than subtle hint, or on to the next hint. */ hint_text = sc_get_game_unsubtle_hint (game, hint); if (hint_text) { if (!os_confirm (GSC_CONF_UNSUBTLE_HINT)) { refused++; continue; } gsc_normal_char ('\n'); gsc_standout_string (hint_text); gsc_normal_string ("\n\n"); } } } /*---------------------------------------------------------------------*/ /* Glk resource handling functions */ /*---------------------------------------------------------------------*/ /* * os_play_sound() * os_stop_sound() * * Stub functions. The unused variables defeat gcc warnings. */ void os_play_sound (const sc_char *filepath, sc_int offset, sc_int length, sc_bool is_looping) { const sc_char *unused1; sc_int unused2, unused3; sc_bool unused4; unused1 = filepath; unused2 = offset; unused3 = length; unused4 = is_looping; } void os_stop_sound (void) { } /* * os_show_graphic() * * For graphic-capable Glk libraries on Linux, attempt graphics using xv. The * graphic capability test isn't really required, it's just a way of having * graphics behave without surprises; someone using a non-graphical Glk * probably won't expect graphics to pop up. * * For other cases, this is a stub function, with unused variables to defeat * gcc warnings. */ #ifdef LINUX_GRAPHICS static int gsclinux_graphics_enabled = TRUE; static char *gsclinux_game_file = NULL; void os_show_graphic (const sc_char *filepath, sc_int offset, sc_int length) { const sc_char *unused1; unused1 = filepath; if (length > 0 && gsclinux_graphics_enabled && glk_gestalt (gestalt_Graphics, 0)) { sc_char *buffer; /* * Try to extract data with dd. Assuming that works, background xv to * display the image, then background a job to delay ten seconds and * then delete the temporary file containing the image. Systems lacking * xv can usually use a small script, named xv, to invoke eog or an * alternative image display binary. Not exactly finessed. */ assert (gsclinux_game_file); buffer = gsc_malloc (strlen (gsclinux_game_file) + 128); sprintf (buffer, "dd if=%s ibs=1c skip=%ld count=%ld obs=100k" " of=/tmp/scare.jpg 2>/dev/null", gsclinux_game_file, offset, length); system (buffer); free (buffer); system ("xv /tmp/scare.jpg >/dev/null 2>&1 &"); system ("( sleep 10; rm /tmp/scare.jpg ) >/dev/null 2>&1 &"); } } #else void os_show_graphic (const sc_char *filepath, sc_int offset, sc_int length) { const sc_char *unused1; sc_int unused2, unused3; unused1 = filepath; unused2 = offset; unused3 = length; } #endif /*---------------------------------------------------------------------*/ /* Glk command escape functions */ /*---------------------------------------------------------------------*/ /* * gsc_command_script() * * Turn game output scripting (logging) on and off. */ static void gsc_command_script (const char *argument) { assert (argument); if (sc_strcasecmp (argument, "on") == 0) { frefid_t fileref; if (gsc_transcript_stream) { gsc_normal_string ("Glk transcript is already on.\n"); return; } fileref = glk_fileref_create_by_prompt (fileusage_Transcript | fileusage_TextMode, filemode_WriteAppend, 0); if (!fileref) { gsc_standout_string ("Glk transcript failed.\n"); return; } gsc_transcript_stream = glk_stream_open_file (fileref, filemode_WriteAppend, 0); glk_fileref_destroy (fileref); if (!gsc_transcript_stream) { gsc_standout_string ("Glk transcript failed.\n"); return; } glk_window_set_echo_stream (gsc_main_window, gsc_transcript_stream); gsc_normal_string ("Glk transcript is now on.\n"); } else if (sc_strcasecmp (argument, "off") == 0) { if (!gsc_transcript_stream) { gsc_normal_string ("Glk transcript is already off.\n"); return; } glk_stream_close (gsc_transcript_stream, NULL); gsc_transcript_stream = NULL; glk_window_set_echo_stream (gsc_main_window, NULL); gsc_normal_string ("Glk transcript is now off.\n"); } else if (strlen (argument) == 0) { gsc_normal_string ("Glk transcript is "); gsc_normal_string (gsc_transcript_stream ? "on" : "off"); gsc_normal_string (".\n"); } else { gsc_normal_string ("Glk transcript can be "); gsc_standout_string ("on"); gsc_normal_string (", or "); gsc_standout_string ("off"); gsc_normal_string (".\n"); } } /* * gsc_command_inputlog() * * Turn game input logging on and off. */ static void gsc_command_inputlog (const char *argument) { assert (argument); if (sc_strcasecmp (argument, "on") == 0) { frefid_t fileref; if (gsc_inputlog_stream) { gsc_normal_string ("Glk input logging is already on.\n"); return; } fileref = glk_fileref_create_by_prompt (fileusage_InputRecord | fileusage_BinaryMode, filemode_WriteAppend, 0); if (!fileref) { gsc_standout_string ("Glk input logging failed.\n"); return; } gsc_inputlog_stream = glk_stream_open_file (fileref, filemode_WriteAppend, 0); glk_fileref_destroy (fileref); if (!gsc_inputlog_stream) { gsc_standout_string ("Glk input logging failed.\n"); return; } gsc_normal_string ("Glk input logging is now on.\n"); } else if (sc_strcasecmp (argument, "off") == 0) { if (!gsc_inputlog_stream) { gsc_normal_string ("Glk input logging is already off.\n"); return; } glk_stream_close (gsc_inputlog_stream, NULL); gsc_inputlog_stream = NULL; gsc_normal_string ("Glk input log is now off.\n"); } else if (strlen (argument) == 0) { gsc_normal_string ("Glk input logging is "); gsc_normal_string (gsc_inputlog_stream ? "on" : "off"); gsc_normal_string (".\n"); } else { gsc_normal_string ("Glk input logging can be "); gsc_standout_string ("on"); gsc_normal_string (", or "); gsc_standout_string ("off"); gsc_normal_string (".\n"); } } /* * gsc_command_readlog() * * Set the game input log, to read input from a file. */ static void gsc_command_readlog (const char *argument) { assert (argument); if (sc_strcasecmp (argument, "on") == 0) { frefid_t fileref; if (gsc_readlog_stream) { gsc_normal_string ("Glk read log is already on.\n"); return; } fileref = glk_fileref_create_by_prompt (fileusage_InputRecord | fileusage_BinaryMode, filemode_Read, 0); if (!fileref) { gsc_standout_string ("Glk read log failed.\n"); return; } if (!glk_fileref_does_file_exist (fileref)) { glk_fileref_destroy (fileref); gsc_standout_string ("Glk read log failed.\n"); return; } gsc_readlog_stream = glk_stream_open_file (fileref, filemode_Read, 0); glk_fileref_destroy (fileref); if (!gsc_readlog_stream) { gsc_standout_string ("Glk read log failed.\n"); return; } gsc_normal_string ("Glk read log is now on.\n"); } else if (sc_strcasecmp (argument, "off") == 0) { if (!gsc_readlog_stream) { gsc_normal_string ("Glk read log is already off.\n"); return; } glk_stream_close (gsc_readlog_stream, NULL); gsc_readlog_stream = NULL; gsc_normal_string ("Glk read log is now off.\n"); } else if (strlen (argument) == 0) { gsc_normal_string ("Glk read log is "); gsc_normal_string (gsc_readlog_stream ? "on" : "off"); gsc_normal_string (".\n"); } else { gsc_normal_string ("Glk read log can be "); gsc_standout_string ("on"); gsc_normal_string (", or "); gsc_standout_string ("off"); gsc_normal_string (".\n"); } } /* * gsc_command_abbreviations() * * Turn abbreviation expansions on and off. */ static void gsc_command_abbreviations (const char *argument) { assert (argument); if (sc_strcasecmp (argument, "on") == 0) { if (gsc_abbreviations_enabled) { gsc_normal_string ("Glk abbreviation expansions are already on.\n"); return; } gsc_abbreviations_enabled = TRUE; gsc_normal_string ("Glk abbreviation expansions are now on.\n"); } else if (sc_strcasecmp (argument, "off") == 0) { if (!gsc_abbreviations_enabled) { gsc_normal_string ("Glk abbreviation expansions are already off.\n"); return; } gsc_abbreviations_enabled = FALSE; gsc_normal_string ("Glk abbreviation expansions are now off.\n"); } else if (strlen (argument) == 0) { gsc_normal_string ("Glk abbreviation expansions are "); gsc_normal_string (gsc_abbreviations_enabled ? "on" : "off"); gsc_normal_string (".\n"); } else { gsc_normal_string ("Glk abbreviation expansions can be "); gsc_standout_string ("on"); gsc_normal_string (", or "); gsc_standout_string ("off"); gsc_normal_string (".\n"); } } /* * gsc_command_print_version_number() * gsc_command_version() * * Print out the Glk library version number. */ static void gsc_command_print_version_number (glui32 version) { char buffer[64]; sprintf (buffer, "%lu.%lu.%lu", version >> 16, (version >> 8) & 0xff, version & 0xff); gsc_normal_string (buffer); } static void gsc_command_version (const char *argument) { glui32 version; assert (argument); gsc_normal_string ("This is version "); gsc_command_print_version_number (GSC_PORT_VERSION); gsc_normal_string (" of the Glk SCARE port.\n"); version = glk_gestalt (gestalt_Version, 0); gsc_normal_string ("The Glk library version is "); gsc_command_print_version_number (version); gsc_normal_string (".\n"); } /* * gsc_command_commands() * * Turn command escapes off. Once off, there's no way to turn them back on. * Commands must be on already to enter this function. */ static void gsc_command_commands (const char *argument) { assert (argument); if (sc_strcasecmp (argument, "on") == 0) { gsc_normal_string ("Glk commands are already on.\n"); } else if (sc_strcasecmp (argument, "off") == 0) { gsc_commands_enabled = FALSE; gsc_normal_string ("Glk commands are now off.\n"); } else if (strlen (argument) == 0) { gsc_normal_string ("Glk commands are "); gsc_normal_string (gsc_commands_enabled ? "on" : "off"); gsc_normal_string (".\n"); } else { gsc_normal_string ("Glk commands can be "); gsc_standout_string ("on"); gsc_normal_string (", or "); gsc_standout_string ("off"); gsc_normal_string (".\n"); } } /* * gsc_command_license() * * Print licensing terms. */ static void gsc_command_license (const char *argument) { assert (argument); gsc_normal_string ("This program is free software; you can redistribute it" " and/or modify it under the terms of version 2 of the" " GNU General Public License as published by the Free" " Software Foundation.\n\n"); gsc_normal_string ("This program is distributed in the hope that it will be" " useful, but "); gsc_standout_string ("WITHOUT ANY WARRANTY"); gsc_normal_string ("; without even the implied warranty of "); gsc_standout_string ("MERCHANTABILITY"); gsc_normal_string (" or "); gsc_standout_string ("FITNESS FOR A PARTICULAR PURPOSE"); gsc_normal_string (". See the GNU General Public License for more" " details.\n\n"); gsc_normal_string ("You should have received a copy of the GNU General" " Public License along with this program; if not, write" " to the Free Software Foundation, Inc., 59 Temple" " Place, Suite 330, Boston, MA 02111-1307 USA\n\n"); gsc_normal_string ("Please report any bugs, omissions, or misfeatures to "); gsc_standout_string ("simon_baldwin@yahoo.com"); gsc_normal_string (".\n"); } /* Glk subcommands and handler functions. */ typedef const struct { const char * const command; /* Glk subcommand. */ void (* const handler) (const char *argument); /* Subcommand handler. */ const int takes_argument; /* Argument flag. */ } gsc_command_t; typedef gsc_command_t *gsc_commandref_t; static void gsc_command_summary (const char *argument); static void gsc_command_help (const char *argument); static gsc_command_t GSC_COMMAND_TABLE[] = { {"summary", gsc_command_summary, FALSE}, {"script", gsc_command_script, TRUE}, {"inputlog", gsc_command_inputlog, TRUE}, {"readlog", gsc_command_readlog, TRUE}, {"abbreviations", gsc_command_abbreviations, TRUE}, {"version", gsc_command_version, FALSE}, {"commands", gsc_command_commands, TRUE}, {"license", gsc_command_license, FALSE}, {"help", gsc_command_help, TRUE}, {NULL, NULL, FALSE} }; /* * gsc_command_summary() * * Report all current Glk settings. */ static void gsc_command_summary (const char *argument) { gsc_commandref_t entry; assert (argument); /* * Call handlers that have status to report with an empty argument, * prompting each to print its current setting. */ for (entry = GSC_COMMAND_TABLE; entry->command; entry++) { if (entry->handler == gsc_command_summary || entry->handler == gsc_command_license || entry->handler == gsc_command_help) continue; entry->handler (""); } } /* * gsc_command_help() * * Document the available Glk commands. */ static void gsc_command_help (const char *command) { gsc_commandref_t entry, matched; assert (command); if (strlen (command) == 0) { gsc_normal_string ("Glk commands are"); for (entry = GSC_COMMAND_TABLE; entry->command; entry++) { gsc_commandref_t next; next = entry + 1; gsc_normal_string (next->command ? " " : " and "); gsc_standout_string (entry->command); gsc_normal_string (next->command ? "," : ".\n\n"); } gsc_normal_string ("Glk commands may be abbreviated, as long as" " the abbreviation is unambiguous. Use "); gsc_standout_string ("glk help"); gsc_normal_string (" followed by a Glk command name for help on that" " command.\n"); return; } matched = NULL; for (entry = GSC_COMMAND_TABLE; entry->command; entry++) { if (sc_strncasecmp (command, entry->command, strlen (command)) == 0) { if (matched) { gsc_normal_string ("The Glk command "); gsc_standout_string (command); gsc_normal_string (" is ambiguous. Try "); gsc_standout_string ("glk help"); gsc_normal_string (" for more information.\n"); return; } matched = entry; } } if (!matched) { gsc_normal_string ("The Glk command "); gsc_standout_string (command); gsc_normal_string (" is not valid. Try "); gsc_standout_string ("glk help"); gsc_normal_string (" for more information.\n"); return; } if (matched->handler == gsc_command_summary) { gsc_normal_string ("Prints a summary of all the current Glk SCARE" " settings.\n"); } else if (matched->handler == gsc_command_script) { gsc_normal_string ("Logs the game's output to a file.\n\nUse "); gsc_standout_string ("glk script on"); gsc_normal_string (" to begin logging game output, and "); gsc_standout_string ("glk script off"); gsc_normal_string (" to end it. Glk SCARE will ask you for a file" " when you turn scripts on.\n"); } else if (matched->handler == gsc_command_inputlog) { gsc_normal_string ("Records the commands you type into a game.\n\nUse "); gsc_standout_string ("glk inputlog on"); gsc_normal_string (", to begin recording your commands, and "); gsc_standout_string ("glk inputlog off"); gsc_normal_string (" to turn off input logs. You can play back" " recorded commands into a game with the "); gsc_standout_string ("glk readlog"); gsc_normal_string (" command.\n"); } else if (matched->handler == gsc_command_readlog) { gsc_normal_string ("Plays back commands recorded with "); gsc_standout_string ("glk inputlog on"); gsc_normal_string (".\n\nUse "); gsc_standout_string ("glk readlog on"); gsc_normal_string (". Command play back stops at the end of the" " file. You can also play back commands from a" " text file created using any standard editor.\n"); } else if (matched->handler == gsc_command_abbreviations) { gsc_normal_string ("Controls abbreviation expansion.\n\nGlk SCARE" " automatically expands several standard single" " letter abbreviations for you; for example, \"x\"" " becomes \"examine\". Use "); gsc_standout_string ("glk abbreviations on"); gsc_normal_string (" to turn this feature on, and "); gsc_standout_string ("glk abbreviations off"); gsc_normal_string (" to turn it off. While the feature is on, you" " can bypass abbreviation expansion for an" " individual game command by prefixing it with a" " single quote.\n"); } else if (matched->handler == gsc_command_version) { gsc_normal_string ("Prints the version numbers of the Glk library" " and the Glk SCARE port.\n"); } else if (matched->handler == gsc_command_commands) { gsc_normal_string ("Turn off Glk commands.\n\nUse "); gsc_standout_string ("glk commands off"); gsc_normal_string (" to disable all Glk commands, including this one." " Once turned off, there is no way to turn Glk" " commands back on while inside the game.\n"); } else if (matched->handler == gsc_command_license) { gsc_normal_string ("Prints Glk SCARE's software license.\n"); } else if (matched->handler == gsc_command_help) gsc_command_help (""); else gsc_normal_string ("There is no help available on that Glk command." " Sorry.\n"); } /* * gsc_command_escape() * * This function is handed each input line. If the line contains a specific * Glk port command, handle it and return TRUE, otherwise return FALSE. */ static int gsc_command_escape (const char *string) { int posn; char *string_copy, *command, *argument; assert (string); /* * Return FALSE if the string doesn't begin with the Glk command escape * introducer. */ posn = strspn (string, "\t "); if (sc_strncasecmp (string + posn, "glk", strlen ("glk")) != 0) return FALSE; /* Take a copy of the string, without any leading space or introducer. */ string_copy = gsc_malloc (strlen (string + posn) + 1 - strlen ("glk")); strcpy (string_copy, string + posn + strlen ("glk")); /* * Find the subcommand; the first word in the string copy. Find its end, * and ensure it terminates with a NUL. */ posn = strspn (string_copy, "\t "); command = string_copy + posn; posn += strcspn (string_copy + posn, "\t "); if (string_copy[posn] != '\0') string_copy[posn++] = '\0'; /* * Now find any argument data for the command, ensuring it too terminates * with a NUL. */ posn += strspn (string_copy + posn, "\t "); argument = string_copy + posn; posn += strcspn (string_copy + posn, "\t "); string_copy[posn] = '\0'; /* * Try to handle the command and argument as a Glk subcommand. If it * doesn't run unambiguously, print command usage. Treat an empty command * as "help". */ if (strlen (command) > 0) { gsc_commandref_t entry, matched; int matches; /* * Search for the first unambiguous table command string matching * the command passed in. */ matches = 0; matched = NULL; for (entry = GSC_COMMAND_TABLE; entry->command; entry++) { if (sc_strncasecmp (command, entry->command, strlen (command)) == 0) { matches++; matched = entry; } } /* If the match was unambiguous, call the command handler. */ if (matches == 1) { gsc_normal_char ('\n'); matched->handler (argument); if (!matched->takes_argument && strlen (argument) > 0) { gsc_normal_string ("[The "); gsc_standout_string (matched->command); gsc_normal_string (" command ignores arguments.]\n"); } } /* No match, or the command was ambiguous. */ else { gsc_normal_string ("\nThe Glk command "); gsc_standout_string (command); gsc_normal_string (" is "); gsc_normal_string (matches == 0 ? "not valid" : "ambiguous"); gsc_normal_string (". Try "); gsc_standout_string ("glk help"); gsc_normal_string (" for more information.\n"); } } else { gsc_normal_char ('\n'); gsc_command_help (""); } /* The string contained a Glk command; return TRUE. */ free (string_copy); return TRUE; } /*---------------------------------------------------------------------*/ /* Glk port input functions */ /*---------------------------------------------------------------------*/ /* Quote used to suppress abbreviation expansion and local commands. */ static const char GSC_QUOTED_INPUT = '\''; /* Table of single-character command abbreviations. */ typedef const struct { const char abbreviation; /* Abbreviation character. */ const char *const expansion; /* Expansion string. */ } gsc_abbreviation_t; typedef gsc_abbreviation_t *gsc_abbreviationref_t; static gsc_abbreviation_t GSC_ABBREVIATIONS[] = { {'c', "close"}, {'g', "again"}, {'i', "inventory"}, {'k', "attack"}, {'l', "look"}, {'p', "open"}, {'q', "quit"}, {'r', "drop"}, {'t', "take"}, {'x', "examine"}, {'y', "yes"}, {'z', "wait"}, {'\0', NULL} }; /* * gsc_expand_abbreviations() * * Expand a few common one-character abbreviations commonly found in other * game systems. */ static void gsc_expand_abbreviations (char *buffer, int size) { char *command, abbreviation; const char *expansion; gsc_abbreviationref_t entry; assert (buffer); /* Ignore anything that isn't a single letter command. */ command = buffer + strspn (buffer, "\t "); if (!(strlen (command) == 1 || (strlen (command) > 1 && isspace (command[1])))) return; /* Scan the abbreviations table for a match. */ abbreviation = glk_char_to_lower ((unsigned char) command[0]); expansion = NULL; for (entry = GSC_ABBREVIATIONS; entry->expansion; entry++) { if (entry->abbreviation == abbreviation) { expansion = entry->expansion; break; } } /* * If a match found, check for a fit, then replace the character with the * expansion string. */ if (expansion) { if (strlen (buffer) + strlen (expansion) - 1 >= (unsigned int) size) return; memmove (command + strlen (expansion) - 1, command, strlen (command) + 1); memcpy (command, expansion, strlen (expansion)); gsc_standout_string ("["); gsc_standout_char (abbreviation); gsc_standout_string (" -> "); gsc_standout_string (expansion); gsc_standout_string ("]\n"); } } /* * os_read_line() * * Read and return a line of player input. */ sc_bool os_read_line (sc_char *buffer, sc_int length) { event_t event; assert (buffer && length > 0); /* If a help request is pending, provide a user hint. */ gsc_output_provide_help_hint (); /* * Ensure normal style, update the status line, and issue an input prompt. */ gsc_reset_glk_style (); gsc_status_notify (); glk_put_string (">"); /* * If we have an input log to read from, use that until it is exhausted. * On end of file, close the stream and resume input from line requests. */ if (gsc_readlog_stream) { glui32 chars; /* Get the next line from the log stream. */ chars = glk_get_line_stream (gsc_readlog_stream, buffer, length); if (chars > 0) { /* Echo the line just read in input style. */ glk_set_style (style_Input); glk_put_buffer (buffer, chars); glk_set_style (style_Normal); /* Return this line as player input. */ return TRUE; } /* * We're at the end of the log stream. Close it, and then continue * on to request a line from Glk. */ glk_stream_close (gsc_readlog_stream, NULL); gsc_readlog_stream = NULL; } /* * No input log being read, or we just hit the end of file on one. Revert * to normal line input; start by getting a new line from Glk. */ glk_request_line_event (gsc_main_window, buffer, length - 1, 0); gsc_event_wait (evtype_LineInput, &event); /* Terminate the input line with a NUL. */ assert (event.val1 <= (glui32) length - 1); buffer[event.val1] = '\0'; /* * If neither abbreviations nor local commands are enabled, use the data * read above without further massaging. */ if (gsc_abbreviations_enabled || gsc_commands_enabled) { char *command; /* * If the first non-space input character is a quote, bypass all * abbreviation expansion and local command recognition, and use the * unadulterated input, less introductory quote. */ command = buffer + strspn (buffer, "\t "); if (command[0] == GSC_QUOTED_INPUT) { /* Delete the quote with memmove(). */ memmove (command, command + 1, strlen (command)); } else { /* Check for, and expand, and abbreviated commands. */ if (gsc_abbreviations_enabled) gsc_expand_abbreviations (buffer, length); /* * Check for standalone "help", then for Glk port special commands; * suppress the interpreter's use of this input for Glk commands by * returning FALSE. */ if (gsc_commands_enabled) { int posn; posn = strspn (buffer, "\t "); if (sc_strncasecmp (buffer + posn, "help", strlen ("help"))== 0) { if (strspn (buffer + posn + strlen ("help"), "\t ") == strlen (buffer + posn + strlen ("help"))) { gsc_output_register_help_request (); } } if (gsc_command_escape (buffer)) { gsc_output_silence_help_hints (); return FALSE; } } } } /* * If there is an input log active, log this input string to it. Note that * by logging here we get any abbreviation expansions but we won't log glk * special commands, nor any input read from a current open input log. */ if (gsc_inputlog_stream) { glk_put_string_stream (gsc_inputlog_stream, buffer); glk_put_char_stream (gsc_inputlog_stream, '\n'); } return TRUE; } /* * os_read_line_debug() * * Read and return a debugger command line. There's no dedicated debugging * window, so this is just a call to the normal readline, with an additional * prompt. */ sc_bool os_read_line_debug (sc_char *buffer, sc_int length) { gsc_output_silence_help_hints (); gsc_reset_glk_style (); glk_put_string ("[SCARE debug]"); return os_read_line (buffer, length); } /* * os_confirm() * * Confirm a game action with a yes/no prompt. */ sc_bool os_confirm (sc_int type) { sc_char response; /* * Always allow game saves and hint display, and if we're reading from an * input log, allow everything no matter what, on the assumption that the * user knows what they are doing. */ if (gsc_readlog_stream || type == SC_CONF_SAVE || type == SC_CONF_VIEW_HINTS) return TRUE; /* Ensure back to normal style, and update status. */ gsc_reset_glk_style (); gsc_status_notify (); /* Prompt for the confirmation, based on the type. */ if (type == GSC_CONF_SUBTLE_HINT) glk_put_string ("View the subtle hint for this topic"); else if (type == GSC_CONF_UNSUBTLE_HINT) glk_put_string ("View the unsubtle hint for this topic"); else if (type == GSC_CONF_CONTINUE_HINTS) glk_put_string ("Continue with hints"); else { glk_put_string ("Do you really want to "); switch (type) { case SC_CONF_QUIT: glk_put_string ("quit"); break; case SC_CONF_RESTART: glk_put_string ("restart"); break; case SC_CONF_SAVE: glk_put_string ("save"); break; case SC_CONF_RESTORE: glk_put_string ("restore"); break; case SC_CONF_VIEW_HINTS: glk_put_string ("view hints"); break; default: glk_put_string ("do that"); break; } } glk_put_string ("? "); /* Loop until 'yes' or 'no' returned. */ do { event_t event; /* Wait for a standard key, ignoring Glk special keys. */ do { glk_request_char_event (gsc_main_window); gsc_event_wait (evtype_CharInput, &event); } while (event.val1 > UCHAR_MAX); response = glk_char_to_upper (event.val1); } while (response != 'Y' && response != 'N'); /* Echo the confirmation response, and a new line. */ glk_set_style (style_Input); glk_put_string (response == 'Y' ? "Yes" : "No"); glk_set_style (style_Normal); glk_put_char ('\n'); /* Use a short delay on restarts, if confirmed. */ if (type == SC_CONF_RESTART && response == 'Y') gsc_short_delay (); /* Return TRUE if 'Y' was entered. */ return (response == 'Y'); } /*---------------------------------------------------------------------*/ /* Glk port event functions */ /*---------------------------------------------------------------------*/ /* Short delay before restarts; 1s, in 100ms segments. */ static const glui32 GSC_DELAY_TIMEOUT = 100; static const glui32 GSC_DELAY_TIMEOUTS_COUNT = 10; /* * gsc_short_delay() * * Delay for a short period; used before restarting a completed game, to * improve the display where 'r', or confirming restart, triggers an otherwise * immediate, and abrupt, restart. */ static void gsc_short_delay (void) { /* Ignore the call if the Glk doesn't have timers. */ if (glk_gestalt (gestalt_Timer, 0)) { glui32 timeout; /* Timeout in small chunks to minimize Glk jitter. */ glk_request_timer_events (GSC_DELAY_TIMEOUT); for (timeout = 0; timeout < GSC_DELAY_TIMEOUTS_COUNT; timeout++) { event_t event; gsc_event_wait (evtype_Timer, &event); } glk_request_timer_events (0); } } /* * gsc_event_wait() * * Process Glk events until one of the expected type arrives. Return the * event of that type. */ static void gsc_event_wait (glui32 wait_type, event_t * event) { assert (event); do { glk_select (event); switch (event->type) { case evtype_Arrange: case evtype_Redraw: /* Refresh any sensitive windows on size events. */ gsc_status_redraw (); break; } } while (event->type != wait_type); } /*---------------------------------------------------------------------*/ /* Glk port file functions */ /*---------------------------------------------------------------------*/ /* * os_open_file() * * Open a file for save or restore, and return a Glk stream for the opened * file. */ void * os_open_file (sc_bool is_save) { glui32 usage, fmode; frefid_t fileref; strid_t stream; usage = fileusage_SavedGame | fileusage_BinaryMode; fmode = is_save ? filemode_Write : filemode_Read; fileref = glk_fileref_create_by_prompt (usage, fmode, 0); if (!fileref) return NULL; if (!is_save && !glk_fileref_does_file_exist (fileref)) { glk_fileref_destroy (fileref); return NULL; } stream = glk_stream_open_file (fileref, fmode, 0); glk_fileref_destroy (fileref); return stream; } /* * os_write_file() * os_read_file() * * Write/read the given buffered data to/from the open Glk stream. */ void os_write_file (void *opaque, const sc_byte *buffer, sc_int length) { strid_t stream = (strid_t) opaque; assert (opaque && buffer); glk_put_buffer_stream (stream, (char *) buffer, length); } sc_int os_read_file (void *opaque, sc_byte *buffer, sc_int length) { strid_t stream = (strid_t) opaque; assert (opaque && buffer); return glk_get_buffer_stream (stream, (char *) buffer, length); } /* * os_close_file() * * Close the opened Glk stream. */ void os_close_file (void *opaque) { strid_t stream = (strid_t) opaque; assert (opaque); glk_stream_close (stream, NULL); } /*---------------------------------------------------------------------*/ /* main() and options parsing */ /*---------------------------------------------------------------------*/ /* Loading message flush delay timeout. */ static const glui32 GSC_LOADING_TIMEOUT = 100; /* Enumerated game end options. */ enum gsc_end_option { GAME_RESTART, GAME_UNDO, GAME_QUIT }; /* * The following value needs to be passed between the startup_code and main * functions. */ static char *gsc_game_message = NULL; /* * gsc_callback() * * Callback function for reading in game and restore file data; fills a * buffer with TAF or TAS file data from a Glk stream, and returns the byte * count. */ static sc_int gsc_callback (void *opaque, sc_byte *buffer, sc_int length) { strid_t stream = (strid_t) opaque; assert (stream); return glk_get_buffer_stream (stream, (char *) buffer, length); } /* * gsc_get_ending_option() * * Offer the option to restart, undo, or quit. Returns the selected game * end option. Called on game completion. */ static enum gsc_end_option gsc_get_ending_option (void) { sc_char response; /* Ensure back to normal style, and update status. */ gsc_reset_glk_style (); gsc_status_notify (); /* Prompt for restart, undo, or quit. */ glk_put_string ("\nWould you like to RESTART, UNDO a turn, or QUIT? "); /* Loop until 'restart', 'undo' or 'quit'. */ do { event_t event; do { glk_request_char_event (gsc_main_window); gsc_event_wait (evtype_CharInput, &event); } while (event.val1 > UCHAR_MAX); response = glk_char_to_upper (event.val1); } while (response != 'R' && response != 'U' && response != 'Q'); /* Echo the confirmation response, and a new line. */ glk_set_style (style_Input); switch (response) { case 'R': glk_put_string ("Restart"); break; case 'U': glk_put_string ("Undo"); break; case 'Q': glk_put_string ("Quit"); break; default: gsc_fatal ("GLK: Invalid response encountered"); glk_exit (); } glk_set_style (style_Normal); glk_put_char ('\n'); /* Return the appropriate value for response. */ switch (response) { case 'R': return GAME_RESTART; case 'U': return GAME_UNDO; case 'Q': return GAME_QUIT; default: gsc_fatal ("GLK: Invalid response encountered"); glk_exit (); } /* Unreachable; supplied to suppress compiler warning. */ return GAME_QUIT; } /* * gsc_startup_code() * gsc_main * * Together, these functions take the place of the original main(). The * first one is called from the platform-specific startup_code(), to parse * and generally handle options. The second is called from glk_main, and * does the real work of running the game. */ static int gsc_startup_code (strid_t game_stream, strid_t restore_stream, sc_uint trace_flags, sc_bool enable_debugger, sc_bool stable_random) { winid_t window; assert (game_stream); /* Open a temporary Glk main window. */ window = glk_window_open (0, 0, 0, wintype_TextBuffer, 0); if (window) { /* Clear and initialize the temporary window. */ glk_window_clear (window); glk_set_window (window); glk_set_style (style_Normal); /* * Display a brief loading game message; here we have to use a timeout * to ensure that the text is flushed to Glk. */ glk_put_string ("Loading game...\n"); if (glk_gestalt (gestalt_Timer, 0)) { event_t event; glk_request_timer_events (GSC_LOADING_TIMEOUT); do { glk_select (&event); } while (event.type != evtype_Timer); glk_request_timer_events (0); } } /* * Set tracing flags, then try to create a SCARE game reference from the * TAF file. Since we need this in our call from glk_main, we have to keep * it in a module static variable. If we can't open the TAF file, then * we'll set the pointer to NULL, and complain about it later in main. * Passing the message string around like this is a nuisance... */ sc_set_trace_flags (trace_flags); gsc_game = sc_game_from_callback (gsc_callback, game_stream); if (!gsc_game) { gsc_game = NULL; gsc_game_message = "Unable to load an Adrift game from the" " requested file."; } else gsc_game_message = NULL; glk_stream_close (game_stream, NULL); /* * If the game was created successfully and there is a restore stream, try * to immediately restore the game from that stream. */ if (gsc_game && restore_stream) { if (!sc_load_game_from_callback (gsc_game, gsc_callback, restore_stream)) { sc_free_game (gsc_game); gsc_game = NULL; gsc_game_message = "Unable to restore this Adrift game from the" " requested file."; } else gsc_game_message = NULL; } if (restore_stream) glk_stream_close (restore_stream, NULL); /* Set up game debugging if the game was created successfully. */ if (gsc_game) sc_set_game_debugger_enabled (gsc_game, enable_debugger); /* Set portable and predictable random number generation if requested. */ if (stable_random) { sc_set_portable_random (TRUE); sc_reseed_random_sequence (1); } /* Close the temporary window. */ if (window) glk_window_close (window, NULL); /* Game set up, perhaps successfully. */ return TRUE; } static void gsc_main (void) { sc_bool is_running; /* Ensure SCARE internal types have the right sizes. */ if (!(sizeof (sc_byte) == 1 && sizeof (sc_char) == 1 && sizeof (sc_bool) == 1 && sizeof (sc_uint) >= 4 && sizeof (sc_int) >= 4 && sizeof (sc_uint) <= 8 && sizeof (sc_int) <= 8)) { gsc_fatal ("GLK: Types sized incorrectly, recompilation is needed"); glk_exit (); } /* Create the Glk window, and set its stream as the current one. */ gsc_main_window = glk_window_open (0, 0, 0, wintype_TextBuffer, 0); if (!gsc_main_window) { gsc_fatal ("GLK: Can't open main window"); glk_exit (); } glk_window_clear (gsc_main_window); glk_set_window (gsc_main_window); glk_set_style (style_Normal); /* If there's a problem with the game file, complain now. */ if (!gsc_game) { assert (gsc_game_message); gsc_header_string ("Glk SCARE Error\n\n"); gsc_normal_string (gsc_game_message); gsc_normal_char ('\n'); glk_exit (); } /* Try to create a one-line status window. We can live without it. */ gsc_status_window = glk_window_open (gsc_main_window, winmethod_Above | winmethod_Fixed, 1, wintype_TextGrid, 0); /* Repeat the game until no more restarts requested. */ is_running = TRUE; while (is_running) { /* Run the game until it ends, or the user quits. */ gsc_status_notify (); sc_interpret_game (gsc_game); /* * If the game did not complete, the user quit explicitly, so leave the * game repeat loop. */ if (!sc_has_game_completed (gsc_game)) { is_running = FALSE; break; } /* * If reading from an input log, close it now. We need to request a * user selection, probably modal, and after that we probably don't * want the follow-on readlog data being used as game input. */ if (gsc_readlog_stream) { glk_stream_close (gsc_readlog_stream, NULL); gsc_readlog_stream = NULL; } /* * Get user selection of restart, undo a turn, or quit completed game. * If undo is unavailable (this should not be possible), degrade to * restart. */ switch (gsc_get_ending_option ()) { case GAME_RESTART: gsc_short_delay (); sc_restart_game (gsc_game); break; case GAME_UNDO: if (sc_is_game_undo_available (gsc_game)) { sc_undo_game_turn (gsc_game); gsc_normal_string ("The previous turn has been undone.\n"); } else { gsc_normal_string ("Sorry, no undo is available.\n"); gsc_short_delay (); sc_restart_game (gsc_game); } break; case GAME_QUIT: is_running = FALSE; break; } } /* All done -- release game resources. */ sc_free_game (gsc_game); /* Close any open transcript, input log, and/or read log. */ if (gsc_transcript_stream) { glk_stream_close (gsc_transcript_stream, NULL); gsc_transcript_stream = NULL; } if (gsc_inputlog_stream) { glk_stream_close (gsc_inputlog_stream, NULL); gsc_inputlog_stream = NULL; } if (gsc_readlog_stream) { glk_stream_close (gsc_readlog_stream, NULL); gsc_readlog_stream = NULL; } } /*---------------------------------------------------------------------*/ /* Linkage between Glk entry/exit calls and the real interpreter */ /*---------------------------------------------------------------------*/ /* * Safety flags, to ensure we always get startup before main, and that * we only get a call to main once. */ static int gsc_startup_called = FALSE, gsc_main_called = FALSE; /* * glk_main() * * Main entry point for Glk. Here, all startup is done, and we call our * function to run the game, or to report errors if gsc_game_message is set. */ void glk_main (void) { assert (gsc_startup_called && !gsc_main_called); gsc_main_called = TRUE; /* Call the generic interpreter main function. */ gsc_main (); } /*---------------------------------------------------------------------*/ /* Glk linkage relevant only to the UNIX platform */ /*---------------------------------------------------------------------*/ #ifdef __unix #include "glkstart.h" /* * Glk arguments for UNIX versions of the Glk interpreter. */ glkunix_argumentlist_t glkunix_arguments[] = { {(char *) "-nc", glkunix_arg_NoValue, (char *) "-nc No local handling for Glk special commands"}, {(char *) "-na", glkunix_arg_NoValue, (char *) "-na Turn off abbreviation expansions"}, #ifdef LINUX_GRAPHICS {(char *) "-ng", glkunix_arg_NoValue, (char *) "-ng Turn off attempts at game graphics"}, #endif {(char *) "-r", glkunix_arg_ValueFollows, (char *) "-r FILE Restore from FILE on starting the game"}, {(char *) "", glkunix_arg_ValueCanFollow, (char *) "filename game to run"}, {NULL, glkunix_arg_End, NULL} }; /* * glkunix_startup_code() * * Startup entry point for UNIX versions of Glk interpreter. Glk will call * glkunix_startup_code() to pass in arguments. On startup, parse arguments * and open a Glk stream to the game, then call the generic gsc_startup_code() * to build a game from the stream. On error, set the message in * gsc_game_message; the core gsc_main() will report it when it's called. */ int glkunix_startup_code (glkunix_startup_t * data) { int argc = data->argc; sc_char **argv = data->argv; int argv_index; sc_char *restore_from; strid_t game_stream, restore_stream; sc_uint trace_flags; sc_bool enable_debugger, stable_random; assert (!gsc_startup_called); gsc_startup_called = TRUE; /* Handle command line arguments. */ restore_from = NULL; for (argv_index = 1; argv_index < argc && argv[argv_index][0] == '-'; argv_index++) { if (strcmp (argv[argv_index], "-nc") == 0) { gsc_commands_enabled = FALSE; continue; } if (strcmp (argv[argv_index], "-na") == 0) { gsc_abbreviations_enabled = FALSE; continue; } #ifdef LINUX_GRAPHICS if (strcmp (argv[argv_index], "-ng") == 0) { gsclinux_graphics_enabled = FALSE; continue; } #endif if (strcmp (argv[argv_index], "-r") == 0) { restore_from = argv[++argv_index]; continue; } return FALSE; } /* On invalid usage, set a complaint message and return. */ if (argv_index != argc - 1) { gsc_game = NULL; if (argv_index < argc - 1) gsc_game_message = "More than one game file" " was given on the command line."; else gsc_game_message = "No game file was given on the command line."; return TRUE; } /* Open a stream to the TAF file, complain if this fails. */ game_stream = glkunix_stream_open_pathname (argv[argv_index], FALSE, 0); if (!game_stream) { gsc_game = NULL; gsc_game_message = "Unable to open the requested game file."; return TRUE; } else gsc_game_message = NULL; /* * If a restore requested, open a stream to the TAF (TAS) file, and * again, complain if this fails. */ if (restore_from) { restore_stream = glkunix_stream_open_pathname (restore_from, FALSE, 0); if (!restore_stream) { glk_stream_close (game_stream, NULL); gsc_game = NULL; gsc_game_message = "Unable to open the requested restore file."; return TRUE; } else gsc_game_message = NULL; } else restore_stream = NULL; /* Set SCARE trace flags and other general setup from the environment. */ if (getenv ("SC_TRACE_FLAGS")) trace_flags = strtoul (getenv ("SC_TRACE_FLAGS"), NULL, 0); else trace_flags = 0; enable_debugger = (getenv ("SC_DEBUGGER_ENABLED") != NULL); stable_random = (getenv ("SC_STABLE_RANDOM_ENABLED") != NULL); #ifdef LINUX_GRAPHICS /* Note the path to the game file for graphics extraction. */ gsclinux_game_file = argv[argv_index]; #endif /* Use the generic startup code to complete startup. */ return gsc_startup_code (game_stream, restore_stream, trace_flags, enable_debugger, stable_random); } #endif /* __unix */ /*---------------------------------------------------------------------*/ /* Glk linkage relevant only to the Windows platform */ /*---------------------------------------------------------------------*/ #ifdef _WIN32 #include #include "WinGlk.h" #include "resource.h" /* Windows constants and external definitions. */ static const unsigned int GSCWIN_GLK_INIT_VERSION = 0x601; extern int InitGlk (unsigned int iVersion); /* * WinMain() * * Entry point for all Glk applications. */ int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { /* Attempt to initialize both the Glk library and SCARE. */ if (!(InitGlk (GSCWIN_GLK_INIT_VERSION) && winglk_startup_code (lpCmdLine))) return 0; /* Run the application; no return from this routine. */ glk_main (); glk_exit (); return 0; } /* * winglk_startup_code() * * Startup entry point for Windows versions of Glk interpreter. */ int winglk_startup_code (const char *cmdline) { const char *filename; frefid_t fileref; strid_t game_stream; sc_uint trace_flags; sc_bool enable_debugger, stable_random; assert (!gsc_startup_called); gsc_startup_called = TRUE; /* Set up application and window. */ winglk_app_set_name ("Scare"); winglk_set_menu_name ("&Scare"); winglk_window_set_title ("Scare Adrift Interpreter"); winglk_set_about_text ("Windows Scare 1.3.6-WinGlk1.25"); winglk_set_gui (IDI_SCARE); /* Open a stream to the game. */ filename = winglk_get_initial_filename (cmdline, "Select an Adrift game to run", "Adrift Files (.taf)|*.taf;All Files (*.*)|*.*||"); if (!filename) return 0; fileref = glk_fileref_create_by_name (fileusage_BinaryMode | fileusage_Data, (char *) filename, 0); if (!fileref) return 0; game_stream = glk_stream_open_file (fileref, filemode_Read, 0); glk_fileref_destroy (fileref); if (!game_stream) return 0; /* Set trace, debugger, and portable random flags. */ if (getenv ("SC_TRACE_FLAGS")) trace_flags = strtoul (getenv ("SC_TRACE_FLAGS"), NULL, 0); else trace_flags = 0; enable_debugger = (getenv ("SC_DEBUGGER_ENABLED") != NULL); stable_random = (getenv ("SC_STABLE_RANDOM_ENABLED") != NULL); /* Use the generic startup code to complete startup. */ return gsc_startup_code (game_stream, NULL, trace_flags, enable_debugger, stable_random); } #endif /* _WIN32 */