3762 lines
106 KiB
C
3762 lines
106 KiB
C
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
|
* This is GNU Go, a Go program. Contact gnugo@gnu.org, or see *
|
|
* http://www.gnu.org/software/gnugo/ for more information. *
|
|
* *
|
|
* Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, *
|
|
* 2008 and 2009 by the Free Software Foundation. *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU General Public License as *
|
|
* published by the Free Software Foundation - version 3 or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU General Public License in file COPYING for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public *
|
|
* License along with this program; if not, write to the Free *
|
|
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, *
|
|
* Boston, MA 02111, USA. *
|
|
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
#include "gnugo.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "liberty.h"
|
|
#include "eyes.h"
|
|
#include "gg_utils.h"
|
|
|
|
#define MAXEYE 20
|
|
|
|
|
|
/* This structure is used in communication between read_eye() and
|
|
* recognize_eye().
|
|
*/
|
|
struct vital_points {
|
|
int attacks[4 * MAXEYE];
|
|
int defenses[4 * MAXEYE];
|
|
int num_attacks;
|
|
int num_defenses;
|
|
};
|
|
|
|
|
|
static void
|
|
compute_primary_domains(int color, int domain[BOARDMAX],
|
|
int lively[BOARDMAX],
|
|
int false_margins[BOARDMAX],
|
|
int first_time);
|
|
static void count_neighbours(struct eye_data eyedata[BOARDMAX]);
|
|
static int is_lively(int owl_call, int pos);
|
|
static int false_margin(int pos, int color, int lively[BOARDMAX]);
|
|
static void originate_eye(int origin, int pos,
|
|
int *esize, int *msize,
|
|
struct eye_data eye[BOARDMAX]);
|
|
static int read_eye(int pos, int *attack_point, int *defense_point,
|
|
struct eyevalue *value,
|
|
struct eye_data eye[BOARDMAX],
|
|
struct half_eye_data heye[BOARDMAX],
|
|
int add_moves);
|
|
static int recognize_eye(int pos, int *attack_point, int *defense_point,
|
|
struct eyevalue *value,
|
|
struct eye_data eye[BOARDMAX],
|
|
struct half_eye_data heye[BOARDMAX],
|
|
struct vital_points *vp);
|
|
static void guess_eye_space(int pos, int effective_eyesize, int margins,
|
|
int bulk_score, struct eye_data eye[BOARDMAX],
|
|
struct eyevalue *value, int *pessimistic_min);
|
|
static void reset_map(int size);
|
|
static void first_map(int *map_value);
|
|
static int next_map(int *q, int map[MAXEYE]);
|
|
static void print_eye(struct eye_data eye[BOARDMAX],
|
|
struct half_eye_data heye[BOARDMAX], int pos);
|
|
static void add_false_eye(int pos, struct eye_data eye[BOARDMAX],
|
|
struct half_eye_data heye[BOARDMAX]);
|
|
static float topological_eye(int pos, int color,
|
|
struct eye_data my_eye[BOARDMAX],
|
|
struct half_eye_data heye[BOARDMAX]);
|
|
static float evaluate_diagonal_intersection(int m, int n, int color,
|
|
int *attack_point,
|
|
int *defense_point,
|
|
struct eye_data my_eye[BOARDMAX]);
|
|
|
|
|
|
/* These are used during the calculations of eye spaces. */
|
|
static int black_domain[BOARDMAX];
|
|
static int white_domain[BOARDMAX];
|
|
|
|
/* Used internally by mapping functions. */
|
|
static int map_size;
|
|
static signed char used_index[MAXEYE];
|
|
|
|
|
|
/*
|
|
* make_domains() is called from make_dragons() and from
|
|
* owl_determine_life(). It marks the black and white domains
|
|
* (eyeshape regions) and collects some statistics about each one.
|
|
*/
|
|
|
|
void
|
|
make_domains(struct eye_data b_eye[BOARDMAX],
|
|
struct eye_data w_eye[BOARDMAX],
|
|
int owl_call)
|
|
{
|
|
int k;
|
|
int pos;
|
|
int lively[BOARDMAX];
|
|
int false_margins[BOARDMAX];
|
|
|
|
memset(black_domain, 0, sizeof(black_domain));
|
|
memset(white_domain, 0, sizeof(white_domain));
|
|
memset(false_margins, 0, sizeof(false_margins));
|
|
|
|
if (b_eye)
|
|
memset(b_eye, 0, BOARDMAX * sizeof(b_eye[0]));
|
|
if (w_eye)
|
|
memset(w_eye, 0, BOARDMAX * sizeof(w_eye[0]));
|
|
|
|
/* Initialize eye data and compute the lively array. */
|
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
|
|
if (ON_BOARD(pos))
|
|
lively[pos] = is_lively(owl_call, pos);
|
|
|
|
/* Compute the domains of influence of each color. */
|
|
compute_primary_domains(BLACK, black_domain, lively, false_margins, 1);
|
|
compute_primary_domains(WHITE, white_domain, lively, false_margins, 0);
|
|
|
|
/* Now we fill out the arrays b_eye and w_eye with data describing
|
|
* each eye shape.
|
|
*/
|
|
|
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
|
if (!ON_BOARD(pos))
|
|
continue;
|
|
|
|
if (board[pos] == EMPTY || !lively[pos]) {
|
|
if (black_domain[pos] == 0 && white_domain[pos] == 0) {
|
|
if (w_eye)
|
|
w_eye[pos].color = GRAY;
|
|
if (b_eye)
|
|
b_eye[pos].color = GRAY;
|
|
}
|
|
else if (black_domain[pos] == 1 && white_domain[pos] == 0 && b_eye) {
|
|
b_eye[pos].color = BLACK;
|
|
for (k = 0; k < 4; k++) {
|
|
int apos = pos + delta[k];
|
|
if (ON_BOARD(apos) && white_domain[apos] && !black_domain[apos]) {
|
|
b_eye[pos].marginal = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (black_domain[pos] == 0 && white_domain[pos] == 1 && w_eye) {
|
|
w_eye[pos].color = WHITE;
|
|
for (k = 0; k < 4; k++) {
|
|
int apos = pos + delta[k];
|
|
if (ON_BOARD(apos) && black_domain[apos] && !white_domain[apos]) {
|
|
w_eye[pos].marginal = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (black_domain[pos] == 1 && white_domain[pos] == 1) {
|
|
if (b_eye) {
|
|
for (k = 0; k < 4; k++) {
|
|
int apos = pos + delta[k];
|
|
if (ON_BOARD(apos) && black_domain[apos]
|
|
&& !white_domain[apos]) {
|
|
b_eye[pos].marginal = 1;
|
|
b_eye[pos].color = BLACK;
|
|
break;
|
|
}
|
|
}
|
|
if (k == 4)
|
|
b_eye[pos].color = GRAY;
|
|
}
|
|
|
|
if (w_eye) {
|
|
for (k = 0; k < 4; k++) {
|
|
int apos = pos + delta[k];
|
|
if (ON_BOARD(apos) && white_domain[apos]
|
|
&& !black_domain[apos]) {
|
|
w_eye[pos].marginal = 1;
|
|
w_eye[pos].color = WHITE;
|
|
break;
|
|
}
|
|
}
|
|
if (k == 4)
|
|
w_eye[pos].color = GRAY;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* The eye spaces are all found. Now we need to find the origins. */
|
|
partition_eyespaces(b_eye, BLACK);
|
|
partition_eyespaces(w_eye, WHITE);
|
|
}
|
|
|
|
/* Find connected eyespace components and compute relevant statistics. */
|
|
void
|
|
partition_eyespaces(struct eye_data eye[BOARDMAX], int color)
|
|
{
|
|
int pos;
|
|
|
|
if (!eye)
|
|
return;
|
|
|
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
|
|
if (ON_BOARD(pos))
|
|
eye[pos].origin = NO_MOVE;
|
|
|
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
|
if (!ON_BOARD(pos))
|
|
continue;
|
|
if (eye[pos].origin == NO_MOVE && eye[pos].color == color) {
|
|
int esize = 0;
|
|
int msize = 0;
|
|
|
|
originate_eye(pos, pos, &esize, &msize, eye);
|
|
eye[pos].esize = esize;
|
|
eye[pos].msize = msize;
|
|
}
|
|
}
|
|
|
|
/* Now we count the number of neighbors and marginal neighbors
|
|
* of each vertex.
|
|
*/
|
|
count_neighbours(eye);
|
|
}
|
|
|
|
|
|
/* Compute the domains of influence of each color, used in determining
|
|
* eye shapes. NOTE: the term influence as used here is distinct from the
|
|
* influence in influence.c.
|
|
*
|
|
* For this algorithm the strings which are not lively are invisible. Ignoring
|
|
* these, the algorithm assigns friendly influence to:
|
|
*
|
|
* (1) every vertex which is occupied by a (lively) friendly stone,
|
|
* (2) every empty vertex adjoining a (lively) friendly stone,
|
|
* (3) every empty vertex for which two adjoining vertices (not
|
|
* on the first line) in the (usually 8) surrounding ones have friendly
|
|
* influence, with two CAVEATS explained below.
|
|
*
|
|
* Thus in the following diagram, e would be assigned friendly influence
|
|
* if a and b have friendly influence, or a and d. It is not sufficent
|
|
* for b and d to have friendly influence, because they are not adjoining.
|
|
*
|
|
* uabc
|
|
* def
|
|
* ghi
|
|
*
|
|
* The constraint that the two adjoining vertices not lie on the first
|
|
* line prevents influence from leaking under a stone on the third line.
|
|
*
|
|
* The first CAVEAT alluded to above is that even if a and b have friendly
|
|
* influence, this does not cause e to have friendly influence if there
|
|
* is a lively opponent stone at d. This constraint prevents
|
|
* influence from leaking past knight's move extensions.
|
|
*
|
|
* The second CAVEAT is that even if a and b have friendly influence
|
|
* this does not cause e to have influence if there are lively opponent
|
|
* stones at u and at c. This prevents influence from leaking past
|
|
* nikken tobis (two space jumps).
|
|
*
|
|
* The corner vertices are handled slightly different.
|
|
*
|
|
* +---
|
|
* |ab
|
|
* |cd
|
|
*
|
|
* We get friendly influence at a if we have friendly influence
|
|
* at b or c and no lively unfriendly stone at b, c or d.
|
|
*
|
|
*/
|
|
|
|
#define sufficient_influence(pos, apos, bpos) \
|
|
(ON_BOARD(bpos) && influence[bpos] > threshold[pos] - influence[apos])
|
|
|
|
static void
|
|
compute_primary_domains(int color, int domain[BOARDMAX],
|
|
int lively[BOARDMAX],
|
|
int false_margins[BOARDMAX],
|
|
int first_time)
|
|
{
|
|
int other = OTHER_COLOR(color);
|
|
int i, j, k;
|
|
int pos, pos2;
|
|
int own, enemy;
|
|
signed char threshold[BOARDMAX];
|
|
signed char influence[BOARDMAX];
|
|
int list[BOARDMAX];
|
|
int size = 0, lastchange = 0;
|
|
|
|
memset(threshold, 0, sizeof(threshold));
|
|
memset(influence, 0, sizeof(influence));
|
|
|
|
/* In the first pass we
|
|
* 1. Give influence to lively own stones and their neighbors.
|
|
* (Cases (1) and (2) above.)
|
|
* 2. Fill influence[] and threshold[] arrays with initial values.
|
|
*/
|
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
|
if (!ON_BOARD(pos))
|
|
continue;
|
|
|
|
if (lively[pos]) {
|
|
if (board[pos] == color) {
|
|
domain[pos] = 1; /* Case (1) above. */
|
|
influence[pos] = 1;
|
|
}
|
|
else
|
|
influence[pos] = -1;
|
|
continue;
|
|
}
|
|
|
|
own = enemy = 0;
|
|
for (k = 0; k < 4; k++) {
|
|
pos2 = pos + delta[k];
|
|
if (ON_BOARD(pos2) && lively[pos2]) {
|
|
if (board[pos2] == color)
|
|
own = 1;
|
|
else
|
|
enemy = 1;
|
|
}
|
|
}
|
|
|
|
if (own) {
|
|
/* To explain the asymmetry between the first time around
|
|
* this loop and subsequent ones, a false margin is adjacent
|
|
* to both B and W lively stones, so it's found on the first
|
|
* pass through the loop.
|
|
*/
|
|
if (first_time) {
|
|
if (board[pos] == EMPTY && (false_margin(pos, color, lively)
|
|
|| false_margin(pos, other, lively)))
|
|
false_margins[pos] = 1;
|
|
else {
|
|
domain[pos] = 1;
|
|
influence[pos] = 1;
|
|
}
|
|
}
|
|
else if (board[pos] != EMPTY || !false_margins[pos]) {
|
|
domain[pos] = 1;
|
|
influence[pos] = 1;
|
|
}
|
|
}
|
|
else
|
|
list[size++] = pos;
|
|
|
|
if (enemy) {
|
|
threshold[pos] = 1;
|
|
influence[pos]--;
|
|
}
|
|
else if (is_edge_vertex(pos))
|
|
influence[pos]--;
|
|
}
|
|
|
|
/* Now we loop over the board until no more vertices can be added to
|
|
* the domain through case (3) above.
|
|
*/
|
|
if (size) {
|
|
k = size;
|
|
while (1) {
|
|
if (!k)
|
|
k = size;
|
|
pos = list[--k];
|
|
|
|
/* Case (3) above. */
|
|
if (sufficient_influence(pos, SOUTH(pos), SE(pos))
|
|
|| sufficient_influence(pos, SOUTH(pos), SW(pos))
|
|
|| sufficient_influence(pos, EAST(pos), SE(pos))
|
|
|| sufficient_influence(pos, EAST(pos), NE(pos))
|
|
|| sufficient_influence(pos, WEST(pos), SW(pos))
|
|
|| sufficient_influence(pos, WEST(pos), NW(pos))
|
|
|| sufficient_influence(pos, NORTH(pos), NW(pos))
|
|
|| sufficient_influence(pos, NORTH(pos), NE(pos))) {
|
|
domain[pos] = 1;
|
|
influence[pos]++;
|
|
|
|
if (!--size)
|
|
break;
|
|
if (k < size)
|
|
list[k] = list[size];
|
|
else
|
|
k--;
|
|
lastchange = k;
|
|
}
|
|
else if (k == lastchange)
|
|
break; /* Looped the whole list and found nothing new */
|
|
}
|
|
}
|
|
|
|
if (0 && (debug & DEBUG_EYES)) {
|
|
start_draw_board();
|
|
for (i = 0; i < board_size; i++)
|
|
for (j = 0; j < board_size; j++) {
|
|
draw_color_char(i, j, domain[POS(i, j)] ? '1' : '0', GG_COLOR_BLACK);
|
|
}
|
|
end_draw_board();
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
count_neighbours(struct eye_data eyedata[BOARDMAX])
|
|
{
|
|
int pos;
|
|
int k;
|
|
|
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
|
if (!ON_BOARD(pos) || eyedata[pos].origin == NO_MOVE)
|
|
continue;
|
|
|
|
eyedata[pos].esize = eyedata[eyedata[pos].origin].esize;
|
|
eyedata[pos].msize = eyedata[eyedata[pos].origin].msize;
|
|
eyedata[pos].neighbors = 0;
|
|
eyedata[pos].marginal_neighbors = 0;
|
|
|
|
for (k = 0; k < 4; k++) {
|
|
int pos2 = pos + delta[k];
|
|
if (ON_BOARD(pos2) && eyedata[pos2].origin == eyedata[pos].origin) {
|
|
eyedata[pos].neighbors++;
|
|
if (eyedata[pos2].marginal)
|
|
eyedata[pos].marginal_neighbors++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
is_lively(int owl_call, int pos)
|
|
{
|
|
if (board[pos] == EMPTY)
|
|
return 0;
|
|
|
|
if (owl_call)
|
|
return owl_lively(pos);
|
|
else
|
|
return (!worm[pos].inessential
|
|
&& (worm[pos].attack_codes[0] == 0
|
|
|| worm[pos].defense_codes[0] != 0));
|
|
}
|
|
|
|
|
|
/* In the following situation, we do not wish the vertex at 'a'
|
|
* included in the O eye space:
|
|
*
|
|
* OOOOXX
|
|
* OXaX..
|
|
* ------
|
|
*
|
|
* This eyespace should parse as (X), not (X!). Thus the vertex
|
|
* should not be included in the eyespace if it is adjacent to
|
|
* an X stone which is alive, yet X cannot play safely at a.
|
|
* The function returns 1 if this situation is found at
|
|
* (pos) for color O.
|
|
*
|
|
* The condition above is true, curiously enough, also for the
|
|
* following case:
|
|
* A group has two eyes, one of size 1 and one which is critical 1/2.
|
|
* It also has to have less than 4 external liberties, since the
|
|
* reading has to be able to capture the group tactically. In that
|
|
* case, the eye of size one will be treated as a false marginal.
|
|
* Thus we have to exclude this case, which is done by requiring (pos)
|
|
* to be adjacent to both white and black stones. Since this test is
|
|
* least expensive, we start with it.
|
|
*
|
|
* As a second optimization we require that one of the other colored
|
|
* neighbors is not lively. This should cut down on the number of
|
|
* calls to attack() and safe_move().
|
|
*/
|
|
|
|
static int
|
|
false_margin(int pos, int color, int lively[BOARDMAX])
|
|
{
|
|
int other = OTHER_COLOR(color);
|
|
int neighbors = 0;
|
|
int k;
|
|
int all_lively;
|
|
int potential_false_margin;
|
|
|
|
/* Require neighbors of both colors. */
|
|
for (k = 0; k < 4; k++)
|
|
if (ON_BOARD(pos + delta[k]))
|
|
neighbors |= board[pos + delta[k]];
|
|
|
|
if (neighbors != (WHITE | BLACK))
|
|
return 0;
|
|
|
|
/* At least one opponent neighbor should be not lively. */
|
|
all_lively = 1;
|
|
for (k = 0; k < 4; k++)
|
|
if (board[pos + delta[k]] == other && !lively[pos + delta[k]])
|
|
all_lively = 0;
|
|
|
|
if (all_lively)
|
|
return 0;
|
|
|
|
potential_false_margin = 0;
|
|
for (k = 0; k < 4; k++) {
|
|
int apos = pos + delta[k];
|
|
if (board[apos] != other || !lively[apos])
|
|
continue;
|
|
|
|
if (stackp == 0 && worm[apos].attack_codes[0] == 0)
|
|
potential_false_margin = 1;
|
|
|
|
if (stackp > 0 && !attack(apos, NULL))
|
|
potential_false_margin = 1;
|
|
}
|
|
|
|
if (potential_false_margin && safe_move(pos, other) == 0) {
|
|
DEBUG(DEBUG_EYES, "False margin for %C at %1m.\n", color, pos);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* originate_eye(pos, pos, *esize, *msize, eye) creates an eyeshape
|
|
* with origin pos. esize and msize return the size and the number of
|
|
* marginal vertices. The repeated variables (pos) are due to the
|
|
* recursive definition of the function.
|
|
*/
|
|
static void
|
|
originate_eye(int origin, int pos,
|
|
int *esize, int *msize,
|
|
struct eye_data eye[BOARDMAX])
|
|
{
|
|
int k;
|
|
ASSERT_ON_BOARD1(origin);
|
|
ASSERT_ON_BOARD1(pos);
|
|
gg_assert(esize != NULL);
|
|
gg_assert(msize != NULL);
|
|
|
|
eye[pos].origin = origin;
|
|
(*esize)++;
|
|
if (eye[pos].marginal)
|
|
(*msize)++;
|
|
|
|
for (k = 0; k < 4; k++) {
|
|
int pos2 = pos + delta[k];
|
|
if (ON_BOARD(pos2)
|
|
&& eye[pos2].color == eye[pos].color
|
|
&& eye[pos2].origin == NO_MOVE
|
|
&& (!eye[pos2].marginal || !eye[pos].marginal))
|
|
originate_eye(origin, pos2, esize, msize, eye);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* propagate_eye(origin) copies the data at the (origin) to the
|
|
* rest of the eye (invariant fields only).
|
|
*/
|
|
|
|
void
|
|
propagate_eye(int origin, struct eye_data eye[BOARDMAX])
|
|
{
|
|
int pos;
|
|
|
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
|
|
if (ON_BOARD(pos) && eye[pos].origin == origin) {
|
|
eye[pos].color = eye[origin].color;
|
|
eye[pos].esize = eye[origin].esize;
|
|
eye[pos].msize = eye[origin].msize;
|
|
eye[pos].origin = eye[origin].origin;
|
|
eye[pos].value = eye[origin].value;
|
|
}
|
|
}
|
|
|
|
|
|
/* Find the dragon or dragons surrounding an eye space. Up to
|
|
* max_dragons dragons adjacent to the eye space are added to
|
|
* the dragon array, and the number of dragons found is returned.
|
|
*/
|
|
|
|
int
|
|
find_eye_dragons(int origin, struct eye_data eye[BOARDMAX], int eye_color,
|
|
int dragons[], int max_dragons)
|
|
{
|
|
int mx[BOARDMAX];
|
|
int num_dragons = 0;
|
|
int pos;
|
|
|
|
memset(mx, 0, sizeof(mx));
|
|
DEBUG(DEBUG_MISCELLANEOUS, "find_eye_dragons: %1m %C\n", origin, eye_color);
|
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
|
if (board[pos] == eye_color
|
|
&& mx[dragon[pos].origin] == 0
|
|
&& ((ON_BOARD(SOUTH(pos))
|
|
&& eye[SOUTH(pos)].origin == origin
|
|
&& !eye[SOUTH(pos)].marginal)
|
|
|| (ON_BOARD(WEST(pos))
|
|
&& eye[WEST(pos)].origin == origin
|
|
&& !eye[WEST(pos)].marginal)
|
|
|| (ON_BOARD(NORTH(pos))
|
|
&& eye[NORTH(pos)].origin == origin
|
|
&& !eye[NORTH(pos)].marginal)
|
|
|| (ON_BOARD(EAST(pos))
|
|
&& eye[EAST(pos)].origin == origin
|
|
&& !eye[EAST(pos)].marginal))) {
|
|
DEBUG(DEBUG_MISCELLANEOUS,
|
|
" dragon: %1m %1m\n", pos, dragon[pos].origin);
|
|
mx[dragon[pos].origin] = 1;
|
|
if (dragons != NULL && num_dragons < max_dragons)
|
|
dragons[num_dragons] = dragon[pos].origin;
|
|
num_dragons++;
|
|
}
|
|
}
|
|
|
|
return num_dragons;
|
|
}
|
|
|
|
/* Print debugging data for the eyeshape at (i,j). Useful with GDB.
|
|
*/
|
|
|
|
static void
|
|
print_eye(struct eye_data eye[BOARDMAX], struct half_eye_data heye[BOARDMAX],
|
|
int pos)
|
|
{
|
|
int m, n;
|
|
int pos2;
|
|
int mini, maxi;
|
|
int minj, maxj;
|
|
int origin = eye[pos].origin;
|
|
|
|
gprintf("Eyespace at %1m: color=%C, esize=%d, msize=%d\n",
|
|
pos, eye[pos].color, eye[pos].esize, eye[pos].msize);
|
|
|
|
for (pos2 = BOARDMIN; pos2 < BOARDMAX; pos2++) {
|
|
if (!ON_BOARD(pos2))
|
|
continue;
|
|
|
|
if (eye[pos2].origin != pos)
|
|
continue;
|
|
|
|
if (eye[pos2].marginal && IS_STONE(board[pos2]))
|
|
gprintf("%1m (X!)\n", pos2);
|
|
else if (is_halfeye(heye, pos2) && IS_STONE(board[pos2])) {
|
|
if (heye[pos2].value == 3.0)
|
|
gprintf("%1m (XH)\n", pos2);
|
|
else
|
|
gprintf("%1m (XH) (topological eye value = %f)\n", pos2,
|
|
heye[pos2].value);
|
|
}
|
|
else if (!eye[pos2].marginal && IS_STONE(board[pos2]))
|
|
gprintf("%1m (X)\n", pos2);
|
|
else if (eye[pos2].marginal && board[pos2] == EMPTY)
|
|
gprintf("%1m (!)\n", pos2);
|
|
else if (is_halfeye(heye, pos2) && board[pos2] == EMPTY) {
|
|
if (heye[pos2].value == 3.0)
|
|
gprintf("%1m (H)\n", pos2);
|
|
else
|
|
gprintf("%1m (H) (topological eye value = %f)\n", pos2,
|
|
heye[pos2].value);
|
|
}
|
|
else
|
|
gprintf("%1m\n", pos2);
|
|
}
|
|
gprintf("\n");
|
|
|
|
/* Determine the size of the eye. */
|
|
mini = board_size;
|
|
maxi = -1;
|
|
minj = board_size;
|
|
maxj = -1;
|
|
for (m = 0; m < board_size; m++)
|
|
for (n = 0; n < board_size; n++) {
|
|
if (eye[POS(m, n)].origin != origin)
|
|
continue;
|
|
|
|
if (m < mini) mini = m;
|
|
if (m > maxi) maxi = m;
|
|
if (n < minj) minj = n;
|
|
if (n > maxj) maxj = n;
|
|
}
|
|
|
|
/* Prints the eye shape. A half eye is shown by h, if empty or H, if an
|
|
* enemy is present. Note that each half eye has a marginal point which is
|
|
* not printed, so the representation here may have less points than the
|
|
* matching eye pattern in eyes.db. Printing a marginal for the half eye
|
|
* would be nice, but difficult to implement.
|
|
*/
|
|
for (m = mini; m <= maxi; m++) {
|
|
gprintf(""); /* Get the indentation right. */
|
|
for (n = minj; n <= maxj; n++) {
|
|
int pos2 = POS(m, n);
|
|
if (eye[pos2].origin == origin) {
|
|
if (board[pos2] == EMPTY) {
|
|
if (eye[pos2].marginal)
|
|
gprintf("%o!");
|
|
else if (is_halfeye(heye, pos2))
|
|
gprintf("%oh");
|
|
else
|
|
gprintf("%o.");
|
|
}
|
|
else if (is_halfeye(heye, pos2))
|
|
gprintf("%oH");
|
|
else
|
|
gprintf("%oX");
|
|
}
|
|
else
|
|
gprintf("%o ");
|
|
}
|
|
gprintf("\n");
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Given an eyespace with origin (pos), this function computes the
|
|
* minimum and maximum numbers of eyes the space can yield. If max and
|
|
* min are different, then vital points of attack and defense are also
|
|
* generated.
|
|
*
|
|
* If add_moves == 1, this function may add a move_reason for (color) at
|
|
* a vital point which is found by the function. If add_moves == 0,
|
|
* set color == EMPTY.
|
|
*/
|
|
|
|
void
|
|
compute_eyes(int pos, struct eyevalue *value,
|
|
int *attack_point, int *defense_point,
|
|
struct eye_data eye[BOARDMAX],
|
|
struct half_eye_data heye[BOARDMAX], int add_moves)
|
|
{
|
|
if (attack_point)
|
|
*attack_point = NO_MOVE;
|
|
if (defense_point)
|
|
*defense_point = NO_MOVE;
|
|
|
|
if (debug & DEBUG_EYES) {
|
|
print_eye(eye, heye, pos);
|
|
DEBUG(DEBUG_EYES, "\n");
|
|
}
|
|
|
|
/* Look up the eye space in the graphs database. */
|
|
if (read_eye(pos, attack_point, defense_point, value, eye, heye, add_moves))
|
|
return;
|
|
|
|
/* Ideally any eye space that hasn't been matched yet should be two
|
|
* secure eyes. Until the database becomes more complete we have
|
|
* some additional heuristics to guess the values of unknown
|
|
* eyespaces.
|
|
*/
|
|
if (eye[pos].esize - 2*eye[pos].msize > 3)
|
|
set_eyevalue(value, 2, 2, 2, 2);
|
|
else if (eye[pos].esize - 2*eye[pos].msize > 0)
|
|
set_eyevalue(value, 1, 1, 1, 1);
|
|
else
|
|
set_eyevalue(value, 0, 0, 0, 0);
|
|
}
|
|
|
|
|
|
/*
|
|
* This function works like compute_eyes(), except that it also gives
|
|
* a pessimistic view of the chances to make eyes. Since it is intended
|
|
* to be used from the owl code, the option to add move reasons has
|
|
* been removed.
|
|
*/
|
|
void
|
|
compute_eyes_pessimistic(int pos, struct eyevalue *value,
|
|
int *pessimistic_min,
|
|
int *attack_point, int *defense_point,
|
|
struct eye_data eye[BOARDMAX],
|
|
struct half_eye_data heye[BOARDMAX])
|
|
{
|
|
static int bulk_coefficients[5] = {-1, -1, 1, 4, 12};
|
|
|
|
int pos2;
|
|
int margins = 0;
|
|
int halfeyes = 0;
|
|
int margins_adjacent_to_margin = 0;
|
|
int effective_eyesize;
|
|
int bulk_score = 0;
|
|
signed char chainlinks[BOARDMAX];
|
|
|
|
/* Stones inside eyespace which do not coincide with a false eye or
|
|
* a halfeye.
|
|
*/
|
|
int interior_stones = 0;
|
|
|
|
memset(chainlinks, 0, BOARDMAX);
|
|
|
|
for (pos2 = BOARDMIN; pos2 < BOARDMAX; pos2++) {
|
|
int k;
|
|
|
|
if (!ON_BOARD(pos2) || eye[pos2].origin != pos)
|
|
continue;
|
|
|
|
if (eye[pos2].marginal || is_halfeye(heye, pos2)) {
|
|
margins++;
|
|
if (eye[pos2].marginal && eye[pos2].marginal_neighbors > 0)
|
|
margins_adjacent_to_margin++;
|
|
if (is_halfeye(heye, pos2))
|
|
halfeyes++;
|
|
}
|
|
else if (IS_STONE(board[pos2]))
|
|
interior_stones++;
|
|
|
|
bulk_score += bulk_coefficients[(int) eye[pos2].neighbors];
|
|
|
|
for (k = 0; k < 4; k++) {
|
|
int neighbor = pos2 + delta[k];
|
|
|
|
if (board[neighbor] == eye[pos].color) {
|
|
if (!chainlinks[neighbor]) {
|
|
bulk_score += 4;
|
|
mark_string(neighbor, chainlinks, 1);
|
|
}
|
|
}
|
|
else if (!ON_BOARD(neighbor))
|
|
bulk_score += 2;
|
|
}
|
|
}
|
|
|
|
/* This is a measure based on the simplified assumption that both
|
|
* players only cares about playing the marginal eye spaces. It is
|
|
* used later to guess the eye value for unidentified eye shapes.
|
|
*/
|
|
effective_eyesize = (eye[pos].esize + halfeyes - 2*margins
|
|
- margins_adjacent_to_margin);
|
|
|
|
if (attack_point)
|
|
*attack_point = NO_MOVE;
|
|
if (defense_point)
|
|
*defense_point = NO_MOVE;
|
|
|
|
if (debug & DEBUG_EYES) {
|
|
print_eye(eye, heye, pos);
|
|
DEBUG(DEBUG_EYES, "\n");
|
|
}
|
|
|
|
/* Look up the eye space in the graphs database. */
|
|
if (read_eye(pos, attack_point, defense_point, value,
|
|
eye, heye, 0)) {
|
|
*pessimistic_min = min_eyes(value) - margins;
|
|
|
|
/* A single point eye which is part of a ko can't be trusted. */
|
|
if (eye[pos].esize == 1
|
|
&& is_ko(pos, OTHER_COLOR(eye[pos].color), NULL))
|
|
*pessimistic_min = 0;
|
|
|
|
DEBUG(DEBUG_EYES, " graph matching - %s, pessimistic_min=%d\n",
|
|
eyevalue_to_string(value), *pessimistic_min);
|
|
}
|
|
|
|
/* Ideally any eye space that hasn't been matched yet should be two
|
|
* secure eyes. Until the database becomes more complete we have
|
|
* some additional heuristics to guess the values of unknown
|
|
* eyespaces.
|
|
*/
|
|
else {
|
|
guess_eye_space(pos, effective_eyesize, margins, bulk_score, eye,
|
|
value, pessimistic_min);
|
|
DEBUG(DEBUG_EYES, " guess_eye - %s, pessimistic_min=%d\n",
|
|
eyevalue_to_string(value), *pessimistic_min);
|
|
}
|
|
|
|
if (*pessimistic_min < 0) {
|
|
*pessimistic_min = 0;
|
|
DEBUG(DEBUG_EYES, " pessimistic min revised to 0\n");
|
|
}
|
|
|
|
/* An eyespace with at least two interior stones is assumed to be
|
|
* worth at least one eye, regardless of previous considerations.
|
|
*/
|
|
if (*pessimistic_min < 1 && interior_stones >= 2) {
|
|
*pessimistic_min = 1;
|
|
DEBUG(DEBUG_EYES, " pessimistic min revised to 1 (interior stones)\n");
|
|
}
|
|
|
|
if (attack_point
|
|
&& *attack_point == NO_MOVE
|
|
&& max_eyes(value) != *pessimistic_min) {
|
|
/* Find one marginal vertex and set as attack and defense point.
|
|
*
|
|
* We make some effort to find the best marginal vertex by giving
|
|
* priority to ones with more than one neighbor in the eyespace.
|
|
* We prefer non-halfeye margins and ones which are not self-atari
|
|
* for the opponent. Margins not on the edge are also favored.
|
|
*/
|
|
int best_attack_point = NO_MOVE;
|
|
int best_defense_point = NO_MOVE;
|
|
float score = 0.0;
|
|
|
|
for (pos2 = BOARDMIN; pos2 < BOARDMAX; pos2++) {
|
|
if (ON_BOARD(pos2) && eye[pos2].origin == pos) {
|
|
float this_score = 0.0;
|
|
int this_attack_point = NO_MOVE;
|
|
int this_defense_point = NO_MOVE;
|
|
if (eye[pos2].marginal && board[pos2] == EMPTY) {
|
|
this_score = eye[pos2].neighbors;
|
|
this_attack_point = pos2;
|
|
this_defense_point = pos2;
|
|
|
|
if (is_self_atari(pos2, OTHER_COLOR(eye[pos].color)))
|
|
this_score -= 0.5;
|
|
|
|
if (is_edge_vertex(pos2))
|
|
this_score -= 0.1;
|
|
}
|
|
else if (is_halfeye(heye, pos2)) {
|
|
this_score = 0.75;
|
|
this_defense_point = heye[pos2].defense_point[0];
|
|
this_attack_point = heye[pos2].attack_point[0];
|
|
}
|
|
else
|
|
continue;
|
|
|
|
if (gg_normalize_float2int(this_score, 0.01)
|
|
> gg_normalize_float2int(score, 0.01)) {
|
|
best_attack_point = this_attack_point;
|
|
best_defense_point = this_defense_point;
|
|
score = this_score;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (score > 0.0) {
|
|
if (defense_point)
|
|
*defense_point = best_defense_point;
|
|
if (attack_point)
|
|
*attack_point = best_attack_point;
|
|
}
|
|
}
|
|
|
|
if (defense_point && *defense_point != NO_MOVE) {
|
|
ASSERT_ON_BOARD1(*defense_point);
|
|
}
|
|
if (attack_point && *attack_point != NO_MOVE) {
|
|
ASSERT_ON_BOARD1(*attack_point);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
guess_eye_space(int pos, int effective_eyesize, int margins,
|
|
int bulk_score, struct eye_data eye[BOARDMAX],
|
|
struct eyevalue *value, int *pessimistic_min)
|
|
{
|
|
if (effective_eyesize > 3) {
|
|
set_eyevalue(value, 2, 2, 2, 2);
|
|
*pessimistic_min = 1;
|
|
|
|
if ((margins == 0 && effective_eyesize > 7)
|
|
|| (margins > 0 && effective_eyesize > 9)) {
|
|
int eyes = 2 + (effective_eyesize - 2 * (margins > 0) - 8) / 2;
|
|
int threshold = (4 * (eye[pos].esize - 2)
|
|
+ (effective_eyesize - 8) * (effective_eyesize - 9));
|
|
|
|
DEBUG(DEBUG_EYES, "size: %d(%d), threshold: %d, bulk score: %d\n",
|
|
eye[pos].esize, effective_eyesize, threshold, bulk_score);
|
|
|
|
if (bulk_score > threshold && effective_eyesize < 15)
|
|
eyes = gg_max(2, eyes - ((bulk_score - threshold) / eye[pos].esize));
|
|
|
|
if (bulk_score < threshold + eye[pos].esize || effective_eyesize >= 15)
|
|
*pessimistic_min = eyes;
|
|
|
|
set_eyevalue(value, eyes, eyes, eyes, eyes);
|
|
}
|
|
}
|
|
else if (effective_eyesize > 0) {
|
|
set_eyevalue(value, 1, 1, 1, 1);
|
|
if (margins > 0)
|
|
*pessimistic_min = 0;
|
|
else
|
|
*pessimistic_min = 1;
|
|
}
|
|
else {
|
|
if (eye[pos].esize - margins > 2)
|
|
set_eyevalue(value, 0, 0, 1, 1);
|
|
else
|
|
set_eyevalue(value, 0, 0, 0, 0);
|
|
*pessimistic_min = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/* This function does some minor reading to improve the results of
|
|
* recognize_eye(). Currently, it has two duties. One is to read
|
|
* positions like this:
|
|
*
|
|
* .XXXX| with half eye with proper eye
|
|
* XXOOO|
|
|
* XO.O.| . (1 eye) . (2 eyes)
|
|
* XXOa.| !.. .*
|
|
* -----+
|
|
*
|
|
* recognize_eye() sees the eyespace of the white dragon as shown
|
|
* (there's a half eye at a and it is considered the same as '!.' by
|
|
* the optics code). Normally, that eye shape gives only one secure
|
|
* eye, and owl thinks that the white dragon is dead unconditionally.
|
|
* This function tries to turn such ko-dependent half eyes into proper
|
|
* eyes and chooses the best alternative. Note that we don't have any
|
|
* attack/defense codes here, since owl will determine them itself.
|
|
*
|
|
* Another one is related to some cases when replacing half eyes with
|
|
* '!.' doesn't work. E.g. consider this eye (optics:328):
|
|
*
|
|
* XXXOO eye graph is 310:
|
|
* X..X.
|
|
* XOXX. !.! (second '!' is due to the halfeye)
|
|
* OXO..
|
|
* O.O..
|
|
*
|
|
* When this function detects such a half eye that can be attacked
|
|
* and/or defended inside its eyespace, it tries to turn it into a
|
|
* proper eye and see what happens. In case it gives an improvement
|
|
* for attacker and/or defender, the function keeps new result but
|
|
* only if new vital points are also vital points for the half eye.
|
|
* The heuristics used here might need improvements since they are
|
|
* based on a single game position.
|
|
*
|
|
* If add_moves != 0, this function may add move reasons for (color)
|
|
* at the vital points which are found by recognize_eye(). If add_moves
|
|
* == 0, set color to be EMPTY.
|
|
*/
|
|
static int
|
|
read_eye(int pos, int *attack_point, int *defense_point,
|
|
struct eyevalue *value, struct eye_data eye[BOARDMAX],
|
|
struct half_eye_data heye[BOARDMAX],
|
|
int add_moves)
|
|
{
|
|
int eye_color;
|
|
int k;
|
|
int pos2;
|
|
int combination_halfeye = NO_MOVE;
|
|
int combination_attack = NO_MOVE;
|
|
int combination_defense = NO_MOVE;
|
|
int num_ko_halfeyes = 0;
|
|
int ko_halfeye = NO_MOVE;
|
|
struct vital_points vp;
|
|
struct vital_points ko_vp;
|
|
struct vital_points *best_vp = &vp;
|
|
|
|
eye_color = recognize_eye(pos, attack_point, defense_point, value,
|
|
eye, heye, &vp);
|
|
if (!eye_color)
|
|
return 0;
|
|
|
|
/* Find ko half eyes and "combination" half eyes if any. */
|
|
for (pos2 = BOARDMIN; pos2 < BOARDMAX; pos2++) {
|
|
if (ON_BOARD(pos2)
|
|
&& eye[pos2].origin == pos
|
|
&& heye[pos2].type == HALF_EYE) {
|
|
if (combination_halfeye == NO_MOVE) {
|
|
int apos = NO_MOVE;
|
|
int dpos = NO_MOVE;
|
|
|
|
for (k = 0; k < heye[pos2].num_attacks; k++) {
|
|
if (eye[heye[pos2].attack_point[k]].origin == pos) {
|
|
apos = heye[pos2].attack_point[k];
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (k = 0; k < heye[pos2].num_defenses; k++) {
|
|
if (eye[heye[pos2].defense_point[k]].origin == pos) {
|
|
dpos = heye[pos2].defense_point[k];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (apos || dpos) {
|
|
combination_halfeye = pos2;
|
|
combination_attack = apos;
|
|
combination_defense = dpos;
|
|
}
|
|
}
|
|
|
|
if (heye[pos2].value < 3.0) {
|
|
num_ko_halfeyes++;
|
|
ko_halfeye = pos2;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* In case we have a "combination" half eye, turn it into a proper eye
|
|
* vertex for a while and see what happens.
|
|
*/
|
|
if (combination_halfeye != NO_MOVE) {
|
|
int result;
|
|
int apos = NO_MOVE;
|
|
int dpos = NO_MOVE;
|
|
struct eyevalue combination_value;
|
|
struct vital_points combination_vp;
|
|
|
|
heye[combination_halfeye].type = 0;
|
|
result = recognize_eye(pos, &apos, &dpos, &combination_value, eye,
|
|
heye, &combination_vp);
|
|
heye[combination_halfeye].type = HALF_EYE;
|
|
|
|
if (result) {
|
|
if (combination_attack
|
|
&& min_eyes(value) > min_eyes(&combination_value)) {
|
|
/* FIXME: I'm not sure we can ever get here. */
|
|
for (k = 0; k < combination_vp.num_attacks; k++) {
|
|
if (combination_vp.attacks[k] == combination_attack) {
|
|
value->a = combination_value.a;
|
|
value->b = combination_value.b;
|
|
*attack_point = apos;
|
|
best_vp->num_attacks = 1;
|
|
best_vp->attacks[0] = combination_attack;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (combination_defense
|
|
&& max_eyes(value) < max_eyes(&combination_value)) {
|
|
/* Turning the half eye into a proper eye gives an improvement.
|
|
* However, we can only accept this result if there is a vital
|
|
* point that defends both the half eye and the whole eyespace.
|
|
*/
|
|
for (k = 0; k < combination_vp.num_defenses; k++) {
|
|
if (combination_vp.defenses[k] == combination_defense) {
|
|
value->c = combination_value.c;
|
|
value->d = combination_value.d;
|
|
*defense_point = dpos;
|
|
best_vp->num_defenses = 1;
|
|
best_vp->defenses[0] = combination_defense;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (min_eyes(value) != max_eyes(value)) {
|
|
ASSERT1(combination_attack || combination_defense, combination_halfeye);
|
|
if (*attack_point == NO_MOVE) {
|
|
*attack_point = combination_attack;
|
|
if (*attack_point == NO_MOVE)
|
|
*attack_point = combination_defense;
|
|
}
|
|
|
|
if (*defense_point == NO_MOVE) {
|
|
*defense_point = combination_defense;
|
|
if (*defense_point == NO_MOVE)
|
|
*defense_point = combination_defense;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* The same with ko half eye (we cannot win two kos at once, therefore we
|
|
* give up if there is more than one ko half eye).
|
|
*/
|
|
if (num_ko_halfeyes == 1) {
|
|
int result;
|
|
int apos = NO_MOVE;
|
|
int dpos = NO_MOVE;
|
|
struct eyevalue ko_value;
|
|
|
|
heye[ko_halfeye].type = 0;
|
|
result = recognize_eye(pos, &apos, &dpos, &ko_value, eye,
|
|
heye, &ko_vp);
|
|
heye[ko_halfeye].type = HALF_EYE;
|
|
|
|
if (result && max_eyes(value) < max_eyes(&ko_value)) {
|
|
/* It is worthy to win the ko. */
|
|
*value = ko_value;
|
|
*attack_point = apos;
|
|
*defense_point = dpos;
|
|
best_vp = &ko_vp;
|
|
}
|
|
}
|
|
|
|
if (add_moves) {
|
|
struct vital_eye_points *vital;
|
|
if (eye_color == WHITE)
|
|
vital = white_vital_points;
|
|
else
|
|
vital = black_vital_points;
|
|
for (k = 0; k < best_vp->num_defenses && k < MAX_EYE_ATTACKS; k++)
|
|
vital[pos].defense_points[k] = best_vp->defenses[k];
|
|
for (k = 0; k < best_vp->num_attacks && k < MAX_EYE_ATTACKS; k++)
|
|
vital[pos].attack_points[k] = best_vp->attacks[k];
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* recognize_eye(pos, *attack_point, *defense_point, *max, *min, eye_data,
|
|
* half_eye_data, color, vp), where pos is the origin of an eyespace, returns
|
|
* owner of eye (his color) if there is a pattern in eyes.db matching the
|
|
* eyespace, or 0 if no match is found. If there is a key point for attack,
|
|
* (*attack_point) is set to its location, or NO_MOVE if there is none.
|
|
* Similarly (*defense_point) is the location of a vital defense point.
|
|
* *value is set according to the pattern found. Vital attack/defense points
|
|
* exist if and only if min_eyes(value) != max_eyes(value).
|
|
*/
|
|
|
|
static int
|
|
recognize_eye(int pos, int *attack_point, int *defense_point,
|
|
struct eyevalue *value,
|
|
struct eye_data eye[BOARDMAX],
|
|
struct half_eye_data heye[BOARDMAX],
|
|
struct vital_points *vp)
|
|
{
|
|
int pos2;
|
|
int eye_color;
|
|
int eye_size = 0;
|
|
int num_marginals = 0;
|
|
int vpos[MAXEYE];
|
|
signed char marginal[MAXEYE], edge[MAXEYE], neighbors[MAXEYE];
|
|
int graph;
|
|
int map[MAXEYE];
|
|
int best_score;
|
|
int r;
|
|
|
|
gg_assert(attack_point != NULL);
|
|
gg_assert(defense_point != NULL);
|
|
|
|
/* Set `eye_color' to the owner of the eye. */
|
|
eye_color = eye[pos].color;
|
|
|
|
if (eye[pos].esize-eye[pos].msize > 8)
|
|
return 0;
|
|
|
|
if (eye[pos].msize > MAXEYE)
|
|
return 0;
|
|
|
|
/* Create list of eye vertices */
|
|
for (pos2 = BOARDMIN; pos2 < BOARDMAX; pos2++) {
|
|
if (!ON_BOARD(pos2))
|
|
continue;
|
|
if (eye[pos2].origin == pos) {
|
|
vpos[eye_size] = pos2;
|
|
marginal[eye_size] = eye[pos2].marginal;
|
|
if (marginal[eye_size])
|
|
num_marginals++;
|
|
neighbors[eye_size] = eye[pos2].neighbors;
|
|
if (0) {
|
|
if (marginal[eye_size])
|
|
TRACE("(%1m)", vpos[eye_size]);
|
|
else
|
|
TRACE(" %1m ", vpos[eye_size]);
|
|
TRACE("\n");
|
|
}
|
|
|
|
if (is_corner_vertex(pos2))
|
|
edge[eye_size] = 2;
|
|
else if (is_edge_vertex(pos2))
|
|
edge[eye_size] = 1;
|
|
else
|
|
edge[eye_size] = 0;
|
|
|
|
if (is_halfeye(heye, pos2)) {
|
|
neighbors[eye_size]++; /* Increase neighbors of half eye. */
|
|
eye_size++;
|
|
/* Use a virtual marginal vertex for mapping purposes. We set it
|
|
* to be at NO_MOVE so it won't accidentally count as a
|
|
* neighbor for another vertex. Note that the half eye precedes
|
|
* the virtual marginal vertex in the list.
|
|
*/
|
|
vpos[eye_size] = NO_MOVE;
|
|
marginal[eye_size] = 1;
|
|
num_marginals++;
|
|
edge[eye_size] = 0;
|
|
neighbors[eye_size] = 1;
|
|
}
|
|
|
|
eye_size++;
|
|
}
|
|
}
|
|
|
|
/* We attempt to construct a map from the graph to the eyespace
|
|
* preserving the adjacency structure. If this can be done, we've
|
|
* identified the eyeshape.
|
|
*/
|
|
|
|
for (graph = 0; graphs[graph].vertex != NULL; graph++) {
|
|
int q;
|
|
|
|
if (graphs[graph].esize != eye_size
|
|
|| graphs[graph].msize != num_marginals)
|
|
continue;
|
|
|
|
reset_map(eye_size);
|
|
q = 0;
|
|
first_map(&map[0]);
|
|
|
|
while (1) {
|
|
struct eye_vertex *gv = &graphs[graph].vertex[q];
|
|
int mv = map[q];
|
|
int ok = 1;
|
|
|
|
if (0)
|
|
TRACE("q=%d: %d %d %d %d %d %d\n",
|
|
q, map[0], map[1], map[2], map[3], map[4], map[5]);
|
|
|
|
if (neighbors[mv] != gv->neighbors
|
|
|| marginal[mv] != gv->marginal
|
|
|| edge[mv] < gv->edge)
|
|
ok = 0;
|
|
|
|
if (ok) {
|
|
if (IS_STONE(board[vpos[mv]])) {
|
|
if (!(gv->flags & CAN_CONTAIN_STONE))
|
|
ok = 0;
|
|
}
|
|
/* Virtual half eye marginals also fall here since they are off
|
|
* board.
|
|
*/
|
|
else if (!(gv->flags & CAN_BE_EMPTY))
|
|
ok = 0;
|
|
}
|
|
|
|
if (ok) {
|
|
int k;
|
|
|
|
for (k = 0; k < gv->neighbors; k++) {
|
|
if (gv->n[k] < q) {
|
|
int mn = map[gv->n[k]];
|
|
|
|
/* Two eye vertices are neighbours if they are adjacent on the
|
|
* board or one of them is a half eye and the other is its
|
|
* virtual marginal vertex (and follows it in vpos[] array).
|
|
*/
|
|
if (vpos[mv] != SOUTH(vpos[mn])
|
|
&& vpos[mv] != WEST(vpos[mn])
|
|
&& vpos[mv] != NORTH(vpos[mn])
|
|
&& vpos[mv] != EAST(vpos[mn])
|
|
&& (mv != mn - 1
|
|
|| vpos[mv] == NO_MOVE
|
|
|| heye[vpos[mv]].type != HALF_EYE)
|
|
&& (mn != mv - 1
|
|
|| vpos[mn] == NO_MOVE
|
|
|| heye[vpos[mn]].type != HALF_EYE)) {
|
|
ok = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!ok) {
|
|
if (!next_map(&q, map))
|
|
break;
|
|
|
|
if (0)
|
|
gprintf(" q=%d, esize=%d: %d %d %d %d %d\n",
|
|
q, eye_size,
|
|
map[0], map[1], map[2], map[3], map[4]);
|
|
}
|
|
else {
|
|
q++;
|
|
if (q == eye_size)
|
|
break; /* A match! */
|
|
|
|
first_map(&map[q]);
|
|
}
|
|
}
|
|
|
|
if (q == eye_size) {
|
|
/* We have found a match! Now sort out the vital moves. */
|
|
*value = graphs[graph].value;
|
|
vp->num_attacks = 0;
|
|
vp->num_defenses = 0;
|
|
|
|
if (eye_move_urgency(value) > 0) {
|
|
/* Collect all attack and defense points in the pattern. */
|
|
int k;
|
|
|
|
for (k = 0; k < eye_size; k++) {
|
|
struct eye_vertex *ev = &graphs[graph].vertex[k];
|
|
|
|
if (ev->flags & EYE_ATTACK_POINT) {
|
|
/* Check for a marginal vertex matching a half eye virtual
|
|
* marginal. This is the case if a half eye preceeds the
|
|
* current vertex in the list.
|
|
*/
|
|
if (ev->marginal
|
|
&& map[k] > 0
|
|
&& vpos[map[k] - 1] != NO_MOVE
|
|
&& is_halfeye(heye, vpos[map[k] - 1])) {
|
|
/* Add all diagonals as vital. */
|
|
int ix;
|
|
struct half_eye_data *he = &heye[vpos[map[k] - 1]];
|
|
|
|
for (ix = 0; ix < he->num_attacks; ix++)
|
|
vp->attacks[vp->num_attacks++] = he->attack_point[ix];
|
|
}
|
|
else
|
|
vp->attacks[vp->num_attacks++] = vpos[map[k]];
|
|
}
|
|
|
|
if (ev->flags & EYE_DEFENSE_POINT) {
|
|
/* Check for a half eye virtual marginal vertex. */
|
|
if (ev->marginal
|
|
&& map[k] > 0
|
|
&& vpos[map[k] - 1] != NO_MOVE
|
|
&& is_halfeye(heye, vpos[map[k] - 1])) {
|
|
/* Add all diagonals as vital. */
|
|
int ix;
|
|
struct half_eye_data *he = &heye[vpos[map[k] - 1]];
|
|
|
|
for (ix = 0; ix < he->num_defenses; ix++)
|
|
vp->defenses[vp->num_defenses++] = he->defense_point[ix];
|
|
}
|
|
else
|
|
vp->defenses[vp->num_defenses++] = vpos[map[k]];
|
|
}
|
|
}
|
|
|
|
gg_assert(vp->num_attacks > 0 && vp->num_defenses > 0);
|
|
|
|
/* We now have all vital attack and defense points listed but
|
|
* we are also expected to single out of one of each to return
|
|
* in *attack_point and *defense_point. Since sometimes those
|
|
* are the only vital points considered, we want to choose the
|
|
* best ones, in the sense that they minimize the risk for
|
|
* error in the eye space analysis.
|
|
*
|
|
* One example is this position
|
|
*
|
|
* |..XXXX
|
|
* |XXX..X
|
|
* |..!O.X
|
|
* |OO.O.X
|
|
* |.O.!XX
|
|
* +------
|
|
*
|
|
* where O has an eyespace of the !..! type. The graph
|
|
* matching finds that both marginal vertices are vital points
|
|
* but here the one at 3-3 fails to defend. (For attack both
|
|
* points work but the 3-3 one is still worse since it leaves
|
|
* a ko threat.)
|
|
*
|
|
* In order to differentiate between the marginal points we
|
|
* count the number of straight and diagonal neighbors within
|
|
* the eye space. In the example above both have one straight
|
|
* neighbor each but the edge margin wins because it also has
|
|
* a diagonal margin.
|
|
*/
|
|
|
|
best_score = -10;
|
|
for (k = 0; k < vp->num_attacks; k++) {
|
|
int apos = vp->attacks[k];
|
|
int score = 0;
|
|
for (r = 0; r < 8; r++)
|
|
if (ON_BOARD(apos + delta[r])
|
|
&& eye[apos + delta[r]].color == eye[pos].color
|
|
&& !eye[apos + delta[r]].marginal) {
|
|
score++;
|
|
if (r < 4) {
|
|
score++;
|
|
if (board[apos + delta[r]] != EMPTY)
|
|
score++;
|
|
}
|
|
}
|
|
|
|
/* If a vital point is not adjacent to any point in the eye
|
|
* space, it must be a move to capture or defend a string
|
|
* related to a halfeye, e.g. the move * in this position,
|
|
*
|
|
* ......|
|
|
* .XXXX.|
|
|
* .X.O..|
|
|
* .XO.OO|
|
|
* .*XO..|
|
|
* ------+
|
|
*
|
|
* Playing this is probably a good idea.
|
|
*/
|
|
if (score == 0)
|
|
score += 2;
|
|
|
|
if (0)
|
|
gprintf("attack point %1m score %d\n", apos, score);
|
|
|
|
if (score > best_score) {
|
|
*attack_point = apos;
|
|
best_score = score;
|
|
}
|
|
}
|
|
|
|
best_score = -10;
|
|
for (k = 0; k < vp->num_defenses; k++) {
|
|
int dpos = vp->defenses[k];
|
|
int score = 0;
|
|
for (r = 0; r < 8; r++)
|
|
if (ON_BOARD(dpos + delta[r])
|
|
&& eye[dpos + delta[r]].color == eye[pos].color
|
|
&& !eye[dpos + delta[r]].marginal) {
|
|
score++;
|
|
if (r < 4) {
|
|
score++;
|
|
if (board[dpos + delta[r]] != EMPTY)
|
|
score++;
|
|
}
|
|
}
|
|
|
|
/* If possible, choose a non-sacrificial defense point.
|
|
* Compare white T8 and T6 in lazarus:21.
|
|
*/
|
|
if (safe_move(dpos, eye_color) != WIN)
|
|
score -= 5;
|
|
|
|
/* See comment to the same code for attack points. */
|
|
if (score == 0)
|
|
score += 2;
|
|
|
|
if (0)
|
|
gprintf("defense point %1m score %d\n", dpos, score);
|
|
|
|
if (score > best_score) {
|
|
*defense_point = dpos;
|
|
best_score = score;
|
|
}
|
|
}
|
|
|
|
DEBUG(DEBUG_EYES, " vital points: %1m (attack) %1m (defense)\n",
|
|
*attack_point, *defense_point);
|
|
DEBUG(DEBUG_EYES, " pattern matched: %d\n", graphs[graph].patnum);
|
|
|
|
}
|
|
TRACE("eye space at %1m of type %d\n", pos, graphs[graph].patnum);
|
|
|
|
return eye_color;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* a MAP is a map of the integers 0,1,2, ... ,q into
|
|
* 0,1, ... , esize-1 where q < esize. This determines a
|
|
* bijection of the first q+1 elements of the graph into the
|
|
* eyespace. The following three functions work with maps.
|
|
*/
|
|
|
|
/* Reset internal data structure used by first_map() and
|
|
* next_map() functions.
|
|
*/
|
|
static void
|
|
reset_map(int size)
|
|
{
|
|
map_size = size;
|
|
memset(used_index, 0, size * sizeof(used_index[0]));
|
|
}
|
|
|
|
|
|
/* The function first_map finds the smallest valid
|
|
* value of a map element.
|
|
*/
|
|
static void
|
|
first_map(int *map_value)
|
|
{
|
|
int k = 0;
|
|
|
|
while (used_index[k])
|
|
k++;
|
|
|
|
used_index[k] = 1;
|
|
*map_value = k;
|
|
}
|
|
|
|
|
|
/* The function next_map produces the next map in lexicographical
|
|
* order. If no next map can be found, q is decremented, then we
|
|
* try again. If the entire map is lexicographically last, the
|
|
* function returns false.
|
|
*/
|
|
static int
|
|
next_map(int *q, int map[MAXEYE])
|
|
{
|
|
int k;
|
|
|
|
do {
|
|
used_index[map[*q]] = 0;
|
|
for (k = map[*q]; ++k < map_size;) {
|
|
if (!used_index[k]) {
|
|
used_index[k] = 1;
|
|
map[*q] = k;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
(*q)--;
|
|
} while (*q >= 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* add_false_eye() turns a proper eyespace into a margin. */
|
|
|
|
static void
|
|
add_false_eye(int pos, struct eye_data eye[BOARDMAX],
|
|
struct half_eye_data heye[BOARDMAX])
|
|
{
|
|
int k;
|
|
ASSERT1(heye[pos].type == FALSE_EYE, pos);
|
|
DEBUG(DEBUG_EYES, "false eye found at %1m\n", pos);
|
|
|
|
if (eye[pos].color == GRAY || eye[pos].marginal != 0)
|
|
return;
|
|
|
|
eye[pos].marginal = 1;
|
|
eye[eye[pos].origin].msize++;
|
|
for (k = 0; k < 4; k++)
|
|
if (ON_BOARD(pos + delta[k])
|
|
&& eye[pos + delta[k]].origin == eye[pos].origin)
|
|
eye[pos + delta[k]].marginal_neighbors++;
|
|
propagate_eye(eye[pos].origin, eye);
|
|
}
|
|
|
|
|
|
/* These functions are used from constraints to identify eye spaces,
|
|
* primarily for late endgame moves.
|
|
*/
|
|
int
|
|
is_eye_space(int pos)
|
|
{
|
|
return (white_eye[pos].color == WHITE
|
|
|| black_eye[pos].color == BLACK);
|
|
}
|
|
|
|
int
|
|
is_proper_eye_space(int pos)
|
|
{
|
|
return ((white_eye[pos].color == WHITE && !white_eye[pos].marginal)
|
|
|| (black_eye[pos].color == BLACK && !black_eye[pos].marginal));
|
|
}
|
|
|
|
/* Return the maximum number of eyes that can be obtained from the
|
|
* eyespace at (i, j). This is most useful in order to determine
|
|
* whether the eyespace can be assumed to produce any territory at
|
|
* all.
|
|
*/
|
|
int
|
|
max_eye_value(int pos)
|
|
{
|
|
int max_white = 0;
|
|
int max_black = 0;
|
|
|
|
if (white_eye[pos].color == WHITE)
|
|
max_white = max_eyes(&white_eye[pos].value);
|
|
|
|
if (black_eye[pos].color == BLACK)
|
|
max_black = max_eyes(&black_eye[pos].value);
|
|
|
|
return gg_max(max_white, max_black);
|
|
}
|
|
|
|
int
|
|
is_marginal_eye_space(int pos)
|
|
{
|
|
return (white_eye[pos].marginal || black_eye[pos].marginal);
|
|
}
|
|
|
|
int
|
|
is_halfeye(struct half_eye_data heye[BOARDMAX], int pos)
|
|
{
|
|
return heye[pos].type == HALF_EYE;
|
|
}
|
|
|
|
int
|
|
is_false_eye(struct half_eye_data heye[BOARDMAX], int pos)
|
|
{
|
|
return heye[pos].type == FALSE_EYE;
|
|
}
|
|
|
|
|
|
/* Find topological half eyes and false eyes by analyzing the
|
|
* diagonal intersections, as described in the Texinfo
|
|
* documentation (Eyes/Eye Topology).
|
|
*/
|
|
void
|
|
find_half_and_false_eyes(int color, struct eye_data eye[BOARDMAX],
|
|
struct half_eye_data heye[BOARDMAX],
|
|
int find_mask[BOARDMAX])
|
|
{
|
|
int eye_color = color;
|
|
int pos;
|
|
float sum;
|
|
|
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
|
/* skip eyespaces which owl doesn't want to be searched */
|
|
if (!ON_BOARD(pos) || (find_mask && find_mask[eye[pos].origin] <= 1))
|
|
continue;
|
|
|
|
/* skip every vertex which can't be a false or half eye */
|
|
if (eye[pos].color != eye_color
|
|
|| eye[pos].marginal
|
|
|| eye[pos].neighbors > 1)
|
|
continue;
|
|
|
|
sum = topological_eye(pos, color, eye, heye);
|
|
if (sum >= 4.0) {
|
|
/* false eye */
|
|
heye[pos].type = FALSE_EYE;
|
|
if (eye[pos].esize == 1
|
|
|| is_legal(pos, OTHER_COLOR(color))
|
|
|| board[pos] == OTHER_COLOR(color))
|
|
add_false_eye(pos, eye, heye);
|
|
}
|
|
else if (sum > 2.0) {
|
|
/* half eye */
|
|
heye[pos].type = HALF_EYE;
|
|
ASSERT1(heye[pos].num_attacks > 0, pos);
|
|
ASSERT_ON_BOARD1(heye[pos].attack_point[0]);
|
|
ASSERT1(heye[pos].num_defenses > 0, pos);
|
|
ASSERT_ON_BOARD1(heye[pos].defense_point[0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* See Texinfo documentation (Eyes:Eye Topology). Returns:
|
|
* - 2 or less if (pos) is a proper eye for (color);
|
|
* - between 2 and 3 if the eye can be made false only by ko
|
|
* - 3 if (pos) is a half eye;
|
|
* - between 3 and 4 if the eye can be made real only by ko
|
|
* - 4 or more if (pos) is a false eye.
|
|
*
|
|
* Attack and defense points for control of the diagonals are stored
|
|
* in the heye[] array.
|
|
*
|
|
* my_eye is the eye space information with respect to (color).
|
|
*/
|
|
|
|
static float
|
|
topological_eye(int pos, int color,
|
|
struct eye_data my_eye[BOARDMAX],
|
|
struct half_eye_data heye[BOARDMAX])
|
|
{
|
|
float sum = 0.0;
|
|
float val;
|
|
int num_attacks = 0;
|
|
int num_defenses = 0;
|
|
int attack_values[4];
|
|
int defense_values[4];
|
|
int k;
|
|
int r;
|
|
int attack_point;
|
|
int defense_point;
|
|
int attack_value;
|
|
int defense_value;
|
|
|
|
memset(attack_values, 0, sizeof(attack_values));
|
|
memset(defense_values, 0, sizeof(defense_values));
|
|
|
|
/* Loop over the diagonal directions. */
|
|
for (k = 4; k < 8; k++) {
|
|
int diag = pos + delta[k];
|
|
val = evaluate_diagonal_intersection(I(pos) + deltai[k],
|
|
J(pos) + deltaj[k], color,
|
|
&attack_point, &defense_point,
|
|
my_eye);
|
|
|
|
/*
|
|
* Eyespaces with cutting points are problematic. In this position
|
|
*
|
|
* .....XXXXX
|
|
* XXXXX.OO.X
|
|
* X.OOOO.O.X
|
|
* X.O.XXXO.X
|
|
* ----------
|
|
*
|
|
* the eyespace will be .XXX. which evaluates to two eyes (seki)
|
|
* unless countermeasures are taken.
|
|
*
|
|
* This can be worked around in the topological analysis by
|
|
* sometimes setting the diagonal value to 2.0 for vertices inside
|
|
* the eyespace which are occupied by opponent stones. More
|
|
* precisely all of the following conditions must hold:
|
|
*
|
|
* a) The value is not already 2.0.
|
|
* a) The (potential) eyepoint is empty.
|
|
* b) The diagonal is occupied by an opponent string,
|
|
* c) which is also adjacent to the (potential) eye and
|
|
* d) at least three stones long.
|
|
* e) The (potential) eye is not on the edge (to steer clear of all the
|
|
* hairy cases that are handled by eyes.db anyway).
|
|
* f) At least two own strings are adjacent to the (potential) eye.
|
|
* g) At least one of the own strings adjacent to the (potential) eye has
|
|
* only one liberty which is an eye space and not decided false, yet.
|
|
*
|
|
* With this revision the eyespace above becomes .XXXh or
|
|
* equivalently .XXX.! which is almost evaluated correctly, eye
|
|
* value 0122 instead of the correct 1122. Compared to the
|
|
* previous value 2222 it's a major improvement.
|
|
*
|
|
* FIXME: This approach has a number of shortcomings.
|
|
*
|
|
* 1. d) is kind of arbitrary and there may be exceptional
|
|
* cases.
|
|
*
|
|
* 2. This diagonal value modification should not apply to
|
|
* two diagonals of the same strings inside the eyespace.
|
|
* E.g. if we have a partial eyespace looking like
|
|
*
|
|
* .OOO.
|
|
* OO.OO
|
|
* OXXXO
|
|
*
|
|
* it doesn't make sense to mark the middle vertex as a
|
|
* false eye. Possibly this doesn't make any difference
|
|
* in practice but it's at the very least confusing.
|
|
*
|
|
* 3. Actually it doesn't make sense to mark vertices as
|
|
* false otherwise either due to these revisions (half
|
|
* eyes make good sense though) as can be seen if a
|
|
* stone is added to the initial diagram,
|
|
*
|
|
* .....XXXXX
|
|
* XXXXXXOO.X
|
|
* X.OOOO.O.X
|
|
* X.O.XXXO.X
|
|
* ----------
|
|
*
|
|
* Now the eyespace instead becomes .XXX! which has the
|
|
* eye value 0011 but if X tries to attack the eye O
|
|
* suddenly gets two solid eyes!
|
|
*
|
|
* The correct analysis would be to remove the vertex
|
|
* from the eyespace rather than turning it into a false
|
|
* eye. Then we would have the eyespace .XXX which is
|
|
* correctly evaluated to one eye (eye value 1112).
|
|
*
|
|
* The problem with this is that removing eye points is
|
|
* messy. It can surely be done but currently there is
|
|
* no support in the code for doing that. It has existed
|
|
* at an earlier time but was removed because the
|
|
* implementation was not robust enough and there was no
|
|
* longer any apparent need for it. To correct this
|
|
* problem is sufficient reason to reimplement that
|
|
* functionality.
|
|
*
|
|
* 4. The test of condition g) has a result which
|
|
* potentially depends on the ordering of the eyespaces
|
|
* and thus presumably on the orientation of the board.
|
|
* It might make more sense to examine whether the
|
|
* string neighbors more than one empty vertex in the
|
|
* same eyespace.
|
|
*/
|
|
if (val < 2.0 && board[pos] == EMPTY && board[diag] == OTHER_COLOR(color)
|
|
&& !is_edge_vertex(pos) && neighbor_of_string(pos, diag)
|
|
&& countstones(diag) >= 3) {
|
|
int strings[3];
|
|
int string_count;
|
|
int s;
|
|
string_count = 0;
|
|
for (r = 0; r < 4; r++) {
|
|
int str;
|
|
str = pos + delta[r];
|
|
|
|
if (board[str] != color)
|
|
continue;
|
|
|
|
ASSERT1(string_count < 3, pos);
|
|
for (s = 0; s < string_count; s++)
|
|
if (same_string(str, strings[s]))
|
|
break;
|
|
if (s != string_count)
|
|
continue;
|
|
|
|
strings[string_count++] = str;
|
|
}
|
|
if (string_count > 1) {
|
|
for (s = 0; s < string_count; s++) {
|
|
int libs[MAX_LIBERTIES];
|
|
int adj_eye_count;
|
|
int lib_count;
|
|
adj_eye_count = 0;
|
|
lib_count = findlib(strings[s], MAX_LIBERTIES, libs);
|
|
if (lib_count > MAX_LIBERTIES)
|
|
continue;
|
|
|
|
for (r = 0; r < lib_count && adj_eye_count < 2; r++)
|
|
if (my_eye[libs[r]].color == OTHER_COLOR(color)
|
|
&& !my_eye[libs[r]].marginal)
|
|
adj_eye_count++;
|
|
if (adj_eye_count < 2) {
|
|
val = 2.0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sum += val;
|
|
|
|
if (val > 0.0 && val < 2.0) {
|
|
/* Diagonals off the edge has value 1.0 but no attack or defense
|
|
* point.
|
|
*/
|
|
if (attack_point != NO_MOVE && defense_point != NO_MOVE) {
|
|
ASSERT_ON_BOARD1(attack_point);
|
|
ASSERT_ON_BOARD1(defense_point);
|
|
/* Store these in sorted (descending) order. We remap val
|
|
* differently for attack and defense points according to:
|
|
*
|
|
* val attack_value defense_value
|
|
* --- ------------ -------------
|
|
* 1.0 3 3
|
|
* <1.0 2 1
|
|
* >1.0 1 2
|
|
*
|
|
* This means that we primarily want to take control of
|
|
* diagonals without ko and secondarily of diagonals we can
|
|
* take unconditionally but not the opponent.
|
|
*/
|
|
if (val == 1.0) {
|
|
attack_value = 3;
|
|
defense_value = 3;
|
|
}
|
|
else if (val < 1.0) {
|
|
attack_value = 2;
|
|
defense_value = 1;
|
|
}
|
|
else {
|
|
attack_value = 1;
|
|
defense_value = 2;
|
|
}
|
|
|
|
for (r = 0; r < 4; r++) {
|
|
if (attack_values[r] < attack_value) {
|
|
int tmp_value = attack_values[r];
|
|
int tmp_point;
|
|
if (tmp_value)
|
|
tmp_point = heye[pos].attack_point[r];
|
|
else
|
|
tmp_point = 0;
|
|
attack_values[r] = attack_value;
|
|
heye[pos].attack_point[r] = attack_point;
|
|
attack_value = tmp_value;
|
|
attack_point = tmp_point;
|
|
}
|
|
|
|
if (defense_values[r] < defense_value) {
|
|
int tmp_value = defense_values[r];
|
|
int tmp_point;
|
|
if (tmp_value)
|
|
tmp_point = heye[pos].defense_point[r];
|
|
else
|
|
tmp_point = 0;
|
|
defense_values[r] = defense_value;
|
|
heye[pos].defense_point[r] = defense_point;
|
|
defense_value = tmp_value;
|
|
defense_point = tmp_point;
|
|
}
|
|
}
|
|
|
|
num_attacks++;
|
|
num_defenses++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Remove attacks and defenses with smaller value than the best
|
|
* ones. (These might be useful to save as well, but not unless we
|
|
* also store the attack/defense values in the half_eye_data.)
|
|
*/
|
|
for (r = 0; r < num_attacks; r++) {
|
|
if (attack_values[r] < attack_values[0]) {
|
|
num_attacks = r;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (r = 0; r < num_defenses; r++) {
|
|
if (defense_values[r] < defense_values[0]) {
|
|
num_defenses = r;
|
|
break;
|
|
}
|
|
}
|
|
|
|
heye[pos].num_attacks = num_attacks;
|
|
heye[pos].num_defenses = num_defenses;
|
|
heye[pos].value = sum;
|
|
|
|
return sum;
|
|
}
|
|
|
|
|
|
|
|
/* Evaluate an intersection (m, n) which is diagonal to an eye space,
|
|
* as described in the Texinfo documentation (Eyes/Eye Topology).
|
|
*
|
|
* Returns:
|
|
*
|
|
* 0 if both coordinates are off the board
|
|
* 1 if one coordinate is off the board
|
|
*
|
|
* 0 if (color) has control over the vertex
|
|
* a if (color) can take control over the vertex unconditionally and
|
|
* the opponent can take control by winning a ko.
|
|
* 1 if both (color) and the opponent can take control of the vertex
|
|
* unconditionally
|
|
* b if (color) can take control over the vertex by winning a ko and
|
|
* the opponent can take control unconditionally.
|
|
* 2 if the opponent has control over the vertex
|
|
*
|
|
* The values a and b are discussed in the documentation. We are
|
|
* currently using a = 0.75 and b = 1.25.
|
|
*
|
|
* Notice that it's necessary to pass the coordinates separately
|
|
* instead of as a 1D coordinate. The reason is that the 1D mapping
|
|
* can't uniquely identify "off the corner" points.
|
|
*
|
|
* my_eye has to be the eye_data with respect to color.
|
|
*/
|
|
static float
|
|
evaluate_diagonal_intersection(int m, int n, int color,
|
|
int *attack_point, int *defense_point,
|
|
struct eye_data my_eye[BOARDMAX])
|
|
{
|
|
float value = 0;
|
|
int other = OTHER_COLOR(color);
|
|
int pos = POS(m, n);
|
|
int acode = 0;
|
|
int apos = NO_MOVE;
|
|
int dcode = 0;
|
|
int dpos = NO_MOVE;
|
|
int off_edge = 0;
|
|
const float a = 0.75;
|
|
const float b = 2 - a;
|
|
|
|
*attack_point = NO_MOVE;
|
|
*defense_point = NO_MOVE;
|
|
|
|
/* Check whether intersection is off the board. We must do this for
|
|
* each board coordinate separately because points "off the corner"
|
|
* are special cases.
|
|
*/
|
|
if (m < 0 || m >= board_size)
|
|
off_edge++;
|
|
|
|
if (n < 0 || n >= board_size)
|
|
off_edge++;
|
|
|
|
/* Must return 0 if both coordinates out of bounds. */
|
|
if (off_edge > 0)
|
|
return (float) (off_edge % 2);
|
|
|
|
/* Discard points within own eyespace, unless marginal or ko point.
|
|
*
|
|
* Comment: For some time discardment of points within own eyespace
|
|
* was contingent on this being the same eyespace as that of the
|
|
* examined vertex. This caused problems, e.g. in this position,
|
|
*
|
|
* |........
|
|
* |XXXXX...
|
|
* |OOOOX...
|
|
* |aO.OX...
|
|
* |OXXOX...
|
|
* |.XXOX...
|
|
* +--------
|
|
*
|
|
* where the empty vertex at a was evaluated as a false eye and the
|
|
* whole group as dead (instead of living in seki).
|
|
*
|
|
* The reason for the requirement of less than two marginal
|
|
* neighbors is this position:
|
|
*
|
|
* |.XXXX...
|
|
* |.OOOX...
|
|
* |O..OX...
|
|
* |aOO.X...
|
|
* |O..XX...
|
|
* |..O.X...
|
|
* |.X..X...
|
|
* |..XXX...
|
|
*
|
|
* where the empty vertex at a should not count as a solid eye.
|
|
* (The eyespace diagonally below a looks like this:
|
|
* .!
|
|
* !
|
|
* so we can clearly see why having two marginal vertices makes a
|
|
* difference.)
|
|
*/
|
|
if (my_eye[pos].color == color
|
|
&& !my_eye[pos].marginal
|
|
&& my_eye[pos].marginal_neighbors < 2
|
|
&& !(board[pos] == EMPTY && does_capture_something(pos, other)))
|
|
return 0.0;
|
|
|
|
if (board[pos] == EMPTY) {
|
|
int your_safety = safe_move(pos, other);
|
|
|
|
apos = pos;
|
|
dpos = pos;
|
|
|
|
/* We should normally have a safe move, but occasionally it may
|
|
* happen that it's not safe. There are complications, however,
|
|
* with a position like this:
|
|
*
|
|
* .XXXX|
|
|
* XXOO.|
|
|
* XO.O.|
|
|
* XXO.O|
|
|
* -----+
|
|
*
|
|
* Therefore we ignore our own safety if opponent's safety depends
|
|
* on ko.
|
|
*/
|
|
if (your_safety == 0)
|
|
value = 0.0;
|
|
else if (your_safety != WIN)
|
|
value = a;
|
|
else { /* So your_safety == WIN. */
|
|
int our_safety = safe_move(pos, color);
|
|
|
|
if (our_safety == 0) {
|
|
int k;
|
|
|
|
value = 2.0;
|
|
|
|
/* This check is intended to fix a certain special case, but might
|
|
* be helpful in other situations as well. Consider this position,
|
|
* happened in owl reading deep enough:
|
|
*
|
|
* |XXXXX
|
|
* |XOOXX
|
|
* |O.OOX
|
|
* |.OXX.
|
|
* +-----
|
|
*
|
|
* Without this check, the corner eye is considered false, not half-
|
|
* eye. Thus, owl thinks that the capture gains at most one eye and
|
|
* gives up.
|
|
*/
|
|
for (k = 4; k < 8; k++) {
|
|
int diagonal = pos + delta[k];
|
|
int lib;
|
|
|
|
if (board[diagonal] == other && findlib(diagonal, 1, &lib) == 1) {
|
|
if (lib != pos && does_secure(color, lib, pos)) {
|
|
value = 1.0;
|
|
apos = lib;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (our_safety == WIN)
|
|
value = 1.0;
|
|
else /* our_safety depends on ko. */
|
|
value = b;
|
|
}
|
|
}
|
|
else if (board[pos] == color) {
|
|
/* This stone had better be safe, otherwise we wouldn't have an
|
|
* eyespace in the first place.
|
|
*/
|
|
value = 0.0;
|
|
}
|
|
else if (board[pos] == other) {
|
|
if (stackp == 0) {
|
|
acode = worm[pos].attack_codes[0];
|
|
apos = worm[pos].attack_points[0];
|
|
dcode = worm[pos].defense_codes[0];
|
|
dpos = worm[pos].defense_points[0];
|
|
}
|
|
else
|
|
attack_and_defend(pos, &acode, &apos, &dcode, &dpos);
|
|
|
|
/* Must test acode first since dcode only is reliable if acode is
|
|
* non-zero.
|
|
*/
|
|
if (acode == 0)
|
|
value = 2.0;
|
|
else if (dcode == 0)
|
|
value = 0.0;
|
|
else if (acode == WIN && dcode == WIN)
|
|
value = 1.0;
|
|
else if (acode == WIN && dcode != WIN)
|
|
value = a;
|
|
else if (acode != WIN && dcode == WIN)
|
|
value = b;
|
|
else if (acode != WIN && dcode != WIN)
|
|
value = 1.0; /* Both contingent on ko. Probably can't happen. */
|
|
}
|
|
|
|
if (value > 0.0 && value < 2.0) {
|
|
/* FIXME:
|
|
* Usually there are several attack and defense moves that would
|
|
* be equally valid. It's not good that we make an arbitrary
|
|
* choice at this point.
|
|
*/
|
|
ASSERT_ON_BOARD1(apos);
|
|
ASSERT_ON_BOARD1(dpos);
|
|
/* Notice:
|
|
* The point to ATTACK the half eye is the point which DEFENDS
|
|
* the stones on the diagonal intersection and vice versa. Thus
|
|
* we must switch attack and defense points here.
|
|
* If the vertex is empty, dpos == apos and it doesn't matter
|
|
* whether we switch.
|
|
*/
|
|
*attack_point = dpos;
|
|
*defense_point = apos;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
/* Conservative relative of topological_eye(). Essentially the same
|
|
* algorithm is used, but only tactically safe opponent strings on
|
|
* diagonals are considered. This may underestimate the false/half eye
|
|
* status, but it should never be overestimated.
|
|
*/
|
|
int
|
|
obvious_false_eye(int pos, int color)
|
|
{
|
|
int i = I(pos);
|
|
int j = J(pos);
|
|
int k;
|
|
int diagonal_sum = 0;
|
|
for (k = 4; k < 8; k++) {
|
|
int di = deltai[k];
|
|
int dj = deltaj[k];
|
|
|
|
if (!ON_BOARD2(i+di, j) && !ON_BOARD2(i, j+dj))
|
|
diagonal_sum--;
|
|
|
|
if (!ON_BOARD2(i+di, j+dj))
|
|
diagonal_sum++;
|
|
else if (BOARD(i+di, j+dj) == OTHER_COLOR(color)
|
|
&& !attack(POS(i+di, j+dj), NULL))
|
|
diagonal_sum += 2;
|
|
}
|
|
|
|
return diagonal_sum >= 4;
|
|
}
|
|
|
|
|
|
/* Set the parameters into struct eyevalue as follows:
|
|
a = number of eyes if attacker plays first twice
|
|
b = number of eyes if attacker plays first
|
|
c = number of eyes if defender plays first
|
|
d =number of eyes if defender plays first twice
|
|
*/
|
|
|
|
void
|
|
set_eyevalue(struct eyevalue *e, int a, int b, int c, int d)
|
|
{
|
|
e->a = a;
|
|
e->b = b;
|
|
e->c = c;
|
|
e->d = d;
|
|
}
|
|
|
|
/* Number of eyes if attacker plays first twice (the threat of the first
|
|
* move by attacker).
|
|
*/
|
|
int
|
|
min_eye_threat(struct eyevalue *e)
|
|
{
|
|
return e->a;
|
|
}
|
|
|
|
/* Number of eyes if attacker plays first followed by alternating play. */
|
|
int
|
|
min_eyes(struct eyevalue *e)
|
|
{
|
|
return e->b;
|
|
}
|
|
|
|
/* Number of eyes if defender plays first followed by alternating play. */
|
|
int
|
|
max_eyes(struct eyevalue *e)
|
|
{
|
|
return e->c;
|
|
}
|
|
|
|
/* Number of eyes if defender plays first twice (the threat of the first
|
|
* move by defender).
|
|
*/
|
|
int
|
|
max_eye_threat(struct eyevalue *e)
|
|
{
|
|
return e->d;
|
|
}
|
|
|
|
/* Add the eyevalues *e1 and *e2, leaving the result in *sum. It is
|
|
* safe to let sum be the same as e1 or e2.
|
|
*/
|
|
void
|
|
add_eyevalues(struct eyevalue *e1, struct eyevalue *e2, struct eyevalue *sum)
|
|
{
|
|
struct eyevalue res;
|
|
res.a = gg_min(gg_min(e1->a + e2->c, e1->c + e2->a),
|
|
gg_max(e1->a + e2->b, e1->b + e2->a));
|
|
res.b = gg_min(gg_max(e1->b + e2->b, gg_min(e1->a + e2->d, e1->b + e2->c)),
|
|
gg_max(e1->b + e2->b, gg_min(e1->d + e2->a, e1->c + e2->b)));
|
|
res.c = gg_max(gg_min(e1->c + e2->c, gg_max(e1->d + e2->a, e1->c + e2->b)),
|
|
gg_min(e1->c + e2->c, gg_max(e1->a + e2->d, e1->b + e2->c)));
|
|
res.d = gg_max(gg_max(e1->d + e2->b, e1->b + e2->d),
|
|
gg_min(e1->d + e2->c, e1->c + e2->d));
|
|
|
|
/* The rules above give 0011 + 0002 = 0012, which is incorrect. Thus
|
|
* we need this annoying exception.
|
|
*/
|
|
if ((e1->d - e1->c == 2 && e2->c - e2->b == 1)
|
|
|| (e1->c - e1->b == 1 && e2->d - e2->c == 2)) {
|
|
res.d = gg_max(gg_min(e1->c + e2->d, e1->d + e2->b),
|
|
gg_min(e1->d + e2->c, e1->b + e2->d));
|
|
}
|
|
|
|
/* The temporary storage in res is necessary if sum is the same as
|
|
* e1 or e2.
|
|
*/
|
|
sum->a = res.a;
|
|
sum->b = res.b;
|
|
sum->c = res.c;
|
|
sum->d = res.d;
|
|
}
|
|
|
|
/* The impact on the number of eyes (counting up to two) if a vital
|
|
* move is made. The possible values are
|
|
* 0 - settled eye, no vital move
|
|
* 2 - 1/2 eye or 3/2 eyes
|
|
* 3 - 3/4 eyes or 5/4 eyes
|
|
* 4 - 1* eyes (a chimera)
|
|
*/
|
|
int
|
|
eye_move_urgency(struct eyevalue *e)
|
|
{
|
|
int a = gg_min(e->a, 2);
|
|
int b = gg_min(e->b, 2);
|
|
int c = gg_min(e->c, 2);
|
|
int d = gg_min(e->d, 2);
|
|
if (b == c)
|
|
return 0;
|
|
else
|
|
return d + c - b - a;
|
|
}
|
|
|
|
/* Produces a string representing the eyevalue.
|
|
*
|
|
* Note: the result string is stored in a statically allocated buffer
|
|
* which will be overwritten the next time this function is called.
|
|
*/
|
|
char *
|
|
eyevalue_to_string(struct eyevalue *e)
|
|
{
|
|
static char result[30];
|
|
if (e->a < 10 && e->b < 10 && e->c < 10 && e->d < 10)
|
|
gg_snprintf(result, 29, "%d%d%d%d", e->a, e->b, e->c, e->d);
|
|
else
|
|
gg_snprintf(result, 29, "[%d,%d,%d,%d]", e->a, e->b, e->c, e->d);
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
/* Test whether the optics code evaluates an eyeshape consistently. */
|
|
void
|
|
test_eyeshape(int eyesize, int *eye_vertices)
|
|
{
|
|
int k;
|
|
int n, N;
|
|
int mx[BOARDMAX];
|
|
int pos;
|
|
int str = NO_MOVE;
|
|
int attack_code;
|
|
int attack_point;
|
|
int defense_code;
|
|
int defense_point;
|
|
int save_verbose;
|
|
struct board_state starting_position;
|
|
|
|
/* Clear the board and initialize the engine properly. */
|
|
clear_board();
|
|
reset_engine();
|
|
|
|
/* Mark the eyespace in the mx array. */
|
|
memset(mx, 0, sizeof(mx));
|
|
for (k = 0; k < eyesize; k++) {
|
|
ASSERT_ON_BOARD1(eye_vertices[k]);
|
|
mx[eye_vertices[k]] = 1;
|
|
}
|
|
|
|
/* Play white stones surrounding the eyespace, including diagonals. */
|
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
|
if (!ON_BOARD(pos) || mx[pos] == 1)
|
|
continue;
|
|
for (k = 0; k < 8; k++) {
|
|
if (ON_BOARD(pos + delta[k]) && mx[pos + delta[k]] == 1) {
|
|
play_move(pos, WHITE);
|
|
str = pos;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Play black stones surrounding the white group, but leaving all
|
|
* liberties empty.
|
|
*/
|
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
|
if (mx[pos] == 1 || board[pos] != EMPTY || liberty_of_string(pos, str))
|
|
continue;
|
|
for (k = 0; k < 8; k++) {
|
|
if (ON_BOARD(pos + delta[k])
|
|
&& liberty_of_string(pos + delta[k], str)) {
|
|
play_move(pos, BLACK);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Show the board if verbose is on. Then turn off traces so we don't
|
|
* get any from make_worms(), make_dragons(), or the owl reading.
|
|
*/
|
|
if (verbose)
|
|
showboard(0);
|
|
save_verbose = verbose;
|
|
verbose = 0;
|
|
|
|
|
|
/* Store this position so we can come back to it. */
|
|
store_board(&starting_position);
|
|
|
|
/* Loop over all configurations of black stones inserted in the
|
|
* eyeshape. There are N = 2^(eyesize) configurations and we can
|
|
* straightforwardly use binary representation to enumerate them.
|
|
*/
|
|
N = 1 << eyesize;
|
|
for (n = 0; n < N; n++) {
|
|
int valid = 1;
|
|
int internal_stones = 0;
|
|
|
|
restore_board(&starting_position);
|
|
/* Play the stones for this configuration. */
|
|
for (k = 0; k < eyesize; k++) {
|
|
if (n & (1 << k)) {
|
|
if (!is_legal(eye_vertices[k], BLACK)) {
|
|
valid = 0;
|
|
break;
|
|
}
|
|
play_move(eye_vertices[k], BLACK);
|
|
internal_stones++;
|
|
}
|
|
}
|
|
|
|
if (!valid)
|
|
continue;
|
|
|
|
if (save_verbose > 1)
|
|
showboard(0);
|
|
|
|
/* Now we are ready to test the consistency. This is most easily
|
|
* done with help from the owl code. First we must prepare for
|
|
* this though.
|
|
*/
|
|
examine_position(EXAMINE_DRAGONS_WITHOUT_OWL, 0);
|
|
|
|
attack_code = owl_attack(str, &attack_point, NULL, NULL);
|
|
|
|
if (attack_code == 0) {
|
|
/* The owl code claims there is no attack. We test this by
|
|
* trying to attack on all empty spaces in the eyeshape.
|
|
*/
|
|
for (k = 0; k < eyesize; k++) {
|
|
if (board[eye_vertices[k]] == EMPTY
|
|
&& is_legal(eye_vertices[k], BLACK)
|
|
&& owl_does_attack(eye_vertices[k], str, NULL)) {
|
|
gprintf("%1m alive, but %1m attacks:\n", str, eye_vertices[k]);
|
|
showboard(0);
|
|
gprintf("\n");
|
|
}
|
|
}
|
|
|
|
/* Furthermore, if the eyespace is almost filled, white should
|
|
* be able to play on the remaining eyespace point and still be
|
|
* alive.
|
|
*/
|
|
if (internal_stones == eyesize - 1) {
|
|
for (k = 0; k < eyesize; k++) {
|
|
if (board[eye_vertices[k]] == EMPTY
|
|
&& !owl_does_defend(eye_vertices[k], str, NULL)) {
|
|
gprintf("%1m alive, but almost filled with nakade:\n", str);
|
|
showboard(0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
defense_code = owl_defend(str, &defense_point, NULL, NULL);
|
|
if (defense_code == 0) {
|
|
/* The owl code claims there is no defense. We test this by
|
|
* trying to defend on all empty spaces in the eyeshape.
|
|
*/
|
|
for (k = 0; k < eyesize; k++) {
|
|
if (board[eye_vertices[k]] == EMPTY
|
|
&& is_legal(eye_vertices[k], WHITE)
|
|
&& owl_does_defend(eye_vertices[k], str, NULL)) {
|
|
gprintf("%1m dead, but %1m defends:\n", str, eye_vertices[k]);
|
|
showboard(0);
|
|
gprintf("\n");
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/* The owl code claims the dragon is critical. Verify the
|
|
* attack and defense points.
|
|
*/
|
|
if (board[attack_point] != EMPTY
|
|
|| !is_legal(attack_point, BLACK)) {
|
|
gprintf("Bad attack point %1m:\n", attack_point);
|
|
showboard(0);
|
|
}
|
|
else if (!owl_does_attack(attack_point, str, NULL)) {
|
|
gprintf("Attack point %1m failed:\n", attack_point);
|
|
showboard(0);
|
|
}
|
|
|
|
if (board[defense_point] != EMPTY
|
|
|| !is_legal(defense_point, WHITE)) {
|
|
gprintf("Bad defense point %1m:\n", defense_point);
|
|
showboard(0);
|
|
}
|
|
else if (!owl_does_defend(defense_point, str, NULL)) {
|
|
gprintf("Defense point %1m failed:\n", defense_point);
|
|
showboard(0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
verbose = save_verbose;
|
|
}
|
|
|
|
/********************************************************************
|
|
* The following static functions are helpers for analyze_eyegraph()
|
|
* further down. The purpose is to evaluate eye graphs according to
|
|
* the rules for local games, as described in doc/eyes.texi.
|
|
*
|
|
* The technique to do this is to convert the eye evaluation problem
|
|
* into a tactical style life and death reading problem. Tactical in
|
|
* the sense of needing to decide whether certain stones can be
|
|
* captured, but not in the sense of the tactical reading that five
|
|
* liberties are considered safe.
|
|
*
|
|
* We illustrate how this works with an example. Consider the eye shape
|
|
*
|
|
* !
|
|
* .X
|
|
* !...
|
|
*
|
|
* The basic idea is to embed the eyespace in a perfectly connected
|
|
* group without additional eyes or eye potential. This is most easily
|
|
* done by the somewhat brutal trick to fill the entire board with
|
|
* stones. We let the group consist of white stones (O) and get this
|
|
* result, disregarding the two marginal eye vertices:
|
|
*
|
|
* A B C D E F G H J K L M N O P Q R S T
|
|
* 19 O O O O O O O O O O O O O O O O O O O 19
|
|
* 18 O O O O O O O O O O O O O O O O O O O 18
|
|
* 17 O O O O O O O O O O O O O O O O O O O 17
|
|
* 16 O O O O O O O O O O O O O O O O O O O 16
|
|
* 15 O O O O O O O O O O O O O O O O O O O 15
|
|
* 14 O O O O O O O O O O O O O O O O O O O 14
|
|
* 13 O O O O O O O O O O O O O O O O O O O 13
|
|
* 12 O O O O O O O O . O O O O O O O O O O 12
|
|
* 11 O O O O O O O . X O O O O O O O O O O 11
|
|
* 10 O O O O O O . . . . O O O O O O O O O 10
|
|
* 9 O O O O O O O O O O O O O O O O O O O 9
|
|
* 8 O O O O O O O O O O O O O O O O O O O 8
|
|
* 7 O O O O O O O O O O O O O O O O O O O 7
|
|
* 6 O O O O O O O O O O O O O O O O O O O 6
|
|
* 5 O O O O O O O O O O O O O O O O O O O 5
|
|
* 4 O O O O O O O O O O O O O O O O O O O 4
|
|
* 3 O O O O O O O O O O O O O O O O O O O 3
|
|
* 2 O O O O O O O O O O O O O O O O O O O 2
|
|
* 1 O O O O O O O O O O O O O O O O O O O 1
|
|
* A B C D E F G H J K L M N O P Q R S T
|
|
*
|
|
* The question now is whether black can capture all the white stones
|
|
* under alternating play where only white may pass. However, first we
|
|
* need to make the top and leftmost eye vertices marginal. This is
|
|
* done by inserting small invincible black groups in the sea of white
|
|
* stones, in contact with the marginal vertices.
|
|
*
|
|
* A B C D E F G H J K L M N O P Q R S T
|
|
* 19 . O O O O O O O O O O O O O O O O O O 19
|
|
* 18 O O O O O O O O X X X O O O O O O O O 18
|
|
* 17 O O O O O O O O X . X O O O O O O O O 17
|
|
* 16 O O O O O O O O X X X O O O O O O O O 16
|
|
* 15 O O O O O O O O X . X O O O O O O O O 15
|
|
* 14 O O O O O O O O X X X O O O O O O O O 14
|
|
* 13 O O O O O O O O X O O O O O O O O O O 13
|
|
* 12 O O O O O O O O . O O O O O O O O O O 12
|
|
* 11 O O O O O O O . X O O O O O O O O O O 11
|
|
* 10 O O O O O O . . . . O O O O O O O O O 10
|
|
* 9 O O O O O O X O O O O O O O O O O O O 9
|
|
* 8 O O O O X X X O O O O O O O O O O O O 8
|
|
* 7 O O O O X . X O O O O O O O O O O O O 7
|
|
* 6 O O O O X X X O O O O O O O O O O O O 6
|
|
* 5 O O O O X . X O O O O O O O O O O O O 5
|
|
* 4 . O O O X X X O O O O O O O O O O O O 4
|
|
* 3 X X . O O O O O O O O O O O O O O O O 3
|
|
* 2 X . X O O O O O O O O O O O O O O O O 2
|
|
* 1 . X X O O O O O O O O O O O O O O O O 1
|
|
* A B C D E F G H J K L M N O P Q R S T
|
|
*
|
|
* In this diagram we have also added an invincible black group in the
|
|
* lower left corner in order to add two outer liberties (at A4 and
|
|
* C3) for the white group (this is sometimes needed for the tactical
|
|
* life and death reading to make sense). Furthermore there is an
|
|
* extra eye at A19. This is used when we want to distinguish between
|
|
* 0 and 1 (or 2) eyes since the tactical life and death reading by
|
|
* itself only cares about two eyes or not. When trying to distinguish
|
|
* between 1 (or 0) and 2 eyes we first fill in A19 again.
|
|
*
|
|
* Depending on the tactical life and death status with or without the
|
|
* extra eye we can determine the number of eyes. By evaluating
|
|
* tactical life and death status after having made a move we can also
|
|
* identify ko threats and critical moves.
|
|
*
|
|
* This code is organized as follows:
|
|
*
|
|
* analyze_eyegraph() converts the eyegraph into the tactical board
|
|
* position as demonstrated, then calls evaluate_eyespace() to its eye
|
|
* value.
|
|
*
|
|
* white_area() is a helper to add a small invincible black group on
|
|
* the board.
|
|
*
|
|
* evaluate_eyespace() calls tactical_life() and itself recursively to
|
|
* determine the eye value and the critical points.
|
|
*
|
|
* tactical_life() determines whether the white stones on the board
|
|
* (assumed to be a single string) can be captured under alternating
|
|
* play.
|
|
*
|
|
* tactical_life_attack() and tactical_life_defend() are two mutually
|
|
* recursive functions which perform the actual reading for
|
|
* tactical_life().
|
|
*
|
|
* Worth to mention in this overview is also the cache used for
|
|
* tactical_life_attack() and tactical_life_defend(). Since we have a
|
|
* limited number of vertices (eye space points + two outer liberties
|
|
* + possibly an extra eye) to play on we use a complete cache with a
|
|
* unique entry for every possible configuration of stones on the
|
|
* considered vertices.
|
|
*
|
|
* For each cache entry four bits are used, two for attack results and
|
|
* two four defense results. Each of these can take the values 0-3
|
|
* with the following interpretations:
|
|
* 0 - not yet considered
|
|
* 1 - result is being computed
|
|
* 2 - result has been computed and was a failure (0)
|
|
* 3 - result has been computed and was a success (1)
|
|
*/
|
|
|
|
/* Like trymove() except that it does a superko check. This does,
|
|
* however, only disallow repetition (besides simple ko) if the move
|
|
* does not capture any stones.
|
|
*/
|
|
static int
|
|
eyegraph_trymove(int pos, int color, const char *message, int str)
|
|
{
|
|
static Hash_data remembered_board_hashes[MAXSTACK];
|
|
int k;
|
|
int does_capture = does_capture_something(pos, color);
|
|
|
|
remembered_board_hashes[stackp] = board_hash;
|
|
|
|
if (!trymove(pos, color, message, str))
|
|
return 0;
|
|
|
|
if (does_capture)
|
|
return 1;
|
|
|
|
for (k = 0; k < stackp; k++)
|
|
if (hashdata_is_equal(board_hash, remembered_board_hashes[k])) {
|
|
popgo();
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
eyegraph_is_margin_or_outer_liberty(int vertex)
|
|
{
|
|
int k;
|
|
int r;
|
|
int num_libs;
|
|
int libs[MAXLIBS];
|
|
int eyes;
|
|
|
|
for (k = 0; k < 4; k++) {
|
|
if (board[vertex + delta[k]] == BLACK) {
|
|
eyes = 0;
|
|
num_libs = findlib(vertex + delta[k], MAXLIBS, libs);
|
|
|
|
for (r = 0; r < num_libs; r++)
|
|
if (is_suicide(libs[r], WHITE))
|
|
eyes++;
|
|
|
|
if (eyes >= 2)
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
eyegraph_order_moves(int num_vertices, int *vertices, int color_to_move, int *moves)
|
|
{
|
|
int num_moves = 0;
|
|
int scores[BOARDMAX];
|
|
int move;
|
|
int score;
|
|
int k;
|
|
int r;
|
|
|
|
for (k = 0; k < num_vertices; k++) {
|
|
if (k >= num_vertices - 3) {
|
|
/* Never useful for white to fill in outer liberties or a second eye. */
|
|
if (color_to_move == WHITE)
|
|
break;
|
|
/* No use playing the second outer liberty before the first one. */
|
|
if (k == num_vertices - 2 && board[vertices[num_vertices - 3]] == EMPTY)
|
|
continue;
|
|
}
|
|
|
|
move = vertices[k];
|
|
score = 0;
|
|
|
|
if (board[move] != EMPTY)
|
|
continue;
|
|
|
|
if (eyegraph_is_margin_or_outer_liberty(move)) {
|
|
if (k < num_vertices - 3)
|
|
score = 5; /* margin */
|
|
else
|
|
score = -10; /* outer liberty */
|
|
}
|
|
|
|
if (accuratelib(move, color_to_move, 2, NULL) == 1)
|
|
score -= 3;
|
|
|
|
for (r = 0; r < 4; r++) {
|
|
if (board[move + delta[r]] == EMPTY)
|
|
score += 2;
|
|
else if (board[move + delta[r]] == BLACK)
|
|
score += 3;
|
|
}
|
|
|
|
moves[num_moves] = move;
|
|
scores[num_moves] = score;
|
|
num_moves++;
|
|
}
|
|
|
|
for (k = 0; k < num_moves; k++) {
|
|
int maxscore = scores[k];
|
|
int max_at = 0;
|
|
|
|
/* Find the move with the biggest score. */
|
|
for (r = k + 1; r < num_moves; r++) {
|
|
if (scores[r] > maxscore) {
|
|
maxscore = scores[r];
|
|
max_at = r;
|
|
}
|
|
}
|
|
|
|
/* Now exchange the move at k with the move at max_at.
|
|
* Don't forget to exchange the scores as well.
|
|
*/
|
|
if (max_at != 0) {
|
|
int temp = moves[max_at];
|
|
moves[max_at] = moves[k];
|
|
moves[k] = temp;
|
|
temp = scores[max_at];
|
|
scores[max_at] = scores[k];
|
|
scores[k] = temp;
|
|
}
|
|
}
|
|
|
|
return num_moves;
|
|
}
|
|
|
|
/* Place a small invincible black group on the board.
|
|
* It is required that previously there were white stones at all
|
|
* involved vertices and on the surrounding vertices.
|
|
*
|
|
* Returns 1 if a group was placed, 0 otherwise.
|
|
*/
|
|
static int
|
|
white_area(int mx[BOARDMAX], int pos, int up, int right, int marginpos,
|
|
int distance)
|
|
{
|
|
int u, v;
|
|
int k;
|
|
int edge = is_edge_vertex(marginpos);
|
|
|
|
for (k = 1; k < distance; k++)
|
|
if (!ON_BOARD(marginpos + k * up)
|
|
|| mx[marginpos + k * up] != WHITE)
|
|
return 0;
|
|
|
|
for (u = -1; u <= 4; u++)
|
|
for (v = -1; v <= 4; v++) {
|
|
int pos2 = pos + u * up + v * right;
|
|
if (!ON_BOARD(pos2)) {
|
|
if (!edge)
|
|
return 0;
|
|
else if (u >= 0 && u <= 3 && v >= 0 && v <= 3)
|
|
return 0;
|
|
else if (I(pos2) != I(NORTH(marginpos))
|
|
&& I(pos2) != I(SOUTH(marginpos))
|
|
&& J(pos2) != J(WEST(marginpos))
|
|
&& J(pos2) != J(EAST(marginpos)))
|
|
return 0;
|
|
}
|
|
else if (mx[pos2] != WHITE)
|
|
return 0;
|
|
}
|
|
|
|
for (u = 0; u <= 3; u++)
|
|
for (v = 0; v <= 3; v++) {
|
|
int pos2 = pos + u * up + v * right;
|
|
mx[pos2] = BLACK;
|
|
}
|
|
|
|
mx[pos + up + right] = EMPTY;
|
|
mx[pos + 2 * up + 2 * right] = EMPTY;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
#define EYEGRAPH_RETURN(result, trace) \
|
|
do { \
|
|
if (sgf_dumptree) \
|
|
sgftreeAddComment(sgf_dumptree, (trace)); \
|
|
return (result); \
|
|
} while (0);
|
|
|
|
static int tactical_life_defend(int str, int num_vertices, int *vertices,
|
|
unsigned char *results);
|
|
|
|
/* Determine whether black can capture all white stones. */
|
|
static int
|
|
tactical_life_attack(int str, int num_vertices, int *vertices,
|
|
unsigned char *results)
|
|
{
|
|
int k;
|
|
int hash = 0;
|
|
int cached_result;
|
|
int result;
|
|
int num_moves;
|
|
int moves[BOARDMAX];
|
|
|
|
/* Compute hash value to index the result cache with. */
|
|
for (k = 0; k < num_vertices; k++) {
|
|
hash *= 3;
|
|
hash += board[vertices[k]];
|
|
}
|
|
hash *= 2;
|
|
hash += (board_ko_pos != NO_MOVE);
|
|
|
|
/* Is the result known from the cache? */
|
|
cached_result = results[hash] & 3;
|
|
|
|
if (0) {
|
|
showboard(0);
|
|
gprintf("%d %d (%d)\n", hash, cached_result, results[hash]);
|
|
}
|
|
|
|
if (cached_result == 2)
|
|
EYEGRAPH_RETURN(0, "tactical_life_attack: 0 (cached)");
|
|
if (cached_result == 3)
|
|
EYEGRAPH_RETURN(1, "tactical_life_attack: win (cached)");
|
|
if (cached_result == 1)
|
|
EYEGRAPH_RETURN(1, "tactical_life_attack: win (open node in cache)");
|
|
|
|
/* Mark this entry in the cache as currently being computed. */
|
|
results[hash] |= 1;
|
|
|
|
/* Try to play on all relevant vertices. */
|
|
num_moves = eyegraph_order_moves(num_vertices, vertices,
|
|
OTHER_COLOR(board[str]), moves);
|
|
for (k = 0; k < num_moves; k++) {
|
|
int move = moves[k];
|
|
if (eyegraph_trymove(move, OTHER_COLOR(board[str]),
|
|
"tactical_life_attack", str)) {
|
|
/* We were successful if the white stones were captured or if no
|
|
* defense can be found.
|
|
*/
|
|
if (board[str] == EMPTY)
|
|
result = 1;
|
|
else
|
|
result = !tactical_life_defend(str, num_vertices, vertices, results);
|
|
|
|
popgo();
|
|
|
|
if (result == 1) {
|
|
/* Store the result (success) in the cache. */
|
|
results[hash] = (results[hash] & (~3)) | 3;
|
|
EYEGRAPH_RETURN(1, "tactical_life_attack: win");
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Store the result (failure) in the cache. */
|
|
results[hash] = (results[hash] & (~3)) | 2;
|
|
EYEGRAPH_RETURN(0, "tactical_life_attack: 0");
|
|
}
|
|
|
|
/* Determine whether white can live with all stones. */
|
|
static int
|
|
tactical_life_defend(int str, int num_vertices, int *vertices,
|
|
unsigned char *results)
|
|
{
|
|
int k;
|
|
int hash = 0;
|
|
int cached_result;
|
|
int result;
|
|
int num_moves;
|
|
int moves[BOARDMAX];
|
|
|
|
/* Compute hash value to index the result cache with. */
|
|
for (k = 0; k < num_vertices; k++) {
|
|
hash *= 3;
|
|
ASSERT1(board[vertices[k]] <= 2, vertices[k]);
|
|
hash += board[vertices[k]];
|
|
}
|
|
hash *= 2;
|
|
hash += (board_ko_pos != NO_MOVE);
|
|
|
|
/* Is the result known from the cache? */
|
|
cached_result = (results[hash] >> 2) & 3;
|
|
|
|
if (0) {
|
|
showboard(0);
|
|
gprintf("%d %d (%d)\n", hash, cached_result, results[hash]);
|
|
}
|
|
|
|
if (cached_result == 2)
|
|
EYEGRAPH_RETURN(0, "tactical_life_defend: 0 (cached)");
|
|
if (cached_result == 3)
|
|
EYEGRAPH_RETURN(1, "tactical_life_defend: win (cached)");
|
|
if (cached_result == 1)
|
|
EYEGRAPH_RETURN(1, "tactical_life_defend: win (node open in cache)");
|
|
|
|
/* Mark this entry in the cache as currently being computed. */
|
|
results[hash] |= (1 << 2);
|
|
|
|
/* Try to play on all relevant vertices. */
|
|
num_moves = eyegraph_order_moves(num_vertices, vertices, board[str], moves);
|
|
for (k = 0; k < num_moves; k++) {
|
|
int move = moves[k];
|
|
if ((!is_suicide(move, OTHER_COLOR(board[str]))
|
|
|| does_capture_something(move, board[str]))
|
|
&& eyegraph_trymove(move, board[str], "tactical_life_defend", str)) {
|
|
/* We were successful if no attack can be found. */
|
|
result = !tactical_life_attack(str, num_vertices, vertices, results);
|
|
|
|
popgo();
|
|
|
|
if (result == 1) {
|
|
/* Store the result (success) in the cache. */
|
|
results[hash] = (results[hash] & (~12)) | (3 << 2);
|
|
EYEGRAPH_RETURN(1, "tactical_life_defend: win");
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If no move worked, also try passing. */
|
|
if (!tactical_life_attack(str, num_vertices, vertices, results)) {
|
|
/* Store the result (success) in the cache. */
|
|
results[hash] = (results[hash] & (~12)) | (3 << 2);
|
|
EYEGRAPH_RETURN(1, "tactical_life_defend: win");
|
|
}
|
|
|
|
/* Store the result (failure) in the cache. */
|
|
results[hash] = (results[hash] & (~12)) | (2 << 2);
|
|
EYEGRAPH_RETURN(0, "tactical_life_defend: 0");
|
|
}
|
|
|
|
/* Determine the tactical life and death status of all white stones.
|
|
* Also find all attack and defense moves. The parameter have_eye
|
|
* determines whether the extra eye in the upper left corner should be
|
|
* used or filled in before starting reading.
|
|
*/
|
|
static void
|
|
tactical_life(int have_eye, int num_vertices, int *vertices,
|
|
int *attack_code, int *num_attacks, int *attack_points,
|
|
int *defense_code, int *num_defenses, int *defense_points,
|
|
unsigned char *results)
|
|
{
|
|
int k;
|
|
int str;
|
|
int num_moves;
|
|
int moves[BOARDMAX];
|
|
|
|
gg_assert(attack_code != NULL && defense_code != NULL);
|
|
|
|
/* We know that the large white group includes A18. This is the
|
|
* vertex we test to determine whether the white stones have been
|
|
* captured.
|
|
*/
|
|
str = POS(1, 0);
|
|
|
|
if (board[str] == EMPTY) {
|
|
/* The stones have already been captured, too late to defend. */
|
|
*attack_code = WIN;
|
|
*defense_code = 0;
|
|
return;
|
|
}
|
|
|
|
/* Fill in the extra eye if have_eye is 0. If filling in would be
|
|
* suicide the white stones can be considered dead.
|
|
*/
|
|
if (!have_eye) {
|
|
if (!eyegraph_trymove(POS(0, 0), WHITE, "tactical_life-A", NO_MOVE)) {
|
|
*attack_code = WIN;
|
|
*defense_code = 0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
*attack_code = 0;
|
|
*defense_code = 0;
|
|
|
|
/* Call tactical_life_attack() and tactical_life_defend() to
|
|
* determine status.
|
|
*/
|
|
if (tactical_life_attack(str, num_vertices, vertices, results)) {
|
|
*attack_code = WIN;
|
|
if (tactical_life_defend(str, num_vertices, vertices, results))
|
|
*defense_code = WIN;
|
|
}
|
|
else
|
|
*defense_code = WIN;
|
|
|
|
|
|
/* If the status is critical, try to play at each relevant vertex
|
|
* and call tactical_life_defend() or tactical_life_attack() to
|
|
* determine whether the move works as attack or defense.
|
|
*/
|
|
if (*attack_code != 0 && *defense_code != 0) {
|
|
if (num_attacks != NULL && attack_points != NULL) {
|
|
*num_attacks = 0;
|
|
num_moves = eyegraph_order_moves(num_vertices, vertices,
|
|
OTHER_COLOR(board[str]), moves);
|
|
for (k = 0; k < num_moves; k++) {
|
|
int move = moves[k];
|
|
if (eyegraph_trymove(move, OTHER_COLOR(board[str]), "tactical_life-B",
|
|
str)) {
|
|
if (board[str] == EMPTY
|
|
|| !tactical_life_defend(str, num_vertices, vertices, results))
|
|
attack_points[(*num_attacks)++] = move;
|
|
popgo();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (num_defenses != NULL && defense_points != NULL) {
|
|
*num_defenses = 0;
|
|
num_moves = eyegraph_order_moves(num_vertices, vertices, board[str],
|
|
moves);
|
|
for (k = 0; k < num_moves; k++) {
|
|
int move = moves[k];
|
|
if (eyegraph_trymove(move, board[str], "tactical_life-C", str)) {
|
|
if (!tactical_life_attack(str, num_vertices, vertices, results))
|
|
defense_points[(*num_defenses)++] = move;
|
|
popgo();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Unfill the extra eye if we didn't use it. */
|
|
if (!have_eye)
|
|
popgo();
|
|
}
|
|
|
|
/* Determine the eye value of the eyespace for the big white group on
|
|
* the board and vital moves. The possible eye values are documented
|
|
* in the preamble to eyes.db. By calling tactical_life() multiple
|
|
* times, with and without using an extra eye, we can compute the eye
|
|
* values. To determine ko threats and vital moves, tactical_life() is
|
|
* called again after trying to play on one of the relevant vertices.
|
|
* In order to find out whether ko threats really are effective and to
|
|
* distinguish between 0122/1122 and 0012/0011 eye values (see
|
|
* discussion on pattern 6141 in the preamble of eyes.db), we may also
|
|
* need to recursively call ourselves after a move has been made.
|
|
*/
|
|
static void
|
|
evaluate_eyespace(struct eyevalue *result, int num_vertices, int *vertices,
|
|
int *num_vital_attacks, int *vital_attacks,
|
|
int *num_vital_defenses, int *vital_defenses,
|
|
unsigned char *tactical_life_results)
|
|
{
|
|
int k;
|
|
int attack_code;
|
|
int num_attacks;
|
|
int attack_points[BOARDMAX];
|
|
int defense_code;
|
|
int num_defenses;
|
|
int defense_points[BOARDMAX];
|
|
int attack_code2;
|
|
int num_attacks2;
|
|
int attack_points2[BOARDMAX];
|
|
int defense_code2;
|
|
struct eyevalue result2;
|
|
int num_vital_attacks2;
|
|
int vital_attacks2[BOARDMAX];
|
|
int num_vital_defenses2;
|
|
int vital_defenses2[BOARDMAX];
|
|
int num_moves;
|
|
int moves[BOARDMAX];
|
|
|
|
*num_vital_attacks = 0;
|
|
*num_vital_defenses = 0;
|
|
|
|
/* Determine tactical life without an extra eye. */
|
|
tactical_life(0, num_vertices, vertices,
|
|
&attack_code, &num_attacks, attack_points,
|
|
&defense_code, &num_defenses, defense_points,
|
|
tactical_life_results);
|
|
|
|
if (attack_code == 0) {
|
|
/* Alive without extra eye.
|
|
* Possible results: 0222, 1222, 2222
|
|
*
|
|
* Determine whether there are ko threats and how serious.
|
|
*/
|
|
int a = 2;
|
|
|
|
if (sgf_dumptree)
|
|
sgftreeAddComment(sgf_dumptree, "Alive without extra eye.\n");
|
|
|
|
num_moves = eyegraph_order_moves(num_vertices, vertices, BLACK, moves);
|
|
for (k = 0; k < num_moves; k++) {
|
|
int acode, dcode;
|
|
int move = moves[k];
|
|
if (eyegraph_trymove(move, BLACK, "evaluate_eyespace-A", NO_MOVE)) {
|
|
tactical_life(0, num_vertices, vertices, &acode, NULL, NULL,
|
|
&dcode, NULL, NULL, tactical_life_results);
|
|
if (acode != 0) {
|
|
tactical_life(1, num_vertices, vertices, &acode, NULL, NULL,
|
|
&dcode, NULL, NULL, tactical_life_results);
|
|
if (acode != 0) {
|
|
if (a == 1)
|
|
*num_vital_attacks = 0;
|
|
a = 0;
|
|
vital_attacks[(*num_vital_attacks)++] = move;
|
|
if (sgf_dumptree)
|
|
sgftreeAddComment(sgf_dumptree,
|
|
"Ko threat to remove both eyes.\n");
|
|
}
|
|
else {
|
|
if (a != 0) {
|
|
vital_attacks[(*num_vital_attacks)++] = move;
|
|
a = 1;
|
|
}
|
|
if (sgf_dumptree)
|
|
sgftreeAddComment(sgf_dumptree, "Ko threat to remove one eye.\n");
|
|
}
|
|
}
|
|
popgo();
|
|
}
|
|
}
|
|
set_eyevalue(result, a, 2, 2, 2);
|
|
if (sgf_dumptree) {
|
|
if (a == 0)
|
|
sgftreeAddComment(sgf_dumptree, "Eyevalue 0222.\n");
|
|
else if (a == 1)
|
|
sgftreeAddComment(sgf_dumptree, "Eyevalue 1222.\n");
|
|
else
|
|
sgftreeAddComment(sgf_dumptree, "Eyevalue 2222.\n");
|
|
}
|
|
}
|
|
else if (defense_code != 0) {
|
|
/* Critical without extra eye.
|
|
* Possible results: 0022, 0122, 1122
|
|
*/
|
|
if (sgf_dumptree)
|
|
sgftreeAddComment(sgf_dumptree, "Critical without extra eye.\n");
|
|
tactical_life(1, num_vertices, vertices,
|
|
&attack_code2, &num_attacks2, attack_points2,
|
|
&defense_code2, NULL, NULL, tactical_life_results);
|
|
for (k = 0; k < num_defenses; k++)
|
|
vital_defenses[(*num_vital_defenses)++] = defense_points[k];
|
|
if (attack_code2 == WIN) {
|
|
/* A chimera. 0022. */
|
|
set_eyevalue(result, 0, 0, 2, 2);
|
|
for (k = 0; k < num_attacks2; k++)
|
|
vital_attacks[(*num_vital_attacks)++] = attack_points2[k];
|
|
if (sgf_dumptree)
|
|
sgftreeAddComment(sgf_dumptree, "Eyevalue: 0022.\n");
|
|
}
|
|
else {
|
|
int a = 1;
|
|
for (k = 0; k < num_attacks; k++) {
|
|
int move = attack_points[k];
|
|
if (eyegraph_trymove(move, BLACK, "evaluate_eyespace-B", NO_MOVE)) {
|
|
evaluate_eyespace(&result2, num_vertices, vertices,
|
|
&num_vital_attacks2, vital_attacks2,
|
|
&num_vital_defenses2, vital_defenses2,
|
|
tactical_life_results);
|
|
/* If result2 is 0011 for some move we have 0122 as final
|
|
* result, otherwise 1122.
|
|
*/
|
|
if (min_eyes(&result2) == 0
|
|
&& max_eyes(&result2) == 1
|
|
&& max_eye_threat(&result2) == 1) {
|
|
if (a == 1)
|
|
*num_vital_attacks = 0;
|
|
a = 0;
|
|
vital_attacks[(*num_vital_attacks)++] = move;
|
|
}
|
|
else if (a == 1)
|
|
vital_attacks[(*num_vital_attacks)++] = move;
|
|
popgo();
|
|
}
|
|
}
|
|
set_eyevalue(result, a, 1, 2, 2);
|
|
if (sgf_dumptree) {
|
|
if (a == 0)
|
|
sgftreeAddComment(sgf_dumptree, "Eyevalue: 0122.\n");
|
|
else
|
|
sgftreeAddComment(sgf_dumptree, "Eyevalue: 1122.\n");
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/* Dead without extra eye.
|
|
* Possible results: 0000, 0001, 0002, 0011, 0012, 0111, 0112, 1111, 1112
|
|
*
|
|
* Now determine tactical life with an extra eye.
|
|
*/
|
|
if (sgf_dumptree)
|
|
sgftreeAddComment(sgf_dumptree, "Dead without extra eye.\n");
|
|
tactical_life(1, num_vertices, vertices,
|
|
&attack_code, &num_attacks, attack_points,
|
|
&defense_code, &num_defenses, defense_points,
|
|
tactical_life_results);
|
|
if (attack_code == 0) {
|
|
/* Alive with extra eye.
|
|
* Possible results: 0111, 0112, 1111, 1112
|
|
*/
|
|
int a = 1;
|
|
int d = 1;
|
|
if (sgf_dumptree)
|
|
sgftreeAddComment(sgf_dumptree, "Alive with extra eye.\n");
|
|
num_moves = eyegraph_order_moves(num_vertices, vertices, BLACK, moves);
|
|
for (k = 0; k < num_moves; k++) {
|
|
int acode, dcode;
|
|
int move = moves[k];
|
|
if (eyegraph_trymove(move, BLACK, "evaluate_eyespace-C", NO_MOVE)) {
|
|
tactical_life(1, num_vertices, vertices, &acode, NULL, NULL,
|
|
&dcode, NULL, NULL, tactical_life_results);
|
|
if (acode != 0) {
|
|
evaluate_eyespace(&result2, num_vertices, vertices,
|
|
&num_vital_attacks2, vital_attacks2,
|
|
&num_vital_defenses2, vital_defenses2,
|
|
tactical_life_results);
|
|
/* This is either 0011 or 0012. Only the first is acceptable. */
|
|
if (max_eye_threat(&result2) == 1) {
|
|
vital_attacks[(*num_vital_attacks)++] = move;
|
|
a = 0;
|
|
if (sgf_dumptree)
|
|
sgftreeAddComment(sgf_dumptree, "Attacking ko threat.\n");
|
|
}
|
|
}
|
|
popgo();
|
|
}
|
|
}
|
|
|
|
num_moves = eyegraph_order_moves(num_vertices, vertices, WHITE, moves);
|
|
for (k = 0; k < num_moves; k++) {
|
|
int acode, dcode;
|
|
int move = moves[k];
|
|
if (eyegraph_trymove(move, WHITE, "evaluate_eyespace-D", NO_MOVE)) {
|
|
tactical_life(0, num_vertices, vertices, &acode, NULL, NULL,
|
|
&dcode, NULL, NULL, tactical_life_results);
|
|
if (dcode != 0) {
|
|
evaluate_eyespace(&result2, num_vertices, vertices,
|
|
&num_vital_attacks2, vital_attacks2,
|
|
&num_vital_defenses2, vital_defenses2,
|
|
tactical_life_results);
|
|
/* This is either 1122 or 0122. Only the first is acceptable. */
|
|
if (min_eye_threat(&result2) == 1) {
|
|
vital_defenses[(*num_vital_defenses)++] = move;
|
|
d = 2;
|
|
if (sgf_dumptree)
|
|
sgftreeAddComment(sgf_dumptree, "Defending ko threat.\n");
|
|
}
|
|
}
|
|
popgo();
|
|
}
|
|
}
|
|
set_eyevalue(result, a, 1, 1, d);
|
|
if (sgf_dumptree) {
|
|
if (a == 0 && d == 1)
|
|
sgftreeAddComment(sgf_dumptree, "Eyevalue 0111.\n");
|
|
else if (a == 0 && d == 2)
|
|
sgftreeAddComment(sgf_dumptree, "Eyevalue 0112.\n");
|
|
else if (a == 1 && d == 1)
|
|
sgftreeAddComment(sgf_dumptree, "Eyevalue 1111.\n");
|
|
else
|
|
sgftreeAddComment(sgf_dumptree, "Eyevalue 1112.\n");
|
|
}
|
|
}
|
|
else if (defense_code != 0) {
|
|
/* Critical with extra eye.
|
|
* Possible results: 0011, 0012
|
|
*/
|
|
int d = 1;
|
|
if (sgf_dumptree)
|
|
sgftreeAddComment(sgf_dumptree, "Critical with extra eye.\n");
|
|
for (k = 0; k < num_attacks; k++)
|
|
vital_attacks[(*num_vital_attacks)++] = attack_points[k];
|
|
for (k = 0; k < num_defenses; k++) {
|
|
int move = defense_points[k];
|
|
if (eyegraph_trymove(move, WHITE, "evaluate_eyespace-E", NO_MOVE)) {
|
|
evaluate_eyespace(&result2, num_vertices, vertices,
|
|
&num_vital_attacks2, vital_attacks2,
|
|
&num_vital_defenses2, vital_defenses2,
|
|
tactical_life_results);
|
|
/* If result2 is 1122 for some move we have 0012 as final
|
|
* result, otherwise 0011.
|
|
*/
|
|
if (min_eye_threat(&result2) == 1
|
|
&& min_eyes(&result2) == 1
|
|
&& max_eyes(&result2) == 2) {
|
|
if (d == 1)
|
|
*num_vital_defenses = 0;
|
|
d = 2;
|
|
vital_defenses[(*num_vital_defenses)++] = move;
|
|
}
|
|
else if (d == 1)
|
|
vital_defenses[(*num_vital_defenses)++] = move;
|
|
popgo();
|
|
}
|
|
}
|
|
set_eyevalue(result, 0, 0, 1, d);
|
|
if (sgf_dumptree) {
|
|
if (d == 1)
|
|
sgftreeAddComment(sgf_dumptree, "Eyevalue: 0011.\n");
|
|
else
|
|
sgftreeAddComment(sgf_dumptree, "Eyevalue: 0012.\n");
|
|
}
|
|
}
|
|
else {
|
|
/* Dead with extra eye.
|
|
* Possible results: 0000, 0001, 0002
|
|
*
|
|
* Determine whether there are ko threats and how serious.
|
|
*/
|
|
int d = 0;
|
|
if (sgf_dumptree)
|
|
sgftreeAddComment(sgf_dumptree, "Dead with extra eye.\n");
|
|
num_moves = eyegraph_order_moves(num_vertices, vertices, WHITE, moves);
|
|
for (k = 0; k < num_moves; k++) {
|
|
int acode, dcode;
|
|
int move = moves[k];
|
|
if (eyegraph_trymove(move, WHITE, "evaluate_eyespace-F", NO_MOVE)) {
|
|
tactical_life(1, num_vertices, vertices, &acode, NULL, NULL,
|
|
&dcode, NULL, NULL, tactical_life_results);
|
|
if (dcode != 0) {
|
|
tactical_life(0, num_vertices, vertices, &acode, NULL, NULL,
|
|
&dcode, NULL, NULL, tactical_life_results);
|
|
if (dcode != 0) {
|
|
if (d == 1)
|
|
*num_vital_defenses = 0;
|
|
d = 2;
|
|
vital_defenses[(*num_vital_defenses)++] = move;
|
|
if (sgf_dumptree)
|
|
sgftreeAddComment(sgf_dumptree,
|
|
"Ko threat to make two eyes.\n");
|
|
}
|
|
else {
|
|
if (d != 2) {
|
|
vital_defenses[(*num_vital_defenses)++] = move;
|
|
d = 1;
|
|
}
|
|
if (sgf_dumptree)
|
|
sgftreeAddComment(sgf_dumptree,
|
|
"Ko threat to make one eye.\n");
|
|
}
|
|
}
|
|
popgo();
|
|
}
|
|
}
|
|
set_eyevalue(result, 0, 0, 0, d);
|
|
if (sgf_dumptree) {
|
|
if (d == 0)
|
|
sgftreeAddComment(sgf_dumptree, "Eyevalue 0000.\n");
|
|
else if (d == 1)
|
|
sgftreeAddComment(sgf_dumptree, "Eyevalue 0001.\n");
|
|
else
|
|
sgftreeAddComment(sgf_dumptree, "Eyevalue 0002.\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Add small invincible black groups in contact with the marginal
|
|
* vertices, without destroying the connectivity of the white stones.
|
|
*
|
|
*/
|
|
static int
|
|
add_margins(int num_margins, int *margins, int mx[BOARDMAX])
|
|
{
|
|
int k;
|
|
int i, j;
|
|
int old_mx[BOARDMAX];
|
|
int pos;
|
|
|
|
if (num_margins == 0)
|
|
return 1;
|
|
|
|
memcpy(old_mx, mx, sizeof(old_mx));
|
|
|
|
pos = margins[num_margins - 1];
|
|
|
|
for (k = 0; k < 4; k++) {
|
|
int up = delta[k];
|
|
int right = delta[(k + 1) % 4];
|
|
|
|
if (!ON_BOARD(pos + up))
|
|
continue;
|
|
|
|
if (mx[pos + up] == WHITE
|
|
&& (!ON_BOARD(pos + up + right) || mx[pos + up + right] == WHITE)
|
|
&& (!ON_BOARD(pos + up - right) || mx[pos + up - right] == WHITE)) {
|
|
for (i = -3; i <= 0; i++) {
|
|
for (j = 2; j < 6; j++) {
|
|
if (white_area(mx, pos + j * up + i * right, up, right, pos, j)) {
|
|
int s = 1;
|
|
while (mx[pos + s * up] == WHITE) {
|
|
mx[pos + s * up] = BLACK;
|
|
s++;
|
|
}
|
|
if (add_margins(num_margins - 1, margins, mx))
|
|
return 1;
|
|
else
|
|
memcpy(mx, old_mx, sizeof(old_mx));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Analyze an eye graph to determine the eye value and vital moves.
|
|
*
|
|
* The eye graph is given by a string which is encoded with "%" for
|
|
* newlines and "O" for spaces. E.g., the eye graph
|
|
*
|
|
* !
|
|
* .X
|
|
* !...
|
|
*
|
|
* is encoded as "OO!%O.X%!...". (The encoding is needed for the GTP
|
|
* interface to this function.)
|
|
*
|
|
* The result is an eye value and a (nonencoded) pattern showing the
|
|
* vital moves, using the same notation as eyes.db. In the example above
|
|
* we would get the eye value 0112 and the graph (showing ko threat moves)
|
|
*
|
|
* @
|
|
* .X
|
|
* !.*.
|
|
*
|
|
* If the eye graph cannot be realized, 0 is returned, 1 otherwise.
|
|
*/
|
|
int
|
|
analyze_eyegraph(const char *coded_eyegraph, struct eyevalue *value,
|
|
char *analyzed_eyegraph)
|
|
{
|
|
int k;
|
|
int i, j;
|
|
int mini, minj;
|
|
int mx[BOARDMAX];
|
|
char mg[BOARDMAX];
|
|
int pos;
|
|
|
|
int num_vital_attacks;
|
|
int vital_attacks[BOARDMAX]; /* Way larger than necessary. */
|
|
int num_vital_defenses;
|
|
int vital_defenses[BOARDMAX]; /* Way larger than necessary. */
|
|
|
|
int maxwidth;
|
|
int current_width;
|
|
int num_rows;
|
|
int horizontal_edge;
|
|
int vertical_edge;
|
|
|
|
int num_margins;
|
|
int margins[BOARDMAX]; /* Way larger than necessary. */
|
|
|
|
int num_vertices;
|
|
int vertices[BOARDMAX]; /* Way larger than necessary. */
|
|
|
|
int table_size;
|
|
unsigned char *tactical_life_results;
|
|
|
|
if (0)
|
|
gprintf("Analyze eyegraph %s\n", coded_eyegraph);
|
|
|
|
/* Mark the eyespace in the mx array. We construct the position in
|
|
* the mx array and copy it to the actual board later.
|
|
*/
|
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
|
|
if (ON_BOARD(pos))
|
|
mx[pos] = WHITE;
|
|
|
|
/* Find out the size of the eye graph pattern so that we can center
|
|
* it properly.
|
|
*/
|
|
maxwidth = 0;
|
|
current_width = 0;
|
|
num_rows = 1;
|
|
horizontal_edge = -1;
|
|
vertical_edge = -1;
|
|
for (k = 0; k < (int) strlen(coded_eyegraph); k++) {
|
|
if (coded_eyegraph[k] == '\n')
|
|
continue;
|
|
if (coded_eyegraph[k] == '%') {
|
|
num_rows++;
|
|
if (current_width > maxwidth)
|
|
maxwidth = current_width;
|
|
current_width = 0;
|
|
}
|
|
else {
|
|
if (coded_eyegraph[k] == '-')
|
|
horizontal_edge = num_rows - 1;
|
|
else if (coded_eyegraph[k] == '|')
|
|
vertical_edge = current_width;
|
|
current_width++;
|
|
}
|
|
}
|
|
if (current_width > maxwidth)
|
|
maxwidth = current_width;
|
|
|
|
/* Cut out the eyespace from the solid white string. */
|
|
num_margins = 0;
|
|
num_vertices = 0;
|
|
|
|
if (horizontal_edge == 0)
|
|
mini = -1;
|
|
else if (horizontal_edge > 0)
|
|
mini = board_size - num_rows + 1;
|
|
else
|
|
mini = (board_size - num_rows) / 2;
|
|
|
|
if (vertical_edge == 0)
|
|
minj = -1;
|
|
else if (vertical_edge > 0)
|
|
minj = board_size - maxwidth + 1;
|
|
else
|
|
minj = (board_size - maxwidth) / 2;
|
|
|
|
i = mini;
|
|
j = minj;
|
|
for (k = 0; k < (int) strlen(coded_eyegraph); k++) {
|
|
char c = coded_eyegraph[k];
|
|
if (c == '\n')
|
|
continue;
|
|
if (c == '%') {
|
|
i++;
|
|
j = minj - 1;
|
|
}
|
|
else if (c == 'X' || c == '$')
|
|
mx[POS(i, j)] = BLACK;
|
|
else if (c == '.' || c == '*' || c == '<' || c == '>'
|
|
|| c == '!' || c == '@' || c == '(' || c == ')')
|
|
mx[POS(i, j)] = EMPTY;
|
|
if (c == '!' || c == '@' || c == '(' || c == ')' || c == '$')
|
|
margins[num_margins++] = POS(i, j);
|
|
if (c != '|' && c != '-' && c != '+' && c != '%'
|
|
&& ON_BOARD(POS(i, j)) && mx[POS(i, j)] != WHITE)
|
|
vertices[num_vertices++] = POS(i, j);
|
|
j++;
|
|
}
|
|
|
|
/* Add an invincible black group in the lower left plus two outer
|
|
* liberties for the white string. However, if the eyespace is
|
|
* placed in or near the lower left corner, we put this group in the
|
|
* upper right instead.
|
|
*/
|
|
pos = POS(board_size - 2, 1);
|
|
if ((vertical_edge == 0 && horizontal_edge != 0)
|
|
|| (horizontal_edge > 0 && vertical_edge <= 0))
|
|
pos = POS(1, board_size - 2);
|
|
mx[pos] = EMPTY;
|
|
mx[NORTH(pos)] = BLACK;
|
|
mx[NW(pos)] = BLACK;
|
|
mx[NE(pos)] = EMPTY;
|
|
mx[WEST(pos)] = BLACK;
|
|
mx[EAST(pos)] = BLACK;
|
|
mx[SW(pos)] = EMPTY;
|
|
mx[SOUTH(pos)] = BLACK;
|
|
mx[SE(pos)] = BLACK;
|
|
if (ON_BOARD(NN(pos)))
|
|
mx[NN(pos)] = EMPTY;
|
|
else
|
|
mx[SS(pos)] = EMPTY;
|
|
|
|
/* Add the two outer liberties in the lower left or upper right to
|
|
* the list of vertices.
|
|
*/
|
|
if (ON_BOARD(NN(pos))) {
|
|
vertices[num_vertices++] = NE(pos);
|
|
vertices[num_vertices++] = NN(pos);
|
|
}
|
|
else {
|
|
vertices[num_vertices++] = SW(pos);
|
|
vertices[num_vertices++] = SS(pos);
|
|
}
|
|
|
|
/* Add an extra eye in the upper left corner. */
|
|
mx[POS(0, 0)] = EMPTY;
|
|
vertices[num_vertices++] = POS(0, 0);
|
|
|
|
if (!add_margins(num_margins, margins, mx))
|
|
return 0;
|
|
|
|
/* Copy the mx array over to the board. */
|
|
clear_board();
|
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
|
|
if (ON_BOARD(pos)) {
|
|
if (mx[pos] == WHITE)
|
|
add_stone(pos, WHITE);
|
|
else if (mx[pos] == BLACK)
|
|
add_stone(pos, BLACK);
|
|
}
|
|
|
|
if (verbose)
|
|
showboard(0);
|
|
|
|
/* If there are any isolated O stones, those should also be added to
|
|
* the playable vertices.
|
|
*/
|
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
|
|
if (board[pos] == WHITE && !same_string(pos, POS(1, 0))) {
|
|
vertices[num_vertices] = vertices[num_vertices - 1];
|
|
vertices[num_vertices - 1] = vertices[num_vertices - 2];
|
|
vertices[num_vertices - 2] = vertices[num_vertices - 3];
|
|
vertices[num_vertices - 3] = pos;
|
|
num_vertices++;
|
|
}
|
|
|
|
if (verbose) {
|
|
int k;
|
|
gprintf("\nPlayable vertices:\n");
|
|
for (k = 0; k < num_vertices; k++)
|
|
gprintf("%1m ", vertices[k]);
|
|
gprintf("\n\n");
|
|
}
|
|
|
|
/* Disable this test if you need to evaluate larger eyespaces, have
|
|
* no shortage of memory, and know what you're doing.
|
|
*/
|
|
if (num_vertices > 17) {
|
|
gprintf("analyze_eyegraph: too large eyespace, %d vertices\n",
|
|
num_vertices);
|
|
gg_assert(num_vertices <= 17);
|
|
}
|
|
|
|
/* The cache must have 2*3^num_vertices entries. */
|
|
table_size = 2;
|
|
for (k = 0; k < num_vertices; k++)
|
|
table_size *= 3;
|
|
|
|
/* Allocate memory for the cache. */
|
|
tactical_life_results = malloc(table_size);
|
|
if (!tactical_life_results) {
|
|
gprintf("analyze_eyegraph: failed to allocate %d bytes\n", table_size);
|
|
gg_assert(tactical_life_results != NULL);
|
|
}
|
|
memset(tactical_life_results, 0, table_size);
|
|
|
|
if (sgf_dumptree)
|
|
sgffile_printboard(sgf_dumptree);
|
|
|
|
/* Evaluate the eyespace on the board. */
|
|
evaluate_eyespace(value, num_vertices, vertices,
|
|
&num_vital_attacks, vital_attacks,
|
|
&num_vital_defenses, vital_defenses,
|
|
tactical_life_results);
|
|
|
|
/* Return the cache memory. */
|
|
free(tactical_life_results);
|
|
|
|
if (verbose) {
|
|
gprintf("Eyevalue: %s\n", eyevalue_to_string(value));
|
|
for (k = 0; k < num_vital_attacks; k++)
|
|
gprintf(" vital attack point %1m\n", vital_attacks[k]);
|
|
for (k = 0; k < num_vital_defenses; k++)
|
|
gprintf(" vital defense point %1m\n", vital_defenses[k]);
|
|
}
|
|
|
|
/* Encode the attack and defense points with symbols in the mg[] array. */
|
|
memset(mg, ' ', sizeof(mg));
|
|
|
|
for (k = 0; k < num_vertices - 2; k++)
|
|
mg[vertices[k]] = (board[vertices[k]] == BLACK ? 'X' : '.');
|
|
|
|
for (k = 0; k < num_margins; k++)
|
|
mg[margins[k]] = (mg[margins[k]] == 'X' ? '$' : '!');
|
|
|
|
for (k = 0; k < num_vital_attacks; k++)
|
|
mg[vital_attacks[k]] = (mg[vital_attacks[k]] == '!' ? '(' : '<');
|
|
|
|
for (k = 0; k < num_vital_defenses; k++) {
|
|
int pos = vital_defenses[k];
|
|
if (mg[pos] == '.')
|
|
mg[pos] = '>';
|
|
else if (mg[pos] == '!')
|
|
mg[pos] = ')';
|
|
else if (mg[pos] == '<')
|
|
mg[pos] = '*';
|
|
else if (mg[pos] == '(')
|
|
mg[pos] = '@';
|
|
}
|
|
|
|
/* Return the central part of the mg[] array (corresponding to the
|
|
* input eye graph).
|
|
*/
|
|
k = 0;
|
|
for (i = mini; i < mini + num_rows; i++) {
|
|
for (j = minj; j < minj + maxwidth; j++) {
|
|
if ((i < 0 || i >= board_size) && (j < 0 || j >= board_size))
|
|
analyzed_eyegraph[k++] = '+';
|
|
else if (i < 0 || i >= board_size)
|
|
analyzed_eyegraph[k++] = '-';
|
|
else if (j < 0 || j >= board_size)
|
|
analyzed_eyegraph[k++] = '|';
|
|
else
|
|
analyzed_eyegraph[k++] = mg[POS(i, j)];
|
|
}
|
|
analyzed_eyegraph[k++] = '\n';
|
|
}
|
|
analyzed_eyegraph[k - 1] = 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/*
|
|
* Local Variables:
|
|
* tab-width: 8
|
|
* c-basic-offset: 2
|
|
* End:
|
|
*/
|