618 lines
21 KiB
C
618 lines
21 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 "liberty.h"
|
||
|
|
||
|
#define INFINITY 1000
|
||
|
|
||
|
static void find_moves_to_make_seki(void);
|
||
|
static void update_status(int dr, enum dragon_status new_status,
|
||
|
enum dragon_status new_safety);
|
||
|
static int close_enough_for_proper_semeai(int apos, int bpos);
|
||
|
|
||
|
/* semeai() searches for pairs of dragons of opposite color which
|
||
|
* have safety DEAD. If such a pair is found, owl_analyze_semeai is
|
||
|
* called to read out which dragon will prevail in a semeai, and
|
||
|
* whether a move now will make a difference in the outcome. The
|
||
|
* dragon statuses are revised, and if a move now will make a
|
||
|
* difference in the outcome this information is stored in
|
||
|
* dragon_data2 and an owl reason is later generated by
|
||
|
* semeai_move_reasons().
|
||
|
*/
|
||
|
|
||
|
#define MAX_DRAGONS 50
|
||
|
|
||
|
void
|
||
|
semeai()
|
||
|
{
|
||
|
int semeai_results_first[MAX_DRAGONS][MAX_DRAGONS];
|
||
|
int semeai_results_second[MAX_DRAGONS][MAX_DRAGONS];
|
||
|
int semeai_move[MAX_DRAGONS][MAX_DRAGONS];
|
||
|
signed char semeai_certain[MAX_DRAGONS][MAX_DRAGONS];
|
||
|
int d1, d2;
|
||
|
int k;
|
||
|
int num_dragons = number_of_dragons;
|
||
|
|
||
|
if (num_dragons > MAX_DRAGONS) {
|
||
|
TRACE("Too many dragons!!! Semeai analysis disabled.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
for (d1 = 0; d1 < num_dragons; d1++)
|
||
|
for (d2 = 0; d2 < num_dragons; d2++) {
|
||
|
semeai_results_first[d1][d2] = -1;
|
||
|
semeai_results_second[d1][d2] = -1;
|
||
|
}
|
||
|
|
||
|
for (d1 = 0; d1 < num_dragons; d1++)
|
||
|
for (k = 0; k < dragon2[d1].neighbors; k++) {
|
||
|
int apos = DRAGON(d1).origin;
|
||
|
int bpos = DRAGON(dragon2[d1].adjacent[k]).origin;
|
||
|
int result_certain;
|
||
|
|
||
|
d2 = dragon[bpos].id;
|
||
|
|
||
|
/* Look for semeais */
|
||
|
|
||
|
if (dragon[apos].color == dragon[bpos].color
|
||
|
|| (dragon[apos].status != DEAD
|
||
|
&& dragon[apos].status != CRITICAL)
|
||
|
|| (dragon[bpos].status != DEAD
|
||
|
&& dragon[bpos].status != CRITICAL))
|
||
|
continue;
|
||
|
|
||
|
|
||
|
/* Ignore inessential worms or dragons */
|
||
|
|
||
|
if (worm[apos].inessential
|
||
|
|| DRAGON2(apos).safety == INESSENTIAL
|
||
|
|| worm[bpos].inessential
|
||
|
|| DRAGON2(bpos).safety == INESSENTIAL)
|
||
|
continue;
|
||
|
|
||
|
/* Sometimes the dragons are considered neighbors but are too
|
||
|
* distant to constitute a proper semeai, e.g. in nngs4:650, P2
|
||
|
* vs. R3. Then the result of semeai reading may be meaningless
|
||
|
* and can confuse the analysis. In order to avoid this we check
|
||
|
* that the dragons either are directly adjacent or at least
|
||
|
* have one common liberty.
|
||
|
*/
|
||
|
if (!close_enough_for_proper_semeai(apos, bpos))
|
||
|
continue;
|
||
|
|
||
|
/* The array semeai_results_first[d1][d2] will contain the status
|
||
|
* of d1 after the d1 d2 semeai, giving d1 the first move.
|
||
|
* The array semeai_results_second[d1][d2] will contain the status
|
||
|
* of d1 after the d1 d2 semeai, giving d2 the first move.
|
||
|
*/
|
||
|
|
||
|
DEBUG(DEBUG_SEMEAI, "Considering semeai between %1m and %1m\n",
|
||
|
apos, bpos);
|
||
|
owl_analyze_semeai(apos, bpos,
|
||
|
&(semeai_results_first[d1][d2]),
|
||
|
&(semeai_results_second[d1][d2]),
|
||
|
&(semeai_move[d1][d2]), 1, &result_certain);
|
||
|
DEBUG(DEBUG_SEMEAI, "results if %s moves first: %s %s, %1m%s\n",
|
||
|
board[apos] == BLACK ? "black" : "white",
|
||
|
result_to_string(semeai_results_first[d1][d2]),
|
||
|
result_to_string(semeai_results_second[d1][d2]),
|
||
|
semeai_move[d1][d2], result_certain ? "" : " (uncertain)");
|
||
|
semeai_certain[d1][d2] = result_certain;
|
||
|
}
|
||
|
|
||
|
/* Look for dragons which lose all their semeais outright. The
|
||
|
* winners in those semeais are considered safe and further semeais
|
||
|
* they are involved in are disregarded. See semeai:81-86 and
|
||
|
* nicklas5:1211 for examples of where this is useful.
|
||
|
*
|
||
|
* Note: To handle multiple simultaneous semeais properly we would
|
||
|
* have to make simultaneous semeai reading. Lacking that we can
|
||
|
* only get rough guesses of the correct status of the involved
|
||
|
* dragons. This code is not guaranteed to be correct in all
|
||
|
* situations but should usually be an improvement.
|
||
|
*/
|
||
|
for (d1 = 0; d1 < num_dragons; d1++) {
|
||
|
int involved_in_semeai = 0;
|
||
|
int all_lost = 1;
|
||
|
for (d2 = 0; d2 < num_dragons; d2++) {
|
||
|
if (semeai_results_first[d1][d2] != -1) {
|
||
|
involved_in_semeai = 1;
|
||
|
if (semeai_results_first[d1][d2] != 0) {
|
||
|
all_lost = 0;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (involved_in_semeai && all_lost) {
|
||
|
/* Leave the status changes to the main loop below. Here we just
|
||
|
* remove the presumably irrelevant semeai results.
|
||
|
*/
|
||
|
for (d2 = 0; d2 < num_dragons; d2++) {
|
||
|
if (semeai_results_first[d1][d2] == 0) {
|
||
|
int d3;
|
||
|
for (d3 = 0; d3 < num_dragons; d3++) {
|
||
|
if (semeai_results_second[d3][d2] > 0) {
|
||
|
semeai_results_first[d3][d2] = -1;
|
||
|
semeai_results_second[d3][d2] = -1;
|
||
|
semeai_results_first[d2][d3] = -1;
|
||
|
semeai_results_second[d2][d3] = -1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (d1 = 0; d1 < num_dragons; d1++) {
|
||
|
int semeais_found = 0;
|
||
|
int best_defense = 0;
|
||
|
int best_attack = 0;
|
||
|
int defense_move = PASS_MOVE;
|
||
|
int attack_move = PASS_MOVE;
|
||
|
int defense_certain = -1;
|
||
|
int attack_certain = -1;
|
||
|
int semeai_attack_target = NO_MOVE;
|
||
|
int semeai_defense_target = NO_MOVE;
|
||
|
|
||
|
for (d2 = 0; d2 < num_dragons; d2++) {
|
||
|
if (semeai_results_first[d1][d2] == -1)
|
||
|
continue;
|
||
|
gg_assert(semeai_results_second[d1][d2] != -1);
|
||
|
semeais_found++;
|
||
|
|
||
|
if (best_defense < semeai_results_first[d1][d2]
|
||
|
|| (best_defense == semeai_results_first[d1][d2]
|
||
|
&& defense_certain < semeai_certain[d1][d2])) {
|
||
|
best_defense = semeai_results_first[d1][d2];
|
||
|
defense_move = semeai_move[d1][d2];
|
||
|
defense_certain = semeai_certain[d1][d2];
|
||
|
gg_assert(board[dragon2[d2].origin] == OTHER_COLOR(board[dragon2[d1].origin]));
|
||
|
semeai_defense_target = dragon2[d2].origin;
|
||
|
}
|
||
|
if (best_attack < semeai_results_second[d2][d1]
|
||
|
|| (best_attack == semeai_results_second[d2][d1]
|
||
|
&& attack_certain < semeai_certain[d2][d1])) {
|
||
|
best_attack = semeai_results_second[d2][d1];
|
||
|
attack_move = semeai_move[d2][d1];
|
||
|
attack_certain = semeai_certain[d2][d1];
|
||
|
semeai_attack_target = dragon2[d2].origin;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (semeais_found) {
|
||
|
dragon2[d1].semeais = semeais_found;
|
||
|
if (best_defense != 0 && best_attack != 0)
|
||
|
update_status(DRAGON(d1).origin, CRITICAL, CRITICAL);
|
||
|
else if (best_attack == 0 && attack_certain)
|
||
|
update_status(DRAGON(d1).origin, ALIVE, ALIVE);
|
||
|
dragon2[d1].semeai_defense_code = best_defense;
|
||
|
dragon2[d1].semeai_defense_point = defense_move;
|
||
|
dragon2[d1].semeai_defense_certain = defense_certain;
|
||
|
ASSERT1(board[semeai_defense_target]
|
||
|
== OTHER_COLOR(board[dragon2[d1].origin]),
|
||
|
dragon2[d1].origin);
|
||
|
dragon2[d1].semeai_defense_target = semeai_defense_target;
|
||
|
dragon2[d1].semeai_attack_code = best_attack;
|
||
|
dragon2[d1].semeai_attack_point = attack_move;
|
||
|
dragon2[d1].semeai_attack_certain = attack_certain;
|
||
|
dragon2[d1].semeai_attack_target = semeai_attack_target;
|
||
|
}
|
||
|
}
|
||
|
find_moves_to_make_seki();
|
||
|
}
|
||
|
|
||
|
/* Find moves turning supposed territory into seki. This is not
|
||
|
* detected above since it either involves an ALIVE dragon adjacent to
|
||
|
* a CRITICAL dragon, or an ALIVE dragon whose eyespace can be invaded
|
||
|
* and turned into a seki.
|
||
|
*
|
||
|
* Currently we only search for tactically critical strings with
|
||
|
* dragon status dead, which are neighbors of only one opponent
|
||
|
* dragon, which is alive. Through semeai analysis we then determine
|
||
|
* whether such a string can in fact live in seki. Relevant testcases
|
||
|
* include gunnar:42 and gifu03:2.
|
||
|
*/
|
||
|
static void
|
||
|
find_moves_to_make_seki()
|
||
|
{
|
||
|
int str;
|
||
|
int defend_move;
|
||
|
int resulta, resultb;
|
||
|
|
||
|
for (str = BOARDMIN; str < BOARDMAX; str++) {
|
||
|
if (IS_STONE(board[str]) && is_worm_origin(str, str)
|
||
|
&& attack_and_defend(str, NULL, NULL, NULL, &defend_move)
|
||
|
&& dragon[str].status == DEAD
|
||
|
&& DRAGON2(str).hostile_neighbors == 1) {
|
||
|
int k;
|
||
|
int color = board[str];
|
||
|
int opponent = NO_MOVE;
|
||
|
int certain;
|
||
|
struct eyevalue reduced_genus;
|
||
|
|
||
|
for (k = 0; k < DRAGON2(str).neighbors; k++) {
|
||
|
opponent = dragon2[DRAGON2(str).adjacent[k]].origin;
|
||
|
if (board[opponent] != color)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
ASSERT1(opponent != NO_MOVE, opponent);
|
||
|
|
||
|
if (dragon[opponent].status != ALIVE)
|
||
|
continue;
|
||
|
|
||
|
/* FIXME: These heuristics are used for optimization. We don't
|
||
|
* want to call expensive semeai code if the opponent
|
||
|
* dragon has more than one eye elsewhere. However, the
|
||
|
* heuristics might still need improvement.
|
||
|
*/
|
||
|
compute_dragon_genus(opponent, &reduced_genus, str);
|
||
|
|
||
|
if (min_eyes(&reduced_genus) > 1
|
||
|
|| DRAGON2(opponent).moyo_size > 10
|
||
|
|| DRAGON2(opponent).moyo_territorial_value > 2.999
|
||
|
|| DRAGON2(opponent).escape_route > 0
|
||
|
|| DRAGON2(str).escape_route > 0)
|
||
|
continue;
|
||
|
|
||
|
owl_analyze_semeai_after_move(defend_move, color, opponent, str,
|
||
|
&resulta, &resultb, NULL, 1, &certain, 0);
|
||
|
|
||
|
if (resultb == WIN) {
|
||
|
owl_analyze_semeai(str, opponent, &resultb, &resulta,
|
||
|
&defend_move, 1, &certain);
|
||
|
resulta = REVERSE_RESULT(resulta);
|
||
|
resultb = REVERSE_RESULT(resultb);
|
||
|
}
|
||
|
|
||
|
/* Do not trust uncertain results. In fact it should only take a
|
||
|
* few nodes to determine the semeai result, if it is a proper
|
||
|
* potential seki position.
|
||
|
*/
|
||
|
if (resultb != WIN && certain) {
|
||
|
int d = dragon[str].id;
|
||
|
DEBUG(DEBUG_SEMEAI, "Move to make seki at %1m (%1m vs %1m)\n",
|
||
|
defend_move, str, opponent);
|
||
|
dragon2[d].semeais++;
|
||
|
update_status(str, CRITICAL, CRITICAL);
|
||
|
dragon2[d].semeai_defense_code = REVERSE_RESULT(resultb);
|
||
|
dragon2[d].semeai_defense_point = defend_move;
|
||
|
dragon2[d].semeai_defense_certain = certain;
|
||
|
gg_assert(board[opponent] == OTHER_COLOR(board[dragon2[d].origin]));
|
||
|
dragon2[d].semeai_defense_target = opponent;
|
||
|
|
||
|
/* We need to determine a proper attack move (the one that
|
||
|
* prevents seki). Currently we try the defense move first,
|
||
|
* and if it doesn't work -- all liberties of the string.
|
||
|
*/
|
||
|
owl_analyze_semeai_after_move(defend_move, OTHER_COLOR(color),
|
||
|
str, opponent, &resulta, NULL,
|
||
|
NULL, 1, NULL, 0);
|
||
|
if (resulta != WIN) {
|
||
|
dragon2[d].semeai_attack_code = REVERSE_RESULT(resulta);
|
||
|
dragon2[d].semeai_attack_point = defend_move;
|
||
|
}
|
||
|
else {
|
||
|
int k;
|
||
|
int libs[MAXLIBS];
|
||
|
int liberties = findlib(str, MAXLIBS, libs);
|
||
|
|
||
|
for (k = 0; k < liberties; k++) {
|
||
|
owl_analyze_semeai_after_move(libs[k], OTHER_COLOR(color),
|
||
|
str, opponent, &resulta, NULL,
|
||
|
NULL, 1, NULL, 0);
|
||
|
if (resulta != WIN) {
|
||
|
dragon2[d].semeai_attack_code = REVERSE_RESULT(resulta);
|
||
|
dragon2[d].semeai_attack_point = libs[k];
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (k == liberties) {
|
||
|
DEBUG(DEBUG_SEMEAI,
|
||
|
"No move to attack in semeai (%1m vs %1m), seki assumed.\n",
|
||
|
str, opponent);
|
||
|
dragon2[d].semeai_attack_code = 0;
|
||
|
dragon2[d].semeai_attack_point = NO_MOVE;
|
||
|
update_status(str, ALIVE, ALIVE_IN_SEKI);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
DEBUG(DEBUG_SEMEAI, "Move to prevent seki at %1m (%1m vs %1m)\n",
|
||
|
dragon2[d].semeai_attack_point, opponent, str);
|
||
|
|
||
|
dragon2[d].semeai_attack_certain = certain;
|
||
|
dragon2[d].semeai_attack_target = opponent;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Now look for dead strings inside a single eyespace of a living dragon.
|
||
|
*
|
||
|
* FIXME: Clearly this loop should share most of its code with the
|
||
|
* one above. It would also be good to reimplement so that
|
||
|
* moves invading a previously empty single eyespace to make
|
||
|
* seki can be found.
|
||
|
*/
|
||
|
for (str = BOARDMIN; str < BOARDMAX; str++) {
|
||
|
if (IS_STONE(board[str]) && is_worm_origin(str, str)
|
||
|
&& !find_defense(str, NULL)
|
||
|
&& dragon[str].status == DEAD
|
||
|
&& DRAGON2(str).hostile_neighbors == 1) {
|
||
|
int k;
|
||
|
int color = board[str];
|
||
|
int opponent = NO_MOVE;
|
||
|
int certain;
|
||
|
struct eyevalue reduced_genus;
|
||
|
|
||
|
for (k = 0; k < DRAGON2(str).neighbors; k++) {
|
||
|
opponent = dragon2[DRAGON2(str).adjacent[k]].origin;
|
||
|
if (board[opponent] != color)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
ASSERT1(opponent != NO_MOVE, opponent);
|
||
|
|
||
|
if (dragon[opponent].status != ALIVE)
|
||
|
continue;
|
||
|
|
||
|
/* FIXME: These heuristics are used for optimization. We don't
|
||
|
* want to call expensive semeai code if the opponent
|
||
|
* dragon has more than one eye elsewhere. However, the
|
||
|
* heuristics might still need improvement.
|
||
|
*/
|
||
|
compute_dragon_genus(opponent, &reduced_genus, str);
|
||
|
if (DRAGON2(opponent).moyo_size > 10 || min_eyes(&reduced_genus) > 1)
|
||
|
continue;
|
||
|
|
||
|
owl_analyze_semeai(str, opponent, &resulta, &resultb,
|
||
|
&defend_move, 1, &certain);
|
||
|
|
||
|
/* Do not trust uncertain results. In fact it should only take a
|
||
|
* few nodes to determine the semeai result, if it is a proper
|
||
|
* potential seki position.
|
||
|
*/
|
||
|
if (resulta != 0 && certain) {
|
||
|
int d = dragon[str].id;
|
||
|
DEBUG(DEBUG_SEMEAI, "Move to make seki at %1m (%1m vs %1m)\n",
|
||
|
defend_move, str, opponent);
|
||
|
dragon2[d].semeais++;
|
||
|
update_status(str, CRITICAL, CRITICAL);
|
||
|
dragon2[d].semeai_defense_code = resulta;
|
||
|
dragon2[d].semeai_defense_point = defend_move;
|
||
|
dragon2[d].semeai_defense_certain = certain;
|
||
|
gg_assert(board[opponent] == OTHER_COLOR(board[dragon2[d].origin]));
|
||
|
dragon2[d].semeai_defense_target = opponent;
|
||
|
|
||
|
/* We need to determine a proper attack move (the one that
|
||
|
* prevents seki). Currently we try the defense move first,
|
||
|
* and if it doesn't work -- all liberties of the string.
|
||
|
*/
|
||
|
owl_analyze_semeai_after_move(defend_move, OTHER_COLOR(color),
|
||
|
str, opponent, &resulta, NULL,
|
||
|
NULL, 1, NULL, 0);
|
||
|
if (resulta != WIN) {
|
||
|
dragon2[d].semeai_attack_code = REVERSE_RESULT(resulta);
|
||
|
dragon2[d].semeai_attack_point = defend_move;
|
||
|
}
|
||
|
else {
|
||
|
int k;
|
||
|
int libs[MAXLIBS];
|
||
|
int liberties = findlib(str, MAXLIBS, libs);
|
||
|
|
||
|
for (k = 0; k < liberties; k++) {
|
||
|
owl_analyze_semeai_after_move(libs[k], OTHER_COLOR(color),
|
||
|
str, opponent, &resulta, NULL,
|
||
|
NULL, 1, NULL, 0);
|
||
|
if (resulta != WIN) {
|
||
|
dragon2[d].semeai_attack_code = REVERSE_RESULT(resulta);
|
||
|
dragon2[d].semeai_attack_point = libs[k];
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (k == liberties) {
|
||
|
DEBUG(DEBUG_SEMEAI,
|
||
|
"No move to attack in semeai (%1m vs %1m), seki assumed.\n",
|
||
|
str, opponent);
|
||
|
dragon2[d].semeai_attack_code = 0;
|
||
|
dragon2[d].semeai_attack_point = NO_MOVE;
|
||
|
update_status(str, ALIVE, ALIVE_IN_SEKI);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
DEBUG(DEBUG_SEMEAI, "Move to prevent seki at %1m (%1m vs %1m)\n",
|
||
|
dragon2[d].semeai_attack_point, opponent, str);
|
||
|
|
||
|
dragon2[d].semeai_attack_certain = certain;
|
||
|
dragon2[d].semeai_attack_target = opponent;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* neighbor_of_dragon(pos, origin) returns true if the vertex at (pos) is a
|
||
|
* neighbor of the dragon with origin at (origin).
|
||
|
*/
|
||
|
static int
|
||
|
neighbor_of_dragon(int pos, int origin)
|
||
|
{
|
||
|
int k;
|
||
|
if (pos == NO_MOVE)
|
||
|
return 0;
|
||
|
|
||
|
for (k = 0; k < 4; k++)
|
||
|
if (ON_BOARD(pos + delta[k]) && dragon[pos + delta[k]].origin == origin)
|
||
|
return 1;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Check whether two dragons are directly adjacent or have at least
|
||
|
* one common liberty.
|
||
|
*/
|
||
|
static int
|
||
|
close_enough_for_proper_semeai(int apos, int bpos)
|
||
|
{
|
||
|
int pos;
|
||
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||
|
if (board[pos] == EMPTY
|
||
|
&& neighbor_of_dragon(pos, apos)
|
||
|
&& neighbor_of_dragon(pos, bpos))
|
||
|
return 1;
|
||
|
else if (IS_STONE(board[pos])) {
|
||
|
if (is_same_dragon(pos, apos) && neighbor_of_dragon(pos, bpos))
|
||
|
return 1;
|
||
|
if (is_same_dragon(pos, bpos) && neighbor_of_dragon(pos, apos))
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* This function adds the semeai related move reasons, using the information
|
||
|
* stored in the dragon2 array.
|
||
|
*
|
||
|
* If the semeai had an uncertain result, and there is a owl move with
|
||
|
* certain result doing the same, we don't trust the semeai move.
|
||
|
*/
|
||
|
void
|
||
|
semeai_move_reasons(int color)
|
||
|
{
|
||
|
int other = OTHER_COLOR(color);
|
||
|
int d;
|
||
|
int liberties;
|
||
|
int libs[MAXLIBS];
|
||
|
int r;
|
||
|
|
||
|
for (d = 0; d < number_of_dragons; d++)
|
||
|
if (dragon2[d].semeais && DRAGON(d).status == CRITICAL) {
|
||
|
if (DRAGON(d).color == color
|
||
|
&& dragon2[d].semeai_defense_point
|
||
|
&& (dragon2[d].owl_defense_point == NO_MOVE
|
||
|
|| dragon2[d].semeai_defense_certain >=
|
||
|
dragon2[d].owl_defense_certain)) {
|
||
|
/* My dragon can be defended. */
|
||
|
add_semeai_move(dragon2[d].semeai_defense_point, dragon2[d].origin);
|
||
|
DEBUG(DEBUG_SEMEAI, "Adding semeai defense move for %1m at %1m\n",
|
||
|
DRAGON(d).origin, dragon2[d].semeai_defense_point);
|
||
|
if (neighbor_of_dragon(dragon2[d].semeai_defense_point,
|
||
|
dragon2[d].semeai_defense_target)
|
||
|
&& !neighbor_of_dragon(dragon2[d].semeai_defense_point,
|
||
|
dragon2[d].origin)
|
||
|
&& !is_self_atari(dragon2[d].semeai_defense_point, color)) {
|
||
|
|
||
|
/* If this is a move to fill the non-common liberties of the
|
||
|
* target, and is not a ko or snap-back, then we mark all
|
||
|
* non-common liberties of the target as potential semeai moves.
|
||
|
*/
|
||
|
|
||
|
liberties = findlib(dragon2[d].semeai_defense_target, MAXLIBS, libs);
|
||
|
|
||
|
for (r = 0; r < liberties; r++) {
|
||
|
if (!neighbor_of_dragon(libs[r], dragon2[d].origin)
|
||
|
&& !is_self_atari(libs[r], color)
|
||
|
&& libs[r] != dragon2[d].semeai_defense_point)
|
||
|
add_potential_semeai_defense(libs[r], dragon2[d].origin,
|
||
|
dragon2[d].semeai_defense_target);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (DRAGON(d).color == other
|
||
|
&& dragon2[d].semeai_attack_point
|
||
|
&& (dragon2[d].owl_attack_point == NO_MOVE
|
||
|
|| dragon2[d].owl_defense_point == NO_MOVE
|
||
|
|| dragon2[d].semeai_attack_certain >=
|
||
|
dragon2[d].owl_attack_certain)) {
|
||
|
/* Your dragon can be attacked. */
|
||
|
add_semeai_move(dragon2[d].semeai_attack_point, dragon2[d].origin);
|
||
|
DEBUG(DEBUG_SEMEAI, "Adding semeai attack move for %1m at %1m\n",
|
||
|
DRAGON(d).origin, dragon2[d].semeai_attack_point);
|
||
|
if (neighbor_of_dragon(dragon2[d].semeai_attack_point,
|
||
|
dragon2[d].origin)
|
||
|
&& !neighbor_of_dragon(dragon2[d].semeai_attack_point,
|
||
|
dragon2[d].semeai_attack_target)
|
||
|
&& !is_self_atari(dragon2[d].semeai_attack_point, color)) {
|
||
|
|
||
|
liberties = findlib(dragon2[d].origin, MAXLIBS, libs);
|
||
|
|
||
|
for (r = 0; r < liberties; r++) {
|
||
|
if (!neighbor_of_dragon(libs[r], dragon2[d].semeai_attack_target)
|
||
|
&& !is_self_atari(libs[r], color)
|
||
|
&& libs[r] != dragon2[d].semeai_attack_point)
|
||
|
add_potential_semeai_attack(libs[r], dragon2[d].origin,
|
||
|
dragon2[d].semeai_attack_target);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Change the status and safety of a dragon. In addition, if the new
|
||
|
* status is not DEAD, make all worms of the dragon essential, so that
|
||
|
* results found by semeai code don't get ignored.
|
||
|
*/
|
||
|
static void
|
||
|
update_status(int dr, enum dragon_status new_status,
|
||
|
enum dragon_status new_safety)
|
||
|
{
|
||
|
int pos;
|
||
|
|
||
|
if (dragon[dr].status != new_status
|
||
|
&& (dragon[dr].status != CRITICAL || new_status != DEAD)) {
|
||
|
DEBUG(DEBUG_SEMEAI, "Changing status of %1m from %s to %s.\n", dr,
|
||
|
status_to_string(dragon[dr].status),
|
||
|
status_to_string(new_status));
|
||
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
|
||
|
if (IS_STONE(board[pos]) && is_same_dragon(dr, pos)) {
|
||
|
dragon[pos].status = new_status;
|
||
|
if (new_status != DEAD)
|
||
|
worm[pos].inessential = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (DRAGON2(dr).safety != new_safety
|
||
|
&& (DRAGON2(dr).safety != CRITICAL || new_safety != DEAD)) {
|
||
|
DEBUG(DEBUG_SEMEAI, "Changing safety of %1m from %s to %s.\n", dr,
|
||
|
status_to_string(DRAGON2(dr).safety), status_to_string(new_safety));
|
||
|
DRAGON2(dr).safety = new_safety;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Local Variables:
|
||
|
* tab-width: 8
|
||
|
* c-basic-offset: 2
|
||
|
* End:
|
||
|
*/
|