Initial commit of GNU Go v3.8.
[sgk-go] / engine / readconnect.c
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* This is GNU Go, a Go program. Contact gnugo@gnu.org, or see *
* http://www.gnu.org/software/gnugo/ for more information. *
* *
* Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, *
* 2008 and 2009 by the Free Software Foundation. *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation - version 3 or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License in file COPYING for more details. *
* *
* You should have received a copy of the GNU General Public *
* License along with this program; if not, write to the Free *
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, *
* Boston, MA 02111, USA. *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "gnugo.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <math.h>
#include "liberty.h"
#include "cache.h"
#include "gg_utils.h"
#include "readconnect.h"
/* Size of array where candidate moves are stored. */
#define MAX_MOVES 362
/* trace of a search */
typedef struct _zone {
int array[BOARDMAX];
unsigned int bits[1+BOARDMAX/32];
int i;
} zone;
static int recursive_connect2(int str1, int str2, int *move,
int has_passed);
static int recursive_disconnect2(int str1, int str2, int *move,
int has_passed);
static int recursive_break(int str, const signed char goal[BOARDMAX],
int *move, int has_passed, Hash_data *goal_hash);
static int recursive_block(int str, const signed char goal[BOARDMAX],
int *move, int has_passed, Hash_data *goal_hash);
static int add_array(int *array, int elt);
static int element_array(int *array, int elt);
static void intersection_array(int *array1, int *array2);
static int snapback(int str);
static int connection_one_move(int str1, int str2);
static int prevent_connection_one_move(int *moves, int str1, int str2);
static int connected_one_move(int str1, int str2);
static int moves_to_connect_in_two_moves(int *moves, int str1, int str2);
static int connection_two_moves(int str1, int str2);
static int prevent_connection_two_moves(int *moves, int str1, int str2);
#if 0
static int connected_two_moves(int str1, int str2);
#endif
static int moves_to_connect_in_three_moves(int *moves, int str1, int str2,
int does_connect);
#if 0
static int simple_connection_three_moves(int str1, int str2);
static int prevent_simple_connection_three_moves(int *moves,
int str1, int str2);
#endif
static int recursive_connect(int str1, int str2, int *move);
static int recursive_disconnect(int str1, int str2, int *move);
static int quiescence_connect(int str1, int str2, int *move);
static int quiescence_capture(int str, int *move);
/* static int capture_one_move(int str); */
static int prevent_capture_one_move(int *moves, int str1);
static int recursive_transitivity(int str1, int str2, int str3, int *move);
static int recursive_non_transitivity(int str1, int str2, int str3, int *move);
static void order_connection_moves(int *moves, int str1, int str2,
int color_to_move, const char *funcname);
static int nodes_connect = 0;
/* Used by alternate connections. */
static signed char connection_shadow[BOARDMAX];
static signed char breakin_shadow[BOARDMAX];
/* Statistics. */
static int global_connection_node_counter = 0;
static void
init_zone(zone *zn)
{
zn->array[0] = 0;
memset(zn->bits, 0, 1 + BOARDMAX / 8);
}
/* send back 1 if the intersection is in the zone
*/
#if 0
static int
elt_zone(zone *zn, int elt)
{
if ((zn->bits[elt >> 5] >> (elt & 31)) & 1)
return 1;
return 0;
}
#endif
/* Adds an intersection to a zone
*/
static void
add_zone(zone *zn, int elt)
{
if (((zn->bits[elt >> 5] >> (elt & 31)) & 1) == 0) {
zn->bits[elt >> 5] |= (1 << (elt & 31));
zn->array[0]++;
zn->array[zn->array[0]] = elt;
}
}
/* start to loop over a zone
*/
#if 0
static int
start_zone(zone *zn)
{
if (zn->array[0] < 1)
return -1;
zn->i = 1;
return zn->array[1];
}
#endif
/* continue to loop over a zone
*/
#if 0
static int
next_zone(zone *zn)
{
zn->i++;
if (zn->i > zn->array[0])
return -1;
return zn->array[zn->i];
}
#endif
/* only keep the elements of zn1 which are also in zn2 */
#if 0
static void
intersection_zone(zone *zn1, zone *zn2)
{
int r, s;
for (r = start_zone(zn1); r > -1; r = next_zone(zn1))
if (!elt_zone(zn2, r)) {
for (s = r; s < zn1->array[0]; s++)
zn1->array[s] = zn1->array[s+1];
zn1->bits[r >> 5] &= ~ (1 << (r & 31));
zn1->array[0]--;
zn1->i--;
}
}
#endif
/* Adds an integer to an array of integers if it is not already there.
* The number of elements of the array is in array[0].
*/
static int
add_array(int *array, int elt)
{
int r;
for (r = 1; r < array[0] + 1; r++)
if (array[r] == elt)
return 0;
array[0]++;
array[array[0]] = elt;
return 1;
}
/* test if an element is part of an array */
static int
element_array(int *array, int elt)
{
int r;
for (r = 1; r < array[0] + 1; r++)
if (array[r] == elt)
return 1;
return 0;
}
/* only keep the elements of array1 which are also in array2 */
static void
intersection_array(int *array1, int *array2)
{
int r, s;
for (r = 1; r < array1[0] + 1; r++)
if (!element_array(array2, array1[r])) {
for (s = r; s < array1[0]; s++)
array1[s] = array1[s+1];
array1[0]--;
r--;
}
}
/* verifies that capturing the stone at str is not a snapback */
static int
snapback(int str)
{
int stones, liberties, lib;
SGFTree *save_sgf_dumptree = sgf_dumptree;
/* if more than one stone captured, not a snapback */
stones = countstones(str);
if (stones > 1)
return 0;
/* if more than one liberty, not a snapback */
liberties = findlib(str, 1, &lib);
if (liberties > 1)
return 0;
/* turn off the sgf traces
*/
sgf_dumptree = NULL;
/* if only one liberty after capture */
if (trymove(lib, OTHER_COLOR(board[str]), "snapback", str)) {
liberties = 0;
if (IS_STONE(board[lib]))
liberties = countlib(lib);
popgo();
sgf_dumptree = save_sgf_dumptree;
if (liberties > 1)
return 0;
return WIN;
}
/* Turn the sgf traces back on. */
sgf_dumptree = save_sgf_dumptree;
return 0;
}
/* connection by playing and finding a ponnuki after play */
static int
ponnuki_connect(int *moves, int str1, int str2, zone *zn)
{
int r, s, k, res = 0;
int liberties, libs[MAXLIBS];
int adj, adjs[MAXCHAIN];
int neighb, neighbs[MAXCHAIN];
/* finds connection through two forbidden liberties for
* the opponent
* + + + + + + +
* + + @ O O @ +
* + @ + @ @ x +
* + + @ + + + +
* - - - - - - -
*
* + + + + + + +
* + + @ O O @ +
* + @ + @ @ O @
* + + @ + + x +
* - - - - - - -
*/
liberties = findlib(str1, MAXLIBS, libs);
for (r = 0; r < liberties; r++)
if (is_self_atari(libs[r], OTHER_COLOR(board[str1])))
for (k = 0; k < 4; k++) {
int pos = libs[r] + delta[k];
if (board[pos] == board[str1]
&& !same_string(pos, str1)
&& !same_string(pos, str2)) {
/* try to connect pos to str2 in one move */
/* play a common liberty */
neighb = findlib(pos, MAXLIBS, neighbs);
for (s = 0; s < neighb; s++)
if (liberty_of_string(neighbs[s], str2)) {
res = 1;
add_zone(zn, libs[r]);
add_zone(zn, neighbs[s]);
add_array(moves, neighbs[s]);
}
/* or capture a common adjacent string */
adj = chainlinks2(pos, adjs, 1);
for (s = 0; s < adj; s++)
if (adjacent_strings(adjs[s], str2)
&& !snapback(adjs[s])) {
res = 1;
neighb = findlib(adjs[s], 1, neighbs);
add_zone(zn, libs[r]);
add_zone(zn, neighbs[0]);
add_array(moves, neighbs[0]);
}
}
}
return res;
}
/* connection in one move, finds all moves and memorizes intersections
* involved in the connection.
*/
static int
moves_connection_one_move(int *moves, int str1, int str2, zone *zn)
{
int r;
int adj, adjs[MAXCHAIN];
/* If one string is missing we have already failed. */
if (board[str1] == EMPTY || board[str2] == EMPTY)
return 0;
/* Common liberties. */
if (have_common_lib(str1, str2, NULL))
return WIN;
/* Common adjacent string in atari, more than one stone, no snapback. */
adj = chainlinks2(str1, adjs, 1);
for (r = 0; r < adj; r++)
if (adjacent_strings(adjs[r], str2)
&& !snapback(adjs[r]))
return WIN;
/* Connections through a ponnuki */
if (ponnuki_connect(moves, str1, str2, zn))
return WIN;
if (ponnuki_connect(moves, str2, str1, zn))
return WIN;
return 0;
}
/* Verifies that the strings str1 and str2 can be connected
* directly by playing one move, either by playing a common liberty
* of the two strings, or by capturing a common adjacent string.
*
* This is the gi1 game function.
*/
static int
connection_one_move(int str1, int str2)
{
int moves[BOARDMAX];
zone zn;
init_zone(&zn);
moves[0] = 0;
return moves_connection_one_move(moves, str1, str2, &zn);
}
/* If the two strings str1 and str2 can be connected sends back WIN fill the
* array moves with the only move that can prevent a connection in one move
* (common liberties, liberties of common adjacent strings in atari).
*
* This is the ip1 game function. */
static int
prevent_connection_one_move(int *moves, int str1, int str2)
{
int r, s;
int libs[MAXLIBS];
int adj, adjs[MAXCHAIN];
int adjadj, adjadjs[MAXCHAIN];
/* Common liberties. */
if (have_common_lib(str1, str2, libs)) {
add_array(moves, libs[0]);
return WIN;
}
/* Save a common adjacent string in atari, more than one stone, no snapback.
*/
adj = chainlinks2(str1, adjs, 1);
for (r = 0; r < adj; r++)
if (adjacent_strings(adjs[r], str2)
&& !snapback(adjs[r])) {
findlib(adjs[r], MAXLIBS, libs);
add_array(moves, libs[0]);
adjadj = chainlinks2(adjs[r], adjadjs, 1);
for (s = 0; s < adjadj; s++) {
findlib(adjadjs[s], MAXLIBS, libs);
add_array(moves, libs[0]);
}
return WIN;
}
return 0;
}
/* Returns WIN if str1 and str2 are connected in at most
* one move even if the opponent plays first.
* Verify that the strings are connectable in one move
* and find the only possible moves that can prevent
* using prevent_connection_one_move. If none of these
* moves works, the two strings are connected.
*
* This is the g1 game function.
*/
static int
connected_one_move(int str1, int str2)
{
int r, res = 0;
int moves[MAX_MOVES];
SGFTree *save_sgf_dumptree = sgf_dumptree;
/* turn off the sgf traces
*/
sgf_dumptree = NULL;
moves[0] = 0;
if (prevent_connection_one_move(moves, str1, str2)) {
order_connection_moves(moves, str1, str2, OTHER_COLOR(board[str1]),
"connected_one_move");
res = WIN;
for (r = 1; ((r < moves[0] + 1) && res); r++) {
if (trymove(moves[r], OTHER_COLOR(board[str1]),
"connected_one_move", str1)) {
if (!connection_one_move(str1, str2))
res = 0;
popgo();
}
}
}
/* Turn the sgf traces back on. */
sgf_dumptree = save_sgf_dumptree;
return res;
}
/* Find the moves that might be able to connect in less than three plies.
* That is moves that can connect the strings if another move of the same
* color is played just after:
* - common liberties of the two strings;
* - moves on the liberties of an opponent string with less than two
* liberties adjacent to both strings, or adjacent to one string and
* that has a common liberty with the second string;
* - liberties of one string that are second order liberties of the
* other string.
*
* Returns WIN if a direct connection has been found. Returns 0
* otherwise.
*/
static int
moves_to_connect_in_two_moves(int *moves, int str1, int str2)
{
int r, s, common_adj_liberty;
int liberties, libs[MAXLIBS];
int adj, adjs[MAXCHAIN];
int adjadj, adjadjs[MAXCHAIN];
int k;
int color = board[str1];
int move;
/* Common liberties. */
if (have_common_lib(str1, str2, libs)) {
add_array(moves, libs[0]);
return 1;
}
/* Capture a common adjacent string or an adjacent liberty of str1
* that has a common liberty with str2...
*/
adj = chainlinks3(str1, adjs, 2);
for (r = 0; r < adj; r++) {
liberties = findlib(adjs[r], MAXLIBS, libs);
common_adj_liberty = 0;
for (s = 0; s < liberties; s++)
if (liberty_of_string(libs[s], str2))
common_adj_liberty = 1;
if (common_adj_liberty || adjacent_strings(adjs[r], str2)) {
for (s = 0; s < liberties; s++)
add_array(moves, libs[s]);
adjadj = chainlinks2(adjs[r], adjadjs, 1);
for (s = 0; s < adjadj; s++) {
findlib(adjadjs[s], MAXLIBS, libs);
add_array(moves, libs[0]);
}
}
}
/* ...and vice versa. */
adj = chainlinks3(str2, adjs, 2);
for (r = 0; r < adj; r++) {
liberties = findlib(adjs[r], MAXLIBS, libs);
common_adj_liberty = 0;
for (s = 0; s < liberties; s++)
if (liberty_of_string(libs[s], str1))
common_adj_liberty = 1;
if (common_adj_liberty || adjacent_strings(adjs[r], str1)) {
for (s = 0; s < liberties; s++)
add_array(moves, libs[s]);
adjadj = chainlinks2(adjs[r], adjadjs, 1);
for (s = 0; s < adjadj; s++) {
findlib(adjadjs[s], MAXLIBS, libs);
add_array(moves, libs[0]);
}
}
}
/* Liberties of str1 that are second order liberties of str2 and
* vice versa.
*/
liberties = findlib(str1, MAXLIBS, libs);
for (r = 0; r < liberties; r++) {
if (board[SOUTH(libs[r])] == EMPTY) {
if (liberty_of_string(SOUTH(libs[r]), str2)) {
add_array(moves, libs[r]);
add_array(moves, SOUTH(libs[r]));
}
}
if (board[WEST(libs[r])] == EMPTY) {
if (liberty_of_string(WEST(libs[r]), str2)) {
add_array(moves, libs[r]);
add_array(moves, WEST(libs[r]));
}
}
if (board[NORTH(libs[r])] == EMPTY) {
if (liberty_of_string(NORTH(libs[r]), str2)) {
add_array(moves, libs[r]);
add_array(moves, NORTH(libs[r]));
}
}
if (board[EAST(libs[r])] == EMPTY) {
if (liberty_of_string(EAST(libs[r]), str2)) {
add_array(moves, libs[r]);
add_array(moves, EAST(libs[r]));
}
}
}
/* Liberties of str1 which are adjacent to a friendly string with
* common liberty with str2.
*/
liberties = findlib(str1, MAXLIBS, libs);
for (r = 0; r < liberties; r++) {
for (k = 0; k < 4; k++) {
int pos = libs[r] + delta[k];
if (board[pos] == color
&& !same_string(pos, str1)
&& quiescence_connect(pos, str2, &move)) {
add_array(moves, libs[r]);
add_array(moves, move);
}
}
}
/* And vice versa. */
liberties = findlib(str2, MAXLIBS, libs);
for (r = 0; r < liberties; r++) {
for (k = 0; k < 4; k++) {
int pos = libs[r] + delta[k];
if (board[pos] == color
&& !same_string(pos, str2)
&& quiescence_connect(pos, str1, &move)) {
add_array(moves, libs[r]);
add_array(moves, move);
}
}
}
return 0;
}
/* Tests if the strings can be connected in three plies starts
* with finding the possible moves that can connect. If two
* moves in a row are played, then try them and stops at the
* first working move. The strings are connected in two moves
* if the function connected_one_move is verified after a move.
*
* This is the gi2 game function.
*/
static int
connection_two_moves(int str1, int str2)
{
int r, res = 0, moves[MAX_MOVES];
SGFTree *save_sgf_dumptree = sgf_dumptree;
/* If one string is missing we have already failed. */
if (board[str1] == EMPTY || board[str2] == EMPTY)
return 0;
moves[0] = 0;
if (moves_to_connect_in_two_moves(moves, str1, str2))
return WIN;
order_connection_moves(moves, str1, str2, board[str1],
"connection_two_moves");
/* turn off the sgf traces
*/
sgf_dumptree = NULL;
for (r = 1; ((r < moves[0] + 1) && !res); r++) {
if (trymove(moves[r], board[str1], "connection_two_moves", str1)) {
if (connected_one_move(str1, str2))
res = WIN;
popgo();
}
}
sgf_dumptree = save_sgf_dumptree;
return res;
}
/* Find the complete set of possible moves that can prevent
* a connection in three plies.
*
* The function is not yet written, but moves_to_connect_in_two_moves does
* a similar job, so it is called temporarly.
*/
static int
moves_to_prevent_connection_in_two_moves(int *moves, int str1, int str2)
{
if (moves_to_connect_in_two_moves(moves, str1, str2))
return 1;
return 0;
}
/* Find all the moves that prevent to connect in a three plies
* deep search and put them in the moves array. Returns 0 if
* there is no three plies connection, or else it tries all the
* possible preventing moves. If after a possible preventing
* moves, there no connection in one move and no connection in
* two moves, then the moves prevents a three plies deep
* connection, and it is added to the moves array.
*
* this is the ip2 game function */
static int
prevent_connection_two_moves(int *moves, int str1, int str2)
{
int r, res = 0;
int possible_moves[MAX_MOVES];
SGFTree *save_sgf_dumptree = sgf_dumptree;
/* turn off the sgf traces
*/
sgf_dumptree = NULL;
if (connection_two_moves(str1, str2)) {
res = WIN;
possible_moves[0] = 0;
moves_to_prevent_connection_in_two_moves(possible_moves, str1, str2);
order_connection_moves(possible_moves, str1, str2,
OTHER_COLOR(board[str1]),
"prevent_connection_two_moves");
for (r = 1; r < possible_moves[0] + 1; r++) {
if (trymove(possible_moves[r], OTHER_COLOR(board[str1]),
"prevent_connection_two_moves", str1)) {
if (!connection_one_move(str1, str2))
if (!connection_two_moves(str1, str2))
add_array(moves, possible_moves[r]);
popgo();
}
}
}
sgf_dumptree = save_sgf_dumptree;
return res;
}
/* Only partially written.
*
* Find all the moves than can connect if two subsequent
* moves of the same color are played after
* - common liberties;
* - liberties of common adjacent strings with 3 liberties or less;
* - liberties of adjacent strings with 2 liberties or less that have
* liberties that are second order liberties of the other string;
* - liberties of one string that are second order liberties of the
* other string;
* - second order liberties of the first string that are second order
* liberties of the other string;
*
* A function that computes the second order liberties of a string is
* needed as well as a function that checks efficiently if an
* intersection is a second order liberty of a given string.
*
* If does_connect is 1, generate moves to connect, otherwise generate
* moves to disconnect.
*/
static int
moves_to_connect_in_three_moves(int *moves, int str1, int str2,
int does_connect)
{
int r, s;
int liberties, libs[MAXLIBS];
int liberties2, libs2[MAXLIBS];
int adj, adjs[MAXCHAIN];
int adjadj, adjadjs[MAXCHAIN];
int move;
int k;
int pos;
int secondlib1[BOARDMAX];
int secondlib2[BOARDMAX];
if (moves_to_connect_in_two_moves(moves, str1, str2))
return 1;
/* Find second order liberties of str1. */
memset(secondlib1, 0, sizeof(secondlib1));
liberties = findlib(str1, MAXLIBS, libs);
for (r = 0; r < liberties; r++)
for (k = 0; k < 4; k++) {
pos = libs[r] + delta[k];
if (board[pos] == EMPTY)
secondlib1[pos] = 1;
else if (board[pos] == board[str1]) {
liberties2 = findlib(pos, MAXLIBS, libs2);
for (s = 0; s < liberties2; s++)
secondlib1[libs2[s]] = 1;
}
}
/* Find second order liberties of str2.
*/
memset(secondlib2, 0, sizeof(secondlib2));
liberties = findlib(str2, MAXLIBS, libs);
for (r = 0; r < liberties; r++)
for (k = 0; k < 4; k++) {
pos = libs[r] + delta[k];
if (board[pos] == EMPTY)
secondlib2[pos] = 1;
else if (board[pos] == board[str2]) {
liberties2 = findlib(pos, MAXLIBS, libs2);
for (s = 0; s < liberties2; s++)
secondlib2[libs2[s]] = 1;
}
}
/* Second order liberties of str1 that are second order liberties
* of str2 and vice versa.
*/
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (secondlib1[pos] && secondlib2[pos])
add_array(moves, pos);
}
/* Capture a neighbor of str1 which is in atari. The captured string
* must in turn have a neighbor which can connect to str2 easily.
*/
adj = chainlinks2(str1, adjs, 1);
for (r = 0; r < adj; r++) {
adjadj = chainlinks(adjs[r], adjadjs);
for (s = 0; s < adjadj; s++) {
if (!same_string(adjadjs[s], str1)
&& quiescence_connect(adjadjs[s], str2, &move)) {
findlib(adjs[r], 1, libs);
add_array(moves, libs[0]);
add_array(moves, move);
}
}
}
/* And vice versa. */
adj = chainlinks2(str2, adjs, 1);
for (r = 0; r < adj; r++) {
adjadj = chainlinks(adjs[r], adjadjs);
for (s = 0; s < adjadj; s++) {
if (!same_string(adjadjs[s], str2)
&& quiescence_connect(adjadjs[s], str1, &move)) {
findlib(adjs[r], 1, libs);
add_array(moves, libs[0]);
add_array(moves, move);
}
}
}
/* Liberties of neighbor of str1 with at most two liberties, which
* are second order liberties of str2.
*/
adj = chainlinks3(str1, adjs, 2);
for (r = 0; r < adj; r++) {
liberties = findlib(adjs[r], 2, libs);
for (s = 0; s < liberties; s++)
if (second_order_liberty_of_string(libs[s], str2))
add_array(moves, libs[s]);
}
/* And vice versa. */
adj = chainlinks3(str2, adjs, 2);
for (r = 0; r < adj; r++) {
liberties = findlib(adjs[r], 2, libs);
for (s = 0; s < liberties; s++)
if (second_order_liberty_of_string(libs[s], str1))
add_array(moves, libs[s]);
}
/* Move in on a three liberty opponent string which is adjacent to
* str1 and has a liberty in common with str2.
*/
adj = chainlinks2(str1, adjs, 3);
for (r = 0; r < adj; r++) {
if (have_common_lib(adjs[r], str2, NULL)) {
liberties = findlib(adjs[r], 3, libs);
for (s = 0; s < liberties; s++) {
/* If generating a connecting move, require the liberty to be
* no further than diagonal to a second order liberty of one
* of the strings.
*/
for (k = 0; k < 8; k++) {
if (!does_connect
|| (ON_BOARD(libs[s] + delta[k])
&& (secondlib1[libs[s] + delta[k]]
|| secondlib2[libs[s] + delta[k]]))) {
add_array(moves, libs[s]);
break;
}
}
}
}
}
/* And vice versa. */
adj = chainlinks2(str2, adjs, 3);
for (r = 0; r < adj; r++) {
if (have_common_lib(adjs[r], str1, NULL)) {
liberties = findlib(adjs[r], 3, libs);
for (s = 0; s < liberties; s++) {
/* If generating a connecting move, require the liberty to be
* no further than diagonal to a second order liberty of one
* of the strings.
*/
for (k = 0; k < 8; k++) {
if (!does_connect
|| (ON_BOARD(libs[s] + delta[k])
&& (secondlib1[libs[s] + delta[k]]
|| secondlib2[libs[s] + delta[k]]))) {
add_array(moves, libs[s]);
break;
}
}
}
}
}
return 0;
}
/* Not yet written.
*
* Find the complete set of possible moves that can prevent
* a connection in 5 plies.
*/
static int
moves_to_prevent_connection_in_three_moves(int *moves, int str1, int str2)
{
if (moves_to_connect_in_three_moves(moves, str1, str2, 0))
return 1;
return 0;
}
/*
* The simplest depth 4 connection:
*
* If there are forced moves to prevent connection in one move,
* try them, and verify that they all lead to a depth 1 or
* depth 3 connection.
*
* This is the g211 game function.
*/
static int
simply_connected_two_moves(int str1, int str2)
{
int r, res = 0;
int moves[MAX_MOVES];
SGFTree *save_sgf_dumptree = sgf_dumptree;
/* turn off the sgf traces
*/
sgf_dumptree = NULL;
/* If one string is missing we have already failed. */
if (board[str1] == EMPTY || board[str2] == EMPTY)
return 0;
moves[0] = 0;
if (prevent_connection_one_move(moves, str1, str2)) {
res = WIN;
order_connection_moves(moves, str1, str2, OTHER_COLOR(board[str1]),
"simply_connected_two_moves");
for (r = 1; ((r < moves[0] + 1) && res); r++) {
if (trymove(moves[r], OTHER_COLOR(board[str1]),
"simply_connected_two_moves", str1)) {
if (!connection_one_move(str1, str2))
if (!connection_two_moves(str1, str2))
res = 0;
popgo();
}
}
}
sgf_dumptree = save_sgf_dumptree;
return res;
}
/* Test if a move is a simple depth 5 connection.
*
* This is the gi311 game function.
*/
static int
simple_connection_three_moves(int str1, int str2)
{
int r, res = 0, moves[MAX_MOVES];
SGFTree *save_sgf_dumptree = sgf_dumptree;
/* turn off the sgf traces
*/
sgf_dumptree = NULL;
moves[0] = 0;
if (moves_to_connect_in_two_moves(moves, str1, str2))
return WIN;
order_connection_moves(moves, str1, str2, board[str1],
"simple_connection_three_moves");
for (r = 1; ((r < moves[0] + 1) && !res); r++) {
if (trymove(moves[r], board[str1],
"simple_connection_three_moves", str1)) {
if (simply_connected_two_moves(str1, str2))
res = WIN;
popgo();
}
}
sgf_dumptree = save_sgf_dumptree;
return res;
}
/* Find the forced moves that prevent a simple depth 5 connection.
* Fills the array moves with the forced moves.
*
* This is the ip311 game function.
*
* It finds moves in very important situations such as:
*
* + + + O + +
* + @ @ O + +
* + @ O @ @ +
* + @ O + + +
* + + + + + +
* - - - - - -
*
* and enables recursive_disconnect to prove the two black
* strings are connected in these situations.
*/
static int
prevent_simple_connection_three_moves(int *moves, int str1, int str2)
{
int r, res = 0;
int possible_moves[MAX_MOVES];
SGFTree *save_sgf_dumptree = sgf_dumptree;
/* turn off the sgf traces
*/
sgf_dumptree = NULL;
if (simple_connection_three_moves(str1, str2)) {
res = WIN;
possible_moves[0] = 0;
moves_to_prevent_connection_in_three_moves(possible_moves, str1, str2);
order_connection_moves(moves, str1, str2, OTHER_COLOR(board[str1]),
"prevent_simple_connection_three_moves");
for (r = 1; r < possible_moves[0] + 1; r++) {
if (trymove(possible_moves[r], OTHER_COLOR(board[str1]),
"prevent_simple_connection_three_moves", str1)) {
if (!connection_one_move(str1, str2))
if (!connection_two_moves(str1, str2))
if (!simple_connection_three_moves(str1, str2))
add_array(moves, possible_moves[r]);
popgo();
}
}
}
sgf_dumptree = save_sgf_dumptree;
return res;
}
/* Find simple connections by looking at common liberties
* or directly capturing a common adjacent string without a snapback
* or looking at a ladder for a common adjacent string.
*/
static int
quiescence_connect(int str1, int str2, int *move)
{
int r;
int lib;
int adj, adjs[MAXCHAIN];
/* Common liberties. */
if (have_common_lib(str1, str2, &lib)) {
*move = lib;
return WIN;
}
/* Common adjacent string in atari, more than one stone, no snapback. */
adj = chainlinks2(str1, adjs, 1);
for (r = 0; r < adj; r++)
if (adjacent_strings(adjs[r], str2)
&& !snapback(adjs[r])) {
findlib(adjs[r], 1, move);
return WIN;
}
/* Common adjacent string two liberties, read ladder. */
adj = chainlinks2(str1, adjs, 2);
for (r = 0; r < adj; r++)
if (adjacent_strings(adjs[r], str2))
if (quiescence_capture(adjs[r], move))
return WIN;
return 0;
}
/* A persistent connection cache has been implemented, but currently
* (3.3.15) it does not have much impact on performance. Possible
* explanations for this include:
* 1. The active area is too often unnecessarily large.
* 2. Between the persistent caches of tactical reading and owl
* reading, there is not much to gain from also caching the
* connection results.
* 3. There is some bug in the implementation.
*
* In order to simplify testing of code modifications, the caching
* code has been made conditional. Setting
* USE_PERSISTENT_CONNECTION_CACHE to 0, 1, or 2 has the following
* effects.
* 0 - Completely turned off.
* 1 - Results are stored in the cache but retrieved results are only
* compared to the non-cached result. Deviations are reported.
* 2 - Fully turned on.
*/
#define USE_PERSISTENT_CONNECTION_CACHE 0
/* Externally callable frontend to recursive_connect().
* Returns WIN if str1 and str2 can be connected.
*/
int
string_connect(int str1, int str2, int *move)
{
int dummy_move;
int save_verbose;
int result;
if (move == NULL)
move = &dummy_move;
nodes_connect = 0;
*move = PASS_MOVE;
if (alternate_connections) {
int reading_nodes_when_called = get_reading_node_counter();
double start = 0;
int tactical_nodes;
int save_connection_node_limit = connection_node_limit;
#if USE_PERSISTENT_CONNECTION_CACHE == 1
int result2 = -1;
int move2;
#endif
if (board[str1] == EMPTY || board[str2] == EMPTY)
return 0;
str1 = find_origin(str1);
str2 = find_origin(str2);
if (str1 > str2) {
int tmp = str1;
str1 = str2;
str2 = tmp;
}
#if USE_PERSISTENT_CONNECTION_CACHE == 1
if (!search_persistent_connection_cache(CONNECT, str1, str2,
&result2, &move2))
result2 = -1;
else if (0)
gprintf("Persistent cache found connect %1m %1m: %d %1m\n",
str1, str2, result2, move2);
#endif
#if USE_PERSISTENT_CONNECTION_CACHE == 2
if (search_persistent_connection_cache(CONNECT, str1, str2, &result, move))
return result;
#endif
connection_node_limit *= pow(1.45, -stackp + get_depth_modification());
save_verbose = verbose;
if (verbose > 0)
verbose--;
start = gg_cputime();
memset(connection_shadow, 0, sizeof(connection_shadow));
result = recursive_connect2(str1, str2, move, 0);
verbose = save_verbose;
tactical_nodes = get_reading_node_counter() - reading_nodes_when_called;
connection_node_limit = save_connection_node_limit;
#if USE_PERSISTENT_CONNECTION_CACHE == 1
if (result2 != -1
&& result2 != result
&& *move != move2)
gprintf("Persistent cache failure connect %1m %1m: %d %1m != %d %1m\n",
str1, str2, result, *move, result2, move2);
#endif
if (0) {
gprintf("%oconnect %1M %1M, result %d %1M (%d, %d nodes, %f seconds)\n",
str1, str2, result, *move,
nodes_connect, tactical_nodes, gg_cputime() - start);
dump_stack();
}
if (0) {
gprintf("%oconnect %1m %1m %d %1m ", str1, str2, result, *move);
dump_stack();
}
#if USE_PERSISTENT_CONNECTION_CACHE > 0
store_persistent_connection_cache(CONNECT, str1, str2, result, *move,
tactical_nodes, connection_shadow);
#endif
return result;
}
return recursive_connect(str1, str2, move);
}
/* returns WIN if str1 and str2 can be connected. */
static int
recursive_connect(int str1, int str2, int *move)
{
int i, res = 0, Moves[MAX_MOVES], ForcedMoves[MAX_MOVES];
SETUP_TRACE_INFO2("recursive_connect", str1, str2);
if (board[str1] == EMPTY || board[str2] == EMPTY) {
SGFTRACE2(PASS_MOVE, 0, "one string already captured");
return 0;
}
if (same_string(str1, str2)) {
SGFTRACE2(PASS_MOVE, WIN, "already connected");
return WIN;
}
if (nodes_connect > connection_node_limit) {
SGFTRACE2(PASS_MOVE, 0, "connection node limit reached");
return 0;
}
if (stackp == connect_depth) {
SGFTRACE2(PASS_MOVE, 0, "connection depth limit reached");
return 0;
}
nodes_connect++;
global_connection_node_counter++;
if (quiescence_connect (str1, str2, move)) {
SGFTRACE2(*move, WIN, "quiescence_connect");
return WIN;
}
ForcedMoves[0] = 0;
Moves[0] = 0;
/* if one of the strings to connect can be captured
* and there are forced moves to prevent the capture
* then the only moves to try are the moves that
* defend the string: all the other moves will
* lead to the capture of the string
*/
if (!prevent_capture_one_move(ForcedMoves, str1))
prevent_capture_one_move(ForcedMoves, str2);
#if 0
else if (prevent_capture_two_moves(ForcedMoves, str1))
;
else if (prevent_capture_two_moves(ForcedMoves, str2))
;
#endif
/* We are at a max node, so any move we can find
* is ok. Try moves that can connect in three moves
* because the function that prevent connection in one
* and two moves are called at AND nodes.
*/
moves_to_connect_in_three_moves(Moves, str1, str2, 1);
/* if there are some forced moves to prevent the capture
* of one of the two strings, then we only look at
* the moves that prevent capture and that might also
* connect
*/
if (ForcedMoves[0] != 0 && Moves[0] != 0)
intersection_array(Moves, ForcedMoves);
order_connection_moves(Moves, str1, str2, board[str1],
"recursive_connect");
for (i = 1; ((i < Moves[0] + 1) && (res == 0)); i++) {
if (trymove(Moves[i], board[str1], "recursive_connect", str1)) {
if (!recursive_disconnect(str1, str2, move)) {
*move = Moves[i];
res = WIN;
}
popgo();
}
}
if (res == WIN) {
SGFTRACE2(*move, WIN, "success");
}
else {
SGFTRACE2(PASS_MOVE, 0, "failure");
}
return res;
}
/* Externally callable frontend to recursive_disconnect().
* Returns WIN if str1 and str2 can be disconnected.
*/
int
disconnect(int str1, int str2, int *move)
{
int i;
int res = WIN;
int Moves[MAX_MOVES];
int dummy_move;
int result;
int save_verbose;
if (move == NULL)
move = &dummy_move;
nodes_connect = 0;
*move = PASS_MOVE;
if (alternate_connections) {
int reading_nodes_when_called = get_reading_node_counter();
int save_connection_node_limit = connection_node_limit;
double start = 0;
int tactical_nodes;
#if USE_PERSISTENT_CONNECTION_CACHE == 1
int result2 = -1;
int move2;
#endif
if (board[str1] == EMPTY || board[str2] == EMPTY)
return WIN;
str1 = find_origin(str1);
str2 = find_origin(str2);
if (str1 > str2) {
int tmp = str1;
str1 = str2;
str2 = tmp;
}
#if USE_PERSISTENT_CONNECTION_CACHE == 1
if (!search_persistent_connection_cache(DISCONNECT, str1, str2,
&result2, &move2))
result2 = -1;
else if (0)
gprintf("Persistent cache found disconnect %1m %1m: %d %1m\n",
str1, str2, result2, move2);
#endif
#if USE_PERSISTENT_CONNECTION_CACHE == 2
if (search_persistent_connection_cache(DISCONNECT, str1, str2,
&result, move))
return result;
#endif
connection_node_limit *= pow(1.5, -stackp + get_depth_modification());
save_verbose = verbose;
if (verbose > 0)
verbose--;
start = gg_cputime();
memset(connection_shadow, 0, sizeof(connection_shadow));
result = recursive_disconnect2(str1, str2, move, 0);
verbose = save_verbose;
tactical_nodes = get_reading_node_counter() - reading_nodes_when_called;
connection_node_limit = save_connection_node_limit;
#if USE_PERSISTENT_CONNECTION_CACHE == 1
if (result2 != -1
&& result2 != result
&& *move != move2)
gprintf("Persistent cache failure disconnect %1m %1m: %d %1m != %d %1m\n",
str1, str2, result, *move, result2, move2);
#endif
if (0) {
gprintf("%odisconnect %1m %1m, result %d %1m (%d, %d nodes, %f seconds)\n",
str1, str2, result, *move,
nodes_connect, tactical_nodes, gg_cputime() - start);
dump_stack();
}
if (0) {
gprintf("%odisconnect %1m %1m %d %1m ", str1, str2, result, *move);
dump_stack();
}
#if USE_PERSISTENT_CONNECTION_CACHE > 0
store_persistent_connection_cache(DISCONNECT, str1, str2, result, *move,
tactical_nodes, connection_shadow);
#endif
return result;
}
Moves[0] = 0;
moves_to_prevent_connection_in_three_moves(Moves, str1, str2);
if (Moves[0] > 0)
res = 0;
order_connection_moves(Moves, str1, str2, OTHER_COLOR(board[str1]),
"disconnect");
for (i = 1; ((i < Moves[0] + 1) && (res == 0)); i++)
if (trymove(Moves[i], OTHER_COLOR(board[str1]),
"disconnect", str1)) {
if (!recursive_connect(str1, str2, move)) {
*move = Moves[i];
res = WIN;
}
popgo();
}
return res;
}
/* Externally callable frontend to recursive_disconnect().
* Returns WIN if str1 and str2 can be disconnected.
*
* Uses much lower node and depths limits.
*/
int
fast_disconnect(int str1, int str2, int *move)
{
int result;
int save_limit = connection_node_limit;
int save_verbose = verbose;
if (board[str1] == EMPTY || board[str2] == EMPTY)
return WIN;
str1 = find_origin(str1);
str2 = find_origin(str2);
if (str1 > str2) {
int tmp = str1;
str1 = str2;
str2 = tmp;
}
modify_depth_values(-3);
connection_node_limit /= 4;
if (verbose > 0)
verbose--;
result = recursive_disconnect2(str1, str2, move, 0);
verbose = save_verbose;
connection_node_limit = save_limit;
modify_depth_values(3);
return result;
}
/* Returns WIN if str1 and str2 can be disconnected. */
static int
recursive_disconnect(int str1, int str2, int *move)
{
int i, res = WIN, Moves[MAX_MOVES];
SETUP_TRACE_INFO2("recursive_disconnect", str1, str2);
if (board[str1] == EMPTY || board[str2] == EMPTY) {
SGFTRACE2(PASS_MOVE, WIN, "one string already captured");
return WIN;
}
if (quiescence_capture(str1, move)) {
SGFTRACE2(*move, WIN, "first string capturable");
return WIN;
}
if (quiescence_capture(str2, move)) {
SGFTRACE2(*move, WIN, "second string capturable");
return WIN;
}
if (same_string(str1, str2)) {
SGFTRACE2(PASS_MOVE, 0, "already connected");
return 0;
}
if (nodes_connect > connection_node_limit) {
SGFTRACE2(PASS_MOVE, WIN, "connection node limit reached");
return WIN;
}
if (stackp == connect_depth) {
SGFTRACE2(PASS_MOVE, WIN, "connection depth limit reached");
return WIN;
}
nodes_connect++;
global_connection_node_counter++;
/* we are at an and node
* only look at forced moves here,
* it ensures that the result of recursive_disconnect
* is proved if it returns 0 (that is connections are proved)
*/
Moves[0] = 0;
if (prevent_connection_one_move(Moves, str1, str2))
res = 0;
else if (prevent_connection_two_moves(Moves, str1, str2))
res = 0;
else if (prevent_simple_connection_three_moves(Moves, str1, str2))
res = 0;
if (res == 0)
order_connection_moves(Moves, str1, str2, OTHER_COLOR(board[str1]),
"recursive_disconnect");
for (i = 1; ((i < Moves[0] + 1) && (res == 0)); i++)
if (trymove(Moves[i], OTHER_COLOR(board[str1]),
"recursive_disconnect", str1)) {
if (!recursive_connect(str1, str2, move)) {
*move = Moves[i];
res = WIN;
}
popgo();
}
if (res == WIN) {
SGFTRACE2(*move, WIN, "success");
}
else {
SGFTRACE2(PASS_MOVE, 0, "failure");
}
return res;
}
/* Reads simple ladders.
*/
static int
quiescence_capture(int str, int *move)
{
SGFTree *save_sgf_dumptree = sgf_dumptree;
int save_count_variations = count_variations;
int result = 0;
/* We turn off the sgf traces here to avoid cluttering them up with
* naive_ladder moves.
*/
sgf_dumptree = NULL;
count_variations = 0;
if (countlib(str) == 1) {
findlib(str, 1, move);
result = WIN;
}
else if (countlib(str) == 2)
result = simple_ladder(str, move);
/* Turn the sgf traces back on. */
sgf_dumptree = save_sgf_dumptree;
count_variations = save_count_variations;
return result;
}
#if 0
static int
capture_one_move(int str)
{
if (countlib(str) == 1)
return 1;
return 0;
}
#endif
/* Find all the possible moves that can prevent the capture
* of a string in atari.
*
* The ip1 game function.
*/
static int
prevent_capture_one_move(int *moves, int str1)
{
int r, res = 0;
int liberties, libs[MAXLIBS];
int adj, adjs[MAXCHAIN];
liberties = findlib(str1, MAXLIBS, libs);
if (liberties == 1) {
add_array(moves, libs[0]);
res = WIN;
adj = chainlinks2(str1, adjs, 1);
for (r = 0; r < adj; r++) {
findlib(adjs[r], 1, libs);
add_array(moves, libs[0]);
}
}
return res;
}
/* Returns WIN if str1, str2 and str3 can be connected. */
static int
recursive_transitivity(int str1, int str2, int str3, int *move)
{
int i, res = 0, Moves[MAX_MOVES], ForcedMoves[MAX_MOVES];
SETUP_TRACE_INFO2("recursive_transitivity", str1, str3);
if (board[str1] == EMPTY || board[str2] == EMPTY || board[str3] == EMPTY) {
SGFTRACE2(PASS_MOVE, 0, "one string already captured");
return 0;
}
if (same_string(str1, str2) && same_string(str1, str3)) {
SGFTRACE2(PASS_MOVE, WIN, "already connected");
return WIN;
}
if (nodes_connect > connection_node_limit) {
SGFTRACE2(PASS_MOVE, 0, "connection node limit reached");
return 0;
}
if (stackp == connect_depth) {
SGFTRACE2(PASS_MOVE, 0, "connection depth limit reached");
return 0;
}
nodes_connect++;
global_connection_node_counter++;
if (same_string(str1, str2))
if (quiescence_connect (str1, str3, move)) {
SGFTRACE2(*move, WIN, "quiescence_connect");
return WIN;
}
if (same_string(str2, str3))
if (quiescence_connect (str1, str2, move)) {
SGFTRACE2(*move, WIN, "quiescence_connect");
return WIN;
}
ForcedMoves[0] = 0;
Moves[0] = 0;
/* If one of the strings to connect can be captured
* and there are forced moves to prevent the capture
* then the only moves to try are the moves that
* defend the string. All the other moves will
* lead to the capture of the string.
*/
if (!prevent_capture_one_move(ForcedMoves, str1))
if (!prevent_capture_one_move(ForcedMoves, str2))
prevent_capture_one_move(ForcedMoves, str3);
/* We are at a max node, so any move we can find
* is ok. Try moves that can connect in two moves
* because the function that prevents connection in one
* move is called at and nodes.
*/
moves_to_connect_in_two_moves(Moves, str1, str2);
moves_to_connect_in_two_moves(Moves, str2, str3);
/* If there are some forced moves to prevent the capture
* of one of the two strings, then we only look at
* the moves that prevent capture and that might also
* connect.
*/
if ((ForcedMoves[0] != 0) && (Moves[0] != 0))
intersection_array(Moves, ForcedMoves);
order_connection_moves(Moves, str1, str2, board[str1],
"recursive_transitivity");
for (i = 1; ((i < Moves[0] + 1) && (res == 0)); i++) {
if (trymove(Moves[i], board[str1], "recursive_transitivity", str1)) {
if (!recursive_non_transitivity(str1, str2, str3, move)) {
*move = Moves[i];
res = WIN;
}
popgo();
}
}
if (res == WIN) {
SGFTRACE2(*move, WIN, "success");
}
else {
SGFTRACE2(PASS_MOVE, 0, "failure");
}
return res;
}
/* It is often assumed that if str1 connects to str2 and str2
* connects to str3 then str1 connects to str3. This is called
* TRANSITIVITY. However there are exceptions such as this
* situation:
*
* XXXXX XXXXX
* OO.OO AA*CC
* ..O.. ..B..
* XXXXX XXXXX
*
* Although strings A and B are connected, and strings B and C
* are connected, a move at * disconnects strings A and C.
*
* This function is a public frontend to recursive_non_transitivity().
* Returns WIN if str1, str2 and str3 can be disconnected.
*/
int
non_transitivity(int str1, int str2, int str3, int *move)
{
int i, res = WIN, Moves[MAX_MOVES];
nodes_connect = 0;
*move = PASS_MOVE;
moves_to_prevent_connection_in_three_moves(Moves, str1, str3);
if (Moves[0] > 0)
res = 0;
order_connection_moves(Moves, str1, str2, OTHER_COLOR(board[str1]),
"non_transitivity");
for (i = 1; ((i < Moves[0] + 1) && (res == 0)); i++)
if (trymove(Moves[i], OTHER_COLOR(board[str1]),
"non_transitivity", str1)) {
if (!recursive_transitivity(str1, str2, str3, move)) {
*move = Moves[i];
res = WIN;
}
popgo();
}
return res;
}
/* Returns WIN if str1, str2 and str3 can be disconnected. */
static int
recursive_non_transitivity(int str1, int str2, int str3, int *move)
{
int i, res = WIN, Moves[MAX_MOVES];
SETUP_TRACE_INFO2("recursive_non_transitivity", str1, str3);
if (board[str1] == EMPTY || board[str2] == EMPTY
|| board[str3] == EMPTY) {
SGFTRACE2(PASS_MOVE, WIN, "one string already captured");
return WIN;
}
if (quiescence_capture(str1, move)) {
SGFTRACE2(*move, WIN, "first string capturable");
return WIN;
}
if (quiescence_capture(str2, move)) {
SGFTRACE2(*move, WIN, "second string capturable");
return WIN;
}
if (quiescence_capture(str3, move)) {
SGFTRACE2(*move, WIN, "third string capturable");
return WIN;
}
if (same_string(str1, str2) && same_string(str1, str3)) {
SGFTRACE2(PASS_MOVE, 0, "already connected");
return 0;
}
if (nodes_connect > connection_node_limit) {
SGFTRACE2(PASS_MOVE, WIN, "connection node limit reached");
return WIN;
}
if (stackp == connect_depth) {
SGFTRACE2(PASS_MOVE, WIN, "connection depth limit reached");
return WIN;
}
nodes_connect++;
global_connection_node_counter++;
/* We are at an and node. Only look at forced moves. */
Moves[0] = 0;
if (prevent_connection_one_move(Moves, str1, str3))
res = 0;
else if (prevent_connection_two_moves(Moves, str1, str3))
res = 0;
else if (prevent_simple_connection_three_moves(Moves, str1, str3))
res = 0;
if (res == 0)
order_connection_moves(Moves, str1, str2, OTHER_COLOR(board[str1]),
"recursive_non_transitivity");
for (i = 1; ((i < Moves[0] + 1) && (res == 0)); i++)
if (trymove(Moves[i], OTHER_COLOR(board[str1]),
"recursive_non_transitivity", str1)) {
if (!recursive_transitivity(str1, str2, str3, move)) {
*move = Moves[i];
res = WIN;
}
popgo();
}
if (res == WIN) {
SGFTRACE2(*move, WIN, "success");
}
else {
SGFTRACE2(PASS_MOVE, 0, "failure");
}
return res;
}
/* Order the moves so that we try the ones likely to succeed early. */
static void
order_connection_moves(int *moves, int str1, int str2, int color_to_move,
const char *funcname)
{
int scores[MAX_MOVES];
int r;
int i, j;
UNUSED(str2);
UNUSED(color_to_move);
for (r = 1; r <= moves[0]; r++) {
int move = moves[r];
/* Look at the neighbors of this move and count the things we
* find. Friendly and opponent stones are related to color, i.e.
* the player to move, not to the color of the string.
*
* We don't use all these values. They are only here so we can
* reuse incremental_order_moves() which was developed for the
* tactical reading.
*/
int number_edges = 0; /* outside board */
int number_same_string = 0; /* the string being attacked */
int number_own = 0; /* friendly stone */
int number_opponent = 0; /* opponent stone */
int captured_stones = 0; /* number of stones captured by this move */
int threatened_stones = 0; /* number of stones threatened by this move */
int saved_stones = 0; /* number of stones in atari saved */
int number_open = 0; /* empty intersection */
int libs;
/* We let the incremental board code do the heavy work. */
incremental_order_moves(move, color_to_move, str1, &number_edges,
&number_same_string, &number_own,
&number_opponent, &captured_stones,
&threatened_stones, &saved_stones, &number_open);
if (0)
gprintf("%o %1m values: %d %d %d %d %d %d %d %d\n", move, number_edges,
number_same_string, number_own, number_opponent,
captured_stones, threatened_stones, saved_stones, number_open);
scores[r] = 0;
libs = approxlib(move, color_to_move, 10, NULL);
/* Avoid self atari. */
if (libs == 1 && captured_stones == 0)
scores[r] -= 10;
/* Good to get many liberties. */
if (libs < 4)
scores[r] += libs;
else
scores[r] += 4;
/* Very good to capture opponent stones. */
if (captured_stones > 0)
scores[r] += 5 + captured_stones;
/* Good to threaten opponent stones. */
if (threatened_stones > 0)
scores[r] += 3;
/* Extremely good to save own stones. */
if (saved_stones > 0)
scores[r] += 10 + saved_stones;
}
/* Now sort the moves. We use selection sort since this array will
* probably never be more than 10 moves long. In this case, the
* overhead imposed by quicksort will probably overshadow the gains
* given by the O(n*log(n)) behaviour over the O(n^2) behaviour of
* selection sort.
*/
for (i = 1; i <= moves[0]; i++) {
/* Find the move with the biggest score. */
int maxscore = scores[i];
int max_at = i;
for (j = i+1; j <= moves[0]; j++) {
if (scores[j] > maxscore) {
maxscore = scores[j];
max_at = j;
}
}
/* Now exchange the move at i with the move at max_at.
* Don't forget to exchange the scores as well.
*/
if (max_at != i) {
int temp = moves[i];
int tempmax = scores[i];
moves[i] = moves[max_at];
scores[i] = scores[max_at];
moves[max_at] = temp;
scores[max_at] = tempmax;
}
}
if (0) {
gprintf("%oVariation %d:\n", count_variations);
for (i = 1; i <= moves[0]; i++)
gprintf("%o %1M %d\n", moves[i], scores[i]);
}
if (sgf_dumptree) {
char buf[500];
char *pos;
int chars;
sprintf(buf, "Move order for %s: %n", funcname, &chars);
pos = buf + chars;
for (i = 1; i <= moves[0]; i++) {
sprintf(pos, "%c%d (%d) %n", J(moves[i]) + 'A' + (J(moves[i]) >= 8),
board_size - I(moves[i]), scores[i], &chars);
pos += chars;
}
sgftreeAddComment(sgf_dumptree, buf);
}
}
/* Clear statistics. */
void
reset_connection_node_counter()
{
global_connection_node_counter = 0;
}
/* Retrieve statistics. */
int
get_connection_node_counter()
{
return global_connection_node_counter;
}
/*********************************************************
*
* Alternate connection reading algorithm.
*
* This code is enabled with the --enable-alternate-connections
* configure flag at build time or toggled with the
* --alternate-connections option at run time.
*
*********************************************************/
/* This has been copied from reading.c and modified.
*/
#define ADD_CANDIDATE_MOVE(move, distance, moves, distances, num_moves)\
do {\
int l;\
for (l = 0; l < num_moves; l++)\
if (moves[l] == (move)) {\
if (distances[l] > distance)\
distances[l] = distance;\
break;\
}\
if ((l == num_moves) && (num_moves < MAX_MOVES)) {\
moves[num_moves] = move;\
distances[num_moves] = distance;\
(num_moves)++;\
}\
} while (0)
static int find_string_connection_moves(int str1, int str2, int color_to_move,
int moves[MAX_MOVES],
int *total_distance);
static void clear_connection_data(struct connection_data *conn);
static int trivial_connection(int str1, int str2, int *move);
static int does_secure_through_ladder(int color, int move, int pos);
static int ladder_capture(int str, int *move);
static int ladder_capturable(int pos, int color);
static int no_escape_from_atari(int str);
static int no_escape_from_ladder(int str);
static int check_self_atari(int pos, int color_to_move);
static int common_vulnerabilities(int a1, int a2, int b1, int b2, int color);
static int common_vulnerability(int apos, int bpos, int color);
/* Try to connect two strings. This function is called in a mutual
* recursion with recursive_disconnect2(). Return codes is identical to
* the tactical reading functions. For the has_passed parameter, see the
* documentation of recursive_disconnect2().
*
* The algorithm is
* 1. Check if the strings are trivially connected or disconnected or
* the result is already cached.
* 2. Find connection moves.
* 3. Try one move at a time and call recursive_disconnect2() to see
* whether we were successful.
* 4. If no move was found we assume success if the connection
* distance was small and failure otherwise.
*/
static int
recursive_connect2(int str1, int str2, int *move, int has_passed)
{
int color = board[str1];
int moves[MAX_MOVES];
int num_moves;
int distance = FP(0.0);
int k;
int xpos;
int savemove = NO_MOVE;
int savecode = 0;
int tried_moves = 0;
int value;
SETUP_TRACE_INFO2("recursive_connect2", str1, str2);
if (move)
*move = NO_MOVE;
nodes_connect++;
global_connection_node_counter++;
if (board[str1] == EMPTY || board[str2] == EMPTY) {
SGFTRACE2(PASS_MOVE, 0, "one string already captured");
return 0;
}
if (same_string(str1, str2)) {
SGFTRACE2(PASS_MOVE, WIN, "already connected");
return WIN;
}
if (nodes_connect > connection_node_limit) {
SGFTRACE2(PASS_MOVE, 0, "connection node limit reached");
return 0;
}
if (stackp > connect_depth2) {
SGFTRACE2(PASS_MOVE, 0, "connection depth limit reached");
return 0;
}
str1 = find_origin(str1);
str2 = find_origin(str2);
if (stackp <= depth && !has_passed
&& tt_get(&ttable, CONNECT, str1, str2, depth - stackp, NULL,
&value, NULL, &xpos) == 2) {
TRACE_CACHED_RESULT2(value, value, xpos);
if (value != 0)
if (move)
*move = xpos;
SGFTRACE2(xpos, value, "cached");
return value;
}
if (trivial_connection(str1, str2, &xpos) == WIN) {
SGFTRACE2(xpos, WIN, "trivial connection");
READ_RETURN_CONN(CONNECT, str1, str2, depth - stackp, move, xpos, WIN);
}
num_moves = find_string_connection_moves(str1, str2, color,
moves, &distance);
for (k = 0; k < num_moves; k++) {
int ko_move;
xpos = moves[k];
if (komaster_trymove(xpos, color, "recursive_connect2", str1,
&ko_move, stackp <= ko_depth && savecode == 0)) {
tried_moves++;
if (!ko_move) {
int acode = recursive_disconnect2(str1, str2, NULL,
has_passed);
popgo();
if (acode == 0) {
SGFTRACE2(xpos, WIN, "connection effective");
READ_RETURN_CONN(CONNECT, str1, str2, depth - stackp,
move, xpos, WIN);
}
/* if the move works with ko we save it, then look for something
* better.
*/
UPDATE_SAVED_KO_RESULT(savecode, savemove, acode, xpos);
}
else {
if (recursive_disconnect2(str1, str2, NULL,
has_passed) != WIN) {
savemove = xpos;
savecode = KO_B;
}
popgo();
}
}
}
if (tried_moves == 0 && distance < FP(1.0)) {
SGFTRACE2(NO_MOVE, WIN, "no move, probably connected");
READ_RETURN_CONN(CONNECT, str1, str2, depth - stackp, move, NO_MOVE, WIN);
}
if (savecode != 0) {
SGFTRACE2(savemove, savecode, "saved move");
READ_RETURN_CONN(CONNECT, str1, str2, depth - stackp,
move, savemove, savecode);
}
SGFTRACE2(0, 0, NULL);
READ_RETURN_CONN(CONNECT, str1, str2, depth - stackp, move, NO_MOVE, 0);
}
/* Try to disconnect two strings. This function is called in a mutual
* recursion with recursive_connect2(). Return codes is identical to
* the tactical reading functions.
*
* The algorithm is
* 1. Check if the strings are trivially connected or disconnected or
* the result is already cached.
* 2. Find disconnection moves.
* 3. Try one move at a time and call recursive_connect2() to see
* whether we were successful.
* 4. If no move was found we assume failure if the connection
* distance was small. Otherwise we pass and let
* recursive_connect2() try to connect. However, if we already have
* passed once we just declare success. Whether a pass already has
* been made is indicated by the has_passed parameter.
*/
static int
recursive_disconnect2(int str1, int str2, int *move, int has_passed)
{
int color = board[str1];
int other = OTHER_COLOR(color);
int moves[MAX_MOVES];
int num_moves;
int distance = FP(0.0);
int k;
int xpos;
int savemove = NO_MOVE;
int savecode = 0;
int tried_moves = 0;
int attack_code1;
int attack_pos1;
int attack_code2;
int attack_pos2;
SGFTree *save_sgf_dumptree = sgf_dumptree;
int save_count_variations = count_variations;
int value;
SETUP_TRACE_INFO2("recursive_disconnect2", str1, str2);
nodes_connect++;
global_connection_node_counter++;
if (move)
*move = NO_MOVE;
if (board[str1] == EMPTY || board[str2] == EMPTY) {
SGFTRACE2(PASS_MOVE, WIN, "one string already captured");
return WIN;
}
if (same_string(str1, str2)) {
SGFTRACE2(PASS_MOVE, 0, "already connected");
return 0;
}
if (nodes_connect > connection_node_limit) {
SGFTRACE2(PASS_MOVE, WIN, "connection node limit reached");
return WIN;
}
if (stackp > connect_depth2) {
SGFTRACE2(PASS_MOVE, WIN, "connection depth limit reached");
return WIN;
}
sgf_dumptree = NULL;
count_variations = 0;
str1 = find_origin(str1);
str2 = find_origin(str2);
attack_code1 = attack(str1, &attack_pos1);
if (attack_code1 == WIN) {
sgf_dumptree = save_sgf_dumptree;
count_variations = save_count_variations;
SGFTRACE2(attack_pos1, WIN, "one string is capturable");
if (move)
*move = attack_pos1;
return WIN;
}
attack_code2 = attack(str2, &attack_pos2);
if (attack_code2 == WIN) {
sgf_dumptree = save_sgf_dumptree;
count_variations = save_count_variations;
SGFTRACE2(attack_pos2, WIN, "one string is capturable");
if (move)
*move = attack_pos2;
return WIN;
}
sgf_dumptree = save_sgf_dumptree;
count_variations = save_count_variations;
if (stackp <= depth
&& tt_get(&ttable, DISCONNECT, str1, str2,
depth - stackp, NULL,
&value, NULL, &xpos) == 2) {
TRACE_CACHED_RESULT2(value, value, xpos);
if (value != 0)
if (move)
*move = xpos;
SGFTRACE2(xpos, value, "cached");
return value;
}
if (ladder_capture(str1, &xpos) == WIN) {
SGFTRACE2(xpos, WIN, "first string capturable");
READ_RETURN_CONN(DISCONNECT, str1, str2, depth - stackp, move, xpos, WIN);
}
if (ladder_capture(str2, &xpos) == WIN) {
SGFTRACE2(xpos, WIN, "second string capturable");
READ_RETURN_CONN(DISCONNECT, str1, str2, depth - stackp, move, xpos, WIN);
}
num_moves = find_string_connection_moves(str1, str2, other,
moves, &distance);
if (attack_code1 != 0 && num_moves < MAX_MOVES) {
for (k = 0; k < num_moves; k++) {
if (moves[k] == attack_pos1)
break;
}
if (k == num_moves)
moves[num_moves++] = attack_pos1;
}
if (attack_code2 != 0 && num_moves < MAX_MOVES) {
for (k = 0; k < num_moves; k++) {
if (moves[k] == attack_pos2)
break;
}
if (k == num_moves)
moves[num_moves++] = attack_pos2;
}
for (k = 0; k < num_moves; k++) {
int ko_move;
xpos = moves[k];
if (komaster_trymove(xpos, other, "recursive_disconnect2", str1,
&ko_move, stackp <= ko_depth && savecode == 0)) {
tried_moves++;
if (!ko_move) {
int dcode = recursive_connect2(str1, str2, NULL,
has_passed);
popgo();
if (dcode == 0) {
SGFTRACE2(xpos, WIN, "disconnection effective");
READ_RETURN_CONN(DISCONNECT, str1, str2, depth - stackp,
move, xpos, WIN);
}
/* if the move works with ko we save it, then look for something
* better.
*/
UPDATE_SAVED_KO_RESULT(savecode, savemove, dcode, xpos);
}
else {
if (recursive_connect2(str1, str2, NULL,
has_passed) != WIN) {
savemove = xpos;
savecode = KO_B;
}
popgo();
}
}
}
if (tried_moves == 0
&& distance >= FP(1.0)
&& (has_passed
|| !recursive_connect2(str1, str2, NULL, 1))) {
SGFTRACE2(NO_MOVE, WIN, "no move, probably disconnected");
READ_RETURN_CONN(DISCONNECT, str1, str2, depth - stackp,
move, NO_MOVE, WIN);
}
if (savecode != 0) {
SGFTRACE2(savemove, savecode, "saved move");
READ_RETURN_CONN(DISCONNECT, str1, str2, depth - stackp,
move, savemove, savecode);
}
SGFTRACE2(0, 0, NULL);
READ_RETURN_CONN(DISCONNECT, str1, str2, depth - stackp, move, NO_MOVE, 0);
}
/* Find moves to connect or disconnect the two strings str1 and str2.
* If color_to_move equals the color of the strings we search for
* connecting moves and otherwise disconnecting moves. The moves are
* returned in the moves[] array and the number of moves is the return
* value of the function. The parameter *total_distance is set to the
* approximated connection distance between the two strings. This is
* most useful when no moves are found. If *total_distance is small
* they are probably already effectively connected and if it is huge
* they are probably disconnected.
*
* The algorithm is to compute connection distances around each string
* and find points where the sum of the distances is small, or more
* exactly where the sum of the distances after the move would be
* small. This can be done with help of delta values returned together
* with distance values from the function
* compute_connection_distances(). This "distance after move" measure
* is modified with various bonuses and then used to order the found
* moves.
*/
static int
find_connection_moves(int str1, int str2, int color_to_move,
struct connection_data *conn1,
struct connection_data *conn2,
int max_dist1, int max_dist2,
int moves[MAX_MOVES], int total_distance,
int cutoff)
{
int color = board[str1];
int other = OTHER_COLOR(color);
int connect_move = (color_to_move == color);
int r;
int distances[MAX_MOVES];
int num_moves = 0;
int acode = 0;
int attack_move = NO_MOVE;
int dcode = 0;
int defense_move = NO_MOVE;
int k;
int i, j;
SGFTree *save_sgf_dumptree = sgf_dumptree;
int save_count_variations = count_variations;
int distance_limit;
/* We turn off the sgf traces here to avoid cluttering them up with
* tactical reading moves.
*/
sgf_dumptree = NULL;
count_variations = 0;
/* Loop through the points with smallish distance from str1 and look
* for ones also having a small distance to str2.
*/
for (r = 0; r < conn1->queue_end; r++) {
int pos = conn1->queue[r];
int dist1 = conn1->distances[pos];
int deltadist1 = conn1->deltas[pos];
int dist2 = conn2->distances[pos];
int deltadist2 = conn2->deltas[pos];
int d1;
int d2;
int distance;
if (dist1 - deltadist1 + dist2 - deltadist2 > FP(2.5)
|| dist1 > max_dist1 + FP(0.2)
|| dist2 > max_dist2 + FP(0.2))
continue;
if (verbose > 0)
gprintf("%oMove %1m, (%f, %f, %f, %f)\n", pos,
FIXED_TO_FLOAT(dist1), FIXED_TO_FLOAT(deltadist1),
FIXED_TO_FLOAT(dist2), FIXED_TO_FLOAT(deltadist2));
/* The basic quality of the move is the sum of the distances to
* each string minus the two delta values. This distance value
* will subsequently be modified to take other factors into
* account.
*/
d1 = dist1 - deltadist1;
d2 = dist2 - deltadist2;
distance = d1 + d2;
if (verbose > 0)
gprintf("%o %f, primary distance\n", FIXED_TO_FLOAT(distance));
/* Bonus if d1 and d2 are well balanced. */
if ((3 * d1) / 2 > d2 && (3 * d2) / 2 > d1) {
distance -= FP(0.1);
if (verbose > 0)
gprintf("%o -0.1, well balanced\n");
}
/* Check whether the move is "between" the two strings. */
if (conn1->coming_from[pos] != NO_MOVE
&& conn1->coming_from[pos] == conn2->coming_from[pos]) {
if (verbose > 0)
gprintf("%o discarded, not between strings\n");
continue;
}
if (board[pos] == EMPTY) {
if (check_self_atari(pos, color_to_move)) {
ADD_CANDIDATE_MOVE(pos, distance, moves, distances, num_moves);
}
else {
if (verbose > 0)
gprintf("%o discarded, self atari\n");
}
}
else if (board[pos] == other) {
attack_and_defend(pos, &acode, &attack_move, &dcode, &defense_move);
if (verbose > 0)
gprintf("%o attack with code %d at %1m, defense with code %d at %1m\n",
acode, attack_move, dcode, defense_move);
if (connect_move && acode != 0) {
if (dcode == 0) {
distance += FP(0.5);
if (verbose > 0)
gprintf("%o +0.5, no defense\n");
}
else {
if (conn1->distances[attack_move]
+ conn2->distances[attack_move] > dist1 + dist2) {
distance += FP(0.5);
if (verbose > 0)
gprintf("%o +0.5, attack point not on shortest path\n");
}
}
ADD_CANDIDATE_MOVE(attack_move, distance - FP(0.15), moves, distances,
num_moves);
if (verbose > 0)
gprintf("%o -0.15 at %1m, capturing a string\n", attack_move);
}
else if (!connect_move && acode != 0 && dcode != 0) {
ADD_CANDIDATE_MOVE(defense_move, distance - FP(0.5), moves, distances,
num_moves);
if (verbose > 0)
gprintf("%o -0.5 at %1m, defending a string\n", defense_move);
}
}
else if (board[pos] == color) {
/* Check whether there are common vulnerable points. */
for (k = 0; k < 4; k++) {
int apos, bpos;
if (k & 1)
apos = conn1->vulnerable1[pos];
else
apos = conn1->vulnerable2[pos];
if (k & 2)
bpos = conn2->vulnerable1[pos];
else
bpos = conn2->vulnerable2[pos];
if (common_vulnerability(apos, bpos, color)) {
if (check_self_atari(apos, color_to_move)) {
ADD_CANDIDATE_MOVE(apos, distance, moves, distances, num_moves);
if (verbose > 0)
gprintf("%o +0.0 at %1m, vulnerability\n", apos);
}
if (bpos != apos
&& check_self_atari(bpos, color_to_move)) {
ADD_CANDIDATE_MOVE(bpos, distance, moves, distances, num_moves);
if (verbose > 0)
gprintf("%o +0.0 at %1m, vulnerability\n", bpos);
}
}
}
}
}
/* Modify the distance values for the moves with various bonuses. */
for (r = 0; r < num_moves; r++) {
int move = moves[r];
int adjacent_to_attacker = 0;
int bonus_given = 0;
for (k = 0; k < 4; k++) {
int pos = move + delta[k];
if (board[pos] == other) {
adjacent_to_attacker = 1;
distances[r] -= FP(0.15);
if (verbose > 0)
gprintf("%o%1M -0.15, adjacent to attacker string\n", move);
if (countlib(pos) <= 2) {
distances[r] -= FP(0.2);
if (verbose > 0)
gprintf("%o%1M -0.2, adjacent to attacker string with at most two liberties\n", move);
if ((connect_move || !bonus_given)
&& (conn1->distances[move] - conn1->deltas[move] <= FP(0.5)
|| conn1->distances[pos] - conn1->deltas[pos] <= FP(0.5))
&& (conn2->distances[move] - conn2->deltas[move] <= FP(0.5)
|| conn2->distances[pos] - conn2->deltas[pos] <= FP(0.5))
&& conn1->distances[pos] < total_distance
&& conn2->distances[pos] < total_distance) {
bonus_given = 1;
distances[r] -= FP(0.7);
if (verbose > 0)
gprintf("%o%1M -0.7, capture or atari of immediately connecting string\n", move);
}
}
}
else if (board[pos] == color) {
if (countlib(pos) <= 2) {
distances[r] -= FP(0.2);
if (verbose > 0)
gprintf("%o%1M -0.2, adjacent to defender string with at most two liberties\n", move);
}
/* The code above (in the 'board[pos] == other' branch) makes
* perfect sense for the defender, but has a tendency to
* overestimate solid connection defenses when the attacker's
* stones happen to be in atari, specially when capturing some
* defender stones instead would help just as well, if not better.
* The following code compensates in such kind of situations.
* See connection:111 and gunnar:53 for example.
*/
if (!connect_move && countlib(pos) == 1
/* let's avoid ko and snapbacks */
&& accuratelib(move, other, 2, NULL) > 1) {
int adjs[MAXCHAIN];
int bonus;
bonus = FP(0.1) * chainlinks2(pos, adjs, 2);
bonus += FP(0.5) * chainlinks2(pos, adjs, 1);
distances[r] -= bonus;
if (verbose > 0)
gprintf("%o%1M -%f, capture of defender string\n",
move, FIXED_TO_FLOAT(bonus));
}
}
}
if (adjacent_to_attacker
&& !connect_move
&& is_edge_vertex(move)) {
distances[r] -= FP(0.1);
if (verbose > 0)
gprintf("%o%1M -0.1, disconnect move on edge\n", move);
}
if (ladder_capturable(move, color_to_move)) {
distances[r] += FP(0.3);
if (verbose > 0)
gprintf("%o%1M +0.3, can be captured in a ladder\n", move);
}
/* Bonus for moves adjacent to endpoint strings with 3 liberties.
* Neighbor strings with less than 3 liberties have already
* generated a bonus above.
*/
if ((liberty_of_string(move, str1)
&& countlib(str1) == 3)
|| (ON_BOARD(str2) && liberty_of_string(move, str2)
&& countlib(str2) == 3)) {
distances[r] -= FP(0.1);
if (verbose > 0)
gprintf("%o%1M -0.1, liberty of endpoint string with 3 libs\n", move);
}
}
/* Turn the sgf traces back on. */
sgf_dumptree = save_sgf_dumptree;
count_variations = save_count_variations;
/* Now sort the moves. We use selection sort since this array will
* probably never be more than 10 moves long. In this case, the
* overhead imposed by quicksort will probably overshadow the gains
* given by the O(n*log(n)) behaviour over the O(n^2) behaviour of
* selection sort.
*/
for (i = 0; i < num_moves; i++) {
/* Find the move with the smallest distance. */
int mindistance = distances[i];
int min_at = i;
for (j = i + 1; j < num_moves; j++) {
if (distances[j] < mindistance) {
mindistance = distances[j];
min_at = j;
}
}
/* Now exchange the move at i with the move at min_at.
* Don't forget to exchange the distances as well.
*/
if (min_at != i) {
int temp = moves[i];
int tempmin = distances[i];
moves[i] = moves[min_at];
distances[i] = distances[min_at];
moves[min_at] = temp;
distances[min_at] = tempmin;
}
}
if (verbose > 0) {
gprintf("%oSorted moves:\n");
for (i = 0; i < num_moves; i++)
gprintf("%o%1M %f\n", moves[i], FIXED_TO_FLOAT(distances[i]));
}
if (sgf_dumptree) {
char buf[500];
char *pos;
int chars;
sprintf(buf, "Move order for %sconnect: %n",
connect_move ? "" : "dis", &chars);
pos = buf + chars;
for (i = 0; i < num_moves; i++) {
sprintf(pos, "%c%d (%4.2f) %n", J(moves[i]) + 'A' + (J(moves[i]) >= 8),
board_size - I(moves[i]), FIXED_TO_FLOAT(distances[i]),
&chars);
pos += chars;
}
if (cutoff < HUGE_CONNECTION_DISTANCE) {
sprintf(pos, "(cutoff %f)%n", FIXED_TO_FLOAT(cutoff), &chars);
pos += chars;
}
sgftreeAddComment(sgf_dumptree, buf);
}
if (num_moves == 0)
return num_moves;
/* Filter out moves with distance at least 1.5 more than the best
* move, or with distance higher than the cutoff specified.
*
* In order to further reduce the branching factor, a decreasing
* cutoff is applied between candidates. For instance, in this case
* 1. d 2. d+0.5 3. d+1.0 4. d+1.5
* the 4th candidate will be tested, while in following one
* 1. d 2. d+0.1 3. d+0.2 4. d+1.5
* it will be discarded.
*/
if (num_moves <= 1 || !is_ko(moves[0], color_to_move, NULL))
distance_limit = distances[0] + FP(1.5);
else
distance_limit = distances[1] + FP(1.5);
/* Special case: If the second best move has a distance less than 1,
* include it if even if the best move has a very low distance.
*/
if (num_moves > 1
&& distances[1] < FP(1.0)
&& distances[1] > distance_limit)
distance_limit = distances[1];
for (r = 0; r < num_moves; r++) {
if (r > 1
&& distances[r] > distances[r-1]
&& distances[r] - distances[r-1] > (8 - r) * FP(0.2))
break;
if (distances[r] > distance_limit
|| distances[r] > cutoff)
break;
}
num_moves = r;
return num_moves;
}
static int
find_string_connection_moves(int str1, int str2, int color_to_move,
int moves[MAX_MOVES], int *total_distance)
{
struct connection_data conn1;
struct connection_data conn2;
int max_dist1;
int max_dist2;
int num_moves;
int lib;
SGFTree *save_sgf_dumptree = sgf_dumptree;
int save_count_variations = count_variations;
/* We turn off the sgf traces here to avoid cluttering them up with
* tactical reading moves.
*/
sgf_dumptree = NULL;
count_variations = 0;
compute_connection_distances(str1, str2, FP(3.051), &conn1, 1);
compute_connection_distances(str2, str1, FP(3.051), &conn2, 1);
if (findlib(str1, 1, &lib) == 1) {
conn1.distances[lib] = 0;
conn1.coming_from[lib] = NO_MOVE;
conn2.distances[lib] = conn2.distances[str1];
conn2.coming_from[lib] = conn1.coming_from[str1];
}
if (findlib(str2, 1, &lib) == 1) {
conn2.distances[lib] = 0;
conn1.distances[lib] = conn1.distances[str2];
}
max_dist1 = conn1.distances[str2];
max_dist2 = conn2.distances[str1];
*total_distance = gg_min(max_dist1, max_dist2);
if (verbose > 0) {
gprintf("%oVariation %d\n", save_count_variations);
dump_stack();
showboard(0);
print_connection_distances(&conn1);
print_connection_distances(&conn2);
}
sgf_dumptree = save_sgf_dumptree;
count_variations = save_count_variations;
num_moves = find_connection_moves(str1, str2, color_to_move,
&conn1, &conn2, max_dist1, max_dist2,
moves, *total_distance,
HUGE_CONNECTION_DISTANCE);
return num_moves;
}
static void
add_to_start_queue(int pos, int dist, struct connection_data *conn)
{
conn->queue[conn->queue_end++] = pos;
conn->distances[pos] = dist;
conn->deltas[pos] = dist;
conn->coming_from[pos] = NO_MOVE;
conn->vulnerable1[pos] = NO_MOVE;
conn->vulnerable2[pos] = NO_MOVE;
}
void
init_connection_data(int color, const signed char goal[BOARDMAX],
int target, int cutoff,
struct connection_data *conn, int speculative)
{
int pos;
signed char mark[BOARDMAX];
memset(mark, 0, BOARDMAX);
VALGRIND_MAKE_WRITABLE(conn, sizeof(conn));
clear_connection_data(conn);
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (goal[pos]) {
if (board[pos] == color) {
int origin = find_origin(pos);
if (!mark[origin]) {
add_to_start_queue(origin, FP(0.0), conn);
mark[origin] = 1;
}
}
else if (board[pos] == EMPTY)
add_to_start_queue(pos, FP(1.0), conn);
}
}
conn->target = target;
conn->cutoff_distance = cutoff;
conn->speculative = speculative;
}
static int
find_break_moves(int str, const signed char goal[BOARDMAX], int color_to_move,
int moves[MAX_MOVES], int *total_distance)
{
struct connection_data conn1;
struct connection_data conn2;
int max_dist1 = HUGE_CONNECTION_DISTANCE;
int max_dist2;
int num_moves;
int str2 = NO_MOVE;
int color = board[str];
int lib;
int k;
SGFTree *save_sgf_dumptree = sgf_dumptree;
int save_count_variations = count_variations;
/* We turn off the sgf traces here to avoid cluttering them up with
* tactical reading moves.
*/
sgf_dumptree = NULL;
count_variations = 0;
compute_connection_distances(str, NO_MOVE, FP(2.501), &conn1, 1);
for (k = 0; k < conn1.queue_end; k++)
if (board[conn1.queue[k]] == color) {
int stones[MAX_BOARD * MAX_BOARD];
int num_stones = findstones(conn1.queue[k],
MAX_BOARD * MAX_BOARD, stones);
int i;
for (i = 0; i < num_stones; i++) {
if (goal[stones[i]]) {
str2 = find_origin(stones[i]);
TRACE("%oUsing %1m as secondary target.\n", str2);
mark_string(str2, breakin_shadow, 1);
break;
}
}
if (i < num_stones)
break;
}
/* Add all stones in the goal to the queue. */
init_connection_data(color, goal, str, FP(2.501), &conn2, 1);
for (k = 0; k < conn2.queue_end; k++) {
if (max_dist1 > conn1.distances[conn2.queue[k]])
max_dist1 = conn1.distances[conn2.queue[k]];
}
spread_connection_distances(color, &conn2);
if (findlib(str, 1, &lib) == 1) {
conn1.distances[lib] = 0;
conn1.coming_from[lib] = NO_MOVE;
conn2.distances[lib] = conn2.distances[str];
conn2.coming_from[lib] = conn1.coming_from[str];
}
max_dist2 = conn2.distances[str];
*total_distance = gg_min(max_dist1, max_dist2);
/* Turn the sgf traces back on. */
sgf_dumptree = save_sgf_dumptree;
count_variations = save_count_variations;
if (verbose > 0) {
gprintf("%oVariation %d\n", save_count_variations);
dump_stack();
showboard(0);
print_connection_distances(&conn1);
print_connection_distances(&conn2);
}
{
int cutoff = HUGE_CONNECTION_DISTANCE;
if (breakin_depth - stackp <= 5)
cutoff = FP(1.101) + (breakin_depth - stackp) * FP(0.15);
num_moves = find_connection_moves(str, str2, color_to_move,
&conn1, &conn2, max_dist1, max_dist2,
moves, *total_distance, cutoff);
}
if (color_to_move != board[str]) {
int move;
if (num_moves < MAX_MOVES
&& ON_BOARD(str2)
&& ladder_capture(str2, &move)) {
moves[num_moves++] = move;
}
}
for (k = 0; k < num_moves; k++)
breakin_shadow[moves[k]] = 1;
return num_moves;
}
/* Can (str) connect to goal[] if the other color moves first? */
static int
recursive_break(int str, const signed char goal[BOARDMAX], int *move,
int has_passed,
Hash_data *goal_hash)
{
int color = board[str];
int moves[MAX_MOVES];
int num_moves;
int distance = FP(0.0);
int k;
int xpos;
int savemove = NO_MOVE;
int savecode = 0;
int tried_moves = 0;
int retval;
SETUP_TRACE_INFO("recursive_break", str);
if (move)
*move = NO_MOVE;
nodes_connect++;
global_connection_node_counter++;
if (board[str] == EMPTY) {
SGFTRACE(PASS_MOVE, 0, "one string already captured");
return 0;
}
if (nodes_connect > breakin_node_limit) {
SGFTRACE(PASS_MOVE, 0, "connection node limit reached");
return 0;
}
if (stackp > breakin_depth) {
SGFTRACE(PASS_MOVE, 0, "connection depth limit reached");
return 0;
}
str = find_origin(str);
if (stackp <= depth && !has_passed
&& tt_get(&ttable, BREAK_IN, str, NO_MOVE, depth - stackp, goal_hash,
&retval, NULL, &xpos) == 2) {
/* FIXME: Use move for move ordering if tt_get() returned 1 */
TRACE_CACHED_RESULT(retval, xpos);
SGFTRACE(xpos, retval, "cached");
if (move)
*move = xpos;
return retval;
}
#if 0
if (trivial_connection(str1, str2, &xpos) == WIN) {
SGFTRACE2(xpos, WIN, "trivial connection");
READ_RETURN_HASH(BREAK_IN, str, depth - stackp, goal_hash,
move, xpos, WIN);
}
#endif
num_moves = find_break_moves(str, goal, color, moves, &distance);
for (k = 0; k < num_moves; k++) {
int ko_move;
xpos = moves[k];
if (komaster_trymove(xpos, color, "recursive_break", str,
&ko_move, stackp <= ko_depth && savecode == 0)) {
tried_moves++;
if (!ko_move) {
int acode = recursive_block(str, goal, NULL, has_passed, goal_hash);
popgo();
if (acode == 0) {
SGFTRACE(xpos, WIN, "break effective");
READ_RETURN_HASH(BREAK_IN, str, depth - stackp, goal_hash,
move, xpos, WIN);
}
/* if the move works with ko we save it, then look for something
* better.
*/
UPDATE_SAVED_KO_RESULT(savecode, savemove, acode, xpos);
}
else {
if (recursive_block(str, goal, NULL, has_passed, goal_hash) != WIN) {
savemove = xpos;
savecode = KO_B;
}
popgo();
}
}
}
/* Because of a couple differences between the break-in and the
* connection reading code, we can't afford to be as optimistic
* as in recursive_connect2() here. See nando:32
*/
if (tried_moves == 0 && distance < FP(0.89)) {
SGFTRACE(NO_MOVE, WIN, "no move, probably connected");
READ_RETURN_HASH(BREAK_IN, str, depth - stackp, goal_hash,
move, NO_MOVE, WIN);
}
if (savecode != 0) {
SGFTRACE(savemove, savecode, "saved move");
READ_RETURN_HASH(BREAK_IN, str, depth - stackp, goal_hash,
move, savemove, savecode);
}
SGFTRACE(0, 0, NULL);
READ_RETURN_HASH(BREAK_IN, str, depth - stackp, goal_hash, move, NO_MOVE, 0);
}
/* Can (str) connect to goal[] if the other color moves first? */
static int
recursive_block(int str, const signed char goal[BOARDMAX], int *move,
int has_passed, Hash_data *goal_hash)
{
int color = board[str];
int other = OTHER_COLOR(color);
int moves[MAX_MOVES];
int num_moves;
int distance = FP(0.0);
int k;
int xpos;
int savemove = NO_MOVE;
int savecode = 0;
int tried_moves = 0;
int retval;
SETUP_TRACE_INFO("recursive_block", str);
nodes_connect++;
global_connection_node_counter++;
if (move)
*move = NO_MOVE;
if (board[str] == EMPTY) {
SGFTRACE(PASS_MOVE, WIN, "string already captured");
return WIN;
}
#if 0
if (same_string(str1, str2)) {
SGFTRACE(PASS_MOVE, 0, "already connected");
return 0;
}
#endif
if (nodes_connect > breakin_node_limit) {
SGFTRACE(PASS_MOVE, WIN, "connection node limit reached");
return WIN;
}
if (stackp > breakin_depth) {
SGFTRACE(PASS_MOVE, WIN, "connection depth limit reached");
return WIN;
}
str = find_origin(str);
if (stackp <= depth
&& tt_get(&ttable, BLOCK_OFF, str, NO_MOVE,
depth - stackp, goal_hash, &retval, NULL, &xpos) == 2) {
TRACE_CACHED_RESULT(retval, xpos);
SGFTRACE(xpos, retval, "cached");
if (move)
*move = xpos;
return retval;
}
if (ladder_capture(str, &xpos) == WIN) {
SGFTRACE(xpos, WIN, "string capturable");
READ_RETURN_HASH(BLOCK_OFF, str, depth - stackp, goal_hash,
move, xpos, WIN);
}
num_moves = find_break_moves(str, goal, other, moves, &distance);
for (k = 0; k < num_moves; k++) {
int ko_move;
xpos = moves[k];
if (komaster_trymove(xpos, other, "recursive_block", str,
&ko_move, stackp <= ko_depth && savecode == 0)) {
tried_moves++;
if (!ko_move) {
int dcode = recursive_break(str, goal, NULL, has_passed, goal_hash);
popgo();
if (dcode == 0) {
SGFTRACE(xpos, WIN, "block effective");
READ_RETURN_HASH(BLOCK_OFF, str, depth - stackp, goal_hash,
move, xpos, WIN);
}
/* if the move works with ko we save it, then look for something
* better.
*/
UPDATE_SAVED_KO_RESULT(savecode, savemove, dcode, xpos);
}
else {
if (recursive_break(str, goal, NULL,
has_passed, goal_hash) != WIN) {
savemove = xpos;
savecode = KO_B;
}
popgo();
}
}
}
if (tried_moves == 0
&& distance >= FP(1.0)
&& (has_passed
|| !recursive_break(str, goal, NULL, 1,
goal_hash))) {
SGFTRACE(NO_MOVE, WIN, "no move, probably disconnected");
READ_RETURN_HASH(BLOCK_OFF, str, depth - stackp, goal_hash,
move, NO_MOVE, WIN);
}
if (savecode != 0) {
SGFTRACE(savemove, savecode, "saved move");
READ_RETURN_HASH(BLOCK_OFF, str, depth - stackp, goal_hash,
move, savemove, savecode);
}
SGFTRACE(0, 0, NULL);
READ_RETURN_HASH(BLOCK_OFF, str, depth - stackp, goal_hash,
move, NO_MOVE, 0);
}
/* Externally callable frontend to recursive_break.
* Returns WIN if (str) can connect to the area goal[] (which may or may
* not contain stones), if he gets the first move.
*/
int
break_in(int str, const signed char goal[BOARDMAX], int *move)
{
int dummy_move;
int save_verbose;
int result;
int reading_nodes_when_called = get_reading_node_counter();
double start = 0;
int tactical_nodes;
Hash_data goal_hash = goal_to_hashvalue(goal);
if (move == NULL)
move = &dummy_move;
nodes_connect = 0;
*move = PASS_MOVE;
if (board[str] == EMPTY)
return 0;
str = find_origin(str);
if (search_persistent_breakin_cache(BREAK_IN, str, &goal_hash,
breakin_node_limit, &result, move)) {
if (debug & DEBUG_BREAKIN) {
gprintf("Break-in from %1m to:\n", str);
goaldump(goal);
gprintf("Result cached: %s %1m\n", result_to_string(result), *move);
}
return result;
}
save_verbose = verbose;
if (verbose > 0)
verbose--;
start = gg_cputime();
memcpy(breakin_shadow, goal, sizeof(breakin_shadow));
result = recursive_break(str, goal, move, 0, &goal_hash);
verbose = save_verbose;
tactical_nodes = get_reading_node_counter() - reading_nodes_when_called;
if (debug & DEBUG_BREAKIN) {
gprintf("%obreak_in %1M, result %s %1M (%d, %d nodes, %f seconds)\n",
str, result_to_string(result), *move,
nodes_connect, tactical_nodes, gg_cputime() - start);
goaldump(goal);
dump_stack();
}
if (0) {
gprintf("%obreak_in %1m %d %1m ", str, result, *move);
dump_stack();
goaldump(goal);
}
store_persistent_breakin_cache(BREAK_IN, str, &goal_hash, result, *move,
tactical_nodes, breakin_node_limit,
breakin_shadow);
return result;
}
/* Externably callable frontend to recursive_block_off.
* Returns WIN if (str) cannot connect to the area goal[] (which may or may
* not contain stones), if the other color moves first.
*/
int
block_off(int str, const signed char goal[BOARDMAX], int *move)
{
int dummy_move;
int result;
int save_verbose;
int reading_nodes_when_called = get_reading_node_counter();
double start = 0;
int tactical_nodes;
Hash_data goal_hash = goal_to_hashvalue(goal);
if (move == NULL)
move = &dummy_move;
nodes_connect = 0;
*move = PASS_MOVE;
str = find_origin(str);
if (search_persistent_breakin_cache(BLOCK_OFF, str, &goal_hash,
breakin_node_limit, &result, move)) {
if (debug & DEBUG_BREAKIN) {
gprintf("Blocking off %1m from:\n", str);
goaldump(goal);
gprintf("Result cached: %s %1m\n", result_to_string(result), *move);
}
return result;
}
save_verbose = verbose;
if (verbose > 0)
verbose--;
start = gg_cputime();
memcpy(breakin_shadow, goal, sizeof(breakin_shadow));
result = recursive_block(str, goal, move, 0, &goal_hash);
verbose = save_verbose;
tactical_nodes = get_reading_node_counter() - reading_nodes_when_called;
if (debug & DEBUG_BREAKIN) {
gprintf("%oblock_off %1m, result %s %1m (%d, %d nodes, %f seconds)\n",
str, result_to_string(result), *move,
nodes_connect, tactical_nodes, gg_cputime() - start);
goaldump(goal);
dump_stack();
}
if (0) {
gprintf("%oblock_off %1m %d %1m ", str, result, *move);
goaldump(goal);
dump_stack();
}
store_persistent_breakin_cache(BLOCK_OFF, str, &goal_hash, result, *move,
tactical_nodes, breakin_node_limit,
breakin_shadow);
return result;
}
/* Store a possibly expensive decision for later evaluation. The
* data getting stored should be self-explanatory.
* The job of the helper function is to
* - decide whether the spreading step will be allowed (typically
* depending on a latter)
* - add the relevant positions to the connection queue in case the test
* was successful.
*
* Elements in the heap are kept sorted according to smallest distance.
*/
static void
push_connection_heap_entry(struct connection_data *conn, int distance,
int coming_from, int target,
connection_helper_fn_ptr helper)
{
int k;
int parent;
struct heap_entry *new_entry = &conn->heap_data[conn->heap_data_size];
gg_assert(conn->heap_data_size < 4 * BOARDMAX);
gg_assert(conn->heap_size < BOARDMAX);
/* Create new heap entry. */
new_entry->distance = distance;
new_entry->coming_from = coming_from;
new_entry->target = target;
new_entry->helper = helper;
/* And insert it into the heap. */
conn->heap_data_size++;
for (k = conn->heap_size++; k > 0; k = parent) {
parent = (k - 1) / 2;
if (conn->heap[parent]->distance <= distance)
break;
conn->heap[k] = conn->heap[parent];
}
conn->heap[k] = new_entry;
}
/* Delete the first entry from the heap. */
static void
pop_connection_heap_entry(struct connection_data *conn)
{
int k;
int child;
conn->heap_size--;
for (k = 0; 2 * k + 1 < conn->heap_size; k = child) {
child = 2 * k + 1;
if (conn->heap[child]->distance > conn->heap[child + 1]->distance)
child++;
if (conn->heap[child]->distance >= conn->heap[conn->heap_size]->distance)
break;
conn->heap[k] = conn->heap[child];
}
conn->heap[k] = conn->heap[conn->heap_size];
}
#define ENQUEUE(conn, from, pos, dist, delta, v1, v2) \
do { \
if (dist < conn->distances[pos]) { \
if (conn->distances[pos] == HUGE_CONNECTION_DISTANCE) \
conn->queue[conn->queue_end++] = pos; \
conn->distances[pos] = dist; \
conn->deltas[pos] = delta; \
conn->coming_from[pos] = from; \
conn->vulnerable1[pos] = v1; \
conn->vulnerable2[pos] = v2; \
} \
} while(0)
#define ENQUEUE_STONE(conn, from, pos, dist, delta, v1, v2) \
do { \
int origin = find_origin(pos); \
if (dist < conn->distances[origin]) { \
if (conn->distances[origin] == HUGE_CONNECTION_DISTANCE) \
conn->queue[conn->queue_end++] = origin; \
conn->distances[origin] = dist; \
conn->deltas[origin] = delta; \
conn->coming_from[origin] = from; \
conn->vulnerable1[origin] = v1; \
conn->vulnerable2[origin] = v2; \
if (origin == conn->target && dist < conn->cutoff_distance) \
conn->cutoff_distance = dist - FP(0.0001); \
} \
} while(0)
static void
case_6_7_helper(struct connection_data *conn, int color)
{
struct heap_entry *data = conn->heap[0];
int pos = data->coming_from;
int apos = data->target;
int other = OTHER_COLOR(color);
if (ladder_capturable(apos, other))
ENQUEUE(conn, pos, apos, data->distance, FP(0.6), apos, NO_MOVE);
else {
int this_delta
= FP(0.85) + FP(0.05) * gg_min(approxlib(apos, other, 5, NULL), 5);
ENQUEUE(conn, pos, apos, data->distance + this_delta - FP(0.6), this_delta,
NO_MOVE, NO_MOVE);
}
}
static void
case_9_10_helper(struct connection_data *conn, int color)
{
struct heap_entry *data = conn->heap[0];
int pos = data->coming_from;
int apos = data->target;
UNUSED(color);
if (no_escape_from_ladder(apos))
ENQUEUE_STONE(conn, pos, apos, data->distance, FP(0.3), NO_MOVE, NO_MOVE);
else {
if (conn->speculative) {
ENQUEUE_STONE(conn, pos, apos, data->distance + FP(0.7), FP(1.0),
NO_MOVE, NO_MOVE);
}
else {
ENQUEUE_STONE(conn, pos, apos, data->distance + FP(0.8), FP(1.1),
NO_MOVE, NO_MOVE);
}
}
}
static void
case_16_17_18_helper(struct connection_data *conn, int color)
{
struct heap_entry *data = conn->heap[0];
int pos = data->coming_from;
int bpos = data->target;
int apos = SOUTH(gg_min(pos, bpos));
int gpos = NORTH(gg_max(pos, bpos));
int other = OTHER_COLOR(color);
if (board[apos] == EMPTY
&& does_secure_through_ladder(color, bpos, apos))
ENQUEUE(conn, pos, bpos, data->distance, FP(1.0), apos, NO_MOVE);
else if (board[gpos] == EMPTY
&& does_secure_through_ladder(color, bpos, gpos))
ENQUEUE(conn, pos, bpos, data->distance, FP(1.0), gpos, NO_MOVE);
else if (conn->distances[bpos] > data->distance + FP(0.3)) {
if (board[apos] == EMPTY
&& board[gpos] == other
&& countlib(gpos) <= 3)
ENQUEUE(conn, pos, bpos, data->distance + FP(0.3), FP(1.0),
apos, NO_MOVE);
else if (board[gpos] == EMPTY
&& board[apos] == other
&& countlib(apos) <= 3)
ENQUEUE(conn, pos, bpos, data->distance + FP(0.3), FP(1.0),
gpos, NO_MOVE);
else
ENQUEUE(conn, pos, bpos, data->distance + FP(0.6), FP(0.9),
NO_MOVE, NO_MOVE);
}
}
/* Do the real work of computing connection distances.
* This is a rough approximation of the number of moves required to secure
* a connection. We also compute delta values which are intended to tell how
* big difference a particular move locally has on the connection
* distance. However, remember that this is only a heuristic with the
* sole purpose of helping to find relevant moves for connection
* problems.
*
* The algorithm is to propagate connection values outwards using a
* breadth-first searching strategy, implemented through an implicitly
* sorted queue. The propagation to new vertices depends on
* geometrical features with significance for connections. E.g. a
* bamboo joint is recognized and the distance added when passing
* through it is small. New points are added to the queue through the
* ENQUEUE macro above. This checks whether the point has already been
* entered on the queue and updates the distance and delta values if
* the previous ones were worse. When a stone is entered, all stones
* of the string are added to the queue simultaneously.
*
* (target) is the other string when called from find_connection_moves().
* (It can be set to NO_MOVE otherwise.)
*
* The propagation is inhibited when the distance becomes too large,
* or larger than the shortest path found to the target so far.
*
*
* The purpose of the fields called vulnerable is to keep track of
* points where the attacker can threaten an individual
* connection. For example the diagonal formation
*
* .O
* O.
*
* is considered a small distance link but both the empty vertices are
* marked as vulnerable. Thus if we are computing connection distance
* from the lower left O in this diagram,
*
* XXX XXX
* .O. .O.
* O.O OaO
* .X. .X.
*
* the distance to the middle O is small but the second diagonal link
* to the lower right O stone is not given a small distance since a
* had already been marked as vulnerable.
*
* It should also be pointed out that this reasoning is not relevant
* in this position where X has no cutting potential,
*
* XXX XXX
* .O. .O.
* O.O OaO
* ... ...
*
* That is because there is a pattern directly recognizing the safe
* link between the two lower stones, without taking the longer road
* over the two diagonal links.
*
* (color) is the color for which we are computing connection distances,
* (target) the position we want to reach (can be set to NO_MOVE),
* (*conn) has to have the queue initialized with the positions
* from which we want to know the distances,
* (cutoff_distance) is the highest distance before we give up,
* (speculative) controls some special cases in the propagation rules
* below.
*
* As an optimization, new points are either added directly via the ENQUEUE
* macro if the necessary test is an immediate (usually purely geometric)
* check, or if the decision is more expensive (usually depending on a
* ladder), it gets postponed and stored via push_connection_heap_entry()
* for later evaluation.
*/
void
spread_connection_distances(int color, struct connection_data *conn)
{
int other = OTHER_COLOR(color);
int stones[MAX_BOARD * MAX_BOARD];
int num_stones = 0;
int stone = 0;
/* Loop until we reach the end of the queue. */
while (conn->queue_start < conn->queue_end || conn->heap_size > 0) {
int k;
int pos;
int distance;
/* Delete heap entries for positions that have already been reached
* with smaller distance.
*/
while (conn->heap_size > 0
&& conn->heap[0]->distance >= conn->distances[conn->heap[0]->target])
pop_connection_heap_entry(conn);
if (stone == num_stones) {
int best_index = -1;
int smallest_dist = HUGE_CONNECTION_DISTANCE;
if (conn->queue_start == conn->queue_end) {
if (conn->heap_size > 0) {
conn->heap[0]->helper(conn, color);
pop_connection_heap_entry(conn);
}
continue;
}
gg_assert(conn->queue_end <= MAX_BOARD * MAX_BOARD);
/* Find the smallest distance among the queued points. */
for (k = conn->queue_start; k < conn->queue_end; k++) {
if (conn->distances[conn->queue[k]] < smallest_dist) {
smallest_dist = conn->distances[conn->queue[k]];
best_index = k;
}
}
/* Exchange the best point with the first element in the queue. */
if (best_index != conn->queue_start) {
int temp = conn->queue[conn->queue_start];
conn->queue[conn->queue_start] = conn->queue[best_index];
conn->queue[best_index] = temp;
}
/* If the first element in heap has smaller distance than the
* smallest we have found so far, call the relevant helper function
* now, and delete the heap entry.
*/
if (conn->heap_size > 0 && conn->heap[0]->distance < smallest_dist) {
conn->heap[0]->helper(conn, color);
pop_connection_heap_entry(conn);
continue;
}
/* Now we are ready to pick the first element in the queue and
* process it.
*/
pos = conn->queue[conn->queue_start++];
if (board[pos] != EMPTY) {
num_stones = findstones(pos, MAX_BOARD * MAX_BOARD, stones);
pos = stones[0];
stone = 1;
}
}
else {
pos = stones[stone++];
conn->distances[pos] = conn->distances[stones[0]];
conn->deltas[pos] = conn->deltas[stones[0]];
conn->coming_from[pos] = conn->coming_from[stones[0]];
conn->vulnerable1[pos] = conn->vulnerable1[stones[0]];
conn->vulnerable2[pos] = conn->vulnerable2[stones[0]];
}
/* No further propagation if the distance is too large. */
distance = conn->distances[pos];
if (distance > conn->cutoff_distance)
break;
/* Search for new vertices to propagate to. */
if (board[pos] == color) {
for (k = 0; k < 4; k++) {
/* List of relative coordinates. (pos) is marked by *.
*
* jef.
* igb.
* kh*ac
* ....
*
*/
int right = delta[k];
int up = delta[(k+1)%4];
/* FIXME: Compactify this list. */
int apos = pos + right;
int bpos = pos + right + up;
int cpos = pos + 2 * right;
int epos = pos + 2*up;
int fpos = pos + right + 2*up;
int gpos = pos + up;
int hpos = pos - right;
int ipos = pos - right + up;
int jpos = pos - right + 2 * up;
int kpos = pos - 2 * right;
/* Case 1. "a" is empty and would be suicide for the opponent. */
if (board[apos] == EMPTY && is_suicide(apos, other))
ENQUEUE(conn, pos, apos, distance, FP(0.0), apos, NO_MOVE);
/* Case 2. "a" is empty and would be self atari for the opponent. */
if (board[apos] == EMPTY
&& conn->distances[apos] > distance + FP(0.1)
&& is_self_atari(apos, other)) {
int lib;
int vulnerable1 = NO_MOVE;
int vulnerable2 = NO_MOVE;
if (approxlib(apos, other, 1, &lib) >= 1) {
if (approxlib(lib, other, 2, NULL) > 2)
vulnerable1 = lib;
if (countlib(pos) == 2) {
int i;
for (i = 0; i < 4; i++) {
if (board[lib + delta[i]] == EMPTY
&& lib + delta[i] != apos
&& trymove(lib + delta[i], other,
"compute_connection_distances", pos)) {
if (ladder_capture(pos, NULL)) {
vulnerable2 = lib + delta[i];
popgo();
break;
}
popgo();
}
}
}
}
if (!common_vulnerabilities(conn->vulnerable1[pos],
conn->vulnerable2[pos],
vulnerable1, vulnerable2, color)) {
ENQUEUE(conn, pos, apos, distance + FP(0.1), FP(0.1),
vulnerable1, vulnerable2);
}
}
/* Case 3. Bamboo joint of "*" + "a" to "e" + "f" through "b" and "g".
* Notice that the order of these tests is significant. We must
* check bpos before fpos and epos to avoid accessing memory
* outside the board array. (Notice that fpos is two steps away
* from pos, which we know is on the board.)
*/
if (board[apos] == color && board[bpos] == EMPTY
&& board[fpos] == color && board[epos] == color
&& board[gpos] == EMPTY) {
ENQUEUE(conn, pos, bpos, distance + FP(0.1), FP(0.1),
NO_MOVE, NO_MOVE);
ENQUEUE(conn, pos, gpos, distance + FP(0.1), FP(0.1),
NO_MOVE, NO_MOVE);
}
/* Case 4. Diagonal connection to another stone "b" through
* empty vertices "a" and "g".
*/
if (board[bpos] == color
&& board[apos] == EMPTY
&& board[gpos] == EMPTY
&& !common_vulnerabilities(conn->vulnerable1[pos],
conn->vulnerable2[pos],
apos, gpos, color)
&& conn->distances[bpos] > distance + FP(0.1)) {
#if 0
ENQUEUE(conn, pos, apos, distance + FP(0.2), FP(0.2),
NO_MOVE, NO_MOVE);
ENQUEUE(conn, pos, gpos, distance + FP(0.2), FP(0.2),
NO_MOVE, NO_MOVE);
#endif
ENQUEUE_STONE(conn, pos, bpos, distance + FP(0.1), FP(0.1),
apos, gpos);
}
/* Case 5. Almost bamboo joint.
*
*/
if (board[gpos] == EMPTY
&& board[epos] == color
&& conn->distances[epos] > distance + FP(0.2)
&& approxlib(gpos, other, 3, NULL) <= 2) {
if (board[bpos] == EMPTY
&& approxlib(bpos, color, 3, NULL) >= 3
&& (board[apos] == color
|| (board[apos] == EMPTY
&& countlib(pos) > 2
&& !common_vulnerabilities(conn->vulnerable1[pos],
conn->vulnerable2[pos],
apos, gpos, color)
&& approxlib(apos, other, 3, NULL) <= 2))
&& (board[fpos] == color
|| (board[fpos] == EMPTY
&& countlib(epos) > 2
&& !common_vulnerabilities(conn->vulnerable1[pos],
conn->vulnerable2[pos],
fpos, gpos, color)
&& approxlib(fpos, other, 3, NULL) <= 2))) {
if (board[apos] == EMPTY && board[fpos] == EMPTY) {
ENQUEUE_STONE(conn, pos, epos, distance + FP(0.2), FP(0.2),
apos, fpos);
}
else if (board[apos] == EMPTY && board[fpos] != EMPTY) {
ENQUEUE_STONE(conn, pos, epos, distance + FP(0.2), FP(0.2),
apos, NO_MOVE);
}
else if (board[apos] != EMPTY && board[fpos] == EMPTY) {
ENQUEUE_STONE(conn, pos, epos, distance + FP(0.2), FP(0.2),
fpos, NO_MOVE);
}
else if (board[apos] != EMPTY && board[fpos] != EMPTY) {
ENQUEUE_STONE(conn, pos, epos, distance + FP(0.2), FP(0.2),
NO_MOVE, NO_MOVE);
}
}
if (board[ipos] == EMPTY
&& approxlib(ipos, color, 3, NULL) >= 3
&& (board[hpos] == color
|| (board[hpos] == EMPTY
&& countlib(pos) > 2
&& !common_vulnerabilities(conn->vulnerable1[pos],
conn->vulnerable2[pos],
hpos, gpos, color)
&& approxlib(hpos, other, 3, NULL) <= 2))
&& (board[jpos] == color
|| (board[jpos] == EMPTY
&& countlib(epos) > 2
&& !common_vulnerabilities(conn->vulnerable1[pos],
conn->vulnerable2[pos],
jpos, gpos, color)
&& approxlib(jpos, other, 3, NULL) <= 2))) {
if (board[hpos] == EMPTY && board[jpos] == EMPTY) {
ENQUEUE_STONE(conn, pos, epos, distance + FP(0.2), FP(0.2),
hpos, jpos);
}
else if (board[hpos] == EMPTY && board[jpos] != EMPTY) {
ENQUEUE_STONE(conn, pos, epos, distance + FP(0.2), FP(0.2),
hpos, NO_MOVE);
}
else if (board[hpos] != EMPTY && board[jpos] == EMPTY) {
ENQUEUE_STONE(conn, pos, epos, distance + FP(0.2), FP(0.2),
jpos, NO_MOVE);
}
else if (board[hpos] != EMPTY && board[jpos] != EMPTY) {
ENQUEUE_STONE(conn, pos, epos, distance + FP(0.2), FP(0.2),
NO_MOVE, NO_MOVE);
}
}
}
/* Case 6. "a" is empty and an opponent move can be captured
* in a ladder.
*
* Case 7. "a" is empty.
*/
if (board[apos] == EMPTY && conn->distances[apos] > distance + FP(0.6)) {
push_connection_heap_entry(conn, distance + FP(0.6), pos, apos,
case_6_7_helper);
}
/* Case 8. Adjacent opponent stone at "a" which can't avoid atari.
*/
if (board[apos] == other
&& conn->distances[apos] > distance + FP(0.1)
&& no_escape_from_atari(apos)) {
ENQUEUE_STONE(conn, pos, apos, distance + FP(0.1), FP(0.1),
NO_MOVE, NO_MOVE);
}
/* Case 9. Adjacent opponent stone at "a" which can't avoid
* ladder capture.
*
* Case 10. "a" is occupied by opponent.
*/
if (board[apos] == other && conn->distances[apos] > distance + FP(0.3)) {
push_connection_heap_entry(conn, distance + FP(0.3), pos, apos,
case_9_10_helper);
}
/* Case 11. Diagonal connection to empty vertex "b" through
* empty vertex "a" or "g", which makes "a" or "g" self-atari
* for opponent.
*/
if (board[bpos] == EMPTY
&& board[apos] == EMPTY
&& conn->distances[bpos] > distance + FP(1.1)
&& does_secure(color, bpos, apos)) {
ENQUEUE(conn, pos, bpos, distance + FP(1.1), FP(1.0), apos, NO_MOVE);
}
if (board[bpos] == EMPTY
&& board[gpos] == EMPTY
&& conn->distances[bpos] > distance + FP(1.1)
&& does_secure(color, bpos, gpos)) {
ENQUEUE(conn, pos, bpos, distance + FP(1.1), FP(1.0), gpos, NO_MOVE);
}
/* Case 12. One-space jump to empty vertex "e" through empty
* vertex "g", which makes "g" self-atari for opponent.
*/
if (board[gpos] == EMPTY
&& board[epos] == EMPTY
&& conn->distances[epos] > distance + FP(1.1)
&& does_secure(color, epos, gpos)) {
ENQUEUE(conn, pos, epos, distance + FP(1.1), FP(1.0), gpos, NO_MOVE);
}
/* Case 13. One-space jump to empty vertex "e" through empty
* vertex "g", making a bamboo joint.
*/
if (board[gpos] == EMPTY
&& board[epos] == EMPTY
&& conn->distances[epos] > distance + FP(1.1)
&& ((board[apos] == color && board[fpos] == color
&& board[bpos] == EMPTY)
|| (board[hpos] == color && board[jpos] == color
&& board[ipos] == EMPTY))) {
ENQUEUE(conn, pos, epos, distance + FP(1.1), FP(1.0), gpos, NO_MOVE);
}
/* Case 14. Diagonal connection to empty vertex "b" through
* empty vertices "a" and "g".
*/
if (board[bpos] == EMPTY
&& board[apos] == EMPTY && board[gpos] == EMPTY
&& conn->distances[bpos] > distance + FP(1.3)) {
ENQUEUE(conn, pos, bpos, distance + FP(1.3), FP(1.0), apos, gpos);
}
/* Case 15. Keima to "f" or "j" on edge. and one space jump on
* first or second line.
*/
if (board[apos] == EMPTY
&& board[bpos] == EMPTY
&& board[gpos] == EMPTY
&& board[epos] == EMPTY
&& board[fpos] == EMPTY
&& (conn->distances[fpos] > distance + FP(1.3)
|| conn->distances[epos] > distance + FP(1.3))
&& countlib(pos) >= 3
&& (!ON_BOARD(cpos) || !ON_BOARD(hpos))) {
ENQUEUE(conn, pos, fpos, distance + FP(1.3), FP(1.0),
NO_MOVE, NO_MOVE);
ENQUEUE(conn, pos, epos, distance + FP(1.3), FP(1.0),
NO_MOVE, NO_MOVE);
}
if (board[hpos] == EMPTY
&& board[ipos] == EMPTY
&& board[gpos] == EMPTY
&& board[epos] == EMPTY
&& board[jpos] == EMPTY
&& (conn->distances[jpos] > distance + FP(1.3)
|| conn->distances[epos] > distance + FP(1.3))
&& countlib(pos) >= 3
&& (!ON_BOARD(apos) || !ON_BOARD(kpos))) {
ENQUEUE(conn, pos, jpos, distance + FP(1.3), FP(1.0),
NO_MOVE, NO_MOVE);
ENQUEUE(conn, pos, epos, distance + FP(1.3), FP(1.0),
NO_MOVE, NO_MOVE);
}
/* Case 16. Diagonal connection to empty vertex "b" through
* empty vertex "a" or "g", which allows opponent move at "a"
* or "g" to be captured in a ladder.
*
* Case 17. Diagonal connection to empty vertex "b" through
* one empty and one opponent vertex "a" and "g", where
* the opponent stone is short of liberties.
*
* Case 18. Diagonal connection to empty vertex "b" through
* empty vertex "a" or "g", with no particular properties.
*/
if (board[bpos] == EMPTY
&& (board[apos] == EMPTY || board[gpos] == EMPTY)
&& conn->distances[bpos] > distance + FP(1.2)) {
push_connection_heap_entry(conn, distance + FP(1.2), pos, bpos,
case_16_17_18_helper);
}
/* Case 19. Clamp at "e" of single stone at "g". */
if (board[gpos] == other
&& board[epos] == EMPTY
&& conn->distances[epos] > distance + FP(2.0)
&& countstones(gpos) == 1) {
ENQUEUE(conn, pos, epos, distance + FP(2.0), FP(1.0),
NO_MOVE, NO_MOVE);
}
/* Case 20. Diagonal connection to empty vertex "b" through
* opponent stones "a" or "g" with few liberties.
*/
if (board[bpos] == EMPTY
&& board[apos] == other
&& board[gpos] == other
&& conn->distances[bpos] > distance + FP(2.0)
&& (countlib(apos) + countlib(gpos) <= 6)) {
ENQUEUE(conn, pos, bpos, distance + FP(2.0), FP(1.0),
NO_MOVE, NO_MOVE);
}
/* Case 21. Diagonal connection to own stone "b" through
* opponent stones "a" or "g" with few liberties.
*/
if (board[bpos] == color
&& board[apos] == other
&& board[gpos] == other
&& conn->distances[bpos] > distance + FP(2.0)
&& (countlib(apos) + countlib(gpos) <= 5)) {
ENQUEUE_STONE(conn, pos, bpos, distance + FP(2.0), FP(1.0),
NO_MOVE, NO_MOVE);
}
}
}
else if (board[pos] == EMPTY
|| (board[pos] == other
&& countlib(pos) <= 2
&& no_escape_from_ladder(pos))) {
for (k = 0; k < 4; k++) {
/* List of relative coordinates. (pos) is marked by *.
*
* jef.
* igb.
* kh*ac
* .d.
*
*/
int right = delta[k];
int up = delta[(k+1)%4];
/* FIXME: Compactify this list. */
int apos = pos + right;
int bpos = pos + right + up;
#if 0
int cpos = pos + 2 * right;
int epos = pos + 2*up;
int fpos = pos + right + 2*up;
#endif
int gpos = pos + up;
#if 0
int hpos = pos - right;
int ipos = pos - right + up;
int jpos = pos - right + 2 * up;
int kpos = pos - 2 * right;
#endif
if (board[apos] == color) {
ENQUEUE_STONE(conn, pos, apos, distance, FP(0.0),
conn->vulnerable1[pos], conn->vulnerable2[pos]);
}
else if (board[apos] == EMPTY) {
int this_delta
= FP(0.8) + FP(0.05) * gg_min(approxlib(apos, other, 6, NULL), 6);
ENQUEUE(conn, pos, apos, distance + this_delta, this_delta,
NO_MOVE, NO_MOVE);
}
else if (board[apos] == other) {
ENQUEUE_STONE(conn, pos, apos, distance + FP(1.0), FP(1.0),
NO_MOVE, NO_MOVE);
}
/* Case 1. Diagonal connection to empty vertex "b" through
* empty vertices "a" and "g".
*/
if (board[bpos] == EMPTY
&& board[apos] == EMPTY
&& board[gpos] == EMPTY
&& conn->distances[bpos] > distance + FP(1.5)) {
ENQUEUE(conn, pos, bpos, distance + FP(1.5), FP(1.0),
NO_MOVE, NO_MOVE);
}
/* Case 2. Diagonal connection to friendly stone at "b" through
* empty vertices "a" and "g".
*/
if (board[bpos] == color
&& board[apos] == EMPTY
&& board[gpos] == EMPTY
&& conn->distances[bpos] > distance + FP(1.3)) {
ENQUEUE_STONE(conn, pos, bpos, distance + FP(1.3), FP(1.0),
NO_MOVE, NO_MOVE);
}
}
}
}
}
void
sort_connection_queue_tail(struct connection_data *conn)
{
int k;
for (k = conn->queue_start; k < conn->queue_end - 1; k++) {
int i;
int best_index = k;
int smallest_dist = conn->distances[conn->queue[k]];
for (i = k + 1; i < conn->queue_end; i++) {
if (conn->distances[conn->queue[i]] < smallest_dist) {
best_index = i;
smallest_dist = conn->distances[conn->queue[i]];
}
}
if (best_index != k) {
int temp = conn->queue[k];
conn->queue[k] = conn->queue[best_index];
conn->queue[best_index] = temp;
}
}
}
/* Replace string origins in a connection queue with complete sets of
* corresponding string stones.
*/
void
expand_connection_queue(struct connection_data *conn)
{
int k;
int full_queue[BOARDMAX];
int full_queue_position = 0;
int full_queue_start = 0;
for (k = 0; k < conn->queue_end; k++) {
if (k == conn->queue_start)
full_queue_start = full_queue_position;
if (board[conn->queue[k]] == EMPTY)
full_queue[full_queue_position++] = conn->queue[k];
else {
full_queue_position += findstones(conn->queue[k],
MAX_BOARD * MAX_BOARD,
full_queue + full_queue_position);
}
}
conn->queue_start = full_queue_start;
conn->queue_end = full_queue_position;
memcpy(conn->queue, full_queue, conn->queue_end * sizeof(int));
}
/* Initialize distance and delta values so that the former are
* everywhere huge and the latter everywhere zero.
*/
static void
clear_connection_data(struct connection_data *conn)
{
int pos;
conn->queue_start = 0;
conn->queue_end = 0;
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
conn->distances[pos] = HUGE_CONNECTION_DISTANCE;
conn->deltas[pos] = FP(0.0);
conn->coming_from[pos] = NO_MOVE;
conn->vulnerable1[pos] = NO_MOVE;
conn->vulnerable2[pos] = NO_MOVE;
}
conn->heap_data_size = 0;
conn->heap_size = 0;
}
/* Compute the connection distances from string (str) to nearby
* vertices, until we reach target or the distance gets too high.
*/
void
compute_connection_distances(int str, int target, int cutoff,
struct connection_data *conn,
int speculative)
{
int color = board[str];
clear_connection_data(conn);
/* Add the origin of the initial string to the queue. */
add_to_start_queue(find_origin(str), FP(0.0), conn);
conn->target = target;
conn->cutoff_distance = cutoff;
conn->speculative = speculative;
spread_connection_distances(color, conn);
}
/* Print the connection distances in a struct connection_data. */
void
print_connection_distances(struct connection_data *conn)
{
int i, j;
int ch;
int pos;
fprintf(stderr, " ");
for (j = 0, ch = 'A'; j < board_size; j++, ch++) {
if (ch == 'I')
ch++;
fprintf(stderr, " %c ", ch);
}
fprintf(stderr, "\n");
for (i = 0; i < board_size; i++) {
fprintf(stderr, "%2d ", board_size - i);
for (j = 0; j < board_size; j++) {
pos = POS(i, j);
if (conn->distances[pos] == HUGE_CONNECTION_DISTANCE) {
if (board[pos] == WHITE)
fprintf(stderr, " O ");
if (board[pos] == BLACK)
fprintf(stderr, " X ");
if (board[pos] == EMPTY)
fprintf(stderr, " . ");
}
else {
fprintf(stderr, "%3.1f ", FIXED_TO_FLOAT(conn->distances[pos]));
}
}
fprintf(stderr, "\n");
}
fprintf(stderr, "\n");
fprintf(stderr, "Vulnerable:\n");
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
if (conn->distances[pos] < HUGE_CONNECTION_DISTANCE
&& (conn->vulnerable1[pos] != NO_MOVE
|| conn->vulnerable2[pos] != NO_MOVE)) {
gprintf(" %1m:", pos);
if (conn->vulnerable1[pos] != NO_MOVE)
gprintf(" %1m", conn->vulnerable1[pos]);
if (conn->vulnerable2[pos] != NO_MOVE)
gprintf(" %1m", conn->vulnerable2[pos]);
gprintf("\n", pos);
}
}
/* Test whether there is a trivial connection between str1 and str2
* and if so return the connecting move in *move. By trivial
* connection we mean that they either have a common liberty or a
* common neighbor which can be tactically attacked.
*/
static int
trivial_connection(int str1, int str2, int *move)
{
SGFTree *save_sgf_dumptree = sgf_dumptree;
int save_count_variations = count_variations;
int adj, adjs[MAXCHAIN];
int r;
int result = 0;
if (have_common_lib(str1, str2, move))
return WIN;
adj = chainlinks(str1, adjs);
/* We turn off the sgf traces here to avoid cluttering them up with
* tactical reading moves.
*/
sgf_dumptree = NULL;
count_variations = 0;
for (r = 0; r < adj; r++)
if (adjacent_strings(adjs[r], str2) && attack(adjs[r], move) == WIN) {
result = WIN;
break;
}
/* Turn the sgf traces back on. */
sgf_dumptree = save_sgf_dumptree;
count_variations = save_count_variations;
return result;
}
/* True if a move by color makes an opponent move at pos a self atari
* or possible to capture in a ladder.
*/
static int
does_secure_through_ladder(int color, int move, int pos)
{
int result = 0;
if (trymove(move, color, NULL, NO_MOVE)) {
if (ladder_capturable(pos, OTHER_COLOR(color)))
result = 1;
popgo();
}
return result;
}
/* Test whether the string str can be immediately taken off the board
* or captured in a ladder. If so the capturing move is returned in
* *move.
*/
static int
ladder_capture(int str, int *move)
{
int result;
SGFTree *save_sgf_dumptree = sgf_dumptree;
int save_count_variations = count_variations;
int liberties = countlib(str);
/* We turn off the sgf traces here to avoid cluttering them up with
* tactical reading moves.
*/
sgf_dumptree = NULL;
count_variations = 0;
if (liberties == 1)
result = attack(str, move);
else if (liberties == 2)
result = simple_ladder(str, move);
else
result = 0;
/* Turn the sgf traces back on. */
sgf_dumptree = save_sgf_dumptree;
count_variations = save_count_variations;
return result;
}
/* Test whether a move at pos by color can be captured in a ladder. */
static int
ladder_capturable(int pos, int color)
{
int result = 0;
if (trymove(pos, color, NULL, NO_MOVE)) {
int liberties = countlib(pos);
if (liberties == 1 && attack(pos, NULL) == WIN)
result = 1;
else if (liberties == 2 && simple_ladder(pos, NULL) == WIN)
result = 1;
popgo();
}
else
result = 1;
return result;
}
/* Test whether the string str with one liberty is stuck with at most
* one liberty. This function trivially returns false if the string
* has more than one liberty to start with.
*/
static int
no_escape_from_atari(int str)
{
int lib;
int adj[MAXCHAIN];
if (findlib(str, 1, &lib) > 1)
return 0;
if (accuratelib(lib, board[str], 2, NULL) > 1)
return 0;
/* FIXME: Should exclude snapback. */
if (chainlinks2(str, adj, 1) > 0)
return 0;
return 1;
}
/* Test whether the string str with one liberty is captured in a
* ladder. This function trivially returns false if the string has
* more than one liberty to start with, except for one special case.
* FIXME: Needs a simple_ladder_defense().
*/
static int
no_escape_from_ladder(int str)
{
int result = 0;
SGFTree *save_sgf_dumptree = sgf_dumptree;
int save_count_variations = count_variations;
int adj[MAXCHAIN];
int libs[2];
/* We turn off the sgf traces here to avoid cluttering them up with
* tactical reading moves.
*/
sgf_dumptree = NULL;
count_variations = 0;
if (countlib(str) == 1 && find_defense(str, NULL) == 0)
result = 1;
if (countlib(str) == 2
&& chainlinks2(str, adj, 1) == 0
&& findlib(str, 2, libs) == 2
&& approxlib(libs[0], board[str], 2, NULL) == 1
&& approxlib(libs[1], board[str], 2, NULL) == 1
&& ladder_capture(str, NULL)
&& !find_defense(str, NULL))
result = 1;
/* Turn the sgf traces back on. */
sgf_dumptree = save_sgf_dumptree;
count_variations = save_count_variations;
return result;
}
/* We usually don't want to spend time with moves which are
* self-atari, unless the stone is involved in a ko.
*/
static int
check_self_atari(int pos, int color_to_move)
{
#if 1
int lib;
#endif
if (!is_self_atari(pos, color_to_move))
return 1;
if (is_ko(pos, color_to_move, NULL))
return 1;
#if 1
/* FIXME: At some time I added this exceptional case but I can no
* longer see how it would be useful. It might still be, however, so
* I leave the code in for a while. /gf
*
* Code reactivated, see nando:31. /nn
*
* Added requirement that no additional stones are sacrificed in the
* self atari. /gf
*
* FIXME: Add a function in board.c to check how big the string
* becomes when playing a move and use for the isolated stone
* test below.
*/
if (approxlib(pos, color_to_move, 1, &lib) >= 1
&& approxlib(lib, OTHER_COLOR(color_to_move), 3, NULL) <= 2
&& ladder_capturable(lib, OTHER_COLOR(color_to_move))) {
int k;
for (k = 0; k < 4; k++) {
if (board[pos + delta[k]] == color_to_move)
break;
}
if (k == 4)
return 1;
}
#endif
return 0;
}
/* Check for overlap between (a1, a2) and (b1, b2). */
static int
common_vulnerabilities(int a1, int a2, int b1, int b2, int color)
{
return (common_vulnerability(a1, b1, color)
|| common_vulnerability(a1, b2, color)
|| common_vulnerability(a2, b1, color)
|| common_vulnerability(a2, b2, color));
}
/* Check if apos and bpos are the same or if they are both liberties
* of a string of the given color with at most three liberties.
*/
static int
common_vulnerability(int apos, int bpos, int color)
{
int k;
if (apos == NO_MOVE || bpos == NO_MOVE)
return 0;
if (apos == bpos)
return 1;
for (k = 0; k < 4; k++)
if (board[apos + delta[k]] == color
&& countlib(apos + delta[k]) <= 3
&& liberty_of_string(bpos, apos + delta[k]))
return 1;
return 0;
}
/*
* Local Variables:
* tab-width: 8
* c-basic-offset: 2
* End:
*/