5740 lines
157 KiB
C
5740 lines
157 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 "liberty.h"
|
||
|
#include "cache.h"
|
||
|
#include "gg_utils.h"
|
||
|
|
||
|
/* If nonzero, attack() and find_defense() write all results to
|
||
|
* stderr. Use this to if you have deviations in results, but cannot
|
||
|
* find where they come from.
|
||
|
*
|
||
|
* Redirect results to a file. Take dumps of two versions and
|
||
|
* (assuming GNU tools) do `sort -t= -s' on both. Then join the
|
||
|
* sorted dumps:
|
||
|
*
|
||
|
* join -t= sorted-first-dump sorted-second-dump \
|
||
|
* | sed -e "s/^[^=]\+=\([^=]\+\)=\1$//" | tr -s "\n" | tr = "\t" \
|
||
|
* | uniq
|
||
|
*
|
||
|
* to get a nice list of deviations. This is only meaningful if you
|
||
|
* dump results of a single test (or at least tests originating at a
|
||
|
* same position).
|
||
|
*/
|
||
|
#define DUMP_ALL_RESULTS 0
|
||
|
|
||
|
|
||
|
/* Size of array where candidate moves are stored. */
|
||
|
#define MAX_MOVES 50
|
||
|
|
||
|
/* Please notice that message had better be a fixed string. Only the
|
||
|
* pointer to it is saved and there is no attempt to free up any
|
||
|
* storage.
|
||
|
*/
|
||
|
#define ADD_CANDIDATE_MOVE(move, this_score, moves, this_message) \
|
||
|
do { \
|
||
|
int u; \
|
||
|
for (u = 0; u < (moves).num; u++) \
|
||
|
if ((moves).pos[u] == (move)) { \
|
||
|
(moves).score[u] += this_score; \
|
||
|
break; \
|
||
|
} \
|
||
|
if ((u == (moves).num) && ((moves).num < MAX_MOVES)) { \
|
||
|
(moves).pos[(moves).num] = move; \
|
||
|
(moves).score[(moves).num] = this_score; \
|
||
|
(moves).message[(moves).num] = this_message; \
|
||
|
(moves).num++; \
|
||
|
} \
|
||
|
} while (0)
|
||
|
|
||
|
#define REMOVE_CANDIDATE_MOVE(move, moves) \
|
||
|
do { \
|
||
|
int u; \
|
||
|
for (u = (moves).num_tried; u < (moves).num; u++) { \
|
||
|
if ((moves).pos[u] == (move)) { \
|
||
|
(moves).pos[u] = (moves).pos[(moves).num - 1]; \
|
||
|
(moves).score[u] = (moves).score[(moves).num - 1]; \
|
||
|
(moves).message[u] = (moves).message[(moves).num - 1]; \
|
||
|
(moves).num--; \
|
||
|
break; \
|
||
|
} \
|
||
|
} \
|
||
|
} while (0)
|
||
|
|
||
|
|
||
|
/* This macro checks whether the reported result is a loss, so we have won
|
||
|
* and can exit, or else if it is the best result so far.
|
||
|
* Note that SGFTRACE must have been setup.
|
||
|
*/
|
||
|
#define CHECK_RESULT(savecode, savemove, code, move_pos, move_ptr, \
|
||
|
trace_message) \
|
||
|
do { \
|
||
|
if (code == 0) { \
|
||
|
if (move_ptr) \
|
||
|
*(move_ptr) = (move_pos); \
|
||
|
SGFTRACE(move_pos, WIN, trace_message); \
|
||
|
return WIN; \
|
||
|
} \
|
||
|
else if (REVERSE_RESULT(code) > savecode) { \
|
||
|
savemove = move_pos; \
|
||
|
savecode = REVERSE_RESULT(code); \
|
||
|
} \
|
||
|
} while (0)
|
||
|
|
||
|
/* Reverse of CHECK_RESULT, for results passed from a helper function. */
|
||
|
#define CHECK_RESULT_UNREVERSED(savecode, savemove, code, move_pos, \
|
||
|
move_ptr, trace_message) \
|
||
|
CHECK_RESULT(savecode, savemove, REVERSE_RESULT(code), move_pos, \
|
||
|
move_ptr, trace_message)
|
||
|
|
||
|
|
||
|
#define RETURN_RESULT(savecode, savemove, move_ptr, trace_message) \
|
||
|
do { \
|
||
|
if (savecode) { \
|
||
|
if (move_ptr) \
|
||
|
*(move_ptr) = (savemove); \
|
||
|
SGFTRACE(savemove, savecode, trace_message); \
|
||
|
} \
|
||
|
else \
|
||
|
SGFTRACE(0, 0, NULL); \
|
||
|
return savecode; \
|
||
|
} while (0)
|
||
|
|
||
|
|
||
|
/* Play a collected batch of moves and see if any of them works. This
|
||
|
* is a defense version.
|
||
|
*/
|
||
|
#define DEFEND_TRY_MOVES(no_deep_branching, attack_hint) \
|
||
|
do { \
|
||
|
int k; \
|
||
|
\
|
||
|
for (k = moves.num_tried; k < moves.num; k++) { \
|
||
|
int ko_move; \
|
||
|
int dpos = moves.pos[k]; \
|
||
|
\
|
||
|
if (komaster_trymove(dpos, color, moves.message[k], str, &ko_move,\
|
||
|
stackp <= ko_depth && savecode == 0)) { \
|
||
|
int acode = do_attack(str, (attack_hint)); \
|
||
|
popgo(); \
|
||
|
\
|
||
|
if (!ko_move) { \
|
||
|
CHECK_RESULT(savecode, savemove, acode, dpos, move, \
|
||
|
"defense effective"); \
|
||
|
} \
|
||
|
else { \
|
||
|
if (acode != WIN) { \
|
||
|
savemove = dpos; \
|
||
|
savecode = KO_B; \
|
||
|
} \
|
||
|
} \
|
||
|
} \
|
||
|
\
|
||
|
if ((no_deep_branching) && stackp >= branch_depth) \
|
||
|
RETURN_RESULT(savecode, savemove, move, "branching limit"); \
|
||
|
} \
|
||
|
\
|
||
|
moves.num_tried = moves.num; \
|
||
|
} while (0)
|
||
|
|
||
|
|
||
|
/* Attack version of the macro above. This one is a bit more
|
||
|
* complicated, because when defender fails to defend, attacker has to
|
||
|
* prove that he can capture the string before claiming victory.
|
||
|
*/
|
||
|
#define ATTACK_TRY_MOVES(no_deep_branching, defense_hint) \
|
||
|
do { \
|
||
|
int k; \
|
||
|
\
|
||
|
for (k = moves.num_tried; k < moves.num; k++) { \
|
||
|
int ko_move; \
|
||
|
int apos = moves.pos[k]; \
|
||
|
\
|
||
|
if ((board_ko_pos != NO_MOVE || !send_two_return_one(apos, other))\
|
||
|
&& komaster_trymove(apos, other, moves.message[k], \
|
||
|
str, &ko_move, \
|
||
|
stackp <= ko_depth && savecode == 0)) { \
|
||
|
int dcode = do_find_defense(str, (defense_hint)); \
|
||
|
\
|
||
|
if (REVERSE_RESULT(dcode) > savecode \
|
||
|
&& do_attack(str, NULL)) { \
|
||
|
if (!ko_move) { \
|
||
|
if (dcode == 0) { \
|
||
|
popgo(); \
|
||
|
RETURN_RESULT(WIN, apos, move, "attack effective"); \
|
||
|
} \
|
||
|
\
|
||
|
savemove = apos; \
|
||
|
savecode = REVERSE_RESULT(dcode); \
|
||
|
} \
|
||
|
else { \
|
||
|
savemove = apos; \
|
||
|
savecode = KO_B; \
|
||
|
} \
|
||
|
} \
|
||
|
\
|
||
|
popgo(); \
|
||
|
} \
|
||
|
\
|
||
|
if ((no_deep_branching) && stackp >= branch_depth) \
|
||
|
RETURN_RESULT(savecode, savemove, move, "branching limit"); \
|
||
|
} \
|
||
|
\
|
||
|
moves.num_tried = moves.num; \
|
||
|
} while (0)
|
||
|
|
||
|
|
||
|
|
||
|
struct reading_moves
|
||
|
{
|
||
|
int pos[MAX_MOVES];
|
||
|
int score[MAX_MOVES];
|
||
|
const char *message[MAX_MOVES];
|
||
|
int num;
|
||
|
int num_tried;
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* The functions in reading.c are used to read whether groups
|
||
|
* can be captured or not. See the Texinfo documentation
|
||
|
* (Reading) for more information.
|
||
|
*
|
||
|
* NULL POINTERS: Many functions in this file can use pointers
|
||
|
* to return the locations of recommended plays. These can be
|
||
|
* set NULL in which case these values are not returned.
|
||
|
*/
|
||
|
|
||
|
static int do_find_defense(int str, int *move);
|
||
|
static int defend1(int str, int *move);
|
||
|
static int defend2(int str, int *move);
|
||
|
static int defend3(int str, int *move);
|
||
|
static int defend4(int str, int *move);
|
||
|
static void special_rescue_moves(int str, int lib,
|
||
|
struct reading_moves *moves);
|
||
|
static void bamboo_rescue_moves(int str, int num_libs, int libs[],
|
||
|
struct reading_moves *moves);
|
||
|
static void special_rescue2_moves(int str, int libs[2],
|
||
|
struct reading_moves *moves);
|
||
|
static void special_rescue3_moves(int str, int libs[3],
|
||
|
struct reading_moves *moves);
|
||
|
static void special_rescue4_moves(int str, int libs[2],
|
||
|
struct reading_moves *moves);
|
||
|
static void hane_rescue_moves(int str, int libs[4],
|
||
|
struct reading_moves *moves);
|
||
|
static void special_rescue5_moves(int str, int libs[3],
|
||
|
struct reading_moves *moves);
|
||
|
static void special_rescue6_moves(int str, int libs[3],
|
||
|
struct reading_moves *moves);
|
||
|
static void set_up_snapback_moves(int str, int lib,
|
||
|
struct reading_moves *moves);
|
||
|
static void edge_clamp_moves(int str, struct reading_moves *moves);
|
||
|
static int do_attack(int str, int *move);
|
||
|
static int attack1(int str, int *move);
|
||
|
static int attack2(int str, int *move);
|
||
|
static int attack3(int str, int *move);
|
||
|
static int attack4(int str, int *move);
|
||
|
static void find_cap_moves(int str, struct reading_moves *moves);
|
||
|
static void special_attack2_moves(int str, int libs[2],
|
||
|
struct reading_moves *moves);
|
||
|
static void special_attack3_moves(int str, int libs[2],
|
||
|
struct reading_moves *moves);
|
||
|
static void special_attack4_moves(int str, int libs[2],
|
||
|
struct reading_moves *moves);
|
||
|
static void draw_back_moves(int str, struct reading_moves *moves);
|
||
|
static void edge_closing_backfill_moves(int str, int apos,
|
||
|
struct reading_moves *moves);
|
||
|
static void edge_block_moves(int str, int apos,
|
||
|
struct reading_moves *moves);
|
||
|
static void propose_edge_moves(int str, int *libs, int liberties,
|
||
|
struct reading_moves *moves, int color);
|
||
|
static void break_chain_moves(int str, struct reading_moves *moves);
|
||
|
static int defend_secondary_chain1_moves(int str, struct reading_moves *moves,
|
||
|
int min_liberties);
|
||
|
static void defend_secondary_chain2_moves(int str, struct reading_moves *moves,
|
||
|
int min_liberties);
|
||
|
static void break_chain2_efficient_moves(int str,
|
||
|
struct reading_moves *moves);
|
||
|
static void do_find_break_chain2_efficient_moves(int str, int adj,
|
||
|
struct reading_moves *moves);
|
||
|
static void break_chain2_moves(int str, struct reading_moves *moves,
|
||
|
int require_safe, int be_aggressive);
|
||
|
static void break_chain2_defense_moves(int str, struct reading_moves *moves,
|
||
|
int be_aggressive);
|
||
|
static void break_chain3_moves(int str, struct reading_moves *moves,
|
||
|
int be_aggressive);
|
||
|
static void break_chain4_moves(int str, struct reading_moves *moves,
|
||
|
int be_aggressive);
|
||
|
static void superstring_moves(int str, struct reading_moves *moves,
|
||
|
int liberty_cap, int does_attack);
|
||
|
static void squeeze_moves(int str, struct reading_moves *moves);
|
||
|
static void superstring_break_chain_moves(int str, int liberty_cap,
|
||
|
struct reading_moves *moves);
|
||
|
static void double_atari_chain2_moves(int str,
|
||
|
struct reading_moves *moves,
|
||
|
int generate_more_moves);
|
||
|
static void order_moves(int str, struct reading_moves *moves,
|
||
|
int color, const char *funcname, int killer);
|
||
|
static int simple_ladder_defend(int str, int *move);
|
||
|
static int in_list(int move, int num_moves, int *moves);
|
||
|
|
||
|
|
||
|
/* Statistics. */
|
||
|
static int reading_node_counter = 0;
|
||
|
static int nodes_when_called = 0;
|
||
|
|
||
|
|
||
|
|
||
|
/* ================================================================ */
|
||
|
/* Goal functions */
|
||
|
/* ================================================================ */
|
||
|
|
||
|
|
||
|
/*
|
||
|
* These functions define goals for the reading process. They use
|
||
|
* the rest of the reading machinery to evaluate whether the goal
|
||
|
* is fulfillable.
|
||
|
*
|
||
|
* The simplest goals are defined by attack() and find_defense(),
|
||
|
* namely to see if it is possible to capture or defend a single
|
||
|
* string. More complex goals are defined by e.g. attack_either()
|
||
|
* or defend_both().
|
||
|
*
|
||
|
* The functions in this section and the next are the only ones which are
|
||
|
* callable from outside this file.
|
||
|
*/
|
||
|
|
||
|
|
||
|
/* attack(str, *move) determines if the string at (str) can be
|
||
|
* captured, and if so, (*move) returns the attacking move, unless
|
||
|
* (move) is a null pointer. Use a null pointer if you are interested
|
||
|
* in the result of the attack but not the attacking move itself.
|
||
|
*
|
||
|
* Return WIN if the attack succeeds unconditionally, 0 if it doesn't.
|
||
|
* Returns KO_A or KO_B if the result depends on ko:
|
||
|
* - Returns KO_A if the attack succeeds provided attacker is willing to
|
||
|
* ignore any ko threat (the attacker makes the first ko capture).
|
||
|
* - Returns KO_B if attack succeeds provided attacker has a ko threat
|
||
|
* which must be answered (the defender makes the first ko capture).
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
attack(int str, int *move)
|
||
|
{
|
||
|
int result;
|
||
|
int nodes;
|
||
|
int origin;
|
||
|
int the_move = NO_MOVE;
|
||
|
int liberties = countlib(str);
|
||
|
|
||
|
nodes_when_called = reading_node_counter;
|
||
|
/* Don't even spend time looking in the cache if there are more than
|
||
|
* enough liberties. We need this before the persistent cache lookup
|
||
|
* to avoid results inconsistent with find_defense().
|
||
|
*/
|
||
|
if (liberties > 4
|
||
|
|| (liberties == 4 && stackp > fourlib_depth)
|
||
|
|| (liberties == 3 && stackp > depth))
|
||
|
return 0;
|
||
|
|
||
|
origin = find_origin(str);
|
||
|
if (search_persistent_reading_cache(ATTACK, origin, &result, &the_move)) {
|
||
|
if (move)
|
||
|
*move = the_move;
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
memset(shadow, 0, sizeof(shadow));
|
||
|
result = do_attack(str, &the_move);
|
||
|
nodes = reading_node_counter - nodes_when_called;
|
||
|
|
||
|
if (debug & DEBUG_READING_PERFORMANCE) {
|
||
|
if (reading_node_counter - nodes_when_called
|
||
|
>= MIN_READING_NODES_TO_REPORT) {
|
||
|
if (result != 0)
|
||
|
gprintf("%oattack %1m(%1m) = %d %1M, %d nodes ", str, origin, result,
|
||
|
the_move, nodes);
|
||
|
else
|
||
|
gprintf("%oattack %1m(%1m) = %d, %d nodes ", str, origin, result,
|
||
|
nodes);
|
||
|
dump_stack();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
store_persistent_reading_cache(ATTACK, origin, result, the_move, nodes);
|
||
|
|
||
|
if (move)
|
||
|
*move = the_move;
|
||
|
|
||
|
#if DUMP_ALL_RESULTS
|
||
|
do_dump_stack();
|
||
|
gprintf("%oattack %1m (%d)=%d %1m\n", str, depth, result, the_move);
|
||
|
#endif
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* find_defense(str, *move) attempts to find a move that will save
|
||
|
* the string at (str). It returns WIN if such a move is found, with
|
||
|
* (*move) the location of the saving move, unless (move) is a
|
||
|
* null pointer. It is not checked that tenuki defends, so this may
|
||
|
* give an erroneous answer if !attack(str).
|
||
|
*
|
||
|
* Returns KO_A or KO_B if the result depends on ko. Returns KO_A if the
|
||
|
* string can be defended provided the defender is willing to ignore
|
||
|
* any ko threat. Returns KO_B if the defender wins by having a ko threat
|
||
|
* which must be answered.
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
find_defense(int str, int *move)
|
||
|
{
|
||
|
int result;
|
||
|
int nodes;
|
||
|
int origin;
|
||
|
int the_move = NO_MOVE;
|
||
|
int liberties = countlib(str);
|
||
|
|
||
|
nodes_when_called = reading_node_counter;
|
||
|
/* Don't even spend time looking in the cache if there are more than
|
||
|
* enough liberties.
|
||
|
*/
|
||
|
if (liberties > 4
|
||
|
|| (liberties == 4 && stackp > fourlib_depth)) {
|
||
|
if (move)
|
||
|
*move = NO_MOVE;
|
||
|
return WIN;
|
||
|
}
|
||
|
|
||
|
origin = find_origin(str);
|
||
|
if (search_persistent_reading_cache(FIND_DEFENSE, origin,
|
||
|
&result, &the_move)) {
|
||
|
if (move)
|
||
|
*move = the_move;
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
memset(shadow, 0, sizeof(shadow));
|
||
|
result = do_find_defense(str, &the_move);
|
||
|
nodes = reading_node_counter - nodes_when_called;
|
||
|
|
||
|
if (debug & DEBUG_READING_PERFORMANCE) {
|
||
|
if (reading_node_counter - nodes_when_called
|
||
|
>= MIN_READING_NODES_TO_REPORT) {
|
||
|
if (result != 0)
|
||
|
gprintf("%odefend %1m(%1m) = %d %1M, %d nodes ", str, origin, result,
|
||
|
the_move, nodes);
|
||
|
else
|
||
|
gprintf("%odefend %1m(%1m) = %d, %d nodes ", str, origin, result,
|
||
|
nodes);
|
||
|
dump_stack();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
store_persistent_reading_cache(FIND_DEFENSE, origin, result,
|
||
|
the_move, nodes);
|
||
|
|
||
|
if (move)
|
||
|
*move = the_move;
|
||
|
|
||
|
#if DUMP_ALL_RESULTS
|
||
|
do_dump_stack();
|
||
|
gprintf("%odefend %1m (%d)=%d %1m\n", str, depth, result, the_move);
|
||
|
#endif
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* attack_and_defend(str, &acode, &attack_point,
|
||
|
* &dcode, &defense_point)
|
||
|
* is a frontend to the attack() and find_defense() functions, which
|
||
|
* guarantees a consistent result. If a string cannot be attacked, 0
|
||
|
* is returned and acode is 0. If a string can be attacked and
|
||
|
* defended, WIN is returned, acode and dcode are both non-zero, and
|
||
|
* (attack_point), (defense_point) both point to vertices on the board.
|
||
|
* If a string can be attacked but not defended, 0 is again returned,
|
||
|
* acode is non-zero, dcode is 0, and (attack_point) points to a vertex
|
||
|
* on the board.
|
||
|
*
|
||
|
* This function in particular guarantees that if there is an attack,
|
||
|
* it will never return (defense_point) = NO_MOVE, which means the string is
|
||
|
* safe without defense. Separate calls to attack() and find_defense()
|
||
|
* may occasionally give this result, due to irregularities introduced
|
||
|
* by the persistent reading cache.
|
||
|
*/
|
||
|
int
|
||
|
attack_and_defend(int str,
|
||
|
int *attack_code, int *attack_point,
|
||
|
int *defend_code, int *defense_point)
|
||
|
{
|
||
|
int acode = 0;
|
||
|
int apos = NO_MOVE;
|
||
|
int dcode = 0;
|
||
|
int dpos = NO_MOVE;
|
||
|
|
||
|
acode = attack(str, &apos);
|
||
|
if (acode != 0)
|
||
|
dcode = find_defense(str, &dpos);
|
||
|
|
||
|
ASSERT1(!(acode != 0 && dcode == WIN && dpos == NO_MOVE), str);
|
||
|
|
||
|
if (attack_code)
|
||
|
*attack_code = acode;
|
||
|
if (attack_point)
|
||
|
*attack_point = apos;
|
||
|
if (defend_code)
|
||
|
*defend_code = dcode;
|
||
|
if (defense_point)
|
||
|
*defense_point = dpos;
|
||
|
|
||
|
return acode != 0 && dcode != 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* attack_either(astr, bstr) returns true if there is a move which
|
||
|
* guarantees that at least one of the strings (astr) and (bstr)
|
||
|
* can be captured. A typical application for this is in connection
|
||
|
* patterns, where after a cut it suffices to capture one of the cutting
|
||
|
* stones.
|
||
|
*
|
||
|
* FIXME: The current implementation only looks for uncoordinated
|
||
|
* attacks. This is insufficient to find double ataris or
|
||
|
* moves such as 'a' in positions like
|
||
|
*
|
||
|
* XOOOOOOOX
|
||
|
* XOXXOXXOX
|
||
|
* XX..a..XX
|
||
|
* ---------
|
||
|
*
|
||
|
* where neither of the threatened X stones can be captured right
|
||
|
* out. Still either can be captured by a move down to a.
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
attack_either(int astr, int bstr)
|
||
|
{
|
||
|
int asuccess = 0;
|
||
|
int bsuccess = 0;
|
||
|
int color = board[astr];
|
||
|
ASSERT1(IS_STONE(color) , astr);
|
||
|
ASSERT1(color == board[bstr], bstr);
|
||
|
|
||
|
/* Start by attacking the string with the fewest liberties. On
|
||
|
* average this seems to be slightly more efficient.
|
||
|
*/
|
||
|
if (countlib(astr) > countlib(bstr)) {
|
||
|
int t = astr;
|
||
|
astr = bstr;
|
||
|
bstr = t;
|
||
|
}
|
||
|
|
||
|
asuccess = attack(astr, NULL);
|
||
|
if (asuccess == WIN)
|
||
|
return asuccess;
|
||
|
|
||
|
bsuccess = attack(bstr, NULL);
|
||
|
if (asuccess || bsuccess) {
|
||
|
return (asuccess > bsuccess) ? asuccess : bsuccess;
|
||
|
}
|
||
|
|
||
|
/* Try (a little) harder */
|
||
|
{
|
||
|
int alibs[2];
|
||
|
int blibs[2];
|
||
|
int alib = findlib(astr, 2, alibs);
|
||
|
int defended0 = WIN;
|
||
|
int defended1 = WIN;
|
||
|
int other = OTHER_COLOR(color);
|
||
|
/* Let's just try the case where the group with the fewest liberties
|
||
|
* has only 2, and try each atari in turn.
|
||
|
*/
|
||
|
if (alib == 2) {
|
||
|
if (trymove(alibs[0], other, "attack_either-A", astr)) {
|
||
|
defended0 = defend_both(astr, bstr);
|
||
|
popgo();
|
||
|
}
|
||
|
if (defended0
|
||
|
&& trymove(alibs[1], other, "attack_either-B", astr)) {
|
||
|
defended1 = defend_both(astr, bstr);
|
||
|
popgo();
|
||
|
}
|
||
|
}
|
||
|
/* The second string is possibly also short in liberties.
|
||
|
* Let's try to improve the result.
|
||
|
*/
|
||
|
if (defended0 > 0 && defended1 > 0
|
||
|
&& findlib(bstr, 2, blibs) == 2) {
|
||
|
defended0 = gg_min(defended0, defended1);
|
||
|
defended1 = defended0;
|
||
|
|
||
|
/* We may get here even if alib==1, in case there is a snapback.
|
||
|
* To avoid referencing uninitialized memory in this case we
|
||
|
* explicitly set alibs[1] to NO_MOVE.
|
||
|
*/
|
||
|
if (alib == 1)
|
||
|
alibs[1] = NO_MOVE;
|
||
|
|
||
|
if (blibs[0] != alibs[0] && blibs[0] != alibs[1]
|
||
|
&& trymove(blibs[0], other, "attack_either-C", bstr)) {
|
||
|
int defended = defend_both(astr, bstr);
|
||
|
defended0 = gg_min(defended0, defended);
|
||
|
popgo();
|
||
|
}
|
||
|
if (defended0
|
||
|
&& blibs[1] != alibs[0] && blibs[1] != alibs[1]
|
||
|
&& trymove(blibs[1], other, "attack_either-D", bstr)) {
|
||
|
int defended = defend_both(astr, bstr);
|
||
|
defended1 = gg_min(defended1, defended);
|
||
|
popgo();
|
||
|
}
|
||
|
}
|
||
|
return REVERSE_RESULT(gg_min(defended0, defended1));
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* defend_both(astr, bstr) returns true if both the strings (astr)
|
||
|
* and (bstr) can be defended simultaneously or if there is no attack.
|
||
|
* A typical application for this is in connection patterns, where
|
||
|
* after a cut it's necessary to defend both cutting stones.
|
||
|
*
|
||
|
* FIXME: The current implementation only makes halfhearted
|
||
|
* attempts to find coordinated defense moves. A proper implementation
|
||
|
* would require some serious reading.
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
defend_both(int astr, int bstr)
|
||
|
{
|
||
|
int a_threatened = 0;
|
||
|
int b_threatened = 0;
|
||
|
int a_savepos;
|
||
|
int b_savepos;
|
||
|
int acode = 0;
|
||
|
int dcode = 0;
|
||
|
|
||
|
int color = board[astr];
|
||
|
ASSERT1(IS_STONE(color) , astr);
|
||
|
ASSERT1(color == board[bstr], bstr);
|
||
|
|
||
|
/* This probably helps here too...
|
||
|
* (see attack_either)
|
||
|
*/
|
||
|
if (countlib(astr) > countlib(bstr)) {
|
||
|
int t = astr;
|
||
|
astr = bstr;
|
||
|
bstr = t;
|
||
|
}
|
||
|
|
||
|
attack_and_defend(astr, &acode, NULL, &dcode, &a_savepos);
|
||
|
if (acode != 0) {
|
||
|
a_threatened = 1;
|
||
|
if (dcode != WIN)
|
||
|
return 0; /* (astr) already lost */
|
||
|
}
|
||
|
|
||
|
attack_and_defend(bstr, &acode, NULL, &dcode, &b_savepos);
|
||
|
if (acode != 0) {
|
||
|
b_threatened = 1;
|
||
|
if (dcode != WIN)
|
||
|
return 0; /* (bstr) already lost */
|
||
|
}
|
||
|
|
||
|
/* Neither string can be attacked or only one of them, in which case
|
||
|
* we have time to save it.
|
||
|
*/
|
||
|
if (!a_threatened || !b_threatened)
|
||
|
return WIN;
|
||
|
|
||
|
/* If both strings are threatened we assume that one will become lost,
|
||
|
* unless find_defense() happened to return the same defense point for
|
||
|
* both (which e.g. may happen if they are in fact the same string).
|
||
|
* This is still a bit too pessimistic, as there may be one move which
|
||
|
* saves both strings. To do this right we should try each move which
|
||
|
* defends either string and see if it also defends the other string.
|
||
|
*/
|
||
|
|
||
|
if (a_savepos == b_savepos)
|
||
|
return WIN; /* Both strings can be attacked but also defended
|
||
|
* by one move. */
|
||
|
|
||
|
/* We also try each of the returned defense points and see whether
|
||
|
* the other string can still be attacked. This still gives a
|
||
|
* somewhat pessimistic estimation.
|
||
|
*/
|
||
|
|
||
|
if (trymove(a_savepos, color, "defend_both-A", astr)) {
|
||
|
if (board[bstr] && !attack(bstr, NULL)) {
|
||
|
popgo();
|
||
|
return WIN;
|
||
|
}
|
||
|
popgo();
|
||
|
}
|
||
|
|
||
|
if (trymove(b_savepos, color, "defend_both-B", bstr)) {
|
||
|
if (board[astr] && !attack(astr, NULL)) {
|
||
|
popgo();
|
||
|
return WIN;
|
||
|
}
|
||
|
popgo();
|
||
|
}
|
||
|
|
||
|
/* The next improvement is to try to attack a common adjacent string. */
|
||
|
{
|
||
|
int adjs1[MAXCHAIN];
|
||
|
int neighbors1;
|
||
|
int adjs2[MAXCHAIN];
|
||
|
int neighbors2;
|
||
|
int r;
|
||
|
int s;
|
||
|
int epos;
|
||
|
int fpos;
|
||
|
|
||
|
neighbors1 = chainlinks(astr, adjs1);
|
||
|
neighbors2 = chainlinks(bstr, adjs2);
|
||
|
|
||
|
for (r = 0; r < neighbors1; r++) {
|
||
|
epos = adjs1[r];
|
||
|
if (countlib(epos) <= 4
|
||
|
&& (epos != a_savepos)
|
||
|
&& (epos != b_savepos)) {
|
||
|
/* Is (epos) also adjacent to (bstr)? */
|
||
|
for (s = 0; s < neighbors2; s++) {
|
||
|
if (adjs2[s] == adjs1[r])
|
||
|
break;
|
||
|
}
|
||
|
if (s == neighbors2)
|
||
|
continue; /* No, it wasn't. */
|
||
|
|
||
|
if (attack(epos, &fpos)) {
|
||
|
if (trymove(fpos, color, "defend_both-C", astr)) {
|
||
|
if (board[astr] && board[bstr]
|
||
|
&& !attack(astr, NULL)
|
||
|
&& !attack(bstr, NULL)) {
|
||
|
popgo();
|
||
|
return WIN;
|
||
|
}
|
||
|
popgo();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Both strings can be attacked but we have only time to defend one. */
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* break_through(apos, bpos, cpos) returns WIN if a position can
|
||
|
* succesfully be broken through and CUT if it can be cut. The position
|
||
|
* is assumed to have the shape (the colors may be reversed)
|
||
|
*
|
||
|
* .O. dbe
|
||
|
* OXO aFc
|
||
|
*
|
||
|
* It is X to move and try to capture at least one of a, b, and c. If
|
||
|
* this succeeds, X is said to have broken through the position.
|
||
|
* Otherwise X may try to cut through the position, which means
|
||
|
* keeping F safe and getting a tactically safe string at either d or
|
||
|
* e.
|
||
|
*
|
||
|
* Important notice: a, b, and c must be given in the correct order.
|
||
|
*
|
||
|
* FIXME: The reading involved here can most likely be improved.
|
||
|
*
|
||
|
* FIXME: We need to take ko results properly into account.
|
||
|
*/
|
||
|
|
||
|
static int
|
||
|
break_through_helper(int apos, int bpos, int cpos,
|
||
|
int dpos, int epos, int Fpos,
|
||
|
int color, int other);
|
||
|
|
||
|
int
|
||
|
break_through(int apos, int bpos, int cpos)
|
||
|
{
|
||
|
int color = board[apos];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
|
||
|
int dpos;
|
||
|
int epos;
|
||
|
int Fpos;
|
||
|
int gpos;
|
||
|
|
||
|
int success = 0;
|
||
|
int success2 = 0;
|
||
|
|
||
|
/* Basic sanity checking. */
|
||
|
ASSERT1(IS_STONE(color) , apos);
|
||
|
ASSERT1(color == board[bpos], bpos);
|
||
|
ASSERT1(color == board[cpos], cpos);
|
||
|
|
||
|
/* Construct the rest of the points in the pattern. */
|
||
|
Fpos = (apos + cpos) / 2; /* F midpoint between a and c. */
|
||
|
dpos = apos + bpos - Fpos; /* Use diagonal relation a+b = d+F. */
|
||
|
epos = bpos + cpos - Fpos; /* Use diagonal relation b+c = e+F. */
|
||
|
|
||
|
/* More sanity checking. */
|
||
|
ASSERT1(board[dpos] == EMPTY , dpos);
|
||
|
ASSERT1(board[epos] == EMPTY , epos);
|
||
|
|
||
|
/* F might already have been captured. (play_break_through_n() can't
|
||
|
* detect this.
|
||
|
*/
|
||
|
if (board[Fpos] == EMPTY)
|
||
|
return 0;
|
||
|
|
||
|
ASSERT1(board[Fpos] == other, Fpos);
|
||
|
|
||
|
/* First X tries to play at d. */
|
||
|
success = break_through_helper(apos, bpos, cpos, dpos, epos, Fpos,
|
||
|
color, other);
|
||
|
if (success == WIN)
|
||
|
return WIN;
|
||
|
|
||
|
success2 = break_through_helper(cpos, bpos, apos, epos, dpos, Fpos,
|
||
|
color, other);
|
||
|
|
||
|
if (success2 == WIN)
|
||
|
return WIN;
|
||
|
|
||
|
if (success2 == CUT)
|
||
|
success = CUT;
|
||
|
|
||
|
/* If we haven't been lucky yet, we might need to start by
|
||
|
* defending F.
|
||
|
*
|
||
|
* FIXME: The function would probably be considerably faster if we
|
||
|
* start by checking whether F needs defense. Beware of ko potential
|
||
|
* though.
|
||
|
*/
|
||
|
success2 = 0;
|
||
|
if (attack_and_defend(Fpos, NULL, NULL, NULL, &gpos)) {
|
||
|
if (trymove(gpos, other, "break_through-A", Fpos)) {
|
||
|
/* Now we let O defend his position by playing either d or e.
|
||
|
* FIXME: There may be other plausible moves too.
|
||
|
*/
|
||
|
if (trymove(dpos, color, "break_through-B", Fpos)) {
|
||
|
/* O connects at d, so X cuts at e. */
|
||
|
if (safe_move(epos, other)) {
|
||
|
success2 = CUT;
|
||
|
if (!board[cpos] || attack(cpos, NULL))
|
||
|
success2 = WIN;
|
||
|
}
|
||
|
popgo();
|
||
|
}
|
||
|
|
||
|
if (success2 > 0 && trymove(epos, color, "break_through-C", Fpos)) {
|
||
|
/* O connects at e, so X cuts at d. */
|
||
|
if (safe_move(dpos, other)) {
|
||
|
/* success2 is already WIN or CUT. */
|
||
|
if (board[apos] && !attack(apos, NULL))
|
||
|
success2 = CUT;
|
||
|
}
|
||
|
else
|
||
|
success2 = 0;
|
||
|
popgo();
|
||
|
}
|
||
|
popgo();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (success2 > 0)
|
||
|
return success2;
|
||
|
|
||
|
return success;
|
||
|
}
|
||
|
|
||
|
/* Helper function for break_through(). Since we can symmetrically
|
||
|
* start by cutting at d or e, we use the same code for both attacks,
|
||
|
* simply switching positions between the two calls.
|
||
|
*/
|
||
|
static int
|
||
|
break_through_helper(int apos, int bpos, int cpos,
|
||
|
int dpos, int epos, int Fpos,
|
||
|
int color, int other)
|
||
|
{
|
||
|
int success = 0;
|
||
|
int gpos;
|
||
|
|
||
|
if (trymove(dpos, other, "break_through_helper-A", Fpos)) {
|
||
|
/* If F can be attacked we can't start in this way. */
|
||
|
if (!attack(Fpos, NULL)) {
|
||
|
/* If d is safe too, we have at least managed to break through. */
|
||
|
if (!attack(dpos, &gpos))
|
||
|
success = CUT;
|
||
|
|
||
|
/* Too bad, d could be attacked. We let O play the attack and
|
||
|
* then try to make a second cut at e. But first we must test if
|
||
|
* O at e is sufficient to capture d.
|
||
|
*/
|
||
|
else {
|
||
|
if (trymove(epos, color, "break_through_helper-E", Fpos)) {
|
||
|
if (!board[dpos] || !find_defense(dpos, NULL)) {
|
||
|
popgo();
|
||
|
popgo();
|
||
|
return 0;
|
||
|
}
|
||
|
popgo();
|
||
|
}
|
||
|
|
||
|
if (gpos == epos) {
|
||
|
popgo();
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (trymove(gpos, color, "break_through_helper-F", Fpos)) {
|
||
|
if (trymove(epos, other, "break_through_helper-G", Fpos)) {
|
||
|
if (!attack(epos, NULL)) {
|
||
|
success = CUT;
|
||
|
/* Make sure b and c are safe. If not, back up & let O try
|
||
|
* to defend in a different way. */
|
||
|
if (board[bpos]
|
||
|
&& board[cpos]
|
||
|
&& defend_both(bpos, cpos)) {
|
||
|
/* Can't do better than CUT. */
|
||
|
popgo();
|
||
|
popgo();
|
||
|
popgo();
|
||
|
return CUT;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
/* Lost everything. (Note we ignore ko at the moment.) */
|
||
|
popgo();
|
||
|
popgo();
|
||
|
popgo();
|
||
|
return 0;
|
||
|
}
|
||
|
popgo();
|
||
|
}
|
||
|
else {
|
||
|
/* Failed to cut at all. */
|
||
|
popgo();
|
||
|
popgo();
|
||
|
return 0;
|
||
|
}
|
||
|
popgo();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* By now, we're sure a cut works, so now we can try
|
||
|
* to capture something.
|
||
|
*/
|
||
|
if (!board[apos] || !board[bpos] || !defend_both(apos, bpos))
|
||
|
success = WIN;
|
||
|
else {
|
||
|
/* Both a and b could be defended, or didn't need to be.
|
||
|
* Let's see if a move at e is sufficient for O.
|
||
|
*/
|
||
|
int attack_on_b = 0;
|
||
|
int attack_on_a = 0;
|
||
|
|
||
|
if (trymove(epos, color, "break_through_helper-B", Fpos)) {
|
||
|
if (attack(bpos, NULL))
|
||
|
attack_on_b = 1;
|
||
|
else if (attack(apos, NULL))
|
||
|
attack_on_a = 1;
|
||
|
popgo();
|
||
|
}
|
||
|
|
||
|
/* Let O find a defense and play it. */
|
||
|
if (attack_on_a || attack_on_b) {
|
||
|
int hpos = NO_MOVE;
|
||
|
|
||
|
if (((attack_on_a && find_defense(apos, &hpos))
|
||
|
|| (attack_on_b && find_defense(bpos, &hpos)))
|
||
|
&& hpos != NO_MOVE
|
||
|
&& trymove(hpos, color, "break_through_helper-C", Fpos)) {
|
||
|
/* Now we make a second cut at e, trying to capture
|
||
|
* either b or c.
|
||
|
*/
|
||
|
if (trymove(epos, other, "break_through_helper-D", Fpos)) {
|
||
|
if (!board[bpos]
|
||
|
|| !board[cpos]
|
||
|
|| !defend_both(bpos, cpos))
|
||
|
success = WIN;
|
||
|
popgo();
|
||
|
}
|
||
|
popgo();
|
||
|
}
|
||
|
else
|
||
|
success = WIN; /* This should have been covered by
|
||
|
* defend_both(), so probably unnecessary. */
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
popgo();
|
||
|
}
|
||
|
|
||
|
return success;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* ---------------------------------------------------------------- */
|
||
|
/* Threats */
|
||
|
/* ---------------------------------------------------------------- */
|
||
|
|
||
|
|
||
|
/* Return up to max_threats threats to capture the string at str.
|
||
|
* If the string is directly attackable the number of threats
|
||
|
* is reported to be 0.
|
||
|
*
|
||
|
* NOTE: You can call attack_threats with moves[] and codes[]
|
||
|
* already partly filled in. So if you want to get the
|
||
|
* threats from scratch, you have to set them to 0
|
||
|
* yourself.
|
||
|
*
|
||
|
* FIXME: Shall we report upgrades, like we can capture in ko but
|
||
|
* have a threat to capture unconditionally?
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
attack_threats(int str, int max_points, int moves[], int codes[])
|
||
|
{
|
||
|
int other;
|
||
|
int num_threats;
|
||
|
int liberties;
|
||
|
int libs[MAXLIBS];
|
||
|
int num_adj;
|
||
|
int adjs[MAXCHAIN];
|
||
|
int k;
|
||
|
int l;
|
||
|
int r;
|
||
|
|
||
|
ASSERT1(IS_STONE(board[str]), str);
|
||
|
other = OTHER_COLOR(board[str]);
|
||
|
|
||
|
/* Only handle strings with no way to capture immediately.
|
||
|
* For now, we treat ko the same as unconditionally. */
|
||
|
if (attack(str, NULL) != 0)
|
||
|
return 0;
|
||
|
|
||
|
/* This test would seem to be unnecessary since we only threaten
|
||
|
* strings with attack_code == 0, but it turns out that single
|
||
|
* stones with one liberty that can be captured, but come to
|
||
|
* live again in a snap-back get attack_code == 0.
|
||
|
*
|
||
|
* The test against 6 liberties is just an optimization.
|
||
|
*/
|
||
|
liberties = findlib(str, MAXLIBS, libs);
|
||
|
if (liberties > 1 && liberties < 6) {
|
||
|
for (k = 0; k < liberties; k++) {
|
||
|
int aa = libs[k];
|
||
|
|
||
|
/* Try to threaten on the liberty. */
|
||
|
if (trymove(aa, other, "attack_threats-A", str)) {
|
||
|
int acode = attack(str, NULL);
|
||
|
if (acode != 0)
|
||
|
movelist_change_point(aa, acode, max_points, moves, codes);
|
||
|
popgo();
|
||
|
}
|
||
|
|
||
|
/* Try to threaten on second order liberties. */
|
||
|
for (l = 0; l < 4; l++) {
|
||
|
int bb = libs[k] + delta[l];
|
||
|
|
||
|
if (!ON_BOARD(bb)
|
||
|
|| IS_STONE(board[bb])
|
||
|
|| liberty_of_string(bb, str))
|
||
|
continue;
|
||
|
|
||
|
if (trymove(bb, other, "attack_threats-B", str)) {
|
||
|
int acode = attack(str, NULL);
|
||
|
if (acode != 0)
|
||
|
movelist_change_point(bb, acode, max_points, moves, codes);
|
||
|
popgo();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Threaten to attack by saving weak neighbors. */
|
||
|
num_adj = chainlinks(str, adjs);
|
||
|
for (k = 0; k < num_adj; k++) {
|
||
|
int bb;
|
||
|
int dd; /* Defense point of weak neighbor. */
|
||
|
int acode;
|
||
|
int dcode;
|
||
|
|
||
|
attack_and_defend(adjs[k], &acode, NULL, &dcode, &dd);
|
||
|
if (acode == 0 || dcode == 0)
|
||
|
continue;
|
||
|
|
||
|
/* The strange code using r == -1 below is only avoid duplication
|
||
|
* of the code starting with "if (trymove..)" below.
|
||
|
* If r == -1 and stackp == 0 then use the defense point what we got from
|
||
|
* attack_and_defend above. Otherwise step through all defense points.
|
||
|
*/
|
||
|
for (r = -1; r < max_points; r++) {
|
||
|
if (stackp == 0) {
|
||
|
if (r == -1)
|
||
|
continue;
|
||
|
if (worm[adjs[k]].defense_codes[r] == 0)
|
||
|
break;
|
||
|
bb = worm[adjs[k]].defense_points[r];
|
||
|
}
|
||
|
else {
|
||
|
if (r == -1)
|
||
|
bb = dd;
|
||
|
else
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* Test the move and see if it is a threat. */
|
||
|
if (trymove(bb, other, "attack_threats-C", str)) {
|
||
|
if (board[str] == EMPTY)
|
||
|
acode = WIN;
|
||
|
else
|
||
|
acode = attack(str, NULL);
|
||
|
if (acode != 0)
|
||
|
movelist_change_point(bb, acode, max_points, moves, codes);
|
||
|
popgo();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Return actual number of threats found regardless of attack code. */
|
||
|
if (codes[max_points - 1] > 0)
|
||
|
return max_points;
|
||
|
for (num_threats = 0; num_threats < max_points; num_threats++)
|
||
|
if (codes[num_threats] == 0)
|
||
|
break;
|
||
|
return num_threats;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* ================================================================ */
|
||
|
/* Defensive functions */
|
||
|
/* ================================================================ */
|
||
|
|
||
|
|
||
|
/* Like find_defense, but takes the komaster argument. If the
|
||
|
* opponent is reading functions will not try
|
||
|
* to take ko.
|
||
|
*/
|
||
|
|
||
|
static int
|
||
|
do_find_defense(int str, int *move)
|
||
|
{
|
||
|
int xpos = NO_MOVE;
|
||
|
int dcode = 0;
|
||
|
int liberties;
|
||
|
int retval;
|
||
|
|
||
|
SETUP_TRACE_INFO("find_defense", str);
|
||
|
|
||
|
/* We first check if the number of liberties is larger than four. In
|
||
|
* that case we don't cache the result and to avoid needlessly
|
||
|
* storing the position in the hash table, we must do this test
|
||
|
* before we look for cached results.
|
||
|
*/
|
||
|
str = find_origin(str);
|
||
|
liberties = countlib(str);
|
||
|
|
||
|
if (liberties > 4
|
||
|
|| (liberties == 4 && stackp > fourlib_depth)
|
||
|
|| (liberties == 3 && stackp > depth)) {
|
||
|
/* No need to cache the result in these cases. */
|
||
|
SGFTRACE(0, WIN, "too many liberties or stackp > depth");
|
||
|
if (move)
|
||
|
*move = 0;
|
||
|
return WIN;
|
||
|
}
|
||
|
|
||
|
/* Set "killer move" up. This move (if set) was successful in
|
||
|
* another variation, so it is reasonable to try it now. However,
|
||
|
* we only do this if the string has at least 3 liberties -
|
||
|
* otherwise the situation changes too much from variation to
|
||
|
* variation.
|
||
|
*/
|
||
|
if (liberties > 2 && move)
|
||
|
xpos = *move;
|
||
|
|
||
|
if (stackp <= depth
|
||
|
&& tt_get(&ttable, FIND_DEFENSE, str, NO_MOVE, depth - stackp, NULL,
|
||
|
&retval, NULL, &xpos) == 2) {
|
||
|
/* Note that if return value is 1 (too small depth), the move will
|
||
|
* still be used for move ordering.
|
||
|
*/
|
||
|
TRACE_CACHED_RESULT(retval, xpos);
|
||
|
SGFTRACE(xpos, retval, "cached");
|
||
|
if (move)
|
||
|
*move = xpos;
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
if (liberties == 1)
|
||
|
dcode = defend1(str, &xpos);
|
||
|
else if (liberties == 2)
|
||
|
dcode = defend2(str, &xpos);
|
||
|
else if (liberties == 3)
|
||
|
dcode = defend3(str, &xpos);
|
||
|
else if (liberties == 4)
|
||
|
dcode = defend4(str, &xpos);
|
||
|
|
||
|
if (dcode) {
|
||
|
READ_RETURN(FIND_DEFENSE, str, depth - stackp, move, xpos, dcode);
|
||
|
}
|
||
|
|
||
|
READ_RETURN0(FIND_DEFENSE, str, depth - stackp);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Determine if a `move' by `color' allows under-the-stones tesuji
|
||
|
* a.k.a. "big snapback". Here is an example:
|
||
|
*
|
||
|
* |XXXX...
|
||
|
* |XXOOXXX
|
||
|
* |OOOXOOX
|
||
|
* |..O*OOX
|
||
|
* +-------
|
||
|
*
|
||
|
* Even though the move at '*' allows black to capture four white
|
||
|
* stones, white can later recapture black stones and create a second
|
||
|
* eye. This is very similar to a snapback.
|
||
|
*
|
||
|
* This function returns true if a move creates a string of with two
|
||
|
* liberties, which can, however, be instantly recaptured by opponent.
|
||
|
* It is actually not required that the move captures something. If
|
||
|
* the caller needs captures, it should check for them itself.
|
||
|
*/
|
||
|
static int
|
||
|
allows_under_the_stones_tesuji(int move, int color)
|
||
|
{
|
||
|
int result = 0;
|
||
|
SGFTree *save_sgf_dumptree;
|
||
|
int save_count_variations;
|
||
|
|
||
|
if (accuratelib(move, color, 3, NULL) != 2)
|
||
|
return 0;
|
||
|
|
||
|
save_sgf_dumptree = sgf_dumptree;
|
||
|
save_count_variations = count_variations;
|
||
|
|
||
|
sgf_dumptree = NULL;
|
||
|
count_variations = 0;
|
||
|
|
||
|
if (trymove(move, color, "allows_under_the_stones_tesuji", NO_MOVE)) {
|
||
|
int libs[2];
|
||
|
|
||
|
findlib(move, 2, libs);
|
||
|
if ((!is_self_atari(libs[0], color)
|
||
|
&& accuratelib(libs[1], OTHER_COLOR(color), 3, NULL) <= 2)
|
||
|
|| (!is_self_atari(libs[1], color)
|
||
|
&& accuratelib(libs[0], OTHER_COLOR(color), 3, NULL) <= 2))
|
||
|
result = 1;
|
||
|
|
||
|
popgo();
|
||
|
}
|
||
|
|
||
|
sgf_dumptree = save_sgf_dumptree;
|
||
|
count_variations = save_count_variations;
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Called by the defendN functions. Don't think too much if there's
|
||
|
* an easy way to get enough liberties.
|
||
|
*/
|
||
|
static int
|
||
|
fast_defense(int str, int liberties, int *libs, int *move)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int j, k, l;
|
||
|
int goal_liberties = (stackp < fourlib_depth ? 5 : 4);
|
||
|
int adj, adjs[MAXCHAIN];
|
||
|
|
||
|
/* We would like to initialize liberty_mark to -1, but some
|
||
|
* compilers warn, quite correctly, that -1 is not an unsigned
|
||
|
* number.
|
||
|
*/
|
||
|
static unsigned liberty_mark = ~0U;
|
||
|
static unsigned lm[BOARDMAX];
|
||
|
|
||
|
ASSERT1(libs != NULL, str);
|
||
|
ASSERT1(move != NULL, str);
|
||
|
|
||
|
for (k = 0; k < liberties; k++) {
|
||
|
/* accuratelib() seems to be more efficient than fastlib() here,
|
||
|
* probably because it catches more cases.
|
||
|
*/
|
||
|
if (accuratelib(libs[k], color, goal_liberties, NULL) >= goal_liberties) {
|
||
|
*move = libs[k];
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Check the cases where an opponent neighbor string is in
|
||
|
* atari.
|
||
|
*/
|
||
|
adj = chainlinks2(str, adjs, 1);
|
||
|
for (j = 0; j < adj; j++) {
|
||
|
int lib;
|
||
|
int missing = goal_liberties - liberties;
|
||
|
int total = 0;
|
||
|
int adj2, adjs2[MAXCHAIN];
|
||
|
int alib, alibs[MAXLIBS];
|
||
|
int num_adjacent_stones;
|
||
|
|
||
|
findlib(adjs[j], 1, &lib);
|
||
|
/* We aren't interested in ko (at this stage). And playing
|
||
|
* our own last liberty to capture is prone to snapbacks,
|
||
|
* so better let the 'normal' reading routines do the job.
|
||
|
*/
|
||
|
if ((liberties == 1 && lib == libs[0]
|
||
|
&& countstones(adjs[j]) <= 2)
|
||
|
|| is_ko(lib, color, NULL))
|
||
|
continue;
|
||
|
|
||
|
/* Would the capture already gain enough liberties ?
|
||
|
* No need to test the case if the move is one of our liberties,
|
||
|
* it has already been done in the first loop of this function.
|
||
|
*/
|
||
|
num_adjacent_stones = count_adjacent_stones(adjs[j], str, missing);
|
||
|
if (!liberty_of_string(lib, str)
|
||
|
&& num_adjacent_stones >= missing) {
|
||
|
*move = lib;
|
||
|
return 1;
|
||
|
}
|
||
|
ASSERT1(num_adjacent_stones >= 1, str);
|
||
|
|
||
|
/* What is the total number of liberties of the friendly strings around
|
||
|
* the lunch?
|
||
|
*/
|
||
|
if (++liberty_mark == 0) {
|
||
|
memset(lm, 0, sizeof(lm));
|
||
|
liberty_mark++;
|
||
|
}
|
||
|
/* Loop over all neighbors of the lunch. */
|
||
|
adj2 = chainlinks(adjs[j], adjs2);
|
||
|
for (k = 0; k < adj2; k++) {
|
||
|
/* Loop over all liberties of the neighbor. */
|
||
|
alib = findlib(adjs2[k], MAXLIBS, alibs);
|
||
|
for (l = 0; l < alib; l++) {
|
||
|
if (lm[alibs[l]] != liberty_mark) {
|
||
|
lm[alibs[l]] = liberty_mark;
|
||
|
total++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* The captured string is treated as common liberties, and
|
||
|
* some adjustements are made :
|
||
|
* - we're adding a stone for capturing the lunch (-1)
|
||
|
* - opponent might be able to remove a liberty (-1)
|
||
|
* - and possibly force us to connect (-1)
|
||
|
* - reduce us by one more liberty with a throw-in; this
|
||
|
* is only possible if there is only one adjacent stone in the
|
||
|
* lunch to the string (-1)
|
||
|
* Probably there are more damezumari-type cases, but as a heuristic,
|
||
|
* it seems good enough.
|
||
|
*/
|
||
|
total += countstones(adjs[j]) - 2;
|
||
|
if (lm[lib] == liberty_mark)
|
||
|
total--;
|
||
|
if (num_adjacent_stones == 1)
|
||
|
total--;
|
||
|
|
||
|
if (total >= goal_liberties) {
|
||
|
/* One case when this code can give a false defense is an
|
||
|
* under-the-stones tesuji or "big snapback." See reading:199
|
||
|
* for an example. While this position is probably very rare,
|
||
|
* it is nice to make GNU Go understand "neat" tesujis.
|
||
|
*/
|
||
|
if (liberties == 1 && lib == libs[0]
|
||
|
&& allows_under_the_stones_tesuji(lib, color)) {
|
||
|
/* This is a bad "fast defense". */
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
*move = lib;
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* If str points to a string with exactly one liberty, defend1
|
||
|
* determines whether it can be saved by extending or capturing
|
||
|
* a boundary chain having one liberty. The function returns WIN if the string
|
||
|
* can be saved, otherwise 0. It returns KO_A or KO_B if it can be saved,
|
||
|
* conditioned on ko. Returns KO_A if it can be saved provided (color) is
|
||
|
* willing to ignore any ko threat. Returns KO_B if it can be saved if (color)
|
||
|
* has a ko threat which must be answered.
|
||
|
*
|
||
|
* The pair defend1-attack2 call each other recursively to
|
||
|
* read situations such as ladders. They read all ladders to the end.
|
||
|
* If the reading ply (stackp) is deeper than the deep-reading cutoff
|
||
|
* parameter depth, whose default value DEPTH is defined in gnugo.h, then a
|
||
|
* string is assumed alive if it can get 3 liberties. When
|
||
|
* fourlib_depth < stackp < depth, a string is considered alive if it can get
|
||
|
* four liberties. When stackp < fourlib_depth, it is considered alive
|
||
|
* if it can get 5 liberties.
|
||
|
* */
|
||
|
|
||
|
static int
|
||
|
defend1(int str, int *move)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int xpos;
|
||
|
int lib;
|
||
|
struct reading_moves moves;
|
||
|
int savemove = 0;
|
||
|
int savecode = 0;
|
||
|
int liberties;
|
||
|
int k;
|
||
|
|
||
|
SETUP_TRACE_INFO("defend1", str);
|
||
|
reading_node_counter++;
|
||
|
|
||
|
ASSERT1(IS_STONE(board[str]), str);
|
||
|
ASSERT1(countlib(str) == 1, str);
|
||
|
|
||
|
/* lib will be the liberty of the string. */
|
||
|
liberties = findlib(str, 1, &lib);
|
||
|
ASSERT1(liberties == 1, str);
|
||
|
|
||
|
if (fast_defense(str, liberties, &lib, &xpos))
|
||
|
RETURN_RESULT(WIN, xpos, move, "fast defense");
|
||
|
|
||
|
/* Collect moves to try in the first batch.
|
||
|
* 1. First order liberty.
|
||
|
* 2. Chain breaking moves.
|
||
|
* 3. Moves to set up a snapback.
|
||
|
*/
|
||
|
moves.pos[0] = lib;
|
||
|
moves.score[0] = 0;
|
||
|
moves.message[0] = "liberty";
|
||
|
moves.num = 1;
|
||
|
moves.num_tried = 0;
|
||
|
|
||
|
break_chain_moves(str, &moves);
|
||
|
set_up_snapback_moves(str, lib, &moves);
|
||
|
|
||
|
order_moves(str, &moves, color, read_function_name, *move);
|
||
|
DEFEND_TRY_MOVES(0, NULL);
|
||
|
|
||
|
/* If the string is a single stone and a capture would give a ko,
|
||
|
* try to defend it with ko by backfilling.
|
||
|
*
|
||
|
* FIXME: What is an example of this? Is it correct that the
|
||
|
* return value is WIN and not KO_A or KO_B?
|
||
|
*/
|
||
|
if (stackp <= backfill_depth
|
||
|
&& countstones(str) == 1
|
||
|
&& is_ko(lib, other, NULL)) {
|
||
|
int libs2[6];
|
||
|
liberties = approxlib(lib, color, 6, libs2);
|
||
|
if (liberties <= 5) {
|
||
|
for (k = 0; k < liberties; k++) {
|
||
|
int apos = libs2[k];
|
||
|
if ((liberties == 1 || !is_self_atari(apos, other))
|
||
|
&& trymove(apos, color, "defend1-C", str)) {
|
||
|
int acode = do_attack(str, NULL);
|
||
|
popgo();
|
||
|
CHECK_RESULT(savecode, savemove, acode, apos, move, "backfilling");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
RETURN_RESULT(savecode, savemove, move, "saved move");
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/* If str points to a group with two liberties, defend2 determines
|
||
|
* whether the group can be saved by extending, or by capturing part of
|
||
|
* its surrounding chain. A group is considered safe if either part of
|
||
|
* the surrounding chain may be captured, or if it can get 3
|
||
|
* liberties. It is presumed that the opponent could kill if tenuki.
|
||
|
* If both extensions work, it prefers the one which maximizes
|
||
|
* liberties.
|
||
|
*
|
||
|
* *move returns the move to save the stones.
|
||
|
*/
|
||
|
|
||
|
static int
|
||
|
defend2(int str, int *move)
|
||
|
{
|
||
|
int color, other;
|
||
|
int xpos = NO_MOVE;
|
||
|
int liberties;
|
||
|
int libs[2];
|
||
|
int liberties2;
|
||
|
int libs2[6];
|
||
|
struct reading_moves moves;
|
||
|
int savemove = 0;
|
||
|
int savecode = 0;
|
||
|
int k;
|
||
|
int r;
|
||
|
int suggest_move = NO_MOVE;
|
||
|
int string_size;
|
||
|
int be_aggressive;
|
||
|
|
||
|
SETUP_TRACE_INFO("defend2", str);
|
||
|
reading_node_counter++;
|
||
|
|
||
|
color = board[str];
|
||
|
other = OTHER_COLOR(color);
|
||
|
|
||
|
ASSERT1(IS_STONE(board[str]), str);
|
||
|
ASSERT1(countlib(str) == 2, str);
|
||
|
|
||
|
liberties = findlib(str, 2, libs);
|
||
|
|
||
|
if (fast_defense(str, liberties, libs, &xpos))
|
||
|
RETURN_RESULT(WIN, xpos, move, "fast defense");
|
||
|
|
||
|
/* Collect moves to try in the first batch.
|
||
|
* 1. First order liberties.
|
||
|
* 2. Chain breaking moves.
|
||
|
* 3. Second order liberties moving up from first line to second.
|
||
|
* 4. Edge clamps.
|
||
|
*/
|
||
|
moves.num = 0;
|
||
|
moves.num_tried = 0;
|
||
|
|
||
|
/* We don't want to play self-atari liberties, unless the string is a
|
||
|
* single stone (in which case it might be a snapback move). Sacrifices
|
||
|
* might be good moves, but not in tactical reading.
|
||
|
*/
|
||
|
string_size = countstones(str);
|
||
|
if (string_size == 1 || !is_self_atari(libs[0], color))
|
||
|
ADD_CANDIDATE_MOVE(libs[0], 0, moves, "liberty");
|
||
|
if (string_size == 1 || !is_self_atari(libs[1], color))
|
||
|
ADD_CANDIDATE_MOVE(libs[1], 0, moves, "liberty");
|
||
|
|
||
|
break_chain_moves(str, &moves);
|
||
|
break_chain2_efficient_moves(str, &moves);
|
||
|
propose_edge_moves(str, libs, liberties, &moves, color);
|
||
|
edge_clamp_moves(str, &moves);
|
||
|
|
||
|
if (stackp <= depth) {
|
||
|
for (k = 0; k < liberties; k++)
|
||
|
special_rescue_moves(str, libs[k], &moves);
|
||
|
bamboo_rescue_moves(str, liberties, libs, &moves);
|
||
|
}
|
||
|
|
||
|
if (stackp <= backfill_depth)
|
||
|
special_rescue2_moves(str, libs, &moves);
|
||
|
|
||
|
order_moves(str, &moves, color, read_function_name, *move);
|
||
|
DEFEND_TRY_MOVES(0, &suggest_move);
|
||
|
|
||
|
/* Look for backfilling moves. */
|
||
|
for (k = 0; k < liberties; k++) {
|
||
|
if (is_self_atari(libs[k], other)) {
|
||
|
liberties2 = approxlib(libs[k], color, 6, libs2);
|
||
|
/* Note: liberties2 must be smaller than 5, otherwise libs[k] had been
|
||
|
* a direct defense.
|
||
|
*/
|
||
|
for (r = 0; r < liberties2; r++) {
|
||
|
xpos = libs2[r];
|
||
|
/* If the newly placed stone would be in atari, but not a single
|
||
|
* stone, we don't even try.
|
||
|
*/
|
||
|
if (!is_self_atari(xpos, color)
|
||
|
&& has_neighbor(xpos, color))
|
||
|
ADD_CANDIDATE_MOVE(xpos, 0, moves, "backfill-A");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
liberties2 = approxlib(libs[k], other, 3, libs2);
|
||
|
if (liberties2 <= 2) {
|
||
|
for (r = 0; r < liberties2; r++) {
|
||
|
xpos = libs2[r];
|
||
|
if (!is_self_atari(xpos, color))
|
||
|
ADD_CANDIDATE_MOVE(xpos, 0, moves, "backfill-B");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
special_rescue4_moves(str, libs, &moves);
|
||
|
|
||
|
/* Only order and test the new set of moves. */
|
||
|
order_moves(str, &moves, color, read_function_name, *move);
|
||
|
DEFEND_TRY_MOVES(0, &suggest_move);
|
||
|
|
||
|
/* If we haven't found any useful moves in first batches, be more
|
||
|
* aggressive in break_chain[23]_moves().
|
||
|
*/
|
||
|
be_aggressive = (moves.num == 0);
|
||
|
|
||
|
if (stackp <= superstring_depth)
|
||
|
superstring_break_chain_moves(str, 4, &moves);
|
||
|
|
||
|
/* If nothing else works, we try playing a liberty of the
|
||
|
* super_string.
|
||
|
*/
|
||
|
if (stackp <= superstring_depth) {
|
||
|
superstring_moves(str, &moves, 3, 0);
|
||
|
squeeze_moves(str, &moves);
|
||
|
}
|
||
|
|
||
|
break_chain2_defense_moves(str, &moves, be_aggressive);
|
||
|
|
||
|
if (stackp <= backfill_depth)
|
||
|
special_rescue5_moves(str, libs, &moves);
|
||
|
|
||
|
if (stackp <= break_chain_depth
|
||
|
|| (be_aggressive && stackp <= backfill_depth))
|
||
|
break_chain3_moves(str, &moves, be_aggressive);
|
||
|
|
||
|
if (be_aggressive && stackp <= backfill_depth)
|
||
|
break_chain4_moves(str, &moves, be_aggressive);
|
||
|
|
||
|
/* Only order and test the new set of moves. */
|
||
|
order_moves(str, &moves, color, read_function_name, *move);
|
||
|
DEFEND_TRY_MOVES(0, &suggest_move);
|
||
|
|
||
|
RETURN_RESULT(savecode, savemove, move, "saved move");
|
||
|
}
|
||
|
|
||
|
|
||
|
/* defend3(str, *move) attempts to find a move rescuing the
|
||
|
* string at (str) with 3 liberties. If such a move can be found,
|
||
|
* it returns true and the saving move in *move.
|
||
|
*/
|
||
|
|
||
|
static int
|
||
|
defend3(int str, int *move)
|
||
|
{
|
||
|
int color;
|
||
|
int xpos = NO_MOVE;
|
||
|
int liberties;
|
||
|
int libs[3];
|
||
|
struct reading_moves moves;
|
||
|
int savemove = 0;
|
||
|
int savecode = 0;
|
||
|
int k;
|
||
|
int suggest_move = NO_MOVE;
|
||
|
|
||
|
SETUP_TRACE_INFO("defend3", str);
|
||
|
reading_node_counter++;
|
||
|
|
||
|
color = board[str];
|
||
|
|
||
|
ASSERT1(IS_STONE(board[str]), str);
|
||
|
ASSERT1(countlib(str) == 3, str);
|
||
|
|
||
|
liberties = findlib(str, 3, libs);
|
||
|
|
||
|
if (fast_defense(str, liberties, libs, &xpos))
|
||
|
RETURN_RESULT(WIN, xpos, move, "fast defense");
|
||
|
|
||
|
/* Collect moves to try in the first batch.
|
||
|
* 1. First order liberties.
|
||
|
* 2. Chain breaking moves.
|
||
|
* 3. Second order liberties moving up from first line to second.
|
||
|
* 4. Edge clamps.
|
||
|
*/
|
||
|
for (k = 0; k < liberties; k++) {
|
||
|
moves.pos[k] = libs[k];
|
||
|
moves.score[k] = 0;
|
||
|
moves.message[k] = "liberty";
|
||
|
}
|
||
|
|
||
|
moves.num = liberties;
|
||
|
moves.num_tried = 0;
|
||
|
|
||
|
break_chain_moves(str, &moves);
|
||
|
break_chain2_efficient_moves(str, &moves);
|
||
|
propose_edge_moves(str, libs, liberties, &moves, color);
|
||
|
edge_clamp_moves(str, &moves);
|
||
|
|
||
|
if (stackp <= backfill2_depth)
|
||
|
hane_rescue_moves(str, libs, &moves);
|
||
|
|
||
|
order_moves(str, &moves, color, read_function_name, *move);
|
||
|
DEFEND_TRY_MOVES(1, &suggest_move);
|
||
|
|
||
|
/* This looks a little too expensive. */
|
||
|
#if 0
|
||
|
/* Look for backfilling moves. */
|
||
|
if (stackp <= backfill_depth) {
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int liberties2;
|
||
|
int libs2[6];
|
||
|
int r;
|
||
|
int s;
|
||
|
for (k = 0; k < liberties; k++) {
|
||
|
if (is_self_atari(libs[k], other)) {
|
||
|
liberties2 = approxlib(libs[k], color, 6, libs2);
|
||
|
for (r = 0; r < liberties2; r++) {
|
||
|
xpos = libs2[r];
|
||
|
/* Don't reconsider previously tested moves. */
|
||
|
for (s = 0; s < moves.num; s++)
|
||
|
if (xpos == moves.pos[s])
|
||
|
break;
|
||
|
if (s < moves.num)
|
||
|
continue;
|
||
|
|
||
|
if (trymove(xpos, color, "defend3-D", str)) {
|
||
|
int acode;
|
||
|
/* If the newly placed stone is in atari, we give up
|
||
|
* without fight.
|
||
|
*/
|
||
|
if (countlib(xpos) == 1)
|
||
|
acode = WIN;
|
||
|
else
|
||
|
acode = do_attack(str, NULL);
|
||
|
|
||
|
popgo();
|
||
|
CHECK_RESULT(savecode, savemove, acode, xpos, move,
|
||
|
"backfill effective");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
liberties2 = approxlib(libs[k], other, 3, libs2);
|
||
|
if (liberties2 <= 3) {
|
||
|
for (r = 0; r < liberties2; r++) {
|
||
|
xpos = libs2[r];
|
||
|
/* Don't reconsider previously tested moves. */
|
||
|
for (s = 0; s < moves.num; s++)
|
||
|
if (xpos == moves.pos[s])
|
||
|
break;
|
||
|
if (s < moves.num)
|
||
|
continue;
|
||
|
|
||
|
if (!is_self_atari(xpos, color)
|
||
|
&& trymove(xpos, color, "defend2-G", str)) {
|
||
|
int acode = do_attack(str, NULL);
|
||
|
popgo();
|
||
|
CHECK_RESULT(savecode, savemove, acode, xpos, move
|
||
|
"backfill effective");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* If nothing else works, try to defend with second order liberties. */
|
||
|
|
||
|
if (stackp <= backfill_depth)
|
||
|
special_rescue3_moves(str, libs, &moves);
|
||
|
|
||
|
if (stackp <= depth) {
|
||
|
for (k = 0; k < liberties; k++)
|
||
|
special_rescue_moves(str, libs[k], &moves);
|
||
|
bamboo_rescue_moves(str, liberties, libs, &moves);
|
||
|
}
|
||
|
|
||
|
if (get_level() >= 8 && stackp <= backfill2_depth)
|
||
|
superstring_break_chain_moves(str, 4, &moves);
|
||
|
|
||
|
if (stackp <= break_chain_depth)
|
||
|
break_chain2_defense_moves(str, &moves, 0);
|
||
|
|
||
|
if (stackp <= backfill_depth) {
|
||
|
special_rescue5_moves(str, libs, &moves);
|
||
|
special_rescue6_moves(str, libs, &moves);
|
||
|
}
|
||
|
|
||
|
/* Only order and test the new set of moves. */
|
||
|
order_moves(str, &moves, color, read_function_name, *move);
|
||
|
DEFEND_TRY_MOVES(1, &suggest_move);
|
||
|
|
||
|
/* If nothing else works, we try playing a liberty of the
|
||
|
* super_string.
|
||
|
*/
|
||
|
if (get_level() >= 8 && stackp <= backfill2_depth) {
|
||
|
superstring_moves(str, &moves, 3, 0);
|
||
|
squeeze_moves(str, &moves);
|
||
|
}
|
||
|
|
||
|
if (stackp <= break_chain_depth)
|
||
|
break_chain3_moves(str, &moves, 0);
|
||
|
|
||
|
/* Only order and test the new set of moves. */
|
||
|
order_moves(str, &moves, color, read_function_name, *move);
|
||
|
DEFEND_TRY_MOVES(1, &suggest_move);
|
||
|
|
||
|
RETURN_RESULT(savecode, savemove, move, "saved move");
|
||
|
}
|
||
|
|
||
|
|
||
|
/* defend4(str, *move) attempts to find a move rescuing the
|
||
|
* string at (str) with 4 liberties. If such a move can be found,
|
||
|
* it returns true, and if the pointer move is not NULL,
|
||
|
* then it returns the saving move in *move.
|
||
|
*/
|
||
|
|
||
|
static int
|
||
|
defend4(int str, int *move)
|
||
|
{
|
||
|
int color;
|
||
|
int xpos = NO_MOVE;
|
||
|
int liberties;
|
||
|
int libs[4];
|
||
|
struct reading_moves moves;
|
||
|
int savemove = 0;
|
||
|
int savecode = 0;
|
||
|
int k;
|
||
|
int suggest_move = NO_MOVE;
|
||
|
|
||
|
SETUP_TRACE_INFO("defend4", str);
|
||
|
reading_node_counter++;
|
||
|
|
||
|
color = board[str];
|
||
|
|
||
|
ASSERT1(IS_STONE(board[str]), str);
|
||
|
ASSERT1(countlib(str) == 4, str);
|
||
|
|
||
|
liberties = findlib(str, 4, libs);
|
||
|
|
||
|
if (fast_defense(str, liberties, libs, &xpos))
|
||
|
RETURN_RESULT(WIN, xpos, move, "fast defense");
|
||
|
|
||
|
/* Collect moves to try in the first batch.
|
||
|
* 1. First order liberties.
|
||
|
* 2. Chain breaking moves.
|
||
|
*/
|
||
|
for (k = 0; k < liberties; k++) {
|
||
|
moves.pos[k] = libs[k];
|
||
|
moves.score[k] = 0;
|
||
|
moves.message[k] = "liberty";
|
||
|
}
|
||
|
|
||
|
moves.num = liberties;
|
||
|
moves.num_tried = 0;
|
||
|
|
||
|
break_chain_moves(str, &moves);
|
||
|
break_chain2_efficient_moves(str, &moves);
|
||
|
|
||
|
if (stackp <= backfill_depth) {
|
||
|
break_chain2_defense_moves(str, &moves, 0);
|
||
|
break_chain3_moves(str, &moves, 0);
|
||
|
break_chain4_moves(str, &moves, 0);
|
||
|
#if 0
|
||
|
hane_rescue_moves(str, libs, &moves);
|
||
|
#endif
|
||
|
if (stackp <= superstring_depth)
|
||
|
superstring_moves(str, &moves, 4, 0);
|
||
|
squeeze_moves(str, &moves);
|
||
|
}
|
||
|
|
||
|
order_moves(str, &moves, color, read_function_name, *move);
|
||
|
DEFEND_TRY_MOVES(1, &suggest_move);
|
||
|
|
||
|
if (stackp <= depth) {
|
||
|
for (k = 0; k < liberties; k++)
|
||
|
special_rescue_moves(str, libs[k], &moves);
|
||
|
bamboo_rescue_moves(str, liberties, libs, &moves);
|
||
|
}
|
||
|
|
||
|
order_moves(str, &moves, color, read_function_name, *move);
|
||
|
DEFEND_TRY_MOVES(1, &suggest_move);
|
||
|
|
||
|
RETURN_RESULT(savecode, savemove, move, "saved move");
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* special_rescue_moves(str, lib, *move) is called with (str) a
|
||
|
* string having a liberty at (lib).
|
||
|
*
|
||
|
* This adds moves on a second order liberty to the list of candidate
|
||
|
* moves in the struct *moves; e.g. in shapes like:
|
||
|
*
|
||
|
* . O O X.XXO
|
||
|
* O.* or ..* or O.* or XOOXO
|
||
|
* O O O ...*.
|
||
|
* -----
|
||
|
*
|
||
|
* This will occasionally save a string where no other move will. To
|
||
|
* reduce the branching caused by these moves, we require that the
|
||
|
* opponent can be trivially captured when trying to intercept on the
|
||
|
* corresponding first order liberty.
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
special_rescue_moves(int str, int lib, struct reading_moves *moves)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int otherlib;
|
||
|
int k;
|
||
|
|
||
|
/* Use approxlib() to test for trivial capture. */
|
||
|
otherlib = approxlib(lib, other, 3, NULL);
|
||
|
if (otherlib > 2)
|
||
|
return;
|
||
|
|
||
|
/* Loop over the four neighbours of the liberty, (lib + d). */
|
||
|
for (k = 0; k < 4; k++) {
|
||
|
int d = delta[k];
|
||
|
if (board[lib + d] == EMPTY) {
|
||
|
|
||
|
/* Don't play into a self atari unless we have a potential snapback. */
|
||
|
if (is_self_atari(lib + d, color) && otherlib > 1)
|
||
|
continue;
|
||
|
|
||
|
/* Be more demanding when the string has four liberties. (Mostly
|
||
|
* because attack4() otherwise would need more move generators.)
|
||
|
* More precisely we require not only the first order liberty to
|
||
|
* become a self atari for the opponent but also one more of the
|
||
|
* neighbors of the proposed move. See reading:144 for a
|
||
|
* position where we otherwise would try to defend at D9 and
|
||
|
* attack4() then lacks move generators to stop black from
|
||
|
* continuing towards the top left corner.
|
||
|
*/
|
||
|
if (countlib(str) > 3) {
|
||
|
int r;
|
||
|
int number_protected = 0;
|
||
|
|
||
|
for (r = 0; r < 4; r++) {
|
||
|
if (board[lib + d + delta[r]] == EMPTY
|
||
|
&& approxlib(lib + d + delta[r], other, 3, NULL) < 3)
|
||
|
number_protected++;
|
||
|
if (number_protected == 2)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (number_protected < 2)
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
ADD_CANDIDATE_MOVE(lib + d, 0, *moves, "special_rescue");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* In situations like
|
||
|
*
|
||
|
* XXXXXO
|
||
|
* XO.*.O
|
||
|
* XO.O.O
|
||
|
* XXXXXO
|
||
|
*
|
||
|
* playing at * is the correct rescue move, although the opponent cannot
|
||
|
* be captured at the respective first-order liberty.
|
||
|
*/
|
||
|
static void
|
||
|
bamboo_rescue_moves(int str, int num_libs, int libs[],
|
||
|
struct reading_moves *moves)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int l1, l2;
|
||
|
|
||
|
for (l1 = 0; l1 < num_libs; l1++)
|
||
|
for (l2 = 0; l2 < num_libs; l2++) {
|
||
|
if (l1 == l2)
|
||
|
continue;
|
||
|
|
||
|
if (libs[l1] == WEST(libs[l2]) || libs[l1] == EAST(libs[l2])) {
|
||
|
if (board[SOUTH(libs[l1])] == EMPTY
|
||
|
&& board[SOUTH(libs[l2])] == color
|
||
|
&& !is_self_atari(SOUTH(libs[l1]), color))
|
||
|
ADD_CANDIDATE_MOVE(SOUTH(libs[l1]), 0, *moves, "bamboo_rescue");
|
||
|
if (board[NORTH(libs[l1])] == EMPTY
|
||
|
&& board[NORTH(libs[l2])] == color
|
||
|
&& !is_self_atari(NORTH(libs[l1]), color))
|
||
|
ADD_CANDIDATE_MOVE(NORTH(libs[l1]), 0, *moves, "bamboo_rescue");
|
||
|
}
|
||
|
else if (libs[l1] == NORTH(libs[l2]) || libs[l1] == SOUTH(libs[l2])) {
|
||
|
if (board[WEST(libs[l1])] == EMPTY
|
||
|
&& board[WEST(libs[l2])] == color
|
||
|
&& !is_self_atari(WEST(libs[l1]), color))
|
||
|
ADD_CANDIDATE_MOVE(WEST(libs[l1]), 0, *moves, "bamboo_rescue");
|
||
|
if (board[EAST(libs[l1])] == EMPTY
|
||
|
&& board[EAST(libs[l2])] == color
|
||
|
&& !is_self_atari(EAST(libs[l1]), color))
|
||
|
ADD_CANDIDATE_MOVE(EAST(libs[l1]), 0, *moves, "bamboo_rescue");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* In a situation like this:
|
||
|
*
|
||
|
* OOXXXX the following code can find the
|
||
|
* .OXOOX defensive move at 'c'.
|
||
|
* .cO.OX
|
||
|
* .X.OOX
|
||
|
* ------
|
||
|
*
|
||
|
* OOXXXX It also can find more general moves like 'c' here.
|
||
|
* .OXOOX
|
||
|
* cXO.OX
|
||
|
* ...OOX
|
||
|
* ------
|
||
|
*/
|
||
|
static void
|
||
|
special_rescue2_moves(int str, int libs[2], struct reading_moves *moves)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int newlibs[4];
|
||
|
int liberties;
|
||
|
int newstr;
|
||
|
int k, r, s;
|
||
|
|
||
|
for (r = 0; r < 2; r++) {
|
||
|
/* Let alib be one of the liberties and require it to be suicide
|
||
|
* for the opponent.
|
||
|
*/
|
||
|
int alib = libs[r];
|
||
|
if (!is_suicide(alib, other))
|
||
|
continue;
|
||
|
|
||
|
for (k = 0; k < 4; k++) {
|
||
|
if (board[alib + delta[k]] == color
|
||
|
&& !same_string(alib + delta[k], str)) {
|
||
|
newstr = alib + delta[k];
|
||
|
liberties = findlib(newstr, 4, newlibs);
|
||
|
|
||
|
for (s = 0; s < liberties && s < 4; s++) {
|
||
|
if (!is_self_atari(newlibs[s], color))
|
||
|
ADD_CANDIDATE_MOVE(newlibs[s], 0, *moves, "special_rescue2");
|
||
|
}
|
||
|
break_chain_moves(newstr, moves);
|
||
|
break_chain2_efficient_moves(newstr, moves);
|
||
|
edge_clamp_moves(newstr, moves);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* In a situation like this:
|
||
|
*
|
||
|
* ...X.XXO
|
||
|
* .XXXOOXO
|
||
|
* XXOO.OXO the following code can find the
|
||
|
* .O..X.*. defensive move at '*'.
|
||
|
* --------
|
||
|
*
|
||
|
* OXO cde
|
||
|
* .*. afg
|
||
|
* --- b--
|
||
|
*/
|
||
|
static void
|
||
|
special_rescue3_moves(int str, int libs[3], struct reading_moves *moves)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int apos, bpos, cpos, dpos, epos, fpos, gpos;
|
||
|
int k, l, r;
|
||
|
|
||
|
ASSERT1(countlib(str) == 3, str);
|
||
|
|
||
|
for (r = 0; r < 3; r++) {
|
||
|
/* Let (apos) be one of the three liberties. */
|
||
|
apos = libs[r];
|
||
|
/* Try to find the configuration above. */
|
||
|
for (k = 0; k < 4; k++) {
|
||
|
bpos = apos + delta[k];
|
||
|
if (ON_BOARD(bpos))
|
||
|
continue;
|
||
|
|
||
|
cpos = apos - delta[k];
|
||
|
if (board[cpos] != color)
|
||
|
continue;
|
||
|
|
||
|
if (!same_string(cpos, str))
|
||
|
continue;
|
||
|
|
||
|
for (l = 0; l < 2; l++) {
|
||
|
int normal = delta[(k+1)%4];
|
||
|
if (l == 1)
|
||
|
normal = -normal;
|
||
|
|
||
|
dpos = cpos + normal;
|
||
|
if (board[dpos] != other)
|
||
|
continue;
|
||
|
|
||
|
epos = dpos + normal;
|
||
|
if (board[epos] != color)
|
||
|
continue;
|
||
|
|
||
|
fpos = apos + normal;
|
||
|
if (board[fpos] != EMPTY)
|
||
|
continue;
|
||
|
|
||
|
gpos = fpos + normal;
|
||
|
if (board[gpos] != EMPTY)
|
||
|
continue;
|
||
|
|
||
|
/* Configuration found. Now require an X move at 'a' not
|
||
|
* getting too many liberties.
|
||
|
*/
|
||
|
|
||
|
if (approxlib(apos, other, 4, NULL) > 3)
|
||
|
continue;
|
||
|
|
||
|
/* Try to play at (fpos). */
|
||
|
ADD_CANDIDATE_MOVE(fpos, 0, *moves, "special_rescue3");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* This code can find moves to counter attack moves generated by
|
||
|
* special_attack3_moves(). In case such an attack move has only two
|
||
|
* liberties, this function finds the liberty which is not common with
|
||
|
* the attacked string.
|
||
|
*
|
||
|
* For a typical example, see reading:198 where black L7 is generated
|
||
|
* by special_attack3_moves() and the response at L8 is generated by
|
||
|
* this function.
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
special_rescue4_moves(int str, int libs[2], struct reading_moves *moves)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int xpos;
|
||
|
int apos;
|
||
|
int bpos;
|
||
|
int libs2[2];
|
||
|
int k;
|
||
|
int r;
|
||
|
|
||
|
ASSERT1(countlib(str) == 2, str);
|
||
|
|
||
|
for (k = 0; k < 2; k++) {
|
||
|
apos = libs[k];
|
||
|
bpos = libs[1-k];
|
||
|
|
||
|
if (apos == SOUTH(bpos) || apos == NORTH(bpos)) {
|
||
|
if (board[WEST(apos)] == other)
|
||
|
xpos = WEST(apos);
|
||
|
else if (board[EAST(apos)] == other)
|
||
|
xpos = EAST(apos);
|
||
|
else
|
||
|
continue;
|
||
|
}
|
||
|
else if (apos == WEST(bpos) || apos == EAST(bpos)) {
|
||
|
if (board[SOUTH(apos)] == other)
|
||
|
xpos = SOUTH(apos);
|
||
|
else if (board[NORTH(apos)] == other)
|
||
|
xpos = NORTH(apos);
|
||
|
else
|
||
|
continue;
|
||
|
}
|
||
|
else
|
||
|
return; /* Incorrect configuration, give up. */
|
||
|
|
||
|
if (findlib(xpos, 2, libs2) == 2) {
|
||
|
for (r = 0; r < 2; r++)
|
||
|
if (libs2[r] != apos && libs2[r] != bpos
|
||
|
&& !is_self_atari(libs2[r], color))
|
||
|
ADD_CANDIDATE_MOVE(libs2[r], 0, *moves, "special_rescue4");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* In a situation like this:
|
||
|
*
|
||
|
* .XXXXX
|
||
|
* XX.*OO
|
||
|
* X.OX.. the following code can find the
|
||
|
* ...... defensive move at '*'.
|
||
|
* ------
|
||
|
*
|
||
|
* .* ac
|
||
|
* OX bd
|
||
|
*
|
||
|
* The only requirement is that d has at most as many liberties as b,
|
||
|
* and as the newly placed stone at c.
|
||
|
*/
|
||
|
static void
|
||
|
hane_rescue_moves(int str, int libs[4], struct reading_moves *moves)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int apos, bpos, cpos, dpos;
|
||
|
int num_libs = countlib(str);
|
||
|
int k, l, r;
|
||
|
|
||
|
ASSERT1(num_libs <= 4, str);
|
||
|
|
||
|
for (r = 0; r < num_libs; r++) {
|
||
|
/* Let (apos) be one of the three liberties. */
|
||
|
apos = libs[r];
|
||
|
/* Try to find the configuration above. */
|
||
|
for (k = 0; k < 4; k++) {
|
||
|
bpos = apos + delta[k];
|
||
|
if (board[bpos] != color)
|
||
|
continue;
|
||
|
|
||
|
if (!same_string(bpos, str))
|
||
|
continue;
|
||
|
|
||
|
for (l = 0; l < 2; l++) {
|
||
|
int normal = delta[(k+1)%4];
|
||
|
if (l == 1)
|
||
|
normal = -normal;
|
||
|
|
||
|
cpos = apos + normal;
|
||
|
if (board[cpos] != EMPTY)
|
||
|
continue;
|
||
|
|
||
|
dpos = bpos + normal;
|
||
|
if (board[dpos] != other)
|
||
|
continue;
|
||
|
|
||
|
/* Configuration found. Now check liberty constraint. */
|
||
|
{
|
||
|
int dlibs = countlib(dpos);
|
||
|
if (dlibs > num_libs
|
||
|
|| dlibs > accuratelib(cpos, color, dlibs, NULL))
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (0 && !in_list(cpos, moves->num, moves->pos)) {
|
||
|
gprintf("hane_rescue_move added for %1m at %1m\n", str, cpos);
|
||
|
dump_stack();
|
||
|
showboard(0);
|
||
|
}
|
||
|
ADD_CANDIDATE_MOVE(cpos, 0, *moves, "hane_rescue");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* In situations like these
|
||
|
*
|
||
|
* |XXXX |.X... |.X...
|
||
|
* |OOOX |.XOO. |XXOO.
|
||
|
* |..OX |OOXO. |OOXO.
|
||
|
* |O.OX |O.X*O |O.XOO
|
||
|
* |.X*. |O.X.O |O.X*O
|
||
|
* +---- +----- +-----
|
||
|
*
|
||
|
* the smaller of the O strings can be defended by *. The property
|
||
|
* they have in common is that the defended string has (at least) two
|
||
|
* liberties in common with an X string and it's effective to play on
|
||
|
* an exterior liberty of this string. Similarly it may be worth
|
||
|
* defending a weak neighbor of the X string.
|
||
|
*
|
||
|
* This function may be called for strings with 2 or 3 liberties and
|
||
|
* returns moves which are potentially useful in these positions.
|
||
|
*/
|
||
|
static void
|
||
|
special_rescue5_moves(int str, int libs[3],
|
||
|
struct reading_moves *moves)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int apos, bpos;
|
||
|
int k, r, s;
|
||
|
int liberties = countlib(str);
|
||
|
int libs2[4];
|
||
|
int liberties2;
|
||
|
|
||
|
ASSERT1(liberties == 2 || liberties == 3, str);
|
||
|
|
||
|
for (r = 0; r < liberties; r++) {
|
||
|
apos = libs[r];
|
||
|
|
||
|
for (k = 0; k < 4; k++) {
|
||
|
bpos = apos + delta[k];
|
||
|
if (board[bpos] != other)
|
||
|
continue;
|
||
|
|
||
|
/* Don't bother if it has too many liberties. */
|
||
|
if (countlib(bpos) > liberties + 1)
|
||
|
continue;
|
||
|
|
||
|
if (count_common_libs(str, bpos) < 2)
|
||
|
continue;
|
||
|
|
||
|
liberties2 = findlib(bpos, 4, libs2);
|
||
|
for (s = 0; s < liberties2; s++)
|
||
|
if (!liberty_of_string(libs2[s], str)
|
||
|
&& !is_self_atari(libs2[s], color))
|
||
|
ADD_CANDIDATE_MOVE(libs2[s], 0, *moves, "special_rescue5-A");
|
||
|
|
||
|
/* Reinforce the second order chain. */
|
||
|
if (liberties2 <= liberties) {
|
||
|
int adj;
|
||
|
int adjs[MAXCHAIN];
|
||
|
int t;
|
||
|
adj = chainlinks2(bpos, adjs, 1);
|
||
|
for (t = 0; t < adj; t++) {
|
||
|
int cpos;
|
||
|
break_chain_moves(adjs[t], moves);
|
||
|
|
||
|
findlib(adjs[t], 1, &cpos);
|
||
|
if (!is_self_atari(cpos, color))
|
||
|
ADD_CANDIDATE_MOVE(cpos, 0, *moves, "special_rescue5-B");
|
||
|
}
|
||
|
|
||
|
/* Defend against double atari in the surrounding chain early. */
|
||
|
double_atari_chain2_moves(bpos, moves, 0);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* In situations like this
|
||
|
*
|
||
|
* |.bOX
|
||
|
* |.Xa.
|
||
|
* |.OXX
|
||
|
* |.O..
|
||
|
* |.XX.
|
||
|
*
|
||
|
* the lower O string can often be defended at a or b.
|
||
|
*
|
||
|
* This function may be called for strings with 3 or 4 liberties and
|
||
|
* returns the * moves in the configuration below:
|
||
|
*
|
||
|
* |..O |.*O
|
||
|
* |.X. |.c*
|
||
|
* |.O? |ab?
|
||
|
*
|
||
|
* It also adds the * move in these configurations:
|
||
|
*
|
||
|
* |.X. |.c*
|
||
|
* |.OX |abX
|
||
|
*
|
||
|
* |.X. |.c*
|
||
|
* |.O. |ab.
|
||
|
*
|
||
|
* Provided that * is not a self atari and that the X strings have
|
||
|
* sufficiently few liberties.
|
||
|
*/
|
||
|
static void
|
||
|
special_rescue6_moves(int str, int libs[3], struct reading_moves *moves)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int apos, bpos, cpos;
|
||
|
int right, up;
|
||
|
int k, l, r;
|
||
|
int liberties = countlib(str);
|
||
|
|
||
|
ASSERT1(liberties == 3 || liberties == 4, str);
|
||
|
|
||
|
for (r = 0; r < liberties; r++) {
|
||
|
apos = libs[r];
|
||
|
|
||
|
for (k = 0; k < 4; k++) {
|
||
|
right = delta[k];
|
||
|
|
||
|
if (ON_BOARD(apos - right))
|
||
|
continue;
|
||
|
|
||
|
bpos = apos + right;
|
||
|
if (board[bpos] != color || !same_string(str, bpos))
|
||
|
continue;
|
||
|
|
||
|
for (l = 0; l < 2; l++) {
|
||
|
up = delta[(k+1) % 4];
|
||
|
if (l == 1)
|
||
|
up = -up;
|
||
|
|
||
|
cpos = bpos + up;
|
||
|
if (board[cpos] != other)
|
||
|
continue;
|
||
|
|
||
|
if (board[apos + up] != EMPTY)
|
||
|
continue;
|
||
|
|
||
|
if (board[cpos + right] != EMPTY)
|
||
|
continue;
|
||
|
|
||
|
if (board[apos + up + up] == EMPTY
|
||
|
&& board[cpos + up] == EMPTY
|
||
|
&& board[cpos + up + right] == color) {
|
||
|
ADD_CANDIDATE_MOVE(cpos + right, 0, *moves, "special_rescue6-A");
|
||
|
ADD_CANDIDATE_MOVE(cpos + up, 0, *moves, "special_rescue6-B");
|
||
|
}
|
||
|
else if (countlib(cpos) <= 3
|
||
|
&& (board[bpos + right] == EMPTY
|
||
|
|| (board[bpos + right] == other
|
||
|
&& countlib(bpos + right) <= 4))
|
||
|
&& !is_self_atari(cpos + right, color)) {
|
||
|
ADD_CANDIDATE_MOVE(cpos + right, 0, *moves, "special_rescue6-C");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* set_up_snapback_moves() is called with (str) a string having a
|
||
|
* single liberty at (lib).
|
||
|
*
|
||
|
* This adds moves which may defend a string in atari by capturing a
|
||
|
* neighbor in a snapback. One example is this position:
|
||
|
*
|
||
|
* OOOOO
|
||
|
* OXXXO
|
||
|
* OX.OX
|
||
|
* OXOXX
|
||
|
* OX*..
|
||
|
* -----
|
||
|
*
|
||
|
* This code also finds the move * to defend the lone O stone with ko
|
||
|
* in this position:
|
||
|
*
|
||
|
* |XXXXX
|
||
|
* |XOOOX
|
||
|
* |OX.OO
|
||
|
* |.*...
|
||
|
* +-----
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
set_up_snapback_moves(int str, int lib, struct reading_moves *moves)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int libs2[2];
|
||
|
|
||
|
ASSERT1(countlib(str) == 1, str);
|
||
|
|
||
|
/* This can only work if our string is a single stone and the
|
||
|
* opponent is short of liberties.
|
||
|
*/
|
||
|
if (stackp <= backfill_depth
|
||
|
&& countstones(str) == 1
|
||
|
&& approxlib(lib, other, 2, libs2) == 1
|
||
|
&& (!is_self_atari(libs2[0], color)
|
||
|
|| is_ko(libs2[0], color, NULL)))
|
||
|
ADD_CANDIDATE_MOVE(libs2[0], 0, *moves, "set_up_snapback");
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/* This function adds liberties of the superstring as candidate moves.
|
||
|
* For performance, this is restricted to strings with liberty_cap
|
||
|
* liberties, and to cases where at most 5 liberties would get considered.
|
||
|
*
|
||
|
* When attacking, we also try backfilling in case the direct approach
|
||
|
* would be self-atari.
|
||
|
* When defending, we also try second order liberties.
|
||
|
*/
|
||
|
static void
|
||
|
superstring_moves(int str, struct reading_moves *moves,
|
||
|
int liberty_cap, int does_attack)
|
||
|
{
|
||
|
int ss_liberties;
|
||
|
int ss_libs[MAX_LIBERTIES + 4];
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int k;
|
||
|
|
||
|
find_superstring_liberties(str, &ss_liberties, ss_libs, liberty_cap);
|
||
|
if (ss_liberties <= 5) {
|
||
|
for (k = 0; k < ss_liberties; k++) {
|
||
|
int apos = ss_libs[k];
|
||
|
int alibs[2];
|
||
|
int alib = accuratelib(apos, other, 2, alibs);
|
||
|
|
||
|
if (liberty_of_string(apos, str))
|
||
|
continue;
|
||
|
|
||
|
if (alib >= 2)
|
||
|
ADD_CANDIDATE_MOVE(apos, 0, *moves, "superstring liberty");
|
||
|
else if (alib == 1
|
||
|
&& does_attack
|
||
|
&& board[alibs[0]] == EMPTY
|
||
|
&& approxlib(alibs[0], other, 3, NULL) >= 3)
|
||
|
ADD_CANDIDATE_MOVE(alibs[0], 0, *moves, "superstring backfill");
|
||
|
|
||
|
if (!does_attack)
|
||
|
special_rescue_moves(str, apos, moves);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* This function is somewhat related to superstring_moves() but tries
|
||
|
* to find moves to squeeze out liberties from the superstring, aiming
|
||
|
* to capture the main string in a shortage of liberties.
|
||
|
*
|
||
|
* For a typical example, see the move E9 in reading:203,204. It is
|
||
|
* assumed that the same move is effective both for attack and
|
||
|
* defense.
|
||
|
*/
|
||
|
static void
|
||
|
squeeze_moves(int str, struct reading_moves *moves)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int libs[4];
|
||
|
int num_libs;
|
||
|
int libs2[4];
|
||
|
int num_libs2;
|
||
|
int k;
|
||
|
int r;
|
||
|
int potential_move = NO_MOVE;
|
||
|
int previous_liberty;
|
||
|
|
||
|
num_libs = findlib(str, 4, libs);
|
||
|
gg_assert(num_libs <= 4);
|
||
|
|
||
|
for (k = 0; k < num_libs; k++) {
|
||
|
if (!is_suicide(libs[k], other))
|
||
|
continue;
|
||
|
|
||
|
num_libs2 = approxlib(libs[k], color, 4, libs2);
|
||
|
if (num_libs2 != num_libs)
|
||
|
continue;
|
||
|
|
||
|
for (r = 0; r < num_libs2; r++)
|
||
|
if (!liberty_of_string(libs2[r], str)) {
|
||
|
potential_move = libs2[r];
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
previous_liberty = libs[k];
|
||
|
|
||
|
while (is_suicide(potential_move, other)) {
|
||
|
num_libs2 = approxlib(potential_move, color, 3, libs2);
|
||
|
if (num_libs2 != 2) {
|
||
|
potential_move = NO_MOVE;
|
||
|
break;
|
||
|
}
|
||
|
if (libs2[0] == previous_liberty) {
|
||
|
previous_liberty = potential_move;
|
||
|
potential_move = libs2[1];
|
||
|
}
|
||
|
else {
|
||
|
previous_liberty = potential_move;
|
||
|
potential_move = libs2[0];
|
||
|
}
|
||
|
if (liberty_of_string(potential_move, str)) {
|
||
|
potential_move = NO_MOVE;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (potential_move == NO_MOVE
|
||
|
|| !is_self_atari(potential_move, other))
|
||
|
continue;
|
||
|
|
||
|
approxlib(potential_move, other, 1, libs2);
|
||
|
|
||
|
num_libs2 = approxlib(libs2[0], color, MAXLIBS, NULL);
|
||
|
|
||
|
if (num_libs2 < 3
|
||
|
&& num_libs2 < approxlib(potential_move, color, MAXLIBS, NULL))
|
||
|
ADD_CANDIDATE_MOVE(potential_move, 0, *moves, "squeeze move");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* In positions like
|
||
|
*
|
||
|
* |.XXOO.
|
||
|
* |XXOX..
|
||
|
* |OOOX*.
|
||
|
* |......
|
||
|
* +------
|
||
|
*
|
||
|
* the O stones to the left are best defended by the move at *.
|
||
|
*
|
||
|
* This function tries to find an adjacent string (apos) with exactly
|
||
|
* three liberties. One of the liberties (bpos) must be on the edge
|
||
|
* (but not in the corner). Diagonal to this liberty must be one stone
|
||
|
* of the attacked string (cpos) and another liberty (dpos) of the
|
||
|
* adjacent string. The third liberty (epos) must be adjacent to
|
||
|
* (dpos). Furthermore must an O stone at (dpos) get at least three
|
||
|
* liberties and and X stone at (epos) must get at most three
|
||
|
* liberties.
|
||
|
*
|
||
|
* |.XXOO.
|
||
|
* |XXOXe.
|
||
|
* |OOcad.
|
||
|
* |...b..
|
||
|
* +------
|
||
|
*
|
||
|
* The defense move at (dpos) is proposed if the above conditions
|
||
|
* are satisfied.
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
edge_clamp_moves(int str, struct reading_moves *moves)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int apos;
|
||
|
int bpos;
|
||
|
int cpos;
|
||
|
int dpos;
|
||
|
int epos;
|
||
|
int adj, adjs[MAXCHAIN];
|
||
|
int libs[3];
|
||
|
int k, l, r;
|
||
|
|
||
|
/* Pick up neighbors with three liberties. */
|
||
|
adj = chainlinks2(str, adjs, 3);
|
||
|
|
||
|
for (r = 0; r < adj; r++) {
|
||
|
apos = adjs[r];
|
||
|
/* Find a liberty at the edge. */
|
||
|
bpos = NO_MOVE;
|
||
|
findlib(apos, 3, libs);
|
||
|
for (k = 0; k < 3; k++) {
|
||
|
if (is_edge_vertex(libs[k])) {
|
||
|
bpos = libs[k];
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (bpos == NO_MOVE)
|
||
|
continue;
|
||
|
|
||
|
/* Edge liberty found. Establish up and right directions. */
|
||
|
for (k = 0; k < 4; k++) {
|
||
|
int up = delta[k];
|
||
|
if (ON_BOARD(bpos - up))
|
||
|
continue;
|
||
|
if (board[bpos + up] != other)
|
||
|
continue;
|
||
|
|
||
|
for (l = 0; l < 2; l++) {
|
||
|
int right = delta[(k+1)%4];
|
||
|
if (l == 1)
|
||
|
right = -right;
|
||
|
|
||
|
cpos = bpos + up - right;
|
||
|
dpos = bpos + up + right;
|
||
|
|
||
|
if (board[cpos] != color || !same_string(cpos, str))
|
||
|
continue;
|
||
|
|
||
|
if (board[dpos] != EMPTY || !liberty_of_string(dpos, apos))
|
||
|
continue;
|
||
|
|
||
|
epos = dpos + up;
|
||
|
|
||
|
if (board[epos] != EMPTY || !liberty_of_string(epos, apos))
|
||
|
continue;
|
||
|
|
||
|
if (approxlib(dpos, color, 3, NULL) < 3)
|
||
|
continue;
|
||
|
|
||
|
if (approxlib(epos, other, 4, NULL) > 3)
|
||
|
continue;
|
||
|
|
||
|
/* (dpos) looks like a good move. Add it to the list with a
|
||
|
* substantial initial score.
|
||
|
*/
|
||
|
ADD_CANDIDATE_MOVE(dpos, 10, *moves, "edge_clamp");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* This function handles some special cases on the edge.
|
||
|
*
|
||
|
* 1. If (str) points to a string and 'a' an edge liberty of it,
|
||
|
* there is no point of trying to defend the string by crawling
|
||
|
* along the edge if there is no hope of ever getting more liberties.
|
||
|
* This is of course if the blocking enemy group has enough liberties
|
||
|
* of its own.
|
||
|
*
|
||
|
* XX XX
|
||
|
* O. Oa
|
||
|
* -- --
|
||
|
*
|
||
|
* This function searches the edge towards the corner and sees if there
|
||
|
* is a friendly stone on one of the two first lines. If not, the move
|
||
|
* is removed from the list of moves.
|
||
|
*
|
||
|
* 2. If (str) points to a string and 'a' an edge liberty of it,
|
||
|
* the drawing back/climbing up move 'b' is often correct attack or
|
||
|
* defense. Another good move to try is 'c' (but usually not for
|
||
|
* defense of a 2 liberty string).
|
||
|
*
|
||
|
* X.? Xbc
|
||
|
* O.. Oa.
|
||
|
* --- ---
|
||
|
*
|
||
|
* This function adds the points configured like 'b' and 'c' relative to
|
||
|
* (str) to the list of moves.
|
||
|
*
|
||
|
* color is the color to move.
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
propose_edge_moves(int str, int *libs, int liberties,
|
||
|
struct reading_moves *moves, int to_move)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int right;
|
||
|
int up;
|
||
|
int apos;
|
||
|
int k, l;
|
||
|
int r;
|
||
|
|
||
|
for (r = 0; r < liberties; r++) {
|
||
|
apos = libs[r];
|
||
|
for (k = 0; k < 4; k++) {
|
||
|
up = delta[k];
|
||
|
if (ON_BOARD(apos - up))
|
||
|
continue;
|
||
|
|
||
|
for (l = 0; l < 2; l++) {
|
||
|
right = delta[(k+1)%4];
|
||
|
if (l == 1)
|
||
|
right = -right;
|
||
|
|
||
|
if (board[apos + up] == other /* other on top of liberty */
|
||
|
&& countlib(apos + up) > 4 /* blocking group must be secure */
|
||
|
&& color == to_move) { /* only applicable as defense */
|
||
|
|
||
|
/* Case 1: other above the liberty (crawl along the edge). */
|
||
|
int xpos = apos;
|
||
|
|
||
|
while (ON_BOARD(xpos)) {
|
||
|
if (board[xpos] == color
|
||
|
|| board[xpos + up] == color)
|
||
|
break;
|
||
|
|
||
|
xpos += right;
|
||
|
}
|
||
|
|
||
|
/* If no friendly stone found, then it is pointless and we
|
||
|
* can just as well remove the move.
|
||
|
*/
|
||
|
if (!ON_BOARD(xpos)) {
|
||
|
REMOVE_CANDIDATE_MOVE(apos, *moves);
|
||
|
}
|
||
|
}
|
||
|
else if (board[apos + up] == EMPTY /* empty above the liberty */
|
||
|
&& board[apos - right + up] == other
|
||
|
&& board[apos + right] == EMPTY) { /* empty to the right */
|
||
|
|
||
|
/* Case 2: Try to escape or contain. */
|
||
|
|
||
|
/* Add b
|
||
|
* If adjacent X stone in atari, boost the initial score of this
|
||
|
* move.
|
||
|
*/
|
||
|
if (countlib(apos + up - right) == 1)
|
||
|
ADD_CANDIDATE_MOVE(apos + up, 10, *moves, "propose_edge-A");
|
||
|
else {
|
||
|
ADD_CANDIDATE_MOVE(apos + up, 0, *moves, "propose_edge-B");
|
||
|
|
||
|
/* Add c if empty */
|
||
|
if (board[apos + right + up] == EMPTY
|
||
|
&& (liberties != 2 || color != to_move))
|
||
|
ADD_CANDIDATE_MOVE(apos + right + up, 0, *moves,
|
||
|
"propose_edge-C");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* ================================================================ */
|
||
|
/* Attacking functions */
|
||
|
/* ================================================================ */
|
||
|
|
||
|
|
||
|
/* Like attack. If the opponent is komaster reading functions will not try
|
||
|
* to take ko.
|
||
|
*/
|
||
|
static int
|
||
|
do_attack(int str, int *move)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int xpos = NO_MOVE;
|
||
|
int liberties;
|
||
|
int result = 0;
|
||
|
int retval;
|
||
|
|
||
|
SETUP_TRACE_INFO("attack", str);
|
||
|
|
||
|
ASSERT1(color != 0, str);
|
||
|
|
||
|
if (color == 0) /* if assertions are turned off, silently fails */
|
||
|
return 0;
|
||
|
|
||
|
str = find_origin(str);
|
||
|
liberties = countlib(str);
|
||
|
|
||
|
if (liberties > 4
|
||
|
|| (liberties == 4 && stackp > fourlib_depth)
|
||
|
|| (liberties == 3 && stackp > depth)) {
|
||
|
/* No need to cache the result in these cases. */
|
||
|
if (sgf_dumptree) {
|
||
|
char buf[100];
|
||
|
sprintf(buf, "got 4 liberties (stackp:%d>%d)",
|
||
|
stackp, fourlib_depth);
|
||
|
SGFTRACE(0, 0, buf);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Set "killer move" up. This move (if set) was successful in
|
||
|
* another variation, so it is reasonable to try it now. However,
|
||
|
* we only do this if the string has 4 liberties - otherwise the
|
||
|
* situation changes too much from variation to variation.
|
||
|
*/
|
||
|
if (liberties > 3 && move)
|
||
|
xpos = *move;
|
||
|
|
||
|
/* Note that if return value is 1 (too small depth), the move will
|
||
|
* still be used for move ordering.
|
||
|
*/
|
||
|
if (stackp <= depth
|
||
|
&& tt_get(&ttable, ATTACK, str, NO_MOVE, depth - stackp, NULL,
|
||
|
&retval, NULL, &xpos) == 2) {
|
||
|
TRACE_CACHED_RESULT(retval, xpos);
|
||
|
SGFTRACE(xpos, retval, "cached");
|
||
|
if (move)
|
||
|
*move = xpos;
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
/* Treat the attack differently depending on how many liberties the
|
||
|
string at (str) has. */
|
||
|
if (liberties == 1)
|
||
|
result = attack1(str, &xpos);
|
||
|
else if (liberties == 2) {
|
||
|
if (stackp > depth + 10)
|
||
|
result = simple_ladder(str, &xpos);
|
||
|
else
|
||
|
result = attack2(str, &xpos);
|
||
|
}
|
||
|
else if (liberties == 3)
|
||
|
result = attack3(str, &xpos);
|
||
|
else if (liberties == 4)
|
||
|
result = attack4(str, &xpos);
|
||
|
|
||
|
|
||
|
ASSERT1(result >= 0 && result <= WIN, str);
|
||
|
|
||
|
if (result) {
|
||
|
READ_RETURN(ATTACK, str, depth - stackp, move, xpos, result);
|
||
|
}
|
||
|
|
||
|
READ_RETURN0(ATTACK, str, depth - stackp);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* If (str) points to a group with exactly one liberty, attack1
|
||
|
* determines whether it can be captured by playing at this liberty.
|
||
|
* If successful, (*move) is the killing move. move may be NULL if
|
||
|
* caller is only interested in whether it can be captured.
|
||
|
*
|
||
|
* The attack may fail for two different reasons. The first one is
|
||
|
* that the attack may be an illegal ko capture, in this case KO_B is
|
||
|
* returned (need to play a ko threat before the attack can be
|
||
|
* fulfilled).
|
||
|
*
|
||
|
* The second cause for failure is that the attack is caught in a
|
||
|
* snapback. We must require that it is a proper snapback, though. By
|
||
|
* proper snapback we mean a position like
|
||
|
*
|
||
|
* XXXXO
|
||
|
* XO.XO
|
||
|
* XOXOO
|
||
|
* -----
|
||
|
*
|
||
|
* where capture by O and recapture by X leaves the X stone intact
|
||
|
* with at least two liberties:
|
||
|
*
|
||
|
* XXXXO
|
||
|
* X..XO
|
||
|
* X.XOO
|
||
|
* -----
|
||
|
*
|
||
|
* There are a number of different kinds of improper snapbacks, which
|
||
|
* have in common that the attacked string ends up captured. We don't
|
||
|
* consider these as failures to attack. Three examples are given below.
|
||
|
*
|
||
|
* XXOOOOO (X can recapture but loses most of the string.)
|
||
|
* X.XXXXO
|
||
|
* -------
|
||
|
*
|
||
|
* XXXOOOOOOOO (Like the previous example, except O loses one more stone)
|
||
|
* XO*XXXXXXXO
|
||
|
* -----------
|
||
|
*
|
||
|
* XXXOO (After three captures, the lone X stone is gone.)
|
||
|
* XO.XO
|
||
|
* -----
|
||
|
*
|
||
|
* This function is fast and never branches. There's little point in
|
||
|
* caching the result.
|
||
|
*/
|
||
|
|
||
|
static int
|
||
|
attack1(int str, int *move)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int xpos;
|
||
|
int savemove = 0;
|
||
|
int savecode = 0;
|
||
|
int liberties;
|
||
|
int libs[6];
|
||
|
int k;
|
||
|
int r;
|
||
|
int adjs[MAXCHAIN];
|
||
|
int adj;
|
||
|
int apos;
|
||
|
|
||
|
|
||
|
SETUP_TRACE_INFO("attack1", str);
|
||
|
reading_node_counter++;
|
||
|
|
||
|
/* Pick up the position of the liberty. */
|
||
|
findlib(str, 1, &xpos);
|
||
|
|
||
|
/* If the attacked string consists of more than one stone, the
|
||
|
* attack never fails. (This assumes simple ko rule. With superko
|
||
|
* rule it could still be a ko violation.)
|
||
|
*/
|
||
|
if (countstones(str) > 1) {
|
||
|
RETURN_RESULT(WIN, xpos, move, "last liberty");
|
||
|
}
|
||
|
|
||
|
/* Try to play on the liberty. This fails if and only if it is an
|
||
|
* illegal ko capture.
|
||
|
*/
|
||
|
if (trymove(xpos, other, "attack1-A", str)) {
|
||
|
/* Is the attacker in atari? If not the attack was successful. */
|
||
|
if (countlib(xpos) > 1) {
|
||
|
popgo();
|
||
|
RETURN_RESULT(WIN, xpos, move, "last liberty");
|
||
|
}
|
||
|
|
||
|
/* If the attacking string is also a single stone, a possible
|
||
|
* recapture would be a ko violation, so the defender has to make
|
||
|
* a ko threat first.
|
||
|
*/
|
||
|
else if (countstones(xpos) == 1) {
|
||
|
if (get_komaster() != other) {
|
||
|
/* If the defender is allowed to take the ko the result is KO_A. */
|
||
|
CHECK_RESULT_UNREVERSED(savecode, savemove, KO_A, xpos, move,
|
||
|
"last liberty - ko");
|
||
|
}
|
||
|
else {
|
||
|
/* But if the attacker is the attack was successful. */
|
||
|
popgo();
|
||
|
RETURN_RESULT(WIN, xpos, move, "last liberty");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Otherwise, do recapture. Notice that the liberty must be
|
||
|
* at (str) since we have already established that this string
|
||
|
* was a single stone.
|
||
|
*/
|
||
|
else if (trymove(str, color, "attack1-B", str)) {
|
||
|
/* If this was a proper snapback, (str) will now have more
|
||
|
* than one liberty.
|
||
|
*/
|
||
|
if (countlib(str) > 1) {
|
||
|
/* Proper snapback, attack fails. */
|
||
|
popgo();
|
||
|
}
|
||
|
else {
|
||
|
popgo();
|
||
|
popgo();
|
||
|
RETURN_RESULT(WIN, xpos, move, "last liberty");
|
||
|
}
|
||
|
}
|
||
|
popgo();
|
||
|
}
|
||
|
else {/* Illegal ko capture. */
|
||
|
if (get_komaster() != color) {
|
||
|
CHECK_RESULT_UNREVERSED(savecode, savemove, KO_B, xpos, move,
|
||
|
"last liberty - ko");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* If not yet successful, try backfilling and back-capturing.
|
||
|
* An example of back-capturing can be found in reading:234.
|
||
|
* Backfilling is maybe only meaningful in positions involving ko.
|
||
|
*/
|
||
|
liberties = approxlib(xpos, color, 6, libs);
|
||
|
if (liberties <= 5)
|
||
|
for (k = 0; k < liberties; k++) {
|
||
|
apos = libs[k];
|
||
|
if (!is_self_atari(apos, other)
|
||
|
&& trymove(apos, other, "attack1-C", str)) {
|
||
|
int dcode = do_find_defense(str, NULL);
|
||
|
if (dcode != WIN && do_attack(str, NULL)) {
|
||
|
if (dcode == 0) {
|
||
|
popgo();
|
||
|
RETURN_RESULT(WIN, apos, move, "backfilling");
|
||
|
}
|
||
|
UPDATE_SAVED_KO_RESULT(savecode, savemove, dcode, apos);
|
||
|
}
|
||
|
popgo();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
adj = chainlinks2(str, adjs, 1);
|
||
|
for (r = 0; r < adj; r++) {
|
||
|
if (liberty_of_string(xpos, adjs[r])) {
|
||
|
int adjs2[MAXCHAIN];
|
||
|
int adj2;
|
||
|
adj2 = chainlinks2(adjs[r], adjs2, 1);
|
||
|
for (k = 0; k < adj2; k++) {
|
||
|
int ko_move;
|
||
|
if (adjs2[k] == str)
|
||
|
continue;
|
||
|
findlib(adjs2[k], 1, &apos);
|
||
|
if (komaster_trymove(apos, other, "attack1-D", str,
|
||
|
&ko_move, stackp <= ko_depth && savecode == 0)) {
|
||
|
if (!ko_move) {
|
||
|
int dcode = do_find_defense(str, NULL);
|
||
|
if (dcode != WIN
|
||
|
&& do_attack(str, NULL)) {
|
||
|
popgo();
|
||
|
CHECK_RESULT(savecode, savemove, dcode, apos, move,
|
||
|
"attack effective");
|
||
|
}
|
||
|
else
|
||
|
popgo();
|
||
|
}
|
||
|
else {
|
||
|
if (do_find_defense(str, NULL) != WIN
|
||
|
&& do_attack(str, NULL) != 0) {
|
||
|
savemove = apos;
|
||
|
savecode = KO_B;
|
||
|
}
|
||
|
popgo();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (savecode == 0) {
|
||
|
RETURN_RESULT(0, 0, move, NULL);
|
||
|
}
|
||
|
|
||
|
RETURN_RESULT(savecode, savemove, move, "saved move");
|
||
|
}
|
||
|
|
||
|
|
||
|
/* If str points to a group with exactly two liberties
|
||
|
* attack2 determines whether it can be captured in ladder or net.
|
||
|
* If yes, *move is the killing move. move may be null if caller
|
||
|
* is only interested in whether it can be captured.
|
||
|
*
|
||
|
* Returns KO_A or KO_B if it can be killed conditioned on ko. Returns
|
||
|
* KO_A if it can be killed provided (other) is willing to ignore any
|
||
|
* ko threat. Returns KO_B if (other) wins provided he has a ko threat
|
||
|
* which must be answered. Can give a return code KO_B yet *move=0 if
|
||
|
* the winning move is an illegal ko capture. In this case, making a
|
||
|
* ko threat and having it answered should transform the position to
|
||
|
* one where the return code is KO_A.
|
||
|
*
|
||
|
* See the comment before defend1 about ladders and reading depth.
|
||
|
*/
|
||
|
|
||
|
static int
|
||
|
attack2(int str, int *move)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int hpos;
|
||
|
int xpos = NO_MOVE;
|
||
|
int liberties, r;
|
||
|
int libs[2];
|
||
|
int libs2[2];
|
||
|
int adj, adjs[MAXCHAIN];
|
||
|
int savemove = 0;
|
||
|
int savecode = 0;
|
||
|
int k;
|
||
|
int atari_possible = 0;
|
||
|
struct reading_moves moves;
|
||
|
int adjacent_liberties = 0;
|
||
|
int pass;
|
||
|
int suggest_move = NO_MOVE;
|
||
|
|
||
|
SETUP_TRACE_INFO("attack2", str);
|
||
|
reading_node_counter++;
|
||
|
moves.num = 0;
|
||
|
moves.num_tried = 0;
|
||
|
|
||
|
str = find_origin(str);
|
||
|
ASSERT1(IS_STONE(board[str]), str);
|
||
|
ASSERT1(countlib(str) == 2, str);
|
||
|
|
||
|
for (pass = 0; pass < 4; pass++) {
|
||
|
|
||
|
switch (pass) {
|
||
|
case 0:
|
||
|
/* The attack may fail if a boundary string is in atari and cannot
|
||
|
* be defended. First we must try defending such a string.
|
||
|
*
|
||
|
* We start by trying to defend the boundary string by looking for an
|
||
|
* adjacent string which is in atari.
|
||
|
*/
|
||
|
adj = chainlinks2(str, adjs, 1);
|
||
|
for (r = 0; r < adj; r++) {
|
||
|
/* If stackp > depth and any boundary chain is in atari, assume safe.
|
||
|
* However, if the captured chain is only of size 1, there can still
|
||
|
* be a working ladder, so continue if that is the case.
|
||
|
* Also if the string in atari shares its liberty with the
|
||
|
* attacked string, drawing it out may enable the ladder to
|
||
|
* continue.
|
||
|
*/
|
||
|
if (stackp > depth
|
||
|
&& countstones(adjs[r]) > 1
|
||
|
&& !have_common_lib(str, adjs[r], NULL)) {
|
||
|
RETURN_RESULT(0, 0, move, "boundary in atari");
|
||
|
}
|
||
|
|
||
|
/* Pick up moves breaking the second order chain. */
|
||
|
if (stackp <= depth)
|
||
|
break_chain_moves(adjs[r], &moves);
|
||
|
|
||
|
findlib(adjs[r], 1, &hpos);
|
||
|
ADD_CANDIDATE_MOVE(hpos, 0, moves, "save_boundary");
|
||
|
}
|
||
|
|
||
|
/* Get the two liberties of (str). */
|
||
|
liberties = findlib(str, 2, libs);
|
||
|
ASSERT1(liberties == 2, str);
|
||
|
|
||
|
if (DIRECT_NEIGHBORS(libs[0], libs[1]))
|
||
|
adjacent_liberties = 1;
|
||
|
|
||
|
for (k = 0; k < 2; k++) {
|
||
|
int apos = libs[k];
|
||
|
if (!is_self_atari(apos, other))
|
||
|
atari_possible = 1;
|
||
|
/* We only want to consider the move at (apos) if:
|
||
|
* stackp <= backfill_depth
|
||
|
* -or- stackp <= depth and it is an isolated stone
|
||
|
* -or- it is not in immediate atari
|
||
|
*/
|
||
|
if (stackp <= backfill_depth
|
||
|
|| ((stackp <= depth || adjacent_liberties)
|
||
|
&& !has_neighbor(apos, other))
|
||
|
|| !is_self_atari(apos, other))
|
||
|
ADD_CANDIDATE_MOVE(apos, 0, moves, "liberty");
|
||
|
|
||
|
/* Try backfilling if atari is impossible. */
|
||
|
if (stackp <= backfill_depth
|
||
|
&& approxlib(apos, other, 2, libs2) == 1) {
|
||
|
ADD_CANDIDATE_MOVE(libs2[0], 0, moves, "backfill");
|
||
|
/* If there is a neighbor in atari, we also try back-capturing. */
|
||
|
for (r = 0; r < 4; r++) {
|
||
|
int bpos = libs2[0] + delta[r];
|
||
|
if (board[bpos] == other && chainlinks2(bpos, adjs, 1) > 0) {
|
||
|
/* FIXME: If there is more than one neighbor in atari, we
|
||
|
* currently just take one randomly. This is maybe not good
|
||
|
* enough. We might also want to check against snapback.
|
||
|
*
|
||
|
* FIXME: What is the purpose of this? It produces some
|
||
|
* completely irrelevant moves (e.g. if bpos is a huge string
|
||
|
* with many liberties and adjs[0] is somewhere else on the
|
||
|
* board).
|
||
|
*/
|
||
|
findlib(adjs[0], 1, &xpos);
|
||
|
ADD_CANDIDATE_MOVE(xpos, 0, moves, "back-capture");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* If we can't make a direct atari, look for edge blocking moves. */
|
||
|
if (!atari_possible)
|
||
|
for (k = 0; k < 2; k++)
|
||
|
edge_block_moves(str, libs[k], &moves);
|
||
|
|
||
|
|
||
|
/* If one of the surrounding chains have only two liberties, which
|
||
|
* coincide with the liberties of the attacked string, we try to
|
||
|
* backcapture.
|
||
|
*/
|
||
|
|
||
|
adj = chainlinks2(str, adjs, 2);
|
||
|
for (r = 0; r < adj; r++) {
|
||
|
int apos = adjs[r];
|
||
|
if (liberty_of_string(libs[0], apos)
|
||
|
&& liberty_of_string(libs[1], apos))
|
||
|
break_chain_moves(apos, &moves);
|
||
|
}
|
||
|
|
||
|
propose_edge_moves(str, libs, liberties, &moves, other);
|
||
|
|
||
|
break;
|
||
|
|
||
|
case 1:
|
||
|
if (stackp <= backfill_depth) {
|
||
|
special_attack2_moves(str, libs, &moves);
|
||
|
special_attack3_moves(str, libs, &moves);
|
||
|
special_attack4_moves(str, libs, &moves);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 2:
|
||
|
find_cap_moves(str, &moves);
|
||
|
break;
|
||
|
|
||
|
case 3:
|
||
|
/* If it is not possible to make a direct atari, we try filling
|
||
|
* a liberty of the superstring.
|
||
|
*/
|
||
|
if (get_level() >= 8
|
||
|
&& stackp <= backfill_depth
|
||
|
&& (stackp <= superstring_depth || !atari_possible)) {
|
||
|
int liberty_cap = 2;
|
||
|
if (stackp <= backfill2_depth)
|
||
|
liberty_cap = 3;
|
||
|
superstring_moves(str, &moves, liberty_cap, 1);
|
||
|
squeeze_moves(str, &moves);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
abort();
|
||
|
} /* switch (pass) */
|
||
|
|
||
|
order_moves(str, &moves, other, read_function_name, *move);
|
||
|
ATTACK_TRY_MOVES(0, &suggest_move);
|
||
|
}
|
||
|
|
||
|
RETURN_RESULT(savecode, savemove, move, "saved move");
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/* attack3(str, *move) is used when (str) points to a group with
|
||
|
* three liberties. It returns true if it finds a way to kill the group.
|
||
|
*
|
||
|
* Return code is KO_A if the group can be killed if the attacker is
|
||
|
* willing to ignore any ko threat.
|
||
|
*
|
||
|
* Return code is KO_B if the group can be killed if the attacker is
|
||
|
* able to find a ko threat which must be answered.
|
||
|
*
|
||
|
* If non-NULL (*move) will be set to the move which makes the
|
||
|
* attack succeed.
|
||
|
*/
|
||
|
|
||
|
static int
|
||
|
attack3(int str, int *move)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int adj, adjs[MAXCHAIN];
|
||
|
int liberties;
|
||
|
int libs[3];
|
||
|
int r;
|
||
|
int k;
|
||
|
struct reading_moves moves;
|
||
|
int savemove = 0;
|
||
|
int savecode = 0;
|
||
|
int pass;
|
||
|
int suggest_move = NO_MOVE;
|
||
|
|
||
|
SETUP_TRACE_INFO("attack3", str);
|
||
|
reading_node_counter++;
|
||
|
moves.num = 0;
|
||
|
moves.num_tried = 0;
|
||
|
|
||
|
ASSERT1(IS_STONE(board[str]), str);
|
||
|
|
||
|
ASSERT1(stackp <= depth, str);
|
||
|
|
||
|
for (pass = 0; pass < 4; pass++) {
|
||
|
|
||
|
switch (pass) {
|
||
|
case 0:
|
||
|
adj = chainlinks2(str, adjs, 1);
|
||
|
for (r = 0; r < adj; r++) {
|
||
|
int hpos;
|
||
|
break_chain_moves(adjs[r], &moves);
|
||
|
|
||
|
findlib(adjs[r], 1, &hpos);
|
||
|
ADD_CANDIDATE_MOVE(hpos, 0, moves, "save_boundary");
|
||
|
}
|
||
|
|
||
|
/* Defend against double atari in the surrounding chain early. */
|
||
|
double_atari_chain2_moves(str, &moves, stackp <= superstring_depth);
|
||
|
|
||
|
/* Get the three liberties of (str). */
|
||
|
liberties = findlib(str, 3, libs);
|
||
|
ASSERT1(liberties == 3, str);
|
||
|
|
||
|
for (k = 0; k < 3; k++) {
|
||
|
#if 0
|
||
|
int libs2[2];
|
||
|
#endif
|
||
|
int apos = libs[k];
|
||
|
/* We only want to consider the move at (apos) if:
|
||
|
* stackp <= backfill_depth
|
||
|
* -or- stackp <= depth and it is an isolated stone
|
||
|
* -or- it is not in immediate atari
|
||
|
*/
|
||
|
if (stackp <= backfill_depth
|
||
|
|| (stackp <= depth
|
||
|
&& !has_neighbor(apos, other))
|
||
|
|| !is_self_atari(apos, other))
|
||
|
ADD_CANDIDATE_MOVE(apos, 0, moves, "liberty");
|
||
|
|
||
|
edge_closing_backfill_moves(str, apos, &moves);
|
||
|
|
||
|
#if 0
|
||
|
/* Try backfilling if atari is impossible. */
|
||
|
if (stackp <= backfill_depth
|
||
|
&& approxlib(apos, other, 2, libs2) == 1) {
|
||
|
ADD_CANDIDATE_MOVE(libs2[0], 0, moves, "backfill");
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* Look for edge blocking moves. */
|
||
|
edge_block_moves(str, apos, &moves);
|
||
|
}
|
||
|
|
||
|
/* Pick up some edge moves. */
|
||
|
propose_edge_moves(str, libs, liberties, &moves, other);
|
||
|
break;
|
||
|
|
||
|
case 1:
|
||
|
/* The simple ataris didn't work. Try something more fancy. */
|
||
|
if (stackp <= backfill_depth)
|
||
|
find_cap_moves(str, &moves);
|
||
|
|
||
|
if (stackp <= fourlib_depth)
|
||
|
draw_back_moves(str, &moves);
|
||
|
|
||
|
break;
|
||
|
|
||
|
case 2:
|
||
|
/* Try to defend chain links with two liberties. */
|
||
|
if (stackp <= backfill2_depth) {
|
||
|
adj = chainlinks2(str, adjs, 2);
|
||
|
for (r = 0; r < adj; r++) {
|
||
|
int libs2[2];
|
||
|
findlib(adjs[r], 2, libs2);
|
||
|
if (approxlib(libs2[0], other, 4, NULL) > 3
|
||
|
&& approxlib(libs2[1], other, 4, NULL) > 3)
|
||
|
continue;
|
||
|
break_chain_moves(adjs[r], &moves);
|
||
|
break_chain2_moves(adjs[r], &moves, 1, 0);
|
||
|
for (k = 0; k < 2; k++)
|
||
|
ADD_CANDIDATE_MOVE(libs2[k], 0, moves, "save_boundary-2");
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 3:
|
||
|
/* If nothing else works, we try filling a liberty of the
|
||
|
* super_string.
|
||
|
*/
|
||
|
if (get_level() >= 8 && stackp <= backfill2_depth) {
|
||
|
superstring_moves(str, &moves, 3, 1);
|
||
|
squeeze_moves(str, &moves);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
abort();
|
||
|
}
|
||
|
|
||
|
order_moves(str, &moves, other, read_function_name, *move);
|
||
|
ATTACK_TRY_MOVES(1, &suggest_move);
|
||
|
} /* for (pass... */
|
||
|
|
||
|
RETURN_RESULT(savecode, savemove, move, "saved move");
|
||
|
}
|
||
|
|
||
|
|
||
|
/* attack4 tries to capture a string with 4 liberties. */
|
||
|
|
||
|
static int
|
||
|
attack4(int str, int *move)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int r;
|
||
|
int k;
|
||
|
int liberties;
|
||
|
int libs[4];
|
||
|
int adj, adjs[MAXCHAIN];
|
||
|
struct reading_moves moves;
|
||
|
int savemove = 0;
|
||
|
int savecode = 0;
|
||
|
int pass;
|
||
|
int suggest_move = NO_MOVE;
|
||
|
|
||
|
SETUP_TRACE_INFO("attack4", str);
|
||
|
|
||
|
ASSERT1(IS_STONE(board[str]), str);
|
||
|
reading_node_counter++;
|
||
|
moves.num = 0;
|
||
|
moves.num_tried = 0;
|
||
|
|
||
|
if (stackp > depth) {
|
||
|
SGFTRACE(0, 0, "stackp > depth");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
for (pass = 0; pass < 2; pass++) {
|
||
|
|
||
|
switch (pass) {
|
||
|
case 0:
|
||
|
adj = chainlinks2(str, adjs, 1);
|
||
|
for (r = 0; r < adj; r++) {
|
||
|
int hpos;
|
||
|
break_chain_moves(adjs[r], &moves);
|
||
|
|
||
|
findlib(adjs[r], 1, &hpos);
|
||
|
ADD_CANDIDATE_MOVE(hpos, 0, moves, "save_boundary");
|
||
|
}
|
||
|
|
||
|
/* Defend against double atari in the surrounding chain early. */
|
||
|
double_atari_chain2_moves(str, &moves, stackp <= superstring_depth);
|
||
|
|
||
|
/* Give a score bonus to the chain preserving moves. */
|
||
|
for (k = 0; k < moves.num; k++)
|
||
|
moves.score[k] += 5;
|
||
|
|
||
|
/* Get the four liberties of (str). */
|
||
|
liberties = findlib(str, 4, libs);
|
||
|
ASSERT1(liberties == 4, str);
|
||
|
|
||
|
for (k = 0; k < 4; k++) {
|
||
|
int apos = libs[k];
|
||
|
/* We only want to consider the move at (apos) if:
|
||
|
* stackp <= backfill_depth
|
||
|
* -or- stackp <= depth and it is an isolated stone
|
||
|
* -or- it is not in immediate atari
|
||
|
*/
|
||
|
if (stackp <= backfill_depth
|
||
|
|| (stackp <= depth
|
||
|
&& !has_neighbor(apos, other))
|
||
|
|| !is_self_atari(apos, other))
|
||
|
ADD_CANDIDATE_MOVE(apos, 0, moves, "liberty");
|
||
|
|
||
|
edge_closing_backfill_moves(str, apos, &moves);
|
||
|
|
||
|
/* Look for edge blocking moves. */
|
||
|
edge_block_moves(str, apos, &moves);
|
||
|
}
|
||
|
|
||
|
/* Pick up some edge moves. */
|
||
|
propose_edge_moves(str, libs, liberties, &moves, other);
|
||
|
break;
|
||
|
|
||
|
case 1:
|
||
|
if (stackp <= backfill_depth)
|
||
|
find_cap_moves(str, &moves);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
abort();
|
||
|
}
|
||
|
|
||
|
order_moves(str, &moves, other, read_function_name, *move);
|
||
|
ATTACK_TRY_MOVES(1, &suggest_move);
|
||
|
} /* for (pass = ... */
|
||
|
|
||
|
RETURN_RESULT(savecode, savemove, move, "saved move");
|
||
|
}
|
||
|
|
||
|
|
||
|
/* If (str) points to a string with 2 - 4 liberties,
|
||
|
* find_cap_moves(str, &moves)
|
||
|
* looks for a configuration of the following type:
|
||
|
*
|
||
|
* Xa
|
||
|
* b*
|
||
|
*
|
||
|
* where X are elements of the string in question and a and b are
|
||
|
* two of its liberties.
|
||
|
*
|
||
|
* For larger strings, this can find moves like
|
||
|
*
|
||
|
* XXXXX
|
||
|
* XX.XX
|
||
|
* X.*.X
|
||
|
* XX.XX
|
||
|
* XXXXX
|
||
|
*
|
||
|
* even though they are not capping moves.
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
find_cap_moves(int str, struct reading_moves *moves)
|
||
|
{
|
||
|
int alib, blib;
|
||
|
int numlibs;
|
||
|
int libs[4];
|
||
|
int i, j;
|
||
|
int ai, aj;
|
||
|
int bi, bj;
|
||
|
|
||
|
numlibs = findlib(str, 4, libs);
|
||
|
if (numlibs > 4 || numlibs < 2)
|
||
|
return;
|
||
|
|
||
|
for (i = 0; i < numlibs - 1; i++) {
|
||
|
for (j = i + 1; j < numlibs; j++) {
|
||
|
alib = libs[i];
|
||
|
blib = libs[j];
|
||
|
|
||
|
/* Check if the two liberties are located like the figure above. */
|
||
|
if (!DIAGONAL_NEIGHBORS(alib, blib))
|
||
|
continue;
|
||
|
|
||
|
ai = I(alib);
|
||
|
aj = J(alib);
|
||
|
bi = I(blib);
|
||
|
bj = J(blib);
|
||
|
/* Which of the two corner points should we use? One of them is
|
||
|
* always occupied by the string at (str), the other one is either
|
||
|
* free or occupied by something else.
|
||
|
*/
|
||
|
if (BOARD(bi, aj) == EMPTY)
|
||
|
ADD_CANDIDATE_MOVE(POS(bi, aj), 10, *moves, "find_cap");
|
||
|
else if (BOARD(ai, bj) == EMPTY)
|
||
|
ADD_CANDIDATE_MOVE(POS(ai, bj), 10, *moves, "find_cap");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/* In a situation like this:
|
||
|
*
|
||
|
* ----- the code that
|
||
|
* cO.OX follows can find
|
||
|
* XXOOX the attacking move
|
||
|
* XO.OX at c.
|
||
|
* XOOOX
|
||
|
* XXXXX
|
||
|
*
|
||
|
* The name of the function corresponds to special_rescue2, which is
|
||
|
* fairly similar to this situation.
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
special_attack2_moves(int str, int libs[2], struct reading_moves *moves)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int newlibs[3];
|
||
|
int xpos;
|
||
|
int k;
|
||
|
|
||
|
for (k = 0; k < 2; k++) {
|
||
|
if (is_suicide(libs[k], other)
|
||
|
&& (approxlib(libs[k], color, 3, newlibs) == 2)) {
|
||
|
if (newlibs[0] != libs[1-k])
|
||
|
xpos = newlibs[0];
|
||
|
else
|
||
|
xpos = newlibs[1];
|
||
|
|
||
|
if (!is_self_atari(xpos, other)) {
|
||
|
ADD_CANDIDATE_MOVE(xpos, 0, *moves, "special_attack2");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* In situations like these:
|
||
|
*
|
||
|
* ..XXX.. ...XX
|
||
|
* .XX.XX. .cO.X
|
||
|
* XXOOOXX ....X
|
||
|
* XO.O.OX XOOXX
|
||
|
* XO.c.OX XXXX.
|
||
|
* -------
|
||
|
*
|
||
|
* the code that follows can find the attacking move at c.
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
special_attack3_moves(int str, int libs[2], struct reading_moves *moves)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int xpos;
|
||
|
int apos;
|
||
|
int bpos;
|
||
|
int k;
|
||
|
|
||
|
ASSERT1(countlib(str) == 2, str);
|
||
|
|
||
|
for (k = 0; k < 2; k++) {
|
||
|
apos = libs[k];
|
||
|
bpos = libs[1-k];
|
||
|
|
||
|
if (apos == SOUTH(bpos) || apos == NORTH(bpos)) {
|
||
|
if (board[WEST(apos)] == EMPTY)
|
||
|
xpos = WEST(apos);
|
||
|
else if (board[EAST(apos)] == EMPTY)
|
||
|
xpos = EAST(apos);
|
||
|
else
|
||
|
continue;
|
||
|
}
|
||
|
else if (apos == WEST(bpos) || apos == EAST(bpos)) {
|
||
|
if (board[SOUTH(apos)] == EMPTY)
|
||
|
xpos = SOUTH(apos);
|
||
|
else if (board[NORTH(apos)] == EMPTY)
|
||
|
xpos = NORTH(apos);
|
||
|
else
|
||
|
continue;
|
||
|
}
|
||
|
else
|
||
|
return; /* Incorrect configuration, give up. */
|
||
|
|
||
|
if (!is_self_atari(xpos, other))
|
||
|
ADD_CANDIDATE_MOVE(xpos, 0, *moves, "special_attack3");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* In situations like these:
|
||
|
*
|
||
|
* ...O.O... ...O.O...
|
||
|
* XXXXOOXXX XXXXOOXXX
|
||
|
* XOOOXXO*. Xsssbbcd.
|
||
|
* .X.O..... .X.sa.e..
|
||
|
* --------- ---------
|
||
|
*
|
||
|
* the code that follows can find the attacking move at *.
|
||
|
*
|
||
|
* Also for situations in which c has three liberties, one of which in common
|
||
|
* with b, the respective attacking move is found (see reading:52 for an
|
||
|
* example).
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
special_attack4_moves(int str, int libs[2], struct reading_moves *moves)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int adj, adjs[MAXCHAIN];
|
||
|
int adj2, adjs2[MAXCHAIN];
|
||
|
int libs2[3];
|
||
|
int apos;
|
||
|
int bpos = 0;
|
||
|
int cpos;
|
||
|
int dpos;
|
||
|
int epos;
|
||
|
int clibs;
|
||
|
int dlibs;
|
||
|
int elibs;
|
||
|
int bc_common_lib;
|
||
|
int k, s, t, u;
|
||
|
|
||
|
ASSERT1(countlib(str) == 2, str);
|
||
|
|
||
|
/* To avoid making this too general, we require that both
|
||
|
* liberties are self ataris for X.
|
||
|
*/
|
||
|
if (!is_self_atari(libs[0], other)
|
||
|
|| !is_self_atari(libs[1], other))
|
||
|
return;
|
||
|
|
||
|
/* Pick up chain links with 2 liberties. */
|
||
|
adj = chainlinks2(str, adjs, 2);
|
||
|
|
||
|
for (k = 0; k < 2; k++) {
|
||
|
apos = libs[k];
|
||
|
|
||
|
/* Check that (apos) also is a liberty of one of the two liberty
|
||
|
* chain links.
|
||
|
*/
|
||
|
for (s = 0; s < adj; s++)
|
||
|
if (liberty_of_string(apos, adjs[s])) {
|
||
|
bpos = adjs[s];
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* Nothing found. */
|
||
|
if (s == adj)
|
||
|
continue;
|
||
|
|
||
|
/* Now require that (bpos) has a chain link, different from (str),
|
||
|
* also with two liberties, or with three liberties, but one in common
|
||
|
* with (bpos).
|
||
|
*/
|
||
|
adj2 = chainlinks3(bpos, adjs2, 3);
|
||
|
|
||
|
for (s = 0; s < adj2; s++) {
|
||
|
cpos = adjs2[s];
|
||
|
if (same_string(cpos, str))
|
||
|
continue;
|
||
|
|
||
|
/* Pick up the liberties of (cpos). */
|
||
|
clibs = findlib(cpos, 3, libs2);
|
||
|
|
||
|
/* No need to do something fancy if it is in atari already. */
|
||
|
if (clibs < 2)
|
||
|
continue;
|
||
|
|
||
|
/* (cpos) has three liberties, none of which in commmon with (bpos)
|
||
|
* attacking it seems too difficult. */
|
||
|
bc_common_lib = have_common_lib(bpos, cpos, NULL);
|
||
|
if (clibs > 2 && !bc_common_lib)
|
||
|
continue;
|
||
|
|
||
|
/* Try playing at a liberty. Before doing this, verify that
|
||
|
* (cpos) cannot get more than three liberties by answering on
|
||
|
* another liberty and that we are not putting ourselves in atari.
|
||
|
* We also should only allow ourselves to get fewer liberties than
|
||
|
* the defender in case (bpos) and (cpos) have a common liberty.
|
||
|
*/
|
||
|
for (t = 0; t < clibs; t++) {
|
||
|
dpos = libs2[t];
|
||
|
|
||
|
if (is_self_atari(dpos, other))
|
||
|
continue;
|
||
|
|
||
|
for (u = 0; u < clibs; u++) {
|
||
|
if (t == u)
|
||
|
continue;
|
||
|
|
||
|
epos = libs2[u];
|
||
|
|
||
|
elibs = approxlib(epos, color, 4, NULL);
|
||
|
if (elibs > 3)
|
||
|
break;
|
||
|
|
||
|
dlibs = approxlib(dpos, other, 3, NULL);
|
||
|
if (elibs > dlibs && !bc_common_lib)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (u >= clibs) /* No break occurred. */
|
||
|
ADD_CANDIDATE_MOVE(dpos, 0, *moves, "special_attack4");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* If (str) points to a string, draw_back(str, &moves)
|
||
|
* looks for a move in the following configuration which attacks
|
||
|
* the string:
|
||
|
*
|
||
|
* X* X=attacker, O=defender
|
||
|
* O.
|
||
|
*
|
||
|
* In the initial implementation we consider cases
|
||
|
* where X has exactly 2 liberties.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
draw_back_moves(int str, struct reading_moves *moves)
|
||
|
{
|
||
|
int r, k;
|
||
|
int adj, adjs[MAXCHAIN];
|
||
|
int libs[2];
|
||
|
|
||
|
adj = chainlinks2(str, adjs, 2);
|
||
|
for (r = 0; r < adj; r++) {
|
||
|
findlib(adjs[r], 2, libs);
|
||
|
for (k = 0; k < 2; k++) {
|
||
|
if (!liberty_of_string(libs[k], str)
|
||
|
&& ((ON_BOARD1(SOUTH(libs[k]))
|
||
|
&& liberty_of_string(SOUTH(libs[k]), str))
|
||
|
|| (ON_BOARD1(WEST(libs[k]))
|
||
|
&& liberty_of_string(WEST(libs[k]), str))
|
||
|
|| (ON_BOARD1(NORTH(libs[k]))
|
||
|
&& liberty_of_string(NORTH(libs[k]), str))
|
||
|
|| (ON_BOARD1(EAST(libs[k]))
|
||
|
&& liberty_of_string(EAST(libs[k]), str)))) {
|
||
|
ADD_CANDIDATE_MOVE(libs[k], 0, *moves, "draw_back");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* In the following position the reading is much simplifed if we start
|
||
|
* with the edge closing backfilling move at *.
|
||
|
*
|
||
|
* |OO...
|
||
|
* |.OOO.
|
||
|
* |.X.O.
|
||
|
* |XXXO.
|
||
|
* |.X.*.
|
||
|
* +-----
|
||
|
*
|
||
|
* This function identifies the situation
|
||
|
*
|
||
|
* ?XOb
|
||
|
* Xatc
|
||
|
* ----
|
||
|
*
|
||
|
* where a is a liberty of the attacked string, t is the proposed move,
|
||
|
* and b and c do not contain more O stones than X stones.
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
edge_closing_backfill_moves(int str, int apos, struct reading_moves *moves)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int k;
|
||
|
int bpos;
|
||
|
int cpos;
|
||
|
int number_x, number_o;
|
||
|
|
||
|
for (k = 0; k < 4; k++) {
|
||
|
int up = delta[k];
|
||
|
int right = delta[(k+1)%4];
|
||
|
if (ON_BOARD(apos - up))
|
||
|
continue;
|
||
|
if (board[apos + up] != color)
|
||
|
return;
|
||
|
if (board[apos + right] == EMPTY
|
||
|
&& (!ON_BOARD(apos - right)
|
||
|
|| board[apos - right] == color))
|
||
|
; /* Everything ok so far. */
|
||
|
else if (board[apos - right] == EMPTY
|
||
|
&& (!ON_BOARD(apos + right)
|
||
|
|| board[apos + right] == color)) {
|
||
|
/* Negate right direction. */
|
||
|
right = -right;
|
||
|
}
|
||
|
else
|
||
|
return;
|
||
|
|
||
|
if (board[apos + up + right] != other)
|
||
|
return;
|
||
|
|
||
|
bpos = apos + up + 2 * right;
|
||
|
if (!ON_BOARD(bpos))
|
||
|
return;
|
||
|
|
||
|
cpos = apos + 2 * right;
|
||
|
|
||
|
number_x = 0;
|
||
|
number_o = 0;
|
||
|
if (board[bpos] == color)
|
||
|
number_x++;
|
||
|
else if (board[bpos] == other)
|
||
|
number_o++;
|
||
|
|
||
|
if (board[cpos] == color)
|
||
|
number_x++;
|
||
|
else if (board[cpos] == other)
|
||
|
number_o++;
|
||
|
|
||
|
if (number_o > number_x)
|
||
|
return;
|
||
|
|
||
|
ADD_CANDIDATE_MOVE(apos + right, 0, *moves, "edge_closing_backfill");
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* The first version of this function seemed to induce too many
|
||
|
* variations and has therefore been replaced by a much more limited
|
||
|
* version.
|
||
|
*/
|
||
|
#if 0
|
||
|
|
||
|
/* In positions like
|
||
|
*
|
||
|
* OO...
|
||
|
* XXO*.
|
||
|
* x.X*.
|
||
|
* -----
|
||
|
*
|
||
|
* where the X stones to the left are being attacked, it is often a
|
||
|
* good idea to first consider either or both of the moves marked by *
|
||
|
* in the diagram. Notice that propose_edge_moves() doesn't help with
|
||
|
* this, since the rightmost X stone is not part of the attacked
|
||
|
* string, only the corresponding superstring.
|
||
|
*
|
||
|
* This function identifies the situation
|
||
|
*
|
||
|
* ?XO.? ?bdf?
|
||
|
* ?.X.o haceg
|
||
|
* ----- -----
|
||
|
*
|
||
|
* where a is a liberty of the attacked string, b is a stone of the
|
||
|
* attacked string, and e and f are the considered moves. Also
|
||
|
* considered is the situation where the conditions to the right are
|
||
|
* not correct but c has only two liberties anyway. If safe, the move
|
||
|
* to make atari on c is proposed.
|
||
|
*
|
||
|
* Notice, this code is disabled, as commented above.
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
edge_block_moves(int str, int apos, struct reading_moves *moves)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int cpos;
|
||
|
int dpos;
|
||
|
int epos;
|
||
|
int fpos;
|
||
|
int gpos;
|
||
|
int hpos;
|
||
|
int score;
|
||
|
int k, l;
|
||
|
|
||
|
/* Search for the right orientation. */
|
||
|
for (k = 0; k < 4; k++) {
|
||
|
int up = delta[k];
|
||
|
if (ON_BOARD(apos - up))
|
||
|
continue;
|
||
|
if (board[apos + up] != color || !same_string(apos + up, str))
|
||
|
return;
|
||
|
|
||
|
for (l = 0; l < 2; l++) {
|
||
|
int right = delta[(k+1)%4];
|
||
|
if (l == 1)
|
||
|
right = -right;
|
||
|
|
||
|
cpos = apos + right;
|
||
|
dpos = apos + right + up;
|
||
|
|
||
|
if (board[cpos] != color || board[dpos] != other)
|
||
|
continue;
|
||
|
|
||
|
epos = cpos + right;
|
||
|
fpos = dpos + right;
|
||
|
gpos = epos + right;
|
||
|
hpos = apos - right;
|
||
|
|
||
|
if (!ON_BOARD(epos))
|
||
|
continue;
|
||
|
|
||
|
if (board[epos] == EMPTY && board[fpos] == EMPTY
|
||
|
&& (board[gpos] != color)) {
|
||
|
/* Everything is set up, suggest moves at e and f. */
|
||
|
if (!ON_BOARD(hpos) || board[hpos] == color)
|
||
|
score = 0;
|
||
|
else
|
||
|
score = -5;
|
||
|
if (countlib(str) == 2)
|
||
|
score -= 10;
|
||
|
ADD_CANDIDATE_MOVE(epos, score, *moves, "edge_block-A");
|
||
|
|
||
|
if (countlib(dpos) == 1)
|
||
|
score = 25;
|
||
|
else
|
||
|
score = 0;
|
||
|
if (countlib(str) == 2)
|
||
|
score -= 10;
|
||
|
ADD_CANDIDATE_MOVE(fpos, score, *moves, "edge_block-B");
|
||
|
}
|
||
|
else if (countlib(cpos) == 2 && countlib(dpos) > 1) {
|
||
|
int libs[2];
|
||
|
int move;
|
||
|
findlib(cpos, 2, libs);
|
||
|
if (libs[0] == apos)
|
||
|
move = libs[1];
|
||
|
else
|
||
|
move = libs[0];
|
||
|
if (!is_self_atari(move, other))
|
||
|
ADD_CANDIDATE_MOVE(move, 0, *moves, "edge_block-C");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#else
|
||
|
|
||
|
/* In positions like
|
||
|
*
|
||
|
* OOX..
|
||
|
* XXO*.
|
||
|
* x.X..
|
||
|
* -----
|
||
|
*
|
||
|
* where the X stones to the left are being attacked, it is usually
|
||
|
* important to start by considering the move at *. Thus we propose
|
||
|
* the move at * with a high initial score.
|
||
|
*
|
||
|
* Also, it is often needed to prevent "crawling" along first line
|
||
|
* which can eventually give defender more liberties, like here:
|
||
|
*
|
||
|
* O.OO..X
|
||
|
* OXXO..X
|
||
|
* ...X*..
|
||
|
* -------
|
||
|
*
|
||
|
* This function identifies the situation
|
||
|
*
|
||
|
* XO.? bdf?
|
||
|
* .X.o aceg
|
||
|
* ---- ----
|
||
|
*
|
||
|
* where a is a liberty of the attacked string, b is a stone of the
|
||
|
* attacked string, and e and f are the considered moves.
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
edge_block_moves(int str, int apos, struct reading_moves *moves)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int k;
|
||
|
|
||
|
/* Search for the right orientation. */
|
||
|
for (k = 0; k < 4; k++) {
|
||
|
int l;
|
||
|
int up = delta[k];
|
||
|
|
||
|
if (ON_BOARD(apos - up))
|
||
|
continue;
|
||
|
if (board[apos + up] != color || !same_string(apos + up, str))
|
||
|
return;
|
||
|
|
||
|
for (l = 0; l < 2; l++) {
|
||
|
int right = delta[(k+1)%4];
|
||
|
int cpos;
|
||
|
int dpos;
|
||
|
int epos;
|
||
|
int fpos;
|
||
|
|
||
|
if (l == 1)
|
||
|
right = -right;
|
||
|
|
||
|
cpos = apos + right;
|
||
|
dpos = apos + right + up;
|
||
|
epos = cpos + right;
|
||
|
fpos = dpos + right;
|
||
|
|
||
|
if (board[cpos] == color && board[dpos] == other
|
||
|
&& board[epos] == EMPTY && board[fpos] == EMPTY) {
|
||
|
if (countlib(dpos) == 1) {
|
||
|
int gpos = epos + right;
|
||
|
|
||
|
/* Check if we have the first situation. */
|
||
|
if (board[gpos] != color)
|
||
|
ADD_CANDIDATE_MOVE(fpos, 30, *moves, "edge_block-A");
|
||
|
}
|
||
|
else {
|
||
|
int edge_scan;
|
||
|
|
||
|
/* Look along board edge to see if the defender's string can
|
||
|
* run away to a friend.
|
||
|
*/
|
||
|
for (edge_scan = epos; ; edge_scan += right) {
|
||
|
if (board[edge_scan] == color || board[edge_scan + up] == color) {
|
||
|
ADD_CANDIDATE_MOVE(epos, 10, *moves, "edge_block-B");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (board[edge_scan] != EMPTY || board[edge_scan + up] != EMPTY)
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
/* ================================================================ */
|
||
|
/* Defending by attacking surrounding strings */
|
||
|
/* ================================================================ */
|
||
|
|
||
|
/* Add the chainbreaking moves relative to the string (str) to the
|
||
|
* (moves) struct.
|
||
|
*/
|
||
|
static void
|
||
|
break_chain_moves(int str, struct reading_moves *moves)
|
||
|
{
|
||
|
int r;
|
||
|
int xpos;
|
||
|
int adj, adjs[MAXCHAIN];
|
||
|
|
||
|
/* Find links in atari. */
|
||
|
adj = chainlinks2(str, adjs, 1);
|
||
|
|
||
|
for (r = 0; r < adj; r++) {
|
||
|
findlib(adjs[r], 1, &xpos);
|
||
|
ADD_CANDIDATE_MOVE(xpos, 1, *moves, "break_chain");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* defend_secondary_chain1_moves() tries to break a chain by defending
|
||
|
* "secondary chain", that is, own strings surrounding a given
|
||
|
* opponent string (which is in turn a chainlink for another own
|
||
|
* string, phew... :). It only defends own strings in atari.
|
||
|
*
|
||
|
* When defending is done by stretching, it is required that the defending
|
||
|
* stone played gets at least `min_liberties', or one less if it is
|
||
|
* adjacent to the opponent chainlink.
|
||
|
*
|
||
|
* Returns true if there where any secondary strings that needed defence
|
||
|
* (which does not imply they actually where defended).
|
||
|
*/
|
||
|
static int
|
||
|
defend_secondary_chain1_moves(int str, struct reading_moves *moves,
|
||
|
int min_liberties)
|
||
|
{
|
||
|
int r, s;
|
||
|
int color = OTHER_COLOR(board[str]);
|
||
|
int xpos;
|
||
|
int adj;
|
||
|
int adj2;
|
||
|
int adjs[MAXCHAIN];
|
||
|
int adjs2[MAXCHAIN];
|
||
|
|
||
|
/* Find links in atari. */
|
||
|
adj = chainlinks2(str, adjs, 1);
|
||
|
|
||
|
for (r = 0; r < adj; r++) {
|
||
|
/* Stretch out. */
|
||
|
findlib(adjs[r], 1, &xpos);
|
||
|
if (approxlib(xpos, color, min_liberties, NULL)
|
||
|
+ neighbor_of_string(xpos, str) >= min_liberties)
|
||
|
ADD_CANDIDATE_MOVE(xpos, 0, *moves, "defend_secondary_chain1-A");
|
||
|
|
||
|
/* Capture adjacent stones in atari, if any. */
|
||
|
adj2 = chainlinks2(adjs[r], adjs2, 1);
|
||
|
for (s = 0; s < adj2; s++) {
|
||
|
findlib(adjs2[s], 1, &xpos);
|
||
|
if (!is_self_atari(xpos, color))
|
||
|
ADD_CANDIDATE_MOVE(xpos, 0, *moves, "defend_secondary_chain1-B");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return adj;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* defend_secondary_chain2_moves() tries to break a chain by defending
|
||
|
* "secondary chain", that is, own strings surrounding a given
|
||
|
* opponent string (which is in turn a chainlink for another own
|
||
|
* string, phew... :). It only defends own strings in
|
||
|
* with two liberties.
|
||
|
*
|
||
|
* When defending is done by stretching, it is required that the defending
|
||
|
* stone played gets at least `min_liberties', or one less if it is
|
||
|
* adjacent to the opponent chainlink. Defence can also be done by capturing
|
||
|
* opponent stones or trying to capture them with an atari.
|
||
|
*/
|
||
|
static void
|
||
|
defend_secondary_chain2_moves(int str, struct reading_moves *moves,
|
||
|
int min_liberties)
|
||
|
{
|
||
|
int r, s, t;
|
||
|
int color = OTHER_COLOR(board[str]);
|
||
|
int xpos;
|
||
|
int adj;
|
||
|
int adj2;
|
||
|
int adjs[MAXCHAIN];
|
||
|
int adjs2[MAXCHAIN];
|
||
|
int libs[2];
|
||
|
|
||
|
/* Find links with two liberties. */
|
||
|
adj = chainlinks2(str, adjs, 2);
|
||
|
|
||
|
for (r = 0; r < adj; r++) {
|
||
|
if (!have_common_lib(str, adjs[r], NULL))
|
||
|
continue;
|
||
|
|
||
|
/* Stretch out. */
|
||
|
findlib(adjs[r], 2, libs);
|
||
|
for (t = 0; t < 2; t++) {
|
||
|
xpos = libs[t];
|
||
|
if (approxlib(xpos, color, min_liberties, NULL)
|
||
|
+ neighbor_of_string(xpos, str) >= min_liberties)
|
||
|
ADD_CANDIDATE_MOVE(xpos, 0, *moves, "defend_secondary_chain2-A");
|
||
|
}
|
||
|
|
||
|
/* Capture adjacent stones in atari, if any. */
|
||
|
adj2 = chainlinks2(adjs[r], adjs2, 1);
|
||
|
for (s = 0; s < adj2; s++) {
|
||
|
findlib(adjs2[s], 1, &xpos);
|
||
|
if (!is_self_atari(xpos, color))
|
||
|
ADD_CANDIDATE_MOVE(xpos, 0, *moves, "defend_secondary_chain2-B");
|
||
|
}
|
||
|
|
||
|
/* Look for neighbours we can atari. */
|
||
|
adj2 = chainlinks2(adjs[r], adjs2, 2);
|
||
|
for (s = 0; s < adj2; s++) {
|
||
|
findlib(adjs2[s], 2, libs);
|
||
|
for (t = 0; t < 2; t++) {
|
||
|
/* Only atari if target has no easy escape with his other liberty. */
|
||
|
if (approxlib(libs[1-t], OTHER_COLOR(color), 3, NULL) < 3
|
||
|
&& !is_self_atari(libs[t], color)) {
|
||
|
ADD_CANDIDATE_MOVE(libs[t], 0, *moves, "defend_secondary_chain2-C");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Find moves which immediately capture chain links with 2
|
||
|
* liberties, in the sense that the links cannot escape atari.
|
||
|
*
|
||
|
* The used heuristics are slightly sloppy, so useless moves may
|
||
|
* appear occasionally. This should, however, only lead to slightly
|
||
|
* worse performance but not to incorrect results.
|
||
|
*/
|
||
|
static void
|
||
|
break_chain2_efficient_moves(int str, struct reading_moves *moves)
|
||
|
{
|
||
|
int r;
|
||
|
int adj, adjs[MAXCHAIN];
|
||
|
|
||
|
/* Find links with 2 liberties. */
|
||
|
adj = chainlinks2(str, adjs, 2);
|
||
|
|
||
|
for (r = 0; r < adj; r++)
|
||
|
do_find_break_chain2_efficient_moves(str, adjs[r], moves);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Helper function for break_chain2_efficient_moves(). */
|
||
|
static void
|
||
|
do_find_break_chain2_efficient_moves(int str, int adj,
|
||
|
struct reading_moves *moves)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int k;
|
||
|
int adj2, adjs2[MAXCHAIN];
|
||
|
int libs[2];
|
||
|
int pos1;
|
||
|
int pos2;
|
||
|
ASSERT1(countlib(adj) == 2, adj);
|
||
|
|
||
|
adj2 = chainlinks2(adj, adjs2, 1);
|
||
|
if (adj2 == 1 && countlib(str) > 2) {
|
||
|
int apos;
|
||
|
break_chain_moves(adjs2[0], moves);
|
||
|
findlib(adjs2[0], 1, &apos);
|
||
|
if (!is_self_atari(apos, color))
|
||
|
ADD_CANDIDATE_MOVE(apos, 0, *moves, "break_chain2_efficient-A");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (adj2 > 1)
|
||
|
return;
|
||
|
|
||
|
findlib(adj, 2, libs);
|
||
|
for (k = 0; k < 2; k++)
|
||
|
if (approxlib(libs[k], other, 3, NULL) <= 2
|
||
|
&& !is_self_atari(libs[1 - k], color))
|
||
|
ADD_CANDIDATE_MOVE(libs[1 - k], 0, *moves, "break_chain2_efficient-B");
|
||
|
|
||
|
/* A common special case is this kind of edge position
|
||
|
*
|
||
|
* ..XXX.
|
||
|
* X.XOO.
|
||
|
* XOOX*.
|
||
|
* ......
|
||
|
* ------
|
||
|
*
|
||
|
* where a move at * is most effective for saving the two stones
|
||
|
* to the left.
|
||
|
*
|
||
|
* The code below tries to identify this case. We use the crude
|
||
|
* heuristic that the two liberties of the X stone we want to
|
||
|
* capture should be placed diagonally and that one liberty should
|
||
|
* be on the edge. Then we propose to play the other liberty.
|
||
|
* Notice that both moves may be proposed when attacking a stone
|
||
|
* on 2-2.
|
||
|
*
|
||
|
* Update: This was too crude. Also require that the X stone is on
|
||
|
* the second line and that the proposed move is not a self-atari.
|
||
|
*/
|
||
|
if (!DIAGONAL_NEIGHBORS(libs[0], libs[1]))
|
||
|
return;
|
||
|
|
||
|
/* Since we know that the two liberties are diagonal, the following
|
||
|
* construction gives the two vertices "between" the liberties.
|
||
|
*/
|
||
|
pos1 = NORTH(gg_max(libs[0], libs[1]));
|
||
|
pos2 = SOUTH(gg_min(libs[0], libs[1]));
|
||
|
if ((board[pos1] != other
|
||
|
|| !is_edge_vertex(pos2)
|
||
|
|| !same_string(pos1, adj))
|
||
|
&& (board[pos2] != other
|
||
|
|| !is_edge_vertex(pos1)
|
||
|
|| !same_string(pos2, adj)))
|
||
|
return;
|
||
|
|
||
|
if (is_edge_vertex(libs[0]) && !is_self_atari(libs[1], color))
|
||
|
ADD_CANDIDATE_MOVE(libs[1], 1, *moves, "break_chain2_efficient-C");
|
||
|
|
||
|
if (is_edge_vertex(libs[1]) && !is_self_atari(libs[0], color))
|
||
|
ADD_CANDIDATE_MOVE(libs[0], 1, *moves, "break_chain2_efficient-C");
|
||
|
}
|
||
|
|
||
|
|
||
|
/* (str) points to a string with two or more liberties. break_chain2_moves()
|
||
|
* tries to defend this string by attacking a neighbouring string with
|
||
|
* two liberties.
|
||
|
* This is done by playing on either of its liberties
|
||
|
* (if (require_safe) is true these are only used if they are not
|
||
|
* self-ataris), taking a neighbour out of atari or by backfilling if
|
||
|
* both liberties are self-ataris.
|
||
|
*/
|
||
|
static void
|
||
|
break_chain2_moves(int str, struct reading_moves *moves, int require_safe,
|
||
|
int be_aggressive)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int r;
|
||
|
int adj;
|
||
|
int adjs[MAXCHAIN];
|
||
|
|
||
|
adj = chainlinks2(str, adjs, 2);
|
||
|
|
||
|
for (r = 0; r < adj; r++) {
|
||
|
int k;
|
||
|
int apos = adjs[r];
|
||
|
int libs[2];
|
||
|
int unsafe[2];
|
||
|
int dummy_adjs[MAXCHAIN];
|
||
|
|
||
|
findlib(apos, 2, libs);
|
||
|
|
||
|
/* If stackp > backfill_depth, don't bother playing liberties of
|
||
|
* 2-liberty strings if those also have at least one neighbor in
|
||
|
* atari. This is intended to solve reading:171 and generally reduce
|
||
|
* the number of nodes.
|
||
|
*/
|
||
|
if (stackp > backfill_depth
|
||
|
&& chainlinks2(apos, dummy_adjs, 1) > 0)
|
||
|
continue;
|
||
|
|
||
|
for (k = 0; k < 2; k++) {
|
||
|
unsafe[k] = is_self_atari(libs[k], color);
|
||
|
if (!unsafe[k]
|
||
|
|| is_ko(libs[k], color, NULL)
|
||
|
|| (!require_safe
|
||
|
&& approxlib(libs[k], other, 5, NULL) < 5))
|
||
|
ADD_CANDIDATE_MOVE(libs[k], 0, *moves, "break_chain2-A");
|
||
|
}
|
||
|
|
||
|
if (stackp <= break_chain_depth
|
||
|
|| (be_aggressive && stackp <= backfill_depth)) {
|
||
|
/* If the chain link cannot escape easily, try to defend all adjacent
|
||
|
* friendly stones in atari (if any). If there are none, defend
|
||
|
* adjacent friendly stones with only two liberties.
|
||
|
*/
|
||
|
if (approxlib(libs[0], other, 4, NULL) < 4
|
||
|
&& approxlib(libs[1], other, 4, NULL) < 4) {
|
||
|
if (!defend_secondary_chain1_moves(adjs[r], moves, 2))
|
||
|
defend_secondary_chain2_moves(adjs[r], moves, 2);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (unsafe[0] && unsafe[1]
|
||
|
&& (stackp <= backfill2_depth || have_common_lib(str, apos, NULL))) {
|
||
|
int lib;
|
||
|
|
||
|
/* Find backfilling moves. */
|
||
|
for (k = 0; k < 2; k++) {
|
||
|
int libs2[3];
|
||
|
if (approxlib(libs[k], other, 3, libs2) == 2) {
|
||
|
if (!is_self_atari(libs2[0], color))
|
||
|
ADD_CANDIDATE_MOVE(libs2[0], 0, *moves, "break_chain2-B");
|
||
|
if (!is_self_atari(libs2[1], color))
|
||
|
ADD_CANDIDATE_MOVE(libs2[1], 0, *moves, "break_chain2-B");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Consider this case (reading:188):
|
||
|
*
|
||
|
* |.OOOXXX
|
||
|
* |OXXXOOO
|
||
|
* |.X.O...
|
||
|
* +-------
|
||
|
*
|
||
|
* We cannot atari the corner X string immediatly, so we need to
|
||
|
* backfill. However, to avoid generating too many variations,
|
||
|
* we require that the opponent string is well restrained.
|
||
|
* Otherwise it could just run away while we backfill.
|
||
|
*/
|
||
|
if (approxlib(libs[0], other, 3, NULL) <= 2
|
||
|
&& approxlib(libs[1], other, 3, NULL) <= 2) {
|
||
|
if (approxlib(libs[0], color, 1, &lib) == 1
|
||
|
&& approxlib(lib, color, 3, NULL) >= 3)
|
||
|
ADD_CANDIDATE_MOVE(lib, 0, *moves, "break_chain2-C");
|
||
|
|
||
|
if (approxlib(libs[1], color, 1, &lib) == 1
|
||
|
&& approxlib(lib, color, 3, NULL) >= 3)
|
||
|
ADD_CANDIDATE_MOVE(lib, 0, *moves, "break_chain2-C");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* (str) points to a group to be defended.
|
||
|
* break_chain2_defense_moves is a wrapper around break_chain2_moves.
|
||
|
* It devalues all entries by 2.
|
||
|
*
|
||
|
* Rationale: Otherwise, these moves get overvalued by order_moves. In
|
||
|
* particular, if there is both a direct and a break_chain2 defense,
|
||
|
* then the latter one might be just an irrelevant intermediate forcing
|
||
|
* move. Hence, we should rather return the direct defense.
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
break_chain2_defense_moves(int str, struct reading_moves *moves,
|
||
|
int be_aggressive)
|
||
|
{
|
||
|
int saved_num_moves = moves->num;
|
||
|
int k;
|
||
|
|
||
|
break_chain2_moves(str, moves, !(stackp <= backfill_depth), be_aggressive);
|
||
|
for (k = saved_num_moves; k < moves->num; k++)
|
||
|
moves->score[k] = -2;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Helper function for break_chain3_moves() and
|
||
|
* superstring_break_chain_moves().
|
||
|
*/
|
||
|
static void
|
||
|
do_find_break_chain3_moves(int *chain_links, int num_chain_links,
|
||
|
struct reading_moves *moves, int be_aggressive,
|
||
|
const char *caller_function_name)
|
||
|
{
|
||
|
int other = board[chain_links[0]];
|
||
|
int color = OTHER_COLOR(other);
|
||
|
signed char move_added[BOARDMAX];
|
||
|
int possible_moves[MAX_MOVES];
|
||
|
int num_possible_moves = 0;
|
||
|
int r;
|
||
|
int k;
|
||
|
|
||
|
gg_assert(num_chain_links > 0);
|
||
|
|
||
|
memset(move_added, 0, sizeof move_added);
|
||
|
|
||
|
for (r = 0; r < num_chain_links; r++) {
|
||
|
int lib1;
|
||
|
int lib2;
|
||
|
int lib3;
|
||
|
int libs[3];
|
||
|
|
||
|
/* We make a list in the (adjs) array of the liberties
|
||
|
* of boundary strings having exactly three liberties. We mark
|
||
|
* each liberty in the mw array so that we do not list any
|
||
|
* more than once.
|
||
|
*/
|
||
|
findlib(chain_links[r], 3, libs);
|
||
|
|
||
|
/* If the 3 liberty chain easily can run away through one of the
|
||
|
* liberties, we don't play on any of the other liberties.
|
||
|
*/
|
||
|
lib1 = approxlib(libs[0], other, 4, NULL);
|
||
|
lib2 = approxlib(libs[1], other, 4, NULL);
|
||
|
if (lib1 >= 4 && lib2 >= 4)
|
||
|
continue;
|
||
|
lib3 = approxlib(libs[2], other, 4, NULL);
|
||
|
|
||
|
if ((lib1 >= 4 || lib2 >= 4) && lib3 >= 4)
|
||
|
continue;
|
||
|
|
||
|
if (lib1 >= 4) {
|
||
|
if (!move_added[libs[0]]) {
|
||
|
possible_moves[num_possible_moves++] = libs[0];
|
||
|
move_added[libs[0]] = 1;
|
||
|
}
|
||
|
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (lib2 >= 4) {
|
||
|
if (!move_added[libs[1]]) {
|
||
|
possible_moves[num_possible_moves++] = libs[1];
|
||
|
move_added[libs[1]] = 1;
|
||
|
}
|
||
|
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (lib3 >= 4) {
|
||
|
if (!move_added[libs[2]]) {
|
||
|
possible_moves[num_possible_moves++] = libs[2];
|
||
|
move_added[libs[2]] = 1;
|
||
|
}
|
||
|
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/* No easy escape, try all liberties. */
|
||
|
for (k = 0; k < 3; k++) {
|
||
|
if (!move_added[libs[k]]) {
|
||
|
possible_moves[num_possible_moves++] = libs[k];
|
||
|
move_added[libs[k]] = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (stackp <= backfill2_depth
|
||
|
|| (be_aggressive && stackp <= backfill_depth))
|
||
|
defend_secondary_chain1_moves(chain_links[r], moves, 3);
|
||
|
}
|
||
|
|
||
|
for (k = 0; k < num_possible_moves; k++) {
|
||
|
/* We do not wish to consider the move if it can be immediately
|
||
|
* recaptured, unless stackp < backfill2_depth. Beyond
|
||
|
* backfill2_depth, the necessary capturing move might not get
|
||
|
* generated in follow-up for the attacker. (This currently only
|
||
|
* makes a difference at stackp == backfill2_depth.)
|
||
|
*/
|
||
|
int move = possible_moves[k];
|
||
|
|
||
|
if (stackp <= break_chain_depth
|
||
|
|| (be_aggressive && stackp <= backfill_depth)
|
||
|
|| approxlib(move, color, 2, NULL) > 1)
|
||
|
/* We use a negative initial score here as we prefer to find
|
||
|
* direct defense moves.
|
||
|
*/
|
||
|
ADD_CANDIDATE_MOVE(move, -2, *moves, caller_function_name);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* (str) points to a group.
|
||
|
* If there is a string in the surrounding chain having
|
||
|
* exactly three liberties whose attack leads to the rescue of
|
||
|
* (str), break_chain3_moves(str, *moves) adds attack moves against
|
||
|
* the surrounding string as candidate moves.
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
break_chain3_moves(int str, struct reading_moves *moves, int be_aggressive)
|
||
|
{
|
||
|
int chain_links[MAXCHAIN];
|
||
|
int num_chain_links = chainlinks2(str, chain_links, 3);
|
||
|
|
||
|
if (num_chain_links > 0) {
|
||
|
do_find_break_chain3_moves(chain_links, num_chain_links,
|
||
|
moves, be_aggressive, "break_chain3");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* (str) points to a group.
|
||
|
* If there is a string in the surrounding chain having
|
||
|
* exactly four liberties whose attack leads to the rescue of
|
||
|
* (str), break_chain4_moves(str, *moves) adds attack moves against
|
||
|
* the surrounding string as candidate moves.
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
break_chain4_moves(int str, struct reading_moves *moves, int be_aggressive)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int r;
|
||
|
int k;
|
||
|
int u = 0, v;
|
||
|
int apos;
|
||
|
int adj;
|
||
|
int adjs[MAXCHAIN];
|
||
|
int libs[4];
|
||
|
int possible_moves[MAX_MOVES];
|
||
|
int mw[BOARDMAX];
|
||
|
|
||
|
memset(mw, 0, sizeof(mw));
|
||
|
|
||
|
adj = chainlinks2(str, adjs, 4);
|
||
|
for (r = 0; r < adj; r++) {
|
||
|
int lib1 = 0, lib2 = 0, lib3 = 0, lib4 = 0;
|
||
|
apos = adjs[r];
|
||
|
|
||
|
/* We make a list in the (adjs) array of the liberties
|
||
|
* of boundary strings having exactly four liberties. We mark
|
||
|
* each liberty in the mw array so that we do not list any
|
||
|
* more than once.
|
||
|
*/
|
||
|
findlib(apos, 4, libs);
|
||
|
|
||
|
/* If the 4 liberty chain easily can run away through one of the
|
||
|
* liberties, we don't play on any of the other liberties.
|
||
|
*/
|
||
|
lib1 = approxlib(libs[0], other, 5, NULL);
|
||
|
lib2 = approxlib(libs[1], other, 5, NULL);
|
||
|
if (lib1 >= 5 && lib2 >= 5)
|
||
|
continue;
|
||
|
lib3 = approxlib(libs[2], other, 5, NULL);
|
||
|
|
||
|
if ((lib1 >= 5 || lib2 >= 5) && lib3 >= 5)
|
||
|
continue;
|
||
|
lib4 = approxlib(libs[3], other, 5, NULL);
|
||
|
|
||
|
if ((lib1 >= 5 || lib2 >= 5 || lib3 >= 5) && lib4 >= 5)
|
||
|
continue;
|
||
|
|
||
|
if (lib1 >= 5 && !mw[libs[0]]) {
|
||
|
mw[libs[0]] = 1;
|
||
|
possible_moves[u++] = libs[0];
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (lib2 >= 5 && !mw[libs[1]]) {
|
||
|
mw[libs[1]] = 1;
|
||
|
possible_moves[u++] = libs[1];
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (lib3 >= 5 && !mw[libs[2]]) {
|
||
|
mw[libs[2]] = 1;
|
||
|
possible_moves[u++] = libs[2];
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (lib4 >= 5 && !mw[libs[3]]) {
|
||
|
mw[libs[3]] = 1;
|
||
|
possible_moves[u++] = libs[3];
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/* No easy escape, try all liberties. */
|
||
|
for (k = 0; k < 4; k++) {
|
||
|
if (!mw[libs[k]]) {
|
||
|
mw[libs[k]] = 1;
|
||
|
possible_moves[u++] = libs[k];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (stackp <= backfill2_depth
|
||
|
|| (be_aggressive && stackp <= backfill_depth))
|
||
|
defend_secondary_chain1_moves(adjs[r], moves, 4);
|
||
|
}
|
||
|
|
||
|
for (v = 0; v < u; v++) {
|
||
|
/* We do not wish to consider the move if it can be
|
||
|
* immediately recaptured, unless stackp < backfill2_depth.
|
||
|
* Beyond backfill2_depth, the necessary capturing move might not
|
||
|
* get generated in follow-up for the attacker.
|
||
|
* (This currently only makes a difference at stackp == backfill2_depth.)
|
||
|
*/
|
||
|
int xpos = possible_moves[v];
|
||
|
if (stackp <= break_chain_depth
|
||
|
|| (be_aggressive && stackp <= backfill_depth)
|
||
|
|| approxlib(xpos, color, 2, NULL) > 1)
|
||
|
/* We use a negative initial score here as we prefer to find
|
||
|
* direct defense moves.
|
||
|
*/
|
||
|
ADD_CANDIDATE_MOVE(xpos, -2, *moves, "break_chain4");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* This function looks for moves attacking those components
|
||
|
* of the surrounding chain of the superstring (see find_superstring
|
||
|
* for the definition) which have fewer than liberty_cap liberties,
|
||
|
* and which are not adjacent to the string itself, since those
|
||
|
* are tested by break_chain_moves.
|
||
|
*/
|
||
|
static void
|
||
|
superstring_break_chain_moves(int str, int liberty_cap,
|
||
|
struct reading_moves *moves)
|
||
|
{
|
||
|
int adj;
|
||
|
int adjs[MAXCHAIN];
|
||
|
int chain_links3[MAXCHAIN];
|
||
|
int num_chain_links3 = 0;
|
||
|
int k;
|
||
|
int apos;
|
||
|
|
||
|
proper_superstring_chainlinks(str, &adj, adjs, liberty_cap);
|
||
|
for (k = 0; k < adj; k++) {
|
||
|
int liberties = countlib(adjs[k]);
|
||
|
if (liberties == 1) {
|
||
|
findlib(adjs[k], 1, &apos);
|
||
|
ADD_CANDIDATE_MOVE(apos, 0, *moves, "superstring_break_chain");
|
||
|
}
|
||
|
else if (liberties == 2)
|
||
|
do_find_break_chain2_efficient_moves(str, adjs[k], moves);
|
||
|
else if (liberties == 3)
|
||
|
chain_links3[num_chain_links3++] = adjs[k];
|
||
|
}
|
||
|
|
||
|
if (num_chain_links3 > 0) {
|
||
|
do_find_break_chain3_moves(chain_links3, num_chain_links3,
|
||
|
moves, 0, "superstring_break_chain-3");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* If `str' points to a group, double_atari_chain2_moves() adds all
|
||
|
* moves which make a double atari on some strings in the surrounding
|
||
|
* chain to the moves[] array. In addition, if `generate_more_moves'
|
||
|
* is set, it adds moves that make atari on a string in the
|
||
|
* surrounding chain and are adjacent to another string with 3
|
||
|
* liberties.
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
double_atari_chain2_moves(int str, struct reading_moves *moves,
|
||
|
int generate_more_moves)
|
||
|
{
|
||
|
int r, k;
|
||
|
int adj;
|
||
|
int adjs[MAXCHAIN];
|
||
|
int libs[3];
|
||
|
int mw[BOARDMAX];
|
||
|
|
||
|
memset(mw, 0, sizeof(mw));
|
||
|
|
||
|
adj = chainlinks2(str, adjs, 2);
|
||
|
for (r = 0; r < adj; r++) {
|
||
|
findlib(adjs[r], 2, libs);
|
||
|
for (k = 0; k < 2; k++) {
|
||
|
mw[libs[k]]++;
|
||
|
if (mw[libs[k]] == 2) {
|
||
|
/* Found a double atari, but don't play there unless the move
|
||
|
* is safe for the defender.
|
||
|
*/
|
||
|
if (!is_self_atari(libs[k], board[str]))
|
||
|
ADD_CANDIDATE_MOVE(libs[k], 1, *moves, "double_atari_chain2-A");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (generate_more_moves) {
|
||
|
int adj3;
|
||
|
int adjs3[MAXCHAIN];
|
||
|
|
||
|
adj3 = chainlinks2(str, adjs3, 3);
|
||
|
for (r = 0; r < adj3; r++) {
|
||
|
findlib(adjs3[r], 3, libs);
|
||
|
for (k = 0; k < 3; k++) {
|
||
|
if (mw[libs[k]] == 1) {
|
||
|
mw[libs[k]] = 2;
|
||
|
if (!is_self_atari(libs[k], board[str]))
|
||
|
ADD_CANDIDATE_MOVE(libs[k], -3, *moves, "double_atari_chain2-B");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* ================================================================ */
|
||
|
/* Restricted Attack and Defense */
|
||
|
/* ================================================================ */
|
||
|
|
||
|
|
||
|
/* These functions try to attack and defend a string, avoiding moves
|
||
|
* from a certain set. It is assumed that as soon as the string gets
|
||
|
* three liberties, it is alive.
|
||
|
*
|
||
|
* These functions can be used to generate backfilling moves as
|
||
|
* follows: Suppose that we would like to make atari on a
|
||
|
* string, but the atari is not safe until we make a backfilling
|
||
|
* move. To find the backfilling move, we make a list of the
|
||
|
* liberties of the string under attack, declaring these moves
|
||
|
* forbidden. Neither player will play them while the restricted
|
||
|
* functions are in effect. We fill the liberty, creating a
|
||
|
* string which is under attack, and look for a defensive move
|
||
|
* which avoids the forbidden moves. This is the backfilling
|
||
|
* move.
|
||
|
*
|
||
|
* These are minimalist functions capable of reading a ladder
|
||
|
* and not much more.
|
||
|
*/
|
||
|
|
||
|
/* Given a list of moves, restricted_defend1 tries to find a
|
||
|
* move that defends the string (str) with one liberty,
|
||
|
* not considering moves from the list.
|
||
|
*/
|
||
|
int
|
||
|
restricted_defend1(int str, int *move,
|
||
|
int num_forbidden_moves, int *forbidden_moves)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int xpos;
|
||
|
int lib;
|
||
|
struct reading_moves moves;
|
||
|
int savemove = 0;
|
||
|
int savecode = 0;
|
||
|
int liberties;
|
||
|
int k;
|
||
|
|
||
|
SETUP_TRACE_INFO("restricted_defend1", str);
|
||
|
reading_node_counter++;
|
||
|
moves.num = 0;
|
||
|
|
||
|
ASSERT1(IS_STONE(board[str]), str);
|
||
|
ASSERT1(countlib(str) == 1, str);
|
||
|
|
||
|
/* (lib) will be the liberty of the string. */
|
||
|
liberties = findlib(str, 1, &lib);
|
||
|
ASSERT1(liberties == 1, str);
|
||
|
|
||
|
/* Collect moves to try in the first batch.
|
||
|
* 1. First order liberty.
|
||
|
* 2. Chain breaking moves.
|
||
|
* 3. Moves to set up a snapback.
|
||
|
*/
|
||
|
moves.pos[0] = lib;
|
||
|
moves.score[0] = 0;
|
||
|
moves.message[0] = "liberty";
|
||
|
moves.num = 1;
|
||
|
moves.num_tried = 0;
|
||
|
|
||
|
break_chain_moves(str, &moves);
|
||
|
set_up_snapback_moves(str, lib, &moves);
|
||
|
order_moves(str, &moves, color, read_function_name, NO_MOVE);
|
||
|
|
||
|
for (k = 0; k < moves.num; k++) {
|
||
|
int ko_capture;
|
||
|
|
||
|
xpos = moves.pos[k];
|
||
|
if (in_list(xpos, num_forbidden_moves, forbidden_moves))
|
||
|
continue;
|
||
|
/* To avoid loops with double ko, we do not allow any ko captures,
|
||
|
* even legal ones, if the opponent is komaster.
|
||
|
*/
|
||
|
if (is_ko(xpos, color, NULL))
|
||
|
ko_capture = 1;
|
||
|
else
|
||
|
ko_capture = 0;
|
||
|
|
||
|
if ((get_komaster() != other || !ko_capture)
|
||
|
&& trymove(xpos, color, moves.message[k], str)) {
|
||
|
int libs = countlib(str);
|
||
|
if (libs > 2) {
|
||
|
popgo();
|
||
|
SGFTRACE(xpos, WIN, "defense effective");
|
||
|
if (move)
|
||
|
*move = xpos;
|
||
|
return WIN;
|
||
|
}
|
||
|
if (libs == 2) {
|
||
|
int acode;
|
||
|
|
||
|
if (!ko_capture)
|
||
|
acode = restricted_attack2(str, NULL,
|
||
|
num_forbidden_moves, forbidden_moves);
|
||
|
else
|
||
|
acode = restricted_attack2(str, NULL,
|
||
|
num_forbidden_moves, forbidden_moves);
|
||
|
popgo();
|
||
|
if (acode == 0) {
|
||
|
SGFTRACE(xpos, WIN, "defense effective");
|
||
|
if (move)
|
||
|
*move = xpos;
|
||
|
return WIN;
|
||
|
}
|
||
|
/* if the move works with ko we save it, then look for something
|
||
|
* better.
|
||
|
*/
|
||
|
UPDATE_SAVED_KO_RESULT(savecode, savemove, acode, xpos);
|
||
|
}
|
||
|
else
|
||
|
popgo();
|
||
|
}
|
||
|
else {
|
||
|
int ko_pos;
|
||
|
if (stackp <= ko_depth
|
||
|
&& savecode == 0
|
||
|
&& (get_komaster() == EMPTY
|
||
|
|| (get_komaster() == color
|
||
|
&& get_kom_pos() == xpos))
|
||
|
&& is_ko(xpos, color, &ko_pos)
|
||
|
&& tryko(xpos, color, "restricted_defend1-B")) {
|
||
|
int libs = countlib(str);
|
||
|
if (libs > 2) {
|
||
|
popgo();
|
||
|
UPDATE_SAVED_KO_RESULT(savecode, savemove, 2, xpos);
|
||
|
}
|
||
|
else if (libs == 2) {
|
||
|
int acode;
|
||
|
acode = restricted_attack2(str, NULL,
|
||
|
num_forbidden_moves, forbidden_moves);
|
||
|
popgo();
|
||
|
UPDATE_SAVED_KO_RESULT(savecode, savemove, acode, xpos);
|
||
|
}
|
||
|
else
|
||
|
popgo();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (savecode != 0) {
|
||
|
if (move)
|
||
|
*move = savemove;
|
||
|
SGFTRACE(savemove, savecode, "saved move");
|
||
|
return savecode;
|
||
|
}
|
||
|
|
||
|
SGFTRACE(0, 0, NULL);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Given a list of moves, restricted_attack2 tries to find a
|
||
|
* move that attacks the string (str) with two liberties,
|
||
|
* not considering moves from the list.
|
||
|
*/
|
||
|
int
|
||
|
restricted_attack2(int str, int *move,
|
||
|
int num_forbidden_moves, int *forbidden_moves)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int apos;
|
||
|
int liberties;
|
||
|
int libs[2];
|
||
|
int savemove = 0;
|
||
|
int savecode = 0;
|
||
|
int k;
|
||
|
|
||
|
SETUP_TRACE_INFO("restricted_attack2", str);
|
||
|
reading_node_counter++;
|
||
|
|
||
|
str = find_origin(str);
|
||
|
ASSERT1(IS_STONE(board[str]), str);
|
||
|
ASSERT1(countlib(str) == 2, str);
|
||
|
|
||
|
/* The attack may fail if a boundary string is in atari and cannot
|
||
|
* be defended. First we must try defending such a string.
|
||
|
*/
|
||
|
/* Get the two liberties of (str). */
|
||
|
liberties = findlib(str, 2, libs);
|
||
|
ASSERT1(liberties == 2, str);
|
||
|
|
||
|
for (k = 0; k < 2; k++) {
|
||
|
int ko_pos;
|
||
|
int ko_capture;
|
||
|
|
||
|
apos = libs[k];
|
||
|
if (in_list(apos, num_forbidden_moves, forbidden_moves))
|
||
|
continue;
|
||
|
/* To avoid loops with double ko, we do not allow any ko captures,
|
||
|
* even legal ones, if the opponent is komaster.
|
||
|
*/
|
||
|
if (is_ko(apos, other, &ko_pos))
|
||
|
ko_capture = 1;
|
||
|
else
|
||
|
ko_capture = 0;
|
||
|
|
||
|
if ((get_komaster() != color || !ko_capture)
|
||
|
&& trymove(apos, other, "restricted_attack2", str)) {
|
||
|
if ((!ko_capture
|
||
|
&& !restricted_defend1(str, NULL,
|
||
|
num_forbidden_moves, forbidden_moves))
|
||
|
|| (ko_capture
|
||
|
&& !restricted_defend1(str, NULL,
|
||
|
num_forbidden_moves, forbidden_moves))) {
|
||
|
popgo();
|
||
|
SGFTRACE(apos, WIN, "attack effective");
|
||
|
if (move)
|
||
|
*move = apos;
|
||
|
return WIN;
|
||
|
}
|
||
|
popgo();
|
||
|
}
|
||
|
else if (savecode == 0
|
||
|
&& (get_komaster() == EMPTY
|
||
|
|| (get_komaster() == other
|
||
|
&& get_kom_pos() == apos))
|
||
|
&& tryko(apos, other, "restricted_attack2")) {
|
||
|
if (!restricted_defend1(str, NULL,
|
||
|
num_forbidden_moves, forbidden_moves)) {
|
||
|
popgo();
|
||
|
savecode = KO_B;
|
||
|
savemove = apos;
|
||
|
}
|
||
|
else
|
||
|
popgo();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (savecode != 0) {
|
||
|
if (move)
|
||
|
*move = savemove;
|
||
|
SGFTRACE(savemove, savecode, "saved move");
|
||
|
return savecode;
|
||
|
}
|
||
|
|
||
|
SGFTRACE(0, 0, NULL);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Returns true if the move is in a given list of moves.
|
||
|
*/
|
||
|
|
||
|
static int
|
||
|
in_list(int move, int num_moves, int *moves)
|
||
|
{
|
||
|
int k;
|
||
|
|
||
|
for (k = 0; k < num_moves; k++)
|
||
|
if (moves[k] == move)
|
||
|
return 1;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* ================================================================ */
|
||
|
/* Move ordering */
|
||
|
/* ================================================================ */
|
||
|
|
||
|
/* Parameters used in the ordering of moves to try in the tactical
|
||
|
* reading.
|
||
|
*/
|
||
|
|
||
|
/* 0 1 2 3 4 >4 */
|
||
|
static int defend_lib_score[6] = {-5, -4, 0, 3, 5, 50};
|
||
|
static int defend_not_adjacent_lib_score[5] = { 0, 0, 2, 3, 5};
|
||
|
static int defend_capture_score[6] = { 0, 6, 9, 13, 18, 24};
|
||
|
static int defend_atari_score[6] = { 0, 2, 4, 6, 7, 8};
|
||
|
static int defend_save_score[6] = { 0, 3, 6, 8, 10, 12};
|
||
|
static int defend_open_score[5] = { 0, 1, 2, 3, 4};
|
||
|
static int attack_own_lib_score[5] = {10, -4, 2, 3, 4};
|
||
|
static int attack_string_lib_score[6] = {-5, 2, 3, 7, 10, 19};
|
||
|
static int attack_capture_score[6] = {-4, 4, 10, 15, 20, 25};
|
||
|
static int attack_save_score[6] = { 0, 10, 13, 18, 21, 24};
|
||
|
static int attack_open_score[5] = { 0, 0, 2, 4, 4};
|
||
|
static int defend_not_edge_score = 5;
|
||
|
static int attack_not_edge_score = 1;
|
||
|
static int attack_ko_score = -15;
|
||
|
static int cannot_defend_penalty = -20;
|
||
|
static int safe_atari_score = 8;
|
||
|
|
||
|
|
||
|
static void
|
||
|
sgf_dumpmoves(struct reading_moves *moves, const char *funcname)
|
||
|
{
|
||
|
char buf[500];
|
||
|
char *pos;
|
||
|
int i, chars;
|
||
|
sprintf(buf, "Move order for %s: %n", funcname, &chars);
|
||
|
pos = buf + chars;
|
||
|
for (i = moves->num_tried; i < moves->num; i++) {
|
||
|
sprintf(pos, "%c%d (%d) %n",
|
||
|
J(moves->pos[i]) + 'A' + (J(moves->pos[i]) >= 8),
|
||
|
board_size - I(moves->pos[i]), moves->score[i], &chars);
|
||
|
pos += chars;
|
||
|
}
|
||
|
sgftreeAddComment(sgf_dumptree, buf);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* The string at (str) is under attack. The moves.num moves in
|
||
|
* (moves) for color have been deemed interesting in
|
||
|
* the attack or defense of the group. Most of these moves will be
|
||
|
* immediate liberties of the group.
|
||
|
*
|
||
|
* This function orders the moves in the order where the move most
|
||
|
* likely to succeed to attack or defend the string will be first and
|
||
|
* so on.
|
||
|
*
|
||
|
* Currently, this is defined as:
|
||
|
* 1) Moves which let the defending string get more liberties are more
|
||
|
* interesting.
|
||
|
* 2) Moves adjacent to the most open liberties are more
|
||
|
* interesting than those with fewer open liberties.
|
||
|
* 3) Moves on the edge are less interesting.
|
||
|
*
|
||
|
* Moves below first_move are ignored and assumed to be sorted already.
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
order_moves(int str, struct reading_moves *moves, int color,
|
||
|
const char *funcname, int killer)
|
||
|
{
|
||
|
int string_color = board[str];
|
||
|
int string_libs = countlib(str);
|
||
|
int r;
|
||
|
int i, j;
|
||
|
|
||
|
/* Don't bother sorting if only one move, or none at all. */
|
||
|
if (moves->num - moves->num_tried < 2) {
|
||
|
/* But let's still have a single candidate in the sgf output */
|
||
|
if (sgf_dumptree && moves->num > moves->num_tried)
|
||
|
sgf_dumpmoves(moves, funcname);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* Assign a score to each move. */
|
||
|
for (r = moves->num_tried; r < moves->num; r++) {
|
||
|
int move = moves->pos[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.
|
||
|
*/
|
||
|
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 */
|
||
|
|
||
|
/* We let the incremental_board code do the heavy work. */
|
||
|
incremental_order_moves(move, color, str, &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);
|
||
|
|
||
|
/* Different score strategies depending on whether the move is
|
||
|
* attacking or defending the string.
|
||
|
*/
|
||
|
if (color == string_color) {
|
||
|
/* Defense move.
|
||
|
*
|
||
|
* 1) Add twice the number of liberties the group receives by
|
||
|
* extending to the intersection of the move, if more than one.
|
||
|
* Only applicable if the move is adjacent to the group.
|
||
|
*/
|
||
|
|
||
|
int libs = approxlib(move, color, 10, NULL);
|
||
|
if (number_same_string > 0) {
|
||
|
if (libs > 5 || (libs == 4 && stackp > fourlib_depth))
|
||
|
moves->score[r] += defend_lib_score[5] + (libs - 4);
|
||
|
else
|
||
|
moves->score[r] += defend_lib_score[libs];
|
||
|
}
|
||
|
else {
|
||
|
/* Add points for the number of liberties the played stone
|
||
|
* obtains when not adjacent to the attacked string.
|
||
|
*/
|
||
|
if (libs > 4)
|
||
|
moves->score[r] += defend_not_adjacent_lib_score[4];
|
||
|
else
|
||
|
moves->score[r] += defend_not_adjacent_lib_score[libs];
|
||
|
}
|
||
|
|
||
|
/* 2) Add the number of open liberties near the move to its score. */
|
||
|
gg_assert(number_open <= 4);
|
||
|
moves->score[r] += defend_open_score[number_open];
|
||
|
|
||
|
/* 3) Add a bonus if the move is not on the edge.
|
||
|
*/
|
||
|
if (number_edges == 0 || captured_stones > 0)
|
||
|
moves->score[r] += defend_not_edge_score;
|
||
|
|
||
|
/* 4) Add thrice the number of captured stones. */
|
||
|
if (captured_stones <= 5)
|
||
|
moves->score[r] += defend_capture_score[captured_stones];
|
||
|
else
|
||
|
moves->score[r] += defend_capture_score[5] + captured_stones;
|
||
|
|
||
|
/* 5) Add points for stones put into atari, unless this is a
|
||
|
* self atari.
|
||
|
*/
|
||
|
if (libs + captured_stones > 1) {
|
||
|
if (threatened_stones <= 5)
|
||
|
moves->score[r] += defend_atari_score[threatened_stones];
|
||
|
else
|
||
|
moves->score[r] += defend_atari_score[5] + threatened_stones;
|
||
|
}
|
||
|
|
||
|
/* 6) Add a bonus for saved stones. */
|
||
|
if (saved_stones <= 5)
|
||
|
moves->score[r] += defend_save_score[saved_stones];
|
||
|
else
|
||
|
moves->score[r] += defend_save_score[5];
|
||
|
}
|
||
|
else {
|
||
|
/* Attack move.
|
||
|
*
|
||
|
* 1) Add the number of liberties the attacker gets when playing
|
||
|
* there, but never more than four.
|
||
|
*/
|
||
|
int libs = approxlib(move, color, 4, NULL);
|
||
|
if (libs > 4)
|
||
|
libs = 4;
|
||
|
moves->score[r] += attack_own_lib_score[libs];
|
||
|
|
||
|
if (libs == 0 && captured_stones == 1)
|
||
|
moves->score[r] += attack_ko_score;
|
||
|
|
||
|
/* 2) If the move is not a self atari and adjacent to the
|
||
|
* string, add the number of liberties the opponent would
|
||
|
* gain by playing there. If the string has two liberties,
|
||
|
* self-ataris are also ok since they may be snapbacks, but
|
||
|
* only if a single stone is sacrificed.
|
||
|
*/
|
||
|
if ((libs + captured_stones > 1 || (string_libs <= 2 && number_own == 0))
|
||
|
&& number_same_string > 0) {
|
||
|
int safe_atari;
|
||
|
int liberties = approxlib(move, string_color, 5, NULL);
|
||
|
if (liberties > 5 || (liberties == 4 && stackp > fourlib_depth))
|
||
|
liberties = 5;
|
||
|
moves->score[r] += attack_string_lib_score[liberties];
|
||
|
|
||
|
safe_atari = (string_libs <= 2 && libs + captured_stones > 1);
|
||
|
/* The defender can't play here without getting into atari, so
|
||
|
* we probably souldn't either.
|
||
|
*/
|
||
|
if (liberties == 1 && saved_stones == 0 && !safe_atari)
|
||
|
moves->score[r] += cannot_defend_penalty;
|
||
|
|
||
|
/* Bonus if we put the attacked string into atari without
|
||
|
* ourselves getting into atari.
|
||
|
*/
|
||
|
if (safe_atari)
|
||
|
moves->score[r] += safe_atari_score;
|
||
|
}
|
||
|
|
||
|
/* 3) Add the number of open liberties near the move to its score. */
|
||
|
gg_assert(number_open <= 4);
|
||
|
moves->score[r] += attack_open_score[number_open];
|
||
|
|
||
|
/* 4) Add a bonus if the move is not on the edge. */
|
||
|
if (number_edges == 0)
|
||
|
moves->score[r] += attack_not_edge_score;
|
||
|
|
||
|
/* 5) Add twice the number of captured stones. */
|
||
|
if (captured_stones <= 5)
|
||
|
moves->score[r] += attack_capture_score[captured_stones];
|
||
|
else
|
||
|
moves->score[r] += attack_capture_score[5];
|
||
|
|
||
|
/* 6) Add a bonus for saved stones. */
|
||
|
if (saved_stones <= 5)
|
||
|
moves->score[r] += attack_save_score[saved_stones];
|
||
|
else
|
||
|
moves->score[r] += attack_save_score[5];
|
||
|
}
|
||
|
if (moves->pos[r] == killer)
|
||
|
moves->score[r] += 50;
|
||
|
}
|
||
|
|
||
|
/* 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 = moves->num_tried; i < moves->num-1; i++) {
|
||
|
int maxscore = moves->score[i];
|
||
|
int max_at = 0; /* This is slightly faster than max_at = i. */
|
||
|
|
||
|
/* Find the move with the biggest score. */
|
||
|
for (j = i + 1; j < moves->num; j++) {
|
||
|
if (moves->score[j] > maxscore) {
|
||
|
maxscore = moves->score[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 != 0) {
|
||
|
int temp = moves->pos[max_at];
|
||
|
const char *temp_message = moves->message[max_at];
|
||
|
|
||
|
moves->pos[max_at] = moves->pos[i];
|
||
|
moves->score[max_at] = moves->score[i];
|
||
|
moves->message[max_at] = moves->message[i];
|
||
|
|
||
|
moves->pos[i] = temp;
|
||
|
moves->score[i] = maxscore;
|
||
|
moves->message[i] = temp_message;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
if (0) {
|
||
|
gprintf("%oVariation %d:\n", count_variations);
|
||
|
for (i = moves->num_tried; i < moves->num; i++)
|
||
|
gprintf("%o %1M %d\n", moves->pos[i], moves->score[i]);
|
||
|
}
|
||
|
|
||
|
if (sgf_dumptree)
|
||
|
sgf_dumpmoves(moves, funcname);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Set new values for the move ordering parameters. */
|
||
|
void
|
||
|
tune_move_ordering(int params[MOVE_ORDERING_PARAMETERS])
|
||
|
{
|
||
|
int k;
|
||
|
for (k = 0; k < 6; k++) {
|
||
|
defend_lib_score[k] = params[k];
|
||
|
if (k < 5)
|
||
|
defend_not_adjacent_lib_score[k] = params[k + 6];
|
||
|
defend_capture_score[k] = params[k + 11];
|
||
|
defend_atari_score[k] = params[k + 17];
|
||
|
defend_save_score[k] = params[k + 23];
|
||
|
if (k < 5) {
|
||
|
defend_open_score[k] = params[k + 29];
|
||
|
attack_own_lib_score[k] = params[k + 34];
|
||
|
}
|
||
|
attack_string_lib_score[k] = params[k + 39];
|
||
|
attack_capture_score[k] = params[k + 45];
|
||
|
attack_save_score[k] = params[k + 51];
|
||
|
if (k < 5)
|
||
|
attack_open_score[k] = params[k + 57];
|
||
|
}
|
||
|
defend_not_edge_score = params[62];
|
||
|
attack_not_edge_score = params[63];
|
||
|
attack_ko_score = params[64];
|
||
|
cannot_defend_penalty = params[65];
|
||
|
safe_atari_score = params[66];
|
||
|
|
||
|
if (verbose) {
|
||
|
gprintf("static int defend_lib_score[6] = {%d, %d, %d, %d, %d, %d};\n",
|
||
|
defend_lib_score[0], defend_lib_score[1],
|
||
|
defend_lib_score[2], defend_lib_score[3],
|
||
|
defend_lib_score[4], defend_lib_score[5]);
|
||
|
gprintf("static int defend_not_adjacent_lib_score[5] = {%d, %d, %d, %d, %d};\n",
|
||
|
defend_not_adjacent_lib_score[0], defend_not_adjacent_lib_score[1],
|
||
|
defend_not_adjacent_lib_score[2], defend_not_adjacent_lib_score[3],
|
||
|
defend_not_adjacent_lib_score[4]);
|
||
|
gprintf("static int defend_capture_score[6] = {%d, %d, %d, %d, %d, %d};\n",
|
||
|
defend_capture_score[0], defend_capture_score[1],
|
||
|
defend_capture_score[2], defend_capture_score[3],
|
||
|
defend_capture_score[4], defend_capture_score[5]);
|
||
|
gprintf("static int defend_atari_score[6] = {%d, %d, %d, %d, %d, %d};\n",
|
||
|
defend_atari_score[0], defend_atari_score[1],
|
||
|
defend_atari_score[2], defend_atari_score[3],
|
||
|
defend_atari_score[4], defend_atari_score[5]);
|
||
|
gprintf("static int defend_save_score[6] = {%d, %d, %d, %d, %d, %d};\n",
|
||
|
defend_save_score[0], defend_save_score[1],
|
||
|
defend_save_score[2], defend_save_score[3],
|
||
|
defend_save_score[4], defend_save_score[5]);
|
||
|
gprintf("static int defend_open_score[5] = {%d, %d, %d, %d, %d};\n",
|
||
|
defend_open_score[0], defend_open_score[1],
|
||
|
defend_open_score[2], defend_open_score[3],
|
||
|
defend_open_score[4]);
|
||
|
gprintf("static int attack_own_lib_score[5] = {%d, %d, %d, %d, %d};\n",
|
||
|
attack_own_lib_score[0], attack_own_lib_score[1],
|
||
|
attack_own_lib_score[2], attack_own_lib_score[3],
|
||
|
attack_own_lib_score[4]);
|
||
|
gprintf("static int attack_string_lib_score[6] = {%d, %d, %d, %d, %d, %d};\n",
|
||
|
attack_string_lib_score[0], attack_string_lib_score[1],
|
||
|
attack_string_lib_score[2], attack_string_lib_score[3],
|
||
|
attack_string_lib_score[4], attack_string_lib_score[5]);
|
||
|
gprintf("static int attack_capture_score[6] = {%d, %d, %d, %d, %d, %d};\n",
|
||
|
attack_capture_score[0], attack_capture_score[1],
|
||
|
attack_capture_score[2], attack_capture_score[3],
|
||
|
attack_capture_score[4], attack_capture_score[5]);
|
||
|
gprintf("static int attack_save_score[6] = {%d, %d, %d, %d, %d, %d};\n",
|
||
|
attack_save_score[0], attack_save_score[1],
|
||
|
attack_save_score[2], attack_save_score[3],
|
||
|
attack_save_score[4], attack_save_score[5]);
|
||
|
gprintf("static int attack_open_score[5] = {%d, %d, %d, %d, %d};\n",
|
||
|
attack_open_score[0], attack_open_score[1],
|
||
|
attack_open_score[2], attack_open_score[3],
|
||
|
attack_open_score[4]);
|
||
|
gprintf("static int defend_not_edge_score = %d;\n", defend_not_edge_score);
|
||
|
gprintf("static int attack_not_edge_score = %d;\n", attack_not_edge_score);
|
||
|
gprintf("static int attack_ko_score = %d;\n", attack_ko_score);
|
||
|
gprintf("static int cannot_defend_penalty = %d;\n", cannot_defend_penalty);
|
||
|
gprintf("static int safe_atari_score = %d;\n", safe_atari_score);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/* ================================================================ */
|
||
|
/* Reading utilities */
|
||
|
/* ================================================================ */
|
||
|
|
||
|
|
||
|
static int safe_move_cache[BOARDMAX][2];
|
||
|
static int safe_move_cache_when[BOARDMAX][2];
|
||
|
static void clear_safe_move_cache(void);
|
||
|
|
||
|
static void
|
||
|
clear_safe_move_cache(void)
|
||
|
{
|
||
|
int k;
|
||
|
|
||
|
for (k = BOARDMIN; k < BOARDMAX; k++) {
|
||
|
safe_move_cache_when[k][0] = -1;
|
||
|
safe_move_cache_when[k][1] = -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* safe_move(move, color) checks whether a move at (move) is illegal
|
||
|
* or can immediately be captured. If stackp==0 the result is cached.
|
||
|
* If the move only can be captured by a ko, it's considered safe.
|
||
|
* This may or may not be a good convention.
|
||
|
*
|
||
|
* For performance reasons, the result of this function is cached.
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
safe_move(int move, int color)
|
||
|
{
|
||
|
int safe = 0;
|
||
|
static int initialized = 0;
|
||
|
int ko_move;
|
||
|
|
||
|
if (!initialized) {
|
||
|
clear_safe_move_cache();
|
||
|
initialized = 1;
|
||
|
}
|
||
|
|
||
|
/* If we have this position cached, use the previous value.
|
||
|
* Only use cached values when stackp is 0 and reading is not being done
|
||
|
* at a modified depth.
|
||
|
*/
|
||
|
if (stackp == 0
|
||
|
&& depth_offset == 0
|
||
|
&& safe_move_cache_when[move][color == BLACK] == position_number)
|
||
|
return safe_move_cache[move][color == BLACK];
|
||
|
|
||
|
/* Otherwise calculate the value... */
|
||
|
if (komaster_trymove(move, color, "safe_move", 0, &ko_move, 1)) {
|
||
|
safe = REVERSE_RESULT(attack(move, NULL));
|
||
|
if (ko_move && safe != 0)
|
||
|
safe = KO_B;
|
||
|
popgo();
|
||
|
}
|
||
|
|
||
|
/* ...and store it in the cache.
|
||
|
* FIXME: Only store result in cache when we're working at
|
||
|
* full depth.
|
||
|
*
|
||
|
* Comment: This is currently not a problem since no reduced depth
|
||
|
* reading is performed.
|
||
|
*/
|
||
|
if (stackp == 0 && depth_offset == 0) {
|
||
|
if (0)
|
||
|
gprintf("Safe move at %1m for %s cached when depth=%d, position number=%d\n",
|
||
|
move, color_to_string(color), depth, position_number);
|
||
|
safe_move_cache_when[move][color == BLACK] = position_number;
|
||
|
safe_move_cache[move][color == BLACK] = safe;
|
||
|
}
|
||
|
|
||
|
return safe;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Checks if a move by color makes an opponent move at pos a self atari.
|
||
|
*/
|
||
|
int
|
||
|
does_secure(int color, int move, int pos)
|
||
|
{
|
||
|
int result = 0;
|
||
|
if (trymove(move, color, NULL, NO_MOVE)) {
|
||
|
if (is_self_atari(pos, OTHER_COLOR(color)))
|
||
|
result = 1;
|
||
|
popgo();
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* ===================== Statistics ============================= */
|
||
|
|
||
|
|
||
|
/* Clear statistics. */
|
||
|
void
|
||
|
reset_reading_node_counter()
|
||
|
{
|
||
|
reading_node_counter = 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Retrieve statistics. */
|
||
|
int
|
||
|
get_reading_node_counter()
|
||
|
{
|
||
|
return reading_node_counter;
|
||
|
}
|
||
|
|
||
|
/* ============ Reading shadow =============== */
|
||
|
|
||
|
/* Draw the reading shadow, for debugging purposes */
|
||
|
|
||
|
void
|
||
|
draw_reading_shadow()
|
||
|
{
|
||
|
int i, j;
|
||
|
int c = ' ';
|
||
|
int pos;
|
||
|
|
||
|
start_draw_board();
|
||
|
|
||
|
for (i = 0; i < board_size; i++) {
|
||
|
fprintf(stderr, "\n%2d", board_size - i);
|
||
|
|
||
|
for (j = 0; j < board_size; j++) {
|
||
|
pos = POS(i, j);
|
||
|
if (!shadow[pos] && board[pos] == EMPTY)
|
||
|
c = '.';
|
||
|
else if (!shadow[pos] && board[pos] == WHITE)
|
||
|
c = 'O';
|
||
|
else if (!shadow[pos] && board[pos] == BLACK)
|
||
|
c = 'X';
|
||
|
if (shadow[pos] && board[pos] == EMPTY)
|
||
|
c = ',';
|
||
|
else if (shadow[pos] && board[pos] == WHITE)
|
||
|
c = 'o';
|
||
|
else if (shadow[pos] && board[pos] == BLACK)
|
||
|
c = 'x';
|
||
|
|
||
|
fprintf(stderr, " %c", c);
|
||
|
}
|
||
|
|
||
|
fprintf(stderr, " %d", board_size - i);
|
||
|
}
|
||
|
|
||
|
end_draw_board();
|
||
|
}
|
||
|
|
||
|
|
||
|
/* ================================================================ */
|
||
|
/* Code for special purposes. */
|
||
|
/* ================================================================ */
|
||
|
|
||
|
/* simple_ladder(str, &move) tries to capture a string (str)
|
||
|
* with exactly two liberties under simplified assumptions, which are
|
||
|
* adequate in a ladder. The rules are as follows:
|
||
|
*
|
||
|
* 1. The attacker is allowed to play at each of the two liberties,
|
||
|
* but no other move. If the move was legal, the string now has
|
||
|
* exactly one liberty.
|
||
|
* 2. The defender must move out of atari. This can only be done by
|
||
|
* either extending at the liberty or capturing a neighboring
|
||
|
* string which was in atari. All such moves may be tested.
|
||
|
* 3. Depending on the resulting number of liberties of the string
|
||
|
* after the defender's move, we value each node as follows:
|
||
|
*
|
||
|
* 3 or more liberties: the attack has failed
|
||
|
* 2 liberties: recurse
|
||
|
* 1 liberty: the attack has succeeded
|
||
|
*
|
||
|
* illegal move for the defender: successful attack
|
||
|
* illegal move for the attacker: failed attack
|
||
|
*
|
||
|
* Return codes are as usual 0 for failure, WIN for success, KO_A for
|
||
|
* a ko where the defender must make the first ko threat and KO_B for
|
||
|
* a ko where the attacked has to make the first threat. If the attack
|
||
|
* was successful, (*move) contains the attacking move, unless it is a
|
||
|
* null pointer.
|
||
|
*
|
||
|
* The differences compared to the attack2()/defend1() combination for
|
||
|
* reading ladders is that this one is a strict ladder reader which
|
||
|
* never allows the defender to have more than one liberty when it's
|
||
|
* in turn to move. This has a number of consequences.
|
||
|
*
|
||
|
* 1. This function will miss tactical captures involving other
|
||
|
* techniques than the ladder.
|
||
|
*
|
||
|
* 2. This function is faster because it gives up faster when the
|
||
|
* ladder doesn't work. In particular it can't branch out in a huge
|
||
|
* tree of exotic variations.
|
||
|
*
|
||
|
* 3. This function always reads ladders to the very end. There are no
|
||
|
* depth limits or other assumptions to stop reading prematurely.
|
||
|
*
|
||
|
* 4. If this function returns WIN, it is guaranteed that the defender
|
||
|
* has no way whatsoever to escape, all possibilities are tried.
|
||
|
* The converse is definitely not true.
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
simple_ladder(int str, int *move)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int apos;
|
||
|
int libs[2];
|
||
|
int savemove = 0;
|
||
|
int savecode = 0;
|
||
|
int dcode;
|
||
|
int k;
|
||
|
struct reading_moves moves;
|
||
|
|
||
|
SETUP_TRACE_INFO("simple_ladder", str);
|
||
|
reading_node_counter++;
|
||
|
moves.num = 0;
|
||
|
moves.num_tried = 0;
|
||
|
|
||
|
str = find_origin(str);
|
||
|
ASSERT1(IS_STONE(board[str]), str);
|
||
|
ASSERT1(countlib(str) == 2, str);
|
||
|
|
||
|
/* Give up if we attacked depending on ko for too long. */
|
||
|
if (stackp > depth + 20 && get_komaster() == OTHER_COLOR(board[str])) {
|
||
|
SGFTRACE(0, 0, NULL);
|
||
|
if (move)
|
||
|
*move = PASS_MOVE;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Get the two liberties of (str). */
|
||
|
findlib(str, 2, libs);
|
||
|
|
||
|
/* If the defender can get enough liberties by playing one of these
|
||
|
* two, then we have no choice but to block there and consequently,
|
||
|
* it is unnecesary to try the other liberty.
|
||
|
*/
|
||
|
|
||
|
if (approxlib(libs[0], color, 4, NULL) <= 3)
|
||
|
ADD_CANDIDATE_MOVE(libs[1], 0, moves, "simple_ladder");
|
||
|
if (approxlib(libs[1], color, 4, NULL) <= 3)
|
||
|
ADD_CANDIDATE_MOVE(libs[0], 0, moves, "simple_ladder");
|
||
|
|
||
|
order_moves(str, &moves, other, read_function_name, NO_MOVE);
|
||
|
|
||
|
for (k = 0; k < moves.num; k++) {
|
||
|
int ko_move;
|
||
|
|
||
|
apos = moves.pos[k];
|
||
|
if (komaster_trymove(apos, other, moves.message[k], str,
|
||
|
&ko_move, savecode == 0)) {
|
||
|
if (!ko_move) {
|
||
|
dcode = simple_ladder_defend(str, NULL);
|
||
|
if (dcode != WIN) {
|
||
|
if (dcode == 0) {
|
||
|
popgo();
|
||
|
SGFTRACE(apos, WIN, "attack effective");
|
||
|
if (move)
|
||
|
*move = apos;
|
||
|
return WIN;
|
||
|
}
|
||
|
UPDATE_SAVED_KO_RESULT(savecode, savemove, dcode, apos);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
if (simple_ladder_defend(str, NULL) != WIN) {
|
||
|
savemove = apos;
|
||
|
savecode = KO_B;
|
||
|
}
|
||
|
}
|
||
|
popgo();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
RETURN_RESULT(savecode, savemove, move, "saved move");
|
||
|
}
|
||
|
|
||
|
|
||
|
static int
|
||
|
simple_ladder_defend(int str, int *move)
|
||
|
{
|
||
|
int color = board[str];
|
||
|
int xpos;
|
||
|
int lib;
|
||
|
struct reading_moves moves;
|
||
|
int savemove = 0;
|
||
|
int savecode = 0;
|
||
|
int k;
|
||
|
|
||
|
SETUP_TRACE_INFO("simple_ladder_defend", str);
|
||
|
reading_node_counter++;
|
||
|
|
||
|
ASSERT1(IS_STONE(board[str]), str);
|
||
|
ASSERT1(countlib(str) == 1, str);
|
||
|
|
||
|
/* lib will be the liberty of the string. */
|
||
|
findlib(str, 1, &lib);
|
||
|
|
||
|
moves.pos[0] = lib;
|
||
|
moves.score[0] = 0;
|
||
|
moves.message[0] = "liberty";
|
||
|
moves.num = 1;
|
||
|
moves.num_tried = 0;
|
||
|
|
||
|
break_chain_moves(str, &moves);
|
||
|
order_moves(str, &moves, color, read_function_name, NO_MOVE);
|
||
|
|
||
|
for (k = 0; k < moves.num; k++) {
|
||
|
int ko_move;
|
||
|
|
||
|
xpos = moves.pos[k];
|
||
|
if (komaster_trymove(xpos, color, moves.message[k], str,
|
||
|
&ko_move, savecode == 0)) {
|
||
|
int acode;
|
||
|
int new_libs = countlib(str);
|
||
|
if (new_libs > 2)
|
||
|
acode = 0;
|
||
|
else if (new_libs < 2)
|
||
|
acode = WIN;
|
||
|
else
|
||
|
acode = simple_ladder(str, NULL);
|
||
|
popgo();
|
||
|
|
||
|
if (!ko_move)
|
||
|
CHECK_RESULT(savecode, savemove, acode, xpos, move,
|
||
|
"defense effective");
|
||
|
else {
|
||
|
if (acode != WIN) {
|
||
|
savemove = xpos;
|
||
|
savecode = KO_B;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
RETURN_RESULT(savecode, savemove, move, "saved move");
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Local Variables:
|
||
|
* tab-width: 8
|
||
|
* c-basic-offset: 2
|
||
|
* End:
|
||
|
*/
|