/* 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 ... */ #include #include #include #include #include "scare.h" #include "scprotos.h" #include "scgamest.h" /* Assorted definitions and constants. */ static const sc_char NEWLINE = '\n'; static const sc_char CARRIAGE_RETURN = '\r'; static const sc_char NUL = '\0'; /* Global tracing flags. */ static sc_uint if_trace_flags = 0; /* * if_initialize() * * First-time runtime checks for the overall interpreter. This function * tries to ensure correct compile options. */ static void if_initialize (void) { static sc_bool initialized = FALSE; /* Only do checks on the first call. */ if (!initialized) { /* Make a few quick checks on types and type sizes. */ if (sizeof (sc_byte) != 1 || sizeof (sc_char) != 1 || sizeof (sc_bool) != 1) { sc_error ("if_initialize: sizeof sc_byte, sc_char, or sc_bool" " is not 1, check compile options\n"); } else if (sizeof (sc_uint) < 4 || sizeof (sc_int) < 4) { sc_error ("if_initialize: sizeof sc_uint or sc_int" " is not at least 4, check compile options\n"); } else if (sizeof (sc_uint) > 8 || sizeof (sc_int) > 8) { sc_error ("if_initialize: sizeof sc_uint or sc_int" " is more than 8, check compile options\n"); } else if (!((sc_uint) -1 > 0)) { sc_error ("if_initialize: sc_uint appears not to be unsigned," " check compile options\n"); } initialized = TRUE; } } /* * if_bool() * sc_set_trace_flags() * if_get_trace_flag() * * Set and retrieve tracing flags. Setting new values propagates the new * tracing setting to all modules that support it. */ static sc_bool if_bool (sc_uint flag) { return flag ? TRUE : FALSE; } void sc_set_trace_flags (sc_uint trace_flags) { if_initialize (); /* Save the value for queries. */ if_trace_flags = trace_flags; /* Propagate tracing to modules that support it. */ parse_debug_trace (if_bool (trace_flags & SC_TR_TRACE_PARSE)); prop_debug_trace (if_bool (trace_flags & SC_TR_TRACE_PROPERTIES)); var_debug_trace (if_bool (trace_flags & SC_TR_TRACE_VARIABLES)); uip_debug_trace (if_bool (trace_flags & SC_TR_TRACE_PARSER)); lib_debug_trace (if_bool (trace_flags & SC_TR_TRACE_LIBRARY)); evt_debug_trace (if_bool (trace_flags & SC_TR_TRACE_EVENTS)); npc_debug_trace (if_bool (trace_flags & SC_TR_TRACE_NPCS)); obj_debug_trace (if_bool (trace_flags & SC_TR_TRACE_OBJECTS)); task_debug_trace (if_bool (trace_flags & SC_TR_TRACE_TASKS)); restr_debug_trace (if_bool (trace_flags & SC_TR_TRACE_TASKS)); pf_debug_trace (if_bool (trace_flags & SC_TR_TRACE_PRINTFILTER)); } sc_bool if_get_trace_flag (sc_uint bitmask) { return if_bool (if_trace_flags & bitmask); } /* * if_print_string_common() * if_print_string() * if_print_debug() * if_print_character_common() * if_print_character() * if_print_debug_character() * if_print_tag() * * Call OS-specific print function for the given arguments. */ static void if_print_string_common (const sc_char *string, void (*print_string_function) (const sc_char *)) { assert (string); if (string[0] != NUL) print_string_function (string); } void if_print_string (const sc_char *string) { if_print_string_common (string, os_print_string); } void if_print_debug (const sc_char *string) { if_print_string_common (string, os_print_string_debug); } static void if_print_character_common (sc_char character, void (*print_string_function) (const sc_char *)) { if (character != NUL) { sc_char buffer[2]; buffer[0] = character; buffer[1] = NUL; print_string_function (buffer); } } void if_print_character (sc_char character) { if_print_character_common (character, os_print_string); } void if_print_debug_character (sc_char character) { if_print_character_common (character, os_print_string_debug); } void if_print_tag (sc_int tag, const sc_char *arg) { assert (arg); os_print_tag (tag, arg); } /* * if_read_line_common() * if_read_line() * if_read_debug() * * Call OS-specific line read function. Clean up any read data a little * before returning it to the caller. */ static void if_read_line_common (sc_char *buffer, sc_int length, sc_bool (*read_line_function) (sc_char *, sc_int)) { sc_bool is_line_available; sc_int last; assert (buffer && length > 0); /* Loop until valid player input is available. */ do { /* Space first with a blank line, and clear the buffer. */ if_print_character (NEWLINE); memset (buffer, NUL, length); is_line_available = read_line_function (buffer, length); } while (!is_line_available); /* Drop any trailing newline/return. */ last = strlen (buffer) - 1; while (last >= 0 && (buffer[last] == CARRIAGE_RETURN || buffer[last] == NEWLINE)) buffer[last--] = NUL; } void if_read_line (sc_char *buffer, sc_int length) { if_read_line_common (buffer, length, os_read_line); } void if_read_debug (sc_char *buffer, sc_int length) { if_read_line_common (buffer, length, os_read_line_debug); } /* * if_confirm() * * Call OS-specific confirm function. */ sc_bool if_confirm (sc_int type) { return os_confirm (type); } /* * if_open_saved_game() * if_write_saved_game() * if_read_saved_game() * if_close_saved_game() * * Call OS-specific functions for saving and restoring games. */ void * if_open_saved_game (sc_bool is_save) { return os_open_file (is_save); } void if_write_saved_game (void *opaque, const sc_byte *buffer, sc_int length) { assert (buffer); os_write_file (opaque, buffer, length); } sc_int if_read_saved_game (void *opaque, sc_byte *buffer, sc_int length) { assert (buffer); return os_read_file (opaque, buffer, length); } void if_close_saved_game (void *opaque) { os_close_file (opaque); } /* * if_display_hints() * * Call OS-specific hint display function. */ void if_display_hints (sc_gameref_t game) { assert (gs_is_game_valid (game)); os_display_hints ((sc_game) game); } /* * if_update_sound() * if_update_graphic() * * Call OS-specific sound and graphic handler functions. */ void if_update_sound (const sc_char *filename, sc_int sound_offset, sc_int sound_length, sc_bool is_looping) { if (strlen (filename) > 0) os_play_sound (filename, sound_offset, sound_length, is_looping); else os_stop_sound (); } void if_update_graphic (const sc_char *filename, sc_int graphic_offset, sc_int graphic_length) { os_show_graphic (filename, graphic_offset, graphic_length); } /* * sc_scare_version() * sc_scare_emulation() * * Return a version string and Adrift emulation level. */ const sc_char * sc_scare_version (void) { if_initialize (); return "SCARE " SCARE_VERSION SCARE_PATCH_LEVEL; } sc_int sc_scare_emulation (void) { if_initialize (); return SCARE_EMULATION; } /* * if_file_read_callback() * if_file_write_callback() * * Standard FILE* reader and writer callback for constructing callback-style * calls from filename and stream variants. */ static sc_int if_file_read_callback (void *opaque, sc_byte *buffer, sc_int length) { FILE *stream = (FILE *) opaque; sc_int bytes; bytes = fread (buffer, 1, length, stream); if (ferror (stream)) sc_error ("if_file_read_callback: warning: read error\n"); return bytes; } static void if_file_write_callback (void *opaque, const sc_byte *buffer, sc_int length) { FILE *stream = (FILE *) opaque; fwrite (buffer, 1, length, stream); if (ferror (stream)) sc_error ("if_file_write_callback: warning: write error\n"); } /* * sc_game_from_filename() * sc_game_from_stream() * sc_game_from_callback() * * Called by the OS-specific layer to create a run context. The _filename() * and _stream() variants are adapters for run_create(). */ sc_game sc_game_from_filename (const sc_char *filename) { FILE *stream; sc_game game; if_initialize (); if (!filename) { sc_error ("sc_game_from_filename: NULL filename\n"); return NULL; } stream = fopen (filename, "rb"); if (!stream) { sc_error ("sc_game_from_filename: fopen error\n"); return NULL; } game = run_create (if_file_read_callback, stream); fclose (stream); return game; } sc_game sc_game_from_stream (FILE * stream) { if_initialize (); if (!stream) { sc_error ("sc_game_from_stream: NULL stream\n"); return NULL; } return run_create (if_file_read_callback, stream); } sc_game sc_game_from_callback (sc_int (*callback) (void *, sc_byte *, sc_int), void *opaque) { if_initialize (); if (!callback) { sc_error ("sc_game_from_callback: NULL callback\n"); return NULL; } return run_create (callback, opaque); } /* * if_game_error() * * Common function to verify that the game passed in to functions below * is a valid game. Returns TRUE on game error, FALSE if okay. */ static sc_bool if_game_error (sc_gameref_t game, const sc_char *function_name) { /* Check for invalid game -- null pointer or bad magic. */ if (!gs_is_game_valid (game)) { if (game) sc_error ("%s: invalid game\n", function_name); else sc_error ("%s: NULL game\n", function_name); return TRUE; } /* No game error. */ return FALSE; } /* * sc_interpret_game() * sc_restart_game() * sc_save_game() * sc_load_game() * sc_undo_game_turn() * sc_quit_game() * * Called by the OS-specific layer to run a game loaded into a run context, * and to quit the interpreter on demand, if required. sc_quit_game() * is implemented as a longjmp(), so never returns to the caller -- * instead, the program behaves as if sc_interpret_game() had returned. * sc_load_game() will longjmp() if the restore is successful (thus * behaving like sc_restart_game()), but will return if the game could not * be restored. sc_undo_game_turn() behaves like sc_load_game(). */ void sc_interpret_game (sc_game game) { const sc_gameref_t game_ = game; if (if_game_error (game_, "sc_interpret_game")) return; run_interpret (game_); } void sc_restart_game (sc_game game) { const sc_gameref_t game_ = game; if (if_game_error (game_, "sc_restart_game")) return; run_restart (game_); } sc_bool sc_save_game (sc_game game) { const sc_gameref_t game_ = game; if (if_game_error (game_, "sc_save_game")) return FALSE; return run_save_prompted (game_); } sc_bool sc_load_game (sc_game game) { const sc_gameref_t game_ = game; if (if_game_error (game_, "sc_load_game")) return FALSE; return run_restore_prompted (game_); } sc_bool sc_undo_game_turn (sc_game game) { const sc_gameref_t game_ = game; if (if_game_error (game_, "sc_undo_game_turn")) return FALSE; return run_undo (game_); } void sc_quit_game (sc_game game) { const sc_gameref_t game_ = game; if (if_game_error (game_, "sc_quit_game")) return; run_quit (game_); } /* * sc_save_game_to_filename() * sc_save_game_to_stream() * sc_save_game_to_callback() * sc_load_game_from_filename() * sc_load_game_from_stream() * sc_load_game_from_callback() * * Low level game saving and loading functions. The normal sc_save_game() * and sc_load_game() functions act exactly as the "save" and "restore" * game commands, in that they prompt the user for a stream to write or read. * These alternative forms allow the caller to directly specify the data * streams. */ sc_bool sc_save_game_to_filename (sc_game game, const sc_char *filename) { const sc_gameref_t game_ = game; FILE *stream; if (if_game_error (game_, "sc_save_game_to_filename")) return FALSE; if (!filename) { sc_error ("sc_save_game_to_filename: NULL filename\n"); return FALSE; } stream = fopen (filename, "wb"); if (!stream) { sc_error ("sc_save_game_to_filename: fopen error\n"); return FALSE; } run_save (game_, if_file_write_callback, stream); fclose (stream); return TRUE; } void sc_save_game_to_stream (sc_game game, FILE *stream) { const sc_gameref_t game_ = game; if (if_game_error (game_, "sc_save_game_to_stream")) return; if (!stream) { sc_error ("sc_save_game_to_stream: NULL stream\n"); return; } run_save (game_, if_file_write_callback, stream); } void sc_save_game_to_callback (sc_game game, void (*callback) (void *, const sc_byte *, sc_int), void *opaque) { const sc_gameref_t game_ = game; if (if_game_error (game_, "sc_save_game_to_callback")) return; if (!callback) { sc_error ("sc_save_game_to_callback: NULL callback\n"); return; } run_save (game_, callback, opaque); } sc_bool sc_load_game_from_filename (sc_game game, const sc_char *filename) { const sc_gameref_t game_ = game; FILE *stream; sc_bool status; if (if_game_error (game_, "sc_load_game_from_filename")) return FALSE; if (!filename) { sc_error ("sc_load_game_from_filename: NULL filename\n"); return FALSE; } stream = fopen (filename, "rb"); if (!stream) { sc_error ("sc_load_game_from_filename: fopen error\n"); return FALSE; } status = run_restore (game_, if_file_read_callback, stream); fclose (stream); return status; } sc_bool sc_load_game_from_stream (sc_game game, FILE *stream) { const sc_gameref_t game_ = game; if (if_game_error (game_, "sc_load_game_from_stream")) return FALSE; if (!stream) { sc_error ("sc_load_game_from_stream: NULL stream\n"); return FALSE; } return run_restore (game_, if_file_read_callback, stream); } sc_bool sc_load_game_from_callback (sc_game game, sc_int (*callback) (void *, sc_byte *, sc_int), void *opaque) { const sc_gameref_t game_ = game; if (if_game_error (game_, "sc_load_game_from_callback")) return FALSE; if (!callback) { sc_error ("sc_load_game_from_callback: NULL callback\n"); return FALSE; } return run_restore (game_, callback, opaque); } /* * sc_free_game() * * Called by the OS-specific layer to free run context memory. */ void sc_free_game (sc_game game) { const sc_gameref_t game_ = game; if (if_game_error (game_, "sc_free_game")) return; run_destroy (game_); } /* * sc_is_game_running() * sc_get_game_name() * sc_get_game_author() * sc_get_game_turns() * sc_get_game_score() * sc_get_game_max_score() * sc_get_game_room () * sc_get_game_status_line () * sc_get_game_preferred_font () * sc_get_game_bold_room_names() * sc_get_game_verbose() * sc_get_game_notify_score_change() * sc_has_game_completed() * sc_is_game_undo_available() * * Return a few attributes of a game. */ sc_bool sc_is_game_running (sc_game game) { const sc_gameref_t game_ = game; if (if_game_error (game_, "sc_is_game_running")) return FALSE; return run_is_running (game_); } const sc_char * sc_get_game_name (sc_game game) { const sc_gameref_t game_ = game; const sc_char *retval; if (if_game_error (game_, "sc_get_game_name")) return "[invalid game]"; run_get_attributes (game_, &retval, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); return retval; } const sc_char * sc_get_game_author (sc_game game) { const sc_gameref_t game_ = game; const sc_char *retval; if (if_game_error (game_, "sc_get_game_author")) return "[invalid game]"; run_get_attributes (game_, NULL, &retval, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); return retval; } sc_int sc_get_game_turns (sc_game game) { const sc_gameref_t game_ = game; sc_int retval; if (if_game_error (game_, "sc_get_game_turns")) return 0; run_get_attributes (game_, NULL, NULL, &retval, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); return retval; } sc_int sc_get_game_score (sc_game game) { const sc_gameref_t game_ = game; sc_int retval; if (if_game_error (game_, "sc_get_game_score")) return 0; run_get_attributes (game_, NULL, NULL, NULL, &retval, NULL, NULL, NULL, NULL, NULL, NULL, NULL); return retval; } sc_int sc_get_game_max_score (sc_game game) { const sc_gameref_t game_ = game; sc_int retval; if (if_game_error (game_, "sc_get_game_max_score")) return 0; run_get_attributes (game_, NULL, NULL, NULL, NULL, &retval, NULL, NULL, NULL, NULL, NULL, NULL); return retval; } const sc_char * sc_get_game_room (sc_game game) { const sc_gameref_t game_ = game; const sc_char *retval; if (if_game_error (game_, "sc_get_game_room")) return "[invalid game]"; run_get_attributes (game_, NULL, NULL, NULL, NULL, NULL, &retval, NULL, NULL, NULL, NULL, NULL); return retval; } const sc_char * sc_get_game_status_line (sc_game game) { const sc_gameref_t game_ = game; const sc_char *retval; if (if_game_error (game_, "sc_get_game_status_line")) return "[invalid game]"; run_get_attributes (game_, NULL, NULL, NULL, NULL, NULL, NULL, &retval, NULL, NULL, NULL, NULL); return retval; } const sc_char * sc_get_game_preferred_font (sc_game game) { const sc_gameref_t game_ = game; const sc_char *retval; if (if_game_error (game_, "sc_get_game_preferred_font")) return "[invalid game]"; run_get_attributes (game_, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &retval, NULL, NULL, NULL); return retval; } sc_bool sc_get_game_bold_room_names (sc_game game) { const sc_gameref_t game_ = game; sc_bool retval; if (if_game_error (game_, "sc_get_game_bold_room_names")) return FALSE; run_get_attributes (game_, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &retval, NULL, NULL); return retval; } sc_bool sc_get_game_verbose (sc_game game) { const sc_gameref_t game_ = game; sc_bool retval; if (if_game_error (game_, "sc_get_game_verbose")) return FALSE; run_get_attributes (game_, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &retval, NULL); return retval; } sc_bool sc_get_game_notify_score_change (sc_game game) { const sc_gameref_t game_ = game; sc_bool retval; if (if_game_error (game_, "sc_get_game_notify_score_change")) return FALSE; run_get_attributes (game_, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &retval); return retval; } sc_bool sc_has_game_completed (sc_game game) { const sc_gameref_t game_ = game; if (if_game_error (game_, "sc_has_game_completed")) return FALSE; return run_has_completed (game_); } sc_bool sc_is_game_undo_available (sc_game game) { const sc_gameref_t game_ = game; if (if_game_error (game_, "sc_is_game_undo_available")) return FALSE; return run_is_undo_available (game_); } /* * sc_set_game_bold_room_names() * sc_set_game_verbose() * sc_set_game_notify_score_change() * * Set a few attributes of a game. */ void sc_set_game_bold_room_names (sc_game game, sc_bool flag) { const sc_gameref_t game_ = game; sc_bool bold, verbose, notify; if (if_game_error (game_, "sc_set_game_bold_room_names")) return; run_get_attributes (game_, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &bold, &verbose, ¬ify); run_set_attributes (game_, flag, verbose, notify); } void sc_set_game_verbose (sc_game game, sc_bool flag) { const sc_gameref_t game_ = game; sc_bool bold, verbose, notify; if (if_game_error (game_, "sc_set_game_verbose")) return; run_get_attributes (game_, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &bold, &verbose, ¬ify); run_set_attributes (game_, bold, flag, notify); } void sc_set_game_notify_score_change (sc_game game, sc_bool flag) { const sc_gameref_t game_ = game; sc_bool bold, verbose, notify; if (if_game_error (game_, "sc_set_game_notify_score_change")) return; run_get_attributes (game_, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &bold, &verbose, ¬ify); run_set_attributes (game_, bold, verbose, flag); } /* * sc_does_game_use_sounds() * sc_does_game_use_graphics() * * Indicate the game's use of resources. */ sc_bool sc_does_game_use_sounds (sc_game game) { const sc_gameref_t game_ = game; if (if_game_error (game_, "sc_does_game_use_sounds")) return FALSE; return res_has_sound (game_); } sc_bool sc_does_game_use_graphics (sc_game game) { const sc_gameref_t game_ = game; if (if_game_error (game_, "sc_does_game_use_graphics")) return FALSE; return res_has_graphics (game_); } /* * sc_iterate_game_hints() * sc_get_game_hint_question() * sc_get_game_subtle_hint() * sc_get_game_sledgehammer_hint() * * Iterate currently available hints, and return strings for a hint. */ sc_game_hint sc_iterate_game_hints (sc_game game, sc_game_hint hint) { const sc_gameref_t game_ = game; if (if_game_error (game_, "sc_iterate_game_hints")) return NULL; return run_hint_iterate (game_, hint); } const sc_char * sc_get_game_hint_question (sc_game game, sc_game_hint hint) { const sc_gameref_t game_ = game; const sc_hintref_t hintref = hint; if (if_game_error (game_, "sc_get_game_hint_question")) return NULL; if (!hintref) { sc_error ("sc_get_game_hint_question: NULL hint\n"); return NULL; } return run_get_hint_question (game_, hintref); } const sc_char * sc_get_game_subtle_hint (sc_game game, sc_game_hint hint) { const sc_gameref_t game_ = game; const sc_hintref_t hintref = hint; if (if_game_error (game_, "sc_get_game_subtle_hint")) return NULL; if (!hintref) { sc_error ("sc_get_game_subtle_hint: NULL hint\n"); return NULL; } return run_get_subtle_hint (game_, hintref); } const sc_char * sc_get_game_unsubtle_hint (sc_game game, sc_game_hint hint) { const sc_gameref_t game_ = game; const sc_hintref_t hintref = hint; if (if_game_error (game_, "sc_get_game_unsubtle_hint")) return NULL; if (!hintref) { sc_error ("sc_get_game_unsubtle_hint: NULL hint\n"); return NULL; } return run_get_unsubtle_hint (game_, hintref); } /* * sc_set_game_debugger_enabled() * sc_is_game_debugger_enabled() * sc_run_game_debugger_command() * * Enable, disable, and query game debugging, and run a single debug command. */ void sc_set_game_debugger_enabled (sc_game game, sc_bool flag) { const sc_gameref_t game_ = game; if (if_game_error (game_, "sc_set_game_debugger_enabled")) return; debug_set_enabled (game_, flag); } sc_bool sc_get_game_debugger_enabled (sc_game game) { const sc_gameref_t game_ = game; if (if_game_error (game_, "sc_get_game_debugger_enabled")) return FALSE; return debug_get_enabled (game_); } sc_bool sc_run_game_debugger_command (sc_game game, const sc_char *debug_command) { const sc_gameref_t game_ = game; if (if_game_error (game_, "sc_run_game_debugger_command")) return FALSE; return debug_run_command (game_, debug_command); } /* * sc_set_portable_random() * sc_reseed_random_sequence() * * Turn portable random number generation on and off, and supply a new seed * for random number generators. */ void sc_set_portable_random (sc_bool flag) { if (flag) sc_set_congruential_random (); else sc_set_platform_random (); } void sc_reseed_random_sequence (sc_uint new_seed) { if (new_seed == 0) { sc_error ("sc_reseed_random_sequence: new_seed may not be 0\n"); return; } sc_seed_random (new_seed); }