pachi_py/pachi/tactics/2lib.c (173 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 "tactics/2lib.h"
#include "tactics/selfatari.h"
/* Whether to avoid capturing/atariing doomed groups (this is big
* performance hit and may reduce playouts balance; it does increase
* the strength, but not quite proportionally to the performance). */
//#define NO_DOOMED_GROUPS
static bool
miai_2lib(struct board *b, group_t group, enum stone color)
{
bool can_connect = false, can_pull_out = false;
/* We have miai if we can either connect on both libs,
* or connect on one lib and escape on another. (Just
* having two escape routes can be risky.) We must make
* sure that we don't consider following as miai:
* X X X O
* X . . O
* O O X O - left dot would be pull-out, right dot connect */
foreach_neighbor(b, board_group_info(b, group).lib[0], {
enum stone cc = board_at(b, c);
if (cc == S_NONE && cc != board_at(b, board_group_info(b, group).lib[1])) {
can_pull_out = true;
} else if (cc != color) {
continue;
}
group_t cg = group_at(b, c);
if (cg && cg != group && board_group_info(b, cg).libs > 1)
can_connect = true;
});
foreach_neighbor(b, board_group_info(b, group).lib[1], {
enum stone cc = board_at(b, c);
if (c == board_group_info(b, group).lib[0])
continue;
if (cc == S_NONE && can_connect) {
return true;
} else if (cc != color) {
continue;
}
group_t cg = group_at(b, c);
if (cg && cg != group && board_group_info(b, cg).libs > 1)
return (can_connect || can_pull_out);
});
return false;
}
static bool
defense_is_hopeless(struct board *b, group_t group, enum stone owner,
enum stone to_play, coord_t lib, coord_t otherlib,
bool use)
{
/* If we are the defender not connecting out, do not
* escape with moves that do not gain liberties anyway
* - either the new extension has just single extra
* liberty, or the "gained" liberties are shared. */
/* XXX: We do not check connecting to a short-on-liberty
* group (e.g. ourselves). */
if (DEBUGL(7))
fprintf(stderr, "\tif_check %d and defending %d and uscount %d ilcount %d\n",
use, to_play == owner,
neighbor_count_at(b, lib, owner),
immediate_liberty_count(b, lib));
if (!use)
return false;
if (to_play == owner && neighbor_count_at(b, lib, owner) == 1) {
if (immediate_liberty_count(b, lib) == 1)
return true;
if (immediate_liberty_count(b, lib) == 2
&& coord_is_adjecent(lib, otherlib, b))
return true;
}
return false;
}
void
can_atari_group(struct board *b, group_t group, enum stone owner,
enum stone to_play, struct move_queue *q,
int tag, bool use_def_no_hopeless)
{
bool have[2] = { false, false };
bool preference[2] = { true, true };
for (int i = 0; i < 2; i++) {
coord_t lib = board_group_info(b, group).lib[i];
assert(board_at(b, lib) == S_NONE);
if (!board_is_valid_play(b, to_play, lib))
continue;
if (DEBUGL(6))
fprintf(stderr, "- checking liberty %s of %s %s, filled by %s\n",
coord2sstr(lib, b),
stone2str(owner), coord2sstr(group, b),
stone2str(to_play));
/* Don't play at the spot if it is extremely short
* of liberties... */
/* XXX: This looks harmful, could significantly
* prefer atari to throwin:
*
* XXXOOOOOXX
* .OO.....OX
* XXXOOOOOOX */
#if 0
if (neighbor_count_at(b, lib, stone_other(owner)) + immediate_liberty_count(b, lib) < 2)
continue;
#endif
/* Prevent hopeless escape attempts. */
if (defense_is_hopeless(b, group, owner, to_play, lib,
board_group_info(b, group).lib[1 - i],
use_def_no_hopeless))
continue;
#ifdef NO_DOOMED_GROUPS
/* If the owner can't play at the spot, we don't want
* to bother either. */
if (is_bad_selfatari(b, owner, lib))
continue;
#endif
/* Of course we don't want to play bad selfatari
* ourselves, if we are the attacker... */
if (
#ifdef NO_DOOMED_GROUPS
to_play != owner &&
#endif
is_bad_selfatari(b, to_play, lib)) {
if (DEBUGL(7))
fprintf(stderr, "\tliberty is selfatari\n");
coord_t coord = pass;
group_t bygroup = 0;
if (to_play != owner) {
/* Okay! We are attacker; maybe we just need
* to connect a false eye before atari - this
* is very common in the corner. */
coord = selfatari_cousin(b, to_play, lib, &bygroup);
}
if (is_pass(coord))
continue;
/* Ok, connect, but prefer not to. */
enum stone byowner = board_at(b, bygroup);
if (DEBUGL(7))
fprintf(stderr, "\treluctantly switching to cousin %s (group %s %s)\n",
coord2sstr(coord, b), coord2sstr(bygroup, b), stone2str(byowner));
/* One more thing - is the cousin sensible defense
* for the other group? */
if (defense_is_hopeless(b, bygroup, byowner, to_play,
coord, lib,
use_def_no_hopeless))
continue;
lib = coord;
preference[i] = false;
/* By now, we must be decided we add the move to the
* queue! [comment intentionally misindented] */
}
have[i] = true;
/* If the move is too "lumpy", prefer the alternative:
*
* #######
* ..O.X.X <- always play the left one!
* OXXXXXX */
if (neighbor_count_at(b, lib, to_play) + neighbor_count_at(b, lib, S_OFFBOARD) >= 3) {
if (DEBUGL(7))
fprintf(stderr, "\tlumpy: mine %d + edge %d\n",
neighbor_count_at(b, lib, to_play),
neighbor_count_at(b, lib, S_OFFBOARD));
preference[i] = false;
}
if (DEBUGL(6))
fprintf(stderr, "+ liberty %s ready with preference %d\n", coord2sstr(lib, b), preference[i]);
/* If we prefer only one of the moves, pick that one. */
if (i == 1 && have[0] && preference[0] != preference[1]) {
if (!preference[0]) {
if (q->move[q->moves - 1] == board_group_info(b, group).lib[0])
q->moves--;
/* ...else{ may happen, since we call
* mq_nodup() and the move might have
* been there earlier. */
} else {
assert(!preference[1]);
continue;
}
}
/* Tasty! Crispy! Good! */
mq_add(q, lib, tag);
mq_nodup(q);
}
if (DEBUGL(7)) {
char label[256];
snprintf(label, 256, "= final %s %s liberties to play by %s",
stone2str(owner), coord2sstr(group, b),
stone2str(to_play));
mq_print(q, b, label);
}
}
void
group_2lib_check(struct board *b, group_t group, enum stone to_play, struct move_queue *q, int tag, bool use_miaisafe, bool use_def_no_hopeless)
{
enum stone color = board_at(b, group_base(group));
assert(color != S_OFFBOARD && color != S_NONE);
if (DEBUGL(5))
fprintf(stderr, "[%s] 2lib check of color %d\n",
coord2sstr(group, b), color);
/* Do not try to atari groups that cannot be harmed. */
if (use_miaisafe && miai_2lib(b, group, color))
return;
can_atari_group(b, group, color, to_play, q, tag, use_def_no_hopeless);
/* Can we counter-atari another group, if we are the defender? */
if (to_play != color)
return;
foreach_in_group(b, group) {
foreach_neighbor(b, c, {
if (board_at(b, c) != stone_other(color))
continue;
group_t g2 = group_at(b, c);
if (board_group_info(b, g2).libs == 1) {
/* We can capture a neighbor. */
mq_add(q, board_group_info(b, g2).lib[0], tag);
mq_nodup(q);
continue;
}
if (board_group_info(b, g2).libs != 2)
continue;
can_atari_group(b, g2, stone_other(color), to_play, q, tag, use_def_no_hopeless);
});
} foreach_in_group_end;
}