Initial commit of GNU Go v3.8.
[sgk-go] / engine / value_moves.c
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* This is GNU Go, a Go program. Contact gnugo@gnu.org, or see *
* http://www.gnu.org/software/gnugo/ for more information. *
* *
* Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, *
* 2008 and 2009 by the Free Software Foundation. *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation - version 3 or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License in file COPYING for more details. *
* *
* You should have received a copy of the GNU General Public *
* License along with this program; if not, write to the Free *
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, *
* Boston, MA 02111, USA. *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "gnugo.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "liberty.h"
#include "gg_utils.h"
#include "move_reasons.h"
/* Count how many distinct strings are (solidly) connected by the move
* at (pos). Add a bonus for strings with few liberties. Also add
* bonus for opponent strings put in atari or removed and for own
* strings in atari adjacent to removed opponent strings.
*
* The parameter to_move should be set when color is the color to
* move. (This function is called for both colors.)
*/
static int
move_connects_strings(int pos, int color, int to_move)
{
int ss[4];
int strings = 0;
int own_strings = 0;
int k, l;
int fewlibs = 0;
for (k = 0; k < 4; k++) {
int ii = pos + delta[k];
int origin;
if (!ON_BOARD(ii) || board[ii] == EMPTY)
continue;
origin = find_origin(ii);
for (l = 0; l < strings; l++)
if (ss[l] == origin)
break;
if (l == strings) {
ss[strings] = origin;
strings++;
}
}
for (k = 0; k < strings; k++) {
if (worm[ss[k]].invincible)
continue;
if (board[ss[k]] == color) {
int newlibs = approxlib(pos, color, MAXLIBS, NULL);
own_strings++;
if (newlibs >= countlib(ss[k])) {
if (countlib(ss[k]) <= 4)
fewlibs++;
if (countlib(ss[k]) <= 2)
fewlibs++;
}
}
else {
if (countlib(ss[k]) <= 2)
fewlibs++;
if (countlib(ss[k]) <= 1 && to_move) {
int dummy[MAXCHAIN];
fewlibs++;
fewlibs += chainlinks2(ss[k], dummy, 1);
}
}
}
/* Do some thresholding. */
if (fewlibs > 4)
fewlibs = 4;
if (to_move && is_ko(pos, color, NULL) && fewlibs > 1)
fewlibs = 1;
if (fewlibs == 0 && own_strings == 1)
own_strings = 0;
return own_strings + fewlibs;
}
/* Find saved dragons and worms, then call blunder_size(). */
static float
value_moves_get_blunder_size(int move, int color)
{
signed char saved_dragons[BOARDMAX];
signed char saved_worms[BOARDMAX];
signed char safe_stones[BOARDMAX];
get_saved_dragons(move, saved_dragons);
get_saved_worms(move, saved_worms);
mark_safe_stones(color, move, saved_dragons, saved_worms, safe_stones);
return blunder_size(move, color, NULL, safe_stones);
}
static int
value_moves_confirm_safety(int move, int color)
{
return (value_moves_get_blunder_size(move, color) == 0.0);
}
/* Test all moves which defend, attack, connect or cut to see if they
* also attack or defend some other worm.
*
* FIXME: We would like to see whether an arbitrary move works to cut
* or connect something else too.
*/
static void
find_more_attack_and_defense_moves(int color)
{
int unstable_worms[MAX_WORMS];
int N = 0; /* number of unstable worms */
int ii;
int k;
int other = OTHER_COLOR(color);
int cursor_at_start_of_line;
TRACE("\nLooking for additional attack and defense moves. Trying moves ...\n");
/* Identify the unstable worms and store them in a list. */
for (ii = BOARDMIN; ii < BOARDMAX; ii++) {
if (IS_STONE(board[ii])
&& worm[ii].origin == ii
&& worm[ii].attack_codes[0] != 0
&& worm[ii].defense_codes[0] != 0) {
unstable_worms[N] = ii;
N++;
}
}
/* To avoid horizon effects, we temporarily increase the depth values. */
increase_depth_values();
for (ii = BOARDMIN; ii < BOARDMAX; ii++) {
if (board[ii] != EMPTY)
continue;
/* Don't consider send-two-return-one moves here. */
if (send_two_return_one(ii, color))
continue;
for (k = 0; k < MAX_REASONS; k++) {
int r = move[ii].reason[k];
if (r < 0)
break;
if (move_reasons[r].type == ATTACK_MOVE
|| move_reasons[r].type == ATTACK_MOVE_GOOD_KO
|| move_reasons[r].type == ATTACK_MOVE_BAD_KO
|| move_reasons[r].type == DEFEND_MOVE
|| move_reasons[r].type == DEFEND_MOVE_GOOD_KO
|| move_reasons[r].type == DEFEND_MOVE_BAD_KO
|| move_reasons[r].type == CONNECT_MOVE
|| move_reasons[r].type == CUT_MOVE)
break;
/* FIXME: Add code for EITHER_MOVE and ALL_MOVE here. */
}
if (k == MAX_REASONS || move[ii].reason[k] == -1)
continue;
/* Try the move at (ii) and see what happens. */
cursor_at_start_of_line = 0;
TRACE("%1m ", ii);
if (trymove(ii, color, "find_more_attack_and_defense_moves", NO_MOVE)) {
for (k = 0; k < N; k++) {
int aa = unstable_worms[k];
/* string of our color, see if there still is an attack,
* unless we already know the move works as defense move.
*/
if (board[aa] == color
&& !defense_move_reason_known(ii, unstable_worms[k])) {
int acode = attack(aa, NULL);
if (acode < worm[aa].attack_codes[0]) {
/* Maybe attack() doesn't find the attack. Try to
* attack with the stored attack move.
*/
int defense_works = 1;
if (trymove(worm[aa].attack_points[0], other,
"find_more_attack_and_defense_moves", 0)) {
if (!board[aa])
defense_works = 0;
else {
int this_acode = REVERSE_RESULT(find_defense(aa, NULL));
if (this_acode > acode) {
acode = this_acode;
if (acode >= worm[aa].attack_codes[0])
defense_works = 0;
}
}
popgo();
}
if (defense_works) {
if (!cursor_at_start_of_line)
TRACE("\n");
TRACE("%ofound extra point of defense of %1m at %1m code %d\n",
aa, ii, REVERSE_RESULT(acode));
cursor_at_start_of_line = 1;
add_defense_move(ii, aa, REVERSE_RESULT(acode));
}
}
}
/* string of opponent color, see if there still is a defense,
* unless we already know the move works as attack move.
*/
if (board[aa] == other
&& !attack_move_reason_known(ii, unstable_worms[k])) {
int dcode = find_defense(aa, NULL);
if (dcode < worm[aa].defense_codes[0]) {
/* Maybe find_defense() doesn't find the defense. Try to
* defend with the stored defense move.
*
* Another option is maybe there is no attack anymore
* (e.g. we pushed the worm into seki), find_defense()
* could easily fail in that case.
*/
int attack_works = 1;
if (attack(aa, NULL) >= worm[aa].attack_codes[0]) {
if (trymove(worm[aa].defense_points[0], other,
"find_more_attack_and_defense_moves", 0)) {
int this_dcode = REVERSE_RESULT(attack(aa, NULL));
if (this_dcode > dcode) {
dcode = this_dcode;
if (dcode >= worm[aa].defense_codes[0])
attack_works = 0;
}
popgo();
}
}
else
attack_works = 0;
if (attack_works) {
if (!cursor_at_start_of_line)
TRACE("\n");
TRACE("%ofound extra point of attack of %1m at %1m code %d\n",
aa, ii, REVERSE_RESULT(dcode));
cursor_at_start_of_line = 1;
add_attack_move(ii, aa, REVERSE_RESULT(dcode));
}
}
}
}
popgo();
}
}
TRACE("\n");
decrease_depth_values();
}
/* Do the real job of find_more_owl_attack_and_defense_moves() with given
* move reason at given position and for given target (`what'). This
* function is used from induce_secondary_move_reasons() for upgrading
* one specific move reason only.
*/
static void
do_find_more_owl_attack_and_defense_moves(int color, int pos,
int move_reason_type, int what)
{
int k;
int dd1 = NO_MOVE;
int dd2 = NO_MOVE;
int save_verbose;
gg_assert(stackp == 0);
/* Never consider moves of the send-two-return-one type here. */
if (send_two_return_one(pos, color))
return;
/* Never consider moves playing into snapback here. */
if (playing_into_snapback(pos, color))
return;
save_verbose = verbose;
if (verbose > 0)
verbose --;
if (move_reason_type == STRATEGIC_ATTACK_MOVE
|| move_reason_type == STRATEGIC_DEFEND_MOVE)
dd1 = what;
else if (move_reason_type == ATTACK_MOVE
|| move_reason_type == ATTACK_MOVE_GOOD_KO
|| move_reason_type == ATTACK_MOVE_BAD_KO
|| move_reason_type == DEFEND_MOVE
|| move_reason_type == DEFEND_MOVE_GOOD_KO
|| move_reason_type == DEFEND_MOVE_BAD_KO
|| move_reason_type == VITAL_EYE_MOVE)
dd1 = what;
else if (move_reason_type == CONNECT_MOVE) {
int worm1 = conn_worm1[what];
int worm2 = conn_worm2[what];
dd1 = dragon[worm1].origin;
dd2 = dragon[worm2].origin;
if (dd1 == dd2)
dd2 = NO_MOVE;
}
else {
verbose = save_verbose;
return;
}
for (k = 0; k < 2; k++) {
int dd = (k == 0 ? dd1 : dd2);
if (dd == NO_MOVE)
continue;
/* Don't care about inessential dragons. */
if (DRAGON2(dd).safety == INESSENTIAL)
continue;
if (DRAGON2(dd).owl_status != CRITICAL)
continue;
if ((move_reason_type == STRATEGIC_ATTACK_MOVE
|| move_reason_type == ATTACK_MOVE
|| move_reason_type == ATTACK_MOVE_GOOD_KO
|| move_reason_type == ATTACK_MOVE_BAD_KO
|| (move_reason_type == VITAL_EYE_MOVE
&& board[dd] == OTHER_COLOR(color)))
&& !owl_attack_move_reason_known(pos, dd)) {
int kworm = NO_MOVE;
int acode = owl_does_attack(pos, dd, &kworm);
if (acode >= DRAGON2(dd).owl_attack_code) {
add_owl_attack_move(pos, dd, kworm, acode);
if (save_verbose)
gprintf("Move at %1m upgraded to owl attack on %1m (%s).\n",
pos, dd, result_to_string(acode));
}
}
if ((move_reason_type == STRATEGIC_DEFEND_MOVE
|| move_reason_type == CONNECT_MOVE
|| move_reason_type == DEFEND_MOVE
|| move_reason_type == DEFEND_MOVE_GOOD_KO
|| move_reason_type == DEFEND_MOVE_BAD_KO
|| (move_reason_type == VITAL_EYE_MOVE
&& board[dd] == color))
&& !owl_defense_move_reason_known(pos, dd)) {
int kworm = NO_MOVE;
/* FIXME: Better use owl_connection_defend() for CONNECT_MOVE ? */
int dcode = owl_does_defend(pos, dd, &kworm);
if (dcode >= DRAGON2(dd).owl_defense_code) {
if (dcode == LOSS)
add_loss_move(pos, dd, kworm);
else
add_owl_defense_move(pos, dd, dcode);
if (save_verbose)
gprintf("Move at %1m upgraded to owl defense for %1m (%s).\n",
pos, dd, result_to_string(dcode));
}
}
}
verbose = save_verbose;
}
/* Try whether the move at (pos) for (color) is also an owl attack on
* (target). (dist) is the distance to the dragon, and is used for a
* safety heuristic: distant moves are only accepted if they kill within
* few owl nodes.
*/
static void
try_large_scale_owl_attack(int pos, int color, int target, int dist)
{
int owl_nodes_before;
int owl_nodes_used;
int kworm = NO_MOVE;
int acode;
int save_verbose = verbose;
int save_owl_node_limit = owl_node_limit;
ASSERT1(board[target] == OTHER_COLOR(color), pos);
ASSERT1(!owl_attack_move_reason_known(pos, target), pos);
DEBUG(DEBUG_LARGE_SCALE, "Trying large scale move %1m on %1m\n", pos, target);
/* To avoid horizon effects, we temporarily increase
* the depth values to find the large scale attacks.
*/
increase_depth_values();
/* To reduce the amount of aji allowed for large scale
* attacks, we reduce the owl limit to 350 nodes for
* attacks at distance <= 1, and 150 nodes for attacks at
* distance >= 2.
*/
if (dist <= 1)
owl_node_limit *= 0.35;
else
owl_node_limit *= 0.15;
if (DRAGON2(target).owl_attack_node_count < owl_node_limit) {
if (verbose > 0)
verbose--;
owl_nodes_before = get_owl_node_counter();
acode = owl_does_attack(pos, target, &kworm);
owl_nodes_used = get_owl_node_counter() - owl_nodes_before;
if (acode >= DRAGON2(target).owl_attack_code
&& acode == WIN) {
add_owl_attack_move(pos, target, kworm, acode);
DEBUG(DEBUG_LARGE_SCALE | DEBUG_MOVE_REASONS,
"Move at %1m owl-attacks %1m on a large scale(%s).\n",
pos, target, result_to_string(acode));
}
else
DEBUG(DEBUG_LARGE_SCALE,
"Move at %1m isn't a clean large scale attack on %1m (%s).\n",
pos, target, result_to_string(acode));
DEBUG(DEBUG_LARGE_SCALE, " owl nodes used = %d, dist = %d\n",
owl_nodes_used, dist);
/* Restore settings. */
verbose = save_verbose;
}
decrease_depth_values();
owl_node_limit = save_owl_node_limit;
}
#define MAXIMUM_LARGE_SCALE_DIST 3
/* Test all the moves to see whether they can owl-attack a specific
* dragon on a large scale . Tested moves are
* 1. Moves that already have a move reason.
* 2. Are not too far away.
* The distance used is the Manhattan distance, and the maximum
* distance is MAXIMUM_LARGE_SCALE_DIST.
*/
static void
find_large_scale_owl_attacks_on_dragon(int color, int target)
{
int x, y;
int x_min = board_size;
int x_max = 0;
int y_min = board_size;
int y_max = 0;
int dist;
ASSERT1(board[target] == OTHER_COLOR(color), target);
/* Find the physical extension of the dragon. */
for (x = 0; x < board_size; x++)
for (y = 0; y < board_size; y++) {
if (is_same_dragon(target, POS(x, y))) {
if (x < x_min)
x_min = x;
if (x > x_max)
x_max = x;
if (y < y_min)
y_min = y;
if (y > y_max)
y_max = y;
}
}
ASSERT1(x_min <= x_max && y_min <= y_max, target);
/* Try to find large scale attacks.
* We do this by first trying to find attacks at dist = 0, then
* dist = 1, etc., up to MAXIMUM_LARGE_SCALE_DIST.
*/
for (dist = 0; dist <= MAXIMUM_LARGE_SCALE_DIST; dist++)
for (x = gg_max(x_min - dist, 0);
x <= gg_min(x_max + dist, board_size - 1); x++)
for (y = gg_max(y_min - dist, 0);
y <= gg_min(y_max + dist, board_size - 1); y++) {
int pos = POS(x, y);
ASSERT1(ON_BOARD2(x, y), pos);
if (board[pos] == EMPTY) {
int a, b, dx, dy;
a = abs(x - x_min);
b = abs(x - x_max);
dx = gg_min(a, b);
a = abs(y - y_min);
b = abs(y - y_max);
dy = gg_min(a, b);
if (gg_max(dx, dy) == dist
&& move[pos].reason[0] >= 0
&& !owl_attack_move_reason_known(pos, target))
/* Maximum Manhatan distance, move reason known but no owl
* attack yet.
*/
try_large_scale_owl_attack(pos, color, target, dist);
}
}
}
/* Try large scale owl attacks against all enemy dragons that are
* small (size <= 6) and critical.
*/
static void
find_large_scale_owl_attack_moves(int color)
{
int d;
DEBUG(DEBUG_LARGE_SCALE, "\nTrying to find large scale attack moves.\n");
for (d = 0; d < number_of_dragons; d++) {
int target = dragon2[d].origin;
if (dragon[target].color == OTHER_COLOR(color)
&& dragon[target].size <= 6
&& dragon[target].status == CRITICAL
&& dragon2[d].owl_status == CRITICAL) {
DEBUG(DEBUG_LARGE_SCALE, "Small critical dragon found at %1m\n", target);
find_large_scale_owl_attacks_on_dragon(color, target);
}
}
}
/* Test certain moves to see whether they (too) can owl-attack or
* defend an owl critical dragon. Tested moves are
* 1. Strategical attacks or defenses for the dragon.
* 2. Vital eye points for the dragon.
* 3. Tactical attacks or defenses for a part of the dragon.
* 4. Moves connecting the dragon to something else.
*/
static void
find_more_owl_attack_and_defense_moves(int color)
{
int pos, pos2;
int k;
int dd = NO_MOVE;
int worth_trying;
int save_verbose;
struct eye_data *our_eyes;
struct eye_data *your_eyes;
struct vital_eye_points *our_vital_points;
struct vital_eye_points *your_vital_points;
if (verbose)
gprintf("\nTrying to upgrade strategical attack and defense moves.\n");
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (!ON_BOARD(pos))
continue;
for (k = 0; k < MAX_REASONS; k++) {
int r = move[pos].reason[k];
if (r < 0)
break;
do_find_more_owl_attack_and_defense_moves(color, pos,
move_reasons[r].type,
move_reasons[r].what);
}
}
if (verbose)
gprintf("\nTrying vital eye moves as owl attacks.\n");
if (color == WHITE) {
our_eyes = white_eye;
your_eyes = black_eye;
our_vital_points = white_vital_points;
your_vital_points = black_vital_points;
}
else {
our_eyes = black_eye;
your_eyes = white_eye;
our_vital_points = black_vital_points;
your_vital_points = white_vital_points;
}
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (!ON_BOARD(pos))
continue;
if (our_eyes[pos].origin == pos
&& our_vital_points[pos].defense_points[0] != NO_MOVE) {
int k, dr;
find_eye_dragons(pos, our_eyes, color, &dr, 1);
for (k = 0; k < MAX_EYE_ATTACKS; k++) {
int move = our_vital_points[pos].defense_points[k];
if (move == NO_MOVE)
break;
do_find_more_owl_attack_and_defense_moves(color, move,
VITAL_EYE_MOVE, dr);
}
}
if (your_eyes[pos].origin == pos
&& your_vital_points[pos].attack_points[0] != NO_MOVE) {
int k, dr;
find_eye_dragons(pos, your_eyes, OTHER_COLOR(color), &dr, 1);
for (k = 0; k < MAX_EYE_ATTACKS; k++) {
int move = your_vital_points[pos].attack_points[k];
if (move == NO_MOVE)
break;
do_find_more_owl_attack_and_defense_moves(color, move,
VITAL_EYE_MOVE, dr);
}
}
}
save_verbose = verbose;
if (verbose > 0)
verbose--;
/* If two critical dragons are adjacent, test whether a move to owl
* attack or defend one also is effective on the other.
*/
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (IS_STONE(board[pos])
&& dragon[pos].origin == pos
&& DRAGON2(pos).owl_status == CRITICAL) {
for (pos2 = BOARDMIN; pos2 < BOARDMAX; pos2++) {
if (board[pos2] != EMPTY)
continue;
worth_trying = 0;
for (k = 0; k < MAX_REASONS; k++) {
int r = move[pos2].reason[k];
if (r < 0)
break;
if (move_reasons[r].type == OWL_ATTACK_MOVE
|| move_reasons[r].type == OWL_ATTACK_MOVE_GOOD_KO
|| move_reasons[r].type == OWL_ATTACK_MOVE_BAD_KO
|| move_reasons[r].type == OWL_DEFEND_MOVE
|| move_reasons[r].type == OWL_DEFEND_MOVE_GOOD_KO
|| move_reasons[r].type == OWL_DEFEND_MOVE_BAD_KO) {
dd = move_reasons[r].what;
if (are_neighbor_dragons(dd, pos)) {
worth_trying = 1;
break;
}
}
/* else ...
FIXME: what about the new OWL_ATTACK_MOVE_GAIN codes ?
*/
}
if (worth_trying) {
if (board[pos] == color
&& !owl_defense_move_reason_known(pos2, pos)) {
int kworm = NO_MOVE;
int dcode = owl_does_defend(pos2, pos, &kworm);
if (dcode >= DRAGON2(pos).owl_defense_code) {
if (dcode == LOSS)
add_loss_move(pos2, pos, kworm);
else
add_owl_defense_move(pos2, pos, dcode);
if (save_verbose)
gprintf("Move at %1m also owl defends %1m (%s).\n",
pos2, pos, result_to_string(dcode));
}
}
else if (board[pos] != color
&& !owl_attack_move_reason_known(pos2, pos)) {
int kworm = NO_MOVE;
int acode = owl_does_attack(pos2, pos, &kworm);
if (acode >= DRAGON2(pos).owl_attack_code) {
add_owl_attack_move(pos2, pos, kworm, acode);
if (save_verbose)
gprintf("Move at %1m also owl attacks %1m (%s).\n",
pos2, pos, result_to_string(acode));
}
}
}
}
}
}
verbose = save_verbose;
}
/* Tests whether the potential semeai move at (pos) with details given via
* (*reason) works, and adds a semeai move if applicable.
*/
static void
try_potential_semeai_move(int pos, int color, struct move_reason *reason)
{
int dr1 = semeai_target1[reason->what];
int dr2 = semeai_target2[reason->what];
int resulta, resultb, certain, old_certain;
ASSERT1(IS_STONE(board[dr1]), pos);
switch (reason->type) {
case POTENTIAL_SEMEAI_ATTACK:
owl_analyze_semeai_after_move(pos, color, dr1, dr2,
&resulta, &resultb, NULL,
1, &certain, 0);
old_certain = DRAGON2(dr1).semeai_attack_certain;
break;
case POTENTIAL_SEMEAI_DEFENSE:
old_certain = DRAGON2(dr1).semeai_defense_certain;
/* In this case other dragon gets to move first after forced move. */
owl_analyze_semeai_after_move(pos, color, dr2, dr1,
&resulta, &resultb, NULL,
1, &certain, 0);
break;
default:
ASSERT1(0, pos);
}
if (resulta == 0 && resultb == 0
&& (certain || !old_certain)) {
add_semeai_move(pos, dr1);
DEBUG(DEBUG_SEMEAI,
"Potential semeai move at %1m for dragon at %1m is real\n",
pos, dr1);
}
else
DEBUG(DEBUG_MOVE_REASONS, "Potential semeai move at %1m for %1m discarded\n",
pos, dr1);
}
/* This functions tests all potential semeai attack moves whether they work,
* provided that there is at least one other move reasons stored for the
* relevant position.
*/
static void
find_more_semeai_moves(int color)
{
int pos;
int save_verbose = verbose;
if (verbose > 0)
verbose--;
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
int k, r;
int potential_semeai_move_found = 0;
int other_move_reason_found = 0;
if (!ON_BOARD1(pos))
continue;
for (k = 0; k < MAX_REASONS; k++) {
r = move[pos].reason[k];
if (r < 0)
break;
switch (move_reasons[r].type) {
case POTENTIAL_SEMEAI_ATTACK:
case POTENTIAL_SEMEAI_DEFENSE:
potential_semeai_move_found = 1;
break;
default:
other_move_reason_found = 1;
}
}
if ((r < 0 || k == MAX_REASONS)
&& !other_move_reason_found)
continue;
if (!potential_semeai_move_found)
continue;
for (k = 0; k < MAX_REASONS; k++) {
int r = move[pos].reason[k];
if (r < 0)
break;
if (move_reasons[r].type == POTENTIAL_SEMEAI_ATTACK
|| move_reasons[r].type == POTENTIAL_SEMEAI_DEFENSE)
try_potential_semeai_move(pos, color, &(move_reasons[r]));
}
}
verbose = save_verbose;
}
/*
* Any move that captures or defends a worm also potentially connects
* or cuts the surrounding strings. Find these secondary move reasons
* and verify them by connection reading.
*
* We also let an owl attack count as a strategical defense of our
* neighbors of the owl attacked dragon. We only do this for
* tactically safe dragons, however, because otherwise the effects of
* capturing have already been taken into account elsewhere.
*
* Also, connecting moves played on inhibited points possibly remove
* nearby connection inhibitions like in following example :
*
* .OX. The * move connects _all_ O stones together, not only
* O... the 2 lower ones.
* XO*O
* X.X.
*
*/
static void
induce_secondary_move_reasons(int color)
{
int pos;
int k;
int i, j;
int aa;
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (!ON_BOARD(pos))
continue;
for (k = 0; k < MAX_REASONS; k++) {
int r = move[pos].reason[k];
if (r < 0)
break;
if (move_reasons[r].type == ATTACK_MOVE
|| move_reasons[r].type == DEFEND_MOVE) {
int attack_move;
int color_to_move;
int num_adj, adjs[MAXCHAIN];
aa = move_reasons[r].what;
if (move_reasons[r].type == ATTACK_MOVE) {
attack_move = 1;
color_to_move = OTHER_COLOR(board[aa]);
}
else {
attack_move = 0;
color_to_move = board[aa];
}
if (worm[aa].defense_codes[0] == 0)
continue; /* No defense. */
/* Don't care about inessential dragons. */
if (DRAGON2(aa).safety == INESSENTIAL)
continue;
/*
* If this is a defense move and the defense is futile for
* strategical reasons, we shouldn't induce a cutting move
* reason.
*
* FIXME: We may want to revise this policy.
*/
if (!attack_move && !move[pos].move_safety)
continue;
num_adj = extended_chainlinks(aa, adjs, 1);
for (i = 0; i < num_adj; i++) {
for (j = i+1; j < num_adj; j++) {
int adj1 = adjs[i];
int adj2 = adjs[j];
if (board[adj1] != board[adj2])
continue;
if (attack_move
&& board[adj1] != board[aa]
&& !disconnect(adj1, adj2, NULL))
continue;
if (!attack_move
&& board[adj1] != board[aa]
&& !string_connect(adj1, adj2, NULL))
continue;
if (attack_move
&& board[adj1] == board[aa])
continue;
if (!attack_move
&& board[adj1] == board[aa]
&& !disconnect(adj1, adj2, NULL))
continue;
if (trymove(pos, color_to_move, "induce_secondary_move_reasons",
aa)) {
if (attack_move
&& board[adj1] != board[aa]
&& !disconnect(adj1, adj2, NULL)) {
popgo();
DEBUG(DEBUG_MOVE_REASONS,
"Connection move at %1m induced for %1m/%1m due to attack of %1m\n",
pos, adj1, adj2, aa);
add_connection_move(pos, adj1, adj2);
do_find_more_owl_attack_and_defense_moves(color, pos, CONNECT_MOVE,
find_connection(adj1, adj2));
}
else if (!attack_move
&& board[adj1] != board[aa]
&& !string_connect(adj1, adj2, NULL)) {
popgo();
DEBUG(DEBUG_MOVE_REASONS,
"Cut move at %1m induced for %1m/%1m due to defense of %1m\n",
pos, adj1, adj2, aa);
add_cut_move(pos, adj1, adj2);
}
else if (!attack_move
&& board[adj1] == board[aa]
&& !disconnect(adj1, adj2, NULL)) {
popgo();
DEBUG(DEBUG_MOVE_REASONS,
"Connection move at %1m induced for %1m/%1m due to defense of %1m\n",
pos, adj1, adj2, aa);
add_connection_move(pos, adj1, adj2);
do_find_more_owl_attack_and_defense_moves(color, pos, CONNECT_MOVE,
find_connection(adj1, adj2));
}
else
popgo();
}
}
}
/* Strategical attack move reason is induced for moves that
* defend neighbor strings of weak opponent dragons a. We
* only count strings that are large (more than three stones)
* or adjoin at least two non-dead non-single-stone opponent
* dragons.
*/
if (!attack_move) {
int strategically_valuable = (worm[aa].size > 3);
signed char neighbor_dragons[BOARDMAX];
memset(neighbor_dragons, 0, sizeof(neighbor_dragons));
if (!strategically_valuable) {
int num_dragons = 0;
for (i = 0; i < num_adj; i++) {
int origin = dragon[adjs[i]].origin;
if (board[origin] != color_to_move
&& neighbor_dragons[origin] != 1
&& dragon[origin].size > 1
&& dragon[origin].status != DEAD) {
if (++num_dragons == 2) {
strategically_valuable = 1;
break;
}
neighbor_dragons[origin] = 1;
}
}
}
if (strategically_valuable) {
for (i = 0; i < num_adj; i++) {
int origin = dragon[adjs[i]].origin;
if (board[origin] != color_to_move
&& neighbor_dragons[origin] != 2
&& dragon[origin].status != DEAD
&& dragon_weak(origin)) {
DEBUG(DEBUG_MOVE_REASONS,
"Strategical attack move at %1m induced for %1m due to defense of %1m\n",
pos, origin, aa);
add_strategical_attack_move(pos, origin);
do_find_more_owl_attack_and_defense_moves(color, pos,
STRATEGIC_ATTACK_MOVE,
origin);
neighbor_dragons[origin] = 2;
}
}
}
}
}
else if (move_reasons[r].type == OWL_ATTACK_MOVE) {
aa = move_reasons[r].what;
for (i = 0; i < DRAGON2(aa).neighbors; i++) {
int bb = dragon2[DRAGON2(aa).adjacent[i]].origin;
if (dragon[bb].color == color && worm[bb].attack_codes[0] == 0
&& !DRAGON2(bb).semeais) {
add_strategical_defense_move(pos, bb);
do_find_more_owl_attack_and_defense_moves(color, pos,
STRATEGIC_DEFEND_MOVE,
bb);
DEBUG(DEBUG_MOVE_REASONS, "Strategic defense at %1m induced for %1m due to owl attack on %1m\n",
pos, bb, aa);
}
}
}
else if (move_reasons[r].type == CONNECT_MOVE
&& cut_possible(pos, OTHER_COLOR(color))) {
int worm1 = conn_worm1[move_reasons[r].what];
int worm2 = conn_worm2[move_reasons[r].what];
int pos2;
for (pos2 = BOARDMIN; pos2 < BOARDMAX; pos2++) {
if (ON_BOARD(pos2) && board[pos2] == EMPTY
&& cut_possible(pos2, OTHER_COLOR(color))
&& square_dist(pos, pos2) <= 5) {
for (j = 0; j < 8; j++) {
int pos3 = pos2 + delta[j];
if (ON_BOARD(pos3) && board[pos3] == color
&& !is_same_worm(pos3, worm1)
&& !is_same_worm(pos3, worm2)) {
if (trymove(pos, color, "induce_secondary_move_reasons-B",
worm1)) {
int break1 = disconnect(pos3, worm1, NULL);
int break2 = disconnect(pos3, worm2, NULL);
popgo();
if (!break1) {
add_connection_move(pos, pos3, worm1);
do_find_more_owl_attack_and_defense_moves(color, pos, CONNECT_MOVE,
find_connection(pos3, worm1));
DEBUG(DEBUG_MOVE_REASONS, "Connection at %1m induced for %1m/%1m due to connection at %1m/%1m\n",
pos, worm1, worm2, pos3, worm1);
}
if (!break2) {
add_connection_move(pos, pos3, worm2);
do_find_more_owl_attack_and_defense_moves(color, pos, CONNECT_MOVE,
find_connection(pos3, worm2));
DEBUG(DEBUG_MOVE_REASONS, "Connection at %1m induced for %1m/%1m due to connection at %1m/%1m\n",
pos, worm1, worm2, pos3, worm2);
}
}
}
}
}
}
}
}
}
}
/* Examine the strategical and tactical safety of the moves. This is
* used to decide whether or not the stone should generate influence
* when the move is evaluated. The idea is to avoid overestimating the
* value of strategically unsafe defense moves and connections of dead
* dragons. This sets the move.move_safety field.
*/
static void
examine_move_safety(int color)
{
int pos;
int k;
start_timer(3);
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
int safety = 0;
int tactical_safety = 0;
if (!ON_BOARD(pos))
continue;
tactical_safety = is_known_safe_move(pos);
for (k = 0; k < MAX_REASONS; k++) {
int r = move[pos].reason[k];
int type;
int what;
if (r == -1)
break;
type = move_reasons[r].type;
what = move_reasons[r].what;
switch (type) {
case CUT_MOVE:
/* We don't trust cut moves, unless some other move reason
* indicates they are safe.
*/
break;
case OWL_DEFEND_MOVE:
case OWL_DEFEND_MOVE_GOOD_KO:
case OWL_DEFEND_MOVE_BAD_KO:
case OWL_DEFEND_MOVE_LOSS:
{
int ii;
for (ii = first_worm_in_dragon(what); ii != NO_MOVE;
ii = next_worm_in_dragon(ii)) {
if (!play_connect_n(color, 0, 1, pos, ii, pos))
break;
}
if (ii != NO_MOVE) {
tactical_safety = 1;
safety = 1;
}
break;
}
case SEMEAI_MOVE:
case MY_ATARI_ATARI_MOVE:
case YOUR_ATARI_ATARI_MOVE:
case EITHER_MOVE: /* FIXME: More advanced handling? */
tactical_safety = 1;
safety = 1;
break;
case ALL_MOVE:
/* We don't trust these, unless some other move reason
* indicates safety.
*/
break;
case EXPAND_TERRITORY_MOVE:
case EXPAND_MOYO_MOVE:
case INVASION_MOVE: /* A real invasion should be safe.
A sacrifice is something else.*/
safety = 1;
break;
case ATTACK_MOVE:
case ATTACK_MOVE_GOOD_KO:
case ATTACK_MOVE_BAD_KO:
case OWL_ATTACK_MOVE:
case OWL_ATTACK_MOVE_GOOD_KO:
case OWL_ATTACK_MOVE_BAD_KO:
case OWL_ATTACK_MOVE_GAIN:
{
int aa = NO_MOVE;
int bb = NO_MOVE;
int size;
int m;
int our_color_neighbors;
if (type == ATTACK_MOVE
|| type == ATTACK_MOVE_GOOD_KO
|| type == ATTACK_MOVE_BAD_KO) {
aa = what;
size = worm[aa].effective_size;
}
else if (type == OWL_ATTACK_MOVE_GAIN) {
aa = either_data[what].what2;
size = worm[aa].effective_size;
}
else {
aa = what;
size = dragon[aa].effective_size;
}
/* No worries if we catch something big. */
if (size >= 8) {
tactical_safety = 1;
safety = 1;
break;
}
/* If the victim has multiple neighbor dragons of our
* color, we leave it to the connection move reason to
* determine safety.
*
* The exception is an owl_attack where we only require
* one neighbor to be alive.
*/
our_color_neighbors = 0;
if (type == ATTACK_MOVE
|| type == ATTACK_MOVE_GOOD_KO
|| type == ATTACK_MOVE_BAD_KO) {
/* We could use the same code as for OWL_ATTACK_MOVE
* below if we were certain that the capturable string
* had not been amalgamated with a living dragon.
*/
int num_adj, adjs[MAXCHAIN];
num_adj = chainlinks(aa, adjs);
for (m = 0; m < num_adj; m++) {
int adj = adjs[m];
if (board[adj] == color) {
/* Check whether this string is part of the same
* dragon as an earlier string. We only want to
* count distinct neighbor dragons.
*/
int n;
for (n = 0; n < m; n++)
if (dragon[adjs[n]].id == dragon[adj].id)
break;
if (n == m) {
/* New dragon. */
our_color_neighbors++;
bb = adj;
}
}
}
}
else {
for (m = 0; m < DRAGON2(aa).neighbors; m++)
if (DRAGON(DRAGON2(aa).adjacent[m]).color == color) {
our_color_neighbors++;
bb = dragon2[DRAGON2(aa).adjacent[m]].origin;
if (dragon[bb].status == ALIVE) {
tactical_safety = 1;
safety = 1;
}
}
}
if (our_color_neighbors > 1)
break;
/* It may happen in certain positions that no neighbor of
* our color is found. The working hypothesis is that
* the move is safe then. One example is a position like
*
* ----+
* OX.X|
* OOX.|
* OOX|
* OO|
*
* where the top right stone only has friendly neighbors
* but can be attacked.
*
* As a further improvement, we also look for a friendly
* dragon adjacent to the considered move.
*/
for (m = 0; m < 4; m++) {
int d = delta[m];
if (board[pos+d] == color) {
bb = pos + d;
break;
}
}
if (bb == NO_MOVE) {
tactical_safety = 1;
safety = 1;
break;
}
/* If the attacker is thought to be alive, we trust that
* sentiment.
*/
if (dragon[bb].status == ALIVE) {
tactical_safety = 1;
safety = 1;
break;
}
/* It remains the possibility that what we have captured
* is just a nakade shape. Ask the owl code whether this
* move saves our attacking dragon.
*
* FIXME: Might need to involve semeai code too here.
*/
if (owl_does_defend(pos, bb, NULL)) {
tactical_safety = 1;
safety = 1;
}
break;
}
case DEFEND_MOVE:
case DEFEND_MOVE_GOOD_KO:
case DEFEND_MOVE_BAD_KO:
{
int aa = what;
if (dragon[aa].status == ALIVE)
/* It would be better if this never happened, but it does
* sometimes. The owl reading can be very slow then.
*/
safety = 1;
else if (!play_connect_n(color, 0, 1, pos, aa, pos)
&& owl_does_defend(pos, aa, NULL))
safety = 1;
break;
}
case ATTACK_THREAT:
case DEFEND_THREAT:
break;
case CONNECT_MOVE:
{
int worm1 = conn_worm1[move_reasons[r].what];
int worm2 = conn_worm2[move_reasons[r].what];
int aa = dragon[worm1].origin;
int bb = dragon[worm2].origin;
if (aa == bb)
continue;
if (DRAGON2(aa).owl_status == ALIVE
|| DRAGON2(bb).owl_status == ALIVE) {
tactical_safety = 1;
safety = 1;
}
else if ((DRAGON2(aa).owl_status == UNCHECKED
&& dragon[aa].crude_status == ALIVE)
|| (DRAGON2(bb).owl_status == UNCHECKED
&& dragon[bb].crude_status == ALIVE)) {
tactical_safety = 1;
safety = 1;
}
else if (owl_connection_defends(pos, aa, bb)) {
tactical_safety = 1;
safety = 1;
}
break;
}
}
if (safety == 1 && (tactical_safety == 1 || safe_move(pos, color)))
break;
}
if (safety == 1 && (tactical_safety || safe_move(pos, color)))
move[pos].move_safety = 1;
else
move[pos].move_safety = 0;
time_report(3, " examine_move_safety: ", pos, 1.0);
}
}
/*
* Returns the pre-computed weakness of a dragon, with corrections
* according to ignore_dead_dragons.
*
* FIXME: Important to test more exactly how effective a strategical
* attack or defense of a weak dragon is. This can be done by
* measuring escape factor and moyo size after the move and
* compare with the old values. Also necessary to test whether
* an attack or defense of a critical dragon is effective.
* Notice that this wouldn't exactly go into this function but
* rather where it's called.
*/
float
dragon_weakness(int dr, int ignore_dead_dragons)
{
int dragon_safety = DRAGON2(dr).safety;
/* Kludge: If a dragon is dead, we return 1.0 in order not
* to try to run away.
*/
if (ignore_dead_dragons
&& (dragon_safety == DEAD
|| dragon_safety == INESSENTIAL
|| dragon_safety == TACTICALLY_DEAD))
return 0.0;
/* When scoring, we don't want to reinforce ALIVE dragons. */
if (doing_scoring && dragon_safety == ALIVE)
return 0.0;
return DRAGON2(dr).weakness;
}
/*
* Strategical value of connecting (or cutting) the dragon at (dragona)
* to the dragon at (dragonb). Notice that this function is asymmetric.
* This is because connection_value(a, b) is intended to measure the
* strategical value on the a dragon from a connection to the b dragon.
*
* Consider the following position:
* +---------+
* |XXO.O.OXX|
* |.XOOOOOX.|
* |XXXX.XXXX|
* |.XOOXOOX.|
* |XXO.X.O.X|
* |OOOXXXOOO|
* |..OOOOO..|
* |.........|
* +---------+
*
* X has three dragons, one invincible to the left (A), one critical to
* the right (B), and one dead in the center (C). The move at the cutting
* point has three move reasons:
* connect A and B
* connect A and C
* connect B and C
*
* The strategical value on A of either connection is of course zero,
* since it's very unconditionally alive. The strategical value on B is
* high when it's connected to A but small (at least should be) from the
* connection to C. Similarly for dragon C. In effect the total
* strategical value of this move is computed as:
*
* max(connection_value(A, B), connection_value(A, C))
* + max(connection_value(B, A), connection_value(B, C))
* + max(connection_value(C, A), connection_value(C, B))
*
* The parameter 'margin' is the margin by which we are ahead.
* If this exceeds 20 points we value connections more. This is because
* we can afford to waste a move making sure of safety.
*/
static float
connection_value(int dragona, int dragonb, int tt, float margin)
{
struct dragon_data2 *da = &DRAGON2(dragona);
struct dragon_data2 *db = &DRAGON2(dragonb);
float sizea = da->strategic_size;
float sizeb = db->strategic_size;
int safetya = da->safety;
int safetyb = db->safety;
float crude_weakness_a
= crude_dragon_weakness(da->safety, &da->genus, da->lunch != NO_MOVE,
da->moyo_territorial_value,
(float) da->escape_route);
float crude_weakness_sum;
struct eyevalue genus_sum;
float terr_val = move[tt].territorial_value;
float return_value;
if (margin > 20.0)
margin = 20.0;
/* When scoring, we want to be restrictive with reinforcement moves.
* Thus if both dragons are alive, strongly alive, or invincible, no
* bonus is awarded.
*
* FIXME: Shouldn't it be sufficient to check this for dragon a?
*/
if (doing_scoring) {
if ((safetya == ALIVE
|| safetya == STRONGLY_ALIVE
|| safetya == INVINCIBLE)
&& (safetyb == ALIVE
|| safetyb == STRONGLY_ALIVE
|| safetyb == INVINCIBLE))
return 0.0;
}
if (safetyb == INESSENTIAL)
return 0.0;
if (crude_weakness_a == 0.0
|| dragon[dragona].status == DEAD)
return 0.0;
if (terr_val < 0.0)
terr_val = 0.0;
add_eyevalues(&da->genus, &db->genus, &genus_sum);
/* FIXME: There is currently no sane way to take the escape values
* into account. Hence we simply pretend they do not change.
*
* FIXME: terr_val is a very crude approximation to the expected
* increase in moyo size. It's especially way off if the move at (tt)
* (owl) defends some stones.
*/
crude_weakness_sum
= crude_dragon_weakness(safetyb, &genus_sum,
(da->lunch != NO_MOVE || db->lunch != NO_MOVE),
da->moyo_territorial_value
+ db->moyo_territorial_value
+ terr_val,
(float) da->escape_route);
/* Kludge: For a CRITICAL dragon, we use the usual effective
* size and give a strategic effect bigger than 2.0 * effective size.
* This is to match the "strategic bonus computation" in
* estimate_strategical_value(). This prefers connection moves that
* owl defend a dragon to other owl defense move.
*/
if (dragon[dragona].status == CRITICAL) {
float bonus = (0.4 - 0.3 * crude_weakness_sum) * sizea;
if (bonus < 0.0)
bonus = 0.0;
/* If ahead, give extra bonus to connections. */
if (margin > 0.0 && bonus > 0.0)
bonus *= 1.0 + 0.05 * margin;
return_value = 2.0 * sizea + bonus;
}
else {
float old_burden = 2.0 * crude_weakness_a * soft_cap(sizea, 15.0);
/* The new burden is the burden of defending new joint dragon; but
* we share this burden proportionally with the other dragon.
*/
float new_burden = 2.0 * crude_weakness_sum * soft_cap(sizea + sizeb, 15.0)
* sizea / (sizea + sizeb);
return_value = 1.05 * (old_burden - new_burden);
/* If ahead, give extra bonus to connections. */
if (margin > 0.0)
return_value *= 1.0 + 0.02 * margin;
}
if (return_value < 0.0)
return_value = 0.0;
return return_value;
}
/*
* This function computes the shape factor, which multiplies
* the score of a move. We take the largest positive contribution
* to shape and add 1 for each additional positive contribution found.
* Then we take the largest negative contribution to shape, and
* add 1 for each additional negative contribution. The resulting
* number is raised to the power 1.05.
*
* The rationale behind this complicated scheme is that every
* shape point is very significant. If two shape contributions
* with values (say) 5 and 3 are found, the second contribution
* should be devalued to 1. Otherwise the engine is too difficult to
* tune since finding multiple contributions to shape can cause
* significant overvaluing of a move.
*/
static float
compute_shape_factor(int pos)
{
float exponent = move[pos].maxpos_shape - move[pos].maxneg_shape;
ASSERT_ON_BOARD1(pos);
if (move[pos].numpos_shape > 1)
exponent += move[pos].numpos_shape - 1;
if (move[pos].numneg_shape > 1)
exponent -= move[pos].numneg_shape - 1;
return pow(1.05, exponent);
}
/*
* Usually the value of attacking a worm is twice its effective size,
* but when evaluating certain move reasons we need to adjust this to
* take effects on neighbors into account, e.g. for an attack_either
* move reason. This does not apply to the attack and defense move
* reasons, however, because then the neighbors already have separate
* attack or defense move reasons (if such apply).
*
* If the worm has an adjacent (friendly) dead dragon we add its
* value. At least one of the surrounding dragons must be alive.
* If not, the worm must produce an eye of sufficient size, and that
* should't be accounted for here. As a guess, we suppose that
* a critical dragon is alive for our purpose here.
*
* On the other hand if it has an adjacent critical worm, and
* if (pos) does not defend that worm, we subtract the value of the
* worm, since (pos) may be defended by attacking that worm. We make at
* most one adjustment of each type.
*/
static float
adjusted_worm_attack_value(int pos, int ww)
{
int num_adj;
int adjs[MAXCHAIN];
int has_live_neighbor = 0;
float adjusted_value = 2 * worm[ww].effective_size;
float adjustment_up = 0.0;
float adjustment_down = 0.0;
int s;
num_adj = chainlinks(ww, adjs);
for (s = 0; s < num_adj; s++) {
int adj = adjs[s];
if (dragon[adj].status != DEAD)
has_live_neighbor = 1;
if (dragon[adj].status == DEAD
&& 2*dragon[adj].effective_size > adjustment_up)
adjustment_up = 2*dragon[adj].effective_size;
if (worm[adj].attack_codes[0] != 0
&& !does_defend(pos, adj)
&& 2*worm[adj].effective_size > adjustment_down)
adjustment_down = 2*worm[adj].effective_size;
}
if (has_live_neighbor)
adjusted_value += adjustment_up;
adjusted_value -= adjustment_down;
/* It can happen that the adjustment down was larger than the effective
* size we started with. In this case we simply return 0.0. (This means
* we ignore the respective EITHER_MOVE reason.)
*/
if (adjusted_value > 0.0)
return adjusted_value;
else
return 0.0;
}
/* The new (3.2) territorial evaluation overvalues moves creating a new
* group in the opponent's sphere of influence. The influence module cannot
* see that the opponent will gain by attacking the new (probably weak)
* group.
* This function uses some heuristics to estimate the strategic penalty
* of invasion moves, and moves that try to run away with a group of size
* 1 in front of opponent's strength.
*/
static float
strategic_penalty(int pos, int color)
{
int k;
float ret_val;
/* We try to detect support from an alive friendly stone by checking
* whether all neighboring intersections belong to the opponent's moyo.
*/
for (k = 0; k < 4; k++)
if (board[pos + delta[k]] == EMPTY
&& whose_area(OPPOSITE_INFLUENCE(color), pos + delta[k])
!= OTHER_COLOR(color))
return 0.0;
if (whose_area(OPPOSITE_INFLUENCE(color), pos) != OTHER_COLOR(color))
return 0.0;
for (k = 0; k < MAX_REASONS; k++) {
int r = move[pos].reason[k];
if (r < 0)
break;
/* We assume that invasion moves can only have the move reasons listed
* below.
*
* FIXME: EXPAND_TERRITORY should always be connected to our own
* stones. Remove later when that change is done.
*/
switch (move_reasons[r].type) {
#if 0
case EXPAND_TERRITORY_MOVE:
#endif
case EXPAND_MOYO_MOVE:
case STRATEGIC_ATTACK_MOVE:
case INVASION_MOVE:
continue;
/* If we find a tactical defense move, we just test whether it concerns
* a single-stone-dragon; if not, we stop, if yes, we let the necessary
* tests be made in the OWL_DEFEND_MOVE case.
*/
case DEFEND_MOVE:
{
int target = move_reasons[r].what;
if (dragon[target].size > 1)
return 0.0;
continue;
}
/* An owl defense of a single stone might be a stupid attempt to run
* away with an unimportant (kikashi like) stone. We assume this is the
* case if this single stone has a strong hostile direct neighbor.
*/
case OWL_DEFEND_MOVE:
{
int target = move_reasons[r].what;
int has_strong_neighbor = 0;
int has_weak_neighbor = 0;
int i;
/* We award no penalty for running away with a cutting stone. */
if (dragon[target].size > 1
|| worm[target].cutstone > 0
|| worm[target].cutstone2 > 0)
return 0.0;
/* Third line moves (or lower) are ok -- they try to live, not run
* away.
*/
if (edge_distance(pos) < 3)
return 0.0;
for (i = 0; i < 4; i++)
if (board[target + delta[i]] == OTHER_COLOR(color)) {
if (dragon[target + delta[i]].size == 1) {
has_weak_neighbor = 1;
break;
}
switch (DRAGON2(target + delta[i]).safety) {
case INVINCIBLE:
case STRONGLY_ALIVE:
has_strong_neighbor = 1;
break;
case ALIVE:
if (DRAGON2(target + delta[i]).weakness > 0.4)
has_weak_neighbor = 1;
break;
default:
has_weak_neighbor = 1;
}
}
if (has_weak_neighbor || (!has_strong_neighbor))
return 0.0;
else
continue;
}
default:
return 0.0;
}
}
/* We have to make a guess how much the point where we want to play
* is dominated by the opponent. The territorial valuation is a
* good try here.
*/
ret_val = influence_territory(INITIAL_INFLUENCE(OTHER_COLOR(color)),
pos, OTHER_COLOR(color));
ret_val *= 12.0;
ret_val = gg_max(0.0, ret_val);
return ret_val;
}
/* True if pos is adjacent to a nondead stone of the given color. This
* function can be called when stackp>0 but the result is given for
* the position when stackp==0. It also checks for nondead stones two
* steps away from pos if a move by color at pos cannot be cut off
* from that stone.
*
* FIXME: Move this somewhere more generally accessible, probably
* utils.c
*/
int
adjacent_to_nondead_stone(int pos, int color)
{
int k;
int stack[MAXSTACK];
int move_color[MAXSTACK];
int saved_stackp = stackp;
int result = 0;
while (stackp > 0) {
get_move_from_stack(stackp - 1, &stack[stackp - 1],
&move_color[stackp - 1]);
popgo();
}
if (trymove(pos, color, NULL, EMPTY)) {
for (k = 0; k < 12; k++) {
int pos2;
if (k < 8)
pos2 = pos + delta[k];
else if (ON_BOARD(pos + delta[k - 8]))
pos2 = pos + 2 * delta[k - 8];
else
continue;
if (ON_BOARD(pos2)
&& worm[pos2].color == color
&& dragon[pos2].status != DEAD
&& !disconnect(pos, pos2, NULL)) {
result = 1;
break;
}
}
popgo();
}
while (stackp < saved_stackp)
tryko(stack[stackp], move_color[stackp], NULL);
return result;
}
static int
max_lunch_eye_value(int pos)
{
int min;
int probable;
int max;
estimate_lunch_eye_value(pos, &min, &probable, &max, 0);
return max;
}
/*
* Estimate the direct territorial value of a move at (pos) by (color).
*/
static void
estimate_territorial_value(int pos, int color, float our_score,
int disable_delta_territory_cache)
{
int other = OTHER_COLOR(color);
int k;
int aa = NO_MOVE;
int bb = NO_MOVE;
float this_value = 0.0;
float tot_value = 0.0;
float secondary_value = 0.0;
int does_block = 0;
signed char safe_stones[BOARDMAX];
float strength[BOARDMAX];
set_strength_data(OTHER_COLOR(color), safe_stones, strength);
for (k = 0; k < MAX_REASONS; k++) {
int r = move[pos].reason[k];
if (r < 0)
break;
if (move_reasons[r].status & TERRITORY_REDUNDANT)
continue;
this_value = 0.0;
switch (move_reasons[r].type) {
case ATTACK_MOVE:
case ATTACK_MOVE_GOOD_KO:
case ATTACK_MOVE_BAD_KO:
aa = move_reasons[r].what;
ASSERT1(board[aa] != color, aa);
/* Defenseless stone. */
if (worm[aa].defense_codes[0] == 0) {
DEBUG(DEBUG_MOVE_REASONS,
" %1m: %f (secondary) - attack on %1m (defenseless)\n",
pos, worm[aa].effective_size, aa);
secondary_value += worm[aa].effective_size;
does_block = 1;
break;
}
this_value = 2 * worm[aa].effective_size;
/* If the stones are dead, there is only a secondary value in
* capturing them tactically as well.
*/
if (dragon[aa].status == DEAD) {
DEBUG(DEBUG_MOVE_REASONS,
" %1m: %f (secondary) - attack on %1m (dead)\n",
pos, 0.2 * this_value, aa);
secondary_value += 0.2 * this_value;
does_block = 1;
break;
}
/* Mark the string as captured, for evaluation in the influence code. */
mark_changed_string(aa, safe_stones, strength, 0);
TRACE(" %1m: attack on worm %1m\n", pos, aa);
/* FIXME: How much should we reduce the value for ko attacks? */
if (move_reasons[r].type == ATTACK_MOVE)
this_value = 0.0;
else if (move_reasons[r].type == ATTACK_MOVE_GOOD_KO) {
this_value *= 0.3;
TRACE(" %1m: -%f - attack on worm %1m only with good ko\n",
pos, this_value, aa);
}
else if (move_reasons[r].type == ATTACK_MOVE_BAD_KO) {
this_value *= 0.5;
TRACE(" %1m: -%f - attack on worm %1m only with bad ko\n",
pos, this_value, aa);
}
tot_value -= this_value;
does_block = 1;
break;
case DEFEND_MOVE:
case DEFEND_MOVE_GOOD_KO:
case DEFEND_MOVE_BAD_KO:
aa = move_reasons[r].what;
ASSERT1(board[aa] == color, aa);
/*
* Estimate value
*/
this_value = 2 * worm[aa].effective_size;
/* If the stones are dead, we use the convention that
* defending them has a strategical value rather than
* territorial. Admittedly this make more sense for attacks on
* dead stones.
*/
if (dragon[aa].status == DEAD) {
DEBUG(DEBUG_MOVE_REASONS,
" %1m: %f (secondary) - defense of %1m (dead)\n",
pos, 0.2 * this_value, aa);
secondary_value += 0.2 * this_value;
break;
}
if (DRAGON2(aa).owl_status == CRITICAL
&& (owl_defense_move_reason_known(pos, aa)
< defense_move_reason_known(pos, aa))
&& !semeai_move_reason_known(pos, aa)) {
DEBUG(DEBUG_MOVE_REASONS,
" %1m: %f (secondary) - ineffective defense of %1m (critical)\n",
pos, 0.2 * this_value, aa);
secondary_value += 0.2 * this_value;
break;
}
/* Mark the string as saved, for evaluation in the influence code. */
mark_changed_string(aa, safe_stones, strength, INFLUENCE_SAVED_STONE);
TRACE(" %1m: defense of worm %1m\n", pos, aa);
/* FIXME: How much should we reduce the value for ko defenses? */
if (move_reasons[r].type == DEFEND_MOVE)
this_value = 0.0;
else if (move_reasons[r].type == DEFEND_MOVE_GOOD_KO) {
this_value *= 0.3;
TRACE(" %1m: -%f - defense of worm %1m with good ko\n",
pos, this_value, aa);
}
else if (move_reasons[r].type == DEFEND_MOVE_BAD_KO) {
this_value *= 0.5;
TRACE(" %1m: -%f - defense of worm %1m with bad ko\n",
pos, this_value, aa);
}
tot_value -= this_value;
/* If a move tactically defends an owl critical string, but
* this move is not listed as an owl defense, it probably is
* ineffective. The 0.45 factor is chosen so that even in
* combination with bad ko it still has a positive net impact.
*/
if (DRAGON2(aa).owl_status == CRITICAL
&& (owl_defense_move_reason_known(pos, aa)
< defense_move_reason_known(pos, aa))) {
this_value = 0.45 * (2 * worm[aa].effective_size);
TRACE(" %1m: -%f - suspected ineffective defense of worm %1m\n",
pos, this_value, aa);
tot_value -= this_value;
}
does_block = 1;
break;
case ATTACK_THREAT:
aa = move_reasons[r].what;
/* Make sure this is a threat to attack opponent stones. */
ASSERT1(board[aa] == other, aa);
if (dragon[aa].status == DEAD) {
DEBUG(DEBUG_MOVE_REASONS,
" %1m: 0.0 - threatens to capture %1m (dead)\n", pos, aa);
break;
}
/* The followup value of a move threatening to attack (aa)
* is twice its effective size, with adjustments. If the
* worm has an adjacent (friendly) dead dragon we add its
* value. On the other hand if it has an adjacent critical
* worm, and if (pos) does not defend that worm, we subtract
* the value of the worm, since (aa) may be defended by
* attacking that worm. We make at most one adjustment
* of each type.
*
* No followup value is awarded if the defense move is a threat
* back on our move because we're likely to end in gote then,
* unless the move is unsafe anyway and played as a ko threat.
*
* FIXME: It might be possible that parts of the dragon
* can be cut in the process of capturing the (aa)
* worm. In that case, not the entire size of the
* adjacent dead dragon should be counted as a positive
* adjustment. However, it seems difficult to do this
* analysis, and in most cases it won't apply, so we
* leave it as it is for now.
*
* FIXME: The same analysis should be applied to
* DEFEND_THREAT,
* ATTACK_EITHER_MOVE, DEFEND_BOTH_MOVE. It should be
* broken out as separate functions and dealt with in
* a structured manner.
*/
if (trymove(pos, color, "estimate_territorial_value-A", NO_MOVE)) {
int adjs[MAXCHAIN];
float adjusted_value = 2 * worm[aa].effective_size;
float adjustment_up = 0.0;
float adjustment_down = 0.0;
int s;
int num_adj;
int defense_move;
/* In rare cases it may happen that the trymove() above
* actually removed the string at aa.
*/
if (board[aa] == EMPTY)
num_adj = 0;
else
num_adj = chainlinks(aa, adjs);
/* No followup value if string can be defended with threat
* against our move. An exception to this is when our move
* isn't safe anyway and we play this only for the followup
* value, typically as a ko threat. Though, "suspicious" owl
* defenses (move_safety != 1) are still tested for possible
* backfires.
*
* This rule may be overwritten with patterns. See pattern
* Sente22 and related test trevord:950 for an example.
*
* FIXME: This is somewhat halfhearted since only one defense
* move is tested.
*/
if (!is_known_good_attack_threat(pos, aa)
&& board[aa] != EMPTY
&& (move[pos].move_safety == 1
|| adjacent_to_nondead_stone(pos, color)
|| owl_defense_move_reason_known(pos, -1))
&& find_defense(aa, &defense_move) == WIN
&& defense_move != NO_MOVE) {
int bad_followup;
int attack_move;
if (attack(pos, &attack_move) != WIN) {
int i;
if (trymove(defense_move, other,
"estimate_territorial_value-b", NO_MOVE)) {
if (board[pos] == EMPTY || attack(pos, NULL) != 0) {
popgo();
popgo();
break;
}
/* Now check all `ATTACK_MOVE' reasons for this same
* move. If the defense against current threat makes a
* string attacked by this move defendable, we reduce
* the followup.
*
* Adjustments done later are concerned with current
* dragon states. Here we actually try to check if
* opponent's reply to our move will have a followup in
* turn.
*/
for (i = 0; i < MAX_REASONS; i++) {
int reason = move[pos].reason[i];
int attacked_string;
if (reason < 0)
break;
attacked_string = move_reasons[reason].what;
if (move_reasons[reason].type == ATTACK_MOVE
&& board[attacked_string] == other) {
int defense_code = find_defense(attacked_string, NULL);
double down_coefficient = 0.0;
switch (defense_code) {
case WIN:
down_coefficient = 2.0;
break;
case KO_A:
down_coefficient = 2.0 * 0.5;
break;
case KO_B:
down_coefficient = 2.0 * 0.7;
break;
}
if (adjustment_down
< (worm[attacked_string].effective_size
* down_coefficient)) {
adjustment_down = (worm[attacked_string].effective_size
* down_coefficient);
}
}
}
popgo();
}
}
else {
/* Our move is attackable to begin with. However, maybe
* the attack is not sufficient to defend opponent's
* string?
*/
if (trymove(attack_move, other,
"estimate_territorial_value-c", NO_MOVE)) {
if (attack(aa, NULL) == 0) {
/* It is sufficient, no followup. */
popgo();
popgo();
break;
}
popgo();
}
/* Heuristically reduce the followup, since our string
* will be still attackable if opponent defends his
* string.
*/
adjustment_down = 2 * countstones(pos);
}
bad_followup = 0;
for (s = 0; s < num_adj; s++) {
int lib;
if (countlib(adjs[s]) == 1) {
findlib(adjs[s], 1, &lib);
if (trymove(lib, other,
"estimate_territorial_value-d", NO_MOVE)) {
if (!attack(aa, NULL)
&& (board[pos] == EMPTY || attack(pos, NULL) != 0)) {
popgo();
bad_followup = 1;
break;
}
popgo();
}
}
}
if (bad_followup) {
popgo();
break;
}
}
for (s = 0; s < num_adj; s++) {
int adj = adjs[s];
if (same_string(pos, adj))
continue;
if (dragon[adj].color == color
&& dragon[adj].status == DEAD
&& 2*dragon[adj].effective_size > adjustment_up)
adjustment_up = 2*dragon[adj].effective_size;
if (dragon[adj].color == color
&& attack(adj, NULL)
&& 2*worm[adj].effective_size > adjustment_down)
adjustment_down = 2*worm[adj].effective_size;
}
popgo();
/* No followup if the string is not substantial. */
{
int save_verbose = verbose;
if (verbose > 0)
verbose --;
if (move[pos].move_safety == 0
&& !owl_substantial(aa)) {
verbose = save_verbose;
break;
}
verbose = save_verbose;
}
adjusted_value += adjustment_up;
adjusted_value -= adjustment_down;
if (adjusted_value > 0.0) {
add_followup_value(pos, adjusted_value);
TRACE(" %1m: %f (followup) - threatens to capture %1m\n",
pos, adjusted_value, aa);
}
}
break;
case DEFEND_THREAT:
aa = move_reasons[r].what;
/* Make sure this is a threat to defend our stones. */
ASSERT1(board[aa] == color, aa);
/* We don't trust tactical defense threats as ko threats, unless
* the move is safe.
*/
if (move[pos].move_safety == 0)
break;
/* No followup value if string can be attacked with threat
* against our move. An exception to this is when our move
* isn't safe anyway and we play this only for the followup
* value, typically as a ko threat.
*
* FIXME: This is somewhat halfhearted since only one attack
* move is tested.
*/
if (trymove(pos, color, "estimate_territorial_value-A", NO_MOVE)) {
int attack_move;
if (move[pos].move_safety == 1
&& attack(aa, &attack_move) == WIN
&& attack_move != NO_MOVE) {
if (trymove(attack_move, other,
"estimate_territorial_value-b", NO_MOVE)) {
if (board[pos] == EMPTY || attack(pos, NULL) != 0) {
popgo();
popgo();
break;
}
popgo();
}
}
popgo();
}
add_followup_value(pos, 2 * worm[aa].effective_size);
TRACE(" %1m: %f (followup) - threatens to defend %1m\n",
pos, 2 * worm[aa].effective_size, aa);
break;
case UNCERTAIN_OWL_DEFENSE:
/* This move reason is valued as a strategical value. */
break;
case CUT_MOVE:
case EXPAND_MOYO_MOVE:
case EXPAND_TERRITORY_MOVE:
case INVASION_MOVE:
does_block = 1;
break;
case CONNECT_MOVE:
/* This used to always set does_block=1, but there is no
* guarantee that a connection move is strategically safe. See
* for example gunnar:72.
*/
if (move[pos].move_safety)
does_block = 1;
break;
case STRATEGIC_ATTACK_MOVE:
case STRATEGIC_DEFEND_MOVE:
/* Do not trust these when we are scoring. Maybe we shouldn't
* trust them otherwise either but require them to be
* accompanied by e.g. an EXPAND move reason.
*/
if (!doing_scoring)
does_block = 1;
break;
case SEMEAI_THREAT:
aa = move_reasons[r].what;
/* threaten to win the semeai as a ko threat */
add_followup_value(pos, 2 * dragon[aa].effective_size);
TRACE(" %1m: %f (followup) - threatens to win semeai for %1m\n",
pos, 2 * dragon[aa].effective_size, aa);
break;
case SEMEAI_MOVE:
case OWL_ATTACK_MOVE:
case OWL_ATTACK_MOVE_GOOD_KO:
case OWL_ATTACK_MOVE_BAD_KO:
case OWL_ATTACK_MOVE_GAIN:
case OWL_DEFEND_MOVE:
case OWL_DEFEND_MOVE_GOOD_KO:
case OWL_DEFEND_MOVE_BAD_KO:
case OWL_DEFEND_MOVE_LOSS:
if (move_reasons[r].type == OWL_ATTACK_MOVE_GAIN
|| move_reasons[r].type == OWL_DEFEND_MOVE_LOSS) {
aa = either_data[move_reasons[r].what].what1;
bb = either_data[move_reasons[r].what].what2;
}
else {
aa = move_reasons[r].what;
bb = NO_MOVE;
}
/* If the dragon is a single ko stone, the owl code currently
* won't detect that the owl attack is conditional. As a
* workaround we deduct 0.5 points for the move here, but only
* if the move is a liberty of the string.
*/
if (dragon[aa].size == 1
&& is_ko_point(aa)
&& liberty_of_string(pos, aa)) {
TRACE(" %1m: -0.5 - penalty for ko stone %1m (workaround)\n",
pos, aa);
tot_value -= 0.5;
}
/* Mark the affected dragon for use in the territory analysis. */
mark_changed_dragon(pos, color, aa, bb, move_reasons[r].type,
safe_stones, strength, &this_value);
this_value *= 2.0;
TRACE(" %1m: owl attack/defend for %1m\n", pos, aa);
/* FIXME: How much should we reduce the value for ko attacks? */
if (move_reasons[r].type == OWL_ATTACK_MOVE
|| move_reasons[r].type == OWL_DEFEND_MOVE
|| move_reasons[r].type == SEMEAI_MOVE)
this_value = 0.0;
else if (move_reasons[r].type == OWL_ATTACK_MOVE_GOOD_KO
|| move_reasons[r].type == OWL_DEFEND_MOVE_GOOD_KO) {
this_value *= 0.3;
TRACE(" %1m: -%f - owl attack/defense of %1m only with good ko\n",
pos, this_value, aa);
}
else if (move_reasons[r].type == OWL_ATTACK_MOVE_BAD_KO
|| move_reasons[r].type == OWL_DEFEND_MOVE_BAD_KO) {
this_value *= 0.5;
TRACE(" %1m: -%f - owl attack/defense of %1m only with bad ko\n",
pos, this_value, aa);
}
else if (move_reasons[r].type == OWL_ATTACK_MOVE_GAIN
|| move_reasons[r].type == OWL_DEFEND_MOVE_LOSS) {
this_value = 0.0;
}
tot_value -= this_value;
/* If the dragon is a single string which can be tactically
* attacked, but this owl attack does not attack tactically, it
* can be suspected to leave some unnecessary aji or even be an
* owl misread. Therefore we give it a small penalty to favor
* the moves which do attack tactically as well.
*
* One example is manyfaces:2 where the single stone S15 can be
* tactically attacked at S16 but where 3.3.2 finds additional
* owl attacks at R14 (clearly ineffective) and T15 (might work,
* but leaves huge amounts of aji).
*/
if ((move_reasons[r].type == OWL_ATTACK_MOVE
|| move_reasons[r].type == OWL_ATTACK_MOVE_GOOD_KO
|| move_reasons[r].type == OWL_ATTACK_MOVE_BAD_KO)
&& dragon[aa].size == worm[aa].size
&& worm[aa].attack_codes[0] == WIN
&& worm[aa].defense_codes[0] != 0
&& attack_move_reason_known(pos, aa) != WIN) {
if (large_scale)
this_value = (2.0 + 0.05 * (2 * worm[aa].effective_size));
else
this_value = 0.05 * (2 * worm[aa].effective_size);
TRACE(" %1m: -%f - suspected ineffective owl attack of worm %1m\n",
pos, this_value, aa);
tot_value -= this_value;
}
does_block = 1;
break;
case OWL_ATTACK_THREAT:
aa = move_reasons[r].what;
if (dragon[aa].status == DEAD) {
DEBUG(DEBUG_MOVE_REASONS,
" %1m: 0.0 - threatens to owl attack %1m (dead)\n", pos, aa);
break;
}
/* The followup value of a move threatening to attack (aa) is
* twice its effective size, unless it has an adjacent
* (friendly) critical dragon. In that case it's probably a
* mistake to make the threat since it can defend itself with
* profit.
*
* FIXME: We probably need to verify that the critical dragon is
* substantial enough that capturing it saves the threatened
* dragon.
*/
{
float value = 2 * dragon[aa].effective_size;
int s;
for (s = 0; s < DRAGON2(aa).neighbors; s++) {
int d = DRAGON2(aa).adjacent[s];
int adj = dragon2[d].origin;
if (dragon[adj].color == color
&& dragon[adj].status == CRITICAL
&& dragon2[d].safety != INESSENTIAL
&& !owl_defense_move_reason_known(pos, adj))
value = 0.0;
}
if (value > 0.0) {
add_followup_value(pos, value);
TRACE(" %1m: %f (followup) - threatens to owl attack %1m\n",
pos, value, aa);
}
}
break;
case OWL_DEFEND_THREAT:
aa = move_reasons[r].what;
add_followup_value(pos, 2 * dragon[aa].effective_size);
TRACE(" %1m: %f (followup) - threatens to owl defend %1m\n",
pos, 2 * dragon[aa].effective_size, aa);
break;
case OWL_PREVENT_THREAT:
/* A move attacking a dragon whose defense can be threatened.
*/
aa = move_reasons[r].what;
if (dragon[aa].status != DEAD) {
DEBUG(DEBUG_MOVE_REASONS,
" %1m: 0.0 - prevent defense threat (dragon is not dead)\n",
pos);
break;
}
/* If the opponent just added a stone to a dead dragon, then
* attack it. If we are ahead, add a safety move here, at most
* half the margin of victory.
*
* This does not apply if we are doing scoring.
*/
if (!doing_scoring
&& is_same_dragon(get_last_opponent_move(color), aa)) {
this_value = 1.5 * dragon[aa].effective_size;
TRACE(" %1m: %f - attack last move played, although it seems dead\n",
pos, this_value);
tot_value += this_value * attack_dragon_weight;
}
else if (!doing_scoring && our_score > 0.0) {
/* tm - devalued this bonus (3.1.17) */
this_value = gg_min(0.9 * dragon[aa].effective_size,
our_score/2.0 - board_size/2.0 - 1.0);
this_value = gg_max(this_value, 0);
TRACE(" %1m: %f - attack %1m, although it seems dead, as we are ahead\n",
pos, this_value, aa);
tot_value += this_value * attack_dragon_weight;
}
else {
add_reverse_followup_value(pos, 2 * dragon[aa].effective_size);
if (board[aa] == color)
TRACE(" %1m: %f (reverse followup) - prevent threat to attack %1m\n",
pos, 2 * dragon[aa].effective_size, aa);
else
TRACE(" %1m: %f (reverse followup) - prevent threat to defend %1m\n",
pos, 2 * dragon[aa].effective_size, aa);
}
break;
case MY_ATARI_ATARI_MOVE:
/* Add 1.0 to compensate for -1.0 penalty because the move is
* thought to be a sacrifice.
*/
this_value = move_reasons[r].what + 1.0;
tot_value += this_value;
TRACE(" %1m: %f - combination attack kills one of several worms\n",
pos, this_value);
break;
case YOUR_ATARI_ATARI_MOVE:
/* Set does_block to force territorial valuation of the move.
* That way we can prefer defenses against combination attacks
* on dame points instead of inside territory.
*/
does_block = 1;
this_value = move_reasons[r].what;
tot_value += this_value;
TRACE(" %1m: %f - defends against combination attack on several worms\n",
pos, this_value);
break;
}
}
/* Currently no difference in the valuation between blocking and
* expanding moves.
*/
this_value = 0.0;
mark_inessential_stones(OTHER_COLOR(color), safe_stones);
if (move[pos].move_safety == 1
&& (is_known_safe_move(pos) || safe_move(pos, color) != 0)) {
safe_stones[pos] = INFLUENCE_SAVED_STONE;
strength[pos] = DEFAULT_STRENGTH;
if (0)
TRACE(" %1m: is a safe move\n", pos);
}
else {
TRACE(" %1m: not a safe move\n", pos);
safe_stones[pos] = 0;
strength[pos] = 0.0;
}
/* We don't check for move safety here. This enables a territorial
* evaluation for sacrifice moves that enable a break-through (or
* an owl defense).
*/
if (does_block
&& tryko(pos, color, "estimate_territorial_value")) {
Hash_data safety_hash = goal_to_hashvalue(safe_stones);
if (disable_delta_territory_cache
|| !retrieve_delta_territory_cache(pos, color, &this_value,
&move[pos].influence_followup_value,
OPPOSITE_INFLUENCE(color),
safety_hash)) {
compute_influence(OTHER_COLOR(color), safe_stones, strength,
&move_influence, pos, "after move");
increase_depth_values();
break_territories(OTHER_COLOR(color), &move_influence, 0, pos);
decrease_depth_values();
this_value = influence_delta_territory(OPPOSITE_INFLUENCE(color),
&move_influence, color, pos);
compute_followup_influence(&move_influence, &followup_influence,
pos, "followup");
if (this_value != 0.0)
TRACE("%1m: %f - change in territory\n", pos, this_value);
else
DEBUG(DEBUG_MOVE_REASONS, "%1m: 0.00 - change in territory\n", pos);
move[pos].influence_followup_value
= influence_delta_territory(&move_influence, &followup_influence,
color, pos);
store_delta_territory_cache(pos, color, this_value,
move[pos].influence_followup_value,
OPPOSITE_INFLUENCE(color), safety_hash);
}
else {
if (this_value != 0.0)
TRACE("%1m: %f - change in territory (cached)\n", pos, this_value);
else
DEBUG(DEBUG_MOVE_REASONS,
"%1m: 0.00 - change in territory (cached)\n", pos);
}
popgo();
}
tot_value += this_value;
/* Test if min_territory or max_territory values constrain the
* delta_territory value.
*/
if (tot_value < move[pos].min_territory
&& move[pos].min_territory > 0) {
tot_value = move[pos].min_territory;
TRACE(" %1m: %f - revised to meet minimum territory value\n",
pos, tot_value);
}
if (tot_value > move[pos].max_territory) {
tot_value = move[pos].max_territory;
TRACE(" %1m: %f - revised to meet maximum territory value\n",
pos, tot_value);
}
move[pos].territorial_value = tot_value;
move[pos].secondary_value += secondary_value;
}
/*
* Estimate the strategical value of a move at (pos).
*/
static void
estimate_strategical_value(int pos, int color, float our_score,
int use_thrashing_dragon_heuristics)
{
int k;
int l;
int aa = NO_MOVE;
int bb = NO_MOVE;
float aa_value = 0.0;
float bb_value = 0.0;
float this_value = 0.0;
float tot_value = 0.0;
/* Strategical value of connecting or cutting dragons. */
float dragon_value[BOARDMAX];
for (aa = BOARDMIN; aa < BOARDMAX; aa++)
dragon_value[aa] = 0.0;
for (k = 0; k < MAX_REASONS; k++) {
int r = move[pos].reason[k];
if (r < 0)
break;
if (move_reasons[r].status & STRATEGICALLY_REDUNDANT)
continue;
this_value = 0.0;
switch (move_reasons[r].type) {
case ATTACK_MOVE:
case ATTACK_MOVE_GOOD_KO:
case ATTACK_MOVE_BAD_KO:
case DEFEND_MOVE:
case DEFEND_MOVE_GOOD_KO:
case DEFEND_MOVE_BAD_KO:
aa = move_reasons[r].what;
/* Defenseless stone */
if (worm[aa].defense_codes[0] == 0)
break;
if (doing_scoring && dragon[aa].status == DEAD)
break;
/* FIXME: This is totally ad hoc, just guessing the value of
* potential cutting points.
* FIXME: When worm[aa].cutstone2 == 1 we should probably add
* a followup value.
*/
if (worm[aa].cutstone2 > 1 && !worm[aa].inessential) {
double ko_factor = 1;
if (move_reasons[r].type == ATTACK_MOVE_GOOD_KO
|| move_reasons[r].type == DEFEND_MOVE_GOOD_KO) {
ko_factor = 0.6;
}
else if (move_reasons[r].type == ATTACK_MOVE_BAD_KO
|| move_reasons[r].type == DEFEND_MOVE_BAD_KO) {
ko_factor = 0.4;
}
this_value = 10.0 * (worm[aa].cutstone2 - 1) * ko_factor;
TRACE(" %1m: %f - %1m cutstone\n", pos, this_value, aa);
}
tot_value += this_value;
/* If the string is a lunch for a weak dragon, the attack or
* defense has a strategical value. This can be valued along
* the same lines as strategic_attack/strategic_defend.
*
* No points are awarded if the lunch is an inessential dragon
* or worm.
*/
if (DRAGON2(aa).safety == INESSENTIAL
|| worm[aa].inessential)
break;
/* If the lunch has no potential to create eyes, no points. */
if (max_lunch_eye_value(aa) == 0)
break;
/* Can't use k in this loop too. */
for (l = 0; l < next_lunch; l++)
if (lunch_worm[l] == aa) {
bb = lunch_dragon[l];
/* FIXME: This value cannot be computed without some measurement
* of how the actual move affects the dragon. The dragon safety
* alone is not enough. The question is whether the dragon is
* threatened or defended by the move or not.
*/
this_value = 1.8 * soft_cap(DRAGON2(bb).strategic_size, 15.0)
* dragon_weakness(bb, 0);
/* If this dragon consists of only one worm and that worm
* can be tactically captured or defended by this move, we
* have already counted the points as territorial value,
* unless it's assumed to be dead.
*/
if (dragon[bb].status != DEAD
&& dragon[bb].size == worm[bb].size
&& (attack_move_reason_known(pos, bb)
|| defense_move_reason_known(pos, bb)))
this_value = 0.0;
/* If this dragon can be tactically attacked and the move
* does not defend or attack, no points.
*/
if (worm[bb].attack_codes[0] != 0
&& ((color == board[bb] && !does_defend(pos, bb))
|| (color == OTHER_COLOR(board[bb])
&& !does_attack(pos, bb))))
this_value = 0.0;
/* If we are doing scoring, are alive, and the move loses
* territory, no points.
*/
if (doing_scoring
&& move[pos].territorial_value < 0.0
&& (DRAGON2(bb).safety == ALIVE
|| DRAGON2(bb).safety == STRONGLY_ALIVE
|| DRAGON2(bb).safety == INVINCIBLE))
this_value = 0.0;
if (this_value > dragon_value[bb]) {
DEBUG(DEBUG_MOVE_REASONS,
" %1m: %f - %1m attacked/defended\n",
pos, this_value, bb);
dragon_value[bb] = this_value;
}
}
break;
case ATTACK_THREAT:
case DEFEND_THREAT:
break;
case EITHER_MOVE:
/* FIXME: Generalize this to more types of threats. */
/* FIXME: We need a policy if a move has several EITHER_MOVE
* reasons. Most likely not all of them can be achieved.
*/
aa = either_data[move_reasons[r].what].what1;
bb = either_data[move_reasons[r].what].what2;
/* If both worms are dead, this move reason has no value. */
if (dragon[aa].status == DEAD
&& dragon[bb].status == DEAD)
break;
/* Also if there is a combination attack, we assume it covers
* the same thing.
* FIXME: This is only applicable as long as the only moves
* handled by EITHER_MOVE are attacks.
*/
if (move_reason_known(pos, MY_ATARI_ATARI_MOVE, -1))
break;
aa_value = adjusted_worm_attack_value(pos, aa);
bb_value = adjusted_worm_attack_value(pos, bb);
this_value = gg_min(aa_value, bb_value);
TRACE(" %1m: %f - either attacks %1m (%f) or attacks %1m (%f)\n",
pos, this_value, aa, aa_value, bb, bb_value);
tot_value += this_value;
break;
case ALL_MOVE:
/* FIXME: Generalize this to more types of threats. */
aa = all_data[move_reasons[r].what].what1;
bb = all_data[move_reasons[r].what].what2;
/* If both worms are dead, this move reason has no value. */
if (dragon[aa].status == DEAD
&& dragon[bb].status == DEAD)
break;
/* Also if there is a combination attack, we assume it covers
* the same thing.
*/
if (move_reason_known(pos, YOUR_ATARI_ATARI_MOVE, -1))
break;
aa_value = worm[aa].effective_size;
bb_value = worm[bb].effective_size;
this_value = 2 * gg_min(aa_value, bb_value);
TRACE(" %1m: %f - both defends %1m (%f) and defends %1m (%f)\n",
pos, this_value, aa, aa_value, bb, bb_value);
tot_value += this_value;
break;
case CONNECT_MOVE:
/* If the opponent just added a stone to a dead dragon, which is
* adjacent to both dragons being connected, then the connection
* is probably a good way to make sure the thrashing dragon
* stays dead. If we are ahead, add a safety move here, at most
* half the margin of victory.
*
* This does only apply if we decided earlier we want to use
* thrashing dragon heuristics.
*/
if (use_thrashing_dragon_heuristics) {
int cc;
aa = dragon[conn_worm1[move_reasons[r].what]].origin;
bb = dragon[conn_worm2[move_reasons[r].what]].origin;
cc = get_last_opponent_move(color);
if (cc != NO_MOVE
&& thrashing_stone[cc]
&& are_neighbor_dragons(aa, cc)
&& are_neighbor_dragons(bb, cc)) {
if (aa == bb)
this_value = 1.6 * DRAGON2(cc).strategic_size;
else if (DRAGON2(aa).safety == INESSENTIAL
|| DRAGON2(bb).safety == INESSENTIAL) {
if ((DRAGON2(aa).safety == INESSENTIAL
&& max_lunch_eye_value(aa) == 0)
|| (DRAGON2(bb).safety == INESSENTIAL
&& max_lunch_eye_value(bb) == 0))
this_value = 0.0;
else
this_value = 0.8 * DRAGON2(cc).strategic_size;
}
else
this_value = 1.7 * DRAGON2(cc).strategic_size;
if (this_value > dragon_value[dragon[cc].origin]) {
dragon_value[dragon[cc].origin] = this_value;
DEBUG(DEBUG_MOVE_REASONS,
" %1m: %f - connect %1m and %1m to attack thrashing dragon %1m\n",
pos, this_value, aa, bb, cc);
}
}
}
if (!move[pos].move_safety)
break;
/* Otherwise fall through. */
case CUT_MOVE:
if (doing_scoring && !move[pos].move_safety)
break;
aa = dragon[conn_worm1[move_reasons[r].what]].origin;
bb = dragon[conn_worm2[move_reasons[r].what]].origin;
if (aa == bb)
continue;
/* If we are ahead by more than 20, value connections more strongly */
if (our_score > 20.0)
this_value = connection_value(aa, bb, pos, our_score);
else
this_value = connection_value(aa, bb, pos, 0);
if (this_value > dragon_value[aa]) {
dragon_value[aa] = this_value;
DEBUG(DEBUG_MOVE_REASONS,
" %1m: %f - %1m cut/connect strategic value\n",
pos, this_value, aa);
}
if (our_score > 20.0)
this_value = connection_value(bb, aa, pos, our_score);
else
this_value = connection_value(bb, aa, pos, 0);
if (this_value > dragon_value[bb]) {
dragon_value[bb] = this_value;
DEBUG(DEBUG_MOVE_REASONS,
" %1m: %f - %1m cut/connect strategic value\n",
pos, this_value, bb);
}
break;
case SEMEAI_MOVE:
/*
* The strategical value of winning a semeai is
* own dragons (usually) becomes fully secure, while adjoining
* enemy dragons do not.
*
* FIXME: Valuation not implemented at all yet.
*/
break;
case STRATEGIC_ATTACK_MOVE:
case STRATEGIC_DEFEND_MOVE:
/* The right way to do this is to estimate the safety of the
* dragon before and after the move. Unfortunately we are
* missing good ways to do this currently.
*
* Temporary solution is to only look at an ad hoc measure of
* the dragon safety and ignoring the effectiveness of the
* move.
*
* FIXME: Improve the implementation.
*/
aa = move_reasons[r].what;
/* FIXME: This value cannot be computed without some
* measurement of how the actual move affects the dragon. The
* dragon safety alone is not enough. The question is whether
* the dragon is threatened by the move or not.
*/
if (use_thrashing_dragon_heuristics
&& thrashing_stone[aa])
this_value = 1.7 * DRAGON2(aa).strategic_size;
else
this_value = 1.8 * soft_cap(DRAGON2(aa).strategic_size, 15.0)
* dragon_weakness(aa, 1);
/* No strategical attack value is awarded if the dragon at (aa)
* has an adjacent (friendly) critical dragon, which is not
* defended by this move. In that case it's probably a mistake
* to make the strategical attack since the dragon can defend
* itself with profit.
*
* FIXME: We probably need to verify that the critical dragon is
* substantial enough that capturing it saves the strategically
* attacked dragon.
*/
if (move_reasons[r].type == STRATEGIC_ATTACK_MOVE) {
int s;
for (s = 0; s < DRAGON2(aa).neighbors; s++) {
int d = DRAGON2(aa).adjacent[s];
int adj = dragon2[d].origin;
if (dragon[adj].color == color
&& dragon[adj].status == CRITICAL
&& dragon2[d].safety != INESSENTIAL
&& !owl_defense_move_reason_known(pos, adj))
this_value = 0.0;
}
}
/* Multiply by attack_dragon_weight to try to find a best fit */
this_value = this_value * attack_dragon_weight;
if (this_value > dragon_value[aa]) {
dragon_value[aa] = this_value;
DEBUG(DEBUG_MOVE_REASONS,
" %1m: %f - %1m strategic attack/defend\n",
pos, this_value, aa);
}
break;
case UNCERTAIN_OWL_DEFENSE:
aa = move_reasons[r].what;
/* If there is an adjacent dragon which is critical we should
* skip this type of move reason, since attacking or defending
* the critical dragon is more urgent.
*/
{
int d;
int found_one = 0;
for (d = 0; d < DRAGON2(aa).neighbors; d++)
if (DRAGON(DRAGON2(aa).adjacent[d]).status == CRITICAL)
found_one = 1;
if (found_one)
break;
}
/* If we are behind, we should skip this type of move reason.
* If we are ahead, we should value it more.
*/
if (our_score < 0.0)
this_value = 0.0;
else
this_value = gg_min(2*DRAGON2(aa).strategic_size, 0.65*our_score);
if (this_value > dragon_value[aa]) {
dragon_value[aa] = this_value;
DEBUG(DEBUG_MOVE_REASONS,
" %1m: %f - %1m uncertain owl defense bonus\n",
pos, this_value, aa);
}
break;
}
}
for (aa = BOARDMIN; aa < BOARDMAX; aa++) {
if (dragon_value[aa] == 0.0)
continue;
ASSERT1(dragon[aa].origin == aa, aa);
/* If this dragon is critical but not attacked/defended by this
* move, we ignore the strategic effect.
*/
if (dragon[aa].status == CRITICAL
&& !owl_move_reason_known(pos, aa)) {
DEBUG(DEBUG_MOVE_REASONS, " %1m: 0.0 - disregarding strategic effect on %1m (critical dragon)\n",
pos, aa);
continue;
}
/* If this dragon consists of only one worm and that worm can
* be tactically captured or defended by this move, we have
* already counted the points as territorial value, unless
* it's assumed to be dead.
* However, we still allow strategical excess value (see below)
* in case the effective_size is substantially bigger (by 2.0)
* than the actualy size.
*/
if (dragon[aa].status != DEAD
&& dragon[aa].size == worm[aa].size
&& worm[aa].effective_size < worm[aa].size + 2.0
&& (attack_move_reason_known(pos, aa)
|| defense_move_reason_known(pos, aa))) {
TRACE(" %1m: %f - %1m strategic value already counted - A.\n",
pos, dragon_value[aa], aa);
continue;
}
/* If the dragon has been owl captured, owl defended, or involved
* in a semeai, we have likewise already counted the points as
* territorial value.
*/
if (attack_move_reason_known(pos, aa)
|| defense_move_reason_known(pos, aa)
|| (owl_move_reason_known(pos, aa)
&& dragon[aa].status == CRITICAL)
|| move_reason_known(pos, SEMEAI_MOVE, aa)) {
/* But if the strategical value was larger than the territorial
* value (e.g. because connecting to strong dragon) we award the
* excess value as a bonus.
*/
float excess_value = (dragon_value[aa] -
2 * DRAGON2(aa).strategic_size);
if (excess_value > 0.0) {
TRACE(" %1m: %f - strategic bonus for %1m\n", pos, excess_value, aa);
tot_value += excess_value;
}
else {
TRACE(" %1m: %f - %1m strategic value already counted - B.\n",
pos, dragon_value[aa], aa);
}
continue;
}
TRACE(" %1m: %f - strategic effect on %1m\n",
pos, dragon_value[aa], aa);
tot_value += dragon_value[aa];
}
/* Finally, subtract penalty for invasion type moves. */
this_value = strategic_penalty(pos, color);
/* Multiply by invasion_malus_weight to allow us to fit the weight */
this_value = this_value * invasion_malus_weight;
if (this_value > 0.0) {
TRACE(" %1m: %f - strategic penalty, considered as invasion.\n",
pos, -this_value);
tot_value -= this_value;
}
move[pos].strategical_value = tot_value;
}
/* Compare two move reasons, used for sorting before presentation. */
static int
compare_move_reasons(const void *p1, const void *p2)
{
const int mr1 = *(const int *) p1;
const int mr2 = *(const int *) p2;
if (move_reasons[mr1].type != move_reasons[mr2].type)
return move_reasons[mr2].type - move_reasons[mr1].type;
else
return move_reasons[mr2].what - move_reasons[mr1].what;
}
/*
* Combine the reasons for a move at (pos) into a simple numerical value.
* These heuristics are now somewhat less ad hoc than before but probably
* still need a lot of improvement.
*/
static float
value_move_reasons(int pos, int color, float pure_threat_value,
float our_score, int use_thrashing_dragon_heuristics)
{
float tot_value;
float shape_factor;
gg_assert(stackp == 0);
/* Is it an antisuji? */
if (is_antisuji_move(pos))
return 0.0; /* This move must not be played. End of story. */
/* Never play on a vertex which is unconditional territory for
* either player. There is absolutely nothing to gain.
*/
if (worm[pos].unconditional_status != UNKNOWN)
return 0.0;
/* If this move has no reason at all, we can skip some steps. */
if (move[pos].reason[0] >= 0
|| move[pos].min_territory > 0.0) {
int num_reasons;
/* Sort the move reasons. This makes it easier to visually compare
* the reasons for different moves in the trace outputs.
*/
num_reasons = 0;
while (move[pos].reason[num_reasons] >= 0 && num_reasons < MAX_REASONS)
num_reasons++;
gg_sort(move[pos].reason, num_reasons, sizeof(move[pos].reason[0]),
compare_move_reasons);
/* Discard move reasons that only duplicate another. */
discard_redundant_move_reasons(pos);
/* Estimate the value of various aspects of the move. The order
* is significant. Territorial value must be computed before
* strategical value. See connection_value().
*/
estimate_territorial_value(pos, color, our_score, 0);
estimate_strategical_value(pos, color, our_score,
use_thrashing_dragon_heuristics);
}
/* Introduction of strategical_weight and territorial_weight,
* for automatic fitting. (3.5.1)
*/
tot_value = territorial_weight * move[pos].territorial_value +
strategical_weight * move[pos].strategical_value;
shape_factor = compute_shape_factor(pos);
if (tot_value > 0.0) {
int c;
float followup_value;
/* Negative territorial followup doesn't make make sense. */
if (move[pos].influence_followup_value < 0.0)
move[pos].influence_followup_value = 0.0;
followup_value = move[pos].followup_value
+ move[pos].influence_followup_value;
TRACE(" %1m: %f - total followup value, added %f as territorial followup\n",
pos, followup_value, move[pos].influence_followup_value);
/* In the endgame, there are a few situations where the value can
* be 0 points + followup. But we want to take the intersections first
* were we actually get some points. 0.5 points is a 1 point ko which
* is the smallest value that is actually worth something.
*/
if (tot_value >= 0.5) {
float old_tot_value = tot_value;
float contribution;
/* We adjust the value according to followup and reverse followup
* values.
*/
contribution = gg_min(gg_min(0.5 * followup_value
+ 0.5 * move[pos].reverse_followup_value,
1.0 * tot_value
+ followup_value),
1.1 * tot_value
+ move[pos].reverse_followup_value);
tot_value += contribution * followup_weight;
/* The first case applies to gote vs gote situation, the
* second to reverse sente, and the third to sente situations.
* The usual rule is that a sente move should count at double
* value. But if we have a 1 point move with big followup (i.e.
* sente) we want to play that before a 2 point gote move. Hence
* the factor 1.1 above.
*/
if (contribution != 0.0) {
TRACE(" %1m: %f - added due to followup (%f) and reverse followup values (%f)\n",
pos, contribution, followup_value,
move[pos].reverse_followup_value);
}
/* If a ko fight is going on, we should use the full followup
* and reverse followup values in the total value. We save the
* additional contribution for later access.
*/
move[pos].additional_ko_value =
followup_value
+ move[pos].reverse_followup_value
- (tot_value - old_tot_value);
/* Not sure whether this could happen, but check for safety. */
if (move[pos].additional_ko_value < 0.0)
move[pos].additional_ko_value = 0.0;
}
else {
move[pos].additional_ko_value =
shape_factor * (move[pos].followup_value
+ move[pos].reverse_followup_value);
}
tot_value += soft_cap(0.05 * move[pos].secondary_value, 0.4);
if (move[pos].secondary_value != 0.0)
TRACE(" %1m: %f - secondary\n", pos,
soft_cap(0.05 * move[pos].secondary_value, 0.4));
if (move[pos].numpos_shape + move[pos].numneg_shape > 0) {
/* shape_factor has already been computed. */
float old_value = tot_value;
/* Maximum 15 points of the territorial value will be weighted by shape_factor */
if (move[pos].territorial_value < 15)
tot_value *= shape_factor;
else {
float non_shape_val = move[pos].territorial_value - 15;
tot_value = (tot_value - non_shape_val) * shape_factor + non_shape_val;
}
if (verbose) {
/* Should all have been TRACE, except we want field sizes. */
gprintf(" %1m: %f - shape ", pos, tot_value - old_value);
fprintf(stderr,
"(shape values +%4.2f(%d) -%4.2f(%d), shape factor %5.3f)\n",
move[pos].maxpos_shape, move[pos].numpos_shape,
move[pos].maxneg_shape, move[pos].numneg_shape,
shape_factor);
}
}
/* Add a special shape bonus for moves which connect own strings
* or cut opponent strings.
*/
c = (move_connects_strings(pos, color, 1)
+ move_connects_strings(pos, OTHER_COLOR(color), 0));
if (c > 0) {
float shape_factor2 = pow(1.02, (float) c) - 1;
float base_value = gg_max(gg_min(tot_value, 5.0), 1.0);
if (verbose) {
/* Should all have been TRACE, except we want field sizes. */
gprintf(" %1m: %f - connects strings ", pos,
base_value * shape_factor2);
fprintf(stderr, "(connect value %d, shape factor %5.3f)\n", c,
shape_factor2);
}
tot_value += base_value * shape_factor2;
}
/* Dame points which have a cut or connect move reason get a small
* extra bonus because these have a tendency to actually be worth
* a point.
*/
if (tot_value < 0.3
&& (move_reason_known(pos, CONNECT_MOVE, -1)
|| move_reason_known(pos, CUT_MOVE, -1))) {
float old_tot_value = tot_value;
tot_value = gg_min(0.3, tot_value + 0.1);
TRACE(" %1m: %f - cut/connect dame bonus\n", pos,
tot_value - old_tot_value);
}
}
else {
move[pos].additional_ko_value =
shape_factor * (move[pos].followup_value +
gg_min(move[pos].followup_value,
move[pos].reverse_followup_value));
}
/* If the move is valued 0 or small, but has followup values and is
* flagged as a worthwhile threat, add up to pure_threat_value to
* the move.
*
* FIXME: We shouldn't have to call confirm_safety() here. It's
* potentially too expensive.
*/
if (pure_threat_value > 0.0
&& move[pos].worthwhile_threat
&& tot_value <= pure_threat_value
&& board[pos] == EMPTY
&& move[pos].additional_ko_value > 0.0
&& is_legal(pos, color)
&& value_moves_confirm_safety(pos, color)) {
float new_tot_value = gg_min(pure_threat_value,
tot_value
+ 0.25 * move[pos].additional_ko_value);
/* Make sure that moves with independent value are preferred over
* those without.
*/
new_tot_value *= (1.0 - 0.1 * (pure_threat_value - tot_value)
/ pure_threat_value);
if (new_tot_value > tot_value) {
TRACE(" %1m: %f - carry out threat or defend against threat\n",
pos, new_tot_value - tot_value);
tot_value = new_tot_value;
}
}
/* min_value is now subject to reduction with a fitted weight (3.5.1) */
move[pos].min_value = move[pos].min_value * minimum_value_weight;
move[pos].max_value = move[pos].max_value * maximum_value_weight;
/* Test if min_value or max_value values constrain the total value.
* First avoid contradictions between min_value and max_value,
* assuming that min_value is right.
*/
if (move[pos].min_value > move[pos].max_value)
move[pos].max_value = move[pos].min_value;
/* If several moves have an identical minimum value, then GNU Go uses the
* following secondary criterion (unless min_value and max_value agree, and
* unless min_value is bigger than 25, in which case it probably comes from
* a J or U pattern):
*/
if (move[pos].min_value < 25)
move[pos].min_value += tot_value / 200;
if (tot_value < move[pos].min_value
&& move[pos].min_value > 0) {
tot_value = move[pos].min_value;
TRACE(" %1m: %f - minimum accepted value\n", pos, tot_value);
}
if (tot_value > move[pos].max_value) {
tot_value = move[pos].max_value;
TRACE(" %1m: %f - maximum accepted value\n",
pos, tot_value);
}
if (tot_value > 0
|| move[pos].territorial_value > 0
|| move[pos].strategical_value > 0) {
TRACE("Move generation values %1m to %f\n", pos, tot_value);
move_considered(pos, tot_value);
}
return tot_value;
}
/*
* Loop over all possible moves and value the move reasons for each.
*/
static void
value_moves(int color, float pure_threat_value, float our_score,
int use_thrashing_dragon_heuristics)
{
int m, n;
int pos;
TRACE("\nMove valuation:\n");
/* Visit the moves in the standard lexicographical order */
for (n = 0; n < board_size; n++)
for (m = board_size-1; m >= 0; m--) {
pos = POS(m, n);
move[pos].value = value_move_reasons(pos, color,
pure_threat_value, our_score,
use_thrashing_dragon_heuristics);
if (move[pos].value == 0.0)
continue;
/* Maybe this test should be performed elsewhere. This is just
* to get some extra safety. We don't filter out illegal ko
* captures here though, because if that is the best move, we
* should reevaluate ko threats.
*/
if (is_legal(pos, color) || is_illegal_ko_capture(pos, color)) {
/* Add a random number between 0 and 0.01 to use in comparisons. */
move[pos].value +=
0.01 * move[pos].random_number * move[pos].randomness_scaling;
}
else {
move[pos].value = 0.0;
TRACE("Move at %1m wasn't legal.\n", pos);
}
}
}
/* Print the values of all moves with values bigger than zero. */
void
print_all_move_values(FILE *output)
{
int pos;
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (!ON_BOARD(pos) || move[pos].final_value <= 0.0)
continue;
gfprintf(output, "%1M %f\n", pos, move[pos].final_value);
}
}
/* Search through all board positions for the 10 highest valued
* moves and print them.
*/
static void
print_top_moves(void)
{
int k;
int pos;
float tval;
for (k = 0; k < 10; k++) {
best_moves[k] = NO_MOVE;
best_move_values[k] = 0.0;
}
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (!ON_BOARD(pos) || move[pos].final_value <= 0.0)
continue;
tval = move[pos].final_value;
record_top_move(pos, tval);
}
if (verbose > 0 || (debug & DEBUG_TOP_MOVES)) {
gprintf("\nTop moves:\n");
for (k = 0; k < 10 && best_move_values[k] > 0.0; k++)
gprintf("%d. %1M %f\n", k+1, best_moves[k], best_move_values[k]);
}
}
/* Add a move to the list of top moves (if it is among the top ten) */
void
record_top_move(int pos, float val)
{
int k;
for (k = 9; k >= 0; k--)
if (val > best_move_values[k]) {
if (k < 9) {
best_move_values[k+1] = best_move_values[k];
best_moves[k+1] = best_moves[k];
}
best_move_values[k] = val;
best_moves[k] = pos;
}
move[pos].final_value = val;
}
/* remove a rejected move from the list of top moves */
void
remove_top_move(int move)
{
int k;
for (k = 0; k < 10; k++) {
if (best_moves[k] == move) {
int l;
for (l = k; l < 9; l++) {
best_moves[l] = best_moves[l+1];
best_move_values[l] = best_move_values[l+1];
}
best_moves[9] = NO_MOVE;
best_move_values[9] = 0.0;
}
}
}
/* This function is called if the biggest move on board was an illegal
* ko capture.
*/
static void
reevaluate_ko_threats(int ko_move, int color, float ko_value)
{
int ko_stone = NO_MOVE;
int opp_ko_move;
int pos;
int k;
int type, what;
int threat_does_work = 0;
int ko_move_target;
int num_good_threats = 0;
int good_threats[BOARDMAX];
int best_threat_quality = -1;
float threat_size;
ko_move_target = get_biggest_owl_target(ko_move);
/* If the move is a simple ko recapture, find the ko stone. (If
* it's not a simple ko recapture, then the move must be a superko
* violation.)
*/
if (is_illegal_ko_capture(ko_move, color)) {
for (k = 0; k <= 3; k++) {
ko_stone = ko_move + delta[k];
if (ON_BOARD(ko_stone) && countlib(ko_stone) == 1)
break;
}
ASSERT_ON_BOARD1(ko_stone);
}
TRACE("Reevaluating ko threats.\n");
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
int threat_quality = 0;
if (!ON_BOARD(pos) || pos == ko_move)
continue;
if (move[pos].additional_ko_value <= 0.0)
continue;
/* Otherwise we look for the biggest threat, and then check whether
* it still works after ko has been resolved.
*/
/* `additional_ko_value' includes reverse followup. While it is good to
* play ko threats which eliminate other threats in turn, we should
* always prefer threats that are larger than the value of the ko.
*/
if (move[pos].followup_value < ko_value)
threat_quality = -1;
threat_size = 0.0;
type = -1;
what = -1;
for (k = 0; k < MAX_REASONS; k++) {
int r = move[pos].reason[k];
if (r < 0)
break;
if (!(move_reasons[r].type & THREAT_BIT))
continue;
switch (move_reasons[r].type) {
case ATTACK_THREAT:
case DEFEND_THREAT:
if (worm[move_reasons[r].what].effective_size
> threat_size) {
threat_size = worm[move_reasons[r].what].effective_size;
type = move_reasons[r].type;
what = move_reasons[r].what;
}
break;
case OWL_ATTACK_THREAT:
case OWL_DEFEND_THREAT:
case SEMEAI_THREAT:
if (dragon[move_reasons[r].what].effective_size
> threat_size) {
threat_size = dragon[move_reasons[r].what]\
.effective_size;
type = move_reasons[r].type;
what = move_reasons[r].what;
}
break;
default:
/* This means probably someone has introduced a new threat type
* without adding the corresponding case above.
*/
gg_assert(0);
break;
}
}
/* If there is no threat recorded, the followup value is probably
* contributed by a pattern. We can do nothing but accept this value.
* (although this does cause problems).
*
* FIXME: In the case of superko violation we have no ko_stone.
* Presumably some of the tests below should be applicable anyway.
* Currently we just say that any threat is ok.
*/
if (type == -1 || ko_stone == NO_MOVE)
threat_does_work = 1;
else {
if (trymove(pos, color, "reevaluate_ko_threats", ko_move)) {
ASSERT_ON_BOARD1(ko_stone);
if (!find_defense(ko_stone, &opp_ko_move))
threat_does_work = 1;
else {
int threat_wastes_point = 0;
if (whose_area(OPPOSITE_INFLUENCE(color), pos) != EMPTY)
threat_wastes_point = 1;
if (trymove(opp_ko_move, OTHER_COLOR(color),
"reevaluate_ko_threats", ko_move)) {
switch (type) {
case ATTACK_THREAT:
/* In case the attack threat was a snapback move, there
* is no stone on the board to attack now and we check
* for a defense of the threatening move instead.
*/
if (board[what] != EMPTY)
threat_does_work = attack(what, NULL);
else
threat_does_work = find_defense(pos, NULL);
break;
case DEFEND_THREAT:
threat_does_work = (board[what] != EMPTY
&& find_defense(what, NULL));
break;
case OWL_ATTACK_THREAT:
case OWL_DEFEND_THREAT:
/* Should we call do_owl_attack/defense here?
* Maybe too expensive? For the moment we just assume
* that the attack does not work if it concerns the
* same dragon as ko_move. (Can this really happen?)
*/
threat_does_work = (ko_move_target != what);
}
popgo();
/* Is this a losing ko threat? */
if (threat_does_work && type == ATTACK_THREAT) {
int apos;
if (attack(pos, &apos)
&& does_defend(apos, what)
&& (forced_backfilling_moves[apos]
|| (!is_proper_eye_space(apos)
&& !false_eye_territory[apos]))) {
threat_does_work = 0;
}
}
/* If we are fighting a tiny ko (1 - 2 points only), we pay
* extra attention to select threats that don't waste points.
* In particular, we don't play threats inside of opponent
* territory if they can be averted on a dame intersection.
*/
if (ko_value < 1.0
&& threat_does_work
&& threat_quality >= 0
&& (type == ATTACK_THREAT || type == DEFEND_THREAT)) {
int averting_pos;
if (type == ATTACK_THREAT)
find_defense(what, &averting_pos);
else
attack(what, &averting_pos);
/* `averting_pos' can be NO_MOVE sometimes, at least when
* when the the threat is a threat to attack. It is not
* clear what to do in such cases.
*/
if (averting_pos != NO_MOVE) {
int averting_wastes_point = 0;
if (whose_territory(OPPOSITE_INFLUENCE(color), averting_pos)
!= EMPTY)
averting_wastes_point = 1;
threat_quality = averting_wastes_point - threat_wastes_point;
if (threat_quality < 0)
threat_does_work = 0;
}
}
}
}
popgo();
}
}
if (threat_does_work) {
if (threat_quality == best_threat_quality)
good_threats[num_good_threats++] = pos;
else if (threat_quality > best_threat_quality) {
best_threat_quality = threat_quality;
num_good_threats = 0;
good_threats[num_good_threats++] = pos;
}
else
DEBUG(DEBUG_MOVE_REASONS,
"%1m: no additional ko value (threat does not work as ko threat)\n", pos);
}
}
for (k = 0; k < num_good_threats; k++) {
pos = good_threats[k];
/* If the move previously had no value, we need to add in the
* randomness contribution now.
*
* FIXME: This is very ugly. Restructure the code so that the
* randomness need only be considered in one place.
*/
if (move[pos].value == 0.0) {
move[pos].value +=
0.01 * move[pos].random_number * move[pos].randomness_scaling;
}
TRACE("%1m: %f + %f = %f\n", pos, move[pos].value,
move[pos].additional_ko_value,
move[pos].value + move[pos].additional_ko_value);
move[pos].value += move[pos].additional_ko_value;
}
}
/* Redistribute points. When one move is declared a replacement for
* another by a replacement move reason, the move values for the
* inferior move are transferred to the replacement.
*/
static void
redistribute_points(void)
{
int source;
int target;
for (target = BOARDMIN; target < BOARDMAX; target++)
if (ON_BOARD(target))
move[target].final_value = move[target].value;
for (source = BOARDMIN; source < BOARDMAX; source++) {
if (!ON_BOARD(source))
continue;
target = replacement_map[source];
if (target == NO_MOVE)
continue;
TRACE("Redistributing points from %1m to %1m.\n", source, target);
if (move[target].final_value < move[source].final_value) {
TRACE("%1m is now valued %f.\n", target, move[source].final_value);
move[target].final_value = move[source].final_value;
}
TRACE("%1m is now valued 0.\n", source);
move[source].final_value = 0.0;
}
}
/* This selects the best move available according to their valuations.
* If the best move is an illegal ko capture, we add ko threat values.
* If the best move is a blunder, it gets devalued and continue to look
* for the best move.
*/
static int
find_best_move(int *the_move, float *value, int color,
int allowed_moves[BOARDMAX])
{
int good_move_found = 0;
signed char blunder_tested[BOARDMAX];
float best_value = 0.0;
int best_move = NO_MOVE;
int pos;
memset(blunder_tested, 0, sizeof(blunder_tested));
while (!good_move_found) {
best_value = 0.0;
best_move = NO_MOVE;
/* Search through all board positions for the highest valued move. */
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
float this_value = move[pos].final_value;
if (allowed_moves && !allowed_moves[pos])
continue;
if (!ON_BOARD(pos) || move[pos].final_value == 0.0)
continue;
if (this_value > best_value) {
if (is_legal(pos, color) || is_illegal_ko_capture(pos, color)) {
best_value = this_value;
best_move = pos;
}
else {
TRACE("Move at %1m would be suicide.\n", pos);
remove_top_move(pos);
move[pos].value = 0.0;
move[pos].final_value = 0.0;
}
}
}
/* If the best move is an illegal ko capture, reevaluate ko
* threats and search again.
*/
if (best_value > 0.0
&& (is_illegal_ko_capture(best_move, color)
|| !is_allowed_move(best_move, color))) {
TRACE("Move at %1m would be an illegal ko capture.\n", best_move);
reevaluate_ko_threats(best_move, color, best_value);
redistribute_points();
time_report(2, " reevaluate_ko_threats", NO_MOVE, 1.0);
remove_top_move(best_move);
move[best_move].value = 0.0;
move[best_move].final_value = 0.0;
print_top_moves();
good_move_found = 0;
}
/* Call blunder_size() to check that we're not about to make a
* blunder. Otherwise devalue this move and scan through all move
* values once more.
*/
else if (best_value > 0.0) {
if (!blunder_tested[best_move]) {
float blunder_size = value_moves_get_blunder_size(best_move, color);
if (blunder_size > 0.0) {
TRACE("Move at %1m is a blunder, subtracting %f.\n", best_move,
blunder_size);
remove_top_move(best_move);
move[best_move].value -= blunder_size;
move[best_move].final_value -= blunder_size;
TRACE("Move at %1m is now valued %f.\n", best_move,
move[best_move].final_value);
record_top_move(best_move, move[best_move].final_value);
good_move_found = 0;
blunder_tested[best_move] = 1;
}
else
good_move_found = 1; /* Best move was not a blunder. */
}
else /* The move apparently was a blunder, but still the best move. */
good_move_found = 1;
}
else
good_move_found = 1; /* It's best to pass. */
}
if (best_value > 0.0
&& best_move != NO_MOVE) {
*the_move = best_move;
*value = best_value;
return 1;
}
return 0;
}
/*
* Review the move reasons to find which (if any) move we want to play.
*
* The parameter pure_threat_value is the value assigned to a move
* which only threatens to capture or kill something. The reason for
* playing these is that the move may be effective because we have
* misevaluated the dangers or because the opponent misplays.
*
* The array allowed_moves restricts which moves may be considered. If
* NULL any move is allowed.
*/
int
review_move_reasons(int *the_move, float *value, int color,
float pure_threat_value, float our_score,
int allowed_moves[BOARDMAX],
int use_thrashing_dragon_heuristics)
{
int save_verbose;
current_color = color;
start_timer(2);
find_more_attack_and_defense_moves(color);
time_report(2, " find_more_attack_and_defense_moves", NO_MOVE, 1.0);
if (get_level() >= 6) {
find_more_owl_attack_and_defense_moves(color);
time_report(2, " find_more_owl_attack_and_defense_moves", NO_MOVE, 1.0);
}
if (large_scale && get_level() >= 6) {
find_large_scale_owl_attack_moves(color);
time_report(2, " find_large_scale_owl_attack_moves", NO_MOVE, 1.0);
}
find_more_semeai_moves(color);
time_report(2, " find_more_semeai_moves", NO_MOVE, 1.0);
save_verbose = verbose;
if (verbose > 0)
verbose--;
examine_move_safety(color);
time_report(2, " examine_move_safety", NO_MOVE, 1.0);
verbose = save_verbose;
/* We can't do this until move_safety is known. */
induce_secondary_move_reasons(color);
time_report(2, " induce_secondary_move_reasons", NO_MOVE, 1.0);
if (printworms || verbose)
list_move_reasons(stderr, NO_MOVE);
/* Evaluate all moves with move reasons. */
value_moves(color, pure_threat_value, our_score,
use_thrashing_dragon_heuristics);
time_report(2, " value_moves", NO_MOVE, 1.0);
/* Perform point redistribution */
redistribute_points();
/* Search through all board positions for the 10 highest valued
* moves and print them.
*/
print_top_moves();
/* Select the highest valued move and return it. */
return find_best_move(the_move, value, color, allowed_moves);
}
/*
* Choosing a strategy based on the current score estimate
* and the game status (between 0.0 (start) and 1.0 (game over)).
*/
void
choose_strategy(int color, float our_score, float game_status)
{
minimum_value_weight = 1.0;
maximum_value_weight = 1.0;
territorial_weight = 1.0;
strategical_weight = 1.0;
attack_dragon_weight = 1.0;
invasion_malus_weight = 1.0;
followup_weight = 1.0;
TRACE(" Game status = %f (0.0 = start, 1.0 = game over)\n", game_status);
if (cosmic_gnugo) {
if (game_status > 0.65 && our_score > 15.0) {
/* We seem to be winning, so we use conservative settings. */
minimum_value_weight = 0.66;
maximum_value_weight = 2.0;
territorial_weight = 0.95;
strategical_weight = 1.0;
attack_dragon_weight = 1.1;
invasion_malus_weight = 1.3;
followup_weight = 1.1;
TRACE(" %s is leading, using conservative settings.\n",
color == WHITE ? "White" : "Black");
}
else if (game_status > 0.16) {
/* We're not winning enough yet, try aggressive settings. */
minimum_value_weight = 0.66;
maximum_value_weight = 2.0;
territorial_weight = 1.4;
strategical_weight = 0.5;
attack_dragon_weight = 0.62;
invasion_malus_weight = 2.0;
followup_weight = 0.62;
/* If we're getting desesperate, try invasions as a last resort */
if (game_status > 0.75 && our_score < -25.0)
invasion_malus_weight = 0.2;
TRACE(" %s is not winning enough, using aggressive settings.\n",
color == WHITE ? "White" : "Black");
}
}
}
/* In order to get valid influence data after a move, we need to rerun
* estimate_territorial_value() for that move. A prerequisite for
* using this function is that move reasons have already been collected.
*
* This function should only be used for debugging purposes.
*/
void
prepare_move_influence_debugging(int pos, int color)
{
float our_score;
if (color == WHITE)
our_score = black_score;
else
our_score = -white_score;
estimate_territorial_value(pos, color, our_score, 1);
}
/* Compute probabilities of each move being played. It is assumed
* that the `move[]' array is filled with proper values (i.e. that
* one of the genmove*() functions has been called).
*
* The value of each move `V_k' should be a uniformly distributed
* random variable (`k' is a unique move index). Let it have values
* from the interval [l_k; u_k] . Then move value has constant
* probability density on the interval:
*
* 1
* d_k = -----------.
* u_k - l_k
*
* We need to determine the probability of `V_k' being the largest of
* {V_1, V_2, ..., V_n}. Probability density is like follows:
*
* D_k(t) = d_k * Product(P{V_i < t} for i != k), l_k <= t <= u_k,
*
* where P{A} is the probability of event `A'. By integrating D_k(t)
* from `l_k' to `u_k' we can find the probability in question:
*
* P{V_k > V_i for i != k} = Integrate(D_k(t) dt from l_k to u_k).
*
* Function D_k(t) is a polynomial on each of subintervals produced by
* points `l_k', `u_k', k = 1, ..., n. When t < min(l_k), D_k(t) is
* zero. On other subintervals it can be evaluated by taking into
* account that
*
* P{V_i < t} = d_i * (t - l_i) if t < u_i;
* P{V_i < t} = 1 if t >= u_i.
*/
void
compute_move_probabilities(float probabilities[BOARDMAX])
{
int k;
int pos;
int num_moves = 0;
int moves[BOARDMAX];
double lower_values[BOARDMAX];
double upper_values[BOARDMAX];
double densities[BOARDMAX];
double common_lower_limit = 0.0;
/* Find all moves with positive values. */
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
probabilities[pos] = 0.0;
if (ON_BOARD(pos)) {
/* FIXME: what about point redistribution? */
if (move[pos].final_value > 0.0) {
double scale = 0.01 * (double) move[pos].randomness_scaling;
moves[num_moves] = pos;
lower_values[num_moves] = ((double) move[pos].final_value
- (scale * move[pos].random_number));
upper_values[num_moves] = lower_values[num_moves] + scale;
densities[num_moves] = 1.0 / scale;
if (lower_values[num_moves] > common_lower_limit)
common_lower_limit = lower_values[num_moves];
num_moves++;
}
}
}
/* Compute probability of each move. */
for (k = 0; k < num_moves; k++) {
int i;
double lower_limit = common_lower_limit;
/* Iterate over subintervals for integration. */
while (lower_limit < upper_values[k]) {
int j;
double upper_limit = upper_values[k];
double span_power;
double polynomial[BOARDMAX];
int degree;
degree = 0;
polynomial[0] = 1.0;
for (i = 0; i < num_moves; i++) {
/* See if we need to decrease current subinterval. */
if (upper_values[i] > lower_limit && upper_values[i] < upper_limit)
upper_limit = upper_values[i];
}
/* Build the probability density polynomial for the current
* subinterval.
*/
for (i = 0; i < num_moves; i++) {
if (i != k && upper_values[i] >= upper_limit) {
polynomial[++degree] = 0.0;
for (j = degree; j > 0; j--) {
polynomial[j] = (densities[i]
* (polynomial[j - 1]
+ ((lower_limit - lower_values[i])
* polynomial[j])));
}
polynomial[0] *= densities[i] * (lower_limit - lower_values[i]);
}
}
/* And compute the integral of the polynomial on the current
* subinterval.
*/
span_power = 1.0;
for (j = 0; j <= degree; j++) {
span_power *= upper_limit - lower_limit;
probabilities[moves[k]] += (polynomial[j] * span_power) / (j + 1);
}
/* Go on to the next subinterval. */
lower_limit = upper_limit;
}
probabilities[moves[k]] *= densities[k];
}
}
/*
* Local Variables:
* tab-width: 8
* c-basic-offset: 2
* End:
*/