Before starting to tinker, ran clang-format on files that seem like relevant starting...
[sgk-go] / interface / play_gtp.c
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* 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 and 2009 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 <assert.h>
#include <ctype.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include "gg_utils.h"
#include "gtp.h"
#include "interface.h"
#include "liberty.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);
/* to get the variations into the sgf file, clear the reading cache */
if (sgf_dumptree)
reading_cache_clear();
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);
/* to get the variations into the sgf file, clear the reading cache */
if (sgf_dumptree)
reading_cache_clear();
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);
/* to get the variations into the sgf file, clear the reading cache */
if (sgf_dumptree)
reading_cache_clear();
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);
/* to get the variations into the sgf file, clear the reading cache */
if (sgf_dumptree)
reading_cache_clear();
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);
/* to get the variations into the sgf file, clear the reading cache */
if (sgf_dumptree)
reading_cache_clear();
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);
/* to get the variations into the sgf file, clear the reading cache */
if (sgf_dumptree)
reading_cache_clear();
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);
/* to get the variations into the sgf file, clear the reading cache */
if (sgf_dumptree)
reading_cache_clear();
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);
/* to get the variations into the sgf file, clear the reading cache */
if (sgf_dumptree)
reading_cache_clear();
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);
/* to get the variations into the sgf file, clear the reading cache */
if (sgf_dumptree)
reading_cache_clear();
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);
/* to get the variations into the sgf file, clear the reading cache */
if (sgf_dumptree)
reading_cache_clear();
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);
/* to get the variations into the sgf file, clear the reading cache */
if (sgf_dumptree)
reading_cache_clear();
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);
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
* Fails: Bad eyeshape, analysis failed
* Returns: Eyevalue, vital points
*/
static int
gtp_analyze_eyegraph(char* s)
{
struct eyevalue value;
char analyzed_eyegraph[1024];
int result = analyze_eyegraph(s, &value, analyzed_eyegraph);
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: 4
* c-basic-offset: 4
* End:
*/