Initial commit of GNU Go v3.8.
[sgk-go] / engine / board.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. *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* The functions in this file implements a go board with incremental
* update of strings and liberties.
*
* See the Texinfo documentation (Utility Functions: Incremental Board)
* for an introduction.
*/
#include "board.h"
#include "hash.h"
#include "sgftree.h"
#include "gg_utils.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
/* This can be used for internal checks w/in board.c that should
* typically not be necessary (for speed).
*/
#if 1
#define PARANOID1(x, pos) ASSERT1(x, pos)
#else
#define PARANOID1(x, pos)
#endif
/* ================================================================ */
/* data structures */
/* ================================================================ */
/* Incremental string data. */
struct string_data {
int color; /* Color of string, BLACK or WHITE */
int size; /* Number of stones in string. */
int origin; /* Coordinates of "origin", i.e. */
/* "upper left" stone. */
int liberties; /* Number of liberties. */
int neighbors; /* Number of neighbor strings */
int mark; /* General purpose mark. */
};
struct string_liberties_data {
int list[MAX_LIBERTIES]; /* Coordinates of liberties. */
};
struct string_neighbors_data {
int list[MAXCHAIN]; /* List of neighbor string numbers. */
};
/* we keep the address and the old value */
struct change_stack_entry {
int *address;
int value;
};
/* we keep the address and the old value */
struct vertex_stack_entry {
Intersection *address;
int value;
};
/* Experimental results show that the average number of change stack
* entries per move usually is in the 20-30 range and very seldom
* exceeds 40. But since we have no way to recover from running out of
* stack space, we allocate with a substantial safety margin.
*/
#define STACK_SIZE 80 * MAXSTACK
#define CLEAR_STACKS() do { \
change_stack_pointer = change_stack; \
vertex_stack_pointer = vertex_stack; \
VALGRIND_MAKE_WRITABLE(change_stack, sizeof(change_stack)); \
VALGRIND_MAKE_WRITABLE(vertex_stack, sizeof(vertex_stack)); \
} while (0)
/* Begin a record : address == NULL */
#define BEGIN_CHANGE_RECORD()\
((change_stack_pointer++)->address = NULL,\
(vertex_stack_pointer++)->address = NULL)
/* Save a value : store the address and the value in the stack */
#define PUSH_VALUE(v)\
(change_stack_pointer->address = &(v),\
(change_stack_pointer++)->value = (v))
/* Save a board value : store the address and the value in the stack */
#define PUSH_VERTEX(v)\
(vertex_stack_pointer->address = &(v),\
(vertex_stack_pointer++)->value = (v))
#define POP_MOVE()\
while ((--change_stack_pointer)->address)\
*(change_stack_pointer->address) =\
change_stack_pointer->value
#define POP_VERTICES()\
while ((--vertex_stack_pointer)->address)\
*(vertex_stack_pointer->address) =\
vertex_stack_pointer->value
/* ================================================================ */
/* static data structures */
/* ================================================================ */
/* Main array of string information. */
static struct string_data string[MAX_STRINGS];
static struct string_liberties_data string_libs[MAX_STRINGS];
static struct string_neighbors_data string_neighbors[MAX_STRINGS];
/* Stacks and stack pointers. */
static struct change_stack_entry change_stack[STACK_SIZE];
static struct change_stack_entry *change_stack_pointer;
static struct vertex_stack_entry vertex_stack[STACK_SIZE];
static struct vertex_stack_entry *vertex_stack_pointer;
/* Index into list of strings. The index is only valid if there is a
* stone at the vertex.
*/
static int string_number[BOARDMAX];
/* The stones in a string are linked together in a cyclic list.
* These are the coordinates to the next stone in the string.
*/
static int next_stone[BOARDMAX];
/* ---------------------------------------------------------------- */
/* Macros to traverse the stones of a string.
*
* Usage:
* int s, pos;
* s = find_the_string()
* pos = FIRST_STONE(s);
* do {
* use_stone(pos);
* pos = NEXT_STONE(pos);
* } while (!BACK_TO_FIRST_STONE(s, pos));
*/
#define FIRST_STONE(s) \
(string[s].origin)
#define NEXT_STONE(pos) \
(next_stone[pos])
#define BACK_TO_FIRST_STONE(s, pos) \
((pos) == string[s].origin)
/* Assorted useful macros.
*
* Some of them could have been functions but are implemented as
* macros for speed.
*/
#define LIBERTY(pos) \
(board[pos] == EMPTY)
#define UNMARKED_LIBERTY(pos) \
(board[pos] == EMPTY && ml[pos] != liberty_mark)
#define MARK_LIBERTY(pos) \
ml[pos] = liberty_mark
#define UNMARKED_STRING(pos) \
(string[string_number[pos]].mark != string_mark)
/* Note that these two macros are not complementary. Both return
* false if board[pos] != color.
*/
#define UNMARKED_COLOR_STRING(pos, color)\
(board[pos] == color\
&& string[string_number[pos]].mark != string_mark)
#define MARKED_COLOR_STRING(pos, color)\
(board[pos] == color\
&& string[string_number[pos]].mark == string_mark)
#define MARK_STRING(pos) string[string_number[pos]].mark = string_mark
#define STRING_AT_VERTEX(pos, s, color)\
((board[pos] == color) && string_number[pos] == (s))
#define NEIGHBOR_OF_STRING(pos, s, color)\
(STRING_AT_VERTEX(SOUTH(pos), s, color)\
|| STRING_AT_VERTEX(WEST(pos), s, color)\
|| STRING_AT_VERTEX(NORTH(pos), s, color)\
|| STRING_AT_VERTEX(EAST(pos), s, color))
/* These four macros have rather confusing names. It should be read as:
* "(pos) is a neighbor of string (s) of (color) in any direction except
* the specified one".
*/
#define NON_SOUTH_NEIGHBOR_OF_STRING(pos, s, color)\
(STRING_AT_VERTEX(SOUTH(pos), s, color)\
|| STRING_AT_VERTEX(WEST(pos), s, color)\
|| STRING_AT_VERTEX(EAST(pos), s, color))
#define NON_WEST_NEIGHBOR_OF_STRING(pos, s, color)\
(STRING_AT_VERTEX(WEST(pos), s, color)\
|| STRING_AT_VERTEX(NORTH(pos), s, color)\
|| STRING_AT_VERTEX(SOUTH(pos), s, color))
#define NON_NORTH_NEIGHBOR_OF_STRING(pos, s, color)\
(STRING_AT_VERTEX(NORTH(pos), s, color)\
|| STRING_AT_VERTEX(EAST(pos), s, color)\
|| STRING_AT_VERTEX(WEST(pos), s, color))
#define NON_EAST_NEIGHBOR_OF_STRING(pos, s, color)\
(STRING_AT_VERTEX(EAST(pos), s, color)\
|| STRING_AT_VERTEX(SOUTH(pos), s, color)\
|| STRING_AT_VERTEX(NORTH(pos), s, color))
#define LIBERTIES(pos)\
string[string_number[pos]].liberties
#define COUNTSTONES(pos) \
string[string_number[pos]].size
#define ADD_LIBERTY(s, pos)\
do {\
if (string[s].liberties < MAX_LIBERTIES)\
string_libs[s].list[string[s].liberties] = pos;\
string[s].liberties++;\
} while (0)
#define ADD_AND_MARK_LIBERTY(s, pos)\
do {\
if (string[s].liberties < MAX_LIBERTIES)\
string_libs[s].list[string[s].liberties] = pos;\
string[s].liberties++;\
ml[pos] = liberty_mark;\
} while (0)
#define ADD_NEIGHBOR(s, pos)\
string_neighbors[s].list[string[s].neighbors++] = string_number[pos]
#define DO_ADD_STONE(pos, color)\
do {\
PUSH_VERTEX(board[pos]);\
board[pos] = color;\
hashdata_invert_stone(&board_hash, pos, color);\
} while (0)
#define DO_REMOVE_STONE(pos)\
do {\
PUSH_VERTEX(board[pos]);\
hashdata_invert_stone(&board_hash, pos, board[pos]);\
board[pos] = EMPTY;\
} while (0)
/* ---------------------------------------------------------------- */
/* Number of the next free string. */
static int next_string;
/* For marking purposes. */
static int ml[BOARDMAX];
static int liberty_mark;
static int string_mark;
/* Forward declarations. */
static void really_do_trymove(int pos, int color);
static int do_trymove(int pos, int color, int ignore_ko);
static void undo_trymove(void);
static int do_approxlib(int pos, int color, int maxlib, int *libs);
static int slow_approxlib(int pos, int color, int maxlib, int *libs);
static int do_accuratelib(int pos, int color, int maxlib, int *libs);
static int is_superko_violation(int pos, int color, enum ko_rules type);
static void new_position(void);
static int propagate_string(int stone, int str);
static void find_liberties_and_neighbors(int s);
static int do_remove_string(int s);
static void do_commit_suicide(int pos, int color);
static void do_play_move(int pos, int color);
static int komaster, kom_pos;
/* Statistics. */
static int trymove_counter = 0;
/* Coordinates for the eight directions, ordered
* south, west, north, east, southwest, northwest, northeast, southeast.
*/
int deltai[8] = { 1, 0, -1, 0, 1, -1, -1, 1};
int deltaj[8] = { 0, -1, 0, 1, -1, -1, 1, 1};
int delta[8] = { NS, -1, -NS, 1, NS-1, -NS-1, -NS+1, NS+1};
/* ================================================================ */
/* Board initialization */
/* ================================================================ */
/*
* Save board state.
*/
void
store_board(struct board_state *state)
{
int k;
gg_assert(stackp == 0);
state->board_size = board_size;
memcpy(state->board, board, sizeof(board));
memcpy(state->initial_board, initial_board, sizeof(initial_board));
state->board_ko_pos = board_ko_pos;
state->white_captured = white_captured;
state->black_captured = black_captured;
state->initial_board_ko_pos = initial_board_ko_pos;
state->initial_white_captured = initial_white_captured;
state->initial_black_captured = initial_black_captured;
state->move_history_pointer = move_history_pointer;
for (k = 0; k < move_history_pointer; k++) {
state->move_history_color[k] = move_history_color[k];
state->move_history_pos[k] = move_history_pos[k];
state->move_history_hash[k] = move_history_hash[k];
}
state->komi = komi;
state->handicap = handicap;
state->move_number = movenum;
}
/*
* Restore a saved board state.
*/
void
restore_board(struct board_state *state)
{
int k;
gg_assert(stackp == 0);
board_size = state->board_size;
memcpy(board, state->board, sizeof(board));
memcpy(initial_board, state->initial_board, sizeof(initial_board));
board_ko_pos = state->board_ko_pos;
white_captured = state->white_captured;
black_captured = state->black_captured;
initial_board_ko_pos = state->initial_board_ko_pos;
initial_white_captured = state->initial_white_captured;
initial_black_captured = state->initial_black_captured;
move_history_pointer = state->move_history_pointer;
for (k = 0; k < move_history_pointer; k++) {
move_history_color[k] = state->move_history_color[k];
move_history_pos[k] = state->move_history_pos[k];
move_history_hash[k] = state->move_history_hash[k];
}
komi = state->komi;
handicap = state->handicap;
movenum = state->move_number;
hashdata_recalc(&board_hash, board, board_ko_pos);
new_position();
}
/*
* Clear the internal board.
*/
void
clear_board(void)
{
int k;
gg_assert(board_size > 0 && board_size <= MAX_BOARD);
memset(board, EMPTY, sizeof(board));
memset(initial_board, EMPTY, sizeof(initial_board));
for (k = 0; k < BOARDSIZE; k++) {
if (!ON_BOARD2(I(k), J(k))) {
board[k] = GRAY;
initial_board[k] = GRAY;
}
}
board_ko_pos = NO_MOVE;
white_captured = 0;
black_captured = 0;
komaster = EMPTY;
kom_pos = NO_MOVE;
initial_board_ko_pos = NO_MOVE;
initial_white_captured = 0;
initial_black_captured = 0;
move_history_pointer = 0;
movenum = 0;
handicap = 0;
hashdata_recalc(&board_hash, board, board_ko_pos);
new_position();
}
/* Test the integrity of the gray border. */
int
test_gray_border(void)
{
int k;
gg_assert(board_size > 0 && board_size <= MAX_BOARD);
for (k = 0; k < BOARDSIZE; k++)
if (!ON_BOARD2(I(k), J(k)))
if (board[k] != GRAY)
return k;
return -1;
}
/* ================================================================ */
/* Temporary moves */
/* ================================================================ */
/* Stack of trial moves to get to current
* position and which color made them. Perhaps
* this should be one array of a structure
*/
static int stack[MAXSTACK];
static int move_color[MAXSTACK];
static Hash_data board_hash_stack[MAXSTACK];
/*
* trymove pushes the position onto the stack, and makes a move
* at pos of color. Returns one if the move is legal. The
* stack pointer is only incremented if the move is legal.
*
* The way to use this is:
*
* if (trymove(...)) {
* ...
* popgo();
* }
*
* The message can be written as a comment to an sgf file using
* sgfdump(). str can be NO_MOVE if it is not needed but otherwise
* the location of str is included in the comment.
*/
int
trymove(int pos, int color, const char *message, int str)
{
UNUSED(str);
/* Do the real work elsewhere. */
if (!do_trymove(pos, color, 0))
return 0;
/* Store the move in an sgf tree if one is available. */
if (sgf_dumptree) {
char buf[100];
if (message == NULL)
message = "UNKNOWN";
if (pos == NO_MOVE) {
if (komaster != EMPTY)
gg_snprintf(buf, 100, "%s (variation %d, hash %s, komaster %s:%s)",
message, count_variations, hashdata_to_string(&board_hash),
color_to_string(komaster), location_to_string(kom_pos));
else
gg_snprintf(buf, 100, "%s (variation %d, hash %s)", message,
count_variations, hashdata_to_string(&board_hash));
}
else {
if (komaster != EMPTY)
gg_snprintf(buf, 100,
"%s at %s (variation %d, hash %s, komaster %s:%s)",
message, location_to_string(pos), count_variations,
hashdata_to_string(&board_hash),
color_to_string(komaster),
location_to_string(kom_pos));
else
gg_snprintf(buf, 100, "%s at %s (variation %d, hash %s)",
message, location_to_string(pos), count_variations,
hashdata_to_string(&board_hash));
}
sgftreeAddPlayLast(sgf_dumptree, color, I(pos), J(pos));
sgftreeAddComment(sgf_dumptree, buf);
}
if (count_variations)
count_variations++;
stats.nodes++;
return 1;
}
/*
* tryko pushes the position onto the stack, and makes a move
* at (pos) of (color). The move is allowed even if it is an
* illegal ko capture. It is to be imagined that (color) has
* made an intervening ko threat which was answered and now
* the continuation is to be explored.
*
* Return 1 if the move is legal with the above caveat. Returns
* zero if it is not legal because of suicide.
*/
int
tryko(int pos, int color, const char *message)
{
/* Do the real work elsewhere. */
if (!do_trymove(pos, color, 1))
return 0;
if (sgf_dumptree) {
char buf[100];
if (message == NULL)
message = "UNKNOWN";
if (komaster != EMPTY)
gg_snprintf(buf, 100, "tryko: %s (variation %d, %s, komaster %s:%s)",
message, count_variations, hashdata_to_string(&board_hash),
color_to_string(komaster), location_to_string(kom_pos));
else
gg_snprintf(buf, 100, "tryko: %s (variation %d, %s)", message,
count_variations, hashdata_to_string(&board_hash));
/* Add two pass moves to the SGF output to simulate the ko threat
* and the answer.
*
* The reason we add these is that certain SGF viewers, including
* Cgoban 1, won't properly display variations with illegal ko
* captures. SGF FF[4] compliant browsers should have no problem
* with this, though.
*/
sgftreeAddPlayLast(sgf_dumptree, color, -1, -1);
sgftreeAddComment(sgf_dumptree, "tenuki (ko threat)");
sgftreeAddPlayLast(sgf_dumptree, OTHER_COLOR(color), -1, -1);
sgftreeAddComment(sgf_dumptree, "tenuki (answers ko threat)");
sgftreeAddPlayLast(sgf_dumptree, color, I(pos), J(pos));
sgftreeAddComment(sgf_dumptree, buf);
}
if (count_variations)
count_variations++;
stats.nodes++;
return 1;
}
/* Really, really make a temporary move. It is assumed that all
* necessary checks have already been made and likewise that various
* administrative bookkeeping outside of the actual board logic has
* either been done or is not needed.
*/
static void
really_do_trymove(int pos, int color)
{
BEGIN_CHANGE_RECORD();
PUSH_VALUE(board_ko_pos);
/*
* FIXME: Do we really have to store board_hash in a stack?
*
* Answer: No, we don't. But for every stone that we add
* or remove, we must call hashdata_invert_stone(). This is
* not difficult per se, but the whole board.c
* will have to be checked, and there is lots of room
* for mistakes.
*
* At the same time, profiling shows that storing the
* hashdata in a stack doesn't take a lot of time, so
* this is not an urgent FIXME.
*/
memcpy(&board_hash_stack[stackp], &board_hash, sizeof(board_hash));
if (board_ko_pos != NO_MOVE)
hashdata_invert_ko(&board_hash, board_ko_pos);
board_ko_pos = NO_MOVE;
stackp++;
if (pos != PASS_MOVE) {
PUSH_VALUE(black_captured);
PUSH_VALUE(white_captured);
do_play_move(pos, color);
}
}
/*
* Do the main work of trymove() and tryko(), i.e. the common parts.
* The ignore_ko flag tells whether an illegal ko capture may be done.
* Return 1 if the move was valid, otherwise 0.
*/
static int
do_trymove(int pos, int color, int ignore_ko)
{
/* 1. The color must be BLACK or WHITE. */
gg_assert(color == BLACK || color == WHITE);
if (pos != PASS_MOVE) {
/* 2. Unless pass, the move must be inside the board. */
ASSERT_ON_BOARD1(pos);
/* Update the reading tree shadow. */
shadow[pos] = 1;
/* 3. The location must be empty. */
if (board[pos] != EMPTY)
return 0;
/* 4. The location must not be the ko point, unless ignore_ko == 1. */
if (!ignore_ko && pos == board_ko_pos) {
if (board[WEST(pos)] == OTHER_COLOR(color)
|| board[EAST(pos)] == OTHER_COLOR(color)) {
return 0;
}
}
/* 5. Test for suicide. */
if (is_suicide(pos, color))
return 0;
}
/* Check for stack overflow. */
if (stackp >= MAXSTACK-2) {
fprintf(stderr,
"gnugo: Truncating search. This is beyond my reading ability!\n");
/* FIXME: Perhaps it's best to just assert here and be done with it? */
if (0) {
ASSERT1(0 && "trymove stack overflow", pos);
}
#if 0
if (verbose > 0) {
showboard(0);
dump_stack();
}
#endif
fflush(stderr);
return 0;
}
/* Only count trymove when we do create a new position. */
trymove_counter++;
/* So far, so good. Now push the move on the move stack. These are
* needed for dump_stack().
*/
stack[stackp] = pos;
move_color[stackp] = color;
really_do_trymove(pos, color);
return 1;
}
/*
* popgo pops the position from the stack.
*/
void
popgo()
{
undo_trymove();
if (sgf_dumptree) {
char buf[100];
int is_tryko = 0;
char *sgf_comment;
/* FIXME: Change the sgfGet*Property() interface so that either
* "C" instead of "C " works or the SGFXX symbols are used.
*/
if (sgfGetCharProperty(sgf_dumptree->lastnode, "C ", &sgf_comment)
&& strncmp(sgf_comment, "tryko:", 6) == 0)
is_tryko = 1;
gg_snprintf(buf, 100, "(next variation: %d)", count_variations);
sgftreeAddComment(sgf_dumptree, buf);
sgf_dumptree->lastnode = sgf_dumptree->lastnode->parent;
/* After tryko() we need to undo two pass nodes too. */
if (is_tryko)
sgf_dumptree->lastnode = sgf_dumptree->lastnode->parent->parent;
}
}
/* Restore board state to the position before the last move. This is
* accomplished by popping everything that was stored on the stacks
* since the last BEGIN_CHANGE_RECORD(). Also stackp is decreased and
* board hash is restored from stack.
*
* This undoes the effects of do_trymove() or really_do_trymove() and
* is appropriate to call instead of popgo() if you have not passed
* through trymove() or tryko().
*/
static void
undo_trymove()
{
gg_assert(change_stack_pointer - change_stack <= STACK_SIZE);
if (0) {
gprintf("Change stack size = %d\n", change_stack_pointer - change_stack);
gprintf("Vertex stack size = %d\n", vertex_stack_pointer - vertex_stack);
}
POP_MOVE();
POP_VERTICES();
stackp--;
memcpy(&board_hash, &(board_hash_stack[stackp]), sizeof(board_hash));
}
/*
* dump_stack() for use under gdb prints the move stack.
*/
void
dump_stack(void)
{
do_dump_stack();
#if !TRACE_READ_RESULTS
if (count_variations)
gprintf("%o (variation %d)", count_variations-1);
#else
gprintf("%o (%s)", hashdata_to_string(&board_hash));
#endif
gprintf("%o\n");
fflush(stderr);
}
/* Bare bones of dump_stack(). */
void
do_dump_stack(void)
{
int n;
for (n = 0; n < stackp; n++)
gprintf("%o%s:%1m ", move_color[n] == BLACK ? "B" : "W", stack[n]);
}
/* ================================================================ */
/* Permanent moves */
/* ================================================================ */
static void
reset_move_history(void)
{
memcpy(initial_board, board, sizeof(board));
initial_board_ko_pos = board_ko_pos;
initial_white_captured = white_captured;
initial_black_captured = black_captured;
move_history_pointer = 0;
}
/* Place a stone on the board and update the board_hash. This operation
* destroys all move history.
*/
void
add_stone(int pos, int color)
{
ASSERT1(stackp == 0, pos);
ASSERT_ON_BOARD1(pos);
ASSERT1(board[pos] == EMPTY, pos);
board[pos] = color;
hashdata_invert_stone(&board_hash, pos, color);
reset_move_history();
new_position();
}
/* Remove a stone from the board and update the board_hash. This
* operation destroys the move history.
*/
void
remove_stone(int pos)
{
ASSERT1(stackp == 0, pos);
ASSERT_ON_BOARD1(pos);
ASSERT1(IS_STONE(board[pos]), pos);
hashdata_invert_stone(&board_hash, pos, board[pos]);
board[pos] = EMPTY;
reset_move_history();
new_position();
}
/* Play a move. Basically the same as play_move() below, but doesn't store
* the move in history list.
*
* Set `update_internals' to zero if you want to play several moves in a
* row to avoid overhead caused by new_position(). Don't forget to call
* it yourself after all the moves have been played.
*/
static void
play_move_no_history(int pos, int color, int update_internals)
{
#if CHECK_HASHING
Hash_data oldkey;
/* Check the hash table to see if it corresponds to the cumulative one. */
hashdata_recalc(&oldkey, board, board_ko_pos);
gg_assert(hashdata_is_equal(oldkey, board_hash));
#endif
if (board_ko_pos != NO_MOVE)
hashdata_invert_ko(&board_hash, board_ko_pos);
board_ko_pos = NO_MOVE;
/* If the move is a pass, we can skip some steps. */
if (pos != PASS_MOVE) {
ASSERT_ON_BOARD1(pos);
ASSERT1(board[pos] == EMPTY, pos);
/* Do play the move. */
if (!is_suicide(pos, color))
do_play_move(pos, color);
else
do_commit_suicide(pos, color);
#if CHECK_HASHING
/* Check the hash table to see if it equals the previous one. */
hashdata_recalc(&oldkey, board, board_ko_pos);
gg_assert(hashdata_is_equal(oldkey, board_hash));
#endif
}
if (update_internals || next_string == MAX_STRINGS)
new_position();
else
CLEAR_STACKS();
}
/* Load the initial position and replay the first n moves. */
static void
replay_move_history(int n)
{
int k;
memcpy(board, initial_board, sizeof(board));
board_ko_pos = initial_board_ko_pos;
white_captured = initial_white_captured;
black_captured = initial_black_captured;
new_position();
for (k = 0; k < n; k++)
play_move_no_history(move_history_pos[k], move_history_color[k], 0);
new_position();
}
/* Play a move. If you want to test for legality you should first call
* is_legal(). This function strictly follows the algorithm:
* 1. Place a stone of given color on the board.
* 2. If there are any adjacent opponent strings without liberties,
* remove them and increase the prisoner count.
* 3. If the newly placed stone is part of a string without liberties,
* remove it and increase the prisoner count.
*
* In spite of the name "permanent move", this move can (usually) be
* unplayed by undo_move(), but it is significantly more costly than
* unplaying a temporary move. There are limitations on the available
* move history, so under certain circumstances the move may not be
* possible to unplay at a later time.
*/
void
play_move(int pos, int color)
{
ASSERT1(stackp == 0, pos);
ASSERT1(color == WHITE || color == BLACK, pos);
ASSERT1(pos == PASS_MOVE || ON_BOARD1(pos), pos);
ASSERT1(pos == PASS_MOVE || board[pos] == EMPTY, pos);
ASSERT1(komaster == EMPTY && kom_pos == NO_MOVE, pos);
if (move_history_pointer >= MAX_MOVE_HISTORY) {
/* The move history is full. We resolve this by collapsing the
* first about 10% of the moves into the initial position.
*/
int number_collapsed_moves = 1 + MAX_MOVE_HISTORY / 10;
int k;
Intersection saved_board[BOARDSIZE];
int saved_board_ko_pos = board_ko_pos;
int saved_white_captured = white_captured;
int saved_black_captured = black_captured;
memcpy(saved_board, board, sizeof(board));
replay_move_history(number_collapsed_moves);
memcpy(initial_board, board, sizeof(board));
initial_board_ko_pos = board_ko_pos;
initial_white_captured = white_captured;
initial_black_captured = black_captured;
for (k = number_collapsed_moves; k < move_history_pointer; k++) {
move_history_color[k - number_collapsed_moves] = move_history_color[k];
move_history_pos[k - number_collapsed_moves] = move_history_pos[k];
move_history_hash[k - number_collapsed_moves] = move_history_hash[k];
}
move_history_pointer -= number_collapsed_moves;
memcpy(board, saved_board, sizeof(board));
board_ko_pos = saved_board_ko_pos;
white_captured = saved_white_captured;
black_captured = saved_black_captured;
new_position();
}
move_history_color[move_history_pointer] = color;
move_history_pos[move_history_pointer] = pos;
move_history_hash[move_history_pointer] = board_hash;
if (board_ko_pos != NO_MOVE)
hashdata_invert_ko(&move_history_hash[move_history_pointer], board_ko_pos);
move_history_pointer++;
play_move_no_history(pos, color, 1);
movenum++;
}
/* Undo n permanent moves. Returns 1 if successful and 0 if it fails.
* If n moves cannot be undone, no move is undone.
*/
int
undo_move(int n)
{
gg_assert(stackp == 0);
/* Fail if and only if the move history is too short. */
if (move_history_pointer < n)
return 0;
replay_move_history(move_history_pointer - n);
move_history_pointer -= n;
movenum -= n;
return 1;
}
/* Return the last move done by the opponent to color. Both if no move
* was found or if the last move was a pass, PASS_MOVE is returned.
*/
int
get_last_opponent_move(int color)
{
int k;
for (k = move_history_pointer - 1; k >= 0; k--)
if (move_history_color[k] == OTHER_COLOR(color))
return move_history_pos[k];
return PASS_MOVE;
}
/* Return the last move done by anyone. Both if no move was found or
* if the last move was a pass, PASS_MOVE is returned.
*/
int
get_last_move()
{
if (move_history_pointer == 0)
return PASS_MOVE;
return move_history_pos[move_history_pointer - 1];
}
/* Return the color of the player doing the last move. If no move was
* found, EMPTY is returned.
*/
int
get_last_player()
{
if (move_history_pointer == 0)
return EMPTY;
return move_history_color[move_history_pointer - 1];
}
/* ================================================================ */
/* Utility functions */
/* ================================================================ */
/*
* Test if the move is a pass or not. Return 1 if it is.
*/
int
is_pass(int pos)
{
return pos == 0;
}
/*
* is_legal(pos, color) determines whether the move (color) at pos is
* legal. This is for internal use in the engine and always assumes
* that suicide is allowed and only simple ko restrictions, no
* superko, regardless of the rules actually used in the game.
*
* Use is_allowed_move() if you want to take alternative suicide and
* ko rules into account.
*/
int
is_legal(int pos, int color)
{
/* 0. A pass move is always legal. */
if (pos == PASS_MOVE)
return 1;
/* 1. The move must be inside the board. */
ASSERT_ON_BOARD1(pos);
/* 2. The location must be empty. */
if (board[pos] != EMPTY)
return 0;
/* 3. The location must not be the ko point. */
if (pos == board_ko_pos) {
/* The ko position is guaranteed to have all neighbors of the
* same color, or off board. If that color is the same as the
* move the ko is being filled, which is always allowed. This
* could be tested with has_neighbor() but here a faster test
* suffices.
*/
if (board[WEST(pos)] == OTHER_COLOR(color)
|| board[EAST(pos)] == OTHER_COLOR(color)) {
return 0;
}
}
/* Check for stack overflow. */
if (stackp >= MAXSTACK-2) {
fprintf(stderr,
"gnugo: Truncating search. This is beyond my reading ability!\n");
/* FIXME: Perhaps it's best to just assert here and be done with it? */
if (0) {
ASSERT1(0 && "is_legal stack overflow", pos);
}
return 0;
}
/* Check for suicide. */
if (is_suicide(pos, color))
return 0;
return 1;
}
/*
* is_suicide(pos, color) determines whether the move (color) at
* (pos) would be a suicide.
*
* This is the case if
* 1. There is no neighboring empty intersection.
* 2. There is no neighboring opponent string with exactly one liberty.
* 3. There is no neighboring friendly string with more than one liberty.
*/
int
is_suicide(int pos, int color)
{
ASSERT_ON_BOARD1(pos);
ASSERT1(board[pos] == EMPTY, pos);
/* Check for suicide. */
if (LIBERTY(SOUTH(pos))
|| (ON_BOARD(SOUTH(pos))
&& ((board[SOUTH(pos)] == color) ^ (LIBERTIES(SOUTH(pos)) == 1))))
return 0;
if (LIBERTY(WEST(pos))
|| (ON_BOARD(WEST(pos))
&& ((board[WEST(pos)] == color) ^ (LIBERTIES(WEST(pos)) == 1))))
return 0;
if (LIBERTY(NORTH(pos))
|| (ON_BOARD(NORTH(pos))
&& ((board[NORTH(pos)] == color) ^ (LIBERTIES(NORTH(pos)) == 1))))
return 0;
if (LIBERTY(EAST(pos))
|| (ON_BOARD(EAST(pos))
&& ((board[EAST(pos)] == color) ^ (LIBERTIES(EAST(pos)) == 1))))
return 0;
return 1;
}
/*
* is_illegal_ko_capture(pos, color) determines whether the move
* (color) at (pos) would be an illegal ko capture.
*/
int
is_illegal_ko_capture(int pos, int color)
{
ASSERT_ON_BOARD1(pos);
ASSERT1(board[pos] == EMPTY, pos);
return (pos == board_ko_pos
&& ((board[WEST(pos)] == OTHER_COLOR(color))
|| (board[EAST(pos)] == OTHER_COLOR(color))));
}
/*
* is_allowed_move(int pos, int color) determines whether a move is
* legal with respect to the suicide and ko rules in play.
*
* This function is only valid when stackp == 0 since there is no
* tracking of superko for trymoves.
*/
int
is_allowed_move(int pos, int color)
{
gg_assert(stackp == 0);
/* 1. A pass move is always legal, no matter what. */
if (pos == PASS_MOVE)
return 1;
/* 2. The move must be inside the board. */
ASSERT_ON_BOARD1(pos);
/* 3. The location must be empty. */
if (board[pos] != EMPTY)
return 0;
/* 4. Simple ko repetition is only allowed if no ko rule is in use.
* For superko rules this check is redundant.
*
* The ko position is guaranteed to have all neighbors of the
* same color, or off board. If that color is the same as the
* move the ko is being filled, which is always allowed. This
* could be tested with has_neighbor() but here a faster test
* suffices.
*/
if (ko_rule != NONE
&& pos == board_ko_pos
&& (board[WEST(pos)] == OTHER_COLOR(color)
|| board[EAST(pos)] == OTHER_COLOR(color)))
return 0;
/* 5. Check for suicide. Suicide rule options:
* FORBIDDEN - No suicides allowed.
* ALLOWED - Suicide of more than one stone allowed.
* ALL_ALLOWED - All suicides allowed.
*/
if (is_suicide(pos, color))
if (suicide_rule == FORBIDDEN
|| (suicide_rule == ALLOWED
&& !has_neighbor(pos, color)))
return 0;
/* 6. Check for whole board repetitions. The superko options are
* SIMPLE, NONE - No superko restrictions.
* PSK - Repetition of a previous position forbidden.
* SSK - Repetition of a previous position with the same
* player to move forbidden.
*/
if (is_superko_violation(pos, color, ko_rule))
return 0;
return 1;
}
/* Necessary work to set the new komaster state. */
static void
set_new_komaster(int new_komaster)
{
PUSH_VALUE(komaster);
hashdata_invert_komaster(&board_hash, komaster);
komaster = new_komaster;
hashdata_invert_komaster(&board_hash, komaster);
}
/* Necessary work to set the new komaster position. */
static void
set_new_kom_pos(int new_kom_pos)
{
PUSH_VALUE(kom_pos);
hashdata_invert_kom_pos(&board_hash, kom_pos);
kom_pos = new_kom_pos;
hashdata_invert_kom_pos(&board_hash, kom_pos);
}
/* Variation of trymove()/tryko() where ko captures (both conditional
* and unconditional) must follow a komaster scheme.
*
* Historical note: Up to GNU Go 3.4 five different komaster schemes
* were implemented and could easily be switched between. In GNU Go
* 3.5.1 four of them were removed to simplify the code and because it
* no longer seemed interesting to be able to switch. The remaining
* komaster scheme was previously known as komaster scheme 5 (or V).
*
* FIXME: This function could be optimized by integrating the
* trymove()/tryko() code.
*/
/* V. Complex scheme, O to move.
*
* 1. Komaster is EMPTY.
* 1a) Unconditional ko capture is allowed.
* Komaster remains EMPTY if previous move was not a ko capture.
* Komaster is set to WEAK_KO if previous move was a ko capture
* and kom_pos is set to the old value of board_ko_pos.
* 1b) Conditional ko capture is allowed. Komaster is set to O and
* kom_pos to the location of the ko, where a stone was
* just removed.
*
* 2. Komaster is O:
* 2a) Only nested ko captures are allowed. Kom_pos is moved to the
* new removed stone.
* 2b) If komaster fills the ko at kom_pos then komaster reverts to
* EMPTY.
*
* 3. Komaster is X:
* Play at kom_pos is not allowed. Any other ko capture
* is allowed. If O takes another ko, komaster becomes GRAY_X.
*
* 4. Komaster is GRAY_O or GRAY_X:
* Ko captures are not allowed. If the ko at kom_pos is
* filled then the komaster reverts to EMPTY.
*
* 5. Komaster is WEAK_KO:
* 5a) After a non-ko move komaster reverts to EMPTY.
* 5b) Unconditional ko capture is only allowed if it is nested ko capture.
* Komaster is changed to WEAK_X and kom_pos to the old value of
* board_ko_pos.
* 5c) Conditional ko capture is allowed according to the rules of 1b.
*/
int
komaster_trymove(int pos, int color, const char *message, int str,
int *is_conditional_ko, int consider_conditional_ko)
{
int other = OTHER_COLOR(color);
int ko_move;
int kpos;
int previous_board_ko_pos = board_ko_pos;
*is_conditional_ko = 0;
ko_move = is_ko(pos, color, &kpos);
if (ko_move) {
/* If opponent is komaster we may not capture his ko. */
if (komaster == other && pos == kom_pos)
return 0;
/* If komaster is gray we may not capture ko at all. */
if (komaster == GRAY_WHITE || komaster == GRAY_BLACK)
return 0;
/* If we are komaster, we may only do nested captures. */
if (komaster == color && !DIAGONAL_NEIGHBORS(kpos, kom_pos))
return 0;
/* If komaster is WEAK_KO, we may only do nested ko capture or
* conditional ko capture.
*/
if (komaster == WEAK_KO) {
if (pos != board_ko_pos && !DIAGONAL_NEIGHBORS(kpos, kom_pos))
return 0;
}
}
if (!trymove(pos, color, message, str)) {
if (!consider_conditional_ko)
return 0;
if (!tryko(pos, color, message))
return 0; /* Suicide. */
*is_conditional_ko = 1;
/* Conditional ko capture, set komaster parameters. */
if (komaster == EMPTY || komaster == WEAK_KO) {
set_new_komaster(color);
set_new_kom_pos(kpos);
return 1;
}
}
if (!ko_move) {
/* If we are komaster, check whether the ko was resolved by the
* current move. If that is the case, revert komaster to EMPTY.
*
* The ko has been resolved in favor of the komaster if it has
* been filled, or if it is no longer a ko and an opponent move
* there is suicide.
*/
if (((komaster == color
|| (komaster == GRAY_WHITE && color == WHITE)
|| (komaster == GRAY_BLACK && color == BLACK))
&& (IS_STONE(board[kom_pos])
|| (!is_ko(kom_pos, other, NULL)
&& is_suicide(kom_pos, other))))) {
set_new_komaster(EMPTY);
set_new_kom_pos(NO_MOVE);
}
if (komaster == WEAK_KO) {
set_new_komaster(EMPTY);
set_new_kom_pos(NO_MOVE);
}
return 1;
}
if (komaster == other) {
if (color == WHITE)
set_new_komaster(GRAY_BLACK);
else
set_new_komaster(GRAY_WHITE);
}
else if (komaster == color) {
/* This is where we update kom_pos after a nested capture. */
set_new_kom_pos(kpos);
}
else {
/* We can reach here when komaster is EMPTY or WEAK_KO. If previous
* move was also a ko capture, we now set komaster to WEAK_KO.
*/
if (previous_board_ko_pos != NO_MOVE) {
set_new_komaster(WEAK_KO);
set_new_kom_pos(previous_board_ko_pos);
}
}
return 1;
}
int
get_komaster()
{
return komaster;
}
int
get_kom_pos()
{
return kom_pos;
}
/* Determine whether vertex is on the edge. */
int
is_edge_vertex(int pos)
{
ASSERT_ON_BOARD1(pos);
if (!ON_BOARD(SW(pos))
|| !ON_BOARD(NE(pos)))
return 1;
return 0;
}
/* Distance to the edge. */
int
edge_distance(int pos)
{
int i = I(pos);
int j = J(pos);
ASSERT_ON_BOARD1(pos);
return gg_min(gg_min(i, board_size-1 - i), gg_min(j, board_size-1 - j));
}
/* Determine whether vertex is a corner. */
int
is_corner_vertex(int pos)
{
ASSERT_ON_BOARD1(pos);
if ((!ON_BOARD(WEST(pos)) || !ON_BOARD(EAST(pos)))
&& (!ON_BOARD(SOUTH(pos)) || !ON_BOARD(NORTH(pos))))
return 1;
return 0;
}
/* Reorientation of point pos. This function could have been
* implemented using the rotate() function in utils/gg_utils.c but we
* don't want to make libboard dependent on utils.
*/
int
rotate1(int pos, int rot)
{
int bs = board_size - 1;
int i = I(pos);
int j = J(pos);
gg_assert(rot >= 0 && rot < 8);
if (pos == PASS_MOVE)
return PASS_MOVE;
if (rot == 0)
return pos; /* identity map */
if (rot == 1)
return POS(bs - j, i); /* rotation over 90 degrees */
if (rot == 2)
return POS(bs - i, bs - j); /* rotation over 180 degrees */
if (rot == 3)
return POS(j, bs - i); /* rotation over 270 degrees */
if (rot == 4)
return POS(j, i); /* flip along diagonal */
if (rot == 5)
return POS(bs - i, j); /* flip */
if (rot == 6)
return POS(bs - j, bs - i); /* flip along diagonal */
if (rot == 7)
return POS(i, bs - j); /* flip */
return PASS_MOVE; /* unreachable */
}
/* Returns true if the empty vertex respectively the string at pos1 is
* adjacent to the empty vertex respectively the string at pos2.
*/
int
are_neighbors(int pos1, int pos2)
{
if (board[pos1] == EMPTY) {
if (board[pos2] == EMPTY)
return (gg_abs(pos1 - pos2) == NS || gg_abs(pos1 - pos2) == WE);
else
return neighbor_of_string(pos1, pos2);
}
else {
if (board[pos2] == EMPTY)
return neighbor_of_string(pos2, pos1);
else
return adjacent_strings(pos1, pos2);
}
}
/* Count the number of liberties of the string at pos. pos must not be
* empty.
*/
int
countlib(int str)
{
ASSERT1(IS_STONE(board[str]), str);
/* We already know the number of liberties. Just look it up. */
return string[string_number[str]].liberties;
}
/* Find the liberties of the string at str. str must not be
* empty. The locations of up to maxlib liberties are written into
* libs[]. The full number of liberties is returned.
*
* If you want the locations of all liberties, whatever their number,
* you should pass MAXLIBS as the value for maxlib and allocate space
* for libs[] accordingly.
*/
int
findlib(int str, int maxlib, int *libs)
{
int k;
int liberties;
int s;
ASSERT1(IS_STONE(board[str]), str);
ASSERT1(libs != NULL, str);
/* We already have the list of liberties and only need to copy it to
* libs[].
*
* However, if the string has more than MAX_LIBERTIES liberties the
* list is truncated and if maxlib is also larger than MAX_LIBERTIES
* we have to traverse the stones in the string in order to find
* where the liberties are.
*/
s = string_number[str];
liberties = string[s].liberties;
if (liberties <= MAX_LIBERTIES || maxlib <= MAX_LIBERTIES) {
/* The easy case, it suffices to copy liberty locations from the
* incrementally updated list.
*/
for (k = 0; k < maxlib && k < liberties; k++)
libs[k] = string_libs[s].list[k];
}
else {
/* The harder case, where we have to traverse the stones in the
* string. We don't have to check explicitly if we are back to
* the start of the chain since we will run out of liberties
* before that happens.
*/
int pos;
liberty_mark++;
for (k = 0, pos = FIRST_STONE(s);
k < maxlib && k < liberties;
pos = NEXT_STONE(pos)) {
if (UNMARKED_LIBERTY(SOUTH(pos))) {
libs[k++] = SOUTH(pos);
MARK_LIBERTY(SOUTH(pos));
if (k >= maxlib)
break;
}
if (UNMARKED_LIBERTY(WEST(pos))) {
libs[k++] = WEST(pos);
MARK_LIBERTY(WEST(pos));
if (k >= maxlib)
break;
}
if (UNMARKED_LIBERTY(NORTH(pos))) {
libs[k++] = NORTH(pos);
MARK_LIBERTY(NORTH(pos));
if (k >= maxlib)
break;
}
if (UNMARKED_LIBERTY(EAST(pos))) {
libs[k++] = EAST(pos);
MARK_LIBERTY(EAST(pos));
if (k >= maxlib)
break;
}
}
}
return liberties;
}
/* Count the liberties a stone of the given color would get if played
* at (pos). The location (pos) must be empty.
*
* The intent of this function is to be as fast as possible, not
* necessarily complete. But if it returns a positive value (meaning
* it has succeeded), the value is guaranteed to be correct.
*
* Captures are ignored based on the ignore_capture flag. The function
* fails if there are more than two neighbor strings of the same
* color. In this case, the return value is -1. Captures are handled
* in a very limited way, so if ignore_capture is 0, and a capture is
* required, it will often return -1.
*
* Note well, that it relies on incremental data.
*/
int
fastlib(int pos, int color, int ignore_captures)
{
int ally1 = -1;
int ally2 = -1;
int fast_liberties = 0;
ASSERT1(board[pos] == EMPTY, pos);
ASSERT1(IS_STONE(color), pos);
/* Find neighboring strings of the same color. If there are more than two of
* them, we give up (it's too difficult to count their common liberties).
*/
if (board[SOUTH(pos)] == color) {
ally1 = string_number[SOUTH(pos)];
if (board[WEST(pos)] == color
&& string_number[WEST(pos)] != ally1) {
ally2 = string_number[WEST(pos)];
if (board[NORTH(pos)] == color
&& string_number[NORTH(pos)] != ally1
&& string_number[NORTH(pos)] != ally2)
return -1;
}
else if (board[NORTH(pos)] == color
&& string_number[NORTH(pos)] != ally1)
ally2 = string_number[NORTH(pos)];
if (board[EAST(pos)] == color
&& string_number[EAST(pos)] != ally1) {
if (ally2 < 0)
ally2 = string_number[EAST(pos)];
else if (string_number[EAST(pos)] != ally2)
return -1;
}
}
else if (board[WEST(pos)] == color) {
ally1 = string_number[WEST(pos)];
if (board[NORTH(pos)] == color
&& string_number[NORTH(pos)] != ally1) {
ally2 = string_number[NORTH(pos)];
if (board[EAST(pos)] == color
&& string_number[EAST(pos)] != ally1
&& string_number[EAST(pos)] != ally2)
return -1;
}
else if (board[EAST(pos)] == color
&& string_number[EAST(pos)] != ally1)
ally2 = string_number[EAST(pos)];
}
else if (board[NORTH(pos)] == color) {
ally1 = string_number[NORTH(pos)];
if (board[EAST(pos)] == color
&& string_number[EAST(pos)] != ally1)
ally2 = string_number[EAST(pos)];
}
else if (board[EAST(pos)] == color)
ally1 = string_number[EAST(pos)];
/* If we are to ignore captures, the things are very easy. */
if (ignore_captures) {
if (ally1 < 0) { /* No allies */
if (LIBERTY(SOUTH(pos)))
fast_liberties++;
if (LIBERTY(WEST(pos)))
fast_liberties++;
if (LIBERTY(NORTH(pos)))
fast_liberties++;
if (LIBERTY(EAST(pos)))
fast_liberties++;
}
else if (ally2 < 0) { /* One ally */
if (LIBERTY(SOUTH(pos))
&& !NON_SOUTH_NEIGHBOR_OF_STRING(SOUTH(pos), ally1, color))
fast_liberties++;
if (LIBERTY(WEST(pos))
&& !NON_WEST_NEIGHBOR_OF_STRING(WEST(pos), ally1, color))
fast_liberties++;
if (LIBERTY(NORTH(pos))
&& !NON_NORTH_NEIGHBOR_OF_STRING(NORTH(pos), ally1, color))
fast_liberties++;
if (LIBERTY(EAST(pos))
&& !NON_EAST_NEIGHBOR_OF_STRING(EAST(pos), ally1, color))
fast_liberties++;
fast_liberties += string[ally1].liberties - 1;
}
else { /* Two allies */
if (LIBERTY(SOUTH(pos))
&& !NON_SOUTH_NEIGHBOR_OF_STRING(SOUTH(pos), ally1, color)
&& !NON_SOUTH_NEIGHBOR_OF_STRING(SOUTH(pos), ally2, color))
fast_liberties++;
if (LIBERTY(WEST(pos))
&& !NON_WEST_NEIGHBOR_OF_STRING(WEST(pos), ally1, color)
&& !NON_WEST_NEIGHBOR_OF_STRING(WEST(pos), ally2, color))
fast_liberties++;
if (LIBERTY(NORTH(pos))
&& !NON_NORTH_NEIGHBOR_OF_STRING(NORTH(pos), ally1, color)
&& !NON_NORTH_NEIGHBOR_OF_STRING(NORTH(pos), ally2, color))
fast_liberties++;
if (LIBERTY(EAST(pos))
&& !NON_EAST_NEIGHBOR_OF_STRING(EAST(pos), ally1, color)
&& !NON_EAST_NEIGHBOR_OF_STRING(EAST(pos), ally2, color))
fast_liberties++;
fast_liberties += string[ally1].liberties + string[ally2].liberties
- count_common_libs(string[ally1].origin, string[ally2].origin) - 1;
}
}
/* We are to take captures into account. This case is much more rare, so
* it is not optimized much.
*/
else {
int k;
for (k = 0; k < 4; k++) {
int neighbor = pos + delta[k];
if (LIBERTY(neighbor)
&& (ally1 < 0 || !NEIGHBOR_OF_STRING(neighbor, ally1, color))
&& (ally2 < 0 || !NEIGHBOR_OF_STRING(neighbor, ally2, color)))
fast_liberties++;
else if (board[neighbor] == OTHER_COLOR(color) /* A capture */
&& LIBERTIES(neighbor) == 1) {
int neighbor_size = COUNTSTONES(neighbor);
if (neighbor_size == 1 || (neighbor_size == 2 && ally1 < 0))
fast_liberties++;
else
return -1;
}
}
if (ally1 >= 0) {
fast_liberties += string[ally1].liberties - 1;
if (ally2 >= 0)
fast_liberties += string[ally2].liberties
- count_common_libs(string[ally1].origin, string[ally2].origin);
}
}
return fast_liberties;
}
/* Effectively true unless we store full position in hash. */
#define USE_BOARD_CACHES (NUM_HASHVALUES <= 4)
struct board_cache_entry {
int threshold;
int liberties;
Hash_data position_hash;
};
/* approxlib() cache. */
static struct board_cache_entry approxlib_cache[BOARDMAX][2];
/* Clears approxlib() cache. This function should be called only once
* during engine initialization. Sets thresholds to zero.
*/
void
clear_approxlib_cache(void)
{
int pos;
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
approxlib_cache[pos][0].threshold = 0;
approxlib_cache[pos][1].threshold = 0;
}
}
/* Find the liberties a stone of the given color would get if played
* at (pos), ignoring possible captures of opponent stones. (pos)
* must be empty. If libs != NULL, the locations of up to maxlib
* liberties are written into libs[]. The counting of liberties may
* or may not be halted when maxlib is reached. The number of liberties
* found is returned.
*
* If you want the number or the locations of all liberties, however
* many they are, you should pass MAXLIBS as the value for maxlib and
* allocate space for libs[] accordingly.
*/
int
approxlib(int pos, int color, int maxlib, int *libs)
{
int liberties;
#ifdef USE_BOARD_CACHES
struct board_cache_entry *entry = &approxlib_cache[pos][color - 1];
ASSERT1(board[pos] == EMPTY, pos);
ASSERT1(IS_STONE(color), pos);
if (!libs) {
/* First see if this result is cached. */
if (hashdata_is_equal(board_hash, entry->position_hash)
&& maxlib <= entry->threshold) {
return entry->liberties;
}
liberties = fastlib(pos, color, 1);
if (liberties >= 0) {
/* Since fastlib() always returns precise result and doesn't take
* `maxlib' into account, we set threshold to MAXLIBS so that this
* result is used regardless of any `maxlib' passed.
*/
entry->threshold = MAXLIBS;
entry->liberties = liberties;
entry->position_hash = board_hash;
return liberties;
}
}
/* We initialize the cache entry threshold to `maxlib'. If do_approxlib()
* or slow_approxlib() finds all the liberties (that is, they don't use
* `maxlib' value for an early return), they will set threshold to
* MAXLIBS themselves.
*/
entry->threshold = maxlib;
if (maxlib <= MAX_LIBERTIES)
liberties = do_approxlib(pos, color, maxlib, libs);
else
liberties = slow_approxlib(pos, color, maxlib, libs);
entry->liberties = liberties;
entry->position_hash = board_hash;
#else /* not USE_BOARD_CACHES */
ASSERT1(board[pos] == EMPTY, pos);
ASSERT1(IS_STONE(color), pos);
if (!libs) {
liberties = fastlib(pos, color, 1);
if (liberties >= 0)
return liberties;
}
if (maxlib <= MAX_LIBERTIES)
liberties = do_approxlib(pos, color, maxlib, libs);
else
liberties = slow_approxlib(pos, color, maxlib, libs);
#endif /* not USE_BOARD_CACHES */
return liberties;
}
/* Does the real work of approxlib(). */
static int
do_approxlib(int pos, int color, int maxlib, int *libs)
{
int k;
int liberties = 0;
/* Look for empty neighbors and the liberties of the adjacent
* strings of the given color. The algorithm below won't work
* correctly if any of the adjacent strings have more than
* MAX_LIBERTIES liberties AND maxlib is larger than MAX_LIBERTIES.
* therefore approxlib() calls more robust slow_approxlib() if
* this might be the case.
*/
/* Start by marking pos itself so it isn't counted among its own
* liberties.
*/
liberty_mark++;
MARK_LIBERTY(pos);
if (UNMARKED_LIBERTY(SOUTH(pos))) {
if (libs != NULL)
libs[liberties] = SOUTH(pos);
liberties++;
/* Stop counting if we reach maxlib. */
if (liberties >= maxlib)
return liberties;
MARK_LIBERTY(SOUTH(pos));
}
else if (board[SOUTH(pos)] == color) {
int s = string_number[SOUTH(pos)];
for (k = 0; k < string[s].liberties; k++) {
int lib = string_libs[s].list[k];
if (UNMARKED_LIBERTY(lib)) {
if (libs != NULL)
libs[liberties] = lib;
liberties++;
if (liberties >= maxlib)
return liberties;
MARK_LIBERTY(lib);
}
}
}
if (UNMARKED_LIBERTY(WEST(pos))) {
if (libs != NULL)
libs[liberties] = WEST(pos);
liberties++;
/* Stop counting if we reach maxlib. */
if (liberties >= maxlib)
return liberties;
MARK_LIBERTY(WEST(pos));
}
else if (board[WEST(pos)] == color) {
int s = string_number[WEST(pos)];
for (k = 0; k < string[s].liberties; k++) {
int lib = string_libs[s].list[k];
if (UNMARKED_LIBERTY(lib)) {
if (libs != NULL)
libs[liberties] = lib;
liberties++;
if (liberties >= maxlib)
return liberties;
MARK_LIBERTY(lib);
}
}
}
if (UNMARKED_LIBERTY(NORTH(pos))) {
if (libs != NULL)
libs[liberties] = NORTH(pos);
liberties++;
/* Stop counting if we reach maxlib. */
if (liberties >= maxlib)
return liberties;
MARK_LIBERTY(NORTH(pos));
}
else if (board[NORTH(pos)] == color) {
int s = string_number[NORTH(pos)];
for (k = 0; k < string[s].liberties; k++) {
int lib = string_libs[s].list[k];
if (UNMARKED_LIBERTY(lib)) {
if (libs != NULL)
libs[liberties] = lib;
liberties++;
if (liberties >= maxlib)
return liberties;
MARK_LIBERTY(lib);
}
}
}
if (UNMARKED_LIBERTY(EAST(pos))) {
if (libs != NULL)
libs[liberties] = EAST(pos);
liberties++;
/* Unneeded since we're about to leave. */
#if 0
if (liberties >= maxlib)
return liberties;
MARK_LIBERTY(EAST(pos));
#endif
}
else if (board[EAST(pos)] == color) {
int s = string_number[EAST(pos)];
for (k = 0; k < string[s].liberties; k++) {
int lib = string_libs[s].list[k];
if (UNMARKED_LIBERTY(lib)) {
if (libs != NULL)
libs[liberties] = lib;
liberties++;
if (liberties >= maxlib)
return liberties;
MARK_LIBERTY(lib);
}
}
}
#if USE_BOARD_CACHES
/* If we reach here, then we have counted _all_ the liberties, so
* we set threshold to MAXLIBS (the result is the same regardless
* of `maxlib' value).
*/
if (!libs)
approxlib_cache[pos][color - 1].threshold = MAXLIBS;
#endif
return liberties;
}
/* Find the liberties a move of the given color at pos would have,
* excluding possible captures, by traversing all adjacent friendly
* strings. This is a fallback used by approxlib() when a faster
* algorithm can't be used.
*/
static int
slow_approxlib(int pos, int color, int maxlib, int *libs)
{
int k;
int liberties = 0;
liberty_mark++;
MARK_LIBERTY(pos);
string_mark++;
for (k = 0; k < 4; k++) {
int d = delta[k];
if (UNMARKED_LIBERTY(pos + d)) {
if (libs)
libs[liberties] = pos + d;
liberties++;
if (liberties == maxlib)
return liberties;
MARK_LIBERTY(pos + d);
}
else if (board[pos + d] == color
&& UNMARKED_STRING(pos + d)) {
int s = string_number[pos + d];
int pos2;
pos2 = FIRST_STONE(s);
do {
int l;
for (l = 0; l < 4; l++) {
int d2 = delta[l];
if (UNMARKED_LIBERTY(pos2 + d2)) {
if (libs)
libs[liberties] = pos2 + d2;
liberties++;
if (liberties == maxlib)
return liberties;
MARK_LIBERTY(pos2 + d2);
}
}
pos2 = NEXT_STONE(pos2);
} while (!BACK_TO_FIRST_STONE(s, pos2));
MARK_STRING(pos + d);
}
}
#if USE_BOARD_CACHES
/* If we reach here, then we have counted _all_ the liberties, so
* we set threshold to MAXLIBS (the result is the same regardless
* of `maxlib' value).
*/
if (!libs)
approxlib_cache[pos][color - 1].threshold = MAXLIBS;
#endif
return liberties;
}
/* accuratelib() cache. */
static struct board_cache_entry accuratelib_cache[BOARDMAX][2];
/* Clears accuratelib() cache. This function should be called only once
* during engine initialization. Sets thresholds to zero.
*/
void
clear_accuratelib_cache(void)
{
int pos;
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
accuratelib_cache[pos][0].threshold = 0;
accuratelib_cache[pos][1].threshold = 0;
}
}
/* Find the liberties a stone of the given color would get if played
* at (pos). This function takes into consideration all captures. Its
* return value is exact in that sense it counts all the liberties,
* unless (maxlib) allows it to stop earlier. (pos) must be empty. If
* libs != NULL, the locations of up to maxlib liberties are written
* into libs[]. The counting of liberties may or may not be halted
* when maxlib is reached. The number of found liberties is returned.
*
* This function guarantees that liberties which are not results of
* captures come first in libs[] array. To find whether all the
* liberties starting from a given one are results of captures, one
* may use if (board[libs[k]] != EMPTY) construction.
*
* If you want the number or the locations of all liberties, however
* many they are, you should pass MAXLIBS as the value for maxlib and
* allocate space for libs[] accordingly.
*/
int
accuratelib(int pos, int color, int maxlib, int *libs)
{
int liberties;
#ifdef USE_BOARD_CACHES
struct board_cache_entry *entry = &accuratelib_cache[pos][color - 1];
ASSERT1(board[pos] == EMPTY, pos);
ASSERT1(IS_STONE(color), pos);
if (!libs) {
/* First see if this result is cached. */
if (hashdata_is_equal(board_hash, entry->position_hash)
&& maxlib <= entry->threshold) {
return entry->liberties;
}
liberties = fastlib(pos, color, 0);
if (liberties >= 0) {
/* Since fastlib() always returns precise result and doesn't take
* `maxlib' into account, we set threshold to MAXLIBS so that this
* result is used regardless of any `maxlib' passed.
*/
entry->threshold = MAXLIBS;
entry->liberties = liberties;
entry->position_hash = board_hash;
return liberties;
}
}
liberties = do_accuratelib(pos, color, maxlib, libs);
/* If accuratelib() found less than `maxlib' liberties, then its
* result is certainly independent of `maxlib' and we set threshold
* to MAXLIBS.
*/
entry->threshold = liberties < maxlib ? MAXLIBS : maxlib;
entry->liberties = liberties;
entry->position_hash = board_hash;
#else /* not USE_BOARD_CACHES */
ASSERT1(board[pos] == EMPTY, pos);
ASSERT1(IS_STONE(color), pos);
if (!libs) {
liberties = fastlib(pos, color, 0);
if (liberties >= 0)
return liberties;
}
liberties = do_accuratelib(pos, color, maxlib, libs);
#endif /* not USE_BOARD_CACHES */
return liberties;
}
/* Does the real work of accuratelib(). */
static int
do_accuratelib(int pos, int color, int maxlib, int *libs)
{
int k, l;
int liberties = 0;
int lib;
int captured[4];
int captures = 0;
string_mark++;
liberty_mark++;
MARK_LIBERTY(pos);
for (k = 0; k < 4; k++) {
int pos2 = pos + delta[k];
if (UNMARKED_LIBERTY(pos2)) {
/* A trivial liberty */
if (libs)
libs[liberties] = pos2;
liberties++;
if (liberties >= maxlib)
return liberties;
MARK_LIBERTY(pos2);
}
else if (UNMARKED_COLOR_STRING(pos2, color)) {
/* An own neighbor string */
struct string_data *s = &string[string_number[pos2]];
struct string_liberties_data *sl = &string_libs[string_number[pos2]];
if (s->liberties <= MAX_LIBERTIES || maxlib <= MAX_LIBERTIES - 1) {
/* The easy case - we already have all (necessary) liberties of
* the string listed
*/
for (l = 0; l < s->liberties; l++) {
lib = sl->list[l];
if (UNMARKED_LIBERTY(lib)) {
if (libs)
libs[liberties] = lib;
liberties++;
if (liberties >= maxlib)
return liberties;
MARK_LIBERTY(lib);
}
}
}
else {
/* The harder case - we need to find all the liberties of the
* string by traversing its stones. We stop as soon as we have
* traversed all the stones or have reached maxlib. Unfortunately,
* we cannot use the trick from findlib() since some of the
* liberties may already have been marked.
*/
int stone = pos2;
do {
if (UNMARKED_LIBERTY(SOUTH(stone))) {
if (libs)
libs[liberties] = SOUTH(stone);
liberties++;
if (liberties >= maxlib)
return liberties;
MARK_LIBERTY(SOUTH(stone));
}
if (UNMARKED_LIBERTY(WEST(stone))) {
if (libs)
libs[liberties] = WEST(stone);
liberties++;
if (liberties >= maxlib)
return liberties;
MARK_LIBERTY(WEST(stone));
}
if (UNMARKED_LIBERTY(NORTH(stone))) {
if (libs)
libs[liberties] = NORTH(stone);
liberties++;
if (liberties >= maxlib)
return liberties;
MARK_LIBERTY(NORTH(stone));
}
if (UNMARKED_LIBERTY(EAST(stone))) {
if (libs)
libs[liberties] = EAST(stone);
liberties++;
if (liberties >= maxlib)
return liberties;
MARK_LIBERTY(EAST(stone));
}
stone = NEXT_STONE(stone);
} while (stone != pos2);
}
MARK_STRING(pos2);
}
else if (board[pos2] == OTHER_COLOR(color)
&& string[string_number[pos2]].liberties == 1) {
/* A capture. */
captured[captures++] = pos2;
}
}
/* Now we look at all the captures found in the previous step */
for (k = 0; k < captures; k++) {
lib = captured[k];
/* Add the stone adjacent to (pos) to the list of liberties if
* it is not also adjacent to an own marked string (otherwise,
* it will be added later).
*/
if (!MARKED_COLOR_STRING(SOUTH(lib), color)
&& !MARKED_COLOR_STRING(WEST(lib), color)
&& !MARKED_COLOR_STRING(NORTH(lib), color)
&& !MARKED_COLOR_STRING(EAST(lib), color)) {
if (libs)
libs[liberties] = lib;
liberties++;
if (liberties >= maxlib)
return liberties;
}
/* Check if we already know of this capture. */
for (l = 0; l < k; l++)
if (string_number[captured[l]] == string_number[lib])
break;
if (l == k) {
/* Traverse all the stones of the capture and add to the list
* of liberties those, which are adjacent to at least one own
* marked string.
*/
do {
if (MARKED_COLOR_STRING(SOUTH(lib), color)
|| MARKED_COLOR_STRING(WEST(lib), color)
|| MARKED_COLOR_STRING(NORTH(lib), color)
|| MARKED_COLOR_STRING(EAST(lib), color)) {
if (libs)
libs[liberties] = lib;
liberties++;
if (liberties >= maxlib)
return liberties;
}
lib = NEXT_STONE(lib);
} while (lib != captured[k]);
}
}
return liberties;
}
/* Find the number of common liberties of the two strings at str1 and str2.
*/
int
count_common_libs(int str1, int str2)
{
int all_libs1[MAXLIBS], *libs1;
int liberties1, liberties2;
int commonlibs = 0;
int k, n, tmp;
ASSERT_ON_BOARD1(str1);
ASSERT_ON_BOARD1(str2);
ASSERT1(IS_STONE(board[str1]), str1);
ASSERT1(IS_STONE(board[str2]), str2);
n = string_number[str1];
liberties1 = string[n].liberties;
if (liberties1 > string[string_number[str2]].liberties) {
n = string_number[str2];
liberties1 = string[n].liberties;
tmp = str1;
str1 = str2;
str2 = tmp;
}
if (liberties1 <= MAX_LIBERTIES) {
/* Speed optimization: don't copy liberties with findlib */
libs1 = string_libs[n].list;
n = string_number[str2];
liberties2 = string[n].liberties;
if (liberties2 <= MAX_LIBERTIES) {
/* Speed optimization: NEIGHBOR_OF_STRING is quite expensive */
liberty_mark++;
for (k = 0; k < liberties1; k++)
MARK_LIBERTY(libs1[k]);
libs1 = string_libs[n].list;
for (k = 0; k < liberties2; k++)
if (!UNMARKED_LIBERTY(libs1[k]))
commonlibs++;
return commonlibs;
}
}
else {
findlib(str1, MAXLIBS, all_libs1);
libs1 = all_libs1;
}
for (k = 0; k < liberties1; k++)
if (NEIGHBOR_OF_STRING(libs1[k], string_number[str2], board[str2]))
commonlibs++;
return commonlibs;
}
/* Find the common liberties of the two strings at str1 and str2. The
* locations of up to maxlib common liberties are written into libs[].
* The full number of common liberties is returned.
*
* If you want the locations of all common liberties, whatever their
* number, you should pass MAXLIBS as the value for maxlib and
* allocate space for libs[] accordingly.
*/
int
find_common_libs(int str1, int str2, int maxlib, int *libs)
{
int all_libs1[MAXLIBS], *libs1;
int liberties1, liberties2;
int commonlibs = 0;
int k, n, tmp;
ASSERT_ON_BOARD1(str1);
ASSERT_ON_BOARD1(str2);
ASSERT1(IS_STONE(board[str1]), str1);
ASSERT1(IS_STONE(board[str2]), str2);
ASSERT1(libs != NULL, str1);
n = string_number[str1];
liberties1 = string[n].liberties;
if (liberties1 > string[string_number[str2]].liberties) {
n = string_number[str2];
liberties1 = string[n].liberties;
tmp = str1;
str1 = str2;
str2 = tmp;
}
if (liberties1 <= MAX_LIBERTIES) {
/* Speed optimization: don't copy liberties with findlib */
libs1 = string_libs[n].list;
n = string_number[str2];
liberties2 = string[n].liberties;
if (liberties2 <= MAX_LIBERTIES) {
/* Speed optimization: NEIGHBOR_OF_STRING is quite expensive */
liberty_mark++;
for (k = 0; k < liberties1; k++)
MARK_LIBERTY(libs1[k]);
libs1 = string_libs[n].list;
for (k = 0; k < liberties2; k++)
if (!UNMARKED_LIBERTY(libs1[k])) {
if (commonlibs < maxlib)
libs[commonlibs] = libs1[k];
commonlibs++;
}
return commonlibs;
}
}
else {
findlib(str1, MAXLIBS, all_libs1);
libs1 = all_libs1;
}
for (k = 0; k < liberties1; k++)
if (NEIGHBOR_OF_STRING(libs1[k], string_number[str2], board[str2])) {
if (commonlibs < maxlib)
libs[commonlibs] = libs1[k];
commonlibs++;
}
return commonlibs;
}
/* Determine whether two strings have at least one common liberty.
* If they do and lib != NULL, one common liberty is returned in *lib.
*/
int
have_common_lib(int str1, int str2, int *lib)
{
int all_libs1[MAXLIBS], *libs1;
int liberties1;
int k, n, tmp;
ASSERT_ON_BOARD1(str1);
ASSERT_ON_BOARD1(str2);
ASSERT1(IS_STONE(board[str1]), str1);
ASSERT1(IS_STONE(board[str2]), str2);
n = string_number[str1];
liberties1 = string[n].liberties;
if (liberties1 > string[string_number[str2]].liberties) {
n = string_number[str2];
liberties1 = string[n].liberties;
tmp = str1;
str1 = str2;
str2 = tmp;
}
if (liberties1 <= MAX_LIBERTIES)
/* Speed optimization: don't copy liberties with findlib */
libs1 = string_libs[n].list;
else {
findlib(str1, MAXLIBS, all_libs1);
libs1 = all_libs1;
}
for (k = 0; k < liberties1; k++) {
if (NEIGHBOR_OF_STRING(libs1[k], string_number[str2], board[str2])) {
if (lib)
*lib = libs1[k];
return 1;
}
}
return 0;
}
/*
* Report the number of stones in a string.
*/
int
countstones(int str)
{
ASSERT_ON_BOARD1(str);
ASSERT1(IS_STONE(board[str]), str);
return COUNTSTONES(str);
}
/* Find the stones of the string at str. str must not be
* empty. The locations of up to maxstones stones are written into
* stones[]. The full number of stones is returned.
*/
int
findstones(int str, int maxstones, int *stones)
{
int s;
int size;
int pos;
int k;
ASSERT_ON_BOARD1(str);
ASSERT1(IS_STONE(board[str]), str);
s = string_number[str];
size = string[s].size;
/* Traverse the stones of the string, by following the cyclic chain. */
pos = FIRST_STONE(s);
for (k = 0; k < maxstones && k < size; k++) {
stones[k] = pos;
pos = NEXT_STONE(pos);
}
return size;
}
/* Counts how many stones in str1 are directly adjacent to str2.
* A limit can be given in the maxstones parameter so that the
* function returns immediately. See fast_defense() in reading.c
*/
int
count_adjacent_stones(int str1, int str2, int maxstones)
{
int s1, s2;
int size;
int pos;
int k;
int count = 0;
ASSERT_ON_BOARD1(str1);
ASSERT1(IS_STONE(board[str1]), str1);
ASSERT_ON_BOARD1(str2);
ASSERT1(IS_STONE(board[str2]), str2);
s1 = string_number[str1];
s2 = string_number[str2];
size = string[s1].size;
/* Traverse the stones of the string, by following the cyclic chain. */
pos = FIRST_STONE(s1);
for (k = 0; k < size && count < maxstones; k++) {
if (NEIGHBOR_OF_STRING(pos, s2, board[str2]))
count++;
pos = NEXT_STONE(pos);
}
return count;
}
/* chainlinks returns (in the (adj) array) the chains surrounding
* the string at (str). The number of chains is returned.
*/
int
chainlinks(int str, int adj[MAXCHAIN])
{
struct string_data *s;
struct string_neighbors_data *sn;
int k;
ASSERT1(IS_STONE(board[str]), str);
/* We already have the list ready, just copy it and fill in the
* desired information.
*/
s = &string[string_number[str]];
sn = &string_neighbors[string_number[str]];
for (k = 0; k < s->neighbors; k++)
adj[k] = string[sn->list[k]].origin;
return s->neighbors;
}
/* chainlinks2 returns (in adj array) those chains surrounding
* the string at str which have exactly lib liberties. The number
* of such chains is returned.
*/
int
chainlinks2(int str, int adj[MAXCHAIN], int lib)
{
struct string_data *s, *t;
struct string_neighbors_data *sn;
int k;
int neighbors;
ASSERT1(IS_STONE(board[str]), str);
/* We already have the list ready, just copy the strings with the
* right number of liberties.
*/
neighbors = 0;
s = &string[string_number[str]];
sn = &string_neighbors[string_number[str]];
for (k = 0; k < s->neighbors; k++) {
t = &string[sn->list[k]];
if (t->liberties == lib)
adj[neighbors++] = t->origin;
}
return neighbors;
}
/* chainlinks3 returns (in adj array) those chains surrounding
* the string at str, which have less or equal lib liberties.
* The number of such chains is returned.
*/
int
chainlinks3(int str, int adj[MAXCHAIN], int lib)
{
struct string_data *s, *t;
struct string_neighbors_data *sn;
int k;
int neighbors;
ASSERT1(IS_STONE(board[str]), str);
/* We already have the list ready, just copy the strings with the
* right number of liberties.
*/
neighbors = 0;
s = &string[string_number[str]];
sn = &string_neighbors[string_number[str]];
for (k = 0; k < s->neighbors; k++) {
t = &string[sn->list[k]];
if (t->liberties <= lib)
adj[neighbors++] = t->origin;
}
return neighbors;
}
/* extended_chainlinks() returns (in the (adj) array) the opponent
* strings being directly adjacent to (str) or having a common liberty
* with (str). The number of such strings is returned.
*
* If the both_colors parameter is true, also own strings sharing a
* liberty are returned.
*/
int
extended_chainlinks(int str, int adj[MAXCHAIN], int both_colors)
{
struct string_data *s;
struct string_neighbors_data *sn;
int n;
int k;
int r;
int libs[MAXLIBS];
int liberties;
ASSERT1(IS_STONE(board[str]), str);
/* We already have the list of directly adjacent strings ready, just
* copy it and mark the strings.
*/
s = &string[string_number[str]];
sn = &string_neighbors[string_number[str]];
string_mark++;
for (n = 0; n < s->neighbors; n++) {
adj[n] = string[sn->list[n]].origin;
MARK_STRING(adj[n]);
}
/* Get the liberties. */
liberties = findlib(str, MAXLIBS, libs);
/* Look for unmarked opponent strings next to a liberty and add the
* ones which are found to the output.
*/
for (r = 0; r < liberties; r++) {
for (k = 0; k < 4; k++) {
if ((board[libs[r] + delta[k]] == OTHER_COLOR(board[str])
|| (both_colors && board[libs[r] + delta[k]] == board[str]))
&& UNMARKED_STRING(libs[r] + delta[k])) {
adj[n] = string[string_number[libs[r] + delta[k]]].origin;
MARK_STRING(adj[n]);
n++;
}
}
}
return n;
}
/* Returns true if a move by (color) fits a shape like:
*
* -----
* O.O*X (O=color)
* OOXXX
*
* More specifically the move should have the following properties:
* - The move is a self-atari
* - The move forms a string of exactly two stones
* - When the opponent captures, the capturing stone becomes a single
* stone in atari
* - When capturing back the original position is repeated
*/
int
send_two_return_one(int move, int color)
{
int other = OTHER_COLOR(color);
int lib = NO_MOVE;
int friendly_neighbor = NO_MOVE;
int k;
ASSERT1(board[move] == EMPTY, move);
for (k = 0; k < 4; k++) {
int pos = move + delta[k];
if (board[pos] == EMPTY)
return 0;
if (board[pos] == color) {
int s;
if (friendly_neighbor != NO_MOVE)
return 0;
friendly_neighbor = pos;
s = string_number[pos];
if (string[s].size != 1 || string[s].liberties != 2)
return 0;
lib = string_libs[s].list[0] + string_libs[s].list[1] - move;
}
else if (board[pos] == other
&& string[string_number[pos]].liberties == 1)
return 0;
}
if (friendly_neighbor == NO_MOVE)
return 0;
for (k = 0; k < 4; k++) {
int pos = lib + delta[k];
if (board[pos] == EMPTY || board[pos] == other)
return 0;
if (board[pos] == color &&
string[string_number[pos]].liberties < 2)
return 0;
}
return 1;
}
/*
* Find the origin of a worm, i.e. the point with the
* smallest 1D board coordinate. The idea is to have a canonical
* reference point for a string.
*/
int
find_origin(int str)
{
ASSERT1(IS_STONE(board[str]), str);
return string[string_number[str]].origin;
}
/* Determine whether a move by color at (pos) would be a self atari,
* i.e. whether it would get more than one liberty. This function
* returns true also for the case of a suicide move.
*/
int
is_self_atari(int pos, int color)
{
int other = OTHER_COLOR(color);
/* number of empty neighbors */
int trivial_liberties = 0;
/* number of captured opponent strings */
int captures = 0;
/* Whether there is a friendly neighbor with a spare liberty. If it
* has more than one spare liberty we immediately return 0.
*/
int far_liberties = 0;
ASSERT_ON_BOARD1(pos);
ASSERT1(board[pos] == EMPTY, pos);
ASSERT1(IS_STONE(color), pos);
/* 1. Try first to solve the problem without much work. */
string_mark++;
if (LIBERTY(SOUTH(pos)))
trivial_liberties++;
else if (board[SOUTH(pos)] == color) {
if (LIBERTIES(SOUTH(pos)) > 2)
return 0;
if (LIBERTIES(SOUTH(pos)) == 2)
far_liberties++;
}
else if (board[SOUTH(pos)] == other
&& LIBERTIES(SOUTH(pos)) == 1 && UNMARKED_STRING(SOUTH(pos))) {
captures++;
MARK_STRING(SOUTH(pos));
}
if (LIBERTY(WEST(pos)))
trivial_liberties++;
else if (board[WEST(pos)] == color) {
if (LIBERTIES(WEST(pos)) > 2)
return 0;
if (LIBERTIES(WEST(pos)) == 2)
far_liberties++;
}
else if (board[WEST(pos)] == other
&& LIBERTIES(WEST(pos)) == 1 && UNMARKED_STRING(WEST(pos))) {
captures++;
MARK_STRING(WEST(pos));
}
if (LIBERTY(NORTH(pos)))
trivial_liberties++;
else if (board[NORTH(pos)] == color) {
if (LIBERTIES(NORTH(pos)) > 2)
return 0;
if (LIBERTIES(NORTH(pos)) == 2)
far_liberties++;
}
else if (board[NORTH(pos)] == other
&& LIBERTIES(NORTH(pos)) == 1 && UNMARKED_STRING(NORTH(pos))) {
captures++;
MARK_STRING(NORTH(pos));
}
if (LIBERTY(EAST(pos)))
trivial_liberties++;
else if (board[EAST(pos)] == color) {
if (LIBERTIES(EAST(pos)) > 2)
return 0;
if (LIBERTIES(EAST(pos)) == 2)
far_liberties++;
}
else if (board[EAST(pos)] == other
&& LIBERTIES(EAST(pos)) == 1 && UNMARKED_STRING(EAST(pos))) {
captures++;
#if 0
MARK_STRING(EAST(pos));
#endif
}
/* Each captured string is guaranteed to produce at least one
* liberty. These are disjoint from both trivial liberties and far
* liberties. The two latter may however coincide.
*/
if (trivial_liberties + captures >= 2)
return 0;
if ((far_liberties > 0) + captures >= 2)
return 0;
if (captures == 0 && far_liberties + trivial_liberties <= 1)
return 1;
/* 2. It was not so easy. We use accuratelib() in this case. */
return accuratelib(pos, color, 2, NULL) <= 1;
}
/*
* Returns true if pos is a liberty of the string at str.
*/
int
liberty_of_string(int pos, int str)
{
ASSERT_ON_BOARD1(pos);
ASSERT_ON_BOARD1(str);
if (IS_STONE(board[pos]))
return 0;
return NEIGHBOR_OF_STRING(pos, string_number[str], board[str]);
}
/*
* Returns true if pos is a second order liberty of the string at str.
*/
int
second_order_liberty_of_string(int pos, int str)
{
int k;
ASSERT_ON_BOARD1(pos);
ASSERT_ON_BOARD1(str);
for (k = 0; k < 4; k++)
if (board[pos + delta[k]] == EMPTY
&& NEIGHBOR_OF_STRING(pos + delta[k], string_number[str], board[str]))
return 1;
return 0;
}
/*
* Returns true if pos is adjacent to the string at str.
*/
int
neighbor_of_string(int pos, int str)
{
int color = board[str];
ASSERT1(IS_STONE(color), str);
ASSERT_ON_BOARD1(pos);
return NEIGHBOR_OF_STRING(pos, string_number[str], color);
}
/*
* Returns true if (pos) has a neighbor of color (color).
*/
int
has_neighbor(int pos, int color)
{
ASSERT_ON_BOARD1(pos);
ASSERT1(IS_STONE(color), pos);
return (board[SOUTH(pos)] == color
|| board[WEST(pos)] == color
|| board[NORTH(pos)] == color
|| board[EAST(pos)] == color);
}
/*
* Returns true if str1 and str2 belong to the same string.
*/
int
same_string(int str1, int str2)
{
ASSERT_ON_BOARD1(str1);
ASSERT_ON_BOARD1(str2);
ASSERT1(IS_STONE(board[str1]), str1);
ASSERT1(IS_STONE(board[str2]), str2);
return string_number[str1] == string_number[str2];
}
/*
* Returns true if the strings at str1 and str2 are adjacent.
*/
int
adjacent_strings(int str1, int str2)
{
int s1, s2;
int k;
ASSERT_ON_BOARD1(str1);
ASSERT_ON_BOARD1(str2);
ASSERT1(IS_STONE(board[str1]), str1);
ASSERT1(IS_STONE(board[str2]), str2);
s1 = string_number[str1];
s2 = string_number[str2];
for (k = 0; k < string[s1].neighbors; k++)
if (string_neighbors[s1].list[k] == s2)
return 1;
return 0;
}
/*
* Return true if the move (pos) by (color) is a ko capture
* (whether capture is legal on this move or not). If so,
* and if ko_pos is not a NULL pointer, then
* *ko_pos returns the location of the captured ko stone.
* If the move is not a ko capture, *ko_pos is set to 0.
*
* A move is a ko capture if and only if
* 1. All neighbors are opponent stones.
* 2. The number of captured stones is exactly one.
*/
int
is_ko(int pos, int color, int *ko_pos)
{
int other = OTHER_COLOR(color);
int captures = 0;
int kpos = 0;
ASSERT_ON_BOARD1(pos);
ASSERT1(color == WHITE || color == BLACK, pos);
if (ON_BOARD(SOUTH(pos))) {
if (board[SOUTH(pos)] != other)
return 0;
else if (LIBERTIES(SOUTH(pos)) == 1) {
kpos = SOUTH(pos);
captures += string[string_number[SOUTH(pos)]].size;
if (captures > 1)
return 0;
}
}
if (ON_BOARD(WEST(pos))) {
if (board[WEST(pos)] != other)
return 0;
else if (LIBERTIES(WEST(pos)) == 1) {
kpos = WEST(pos);
captures += string[string_number[WEST(pos)]].size;
if (captures > 1)
return 0;
}
}
if (ON_BOARD(NORTH(pos))) {
if (board[NORTH(pos)] != other)
return 0;
else if (LIBERTIES(NORTH(pos)) == 1) {
kpos = NORTH(pos);
captures += string[string_number[NORTH(pos)]].size;
if (captures > 1)
return 0;
}
}
if (ON_BOARD(EAST(pos))) {
if (board[EAST(pos)] != other)
return 0;
else if (LIBERTIES(EAST(pos)) == 1) {
kpos = EAST(pos);
captures += string[string_number[EAST(pos)]].size;
if (captures > 1)
return 0;
}
}
if (captures == 1) {
if (ko_pos)
*ko_pos = kpos;
return 1;
}
return 0;
}
/* Return true if pos is either a stone, which if captured would give
* ko, or if pos is an empty intersection adjacent to a ko stone.
*/
int
is_ko_point(int pos)
{
ASSERT_ON_BOARD1(pos);
if (board[pos] == EMPTY) {
int color;
if (ON_BOARD(SOUTH(pos)))
color = board[SOUTH(pos)];
else
color = board[NORTH(pos)];
if (IS_STONE(color) && is_ko(pos, OTHER_COLOR(color), NULL))
return 1;
}
else {
struct string_data *s = &string[string_number[pos]];
struct string_liberties_data *sl = &string_libs[string_number[pos]];
if (s->liberties == 1 && s->size == 1
&& is_ko(sl->list[0], OTHER_COLOR(s->color), NULL))
return 1;
}
return 0;
}
/* Return true if a move by color at pos is a superko violation
* according to the specified type of ko rules. This function does not
* detect simple ko unless it's also a superko violation.
*
* The superko detection is done by comparing board hashes from
* previous positions. For this to work correctly it's necessary to
* remove the contribution to the hash from the simple ko position.
* The move_history_hash array contains board hashes for previous
* positions, also without simple ko position contributions.
*/
static int
is_superko_violation(int pos, int color, enum ko_rules type)
{
Hash_data this_board_hash = board_hash;
Hash_data new_board_hash;
int k;
/* No superko violations if the ko rule is not a superko rule. */
if (type == NONE || type == SIMPLE)
return 0;
if (board_ko_pos != NO_MOVE)
hashdata_invert_ko(&this_board_hash, board_ko_pos);
really_do_trymove(pos, color);
new_board_hash = board_hash;
if (board_ko_pos != NO_MOVE)
hashdata_invert_ko(&new_board_hash, board_ko_pos);
undo_trymove();
/* The current position is only a problem with positional superko
* and a single stone suicide.
*/
if (type == PSK && hashdata_is_equal(this_board_hash, new_board_hash))
return 1;
for (k = move_history_pointer - 1; k >= 0; k--)
if (hashdata_is_equal(move_history_hash[k], new_board_hash)
&& (type == PSK
|| move_history_color[k] == OTHER_COLOR(color)))
return 1;
return 0;
}
/* Returns 1 if at least one string is captured when color plays at pos.
*/
int
does_capture_something(int pos, int color)
{
int other = OTHER_COLOR(color);
ASSERT1(board[pos] == EMPTY, pos);
if (board[SOUTH(pos)] == other && LIBERTIES(SOUTH(pos)) == 1)
return 1;
if (board[WEST(pos)] == other && LIBERTIES(WEST(pos)) == 1)
return 1;
if (board[NORTH(pos)] == other && LIBERTIES(NORTH(pos)) == 1)
return 1;
if (board[EAST(pos)] == other && LIBERTIES(EAST(pos)) == 1)
return 1;
return 0;
}
/* For each stone in the string at pos, set mx to value mark. */
void
mark_string(int str, signed char mx[BOARDMAX], signed char mark)
{
int pos = str;
ASSERT1(IS_STONE(board[str]), str);
do {
mx[pos] = mark;
pos = NEXT_STONE(pos);
} while (pos != str);
}
/* Returns true if at least one move has been played at pos
* at deeper than level 'cutoff' in the reading tree.
*/
int
move_in_stack(int pos, int cutoff)
{
int k;
for (k = cutoff; k < stackp; k++)
if (stack[k] == pos)
return 1;
return 0;
}
/* Retrieve a move from the move stack. */
void
get_move_from_stack(int k, int *move, int *color)
{
gg_assert(k < stackp);
*move = stack[k];
*color = move_color[k];
}
/* Return the number of stones of the indicated color(s) on the board.
* This only counts stones in the permanent position, not stones placed
* by trymove() or tryko(). Use stones_on_board(BLACK | WHITE) to get
* the total number of stones on the board.
*
* FIXME: This seems wrong, it uses the modified board, not the permanent
* one. /ab
*/
int
stones_on_board(int color)
{
static int stone_count_for_position = -1;
static int white_stones = 0;
static int black_stones = 0;
gg_assert(stackp == 0);
if (stone_count_for_position != position_number) {
int pos;
white_stones = 0;
black_stones = 0;
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (board[pos] == WHITE)
white_stones++;
else if (board[pos] == BLACK)
black_stones++;
}
stone_count_for_position = position_number;
}
return ((color & BLACK ? black_stones : 0) +
(color & WHITE ? white_stones : 0));
}
/* ===================== Statistics ============================= */
/* Clear statistics. */
void
reset_trymove_counter()
{
trymove_counter = 0;
}
/* Retrieve statistics. */
int
get_trymove_counter()
{
return trymove_counter;
}
/* ================================================================ */
/* Lower level functions */
/* ================================================================ */
/* This function should be called if the board is modified by other
* means than do_play_move() or undo_trymove().
*
* We have reached a new position. Increase the position counter and
* re-initialize the incremental strings.
*
* Set up incremental board structures and populate them with the
* strings available in the position given by board[]. Clear the stacks
* and start the mark numbers from zero. All undo information is lost
* by calling this function.
*/
static void
new_position(void)
{
int pos;
int s;
position_number++;
next_string = 0;
liberty_mark = 0;
string_mark = 0;
CLEAR_STACKS();
memset(string, 0, sizeof(string));
memset(string_libs, 0, sizeof(string_libs));
memset(string_neighbors, 0, sizeof(string_neighbors));
memset(ml, 0, sizeof(ml));
VALGRIND_MAKE_WRITABLE(next_stone, sizeof(next_stone));
/* propagate_string relies on non-assigned stones to have
* string_number -1.
*/
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
if (ON_BOARD(pos))
string_number[pos] = -1;
/* Find the existing strings. */
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (!ON_BOARD(pos))
continue;
if (IS_STONE(board[pos]) && string_number[pos] == -1) {
string_number[pos] = next_string;
string[next_string].size = propagate_string(pos, pos);
string[next_string].color = board[pos];
string[next_string].origin = pos;
string[next_string].mark = 0;
next_string++;
PARANOID1(next_string < MAX_STRINGS, pos);
}
}
/* Fill in liberty and neighbor info. */
for (s = 0; s < next_string; s++) {
find_liberties_and_neighbors(s);
}
}
#if 0
/*
* Debug function. Dump all string information.
*/
static void
dump_incremental_board(void)
{
int pos;
int s;
int i;
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (!ON_BOARD(pos))
continue;
if (board[pos] == EMPTY)
fprintf(stderr, " . ");
else
fprintf(stderr, "%2d ", string_number[pos]);
fprintf(stderr, "\n");
}
for (s = 0; s < next_string; s++) {
if (board[string[s].origin] == EMPTY)
continue;
gprintf("%o%d %s %1m size %d, %d liberties, %d neighbors\n", s,
color_to_string(string[s].color),
string[s].origin, string[s].size,
string[s].liberties, string[s].neighbors);
gprintf("%ostones:");
pos = FIRST_STONE(s);
do {
gprintf("%o %1m", pos);
pos = NEXT_STONE(pos);
} while (!BACK_TO_FIRST_STONE(s, pos));
gprintf("%o\nliberties:");
for (i = 0; i < string[s].liberties; i++)
gprintf("%o %1m", string[s].libs[i]);
gprintf("%o\nneighbors:");
for (i = 0; i < string[s].neighbors; i++)
gprintf("%o %d(%1m)", string[s].neighborlist[i],
string[string[s].neighborlist[i]].origin);
gprintf("%o\n\n");
}
}
#endif
/* Build a string and its cyclic list representation from scratch.
* propagate_string(stone, str) adds the stone (stone) to the string
* (str) and recursively continues with not already included friendly
* neighbors. To start a new string at (stone), use
* propagate_string(stone, stone). The size of the string is returned.
*/
static int
propagate_string(int stone, int str)
{
int size = 1;
int k;
if (stone == str) {
/* Start a new string. */
next_stone[stone] = stone;
}
else {
/* Link the stone at (stone) to the string including (str) */
string_number[stone] = string_number[str];
next_stone[stone] = next_stone[str];
next_stone[str] = stone;
}
/* Look in all four directions for more stones to add. */
for (k = 0; k < 4; k++) {
int d = delta[k];
if (ON_BOARD(stone + d)
&& board[stone + d] == board[stone]
&& string_number[stone + d] == -1)
size += propagate_string(stone + d, str);
}
return size;
}
/* Build the lists of liberties and neighbors of a string from
* scratch. No information is pushed onto the stack by this function.
*/
static void
find_liberties_and_neighbors(int s)
{
int pos;
int other = OTHER_COLOR(string[s].color);
/* Clear the marks. */
liberty_mark++;
string_mark++;
/* Traverse the stones of the string, by following the cyclic chain. */
pos = FIRST_STONE(s);
do {
/* Look in each direction for new liberties or new neighbors. Mark
* already visited liberties and neighbors.
*/
if (UNMARKED_LIBERTY(SOUTH(pos))) {
ADD_AND_MARK_LIBERTY(s, SOUTH(pos));
}
else if (UNMARKED_COLOR_STRING(SOUTH(pos), other)) {
ADD_NEIGHBOR(s, SOUTH(pos));
MARK_STRING(SOUTH(pos));
}
if (UNMARKED_LIBERTY(WEST(pos))) {
ADD_AND_MARK_LIBERTY(s, WEST(pos));
}
else if (UNMARKED_COLOR_STRING(WEST(pos), other)) {
ADD_NEIGHBOR(s, WEST(pos));
MARK_STRING(WEST(pos));
}
if (UNMARKED_LIBERTY(NORTH(pos))) {
ADD_AND_MARK_LIBERTY(s, NORTH(pos));
}
else if (UNMARKED_COLOR_STRING(NORTH(pos), other)) {
ADD_NEIGHBOR(s, NORTH(pos));
MARK_STRING(NORTH(pos));
}
if (UNMARKED_LIBERTY(EAST(pos))) {
ADD_AND_MARK_LIBERTY(s, EAST(pos));
}
else if (UNMARKED_COLOR_STRING(EAST(pos), other)) {
ADD_NEIGHBOR(s, EAST(pos));
MARK_STRING(EAST(pos));
}
pos = NEXT_STONE(pos);
} while (!BACK_TO_FIRST_STONE(s, pos));
}
/* Update the liberties of a string from scratch, first pushing the
* old information.
*/
static void
update_liberties(int s)
{
int pos;
int k;
/* Push the old information. */
PUSH_VALUE(string[s].liberties);
for (k = 0; k < string[s].liberties && k < MAX_LIBERTIES; k++) {
PUSH_VALUE(string_libs[s].list[k]);
}
string[s].liberties = 0;
/* Clear the liberty mark. */
liberty_mark++;
/* Traverse the stones of the string, by following the cyclic chain. */
pos = FIRST_STONE(s);
do {
/* Look in each direction for new liberties. Mark already visited
* liberties.
*/
if (UNMARKED_LIBERTY(SOUTH(pos))) {
ADD_AND_MARK_LIBERTY(s, SOUTH(pos));
}
if (UNMARKED_LIBERTY(WEST(pos))) {
ADD_AND_MARK_LIBERTY(s, WEST(pos));
}
if (UNMARKED_LIBERTY(NORTH(pos))) {
ADD_AND_MARK_LIBERTY(s, NORTH(pos));
}
if (UNMARKED_LIBERTY(EAST(pos))) {
ADD_AND_MARK_LIBERTY(s, EAST(pos));
}
pos = NEXT_STONE(pos);
} while (!BACK_TO_FIRST_STONE(s, pos));
}
/* Remove a string from the list of neighbors and push the changed
* information.
*/
static void
remove_neighbor(int str_number, int n)
{
int k;
int done = 0;
struct string_data *s = &string[str_number];
struct string_neighbors_data *sn = &string_neighbors[str_number];
for (k = 0; k < s->neighbors; k++)
if (sn->list[k] == n) {
/* We need to push the last entry too because it may become
* destroyed later.
*/
PUSH_VALUE(sn->list[s->neighbors - 1]);
PUSH_VALUE(sn->list[k]);
PUSH_VALUE(s->neighbors);
sn->list[k] = sn->list[s->neighbors - 1];
s->neighbors--;
done = 1;
break;
}
gg_assert(done);
}
/* Remove one liberty from the list of liberties, pushing changed
* information. If the string had more liberties than the size of the
* list, rebuild the list from scratch.
*/
static void
remove_liberty(int str_number, int pos)
{
int k;
struct string_data *s = &string[str_number];
struct string_liberties_data *sl = &string_libs[str_number];
if (s->liberties > MAX_LIBERTIES)
update_liberties(str_number);
else {
for (k = 0; k < s->liberties; k++)
if (sl->list[k] == pos) {
/* We need to push the last entry too because it may become
* destroyed later.
*/
PUSH_VALUE(sl->list[s->liberties - 1]);
PUSH_VALUE(sl->list[k]);
PUSH_VALUE(s->liberties);
sl->list[k] = sl->list[s->liberties - 1];
s->liberties--;
break;
}
}
}
/* Remove a string from the board, pushing necessary information to
* restore it. Return the number of removed stones.
*/
static int
do_remove_string(int s)
{
int pos;
int k;
int size = string[s].size;
/* Traverse the stones of the string, by following the cyclic chain. */
pos = FIRST_STONE(s);
do {
/* Push color, string number and cyclic chain pointers. */
PUSH_VALUE(string_number[pos]);
PUSH_VALUE(next_stone[pos]);
DO_REMOVE_STONE(pos);
pos = NEXT_STONE(pos);
} while (!BACK_TO_FIRST_STONE(s, pos));
/* The neighboring strings have obtained some new liberties and lost
* a neighbor. For speed reasons we handle two most common cases
* when string size is 1 or 2 stones here instead of calling
* update_liberties().
*/
if (size == 1) {
for (k = 0; k < string[s].neighbors; k++) {
int neighbor = string_neighbors[s].list[k];
remove_neighbor(neighbor, s);
PUSH_VALUE(string[neighbor].liberties);
if (string[neighbor].liberties < MAX_LIBERTIES)
string_libs[neighbor].list[string[neighbor].liberties] = pos;
string[neighbor].liberties++;
}
}
else if (size == 2) {
int other = OTHER_COLOR(string[s].color);
int pos2 = NEXT_STONE(pos);
for (k = 0; k < string[s].neighbors; k++) {
int neighbor = string_neighbors[s].list[k];
remove_neighbor(neighbor, s);
PUSH_VALUE(string[neighbor].liberties);
if (NEIGHBOR_OF_STRING(pos, neighbor, other)) {
if (string[neighbor].liberties < MAX_LIBERTIES)
string_libs[neighbor].list[string[neighbor].liberties] = pos;
string[neighbor].liberties++;
}
if (NEIGHBOR_OF_STRING(pos2, neighbor, other)) {
if (string[neighbor].liberties < MAX_LIBERTIES)
string_libs[neighbor].list[string[neighbor].liberties] = pos2;
string[neighbor].liberties++;
}
}
}
else {
for (k = 0; k < string[s].neighbors; k++) {
remove_neighbor(string_neighbors[s].list[k], s);
update_liberties(string_neighbors[s].list[k]);
}
}
/* Update the number of captured stones. These are assumed to
* already have been pushed.
*/
if (string[s].color == WHITE)
white_captured += size;
else
black_captured += size;
return size;
}
/* We have played an isolated new stone and need to create a new
* string for it.
*/
static void
create_new_string(int pos)
{
int s;
int color = board[pos];
int other = OTHER_COLOR(color);
/* Get the next free string number. */
PUSH_VALUE(next_string);
s = next_string++;
PARANOID1(s < MAX_STRINGS, pos);
string_number[pos] = s;
/* Set up a size one cycle for the string. */
next_stone[pos] = pos;
/* Set trivially known values and initialize the rest to zero. */
string[s].color = color;
string[s].size = 1;
string[s].origin = pos;
string[s].liberties = 0;
string[s].neighbors = 0;
string[s].mark = 0;
/* Clear the string mark. */
string_mark++;
/* In each direction, look for a liberty or a nonmarked opponent
* neighbor. Mark visited neighbors. There is no need to mark the
* liberties since we can't find them twice. */
if (LIBERTY(SOUTH(pos))) {
ADD_LIBERTY(s, SOUTH(pos));
}
else if (UNMARKED_COLOR_STRING(SOUTH(pos), other)) {
int s2 = string_number[SOUTH(pos)];
/* Add the neighbor to our list. */
ADD_NEIGHBOR(s, SOUTH(pos));
/* Add us to our neighbor's list. */
PUSH_VALUE(string[s2].neighbors);
ADD_NEIGHBOR(s2, pos);
MARK_STRING(SOUTH(pos));
}
if (LIBERTY(WEST(pos))) {
ADD_LIBERTY(s, WEST(pos));
}
else if (UNMARKED_COLOR_STRING(WEST(pos), other)) {
int s2 = string_number[WEST(pos)];
/* Add the neighbor to our list. */
ADD_NEIGHBOR(s, WEST(pos));
/* Add us to our neighbor's list. */
PUSH_VALUE(string[s2].neighbors);
ADD_NEIGHBOR(s2, pos);
MARK_STRING(WEST(pos));
}
if (LIBERTY(NORTH(pos))) {
ADD_LIBERTY(s, NORTH(pos));
}
else if (UNMARKED_COLOR_STRING(NORTH(pos), other)) {
int s2 = string_number[NORTH(pos)];
/* Add the neighbor to our list. */
ADD_NEIGHBOR(s, NORTH(pos));
/* Add us to our neighbor's list. */
PUSH_VALUE(string[s2].neighbors);
ADD_NEIGHBOR(s2, pos);
MARK_STRING(NORTH(pos));
}
if (LIBERTY(EAST(pos))) {
ADD_LIBERTY(s, EAST(pos));
}
else if (UNMARKED_COLOR_STRING(EAST(pos), other)) {
int s2 = string_number[EAST(pos)];
/* Add the neighbor to our list. */
ADD_NEIGHBOR(s, EAST(pos));
/* Add us to our neighbor's list. */
PUSH_VALUE(string[s2].neighbors);
ADD_NEIGHBOR(s2, pos);
/* No need to mark since no visits left. */
#if 0
MARK_STRING(EAST(pos));
#endif
}
}
/* We have played a stone with exactly one friendly neighbor. Add the
* new stone to that string.
*/
static void
extend_neighbor_string(int pos, int s)
{
int k;
int liberties_updated = 0;
int color = board[pos];
int other = OTHER_COLOR(color);
/* Link in the stone in the cyclic list. */
int pos2 = string[s].origin;
next_stone[pos] = next_stone[pos2];
PUSH_VALUE(next_stone[pos2]);
next_stone[pos2] = pos;
/* Do we need to update the origin? */
if (pos < pos2) {
PUSH_VALUE(string[s].origin);
string[s].origin = pos;
}
string_number[pos] = s;
/* The size of the string has increased by one. */
PUSH_VALUE(string[s].size);
string[s].size++;
/* If s has too many liberties, we don't know where they all are and
* can't update the liberties with the algorithm we otherwise
* use. In that case we can only recompute the liberties from
* scratch.
*/
if (string[s].liberties > MAX_LIBERTIES) {
update_liberties(s);
liberties_updated = 1;
}
else {
/* The place of the new stone is no longer a liberty. */
remove_liberty(s, pos);
}
/* Mark old neighbors of the string. */
string_mark++;
for (k = 0; k < string[s].neighbors; k++)
string[string_neighbors[s].list[k]].mark = string_mark;
/* Look at the neighbor locations of pos for new liberties and/or
* neighbor strings.
*/
/* If we find a liberty, look two steps away to determine whether
* this already is a liberty of s.
*/
if (LIBERTY(SOUTH(pos))) {
if (!liberties_updated
&& !NON_SOUTH_NEIGHBOR_OF_STRING(SOUTH(pos), s, color))
ADD_LIBERTY(s, SOUTH(pos));
}
else if (UNMARKED_COLOR_STRING(SOUTH(pos), other)) {
int s2 = string_number[SOUTH(pos)];
PUSH_VALUE(string[s].neighbors);
ADD_NEIGHBOR(s, SOUTH(pos));
PUSH_VALUE(string[s2].neighbors);
ADD_NEIGHBOR(s2, pos);
MARK_STRING(SOUTH(pos));
}
if (LIBERTY(WEST(pos))) {
if (!liberties_updated
&& !NON_WEST_NEIGHBOR_OF_STRING(WEST(pos), s, color))
ADD_LIBERTY(s, WEST(pos));
}
else if (UNMARKED_COLOR_STRING(WEST(pos), other)) {
int s2 = string_number[WEST(pos)];
PUSH_VALUE(string[s].neighbors);
ADD_NEIGHBOR(s, WEST(pos));
PUSH_VALUE(string[s2].neighbors);
ADD_NEIGHBOR(s2, pos);
MARK_STRING(WEST(pos));
}
if (LIBERTY(NORTH(pos))) {
if (!liberties_updated
&& !NON_NORTH_NEIGHBOR_OF_STRING(NORTH(pos), s, color))
ADD_LIBERTY(s, NORTH(pos));
}
else if (UNMARKED_COLOR_STRING(NORTH(pos), other)) {
int s2 = string_number[NORTH(pos)];
PUSH_VALUE(string[s].neighbors);
ADD_NEIGHBOR(s, NORTH(pos));
PUSH_VALUE(string[s2].neighbors);
ADD_NEIGHBOR(s2, pos);
MARK_STRING(NORTH(pos));
}
if (LIBERTY(EAST(pos))) {
if (!liberties_updated
&& !NON_EAST_NEIGHBOR_OF_STRING(EAST(pos), s, color))
ADD_LIBERTY(s, EAST(pos));
}
else if (UNMARKED_COLOR_STRING(EAST(pos), other)) {
int s2 = string_number[EAST(pos)];
PUSH_VALUE(string[s].neighbors);
ADD_NEIGHBOR(s, EAST(pos));
PUSH_VALUE(string[s2].neighbors);
ADD_NEIGHBOR(s2, pos);
#if 0
MARK_STRING(EAST(pos));
#endif
}
}
/* Incorporate the string at pos with the string s.
*/
static void
assimilate_string(int s, int pos)
{
int k;
int last;
int s2 = string_number[pos];
string[s].size += string[s2].size;
/* Walk through the s2 stones and change string number. Also pick up
* the last stone in the cycle for later use.
*/
pos = FIRST_STONE(s2);
do {
PUSH_VALUE(string_number[pos]);
string_number[pos] = s;
last = pos;
pos = NEXT_STONE(pos);
} while (!BACK_TO_FIRST_STONE(s2, pos));
/* Link the two cycles together. */
{
int pos2 = string[s].origin;
PUSH_VALUE(next_stone[last]);
PUSH_VALUE(next_stone[pos2]);
next_stone[last] = next_stone[pos2];
next_stone[pos2] = string[s2].origin;
/* Do we need to update the origin? */
if (string[s2].origin < pos2)
string[s].origin = string[s2].origin;
}
/* Pick up the liberties of s2 that we don't already have.
* It is assumed that the liberties of s have been marked before
* this function is called.
*/
if (string[s2].liberties <= MAX_LIBERTIES) {
for (k = 0; k < string[s2].liberties; k++) {
int pos2 = string_libs[s2].list[k];
if (UNMARKED_LIBERTY(pos2)) {
ADD_AND_MARK_LIBERTY(s, pos2);
}
}
}
else {
/* If s2 had too many liberties the above strategy wouldn't be
* effective, since not all liberties are listed in
* libs[] the chain of stones for s2 is no
* longer available (it has already been merged with s) so we
* can't reconstruct the s2 liberties. Instead we capitulate and
* rebuild the list of liberties for s (including the neighbor
* strings assimilated so far) from scratch.
*/
liberty_mark++; /* Reset the mark. */
string[s].liberties = 0; /* To avoid pushing the current list. */
update_liberties(s);
}
/* Remove s2 as neighbor to the neighbors of s2 and instead add s if
* they don't already have added it. Also add the neighbors of s2 as
* neighbors of s, unless they already have been added. The already
* known neighbors of s are assumed to have been marked before this
* function is called.
*/
for (k = 0; k < string[s2].neighbors; k++) {
int t = string_neighbors[s2].list[k];
remove_neighbor(t, s2);
if (string[t].mark != string_mark) {
PUSH_VALUE(string[t].neighbors);
string_neighbors[t].list[string[t].neighbors++] = s;
string_neighbors[s].list[string[s].neighbors++] = t;
string[t].mark = string_mark;
}
}
}
/* Create a new string for the stone at pos and assimilate all
* friendly neighbor strings.
*/
static void
assimilate_neighbor_strings(int pos)
{
int s;
int color = board[pos];
int other = OTHER_COLOR(color);
/* Get the next free string number. */
PUSH_VALUE(next_string);
s = next_string++;
PARANOID1(s < MAX_STRINGS, pos);
string_number[pos] = s;
/* Set up a size one cycle for the string. */
next_stone[pos] = pos;
/* Set trivially known values and initialize the rest to zero. */
string[s].color = color;
string[s].size = 1;
string[s].origin = pos;
string[s].liberties = 0;
string[s].neighbors = 0;
/* Clear the marks. */
liberty_mark++;
string_mark++;
/* Mark ourselves. */
string[s].mark = string_mark;
/* Look in each direction for
*
* 1. liberty: Add if not already visited.
* 2. opponent string: Add it among our neighbors and us among its
* neighbors, unless already visited.
* 3. friendly string: Assimilate.
*/
if (UNMARKED_LIBERTY(SOUTH(pos))) {
ADD_AND_MARK_LIBERTY(s, SOUTH(pos));
}
else if (UNMARKED_COLOR_STRING(SOUTH(pos), other)) {
ADD_NEIGHBOR(s, SOUTH(pos));
PUSH_VALUE(string[string_number[SOUTH(pos)]].neighbors);
ADD_NEIGHBOR(string_number[SOUTH(pos)], pos);
MARK_STRING(SOUTH(pos));
}
else if (UNMARKED_COLOR_STRING(SOUTH(pos), color)) {
assimilate_string(s, SOUTH(pos));
}
if (UNMARKED_LIBERTY(WEST(pos))) {
ADD_AND_MARK_LIBERTY(s, WEST(pos));
}
else if (UNMARKED_COLOR_STRING(WEST(pos), other)) {
ADD_NEIGHBOR(s, WEST(pos));
PUSH_VALUE(string[string_number[WEST(pos)]].neighbors);
ADD_NEIGHBOR(string_number[WEST(pos)], pos);
MARK_STRING(WEST(pos));
}
else if (UNMARKED_COLOR_STRING(WEST(pos), color)) {
assimilate_string(s, WEST(pos));
}
if (UNMARKED_LIBERTY(NORTH(pos))) {
ADD_AND_MARK_LIBERTY(s, NORTH(pos));
}
else if (UNMARKED_COLOR_STRING(NORTH(pos), other)) {
ADD_NEIGHBOR(s, NORTH(pos));
PUSH_VALUE(string[string_number[NORTH(pos)]].neighbors);
ADD_NEIGHBOR(string_number[NORTH(pos)], pos);
MARK_STRING(NORTH(pos));
}
else if (UNMARKED_COLOR_STRING(NORTH(pos), color)) {
assimilate_string(s, NORTH(pos));
}
if (UNMARKED_LIBERTY(EAST(pos))) {
#if 0
ADD_AND_MARK_LIBERTY(s, EAST(pos));
#else
ADD_LIBERTY(s, EAST(pos));
#endif
}
else if (UNMARKED_COLOR_STRING(EAST(pos), other)) {
ADD_NEIGHBOR(s, EAST(pos));
PUSH_VALUE(string[string_number[EAST(pos)]].neighbors);
ADD_NEIGHBOR(string_number[EAST(pos)], pos);
#if 0
MARK_STRING(EAST(pos));
#endif
}
else if (UNMARKED_COLOR_STRING(EAST(pos), color)) {
assimilate_string(s, EAST(pos));
}
}
/* Suicide at `pos' (the function assumes that the move is indeed suicidal).
* Remove the neighboring friendly strings.
*/
static void
do_commit_suicide(int pos, int color)
{
if (board[SOUTH(pos)] == color)
do_remove_string(string_number[SOUTH(pos)]);
if (board[WEST(pos)] == color)
do_remove_string(string_number[WEST(pos)]);
if (board[NORTH(pos)] == color)
do_remove_string(string_number[NORTH(pos)]);
if (board[EAST(pos)] == color)
do_remove_string(string_number[EAST(pos)]);
/* Count the stone we "played" as captured. */
if (color == WHITE)
white_captured++;
else
black_captured++;
}
/* Play a move without legality checking. This is a low-level function,
* it assumes that the move is not a suicide. Such cases must be handled
* where the function is called.
*/
static void
do_play_move(int pos, int color)
{
int other = OTHER_COLOR(color);
int captured_stones = 0;
int neighbor_allies = 0;
int s = -1;
/* Clear string mark. */
string_mark++;
/* Put down the stone. We also set its string number to -1 for a while
* so that NEIGHBOR_OF_STRING() and friends don't get confused with the
* stone.
*/
DO_ADD_STONE(pos, color);
string_number[pos] = -1;
/* Look in all directions. Count the number of neighbor strings of the same
* color, remove captured strings and remove `pos' as liberty for opponent
* strings that are not captured.
*/
if (board[SOUTH(pos)] == color) {
neighbor_allies++;
s = string_number[SOUTH(pos)];
MARK_STRING(SOUTH(pos));
}
else if (board[SOUTH(pos)] == other) {
if (LIBERTIES(SOUTH(pos)) > 1) {
remove_liberty(string_number[SOUTH(pos)], pos);
MARK_STRING(SOUTH(pos));
}
else
captured_stones += do_remove_string(string_number[SOUTH(pos)]);
}
if (UNMARKED_COLOR_STRING(WEST(pos), color)) {
neighbor_allies++;
s = string_number[WEST(pos)];
MARK_STRING(WEST(pos));
}
else if (UNMARKED_COLOR_STRING(WEST(pos), other)) {
if (LIBERTIES(WEST(pos)) > 1) {
remove_liberty(string_number[WEST(pos)], pos);
MARK_STRING(WEST(pos));
}
else
captured_stones += do_remove_string(string_number[WEST(pos)]);
}
if (UNMARKED_COLOR_STRING(NORTH(pos), color)) {
neighbor_allies++;
s = string_number[NORTH(pos)];
MARK_STRING(NORTH(pos));
}
else if (UNMARKED_COLOR_STRING(NORTH(pos), other)) {
if (LIBERTIES(NORTH(pos)) > 1) {
remove_liberty(string_number[NORTH(pos)], pos);
MARK_STRING(NORTH(pos));
}
else
captured_stones += do_remove_string(string_number[NORTH(pos)]);
}
if (UNMARKED_COLOR_STRING(EAST(pos), color)) {
neighbor_allies++;
s = string_number[EAST(pos)];
#if 0
MARK_STRING(EAST(pos));
#endif
}
else if (UNMARKED_COLOR_STRING(EAST(pos), other)) {
if (LIBERTIES(EAST(pos)) > 1) {
remove_liberty(string_number[EAST(pos)], pos);
#if 0
MARK_STRING(EAST(pos));
#endif
}
else
captured_stones += do_remove_string(string_number[EAST(pos)]);
}
/* Choose strategy depending on the number of friendly neighbors. */
if (neighbor_allies == 0)
create_new_string(pos);
else if (neighbor_allies == 1) {
gg_assert(s >= 0);
extend_neighbor_string(pos, s);
return; /* can't be a ko, we're done */
}
else {
assimilate_neighbor_strings(pos);
return; /* can't be a ko, we're done */
}
/* Check whether this move was a ko capture and if so set
* board_ko_pos.
*
* No need to push board_ko_pos on the stack,
* because this has been done earlier.
*/
s = string_number[pos];
if (string[s].liberties == 1
&& string[s].size == 1
&& captured_stones == 1) {
/* In case of a double ko: clear old ko position first. */
if (board_ko_pos != NO_MOVE)
hashdata_invert_ko(&board_hash, board_ko_pos);
board_ko_pos = string_libs[s].list[0];
hashdata_invert_ko(&board_hash, board_ko_pos);
}
}
/* ================================================================ *
* The following functions don't actually belong here. They are
* only here because they are faster here where they have access to
* the incremental data structures.
* ================================================================ */
/* Help collect the data needed by order_moves() in reading.c.
* It's the caller's responsibility to initialize the result parameters.
*/
#define NO_UNROLL 0
void
incremental_order_moves(int move, int color, int str,
int *number_edges, int *number_same_string,
int *number_own, int *number_opponent,
int *captured_stones, int *threatened_stones,
int *saved_stones, int *number_open)
{
#if NO_UNROLL == 1
int pos;
int k;
/* Clear the string mark. */
string_mark++;
for (k = 0; k < 4; k++) {
pos = move + delta[k];
if (!ON_BOARD(pos))
(*number_edges)++;
else if (board[pos] == EMPTY)
(*number_open)++;
else {
int s = string_number[pos];
if (string_number[str] == s)
(*number_same_string)++;
if (board[pos] == color) {
(*number_own)++;
if (string[s].liberties == 1)
(*saved_stones) += string[s].size;
}
else {
(*number_opponent)++;
if (string[s].liberties == 1) {
int r;
struct string_data *t;
(*captured_stones) += string[s].size;
for (r = 0; r < string[s].neighbors; r++) {
t = &string[string[s].neighborlist[r]];
if (t->liberties == 1)
(*saved_stones) += t->size;
}
}
else if (string[s].liberties == 2 && UNMARKED_STRING(pos)) {
(*threatened_stones) += string[s].size;
MARK_STRING(pos);
}
}
}
}
#else
#define code1(arg) \
if (!ON_BOARD(arg)) \
(*number_edges)++; \
else if (board[arg] == EMPTY) \
(*number_open)++; \
else { \
int s = string_number[arg]; \
if (string_number[str] == s) \
(*number_same_string)++; \
if (board[arg] == color) { \
(*number_own)++; \
if (string[s].liberties == 1) \
(*saved_stones) += string[s].size; \
} \
else { \
(*number_opponent)++; \
if (string[s].liberties == 1) { \
int r; \
struct string_data *t; \
(*captured_stones) += string[s].size; \
for (r = 0; r < string[s].neighbors; r++) { \
t = &string[string_neighbors[s].list[r]]; \
if (t->liberties == 1) \
(*saved_stones) += t->size; \
} \
} \
else if (string[s].liberties == 2 && UNMARKED_STRING(arg)) { \
(*threatened_stones) += string[s].size; \
MARK_STRING(arg); \
} \
} \
}
/* Clear the string mark. */
string_mark++;
code1(SOUTH(move));
code1(WEST(move));
code1(NORTH(move));
code1(EAST(move));
#endif
}
int
square_dist(int pos1, int pos2)
{
int idist = I(pos1) - I(pos2);
int jdist = J(pos1) - J(pos2);
return idist*idist + jdist*jdist;
}
/*
* Local Variables:
* tab-width: 8
* c-basic-offset: 2
* End:
*/