/* 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 <assert.h>
#include <stddef.h>
#include <string.h>
#include <ctype.h>

#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, &notify);
  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, &notify);
  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, &notify);
  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);
}
