/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
 * This is GNU Go, a Go program. Contact gnugo@gnu.org, or see       *
 * http://www.gnu.org/software/gnugo/ for more information.          *
 *                                                                   *
 * Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,   *
 * 2008, 2009 and 2010 by the Free Software Foundation.              *
 *                                                                   *
 * This program is free software; you can redistribute it and/or     *
 * modify it under the terms of the GNU General Public License as    *
 * published by the Free Software Foundation - version 3 or          *
 * (at your option) any later version.                               *
 *                                                                   *
 * 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 in file COPYING 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., 51 Franklin Street, Fifth Floor,       *
 * Boston, MA 02111, USA.                                            *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "gnugo.h"

#include <stdio.h>
#include <assert.h>
#include <ctype.h>
#include <string.h>
#include <math.h>

#include "interface.h"
#include "liberty.h"
#include "gtp.h"
#include "gg_utils.h"

/* Internal state that's not part of the engine. */
static int report_uncertainty = 0;
static int gtp_orientation = 0;

static void gtp_print_code(int c);
static void gtp_print_vertices2(int n, int *moves);
static void rotate_on_input(int ai, int aj, int *bi, int *bj);
static void rotate_on_output(int ai, int aj, int *bi, int *bj);


#define DECLARE(func) static int func(char *s)

DECLARE(gtp_aa_confirm_safety);
DECLARE(gtp_accurate_approxlib);
DECLARE(gtp_accuratelib);
DECLARE(gtp_advance_random_seed);
DECLARE(gtp_all_legal);
DECLARE(gtp_all_move_values);
DECLARE(gtp_analyze_eyegraph);
DECLARE(gtp_analyze_semeai);
DECLARE(gtp_analyze_semeai_after_move);
DECLARE(gtp_attack);
DECLARE(gtp_attack_either);
DECLARE(gtp_block_off);
DECLARE(gtp_break_in);
DECLARE(gtp_captures);
DECLARE(gtp_clear_board);
DECLARE(gtp_clear_cache);
DECLARE(gtp_combination_attack);
DECLARE(gtp_combination_defend);
DECLARE(gtp_connect);
DECLARE(gtp_countlib);
DECLARE(gtp_cputime);
DECLARE(gtp_decrease_depths);
DECLARE(gtp_defend);
DECLARE(gtp_defend_both);
DECLARE(gtp_disconnect);
DECLARE(gtp_does_attack);
DECLARE(gtp_does_defend);
DECLARE(gtp_does_surround);
DECLARE(gtp_dragon_data);
DECLARE(gtp_dragon_status);
DECLARE(gtp_dragon_stones);
DECLARE(gtp_draw_search_area);
DECLARE(gtp_dump_stack);
DECLARE(gtp_echo);
DECLARE(gtp_echo_err);
DECLARE(gtp_estimate_score);
DECLARE(gtp_eval_eye);
DECLARE(gtp_experimental_score);
DECLARE(gtp_eye_data);
DECLARE(gtp_final_score);
DECLARE(gtp_final_status);
DECLARE(gtp_final_status_list);
DECLARE(gtp_findlib);
DECLARE(gtp_finish_sgftrace);
DECLARE(gtp_fixed_handicap);
DECLARE(gtp_followup_influence);
DECLARE(gtp_genmove);
DECLARE(gtp_genmove_black);
DECLARE(gtp_genmove_white);
DECLARE(gtp_get_connection_node_counter);
DECLARE(gtp_get_handicap);
DECLARE(gtp_get_komi);
DECLARE(gtp_get_life_node_counter);
DECLARE(gtp_get_owl_node_counter);
DECLARE(gtp_get_random_seed);
DECLARE(gtp_get_reading_node_counter);
DECLARE(gtp_get_trymove_counter);
DECLARE(gtp_gg_genmove);
DECLARE(gtp_gg_undo);
DECLARE(gtp_half_eye_data);
DECLARE(gtp_increase_depths);
DECLARE(gtp_initial_influence);
DECLARE(gtp_invariant_hash);
DECLARE(gtp_invariant_hash_for_moves);
DECLARE(gtp_is_legal);
DECLARE(gtp_is_surrounded);
DECLARE(gtp_kgs_genmove_cleanup);
DECLARE(gtp_known_command);
DECLARE(gtp_ladder_attack);
DECLARE(gtp_last_move);
DECLARE(gtp_limit_search);
DECLARE(gtp_list_commands);
DECLARE(gtp_list_stones);
DECLARE(gtp_loadsgf);
DECLARE(gtp_move_influence);
DECLARE(gtp_move_probabilities);
DECLARE(gtp_move_reasons);
DECLARE(gtp_move_uncertainty);
DECLARE(gtp_move_history);
DECLARE(gtp_name);
DECLARE(gtp_owl_attack);
DECLARE(gtp_owl_connection_defends);
DECLARE(gtp_owl_defend);
DECLARE(gtp_owl_does_attack);
DECLARE(gtp_owl_does_defend);
DECLARE(gtp_owl_substantial);
DECLARE(gtp_owl_threaten_attack);
DECLARE(gtp_owl_threaten_defense);
DECLARE(gtp_place_free_handicap);
DECLARE(gtp_play);
DECLARE(gtp_playblack);
DECLARE(gtp_playwhite);
DECLARE(gtp_popgo);
DECLARE(gtp_printsgf);
DECLARE(gtp_program_version);
DECLARE(gtp_protocol_version);
DECLARE(gtp_query_boardsize);
DECLARE(gtp_query_orientation);
DECLARE(gtp_quit);
DECLARE(gtp_reg_genmove);
DECLARE(gtp_report_uncertainty);
DECLARE(gtp_reset_connection_node_counter);
DECLARE(gtp_reset_life_node_counter);
DECLARE(gtp_reset_owl_node_counter);
DECLARE(gtp_reset_reading_node_counter);
DECLARE(gtp_reset_search_mask);
DECLARE(gtp_reset_trymove_counter);
DECLARE(gtp_restricted_genmove);
DECLARE(gtp_same_dragon);
DECLARE(gtp_set_boardsize);
DECLARE(gtp_set_free_handicap);
DECLARE(gtp_set_komi);
DECLARE(gtp_set_level);
DECLARE(gtp_set_orientation);
DECLARE(gtp_set_random_seed);
DECLARE(gtp_set_search_diamond);
DECLARE(gtp_set_search_limit);
DECLARE(gtp_showboard);
DECLARE(gtp_start_sgftrace);
DECLARE(gtp_surround_map);
DECLARE(gtp_tactical_analyze_semeai);
DECLARE(gtp_test_eyeshape);
DECLARE(gtp_time_left);
DECLARE(gtp_time_settings);
DECLARE(gtp_top_moves);
DECLARE(gtp_top_moves_black);
DECLARE(gtp_top_moves_white);
DECLARE(gtp_tryko);
DECLARE(gtp_trymove);
DECLARE(gtp_tune_move_ordering);
DECLARE(gtp_unconditional_status);
DECLARE(gtp_undo);
DECLARE(gtp_what_color);
DECLARE(gtp_worm_cutstone);
DECLARE(gtp_worm_data);
DECLARE(gtp_worm_stones);

/* List of known commands. */
static struct gtp_command commands[] = {
  {"aa_confirm_safety",       gtp_aa_confirm_safety},
  {"accurate_approxlib",      gtp_accurate_approxlib},
  {"accuratelib",             gtp_accuratelib},
  {"advance_random_seed",     gtp_advance_random_seed},
  {"all_legal",        	      gtp_all_legal},
  {"all_move_values",         gtp_all_move_values},
  {"analyze_eyegraph", 	      gtp_analyze_eyegraph},
  {"analyze_semeai",          gtp_analyze_semeai},
  {"analyze_semeai_after_move", gtp_analyze_semeai_after_move},
  {"attack",           	      gtp_attack},
  {"attack_either",           gtp_attack_either},
  {"black",            	      gtp_playblack},
  {"block_off",		      gtp_block_off},
  {"boardsize",        	      gtp_set_boardsize},
  {"break_in",		      gtp_break_in},
  {"captures",        	      gtp_captures},
  {"clear_board",      	      gtp_clear_board},
  {"clear_cache",	      gtp_clear_cache},
  {"color",            	      gtp_what_color},
  {"combination_attack",      gtp_combination_attack},
  {"combination_defend",      gtp_combination_defend},
  {"connect",         	      gtp_connect},
  {"countlib",         	      gtp_countlib},
  {"cputime",		      gtp_cputime},
  {"decrease_depths",  	      gtp_decrease_depths},
  {"defend",           	      gtp_defend},
  {"defend_both",	      gtp_defend_both},
  {"disconnect",       	      gtp_disconnect},
  {"does_attack",             gtp_does_attack},
  {"does_defend",             gtp_does_defend},
  {"does_surround",           gtp_does_surround},
  {"dragon_data",             gtp_dragon_data},
  {"dragon_status",    	      gtp_dragon_status},
  {"dragon_stones",           gtp_dragon_stones},
  {"draw_search_area",        gtp_draw_search_area},
  {"dump_stack",       	      gtp_dump_stack},
  {"echo" ,                   gtp_echo},
  {"echo_err" ,               gtp_echo_err},
  {"estimate_score",          gtp_estimate_score},
  {"eval_eye",         	      gtp_eval_eye},
  {"experimental_score",      gtp_experimental_score},
  {"eye_data",                gtp_eye_data},
  {"final_score",             gtp_final_score},
  {"final_status",            gtp_final_status},
  {"final_status_list",       gtp_final_status_list},
  {"findlib",          	      gtp_findlib},
  {"finish_sgftrace",  	      gtp_finish_sgftrace},
  {"fixed_handicap",   	      gtp_fixed_handicap},
  {"followup_influence",      gtp_followup_influence},
  {"genmove",                 gtp_genmove},
  {"genmove_black",           gtp_genmove_black},
  {"genmove_white",           gtp_genmove_white},
  {"get_connection_node_counter", gtp_get_connection_node_counter},
  {"get_handicap",   	      gtp_get_handicap},
  {"get_komi",        	      gtp_get_komi},
  {"get_life_node_counter",   gtp_get_life_node_counter},
  {"get_owl_node_counter",    gtp_get_owl_node_counter},
  {"get_random_seed",  	      gtp_get_random_seed},
  {"get_reading_node_counter", gtp_get_reading_node_counter},
  {"get_trymove_counter",     gtp_get_trymove_counter},
  {"gg-undo",                 gtp_gg_undo},
  {"gg_genmove",              gtp_gg_genmove},
  {"half_eye_data",           gtp_half_eye_data},
  {"help",                    gtp_list_commands},
  {"increase_depths",  	      gtp_increase_depths},
  {"initial_influence",       gtp_initial_influence},
  {"invariant_hash_for_moves",gtp_invariant_hash_for_moves},
  {"invariant_hash",   	      gtp_invariant_hash},
  {"is_legal",         	      gtp_is_legal},
  {"is_surrounded",           gtp_is_surrounded},
  {"kgs-genmove_cleanup",     gtp_kgs_genmove_cleanup},
  {"known_command",    	      gtp_known_command},
  {"komi",        	      gtp_set_komi},
  {"ladder_attack",    	      gtp_ladder_attack},
  {"last_move",    	      gtp_last_move},
  {"level",        	      gtp_set_level},
  {"limit_search",     	      gtp_limit_search},
  {"list_commands",    	      gtp_list_commands},
  {"list_stones",    	      gtp_list_stones},
  {"loadsgf",          	      gtp_loadsgf},
  {"move_influence",          gtp_move_influence},
  {"move_probabilities",      gtp_move_probabilities},
  {"move_reasons",            gtp_move_reasons},
  {"move_uncertainty",	      gtp_move_uncertainty},
  {"move_history",	      gtp_move_history},
  {"name",                    gtp_name},
  {"new_score",               gtp_estimate_score},
  {"orientation",     	      gtp_set_orientation},
  {"owl_attack",     	      gtp_owl_attack},
  {"owl_connection_defends",  gtp_owl_connection_defends},
  {"owl_defend",     	      gtp_owl_defend},
  {"owl_does_attack", 	      gtp_owl_does_attack},
  {"owl_does_defend", 	      gtp_owl_does_defend},
  {"owl_substantial", 	      gtp_owl_substantial},
  {"owl_threaten_attack",     gtp_owl_threaten_attack},
  {"owl_threaten_defense",    gtp_owl_threaten_defense},
  {"place_free_handicap",     gtp_place_free_handicap},
  {"play",            	      gtp_play},
  {"popgo",            	      gtp_popgo},
  {"printsgf",         	      gtp_printsgf},
  {"protocol_version",        gtp_protocol_version},
  {"query_boardsize",         gtp_query_boardsize},
  {"query_orientation",       gtp_query_orientation},
  {"quit",             	      gtp_quit},
  {"reg_genmove",             gtp_reg_genmove},
  {"report_uncertainty",      gtp_report_uncertainty},
  {"reset_connection_node_counter", gtp_reset_connection_node_counter},
  {"reset_life_node_counter", gtp_reset_life_node_counter},
  {"reset_owl_node_counter",  gtp_reset_owl_node_counter},
  {"reset_reading_node_counter", gtp_reset_reading_node_counter},
  {"reset_search_mask",       gtp_reset_search_mask},
  {"reset_trymove_counter",   gtp_reset_trymove_counter},
  {"restricted_genmove",      gtp_restricted_genmove},
  {"same_dragon",    	      gtp_same_dragon},
  {"set_free_handicap",       gtp_set_free_handicap},
  {"set_random_seed",  	      gtp_set_random_seed},
  {"set_search_diamond",      gtp_set_search_diamond},
  {"set_search_limit",        gtp_set_search_limit},
  {"showboard",        	      gtp_showboard},
  {"start_sgftrace",  	      gtp_start_sgftrace},
  {"surround_map",            gtp_surround_map},
  {"tactical_analyze_semeai", gtp_tactical_analyze_semeai},
  {"test_eyeshape",           gtp_test_eyeshape},
  {"time_left",               gtp_time_left},
  {"time_settings",           gtp_time_settings},
  {"top_moves",               gtp_top_moves},
  {"top_moves_black",         gtp_top_moves_black},
  {"top_moves_white",         gtp_top_moves_white},
  {"tryko",          	      gtp_tryko},
  {"trymove",          	      gtp_trymove},
  {"tune_move_ordering",      gtp_tune_move_ordering},
  {"unconditional_status",    gtp_unconditional_status},
  {"undo",                    gtp_undo},
  {"version",                 gtp_program_version},
  {"white",            	      gtp_playwhite},
  {"worm_cutstone",           gtp_worm_cutstone},
  {"worm_data",               gtp_worm_data},
  {"worm_stones",             gtp_worm_stones},
  {NULL,                      NULL}
};


/* Start playing using the Go Text Protocol. */
void
play_gtp(FILE *gtp_input, FILE *gtp_output, FILE *gtp_dump_commands,
	 int gtp_initial_orientation)
{
  /* Make sure `gtp_output' is unbuffered. (Line buffering is also
   * okay but not necessary. Block buffering breaks GTP mode.)
   *
   * FIXME: Maybe should go to `gtp.c'?
   */
  setbuf(gtp_output, NULL);

  /* Inform the GTP utility functions about the board size. */
  gtp_internal_set_boardsize(board_size);
  gtp_orientation = gtp_initial_orientation;
  gtp_set_vertex_transform_hooks(rotate_on_input, rotate_on_output);

  /* Initialize time handling. */
  init_timers();
  
  /* Prepare pattern matcher and reading code. */
  reset_engine();
  clearstats();
  gtp_main_loop(commands, gtp_input, gtp_output, gtp_dump_commands);
  if (showstatistics)
    showstats();
}


/****************************
 * Administrative commands. *
 ****************************/

/* Function:  Quit
 * Arguments: none
 * Fails:     never
 * Returns:   nothing
 *
 * Status:    GTP version 2 standard command.
 */
static int
gtp_quit(char *s)
{
  UNUSED(s);
  gtp_success("");
  return GTP_QUIT;
}


/* Function:  Report protocol version.
 * Arguments: none
 * Fails:     never
 * Returns:   protocol version number
 *
 * Status:    GTP version 2 standard command.
 */
static int
gtp_protocol_version(char *s)
{
  UNUSED(s);
  return gtp_success("%d", gtp_version);
}


/****************************
 * Program identity.        *
 ****************************/

/* Function:  Report the name of the program.
 * Arguments: none
 * Fails:     never
 * Returns:   program name
 *
 * Status:    GTP version 2 standard command.
 */
static int
gtp_name(char *s)
{
  UNUSED(s);
  return gtp_success("GNU Go");
}




/* Function:  Report the version number of the program.
 * Arguments: none
 * Fails:     never
 * Returns:   version number
 *
 * Status:    GTP version 2 standard command.
 */
static int
gtp_program_version(char *s)
{
  UNUSED(s);
  return gtp_success(VERSION);
}


/***************************
 * Setting the board size. *
 ***************************/

/* Function:  Set the board size to NxN and clear the board.
 * Arguments: integer
 * Fails:     board size outside engine's limits
 * Returns:   nothing
 *
 * Status:    GTP version 2 standard command.
 */
static int
gtp_set_boardsize(char *s)
{
  int boardsize;

  if (sscanf(s, "%d", &boardsize) < 1)
    return gtp_failure("boardsize not an integer");
  
  if (!check_boardsize(boardsize, NULL)) {
    if (gtp_version == 1)
      return gtp_failure("unacceptable boardsize");
    else
      return gtp_failure("unacceptable size");
  }

  /* If this is called with a non-empty board, we assume that a new
   * game will be started, for which we want a new random seed.
   */
  if (stones_on_board(BLACK | WHITE) > 0)
    update_random_seed();

  board_size = boardsize;
  clear_board();
  gtp_internal_set_boardsize(boardsize);
  reset_engine();
  return gtp_success("");
}

/* Function:  Find the current boardsize
 * Arguments: none
 * Fails:     never
 * Returns:   board_size
 */
static int
gtp_query_boardsize(char *s)
{
  UNUSED(s);

  return gtp_success("%d", board_size);
}

/***********************
 * Clearing the board. *
 ***********************/

/* Function:  Clear the board.
 * Arguments: none
 * Fails:     never
 * Returns:   nothing
 *
 * Status:    GTP version 2 standard command.
 */
static int
gtp_clear_board(char *s)
{
  UNUSED(s);

  /* If this is called with a non-empty board, we assume that a new
   * game will be started, for which we want a new random seed.
   */
  if (stones_on_board(BLACK | WHITE) > 0)
    update_random_seed();

  clear_board();
  init_timers();
  
  return gtp_success("");
}

/****************************
 * Setting the orientation. *
 ****************************/

/* Function:  Set the orienation to N and clear the board
 * Arguments: integer
 * Fails:     illegal orientation
 * Returns:   nothing
 */
static int
gtp_set_orientation(char *s)
{
  int orientation;
  if (sscanf(s, "%d", &orientation) < 1)
    return gtp_failure("orientation not an integer");
  
  if (orientation < 0 || orientation > 7)
    return gtp_failure("unacceptable orientation");

  clear_board();
  gtp_orientation = orientation;
  gtp_set_vertex_transform_hooks(rotate_on_input, rotate_on_output);
  return gtp_success("");
}

/* Function:  Find the current orientation
 * Arguments: none
 * Fails:     never
 * Returns:   orientation
 */
static int
gtp_query_orientation(char *s)
{
  UNUSED(s);

  return gtp_success("%d", gtp_orientation);
}

/***************************
 * Setting komi.           *
 ***************************/

/* Function:  Set the komi.
 * Arguments: float
 * Fails:     incorrect argument
 * Returns:   nothing
 *
 * Status:    GTP version 2 standard command.
 */
static int
gtp_set_komi(char *s)
{
  if (sscanf(s, "%f", &komi) < 1)
    return gtp_failure("komi not a float");
  
  return gtp_success("");
}


/***************************
 * Getting komi            *
 ***************************/

/* Function:  Get the komi
 * Arguments: none
 * Fails:     never
 * Returns:   Komi 
 */
static int
gtp_get_komi(char *s)
{
  UNUSED(s);
  return gtp_success("%4.1f", komi);
}


/******************
 * Playing moves. *
 ******************/

/* Function:  Play a black stone at the given vertex.
 * Arguments: vertex
 * Fails:     invalid vertex, illegal move
 * Returns:   nothing
 *
 * Status:    Obsolete GTP version 1 command.
 */
static int
gtp_playblack(char *s)
{
  int i, j;
  char *c;

  for (c = s; *c; c++)
    *c = tolower((int)*c);

  if (strncmp(s, "pass", 4) == 0) {
    i = -1;
    j = -1;
  }
  else if (!gtp_decode_coord(s, &i, &j))
    return gtp_failure("invalid coordinate");

  if (!is_allowed_move(POS(i, j), BLACK))
    return gtp_failure("illegal move");

  gnugo_play_move(POS(i, j), BLACK);
  return gtp_success("");
}


/* Function:  Play a white stone at the given vertex.
 * Arguments: vertex
 * Fails:     invalid vertex, illegal move
 * Returns:   nothing
 *
 * Status:    Obsolete GTP version 1 command.
 */
static int
gtp_playwhite(char *s)
{
  int i, j;
  char *c;

  for (c = s; *c; c++)
    *c = tolower((int)*c);

  if (strncmp(s, "pass", 4) == 0) {
    i = -1;
    j = -1;
  }
  else if (!gtp_decode_coord(s, &i, &j))
    return gtp_failure("invalid coordinate");
  
  if (!is_allowed_move(POS(i, j), WHITE))
    return gtp_failure("illegal move");

  gnugo_play_move(POS(i, j), WHITE);
  return gtp_success("");
}


/* Function:  Play a stone of the given color at the given vertex.
 * Arguments: color, vertex
 * Fails:     invalid vertex, illegal move
 * Returns:   nothing
 *
 * Status:    GTP version 2 standard command.
 */
static int
gtp_play(char *s)
{
  int i, j;
  int color;

  if (!gtp_decode_move(s, &color, &i, &j))
    return gtp_failure("invalid color or coordinate");

  if (!is_allowed_move(POS(i, j), color))
    return gtp_failure("illegal move");

  gnugo_play_move(POS(i, j), color);
  return gtp_success("");
}


/* Function:  Set up fixed placement handicap stones.
 * Arguments: number of handicap stones
 * Fails:     invalid number of stones for the current boardsize
 * Returns:   list of vertices with handicap stones
 *
 * Status:    GTP version 2 standard command.
 */
static int
gtp_fixed_handicap(char *s)
{
  int m, n;
  int first = 1;
  int this_handicap;

  if (gtp_version == 1)
    clear_board();
  else if (stones_on_board(BLACK | WHITE) > 0)
    return gtp_failure("board not empty");

  if (sscanf(s, "%d", &this_handicap) < 1)
    return gtp_failure("handicap not an integer");
  
  if (this_handicap < 2 && (gtp_version > 1 || this_handicap != 0))
    return gtp_failure("invalid handicap");

  if (place_fixed_handicap(this_handicap) != this_handicap) {
    clear_board();
    return gtp_failure("invalid handicap");
  }

  handicap = this_handicap;

  gtp_start_response(GTP_SUCCESS);

  for (m = 0; m < board_size; m++)
    for (n = 0; n < board_size; n++)
      if (BOARD(m, n) != EMPTY) {
	if (!first)
	  gtp_printf(" ");
	else
	  first = 0;
	gtp_mprintf("%m", m, n);
      }
  
  return gtp_finish_response();
}


/* Function:  Choose free placement handicap stones and put them on the board.
 * Arguments: number of handicap stones
 * Fails:     invalid number of stones
 * Returns:   list of vertices with handicap stones
 *
 * Status:    GTP version 2 standard command.
 */
static int
gtp_place_free_handicap(char *s)
{
  int m, n;
  int first = 1;
  int this_handicap;
  if (sscanf(s, "%d", &this_handicap) < 1)
    return gtp_failure("handicap not an integer");
  
  if (stones_on_board(BLACK | WHITE) > 0)
    return gtp_failure("board not empty");

  if (this_handicap < 2)
    return gtp_failure("invalid handicap");

  handicap = place_free_handicap(this_handicap);

  gtp_start_response(GTP_SUCCESS);

  for (m = 0; m < board_size; m++)
    for (n = 0; n < board_size; n++)
      if (BOARD(m, n) != EMPTY) {
	if (!first)
	  gtp_printf(" ");
	else
	  first = 0;
	gtp_mprintf("%m", m, n);
      }
  
  return gtp_finish_response();
}


/* Function:  Put free placement handicap stones on the board.
 * Arguments: list of vertices with handicap stones
 * Fails:     board not empty, bad list of vertices
 * Returns:   nothing
 *
 * Status:    GTP version 2 standard command.
 */
static int
gtp_set_free_handicap(char *s)
{
  int n;
  int i, j;
  int k;
  
  if (stones_on_board(BLACK | WHITE) > 0)
    return gtp_failure("board not empty");

  for (k = 0; k < MAX_BOARD * MAX_BOARD; k++) {
    n = gtp_decode_coord(s, &i, &j);
    if (n > 0) {
      if (board[POS(i, j)] != EMPTY) {
	clear_board();
	return gtp_failure("repeated vertex");
      }
      add_stone(POS(i, j), BLACK);
      s += n;
    }
    else if (sscanf(s, "%*s") != EOF)
      return gtp_failure("invalid coordinate");
    else
      break;
  }

  if (k < 2) {
    clear_board();
    return gtp_failure("invalid handicap");
  }

  handicap = k;
  
  return gtp_success("");
}


/* Function:  Get the handicap
 * Arguments: none
 * Fails:     never
 * Returns:   handicap
 */
static int
gtp_get_handicap(char *s)
{
  UNUSED(s);
  return gtp_success("%d", handicap);
}


/* Function:  Load an sgf file, possibly up to a move number or the first
 *            occurence of a move.           
 * Arguments: filename + move number, vertex, or nothing
 * Fails:     missing filename or failure to open or parse file
 * Returns:   color to play
 *
 * Status:    GTP version 2 standard command.
 */
static int
gtp_loadsgf(char *s)
{
  char filename[GTP_BUFSIZE];
  char untilstring[GTP_BUFSIZE];
  SGFTree sgftree;
  Gameinfo gameinfo;
  int nread;
  int color_to_move;
  
  nread = sscanf(s, "%s %s", filename, untilstring);
  if (nread < 1)
    return gtp_failure("missing filename");

  sgftree_clear(&sgftree);
  if (!sgftree_readfile(&sgftree, filename))
    return gtp_failure("cannot open or parse '%s'", filename);

  if (nread == 1)
    color_to_move = gameinfo_play_sgftree_rot(&gameinfo, &sgftree, NULL,
					      gtp_orientation);
  else
    color_to_move = gameinfo_play_sgftree_rot(&gameinfo, &sgftree, untilstring,
                                              gtp_orientation);

  if (color_to_move == EMPTY)
    return gtp_failure("cannot load '%s'", filename);
  
  gtp_internal_set_boardsize(board_size);
  reset_engine();
  init_timers();

  sgfFreeNode(sgftree.root);

  gtp_start_response(GTP_SUCCESS);
  gtp_mprintf("%C", color_to_move);
  return gtp_finish_response();
}


/*****************
 * Board status. *
 *****************/

/* Function:  Return the color at a vertex.
 * Arguments: vertex
 * Fails:     invalid vertex
 * Returns:   "black", "white", or "empty"
 */
static int
gtp_what_color(char *s)
{
  int i, j;
  if (!gtp_decode_coord(s, &i, &j))
    return gtp_failure("invalid coordinate");
  
  return gtp_success(color_to_string(BOARD(i, j)));
}


/* Function:  List vertices with either black or white stones.
 * Arguments: color
 * Fails:     invalid color
 * Returns:   list of vertices
 */
static int
gtp_list_stones(char *s)
{
  int i, j;
  int color = EMPTY;
  int vertexi[MAX_BOARD * MAX_BOARD];
  int vertexj[MAX_BOARD * MAX_BOARD];
  int vertices = 0;
  
  if (!gtp_decode_color(s, &color))
    return gtp_failure("invalid color");

  for (i = 0; i < board_size; i++)
    for (j = 0; j < board_size; j++)
      if (BOARD(i, j) == color) {
	vertexi[vertices] = i;
	vertexj[vertices++] = j;
      }

  gtp_start_response(GTP_SUCCESS);
  gtp_print_vertices(vertices, vertexi, vertexj);
  return gtp_finish_response();
}


/* Function:  Count number of liberties for the string at a vertex.
 * Arguments: vertex
 * Fails:     invalid vertex, empty vertex
 * Returns:   Number of liberties.
 */
static int
gtp_countlib(char *s)
{
  int i, j;
  if (!gtp_decode_coord(s, &i, &j))
    return gtp_failure("invalid coordinate");

  if (BOARD(i, j) == EMPTY)
    return gtp_failure("vertex must not be empty");

  return gtp_success("%d", countlib(POS(i, j)));
}


/* Function:  Return the positions of the liberties for the string at a vertex.
 * Arguments: vertex
 * Fails:     invalid vertex, empty vertex
 * Returns:   Sorted space separated list of vertices.
 */
static int
gtp_findlib(char *s)
{
  int i, j;
  int libs[MAXLIBS];
  int liberties;
  
  if (!gtp_decode_coord(s, &i, &j))
    return gtp_failure("invalid coordinate");

  if (BOARD(i, j) == EMPTY)
    return gtp_failure("vertex must not be empty");

  liberties = findlib(POS(i, j), MAXLIBS, libs);
  gtp_start_response(GTP_SUCCESS);
  gtp_print_vertices2(liberties, libs);
  return gtp_finish_response();
}


/* Function:  Determine which liberties a stone of given color
 *            will get if played at given vertex.
 * Arguments: move (color + vertex)
 * Fails:     invalid color, invalid vertex, occupied vertex
 * Returns:   Sorted space separated list of liberties
 */
static int
gtp_accuratelib(char *s)
{
  int i, j;
  int color;
  int libs[MAXLIBS];
  int liberties;

  if (!gtp_decode_move(s, &color, &i, &j))
    return gtp_failure("invalid color or coordinate");

  if (BOARD(i, j) != EMPTY)
    return gtp_failure("vertex must be empty");

  liberties = accuratelib(POS(i, j), color, MAXLIBS, libs);

  gtp_start_response(GTP_SUCCESS);
  gtp_print_vertices2(liberties, libs);
  return gtp_finish_response();
}


/* Function:  Determine which liberties a stone of given color
 *            will get if played at given vertex.
 * Arguments: move (color + vertex)
 * Fails:     invalid color, invalid vertex, occupied vertex
 * Returns:   Sorted space separated list of liberties
 *
 * Supposedly identical in behavior to the above function and
 * can be retired when this is confirmed.
 */
static int
gtp_accurate_approxlib(char *s)
{
  int i, j;
  int color;
  int libs[MAXLIBS];
  int liberties;

  if (!gtp_decode_move(s, &color, &i, &j))
    return gtp_failure("invalid color or coordinate");

  if (BOARD(i, j) != EMPTY)
    return gtp_failure("vertex must be empty");

  liberties = accuratelib(POS(i, j), color, MAXLIBS, libs);

  gtp_start_response(GTP_SUCCESS);
  gtp_print_vertices2(liberties, libs);
  return gtp_finish_response();
}


/* Function:  Tell whether a move is legal.
 * Arguments: move
 * Fails:     invalid move
 * Returns:   1 if the move is legal, 0 if it is not.
 */
static int
gtp_is_legal(char *s)
{
  int i, j;
  int color;
  
  if (!gtp_decode_move(s, &color, &i, &j))
    return gtp_failure("invalid color or coordinate");

  return gtp_success("%d", is_allowed_move(POS(i, j), color));
}


/* Function:  List all legal moves for either color.
 * Arguments: color
 * Fails:     invalid color
 * Returns:   Sorted space separated list of vertices.
 */
static int
gtp_all_legal(char *s)
{
  int i, j;
  int color;
  int movei[MAX_BOARD * MAX_BOARD];
  int movej[MAX_BOARD * MAX_BOARD];
  int moves = 0;
  
  if (!gtp_decode_color(s, &color))
    return gtp_failure("invalid color");

  for (i = 0; i < board_size; i++)
    for (j = 0; j < board_size; j++)
      if (BOARD(i, j) == EMPTY && is_allowed_move(POS(i, j), color)) {
	movei[moves] = i;
	movej[moves++] = j;
      }

  gtp_start_response(GTP_SUCCESS);
  gtp_print_vertices(moves, movei, movej);
  return gtp_finish_response();
}


/* Function:  List the number of captures taken by either color.
 * Arguments: color
 * Fails:     invalid color
 * Returns:   Number of captures.
 */
static int
gtp_captures(char *s)
{
  int color;
  
  if (!gtp_decode_color(s, &color))
    return gtp_failure("invalid color");

  if (color == BLACK)
    return gtp_success("%d", white_captured);
  else
    return gtp_success("%d", black_captured);
}


/* Function:  Return the last move.
 * Arguments: none
 * Fails:     no previous move known
 * Returns:   Color and vertex of last move.
 */
static int
gtp_last_move(char *s)
{
  int pos;
  int color;
  UNUSED(s);
  
  if (move_history_pointer <= 0)
    return gtp_failure("no previous move known");
  
  pos = move_history_pos[move_history_pointer - 1];
  color = move_history_color[move_history_pointer - 1];
  
  gtp_start_response(GTP_SUCCESS);
  gtp_mprintf("%C %m", color, I(pos), J(pos));
  return gtp_finish_response();
}

/* Function:  Print the move history in reverse order
 * Arguments: none
 * Fails:     never
 * Returns:   List of moves played in reverse order in format: 
 *            color move (one move per line)
 */
static int
gtp_move_history(char *s)
{
  int k, pos, color;
  UNUSED(s);
  
  gtp_start_response(GTP_SUCCESS);
  if (move_history_pointer > 0)
    for (k = move_history_pointer-1; k >= 0; k--) {
      color = move_history_color[k];
      pos = move_history_pos[k];
      gtp_mprintf("%C %m\n", color, I(pos), J(pos));
    }
  else
    gtp_printf("\n");
  gtp_printf("\n");
  return GTP_OK;
}


/* Function:  Return the rotation/reflection invariant board hash.
 * Arguments: none
 * Fails:     never
 * Returns:   Invariant hash for the board as a hexadecimal number.
 */
static int
gtp_invariant_hash(char *s)
{
  Hash_data hash;
  UNUSED(s);
  hashdata_calc_orientation_invariant(&hash, board, board_ko_pos);
  return gtp_success("%s", hashdata_to_string(&hash));
}


/* Function:  Return the rotation/reflection invariant board hash
 *            obtained by playing all the possible moves for the
 *            given color.
 * Arguments: color
 * Fails:     invalid color
 * Returns:   List of moves + invariant hash as a hexadecimal number,
 *            one pair of move + hash per line.
 */
static int
gtp_invariant_hash_for_moves(char *s)
{
  Hash_data hash;
  int color;
  int pos;
  int move_found = 0;
  
  if (!gtp_decode_color(s, &color))
    return gtp_failure("invalid color");

  gtp_start_response(GTP_SUCCESS);

  for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
    if (board[pos] == EMPTY
	&& trymove(pos, color, "gtp_invariant_hash_for_moves", NO_MOVE)) {
      hashdata_calc_orientation_invariant(&hash, board, board_ko_pos);
      gtp_mprintf("%m %s\n", I(pos), J(pos), hashdata_to_string(&hash));
      popgo();
      move_found = 1;
    }
  }

  if (!move_found)
    gtp_printf("\n");
  
  gtp_printf("\n");
  return GTP_OK;
}



/**********************
 * Retractable moves. *
 **********************/

/* Function:  Play a stone of the given color at the given vertex.
 * Arguments: move (color + vertex)
 * Fails:     invalid color, invalid vertex, illegal move
 * Returns:   nothing
 */
static int
gtp_trymove(char *s)
{
  int i, j;
  int color;
  if (!gtp_decode_move(s, &color, &i, &j))
    return gtp_failure("invalid color or coordinate");

  if (!trymove(POS(i, j), color, "gtp_trymove", NO_MOVE))
    return gtp_failure("illegal move");

  return gtp_success("");
}

/* Function:  Play a stone of the given color at the given vertex, 
 *            allowing illegal ko capture.
 * Arguments: move (color + vertex)
 * Fails:     invalid color, invalid vertex, illegal move
 * Returns:   nothing
 */
static int
gtp_tryko(char *s)
{
  int i, j;
  int color;
  if (!gtp_decode_move(s, &color, &i, &j) || POS(i, j) == PASS_MOVE)
    return gtp_failure("invalid color or coordinate");

  if (!tryko(POS(i, j), color, "gtp_tryko"))
    return gtp_failure("illegal move");

  return gtp_success("");
}


/* Function:  Undo a trymove or tryko.
 * Arguments: none
 * Fails:     stack empty
 * Returns:   nothing
 */
static int
gtp_popgo(char *s)
{
  UNUSED(s);

  if (stackp == 0)
    return gtp_failure("Stack empty.");

  popgo();
  return gtp_success("");
}

/*********************
 * Caching	     *
 *********************/

/* Function:  clear the caches.
 * Arguments: none.
 * Fails:     never.
 * Returns:   nothing.
 */

static int
gtp_clear_cache(char *s)
{
  UNUSED(s);
  clear_persistent_caches();
  reading_cache_clear();
  return gtp_success("");
}

/*********************
 * Tactical reading. *
 *********************/

/* Function:  Try to attack a string.
 * Arguments: vertex
 * Fails:     invalid vertex, empty vertex
 * Returns:   attack code followed by attack point if attack code nonzero.
 */
static int
gtp_attack(char *s)
{
  int i, j;
  int apos;
  int attack_code;
  
  if (!gtp_decode_coord(s, &i, &j))
    return gtp_failure("invalid coordinate");

  if (BOARD(i, j) == EMPTY)
    return gtp_failure("vertex must not be empty");

  attack_code = attack(POS(i, j), &apos);
  gtp_start_response(GTP_SUCCESS);
  gtp_print_code(attack_code);
  if (attack_code > 0) {
    gtp_printf(" ");
    gtp_print_vertex(I(apos), J(apos));
  }
  return gtp_finish_response();
}  


/* Function:  Try to attack either of two strings
 * Arguments: two vertices
 * Fails:     invalid vertex, empty vertex
 * Returns:   attack code against the strings.  Guarantees there
 *            exists a move which will attack one of the two
 *            with attack_code, but does not return the move.
 */
static int
gtp_attack_either(char *s)
{
  int ai, aj;
  int bi, bj;
  int n;
  int acode;

  n = gtp_decode_coord(s, &ai, &aj);
  if (n == 0)
    return gtp_failure("invalid coordinate");

  if (BOARD(ai, aj) == EMPTY)
    return gtp_failure("string vertex must be empty");

  n = gtp_decode_coord(s + n, &bi, &bj);
  if (n == 0)
    return gtp_failure("invalid coordinate");

  if (BOARD(bi, bj) == EMPTY)
    return gtp_failure("string vertex must not be empty");

  acode = attack_either(POS(ai, aj), POS(bi, bj));

  gtp_start_response(GTP_SUCCESS);
  gtp_print_code(acode);
  return gtp_finish_response();
}


/* Function:  Try to defend a string.
 * Arguments: vertex
 * Fails:     invalid vertex, empty vertex
 * Returns:   defense code followed by defense point if defense code nonzero.
 */
static int
gtp_defend(char *s)
{
  int i, j;
  int dpos;
  int defend_code;
  
  if (!gtp_decode_coord(s, &i, &j))
    return gtp_failure("invalid coordinate");

  if (BOARD(i, j) == EMPTY)
    return gtp_failure("vertex must not be empty");

  defend_code = find_defense(POS(i, j), &dpos);
  gtp_start_response(GTP_SUCCESS);
  gtp_print_code(defend_code);
  if (defend_code > 0) {
    gtp_printf(" ");
    gtp_print_vertex(I(dpos), J(dpos));
  }
  return gtp_finish_response();
}  


/* Function:  Examine whether a specific move attacks a string tactically.
 * Arguments: vertex (move), vertex (dragon)
 * Fails:     invalid vertex, empty vertex
 * Returns:   attack code
 */
static int
gtp_does_attack(char *s)
{
  int i, j;
  int ti, tj;
  int attack_code;
  int n;

  n = gtp_decode_coord(s, &ti, &tj);
  if (n == 0)
    return gtp_failure("invalid coordinate");

  if (BOARD(ti, tj) != EMPTY)
    return gtp_failure("move vertex must be empty");

  n = gtp_decode_coord(s + n, &i, &j);
  if (n == 0)
    return gtp_failure("invalid coordinate");

  if (BOARD(i, j) == EMPTY)
    return gtp_failure("string vertex must not be empty");

  /* to get the variations into the sgf file, clear the reading cache */
  if (sgf_dumptree)
    reading_cache_clear();
  
  attack_code = does_attack(POS(ti, tj), POS(i, j));
  gtp_start_response(GTP_SUCCESS);
  gtp_print_code(attack_code);
  return gtp_finish_response();
}  


/* Function:  Examine whether a specific move defends a string tactically.
 * Arguments: vertex (move), vertex (dragon)
 * Fails:     invalid vertex, empty vertex
 * Returns:   attack code
 */
static int
gtp_does_defend(char *s)
{
  int i, j;
  int ti, tj;
  int defense_code;
  int n;

  n = gtp_decode_coord(s, &ti, &tj);
  if (n == 0)
    return gtp_failure("invalid coordinate");

  if (BOARD(ti, tj) != EMPTY)
    return gtp_failure("move vertex must be empty");

  n = gtp_decode_coord(s + n, &i, &j);
  if (n == 0)
    return gtp_failure("invalid coordinate");

  if (BOARD(i, j) == EMPTY)
    return gtp_failure("string vertex must not be empty");

  /* to get the variations into the sgf file, clear the reading cache */
  if (sgf_dumptree)
    reading_cache_clear();
  
  defense_code = does_defend(POS(ti, tj), POS(i, j));
  gtp_start_response(GTP_SUCCESS);
  gtp_print_code(defense_code);
  return gtp_finish_response();
}  


/* Function:  Try to attack a string strictly in a ladder.
 * Arguments: vertex
 * Fails:     invalid vertex, empty vertex
 * Returns:   attack code followed by attack point if attack code nonzero.
 */
static int
gtp_ladder_attack(char *s)
{
  int i, j;
  int apos;
  int attack_code;
  
  if (!gtp_decode_coord(s, &i, &j))
    return gtp_failure("invalid coordinate");

  if (BOARD(i, j) == EMPTY)
    return gtp_failure("vertex must not be empty");

  if (countlib(POS(i, j)) != 2)
    return gtp_failure("string must have exactly 2 liberties");

  attack_code = simple_ladder(POS(i, j), &apos);
  gtp_start_response(GTP_SUCCESS);
  gtp_print_code(attack_code);
  if (attack_code > 0) {
    gtp_printf(" ");
    gtp_print_vertex(I(apos), J(apos));
  }
  return gtp_finish_response();
}  


/* Function:  Increase depth values by one.
 * Arguments: none
 * Fails:     never
 * Returns:   nothing
 */
static int
gtp_increase_depths(char *s)
{
  UNUSED(s);
  increase_depth_values();
  return gtp_success("");
}  


/* Function:  Decrease depth values by one.
 * Arguments: none
 * Fails:     never
 * Returns:   nothing
 */
static int
gtp_decrease_depths(char *s)
{
  UNUSED(s);
  decrease_depth_values();
  return gtp_success("");
}  


/******************
 * owl reading. *
 ******************/

/* Function:  Try to attack a dragon.
 * Arguments: vertex
 * Fails:     invalid vertex, empty vertex
 * Returns:   attack code followed by attack point if attack code nonzero.
 */
static int
gtp_owl_attack(char *s)
{
  int i, j;
  int attack_point;
  int attack_code;
  int result_certain;
  int kworm;

  if (!gtp_decode_coord(s, &i, &j))
    return gtp_failure("invalid coordinate");

  if (BOARD(i, j) == EMPTY)
    return gtp_failure("vertex must not be empty");

  silent_examine_position(EXAMINE_DRAGONS_WITHOUT_OWL);
  
  attack_code = owl_attack(POS(i, j), &attack_point, &result_certain, &kworm);
  gtp_start_response(GTP_SUCCESS);
  gtp_print_code(attack_code);
  if (attack_code > 0) {
    gtp_printf(" ");
    gtp_print_vertex(I(attack_point), J(attack_point));
  }
  if (!result_certain && report_uncertainty)
    gtp_printf(" uncertain");
  return gtp_finish_response();
}  


/* Function:  Try to defend a dragon.
 * Arguments: vertex
 * Fails:     invalid vertex, empty vertex
 * Returns:   defense code followed by defense point if defense code nonzero.
 */
static int
gtp_owl_defend(char *s)
{
  int i, j;
  int defense_point;
  int defend_code;
  int result_certain;
  int kworm;
  
  if (!gtp_decode_coord(s, &i, &j))
    return gtp_failure("invalid coordinate");

  if (BOARD(i, j) == EMPTY)
    return gtp_failure("vertex must not be empty");

  silent_examine_position(EXAMINE_DRAGONS_WITHOUT_OWL);
  
  defend_code = owl_defend(POS(i, j), &defense_point, &result_certain, &kworm);
  gtp_start_response(GTP_SUCCESS);
  gtp_print_code(defend_code);
  if (defend_code > 0) {
    gtp_printf(" ");
    gtp_print_vertex(I(defense_point), J(defense_point));
  }
  if (!result_certain && report_uncertainty)
    gtp_printf(" uncertain");
  return gtp_finish_response();
}  

/* Function:  Try to attack a dragon in 2 moves.
 * Arguments: vertex
 * Fails:     invalid vertex, empty vertex
 * Returns:   attack code followed by the two attack points if
 *            attack code nonzero.
 */
static int
gtp_owl_threaten_attack(char *s)
{
  int i, j;
  int attack_point1;
  int attack_point2;
  int attack_code;

  if (!gtp_decode_coord(s, &i, &j))
    return gtp_failure("invalid coordinate");

  if (BOARD(i, j) == EMPTY)
    return gtp_failure("vertex must not be empty");

  silent_examine_position(EXAMINE_DRAGONS_WITHOUT_OWL);
  
  attack_code = owl_threaten_attack(POS(i, j), &attack_point1, &attack_point2);
  gtp_start_response(GTP_SUCCESS);
  gtp_print_code(attack_code);
  if (attack_code > 0) {
    gtp_printf(" ");
    gtp_print_vertex(I(attack_point1), J(attack_point1));
    gtp_printf(" ");
    gtp_print_vertex(I(attack_point2), J(attack_point2));
  }
  return gtp_finish_response();
}  


/* Function:  Try to defend a dragon with 2 moves.
 * Arguments: vertex
 * Fails:     invalid vertex, empty vertex
 * Returns:   defense code followed by the 2 defense points if
 *            defense code nonzero.
 */
static int
gtp_owl_threaten_defense(char *s)
{
  int i, j;
  int defense_point1;
  int defense_point2;
  int defend_code;
  
  if (!gtp_decode_coord(s, &i, &j))
    return gtp_failure("invalid coordinate");

  if (BOARD(i, j) == EMPTY)
    return gtp_failure("vertex must not be empty");

  silent_examine_position(EXAMINE_DRAGONS_WITHOUT_OWL);
  
  defend_code = owl_threaten_defense(POS(i, j), &defense_point1,
				     &defense_point2);
  gtp_start_response(GTP_SUCCESS);
  gtp_print_code(defend_code);
  if (defend_code > 0) {
    gtp_printf(" ");
    gtp_print_vertex(I(defense_point1), J(defense_point1));
    gtp_printf(" ");
    gtp_print_vertex(I(defense_point2), J(defense_point2));
  }
  return gtp_finish_response();
}  


/* Function:  Examine whether a specific move attacks a dragon.
 * Arguments: vertex (move), vertex (dragon)
 * Fails:     invalid vertex, empty vertex
 * Returns:   attack code
 */
static int
gtp_owl_does_attack(char *s)
{
  int i, j;
  int ti, tj;
  int attack_code;
  int kworm;
  int n;

  n = gtp_decode_coord(s, &ti, &tj);
  if (n == 0)
    return gtp_failure("invalid coordinate");

  if (BOARD(ti, tj) != EMPTY)
    return gtp_failure("move vertex must be empty");

  n = gtp_decode_coord(s + n, &i, &j);
  if (n == 0)
    return gtp_failure("invalid coordinate");

  if (BOARD(i, j) == EMPTY)
    return gtp_failure("dragon vertex must not be empty");

  silent_examine_position(EXAMINE_DRAGONS_WITHOUT_OWL);
  
  attack_code = owl_does_attack(POS(ti, tj), POS(i, j), &kworm);
  gtp_start_response(GTP_SUCCESS);
  gtp_print_code(attack_code);
  return gtp_finish_response();
}  


/* Function:  Examine whether a specific move defends a dragon.
 * Arguments: vertex (move), vertex (dragon)
 * Fails:     invalid vertex, empty vertex
 * Returns:   defense code
 */
static int
gtp_owl_does_defend(char *s)
{
  int i, j;
  int ti, tj;
  int defense_code;
  int kworm;
  int n;

  n = gtp_decode_coord(s, &ti, &tj);
  if (n == 0)
    return gtp_failure("invalid coordinate");

  if (BOARD(ti, tj) != EMPTY)
    return gtp_failure("move vertex must be empty");

  n = gtp_decode_coord(s + n, &i, &j);
  if (n == 0)
    return gtp_failure("invalid coordinate");

  if (BOARD(i, j) == EMPTY)
    return gtp_failure("dragon vertex must not be empty");

  silent_examine_position(EXAMINE_DRAGONS_WITHOUT_OWL);
  
  defense_code = owl_does_defend(POS(ti, tj), POS(i, j), &kworm);
  gtp_start_response(GTP_SUCCESS);
  gtp_print_code(defense_code);
  return gtp_finish_response();
}  


/* Function:  Examine whether a connection defends involved dragons.
 * Arguments: vertex (move), vertex (dragon1), vertex (dragon2)
 * Fails:     invalid vertex, empty vertex
 * Returns:   defense code
 */
static int
gtp_owl_connection_defends(char *s)
{
  int ai, aj;
  int bi, bj;
  int ti, tj;
  int defense_code;
  int n;

  n = gtp_decode_coord(s, &ti, &tj);
  if (n == 0)
    return gtp_failure("invalid coordinate");

  if (BOARD(ti, tj) != EMPTY)
    return gtp_failure("move vertex must be empty");

  s += n;
  n = gtp_decode_coord(s, &ai, &aj);
  if (n == 0)
    return gtp_failure("invalid coordinate");

  s += n;
  n = gtp_decode_coord(s, &bi, &bj);
  if (n == 0)
    return gtp_failure("invalid coordinate");

  if (BOARD(ai, aj) == EMPTY || BOARD(bi, bj) == EMPTY)
    return gtp_failure("dragon vertex must not be empty");

  if (BOARD(ai, aj) != BOARD(bi, bj))
    return gtp_failure("dragon vertices must have the same color");

  silent_examine_position(EXAMINE_DRAGONS_WITHOUT_OWL);
  
  defense_code = owl_connection_defends(POS(ti, tj), POS(ai, aj), POS(bi, bj));
  gtp_start_response(GTP_SUCCESS);
  gtp_print_code(defense_code);
  return gtp_finish_response();
}


/* Function:  Try to defend both of two strings
 * Arguments: two vertices
 * Fails:     invalid vertex, empty vertex
 * Returns:   defend code for the strings.  Guarantees there
 *            exists a move which will defend both of the two
 *            with defend_code, but does not return the move.
 */
static int
gtp_defend_both(char *s)
{
  int ai, aj;
  int bi, bj;
  int n;
  int dcode;

  n = gtp_decode_coord(s, &ai, &aj);
  if (n == 0)
    return gtp_failure("invalid coordinate");

  if (BOARD(ai, aj) == EMPTY)
    return gtp_failure("string vertex must be empty");

  n = gtp_decode_coord(s + n, &bi, &bj);
  if (n == 0)
    return gtp_failure("invalid coordinate");

  if (BOARD(bi, bj) == EMPTY)
    return gtp_failure("string vertex must not be empty");

  dcode = defend_both(POS(ai, aj), POS(bi, bj));

  gtp_start_response(GTP_SUCCESS);
  gtp_print_code(dcode);
  return gtp_finish_response();
}



/* Function:  Determine whether capturing a string gives a living dragon
 * Arguments: vertex
 * Fails:     invalid vertex, empty vertex
 * Returns:   1 if dragon can live, 0 otherwise
 */
static int
gtp_owl_substantial(char *s)
{
  int i, j;
  int result;
  
  if (!gtp_decode_coord(s, &i, &j))
    return gtp_failure("invalid coordinate");

  if (BOARD(i, j) == EMPTY)
    return gtp_failure("vertex must not be empty");

  silent_examine_position(EXAMINE_DRAGONS_WITHOUT_OWL);
  
  result = owl_substantial(POS(i, j));
  return gtp_success("%d", result);
}  


/* Function:  Analyze a semeai
 * Arguments: dragona, dragonb
 * Fails:     invalid vertices, empty vertices
 * Returns:   semeai defense result, semeai attack result, semeai move
 */
static int
gtp_analyze_semeai(char *s)
{
  int i, j;
  int k;
  int dragona, dragonb;
  int resulta, resultb, move, result_certain;
  
  k = gtp_decode_coord(s, &i, &j);

  if (k == 0)
    return gtp_failure("invalid coordinate");
  dragona = POS(i, j);
  if (BOARD(i, j) == EMPTY)
    return gtp_failure("vertex must not be empty");

  if (!gtp_decode_coord(s+k, &i, &j))
    return gtp_failure("invalid coordinate");
  dragonb = POS(i, j);
  if (BOARD(i, j) == EMPTY)
    return gtp_failure("vertex must not be empty");

  silent_examine_position(EXAMINE_DRAGONS_WITHOUT_OWL);

  owl_analyze_semeai(dragona, dragonb, &resulta, &resultb, &move, 1,
  		     &result_certain);
  gtp_start_response(GTP_SUCCESS);
  gtp_print_code(resulta);
  gtp_printf(" ");
  gtp_print_code(resultb);
  gtp_mprintf(" %m", I(move), J(move));
  if (!result_certain && report_uncertainty)
    gtp_printf(" uncertain");

  return gtp_finish_response();
}  


/* Function:  Analyze a semeai after a move have been made.
 * Arguments: color, vertex, dragona, dragonb
 * Fails:     invalid vertices
 * Returns:   semeai defense result, semeai attack result, semeai move
 */
static int
gtp_analyze_semeai_after_move(char *s)
{
  int i, j;
  int color;
  int move;
  int k;
  int dragona, dragonb;
  int resulta, resultb, semeai_move, result_certain;
  
  k = gtp_decode_move(s, &color, &i, &j);
  move = POS(i, j);
  if (k == 0 || move == NO_MOVE)
    return gtp_failure("invalid color or coordinate");
  if (board[move] != EMPTY)
    return gtp_failure("move vertex is not empty");
  s += k;
  
  k = gtp_decode_coord(s, &i, &j);
  if (k == 0)
    return gtp_failure("invalid coordinate");
  dragona = POS(i, j);
  if (board[dragona] == EMPTY)
    return gtp_failure("dragon vertex must not be empty");
  s += k;

  if (!gtp_decode_coord(s, &i, &j))
    return gtp_failure("invalid coordinate");
  dragonb = POS(i, j);
  if (board[dragonb] == EMPTY)
    return gtp_failure("dragon vertex must not be empty");

  silent_examine_position(EXAMINE_DRAGONS_WITHOUT_OWL);

  owl_analyze_semeai_after_move(move, color, dragona, dragonb,
				&resulta, &resultb, &semeai_move, 1,
				&result_certain, 0);
  gtp_start_response(GTP_SUCCESS);
  gtp_print_code(resulta);
  gtp_printf(" ");
  gtp_print_code(resultb);
  gtp_mprintf(" %m", I(semeai_move), J(semeai_move));
  if (!result_certain && report_uncertainty)
    gtp_printf(" uncertain");

  return gtp_finish_response();
}  


/* Function:  Analyze a semeai, not using owl
 * Arguments: dragona, dragonb
 * Fails:     invalid vertices, empty vertices
 * Returns:   status of dragona, dragonb assuming dragona moves first
 */
static int
gtp_tactical_analyze_semeai(char *s)
{
  int i, j;
  int k;
  int dragona, dragonb;
  int resulta, resultb, move, result_certain;
  
  k = gtp_decode_coord(s, &i, &j);

  if (k == 0)
    return gtp_failure("invalid coordinate");
  dragona = POS(i, j);
  if (BOARD(i, j) == EMPTY)
    return gtp_failure("vertex must not be empty");

  if (!gtp_decode_coord(s+k, &i, &j))
    return gtp_failure("invalid coordinate");
  dragonb = POS(i, j);
  if (BOARD(i, j) == EMPTY)
    return gtp_failure("vertex must not be empty");

  silent_examine_position(EXAMINE_DRAGONS_WITHOUT_OWL);

  owl_analyze_semeai(dragona, dragonb, &resulta, &resultb, &move, 0,
                     &result_certain);
  gtp_start_response(GTP_SUCCESS);
  gtp_print_code(resulta);
  gtp_printf(" ");
  gtp_print_code(resultb);
  gtp_mprintf(" %m", I(move), J(move));
  if (!result_certain && report_uncertainty)
    gtp_printf(" uncertain");

  return gtp_finish_response();
}  


/***********************
 * Connection reading. *
 ***********************/

/* Function:  Try to connect two strings.
 * Arguments: vertex, vertex
 * Fails:     invalid vertex, empty vertex, vertices of different colors
 * Returns:   connect result followed by connect point if successful.
 */
static int
gtp_connect(char *s)
{
  int ai, aj;
  int bi, bj;
  int connect_move = PASS_MOVE;
  int result;
  int n;
  
  n = gtp_decode_coord(s, &ai, &aj);
  if (n == 0)
    return gtp_failure("invalid coordinate");

  if (!gtp_decode_coord(s + n, &bi, &bj))
    return gtp_failure("invalid coordinate");

  if (BOARD(ai, aj) == EMPTY || BOARD(bi, bj) == EMPTY)
    return gtp_failure("vertex must not be empty");

  if (BOARD(ai, aj) != BOARD(bi, bj))
    return gtp_failure("vertices must have same color");

  result = string_connect(POS(ai, aj), POS(bi, bj), &connect_move);
  gtp_start_response(GTP_SUCCESS);
  gtp_print_code(result);
  if (result != 0)
    gtp_mprintf(" %m", I(connect_move), J(connect_move));

  return gtp_finish_response();
}  


/* Function:  Try to disconnect two strings.
 * Arguments: vertex, vertex
 * Fails:     invalid vertex, empty vertex, vertices of different colors
 * Returns:   disconnect result followed by disconnect point if successful.
 */
static int
gtp_disconnect(char *s)
{
  int ai, aj;
  int bi, bj;
  int disconnect_move = PASS_MOVE;
  int result;
  int n;
  
  n = gtp_decode_coord(s, &ai, &aj);
  if (n == 0)
    return gtp_failure("invalid coordinate");

  if (!gtp_decode_coord(s + n, &bi, &bj))
    return gtp_failure("invalid coordinate");

  if (BOARD(ai, aj) == EMPTY || BOARD(bi, bj) == EMPTY)
    return gtp_failure("vertex must not be empty");

  if (BOARD(ai, aj) != BOARD(bi, bj))
    return gtp_failure("vertices must have same color");

  result = disconnect(POS(ai, aj), POS(bi, bj), &disconnect_move);
  gtp_start_response(GTP_SUCCESS);
  gtp_print_code(result);
  if (result != 0)
    gtp_mprintf(" %m", I(disconnect_move), J(disconnect_move));

  return gtp_finish_response();
}  


/* Function:  Try to break from string into area.
 * Arguments: vertex, vertices
 * Fails:     invalid vertex, empty vertex.
 * Returns:   result followed by break in point if successful.
 */
static int
gtp_break_in(char *s)
{
  int ai, aj;
  int i, j;
  signed char goal[BOARDMAX];
  int break_move = PASS_MOVE;
  int result;
  int n;
  int k;
  
  n = gtp_decode_coord(s, &ai, &aj);
  if (n == 0)
    return gtp_failure("invalid coordinate");

  memset(goal, 0, BOARDMAX);
  s += n;

  for (k = 0; k < MAX_BOARD * MAX_BOARD; k++) {
    n = gtp_decode_coord(s, &i, &j);
    if (n > 0) {
      goal[POS(i, j)] = 1;
      s += n;
    }
    else if (sscanf(s, "%*s") != EOF)
      return gtp_failure("invalid coordinate");
    else
      break;
  }

  if (BOARD(ai, aj) == EMPTY)
    return gtp_failure("vertex must not be empty");

  result = break_in(POS(ai, aj), goal, &break_move);
  gtp_start_response(GTP_SUCCESS);
  gtp_print_code(result);
  if (result != 0)
    gtp_mprintf(" %m", I(break_move), J(break_move));

  return gtp_finish_response();
}

/* Function:  Try to block string from area.
 * Arguments: vertex, vertices
 * Fails:     invalid vertex, empty vertex.
 * Returns:   result followed by block point if successful.
 */
static int
gtp_block_off(char *s)
{
  int ai, aj;
  int i, j;
  signed char goal[BOARDMAX];
  int block_move = PASS_MOVE;
  int result;
  int n;
  int k;
  
  n = gtp_decode_coord(s, &ai, &aj);
  if (n == 0)
    return gtp_failure("invalid coordinate");

  memset(goal, 0, BOARDMAX);
  s += n;

  for (k = 0; k < MAX_BOARD * MAX_BOARD; k++) {
    n = gtp_decode_coord(s, &i, &j);
    if (n > 0) {
      goal[POS(i, j)] = 1;
      s += n;
    }
    else if (sscanf(s, "%*s") != EOF)
      return gtp_failure("invalid coordinate");
    else
      break;
  }

  if (BOARD(ai, aj) == EMPTY)
    return gtp_failure("vertex must not be empty");

  result = block_off(POS(ai, aj), goal, &block_move);
  gtp_start_response(GTP_SUCCESS);
  gtp_print_code(result);
  if (result != 0)
    gtp_mprintf(" %m", I(block_move), J(block_move));

  return gtp_finish_response();
}



/********
 * eyes *
 ********/

/* Function:  Evaluate an eye space
 * Arguments: vertex
 * Fails:     invalid vertex
 * Returns:   Minimum and maximum number of eyes. If these differ an
 *            attack and a defense point are additionally returned.
 *            If the vertex is not an eye space or not of unique color,
 *            a single -1 is returned.
 */

static int
gtp_eval_eye(char *s)
{
  int m, n;
  struct eyevalue value;
  int attack_point;
  int defense_point;
  int pos;

  if (!gtp_decode_coord(s, &m, &n))
    return gtp_failure("invalid coordinate");

  silent_examine_position(EXAMINE_DRAGONS_WITHOUT_OWL);
  
  if (black_eye[POS(m, n)].color == BLACK) {
    pos = black_eye[POS(m, n)].origin;
    compute_eyes(pos, &value, &attack_point, &defense_point,
		 black_eye, half_eye, 0);
  }
  else if (white_eye[POS(m, n)].color == WHITE) {
    pos = white_eye[POS(m, n)].origin;
    compute_eyes(pos, &value, &attack_point, &defense_point,
		 white_eye, half_eye, 0);
  }
  else
    /* Not an eye or not of unique color. */
    return gtp_success("-1");

  gtp_start_response(GTP_SUCCESS);
  gtp_printf("%d %d", min_eyes(&value), max_eyes(&value));
  if (eye_move_urgency(&value) > 0) {
    gtp_printf(" ");
    gtp_print_vertex(I(attack_point), J(attack_point));
    gtp_printf(" ");
    gtp_print_vertex(I(defense_point), J(defense_point));
  }
  return gtp_finish_response();
}


/*****************
 * dragon status *
 *****************/

/* Function:  Determine status of a dragon.
 * Arguments: optional vertex
 * Fails:     invalid vertex, empty vertex
 * Returns:   status ("alive", "critical", "dead", or "unknown"),
 *            attack point, defense point. Points of attack and
 *            defense are only given if the status is critical.
 *            If no vertex is given, the status is listed for all
 *            dragons, one per row in the format "A4: alive".
 *
 * FIXME: Should be able to distinguish between life in seki
 *        and independent life. Should also be able to identify ko.
 */

static int
gtp_dragon_status(char *s)
{
  int i, j;
  int str = NO_MOVE;
  int pos;
  int empty_response = 1;

  if (gtp_decode_coord(s, &i, &j)) {
    str = POS(i, j);
    if (board[str] == EMPTY)
      return gtp_failure("vertex must not be empty");
  }
  else if (sscanf(s, "%*s") != EOF)
    return gtp_failure("invalid coordinate");

  silent_examine_position(EXAMINE_DRAGONS);
  
  gtp_start_response(GTP_SUCCESS);

  for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
    if (ON_BOARD(pos)
	&& (pos == str
	    || (str == NO_MOVE
		&& board[pos] != EMPTY
		&& dragon[pos].origin == pos))) {
      if (str == NO_MOVE)
	gtp_mprintf("%m: ", I(pos), J(pos));
      
      if (dragon[pos].status == ALIVE)
	gtp_printf("alive\n");
      else if (dragon[pos].status == DEAD)
	gtp_printf("dead\n");
      else if (dragon[pos].status == UNKNOWN)
	gtp_printf("unknown\n");
      else {
	/* Only remaining possibility. */
	assert(dragon[pos].status == CRITICAL); 
	/* Status critical, need to return attack and defense point as well. */
	gtp_mprintf("critical %m %m\n", 
		    I(DRAGON2(pos).owl_attack_point),
		    J(DRAGON2(pos).owl_attack_point),
		    I(DRAGON2(pos).owl_defense_point),
		    J(DRAGON2(pos).owl_defense_point));
      }
      empty_response = 0;
    }
  }

  if (empty_response)
    gtp_printf("\n");

  gtp_printf("\n");
  return GTP_OK;
}


/* Function:  Determine whether two stones belong to the same dragon.
 * Arguments: vertex, vertex
 * Fails:     invalid vertex, empty vertex
 * Returns:   1 if the vertices belong to the same dragon, 0 otherwise
 */

static int
gtp_same_dragon(char *s)
{
  int ai, aj;
  int bi, bj;
  int n;

  n = gtp_decode_coord(s, &ai, &aj);
  if (n == 0)
    return gtp_failure("invalid coordinate");

  if (!gtp_decode_coord(s + n, &bi, &bj))
    return gtp_failure("invalid coordinate");

  if (BOARD(ai, aj) == EMPTY || BOARD(bi, bj) == EMPTY)
    return gtp_failure("vertex must not be empty");

  silent_examine_position(EXAMINE_DRAGONS_WITHOUT_OWL);
  
  return gtp_success("%d", dragon[POS(ai, aj)].id == dragon[POS(bi, bj)].id);
}


/************************
 * Unconditional status *
 ************************/

/* Function:  Determine the unconditional status of a vertex.
 * Arguments: vertex
 * Fails:     invalid vertex
 * Returns:   unconditional status ("undecided", "alive", "dead",
 *            "white_territory", "black_territory"). Occupied vertices can
 *            be undecided, alive, or dead. Empty vertices can be
 *            undecided, white territory, or black territory.
 */

static int
gtp_unconditional_status(char *s)
{
  int i, j;
  enum dragon_status status;

  if (!gtp_decode_coord(s, &i, &j))
    return gtp_failure("invalid coordinate");

  silent_examine_position(EXAMINE_WORMS);
  
  status = worm[POS(i, j)].unconditional_status;
  if (status == UNKNOWN)
    return gtp_success("undecided");
  return gtp_success("%s", status_to_string(status));
}


/***********************
 * combination attacks *
 ***********************/

/* Function:  Find a move by color capturing something through a
 *            combination attack.
 * Arguments: color
 * Fails:     invalid color
 * Returns:   Recommended move, PASS if no move found
 */

static int
gtp_combination_attack(char *s)
{
  int color;
  int attack_point;
  int n;

  n = gtp_decode_color(s, &color);
  if (!n)
    return gtp_failure("invalid color");

  silent_examine_position(EXAMINE_ALL);

  if (!atari_atari(color, &attack_point, NULL, verbose))
    attack_point = NO_MOVE;
  
  gtp_start_response(GTP_SUCCESS);
  gtp_print_vertex(I(attack_point), J(attack_point));
  return gtp_finish_response();
}

/* Function:  If color can capture something through a
 *            combination attack, list moves by the opponent of color
 *            to defend against this attack.
 * Arguments: color
 * Fails:     invalid color
 * Returns:   Recommended moves, PASS if no combination attack found.
 */

static int
gtp_combination_defend(char *s)
{
  int color;
  signed char defense_points[BOARDMAX];
  int pos;
  int first = 1;
  int n;

  n = gtp_decode_color(s, &color);
  if (!n)
    return gtp_failure("invalid color");

  silent_examine_position(EXAMINE_ALL);

  memset(defense_points, 0, sizeof(defense_points));
  if (!atari_atari(color, NULL, defense_points, verbose))
    return gtp_success("PASS");
  
  gtp_start_response(GTP_SUCCESS);
  for (pos = BOARDMIN; pos < BOARDMAX; pos++)
    if (ON_BOARD(pos) && defense_points[pos]) {
      if (!first)
	gtp_printf(" ");
      else
	first = 0;
      gtp_print_vertex(I(pos), J(pos));
    }
  
  return gtp_finish_response();
}

/* Function:  Run atari_atari_confirm_safety().
 * Arguments: move, optional int
 * Fails:     invalid move
 * Returns:   success code, if failure also defending move
 */

static int
gtp_aa_confirm_safety(char *s)
{
  int color;
  int i, j;
  int n;
  int minsize = 0;
  int result;
  int defense_point = NO_MOVE;
  signed char saved_dragons[BOARDMAX];
  signed char saved_worms[BOARDMAX];

  n = gtp_decode_move(s, &color, &i, &j);
  if (n == 0 || POS(i, j) == NO_MOVE)
    return gtp_failure("invalid color or coordinate");

  sscanf(s + n, "%d", &minsize);

  genmove(color, NULL, NULL);
  get_saved_dragons(POS(i, j), saved_dragons);
  get_saved_worms(POS(i, j), saved_worms);
  
  if (!tryko(POS(i, j), color, NULL))
    return gtp_failure("invalid move");
  popgo();

  result = atari_atari_confirm_safety(color, POS(i, j),
				      &defense_point, minsize,
				      saved_dragons, saved_worms);
  
  gtp_start_response(GTP_SUCCESS);
  gtp_mprintf("%d", result);
  if (result == 0)
    gtp_mprintf(" %m", I(defense_point), J(defense_point));
  
  return gtp_finish_response();
}


/********************
 * generating moves *
 ********************/

/* Function:  Generate and play the supposedly best black move.
 * Arguments: none
 * Fails:     never
 * Returns:   a move coordinate or "PASS"
 *
 * Status:    Obsolete GTP version 1 command.
 */
static int
gtp_genmove_black(char *s)
{
  int move;
  UNUSED(s);

  if (stackp > 0)
    return gtp_failure("genmove cannot be called when stackp > 0");

  move = genmove(BLACK, NULL, NULL);

  gnugo_play_move(move, BLACK);

  gtp_start_response(GTP_SUCCESS);
  gtp_print_vertex(I(move), J(move));
  return gtp_finish_response();
}

/* Function:  Generate and play the supposedly best white move.
 * Arguments: none
 * Fails:     never
 * Returns:   a move coordinate or "PASS"
 *
 * Status:    Obsolete GTP version 1 command.
 */
static int
gtp_genmove_white(char *s)
{
  int move;
  UNUSED(s);

  if (stackp > 0)
    return gtp_failure("genmove cannot be called when stackp > 0");

  move = genmove(WHITE, NULL, NULL);

  gnugo_play_move(move, WHITE);

  gtp_start_response(GTP_SUCCESS);
  gtp_print_vertex(I(move), J(move));
  return gtp_finish_response();
}

/* Function:  Generate and play the supposedly best move for either color.
 * Arguments: color to move
 * Fails:     invalid color
 * Returns:   a move coordinate or "PASS" (or "resign" if resignation_allowed)
 *
 * Status:    GTP version 2 standard command.
 */
static int
gtp_genmove(char *s)
{
  int move;
  int resign;
  int color;
  int n;

  n = gtp_decode_color(s, &color);
  if (!n)
    return gtp_failure("invalid color");

  if (stackp > 0)
    return gtp_failure("genmove cannot be called when stackp > 0");

  adjust_level_offset(color);
  move = genmove(color, NULL, &resign);

  if (resign)
    return gtp_success("resign");

  gnugo_play_move(move, color);

  gtp_start_response(GTP_SUCCESS);
  gtp_print_vertex(I(move), J(move));
  return gtp_finish_response();
}


/* Function:  Generate the supposedly best move for either color.
 * Arguments: color to move
 * Fails:     invalid color
 * Returns:   a move coordinate (or "PASS")
 *
 * Status:    GTP version 2 standard command.
 */
static int
gtp_reg_genmove(char *s)
{
  int move;
  int color;
  int n;
  unsigned int saved_random_seed = get_random_seed();

  n = gtp_decode_color(s, &color);
  if (!n)
    return gtp_failure("invalid color");

  if (stackp > 0)
    return gtp_failure("genmove cannot be called when stackp > 0");

  /* This is intended for regression purposes and should therefore be
   * deterministic. The best way to ensure this is to reset the random
   * number generator before calling genmove(). It is always seeded by
   * 0.
   */
  set_random_seed(0);
  
  move = genmove_conservative(color, NULL);

  set_random_seed(saved_random_seed);
  gtp_start_response(GTP_SUCCESS);
  gtp_print_vertex(I(move), J(move));
  return gtp_finish_response();
}

/* Function:  Generate the supposedly best move for either color.
 * Arguments: color to move, optionally a random seed
 * Fails:     invalid color
 * Returns:   a move coordinate (or "PASS")
 *
 * This differs from reg_genmove in the optional random seed.
 */
static int
gtp_gg_genmove(char *s)
{
  int move;
  int color;
  int n;
  unsigned int saved_random_seed = get_random_seed();
  unsigned int seed;

  n = gtp_decode_color(s, &color);
  if (!n)
    return gtp_failure("invalid color");

  if (stackp > 0)
    return gtp_failure("genmove cannot be called when stackp > 0");

  /* This is intended for regression purposes and should therefore be
   * deterministic. The best way to ensure this is to reset the random
   * number generator before calling genmove(). By default it is
   * seeded with 0, but if an optional unsigned integer is given in
   * the command after the color, this is used as seed instead.
   */
  seed = 0;
  sscanf(s+n, "%u", &seed);
  set_random_seed(seed);
  
  move = genmove_conservative(color, NULL);
  set_random_seed(saved_random_seed);
  gtp_start_response(GTP_SUCCESS);
  gtp_print_vertex(I(move), J(move));
  return gtp_finish_response();
}


/* Function:  Generate the supposedly best move for either color from a
 *            choice of allowed vertices.
 * Arguments: color to move, allowed vertices
 * Fails:     invalid color, invalid vertex, no vertex listed
 * Returns:   a move coordinate (or "PASS")
 */
static int
gtp_restricted_genmove(char *s)
{
  int move;
  int i, j;
  int color;
  int n;
  unsigned int saved_random_seed = get_random_seed();
  int allowed_moves[BOARDMAX];
  int number_allowed_moves = 0;
  memset(allowed_moves, 0, sizeof(allowed_moves));

  n = gtp_decode_color(s, &color);
  if (!n)
    return gtp_failure("invalid color");

  s += n;
  while (1) {
    n = gtp_decode_coord(s, &i, &j);
    if (n > 0) {
      allowed_moves[POS(i, j)] = 1;
      number_allowed_moves++;
      s += n;
    }
    else if (sscanf(s, "%*s") != EOF)
      return gtp_failure("invalid coordinate");
    else
      break;
  }

  if (number_allowed_moves == 0)
    return gtp_failure("no allowed vertex");

  if (stackp > 0)
    return gtp_failure("genmove cannot be called when stackp > 0");

  /* This is intended for regression purposes and should therefore be
   * deterministic. The best way to ensure this is to reset the random
   * number generator before calling genmove(). It is always seeded by
   * 0.
   */
  set_random_seed(0);
  
  move = genmove_restricted(color, allowed_moves);
  set_random_seed(saved_random_seed);
  gtp_start_response(GTP_SUCCESS);
  gtp_print_vertex(I(move), J(move));
  return gtp_finish_response();
}


/* Function:  Generate and play the supposedly best move for either color,
 *            not passing until all dead opponent stones have been removed.
 * Arguments: color to move
 * Fails:     invalid color
 * Returns:   a move coordinate (or "PASS")
 *
 * Status:    KGS specific command.
 *
 * A similar command, but possibly somewhat different, will likely be added
 * to GTP version 3 at a later time.
 */
static int
gtp_kgs_genmove_cleanup(char *s)
{
  int move;
  int color;
  int n;
  int save_capture_all_dead = capture_all_dead;

  n = gtp_decode_color(s, &color);
  if (!n)
    return gtp_failure("invalid color");

  if (stackp > 0)
    return gtp_failure("kgs-genmove_cleanup cannot be called when stackp > 0");

  /* Turn on the capture_all_dead option to force removal of dead
   * opponent stones.
   */
  capture_all_dead = 1;
  
  adjust_level_offset(color);
  move = genmove(color, NULL, NULL);

  capture_all_dead = save_capture_all_dead;
  
  gnugo_play_move(move, color);

  gtp_start_response(GTP_SUCCESS);
  gtp_print_vertex(I(move), J(move));
  return gtp_finish_response();
}


/* Function : List the move reasons for a move.
 * Arguments: vertex
 * Fails:   : invalid vertex, occupied vertex
 * Returns  : list of move reasons (may be empty)
 */

static int
gtp_move_reasons(char *s)
{
  int i, j;
  if (!gtp_decode_coord(s, &i, &j))
    return gtp_failure("invalid coordinate");

  if (BOARD(i, j) != EMPTY)
    return gtp_failure("vertex must not be occupied");

  gtp_start_response(GTP_SUCCESS);
  if (list_move_reasons(gtp_output_file, POS(i, j)) == 0)
    gtp_printf("\n");
  gtp_printf("\n");
  return GTP_OK;
}

/* Function : Generate a list of all moves with values larger than zero in
 *            the previous genmove command.
 *            If no previous genmove command has been issued, the result
 *            of this command will be meaningless.
 * Arguments: none
 * Fails:   : never
 * Returns  : list of moves with values
 */

static int
gtp_all_move_values(char *s)
{
  UNUSED(s);
  gtp_start_response(GTP_SUCCESS);
  print_all_move_values(gtp_output_file);
  gtp_printf("\n");
  return GTP_OK;
}

/* Function : Generate a sorted list of the best moves in the previous genmove
 *            command.
 *            If no previous genmove command has been issued, the result
 *            of this command will be meaningless.
 * Arguments: none
 * Fails:   : never
 * Returns  : list of moves with weights
 */

/* FIXME: Don't we want the moves one per row? */
static int
gtp_top_moves(char *s)
{
  int k;
  UNUSED(s);
  gtp_start_response(GTP_SUCCESS);
  for (k = 0; k < 10; k++)
    if (best_move_values[k] > 0.0) {
      gtp_print_vertex(I(best_moves[k]), J(best_moves[k]));
      gtp_printf(" %.2f ", best_move_values[k]);
    }
  gtp_printf("\n\n");
  return GTP_OK;
}

/* Function : Generate a list of the best moves for white with weights
 * Arguments: none
 * Fails:   : never
 * Returns  : list of moves with weights
 */

static int
gtp_top_moves_white(char *s)
{
  int k;
  UNUSED(s);
  genmove(WHITE, NULL, NULL);
  gtp_start_response(GTP_SUCCESS);
  for (k = 0; k < 10; k++)
    if (best_move_values[k] > 0.0) {
      gtp_print_vertex(I(best_moves[k]), J(best_moves[k]));
      gtp_printf(" %.2f ", best_move_values[k]);
    }
  return gtp_finish_response();
}

/* Function : Generate a list of the best moves for black with weights
 * Arguments: none
 * Fails:   : never
 * Returns  : list of moves with weights
 */

static int
gtp_top_moves_black(char *s)
{
  int k;
  UNUSED(s);
  genmove(BLACK, NULL, NULL);
  gtp_start_response(GTP_SUCCESS);
  for (k = 0; k < 10; k++)
    if (best_move_values[k] > 0.0) {
      gtp_print_vertex(I(best_moves[k]), J(best_moves[k]));
      gtp_printf(" %.2f ", best_move_values[k]);
    }
  return gtp_finish_response();
}



/* Function:  Set the playing level.
 * Arguments: int
 * Fails:     incorrect argument
 * Returns:   nothing
 */
static int
gtp_set_level(char *s)
{
  int new_level;
  if (sscanf(s, "%d", &new_level) < 1)
    return gtp_failure("level not an integer");
  
  set_level(new_level);
  return gtp_success("");
}

/* Function:  Undo one move
 * Arguments: none
 * Fails:     If move history is too short.
 * Returns:   nothing
 *
 * Status:    GTP version 2 standard command.
 */

static int
gtp_undo(char *s)
{
  UNUSED(s);

  if (stackp > 0 || !undo_move(1))
    return gtp_failure("cannot undo");

  reset_engine();
  
  return gtp_success("");
}


/* Function:  Undo a number of moves
 * Arguments: optional int
 * Fails:     If move history is too short.
 * Returns:   nothing
 */

static int
gtp_gg_undo(char *s)
{
  int number_moves = 1;

  sscanf(s, "%d", &number_moves);

  if (number_moves < 0)
    return gtp_failure("can't undo a negative number of moves");

  if (stackp > 0 || !undo_move(number_moves))
    return gtp_failure("cannot undo");

  reset_engine();
  
  return gtp_success("");
}


/*****************
 * time handling *
 *****************/

/* Function:  Set time allowance
 * Arguments: int main_time, int byo_yomi_time, int byo_yomi_stones
 * Fails:     syntax error
 * Returns:   nothing
 *
 * Status:    GTP version 2 standard command.
 */

static int
gtp_time_settings(char *s)
{
  int main_time, byoyomi_time, byoyomi_stones;
  
  if (sscanf(s, "%d %d %d", &main_time, &byoyomi_time, &byoyomi_stones) < 3)
    return gtp_failure("not three integers");

  clock_settings(main_time, byoyomi_time, byoyomi_stones);
  return gtp_success("");
}


/* Function:  Report remaining time
 * Arguments: color color, int time, int stones
 * Fails:     syntax error
 * Returns:   nothing
 *
 * Status:    GTP version 2 standard command.
 */

static int
gtp_time_left(char *s)
{
  int color;
  int time;
  int stones;
  int n;

  n = gtp_decode_color(s, &color);
  if (!n)
    return gtp_failure("invalid color");
  
  if (sscanf(s+n, "%d %d", &time, &stones) < 2)
    return gtp_failure("time and stones not two integers");

  update_time_left(color, time, stones);
  
  return gtp_success("");
}


/***********
 * scoring *
 ***********/

static float final_score;
static enum dragon_status final_status[MAX_BOARD][MAX_BOARD];
static enum dragon_status status_numbers[6] = {ALIVE, DEAD, ALIVE_IN_SEKI,
					       WHITE_TERRITORY,
					       BLACK_TERRITORY, DAME};
static const char *status_names[6] = {"alive", "dead", "seki",
				      "white_territory", "black_territory",
				      "dame"};

/* Helper function. */
static void
finish_and_score_game(int seed)
{
  int move;
  int i, j;
  int next;
  int pass = 0;
  int moves = 0;
  int saved_board[MAX_BOARD][MAX_BOARD];
  struct board_state saved_pos;
  static int current_board[MAX_BOARD][MAX_BOARD];
  static int current_seed = -1;
  int cached_board = 1;

  if (current_seed != seed) {
    current_seed = seed;
    cached_board = 0;
  }

  for (i = 0; i < board_size; i++)
    for (j = 0; j < board_size; j++)
      if (BOARD(i, j) != current_board[i][j]) {
	current_board[i][j] = BOARD(i, j);
	cached_board = 0;
      }

  /* If this is exactly the same position as the one we analyzed the
   * last time, the contents of final_score and final_status are up to date.
   */
  if (cached_board)
    return;

  doing_scoring = 1;
  store_board(&saved_pos);

  /* Let black start if we have no move history. Otherwise continue
   * alternation.
   */
  if (get_last_player() == EMPTY)
    next = BLACK;
  else
    next = OTHER_COLOR(get_last_player());

  do {
    move = genmove_conservative(next, NULL);
    gnugo_play_move(move, next);
    if (move != PASS_MOVE) {
      pass = 0;
      moves++;
    }
    else
      pass++;

    next = OTHER_COLOR(next);
  } while (pass < 2 && moves < board_size * board_size);

  final_score = aftermath_compute_score(next, NULL);
  for (i = 0; i < board_size; i++)
    for (j = 0; j < board_size; j++) {
      final_status[i][j] = aftermath_final_status(next, POS(i, j));
      saved_board[i][j] = BOARD(i, j);
    }

  restore_board(&saved_pos);
  doing_scoring = 0;

  /* Update the status for vertices which were changed while finishing
   * the game, up to filling dame.
   */
  for (i = 0; i < board_size; i++)
    for (j = 0; j < board_size; j++) {
      if (BOARD(i, j) == saved_board[i][j])
	continue;

      if (BOARD(i, j) == EMPTY) {
	if (final_status[i][j] == ALIVE
	    || final_status[i][j] == ALIVE_IN_SEKI)
	  final_status[i][j] = DAME;
	else if (final_status[i][j] == DEAD) {
	  if (saved_board[i][j] == BLACK)
	    final_status[i][j] = WHITE_TERRITORY;
	  else
	    final_status[i][j] = BLACK_TERRITORY;
	}
      }
      else if (BOARD(i, j) == BLACK) {
	if (final_status[i][j] == WHITE_TERRITORY)
	  final_status[i][j] = DEAD;
	else if (final_status[i][j] == DAME)
	  final_status[i][j] = ALIVE_IN_SEKI;
	else if (final_status[i][j] == BLACK_TERRITORY)
	  final_status[i][j] = ALIVE;
	else
	  final_status[i][j] = DEAD;
      }
      else if (BOARD(i, j) == WHITE) {
	if (final_status[i][j] == BLACK_TERRITORY)
	  final_status[i][j] = DEAD;
	else if (final_status[i][j] == DAME)
	  final_status[i][j] = ALIVE_IN_SEKI;
	else if (final_status[i][j] == WHITE_TERRITORY)
	  final_status[i][j] = ALIVE;
	else
	  final_status[i][j] = DEAD;
      }
    }
}


/* Function:  Compute the score of a finished game.
 * Arguments: Optional random seed
 * Fails:     never
 * Returns:   Score in SGF format (RE property).
 *
 * Status:    GTP version 2 standard command.
 */
static int
gtp_final_score(char *s)
{
  unsigned int saved_random_seed = get_random_seed();
  int seed;
  /* This is intended for regression purposes and should therefore be
   * deterministic. The best way to ensure this is to reset the random
   * number generator before calling genmove(). By default it is
   * seeded with 0, but if an optional unsigned integer is given in
   * the command after the color, this is used as seed instead.
   */
  seed = 0;
  sscanf(s, "%d", &seed);
  set_random_seed(seed);

  finish_and_score_game(seed);

  set_random_seed(saved_random_seed);

  gtp_start_response(GTP_SUCCESS);
  if (final_score > 0.0)
    gtp_printf("W+%3.1f", final_score);
  else if (final_score < 0.0)
    gtp_printf("B+%3.1f", -final_score);
  else
    gtp_printf("0");
  return gtp_finish_response();
}


/* Function:  Report the final status of a vertex in a finished game.
 * Arguments: Vertex, optional random seed
 * Fails:     invalid vertex
 * Returns:   Status in the form of one of the strings "alive", "dead",
 *            "seki", "white_territory", "black_territory", or "dame".
 */
static int
gtp_final_status(char *s)
{
  int seed;
  int n;
  int ai, aj;
  int k;
  unsigned int saved_random_seed = get_random_seed();
  const char *result = NULL;

  n = gtp_decode_coord(s, &ai, &aj);
  if (n == 0)
    return gtp_failure("invalid coordinate");

  /* This is intended for regression purposes and should therefore be
   * deterministic. The best way to ensure this is to reset the random
   * number generator before calling genmove(). By default it is
   * seeded with 0, but if an optional unsigned integer is given in
   * the command after the color, this is used as seed instead.
   */
  seed = 0;
  sscanf(s + n, "%d", &seed);
  set_random_seed(seed);

  finish_and_score_game(seed);

  set_random_seed(saved_random_seed);
  for (k = 0; k < 6; k++)
    if (final_status[ai][aj] == status_numbers[k]) {
      result = status_names[k];
      break;
    }
  assert(result != NULL);

  return gtp_success(result);
}


/* Function:  Report vertices with a specific final status in a finished game.
 * Arguments: Status in the form of one of the strings "alive", "dead",
 *            "seki", "white_territory", "black_territory", or "dame".
 *            An optional random seed can be added.
 * Fails:     missing or invalid status string
 * Returns:   Vertices having the specified status. These are split with
 *            one string on each line if the vertices are nonempty (i.e.
 *            for "alive", "dead", and "seki").
 *
 * Status:    GTP version 2 standard command.
 *            However, "dame", "white_territory", and "black_territory"
 *            are private extensions.
 */
static int
gtp_final_status_list(char *s)
{
  int seed;
  int n;
  int i, j;
  enum dragon_status status = UNKNOWN;
  int k;
  char status_string[GTP_BUFSIZE];
  int first;
  unsigned int saved_random_seed = get_random_seed();

  if (sscanf(s, "%s %n", status_string, &n) != 1)
    return gtp_failure("missing status");
  
  for (k = 0; k < 6; k++) {
    if (strcmp(status_string, status_names[k]) == 0)
      status = status_numbers[k];
  }

  if (status == UNKNOWN)
    return gtp_failure("invalid status");

  /* This is intended for regression purposes and should therefore be
   * deterministic. The best way to ensure this is to reset the random
   * number generator before calling genmove(). By default it is
   * seeded with 0, but if an optional unsigned integer is given in
   * the command after the color, this is used as seed instead.
   */
  seed = 0;
  sscanf(s + n, "%d", &seed);
  set_random_seed(seed);

  finish_and_score_game(seed);

  set_random_seed(saved_random_seed);

  gtp_start_response(GTP_SUCCESS);

  first = 1;
  for (i = 0; i < board_size; i++)
    for (j = 0; j < board_size; j++) {
      if (final_status[i][j] != status)
	continue;
      if (BOARD(i, j) == EMPTY) {
	if (!first)
	  gtp_printf(" ");
	else
	  first = 0;
	gtp_print_vertex(i, j);
      }
      else {
	int num_stones;
	int stones[MAX_BOARD * MAX_BOARD];
	if (find_origin(POS(i, j)) != POS(i, j))
	  continue;
	if (!first)
	  gtp_printf("\n");
	else
	  first = 0;
	num_stones = findstones(POS(i, j), board_size * board_size, stones);
	gtp_print_vertices2(num_stones, stones);
      }
    }

  return gtp_finish_response();
}

/* Function:  Estimate the score
 * Arguments: None
 * Fails:     never
 * Returns:   upper and lower bounds for the score
 */

static int
gtp_estimate_score(char *s)
{
  float score;
  float upper_bound, lower_bound;
  UNUSED(s);

  score = gnugo_estimate_score(&upper_bound, &lower_bound);
  gtp_start_response(GTP_SUCCESS);
  /* Traditionally W wins jigo */
  if (score >= 0.0) 
    gtp_printf("W+%3.1f (upper bound: %3.1f, lower: %3.1f)", 
	       score, upper_bound, lower_bound);
  else if (score < 0.0)
    gtp_printf("B+%3.1f (upper bound: %3.1f, lower: %3.1f)", 
	       -score, upper_bound, lower_bound);
  return gtp_finish_response();
}  

/* Function:  Estimate the score, taking into account which player moves next
 * Arguments: Color to play
 * Fails:     Invalid color
 * Returns:   Score.
 *
 * This function generates a move for color, then adds the
 * value of the move generated to the value of the position.
 * Critical dragons are awarded to the opponent since the
 * value of rescuing a critical dragon is taken into account
 * in the value of the move generated.
 */

static int
gtp_experimental_score(char *s)
{
  float upper_bound, lower_bound, score;
  int color;

  if (!gtp_decode_color(s, &color)
      || (color != BLACK && color != WHITE))
    return gtp_failure("invalid color");

  genmove_conservative(color, NULL);
  gnugo_estimate_score(&upper_bound, &lower_bound);

  if (debug & DEBUG_SCORING)
    fprintf(stderr, "upper = %3.1f, lower = %3.1f, best = %3.1f\n",
	    upper_bound, lower_bound, best_move_values[0]);
  if (color == WHITE)
    score = lower_bound + best_move_values[0];
  else
    score = upper_bound - best_move_values[0];

  return gtp_success("%3.1f", score);
}  


/**************
 * statistics *
 **************/

/* Function:  Reset the count of life nodes.
 * Arguments: none
 * Fails:     never
 * Returns:   nothing
 *
 * Note: This function is obsolete and only remains for backwards
 * compatibility.
 */
static int
gtp_reset_life_node_counter(char *s)
{
  UNUSED(s);
  return gtp_success("");
}


/* Function:  Retrieve the count of life nodes.
 * Arguments: none
 * Fails:     never
 * Returns:   number of life nodes
 *
 * Note: This function is obsolete and only remains for backwards
 * compatibility.
 */
static int
gtp_get_life_node_counter(char *s)
{
  UNUSED(s);
  return gtp_success("0");
}


/* Function:  Reset the count of owl nodes.
 * Arguments: none
 * Fails:     never
 * Returns:   nothing
 */
static int
gtp_reset_owl_node_counter(char *s)
{
  UNUSED(s);
  reset_owl_node_counter();
  return gtp_success("");
}


/* Function:  Retrieve the count of owl nodes.
 * Arguments: none
 * Fails:     never
 * Returns:   number of owl nodes
 */
static int
gtp_get_owl_node_counter(char *s)
{
  int nodes = get_owl_node_counter();
  UNUSED(s);
  return gtp_success("%d", nodes);
}


/* Function:  Reset the count of reading nodes.
 * Arguments: none
 * Fails:     never
 * Returns:   nothing
 */
static int
gtp_reset_reading_node_counter(char *s)
{
  UNUSED(s);
  reset_reading_node_counter();
  return gtp_success("");
}


/* Function:  Retrieve the count of reading nodes.
 * Arguments: none
 * Fails:     never
 * Returns:   number of reading nodes
 */
static int
gtp_get_reading_node_counter(char *s)
{
  int nodes = get_reading_node_counter();
  UNUSED(s);
  return gtp_success("%d", nodes);
}


/* Function:  Reset the count of trymoves/trykos.
 * Arguments: none
 * Fails:     never
 * Returns:   nothing
 */
static int
gtp_reset_trymove_counter(char *s)
{
  UNUSED(s);
  reset_trymove_counter();
  return gtp_success("");
}


/* Function:  Retrieve the count of trymoves/trykos.
 * Arguments: none
 * Fails:     never
 * Returns:   number of trymoves/trykos
 */
static int
gtp_get_trymove_counter(char *s)
{
  int nodes = get_trymove_counter();
  UNUSED(s);
  return gtp_success("%d", nodes);
}


/* Function:  Reset the count of connection nodes.
 * Arguments: none
 * Fails:     never
 * Returns:   nothing
 */
static int
gtp_reset_connection_node_counter(char *s)
{
  UNUSED(s);
  reset_connection_node_counter();
  return gtp_success("");
}


/* Function:  Retrieve the count of connection nodes.
 * Arguments: none
 * Fails:     never
 * Returns:   number of connection nodes
 */
static int
gtp_get_connection_node_counter(char *s)
{
  int nodes = get_connection_node_counter();
  UNUSED(s);
  return gtp_success("%d", nodes);
}



/*********
 * debug *
 *********/


/* Function:  Test an eyeshape for inconsistent evaluations
 * Arguments: Eyeshape vertices
 * Fails:     Bad vertices
 * Returns:   Failure reports on stderr.
 */
static int
gtp_test_eyeshape(char *s)
{
  int n;
  int i, j;
  int eye_vertices[MAX_BOARD * MAX_BOARD];
  int eyesize = 0;

  n = gtp_decode_coord(s, &i, &j);
  while (n > 0) {
    eye_vertices[eyesize] = POS(i, j);
    eyesize++;
    s += n;
    n = gtp_decode_coord(s, &i, &j);
  }
  
  if (eyesize == 0)
    return gtp_failure("invalid coordinate");

  test_eyeshape(eyesize, eye_vertices);

  return gtp_success("");
}


/* Function:  Compute an eyevalue and vital points for an eye graph
 * Arguments: Eyeshape encoded in string OR two integers followed by eyeshape
 * Fails:     Bad eyeshape, analysis failed
 * Returns:   Eyevalue, vital points
 */
static int
gtp_analyze_eyegraph(char *s)
{
  struct eyevalue value;
  int outer_liberties;
  int ko_threats;
  int n = 0;
  char analyzed_eyegraph[1024];
  int result;

  if (sscanf(s, "%d %d %n", &outer_liberties, &ko_threats, &n) < 2) {
    outer_liberties = 4;
    ko_threats = 1;
  }

  result = analyze_eyegraph(s + n, &value, analyzed_eyegraph,
			    outer_liberties, ko_threats);

  if (result == 0)
    return gtp_failure("failed to analyze");

  return gtp_success("%s\n%s", eyevalue_to_string(&value), analyzed_eyegraph);
}



/* Function:  Returns elapsed CPU time in seconds.
 * Arguments: none
 * Fails:     never
 * Returns:   Total elapsed (user + system) CPU time in seconds.
 */
static int
gtp_cputime(char *s)
{
  UNUSED(s);
  return gtp_success("%.3f", gg_cputime());
}



/* Function:  Write the position to stdout.
 * Arguments: none
 * Fails:     never
 * Returns:   nothing
 *
 * Status:    GTP version 2 standard command.
 */
static int
gtp_showboard(char *s)
{
  UNUSED(s);
  
  gtp_start_response(GTP_SUCCESS);
  gtp_printf("\n");
  simple_showboard(gtp_output_file);
  return gtp_finish_response();
}


/* Function:  Dump stack to stderr.
 * Arguments: none
 * Fails:     never
 * Returns:   nothing
 */
static int
gtp_dump_stack(char *s)
{
  UNUSED(s);
  dump_stack();
  return gtp_success("");
}

/* Determine whether a string starts with a specific substring. */
static int
has_prefix(const char *s, const char *prefix)
{
  return strncmp(s, prefix, strlen(prefix)) == 0;
}

static int
print_influence_data(struct influence_data *q, char *what_data)
{
  float white_influence[BOARDMAX];
  float black_influence[BOARDMAX];
  float white_strength[BOARDMAX];
  float black_strength[BOARDMAX];
  float white_attenuation[BOARDMAX]; 
  float black_attenuation[BOARDMAX];
  float white_permeability[BOARDMAX];
  float black_permeability[BOARDMAX];
  float territory_value[BOARDMAX];
  int influence_regions[BOARDMAX];
  int non_territory[BOARDMAX];
  int m, n;
  
  float *float_pointer = NULL;
  int *int_pointer = NULL;
  
  while (*what_data == ' ')
    what_data++;

  get_influence(q, white_influence, black_influence,
		white_strength, black_strength,
		white_attenuation, black_attenuation,
		white_permeability, black_permeability,
		territory_value, influence_regions, non_territory);

  if (has_prefix(what_data, "white_influence"))
    float_pointer = white_influence;
  else if (has_prefix(what_data, "black_influence"))
    float_pointer = black_influence;
  else if (has_prefix(what_data, "white_strength"))
    float_pointer = white_strength;
  else if (has_prefix(what_data, "black_strength"))
    float_pointer = black_strength;
  else if (has_prefix(what_data, "white_attenuation"))
    float_pointer = white_attenuation;
  else if (has_prefix(what_data, "black_attenuation"))
    float_pointer = black_attenuation;
  else if (has_prefix(what_data, "white_permeability"))
    float_pointer = white_permeability;
  else if (has_prefix(what_data, "black_permeability"))
    float_pointer = black_permeability;
  else if (has_prefix(what_data, "territory_value"))
    float_pointer = territory_value;
  else if (has_prefix(what_data, "influence_regions"))
    int_pointer = influence_regions;
  else if (has_prefix(what_data, "non_territory"))
    int_pointer = non_territory;
  else
    return gtp_failure("unknown influence data");
  
  gtp_start_response(GTP_SUCCESS);
  for (m = 0; m < board_size; m++) {
    for (n = 0; n < board_size; n++) {
      if (float_pointer)
	gtp_printf("%6.2f ", float_pointer[POS(m, n)]);
      else
	gtp_printf("%2d ", int_pointer[POS(m, n)]);
    }
    gtp_printf("\n");
  }
  
  /* We already have one newline and thus can't use gtp_finish_response(). */
  gtp_printf("\n");
  return GTP_OK;
}

/* Function:  Return information about the initial influence function.
 * Arguments: color to move, what information
 * Fails:     never
 * Returns:   Influence data formatted like:
 *
 *   0.51   1.34   3.20   6.60   9.09   8.06   1.96   0.00   0.00 
 *   0.45   1.65   4.92  12.19  17.47  15.92   4.03   0.00   0.00 
 *                   .
 *                   .
 *                   .
 *   0.00   0.00   0.00   0.00   0.00 100.00  75.53  41.47  23.41
 *
 * The available choices of information are:
 * 
 * white_influence (float)
 * black_influence (float)
 * white_strength (float)
 * black_strength (float)
 * white_attenuation (float)
 * black_attenuation (float)
 * white_permeability (float)
 * black_permeability (float)
 * territory_value (float)
 * influence_regions (int)
 * non_territory (int)
 *
 * The encoding of influence_regions is as follows:
 *  4 white stone
 *  3 white territory
 *  2 white moyo
 *  1 white area
 *  0 neutral
 * -1 black area
 * -2 black moyo
 * -3 black territory
 * -4 black stone
 */
static int
gtp_initial_influence(char *s)
{
  int color;
  struct influence_data *q;
  int n;

  n = gtp_decode_color(s, &color);
  if (n == 0)
    return gtp_failure("invalid color");

  q = INITIAL_INFLUENCE(color);
  
  silent_examine_position(EXAMINE_ALL);

  return print_influence_data(q, s + n);
}


/* Function:  Return information about the influence function after a move.
 * Arguments: move, what information
 * Fails:     never
 * Returns:   Influence data formatted like for initial_influence.
 */
static int
gtp_move_influence(char *s)
{
  int color;
  int i, j;
  int n;

  n = gtp_decode_move(s, &color, &i, &j);
  if (n == 0)
    return gtp_failure("invalid move");

  prepare_move_influence_debugging(POS(i, j), color);
  
  return print_influence_data(&move_influence, s + n);
}


/* Function:  List probabilities of each move being played (when non-zero).
 *            If no previous genmove command has been issued, the result
 *            of this command will be meaningless.
 * Arguments: none
 * Fails:     never
 * Returns:   Move, probabilty pairs, one per row.
 */
static int
gtp_move_probabilities(char *s)
{
  float probabilities[BOARDMAX];
  int pos;
  int any_moves_printed = 0;

  UNUSED(s);

  compute_move_probabilities(probabilities);

  gtp_start_response(GTP_SUCCESS);
  for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
    if (ON_BOARD(pos) && probabilities[pos] != 0.0) {
      gtp_mprintf("%m ", I(pos), J(pos));
      gtp_printf("%.4f\n", probabilities[pos]);
      any_moves_printed = 1;
    }
  }

  if (!any_moves_printed)
    gtp_printf("\n");
  gtp_printf("\n");

  return GTP_OK;
}


/* Function:  Return the number of bits of uncertainty in the move.
 *            If no previous genmove command has been issued, the result
 *            of this command will be meaningless.
 * Arguments: none
 * Fails:     never
 * Returns:   bits of uncertainty
 */
static int
gtp_move_uncertainty(char *s)
{
  float probabilities[BOARDMAX];
  int pos;
  double uncertainty = 0.0;

  UNUSED(s);

  compute_move_probabilities(probabilities);

  gtp_start_response(GTP_SUCCESS);
  for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
    if (ON_BOARD(pos) && probabilities[pos] > 0.0) {
      /* Shannon's formula */
      uncertainty += -1 * ((double)probabilities[pos]) *
	log((double)probabilities[pos]) / log(2.0);
    }
  }

  gtp_printf("%.4f\n\n", uncertainty);

  return GTP_OK;
}



/* Function:  Return information about the followup influence after a move.
 * Arguments: move, what information
 * Fails:     never
 * Returns:   Influence data formatted like for initial_influence.
 */
static int
gtp_followup_influence(char *s)
{
  int color;
  int i, j;
  int n;

  n = gtp_decode_move(s, &color, &i, &j);
  if (n == 0)
    return gtp_failure("invalid move");

  prepare_move_influence_debugging(POS(i, j), color);
  
  return print_influence_data(&followup_influence, s + n);
}


/* Function:  Return the information in the worm data structure.
 * Arguments: optional vertex
 * Fails:     never
 * Returns:   Worm data formatted like:
 *
 * A19:
 * color           black
 * size            10
 * effective_size  17.83
 * origin          A19
 * liberties       8
 * liberties2      15
 * liberties3      10
 * liberties4      8
 * attack          PASS
 * attack_code     0
 * lunch           B19
 * defend          PASS
 * defend_code     0
 * cutstone        2
 * cutstone2       0
 * genus           0
 * inessential     0
 * B19:
 * color           white
 * .
 * .
 * .
 * inessential     0
 * C19:
 * ...
 *
 * If an intersection is specified, only data for this one will be returned.
 */
static int
gtp_worm_data(char *s)
{
  int i = -1;
  int j = -1;
  int m, n;

  if (sscanf(s, "%*c") >= 0 && !gtp_decode_coord(s, &i, &j))
    return gtp_failure("invalid color or coordinate");

  silent_examine_position(EXAMINE_WORMS);

  gtp_start_response(GTP_SUCCESS);
  
  for (m = 0; m < board_size; m++)
    for (n = 0; n < board_size; n++)
      if (i == -1 || (m == i && n == j)) {
	struct worm_data *w = &worm[POS(m, n)];
	gtp_print_vertex(m, n);
	gtp_printf(":\n");
	gtp_mprintf("origin               %m\n",  I(w->origin), J(w->origin));
	gtp_mprintf("color                %C\n",  w->color);
	gtp_printf("size                 %d\n",   w->size);
	gtp_printf("effective_size       %.2f\n", w->effective_size);
	gtp_printf("liberties            %d\n",   w->liberties);
	gtp_printf("liberties2           %d\n",   w->liberties2);
	gtp_printf("liberties3           %d\n",   w->liberties3);
	gtp_printf("liberties4           %d\n",   w->liberties4);
	gtp_printf("attack_code          %d\n",   w->attack_codes[0]);
	gtp_mprintf("attack_point         %m\n",  
		    I(w->attack_points[0]), J(w->attack_points[0]));
	gtp_printf("defense_code         %d\n",   w->defense_codes[0]);
	gtp_mprintf("defense_point        %m\n",  
		    I(w->defense_points[0]), J(w->defense_points[0]));
	gtp_mprintf("lunch                %m\n",  
		    I(w->lunch), J(w->lunch));
	gtp_printf("cutstone             %d\n",   w->cutstone);
	gtp_printf("cutstone2            %d\n",   w->cutstone2);
	gtp_printf("genus                %d\n",   w->genus);
	gtp_printf("inessential          %d\n",   w->inessential);
	gtp_printf("invincible           %d\n",   w->invincible);
	gtp_printf("unconditional_status %s\n",
		   status_to_string(w->unconditional_status));
      }
  
  gtp_printf("\n");
  return GTP_OK;
}

/* Function:  List the stones of a worm
 * Arguments: the location, "BLACK" or "WHITE"
 * Fails:     if called on an empty or off-board location
 * Returns:   list of stones
 */
static int
gtp_worm_stones(char *s)
{
  int i = -1;
  int j = -1;
  int color = EMPTY;
  int m, n;
  int u, v;
  int board_empty = 1;

  if (sscanf(s, "%*c") >= 0) {
    if (!gtp_decode_coord(s, &i, &j)
	&& !gtp_decode_color(s, &color))
      return gtp_failure("invalid coordinate");
  }
    
  if (BOARD(i, j) == EMPTY)
    return gtp_failure("worm_stones called on an empty vertex");

  gtp_start_response(GTP_SUCCESS);
  
  for (u = 0; u < board_size; u++)
    for (v = 0; v < board_size; v++) {
      if (BOARD(u, v) == EMPTY
	  || (color != EMPTY && BOARD(u, v) != color))
	continue;
      board_empty = 0;
      if (find_origin(POS(u, v)) != POS(u, v))
	continue;
      if (ON_BOARD2(i, j) 
	  && !same_string(POS(u, v), POS(i, j)))
	continue;
      for (m = 0; m < board_size; m++)
	for (n = 0; n < board_size; n++)
	  if (BOARD(m, n) != EMPTY
	      && same_string(POS(m, n), POS(u, v)))
	    gtp_mprintf("%m ", m, n);
      gtp_printf("\n");
    }
  
  if (board_empty) 
    gtp_printf("\n"); /* in case no stones have been printed */
  gtp_printf("\n");
  return GTP_OK;
}



/* Function:  Return the cutstone field in the worm data structure.
 * Arguments: non-empty vertex
 * Fails:     never
 * Returns:   cutstone
 */
static int
gtp_worm_cutstone(char *s)
{

  int i, j;
  if (!gtp_decode_coord(s, &i, &j))
    return gtp_failure("invalid coordinate");

  if (BOARD(i, j) == EMPTY)
    return gtp_failure("vertex must not be empty");

  silent_examine_position(EXAMINE_WORMS);

  return gtp_success(" %d", worm[POS(i, j)].cutstone);
}

/* Function:  Return the information in the dragon data structure.
 * Arguments: optional intersection
 * Fails:     never
 * Returns:   Dragon data formatted in the corresponding way to gtp_worm_data.
 */
static int
gtp_dragon_data(char *s)
{
  int i = -1;
  int j = -1;
  int m, n;
  int newline_needed = 0;

  if (sscanf(s, "%*c") >= 0 && !gtp_decode_coord(s, &i, &j))
    return gtp_failure("invalid coordinate");

  if (stackp > 0)
    return gtp_failure("dragon data unavailable when stackp > 0");

  silent_examine_position(FULL_EXAMINE_DRAGONS);

  gtp_start_response(GTP_SUCCESS);

  if (ON_BOARD2(i, j) && BOARD(i, j) == EMPTY)
    gtp_mprintf("%m empty\n", i, j);
  else {
    newline_needed = 1;
    for (m = 0; m < board_size; m++)
      for (n = 0; n < board_size; n++)
	if ((m == i && n == j)
	    || (i == -1
		&& BOARD(m, n) != EMPTY
		&& dragon[POS(m, n)].origin == POS(m, n))) {
	  gtp_print_vertex(m, n);
	  gtp_printf(":\n");
	  report_dragon(gtp_output_file, POS(m, n));
	  newline_needed = 0;
	}
  }
  if (newline_needed)
    gtp_printf("\n");
  gtp_printf("\n");
  return GTP_OK;
}

/* Function:  List the stones of a dragon
 * Arguments: the location
 * Fails:     if called on an empty or off-board location
 * Returns:   list of stones
 */
static int
gtp_dragon_stones(char *s)
{
  int i = -1;
  int j = -1;
  int color = EMPTY;
  int m, n;
  int u, v;

  if (sscanf(s, "%*c") >= 0) {
    if (!gtp_decode_coord(s, &i, &j)
	&& !gtp_decode_color(s, &color))
    return gtp_failure("invalid coordinate");
  }

  if (BOARD(i, j) == EMPTY)
    return gtp_failure("dragon_stones called on an empty vertex");

  silent_examine_position(EXAMINE_DRAGONS);

  gtp_start_response(GTP_SUCCESS);

  
  for (u = 0; u < board_size; u++)
    for (v = 0; v < board_size; v++) {
      if (BOARD(u, v) == EMPTY
	  || (color != EMPTY && BOARD(u, v) != color))
	continue;
      if (dragon[POS(u, v)].origin != POS(u, v))
	continue;
      if (ON_BOARD2(i, j) && dragon[POS(i, j)].origin != POS(u, v))
	continue;
      for (m = 0; m < board_size; m++)
	for (n = 0; n < board_size; n++)
	  if (dragon[POS(m, n)].origin == POS(u, v))
	    gtp_mprintf("%m ", m, n);
      gtp_printf("\n");
    }
  
  gtp_printf("\n");
  return GTP_OK;
}

/* Function:  Return the information in the eye data structure.
 * Arguments: color, vertex
 * Fails:     never
 * Returns:   eye data fields and values, one pair per row
 */
static int
gtp_eye_data(char *s)
{
  int color = EMPTY;
  int i = -1;
  int j = -1;
  struct eye_data *e;

  if (!gtp_decode_move(s, &color, &i, &j))
    return gtp_failure("invalid color or coordinate");

  if (stackp > 0)
    return gtp_failure("eye data unavailable when stackp > 0");

  silent_examine_position(EXAMINE_DRAGONS_WITHOUT_OWL);

  gtp_start_response(GTP_SUCCESS);

  if (color == BLACK)
    e = &black_eye[POS(i, j)];
  else
    e = &white_eye[POS(i, j)];
  
  gtp_mprintf("origin               %m\n", I(e->origin), J(e->origin));
  gtp_mprintf("color                %C\n", e->color);
  gtp_printf("esize                %d\n", e->esize);
  gtp_printf("msize                %d\n", e->msize);
  gtp_printf("value                %s\n", eyevalue_to_string(&e->value));
  gtp_printf("marginal             %d\n", e->marginal);
  gtp_printf("neighbors            %d\n", e->neighbors);
  gtp_printf("marginal_neighbors   %d\n", e->marginal_neighbors);
  
  gtp_printf("\n");
  return GTP_OK;
}


/* Function:  Return the information in the half eye data structure.
 * Arguments: vertex
 * Fails:     never
 * Returns:   half eye data fields and values, one pair per row
 */
static int
gtp_half_eye_data(char *s)
{
  int i = -1;
  int j = -1;
  struct half_eye_data *h;
  int k;

  if (!gtp_decode_coord(s, &i, &j))
    return gtp_failure("invalid coordinate");

  if (stackp > 0)
    return gtp_failure("half eye data unavailable when stackp > 0");

  silent_examine_position(EXAMINE_DRAGONS_WITHOUT_OWL);

  gtp_start_response(GTP_SUCCESS);

  h = &half_eye[POS(i, j)];
  
  gtp_printf("value                %.2f\n", h->value);
  if (h->type == HALF_EYE)
    gtp_printf("type                 HALF_EYE\n");
  else if (h->type == FALSE_EYE)
    gtp_printf("type                 FALSE_EYE\n");
  else
    gtp_printf("type                 %d\n", h->type);
  gtp_printf("num_attacks          %d\n", h->num_attacks);
  for (k = 0; k < h->num_attacks; k++)
    gtp_mprintf("attack_point[%d]      %m\n", k, I(h->attack_point[k]),
		J(h->attack_point[k]));
  gtp_printf("num_defenses         %d\n", h->num_defenses);
  for (k = 0; k < h->num_defenses; k++)
    gtp_mprintf("defense_point[%d]     %m\n", k, I(h->defense_point[k]),
		J(h->defense_point[k]));
  
  gtp_printf("\n");
  return GTP_OK;
}


static SGFTree gtp_sgftree;

/* Function:  Start storing moves executed during reading in an sgf
 *            tree in memory. 
 * Arguments: none
 * Fails:     never
 * Returns:   nothing
 *
 * Warning: You had better know what you're doing if you try to use this
 *          command.
 */
static int
gtp_start_sgftrace(char *s)
{
  UNUSED(s);
  sgffile_begindump(&gtp_sgftree);
  count_variations = 1;
  return gtp_success("");
}


/* Function:  Finish storing moves in an sgf tree and write it to file. 
 * Arguments: filename
 * Fails:     never
 * Returns:   nothing
 *
 * Warning: You had better know what you're doing if you try to use this
 *          command.
 */
static int
gtp_finish_sgftrace(char *s)
{
  char filename[GTP_BUFSIZE];
  int nread;
  
  nread = sscanf(s, "%s", filename);
  if (nread < 1)
    return gtp_failure("missing filename");

  sgffile_enddump(filename);
  count_variations = 0;
  return gtp_success("");
}


/* Function:  Dump the current position as a static sgf file to filename,
 *            or as output if filename is missing or "-" 
 * Arguments: optional filename
 * Fails:     never
 * Returns:   nothing if filename, otherwise the sgf
 */
static int
gtp_printsgf(char *s)
{
  char filename[GTP_BUFSIZE];
  int nread;
  int next;
  
  if (get_last_player() == EMPTY)
    next = BLACK;
  else
    next = OTHER_COLOR(get_last_player());

  nread = sscanf(s, "%s", filename);

  if (nread < 1)
    gg_snprintf(filename, GTP_BUFSIZE, "%s", "-");

  if (strcmp(filename, "-") == 0) {
    gtp_start_response(GTP_SUCCESS);
    sgffile_printsgf(next, filename);
    gtp_printf("\n");
    return GTP_OK;
  }
  else {
    sgffile_printsgf(next, filename);
    return gtp_success("");
  }
}


/* Function:  Tune the parameters for the move ordering in the tactical
 *            reading.
 * Arguments: MOVE_ORDERING_PARAMETERS integers
 * Fails:     incorrect arguments
 * Returns:   nothing
 */
static int
gtp_tune_move_ordering(char *s)
{
  int params[MOVE_ORDERING_PARAMETERS];
  int k;
  int p;
  int n;

  for (k = 0; k < MOVE_ORDERING_PARAMETERS; k++) {
    if (sscanf(s, "%d%n", &p, &n) == 0)
      return gtp_failure("incorrect arguments, expected %d integers",
			 MOVE_ORDERING_PARAMETERS);
    params[k] = p;
    s += n;
  }

  tune_move_ordering(params);
  return gtp_success("");
}

/* Function:  Echo the parameter
 * Arguments: string
 * Fails:     never
 * Returns:   nothing
 */
static int
gtp_echo(char *s)
{
  return gtp_success("%s", s);
}


/* Function:  Echo the parameter to stdout AND stderr
 * Arguments: string
 * Fails:     never
 * Returns:   nothing
 */
static int
gtp_echo_err(char *s)
{
  fprintf(stderr, "%s", s);
  fflush(gtp_output_file);
  fflush(stderr);
  return gtp_success("%s", s);
}

/* Function:  List all known commands
 * Arguments: none
 * Fails:     never
 * Returns:   list of known commands, one per line
 *
 * Status:    GTP version 2 standard command.
 */
static int
gtp_list_commands(char *s)
{
  int k;
  UNUSED(s);

  gtp_start_response(GTP_SUCCESS);

  for (k = 0; commands[k].name != NULL; k++)
    gtp_printf("%s\n", commands[k].name);

  gtp_printf("\n");
  return GTP_OK;
}


/* Function:  Tell whether a command is known.
 * Arguments: command name
 * Fails:     never
 * Returns:   "true" if command exists, "false" if not
 *
 * Status:    GTP version 2 standard command.
 */
static int
gtp_known_command(char *s)
{
  int k;
  char command[GTP_BUFSIZE];

  if (sscanf(s, "%s", command) == 1) {
    for (k = 0; commands[k].name != NULL; k++)
      if (strcmp(command, commands[k].name) == 0)
	return gtp_success("true");
  }

  return gtp_success("false");
}


/* Function:  Turn uncertainty reports from owl_attack
 *            and owl_defend on or off.
 * Arguments: "on" or "off"
 * Fails:     invalid argument
 * Returns:   nothing
 */
static int
gtp_report_uncertainty(char *s)
{
  if (!strncmp(s, "on", 2)) {
    report_uncertainty = 1;
    return gtp_success("");
  }
  if (!strncmp(s, "off", 3)) {
    report_uncertainty = 0;
    return gtp_success("");
  }
  return gtp_failure("invalid argument");
}
    

static void
gtp_print_code(int c)
{
  static int conversion[6] = { 
    0, /* LOSE */
    3, /* KO_B */
    5, /* LOSS */
    4, /* GAIN */
    2, /* KO_A */
    1, /* WIN  */
  };
  gtp_printf("%d", conversion[c]);
}

static void
gtp_print_vertices2(int n, int *moves)
{
  int movei[MAX_BOARD * MAX_BOARD];
  int movej[MAX_BOARD * MAX_BOARD];
  int k;

  for (k = 0; k < n; k++) {
    movei[k] = I(moves[k]);
    movej[k] = J(moves[k]);
  }
  
  gtp_print_vertices(n, movei, movej);
}

/*************
 * transform *
 *************/

static void
rotate_on_input(int ai, int aj, int *bi, int *bj)
{
  rotate(ai, aj, bi, bj, board_size, gtp_orientation);
}

static void
rotate_on_output(int ai, int aj, int *bi, int *bj)
{
  inv_rotate(ai, aj, bi, bj, board_size, gtp_orientation);
}


/***************
 * random seed *
 ***************/

/* Function:  Get the random seed
 * Arguments: none
 * Fails:     never
 * Returns:   random seed
 */
static int
gtp_get_random_seed(char *s)
{
  UNUSED(s);
  return gtp_success("%d", get_random_seed());
}

/* Function:  Set the random seed
 * Arguments: integer
 * Fails:     invalid data
 * Returns:   nothing
 */
static int
gtp_set_random_seed(char *s)
{
  int seed;
  if (sscanf(s, "%d", &seed) < 1)
    return gtp_failure("invalid seed");
  
  set_random_seed(seed);
  return gtp_success("");
}


/* Function:  Advance the random seed by a number of games.
 * Arguments: integer
 * Fails:     invalid data
 * Returns:   New random seed.
 */
static int
gtp_advance_random_seed(char *s)
{
  int i;
  int games;
  if (sscanf(s, "%d", &games) < 1
      || games < 0)
    return gtp_failure("invalid number of games");
  
  for (i = 0; i < games; i++)
    update_random_seed();

  return gtp_success("%d", get_random_seed());
}

/***************
 * surrounding *
 ***************/

/* Function:  Determine if a dragon is surrounded
 * Arguments: vertex (dragon)
 * Fails:     invalid vertex, empty vertex
 * Returns:   1 if surrounded, 2 if weakly surrounded, 0 if not
 */
static int
gtp_is_surrounded(char *s)
{
  int i, j;
  int n;

  n = gtp_decode_coord(s, &i, &j);
  if (n == 0)
    return gtp_failure("invalid coordinate");

  if (BOARD(i, j) == EMPTY)
    return gtp_failure("dragon vertex must be nonempty");

  silent_examine_position(EXAMINE_DRAGONS);
  return gtp_success("%d", DRAGON2(POS(i, j)).surround_status);
}

/* Function:  Determine if a move surrounds a dragon
 * Arguments: vertex (move), vertex (dragon)
 * Fails:     invalid vertex, empty (dragon, nonempty (move)
 * Returns:   1 if (move) surrounds (dragon)
 */
static int
gtp_does_surround(char *s)
{
  int si, sj, di, dj;
  int n;

  n = gtp_decode_coord(s, &si, &sj);
  if (n == 0)
    return gtp_failure("invalid coordinate");

  if (BOARD(si, sj) != EMPTY)
    return gtp_failure("move vertex must be empty");

  n = gtp_decode_coord(s + n, &di, &dj);
  if (n == 0)
    return gtp_failure("invalid coordinate");

  if (BOARD(di, dj) == EMPTY)
    return gtp_failure("dragon vertex must be nonempty");

  silent_examine_position(EXAMINE_DRAGONS);
  return gtp_success("%d", does_surround(POS(si, sj), POS(di, dj)));
}

/* Function:  Report the surround map for dragon at a vertex
 * Arguments: vertex (dragon), vertex (mapped location)
 * Fails:     invalid vertex, empty dragon
 * Returns:   value of surround map at (mapped location), or -1 if
 *            dragon not surrounded.
 */

static int
gtp_surround_map(char *s)
{
  int di, dj, mi, mj;
  int n;

  n = gtp_decode_coord(s, &di, &dj);
  if (n == 0)
    return gtp_failure("invalid coordinate");

  if (BOARD(di, dj) == EMPTY)
    return gtp_failure("dragon vertex must not be empty");

  n = gtp_decode_coord(s + n, &mi, &mj);
  if (n == 0)
    return gtp_failure("invalid coordinate");

  silent_examine_position(EXAMINE_DRAGONS);
  return gtp_success("%d", surround_map(POS(di, dj), POS(mi, mj)));
}

/***************
 * search area *
 ***************/

/* Function:  limit search, and establish a search diamond
 * Arguments: pos
 * Fails:     invalid value
 * Returns:   nothing
 */
static int
gtp_set_search_diamond(char *s)
{
  int i, j;

  if (!gtp_decode_coord(s, &i, &j))
    return gtp_failure("invalid coordinate");
  
  set_limit_search(1);
  set_search_diamond(POS(i, j));
  return gtp_success("");
}

/* Function:  unmark the entire board for limited search
 * Arguments: none
 * Fails:     never
 * Returns:   nothing
 */
static int
gtp_reset_search_mask(char *s)
{
  UNUSED(s);

  reset_search_mask();
  return gtp_success("");
}
  
/* Function:  sets the global variable limit_search
 * Arguments: value
 * Fails:     invalid arguments
 * Returns:   nothing
 */
static int
gtp_limit_search(char *s)
{
  int value;

  if (sscanf(s, "%d", &value) < 1)
    return gtp_failure("invalid value for search limit");
  set_limit_search(value);
  return gtp_success("");
}

/* Function:  mark a vertex for limited search
 * Arguments: position
 * Fails:     invalid arguments
 * Returns:   nothing
 */
static int
gtp_set_search_limit(char *s)
{
  int i, j;

  gtp_decode_coord(s, &i, &j);
  set_search_mask(POS(i, j), 1);
  return gtp_success("");
}
  
/* Function:  Draw search area. Writes to stderr.
 * Arguments: none
 * Fails:     never
 * Returns:   nothing
 */
static int
gtp_draw_search_area(char *s)
{
  UNUSED(s);

  gtp_start_response(GTP_SUCCESS);
  gtp_printf("\n");
  draw_search_area();
  return gtp_finish_response();
}



/*
 * Local Variables:
 * tab-width: 8
 * c-basic-offset: 2
 * End:
 */
