pachi_py/pachi/tactics/selfatari.c (466 lines of code) (raw):
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#define DEBUG
#include "board.h"
#include "debug.h"
#include "mq.h"
#include "random.h"
#include "tactics/1lib.h"
#include "tactics/selfatari.h"
#include "nakade.h"
struct selfatari_state {
int groupcts[S_MAX];
group_t groupids[S_MAX][4];
coord_t groupneis[S_MAX][4];
/* This is set if this move puts a group out of _all_
* liberties; we need to watch out for snapback then. */
bool friend_has_no_libs;
/* We may have one liberty, but be looking for one more.
* In that case, @needs_more_lib is id of group
* already providing one, don't consider it again. */
group_t needs_more_lib;
/* ID of the first liberty, providing it again is not
* interesting. */
coord_t needs_more_lib_except;
};
static bool
three_liberty_suicide(struct board *b, group_t g, enum stone color, coord_t to, struct selfatari_state *s)
{
/* If a group has three liberties, by playing on one of
* them it is possible to kill the group clumsily. Check
* against that condition: "After our move, the opponent
* can unconditionally capture the group."
*
* Examples:
*
* O O O O O O O X X O O O O O O v-v- ladder
* O X X X X X O . O X X X X X O . . . O O
* O X ! . ! X O . O X ! . ! O . O X X . O
* O X X X X X O # # # # # # # # O O O O O */
/* Extract the other two liberties. */
coord_t other_libs[2];
bool other_libs_adj[2];
for (int i = 0, j = 0; i < 3; i++) {
coord_t lib = board_group_info(b, g).lib[i];
if (lib != to) {
other_libs_adj[j] = coord_is_adjecent(lib, to, b);
other_libs[j++] = lib;
}
}
/* Make sure this move is not useful by gaining liberties,
* splitting the other two liberties (quite possibly splitting
* 3-eyespace!) or connecting to a different group. */
if (immediate_liberty_count(b, to) - (other_libs_adj[0] || other_libs_adj[1]) > 0)
return false;
assert(!(other_libs_adj[0] && other_libs_adj[1]));
if (s->groupcts[color] > 1)
return false;
/* Playing on the third liberty might be useful if it enables
* capturing some group (are we doing nakade or semeai?). */
for (int i = 0; i < s->groupcts[stone_other(color)]; i++)
if (board_group_info(b, s->groupids[stone_other(color)][i]).libs <= 3)
return false;
/* Okay. This looks like a pretty dangerous situation. The
* move looks useless, it definitely converts us to a 2-lib
* group. But we still want to play it e.g. if it takes off
* liberties of some unconspicous enemy group, and of course
* also at the game end to leave just single-point eyes. */
if (DEBUGL(6))
fprintf(stderr, "3-lib danger\n");
/* Therefore, the final suicidal test is: (After filling this
* liberty,) when opponent fills liberty [0], playing liberty
* [1] will not help the group, or vice versa. */
bool other_libs_neighbors = coord_is_adjecent(other_libs[0], other_libs[1], b);
for (int i = 0; i < 2; i++) {
int null_libs = other_libs_neighbors + other_libs_adj[i];
if (board_is_one_point_eye(b, other_libs[1 - i], color)) {
/* The other liberty is an eye, happily go ahead.
* There are of course situations where this will
* take off semeai liberties, but without this check,
* many terminal endgame plays will be messed up. */
return false;
}
if (immediate_liberty_count(b, other_libs[i]) - null_libs > 1) {
/* Gains liberties. */
/* TODO: Check for ladder! */
next_lib:
continue;
}
foreach_neighbor(b, other_libs[i], {
if (board_at(b, c) == color
&& group_at(b, c) != g
&& board_group_info(b, group_at(b, c)).libs > 1) {
/* Can connect to a friend. */
/* TODO: > 2? But maybe the group can capture
* a neighbor! But then better let it do that
* first? */
goto next_lib;
}
});
/* If we can capture a neighbor, better do it now
* before wasting a liberty. So no need to check. */
/* Ok, the last liberty has no way to get out. */
if (DEBUGL(6))
fprintf(stderr, "3-lib dangerous: %s\n", coord2sstr(other_libs[i], b));
return true;
}
return false;
}
static int
examine_friendly_groups(struct board *b, enum stone color, coord_t to, struct selfatari_state *s)
{
for (int i = 0; i < s->groupcts[color]; i++) {
/* We can escape by connecting to this group if it's
* not in atari. */
group_t g = s->groupids[color][i];
if (board_group_info(b, g).libs == 1) {
if (!s->needs_more_lib)
s->friend_has_no_libs = true;
// or we already have a friend with 1 lib
continue;
}
/* Could we self-atari the group here? */
if (board_group_info(b, g).libs > 2) {
if (board_group_info(b, g).libs == 3
&& three_liberty_suicide(b, g, color, to, s))
return true;
return false;
}
/* We need to have another liberty, and
* it must not be the other liberty of
* the group. */
int lib2 = board_group_other_lib(b, g, to);
/* Maybe we already looked at another
* group providing one liberty? */
if (s->needs_more_lib && s->needs_more_lib != g
&& s->needs_more_lib_except != lib2)
return false;
/* Can we get the liberty locally? */
/* Yes if we are route to more liberties... */
if (s->groupcts[S_NONE] > 1)
return false;
/* ...or one liberty, but not lib2. */
if (s->groupcts[S_NONE] > 0
&& !coord_is_adjecent(lib2, to, b))
return false;
/* ...ok, then we can still contribute a liberty
* later by capturing something. */
s->needs_more_lib = g;
s->needs_more_lib_except = lib2;
s->friend_has_no_libs = false;
}
return -1;
}
static int
examine_enemy_groups(struct board *b, enum stone color, coord_t to, struct selfatari_state *s)
{
/* We may be able to gain a liberty by capturing this group. */
group_t can_capture = 0;
/* Examine enemy groups: */
for (int i = 0; i < s->groupcts[stone_other(color)]; i++) {
/* We can escape by capturing this group if it's in atari. */
group_t g = s->groupids[stone_other(color)][i];
if (board_group_info(b, g).libs > 1)
continue;
/* But we need to get to at least two liberties by this;
* we already have one outside liberty, or the group is
* more than 1 stone (in that case, capturing is always
* nice!). */
if (s->groupcts[S_NONE] > 0 || !group_is_onestone(b, g))
return false;
/* ...or, it's a ko stone, */
if (neighbor_count_at(b, g, color) + neighbor_count_at(b, g, S_OFFBOARD) == 3) {
/* and we don't have a group to save: then, just taking
* single stone means snapback! */
if (!s->friend_has_no_libs)
return false;
}
/* ...or, we already have one indirect liberty provided
* by another group. */
if (s->needs_more_lib || (can_capture && can_capture != g))
return false;
can_capture = g;
}
if (DEBUGL(6))
fprintf(stderr, "no cap group\n");
if (!s->needs_more_lib && !can_capture && !s->groupcts[S_NONE]) {
/* We have no hope for more fancy tactics - this move is simply
* a suicide, not even a self-atari. */
if (DEBUGL(6))
fprintf(stderr, "suicide\n");
return true;
}
/* XXX: I wonder if it makes sense to continue if we actually
* just !s->needs_more_lib. */
return -1;
}
static inline bool
is_neighbor_group(struct board *b, enum stone color, group_t g, struct selfatari_state *s)
{
for (int i = 0; i < s->groupcts[color]; i++)
if (g == s->groupids[color][i])
return true;
return false;
}
/* Instead of playing this self-atari, could we have connected/escaped by
* playing on the other liberty of a neighboring group ?
* Or there's a strong enemy group there (only checked if @check_enemy is set)
*/
static inline bool
is_bad_nakade(struct board *b, enum stone color, coord_t to, coord_t lib2,
bool check_enemy, struct selfatari_state *s)
{
/* Let's look at neighbors of the other liberty: */
foreach_neighbor(b, lib2, {
/* If the other liberty has empty neighbor,
* it must be the original liberty; otherwise,
* since the whole group has only 2 liberties,
* the other liberty may not be internal and
* we are nakade'ing eyeless group from outside,
* which is stupid. */
if (board_at(b, c) == S_NONE) {
if (c == to)
continue;
else
return true;
}});
/* Let's look at neighbors of the other liberty: */
foreach_neighbor(b, lib2, {
if (board_at(b, c) != color)
continue;
group_t g2 = group_at(b, c);
/* Looking for a group we don't know about */
if (is_neighbor_group(b, color, g2, s))
continue;
/* Should connect these groups instead of self-atari
* on the other side. */
if (board_at(b, c) == color)
return true;
/* FIXME Do we really need this ? */
if (!check_enemy)
continue;
/* The neighbor is enemy color. It's ok if this is its
* only liberty or it's one of our neighbor groups. */
if (board_group_info(b, g2).libs == 1)
continue;
if (board_group_info(b, g2).libs == 2
&& (board_group_info(b, g2).lib[0] == to
|| board_group_info(b, g2).lib[1] == to))
continue;
/* Stronger enemy group. No nakade. */
return true;
});
return false;
}
/* Instead of playing this self-atari, could we have connected/escaped by
* playing on the other liberty of a neighboring group ? */
static inline bool
can_escape_instead(struct board *b, enum stone color, coord_t to, struct selfatari_state *s)
{
for (int i = 0; i < s->groupcts[color]; i++) {
group_t g = s->groupids[color][i];
if (board_group_info(b, g).libs != 2)
continue;
coord_t other = board_group_other_lib(b, g, to);
/* Let's look at the other liberty of that group. */
if (immediate_liberty_count(b, other) >= 2 || /* Can escape ! */
is_bad_nakade(b, color, to, other, false, s)) /* Should connect instead */
return true;
}
return false;
}
static inline bool
unreachable_lib_from_neighbors(struct board *b, enum stone color, coord_t to, struct selfatari_state *s,
coord_t lib)
{
for (int i = 0; i < s->groupcts[color]; i++) {
group_t g = s->groupids[color][i];
for (int j = 0; j < board_group_info(b, g).libs; j++)
if (board_group_info(b, g).lib[j] == lib)
return false;
}
return true;
}
/* This only looks at existing empty spots, not captures */
static inline bool
capture_would_make_extra_eye(struct board *b, enum stone color, coord_t to, struct selfatari_state *s)
{
foreach_neighbor(b, to, {
if (board_at(b, c) == S_NONE)
if (unreachable_lib_from_neighbors(b, color, to, s, c))
return true;
});
return false;
}
static bool
nakade_making_dead_shape(struct board *b, enum stone color, coord_t to, struct selfatari_state *s,
bool atariing_group, int stones)
{
int r;
struct move m;
group_t g;
int cap_would_make_eye = false;
struct board b2;
if (!stones)
return false;
assert(stones != 1);
assert(stones <= 5);
/* If not atariing surrounding group it's a good move if:
* - Shape after capturing us is dead AND
* - Oponent gets extra eye if he plays first OR
* - would create living shape
*
* If atariing surrounding group we only care about dead shape. */
if (!atariing_group)
cap_would_make_eye = capture_would_make_extra_eye(b, color, to, s);
/* TODO: If there's so much eye space that even with filling + capture
* opponent still makes an extra eye it's a silly move. */
/* Can oponent make living shape if we don't play ?
* (don't bother killing stuff that's already dead...) */
do if (!atariing_group && !cap_would_make_eye && s->groupcts[color] == 1)
{
board_copy(&b2, b);
/* Play opponent color where we want to play */
m.coord = to;
m.color = stone_other(color);
if (board_play(&b2, &m) == -1) /* Illegal move (eye ...) */
{ board_done_noalloc(&b2); break; }
/* Had 2 libs ? One more move to capture us then */
if (neighbor_count_at(b, to, color) == neighbor_count_at(&b2, to, color)) {
group_t standing = -1;
foreach_neighbor(&b2, to, {
g = group_at(&b2, c);
if (board_at(&b2, c) == color) {
assert(board_group_info(&b2, g).libs == 1); /* Should be in atari */
standing = g;
}
});
assert(standing != -1);
m.coord = board_group_info(&b2, standing).lib[0];
r = board_play(&b2, &m); assert(r != -1);
}
/* Empty now since it's been captured */
coord_t empty = group_base(s->groupids[color][0]);
int would_live = !nakade_dead_shape(&b2, empty, stone_other(color));
board_done_noalloc(&b2);
if (!would_live) /* And !cap_would_make_eye here */
return false; /* Bad nakade */
} while (0);
board_copy(&b2, b);
/* Play self-atari */
m.coord = to;
m.color = color;
r = board_play(&b2, &m); assert(r != -1);
/* Play capture */
g = group_at(&b2, to);
m.coord = board_group_info(&b2, g).lib[0];
m.color = stone_other(color);
r = board_play(&b2, &m); assert(r != -1);
int dead_shape = nakade_dead_shape(&b2, to, stone_other(color));
board_done_noalloc(&b2);
return dead_shape;
}
/* More complex throw-in, or in-progress capture from
* the inside - we are in one of several situations:
* a O O O O X b O O O X c O O O X d O O O O O
* O . X . O O X . . O . X . O . X . O
* # # # # # # # # # # # # # # # # # #
* Throw-ins have been taken care of in check_throwin(),
* so it's either b or d now:
* - b is desirable here (since maybe O has no backup two eyes)
* - d is desirable if putting group in atari (otherwise we
* would never capture a single-eyed group). */
#define check_throw_in_or_inside_capture(b, color, to, s, capturing) \
if (s->groupcts[color] == 1 && group_is_onestone(b, s->groupids[color][0])) { \
group_t g2 = s->groupids[color][0]; \
assert(board_group_info(b, g2).libs <= 2); \
if (board_group_info(b, g2).libs == 1) \
return false; /* b */ \
return !capturing; \
}
static int
setup_nakade_or_snapback(struct board *b, enum stone color, coord_t to, struct selfatari_state *s)
{
/* There is another possibility - we can self-atari if it is
* a nakade: we put an enemy group in atari from the inside. */
/* This branch also allows eyes falsification:
* O O O . . (This is different from throw-in to false eye
* X X O O . checked below in that there is no X stone at the
* X . X O . right of the star point in this diagram.)
* X X X O O
* X O * . . */
/* We also allow to only nakade if the created shape is dead
* (http://senseis.xmp.net/?Nakade). */
/* This branch also covers snapback, which is kind of special
* nakade case. ;-) */
/* FIXME looks like check_throwin() does it actually. */
/* Look at the enemy groups and determine the other contended
* liberty. We must make sure the liberty:
* (i) is an internal liberty
* (ii) filling it to capture our group will not gain safety */
coord_t lib2 = pass;
for (int i = 0; i < s->groupcts[stone_other(color)]; i++) {
group_t g = s->groupids[stone_other(color)][i];
if (board_group_info(b, g).libs != 2)
continue;
coord_t this_lib2 = board_group_other_lib(b, g, to);
if (is_pass(lib2))
lib2 = this_lib2;
else if (this_lib2 != lib2) {
/* If we have two neighboring groups that do
* not share the other liberty, this for sure
* is not a good nakade. */
return -1;
}
}
if (is_pass(lib2)) {
/* Not putting any group in atari.
* Could be creating dead shape though */
/* Before checking if it's a useful nakade
* make sure it can't connect out ! */
if (can_escape_instead(b, color, to, s))
return -1;
check_throw_in_or_inside_capture(b, color, to, s, false);
int stones = 0;
for (int j = 0; j < s->groupcts[color]; j++) {
group_t g2 = s->groupids[color][j];
stones += group_stone_count(b, g2, 6);
if (stones > 5)
return true;
}
return (nakade_making_dead_shape(b, color, to, s, false, stones) ? false : -1);
}
/* Let's look at neighbors of the other liberty: */
if (is_bad_nakade(b, color, to, lib2, true, s) ||
can_escape_instead(b, color, to, s))
return -1;
/* Now, we must distinguish between nakade and eye
* falsification; moreover, we must not falsify an eye
* by more than two stones. */
if (s->groupcts[color] < 1)
return false; /* Simple throw-in, an easy case */
check_throw_in_or_inside_capture(b, color, to, s, true);
/* We would create more than 2-stone group; in that
* case, the liberty of our result must be lib2,
* indicating this really is a nakade. */
int stones = 0;
for (int j = 0; j < s->groupcts[color]; j++) {
group_t g2 = s->groupids[color][j];
assert(board_group_info(b, g2).libs <= 2);
if (board_group_info(b, g2).libs == 2) {
if (board_group_info(b, g2).lib[0] != lib2
&& board_group_info(b, g2).lib[1] != lib2)
return -1;
} else
assert(board_group_info(b, g2).lib[0] == to);
/* See below: */
stones += group_stone_count(b, g2, 6);
if (stones > 5)
return true;
}
return !nakade_making_dead_shape(b, color, to, s, true, stones);
}
#if 0
/* Fast but there are issues with this (triangle six is not dead !)
* We also need to know status if opponent plays first */
static inline int
nakade_making_dead_shape_hack(struct board *b, enum stone color, coord_t to, int lib2,
struct selfatari_state *s, int stones)
{
/* It also remains to be seen whether it is nakade
* and not seki destruction. To do this properly, we
* would have to look at the group shape. But we can
* cheat too! Brett Combs helps to introduce a static
* rule that should in fact cover *all* cases:
* 1. Total number of pre-selfatari nakade stones must
* be 5 or smaller. (See above for that.)
* 2. If the selfatari is 8-touching all nakade stones,
* it is proper nakade.
* 3. Otherwise, there must be only a single nakade
* group, it must be at least 4-stone and its other
* liberty must be 8-touching the same number of
* stones as us. */
int touch8 = neighbor_count_at(b, to, color);
foreach_diag_neighbor(b, to) {
if (board_at(b, c) != color) continue;
/* Consider only internal stones. Otherwise, e.g.
* X O . X
* X . O X can make trouble, bottom O is
* O X X X irrelevant. */
if (board_group_info(b, group_at(b, c)).lib[0] == to
|| board_group_info(b, group_at(b, c)).lib[1] == to)
touch8++;
} foreach_diag_neighbor_end;
if (touch8 == stones)
return true;
if (s->groupcts[color] > 1)
return false;
if (stones == 3) // 4 stones and self-atari not 8-connected to all of them -> living shape
return false;
if (stones < 3) // always dead shape
return true;
int ltouch8 = neighbor_count_at(b, lib2, color);
foreach_diag_neighbor(b, lib2) {
if (board_at(b, c) != color) continue;
if (board_group_info(b, group_at(b, c)).lib[0] == to
|| board_group_info(b, group_at(b, c)).lib[1] == to)
ltouch8++;
} foreach_diag_neighbor_end;
return ltouch8 == touch8;
}
#endif
static int
check_throwin(struct board *b, enum stone color, coord_t to, struct selfatari_state *s)
{
/* We can be throwing-in to false eye:
* X X X O X X X O X X X X X
* X . * X * O . X * O O . X
* # # # # # # # # # # # # # */
/* We cannot sensibly throw-in into a corner. */
if (neighbor_count_at(b, to, S_OFFBOARD) < 2
&& neighbor_count_at(b, to, stone_other(color))
+ neighbor_count_at(b, to, S_OFFBOARD) == 3
&& board_is_false_eyelike(b, to, stone_other(color))) {
assert(s->groupcts[color] <= 1);
/* Single-stone throw-in may be ok... */
if (s->groupcts[color] == 0) {
/* O X . There is one problem - when it's
* . * X actually not a throw-in!
* # # # */
foreach_neighbor(b, to, {
if (board_at(b, c) == S_NONE) {
/* Is the empty neighbor an escape path? */
/* (Note that one S_NONE neighbor is already @to.) */
if (neighbor_count_at(b, c, stone_other(color))
+ neighbor_count_at(b, c, S_OFFBOARD) < 2)
return -1;
}
});
return false;
}
/* Multi-stone throwin...? */
assert(s->groupcts[color] == 1);
group_t g = s->groupids[color][0];
assert(board_group_info(b, g).libs <= 2);
/* Suicide is definitely NOT ok, no matter what else
* we could test. */
if (board_group_info(b, g).libs == 1)
return true;
/* In that case, we must be connected to at most one stone,
* or throwin will not destroy any eyes. */
if (group_is_onestone(b, g))
return false;
}
return -1;
}
bool
is_bad_selfatari_slow(struct board *b, enum stone color, coord_t to)
{
if (DEBUGL(5))
fprintf(stderr, "sar check %s %s\n", stone2str(color), coord2sstr(to, b));
/* Assess if we actually gain any liberties by this escape route.
* Note that this is not 100% as we cannot check whether we are
* connecting out or just to ourselves. */
struct selfatari_state s;
memset(&s, 0, sizeof(s));
int d;
foreach_neighbor(b, to, {
enum stone color = board_at(b, c);
group_t group = group_at(b, c);
bool dup = false;
for (int i = 0; i < s.groupcts[color]; i++)
if (s.groupids[color][i] == group) {
dup = true;
break;
}
if (!dup) {
s.groupneis[color][s.groupcts[color]] = c;
s.groupids[color][s.groupcts[color]++] = group_at(b, c);
}
});
/* We have shortage of liberties; that's the point. */
assert(s.groupcts[S_NONE] <= 1);
d = examine_friendly_groups(b, color, to, &s);
if (d >= 0)
return d;
if (DEBUGL(6))
fprintf(stderr, "no friendly group\n");
d = examine_enemy_groups(b, color, to, &s);
if (d >= 0)
return d;
if (DEBUGL(6))
fprintf(stderr, "no capture\n");
d = check_throwin(b, color, to, &s);
if (d >= 0)
return d;
if (DEBUGL(6))
fprintf(stderr, "no throw-in group\n");
d = setup_nakade_or_snapback(b, color, to, &s);
if (d >= 0)
return d;
if (DEBUGL(6))
fprintf(stderr, "no nakade group\n");
/* No way to pull out, no way to connect out. This really
* is a bad self-atari! */
return true;
}
coord_t
selfatari_cousin(struct board *b, enum stone color, coord_t coord, group_t *bygroup)
{
group_t groups[4]; int groups_n = 0;
int groupsbycolor[4] = {0, 0, 0, 0};
if (DEBUGL(6))
fprintf(stderr, "cousin group search: ");
foreach_neighbor(b, coord, {
enum stone s = board_at(b, c);
group_t g = group_at(b, c);
if (board_group_info(b, g).libs == 2) {
groups[groups_n++] = g;
groupsbycolor[s]++;
if (DEBUGL(6))
fprintf(stderr, "%s(%s) ", coord2sstr(c, b), stone2str(s));
}
});
if (DEBUGL(6))
fprintf(stderr, "\n");
if (!groups_n)
return pass;
int gn;
if (groupsbycolor[stone_other(color)]) {
/* Prefer to fill the other liberty of an opponent
* group to filling own approach liberties. */
int gl = fast_random(groups_n);
for (gn = gl; gn < groups_n; gn++)
if (board_at(b, groups[gn]) == stone_other(color))
goto found;
for (gn = 0; gn < gl; gn++)
if (board_at(b, groups[gn]) == stone_other(color))
goto found;
found:;
} else {
gn = fast_random(groups_n);
}
int gl = gn;
for (; gn - gl < groups_n; gn++) {
int gnm = gn % groups_n;
group_t group = groups[gnm];
coord_t lib2;
/* Can we get liberties by capturing a neighbor? */
struct move_queue ccq; ccq.moves = 0;
if (can_countercapture(b, color, group, color, &ccq, 0)) {
lib2 = mq_pick(&ccq);
} else {
lib2 = board_group_other_lib(b, group, coord);
if (board_is_one_point_eye(b, lib2, board_at(b, group)))
continue;
if (is_bad_selfatari(b, color, lib2))
continue;
}
if (bygroup)
*bygroup = group;
return lib2;
}
return pass;
}