4351 lines
118 KiB
C
4351 lines
118 KiB
C
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
|
* This is GNU Go, a Go program. Contact gnugo@gnu.org, or see *
|
|
* http://www.gnu.org/software/gnugo/ for more information. *
|
|
* *
|
|
* Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, *
|
|
* 2008 and 2009 by the Free Software Foundation. *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU General Public License as *
|
|
* published by the Free Software Foundation - version 3 or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU General Public License in file COPYING for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public *
|
|
* License along with this program; if not, write to the Free *
|
|
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, *
|
|
* Boston, MA 02111, USA. *
|
|
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
#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:
|
|
*/
|