/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* 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. *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* 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_move_values
);
DECLARE(gtp_analyze_eyegraph
);
DECLARE(gtp_analyze_semeai
);
DECLARE(gtp_analyze_semeai_after_move
);
DECLARE(gtp_attack_either
);
DECLARE(gtp_clear_board
);
DECLARE(gtp_clear_cache
);
DECLARE(gtp_combination_attack
);
DECLARE(gtp_combination_defend
);
DECLARE(gtp_decrease_depths
);
DECLARE(gtp_defend_both
);
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_estimate_score
);
DECLARE(gtp_experimental_score
);
DECLARE(gtp_final_score
);
DECLARE(gtp_final_status
);
DECLARE(gtp_final_status_list
);
DECLARE(gtp_finish_sgftrace
);
DECLARE(gtp_fixed_handicap
);
DECLARE(gtp_followup_influence
);
DECLARE(gtp_genmove_black
);
DECLARE(gtp_genmove_white
);
DECLARE(gtp_get_connection_node_counter
);
DECLARE(gtp_get_handicap
);
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_half_eye_data
);
DECLARE(gtp_increase_depths
);
DECLARE(gtp_initial_influence
);
DECLARE(gtp_invariant_hash
);
DECLARE(gtp_invariant_hash_for_moves
);
DECLARE(gtp_is_surrounded
);
DECLARE(gtp_kgs_genmove_cleanup
);
DECLARE(gtp_known_command
);
DECLARE(gtp_ladder_attack
);
DECLARE(gtp_limit_search
);
DECLARE(gtp_list_commands
);
DECLARE(gtp_list_stones
);
DECLARE(gtp_move_influence
);
DECLARE(gtp_move_probabilities
);
DECLARE(gtp_move_reasons
);
DECLARE(gtp_move_uncertainty
);
DECLARE(gtp_move_history
);
DECLARE(gtp_owl_connection_defends
);
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_program_version
);
DECLARE(gtp_protocol_version
);
DECLARE(gtp_query_boardsize
);
DECLARE(gtp_query_orientation
);
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_orientation
);
DECLARE(gtp_set_random_seed
);
DECLARE(gtp_set_search_diamond
);
DECLARE(gtp_set_search_limit
);
DECLARE(gtp_start_sgftrace
);
DECLARE(gtp_surround_map
);
DECLARE(gtp_tactical_analyze_semeai
);
DECLARE(gtp_test_eyeshape
);
DECLARE(gtp_time_settings
);
DECLARE(gtp_top_moves_black
);
DECLARE(gtp_top_moves_white
);
DECLARE(gtp_tune_move_ordering
);
DECLARE(gtp_unconditional_status
);
DECLARE(gtp_worm_cutstone
);
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_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
},
{ "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
},
{ "printsgf", gtp_printsgf
},
{ "protocol_version", gtp_protocol_version
},
{ "query_boardsize", gtp_query_boardsize
},
{ "query_orientation", gtp_query_orientation
},
{ "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
},
{ "trymove", gtp_trymove
},
{ "tune_move_ordering", gtp_tune_move_ordering
},
{ "unconditional_status", gtp_unconditional_status
},
{ "version", gtp_program_version
},
{ "white", gtp_playwhite
},
{ "worm_cutstone", gtp_worm_cutstone
},
{ "worm_data", gtp_worm_data
},
{ "worm_stones", gtp_worm_stones
},
/* 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. */
/* Prepare pattern matcher and reading code. */
gtp_main_loop(commands
, gtp_input
, gtp_output
, gtp_dump_commands
);
/****************************
* Administrative commands. *
****************************/
* Status: GTP version 2 standard command.
/* Function: Report protocol version.
* Returns: protocol version number
* Status: GTP version 2 standard command.
gtp_protocol_version(char* s
)
return gtp_success("%d", gtp_version
);
/****************************
****************************/
/* Function: Report the name of the program.
* Status: GTP version 2 standard command.
return gtp_success("GNU Go");
/* Function: Report the version number of the program.
* Returns: version number
* Status: GTP version 2 standard command.
gtp_program_version(char* s
)
return gtp_success(VERSION
);
/***************************
* Setting the board size. *
***************************/
/* Function: Set the board size to NxN and clear the board.
* Fails: board size outside engine's limits
* Status: GTP version 2 standard command.
gtp_set_boardsize(char* s
)
if (sscanf(s
, "%d", &boardsize
) < 1)
return gtp_failure("boardsize not an integer");
if (!check_boardsize(boardsize
, NULL
)) {
return gtp_failure("unacceptable boardsize");
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)
gtp_internal_set_boardsize(boardsize
);
/* Function: Find the current boardsize
gtp_query_boardsize(char* s
)
return gtp_success("%d", board_size
);
/* Function: Clear the board.
* Status: GTP version 2 standard command.
/* 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)
/****************************
* Setting the orientation. *
****************************/
/* Function: Set the orienation to N and clear the board
* Fails: illegal orientation
gtp_set_orientation(char* s
)
if (sscanf(s
, "%d", &orientation
) < 1)
return gtp_failure("orientation not an integer");
if (orientation
< 0 || orientation
> 7)
return gtp_failure("unacceptable orientation");
gtp_orientation
= orientation
;
gtp_set_vertex_transform_hooks(rotate_on_input
, rotate_on_output
);
/* Function: Find the current orientation
gtp_query_orientation(char* s
)
return gtp_success("%d", gtp_orientation
);
/***************************
***************************/
/* Function: Set the komi.
* Fails: incorrect argument
* Status: GTP version 2 standard command.
if (sscanf(s
, "%f", &komi
) < 1)
return gtp_failure("komi not a float");
/***************************
***************************/
/* Function: Get the komi
return gtp_success("%4.1f", komi
);
/* Function: Play a black stone at the given vertex.
* Fails: invalid vertex, illegal move
* Status: Obsolete GTP version 1 command.
if (strncmp(s
, "pass", 4) == 0) {
} 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
);
/* Function: Play a white stone at the given vertex.
* Fails: invalid vertex, illegal move
* Status: Obsolete GTP version 1 command.
if (strncmp(s
, "pass", 4) == 0) {
} 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
);
/* Function: Play a stone of the given color at the given vertex.
* Arguments: color, vertex
* Fails: invalid vertex, illegal move
* Status: GTP version 2 standard command.
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
);
/* 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.
gtp_fixed_handicap(char* s
)
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
) {
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
) {
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.
gtp_place_free_handicap(char* s
)
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");
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
) {
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
* Status: GTP version 2 standard command.
gtp_set_free_handicap(char* s
)
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 (board
[POS(i
, j
)] != EMPTY
) {
return gtp_failure("repeated vertex");
add_stone(POS(i
, j
), BLACK
);
} else if (sscanf(s
, "%*s") != EOF
)
return gtp_failure("invalid coordinate");
return gtp_failure("invalid handicap");
/* Function: Get the handicap
gtp_get_handicap(char* s
)
return gtp_success("%d", handicap
);
/* Function: Load an sgf file, possibly up to a move number or the first
* Arguments: filename + move number, vertex, or nothing
* Fails: missing filename or failure to open or parse file
* Status: GTP version 2 standard command.
char filename
[GTP_BUFSIZE
];
char untilstring
[GTP_BUFSIZE
];
nread
= sscanf(s
, "%s %s", filename
, untilstring
);
return gtp_failure("missing filename");
if (!sgftree_readfile(&sgftree
, filename
))
return gtp_failure("cannot open or parse '%s'", filename
);
color_to_move
= gameinfo_play_sgftree_rot(&gameinfo
, &sgftree
, NULL
,
color_to_move
= gameinfo_play_sgftree_rot(&gameinfo
, &sgftree
, untilstring
,
if (color_to_move
== EMPTY
)
return gtp_failure("cannot load '%s'", filename
);
gtp_internal_set_boardsize(board_size
);
sgfFreeNode(sgftree
.root
);
gtp_start_response(GTP_SUCCESS
);
gtp_mprintf("%C", color_to_move
);
return gtp_finish_response();
/* Function: Return the color at a vertex.
* Returns: "black", "white", or "empty"
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.
* Returns: list of vertices
int vertexi
[MAX_BOARD
* MAX_BOARD
];
int vertexj
[MAX_BOARD
* MAX_BOARD
];
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
) {
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.
* Fails: invalid vertex, empty vertex
* Returns: Number of 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");
return gtp_success("%d", countlib(POS(i
, j
)));
/* Function: Return the positions of the liberties for the string at a vertex.
* Fails: invalid vertex, empty vertex
* Returns: Sorted space separated list of vertices.
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
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.
gtp_accurate_approxlib(char* s
)
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.
* Returns: 1 if the move is legal, 0 if it is not.
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.
* Returns: Sorted space separated list of vertices.
int movei
[MAX_BOARD
* MAX_BOARD
];
int movej
[MAX_BOARD
* MAX_BOARD
];
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
)) {
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.
* Returns: Number of captures.
if (!gtp_decode_color(s
, &color
))
return gtp_failure("invalid color");
return gtp_success("%d", white_captured
);
return gtp_success("%d", black_captured
);
/* Function: Return the last move.
* Fails: no previous move known
* Returns: Color and vertex of last move.
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
* Returns: List of moves played in reverse order in format:
* color move (one move per line)
gtp_move_history(char* 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
));
/* Function: Return the rotation/reflection invariant board hash.
* Returns: Invariant hash for the board as a hexadecimal number.
gtp_invariant_hash(char* 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
* Returns: List of moves + invariant hash as a hexadecimal number,
* one pair of move + hash per line.
gtp_invariant_hash_for_moves(char* s
)
if (!gtp_decode_color(s
, &color
))
return gtp_failure("invalid color");
gtp_start_response(GTP_SUCCESS
);
for (pos
= BOARDMIN
; pos
< BOARDMAX
; pos
++) {
&& 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
));
/* Function: Play a stone of the given color at the given vertex.
* Arguments: move (color + vertex)
* Fails: invalid color, invalid vertex, illegal move
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");
/* 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
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");
/* Function: Undo a trymove or tryko.
return gtp_failure("Stack empty.");
/* Function: clear the caches.
clear_persistent_caches();
/* Function: Try to attack a string.
* Fails: invalid vertex, empty vertex
* Returns: attack code followed by attack point if attack code nonzero.
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
);
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.
gtp_attack_either(char* s
)
n
= gtp_decode_coord(s
, &ai
, &aj
);
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
);
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
);
return gtp_finish_response();
/* Function: Try to defend a string.
* Fails: invalid vertex, empty vertex
* Returns: defense code followed by defense point if defense code nonzero.
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
);
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
n
= gtp_decode_coord(s
, &ti
, &tj
);
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
);
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 */
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
n
= gtp_decode_coord(s
, &ti
, &tj
);
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
);
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 */
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.
* Fails: invalid vertex, empty vertex
* Returns: attack code followed by attack point if attack code nonzero.
gtp_ladder_attack(char* s
)
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
);
gtp_print_vertex(I(apos
), J(apos
));
return gtp_finish_response();
/* Function: Increase depth values by one.
gtp_increase_depths(char* s
)
/* Function: Decrease depth values by one.
gtp_decrease_depths(char* s
)
/* Function: Try to attack a dragon.
* Fails: invalid vertex, empty vertex
* Returns: attack code followed by attack point if attack code nonzero.
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 */
attack_code
= owl_attack(POS(i
, j
), &attack_point
, &result_certain
, &kworm
);
gtp_start_response(GTP_SUCCESS
);
gtp_print_code(attack_code
);
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.
* Fails: invalid vertex, empty vertex
* Returns: defense code followed by defense point if defense code nonzero.
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 */
defend_code
= owl_defend(POS(i
, j
), &defense_point
, &result_certain
, &kworm
);
gtp_start_response(GTP_SUCCESS
);
gtp_print_code(defend_code
);
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.
* Fails: invalid vertex, empty vertex
* Returns: attack code followed by the two attack points if
gtp_owl_threaten_attack(char* s
)
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 */
attack_code
= owl_threaten_attack(POS(i
, j
), &attack_point1
, &attack_point2
);
gtp_start_response(GTP_SUCCESS
);
gtp_print_code(attack_code
);
gtp_print_vertex(I(attack_point1
), J(attack_point1
));
gtp_print_vertex(I(attack_point2
), J(attack_point2
));
return gtp_finish_response();
/* Function: Try to defend a dragon with 2 moves.
* Fails: invalid vertex, empty vertex
* Returns: defense code followed by the 2 defense points if
gtp_owl_threaten_defense(char* s
)
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 */
defend_code
= owl_threaten_defense(POS(i
, j
), &defense_point1
,
gtp_start_response(GTP_SUCCESS
);
gtp_print_code(defend_code
);
gtp_print_vertex(I(defense_point1
), J(defense_point1
));
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
gtp_owl_does_attack(char* s
)
n
= gtp_decode_coord(s
, &ti
, &tj
);
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
);
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 */
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
gtp_owl_does_defend(char* s
)
n
= gtp_decode_coord(s
, &ti
, &tj
);
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
);
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 */
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
gtp_owl_connection_defends(char* s
)
n
= gtp_decode_coord(s
, &ti
, &tj
);
return gtp_failure("invalid coordinate");
if (BOARD(ti
, tj
) != EMPTY
)
return gtp_failure("move vertex must be empty");
n
= gtp_decode_coord(s
, &ai
, &aj
);
return gtp_failure("invalid coordinate");
n
= gtp_decode_coord(s
, &bi
, &bj
);
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 */
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.
n
= gtp_decode_coord(s
, &ai
, &aj
);
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
);
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
);
return gtp_finish_response();
/* Function: Determine whether capturing a string gives a living dragon
* Fails: invalid vertex, empty vertex
* Returns: 1 if dragon can live, 0 otherwise
gtp_owl_substantial(char* s
)
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 */
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
gtp_analyze_semeai(char* s
)
int resulta
, resultb
, move
, result_certain
;
k
= 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 (!gtp_decode_coord(s
+ k
, &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 */
owl_analyze_semeai(dragona
, dragonb
, &resulta
, &resultb
, &move
, 1,
gtp_start_response(GTP_SUCCESS
);
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
gtp_analyze_semeai_after_move(char* s
)
int resulta
, resultb
, semeai_move
, result_certain
;
k
= gtp_decode_move(s
, &color
, &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");
k
= gtp_decode_coord(s
, &i
, &j
);
return gtp_failure("invalid coordinate");
if (board
[dragona
] == EMPTY
)
return gtp_failure("dragon vertex must not be empty");
if (!gtp_decode_coord(s
, &i
, &j
))
return gtp_failure("invalid coordinate");
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 */
owl_analyze_semeai_after_move(move
, color
, dragona
, dragonb
,
&resulta
, &resultb
, &semeai_move
, 1,
gtp_start_response(GTP_SUCCESS
);
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
gtp_tactical_analyze_semeai(char* s
)
int resulta
, resultb
, move
, result_certain
;
k
= 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 (!gtp_decode_coord(s
+ k
, &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 */
owl_analyze_semeai(dragona
, dragonb
, &resulta
, &resultb
, &move
, 0,
gtp_start_response(GTP_SUCCESS
);
gtp_mprintf(" %m", I(move
), J(move
));
if (!result_certain
&& report_uncertainty
)
gtp_printf(" uncertain");
return gtp_finish_response();
/* 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.
int connect_move
= PASS_MOVE
;
n
= gtp_decode_coord(s
, &ai
, &aj
);
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_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.
int disconnect_move
= PASS_MOVE
;
n
= gtp_decode_coord(s
, &ai
, &aj
);
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_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.
signed char goal
[BOARDMAX
];
int break_move
= PASS_MOVE
;
n
= gtp_decode_coord(s
, &ai
, &aj
);
return gtp_failure("invalid coordinate");
memset(goal
, 0, BOARDMAX
);
for (k
= 0; k
< MAX_BOARD
* MAX_BOARD
; k
++) {
n
= gtp_decode_coord(s
, &i
, &j
);
} else if (sscanf(s
, "%*s") != EOF
)
return gtp_failure("invalid coordinate");
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_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.
signed char goal
[BOARDMAX
];
int block_move
= PASS_MOVE
;
n
= gtp_decode_coord(s
, &ai
, &aj
);
return gtp_failure("invalid coordinate");
memset(goal
, 0, BOARDMAX
);
for (k
= 0; k
< MAX_BOARD
* MAX_BOARD
; k
++) {
n
= gtp_decode_coord(s
, &i
, &j
);
} else if (sscanf(s
, "%*s") != EOF
)
return gtp_failure("invalid coordinate");
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_mprintf(" %m", I(block_move
), J(block_move
));
return gtp_finish_response();
/* Function: Evaluate an eye space
* 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.
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
,
} else if (white_eye
[POS(m
, n
)].color
== WHITE
) {
pos
= white_eye
[POS(m
, n
)].origin
;
compute_eyes(pos
, &value
, &attack_point
, &defense_point
,
/* 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_print_vertex(I(attack_point
), J(attack_point
));
gtp_print_vertex(I(defense_point
), J(defense_point
));
return gtp_finish_response();
/* 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.
gtp_dragon_status(char* s
)
if (gtp_decode_coord(s
, &i
, &j
)) {
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
++) {
&& dragon
[pos
].origin
== pos
))) {
gtp_mprintf("%m: ", I(pos
), J(pos
));
if (dragon
[pos
].status
== ALIVE
)
else if (dragon
[pos
].status
== DEAD
)
else if (dragon
[pos
].status
== UNKNOWN
)
/* 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
));
/* 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
n
= gtp_decode_coord(s
, &ai
, &aj
);
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
);
/************************
************************/
/* Function: Determine the unconditional status of a 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.
gtp_unconditional_status(char* s
)
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
;
return gtp_success("undecided");
return gtp_success("%s", status_to_string(status
));
/* Function: Find a move by color capturing something through a
* Returns: Recommended move, PASS if no move found
gtp_combination_attack(char* s
)
n
= gtp_decode_color(s
, &color
);
return gtp_failure("invalid color");
silent_examine_position(EXAMINE_ALL
);
if (!atari_atari(color
, &attack_point
, NULL
, verbose
))
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.
* Returns: Recommended moves, PASS if no combination attack found.
gtp_combination_defend(char* s
)
signed char defense_points
[BOARDMAX
];
n
= gtp_decode_color(s
, &color
);
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
]) {
gtp_print_vertex(I(pos
), J(pos
));
return gtp_finish_response();
/* Function: Run atari_atari_confirm_safety().
* Arguments: move, optional int
* Returns: success code, if failure also defending move
gtp_aa_confirm_safety(char* s
)
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
),
saved_dragons
, saved_worms
);
gtp_start_response(GTP_SUCCESS
);
gtp_mprintf("%d", result
);
gtp_mprintf(" %m", I(defense_point
), J(defense_point
));
return gtp_finish_response();
/* Function: Generate and play the supposedly best black move.
* Returns: a move coordinate or "PASS"
* Status: Obsolete GTP version 1 command.
gtp_genmove_black(char* s
)
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.
* Returns: a move coordinate or "PASS"
* Status: Obsolete GTP version 1 command.
gtp_genmove_white(char* s
)
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
* Returns: a move coordinate or "PASS" (or "resign" if resignation_allowed)
* Status: GTP version 2 standard command.
n
= gtp_decode_color(s
, &color
);
return gtp_failure("invalid color");
return gtp_failure("genmove cannot be called when stackp > 0");
adjust_level_offset(color
);
move
= genmove(color
, NULL
, &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
* Returns: a move coordinate (or "PASS")
* Status: GTP version 2 standard command.
unsigned int saved_random_seed
= get_random_seed();
n
= gtp_decode_color(s
, &color
);
return gtp_failure("invalid color");
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
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
* Returns: a move coordinate (or "PASS")
* This differs from reg_genmove in the optional random seed.
unsigned int saved_random_seed
= get_random_seed();
n
= gtp_decode_color(s
, &color
);
return gtp_failure("invalid color");
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.
sscanf(s
+ n
, "%u", &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")
gtp_restricted_genmove(char* s
)
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
);
return gtp_failure("invalid color");
n
= gtp_decode_coord(s
, &i
, &j
);
allowed_moves
[POS(i
, j
)] = 1;
} else if (sscanf(s
, "%*s") != EOF
)
return gtp_failure("invalid coordinate");
if (number_allowed_moves
== 0)
return gtp_failure("no allowed vertex");
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
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
* 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.
gtp_kgs_genmove_cleanup(char* s
)
int save_capture_all_dead
= capture_all_dead
;
n
= gtp_decode_color(s
, &color
);
return gtp_failure("invalid color");
return gtp_failure("kgs-genmove_cleanup cannot be called when stackp > 0");
/* Turn on the capture_all_dead option to force removal of dead
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.
* Fails: : invalid vertex, occupied vertex
* Returns : list of move reasons (may be empty)
gtp_move_reasons(char* s
)
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)
/* 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.
* Returns : list of moves with values
gtp_all_move_values(char* s
)
gtp_start_response(GTP_SUCCESS
);
print_all_move_values(gtp_output_file
);
/* Function : Generate a sorted list of the best moves in the previous genmove
* If no previous genmove command has been issued, the result
* of this command will be meaningless.
* Returns : list of moves with weights
/* FIXME: Don't we want the moves one per row? */
gtp_start_response(GTP_SUCCESS
);
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
]);
/* Function : Generate a list of the best moves for white with weights
* Returns : list of moves with weights
gtp_top_moves_white(char* s
)
genmove(WHITE
, NULL
, NULL
);
gtp_start_response(GTP_SUCCESS
);
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
* Returns : list of moves with weights
gtp_top_moves_black(char* s
)
genmove(BLACK
, NULL
, NULL
);
gtp_start_response(GTP_SUCCESS
);
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.
* Fails: incorrect argument
if (sscanf(s
, "%d", &new_level
) < 1)
return gtp_failure("level not an integer");
/* Function: Undo one move
* Fails: If move history is too short.
* Status: GTP version 2 standard command.
if (stackp
> 0 || !undo_move(1))
return gtp_failure("cannot undo");
/* Function: Undo a number of moves
* Arguments: optional int
* Fails: If move history is too short.
sscanf(s
, "%d", &number_moves
);
return gtp_failure("can't undo a negative number of moves");
if (stackp
> 0 || !undo_move(number_moves
))
return gtp_failure("cannot undo");
/* Function: Set time allowance
* Arguments: int main_time, int byo_yomi_time, int byo_yomi_stones
* Status: GTP version 2 standard command.
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
);
/* Function: Report remaining time
* Arguments: color color, int time, int stones
* Status: GTP version 2 standard command.
n
= gtp_decode_color(s
, &color
);
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
);
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
,
static const char* status_names
[6] = { "alive", "dead", "seki",
"white_territory", "black_territory",
finish_and_score_game(int seed
)
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;
if (current_seed
!= seed
) {
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
);
/* 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.
/* Let black start if we have no move history. Otherwise continue
if (get_last_player() == EMPTY
)
next
= OTHER_COLOR(get_last_player());
move
= genmove_conservative(next
, NULL
);
gnugo_play_move(move
, next
);
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
);
/* 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
])
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
;
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
;
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
;
final_status
[i
][j
] = DEAD
;
/* Function: Compute the score of a finished game.
* Arguments: Optional random seed
* Returns: Score in SGF format (RE property).
* Status: GTP version 2 standard command.
unsigned int saved_random_seed
= get_random_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.
finish_and_score_game(seed
);
set_random_seed(saved_random_seed
);
gtp_start_response(GTP_SUCCESS
);
gtp_printf("W+%3.1f", final_score
);
else if (final_score
< 0.0)
gtp_printf("B+%3.1f", -final_score
);
return gtp_finish_response();
/* Function: Report the final status of a vertex in a finished game.
* Arguments: Vertex, optional random seed
* Returns: Status in the form of one of the strings "alive", "dead",
* "seki", "white_territory", "black_territory", or "dame".
gtp_final_status(char* s
)
unsigned int saved_random_seed
= get_random_seed();
const char* result
= NULL
;
n
= gtp_decode_coord(s
, &ai
, &aj
);
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.
sscanf(s
+ n
, "%d", &seed
);
finish_and_score_game(seed
);
set_random_seed(saved_random_seed
);
if (final_status
[ai
][aj
] == status_numbers
[k
]) {
result
= status_names
[k
];
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.
gtp_final_status_list(char* s
)
enum dragon_status status
= UNKNOWN
;
char status_string
[GTP_BUFSIZE
];
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
];
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.
sscanf(s
+ n
, "%d", &seed
);
finish_and_score_game(seed
);
set_random_seed(saved_random_seed
);
gtp_start_response(GTP_SUCCESS
);
for (i
= 0; i
< board_size
; i
++)
for (j
= 0; j
< board_size
; j
++) {
if (final_status
[i
][j
] != status
)
if (BOARD(i
, j
) == EMPTY
) {
int stones
[MAX_BOARD
* MAX_BOARD
];
if (find_origin(POS(i
, j
)) != POS(i
, j
))
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
* Returns: upper and lower bounds for the score
gtp_estimate_score(char* s
)
float upper_bound
, lower_bound
;
score
= gnugo_estimate_score(&upper_bound
, &lower_bound
);
gtp_start_response(GTP_SUCCESS
);
/* Traditionally W wins jigo */
gtp_printf("W+%3.1f (upper bound: %3.1f, lower: %3.1f)",
score
, upper_bound
, lower_bound
);
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
* 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.
gtp_experimental_score(char* s
)
float upper_bound
, lower_bound
, score
;
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]);
score
= lower_bound
+ best_move_values
[0];
score
= upper_bound
- best_move_values
[0];
return gtp_success("%3.1f", score
);
/* Function: Reset the count of life nodes.
* Note: This function is obsolete and only remains for backwards
gtp_reset_life_node_counter(char* s
)
/* Function: Retrieve the count of life nodes.
* Returns: number of life nodes
* Note: This function is obsolete and only remains for backwards
gtp_get_life_node_counter(char* s
)
/* Function: Reset the count of owl nodes.
gtp_reset_owl_node_counter(char* s
)
reset_owl_node_counter();
/* Function: Retrieve the count of owl nodes.
* Returns: number of owl nodes
gtp_get_owl_node_counter(char* s
)
int nodes
= get_owl_node_counter();
return gtp_success("%d", nodes
);
/* Function: Reset the count of reading nodes.
gtp_reset_reading_node_counter(char* s
)
reset_reading_node_counter();
/* Function: Retrieve the count of reading nodes.
* Returns: number of reading nodes
gtp_get_reading_node_counter(char* s
)
int nodes
= get_reading_node_counter();
return gtp_success("%d", nodes
);
/* Function: Reset the count of trymoves/trykos.
gtp_reset_trymove_counter(char* s
)
/* Function: Retrieve the count of trymoves/trykos.
* Returns: number of trymoves/trykos
gtp_get_trymove_counter(char* s
)
int nodes
= get_trymove_counter();
return gtp_success("%d", nodes
);
/* Function: Reset the count of connection nodes.
gtp_reset_connection_node_counter(char* s
)
reset_connection_node_counter();
/* Function: Retrieve the count of connection nodes.
* Returns: number of connection nodes
gtp_get_connection_node_counter(char* s
)
int nodes
= get_connection_node_counter();
return gtp_success("%d", nodes
);
/* Function: Test an eyeshape for inconsistent evaluations
* Arguments: Eyeshape vertices
* Returns: Failure reports on stderr.
gtp_test_eyeshape(char* s
)
int eye_vertices
[MAX_BOARD
* MAX_BOARD
];
n
= gtp_decode_coord(s
, &i
, &j
);
eye_vertices
[eyesize
] = POS(i
, j
);
n
= gtp_decode_coord(s
, &i
, &j
);
return gtp_failure("invalid coordinate");
test_eyeshape(eyesize
, eye_vertices
);
/* 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
gtp_analyze_eyegraph(char* s
)
char analyzed_eyegraph
[1024];
int result
= analyze_eyegraph(s
, &value
, analyzed_eyegraph
);
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.
* Returns: Total elapsed (user + system) CPU time in seconds.
return gtp_success("%.3f", gg_cputime());
/* Function: Write the position to stdout.
* Status: GTP version 2 standard command.
gtp_start_response(GTP_SUCCESS
);
simple_showboard(gtp_output_file
);
return gtp_finish_response();
/* Function: Dump stack to stderr.
/* Determine whether a string starts with a specific substring. */
has_prefix(const char* s
, const char* prefix
)
return strncmp(s
, prefix
, strlen(prefix
)) == 0;
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
];
float* float_pointer
= NULL
;
while (*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
;
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
++) {
gtp_printf("%6.2f ", float_pointer
[POS(m
, n
)]);
gtp_printf("%2d ", int_pointer
[POS(m
, n
)]);
/* We already have one newline and thus can't use gtp_finish_response(). */
/* Function: Return information about the initial influence function.
* Arguments: color to move, what information
* 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_attenuation (float)
* black_attenuation (float)
* white_permeability (float)
* black_permeability (float)
* territory_value (float)
* influence_regions (int)
* The encoding of influence_regions is as follows:
gtp_initial_influence(char* s
)
struct influence_data
* q
;
n
= gtp_decode_color(s
, &color
);
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
* Returns: Influence data formatted like for initial_influence.
gtp_move_influence(char* s
)
n
= gtp_decode_move(s
, &color
, &i
, &j
);
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.
* Returns: Move, probabilty pairs, one per row.
gtp_move_probabilities(char* s
)
float probabilities
[BOARDMAX
];
int any_moves_printed
= 0;
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
]);
/* 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.
* Returns: bits of uncertainty
gtp_move_uncertainty(char* s
)
float probabilities
[BOARDMAX
];
double uncertainty
= 0.0;
compute_move_probabilities(probabilities
);
gtp_start_response(GTP_SUCCESS
);
for (pos
= BOARDMIN
; pos
< BOARDMAX
; pos
++) {
if (ON_BOARD(pos
) && probabilities
[pos
] > 0.0) {
uncertainty
+= -1 * ((double)probabilities
[pos
]) * log((double)probabilities
[pos
]) / log(2.0);
gtp_printf("%.4f\n\n", uncertainty
);
/* Function: Return information about the followup influence after a move.
* Arguments: move, what information
* Returns: Influence data formatted like for initial_influence.
gtp_followup_influence(char* s
)
n
= gtp_decode_move(s
, &color
, &i
, &j
);
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
* Returns: Worm data formatted like:
* If an intersection is specified, only data for this one will be returned.
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_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
));
/* 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
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
++) {
|| (color
!= EMPTY
&& BOARD(u
, v
) != color
))
if (find_origin(POS(u
, v
)) != POS(u
, v
))
&& !same_string(POS(u
, v
), POS(i
, j
)))
for (m
= 0; m
< board_size
; m
++)
for (n
= 0; n
< board_size
; n
++)
&& same_string(POS(m
, n
), POS(u
, v
)))
gtp_mprintf("%m ", m
, n
);
gtp_printf("\n"); /* in case no stones have been printed */
/* Function: Return the cutstone field in the worm data structure.
* Arguments: non-empty vertex
gtp_worm_cutstone(char* s
)
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
* Returns: Dragon data formatted in the corresponding way to gtp_worm_data.
if (sscanf(s
, "%*c") >= 0 && !gtp_decode_coord(s
, &i
, &j
))
return gtp_failure("invalid coordinate");
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
);
for (m
= 0; m
< board_size
; m
++)
for (n
= 0; n
< board_size
; n
++)
&& dragon
[POS(m
, n
)].origin
== POS(m
, n
))) {
report_dragon(gtp_output_file
, POS(m
, n
));
/* Function: List the stones of a dragon
* Arguments: the location
* Fails: if called on an empty or off-board location
* Returns: list of stones
gtp_dragon_stones(char* s
)
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
++) {
|| (color
!= EMPTY
&& BOARD(u
, v
) != color
))
if (dragon
[POS(u
, v
)].origin
!= POS(u
, v
))
if (ON_BOARD2(i
, j
) && dragon
[POS(i
, j
)].origin
!= POS(u
, v
))
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
);
/* Function: Return the information in the eye data structure.
* Arguments: color, vertex
* Returns: eye data fields and values, one pair per row
if (!gtp_decode_move(s
, &color
, &i
, &j
))
return gtp_failure("invalid color or coordinate");
return gtp_failure("eye data unavailable when stackp > 0");
silent_examine_position(EXAMINE_DRAGONS_WITHOUT_OWL
);
gtp_start_response(GTP_SUCCESS
);
e
= &black_eye
[POS(i
, j
)];
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
);
/* Function: Return the information in the half eye data structure.
* Returns: half eye data fields and values, one pair per row
gtp_half_eye_data(char* s
)
if (!gtp_decode_coord(s
, &i
, &j
))
return gtp_failure("invalid coordinate");
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
);
gtp_printf("type HALF_EYE\n");
else if (h
->type
== FALSE_EYE
)
gtp_printf("type FALSE_EYE\n");
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
]),
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
]),
static SGFTree gtp_sgftree
;
/* Function: Start storing moves executed during reading in an sgf
* Warning: You had better know what you're doing if you try to use this
gtp_start_sgftrace(char* s
)
sgffile_begindump(>p_sgftree
);
/* Function: Finish storing moves in an sgf tree and write it to file.
* Warning: You had better know what you're doing if you try to use this
gtp_finish_sgftrace(char* s
)
char filename
[GTP_BUFSIZE
];
nread
= sscanf(s
, "%s", filename
);
return gtp_failure("missing filename");
sgffile_enddump(filename
);
/* Function: Dump the current position as a static sgf file to filename,
* or as output if filename is missing or "-"
* Arguments: optional filename
* Returns: nothing if filename, otherwise the sgf
char filename
[GTP_BUFSIZE
];
if (get_last_player() == EMPTY
)
next
= OTHER_COLOR(get_last_player());
nread
= sscanf(s
, "%s", filename
);
gg_snprintf(filename
, GTP_BUFSIZE
, "%s", "-");
if (strcmp(filename
, "-") == 0) {
gtp_start_response(GTP_SUCCESS
);
sgffile_printsgf(next
, filename
);
sgffile_printsgf(next
, filename
);
/* Function: Tune the parameters for the move ordering in the tactical
* Arguments: MOVE_ORDERING_PARAMETERS integers
* Fails: incorrect arguments
gtp_tune_move_ordering(char* s
)
int params
[MOVE_ORDERING_PARAMETERS
];
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
);
tune_move_ordering(params
);
/* Function: Echo the parameter
return gtp_success("%s", s
);
/* Function: Echo the parameter to stdout AND stderr
fprintf(stderr
, "%s", s
);
return gtp_success("%s", s
);
/* Function: List all known commands
* Returns: list of known commands, one per line
* Status: GTP version 2 standard command.
gtp_list_commands(char* s
)
gtp_start_response(GTP_SUCCESS
);
for (k
= 0; commands
[k
].name
!= NULL
; k
++)
gtp_printf("%s\n", commands
[k
].name
);
/* Function: Tell whether a command is known.
* Arguments: command name
* Returns: "true" if command exists, "false" if not
* Status: GTP version 2 standard command.
gtp_known_command(char* s
)
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
gtp_report_uncertainty(char* s
)
if (!strncmp(s
, "on", 2)) {
if (!strncmp(s
, "off", 3)) {
return gtp_failure("invalid argument");
static int conversion
[6] = {
gtp_printf("%d", conversion
[c
]);
gtp_print_vertices2(int n
, int* moves
)
int movei
[MAX_BOARD
* MAX_BOARD
];
int movej
[MAX_BOARD
* MAX_BOARD
];
for (k
= 0; k
< n
; k
++) {
gtp_print_vertices(n
, movei
, movej
);
rotate_on_input(int ai
, int aj
, int* bi
, int* bj
)
rotate(ai
, aj
, bi
, bj
, board_size
, gtp_orientation
);
rotate_on_output(int ai
, int aj
, int* bi
, int* bj
)
inv_rotate(ai
, aj
, bi
, bj
, board_size
, gtp_orientation
);
/* Function: Get the random seed
gtp_get_random_seed(char* s
)
return gtp_success("%d", get_random_seed());
/* Function: Set the random seed
gtp_set_random_seed(char* s
)
if (sscanf(s
, "%d", &seed
) < 1)
return gtp_failure("invalid seed");
/* Function: Advance the random seed by a number of games.
* Returns: New random seed.
gtp_advance_random_seed(char* s
)
if (sscanf(s
, "%d", &games
) < 1
return gtp_failure("invalid number of games");
for (i
= 0; i
< games
; i
++)
return gtp_success("%d", get_random_seed());
/* 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
gtp_is_surrounded(char* s
)
n
= gtp_decode_coord(s
, &i
, &j
);
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)
gtp_does_surround(char* s
)
n
= gtp_decode_coord(s
, &si
, &sj
);
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
);
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
gtp_surround_map(char* s
)
n
= gtp_decode_coord(s
, &di
, &dj
);
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
);
return gtp_failure("invalid coordinate");
silent_examine_position(EXAMINE_DRAGONS
);
return gtp_success("%d", surround_map(POS(di
, dj
), POS(mi
, mj
)));
/* Function: limit search, and establish a search diamond
gtp_set_search_diamond(char* s
)
if (!gtp_decode_coord(s
, &i
, &j
))
return gtp_failure("invalid coordinate");
set_search_diamond(POS(i
, j
));
/* Function: unmark the entire board for limited search
gtp_reset_search_mask(char* s
)
/* Function: sets the global variable limit_search
* Fails: invalid arguments
gtp_limit_search(char* s
)
if (sscanf(s
, "%d", &value
) < 1)
return gtp_failure("invalid value for search limit");
/* Function: mark a vertex for limited search
* Fails: invalid arguments
gtp_set_search_limit(char* s
)
gtp_decode_coord(s
, &i
, &j
);
set_search_mask(POS(i
, j
), 1);
/* Function: Draw search area. Writes to stderr.
gtp_draw_search_area(char* s
)
gtp_start_response(GTP_SUCCESS
);
return gtp_finish_response();