/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* 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. *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* This file contains functions that deals with threats and,
* especially, combinations of threats.
static void find_double_threats(int color
);
/* Generate move reasons for combination attacks and defenses against
* This is one of the move generators called from genmove().
signed char defense_points
[BOARDMAX
];
int other
= OTHER_COLOR(color
);
/* Find intersections with multiple threats. */
find_double_threats(color
);
gprintf("\nlooking for combination attacks ...\n");
aa_val
= atari_atari(color
, &attack_point
, NULL
, save_verbose
);
gprintf("Combination attack for %C with size %d found at %1m\n",
color
, aa_val
, attack_point
);
add_my_atari_atari_move(attack_point
, aa_val
);
aa_val
= atari_atari(other
, &attack_point
, defense_points
, save_verbose
);
gprintf("Combination attack for %C with size %d found at %1m\n",
other
, aa_val
, attack_point
);
for (pos
= BOARDMIN
; pos
< BOARDMAX
; pos
++) {
if (ON_BOARD(pos
) && defense_points
[pos
]) {
add_your_atari_atari_move(pos
, aa_val
);
gprintf("- defense at %1m\n", pos
);
#define MAX_THREATENED_STRINGS 10 /* Should be enough for one intersection */
find_double_threats(int color
)
for (ii
= BOARDMIN
; ii
< BOARDMAX
; ii
++) {
int num_a_threatened_groups
;
int a_threatened_groups
[MAX_THREATENED_STRINGS
];
int num_d_threatened_groups
;
int d_threatened_groups
[MAX_THREATENED_STRINGS
];
/* Generate an EITHER_MOVE move reasons for each pair of the
* threatened strings. We must also remove the threats, because
* otherwise we would get followup points for them as well.
* - This is perhaps not the best way to do it, but realistically
* it will be seldom that more than two strings are threatened
* at the same point. Still, we should find a better way.
* - EITHER_MOVE should be generalized to more than two strings.
num_a_threatened_groups
= get_attack_threats(ii
, MAX_THREATENED_STRINGS
,
if (num_a_threatened_groups
> 1) {
if (trymove(ii
, color
, "find_double_threats-A", ii
)) {
for (k
= 0; k
< num_a_threatened_groups
- 1; ++k
)
for (l
= k
+ 1; l
< num_a_threatened_groups
; ++l
) {
/* Note: If we used attack_either() here instead of trymove()
* and !defend_both(), we would not make use of the fact
* that we already know of a common threat point for
* Besides, attack_either is currently (3.1.11) not very good.
* The call to attack() is intended to detect the case
* where the move at ii is a snapback capture.
if (board
[a_threatened_groups
[k
]] == EMPTY
|| board
[a_threatened_groups
[l
]] == EMPTY
) {
TRACE("Double threat at %1m, either %1m or %1m attacked.\n",
ii
, a_threatened_groups
[k
], a_threatened_groups
[l
]);
add_either_move(ii
, ATTACK_STRING
, a_threatened_groups
[k
],
ATTACK_STRING
, a_threatened_groups
[l
]);
remove_attack_threat_move(ii
, a_threatened_groups
[k
]);
remove_attack_threat_move(ii
, a_threatened_groups
[l
]);
else if (!defend_both(a_threatened_groups
[k
],
a_threatened_groups
[l
])) {
TRACE("Double threat at %1m, either %1m or %1m attacked.\n",
ii
, a_threatened_groups
[k
], a_threatened_groups
[l
]);
add_either_move(ii
, ATTACK_STRING
, a_threatened_groups
[k
],
ATTACK_STRING
, a_threatened_groups
[l
]);
remove_attack_threat_move(ii
, a_threatened_groups
[k
]);
remove_attack_threat_move(ii
, a_threatened_groups
[l
]);
* - combinations of owl threats and other threats
* - combinations of threats to cut and connect
* - combinations of breakins into enemy territory
/* ================================================================ */
/* Combination attacks */
/* ================================================================ */
/* atari_atari(color, *move) looks for a series of ataris on
* strings of the other color culminating in the capture of
* a string which is thought to be invulnerable by the reading
* code. Such a move can be missed since it may be that each
* string involved individually can be rescued, but nevertheless
* one of them can be caught. The simplest example is a double
* atari. The return value is the size of the smallest opponent
* One danger with this scheme is that the first atari
* tried might be irrelevant to the actual combination.
* To detect this possibility, once we've found a combination,
* we mark that first move as forbidden, then try again. If
* no combination of the same size or larger turns up, then
* the first move was indeed essential.
* For the purpose of the move generation, returns the
* size of the smallest of the worms under attack.
/* Local struct to keep track of atari_atari attack moves and what
#define AA_MAX_TARGETS_PER_MOVE 4
int target
[AA_MAX_TARGETS_PER_MOVE
];
#define AA_MAX_MOVES MAX_BOARD * MAX_BOARD
static int aa_status
[BOARDMAX
]; /* ALIVE, DEAD or CRITICAL */
static int forbidden
[BOARDMAX
];
static int aa_values
[BOARDMAX
];
static void compute_aa_status(int color
,
const signed char safe_stones
[BOARDMAX
]);
static void compute_aa_values(int color
);
static int get_aa_status(int pos
);
static int do_atari_atari(int color
, int *attack_point
, int *defense_point
,
signed char all_potential_defenses
[BOARDMAX
],
int last_friendly
, int save_verbose
, int minsize
,
signed char goal
[BOARDMAX
]);
static int atari_atari_succeeded(int color
, int *attack_point
,
int *defense_point
, int last_friendly
,
int save_verbose
, int minsize
);
static void atari_atari_find_attack_moves(int color
, int minsize
,
struct aa_move attacks
[AA_MAX_MOVES
],
signed char goal
[BOARDMAX
]);
static void atari_atari_attack_patterns(int color
, int minsize
,
struct aa_move attacks
[AA_MAX_MOVES
],
signed char goal
[BOARDMAX
]);
static void atari_atari_attack_callback(int anchor
, int color
,
static int atari_atari_find_defense_moves(int targets
[AA_MAX_TARGETS_PER_MOVE
],
int moves
[AA_MAX_MOVES
]);
static int get_aa_value(int str
);
static int update_aa_goal(signed char goal
[BOARDMAX
],
signed char new_goal
[BOARDMAX
],
static void aa_init_moves(struct aa_move attacks
[AA_MAX_MOVES
]);
static void aa_add_move(struct aa_move attacks
[AA_MAX_MOVES
],
static int aa_move_known(struct aa_move attacks
[AA_MAX_MOVES
],
static void aa_sort_moves(struct aa_move attacks
[AA_MAX_MOVES
]);
/* Set to 1 if you want verbose traces from this function. */
atari_atari(int color
, int *attack_move
, signed char defense_moves
[BOARDMAX
],
int other
= OTHER_COLOR(color
);
signed char saved_defense_moves
[BOARDMAX
];
/* Collect worm statuses of opponent's worms. We need to
* know this because we only want to report unexpected
* results. For example, we do not want to report success
* if we find we can kill a worm which is already dead.
* The worm status of empty points is set to UNKNOWN to signal
* that stones added along the way need special attention.
memset(forbidden
, 0, sizeof(forbidden
));
compute_aa_status(color
, NULL
);
compute_aa_values(color
);
memset(defense_moves
, 0, BOARDMAX
);
aa_val
= do_atari_atari(color
, &apos
, &dpos
, defense_moves
, NO_MOVE
,
/* We try excluding the first atari found and see if the
* combination still works. Repeat until failure.
memcpy(saved_defense_moves
, defense_moves
, BOARDMAX
);
memset(defense_moves
, 0, BOARDMAX
);
new_aa_val
= do_atari_atari(color
, &apos
, &dpos
, defense_moves
, NO_MOVE
,
save_verbose
, aa_val
, NULL
);
/* The last do_atari_atari call fails. When do_atari_atari fails,
* it does not change the value of (apos), so these correspond
* to a move that works and is necessary.
memcpy(defense_moves
, saved_defense_moves
, BOARDMAX
);
/* defense_moves[] contains potential defense moves. Now we
* examine which of them really work.
for (pos
= BOARDMIN
; pos
< BOARDMAX
; pos
++) {
if (!ON_BOARD(pos
) || !defense_moves
[pos
])
if (!trymove(pos
, other
, "atari_atari", NO_MOVE
)) {
gprintf("%1m deleted defense point, illegal\n", pos
);
gprintf("%1m deleted defense point, unsafe\n", pos
);
if (do_atari_atari(color
, &apos
, &dpos
, NULL
, NO_MOVE
,
save_verbose
, aa_val
, NULL
) > 0) {
gprintf("%1m deleted defense point, didn't work\n", pos
);
/* Wrapper around atari_atari_blunder_size. Check whether a
* combination attack of size at least minsize appears after move
* at (move) has been made.
* The arrays saved_dragons[] and saved_worms[] should be one for
* stones belonging to dragons or worms respectively, which are
* supposedly saved by (move).
* FIXME: We probably want to change the calling convention of this
* function to return all defense moves.
atari_atari_confirm_safety(int color
, int move
, int *defense
, int minsize
,
const signed char saved_dragons
[BOARDMAX
],
const signed char saved_worms
[BOARDMAX
])
signed char safe_stones
[BOARDMAX
];
signed char defense_moves
[BOARDMAX
];
mark_safe_stones(color
, move
, saved_dragons
, saved_worms
, safe_stones
);
blunder_size
= atari_atari_blunder_size(color
, move
, defense_moves
,
/* Return one arbitrary defense move. */
for (pos
= BOARDMIN
; pos
< BOARDMAX
; pos
++)
if (ON_BOARD(pos
) && defense_moves
[pos
]) {
return blunder_size
>= minsize
;
/* This function checks whether any new combination attack appears after
* move at (move) has been made, and returns its size (in points).
* safe_stones marks which of our stones are supposedly safe after this move.
atari_atari_blunder_size(int color
, int move
,
signed char defense_moves
[BOARDMAX
],
const signed char safe_stones
[BOARDMAX
])
int defense_point
= NO_MOVE
;
int aa_val
, after_aa_val
;
int other
= OTHER_COLOR(color
);
signed char defense_points
[BOARDMAX
];
int last_forbidden
= NO_MOVE
;
/* If aa_depth is too small, we can't see any combination attacks,
* so in this respect the move is safe enough.
memset(forbidden
, 0, sizeof(forbidden
));
memset(defense_points
, 0, sizeof(defense_points
));
compute_aa_status(other
, safe_stones
);
compute_aa_values(other
);
/* Accept illegal ko capture here. */
if (!tryko(move
, color
, NULL
))
/* Really shouldn't happen. */
abortgo(__FILE__
, __LINE__
, "trymove", move
);
aa_val
= do_atari_atari(other
, &apos
, &defense_point
, defense_points
,
if (aa_val
== 0 || defense_point
== NO_MOVE
) {
/* No sufficiently large combination attack, so the move is safe from
* On rare occasions do_atari_atari might find a combination
* but no defense. In this case we assume that the combination
while (aa_val
>= after_aa_val
&& defense_point
!= NO_MOVE
) {
/* Try dropping moves from the combination and see if it still
* works. What we really want is to get the proper defense move
aa_val
= do_atari_atari(other
, &apos
, &defense_point
, NULL
,
NO_MOVE
, 0, aa_val
, NULL
);
/* We know that a combination exists, but we don't know if
* the original move at (aa) was really relevant. So we
* try omitting it and see if a combination is still found.
compute_aa_status(other
, NULL
);
compute_aa_values(other
);
forbidden
[last_forbidden
] = 0;
aa_val
= do_atari_atari(other
, NULL
, NULL
, NULL
, NO_MOVE
, 0, 0, NULL
);
if (aa_val
>= after_aa_val
)
/* Try the potential defense moves to see which are effective. */
/* defense_points[] contains potential defense moves. Now we
* examine which of them really work.
/* FIXME: Maybe these should be moved after the tryko() below? */
compute_aa_status(other
, safe_stones
);
compute_aa_values(other
);
memcpy(defense_moves
, defense_points
, sizeof(defense_points
));
for (pos
= BOARDMIN
; pos
< BOARDMAX
; pos
++) {
if (!ON_BOARD(pos
) || !defense_moves
[pos
] || pos
== move
)
if (!trymove(pos
, color
, "atari_atari", NO_MOVE
)) {
/* Accept illegal ko capture here. */
if (!tryko(move
, color
, NULL
))
/* Really shouldn't happen. */
abortgo(__FILE__
, __LINE__
, "trymove", move
);
if (do_atari_atari(other
, &apos
, &defense_point
, NULL
, NO_MOVE
,
0, after_aa_val
, NULL
) >= after_aa_val
)
return after_aa_val
- aa_val
;
/* ---------------------------------------------------------------- */
/* Helper functions for atari_atari. */
/* ---------------------------------------------------------------- */
/* Helper function for computing the aa_status for all opponent's strings.
* If safe_stones is given, we just copy the information from there.
* If called at stackp > 0, safe_stones must be provided since the
* dragon_data is not valid then.
compute_aa_status(int color
, const signed char safe_stones
[BOARDMAX
])
int other
= OTHER_COLOR(color
);
SGFTree
*save_sgf_dumptree
= sgf_dumptree
;
int save_count_variations
= count_variations
;
int save_verbose
= verbose
;
gg_assert(safe_stones
|| stackp
== 0);
/* Collect worm statuses of opponent's worms. We need to
* know this because we only want to report unexpected
* results. For example, we do not want to report success
* if we find we can kill a worm which is already dead.
* The worm status of empty points is set to UNKNOWN to signal
* that stones added along the way need special attention.
for (pos
= BOARDMIN
; pos
< BOARDMAX
; pos
++) {
if (board
[pos
] == other
) {
if (dragon
[pos
].status
== DEAD
)
else if (dragon
[pos
].status
== CRITICAL
)
aa_status
[pos
] = CRITICAL
;
else if (worm
[pos
].attack_codes
[0] != 0) {
if (worm
[pos
].defense_codes
[0] != 0)
aa_status
[pos
] = CRITICAL
;
aa_status
[pos
] = UNKNOWN
;
/* reclassify a worm with 2 liberties as INSUBSTANTIAL if capturing
* it does not result in a live group.
for (pos
= BOARDMIN
; pos
< BOARDMAX
; pos
++) {
&& find_origin(pos
) == pos
&& aa_status
[pos
] == ALIVE
) {
/* Don't waste time running owl_substantial() if we can't safely
if (is_self_atari(libs
[0], color
)
&& is_self_atari(libs
[1], color
))
if (!owl_substantial(pos
)) {
for (pos2
= BOARDMIN
; pos2
< BOARDMAX
; pos2
++)
if (board
[pos2
] == other
&& find_origin(pos2
) == pos
)
aa_status
[pos2
] = INSUBSTANTIAL
;
if (debug
& DEBUG_ATARI_ATARI
) {
gprintf("compute_aa_status() for %C\n", color
);
gprintf("aa_status: (ALIVE worms not listed)\n");
for (pos
= BOARDMIN
; pos
< BOARDMAX
; pos
++) {
if (board
[pos
] == other
&& is_worm_origin(pos
, pos
)) {
const char *status
= "UNKNOWN (shouldn't happen)";
if (aa_status
[pos
] == DEAD
)
else if (aa_status
[pos
] == CRITICAL
)
else if (aa_status
[pos
] == INSUBSTANTIAL
)
status
= "INSUBSTANTIAL";
if (aa_status
[pos
] != ALIVE
)
gprintf("%1M: %s\n", pos
, status
);
sgf_dumptree
= save_sgf_dumptree
;
count_variations
= save_count_variations
;
/* Helper function for retrieving the aa_status for a string. We can't
* reliably do this simply by looking up aa_status[pos] since this is
* only valid at vertices which were non-empty at the start of the
* reading. For later added stones, we need to find their aa_status by
* locating a part of the string which was a worm at the beginning of
int stones
[MAX_BOARD
* MAX_BOARD
];
if (aa_status
[pos
] != UNKNOWN
)
num_stones
= findstones(pos
, MAX_BOARD
* MAX_BOARD
, stones
);
for (k
= 0; k
< num_stones
; k
++)
if (aa_status
[stones
[k
]] != UNKNOWN
)
return aa_status
[stones
[k
]];
/* Helper function for atari_atari. Here worms is the number of
* opponent worms involved in the combination, and (last_friendly) is
* the location of the last friendly move played. Moves marked
* with the forbidden array are not tried. If no move is found,
* the values of *attack_point and *defense_point are not changed.
* If not NULL, *attack_point is left pointing to the location of the
* attacking move, and *defense_point points to a move defending the
* combination. In rare cases a defensive move might not be found. If
* a non-static function calling do_atari_atari gets a return value of
* 1 but NO_MOVE as the defense point, this should be treated as
* equivalent to a return value of 0.
* The goal array limits where we are allowed to consider threats.
* Only strings for which goal is set to 1 may be threatened. If goal
* is NULL, anything may be attacked. Thus goal is typically NULL when
* do_atari_atari() is called from an external function. After the
* first threat has been made, the goal array is set to one in a
* neighborhood of the move and after subsequent threats it is
* expanded with neighborhoods of those moves. The details of this can
* be found in the function update_aa_goal().
do_atari_atari(int color
, int *attack_point
, int *defense_point
,
signed char all_potential_defenses
[BOARDMAX
], int last_friendly
,
int save_verbose
, int minsize
, signed char goal
[BOARDMAX
])
int other
= OTHER_COLOR(color
);
struct aa_move attacks
[AA_MAX_MOVES
];
int defense_moves
[AA_MAX_MOVES
];
SGFTree
*save_sgf_dumptree
;
int save_count_variations
;
if (debug
& DEBUG_ATARI_ATARI
) {
gprintf("%odo_atari_atari: ");
gprintf("%oforbidden moves: ");
for (pos
= BOARDMIN
; pos
< BOARDMAX
; pos
++)
if (ON_BOARD(pos
) && forbidden
[pos
])
for (pos
= BOARDMIN
; pos
< BOARDMAX
; pos
++)
if (ON_BOARD(pos
) && goal
[pos
])
/* First look for strings adjacent to the last friendly move played
* (or to another stone in the same string) which can be
* unexpectedly attacked. If so, the combination attack
if (last_friendly
!= NO_MOVE
) {
save_sgf_dumptree
= sgf_dumptree
;
save_count_variations
= count_variations
;
retval
= atari_atari_succeeded(color
, attack_point
, defense_point
,
last_friendly
, save_verbose
, minsize
);
sgf_dumptree
= save_sgf_dumptree
;
count_variations
= save_count_variations
;
/* FIXME: Better message. */
sgftreeAddComment(sgf_dumptree
, "attack found");
/* Find attack moves. These are typically ataris but may also be
save_sgf_dumptree
= sgf_dumptree
;
save_count_variations
= count_variations
;
atari_atari_find_attack_moves(color
, minsize
, attacks
, goal
);
sgf_dumptree
= save_sgf_dumptree
;
count_variations
= save_count_variations
;
/* Try the attacking moves and let the opponent defend. Then call
for (k
= 0; attacks
[k
].move
!= NO_MOVE
; k
++) {
int str
= attacks
[k
].target
[0];
int apos
= attacks
[k
].move
;
if (!trymove(apos
, color
, "do_atari_atari-A", str
))
if (all_potential_defenses
) {
all_potential_defenses
[apos
] = 1;
if (countlib(apos
) <= 2) {
int num_libs
= findlib(apos
, 2, libs
);
all_potential_defenses
[libs
[0]] = 1;
all_potential_defenses
[libs
[1]] = 1;
if (!IS_STONE(board
[str
])) {
/* Error situation. This could be caused by a wrong matcher status. */
if (save_verbose
|| (debug
& DEBUG_ATARI_ATARI
))
gprintf("%oError condition found by atari_atari\n");
/* Try to defend the stone (str) which is threatened. */
aa_val
= get_aa_value(str
);
/* Pick up defense moves. */
save_sgf_dumptree
= sgf_dumptree
;
save_count_variations
= count_variations
;
num_defense_moves
= atari_atari_find_defense_moves(attacks
[k
].target
,
sgf_dumptree
= save_sgf_dumptree
;
count_variations
= save_count_variations
;
for (r
= 0; r
< num_defense_moves
; r
++) {
if (all_potential_defenses
)
all_potential_defenses
[bpos
] = 1;
if (trymove(bpos
, other
, "do_atari_atari-B", str
)) {
signed char new_goal
[BOARDMAX
];
/* These moves may have been irrelevant for later
* reading, so in order to avoid horizon problems, we
* need to temporarily increase the depth values.
update_aa_goal(goal
, new_goal
, apos
, color
);
new_aa_val
= do_atari_atari(color
, NULL
, defense_point
,
apos
, save_verbose
, minsize
, new_goal
);
/* Defense successful, no need to try any further. */
/* Undo the attacking move. */
/* atari_atari successful */
if (num_defense_moves
== 0) {
if (save_verbose
|| (debug
& DEBUG_ATARI_ATARI
)) {
gprintf("%oThe worm %1m can be attacked at %1m after ", str
, apos
);
/* FIXME: Better message. */
sgftreeAddComment(sgf_dumptree
, "attack found");
save_sgf_dumptree
= sgf_dumptree
;
save_count_variations
= count_variations
;
if (!find_defense(str
, defense_point
))
*defense_point
= NO_MOVE
;
/* If no defense point is known and (apos) is a safe
* move for other, it probably defends the combination.
if ((*defense_point
== NO_MOVE
|| !safe_move(*defense_point
, other
))
&& safe_move(apos
, other
))
sgf_dumptree
= save_sgf_dumptree
;
count_variations
= save_count_variations
;
DEBUG(DEBUG_ATARI_ATARI
, "%oreturn value:%d (%1m)\n", aa_val
, str
);
/* No atari_atari attack. */
atari_atari_succeeded(int color
, int *attack_point
, int *defense_point
,
int last_friendly
, int save_verbose
, int minsize
)
int other
= OTHER_COLOR(color
);
for (pos
= BOARDMIN
; pos
< BOARDMAX
; pos
++) {
if (pos
!= find_origin(pos
))
&& get_aa_value(pos
) < minsize
)
if (get_aa_status(pos
) != ALIVE
)
if (board
[last_friendly
] != EMPTY
&& !adjacent_strings(last_friendly
, pos
))
if (board
[last_friendly
] == EMPTY
&& !liberty_of_string(last_friendly
, pos
))
if (debug
& DEBUG_ATARI_ATARI
)
gprintf("Considering attack of %1m. depth = %d.\n", pos
, depth
);
if (attack(pos
, &apos
) && !forbidden
[apos
]) {
if (save_verbose
|| (debug
& DEBUG_ATARI_ATARI
)) {
gprintf("%oThe worm %1m can be attacked at %1m after ", pos
, apos
);
/* We look for a move defending the combination.
* Normally this is found by find_defense but failing
* that, if the attacking move is a safe move for color,
if (!find_defense(pos
, defense_point
)) {
if (safe_move(apos
, other
))
*defense_point
= NO_MOVE
;
DEBUG(DEBUG_ATARI_ATARI
, "%oreturn value:%d (%1m)\n",
return get_aa_value(pos
);
#define MAX_THREAT_MOVES MAX_TACTICAL_POINTS
atari_atari_find_attack_moves(int color
, int minsize
,
struct aa_move attacks
[AA_MAX_MOVES
],
signed char goal
[BOARDMAX
])
atari_atari_attack_patterns(color
, minsize
, attacks
, goal
);
/* Sort the attack moves. */
if (debug
& DEBUG_ATARI_ATARI
) {
gprintf("Attack moves:");
for (k
= 0; k
< AA_MAX_MOVES
&& attacks
[k
].move
!= NO_MOVE
; k
++) {
gprintf("%o %1m(", attacks
[k
].move
);
for (r
= 0; r
< AA_MAX_TARGETS_PER_MOVE
; r
++) {
if (attacks
[k
].target
[r
] == NO_MOVE
)
gprintf("%o%s%1m", r
== 0 ? "" : ",", attacks
[k
].target
[r
]);
/* FIXME: Move these to a struct and pass to callback through the
static int current_minsize
;
static struct aa_move
*current_attacks
;
static int conditional_attack_point
[BOARDMAX
];
atari_atari_attack_patterns(int color
, int minsize
,
struct aa_move attacks
[AA_MAX_MOVES
],
signed char goal
[BOARDMAX
])
signed char revised_goal
[BOARDMAX
];
current_minsize
= minsize
;
current_attacks
= attacks
;
memset(conditional_attack_point
, 0, sizeof(conditional_attack_point
));
/* If goal is NULL and there are forbidden moves we need to compute
* a new goal around the forbidden moves.
if (goal
== NULL
&& update_aa_goal(goal
, revised_goal
, NO_MOVE
, color
))
for (pos
= BOARDMIN
; pos
< BOARDMAX
; pos
++)
if (ON_BOARD(pos
) && goal
[pos
])
matchpat(atari_atari_attack_callback
, color
, &aa_attackpat_db
, NULL
, goal
);
/* Try to attack every X string in the pattern, whether there is an attack
* before or not. Only exclude already known attacking moves.
atari_atari_attack_callback(int anchor
, int color
,
struct pattern
*pattern
, int ll
, void *data
)
move
= AFFINE_TRANSFORM(pattern
->move_offset
, ll
, anchor
);
/* If the pattern has a constraint, call the autohelper to see
* if the pattern must be rejected.
if (pattern
->autohelper_flag
& HAVE_CONSTRAINT
)
if (!pattern
->autohelper(ll
, move
, color
, 0))
/* If the pattern has a helper, call it to see if the pattern must
if (!pattern
->helper(pattern
, ll
, move
, color
))
/* Loop through pattern elements in search of X strings to
for (k
= 0; k
< pattern
->patlen
; ++k
) { /* match each point */
if (pattern
->patn
[k
].att
== ATT_X
) {
/* transform pattern real coordinate */
int str
= find_origin(AFFINE_TRANSFORM(pattern
->patn
[k
].offset
,
&& get_aa_value(str
) < current_minsize
)
if (aa_move_known(current_attacks
, move
, str
))
if (get_aa_status(str
) != ALIVE
)
/* Usually we don't want to play self atari. However, if we
* capture in snapback it's okay. For s class patterns we don't
if (!(pattern
->class & CLASS_s
) && is_self_atari(move
, color
)) {
if (!safe_move(move
, color
))
* Play (move) and see if there is an attack.
if (trymove(move
, color
, "attack_callback", str
)) {
int attack_point
= NO_MOVE
;
acode
= attack(str
, &attack_point
);
if ((pattern
->class & CLASS_c
)
&& !aa_move_known(current_attacks
, move
, NO_MOVE
)) {
/* Conditional pattern. */
"aa_attack pattern %s+%d (conditional) found threat on %1m at %1m with code %d\n",
pattern
->name
, ll
, str
, move
, acode
);
if (conditional_attack_point
[move
] == NO_MOVE
)
conditional_attack_point
[move
] = str
;
else if (conditional_attack_point
[move
] != str
) {
aa_add_move(current_attacks
, move
,
conditional_attack_point
[move
]);
aa_add_move(current_attacks
, move
, str
);
aa_add_move(current_attacks
, move
, str
);
"aa_attack pattern %s+%d found threat on %1m at %1m with code %d\n",
pattern
->name
, ll
, str
, move
, acode
);
atari_atari_find_defense_moves(int targets
[AA_MAX_TARGETS_PER_MOVE
],
memset(mx
, 0, sizeof(mx
));
for (r
= 0; r
< AA_MAX_TARGETS_PER_MOVE
&& targets
[r
] != NO_MOVE
; r
++) {
/* If the attack move happened to remove (str), there's no defense. */
/* Because we know (str) is threatened there is an
* attack and we can be sure find_defense() will give a
* useful defense point if it returns non-zero. Usually we
* would need to call attack_and_defend() to be certain of
if (!find_defense(str
, &move
))
moves
[num_moves
++] = move
;
if (num_moves
== AA_MAX_MOVES
)
/* Consider all moves to attack a neighbor or to play on a liberty. */
liberties
= findlib(str
, 4, libs
);
for (k
= 0; k
< liberties
; k
++) {
&& trymove(libs
[k
], board
[str
], "aa_defend-A", str
)) {
if (attack(str
, NULL
) == 0) {
moves
[num_moves
++] = libs
[k
];
if (num_moves
== AA_MAX_MOVES
)
neighbors
= chainlinks(str
, adjs
);
for (k
= 0; k
< neighbors
; k
++) {
if (attack(adjs
[k
], &attack_point
) == WIN
moves
[num_moves
++] = attack_point
;
if (num_moves
== AA_MAX_MOVES
)
/* If the neighbor has at most three liberties, try all of them
* for defense, except self-ataris.
liberties
= findlib(adjs
[k
], 3, libs
);
for (s
= 0; s
< liberties
; s
++) {
&& !is_self_atari(libs
[s
], board
[str
])
&& trymove(libs
[s
], board
[str
], "aa_defend-B", str
)) {
if (attack(str
, NULL
) == 0) {
moves
[num_moves
++] = libs
[s
];
if (num_moves
== AA_MAX_MOVES
)
if (debug
& DEBUG_ATARI_ATARI
) {
gprintf("Defense moves for %1m:", str
);
for (k
= 0; k
< num_moves
; k
++)
gprintf("%o %1m", moves
[k
]);
/* Try to guess the value of the strings. We do this by adding twice
* the number of stones to the number of liberties and second order
* liberties within the moyo around the string. This is of course
* quite crude since it doesn't take into account any strategic
* effects, e.g. a string being cutting stones.
compute_aa_values(int color
)
int other
= OTHER_COLOR(color
);
for (pos
= BOARDMIN
; pos
< BOARDMAX
; pos
++) {
|| pos
!= find_origin(pos
)
|| aa_status
[pos
] != ALIVE
) {
memset(mx
, 0, sizeof(mx
));
liberties
= findlib(pos
, MAXLIBS
, libs
);
value
= 2 * countstones(pos
);
for (r
= 0; r
< liberties
; r
++) {
&& (whose_moyo(&initial_black_influence
, libs
[r
]) == other
|| whose_moyo(&initial_white_influence
, libs
[r
]) == other
)) {
for (k
= 0; k
< 4; k
++) {
int librd
= libs
[r
] + delta
[k
];
if (!ON_BOARD1(librd
) || mx
[librd
])
if (board
[librd
] == EMPTY
&& (whose_moyo(&initial_black_influence
, librd
) == other
|| (whose_moyo(&initial_white_influence
, librd
) == other
)))
DEBUG(DEBUG_ATARI_ATARI
, "aa_value for %1m = %d\n", pos
, value
);
/* The aa_value for a string is the sum of the aa_values for all
* included strings in the original position. This will systematically
* overvalue strings which consist of multiple original strings, but
* this is okay since the defender very rarely should defend a string
* first and then sacrifice it later.
int stones
[MAX_BOARD
* MAX_BOARD
];
int num_stones
= findstones(str
, MAX_BOARD
* MAX_BOARD
, stones
);
for (k
= 0; k
< num_stones
; k
++)
value
+= aa_values
[stones
[k
]];
/* update_aa_goal(goal, new_goal, apos, color) extends the goal array
* with vertices in a neighborhood of apos. The algorithm is that
* starting at apos, a distance measure is computed to nearby
* vertices. The distance increases with one for each step through
* empty vertices and by a liberty depending number when passing
* through strings of the attacked color. Strings with 3 or fewer
* liberties are free to pass through while strings with more
* liberties cost (libs - 3) to pass through. Stones with a distance
* of 5 or less are included in the goal.
* Additionally neighborhoods of the moves in the forbidden array are
* included in the goal, to make it possible to limit the goal to a
* specific area from the beginning. This is needed when trying to
* decide which moves are relevant to the combination.
#define ENQUEUE(pos, dist) \
if ((dist) <= MAX_AA_DIST) { \
queue[queue_end++] = (pos); \
else if (dists[pos] < (dist)) \
update_aa_goal(signed char goal
[BOARDMAX
], signed char new_goal
[BOARDMAX
],
int other
= OTHER_COLOR(color
);
int queue
[MAX_BOARD
* MAX_BOARD
];
memset(new_goal
, 0, BOARDMAX
);
memcpy(new_goal
, goal
, BOARDMAX
);
memset(dists
, 0, sizeof(dists
));
queue
[queue_end
++] = apos
;
/* Disabled for now, since it does nothing but break atari_atari:16
* and trevorc:1540. It could be reactivated when the rest of the
* function would be modified in order to garanty that a forbidden
* move is strictly equivalent to a played move in terms of goal
* mapping. I doubt it would be anything worth though...
for (pos
= BOARDMIN
; pos
< BOARDMAX
; pos
++) {
if (ON_BOARD(pos
) && forbidden
[pos
]) {
queue
[queue_end
++] = pos
;
for (r
= 0; r
< queue_end
; r
++) {
int smallest_dist
= MAX_BOARD
* MAX_BOARD
;
gg_assert(queue_end
< MAX_BOARD
* MAX_BOARD
);
for (k
= r
; k
< queue_end
; k
++) {
if (dists
[queue
[k
]] < smallest_dist
) {
smallest_dist
= dists
[queue
[k
]];
queue
[r
] = queue
[best_index
];
/* FIXME: We shouldn't let dead opponent stones stop the
* propagation of distance.
* As a partial fix we include pos == apos in a test below.
for (k
= 0; k
< 4; k
++) {
int pos2
= pos
+ delta
[k
];
if ((board
[pos
] != color
|| pos
== apos
) && board
[pos2
] == EMPTY
) {
ENQUEUE(pos2
, dists
[pos
] + 1);
else if (board
[pos
] != other
&& board
[pos2
] == other
) {
int stones
[MAX_BOARD
* MAX_BOARD
];
int size
= findstones(pos2
, MAX_BOARD
* MAX_BOARD
, stones
);
int libs
= countlib(pos2
);
int deltadist
= libs
- 3;
for (s
= 0; s
< size
; s
++)
ENQUEUE(stones
[s
], dists
[pos
] + deltadist
);
/* Initialize an array with atari_atari attacks. The convention is that
* the array ends when a NO_MOVE is encountered in the move field.
aa_init_moves(struct aa_move attacks
[AA_MAX_MOVES
])
attacks
[0].move
= NO_MOVE
;
/* Add an atari_atari attack move to a struct aa_move array. If the
* move already is included in the array, we check whether the target
* also is known for that move and add it if not.
aa_add_move(struct aa_move attacks
[AA_MAX_MOVES
], int move
, int target
)
for (k
= 0; k
< AA_MAX_MOVES
; k
++)
if (attacks
[k
].move
== move
|| attacks
[k
].move
== NO_MOVE
)
/* If the array is full, give up. */
target
= find_origin(target
);
if (attacks
[k
].move
== NO_MOVE
) {
attacks
[k
].target
[0] = target
;
if (AA_MAX_TARGETS_PER_MOVE
> 0)
attacks
[k
].target
[1] = NO_MOVE
;
if (k
< AA_MAX_MOVES
- 1)
attacks
[k
+1].move
= NO_MOVE
;
/* Known move, maybe new target. */
for (r
= 0; r
< AA_MAX_TARGETS_PER_MOVE
; r
++)
if (attacks
[k
].target
[r
] == target
|| attacks
[k
].target
[r
] == NO_MOVE
)
/* No place for more targets. */
if (r
== AA_MAX_TARGETS_PER_MOVE
)
if (attacks
[k
].target
[r
] == target
)
attacks
[k
].target
[r
] = target
;
if (r
< AA_MAX_TARGETS_PER_MOVE
- 1)
attacks
[k
].target
[r
+ 1] = NO_MOVE
;
/* Check whether an atari_atari attack move is included in an struct
* aa_move array. If target is not NO_MOVE, we also require that the
* target is known for the move.
aa_move_known(struct aa_move attacks
[AA_MAX_MOVES
], int move
, int target
)
for (k
= 0; k
< AA_MAX_MOVES
; k
++)
if (attacks
[k
].move
== move
|| attacks
[k
].move
== NO_MOVE
)
/* If the array is full, give up and claim the move to be known. */
if (attacks
[k
].move
== NO_MOVE
)
/* Move known, but how about the target?
* If no target specified, just return 1.
target
= find_origin(target
);
for (r
= 0; r
< AA_MAX_TARGETS_PER_MOVE
; r
++)
if (attacks
[k
].target
[r
] == target
|| attacks
[k
].target
[r
] == NO_MOVE
)
/* No place for more targets. Give up and claim the target to be known. */
if (r
== AA_MAX_TARGETS_PER_MOVE
)
if (attacks
[k
].target
[r
] == target
)
/* Auxiliary function for aa_sort_moves(). */
target_comp_func(const void *a
, const void *b
)
int asize
= get_aa_value(*((const int *) a
));
int bsize
= get_aa_value(*((const int *) b
));
/* Auxiliary function for aa_sort_moves(). */
move_comp_func(const void *a
, const void *b
)
const struct aa_move
*aa
= a
;
const struct aa_move
*bb
= b
;
int asize
= get_aa_value(aa
->target
[0]);
int bsize
= get_aa_value(bb
->target
[0]);
/* Sort the attack moves. For each move the targets are sorted in
* decreasing size. Then the moves are sorted with increasing size
aa_sort_moves(struct aa_move attacks
[AA_MAX_MOVES
])
for (k
= 0; k
< AA_MAX_MOVES
&& attacks
[k
].move
!= NO_MOVE
; k
++) {
for (r
= 0; r
< AA_MAX_TARGETS_PER_MOVE
; r
++)
if (attacks
[k
].target
[r
] == NO_MOVE
)
gg_sort(attacks
[k
].target
, number_of_targets
,
sizeof(attacks
[k
].target
[0]), target_comp_func
);
gg_sort(attacks
, number_of_attacks
, sizeof(attacks
[0]), move_comp_func
);
/* Returns true if a move by (color) at (pos) is atari on something.
is_atari(int pos
, int color
)
int other
= OTHER_COLOR(color
);
if (!is_legal(pos
, color
))
if (board
[SOUTH(pos
)] == other
&& countlib(SOUTH(pos
)) == 2)
if (board
[WEST(pos
)] == other
&& countlib(WEST(pos
)) == 2)
if (board
[NORTH(pos
)] == other
&& countlib(NORTH(pos
)) == 2)
if (board
[EAST(pos
)] == other
&& countlib(EAST(pos
)) == 2)