Initial commit of GNU Go v3.8.
[sgk-go] / engine / worm.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 <string.h>
#include "liberty.h"
#include "patterns.h"
static void compute_effective_worm_sizes(void);
static void do_compute_effective_worm_sizes(int color,
int (*cw)[MAX_CLOSE_WORMS],
int *ncw, int max_distance);
static void compute_unconditional_status(void);
static void find_worm_attacks_and_defenses(void);
static void find_worm_threats(void);
static int find_lunch(int str, int *lunch);
static void change_tactical_point(int str, int move, int code,
int points[MAX_TACTICAL_POINTS],
int codes[MAX_TACTICAL_POINTS]);
static void propagate_worm2(int str);
static int genus(int str);
static void markcomponent(int str, int pos, int mg[BOARDMAX]);
static int examine_cavity(int pos, int *edge);
static void cavity_recurse(int pos, int mx[BOARDMAX],
int *border_color, int *edge, int str);
static void ping_cave(int str, int *result1, int *result2,
int *result3, int *result4);
static void ping_recurse(int pos, int *counter,
int mx[BOARDMAX],
int mr[BOARDMAX], int color);
static int touching(int pos, int color);
static void find_attack_patterns(void);
static void attack_callback(int anchor, int color,
struct pattern *pattern, int ll, void *data);
static void find_defense_patterns(void);
static void defense_callback(int anchor, int color,
struct pattern *pattern, int ll, void *data);
static void build_worms(void);
static void report_worm(int pos);
/* A worm or string is a maximal connected set of stones of the same color,
* black or white.
*
* Cavities are sets of connected empty vertices.
*/
/* make_worms() finds all worms and assembles some data about them.
*
* Each worm is marked with an origin. This is an arbitrarily chosen
* element of the worm, in practice the algorithm puts the origin at
* the first element when they are given the lexicographical order,
* though its location is irrelevant for applications. To see if two
* stones lie in the same worm, compare their origins.
*
* We will use the field dragon[ii].genus to keep track of
* black- or white-bordered cavities (essentially eyes) which are found.
* so this field must be zero'd now.
*/
void
make_worms(void)
{
int pos;
/* Build the basic worm data: color, origin, size, liberties. */
build_worms();
/* No point continuing if the board is completely empty. */
if (stones_on_board(BLACK | WHITE) == 0)
return;
/* Compute effective sizes of all worms. */
compute_effective_worm_sizes();
/* Look for unconditionally alive and dead worms, and unconditional
* territory.
*/
compute_unconditional_status();
find_worm_attacks_and_defenses();
gg_assert(stackp == 0);
/* Count liberties of different orders and initialize cutstone fields. */
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (IS_STONE(board[pos]) && is_worm_origin(pos, pos)) {
int lib1, lib2, lib3, lib4;
ping_cave(pos, &lib1, &lib2, &lib3, &lib4);
ASSERT1(worm[pos].liberties == lib1, pos);
worm[pos].liberties2 = lib2;
worm[pos].liberties3 = lib3;
worm[pos].liberties4 = lib4;
worm[pos].cutstone = 0;
worm[pos].cutstone2 = 0;
propagate_worm(pos);
}
}
gg_assert(stackp == 0);
/*
* There are two concepts of cutting stones in the worm array.
*
* worm.cutstone:
*
* A CUTTING STONE is one adjacent to two enemy strings,
* which do not have a liberty in common. The most common
* type of cutting string is in this situation.
*
* XO
* OX
*
* A POTENTIAL CUTTING STONE is adjacent to two enemy
* strings which do share a liberty. For example, X in:
*
* XO
* O.
*
* For cutting strings we set worm[m][n].cutstone=2. For potential
* cutting strings we set worm[m][n].cutstone=1. For other strings,
* worm[m][n].cutstone=0.
*
* worm.cutstone2:
*
* Cutting points are identified by the patterns in the
* connections database. Proper cuts are handled by the fact
* that attacking and defending moves also count as moves
* cutting or connecting the surrounding dragons.
*
* The cutstone field will now be set. The cutstone2 field is set
* later, during find_cuts(), called from make_dragons().
*
* We maintain both fields because the historically older cutstone
* field is needed to deal with the fact that e.g. in the position
*
*
* OXX.O
* .OOXO
* OXX.O
*
* the X stones are amalgamated into one dragon because neither cut
* works as long as the two O stones are in atari. Therefore we add
* one to the cutstone field for each potential cutting point,
* indicating that these O stones are indeed worth rescuing.
*
* For the time being we use both concepts in parallel. It's
* possible we also need the old concept for correct handling of lunches.
*/
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
int w1 = NO_MOVE;
int w2 = NO_MOVE;
int k;
int pos2;
/* Only work on each worm once. This is easiest done if we only
* work with the origin of each worm.
*/
if (!IS_STONE(board[pos]) || !is_worm_origin(pos, pos))
continue;
/* Try to find two adjacent worms (w1) and (w2)
* of opposite colour from (pos).
*/
for (pos2 = BOARDMIN; pos2 < BOARDMAX; pos2++) {
/* Work only with the opposite color from (pos). */
if (board[pos2] != OTHER_COLOR(board[pos]))
continue;
for (k = 0; k < 4; k++) {
if (!ON_BOARD(pos2 + delta[k])
|| worm[pos2 + delta[k]].origin != pos)
continue;
ASSERT1(board[pos2 + delta[k]] == board[pos], pos);
/* If we have not already found a worm which meets the criteria,
* store it into (w1), otherwise store it into (w2).
*/
if (w1 == NO_MOVE)
w1 = worm[pos2].origin;
else if (!is_same_worm(pos2, w1))
w2 = worm[pos2].origin;
}
}
/*
* We now verify the definition of cutting stones. We have
* verified that the string at (pos) is adjacent to two enemy
* strings at (w1) and (w2). We need to know if these
* strings share a liberty.
*/
/* Only do this if we really found something. */
if (w2 != NO_MOVE) {
worm[pos].cutstone = 2;
if (count_common_libs(w1, w2) > 0)
worm[pos].cutstone = 1;
DEBUG(DEBUG_WORMS, "Worm at %1m has w1 %1m and w2 %1m, cutstone %d\n",
pos, w1, w2, worm[pos].cutstone);
}
}
gg_assert(stackp == 0);
/* Set the genus of all worms. */
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (IS_STONE(board[pos]) && is_worm_origin(pos, pos)) {
worm[pos].genus = genus(pos);
propagate_worm(pos);
}
}
gg_assert(stackp == 0);
/* Now we try to improve the values of worm.attack and worm.defend.
* If we find that capturing the string at str also defends the
* string at str2, or attacks it, then we add points of attack and
* defense. We don't add attacking point for strings that can't be
* defended.
*/
{
int color;
int str;
int moves_to_try[BOARDMAX];
memset(moves_to_try, 0, sizeof(moves_to_try));
/* Find which colors to try at what points. */
for (str = BOARDMIN; str < BOARDMAX; str++) {
if (IS_STONE(board[str]) && is_worm_origin(str, str)) {
color = board[str];
moves_to_try[worm[str].defense_points[0]] |= color;
moves_to_try[worm[str].attack_points[0]] |= OTHER_COLOR(color);
}
}
/* Loop over the board and over the colors and try the moves found
* in the previous loop.
*/
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (!ON_BOARD(pos))
continue;
for (color = WHITE; color <= BLACK; color++) {
if (!(moves_to_try[pos] & color))
continue;
/* Try to play color at pos and see what it leads to. */
if (!trymove(pos, color, "make_worms", NO_MOVE))
continue;
/* We must read to the same depth that was used in the
* initial determination of worm.attack and worm.defend
* to avoid horizon effects. Since stackp has been
* incremented we must also increment depth values.
*/
DEBUG(DEBUG_WORMS, "trying %1m\n", pos);
increase_depth_values();
/* Now we try to find a group which is saved or attacked as well
* by this move.
*/
for (str = BOARDMIN; str < BOARDMAX; str++) {
if (!IS_STONE(board[str])
|| !is_worm_origin(str, str))
continue;
/* If the worm is of the opposite color to the move,
* then we try to defend it. If there was a previous
* attack and defense of it, and there is no defense
* for the attack now...
*/
if (worm[str].color == OTHER_COLOR(color)
&& worm[str].attack_codes[0] != 0
&& worm[str].defense_codes[0] != 0) {
int dcode = find_defense(str, NULL);
if (dcode < worm[str].defense_codes[0]) {
int attack_works = 1;
/* Sometimes find_defense() fails to find a
* defense which has been found by other means.
* Try if the old defense move still works.
*
* However, we first check if the _attack_ still exists,
* because we could, for instance, drive the worm into
* seki with our move.
*/
if (attack(str, NULL) >= worm[str].attack_codes[0]) {
if (worm[str].defense_codes[0] != 0
&& trymove(worm[str].defense_points[0],
OTHER_COLOR(color), "make_worms", 0)) {
int this_dcode = REVERSE_RESULT(attack(str, NULL));
if (this_dcode > dcode) {
dcode = this_dcode;
if (dcode >= worm[str].defense_codes[0])
attack_works = 0;
}
popgo();
}
}
else
attack_works = 0;
/* ...then add an attack point of that worm at pos. */
if (attack_works) {
DEBUG(DEBUG_WORMS,
"adding point of attack of %1m at %1m with code %d\n",
str, pos, REVERSE_RESULT(dcode));
change_attack(str, pos, REVERSE_RESULT(dcode));
}
}
}
/* If the worm is of the same color as the move we try to
* attack it. If there previously was an attack on it, but
* there is none now, then add a defense point of str at
* pos.
*/
else if (worm[str].color == color
&& worm[str].attack_codes[0] != 0) {
int acode = attack(str, NULL);
if (acode < worm[str].attack_codes[0]) {
int defense_works = 1;
/* Sometimes attack() fails to find an
* attack which has been found by other means.
* Try if the old attack move still works.
*/
if (worm[str].attack_codes[0] != 0
&& trymove(worm[str].attack_points[0],
OTHER_COLOR(color), "make_worms", 0)) {
int this_acode;
if (board[str] == EMPTY)
this_acode = WIN;
else
this_acode = REVERSE_RESULT(find_defense(str, NULL));
if (this_acode > acode) {
acode = this_acode;
if (acode >= worm[str].attack_codes[0])
defense_works = 0;
}
popgo();
}
/* ...then add an attack point of that worm at pos. */
if (defense_works) {
DEBUG(DEBUG_WORMS,
"adding point of defense of %1m at %1m with code %d\n",
str, pos, REVERSE_RESULT(acode));
change_defense(str, pos, REVERSE_RESULT(acode));
}
}
}
}
decrease_depth_values();
popgo();
}
}
}
gg_assert(stackp == 0);
/* Sometimes it happens that the tactical reading finds adjacent
* strings which both can be attacked but not defended. (The reason
* seems to be that the attacker tries harder to attack a string,
* than the defender tries to capture it's neighbors.) When this
* happens, the eyes code produces overlapping eye spaces and, still
* worse, all the nondefendable stones actually get amalgamated with
* their allies on the outside.
*
* To solve this we scan through the strings which can't be defended
* and check whether they have a neighbor that can be attacked. In
* this case we set the defense point of the former string to the
* attacking point of the latter.
*
* Please notice that find_defense() will still read this out
* incorrectly, which may lead to some confusion later.
*/
/* First look for vertical neighbors. */
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (IS_STONE(board[pos])
&& IS_STONE(board[SOUTH(pos)])
&& !is_same_worm(pos, SOUTH(pos))) {
if (worm[pos].attack_codes[0] != 0
&& worm[SOUTH(pos)].attack_codes[0] != 0) {
if (worm[pos].defense_codes[0] == 0
&& does_defend(worm[SOUTH(pos)].attack_points[0], pos)) {
/* FIXME: need to check ko relationship here */
change_defense(pos, worm[SOUTH(pos)].attack_points[0], WIN);
}
if (worm[SOUTH(pos)].defense_codes[0] == 0
&& does_defend(worm[pos].attack_points[0], SOUTH(pos))) {
/* FIXME: need to check ko relationship here */
change_defense(SOUTH(pos), worm[pos].attack_points[0], WIN);
}
}
}
}
/* Then look for horizontal neighbors. */
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (IS_STONE(board[pos])
&& IS_STONE(board[EAST(pos)])
&& !is_same_worm(pos, EAST(pos))) {
if (worm[pos].attack_codes[0] != 0
&& worm[EAST(pos)].attack_codes[0] != 0) {
if (worm[pos].defense_codes[0] == 0
&& does_defend(worm[EAST(pos)].attack_points[0], pos)) {
/* FIXME: need to check ko relationship here */
change_defense(pos, worm[EAST(pos)].attack_points[0], WIN);
}
if (worm[EAST(pos)].defense_codes[0] == 0
&& does_defend(worm[pos].attack_points[0], EAST(pos))) {
/* FIXME: need to check ko relationship here */
change_defense(EAST(pos), worm[pos].attack_points[0], WIN);
}
}
}
}
gg_assert(stackp == 0);
/* Find adjacent worms that can be easily captured, aka lunches. */
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
int lunch;
if (!IS_STONE(board[pos]) || !is_worm_origin(pos, pos))
continue;
if (find_lunch(pos, &lunch)
&& (worm[lunch].attack_codes[0] == WIN
|| worm[lunch].attack_codes[0] == KO_A)) {
DEBUG(DEBUG_WORMS, "lunch found for %1m at %1m\n", pos, lunch);
worm[pos].lunch = lunch;
}
else
worm[pos].lunch = NO_MOVE;
propagate_worm(pos);
}
if (!disable_threat_computation)
find_worm_threats();
/* Identify INESSENTIAL strings.
*
* These are defined as surrounded strings which have no life
* potential unless part of their surrounding chain can be captured.
* We give a conservative definition of inessential:
* - the genus must be zero
* - there can no second order liberties
* - there can be no more than two edge liberties
* - if it is removed from the board, the remaining cavity has
* border color the opposite color of the string
* - it contains at most two edge vertices.
*
* If we get serious about identifying seki, we might want to add:
*
* - if it has fewer than 4 liberties it is tactically dead.
*
* The last condition is helpful in excluding strings which are
* alive in seki.
*
* An inessential string can be thought of as residing inside the
* opponent's eye space.
*/
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (IS_STONE(board[pos])
&& worm[pos].origin == pos
&& worm[pos].genus == 0
&& worm[pos].liberties2 == 0
&& !worm[pos].cutstone
&& worm[pos].lunch == NO_MOVE) {
int edge;
int border_color = examine_cavity(pos, &edge);
if (border_color != GRAY && edge < 3) {
DEBUG(DEBUG_WORMS, "Worm %1m identified as inessential.\n", pos);
worm[pos].inessential = 1;
propagate_worm(pos);
}
}
}
}
/*
* Clear all worms and initialize the basic data fields:
* color, origin, size, liberties
* This is a substep of make_worms().
*/
static void
build_worms()
{
int pos;
/* Set all worm data fields to 0. */
memset(worm, 0 , sizeof(worm));
/* Initialize the worm data for each worm. */
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
if (ON_BOARD(pos))
worm[pos].origin = NO_MOVE;
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (!ON_BOARD(pos) || worm[pos].origin != NO_MOVE)
continue;
worm[pos].color = board[pos];
worm[pos].origin = pos;
worm[pos].inessential = 0;
worm[pos].invincible = 0;
worm[pos].unconditional_status = UNKNOWN;
worm[pos].effective_size = 0.0;
if (IS_STONE(board[pos])) {
worm[pos].liberties = countlib(pos);
worm[pos].size = countstones(pos);
propagate_worm(pos);
}
}
}
/* Compute effective size of each worm.
*
* Effective size is the number of stones in a worm plus half the
* number of empty intersections that are at least as close to this
* worm as to any other worm. This is used to estimate the direct
* territorial value of capturing a worm. Intersections that are
* shared are counted with equal fractional values for each worm.
*
* We never count intersections further away than distance 3.
*
* This function is also used to compute arrays with information about
* the distances to worms of both or either color. In the latter case
* we count intersections up to a distance of 5.
*/
static void
compute_effective_worm_sizes()
{
do_compute_effective_worm_sizes(BLACK | WHITE, close_worms,
number_close_worms, 3);
do_compute_effective_worm_sizes(BLACK, close_black_worms,
number_close_black_worms, 5);
do_compute_effective_worm_sizes(WHITE, close_white_worms,
number_close_white_worms, 5);
}
static void
do_compute_effective_worm_sizes(int color, int (*cw)[MAX_CLOSE_WORMS],
int *ncw, int max_distance)
{
int pos;
/* Distance to closest worm, -1 means unassigned, 0 that there is
* a stone at the location, 1 a liberty of a stone, and so on.
*/
int distance[BOARDMAX];
/* Pointer to the origin of the closest worms. A very large number of
* worms may potentially be equally close, but no more than
* 2*(board_size-1).
*/
static int worms[BOARDMAX][2*(MAX_BOARD-1)];
int nworms[BOARDMAX]; /* number of equally close worms */
int found_one;
int dist; /* current distance */
int k, l;
int r;
/* Initialize arrays. */
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (!ON_BOARD(pos))
continue;
for (k = 0; k < 2*(board_size-1); k++)
worms[pos][k] = NO_MOVE;
nworms[pos] = 0;
if (board[pos] & color) {
distance[pos] = 0;
worms[pos][0] = worm[pos].origin;
nworms[pos]++;
}
else
distance[pos] = -1;
}
dist = 0;
found_one = 1;
while (found_one && dist <= max_distance) {
found_one = 0;
dist++;
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (!ON_BOARD(pos) || distance[pos] != -1)
continue; /* already claimed */
for (r = 0; r < 4; r++) {
int pos2 = pos + delta[r];
if (ON_BOARD(pos2) && distance[pos2] == dist - 1) {
found_one = 1;
distance[pos] = dist;
for (k = 0; k < nworms[pos2]; k++) {
int already_counted = 0;
for (l = 0; l < nworms[pos]; l++)
if (worms[pos][l] == worms[pos2][k]) {
already_counted = 1;
break;
}
if (!already_counted) {
ASSERT1(nworms[pos] < 2*(board_size-1), pos);
worms[pos][nworms[pos]] = worms[pos2][k];
nworms[pos]++;
}
}
}
}
}
}
/* Compute the effective sizes but only when all worms are considered. */
if (color == (BLACK | WHITE)) {
/* Distribute (fractional) contributions to the worms. */
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (!ON_BOARD(pos))
continue;
for (k = 0; k < nworms[pos]; k++) {
int w = worms[pos][k];
if (board[pos] == EMPTY)
worm[w].effective_size += 0.5/nworms[pos];
else
worm[w].effective_size += 1.0;
}
}
/* Propagate the effective size values all over the worms. */
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
if (IS_STONE(board[pos]) && is_worm_origin(pos, pos))
propagate_worm(pos);
}
/* Fill in the appropriate close_*_worms (cw) and
* number_close_*_worms (ncw) arrays.
*/
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (!ON_BOARD(pos))
continue;
if (nworms[pos] > MAX_CLOSE_WORMS)
ncw[pos] = 0;
else
ncw[pos] = nworms[pos];
for (k = 0; k < ncw[pos]; k++)
cw[pos][k] = worms[pos][k];
}
}
/* Identify worms which are unconditionally uncapturable in the
* strongest sense, i.e. even if the opponent is allowed an arbitrary
* number of consecutive moves. Also identify worms which are
* similarly unconditionally dead and empty points which are
* unconditional territory for either player.
*/
static void
compute_unconditional_status()
{
int unconditional_territory[BOARDMAX];
int pos;
int color;
for (color = WHITE; color <= BLACK; color++) {
unconditional_life(unconditional_territory, color);
if (get_level() >= 10)
find_unconditionally_meaningless_moves(unconditional_territory, color);
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (!ON_BOARD(pos) || !unconditional_territory[pos])
continue;
if (board[pos] == color) {
worm[pos].unconditional_status = ALIVE;
if (unconditional_territory[pos] == 1)
worm[pos].invincible = 1;
}
else if (board[pos] == EMPTY) {
if (color == WHITE)
worm[pos].unconditional_status = WHITE_TERRITORY;
else
worm[pos].unconditional_status = BLACK_TERRITORY;
}
else
worm[pos].unconditional_status = DEAD;
}
}
gg_assert(stackp == 0);
}
/*
* Analyze tactical safety of each worm.
*/
static void
find_worm_attacks_and_defenses()
{
int str;
int k;
int acode, dcode;
int attack_point;
int defense_point;
static int libs[MAXLIBS];
int liberties;
int color;
int other;
/* 1. Start with finding attack points. */
for (str = BOARDMIN; str < BOARDMAX; str++) {
if (!IS_STONE(board[str]) || !is_worm_origin(str, str))
continue;
TRACE("considering attack of %1m\n", str);
/* Initialize all relevant fields at once. */
for (k = 0; k < MAX_TACTICAL_POINTS; k++) {
worm[str].attack_codes[k] = 0;
worm[str].attack_points[k] = 0;
worm[str].defense_codes[k] = 0;
worm[str].defense_points[k] = 0;
}
propagate_worm(str);
acode = attack(str, &attack_point);
if (acode != 0) {
DEBUG(DEBUG_WORMS, "worm at %1m can be attacked at %1m\n",
str, attack_point);
change_attack(str, attack_point, acode);
}
}
gg_assert(stackp == 0);
/* 2. Use pattern matching to find a few more attacks. */
find_attack_patterns();
gg_assert(stackp == 0);
/* 3. Now find defense moves. */
for (str = BOARDMIN; str < BOARDMAX; str++) {
if (!IS_STONE(board[str]) || !is_worm_origin(str, str))
continue;
if (worm[str].attack_codes[0] != 0) {
TRACE("considering defense of %1m\n", str);
dcode = find_defense(str, &defense_point);
if (dcode != 0) {
TRACE("worm at %1m can be defended at %1m\n", str, defense_point);
if (defense_point != NO_MOVE)
change_defense(str, defense_point, dcode);
}
else {
/* If the point of attack is not adjacent to the worm,
* it is possible that this is an overlooked point of
* defense, so we try and see if it defends.
*/
attack_point = worm[str].attack_points[0];
if (!liberty_of_string(attack_point, str))
if (trymove(attack_point, worm[str].color, "make_worms", NO_MOVE)) {
int acode = attack(str, NULL);
if (acode != WIN) {
change_defense(str, attack_point, REVERSE_RESULT(acode));
TRACE("worm at %1m can be defended at %1m with code %d\n",
str, attack_point, REVERSE_RESULT(acode));
}
popgo();
}
}
}
}
gg_assert(stackp == 0);
/* 4. Use pattern matching to find a few more defense moves. */
find_defense_patterns();
gg_assert(stackp == 0);
/*
* 5. Find additional attacks and defenses by testing all immediate
* liberties. Further attacks and defenses are found by pattern
* matching and by trying whether each attack or defense point
* attacks or defends other strings.
*/
for (str = BOARDMIN; str < BOARDMAX; str++) {
color = board[str];
if (!IS_STONE(color) || !is_worm_origin(str, str))
continue;
other = OTHER_COLOR(color);
if (worm[str].attack_codes[0] == 0)
continue;
/* There is at least one attack on this group. Try the
* liberties.
*/
liberties = findlib(str, MAXLIBS, libs);
for (k = 0; k < liberties; k++) {
int pos = libs[k];
if (!attack_move_known(pos, str)) {
/* Try to attack on the liberty. Don't consider
* send-two-return-one moves.
*/
if (!send_two_return_one(pos, other)
&& trymove(pos, other, "make_worms", str)) {
if (board[str] == EMPTY || attack(str, NULL)) {
if (board[str] == EMPTY)
dcode = 0;
else
dcode = find_defense(str, NULL);
if (dcode != WIN)
change_attack(str, pos, REVERSE_RESULT(dcode));
}
popgo();
}
}
/* Try to defend at the liberty. */
if (!defense_move_known(pos, str)) {
if (worm[str].defense_codes[0] != 0)
if (trymove(pos, color, "make_worms", NO_MOVE)) {
acode = attack(str, NULL);
if (acode != WIN)
change_defense(str, pos, REVERSE_RESULT(acode));
popgo();
}
}
}
}
gg_assert(stackp == 0);
}
/*
* Find moves threatening to attack or save all worms.
*/
static void
find_worm_threats()
{
int str;
static int libs[MAXLIBS];
int liberties;
int k;
int l;
int color;
for (str = BOARDMIN; str < BOARDMAX; str++) {
color = board[str];
if (!IS_STONE(color) || !is_worm_origin(str, str))
continue;
/* 1. Start with finding attack threats. */
/* Only try those worms that have no attack. */
if (worm[str].attack_codes[0] == 0) {
attack_threats(str, MAX_TACTICAL_POINTS,
worm[str].attack_threat_points,
worm[str].attack_threat_codes);
#if 0
/* Threaten to attack by saving weak neighbors. */
num_adj = chainlinks(str, adjs);
for (k = 0; k < num_adj; k++) {
if (worm[adjs[k]].attack_codes[0] != 0
&& worm[adjs[k]].defense_codes[0] != 0)
for (r = 0; r < MAX_TACTICAL_POINTS; r++) {
int bb;
if (worm[adjs[k]].defense_codes[r] == 0)
break;
bb = worm[adjs[k]].defense_points[r];
if (trymove(bb, other, "threaten attack", str,
EMPTY, NO_MOVE)) {
int acode;
if (board[str] == EMPTY)
acode = WIN;
else
acode = attack(str, NULL);
if (acode != 0)
change_attack_threat(str, bb, acode);
popgo();
}
}
}
#endif
/* FIXME: Try other moves also (patterns?). */
}
/* 2. Continue with finding defense threats. */
/* Only try those worms that have an attack. */
if (worm[str].attack_codes[0] != 0
&& worm[str].defense_codes[0] == 0) {
liberties = findlib(str, MAXLIBS, libs);
for (k = 0; k < liberties; k++) {
int aa = libs[k];
/* Try to threaten on the liberty. */
if (trymove(aa, color, "threaten defense", NO_MOVE)) {
if (attack(str, NULL) == WIN) {
int dcode = find_defense(str, NULL);
if (dcode != 0)
change_defense_threat(str, aa, dcode);
}
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, color, "threaten defense", str)) {
if (attack(str, NULL) == WIN) {
int dcode = find_defense(str, NULL);
if (dcode != 0)
change_defense_threat(str, bb, dcode);
}
popgo();
}
}
}
/* It might be interesting to look for defense threats by
* attacking weak neighbors, similar to threatening attack by
* defending a weak neighbor. However, in this case it seems
* probable that if there is such an attack, it's a real
* defense, not only a threat.
*/
/* FIXME: Try other moves also (patterns?). */
}
}
}
/* find_lunch(str, &worm) looks for a worm adjoining the
* string at (str) which can be easily captured. Whether or not it can
* be defended doesn't matter.
*
* Returns the location of the string in (*lunch).
*/
static int
find_lunch(int str, int *lunch)
{
int pos;
int k;
ASSERT1(IS_STONE(board[str]), str);
ASSERT1(stackp == 0, str);
*lunch = NO_MOVE;
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (board[pos] != OTHER_COLOR(board[str]))
continue;
for (k = 0; k < 8; k++) {
int apos = pos + delta[k];
if (ON_BOARD(apos) && is_same_worm(apos, str)) {
if (worm[pos].attack_codes[0] != 0 && !is_ko_point(pos)) {
/*
* If several adjacent lunches are found, we pick the
* juiciest. First maximize cutstone, then minimize liberties.
* We can only do this if the worm data is available,
* i.e. if stackp==0.
*/
if (*lunch == NO_MOVE
|| worm[pos].cutstone > worm[*lunch].cutstone
|| (worm[pos].cutstone == worm[*lunch].cutstone
&& worm[pos].liberties < worm[*lunch].liberties)) {
*lunch = worm[pos].origin;
}
}
break;
}
}
}
if (*lunch != NO_MOVE)
return 1;
return 0;
}
/*
* Test whether two worms are the same. Used by autohelpers.
* Before this function can be called, build_worms must have been run.
*/
int
is_same_worm(int w1, int w2)
{
return worm[w1].origin == worm[w2].origin;
}
/*
* Test whether the origin of the worm at (w) is (pos).
*/
int
is_worm_origin(int w, int pos)
{
return worm[w].origin == pos;
}
/*
* change_defense(str, move, dcode) is used to add and remove defense
* points. It can also be used to change the defense code. The meaning
* of the call is that the string (str) can be defended by (move) with
* defense code (dcode). If (dcode) is zero, the move is removed from
* the list of defense moves if it was previously listed.
*/
void
change_defense(int str, int move, int dcode)
{
str = worm[str].origin;
change_tactical_point(str, move, dcode,
worm[str].defense_points, worm[str].defense_codes);
}
/*
* change_attack(str, move, acode) is used to add and remove attack
* points. It can also be used to change the attack code. The meaning
* of the call is that the string (str) can be attacked by (move) with
* attack code (acode). If (acode) is zero, the move is removed from
* the list of attack moves if it was previously listed.
*/
void
change_attack(int str, int move, int acode)
{
str = worm[str].origin;
DEBUG(DEBUG_WORMS, "change_attack: %1m %1m %d\n", str, move, acode);
change_tactical_point(str, move, acode,
worm[str].attack_points, worm[str].attack_codes);
}
/*
* change_defense_threat(str, move, dcode) is used to add and remove
* defense threat points. It can also be used to change the defense
* threat code. The meaning of the call is that the string (str) can
* threaten to be defended by (move) with defense threat code (dcode).
* If (dcode) is zero, the move is removed from the list of defense
* threat moves if it was previously listed.
*/
void
change_defense_threat(int str, int move, int dcode)
{
str = worm[str].origin;
change_tactical_point(str, move, dcode,
worm[str].defense_threat_points,
worm[str].defense_threat_codes);
}
/*
* change_attack_threat(str, move, acode) is used to add and remove
* attack threat points. It can also be used to change the attack
* threat code. The meaning of the call is that the string (str) can
* threaten to be attacked by (move) with attack threat code (acode).
* If (acode) is zero, the move is removed from the list of attack
* threat moves if it was previously listed.
*/
void
change_attack_threat(int str, int move, int acode)
{
str = worm[str].origin;
change_tactical_point(str, move, acode,
worm[str].attack_threat_points,
worm[str].attack_threat_codes);
}
/* Check whether (move) is listed as an attack point for (str) and
* return the attack code. If (move) is not listed, return 0.
*/
int
attack_move_known(int move, int str)
{
return movelist_move_known(move, MAX_TACTICAL_POINTS,
worm[str].attack_points,
worm[str].attack_codes);
}
/* Check whether (move) is listed as a defense point for (str) and
* return the defense code. If (move) is not listed, return 0.
*/
int
defense_move_known(int move, int str)
{
return movelist_move_known(move, MAX_TACTICAL_POINTS,
worm[str].defense_points,
worm[str].defense_codes);
}
/* Check whether (move) is listed as an attack threat point for (str)
* and return the attack threat code. If (move) is not listed, return
* 0.
*/
int
attack_threat_move_known(int move, int str)
{
return movelist_move_known(move, MAX_TACTICAL_POINTS,
worm[str].attack_threat_points,
worm[str].attack_threat_codes);
}
/* Check whether (move) is listed as a defense threat point for (str)
* and return the defense threat code. If (move) is not listed, return
* 0.
*/
int
defense_threat_move_known(int move, int str)
{
return movelist_move_known(move, MAX_TACTICAL_POINTS,
worm[str].defense_threat_points,
worm[str].defense_threat_codes);
}
/*
* This function does the real work for change_attack(),
* change_defense(), change_attack_threat(), and
* change_defense_threat().
*/
static void
change_tactical_point(int str, int move, int code,
int points[MAX_TACTICAL_POINTS],
int codes[MAX_TACTICAL_POINTS])
{
ASSERT_ON_BOARD1(str);
ASSERT1(str == worm[str].origin, str);
movelist_change_point(move, code, MAX_TACTICAL_POINTS, points, codes);
propagate_worm2(str);
}
/*
* propagate_worm() takes the worm data at one stone and copies it to
* the remaining members of the worm.
*
* Even though we don't need to copy all the fields, it's probably
* better to do a structure copy which should compile to a block copy.
*/
void
propagate_worm(int pos)
{
int k;
int num_stones;
int stones[MAX_BOARD * MAX_BOARD];
gg_assert(stackp == 0);
ASSERT1(IS_STONE(board[pos]), pos);
num_stones = findstones(pos, MAX_BOARD * MAX_BOARD, stones);
for (k = 0; k < num_stones; k++)
if (stones[k] != pos)
worm[stones[k]] = worm[pos];
}
/*
* propagate_worm2() is a relative to propagate_worm() which can be
* used when stackp>0 but not for the initial construction of the
* worms.
*/
static void
propagate_worm2(int str)
{
int pos;
ASSERT_ON_BOARD1(str);
ASSERT1(IS_STONE(worm[str].color), str);
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
if (board[pos] == board[str] && is_same_worm(pos, str)
&& pos != str)
worm[pos] = worm[str];
}
/* Report all known attack, defense, attack threat, and defense threat
* moves. But limit this to the moves which can be made by (color).
*/
void
worm_reasons(int color)
{
int pos;
int k;
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (!ON_BOARD(pos) || board[pos] == EMPTY)
continue;
if (!is_worm_origin(pos, pos))
continue;
if (board[pos] == OTHER_COLOR(color)) {
for (k = 0; k < MAX_TACTICAL_POINTS; k++) {
if (worm[pos].attack_codes[k] != 0)
add_attack_move(worm[pos].attack_points[k], pos,
worm[pos].attack_codes[k]);
if (worm[pos].attack_threat_codes[k] != 0)
add_attack_threat_move(worm[pos].attack_threat_points[k], pos,
worm[pos].attack_threat_codes[k]);
}
}
if (board[pos] == color) {
for (k = 0; k < MAX_TACTICAL_POINTS; k++) {
if (worm[pos].defense_codes[k] != 0)
add_defense_move(worm[pos].defense_points[k], pos,
worm[pos].defense_codes[k]);
if (worm[pos].defense_threat_codes[k] != 0)
add_defense_threat_move(worm[pos].defense_threat_points[k], pos,
worm[pos].defense_threat_codes[k]);
}
}
}
}
/* ping_cave(str, *lib1, ...) is applied when (str) points to a string.
* It computes the vector (*lib1, *lib2, *lib3, *lib4),
* where *lib1 is the number of liberties of the string,
* *lib2 is the number of second order liberties (empty vertices
* at distance two) and so forth.
*
* The definition of liberties of order >1 is adapted to the problem
* of detecting the shape of the surrounding cavity. In particular
* we want to be able to see if a group is loosely surrounded.
*
* A liberty of order n is an empty space which may be connected
* to the string by placing n stones of the same color on the board,
* but no fewer. The path of connection may pass through an intervening group
* of the same color. The stones placed at distance >1 may not touch a
* group of the opposite color. At the edge, also diagonal neighbors
* count as touching. The path may also not pass through a liberty at distance
* 1 if that liberty is flanked by two stones of the opposing color. This
* reflects the fact that the O stone is blocked from expansion to the
* left by the two X stones in the following situation:
*
* X.
* .O
* X.
*
* On the edge, one stone is sufficient to block expansion:
*
* X.
* .O
* --
*/
static void
ping_cave(int str, int *lib1, int *lib2, int *lib3, int *lib4)
{
int pos;
int k;
int libs[MAXLIBS];
int mrc[BOARDMAX];
int mse[BOARDMAX];
int color = board[str];
int other = OTHER_COLOR(color);
memset(mse, 0, sizeof(mse));
/* Find and mark the first order liberties. */
*lib1 = findlib(str, MAXLIBS, libs);
for (k = 0; k < *lib1; k++)
mse[libs[k]] = 1;
/* Reset mse at liberties which are flanked by two stones of the
* opposite color, or one stone and the edge.
*/
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
if (ON_BOARD(pos)
&& mse[pos]
&& ((( !ON_BOARD(SOUTH(pos)) || board[SOUTH(pos)] == other)
&& ( !ON_BOARD(NORTH(pos)) || board[NORTH(pos)] == other))
|| (( !ON_BOARD(WEST(pos)) || board[WEST(pos)] == other)
&& (!ON_BOARD(EAST(pos)) || board[EAST(pos)] == other))))
mse[pos] = 0;
*lib2 = 0;
memset(mrc, 0, sizeof(mrc));
ping_recurse(str, lib2, mse, mrc, color);
*lib3 = 0;
memset(mrc, 0, sizeof(mrc));
ping_recurse(str, lib3, mse, mrc, color);
*lib4 = 0;
memset(mrc, 0, sizeof(mrc));
ping_recurse(str, lib4, mse, mrc, color);
}
/* recursive function called by ping_cave */
static void
ping_recurse(int pos, int *counter,
int mx[BOARDMAX], int mr[BOARDMAX],
int color)
{
int k;
mr[pos] = 1;
for (k = 0; k < 4; k++) {
int apos = pos + delta[k];
if (board[apos] == EMPTY
&& mx[apos] == 0
&& mr[apos] == 0
&& !touching(apos, OTHER_COLOR(color))) {
(*counter)++;
mr[apos] = 1;
mx[apos] = 1;
}
}
if (!is_ko_point(pos)) {
for (k = 0; k < 4; k++) {
int apos = pos + delta[k];
if (ON_BOARD(apos)
&& mr[apos] == 0
&& (mx[apos] == 1
|| board[apos] == color))
ping_recurse(apos, counter, mx, mr, color);
}
}
}
/* touching(pos, color) returns true if the vertex at (pos) is
* touching any stone of (color).
*/
static int
touching(int pos, int color)
{
return (board[SOUTH(pos)] == color
|| board[WEST(pos)] == color
|| board[NORTH(pos)] == color
|| board[EAST(pos)] == color);
}
/* The GENUS of a string is the number of connected components of
* its complement, minus one. It is an approximation to the number of
* eyes of the string.
*/
static int
genus(int str)
{
int pos;
int mg[BOARDMAX];
int gen = -1;
memset(mg, 0, sizeof(mg));
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (ON_BOARD(pos)
&& !mg[pos]
&& (board[pos] == EMPTY || !is_same_worm(pos, str))) {
markcomponent(str, pos, mg);
gen++;
}
}
return gen;
}
/* This recursive function marks the component at (pos) of
* the complement of the string with origin (str)
*/
static void
markcomponent(int str, int pos, int mg[BOARDMAX])
{
int k;
mg[pos] = 1;
for (k = 0; k < 4; k++) {
int apos = pos + delta[k];
if (ON_BOARD(apos)
&& mg[apos] == 0
&& (board[apos] == EMPTY || !is_same_worm(apos, str)))
markcomponent(str, apos, mg);
}
}
/* examine_cavity(pos, *edge), if (pos) is EMPTY, examines the
* cavity at (m, n) and returns its bordercolor,
* which can be BLACK, WHITE or GRAY. The edge parameter is set to the
* number of edge vertices in the cavity.
*
* If (pos) is nonempty, it returns the same result, imagining
* that the string at (pos) is removed. The edge parameter is
* set to the number of vertices where the cavity meets the
* edge in a point outside the removed string.
*/
static int
examine_cavity(int pos, int *edge)
{
int border_color = EMPTY;
int ml[BOARDMAX];
int origin = NO_MOVE;
ASSERT_ON_BOARD1(pos);
gg_assert(edge != NULL);
memset(ml, 0, sizeof(ml));
*edge = 0;
if (IS_STONE(board[pos]))
origin = find_origin(pos);
cavity_recurse(pos, ml, &border_color, edge, origin);
if (border_color != EMPTY)
return border_color;
/* We should have returned now, unless the board is completely empty.
* Verify that this is the case and then return GRAY.
*
* Notice that the board appears completely empty if there's only a
* single string and pos points to it.
*/
gg_assert(border_color == EMPTY
&& ((pos == NO_MOVE
&& stones_on_board(BLACK | WHITE) == 0)
|| (pos != NO_MOVE
&& stones_on_board(BLACK | WHITE) == countstones(pos))));
return GRAY;
}
/* helper function for examine_cavity.
* border_color contains information so far : transitions allowed are
* EMPTY -> BLACK/WHITE
* BLACK/WHITE -> BLACK | WHITE
*
* mx[pos] is 1 if (pos) has already been visited.
*
* if (str) points to the origin of a string, it will be ignored.
*
* On (fully-unwound) exit
* *border_color should be BLACK, WHITE or BLACK | WHITE
* *edge is the count of edge pieces
*
* *border_color should be EMPTY if and only if the board
* is completely empty or only contains the ignored string.
*/
static void
cavity_recurse(int pos, int mx[BOARDMAX],
int *border_color, int *edge, int str)
{
int k;
ASSERT1(mx[pos] == 0, pos);
mx[pos] = 1;
if (is_edge_vertex(pos) && board[pos] == EMPTY)
(*edge)++;
/* Loop over the four neighbors. */
for (k = 0; k < 4; k++) {
int apos = pos + delta[k];
if (ON_BOARD(apos) && !mx[apos]) {
int neighbor_empty = 0;
if (board[apos] == EMPTY)
neighbor_empty = 1;
else {
/* Count the neighbor as empty if it is part of the (ai, aj) string. */
if (str == find_origin(apos))
neighbor_empty = 1;
else
neighbor_empty = 0;
}
if (!neighbor_empty)
*border_color |= board[apos];
else
cavity_recurse(apos, mx, border_color, edge, str);
}
}
}
/* Find attacking moves by pattern matching, for both colors. */
static void
find_attack_patterns(void)
{
matchpat(attack_callback, ANCHOR_OTHER, &attpat_db, NULL, NULL);
}
/* Try to attack every X string in the pattern, whether there is an attack
* before or not. Only exclude already known attacking moves.
*/
static void
attack_callback(int anchor, int color, struct pattern *pattern, int ll,
void *data)
{
int move;
int k;
UNUSED(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))
return;
}
/* If the pattern has a helper, call it to see if the pattern must
* be rejected.
*/
if (pattern->helper) {
if (!pattern->helper(pattern, ll, move, color)) {
DEBUG(DEBUG_WORMS,
"Attack pattern %s+%d rejected by helper at %1m\n",
pattern->name, ll, move);
return;
}
}
/* Loop through pattern elements in search of X strings to attack. */
for (k = 0; k < pattern->patlen; ++k) { /* match each point */
if (pattern->patn[k].att == ATT_X) {
/* transform pattern real coordinate */
int pos = AFFINE_TRANSFORM(pattern->patn[k].offset, ll, anchor);
int str = worm[pos].origin;
/* A string with 5 liberties or more is considered tactically alive. */
if (countlib(str) > 4)
continue;
if (attack_move_known(move, str))
continue;
/* No defenses are known at this time, so defend_code is always 0. */
#if 0
/* If the string can be attacked but not defended, ignore it. */
if (worm[str].attack_codes[0] == WIN && worm[str].defense_codes[0] == 0)
continue;
#endif
/* FIXME: Don't attack the same string more than once.
* Play (move) and see if there is a defense.
*/
if (trymove(move, color, "attack_callback", str)) {
int dcode;
if (!board[str])
dcode = 0;
else if (!attack(str, NULL))
dcode = WIN;
else
dcode = find_defense(str, NULL);
popgo();
/* Do not pick up suboptimal attacks at this time. Since we
* don't know whether the string can be defended it's quite
* possible that it only has a ko defense and then we would
* risk to find an irrelevant move to attack with ko.
*/
if (dcode != WIN && REVERSE_RESULT(dcode) >= worm[str].attack_codes[0]) {
change_attack(str, move, REVERSE_RESULT(dcode));
DEBUG(DEBUG_WORMS,
"Attack pattern %s+%d found attack on %1m at %1m with code %d\n",
pattern->name, ll, str, move, REVERSE_RESULT(dcode));
}
}
}
}
}
static void
find_defense_patterns(void)
{
matchpat(defense_callback, ANCHOR_COLOR, &defpat_db, NULL, NULL);
}
static void
defense_callback(int anchor, int color, struct pattern *pattern, int ll,
void *data)
{
int move;
int k;
UNUSED(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))
return;
}
/* If the pattern has a helper, call it to see if the pattern must
* be rejected.
*/
if (pattern->helper) {
if (!pattern->helper(pattern, ll, move, color)) {
DEBUG(DEBUG_WORMS,
"Defense pattern %s+%d rejected by helper at %1m\n",
pattern->name, ll, move);
return;
}
}
/* Loop through pattern elements in search for O strings to defend. */
for (k = 0; k < pattern->patlen; ++k) { /* match each point */
if (pattern->patn[k].att == ATT_O) {
/* transform pattern real coordinate */
int pos = AFFINE_TRANSFORM(pattern->patn[k].offset, ll, anchor);
int str = worm[pos].origin;
if (worm[str].attack_codes[0] == 0
|| defense_move_known(move, str))
continue;
/* FIXME: Don't try to defend the same string more than once.
* FIXME: For all attacks on this string, we should test whether
* the proposed move happens to refute the attack.
* Play (move) and see if there is an attack.
*/
if (trymove(move, color, "defense_callback", str)) {
int acode = attack(str, NULL);
popgo();
if (acode < worm[str].attack_codes[0]) {
change_defense(str, move, REVERSE_RESULT(acode));
DEBUG(DEBUG_WORMS,
"Defense pattern %s+%d found defense of %1m at %1m with code %d\n",
pattern->name, ll, str, move, REVERSE_RESULT(acode));
}
}
}
}
}
void
get_lively_stones(int color, signed char safe_stones[BOARDMAX])
{
int pos;
memset(safe_stones, 0, BOARDMAX * sizeof(*safe_stones));
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
if (IS_STONE(board[pos]) && find_origin(pos) == pos) {
if ((stackp == 0 && worm[pos].attack_codes[0] == 0) || !attack(pos, NULL)
|| (board[pos] == color
&& ((stackp == 0 && worm[pos].defense_codes[0] != 0)
|| find_defense(pos, NULL))))
mark_string(pos, safe_stones, 1);
}
}
void
compute_worm_influence()
{
signed char safe_stones[BOARDMAX];
get_lively_stones(BLACK, safe_stones);
compute_influence(BLACK, safe_stones, NULL, &initial_black_influence,
NO_MOVE, "initial black influence");
get_lively_stones(WHITE, safe_stones);
compute_influence(WHITE, safe_stones, NULL, &initial_white_influence,
NO_MOVE, "initial white influence");
}
/* ================================================================ */
/* Debugger functions */
/* ================================================================ */
/* For use in gdb, print details of the worm at (m, n).
* Add this to your .gdbinit file:
*
* define worm
* set ascii_report_worm("$arg0")
* end
*
* Now 'worm S8' will report the details of the S8 worm.
*
*/
void
ascii_report_worm(char *string)
{
int pos = string_to_location(board_size, string);
report_worm(pos);
}
static void
report_worm(int pos)
{
int i;
if (board[pos] == EMPTY) {
gprintf("There is no worm at %1m\n", pos);
return;
}
gprintf("*** worm at %1m:\n", pos);
gprintf("color: %s; origin: %1m; size: %d; effective size: %f\n",
(worm[pos].color == WHITE) ? "White" : "Black",
worm[pos].origin, worm[pos].size, worm[pos].effective_size);
gprintf("liberties: %d order 2 liberties:%d order 3:%d order 4:%d\n",
worm[pos].liberties,
worm[pos].liberties2,
worm[pos].liberties3,
worm[pos].liberties4);
/* List all attack points. */
if (worm[pos].attack_points[0] == NO_MOVE)
gprintf("no attack point, ");
else {
gprintf("attack point(s):");
i = 0;
while (worm[pos].attack_points[i] != NO_MOVE) {
if (i > 0)
gprintf(",");
gprintf(" %1m: %s", worm[pos].attack_points[i],
result_to_string(worm[pos].attack_codes[i]));
i++;
}
gprintf("\n;");
}
/* List all defense points. */
if (worm[pos].defense_points[0] == NO_MOVE)
gprintf("no defense point, ");
else {
gprintf("defense point(s):");
i = 0;
while (worm[pos].defense_points[i] != NO_MOVE) {
if (i > 0)
gprintf(",");
gprintf(" %1m: %s", worm[pos].defense_points[i],
result_to_string(worm[pos].defense_codes[i]));
i++;
}
gprintf("\n;");
}
/* List all attack threat points. */
if (worm[pos].attack_threat_points[0] == NO_MOVE)
gprintf("no attack threat point, ");
else {
gprintf("attack threat point(s):");
i = 0;
while (worm[pos].attack_threat_points[i] != NO_MOVE) {
if (i > 0)
gprintf(",");
gprintf(" %1m: %s", worm[pos].attack_threat_points[i],
result_to_string(worm[pos].attack_threat_codes[i]));
i++;
}
gprintf("\n;");
}
/* List all defense threat points. */
if (worm[pos].defense_threat_points[0] == NO_MOVE)
gprintf("no defense threat point, ");
else {
gprintf("defense threat point(s):");
i = 0;
while (worm[pos].defense_threat_points[i] != NO_MOVE) {
if (i > 0)
gprintf(",");
gprintf(" %1m: %s", worm[pos].defense_threat_points[i],
result_to_string(worm[pos].defense_threat_codes[i]));
i++;
}
gprintf("\n;");
}
/* Report lunch if any. */
if (worm[pos].lunch != NO_MOVE)
gprintf("lunch at %1m\n", worm[pos].lunch);
gprintf("cutstone: %d, cutstone2: %d\n",
worm[pos].cutstone, worm[pos].cutstone2);
gprintf("genus: %d, ", worm[pos].genus);
if (worm[pos].inessential)
gprintf("inessential: YES, ");
else
gprintf("inessential: NO, ");
if (worm[pos].invincible)
gprintf("invincible: YES, \n");
else
gprintf("invincible: NO, \n");
gprintf("unconditional status %s\n",
status_to_string(worm[pos].unconditional_status));
}
/*
* Local Variables:
* tab-width: 8
* c-basic-offset: 2
* End:
*/