Initial commit of GNU Go v3.8.
[sgk-go] / patterns / helpers.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:
*/