4332 lines
111 KiB
C
4332 lines
111 KiB
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:
|
||
|
*/
|