2606 lines
76 KiB
C
2606 lines
76 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. *
|
||
|
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||
|
|
||
|
/* A "dragon" is a union of strings of the same color which will be
|
||
|
* treated as a unit. The dragons are generated anew at each
|
||
|
* move. If two strings are in the dragon, it is GNU Go's working
|
||
|
* hypothesis that they will live or die together and are
|
||
|
* effectively connected.
|
||
|
*
|
||
|
* _____/| (! !)
|
||
|
* / ____/| /@ @)
|
||
|
* / / __ // +--oo
|
||
|
* | / | >> /< _-v--}
|
||
|
* | | UUU\\\ / / \\
|
||
|
* | | __ _\\\ \ \ U
|
||
|
* | | / V \\--> \ \
|
||
|
* | <_/ \_/ }
|
||
|
* | __ ____ /
|
||
|
* \ / \___/ / /\
|
||
|
* < \< < <\ \
|
||
|
* ( ))) ( )))))
|
||
|
*/
|
||
|
|
||
|
#include "gnugo.h"
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <ctype.h>
|
||
|
|
||
|
#include "liberty.h"
|
||
|
#include "gg_utils.h"
|
||
|
|
||
|
static void initialize_supplementary_dragon_data(void);
|
||
|
static void find_lunches(void);
|
||
|
static void eye_computations(void);
|
||
|
static void revise_inessentiality(void);
|
||
|
static void find_neighbor_dragons(void);
|
||
|
static void add_adjacent_dragons(int a, int b);
|
||
|
static void add_adjacent_dragon(int a, int b);
|
||
|
static int dragon_invincible(int pos);
|
||
|
static int dragon_looks_inessential(int origin);
|
||
|
static void identify_thrashing_dragons(void);
|
||
|
static void analyze_false_eye_territory(void);
|
||
|
static int connected_to_eye(int pos, int str, int color, int eye_color,
|
||
|
struct eye_data *eye);
|
||
|
static void connected_to_eye_recurse(int pos, int str, int color,
|
||
|
int eye_color, struct eye_data *eye,
|
||
|
signed char *mx, signed char *me,
|
||
|
int *halfeyes);
|
||
|
static enum dragon_status compute_crude_status(int pos);
|
||
|
static int compute_escape(int pos, int dragon_status_known);
|
||
|
static void compute_surrounding_moyo_sizes(const struct influence_data *q);
|
||
|
static void clear_cut_list(void);
|
||
|
|
||
|
static int dragon2_initialized;
|
||
|
static int lively_white_dragons;
|
||
|
static int lively_black_dragons;
|
||
|
|
||
|
/* This is a private array to obtain a list of worms belonging to each
|
||
|
* dragon. Public access is via first_worm_in_dragon() and
|
||
|
* next_worm_in_dragon().
|
||
|
*/
|
||
|
static int next_worm_list[BOARDMAX];
|
||
|
|
||
|
/* Alternative for DRAGON2 macro with asserts. */
|
||
|
struct dragon_data2 *
|
||
|
dragon2_func(int pos)
|
||
|
{
|
||
|
ASSERT1(ON_BOARD1(pos)
|
||
|
&& dragon[pos].id >= 0
|
||
|
&& dragon[pos].id < number_of_dragons, pos);
|
||
|
return &dragon2[dragon[pos].id];
|
||
|
}
|
||
|
|
||
|
/* This basic function finds all dragons and collects some basic information
|
||
|
* about them in the dragon array.
|
||
|
*
|
||
|
* color is the player in turn to move. This does in no way affect the
|
||
|
* information collected about the dragons, but it does affect what
|
||
|
* information is passed on to the move generation code. If
|
||
|
* color == EMPTY no information at all is passed on to the move generation.
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
make_dragons(int stop_before_owl)
|
||
|
{
|
||
|
int str;
|
||
|
int d;
|
||
|
|
||
|
dragon2_initialized = 0;
|
||
|
initialize_dragon_data();
|
||
|
|
||
|
/* Find explicit connections patterns in database and amalgamate
|
||
|
* involved dragons.
|
||
|
*/
|
||
|
memset(cutting_points, 0, sizeof(cutting_points));
|
||
|
find_cuts();
|
||
|
find_connections();
|
||
|
|
||
|
/* At this time, all dragons have been finalized and we can
|
||
|
* initialize the dragon2[] array. After that we can no longer allow
|
||
|
* amalgamation of dragons.
|
||
|
*/
|
||
|
initialize_supplementary_dragon_data();
|
||
|
|
||
|
make_domains(black_eye, white_eye, 0);
|
||
|
|
||
|
/* Find adjacent worms which can be easily captured: */
|
||
|
find_lunches();
|
||
|
|
||
|
/* Find topological half eyes and false eyes. */
|
||
|
find_half_and_false_eyes(BLACK, black_eye, half_eye, NULL);
|
||
|
find_half_and_false_eyes(WHITE, white_eye, half_eye, NULL);
|
||
|
|
||
|
/* Compute the number of eyes, half eyes, determine attack/defense points
|
||
|
* etc. for all eye spaces. */
|
||
|
eye_computations();
|
||
|
/* Try to determine whether topologically false and half eye points
|
||
|
* contribute to territory even if the eye doesn't solidify.
|
||
|
*/
|
||
|
analyze_false_eye_territory();
|
||
|
|
||
|
/* Now we compute the genus. */
|
||
|
for (d = 0; d < number_of_dragons; d++)
|
||
|
compute_dragon_genus(dragon2[d].origin, &dragon2[d].genus, NO_MOVE);
|
||
|
|
||
|
/* Compute the escape route measure. */
|
||
|
for (str = BOARDMIN; str < BOARDMAX; str++)
|
||
|
if (IS_STONE(board[str]) && dragon[str].origin == str)
|
||
|
DRAGON2(str).escape_route = compute_escape(str, 0);
|
||
|
|
||
|
/* Set dragon weaknesses according to initial_influence. */
|
||
|
compute_refined_dragon_weaknesses();
|
||
|
for (d = 0; d < number_of_dragons; d++)
|
||
|
dragon2[d].weakness_pre_owl = dragon2[d].weakness;
|
||
|
|
||
|
/* Determine status: ALIVE, DEAD, CRITICAL or UNKNOWN */
|
||
|
for (str = BOARDMIN; str < BOARDMAX; str++)
|
||
|
if (ON_BOARD(str))
|
||
|
if (dragon[str].origin == str && board[str])
|
||
|
dragon[str].crude_status = compute_crude_status(str);
|
||
|
|
||
|
/* We must update the dragon status at every intersection before we
|
||
|
* call the owl code. This updates all fields.
|
||
|
*/
|
||
|
for (str = BOARDMIN; str < BOARDMAX; str++)
|
||
|
if (ON_BOARD(str) && board[str] != EMPTY)
|
||
|
dragon[str] = dragon[dragon[str].origin];
|
||
|
|
||
|
find_neighbor_dragons();
|
||
|
|
||
|
for (d = 0; d < number_of_dragons; d++) {
|
||
|
dragon2[d].surround_status
|
||
|
= compute_surroundings(dragon2[d].origin, NO_MOVE, 0,
|
||
|
&(dragon2[d].surround_size));
|
||
|
if (dragon2[d].surround_status == SURROUNDED) {
|
||
|
dragon2[d].escape_route = 0;
|
||
|
if (debug & DEBUG_DRAGONS)
|
||
|
gprintf("surrounded dragon found at %1m\n", dragon2[d].origin);
|
||
|
}
|
||
|
else if (dragon2[d].surround_status == WEAKLY_SURROUNDED) {
|
||
|
dragon2[d].escape_route /= 2;
|
||
|
if (debug & DEBUG_DRAGONS)
|
||
|
gprintf("weakly surrounded dragon found at %1m\n", dragon2[d].origin);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (stop_before_owl)
|
||
|
return;
|
||
|
|
||
|
/* Determine life and death status of each dragon using the owl code
|
||
|
* if necessary.
|
||
|
*/
|
||
|
start_timer(2);
|
||
|
for (str = BOARDMIN; str < BOARDMAX; str++)
|
||
|
if (ON_BOARD(str)) {
|
||
|
int attack_point = NO_MOVE;
|
||
|
int defense_point = NO_MOVE;
|
||
|
struct eyevalue no_eyes;
|
||
|
set_eyevalue(&no_eyes, 0, 0, 0, 0);
|
||
|
|
||
|
if (board[str] == EMPTY
|
||
|
|| dragon[str].origin != str)
|
||
|
continue;
|
||
|
|
||
|
/* Some dragons can be ignored but be extra careful with big dragons. */
|
||
|
if (crude_dragon_weakness(ALIVE, &no_eyes, 0,
|
||
|
DRAGON2(str).moyo_territorial_value,
|
||
|
DRAGON2(str).escape_route - 10)
|
||
|
< 0.00001 + gg_max(0.12, 0.32 - 0.01*dragon[str].effective_size)) {
|
||
|
DRAGON2(str).owl_status = UNCHECKED;
|
||
|
DRAGON2(str).owl_attack_point = NO_MOVE;
|
||
|
DRAGON2(str).owl_defense_point = NO_MOVE;
|
||
|
}
|
||
|
else {
|
||
|
int acode = 0;
|
||
|
int dcode = 0;
|
||
|
int kworm = NO_MOVE;
|
||
|
int owl_nodes_before = get_owl_node_counter();
|
||
|
start_timer(3);
|
||
|
acode = owl_attack(str, &attack_point,
|
||
|
&DRAGON2(str).owl_attack_certain, &kworm);
|
||
|
DRAGON2(str).owl_attack_node_count
|
||
|
= get_owl_node_counter() - owl_nodes_before;
|
||
|
if (acode != 0) {
|
||
|
DRAGON2(str).owl_attack_point = attack_point;
|
||
|
DRAGON2(str).owl_attack_code = acode;
|
||
|
DRAGON2(str).owl_attack_kworm = kworm;
|
||
|
if (attack_point != NO_MOVE) {
|
||
|
kworm = NO_MOVE;
|
||
|
dcode = owl_defend(str, &defense_point,
|
||
|
&DRAGON2(str).owl_defense_certain, &kworm);
|
||
|
if (dcode != 0) {
|
||
|
if (defense_point != NO_MOVE) {
|
||
|
DRAGON2(str).owl_status = (acode == GAIN ? ALIVE : CRITICAL);
|
||
|
DRAGON2(str).owl_defense_point = defense_point;
|
||
|
DRAGON2(str).owl_defense_code = dcode;
|
||
|
DRAGON2(str).owl_defense_kworm = kworm;
|
||
|
}
|
||
|
else {
|
||
|
/* Due to irregularities in the owl code, it may
|
||
|
* occasionally happen that a dragon is found to be
|
||
|
* attackable but also alive as it stands. In this case
|
||
|
* we still choose to say that the owl_status is
|
||
|
* CRITICAL, although we don't have any defense move to
|
||
|
* propose. Having the status right is important e.g.
|
||
|
* for connection moves to be properly valued.
|
||
|
*/
|
||
|
DRAGON2(str).owl_status = (acode == GAIN ? ALIVE : CRITICAL);
|
||
|
DEBUG(DEBUG_OWL_PERFORMANCE,
|
||
|
"Inconsistent owl attack and defense results for %1m.\n",
|
||
|
str);
|
||
|
/* Let's see whether the attacking move might be the right
|
||
|
* defense:
|
||
|
*/
|
||
|
dcode = owl_does_defend(DRAGON2(str).owl_attack_point,
|
||
|
str, NULL);
|
||
|
if (dcode != 0) {
|
||
|
DRAGON2(str).owl_defense_point
|
||
|
= DRAGON2(str).owl_attack_point;
|
||
|
DRAGON2(str).owl_defense_code = dcode;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (dcode == 0) {
|
||
|
DRAGON2(str).owl_status = DEAD;
|
||
|
DRAGON2(str).owl_defense_point = NO_MOVE;
|
||
|
DRAGON2(str).owl_defense_code = 0;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
if (!DRAGON2(str).owl_attack_certain) {
|
||
|
kworm = NO_MOVE;
|
||
|
dcode = owl_defend(str, &defense_point,
|
||
|
&DRAGON2(str).owl_defense_certain, &kworm);
|
||
|
if (dcode != 0) {
|
||
|
/* If the result of owl_attack was not certain, we may
|
||
|
* still want the result of owl_defend */
|
||
|
DRAGON2(str).owl_defense_point = defense_point;
|
||
|
DRAGON2(str).owl_defense_code = dcode;
|
||
|
DRAGON2(str).owl_defense_kworm = kworm;
|
||
|
}
|
||
|
}
|
||
|
DRAGON2(str).owl_status = ALIVE;
|
||
|
DRAGON2(str).owl_attack_point = NO_MOVE;
|
||
|
DRAGON2(str).owl_attack_code = 0;
|
||
|
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
time_report(2, " owl reading", NO_MOVE, 1.0);
|
||
|
|
||
|
/* Compute the status to be used by the matcher. We most trust the
|
||
|
* owl status, if it is available. If it's not we assume that we are
|
||
|
* already confident that the dragon is alive, regardless of
|
||
|
* crude_status.
|
||
|
*/
|
||
|
for (str = BOARDMIN; str < BOARDMAX; str++)
|
||
|
if (IS_STONE(board[str])) {
|
||
|
if (DRAGON2(str).owl_status != UNCHECKED)
|
||
|
dragon[str].status = DRAGON2(str).owl_status;
|
||
|
else
|
||
|
dragon[str].status = ALIVE;
|
||
|
}
|
||
|
|
||
|
/* The dragon data is now correct at the origin of each dragon but
|
||
|
* we need to copy it to every vertex.
|
||
|
*/
|
||
|
for (str = BOARDMIN; str < BOARDMAX; str++)
|
||
|
if (ON_BOARD(str) && board[str] != EMPTY)
|
||
|
dragon[str] = dragon[dragon[str].origin];
|
||
|
|
||
|
identify_thrashing_dragons();
|
||
|
|
||
|
/* Owl threats. */
|
||
|
for (str = BOARDMIN; str < BOARDMAX; str++)
|
||
|
if (ON_BOARD(str)
|
||
|
&& board[str] != EMPTY
|
||
|
&& dragon[str].origin == str) {
|
||
|
struct eyevalue no_eyes;
|
||
|
set_eyevalue(&no_eyes, 0, 0, 0, 0);
|
||
|
if (crude_dragon_weakness(ALIVE, &no_eyes, 0,
|
||
|
DRAGON2(str).moyo_territorial_value,
|
||
|
DRAGON2(str).escape_route - 10)
|
||
|
< 0.00001 + gg_max(0.12, 0.32 - 0.01*dragon[str].effective_size)) {
|
||
|
DRAGON2(str).owl_threat_status = UNCHECKED;
|
||
|
DRAGON2(str).owl_second_attack_point = NO_MOVE;
|
||
|
DRAGON2(str).owl_second_defense_point = NO_MOVE;
|
||
|
}
|
||
|
else {
|
||
|
int acode = DRAGON2(str).owl_attack_code;
|
||
|
int dcode = DRAGON2(str).owl_defense_code;
|
||
|
int defense_point, second_defense_point;
|
||
|
|
||
|
if (get_level() >= 8
|
||
|
&& !disable_threat_computation
|
||
|
&& (owl_threats
|
||
|
|| thrashing_stone[str])) {
|
||
|
if (acode && !dcode && DRAGON2(str).owl_attack_point != NO_MOVE) {
|
||
|
if (owl_threaten_defense(str, &defense_point,
|
||
|
&second_defense_point)) {
|
||
|
DRAGON2(str).owl_threat_status = CAN_THREATEN_DEFENSE;
|
||
|
DRAGON2(str).owl_defense_point = defense_point;
|
||
|
DRAGON2(str).owl_second_defense_point = second_defense_point;
|
||
|
}
|
||
|
else
|
||
|
DRAGON2(str).owl_threat_status = DEAD;
|
||
|
}
|
||
|
else if (!acode) {
|
||
|
int attack_point, second_attack_point;
|
||
|
if (owl_threaten_attack(str,
|
||
|
&attack_point, &second_attack_point)) {
|
||
|
DRAGON2(str).owl_threat_status = CAN_THREATEN_ATTACK;
|
||
|
DRAGON2(str).owl_attack_point = attack_point;
|
||
|
DRAGON2(str).owl_second_attack_point = second_attack_point;
|
||
|
}
|
||
|
else
|
||
|
DRAGON2(str).owl_threat_status = ALIVE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Once again, the dragon data is now correct at the origin of each dragon
|
||
|
* but we need to copy it to every vertex.
|
||
|
*/
|
||
|
for (str = BOARDMIN; str < BOARDMAX; str++)
|
||
|
if (ON_BOARD(str) && board[str] != EMPTY)
|
||
|
dragon[str] = dragon[dragon[str].origin];
|
||
|
|
||
|
time_report(2, " owl threats ", NO_MOVE, 1.0);
|
||
|
|
||
|
|
||
|
/* Compute the safety value. */
|
||
|
for (d = 0; d < number_of_dragons; d++) {
|
||
|
int true_genus;
|
||
|
int origin = dragon2[d].origin;
|
||
|
struct eyevalue *genus = &dragon2[d].genus;
|
||
|
|
||
|
/* FIXME: We lose information when constructing true_genus. This
|
||
|
* code can be improved.
|
||
|
*/
|
||
|
true_genus = max_eyes(genus) + min_eyes(genus);
|
||
|
if (dragon_looks_inessential(origin))
|
||
|
dragon2[d].safety = INESSENTIAL;
|
||
|
else if (dragon[origin].size == worm[origin].size
|
||
|
&& worm[origin].attack_codes[0] != 0
|
||
|
&& worm[origin].defense_codes[0] == 0)
|
||
|
dragon2[d].safety = TACTICALLY_DEAD;
|
||
|
else if (0) /* Seki is detected by the call to semeai() below. */
|
||
|
dragon2[d].safety = ALIVE_IN_SEKI;
|
||
|
else if (dragon_invincible(origin)) {
|
||
|
dragon2[d].safety = INVINCIBLE;
|
||
|
/* Sometimes the owl analysis may have misevaluated invincible
|
||
|
* dragons, typically if they live by topologically false eyes.
|
||
|
* Therefore we also set the status here.
|
||
|
*/
|
||
|
DRAGON(d).status = ALIVE;
|
||
|
}
|
||
|
else if (dragon2[d].owl_status == DEAD)
|
||
|
dragon2[d].safety = DEAD;
|
||
|
else if (dragon2[d].owl_status == CRITICAL)
|
||
|
dragon2[d].safety = CRITICAL;
|
||
|
else if (true_genus >= 6 || dragon2[d].moyo_size > 20)
|
||
|
dragon2[d].safety = STRONGLY_ALIVE;
|
||
|
else
|
||
|
dragon2[d].safety = ALIVE;
|
||
|
}
|
||
|
|
||
|
/* The status is now correct at the origin of each dragon
|
||
|
* but we need to copy it to every vertex.
|
||
|
*/
|
||
|
for (str = BOARDMIN; str < BOARDMAX; str++)
|
||
|
if (ON_BOARD(str))
|
||
|
dragon[str].status = dragon[dragon[str].origin].status;
|
||
|
|
||
|
/* Revise inessentiality of critical worms and dragons. */
|
||
|
revise_inessentiality();
|
||
|
|
||
|
semeai();
|
||
|
time_report(2, " semeai module", NO_MOVE, 1.0);
|
||
|
|
||
|
/* Count the non-dead dragons. */
|
||
|
lively_white_dragons = 0;
|
||
|
lively_black_dragons = 0;
|
||
|
for (d = 0; d < number_of_dragons; d++)
|
||
|
if (DRAGON(d).status != DEAD) {
|
||
|
if (DRAGON(d).color == WHITE)
|
||
|
lively_white_dragons++;
|
||
|
else
|
||
|
lively_black_dragons++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Find capturable worms adjacent to each dragon. */
|
||
|
static void
|
||
|
find_lunches()
|
||
|
{
|
||
|
int str;
|
||
|
for (str = BOARDMIN; str < BOARDMAX; str++)
|
||
|
if (ON_BOARD(str)) {
|
||
|
int food;
|
||
|
|
||
|
if (worm[str].origin != str
|
||
|
|| board[str] == EMPTY
|
||
|
|| worm[str].lunch == NO_MOVE)
|
||
|
continue;
|
||
|
|
||
|
food = worm[str].lunch;
|
||
|
|
||
|
/* In contrast to worm lunches, a dragon lunch must also be
|
||
|
* able to defend itself.
|
||
|
*/
|
||
|
if (worm[food].defense_codes[0] == 0)
|
||
|
continue;
|
||
|
|
||
|
/* Tell the move generation code about the lunch. */
|
||
|
add_lunch(str, food);
|
||
|
|
||
|
/* If several lunches are found, we pick the juiciest.
|
||
|
* First maximize cutstone, then minimize liberties.
|
||
|
*/
|
||
|
{
|
||
|
int origin = dragon[str].origin;
|
||
|
int lunch = DRAGON2(origin).lunch;
|
||
|
|
||
|
if (lunch == NO_MOVE
|
||
|
|| worm[food].cutstone > worm[lunch].cutstone
|
||
|
|| (worm[food].cutstone == worm[lunch].cutstone
|
||
|
&& (worm[food].liberties < worm[lunch].liberties))) {
|
||
|
DRAGON2(origin).lunch = worm[food].origin;
|
||
|
TRACE("at %1m setting %1m.lunch to %1m (cutstone=%d)\n",
|
||
|
str, origin,
|
||
|
worm[food].origin, worm[food].cutstone);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Compute the value of each eye space. Store its attack and defense point.
|
||
|
* A more comlete list of attack and defense points is stored in the lists
|
||
|
* black_vital_points and white_vital_points.
|
||
|
*/
|
||
|
static void
|
||
|
eye_computations()
|
||
|
{
|
||
|
int str;
|
||
|
|
||
|
for (str = BOARDMIN; str < BOARDMAX; str++) {
|
||
|
if (!ON_BOARD(str))
|
||
|
continue;
|
||
|
|
||
|
if (black_eye[str].color == BLACK
|
||
|
&& black_eye[str].origin == str) {
|
||
|
struct eyevalue value;
|
||
|
int attack_point, defense_point;
|
||
|
|
||
|
compute_eyes(str, &value, &attack_point, &defense_point,
|
||
|
black_eye, half_eye, 1);
|
||
|
DEBUG(DEBUG_EYES, "Black eyespace at %1m: %s\n", str,
|
||
|
eyevalue_to_string(&value));
|
||
|
black_eye[str].value = value;
|
||
|
propagate_eye(str, black_eye);
|
||
|
}
|
||
|
|
||
|
if (white_eye[str].color == WHITE
|
||
|
&& white_eye[str].origin == str) {
|
||
|
struct eyevalue value;
|
||
|
int attack_point, defense_point;
|
||
|
|
||
|
compute_eyes(str, &value, &attack_point, &defense_point,
|
||
|
white_eye, half_eye, 1);
|
||
|
DEBUG(DEBUG_EYES, "White eyespace at %1m: %s\n", str,
|
||
|
eyevalue_to_string(&value));
|
||
|
white_eye[str].value = value;
|
||
|
propagate_eye(str, white_eye);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* This function revises the inessentiality of critical worms and dragons
|
||
|
* according to the criteria explained in the comments below.
|
||
|
*/
|
||
|
static void
|
||
|
revise_inessentiality()
|
||
|
{
|
||
|
int str;
|
||
|
/* Revise essentiality of critical worms. Specifically, a critical
|
||
|
* worm which is adjacent to no enemy dragon with status
|
||
|
* better than DEAD, is considered INESSENTIAL.
|
||
|
*
|
||
|
* A typical case of this is
|
||
|
*
|
||
|
* |.XXXX
|
||
|
* |.OO.X
|
||
|
* |X.O.X
|
||
|
* |.OO.X
|
||
|
* +-----
|
||
|
*
|
||
|
* However, to be able to deal with the position
|
||
|
*
|
||
|
* |.XXXX
|
||
|
* |.OOOO
|
||
|
* |..O.O
|
||
|
* |X.OOO
|
||
|
* |..O.O
|
||
|
* +-----
|
||
|
*
|
||
|
* we need to extend "adjacent" to "adjacent or shares a liberty",
|
||
|
* which is why we use extended_chainlinks() rather than
|
||
|
* chainlinks().
|
||
|
*
|
||
|
* Finally, if the position above is slightly modified to
|
||
|
*
|
||
|
* |.XXXXX
|
||
|
* |.OOOOO
|
||
|
* |...O.O
|
||
|
* |X..OOO
|
||
|
* |...O.O
|
||
|
* +------
|
||
|
*
|
||
|
* we have a position where the critical X stone doesn't share a
|
||
|
* liberty with any string at all. Thus the revised rule is:
|
||
|
*
|
||
|
* A critical worm which is adjacent to or share a liberty with at
|
||
|
* least one dead opponent dragon and no opponent dragon which is
|
||
|
* not dead, is considered inessential.
|
||
|
*/
|
||
|
|
||
|
for (str = BOARDMIN; str < BOARDMAX; str++)
|
||
|
if (ON_BOARD(str)) {
|
||
|
if (is_worm_origin(str, str)
|
||
|
&& worm[str].attack_codes[0] != 0
|
||
|
&& worm[str].defense_codes[0] != 0
|
||
|
&& !worm[str].inessential) {
|
||
|
int adjs[MAXCHAIN];
|
||
|
int neighbors;
|
||
|
int r;
|
||
|
int essential = 0;
|
||
|
|
||
|
neighbors = extended_chainlinks(str, adjs, 0);
|
||
|
for (r = 0; r < neighbors; r++)
|
||
|
if (dragon[adjs[r]].status != DEAD) {
|
||
|
essential = 1;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (!essential && neighbors > 0) {
|
||
|
DEBUG(DEBUG_WORMS, "Worm %1m revised to be inessential.\n", str);
|
||
|
worm[str].inessential = 1;
|
||
|
propagate_worm(str);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Revise essentiality of critical dragons. Specifically, a critical
|
||
|
* dragon consisting entirely of inessential worms is considered
|
||
|
* INESSENTIAL.
|
||
|
*/
|
||
|
for (str = BOARDMIN; str < BOARDMAX; str++) {
|
||
|
if (ON_BOARD(str)
|
||
|
&& board[str] != EMPTY
|
||
|
&& dragon[str].origin == str
|
||
|
&& DRAGON2(str).safety == CRITICAL) {
|
||
|
int w;
|
||
|
for (w = first_worm_in_dragon(str); w != NO_MOVE;
|
||
|
w = next_worm_in_dragon(w)) {
|
||
|
if (!worm[w].inessential)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (w == NO_MOVE) {
|
||
|
DEBUG(DEBUG_DRAGONS, "Dragon %1m revised to be inessential.\n", str);
|
||
|
DRAGON2(str).safety = INESSENTIAL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Initialize the dragon[] array. */
|
||
|
|
||
|
void
|
||
|
initialize_dragon_data(void)
|
||
|
{
|
||
|
int str;
|
||
|
/* VALGRIND_MAKE_WRITABLE(dragon, BOARDMAX * sizeof(struct dragon_data)); */
|
||
|
for (str = BOARDMIN; str < BOARDMAX; str++)
|
||
|
if (ON_BOARD(str)) {
|
||
|
|
||
|
dragon[str].id = -1;
|
||
|
dragon[str].size = worm[str].size;
|
||
|
dragon[str].effective_size = worm[str].effective_size;
|
||
|
dragon[str].color = worm[str].color;
|
||
|
dragon[str].origin = worm[str].origin;
|
||
|
dragon[str].crude_status = UNKNOWN;
|
||
|
dragon[str].status = UNKNOWN;
|
||
|
half_eye[str].type = 0;
|
||
|
half_eye[str].value = 10.0; /* Something big. */
|
||
|
|
||
|
if (IS_STONE(board[str]) && worm[str].origin == str)
|
||
|
DEBUG(DEBUG_DRAGONS,
|
||
|
"Initializing dragon from worm at %1m, size %d\n",
|
||
|
str, worm[str].size);
|
||
|
}
|
||
|
memset(next_worm_list, 0, sizeof(next_worm_list));
|
||
|
|
||
|
/* We need to reset this to avoid trouble on an empty board when
|
||
|
* moves have previously been generated for a non-empty board.
|
||
|
*
|
||
|
* Comment: The cause of this is that make_dragons() is not called
|
||
|
* for an empty board, only initialize_dragon_data(), so we never
|
||
|
* reach initialize_supplementary_dragon_data().
|
||
|
*/
|
||
|
number_of_dragons = 0;
|
||
|
|
||
|
clear_cut_list();
|
||
|
|
||
|
memset(black_vital_points, 0, BOARDMAX * sizeof(struct vital_eye_points));
|
||
|
memset(white_vital_points, 0, BOARDMAX * sizeof(struct vital_eye_points));
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Initialize the dragon2[] array. */
|
||
|
static void
|
||
|
initialize_supplementary_dragon_data(void)
|
||
|
{
|
||
|
int str;
|
||
|
int d;
|
||
|
int origin;
|
||
|
|
||
|
/* Give each dragon (caves excluded) an id number for indexing into
|
||
|
* the dragon2 array. After this the DRAGON2 macro can be used.
|
||
|
*/
|
||
|
number_of_dragons = 0;
|
||
|
for (str = BOARDMIN; str < BOARDMAX; str++) {
|
||
|
if (!ON_BOARD(str))
|
||
|
continue;
|
||
|
origin = dragon[str].origin;
|
||
|
|
||
|
if (board[str] == EMPTY)
|
||
|
continue;
|
||
|
|
||
|
if (dragon[origin].id == -1)
|
||
|
dragon[origin].id = number_of_dragons++;
|
||
|
dragon[str].id = dragon[origin].id;
|
||
|
}
|
||
|
|
||
|
/* Now number_of_dragons contains the number of dragons and we can
|
||
|
* allocate a dragon2 array of the appropriate size. First throw
|
||
|
* away the old array.
|
||
|
*
|
||
|
* FIXME: As a future optimization we should only allocate a new
|
||
|
* array if the old one is too small.
|
||
|
*/
|
||
|
if (dragon2 != NULL)
|
||
|
free(dragon2);
|
||
|
|
||
|
dragon2 = malloc(number_of_dragons * sizeof(*dragon2));
|
||
|
gg_assert(dragon2 != NULL);
|
||
|
|
||
|
/* Find the origins of the dragons to establish the mapping back to
|
||
|
* the board. After this the DRAGON macro can be used.
|
||
|
*/
|
||
|
for (str = BOARDMIN; str < BOARDMAX; str++) {
|
||
|
if (!ON_BOARD(str))
|
||
|
continue;
|
||
|
if (IS_STONE(board[str])
|
||
|
&& dragon[str].origin == str) {
|
||
|
DRAGON2(str).origin = str;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Initialize the rest of the dragon2 data. */
|
||
|
for (d = 0; d < number_of_dragons; d++) {
|
||
|
dragon2[d].neighbors = 0;
|
||
|
dragon2[d].hostile_neighbors = 0;
|
||
|
|
||
|
dragon2[d].moyo_size = -1;
|
||
|
dragon2[d].moyo_territorial_value = 0.0;
|
||
|
dragon2[d].safety = -1;
|
||
|
dragon2[d].escape_route = 0;
|
||
|
dragon2[d].heye = NO_MOVE;
|
||
|
dragon2[d].lunch = NO_MOVE;
|
||
|
dragon2[d].surround_status = 0;
|
||
|
set_eyevalue(&dragon2[d].genus, 0, 0, 0, 0);
|
||
|
|
||
|
dragon2[d].semeais = 0;
|
||
|
dragon2[d].semeai_defense_code = 0;
|
||
|
dragon2[d].semeai_defense_point = NO_MOVE;
|
||
|
dragon2[d].semeai_attack_code = 0;
|
||
|
dragon2[d].semeai_attack_point = NO_MOVE;
|
||
|
dragon2[d].owl_attack_point = NO_MOVE;
|
||
|
dragon2[d].owl_attack_code = 0;
|
||
|
dragon2[d].owl_attack_certain = 1;
|
||
|
dragon2[d].owl_defense_point = NO_MOVE;
|
||
|
dragon2[d].owl_defense_code = 0;
|
||
|
dragon2[d].owl_defense_certain = 1;
|
||
|
dragon2[d].owl_status = UNCHECKED;
|
||
|
dragon2[d].owl_threat_status = UNCHECKED;
|
||
|
dragon2[d].owl_second_attack_point = NO_MOVE;
|
||
|
dragon2[d].owl_second_defense_point = NO_MOVE;
|
||
|
}
|
||
|
|
||
|
dragon2_initialized = 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Examine which dragons are adjacent to each other. This is
|
||
|
* complicated by the fact that adjacency may involve a certain
|
||
|
* amount of empty space.
|
||
|
*
|
||
|
* The approach we use is to extend the dragons into their
|
||
|
* surrounding influence areas until they collide. We also accept
|
||
|
* one step extensions into neutral regions. After having done this
|
||
|
* we can look for immediate adjacencies.
|
||
|
*/
|
||
|
static void
|
||
|
find_neighbor_dragons()
|
||
|
{
|
||
|
int m, n;
|
||
|
int pos;
|
||
|
int pos2;
|
||
|
int i, j;
|
||
|
int d;
|
||
|
int dragons[BOARDMAX];
|
||
|
int distances[BOARDMAX];
|
||
|
int dist;
|
||
|
int k;
|
||
|
int color;
|
||
|
|
||
|
gg_assert(dragon2_initialized);
|
||
|
|
||
|
/* Initialize the arrays. */
|
||
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||
|
if (IS_STONE(board[pos])) {
|
||
|
dragons[pos] = dragon[pos].id;
|
||
|
distances[pos] = 0;
|
||
|
}
|
||
|
else if (ON_BOARD(pos)) {
|
||
|
dragons[pos] = -1;
|
||
|
distances[pos] = -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Expand from dist-1 to dist. Break out of the loop at the end if
|
||
|
* we couldn't expand anything. Never expand more than five steps.
|
||
|
*/
|
||
|
for (dist = 1; dist <= 5; dist++) {
|
||
|
int found_one = 0;
|
||
|
|
||
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||
|
if (!ON_BOARD(pos))
|
||
|
continue;
|
||
|
|
||
|
if (distances[pos] != dist-1 || dragons[pos] < 0)
|
||
|
continue;
|
||
|
|
||
|
color = DRAGON(dragons[pos]).color;
|
||
|
for (k = 0; k < 4; k++) {
|
||
|
pos2 = pos + delta[k];
|
||
|
|
||
|
if (!ON_BOARD1(pos2))
|
||
|
continue;
|
||
|
|
||
|
/* Consider expansion from (pos) to adjacent intersection
|
||
|
* (pos2).
|
||
|
*/
|
||
|
if (distances[pos2] >= 0 && distances[pos2] < dist)
|
||
|
continue; /* (pos2) already occupied. */
|
||
|
|
||
|
/* We can always expand the first step, regardless of influence. */
|
||
|
if (dist == 1
|
||
|
|| (whose_area(INITIAL_INFLUENCE(color), pos) == color
|
||
|
&& whose_area(INITIAL_INFLUENCE(color), pos2)
|
||
|
!= OTHER_COLOR(color))) {
|
||
|
/* Expansion ok. Now see if someone else has tried to
|
||
|
* expand here. In that case we indicate a collision by
|
||
|
* setting the dragon number to -2.
|
||
|
*/
|
||
|
if (distances[pos2] == dist) {
|
||
|
if (dragons[pos2] != dragons[pos])
|
||
|
dragons[pos2] = -2;
|
||
|
}
|
||
|
else {
|
||
|
dragons[pos2] = dragons[pos];
|
||
|
distances[pos2] = dist;
|
||
|
found_one = 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (!found_one)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (0) {
|
||
|
for (m = 0; m < board_size; m++) {
|
||
|
for (n = 0; n < board_size; n++)
|
||
|
fprintf(stderr, "%3d", dragons[POS(m, n)]);
|
||
|
fprintf(stderr, "\n");
|
||
|
}
|
||
|
fprintf(stderr, "\n");
|
||
|
|
||
|
for (m = 0; m < board_size; m++) {
|
||
|
for (n = 0; n < board_size; n++)
|
||
|
fprintf(stderr, "%3d", distances[POS(m, n)]);
|
||
|
fprintf(stderr, "\n");
|
||
|
}
|
||
|
fprintf(stderr, "\n");
|
||
|
}
|
||
|
|
||
|
/* Now go through dragons to find neighbors. It suffices to look
|
||
|
* south and east for neighbors. In the case of a collision zone
|
||
|
* where dragons==-2 we set all the neighbors of this intersection
|
||
|
* as adjacent to each other.
|
||
|
*/
|
||
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||
|
if (!ON_BOARD(pos))
|
||
|
continue;
|
||
|
if (dragons[pos] == -2) {
|
||
|
int neighbors = 0;
|
||
|
int adjacent[4];
|
||
|
|
||
|
for (k = 0; k < 4; k++) {
|
||
|
pos2 = pos + delta[k];
|
||
|
|
||
|
if (ON_BOARD1(pos2) && dragons[pos2] >= 0)
|
||
|
adjacent[neighbors++] = dragons[pos2];
|
||
|
}
|
||
|
for (i = 0; i < neighbors; i++)
|
||
|
for (j = i+1; j < neighbors; j++)
|
||
|
add_adjacent_dragons(adjacent[i], adjacent[j]);
|
||
|
}
|
||
|
else if (dragons[pos] >= 0) {
|
||
|
if (ON_BOARD(NORTH(pos))) {
|
||
|
if (dragons[NORTH(pos)] >= 0
|
||
|
&& dragons[NORTH(pos)] != dragons[pos])
|
||
|
add_adjacent_dragons(dragons[pos], dragons[NORTH(pos)]);
|
||
|
}
|
||
|
if (ON_BOARD(EAST(pos))) {
|
||
|
if (dragons[EAST(pos)] >= 0
|
||
|
&& dragons[EAST(pos)] != dragons[pos])
|
||
|
add_adjacent_dragons(dragons[pos], dragons[EAST(pos)]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (0) {
|
||
|
for (d = 0; d < number_of_dragons; d++) {
|
||
|
gprintf("dragon %d at %1m:", d, dragon2[d].origin);
|
||
|
for (i = 0; i < dragon2[d].neighbors; i++)
|
||
|
gprintf(" %1m(%d)", dragon2[dragon2[d].adjacent[i]].origin,
|
||
|
dragon2[d].adjacent[i]);
|
||
|
gprintf("\n");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Add the dragons with id a and b as adjacent to each other. */
|
||
|
static void
|
||
|
add_adjacent_dragons(int a, int b)
|
||
|
{
|
||
|
gg_assert(a >= 0 && a < number_of_dragons
|
||
|
&& b >= 0 && b < number_of_dragons);
|
||
|
if (a == b)
|
||
|
return;
|
||
|
add_adjacent_dragon(a, b);
|
||
|
add_adjacent_dragon(b, a);
|
||
|
}
|
||
|
|
||
|
/* Add the dragon with id b as adjacent to a. */
|
||
|
static void
|
||
|
add_adjacent_dragon(int a, int b)
|
||
|
{
|
||
|
int i;
|
||
|
gg_assert(a >= 0 && a < number_of_dragons
|
||
|
&& b >= 0 && b < number_of_dragons);
|
||
|
/* If the array of adjacent dragons already is full, ignore
|
||
|
* additional neighbors.
|
||
|
*/
|
||
|
if (dragon2[a].neighbors == MAX_NEIGHBOR_DRAGONS)
|
||
|
return;
|
||
|
|
||
|
for (i = 0; i < dragon2[a].neighbors; i++)
|
||
|
if (dragon2[a].adjacent[i] == b)
|
||
|
return;
|
||
|
|
||
|
dragon2[a].adjacent[dragon2[a].neighbors++] = b;
|
||
|
|
||
|
if (DRAGON(a).color == OTHER_COLOR(DRAGON(b).color))
|
||
|
dragon2[a].hostile_neighbors++;
|
||
|
}
|
||
|
|
||
|
/* A dragon is considered invincible if it satisfies either of the two
|
||
|
* following conditions:
|
||
|
* a) At least two distinct eyespaces without topological halfeyes,
|
||
|
* marginal vertices, or tactically critical or alive opponent strings.
|
||
|
* Furthermore there may not be an owl attack of the dragon.
|
||
|
* b) At least one string which is unconditionally alive according to the
|
||
|
* unconditional_life() function in utils.c.
|
||
|
*
|
||
|
* For the requirement on opponent strings in a), see e.g.
|
||
|
* seki:404,408,409,413,504,604,908.
|
||
|
*/
|
||
|
|
||
|
static int
|
||
|
dragon_invincible(int dr)
|
||
|
{
|
||
|
struct eye_data *eye;
|
||
|
int eye_color;
|
||
|
int k;
|
||
|
int pos;
|
||
|
int strong_eyes = 0;
|
||
|
int mx[BOARDMAX];
|
||
|
|
||
|
ASSERT1(IS_STONE(board[dr]), dr);
|
||
|
|
||
|
/* First look for invincible strings in the dragon. */
|
||
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||
|
if (ON_BOARD(pos) && is_same_dragon(pos, dr) && worm[pos].invincible)
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/* Can the dragon be owl attacked? */
|
||
|
if (DRAGON2(dr).owl_status != UNCHECKED && DRAGON2(dr).owl_status != ALIVE)
|
||
|
return 0;
|
||
|
|
||
|
/* Examine the eye spaces. */
|
||
|
if (board[dr] == BLACK) {
|
||
|
eye = black_eye;
|
||
|
eye_color = BLACK;
|
||
|
}
|
||
|
else {
|
||
|
eye = white_eye;
|
||
|
eye_color = WHITE;
|
||
|
}
|
||
|
|
||
|
memset(mx, 0, sizeof(mx));
|
||
|
|
||
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||
|
if (board[pos] == board[dr] && is_same_dragon(pos, dr)) {
|
||
|
for (k = 0; k < 4; k++) {
|
||
|
int pos2 = pos + delta[k];
|
||
|
if (ON_BOARD(pos2)
|
||
|
&& eye[pos2].color == eye_color
|
||
|
&& eye[pos2].origin != NO_MOVE) {
|
||
|
if (eye[pos2].marginal
|
||
|
|| is_halfeye(half_eye, pos2))
|
||
|
mx[eye[pos2].origin] = 2; /* bad eye */
|
||
|
else if (mx[eye[pos2].origin] == 0)
|
||
|
mx[eye[pos2].origin] = 1; /* good eye */
|
||
|
|
||
|
if (board[pos2] == OTHER_COLOR(board[dr])
|
||
|
&& (!attack(pos2, NULL) || find_defense(pos2, NULL)))
|
||
|
mx[eye[pos2].origin] = 2; /* bad eye */
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||
|
/* Necessary to check eye margins here since the loop above only
|
||
|
* considers margins which are directly adjacent to some stone of
|
||
|
* the dragon.
|
||
|
*/
|
||
|
if (mx[pos] == 1
|
||
|
&& eye[pos].msize == 0)
|
||
|
strong_eyes++;
|
||
|
}
|
||
|
|
||
|
if (strong_eyes >= 2)
|
||
|
return 1;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* A dragon looks inessential if it satisfies all of
|
||
|
* 1. Is a single string.
|
||
|
* 2. Is not owl substantial.
|
||
|
*
|
||
|
* FIXME: Probably need a better definition of INESSENTIAL dragons.
|
||
|
* There are cases where a string is owl insubstantial
|
||
|
* yet allowing it to be captured greatly weakens our
|
||
|
* position.
|
||
|
*/
|
||
|
static int
|
||
|
dragon_looks_inessential(int origin)
|
||
|
{
|
||
|
#if 0
|
||
|
int d;
|
||
|
int k;
|
||
|
#endif
|
||
|
|
||
|
if (dragon[origin].size != worm[origin].size)
|
||
|
return 0;
|
||
|
|
||
|
if (owl_substantial(origin))
|
||
|
return 0;
|
||
|
|
||
|
#if 0
|
||
|
/* This is a proposed modification which solves 13x13:72 but
|
||
|
* breaks buzco:5. It adds the two requirements:
|
||
|
*
|
||
|
* 3. Has no opponent neighbor with status better than DEAD.
|
||
|
* 4. Has no opponent neighbor with escape value bigger than 0.
|
||
|
*
|
||
|
* This probably needs to be revised before it's enabled.
|
||
|
*/
|
||
|
for (k = 0; k < DRAGON2(origin).neighbors; k++) {
|
||
|
d = DRAGON2(origin).adjacent[k];
|
||
|
if (DRAGON(d).color != board[origin]
|
||
|
&& (DRAGON(d).status != DEAD
|
||
|
|| dragon2[d].escape_route > 0))
|
||
|
return 0;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Report which stones are alive if it's (color)'s turn to move. I.e.
|
||
|
* critical stones belonging to (color) are considered alive.
|
||
|
* A stone is dead resp. critical if the tactical reading code _or_ the
|
||
|
* owl code thinks so.
|
||
|
*/
|
||
|
static void
|
||
|
get_alive_stones(int color, signed char safe_stones[BOARDMAX])
|
||
|
{
|
||
|
int d;
|
||
|
get_lively_stones(color, safe_stones);
|
||
|
for (d = 0; d < number_of_dragons; d++) {
|
||
|
if (dragon2[d].safety == DEAD
|
||
|
|| (dragon2[d].safety == CRITICAL
|
||
|
&& board[dragon2[d].origin] == OTHER_COLOR(color))) {
|
||
|
mark_dragon(dragon2[d].origin, safe_stones, 0);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* If the opponent's last move is a dead dragon, this is called a
|
||
|
* *thrashing dragon*. We must be careful because the opponent may be
|
||
|
* trying to trick us, so even though GNU Go thinks the stone is dead,
|
||
|
* we should consider attacking it, particularly if we are ahead.
|
||
|
*
|
||
|
* This function determines whether the last played move is part of a
|
||
|
* dead dragon. It also looks for dead friendly neighbors of the
|
||
|
* thrashing dragon, which are also considered as thrashing. The
|
||
|
* stones of the primary thrashing dragon are marked by 1 in the
|
||
|
* thrashing_stone[] array and its neighbors are marked by 2.
|
||
|
* Neighbors of neighbors are marked 3, and so on, up to at most
|
||
|
* distance 5.
|
||
|
*/
|
||
|
static void
|
||
|
identify_thrashing_dragons()
|
||
|
{
|
||
|
int k;
|
||
|
int dist;
|
||
|
int last_move;
|
||
|
int color;
|
||
|
|
||
|
thrashing_dragon = 0;
|
||
|
memset(thrashing_stone, 0, sizeof(thrashing_stone));
|
||
|
|
||
|
last_move = get_last_move();
|
||
|
if (last_move == NO_MOVE
|
||
|
|| dragon[last_move].status != DEAD)
|
||
|
return;
|
||
|
|
||
|
thrashing_dragon = dragon[last_move].origin;
|
||
|
DEBUG(DEBUG_DRAGONS, "thrashing dragon found at %1m\n", thrashing_dragon);
|
||
|
mark_dragon(thrashing_dragon, thrashing_stone, 1);
|
||
|
color = board[thrashing_dragon];
|
||
|
|
||
|
for (dist = 1; dist < 5; dist++) {
|
||
|
int pos;
|
||
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||
|
if (board[pos] != color
|
||
|
|| dragon[pos].origin != pos
|
||
|
|| thrashing_stone[pos] != dist)
|
||
|
continue;
|
||
|
|
||
|
for (k = 0; k < DRAGON2(pos).neighbors; k++) {
|
||
|
int d = DRAGON2(pos).adjacent[k];
|
||
|
if (DRAGON(d).color == color
|
||
|
&& DRAGON(d).status == DEAD
|
||
|
&& thrashing_stone[dragon2[d].origin] == 0) {
|
||
|
DEBUG(DEBUG_DRAGONS,
|
||
|
"neighbor at distance %d of thrashing dragon found at %1m\n",
|
||
|
dist + 1, DRAGON(d).origin);
|
||
|
mark_dragon(DRAGON(d).origin, thrashing_stone,
|
||
|
(signed char)(dist + 1));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
set_dragon_strengths(const signed char safe_stones[BOARDMAX],
|
||
|
float strength[BOARDMAX])
|
||
|
{
|
||
|
int ii;
|
||
|
for (ii = BOARDMIN; ii < BOARDMAX; ii++)
|
||
|
if (ON_BOARD(ii)) {
|
||
|
if (safe_stones[ii]) {
|
||
|
ASSERT1(IS_STONE(board[ii]), ii);
|
||
|
strength[ii] = DEFAULT_STRENGTH
|
||
|
* (1.0 - 0.3 * DRAGON2(ii).weakness_pre_owl);
|
||
|
}
|
||
|
else
|
||
|
strength[ii] = 0.0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Marks all inessential stones with INFLUENCE_SAFE_STONE, leaves
|
||
|
* everything else unchanged.
|
||
|
*/
|
||
|
void
|
||
|
mark_inessential_stones(int color, signed char safe_stones[BOARDMAX])
|
||
|
{
|
||
|
int ii;
|
||
|
for (ii = BOARDMIN; ii < BOARDMAX; ii++)
|
||
|
if (IS_STONE(board[ii])
|
||
|
&& (DRAGON2(ii).safety == INESSENTIAL
|
||
|
|| (worm[ii].inessential
|
||
|
/* FIXME: Why is the check below needed?
|
||
|
* Why does it use .safety, not .status? /ab
|
||
|
*/
|
||
|
&& ((DRAGON2(ii).safety != DEAD
|
||
|
&& DRAGON2(ii).safety != TACTICALLY_DEAD
|
||
|
&& DRAGON2(ii).safety != CRITICAL)
|
||
|
|| (DRAGON2(ii).safety == CRITICAL
|
||
|
&& board[ii] == color)))))
|
||
|
safe_stones[ii] = INFLUENCE_SAFE_STONE;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
set_strength_data(int color, signed char safe_stones[BOARDMAX],
|
||
|
float strength[BOARDMAX])
|
||
|
{
|
||
|
gg_assert(IS_STONE(color) || color == EMPTY);
|
||
|
|
||
|
get_alive_stones(color, safe_stones);
|
||
|
set_dragon_strengths(safe_stones, strength);
|
||
|
mark_inessential_stones(color, safe_stones);
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
compute_dragon_influence()
|
||
|
{
|
||
|
signed char safe_stones[BOARDMAX];
|
||
|
float strength[BOARDMAX];
|
||
|
|
||
|
set_strength_data(BLACK, safe_stones, strength);
|
||
|
compute_influence(BLACK, safe_stones, strength, &initial_black_influence,
|
||
|
NO_MOVE, "initial black influence, dragons known");
|
||
|
break_territories(BLACK, &initial_black_influence, 1, NO_MOVE);
|
||
|
|
||
|
set_strength_data(WHITE, safe_stones, strength);
|
||
|
compute_influence(WHITE, safe_stones, strength, &initial_white_influence,
|
||
|
NO_MOVE, "initial white influence, dragons known");
|
||
|
break_territories(WHITE, &initial_white_influence, 1, NO_MOVE);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Compute dragon's genus, possibly excluding one given eye. To
|
||
|
* compute full genus, just set `eye_to_exclude' to NO_MOVE.
|
||
|
*/
|
||
|
void
|
||
|
compute_dragon_genus(int d, struct eyevalue *genus, int eye_to_exclude)
|
||
|
{
|
||
|
int pos;
|
||
|
int dr;
|
||
|
|
||
|
ASSERT1(IS_STONE(board[d]), d);
|
||
|
gg_assert(eye_to_exclude == NO_MOVE || ON_BOARD1(eye_to_exclude));
|
||
|
|
||
|
set_eyevalue(genus, 0, 0, 0, 0);
|
||
|
|
||
|
if (board[d] == BLACK) {
|
||
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||
|
if (!ON_BOARD(pos))
|
||
|
continue;
|
||
|
|
||
|
if (black_eye[pos].color == BLACK
|
||
|
&& black_eye[pos].origin == pos
|
||
|
&& (eye_to_exclude == NO_MOVE
|
||
|
|| black_eye[eye_to_exclude].origin != pos)
|
||
|
&& find_eye_dragons(pos, black_eye, BLACK, &dr, 1) == 1
|
||
|
&& is_same_dragon(dr, d)) {
|
||
|
TRACE("eye at %1m (%s) found for dragon at %1m--augmenting genus\n",
|
||
|
pos, eyevalue_to_string(&black_eye[pos].value), dr);
|
||
|
|
||
|
if (eye_to_exclude == NO_MOVE
|
||
|
&& (eye_move_urgency(&black_eye[pos].value)
|
||
|
> eye_move_urgency(genus)))
|
||
|
DRAGON2(d).heye = black_vital_points[pos].defense_points[0];
|
||
|
|
||
|
add_eyevalues(genus, &black_eye[pos].value, genus);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||
|
if (!ON_BOARD(pos))
|
||
|
continue;
|
||
|
|
||
|
if (white_eye[pos].color == WHITE
|
||
|
&& white_eye[pos].origin == pos
|
||
|
&& (eye_to_exclude == NO_MOVE
|
||
|
|| white_eye[eye_to_exclude].origin != pos)
|
||
|
&& find_eye_dragons(pos, white_eye, WHITE, &dr, 1) == 1
|
||
|
&& is_same_dragon(dr, d)) {
|
||
|
TRACE("eye at %1m (%s) found for dragon at %1m--augmenting genus\n",
|
||
|
pos, eyevalue_to_string(&white_eye[pos].value), dr);
|
||
|
|
||
|
if (eye_to_exclude == NO_MOVE
|
||
|
&& (eye_move_urgency(&white_eye[pos].value)
|
||
|
> eye_move_urgency(genus)))
|
||
|
DRAGON2(d).heye = white_vital_points[pos].defense_points[0];
|
||
|
|
||
|
add_eyevalues(genus, &white_eye[pos].value, genus);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Try to determine whether topologically false and half eye points
|
||
|
* contribute to territory even if the eye doesn't solidify. The purpose
|
||
|
* is to be able to distinguish between, e.g., these positions:
|
||
|
*
|
||
|
* |.OOOOO |.OOOOO
|
||
|
* |.O.XXO |.O.OXO
|
||
|
* |OOX.XO |OOX.XO
|
||
|
* |O*XXXO and |O*XXXO
|
||
|
* |OX.XOO |OX.XOO
|
||
|
* |X.XOO. |X.XOO.
|
||
|
* |.XXO.. |.XXO..
|
||
|
* +------ +------
|
||
|
*
|
||
|
* In the left one the move at * is a pure dame point while in the
|
||
|
* right one it is worth one point of territory for either player.
|
||
|
*
|
||
|
* In general the question is whether a topologically false eye vertex
|
||
|
* counts as territory or not and the answer depends on whether each
|
||
|
* string adjoining the eye is externally connected to at least one
|
||
|
* proper eye.
|
||
|
*
|
||
|
* This function loops over the topologically false and half eye
|
||
|
* vertices and calls connected_to_eye() for each adjoining string to
|
||
|
* determine whether they all have external connection to an eye. The
|
||
|
* result is stored in the false_eye_territory[] array.
|
||
|
*/
|
||
|
static void
|
||
|
analyze_false_eye_territory(void)
|
||
|
{
|
||
|
int pos;
|
||
|
int color;
|
||
|
int eye_color;
|
||
|
struct eye_data *eye;
|
||
|
int k;
|
||
|
|
||
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||
|
if (!ON_BOARD(pos))
|
||
|
continue;
|
||
|
|
||
|
false_eye_territory[pos] = 0;
|
||
|
|
||
|
/* The analysis only applies to false and half eyes. */
|
||
|
if (half_eye[pos].type == 0)
|
||
|
continue;
|
||
|
|
||
|
/* Determine the color of the eye. */
|
||
|
if (white_eye[pos].color == WHITE) {
|
||
|
color = WHITE;
|
||
|
eye_color = WHITE;
|
||
|
eye = white_eye;
|
||
|
}
|
||
|
else if (black_eye[pos].color == BLACK) {
|
||
|
color = BLACK;
|
||
|
eye_color = BLACK;
|
||
|
eye = black_eye;
|
||
|
}
|
||
|
else
|
||
|
continue;
|
||
|
|
||
|
/* Make sure we have a "closed" position. Positions like
|
||
|
*
|
||
|
* |XXXXXX.
|
||
|
* |OOOOOXX
|
||
|
* |.O.O*..
|
||
|
* +-------
|
||
|
*
|
||
|
* disqualify without further analysis. (* is a false eye vertex)
|
||
|
*/
|
||
|
for (k = 0; k < 4; k++)
|
||
|
if (ON_BOARD(pos + delta[k])
|
||
|
&& board[pos + delta[k]] != color
|
||
|
&& eye[pos + delta[k]].color != eye_color)
|
||
|
break;
|
||
|
|
||
|
if (k < 4)
|
||
|
continue;
|
||
|
|
||
|
/* Check that all adjoining strings have external connection to an
|
||
|
* eye.
|
||
|
*/
|
||
|
for (k = 0; k < 4; k++)
|
||
|
if (ON_BOARD(pos + delta[k])
|
||
|
&& board[pos + delta[k]] == color
|
||
|
&& !connected_to_eye(pos, pos + delta[k], color, eye_color, eye))
|
||
|
break;
|
||
|
|
||
|
if (k == 4) {
|
||
|
false_eye_territory[pos] = 1;
|
||
|
if (0)
|
||
|
gprintf("False eye territory at %1m\n", pos);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* FIXME: This initialization doesn't really belong here but must be
|
||
|
* done somewhere within examine_position().
|
||
|
* The array is eventually filled by the endgame() function.
|
||
|
*/
|
||
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
|
||
|
if (ON_BOARD(pos))
|
||
|
forced_backfilling_moves[pos] = 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* This function (implicitly) finds the connected set of strings of a
|
||
|
* dragon, starting from (str) which is next to the analyzed halfeye
|
||
|
* at (pos). Strings are for this purpose considered connected if and
|
||
|
* only if they have a common liberty, which is not allowed to be the
|
||
|
* half eye itself or one of its diagonal neighbors. For these strings
|
||
|
* it is examined whether their liberties are parts of eyespaces worth
|
||
|
* at least two halfeyes (again not counting the eyespace at (pos)).
|
||
|
*
|
||
|
* The real work is done by the recursive function
|
||
|
* connected_to_eye_recurse() below.
|
||
|
*/
|
||
|
static int
|
||
|
connected_to_eye(int pos, int str, int color, int eye_color,
|
||
|
struct eye_data *eye)
|
||
|
{
|
||
|
signed char mx[BOARDMAX];
|
||
|
signed char me[BOARDMAX];
|
||
|
int k;
|
||
|
int halfeyes;
|
||
|
|
||
|
/* mx marks strings and liberties which have already been investigated.
|
||
|
* me marks the origins of eyespaces which have already been counted.
|
||
|
* Start by marking (pos) and the surrounding vertices in mx.
|
||
|
*/
|
||
|
memset(mx, 0, sizeof(mx));
|
||
|
memset(me, 0, sizeof(me));
|
||
|
mx[pos] = 1;
|
||
|
for (k = 0; k < 8; k++)
|
||
|
if (ON_BOARD(pos + delta[k]))
|
||
|
mx[pos + delta[k]] = 1;
|
||
|
|
||
|
halfeyes = 0;
|
||
|
connected_to_eye_recurse(pos, str, color, eye_color, eye, mx, me, &halfeyes);
|
||
|
|
||
|
if (halfeyes >= 2)
|
||
|
return 1;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Recursive helper for connected_to_eye(). Stop searching when we
|
||
|
* have found at least two halfeyes.
|
||
|
*/
|
||
|
static void
|
||
|
connected_to_eye_recurse(int pos, int str, int color, int eye_color,
|
||
|
struct eye_data *eye, signed char *mx,
|
||
|
signed char *me, int *halfeyes)
|
||
|
{
|
||
|
int liberties;
|
||
|
int libs[MAXLIBS];
|
||
|
int r;
|
||
|
int k;
|
||
|
|
||
|
mark_string(str, mx, 1);
|
||
|
liberties = findlib(str, MAXLIBS, libs);
|
||
|
|
||
|
/* Search the liberties of (str) for eyespaces. */
|
||
|
for (r = 0; r < liberties; r++) {
|
||
|
if (eye[libs[r]].color == eye_color
|
||
|
&& libs[r] != pos
|
||
|
&& !me[eye[libs[r]].origin]) {
|
||
|
me[eye[libs[r]].origin] = 1;
|
||
|
*halfeyes += (min_eyes(&eye[libs[r]].value)
|
||
|
+ max_eyes(&eye[libs[r]].value));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (*halfeyes >= 2)
|
||
|
return;
|
||
|
|
||
|
/* Search for new strings in the same dragon with a liberty in
|
||
|
* common with (str), and recurse.
|
||
|
*/
|
||
|
for (r = 0; r < liberties; r++) {
|
||
|
if (mx[libs[r]])
|
||
|
continue;
|
||
|
mx[libs[r]] = 1;
|
||
|
for (k = 0; k < 4; k++) {
|
||
|
if (ON_BOARD(libs[r] + delta[k])
|
||
|
&& board[libs[r] + delta[k]] == color
|
||
|
&& is_same_dragon(str, libs[r] + delta[k])
|
||
|
&& !mx[libs[r] + delta[k]])
|
||
|
connected_to_eye_recurse(pos, libs[r] + delta[k], color, eye_color,
|
||
|
eye, mx, me, halfeyes);
|
||
|
if (*halfeyes >= 2)
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* print status info on all dragons. (Can be invoked from gdb)
|
||
|
*/
|
||
|
void
|
||
|
show_dragons(void)
|
||
|
{
|
||
|
int pos;
|
||
|
int k;
|
||
|
|
||
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||
|
struct worm_data *w = &(worm[pos]);
|
||
|
if (!IS_STONE(board[pos]))
|
||
|
continue;
|
||
|
|
||
|
if (w->origin == pos) {
|
||
|
gprintf("%1m : (dragon %1m) %s string of size %d (%f), genus %d: (%d,%d,%d,%d)",
|
||
|
pos, dragon[pos].origin,
|
||
|
color_to_string(board[pos]),
|
||
|
w->size,
|
||
|
w->effective_size,
|
||
|
w->genus,
|
||
|
w->liberties,
|
||
|
w->liberties2,
|
||
|
w->liberties3,
|
||
|
w->liberties4);
|
||
|
if (w->cutstone == 1)
|
||
|
gprintf("%o - is a potential cutting stone\n");
|
||
|
else if (w->cutstone == 2)
|
||
|
gprintf("%o - is a cutting stone\n");
|
||
|
else
|
||
|
gprintf("%o\n");
|
||
|
|
||
|
if (w->cutstone2 > 0)
|
||
|
gprintf("- cutstone2 = %d\n", w->cutstone2);
|
||
|
|
||
|
for (k = 0; k < MAX_TACTICAL_POINTS; k++) {
|
||
|
if (w->attack_codes[k] == 0)
|
||
|
break;
|
||
|
gprintf("- attackable at %1m, attack code = %d\n",
|
||
|
w->attack_points[k], w->attack_codes[k]);
|
||
|
}
|
||
|
|
||
|
for (k = 0; k < MAX_TACTICAL_POINTS; k++) {
|
||
|
if (w->defense_codes[k] == 0)
|
||
|
break;
|
||
|
gprintf("- defendable at %1m, defend code = %d\n",
|
||
|
w->defense_points[k], w->defense_codes[k]);
|
||
|
}
|
||
|
|
||
|
for (k = 0; k < MAX_TACTICAL_POINTS; k++) {
|
||
|
if (w->attack_threat_codes[k] == 0)
|
||
|
break;
|
||
|
gprintf("- attack threat at %1m, attack threat code = %d\n",
|
||
|
w->attack_threat_points[k], w->attack_threat_codes[k]);
|
||
|
}
|
||
|
|
||
|
for (k = 0; k < MAX_TACTICAL_POINTS; k++) {
|
||
|
if (w->defense_threat_codes[k] == 0)
|
||
|
break;
|
||
|
gprintf("- defense threat at %1m, defense threat code = %d\n",
|
||
|
w->defense_threat_points[k], w->defense_threat_codes[k]);
|
||
|
}
|
||
|
|
||
|
if (w->lunch != NO_MOVE)
|
||
|
gprintf("... adjacent worm %1m is lunch\n", w->lunch);
|
||
|
|
||
|
if (w->inessential)
|
||
|
gprintf("- is inessential\n");
|
||
|
|
||
|
if (w->invincible)
|
||
|
gprintf("- is invincible\n");
|
||
|
|
||
|
if (is_ko_point(pos))
|
||
|
gprintf("- is a ko stone\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
gprintf("%o\n");
|
||
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||
|
struct dragon_data *dd = &(dragon[pos]);
|
||
|
struct dragon_data2 *d2;
|
||
|
|
||
|
if (!IS_STONE(board[pos]))
|
||
|
continue;
|
||
|
|
||
|
d2 = &(dragon2[dd->id]);
|
||
|
|
||
|
if (dd->origin == pos) {
|
||
|
gprintf("%1m : %s dragon size %d (%f), genus %s, escape factor %d, crude status %s, status %s, moyo size %d, moyo territory value %f, safety %s, weakness pre owl %f, weakness %f",
|
||
|
pos,
|
||
|
board[pos] == BLACK ? "B" : "W",
|
||
|
dd->size,
|
||
|
dd->effective_size,
|
||
|
eyevalue_to_string(&d2->genus),
|
||
|
d2->escape_route,
|
||
|
status_to_string(dd->crude_status),
|
||
|
status_to_string(dd->status),
|
||
|
d2->moyo_size,
|
||
|
d2->moyo_territorial_value,
|
||
|
status_to_string(d2->safety),
|
||
|
d2->weakness_pre_owl,
|
||
|
d2->weakness);
|
||
|
gprintf(", owl status %s\n", status_to_string(d2->owl_status));
|
||
|
if (d2->owl_status == CRITICAL) {
|
||
|
gprintf("... owl attackable at %1m, code %d\n",
|
||
|
d2->owl_attack_point, d2->owl_attack_code);
|
||
|
gprintf("... owl defendable at %1m, code %d\n",
|
||
|
d2->owl_defense_point, d2->owl_defense_code);
|
||
|
}
|
||
|
if (dd->status == CRITICAL && d2->semeais) {
|
||
|
if (d2->semeai_defense_point)
|
||
|
gprintf("... semeai defense move at %1m, result code %s\n",
|
||
|
d2->semeai_defense_point,
|
||
|
result_to_string(d2->semeai_defense_code));
|
||
|
if (d2->semeai_attack_point)
|
||
|
gprintf("... semeai attack move at %1m, result code %s\n",
|
||
|
d2->semeai_attack_point,
|
||
|
result_to_string(d2->semeai_attack_code));
|
||
|
}
|
||
|
gprintf("... neighbors");
|
||
|
for (k = 0; k < d2->neighbors; k++) {
|
||
|
int d = d2->adjacent[k];
|
||
|
gprintf(" %1m", dragon2[d].origin);
|
||
|
}
|
||
|
gprintf("\n");
|
||
|
if (d2->lunch != NO_MOVE)
|
||
|
gprintf("... adjacent worm %1m is lunch\n", d2->lunch);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
static int new_dragon_origins[BOARDMAX];
|
||
|
|
||
|
/* Compute new dragons, e.g. after having made a move. This will not
|
||
|
* affect any global state.
|
||
|
*/
|
||
|
void
|
||
|
compute_new_dragons(int dragon_origins[BOARDMAX])
|
||
|
{
|
||
|
int pos;
|
||
|
int saved_cutting_points[BOARDMAX];
|
||
|
|
||
|
/* This is currently necessary in order not to mess up the
|
||
|
* worm[].cutstone2 field. See cutstone2_helper in
|
||
|
* patterns/helpers.c. On the other hand it shouldn't be very
|
||
|
* interesting to recompute dragons in the original position.
|
||
|
*/
|
||
|
gg_assert(stackp > 0);
|
||
|
|
||
|
memcpy(saved_cutting_points, cutting_points, sizeof(cutting_points));
|
||
|
memset(cutting_points, 0, sizeof(cutting_points));
|
||
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
|
||
|
if (ON_BOARD(pos)) {
|
||
|
if (board[pos] == EMPTY)
|
||
|
new_dragon_origins[pos] = NO_MOVE;
|
||
|
else
|
||
|
new_dragon_origins[pos] = find_origin(pos);
|
||
|
}
|
||
|
|
||
|
find_cuts();
|
||
|
find_connections();
|
||
|
|
||
|
memcpy(cutting_points, saved_cutting_points, sizeof(cutting_points));
|
||
|
memcpy(dragon_origins, new_dragon_origins, sizeof(new_dragon_origins));
|
||
|
}
|
||
|
|
||
|
|
||
|
/* This gets called if we are trying to compute dragons outside of
|
||
|
* make_dragons(), typically after a move has been made.
|
||
|
*/
|
||
|
static void
|
||
|
join_new_dragons(int d1, int d2)
|
||
|
{
|
||
|
int pos;
|
||
|
/* Normalize dragon coordinates. */
|
||
|
d1 = new_dragon_origins[d1];
|
||
|
d2 = new_dragon_origins[d2];
|
||
|
|
||
|
/* If d1 and d2 are the same dragon, we do nothing. */
|
||
|
if (d1 == d2)
|
||
|
return;
|
||
|
|
||
|
ASSERT1(board[d1] == board[d2], d1);
|
||
|
ASSERT1(IS_STONE(board[d1]), d1);
|
||
|
|
||
|
/* Don't bother to do anything fancy with dragon origins. */
|
||
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
|
||
|
if (ON_BOARD(pos) && new_dragon_origins[pos] == d2)
|
||
|
new_dragon_origins[pos] = d1;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* join_dragons amalgamates the dragon at (d1) to the
|
||
|
* dragon at (d2).
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
join_dragons(int d1, int d2)
|
||
|
{
|
||
|
int ii;
|
||
|
int origin; /* new origin */
|
||
|
|
||
|
/* If not called from make_dragons(), we don't work on the main
|
||
|
* dragon[] array.
|
||
|
*/
|
||
|
if (stackp > 0) {
|
||
|
join_new_dragons(d1, d2);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* Normalize dragon coordinates. */
|
||
|
d1 = dragon[d1].origin;
|
||
|
d2 = dragon[d2].origin;
|
||
|
|
||
|
/* If d1 and d2 are the same dragon, we do nothing. */
|
||
|
if (d1 == d2)
|
||
|
return;
|
||
|
|
||
|
ASSERT1(board[d1] == board[d2], d1);
|
||
|
gg_assert(dragon2_initialized == 0);
|
||
|
ASSERT1(IS_STONE(board[d1]), d1);
|
||
|
|
||
|
/* We want to have the origin pointing to the largest string of
|
||
|
* the dragon. If this is not unique, we take the "upper
|
||
|
* leftmost" one.
|
||
|
*/
|
||
|
if (worm[d1].size > worm[d2].size
|
||
|
|| (worm[d1].size == worm[d2].size
|
||
|
&& d1 < d2)) {
|
||
|
origin = d1;
|
||
|
DEBUG(DEBUG_DRAGONS, "joining dragon at %1m to dragon at %1m\n", d2, d1);
|
||
|
}
|
||
|
else {
|
||
|
origin = d2;
|
||
|
DEBUG(DEBUG_DRAGONS, "joining dragon at %1m to dragon at %1m\n", d1, d2);
|
||
|
}
|
||
|
|
||
|
dragon[origin].size = dragon[d2].size + dragon[d1].size;
|
||
|
dragon[origin].effective_size = (dragon[d2].effective_size
|
||
|
+ dragon[d1].effective_size);
|
||
|
|
||
|
/* Join the second next_worm_in_dragon chain at the end of the first one. */
|
||
|
{
|
||
|
int last_worm_origin_dragon = origin;
|
||
|
while (next_worm_list[last_worm_origin_dragon] != NO_MOVE)
|
||
|
last_worm_origin_dragon = next_worm_list[last_worm_origin_dragon];
|
||
|
if (origin == d1)
|
||
|
next_worm_list[last_worm_origin_dragon] = d2;
|
||
|
else
|
||
|
next_worm_list[last_worm_origin_dragon] = d1;
|
||
|
}
|
||
|
|
||
|
for (ii = BOARDMIN; ii < BOARDMAX; ii++) {
|
||
|
if (ON_BOARD(ii)
|
||
|
&& (dragon[ii].origin == d1 || dragon[ii].origin == d2))
|
||
|
dragon[ii].origin = origin;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/*
|
||
|
* compute_crude_status(pos) tries to determine whether the dragon
|
||
|
* at (pos) is ALIVE, DEAD, or UNKNOWN. The algorithm is not perfect
|
||
|
* and can give incorrect answers.
|
||
|
*
|
||
|
* The dragon is judged alive if its genus is >1. It is judged dead if
|
||
|
* the genus is <2, it has no escape route, and no adjoining string can
|
||
|
* be easily captured. Otherwise it is judged UNKNOWN. */
|
||
|
|
||
|
static enum dragon_status
|
||
|
compute_crude_status(int pos)
|
||
|
{
|
||
|
/* FIXME: We lose information when constructing true_genus. This
|
||
|
* code can be improved.
|
||
|
*/
|
||
|
struct eyevalue *genus = &DRAGON2(pos).genus;
|
||
|
int true_genus = max_eyes(genus) + min_eyes(genus);
|
||
|
int lunch = DRAGON2(pos).lunch;
|
||
|
|
||
|
gg_assert(dragon2_initialized);
|
||
|
|
||
|
/* If it has two sure eyes, everything is just dandy. */
|
||
|
if (true_genus > 3)
|
||
|
return ALIVE;
|
||
|
|
||
|
/* If the dragon consists of one worm, there is an attack, but
|
||
|
* no defense and there is less than one eye and one half eye,
|
||
|
* the situation is hopeless.
|
||
|
*/
|
||
|
if (dragon[pos].size == worm[pos].size
|
||
|
&& worm[pos].attack_codes[0] != 0
|
||
|
&& worm[pos].defense_codes[0] == 0
|
||
|
&& true_genus < 3)
|
||
|
return DEAD;
|
||
|
|
||
|
if (lunch != NO_MOVE
|
||
|
&& true_genus < 3
|
||
|
&& worm[lunch].defense_codes[0] != 0
|
||
|
&& DRAGON2(pos).escape_route < 5)
|
||
|
if (true_genus == 2 || worm[lunch].size > 2)
|
||
|
return CRITICAL;
|
||
|
|
||
|
if (lunch != NO_MOVE
|
||
|
&& true_genus >= 3)
|
||
|
return ALIVE;
|
||
|
|
||
|
if (lunch == NO_MOVE || worm[lunch].cutstone < 2) {
|
||
|
if (true_genus < 3
|
||
|
&& DRAGON2(pos).escape_route == 0
|
||
|
&& DRAGON2(pos).moyo_size < 5)
|
||
|
return DEAD;
|
||
|
|
||
|
if (true_genus == 3
|
||
|
&& DRAGON2(pos).escape_route < 5)
|
||
|
return CRITICAL;
|
||
|
}
|
||
|
|
||
|
if (DRAGON2(pos).moyo_territorial_value > 9.99)
|
||
|
return ALIVE;
|
||
|
|
||
|
return UNKNOWN;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* The dragon escape measure. This is defined as follows.
|
||
|
*
|
||
|
* Let a PATH be a sequence of adjacent intersections that do nowhere
|
||
|
* touch or include an opponent stone or touch the border. It may
|
||
|
* include friendly stones and those are allowed to touch opponent
|
||
|
* stones or the border). Let a DISTANCE N INTERSECTION be an
|
||
|
* intersection connected to a dragon by a path of length N, but by no
|
||
|
* shorter path. The connection of the path to the dragon may either
|
||
|
* be by direct adjacency or, in the first step, diagonally if both
|
||
|
* adjoining intersections are empty.
|
||
|
*
|
||
|
* It is assumed that each intersection has an escape value, which
|
||
|
* would typically depend on influence and (preliminary) dragon
|
||
|
* status. We define the escape potential as the sum of the escape
|
||
|
* values over the distance four intersections of the dragon.
|
||
|
*
|
||
|
* Example of distance N intersections, 1 <= N <= 4:
|
||
|
*
|
||
|
* . . . . . . . . . . . . . . . . . .
|
||
|
* . . . . . X . . O . . . . . X . . O
|
||
|
* . . X . . . . . O . . X . 2 . 4 . O
|
||
|
* X . . . . . . . . X . . 1 1 2 3 4 .
|
||
|
* X O . O . . . . O X O 1 O 1 2 3 4 O
|
||
|
* X O . O . . . . . X O 1 O 1 . 4 . .
|
||
|
* X O . . . X . O O X O 1 . . X . . O
|
||
|
* . . . X . . . . . . 1 . X . . . . .
|
||
|
* X . . . . X . . . X . . . . X . . .
|
||
|
* . . . . . . . . . . . . . . . . . .
|
||
|
*
|
||
|
* Additionally, a path may not pass a connection inhibited
|
||
|
* intersection.
|
||
|
*/
|
||
|
|
||
|
#define ENQUEUE(pos) (queue[queue_end++] = (pos),\
|
||
|
mx[pos] = 1)
|
||
|
|
||
|
/* Compute the escape potential described above. The dragon is marked
|
||
|
* in the goal array.
|
||
|
*/
|
||
|
int
|
||
|
dragon_escape(signed char goal[BOARDMAX], int color,
|
||
|
signed char escape_value[BOARDMAX])
|
||
|
{
|
||
|
int ii;
|
||
|
int k;
|
||
|
static int mx[BOARDMAX];
|
||
|
static int mx_initialized = 0;
|
||
|
int queue[MAX_BOARD * MAX_BOARD];
|
||
|
int queue_start = 0;
|
||
|
int queue_end = 0;
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int distance;
|
||
|
int escape_potential = 0;
|
||
|
|
||
|
gg_assert(IS_STONE(color));
|
||
|
|
||
|
if (!mx_initialized) {
|
||
|
memset(mx, 0, sizeof(mx));
|
||
|
mx_initialized = 1;
|
||
|
}
|
||
|
|
||
|
/* Enter the stones of the dragon in the queue. */
|
||
|
for (ii = BOARDMIN; ii < BOARDMAX; ii++)
|
||
|
if (ON_BOARD(ii) && goal[ii])
|
||
|
ENQUEUE(ii);
|
||
|
|
||
|
/* Find points at increasing distances from the dragon. At distance
|
||
|
* four, sum the escape values at those points to get the escape
|
||
|
* potential.
|
||
|
*/
|
||
|
for (distance = 0; distance <= 4; distance++) {
|
||
|
int save_queue_end = queue_end;
|
||
|
while (queue_start < save_queue_end) {
|
||
|
ii = queue[queue_start];
|
||
|
queue_start++;
|
||
|
|
||
|
/* Do not pass connection inhibited intersections. */
|
||
|
if (cut_possible(ii, OTHER_COLOR(color)))
|
||
|
continue;
|
||
|
if (distance == 4)
|
||
|
escape_potential += escape_value[ii];
|
||
|
else {
|
||
|
if (ON_BOARD(SOUTH(ii))
|
||
|
&& !mx[SOUTH(ii)]
|
||
|
&& (board[SOUTH(ii)] == color
|
||
|
|| (board[SOUTH(ii)] == EMPTY
|
||
|
&& ON_BOARD(SE(ii)) && board[SE(ii)] != other
|
||
|
&& ON_BOARD(SS(ii)) && board[SS(ii)] != other
|
||
|
&& ON_BOARD(SW(ii)) && board[SW(ii)] != other)))
|
||
|
ENQUEUE(SOUTH(ii));
|
||
|
|
||
|
if (ON_BOARD(WEST(ii))
|
||
|
&& !mx[WEST(ii)]
|
||
|
&& (board[WEST(ii)] == color
|
||
|
|| (board[WEST(ii)] == EMPTY
|
||
|
&& ON_BOARD(SW(ii)) && board[SW(ii)] != other
|
||
|
&& ON_BOARD(WW(ii)) && board[WW(ii)] != other
|
||
|
&& ON_BOARD(NW(ii)) && board[NW(ii)] != other)))
|
||
|
ENQUEUE(WEST(ii));
|
||
|
|
||
|
if (ON_BOARD(NORTH(ii))
|
||
|
&& !mx[NORTH(ii)]
|
||
|
&& (board[NORTH(ii)] == color
|
||
|
|| (board[NORTH(ii)] == EMPTY
|
||
|
&& ON_BOARD(NW(ii)) && board[NW(ii)] != other
|
||
|
&& ON_BOARD(NN(ii)) && board[NN(ii)] != other
|
||
|
&& ON_BOARD(NE(ii)) && board[NE(ii)] != other)))
|
||
|
ENQUEUE(NORTH(ii));
|
||
|
|
||
|
if (ON_BOARD(EAST(ii))
|
||
|
&& !mx[EAST(ii)]
|
||
|
&& (board[EAST(ii)] == color
|
||
|
|| (board[EAST(ii)] == EMPTY
|
||
|
&& ON_BOARD(NE(ii)) && board[NE(ii)] != other
|
||
|
&& ON_BOARD(EE(ii)) && board[EE(ii)] != other
|
||
|
&& ON_BOARD(SE(ii)) && board[SE(ii)] != other)))
|
||
|
ENQUEUE(EAST(ii));
|
||
|
|
||
|
/* For distance one intersections, allow kosumi to move out. I.e.
|
||
|
*
|
||
|
* ??..
|
||
|
* X.*.
|
||
|
* ?O.?
|
||
|
* ??X?
|
||
|
*
|
||
|
*/
|
||
|
if (distance == 0) {
|
||
|
if (board[SOUTH(ii)] == EMPTY
|
||
|
&& board[WEST(ii)] == EMPTY
|
||
|
&& !mx[SW(ii)]
|
||
|
&& (board[SW(ii)] == color
|
||
|
|| (board[SW(ii)] == EMPTY
|
||
|
&& ON_BOARD(SOUTH(SW(ii)))
|
||
|
&& board[SOUTH(SW(ii))] != other
|
||
|
&& ON_BOARD(WEST(SW(ii)))
|
||
|
&& board[WEST(SW(ii))] != other)))
|
||
|
ENQUEUE(SW(ii));
|
||
|
|
||
|
if (board[WEST(ii)] == EMPTY
|
||
|
&& board[NORTH(ii)] == EMPTY
|
||
|
&& !mx[NW(ii)]
|
||
|
&& (board[NW(ii)] == color
|
||
|
|| (board[NW(ii)] == EMPTY
|
||
|
&& ON_BOARD(WEST(NW(ii)))
|
||
|
&& board[WEST(NW(ii))] != other
|
||
|
&& ON_BOARD(NORTH(NW(ii)))
|
||
|
&& board[NORTH(NW(ii))] != other)))
|
||
|
ENQUEUE(NW(ii));
|
||
|
|
||
|
if (board[NORTH(ii)] == EMPTY
|
||
|
&& board[EAST(ii)] == EMPTY
|
||
|
&& !mx[NE(ii)]
|
||
|
&& (board[NE(ii)] == color
|
||
|
|| (board[NE(ii)] == EMPTY
|
||
|
&& ON_BOARD(NORTH(NE(ii)))
|
||
|
&& board[NORTH(NE(ii))] != other
|
||
|
&& ON_BOARD(EAST(NE(ii)))
|
||
|
&& board[EAST(NE(ii))] != other)))
|
||
|
ENQUEUE(NE(ii));
|
||
|
|
||
|
if (board[EAST(ii)] == EMPTY
|
||
|
&& board[SOUTH(ii)] == EMPTY
|
||
|
&& !mx[SE(ii)]
|
||
|
&& (board[SE(ii)] == color
|
||
|
|| (board[SE(ii)] == EMPTY
|
||
|
&& ON_BOARD(EAST(SE(ii)))
|
||
|
&& board[EAST(SE(ii))] != other
|
||
|
&& ON_BOARD(SOUTH(SE(ii)))
|
||
|
&& board[SOUTH(SE(ii))] != other)))
|
||
|
ENQUEUE(SE(ii));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Reset used mx cells. */
|
||
|
for (k = 0; k < queue_end; k++) {
|
||
|
/* The assertion fails if the same element should have been queued
|
||
|
* twice, which might happen if ENQUEUE() is called without
|
||
|
* checking mx[].
|
||
|
*/
|
||
|
ASSERT1(mx[queue[k]] == 1, queue[k]);
|
||
|
mx[queue[k]] = 0;
|
||
|
}
|
||
|
|
||
|
return escape_potential;
|
||
|
}
|
||
|
|
||
|
/* Wrapper to call the function above and compute the escape potential
|
||
|
* for the dragon at (pos).
|
||
|
*/
|
||
|
static int
|
||
|
compute_escape(int pos, int dragon_status_known)
|
||
|
{
|
||
|
int ii;
|
||
|
signed char goal[BOARDMAX];
|
||
|
signed char escape_value[BOARDMAX];
|
||
|
signed char safe_stones[BOARDMAX];
|
||
|
|
||
|
ASSERT1(IS_STONE(board[pos]), pos);
|
||
|
|
||
|
for (ii = BOARDMIN; ii < BOARDMAX; ii++)
|
||
|
if (ON_BOARD(ii))
|
||
|
goal[ii] = is_same_dragon(ii, pos);
|
||
|
|
||
|
/* Compute escape_value array. Points are awarded for moyo (4),
|
||
|
* area (2) or EMPTY (1). Values may change without notice.
|
||
|
*/
|
||
|
get_lively_stones(OTHER_COLOR(board[pos]), safe_stones);
|
||
|
compute_escape_influence(board[pos], safe_stones, NULL, 0, escape_value);
|
||
|
|
||
|
/* If we can reach a live group, award 6 points. */
|
||
|
for (ii = BOARDMIN; ii < BOARDMAX; ii++) {
|
||
|
if (!ON_BOARD(ii))
|
||
|
continue;
|
||
|
|
||
|
if (dragon_status_known) {
|
||
|
if (dragon[ii].crude_status == ALIVE)
|
||
|
escape_value[ii] = 6;
|
||
|
else if (dragon[ii].crude_status == UNKNOWN
|
||
|
&& (DRAGON2(ii).escape_route > 5
|
||
|
|| DRAGON2(ii).moyo_size > 5))
|
||
|
escape_value[ii] = 4;
|
||
|
}
|
||
|
else {
|
||
|
if (board[ii] == board[pos]
|
||
|
&& !goal[ii]
|
||
|
&& worm[ii].attack_codes[0] == 0)
|
||
|
escape_value[ii] = 2;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return dragon_escape(goal, board[pos], escape_value);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Sum up the surrounding moyo sizes for each dragon. For this
|
||
|
* we retrieve the moyo data stored in influence_data (*q) (which must
|
||
|
* have been computed previously) from the influence module.
|
||
|
* We set dragon2[].moyo_size and .moyo_value if it is smaller than the
|
||
|
* current entry.
|
||
|
*
|
||
|
* Currently this is implemented differently depending on whether
|
||
|
* experimental connections are used or not. The reason why this is
|
||
|
* needed is that most of the B patterns in conn.db are disabled for
|
||
|
* experimental connections, which may cause the moyo segmentation to
|
||
|
* pass through cutting points between dragons, making the surrounding
|
||
|
* moyo size mostly useless. Instead we only use the part of the
|
||
|
* surrounding moyo which is closest to some worm of the dragon.
|
||
|
*/
|
||
|
static void
|
||
|
compute_surrounding_moyo_sizes(const struct influence_data *q)
|
||
|
{
|
||
|
int pos;
|
||
|
int d;
|
||
|
int k;
|
||
|
int moyo_color;
|
||
|
float moyo_sizes[BOARDMAX];
|
||
|
float moyo_values[BOARDMAX];
|
||
|
|
||
|
|
||
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||
|
moyo_sizes[pos] = 0.0;
|
||
|
moyo_values[pos] = 0.0;
|
||
|
}
|
||
|
|
||
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||
|
if (!ON_BOARD(pos))
|
||
|
continue;
|
||
|
moyo_color = whose_moyo_restricted(q, pos);
|
||
|
|
||
|
if (moyo_color == board[pos])
|
||
|
continue;
|
||
|
|
||
|
if (moyo_color == WHITE) {
|
||
|
for (k = 0; k < number_close_white_worms[pos]; k++) {
|
||
|
int w = close_white_worms[pos][k];
|
||
|
int dr = dragon[w].origin;
|
||
|
|
||
|
moyo_sizes[dr] += 1.0 / number_close_white_worms[pos];
|
||
|
moyo_values[dr] += (gg_min(influence_territory(q, pos, WHITE), 1.0)
|
||
|
/ number_close_white_worms[pos]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (moyo_color == BLACK) {
|
||
|
for (k = 0; k < number_close_black_worms[pos]; k++) {
|
||
|
int w = close_black_worms[pos][k];
|
||
|
int dr = dragon[w].origin;
|
||
|
|
||
|
moyo_sizes[dr] += 1.0 / number_close_black_worms[pos];
|
||
|
moyo_values[dr] += (gg_min(influence_territory(q, pos, BLACK), 1.0)
|
||
|
/ number_close_black_worms[pos]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (d = 0; d < number_of_dragons; d++) {
|
||
|
int this_moyo_size = (int) moyo_sizes[dragon2[d].origin];
|
||
|
float this_moyo_value = moyo_values[dragon2[d].origin];
|
||
|
|
||
|
if (this_moyo_size < dragon2[d].moyo_size) {
|
||
|
dragon2[d].moyo_size = this_moyo_size;
|
||
|
dragon2[d].moyo_territorial_value = this_moyo_value;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
static struct interpolation_data moyo_value2weakness =
|
||
|
{ 5, 0.0, 15.0, {1.0, 0.65, 0.3, 0.15, 0.05, 0.0}};
|
||
|
static struct interpolation_data escape_route2weakness =
|
||
|
{ 5, 0.0, 25.0, {1.0, 0.6, 0.3, 0.1, 0.05, 0.0}};
|
||
|
static struct interpolation_data genus2weakness =
|
||
|
{ 6, 0.0, 3.0, {1.0, 0.95, 0.8, 0.5, 0.2, 0.1, 0.0}};
|
||
|
|
||
|
float
|
||
|
crude_dragon_weakness(int safety, struct eyevalue *genus, int has_lunch,
|
||
|
float moyo_value, float escape_route)
|
||
|
{
|
||
|
/* FIXME: We lose information when constructing true_genus. This
|
||
|
* code can be improved.
|
||
|
*/
|
||
|
float true_genus = 0.5 * (max_eyes(genus) + min_eyes(genus)
|
||
|
+ (has_lunch != 0));
|
||
|
float weakness_value[3];
|
||
|
float weakness;
|
||
|
int i, j;
|
||
|
|
||
|
if (safety == INVINCIBLE || safety == INESSENTIAL)
|
||
|
return 0.0;
|
||
|
if (safety == TACTICALLY_DEAD || safety == DEAD || safety == CRITICAL)
|
||
|
return 1.0;
|
||
|
|
||
|
weakness_value[0] = gg_interpolate(&moyo_value2weakness, moyo_value);
|
||
|
weakness_value[1] = gg_interpolate(&escape_route2weakness, escape_route);
|
||
|
weakness_value[2] = gg_interpolate(&genus2weakness, true_genus);
|
||
|
|
||
|
DEBUG(DEBUG_DRAGONS,
|
||
|
" moyo value %f -> %f, escape %f -> %f, eyes %f -> %f\n",
|
||
|
moyo_value, weakness_value[0],
|
||
|
escape_route, weakness_value[1],
|
||
|
true_genus, weakness_value[2]);
|
||
|
|
||
|
for (i = 0; i < 3; i++)
|
||
|
for (j = i + 1; j < 3; j++)
|
||
|
if (weakness_value[j] < weakness_value[i]) {
|
||
|
float tmp = weakness_value[i];
|
||
|
weakness_value[i] = weakness_value[j];
|
||
|
weakness_value[j] = tmp;
|
||
|
}
|
||
|
|
||
|
/* The overall weakness is mostly, but not completely determined by the
|
||
|
* best value found so far:
|
||
|
*/
|
||
|
weakness = gg_min(0.7 * weakness_value[0] + 0.3 * weakness_value[1],
|
||
|
1.3 * weakness_value[0]);
|
||
|
|
||
|
gg_assert(weakness >= 0.0 && weakness <= 1.0);
|
||
|
|
||
|
return weakness;
|
||
|
}
|
||
|
|
||
|
/* This function tries to guess a coefficient measuring the weakness of
|
||
|
* a dragon. This coefficient * the effective size of the dragon can be
|
||
|
* used to award a strategic penalty for weak dragons.
|
||
|
*/
|
||
|
static float
|
||
|
compute_dragon_weakness_value(int d)
|
||
|
{
|
||
|
int origin = dragon2[d].origin;
|
||
|
float weakness;
|
||
|
|
||
|
/* Possible ingredients for the computation:
|
||
|
* '+' means currently used, '-' means not (yet?) used
|
||
|
* - pre-owl moyo_size
|
||
|
* + post-owl moyo_size and its territory value
|
||
|
* + escape factor
|
||
|
* + number of eyes
|
||
|
* - minus number of vital attack moves?
|
||
|
* + from owl:
|
||
|
* + attack certain?
|
||
|
* - number of owl nodes
|
||
|
* - maybe reading shadow?
|
||
|
* + threat to attack?
|
||
|
* - possible connections to neighbour dragons
|
||
|
*/
|
||
|
|
||
|
DEBUG(DEBUG_DRAGONS, "Computing weakness of dragon at %1m:\n", origin);
|
||
|
|
||
|
weakness = crude_dragon_weakness(dragon2[d].safety, &dragon2[d].genus,
|
||
|
dragon2[d].lunch != NO_MOVE,
|
||
|
dragon2[d].moyo_territorial_value,
|
||
|
(float) dragon2[d].escape_route);
|
||
|
|
||
|
/* Now corrections due to (uncertain) owl results resp. owl threats. */
|
||
|
if (!dragon2[d].owl_attack_certain)
|
||
|
weakness += gg_min(0.25 * (1.0 - weakness), 0.25 * weakness);
|
||
|
if (!dragon2[d].owl_defense_certain)
|
||
|
weakness += gg_min(0.25 * (1.0 - weakness), 0.25 * weakness);
|
||
|
if (dragon2[d].owl_threat_status == CAN_THREATEN_ATTACK)
|
||
|
weakness += 0.15 * (1.0 - weakness);
|
||
|
|
||
|
if (weakness < 0.0)
|
||
|
weakness = 0.0;
|
||
|
if (weakness > 1.0)
|
||
|
weakness = 1.0;
|
||
|
|
||
|
DEBUG(DEBUG_DRAGONS, " result: %f.\n", weakness);
|
||
|
return weakness;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* This function has to be called _after_ the owl analysis and the
|
||
|
* subsequent re-run of the influence code.
|
||
|
*/
|
||
|
void
|
||
|
compute_refined_dragon_weaknesses()
|
||
|
{
|
||
|
int d;
|
||
|
|
||
|
/* Compute the surrounding moyo sizes. */
|
||
|
for (d = 0; d < number_of_dragons; d++)
|
||
|
dragon2[d].moyo_size = 2 * BOARDMAX;
|
||
|
|
||
|
/* Set moyo sizes according to initial_influence. */
|
||
|
compute_surrounding_moyo_sizes(&initial_black_influence);
|
||
|
compute_surrounding_moyo_sizes(&initial_white_influence);
|
||
|
|
||
|
for (d = 0; d < number_of_dragons; d++)
|
||
|
dragon2[d].weakness = compute_dragon_weakness_value(d);
|
||
|
}
|
||
|
|
||
|
/* The strategic size is the effective size, plus a bonus for all weak
|
||
|
* neighbouring dragons of the opponent.
|
||
|
*/
|
||
|
void
|
||
|
compute_strategic_sizes()
|
||
|
{
|
||
|
float *bonus = calloc(number_of_dragons, sizeof(float));
|
||
|
int d;
|
||
|
int k;
|
||
|
|
||
|
for (d = 0; d < number_of_dragons; d++) {
|
||
|
/* Compute bonus for all neighbors of dragon (d). The total bonus for
|
||
|
* all neighbors is effective_size(d) * weakness(d), and it is given
|
||
|
* to a neighbor d2 proportionally to the value of
|
||
|
* effective_size(d2) * weakness(d2).
|
||
|
*/
|
||
|
float sum = 0.0;
|
||
|
if (dragon2[d].safety == INESSENTIAL)
|
||
|
continue;
|
||
|
for (k = 0; k < dragon2[d].neighbors; k++) {
|
||
|
int d2 = dragon2[d].adjacent[k];
|
||
|
if (board[dragon2[d2].origin] == OTHER_COLOR(board[dragon2[d].origin])
|
||
|
&& dragon2[d2].safety != INESSENTIAL)
|
||
|
sum += DRAGON(d2).effective_size * dragon2[d2].weakness;
|
||
|
}
|
||
|
if (sum == 0.0)
|
||
|
continue;
|
||
|
for (k = 0; k < dragon2[d].neighbors; k++) {
|
||
|
int d2 = dragon2[d].adjacent[k];
|
||
|
if (board[dragon2[d2].origin] == OTHER_COLOR(board[dragon2[d].origin])
|
||
|
&& dragon2[d2].safety != INESSENTIAL) {
|
||
|
bonus[d2] += ((DRAGON(d2).effective_size * dragon2[d2].weakness) / sum)
|
||
|
* DRAGON(d).effective_size * dragon2[d].weakness;
|
||
|
if (0)
|
||
|
gprintf("Dragon %1m receives %f effective size bonus from %1m.\n",
|
||
|
dragon2[d2].origin,
|
||
|
((DRAGON(d2).effective_size * dragon2[d2].weakness) / sum)
|
||
|
* DRAGON(d).effective_size * dragon2[d].weakness,
|
||
|
dragon2[d].origin);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (d = 0; d < number_of_dragons; d++) {
|
||
|
if (0)
|
||
|
gprintf("Dragon %1m gets effective size bonus of %f.\n",
|
||
|
dragon2[d].origin, bonus[d]);
|
||
|
/* We cap strategic size at 3 * effective_size. (This is ad hoc.) */
|
||
|
dragon2[d].strategic_size = gg_min(bonus[d] + DRAGON(d).effective_size,
|
||
|
3 * DRAGON(d).effective_size);
|
||
|
}
|
||
|
|
||
|
free(bonus);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Test whether two dragons are the same. Used by autohelpers and elsewhere.
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
is_same_dragon(int d1, int d2)
|
||
|
{
|
||
|
if (d1 == NO_MOVE || d2 == NO_MOVE)
|
||
|
return (d1 == d2);
|
||
|
|
||
|
ASSERT_ON_BOARD1(d1);
|
||
|
ASSERT_ON_BOARD1(d2);
|
||
|
|
||
|
return (dragon[d1].origin == dragon[d2].origin);
|
||
|
}
|
||
|
|
||
|
/* Test whether two dragons are neighbors. */
|
||
|
int
|
||
|
are_neighbor_dragons(int d1, int d2)
|
||
|
{
|
||
|
int k;
|
||
|
d1 = dragon[d1].origin;
|
||
|
d2 = dragon[d2].origin;
|
||
|
|
||
|
for (k = 0; k < DRAGON2(d1).neighbors; k++)
|
||
|
if (dragon2[DRAGON2(d1).adjacent[k]].origin == d2)
|
||
|
return 1;
|
||
|
|
||
|
/* Just to be make sure that this function is always symmetric, we
|
||
|
* do it the other way round too.
|
||
|
*/
|
||
|
for (k = 0; k < DRAGON2(d2).neighbors; k++)
|
||
|
if (dragon2[DRAGON2(d2).adjacent[k]].origin == d1)
|
||
|
return 1;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Mark the stones of a dragon. */
|
||
|
void
|
||
|
mark_dragon(int pos, signed char mx[BOARDMAX], signed char mark)
|
||
|
{
|
||
|
int w;
|
||
|
for (w = first_worm_in_dragon(dragon[pos].origin); w != NO_MOVE;
|
||
|
w = next_worm_in_dragon(w))
|
||
|
mark_string(w, mx, mark);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* The following two functions allow to traverse all worms in a dragon:
|
||
|
* for (ii = first_worm_in_dragon(pos); ii != NO_MOVE;
|
||
|
* ii = next_worm_in_dragon(ii);)
|
||
|
* ...
|
||
|
* At the moment first_worm_in_dragon(pos) will always be the origin
|
||
|
* of the dragon, but you should not rely on that.
|
||
|
*/
|
||
|
int
|
||
|
first_worm_in_dragon(int d)
|
||
|
{
|
||
|
return dragon[d].origin;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
next_worm_in_dragon(int w)
|
||
|
{
|
||
|
ASSERT1(worm[w].origin == w, w);
|
||
|
return next_worm_list[w];
|
||
|
}
|
||
|
|
||
|
|
||
|
/* ================================================================ */
|
||
|
/* A few status functions */
|
||
|
/* ================================================================ */
|
||
|
|
||
|
/*
|
||
|
* These functions are only here because then we don't need to expose
|
||
|
* the dragon structure to the external program.
|
||
|
*/
|
||
|
|
||
|
enum dragon_status
|
||
|
crude_status(int pos)
|
||
|
{
|
||
|
return dragon[pos].crude_status;
|
||
|
}
|
||
|
|
||
|
|
||
|
enum dragon_status
|
||
|
dragon_status(int pos)
|
||
|
{
|
||
|
return dragon[pos].status;
|
||
|
}
|
||
|
|
||
|
|
||
|
int
|
||
|
lively_dragon_exists(int color)
|
||
|
{
|
||
|
if (color == WHITE)
|
||
|
return lively_white_dragons > 0;
|
||
|
else
|
||
|
return lively_black_dragons > 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Is this dragon weak? */
|
||
|
|
||
|
int
|
||
|
dragon_weak(int pos)
|
||
|
{
|
||
|
ASSERT_ON_BOARD1(pos);
|
||
|
/* FIXME: This should not happen, but avoids a crash. What is
|
||
|
* the proper fix for calling this at stackp != 0 ?
|
||
|
*/
|
||
|
if (dragon[pos].id < 0 || dragon[pos].id >= number_of_dragons)
|
||
|
return 1;
|
||
|
return (DRAGON2(pos).weakness > 0.40001);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Returns the size of the biggest critical dragon on the board. */
|
||
|
|
||
|
int
|
||
|
size_of_biggest_critical_dragon(void)
|
||
|
{
|
||
|
int str;
|
||
|
int max_size = 0;
|
||
|
|
||
|
for (str = BOARDMIN; str < BOARDMAX; str++)
|
||
|
if (ON_BOARD(str)) {
|
||
|
|
||
|
if (board[str] == EMPTY
|
||
|
|| dragon[str].origin != str)
|
||
|
continue;
|
||
|
|
||
|
/* Get the best available status for the dragon */
|
||
|
if (dragon[str].status == CRITICAL) {
|
||
|
if (dragon[str].size >= max_size)
|
||
|
max_size = dragon[str].size;
|
||
|
}
|
||
|
}
|
||
|
return max_size;
|
||
|
}
|
||
|
|
||
|
|
||
|
/************************************************************************
|
||
|
* A list of all cuts found during connection matching *
|
||
|
************************************************************************/
|
||
|
|
||
|
#define MAX_CUTS 3 * MAX_BOARD * MAX_BOARD
|
||
|
|
||
|
struct cut_data {
|
||
|
int apos;
|
||
|
int bpos;
|
||
|
int move;
|
||
|
};
|
||
|
|
||
|
static int num_cuts = 0;
|
||
|
static struct cut_data cut_list[MAX_CUTS];
|
||
|
|
||
|
static void
|
||
|
clear_cut_list()
|
||
|
{
|
||
|
num_cuts = 0;
|
||
|
}
|
||
|
|
||
|
/* Store in the list that (move) disconnects the two strings at
|
||
|
* apos and bpos.
|
||
|
*/
|
||
|
void
|
||
|
add_cut(int apos, int bpos, int move)
|
||
|
{
|
||
|
gg_assert(board[apos] == board[bpos]);
|
||
|
if (num_cuts == MAX_CUTS)
|
||
|
return;
|
||
|
if (apos > bpos) {
|
||
|
int tmp = apos;
|
||
|
apos = bpos;
|
||
|
bpos = tmp;
|
||
|
}
|
||
|
if (move == NO_MOVE)
|
||
|
return;
|
||
|
cut_list[num_cuts].apos = apos;
|
||
|
cut_list[num_cuts].bpos = bpos;
|
||
|
cut_list[num_cuts].move = move;
|
||
|
num_cuts++;
|
||
|
if (0)
|
||
|
gprintf("Added %d-th cut at %1m between %1m and %1m.\n", num_cuts,
|
||
|
move, apos, bpos);
|
||
|
}
|
||
|
|
||
|
/* For every move in the cut list disconnecting two of opponent's strings,
|
||
|
* test whether the two strings can be connected at all. If so, add a
|
||
|
* CUT_MOVE reason.
|
||
|
*/
|
||
|
void
|
||
|
cut_reasons(int color)
|
||
|
{
|
||
|
int k;
|
||
|
for (k = 0; k < num_cuts; k++)
|
||
|
if (board[cut_list[k].apos] == OTHER_COLOR(color)
|
||
|
&& !is_same_dragon(cut_list[k].apos, cut_list[k].bpos)
|
||
|
&& string_connect(cut_list[k].apos, cut_list[k].bpos, NULL) == WIN)
|
||
|
add_cut_move(cut_list[k].move, cut_list[k].apos, cut_list[k].bpos);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* ================================================================ */
|
||
|
/* Debugger functions */
|
||
|
/* ================================================================ */
|
||
|
|
||
|
/* For use in gdb, print details of the dragon at (m, n).
|
||
|
* Add this to your .gdbinit file:
|
||
|
*
|
||
|
* define dragon
|
||
|
* set ascii_report_dragon("$arg0")
|
||
|
* end
|
||
|
*
|
||
|
* Now 'dragon S8' will report the details of the S8 dragon.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
ascii_report_dragon(char *string)
|
||
|
{
|
||
|
int pos = string_to_location(board_size, string);
|
||
|
|
||
|
if (!ON_BOARD(pos))
|
||
|
fprintf(stderr, "unknown position %s\n", string);
|
||
|
else
|
||
|
report_dragon(stderr, pos);
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
report_dragon(FILE *outfile, int pos)
|
||
|
{
|
||
|
int w;
|
||
|
int k;
|
||
|
struct dragon_data *d = &(dragon[pos]);
|
||
|
struct dragon_data2 *d2 = &(dragon2[d->id]);
|
||
|
|
||
|
if (board[pos] == EMPTY) {
|
||
|
gprintf("There is no dragon at %1m\n", pos);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (d->id < 0) {
|
||
|
gprintf("Dragon data not available at %1m\n", pos);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
gfprintf(outfile, "color %s\n", color_to_string(d->color));
|
||
|
gfprintf(outfile, "origin %1m\n", d->origin);
|
||
|
gfprintf(outfile, "size %d\n", d->size);
|
||
|
gfprintf(outfile, "effective_size %f\n", d->effective_size);
|
||
|
gfprintf(outfile, "strategic_size %f\n", d2->strategic_size);
|
||
|
gfprintf(outfile, "genus %s\n",
|
||
|
eyevalue_to_string(&d2->genus));
|
||
|
gfprintf(outfile, "heye %1m\n", d2->heye);
|
||
|
gfprintf(outfile, "escape_route %d\n", d2->escape_route);
|
||
|
gfprintf(outfile, "lunch %1m\n", d2->lunch);
|
||
|
gfprintf(outfile, "crude_status %s\n",
|
||
|
status_to_string(d->crude_status));
|
||
|
gfprintf(outfile, "owl_status %s\n",
|
||
|
status_to_string(d2->owl_status));
|
||
|
gfprintf(outfile, "status %s\n",
|
||
|
status_to_string(d->status));
|
||
|
gfprintf(outfile, "safety %s\n",
|
||
|
status_to_string(d2->safety));
|
||
|
gfprintf(outfile, "weakness %f\n", d2->weakness);
|
||
|
gfprintf(outfile, "weakness_pre_owl %f\n", d2->weakness_pre_owl);
|
||
|
gfprintf(outfile, "surround_status %d\n", d2->surround_status);
|
||
|
gfprintf(outfile, "surround_size %d\n", d2->surround_size);
|
||
|
gfprintf(outfile, "moyo_size %d\n", d2->moyo_size);
|
||
|
gfprintf(outfile, "moyo_territorial_value %f\n",
|
||
|
d2->moyo_territorial_value);
|
||
|
gfprintf(outfile, "neighbors ");
|
||
|
for (k = 0; k < d2->neighbors; k++)
|
||
|
gfprintf(outfile, "%1m ", DRAGON(d2->adjacent[k]).origin);
|
||
|
gfprintf(outfile, "\nhostile_neighbors %d\n", d2->hostile_neighbors);
|
||
|
gfprintf(outfile, "owl_attack_code %d\n", d2->owl_attack_code);
|
||
|
gfprintf(outfile, "owl_attack_point %1m\n", d2->owl_attack_point);
|
||
|
gfprintf(outfile, "owl_attack_certain %s\n",
|
||
|
(d2->owl_attack_certain) ? "YES" : "NO");
|
||
|
gfprintf(outfile, "owl_2nd_attack_point %1m\n",
|
||
|
d2->owl_second_attack_point);
|
||
|
gfprintf(outfile, "owl_threat_status %s\n",
|
||
|
status_to_string(d2->owl_threat_status));
|
||
|
gfprintf(outfile, "owl_defense_code %d\n", d2->owl_defense_code);
|
||
|
gfprintf(outfile, "owl_defense_point %1m\n", d2->owl_defense_point);
|
||
|
gfprintf(outfile, "owl_defense_certain %s\n",
|
||
|
(d2->owl_defense_certain) ? "YES" : "NO");
|
||
|
gfprintf(outfile, "owl_2nd_defense_point %1m\n",
|
||
|
d2->owl_second_defense_point);
|
||
|
gfprintf(outfile, "owl_attack_kworm %1m\n", d2->owl_attack_kworm);
|
||
|
gfprintf(outfile, "owl_defense_kworm %1m\n", d2->owl_defense_kworm);
|
||
|
gfprintf(outfile, "semeais %d\n", d2->semeais);
|
||
|
gfprintf(outfile, "semeai_defense_code %d\n", d2->semeai_defense_code);
|
||
|
gfprintf(outfile, "semeai_defense_point %1m\n", d2->semeai_defense_point);
|
||
|
gfprintf(outfile, "semeai_defense_certain %d\n",
|
||
|
d2->semeai_defense_certain);
|
||
|
gfprintf(outfile, "semeai_defense_target %1m\n",
|
||
|
d2->semeai_defense_target);
|
||
|
gfprintf(outfile, "semeai_attack_code %d\n", d2->semeai_attack_code);
|
||
|
gfprintf(outfile, "semeai_attack_point %1m\n", d2->semeai_attack_point);
|
||
|
gfprintf(outfile, "semeai_attack_certain %d\n", d2->semeai_attack_certain);
|
||
|
gfprintf(outfile, "semeai_attack_target %1m\n", d2->semeai_attack_target);
|
||
|
gfprintf(outfile, "strings ");
|
||
|
for (w = first_worm_in_dragon(pos); w != NO_MOVE; w = next_worm_in_dragon(w))
|
||
|
gfprintf(outfile, "%1m ", w);
|
||
|
gfprintf(outfile, "\n");
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Local Variables:
|
||
|
* tab-width: 8
|
||
|
* c-basic-offset: 2
|
||
|
* End:
|
||
|
*/
|