890 lines
20 KiB
C
890 lines
20 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 <stdio.h>
|
||
|
#include "liberty.h"
|
||
|
#include "patterns.h"
|
||
|
|
||
|
|
||
|
#define TRYMOVE(pos, color) trymove(pos, color, "helper", NO_MOVE)
|
||
|
#define OFFSET_BY(x, y) AFFINE_TRANSFORM(OFFSET(x, y), trans, move)
|
||
|
#define ARGS struct pattern *pattern, int trans, int move, int color
|
||
|
|
||
|
|
||
|
/* This file contains helper functions which assist the pattern matcher.
|
||
|
* They are invoked with (move) = the position on the board marked with '*'.
|
||
|
* They are invoked with color = WHITE or BLACK: any pieces on the
|
||
|
* board marked with 'O' in the pattern will always contain color,
|
||
|
* and 'X's contain OTHER_COLOR(color)
|
||
|
*
|
||
|
* The helper must return 0 if the pattern is rejected and 1 otherwise.
|
||
|
*/
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
/* Jump out into nothingness. To avoid jumping into our own territory,
|
||
|
* we use the "area" measure. Also we never ever jump into our own
|
||
|
* established eyespace.
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
jump_out_helper(ARGS)
|
||
|
{
|
||
|
int own_eyespace;
|
||
|
|
||
|
UNUSED(trans); UNUSED(pattern);
|
||
|
|
||
|
own_eyespace = (white_eye[move].color == color);
|
||
|
|
||
|
if (whose_area(OPPOSITE_INFLUENCE(color), move) != color && !own_eyespace)
|
||
|
return 1;
|
||
|
else
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Make a long jump into nothingness. Since these jumps are not
|
||
|
* securely connected we don't use them to jump into the opponent's
|
||
|
* zone of control.
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
jump_out_far_helper(ARGS)
|
||
|
{
|
||
|
if (whose_area(OPPOSITE_INFLUENCE(color), move) != OTHER_COLOR(color))
|
||
|
return jump_out_helper(pattern, trans, move, color);
|
||
|
else
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Active until the opponent has played his first stone.
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
high_handicap_helper(ARGS)
|
||
|
{
|
||
|
UNUSED(trans); UNUSED(pattern); UNUSED(move);
|
||
|
|
||
|
return !doing_scoring && stones_on_board(OTHER_COLOR(color)) == 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Active when the opponent is thought to be everywhere dead. This
|
||
|
* typically happens early in high handicap games on small boards.
|
||
|
* This helper is used by patterns intended to reinforce possible
|
||
|
* weaknesses in the position.
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
reinforce_helper(ARGS)
|
||
|
{
|
||
|
UNUSED(trans); UNUSED(pattern);
|
||
|
|
||
|
return (!doing_scoring
|
||
|
&& !lively_dragon_exists(OTHER_COLOR(color))
|
||
|
&& safe_move(move, color));
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
* XXO XXc decrease eye space in sente (unless it kills)
|
||
|
* .*X e*a
|
||
|
* --- ---
|
||
|
*
|
||
|
* or
|
||
|
*
|
||
|
* XXO XXc decrease eye space in sente (unless it kills)
|
||
|
* .*X e*a
|
||
|
* XXO XXd
|
||
|
*
|
||
|
* or
|
||
|
*
|
||
|
* |XXO |XXc decrease eye space in sente (unless it kills)
|
||
|
* |.*X |e*a
|
||
|
* |XXO |XXd
|
||
|
*
|
||
|
* or
|
||
|
*
|
||
|
* |XXO |XXc decrease eye space in sente (unless it kills)
|
||
|
* |.*X |e*a
|
||
|
* +--- +---
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
throw_in_atari_helper(ARGS)
|
||
|
{
|
||
|
int apos, bpos, cpos, dpos;
|
||
|
int success = 0;
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int libs[2];
|
||
|
UNUSED(pattern);
|
||
|
|
||
|
apos = OFFSET_BY(0, 1);
|
||
|
cpos = OFFSET_BY(-1, 1);
|
||
|
dpos = OFFSET_BY(1, 1);
|
||
|
|
||
|
/* Find second liberty of the stone a. */
|
||
|
findlib(apos, 2, libs);
|
||
|
if (libs[0] != move)
|
||
|
bpos = libs[0];
|
||
|
else
|
||
|
bpos = libs[1];
|
||
|
|
||
|
if (TRYMOVE(move, color)) {
|
||
|
if (!attack(cpos, NULL) && !(ON_BOARD(dpos) && attack(dpos, NULL))) {
|
||
|
if (TRYMOVE(bpos, other)) {
|
||
|
if (attack(apos, NULL))
|
||
|
success = 1;
|
||
|
popgo();
|
||
|
}
|
||
|
else {
|
||
|
success = 1; /* X move at (bpos) would have been suicide */
|
||
|
}
|
||
|
}
|
||
|
popgo();
|
||
|
}
|
||
|
|
||
|
/* The followup is to capture the "a" string. Estimate the value to
|
||
|
* twice the size.
|
||
|
*/
|
||
|
add_followup_value(move, 2 * worm[apos].effective_size);
|
||
|
TRACE("...followup value %f\n", 2 * worm[apos].effective_size);
|
||
|
|
||
|
return success;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* This is intended for use in autohelpers. */
|
||
|
|
||
|
/* Check whether the string at (str) can attack any surrounding
|
||
|
* string. If so, return false as the move to create a seki (probably)
|
||
|
* wouldn't work.
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
seki_helper(int str)
|
||
|
{
|
||
|
int r;
|
||
|
int adj;
|
||
|
int adjs[MAXCHAIN];
|
||
|
|
||
|
adj = chainlinks(str, adjs);
|
||
|
for (r = 0; r < adj; r++)
|
||
|
if (worm[adjs[r]].attack_codes[0] != 0)
|
||
|
return 0;
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* XO aO
|
||
|
* O* O*
|
||
|
*
|
||
|
* Increase the cutstone2 field if * is a potential cutting point,
|
||
|
* i.e. if it does work as a cutting point once 'a' has been
|
||
|
* defended. This helper is expected to always return 0.
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
cutstone2_helper(ARGS)
|
||
|
{
|
||
|
int apos;
|
||
|
int bpos;
|
||
|
int cpos;
|
||
|
int dpos;
|
||
|
UNUSED(pattern);
|
||
|
UNUSED(color);
|
||
|
|
||
|
if (stackp > 0)
|
||
|
return 0;
|
||
|
|
||
|
apos = OFFSET_BY(-1, -1);
|
||
|
bpos = OFFSET_BY(-1, 0);
|
||
|
cpos = OFFSET_BY( 0, -1);
|
||
|
|
||
|
if (worm[apos].defense_codes[0] == 0)
|
||
|
return 0;
|
||
|
|
||
|
dpos = worm[apos].defense_points[0];
|
||
|
|
||
|
if (TRYMOVE(dpos, board[apos])) {
|
||
|
if (!board[bpos] || attack(bpos, NULL)
|
||
|
|| !board[cpos] || attack(cpos, NULL)
|
||
|
|| safe_move(move, board[apos]) != 0) {
|
||
|
popgo();
|
||
|
worm[worm[apos].origin].cutstone2++;
|
||
|
propagate_worm(worm[apos].origin);
|
||
|
return 0;
|
||
|
}
|
||
|
popgo();
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* ?x?? ?x??
|
||
|
* ?X.. ?Xb.
|
||
|
* O*.. c*a.
|
||
|
*
|
||
|
* Is the push at * sente? c must have exactly two liberties. This is
|
||
|
* called edge_double_sente_helper because it mainly called for edge
|
||
|
* hanes, but it can be used anywhere on the board.
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
edge_double_sente_helper(int move, int apos, int bpos, int cpos)
|
||
|
{
|
||
|
int color = board[cpos];
|
||
|
int success = 0;
|
||
|
ASSERT1((color == BLACK || color == WHITE), move);
|
||
|
|
||
|
if (TRYMOVE(move, color)) {
|
||
|
ASSERT1(countlib(move) == 2, move);
|
||
|
success = connect_and_cut_helper(move, apos, bpos);
|
||
|
popgo();
|
||
|
}
|
||
|
|
||
|
return success;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* This is intended for use in autohelpers.
|
||
|
*
|
||
|
* Give a conservative estimate of the value of saving the string (str)
|
||
|
* by capturing one opponent stone.
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
threaten_to_save_helper(int move, int str)
|
||
|
{
|
||
|
add_followup_value(move, 2.0 + 2.0 * worm[str].effective_size);
|
||
|
TRACE("...followup value %f\n", 2.0 + 2.0 * worm[str].effective_size);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* For use in autohelpers.
|
||
|
*
|
||
|
* Adds a reverse followup value if the opponent's move here would threaten
|
||
|
* to capture (str).
|
||
|
*/
|
||
|
void
|
||
|
prevent_attack_threat_helper(int move, int str)
|
||
|
{
|
||
|
add_reverse_followup_value(move, 2.0 * worm[str].effective_size);
|
||
|
TRACE("...reverse followup value %f\n", 2.0 * worm[str].effective_size);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* This function is obsolete. Code in `value_moves.c' is more general. */
|
||
|
#if 0
|
||
|
|
||
|
/*
|
||
|
* This is intended for use in autohelpers.
|
||
|
*
|
||
|
* Estimate the value of capturing the string (str) and add this as
|
||
|
* a followup value. We don't do this for too stupid looking threats,
|
||
|
* however, e.g. in a position like
|
||
|
*
|
||
|
* OOOO..
|
||
|
* XXX.*.
|
||
|
* XOOOX.
|
||
|
* XXXXO.
|
||
|
*
|
||
|
* where X can get out of atari with profit by capturing three O stones.
|
||
|
*
|
||
|
* Another case where we don't award the followup value is when the
|
||
|
* opponent can defend with a threat against our move, e.g. in this
|
||
|
* position:
|
||
|
*
|
||
|
* .OOOXX.
|
||
|
* .OXXO.X
|
||
|
* ..*.X..
|
||
|
* ..XX...
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
threaten_to_capture_helper(int move, int str)
|
||
|
{
|
||
|
int adj, adjs[MAXCHAIN];
|
||
|
int defense_move;
|
||
|
int k;
|
||
|
|
||
|
adj = chainlinks2(str, adjs, 1);
|
||
|
for (k = 0; k < adj; k++)
|
||
|
if (worm[adjs[k]].defense_codes[0] != 0
|
||
|
&& !does_defend(move, adjs[k]))
|
||
|
return;
|
||
|
|
||
|
if (!TRYMOVE(move, OTHER_COLOR(board[str])))
|
||
|
return;
|
||
|
if (find_defense(str, &defense_move) != 0
|
||
|
&& defense_move != NO_MOVE
|
||
|
&& TRYMOVE(defense_move, board[str])) {
|
||
|
if (board[move] == EMPTY || attack(move, NULL) != 0) {
|
||
|
popgo();
|
||
|
popgo();
|
||
|
return;
|
||
|
}
|
||
|
popgo();
|
||
|
}
|
||
|
|
||
|
/* In addition to the move found by find_defense(), also try all
|
||
|
* chain breaking moves in the same way.
|
||
|
*/
|
||
|
adj = chainlinks2(str, adjs, 1);
|
||
|
for (k = 0; k < adj; k++) {
|
||
|
int lib;
|
||
|
findlib(adjs[k], 1, &lib);
|
||
|
if (TRYMOVE(lib, board[str])) {
|
||
|
if (!attack(str, NULL)
|
||
|
&& (board[move] == EMPTY || attack(move, NULL) != 0)) {
|
||
|
popgo();
|
||
|
popgo();
|
||
|
return;
|
||
|
}
|
||
|
popgo();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
popgo();
|
||
|
|
||
|
add_followup_value(move, 2.0 * worm[str].effective_size);
|
||
|
TRACE("...followup value %f\n", 2.0 * worm[str].effective_size);
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
|
||
|
/*
|
||
|
* This is intended for use in autohelpers.
|
||
|
*
|
||
|
* Estimate the value of defending a string which can be put into
|
||
|
* atari and add this as a reverse followup value.
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
defend_against_atari_helper(int move, int str)
|
||
|
{
|
||
|
int adj, adjs[MAXCHAIN];
|
||
|
int libs[2];
|
||
|
int k;
|
||
|
|
||
|
ASSERT1(countlib(str) == 2, str);
|
||
|
|
||
|
/* No value if the string can capture out of atari. */
|
||
|
adj = chainlinks2(str, adjs, 1);
|
||
|
for (k = 0; k < adj; k++)
|
||
|
if (worm[adjs[k]].defense_codes[0] != 0
|
||
|
&& !does_defend(move, adjs[k]))
|
||
|
return;
|
||
|
|
||
|
/* No value if opponent has no safe atari. */
|
||
|
findlib(str, 2, libs);
|
||
|
if (!safe_move(libs[0], OTHER_COLOR(board[str]))
|
||
|
&& !safe_move(libs[1], OTHER_COLOR(board[str])))
|
||
|
return;
|
||
|
|
||
|
TRACE("...reverse followup value %f\n", 2.0 * worm[str].effective_size);
|
||
|
add_reverse_followup_value(move, 2.0 * worm[str].effective_size);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* This is intended for use in conn.db autohelpers.
|
||
|
*
|
||
|
* Amalgamate either a with b or c with b, depending on which of the
|
||
|
* two dragons a and c is largest.
|
||
|
*
|
||
|
* If either of these pairs already have been amalgamated somehow,
|
||
|
* do nothing.
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
amalgamate_most_valuable_helper(int apos, int bpos, int cpos)
|
||
|
{
|
||
|
if (!is_same_dragon(apos, bpos) && !is_same_dragon(bpos, cpos)) {
|
||
|
if (dragon[apos].effective_size >= dragon[cpos].effective_size)
|
||
|
join_dragons(apos, bpos);
|
||
|
else
|
||
|
join_dragons(bpos, cpos);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* This is intended for use in autohelpers.
|
||
|
*
|
||
|
* Returns 1 if (pos) is adjacent to a stone which can be captured by ko.
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
finish_ko_helper(int pos)
|
||
|
{
|
||
|
int adj, adjs[MAXCHAIN];
|
||
|
int lib;
|
||
|
int k;
|
||
|
|
||
|
adj = chainlinks2(pos, adjs, 1);
|
||
|
for (k = 0; k < adj; k++) {
|
||
|
if (countstones(adjs[k]) == 1) {
|
||
|
findlib(adjs[k], 1, &lib);
|
||
|
if (is_ko(lib, board[pos], NULL))
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* This is intended for use in autohelpers.
|
||
|
*
|
||
|
* Returns 1 if (ai, aj) is next to a ko point.
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
squeeze_ko_helper(int pos)
|
||
|
{
|
||
|
int libs[2];
|
||
|
int liberties;
|
||
|
int k;
|
||
|
|
||
|
liberties = findlib(pos, 2, libs);
|
||
|
ASSERT1(liberties == 2, pos);
|
||
|
|
||
|
for (k = 0; k < liberties; k++) {
|
||
|
int aa = libs[k];
|
||
|
if (is_ko(aa, OTHER_COLOR(board[pos]), NULL))
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* This is intended for use in autohelpers.
|
||
|
*
|
||
|
* If after playing a and b, the string at c can be attacked, this
|
||
|
* function adds a small fixed move value for a move which defends
|
||
|
* c.
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
backfill_helper(int apos, int bpos, int cpos)
|
||
|
{
|
||
|
int color = board[cpos];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int dpos = NO_MOVE;
|
||
|
|
||
|
if (TRYMOVE(apos, color)) {
|
||
|
if (TRYMOVE(bpos, other)) {
|
||
|
if (attack(cpos, NULL) && find_defense(cpos, &dpos)) {
|
||
|
set_minimum_move_value(dpos, 0.1);
|
||
|
TRACE("%o...setting min move value of %1m to 0.1\n", dpos);
|
||
|
}
|
||
|
popgo();
|
||
|
}
|
||
|
popgo();
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Returns true if (apos) kills or threatens to kill (bpos). */
|
||
|
|
||
|
int
|
||
|
owl_threatens_attack(int apos, int bpos)
|
||
|
{
|
||
|
if (DRAGON2(bpos).owl_status == CRITICAL
|
||
|
&& DRAGON2(bpos).owl_attack_point == apos)
|
||
|
return 1;
|
||
|
|
||
|
if (DRAGON2(bpos).owl_threat_status == CAN_THREATEN_ATTACK)
|
||
|
if (DRAGON2(bpos).owl_attack_point == apos
|
||
|
|| DRAGON2(bpos).owl_second_attack_point == apos)
|
||
|
return 1;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Returns true if O needs to connect at c in the position below after
|
||
|
* O at b and X at d, because X can cut at c. In general d is the
|
||
|
* second liberty of A, which must have exactly two liberties.
|
||
|
*
|
||
|
* |.X |dX
|
||
|
* |XO |AO
|
||
|
* |XO |Ae
|
||
|
* |.. |bc
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
connect_and_cut_helper(int Apos, int bpos, int cpos)
|
||
|
{
|
||
|
int dpos;
|
||
|
int epos = NO_MOVE;
|
||
|
int other = board[Apos];
|
||
|
int color = OTHER_COLOR(other);
|
||
|
int libs[2];
|
||
|
int liberties = findlib(Apos, 2, libs);
|
||
|
int result = 0;
|
||
|
int k;
|
||
|
|
||
|
gg_assert(IS_STONE(color));
|
||
|
gg_assert(liberties == 2);
|
||
|
|
||
|
if (libs[0] == bpos)
|
||
|
dpos = libs[1];
|
||
|
else
|
||
|
dpos = libs[0];
|
||
|
|
||
|
for (k = 0; k < 4; k++)
|
||
|
if (board[cpos + delta[k]] == color
|
||
|
&& neighbor_of_string(cpos + delta[k], Apos)) {
|
||
|
epos = cpos + delta[k];
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
gg_assert(epos != NO_MOVE);
|
||
|
|
||
|
if (TRYMOVE(bpos, color)) {
|
||
|
if (TRYMOVE(dpos, other)) {
|
||
|
if (TRYMOVE(cpos, other)) {
|
||
|
if (board[bpos] == EMPTY
|
||
|
|| board[epos] == EMPTY
|
||
|
|| !defend_both(bpos, epos))
|
||
|
result = 1;
|
||
|
popgo();
|
||
|
}
|
||
|
popgo();
|
||
|
}
|
||
|
popgo();
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/*
|
||
|
* This is similar to connect_and_cut_helper(), except it starts with
|
||
|
* a move at A and that d is found as a general defense point for A. A
|
||
|
* is no longer restricted to two liberties.
|
||
|
*
|
||
|
* |.X |dX
|
||
|
* |XO |XO
|
||
|
* |.O |Ae
|
||
|
* |.. |bc
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
connect_and_cut_helper2(int Apos, int bpos, int cpos, int color)
|
||
|
{
|
||
|
int dpos;
|
||
|
int epos = NO_MOVE;
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int result = 0;
|
||
|
int k;
|
||
|
|
||
|
gg_assert(IS_STONE(color));
|
||
|
|
||
|
|
||
|
if (TRYMOVE(Apos, color)) {
|
||
|
for (k = 0; k < 4; k++)
|
||
|
if (board[cpos + delta[k]] == other
|
||
|
&& neighbor_of_string(cpos + delta[k], Apos)) {
|
||
|
epos = cpos + delta[k];
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
gg_assert(epos != NO_MOVE);
|
||
|
|
||
|
if (TRYMOVE(bpos, other)) {
|
||
|
if (!find_defense(Apos, &dpos) || dpos == NO_MOVE) {
|
||
|
popgo();
|
||
|
popgo();
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (TRYMOVE(dpos, color)) {
|
||
|
if (TRYMOVE(cpos, color)) {
|
||
|
if (board[bpos] == EMPTY
|
||
|
|| board[epos] == EMPTY
|
||
|
|| !defend_both(bpos, epos))
|
||
|
result = 1;
|
||
|
popgo();
|
||
|
}
|
||
|
popgo();
|
||
|
}
|
||
|
popgo();
|
||
|
}
|
||
|
popgo();
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
void
|
||
|
test_attack_either_move(int move, int color, int worma, int wormb)
|
||
|
{
|
||
|
ASSERT_ON_BOARD1(move);
|
||
|
ASSERT1(board[move] == EMPTY, move);
|
||
|
ASSERT1(board[worma] == OTHER_COLOR(color)
|
||
|
&& board[wormb] == OTHER_COLOR(color), move);
|
||
|
|
||
|
if (!defend_both(worma, wormb)) {
|
||
|
if (0)
|
||
|
gprintf("%1m: Reject attack_either_move for %1m, %1m (can't defend both)\n",
|
||
|
move, worma, wormb);
|
||
|
return;
|
||
|
}
|
||
|
if (trymove(move, color, "test_attack_either_move", worma)) {
|
||
|
if (board[worma] == OTHER_COLOR(color)
|
||
|
&& board[wormb] == OTHER_COLOR(color)) {
|
||
|
if (!find_defense(worma, NULL) || !find_defense(wormb, NULL)) {
|
||
|
if (0)
|
||
|
gprintf("%1m: Rej. attack_either_move for %1m & %1m (regular attack)\n",
|
||
|
move, worma, wormb);
|
||
|
}
|
||
|
else if (!defend_both(worma, wormb))
|
||
|
add_either_move(move, ATTACK_STRING, worma, ATTACK_STRING, wormb);
|
||
|
else {
|
||
|
if (0)
|
||
|
gprintf("%1m: Rej. attack_either_move for %1m & %1m (doesn't work)\n",
|
||
|
move, worma, wormb);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
if (0)
|
||
|
gprintf("%1m: Rej. attack_either_move for %1m & %1m (captured directly)\n",
|
||
|
move, worma, wormb);
|
||
|
popgo();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* True if str is adjacent to a stone in atari, which is tactically
|
||
|
* attackable (to exclude pointless captures of snapback stones).
|
||
|
*/
|
||
|
int
|
||
|
adjacent_to_stone_in_atari(int str)
|
||
|
{
|
||
|
int adj;
|
||
|
int adjs[MAXCHAIN];
|
||
|
int k;
|
||
|
|
||
|
adj = chainlinks2(str, adjs, 1);
|
||
|
for (k = 0; k < adj; k++)
|
||
|
if (attack(adjs[k], NULL))
|
||
|
return 1;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* True if str is adjacent to a stone in atari, which is tactically
|
||
|
* defendable.
|
||
|
*/
|
||
|
int
|
||
|
adjacent_to_defendable_stone_in_atari(int str)
|
||
|
{
|
||
|
int adj;
|
||
|
int adjs[MAXCHAIN];
|
||
|
int k;
|
||
|
|
||
|
adj = chainlinks2(str, adjs, 1);
|
||
|
for (k = 0; k < adj; k++)
|
||
|
if (attack_and_defend(adjs[k], NULL, NULL, NULL, NULL))
|
||
|
return 1;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
backfill_replace(int move, int str)
|
||
|
{
|
||
|
int defense_move = NO_MOVE;
|
||
|
|
||
|
if (TRYMOVE(move, OTHER_COLOR(board[str]))) {
|
||
|
if (attack_and_defend(str, NULL, NULL, NULL, &defense_move)) {
|
||
|
/* Must undo the trymove before adding the replacement move. */
|
||
|
popgo();
|
||
|
add_replacement_move(move, defense_move, board[str]);
|
||
|
}
|
||
|
else
|
||
|
popgo();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* True if
|
||
|
* 1. White to move.
|
||
|
* 2. All white stones look dead.
|
||
|
* 3. Less than 40% of the board is filled or less than 20% of the
|
||
|
* board is filled with white stones.
|
||
|
*
|
||
|
* This is intended for patterns forcing white to thrash around early
|
||
|
* in high handicap games, instead of passing because it looks like no
|
||
|
* stones can live.
|
||
|
*/
|
||
|
int
|
||
|
thrash_around_helper(ARGS)
|
||
|
{
|
||
|
UNUSED(pattern);
|
||
|
UNUSED(trans);
|
||
|
UNUSED(move);
|
||
|
|
||
|
/* Do not generate these moves when doing scoring or if fuseki move
|
||
|
* generation is disabled (typically used when solving life and
|
||
|
* death problems embedded on a big board).
|
||
|
*/
|
||
|
if (doing_scoring
|
||
|
|| disable_fuseki
|
||
|
|| (stones_on_board(BLACK | WHITE) > board_size * board_size * 2 / 5
|
||
|
&& stones_on_board(WHITE) > board_size * board_size / 5)
|
||
|
|| color == BLACK
|
||
|
|| lively_dragon_exists(WHITE))
|
||
|
return 0;
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Returns true if
|
||
|
*
|
||
|
* 1. The board size is odd.
|
||
|
* 2. A white move is being generated.
|
||
|
* 3. The komi is less than or equal to zero.
|
||
|
* 4. str is placed at tengen.
|
||
|
* 5. At least 10 moves have been played.
|
||
|
* 6. The board is currently mirror symmetric.
|
||
|
*
|
||
|
* This is intended for patterns to break mirror go when black starts at
|
||
|
* tengen and then mirrors white. We only care about breaking the mirroring
|
||
|
* if komi is non-positive, otherwise the mirroring is to our advantage.
|
||
|
*/
|
||
|
int
|
||
|
break_mirror_helper(int str, int color)
|
||
|
{
|
||
|
if (board_size % 2 == 1
|
||
|
&& color == WHITE
|
||
|
&& komi <= 0.0
|
||
|
&& I(str) == (board_size - 1) / 2
|
||
|
&& J(str) == (board_size - 1) / 2
|
||
|
&& stones_on_board(BLACK | WHITE) > 10
|
||
|
&& test_symmetry_after_move(PASS_MOVE, EMPTY, 1))
|
||
|
return 1;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* This helper is intended to detect semeai kind of positions where
|
||
|
* the tactical reading can't be trusted enough to allow amalgamation
|
||
|
* over presumably tactically dead strings.
|
||
|
*
|
||
|
* It has turned out to be best not to trust tactical reading of three
|
||
|
* and four liberty strings at all. Not trusting two liberty strings
|
||
|
* leads to an underamalgamation and unnecessarily many dragons on the
|
||
|
* board. Therefore we try to detect two liberty strings with an
|
||
|
* enclosed nakade, which after capturing leads to an unreliable
|
||
|
* reading at three or four liberties.
|
||
|
*
|
||
|
* More specifically we check whether the string has a neighbor with
|
||
|
* the following properties:
|
||
|
* 1. At least three stones in size.
|
||
|
* 2. All its liberties are common liberties with the string.
|
||
|
* 3. It has no second order liberties.
|
||
|
* 4. Its liberties are adjacent to no other strings than itself and
|
||
|
* the primary string.
|
||
|
*
|
||
|
* If we find such a neighbor 1 is returned, otherwise 0.
|
||
|
*/
|
||
|
int distrust_tactics_helper(int str)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int adj;
|
||
|
int adjs[MAXCHAIN];
|
||
|
int k;
|
||
|
int r;
|
||
|
int s;
|
||
|
int lib = countlib(str);
|
||
|
|
||
|
ASSERT1(IS_STONE(board[str]), str);
|
||
|
|
||
|
if (lib > 2)
|
||
|
return 1;
|
||
|
else if (lib == 1)
|
||
|
return 0;
|
||
|
|
||
|
adj = chainlinks3(str, adjs, lib);
|
||
|
for (r = 0; r < adj; r++) {
|
||
|
int nakade = 1;
|
||
|
int adjlib;
|
||
|
int adjlibs[3];
|
||
|
if (countstones(adjs[r]) < 3)
|
||
|
continue;
|
||
|
adjlib = findlib(adjs[r], 3, adjlibs);
|
||
|
for (s = 0; s < adjlib; s++) {
|
||
|
int str_found = 0;
|
||
|
for (k = 0; k < 4; k++) {
|
||
|
int pos = adjlibs[s] + delta[k];
|
||
|
if (board[pos] == EMPTY
|
||
|
&& !liberty_of_string(pos, adjs[r]))
|
||
|
nakade = 0;
|
||
|
else if (board[pos] == color) {
|
||
|
if (same_string(pos, str))
|
||
|
str_found = 1;
|
||
|
else
|
||
|
nakade = 0;
|
||
|
}
|
||
|
else if (board[pos] == OTHER_COLOR(color)
|
||
|
&& !same_string(pos, adjs[r]))
|
||
|
nakade = 0;
|
||
|
}
|
||
|
if (!str_found)
|
||
|
nakade = 0;
|
||
|
}
|
||
|
if (nakade)
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* LOCAL Variables:
|
||
|
* tab-width: 8
|
||
|
* c-basic-offset: 2
|
||
|
* End:
|
||
|
*/
|