Initial commit of GNU Go v3.8.
[sgk-go] / engine / influence.c
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* This is GNU Go, a Go program. Contact gnugo@gnu.org, or see *
* http://www.gnu.org/software/gnugo/ for more information. *
* *
* Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, *
* 2008 and 2009 by the Free Software Foundation. *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation - version 3 or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License in file COPYING for more details. *
* *
* You should have received a copy of the GNU General Public *
* License along with this program; if not, write to the Free *
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, *
* Boston, MA 02111, USA. *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "gnugo.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "liberty.h"
#include "influence.h"
#include "patterns.h"
#include "gg_utils.h"
static void add_influence_source(int pos, int color, float strength,
float attenuation,
struct influence_data *q);
static void print_influence(const struct influence_data *q,
const char *info_string);
static void print_numeric_influence(const struct influence_data *q,
const float values[BOARDMAX],
const char *format, int width,
int draw_stones, int mark_epsilon);
static void print_influence_areas(const struct influence_data *q);
static void value_territory(struct influence_data *q);
static void enter_intrusion_source(int source_pos, int strength_pos,
float strength, float attenuation,
struct influence_data *q);
static void add_marked_intrusions(struct influence_data *q);
/* Influence computed for the initial position, i.e. before making
* some move.
*/
struct influence_data initial_black_influence;
struct influence_data initial_white_influence;
/* Influence computed after some move has been made. */
struct influence_data move_influence;
struct influence_data followup_influence;
/* Influence used for estimation of escape potential. */
static struct influence_data escape_influence;
/* Pointer to influence data used during pattern matching. */
static struct influence_data *current_influence = NULL;
/* Thresholds values used in the whose_moyo() functions */
static struct moyo_determination_data moyo_data;
static struct moyo_determination_data moyo_restricted_data;
/* Thresholds value used in the whose_territory() function */
static float territory_determination_value;
/* This curve determines how much influence is needed at least to claim
* an intersection as territory, in dependence of the "center value".
* (In the center, more effort is needed to get territory!)
* The center value is at the moment defined as follows:
* If d1, d2 are the distance to vertical and horizontal border, resp.,
* with d1<d2, then
* central = 3 * d1 + min(d2, 4)
* So this is mainly a function of the distance to the border; the
* distance to the second-nearest border gives a small correction of at
* most 4. This distinguishes edge and corner positions.
*
* The values for intersections close to a corner or to the edge have
* to be consistent such that standard corner enclosure etc. are
* sufficient to claim territory. The center values are more arbitrary
* suspect to tuning.
*/
static struct interpolation_data min_infl_for_territory =
{ 6, 0.0, 24.0, { 6.0, 15.0, 26.0, 36.0, 45.0, 50.0, 55.0 }};
/* Determines the territory correction factor in dependence of the ratio
* ( influence of stronger color / min_infl_for_territory(intersection))
*/
static struct interpolation_data territory_correction =
{ 5, (float) 0.0, 1.0, {0.0, 0.25, 0.45, 0.65, 0.85, 1.0}};
/* If set, print influence map when computing this move. Purely for
* debugging.
*/
static int debug_influence = NO_MOVE;
/* Assigns an id to all influence computations for reference in the
* delta territory cache.
*/
static int influence_id = 0;
/* This is the core of the influence function. Given the coordinates
* and color of an influence source, it radiates the influence
* outwards until it hits a barrier or the strength of the influence
* falls under a certain threshold.
*
* The radiation is performed by a breadth first propagation,
* implemented by means of an internal queue.
*
* Since this function has turned out be one of the bottlenecks, loop
* unrolling makes a noticeable performance difference. It does,
* however, make the code much harder to read and maintain. Therefore
* we include both the original and the unrolled versions.
*/
#define EXPLICIT_LOOP_UNROLLING 1
#if EXPLICIT_LOOP_UNROLLING
/* In addition to the parameters, this macro expects
* m,n = original source of influence
* ii = point influence is being spread from
* delta_i = I(ii) - m
* delta_j = J(ii) - n
* current_strength combines strength and damping factor
* b is 1/(square of distance from m,n to i,j) ; or halved
* for diagonals
*
* arg is i + arg_di ; arg_j is j + arg_dj
* arg_d is 1 for diagonal movement
*
*/
#define code1(arg_di, arg_dj, arg, arg_d) do { \
if (!q->safe[arg] \
&& ((arg_di)*(delta_i) + (arg_dj)*(delta_j) > 0 \
|| queue_start == 1)) { \
float contribution; \
float permeability = permeability_array[ii]; \
if (arg_d) { \
permeability *= gg_max(permeability_array[ii + DELTA(arg_di, 0)], \
permeability_array[ii + DELTA(0, arg_dj)]); \
if (permeability == 0.0) \
continue; \
} \
contribution = current_strength * permeability; \
if (queue_start != 1) { \
int a = (arg_di)*(delta_i) + (arg_dj)*(delta_j); \
contribution *= (a*a) * b; /* contribution *= cos(phi) */ \
} \
if (contribution <= INFLUENCE_CUTOFF) \
continue; \
if (working[arg] == 0.0) { \
q->queue[queue_end] = (arg); \
queue_end++; \
} \
working[arg] += contribution; \
} } while (0)
#endif
static void
accumulate_influence(struct influence_data *q, int pos, int color)
{
int ii;
int m = I(pos);
int n = J(pos);
int k;
#if !EXPLICIT_LOOP_UNROLLING
int d;
#endif
float b;
float inv_attenuation;
float inv_diagonal_damping;
float *permeability_array;
/* Clear the queue. Entry 0 is implicitly (m, n). */
int queue_start = 0;
int queue_end = 1;
static float working[BOARDMAX];
static int working_area_initialized = 0;
if (!working_area_initialized) {
for (ii = 0; ii < BOARDMAX; ii++)
working[ii] = 0.0;
working_area_initialized = 1;
}
if (0)
gprintf("Accumulating influence for %s at %m\n",
color_to_string(color), m, n);
/* Attenuation only depends on the influence origin. */
if (color == WHITE)
inv_attenuation = 1.0 / q->white_attenuation[pos];
else
inv_attenuation = 1.0 / q->black_attenuation[pos];
if (q->is_territorial_influence)
inv_diagonal_damping = 1.0 / TERR_DIAGONAL_DAMPING;
else
inv_diagonal_damping = 1.0 / DIAGONAL_DAMPING;
if (color == WHITE)
permeability_array = q->white_permeability;
else
permeability_array = q->black_permeability;
/* We put the original source into slot 0. */
q->queue[0] = pos;
if (color == WHITE)
working[pos] = q->white_strength[pos];
else
working[pos] = q->black_strength[pos];
/* Spread influence until the stack is empty. */
while (queue_start < queue_end) {
float current_strength;
int delta_i, delta_j;
ii = q->queue[queue_start];
delta_i = I(ii) - m;
delta_j = J(ii) - n;
queue_start++;
if (permeability_array[ii] == 0.0)
continue;
if (0)
gprintf("Picked %1m from queue. w=%f start=%d end=%d\n",
ii, working[ii], queue_start, queue_end);
if (queue_start == 1)
b = 1.0;
else
b = 1.0 / ((delta_i)*(delta_i) + (delta_j)*(delta_j));
current_strength = working[ii] * inv_attenuation;
#if !EXPLICIT_LOOP_UNROLLING
/* Try to spread influence in each of the eight directions. */
for (d = 0; d < 8; d++) {
int di = deltai[d];
int dj = deltaj[d];
int d_ii = delta[d];
/* Verify that (ii + d_ii) is
* 1. Inside the board.
* 2. Not occupied.
* 3. Directed outwards. For the origin all directions are outwards.
*/
if (ON_BOARD(ii + d_ii)
&& (!q->safe[ii + d_ii])
&& (di*(delta_i) + dj*(delta_j) > 0
|| queue_start == 1)) {
float contribution;
float permeability = permeability_array[ii];
float dfactor;
float inv_damping;
/* Now compute the damping of the influence.
* First we have the permeability at the point we are
* spreading from. For diagonal movement we also take the
* permeability of the vertices we are "passing by" into
* account.
*/
if (d > 3) { /* diagonal movement */
permeability *= gg_max(permeability_array[ii + DELTA(di, 0)],
permeability_array[ii + DELTA(0, dj)]);
inv_damping = inv_diagonal_damping;
dfactor = 0.5;
}
else {
inv_damping = 1.0;
dfactor = 1.0;
}
if (permeability == 0.0)
continue;
contribution = permeability * current_strength * inv_damping;
/* Finally direction dependent damping. */
if (ii != pos) {
int a = di*(delta_i) + dj*(delta_j);
gg_assert(a > 0);
contribution *= (a*a) * b * dfactor;
}
/* Stop spreading influence if the contribution becomes too low. */
if (contribution <= INFLUENCE_CUTOFF)
continue;
/* If no influence here before, add the point to the queue for
* further spreading.
*/
if (0)
gprintf(" Spreading %s influence from %1m to %1m, d=%d\n",
color_to_string(color), ii, ii + d_ii, d);
if (working[ii + d_ii] == 0.0) {
q->queue[queue_end] = ii + d_ii;
queue_end++;
}
working[ii + d_ii] += contribution;
}
}
#else
if (ON_BOARD(ii + delta[0]))
code1(deltai[0], deltaj[0], ii + delta[0], 0);
if (ON_BOARD(ii + delta[1]))
code1(deltai[1], deltaj[1], ii + delta[1], 0);
if (ON_BOARD(ii + delta[2]))
code1(deltai[2], deltaj[2], ii + delta[2], 0);
if (ON_BOARD(ii + delta[3]))
code1(deltai[3], deltaj[3], ii + delta[3], 0);
/* Update factors for diagonal movement. */
b *= 0.5;
current_strength *= inv_diagonal_damping;
if (ON_BOARD(ii + delta[4]))
code1(deltai[4], deltaj[4], ii + delta[4], 1);
if (ON_BOARD(ii + delta[5]))
code1(deltai[5], deltaj[5], ii + delta[5], 1);
if (ON_BOARD(ii + delta[6]))
code1(deltai[6], deltaj[6], ii + delta[6], 1);
if (ON_BOARD(ii + delta[7]))
code1(deltai[7], deltaj[7], ii + delta[7], 1);
#endif
}
/* Add the values in the working area to the accumulated influence
* and simultaneously reset the working area. We know that all
* influenced points were stored in the queue, so we just traverse
* it.
*/
for (k = 0; k < queue_end; k++) {
ii = q->queue[k];
if (color == WHITE) {
if (working[ii] > 1.01 * INFLUENCE_CUTOFF
|| q->white_influence[ii] == 0.0)
q->white_influence[ii] += working[ii];
}
else {
if (working[ii] > 1.01 * INFLUENCE_CUTOFF
|| q->black_influence[ii] == 0.0)
q->black_influence[ii] += working[ii];
}
working[ii] = 0.0;
}
}
/* Initialize the influence_data structure. */
static void
init_influence(struct influence_data *q,
const signed char safe_stones[BOARDMAX],
const float strength[BOARDMAX])
{
int ii;
float attenuation;
/* Initialisation of some global positional values, based on
* game stage.
*/
if (cosmic_gnugo) {
float t;
if ((board_size != 19) || (movenum <= 2) || ((movenum / 2) % 2))
cosmic_importance = 0.0;
else {
cosmic_importance = 1.0 - (movenum / 150.0)*(movenum / 150.0);
cosmic_importance = gg_max(0.0, cosmic_importance);
}
t = cosmic_importance;
moyo_data.influence_balance = t * 15.0 + (1.0-t) * 5.0;
moyo_data.my_influence_minimum = t * 5.0 + (1.0-t) * 5.0;
moyo_data.opp_influence_maximum = t * 30.0 + (1.0-t) * 30.0;
/* we use the same values for moyo and moyo_restricted */
moyo_restricted_data = moyo_data;
territory_determination_value = t * 0.95 + (1.0-t) * 0.95;
min_infl_for_territory.values[0] = t * 6.0 + (1.0-t) * 10.0;
min_infl_for_territory.values[1] = t * 10.0 + (1.0-t) * 15.0;
min_infl_for_territory.values[2] = t * 20.0 + (1.0-t) * 15.0;
min_infl_for_territory.values[3] = t * 20.0 + (1.0-t) * 20.0;
min_infl_for_territory.values[4] = t * 20.0 + (1.0-t) * 20.0;
min_infl_for_territory.values[5] = t * 15.0 + (1.0-t) * 15.0;
min_infl_for_territory.values[6] = t * 10.0 + (1.0-t) * 15.0;
}
else {
/* non-cosmic values */
cosmic_importance = 0.0;
moyo_data.influence_balance = 7.0;
moyo_data.my_influence_minimum = 5.0;
moyo_data.opp_influence_maximum = 10.0;
moyo_restricted_data.influence_balance = 10.0;
moyo_restricted_data.my_influence_minimum = 10.0;
moyo_restricted_data.opp_influence_maximum = 10.0;
territory_determination_value = 0.95;
min_infl_for_territory.values[0] = 6.0;
min_infl_for_territory.values[1] = 15.0;
min_infl_for_territory.values[2] = 26.0;
min_infl_for_territory.values[3] = 36.0;
min_infl_for_territory.values[4] = 45.0;
min_infl_for_territory.values[5] = 50.0;
min_infl_for_territory.values[6] = 55.0;
}
if (q->is_territorial_influence)
attenuation = TERR_DEFAULT_ATTENUATION;
else
attenuation = 2 * DEFAULT_ATTENUATION;
q->intrusion_counter = 0;
/* Remember this for later. */
memcpy(q->safe, safe_stones, BOARDMAX * sizeof(*safe_stones));
q->captured = black_captured - white_captured;
for (ii = BOARDMIN; ii < BOARDMAX; ii++)
if (ON_BOARD(ii)) {
/* Initialize. */
q->white_influence[ii] = 0.0;
q->black_influence[ii] = 0.0;
q->white_attenuation[ii] = attenuation;
q->black_attenuation[ii] = attenuation;
q->white_permeability[ii] = 1.0;
q->black_permeability[ii] = 1.0;
q->white_strength[ii] = 0.0;
q->black_strength[ii] = 0.0;
q->non_territory[ii] = EMPTY;
if (IS_STONE(board[ii])) {
if (!safe_stones[ii]) {
if (board[ii] == WHITE)
q->white_permeability[ii] = 0.0;
else
q->black_permeability[ii] = 0.0;
}
else {
if (board[ii] == WHITE) {
if (strength)
q->white_strength[ii] = strength[ii];
else
q->white_strength[ii] = DEFAULT_STRENGTH;
q->black_permeability[ii] = 0.0;
}
else {
if (strength)
q->black_strength[ii] = strength[ii];
else
q->black_strength[ii] = DEFAULT_STRENGTH;
q->white_permeability[ii] = 0.0;
}
}
}
else
/* Ideally, safe_stones[] should always be zero for empty
* intersections. This is currently, however, sometimes not true
* when an inessential worm gets captured. So we revise this
* in our private copy here.
*/
q->safe[ii] = 0;
}
}
/* Adds an influence source at position pos with prescribed strength
* and attenuation. color can be BLACK, WHITE or both. If there
* already exists an influence source of the respective color at pos
* that is stronger than the new one, we do nothing.
*/
static void
add_influence_source(int pos, int color, float strength, float attenuation,
struct influence_data *q)
{
if ((color & WHITE) && (q->white_strength[pos] < strength)) {
q->white_strength[pos] = strength;
q->white_attenuation[pos] = attenuation;
}
if ((color & BLACK) && (q->black_strength[pos] < strength)) {
q->black_strength[pos] = strength;
q->black_attenuation[pos] = attenuation;
}
}
/* Adds an intrusion as an entry in the list q->intrusions. */
static void
enter_intrusion_source(int source_pos, int strength_pos,
float strength, float attenuation,
struct influence_data *q)
{
if (q->intrusion_counter >= MAX_INTRUSIONS) {
DEBUG(DEBUG_INFLUENCE, "intrusion list exhausted\n");
return;
}
q->intrusions[q->intrusion_counter].source_pos = source_pos;
q->intrusions[q->intrusion_counter].strength_pos = strength_pos;
q->intrusions[q->intrusion_counter].strength = strength;
q->intrusions[q->intrusion_counter].attenuation = attenuation;
q->intrusion_counter++;
}
/* Comparison of intrusions datas, to sort them. */
static int
compare_intrusions(const void *p1, const void *p2)
{
const struct intrusion_data *intr1 = p1;
const struct intrusion_data *intr2 = p2;
if (intr1->source_pos - intr2->source_pos != 0)
return (intr1->source_pos - intr2->source_pos);
else if (intr1->strength_pos - intr2->strength_pos != 0)
return (intr1->strength_pos - intr2->strength_pos);
else if (intr1->strength > intr2->strength)
return 1;
else
return -1;
}
/* It may happen that we have a low intensity influence source at a
* blocked intersection (due to an intrusion). This function resets the
* permeabilities.
*/
static void
reset_unblocked_blocks(struct influence_data *q)
{
int pos;
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
if (ON_BOARD(pos)) {
if (!q->safe[pos] && q->white_strength[pos] > 0.0
&& q->white_permeability[pos] != 1.0) {
DEBUG(DEBUG_INFLUENCE, " black block removed from %1m\n", pos);
q->white_permeability[pos] = 1.0;
}
if (!q->safe[pos] && q->black_strength[pos] > 0.0
&& q->black_permeability[pos] != 1.0) {
DEBUG(DEBUG_INFLUENCE, " white block removed from %1m\n", pos);
q->black_permeability[pos] = 1.0;
}
}
}
/* This function goes through the list of intrusion sources, and adds
* the intrusion as influence sources for color. The strength is
* corrected so that each stone's intrusions sources can have total
* strength of at most 60%/100% of the strength of the stone.
* (100% is if q == &followup_influence, 60% otherwise).
*/
static void
add_marked_intrusions(struct influence_data *q)
{
int i;
int j = 0;
int source_pos;
float strength_sum;
float correction;
float source_strength;
float allowed_strength;
int color = q->color_to_move;
gg_sort(q->intrusions, q->intrusion_counter, sizeof(q->intrusions[0]),
compare_intrusions);
/* Go through all intrusion sources. */
for (i = 0; i < q->intrusion_counter; i = j) {
strength_sum = 0.0;
source_pos = q->intrusions[i].source_pos;
/* "Anonymous" intrusios go in uncorrected. */
if (source_pos == NO_MOVE) {
add_influence_source(q->intrusions[i].strength_pos, color,
q->intrusions[j].strength,
q->intrusions[j].attenuation, q);
DEBUG(DEBUG_INFLUENCE, "Adding %s intrusion at %1m, value %f\n",
(color == BLACK) ? "black" : "white",
q->intrusions[j].strength_pos, q->intrusions[j].strength);
j = i+1;
continue;
}
if (color == BLACK)
source_strength = q->black_strength[source_pos];
else
source_strength = q->white_strength[source_pos];
/* First loop: Determine correction factor. */
for (j = i; (j < q->intrusion_counter)
&& (q->intrusions[j].source_pos == source_pos); j++) {
/* Of identical strength positions, only take strongest value. */
if (j == i
|| q->intrusions[j].strength_pos != q->intrusions[j-1].strength_pos)
strength_sum += q->intrusions[j].strength;
}
if (q == &followup_influence)
allowed_strength = source_strength;
else
allowed_strength = 0.6 * source_strength;
if (strength_sum > allowed_strength)
correction = (allowed_strength / strength_sum);
else
correction = 1.0;
/* Second loop: Add influence sources. */
for (j = i; (j < q->intrusion_counter)
&& (q->intrusions[j].source_pos == source_pos); j++) {
/* Of identical strenght positions, only take strongest value. */
if (j == i || q->intrusions[j].strength_pos
!= q->intrusions[j-1].strength_pos) {
add_influence_source(q->intrusions[j].strength_pos, color,
correction * q->intrusions[j].strength,
q->intrusions[j].attenuation, q);
DEBUG(DEBUG_INFLUENCE,
"Adding %s intrusion for %1m at %1m, value %f (correction %f)\n",
(color == BLACK) ? "black" : "white", source_pos,
q->intrusions[j].strength_pos,
correction * q->intrusions[j].strength, correction);
}
}
}
}
/* Callback for the matched patterns in influence.db and barriers.db.
* The pattern classes used here are:
* A - Barrier pattern, where O plays first and X tries to block influence.
* D - Barrier pattern, where O plays first and O tries to block influence.
* B - Intrusion patterns, adding a low intensity influence source.
* E - Enhance patterns, FIXME: document this one!
* t - Non-territory patterns, marking vertices as not territory.
* I - Invasion patterns, adding a low intensity influence source.
* e - Escape bonus. Used together with I to increase the value substantially
* if escape influence is being computed.
*
* Classes A, D, and B are matched with color as O, and it is assumed
* that O is in turn to move. Classes E and I are matched with either
* color as O.
*/
static void
influence_callback(int anchor, int color, struct pattern *pattern, int ll,
void *data)
{
int pos = AFFINE_TRANSFORM(pattern->move_offset, ll, anchor);
int k;
struct influence_data *q = data;
/* We also ignore enhancement patterns in territorial influence. */
if ((pattern->class & CLASS_E) && q->is_territorial_influence)
return;
/* Don't use invasion (I) patterns when scoring. */
if (doing_scoring && (pattern->class & CLASS_I))
return;
/* Loop through pattern elements to see if an A or D pattern
* can possibly have any effect. If not we can skip evaluating
* constraint and/or helper.
*/
if (pattern->class & (CLASS_A | CLASS_D)) {
int something_to_do = 0;
gg_assert(q->is_territorial_influence);
for (k = 0; k < pattern->patlen; ++k) { /* match each point */
int blocking_color;
int ii;
/* The order of elements is: All commas, all "!", then other. */
if (pattern->patn[k].att != ATT_comma
&& pattern->patn[k].att != ATT_not)
break;
ii = AFFINE_TRANSFORM(pattern->patn[k].offset, ll, anchor);
if (pattern->class & CLASS_D)
blocking_color = color;
else
blocking_color = OTHER_COLOR(color);
if ((blocking_color == WHITE
&& q->black_permeability[ii] != 0.0)
|| (blocking_color == BLACK
&& q->white_permeability[ii] != 0.0)) {
something_to_do = 1;
break;
}
}
if (!something_to_do)
return;
}
/* Require that all O stones in the pattern have non-zero influence
* strength for patterns of type D, E, B, t, and all X stones have
* non-zero strength for patterns of type A and t.
*
* Patterns also having class s are an exception from this rule.
*/
if ((pattern->class & (CLASS_D | CLASS_A | CLASS_B | CLASS_E | CLASS_t))
&& !(pattern->class & CLASS_s)) {
for (k = 0; k < pattern->patlen; ++k) { /* match each point */
int ii = AFFINE_TRANSFORM(pattern->patn[k].offset, ll, anchor);
if (pattern->patn[k].att == ATT_O) {
if ((pattern->class & (CLASS_B | CLASS_t | CLASS_E | CLASS_D))
&& ((color == WHITE && q->white_strength[ii] == 0.0)
|| (color == BLACK && q->black_strength[ii] == 0.0)))
return;
}
else if (pattern->patn[k].att == ATT_X) {
if ((pattern->class & (CLASS_A | CLASS_t))
&& ((color == BLACK && q->white_strength[ii] == 0.0)
|| (color == WHITE && q->black_strength[ii] == 0.0)))
return; /* Match failed. */
}
}
}
/* If the pattern has a constraint, call the autohelper to see
* if the pattern must be rejected.
*/
if ((pattern->autohelper_flag & HAVE_CONSTRAINT)
&& !pattern->autohelper(ll, pos, color, 0))
return;
DEBUG(DEBUG_INFLUENCE, "influence pattern '%s'+%d matched at %1m\n",
pattern->name, ll, anchor);
/* For t patterns, everything happens in the action. */
if ((pattern->class & CLASS_t)
&& (pattern->autohelper_flag & HAVE_ACTION)) {
pattern->autohelper(ll, pos, color, INFLUENCE_CALLBACK);
return;
}
/* For I patterns, add a low intensity, both colored, influence
* source at *.
*/
if (pattern->class & CLASS_I) {
int this_color = EMPTY;
float strength;
float attenuation;
if (q->color_to_move == EMPTY || (pattern->class & CLASS_s))
this_color = BLACK | WHITE;
else if (q->color_to_move != color)
this_color = q->color_to_move;
if (cosmic_gnugo) {
float t = 0.15 + (1.0 - cosmic_importance);
t = gg_min(1.0, t);
t = gg_max(0.0, t);
strength = t * pattern->value;
attenuation = 1.6;
}
else {
strength = pattern->value;
attenuation = 1.5;
}
/* Increase strength if we're computing escape influence. */
if (!q->is_territorial_influence && (pattern->class & CLASS_e))
add_influence_source(pos, this_color, 20 * strength, attenuation, q);
else
add_influence_source(pos, this_color, strength, attenuation, q);
DEBUG(DEBUG_INFLUENCE,
" low intensity influence source at %1m, strength %f, color %C\n",
pos, strength, this_color);
return;
}
/* For E patterns, add a new influence source of the same color and
* pattern defined strength at *.
*/
if (pattern->class & CLASS_E) {
add_influence_source(pos, color, pattern->value, DEFAULT_ATTENUATION, q);
DEBUG(DEBUG_INFLUENCE,
" extra %C source at %1m, strength %f\n", color,
pos, pattern->value);
return;
}
/* For B patterns add intrusions sources at "!" points. */
if (pattern->class & CLASS_B) {
float strength;
if (cosmic_gnugo) {
float t = 0.15 + (1.0 - cosmic_importance);
t = gg_min(1.0, t);
t = gg_max(0.0, t);
strength = t * pattern->value;
}
else
strength = pattern->value;
for (k = 0; k < pattern->patlen; ++k) /* match each point */
if (pattern->patn[k].att == ATT_not) {
/* transform pattern real coordinate */
int ii = AFFINE_TRANSFORM(pattern->patn[k].offset, ll, anchor);
/* Low intensity influence source for the color in turn to move. */
if (q->is_territorial_influence)
enter_intrusion_source(anchor, ii, strength,
TERR_DEFAULT_ATTENUATION, q);
else
add_influence_source(ii, color, strength, DEFAULT_ATTENUATION, q);
DEBUG(DEBUG_INFLUENCE, " intrusion at %1m\n", ii);
}
return;
}
gg_assert(pattern->class & (CLASS_D | CLASS_A));
/* For A, D patterns, add blocks for all "," or "!" points. */
for (k = 0; k < pattern->patlen; ++k) { /* match each point */
if (pattern->patn[k].att == ATT_comma
|| pattern->patn[k].att == ATT_not) {
/* transform pattern real coordinate */
int ii = AFFINE_TRANSFORM(pattern->patn[k].offset, ll, anchor);
int blocking_color;
if (pattern->class & CLASS_D)
blocking_color = color;
else
blocking_color = OTHER_COLOR(color);
DEBUG(DEBUG_INFLUENCE, " barrier for %s influence at %1m\n",
color_to_string(OTHER_COLOR(blocking_color)), ii);
if (pattern->patn[k].att == ATT_comma) {
if (blocking_color == WHITE)
q->black_permeability[ii] = 0.0;
else
q->white_permeability[ii] = 0.0;
}
/* Weak barrier at !-marked points. */
else {
if (blocking_color == WHITE)
q->black_permeability[ii] *= 0.7;
else
q->white_permeability[ii] *= 0.7;
}
}
}
}
/* Callback for matched barriers patterns in followup influence.
* This adds an intrusion source for all B patterns in barriers.db for
* the color that has made a move if all the following conditions are
* fulfilled:
* - the anchor ("Q") is adjacent (directly or diagonally) to a "saved stone"
* (this is ensured by matchpat before calling back here)
* - at least one of the O stones in the pattern is a saved stone.
* - the usual pattern constraint ("; oplay_attack_either(...)") is fulfilled
* - the pattern action (typically ">return (!xplay_attack(...))") returns
* true if called with parameter action = FOLLOWUP_INFLUENCE_CALLBACK.
* "Saved stones" are: the move played + tactically rescued stones + stones
* in a critcal dragon brought to life by this move
*/
static void
followup_influence_callback(int anchor, int color, struct pattern *pattern,
int ll, void *data)
{
int k;
int t;
struct influence_data *q = data;
UNUSED(color);
/* We use only B patterns in followup influence. */
if (!(pattern->class & CLASS_B))
return;
t = AFFINE_TRANSFORM(pattern->move_offset, ll, anchor);
/* If the pattern has a constraint, call the autohelper to see
* if the pattern must be rejected.
*/
if (pattern->autohelper_flag & HAVE_CONSTRAINT
&& !pattern->autohelper(ll, t, color, 0))
return;
/* Actions in B patterns are used as followup specific constraints. */
if ((pattern->autohelper_flag & HAVE_ACTION)
&& !pattern->autohelper(ll, t, color, FOLLOWUP_INFLUENCE_CALLBACK))
return;
DEBUG(DEBUG_INFLUENCE, "influence pattern '%s'+%d matched at %1m\n",
pattern->name, ll, anchor);
for (k = 0; k < pattern->patlen; ++k) /* match each point */
if (pattern->patn[k].att == ATT_not) {
/* transform pattern real coordinate */
int ii = AFFINE_TRANSFORM(pattern->patn[k].offset, ll, anchor);
/* Low intensity influence source for the color in turn to move. */
enter_intrusion_source(anchor, ii, pattern->value,
TERR_DEFAULT_ATTENUATION, q);
DEBUG(DEBUG_INFLUENCE, " followup for %1m: intrusion at %1m\n",
anchor, ii);
}
}
/* Called from actions for t patterns. Marks (pos) as not being
* territory for (color).
*/
void
influence_mark_non_territory(int pos, int color)
{
DEBUG(DEBUG_INFLUENCE, " non-territory for %C at %1m\n", color, pos);
current_influence->non_territory[pos] |= color;
}
/* Erases all territory for color at (pos), and all directly neighboring
* fields.
*/
void
influence_erase_territory(struct influence_data *q, int pos, int color)
{
int k;
ASSERT1((color == WHITE && q->territory_value[pos] >= 0.0)
|| (color == BLACK && q->territory_value[pos] <= 0.0), pos);
current_influence = q;
q->territory_value[pos] = 0.0;
influence_mark_non_territory(pos, color);
for (k = 0; k < 4; k++) {
if (ON_BOARD(pos + delta[k])) {
q->territory_value[pos + delta[k]] = 0.0;
influence_mark_non_territory(pos + delta[k], color);
}
}
}
/* Match the patterns in influence.db and barriers.db in order to add:
* - influence barriers,
* - extra influence sources at possible invasion and intrusion points, and
* - extra influence induced by strong positions.
* Reduce permeability around each living stone.
* Reset permeability to 1.0 at intrusion points.
*/
static void
find_influence_patterns(struct influence_data *q)
{
int ii;
current_influence = q;
matchpat(influence_callback, ANCHOR_COLOR, &influencepat_db, q, NULL);
if (q->color_to_move != EMPTY)
matchpat(influence_callback, q->color_to_move, &barrierspat_db, q, NULL);
if (q->is_territorial_influence)
add_marked_intrusions(q);
/* Additionally, we introduce a weaker kind of barriers around living
* stones.
*/
for (ii = BOARDMIN; ii < BOARDMAX; ii++)
if (ON_BOARD(ii) && !q->safe[ii]) {
int k;
float black_reduction = 1.0;
float white_reduction = 1.0;
for (k = 0; k < 8; k++) {
int d = delta[k];
if (IS_STONE(board[ii + d]) && q->safe[ii + d]) {
/* Reduce less diagonally. */
float reduction = (k < 4) ? 0.25 : 0.65;
if (board[ii + d] == BLACK)
white_reduction *= reduction;
else
black_reduction *= reduction;
}
else if (IS_STONE(board[ii + d]) && !q->safe[ii + d]) {
if (board[ii + d] == BLACK)
white_reduction = -100.0;
else
black_reduction = -100.0;
}
}
if (black_reduction > 0.0)
q->black_permeability[ii] *= black_reduction;
if (white_reduction > 0.0)
q->white_permeability[ii] *= white_reduction;
}
reset_unblocked_blocks(q);
}
/* This function checks whether we have two or more adjacent blocks for
* influence of color next to pos. If yes, it returns the position of the
* least valuable blocks; otherwise, it returns NO_MOVE.
*/
static int
check_double_block(int color, int pos, const struct influence_data *q)
{
int k;
int block_neighbors = 0;
const float *permeability = ((color == BLACK) ? q->black_permeability :
q->white_permeability);
/* Count neighboring blocks. */
for (k = 0; k < 4; k++)
if (board[pos + delta[k]] == EMPTY && permeability[pos + delta[k]] == 0.0)
block_neighbors++;
if (block_neighbors >= 2) {
/* Search for least valuable block. */
float smallest_value = 4.0 * MAX_BOARD * MAX_BOARD;
int smallest_block = NO_MOVE;
/* We count opponent's territory as positive. */
float sign = ((color == WHITE) ? -1.0 : 1.0);
for (k = 0; k < 4; k++) {
int neighbor = pos + delta[k];
if (board[neighbor] == EMPTY && permeability[neighbor] == 0.0) {
/* Value is sum of opponents territory at this and all 4 neighboring
* intersections.
*/
float this_value = sign * q->territory_value[neighbor];
int j;
for (j = 0; j < 4; j++)
if (ON_BOARD(neighbor + delta[j]))
this_value += sign * q->territory_value[neighbor + delta[j]];
/* We use an artifical tie breaker to avoid possible platform
* dependency.
*/
if (this_value + 0.0005 < smallest_value) {
smallest_block = neighbor;
smallest_value = this_value;
}
}
}
ASSERT1(ON_BOARD1(smallest_block), pos);
return smallest_block;
}
return NO_MOVE;
}
#define MAX_DOUBLE_BLOCKS 20
/* This function checks for the situation where an influence source for
* the color to move is direclty neighbored by 2 or more influence blocks.
* It then removes the least valuable of these blocks, and re-runs the
* influence accumulation for this position.
*
* See endgame:840 for an example where this is essential.
*/
static void
remove_double_blocks(struct influence_data *q,
const signed char inhibited_sources[BOARDMAX])
{
int ii;
float *strength = ((q->color_to_move == WHITE) ? q->white_strength :
q->black_strength);
int double_blocks[MAX_DOUBLE_BLOCKS];
int num_blocks = 0;
for (ii = BOARDMIN; ii < BOARDMAX; ii++)
if (board[ii] == EMPTY
&& !(inhibited_sources && inhibited_sources[ii])
&& strength[ii] > 0.0) {
double_blocks[num_blocks] = check_double_block(q->color_to_move, ii, q);
if (double_blocks[num_blocks] != NO_MOVE) {
num_blocks++;
if (num_blocks == MAX_DOUBLE_BLOCKS)
break;
}
}
{
int k;
float *permeability = ((q->color_to_move == BLACK)
? q->black_permeability : q->white_permeability);
for (k = 0; k < num_blocks; k++) {
DEBUG(DEBUG_INFLUENCE, "Removing block for %s at %1m.\n",
color_to_string(q->color_to_move), double_blocks[k]);
permeability[double_blocks[k]] = 1.0;
accumulate_influence(q, double_blocks[k], q->color_to_move);
}
}
}
/* Do the real work of influence computation. This is called from
* compute_influence and compute_escape_influence.
*
* q->is_territorial_influence and q->color_to_move must be set by the caller.
*/
static void
do_compute_influence(const signed char safe_stones[BOARDMAX],
const signed char inhibited_sources[BOARDMAX],
const float strength[BOARDMAX], struct influence_data *q,
int move, const char *trace_message)
{
int ii;
init_influence(q, safe_stones, strength);
modify_depth_values(stackp - 1);
find_influence_patterns(q);
modify_depth_values(1 - stackp);
for (ii = BOARDMIN; ii < BOARDMAX; ii++)
if (ON_BOARD(ii) && !(inhibited_sources && inhibited_sources[ii])) {
if (q->white_strength[ii] > 0.0)
accumulate_influence(q, ii, WHITE);
if (q->black_strength[ii] > 0.0)
accumulate_influence(q, ii, BLACK);
}
value_territory(q);
remove_double_blocks(q, inhibited_sources);
value_territory(q);
if ((move == NO_MOVE
&& (printmoyo & PRINTMOYO_INITIAL_INFLUENCE))
|| (debug_influence && move == debug_influence))
print_influence(q, trace_message);
}
/* Compute the influence values for both colors.
*
* The caller must
* - set up the board[] state
* - mark safe stones with INFLUENCE_SAFE_STONE, dead stones with 0
* - mark stones newly saved by a move with INFLUENCE_SAVED_STONE
* (this is relevant if the influence_data *q is reused to compute
* a followup value for this move).
*
* Results will be stored in q.
*
* (move) has no effects except toggling debugging. Set it to -1
* for no debug output at all (otherwise it will be controlled by
* the -m command line option).
*
* It is assumed that color is in turn to move. (This affects the
* barrier patterns (class A, D) and intrusions (class B)). Color
*/
void
compute_influence(int color, const signed char safe_stones[BOARDMAX],
const float strength[BOARDMAX], struct influence_data *q,
int move, const char *trace_message)
{
int save_debug = debug;
VALGRIND_MAKE_WRITABLE(q, sizeof(*q));
q->is_territorial_influence = 1;
q->color_to_move = color;
/* Turn off DEBUG_INFLUENCE for influence computations we are not
* interested in.
*/
if ((move == NO_MOVE
&& !(printmoyo & PRINTMOYO_INITIAL_INFLUENCE))
|| (move != NO_MOVE && move != debug_influence))
debug = debug &~ DEBUG_INFLUENCE;
influence_id++;
q->id = influence_id;
do_compute_influence(safe_stones, NULL, strength,
q, move, trace_message);
debug = save_debug;
}
/* Return the color of the territory at (pos). If it's territory for
* neither color, EMPTY is returned.
*/
int
whose_territory(const struct influence_data *q, int pos)
{
float bi = q->black_influence[pos];
float wi = q->white_influence[pos];
float terr = q->territory_value[pos];
ASSERT_ON_BOARD1(pos);
if (bi > 0.0 && wi == 0.0 && terr < -territory_determination_value)
return BLACK;
if (wi > 0.0 && bi == 0.0 && terr > territory_determination_value)
return WHITE;
return EMPTY;
}
/* Return the color who has a moyo at (pos). If neither color has a
* moyo there, EMPTY is returned. The definition of moyo in terms of the
* influences is totally ad hoc.
*/
int
whose_moyo(const struct influence_data *q, int pos)
{
float bi = q->black_influence[pos];
float wi = q->white_influence[pos];
int territory_color = whose_territory(q, pos);
if (territory_color != EMPTY)
return territory_color;
if (bi > moyo_data.influence_balance * wi
&& bi > moyo_data.my_influence_minimum
&& wi < moyo_data.opp_influence_maximum)
return BLACK;
if (wi > moyo_data.influence_balance * bi
&& wi > moyo_data.my_influence_minimum
&& bi < moyo_data.opp_influence_maximum)
return WHITE;
return EMPTY;
}
/* Return the color who has a moyo at (pos). If neither color has a
* moyo there, EMPTY is returned.
* The definition of moyo in terms of the influences is totally ad
* hoc.
*
* It has a slightly different definition of moyo than whose_moyo.
*/
int
whose_moyo_restricted(const struct influence_data *q, int pos)
{
float bi = q->black_influence[pos];
float wi = q->white_influence[pos];
int territory_color = whose_territory(q, pos);
/* default */
if (territory_color != EMPTY)
return territory_color;
else if (bi > moyo_restricted_data.influence_balance * wi
&& bi > moyo_restricted_data.my_influence_minimum
&& wi < moyo_restricted_data.opp_influence_maximum)
return BLACK;
else if (wi > moyo_restricted_data.influence_balance * bi
&& wi > moyo_restricted_data.my_influence_minimum
&& bi < moyo_restricted_data.opp_influence_maximum)
return WHITE;
else
return EMPTY;
}
/* Return the color who has dominating influence ("area") at (pos).
* If neither color dominates the influence there, EMPTY is returned.
* The definition of area in terms of the influences is totally ad
* hoc.
*/
int
whose_area(const struct influence_data *q, int pos)
{
float bi = q->black_influence[pos];
float wi = q->white_influence[pos];
int moyo_color = whose_moyo(q, pos);
if (moyo_color != EMPTY)
return moyo_color;
if (bi > 3.0 * wi && bi > 1.0 && wi < 40.0)
return BLACK;
if (wi > 3.0 * bi && wi > 1.0 && bi < 40.0)
return WHITE;
return EMPTY;
}
static void
value_territory(struct influence_data *q)
{
int ii;
int dist_i, dist_j;
float central;
float first_guess[BOARDMAX];
float ratio;
int k;
memset(first_guess, 0, BOARDMAX*sizeof(float));
memset(q->territory_value, 0, BOARDMAX*sizeof(float));
/* First loop: guess territory directly from influence. */
for (ii = BOARDMIN; ii < BOARDMAX; ii++)
if (ON_BOARD(ii)
&& !q->safe[ii]) {
float diff = 0.0;
if (q->white_influence[ii] + q->black_influence[ii] > 0)
diff = (q->white_influence[ii] - q->black_influence[ii])
/ (q->white_influence[ii] + q->black_influence[ii]);
first_guess[ii] = diff * diff * diff;
/* If both side have small influence, we have to reduce this value.
* What we consider "small influence" depends on how central this
* intersection lies.
*
* The values of central on an 11x11 board become:
*
* 4 5 6 7 7 7 7 7 6 5 4
* 5 8 9 10 10 10 10 10 9 8 5
* 6 9 12 13 13 13 13 13 12 9 6
* 7 10 13 16 16 16 16 16 13 10 7
* 7 10 13 16 17 17 17 16 13 10 7
* 7 10 13 16 17 18 17 16 13 10 7
* 7 10 13 16 17 17 17 16 13 10 7
* 7 10 13 16 16 16 16 16 13 10 7
* 6 9 12 13 13 13 13 13 12 9 6
* 5 8 9 10 10 10 10 10 9 8 5
* 4 5 6 7 7 7 7 7 6 5 4
*/
dist_i = gg_min(I(ii), board_size - I(ii) - 1);
dist_j = gg_min(J(ii), board_size - J(ii) - 1);
if (dist_i > dist_j)
dist_i = gg_min(4, dist_i);
else
dist_j = gg_min(4, dist_j);
central = (float) 2 * gg_min(dist_i, dist_j) + dist_i + dist_j;
ratio = gg_max(q->black_influence[ii], q->white_influence[ii])
/ gg_interpolate(&min_infl_for_territory, central);
/* Do not make this adjustment when scoring unless both
* players have non-zero influence.
*/
if (doing_scoring && (q->black_influence[ii] == 0.0
|| q->white_influence[ii] == 0.0))
ratio = 1.0;
first_guess[ii] *= gg_interpolate(&territory_correction, ratio);
/* Dead stone, upgrade to territory. Notice that this is not
* the point for a prisoner, which is added later. Instead
* this is to make sure that the vertex is not regarded as
* moyo or area. Also notice that the non-territory
* degradation below may over-rule this decision.
*/
if (board[ii] == BLACK)
first_guess[ii] = 1.0;
else if (board[ii] == WHITE)
first_guess[ii] = -1.0;
q->territory_value[ii] = first_guess[ii];
}
/* Second loop: Correct according to neighbour vertices. Each territory
* value is degraded to the minimum value of its neighbors (unless this
* neighbor has reduced permeability for the opponent's influence).
*/
for (ii = BOARDMIN; ii < BOARDMAX; ii++)
if (ON_BOARD(ii)
/* Do not overrule dead stone territory above.
* FIXME: This does not do what it claims to do. Correcting it
* seems to break some tests, though.
*/
&& !q->safe[ii]) {
/* Loop over all neighbors. */
for (k = 0; k < 4; k++) {
if (!ON_BOARD(ii + delta[k]))
continue;
if (q->territory_value[ii] > 0.0) {
/* White territory. */
if (!q->safe[ii + delta[k]]) {
float neighbor_val =
q->black_permeability[ii + delta[k]]
* first_guess[ii + delta[k]]
+ (1.0 - q->black_permeability[ii + delta[k]])
* first_guess[ii];
q->territory_value[ii]
= gg_max(0, gg_min(q->territory_value[ii], neighbor_val));
}
}
else {
/* Black territory. */
if (!q->safe[ii + delta[k]]) {
float neighbor_val =
q->white_permeability[ii + delta[k]]
* first_guess[ii + delta[k]]
+ (1 - q->white_permeability[ii + delta[k]])
* first_guess[ii];
q->territory_value[ii]
= gg_min(0, gg_max(q->territory_value[ii], neighbor_val));
}
}
}
}
/* Third loop: Nonterritory patterns, points for prisoners. */
for (ii = BOARDMIN; ii < BOARDMAX; ii++)
if (ON_BOARD(ii)
&& !q->safe[ii]) {
/* If marked as non-territory for the color currently owning
* it, reset the territory value.
*/
if (q->territory_value[ii] > 0.0
&& (q->non_territory[ii] & WHITE))
q->territory_value[ii] = 0.0;
if (q->territory_value[ii] < 0.0
&& (q->non_territory[ii] & BLACK))
q->territory_value[ii] = 0.0;
/* Dead stone, add one to the territory value. */
if (board[ii] == BLACK)
q->territory_value[ii] += 1.0;
else if (board[ii] == WHITE)
q->territory_value[ii] -= 1.0;
}
}
/* Segment the influence map into connected regions of territory,
* moyo, or area. What to segment on is determined by the the function
* pointer region_owner. The segmentation is performed for both
* colors. The connected regions may include stones of the own color,
* but only empty intersections (and dead opponent stones) count
* toward the region size.
*/
static void
segment_region(struct influence_data *q, owner_function_ptr region_owner,
struct moyo_data *regions)
{
int ii;
static signed char marked[BOARDMAX];
regions->number = 0;
/* Reset the markings. */
for (ii = BOARDMIN; ii < BOARDMAX; ii++) {
marked[ii] = 0;
regions->segmentation[ii] = 0;
}
for (ii = BOARDMIN; ii < BOARDMAX; ii++)
if (ON_BOARD(ii)
&& !marked[ii]
&& region_owner(q, ii) != EMPTY) {
/* Found an unlabelled intersection. Use flood filling to find
* the rest of the region.
*/
int size = 0;
float terr_val = 0.0;
int queue_start = 0;
int queue_end = 1;
int color = region_owner(q, ii);
regions->number++;
marked[ii] = 1;
q->queue[0] = ii;
while (queue_start < queue_end) {
int tt = q->queue[queue_start];
int k;
queue_start++;
if (!q->safe[tt] || board[tt] != color) {
size++;
if (q->is_territorial_influence)
terr_val += gg_abs(q->territory_value[tt]);
}
regions->segmentation[tt] = regions->number;
for (k = 0; k < 4; k++) {
int d = delta[k];
if (ON_BOARD(tt + d)
&& !marked[tt + d]
&& region_owner(q, tt + d) == color) {
q->queue[queue_end] = tt + d;
queue_end++;
marked[tt + d] = 1;
}
}
}
regions->size[regions->number] = size;
regions->territorial_value[regions->number] = terr_val;
regions->owner[regions->number] = color;
}
}
/* Export a territory segmentation. */
void
influence_get_territory_segmentation(struct influence_data *q,
struct moyo_data *moyos)
{
segment_region(q, whose_territory, moyos);
}
/* Export the territory valuation at an intersection from initial_influence;
* it is given from (color)'s point of view.
*/
float
influence_territory(const struct influence_data *q, int pos, int color)
{
if (color == WHITE)
return q->territory_value[pos];
else
return -q->territory_value[pos];
}
int
influence_considered_lively(const struct influence_data *q, int pos)
{
int color = board[pos];
ASSERT1(IS_STONE(color), pos);
return (q->safe[pos]
&& ((color == WHITE && q->white_strength[pos] > 0)
|| (color == BLACK && q->black_strength[pos] > 0)));
}
/* Compute a followup influence. It is assumed that the stones that
* deserve a followup have been marked INFLUENCE_SAVED_STONE in
* base->safe.
*/
void
compute_followup_influence(const struct influence_data *base,
struct influence_data *q,
int move, const char *trace_message)
{
int ii;
signed char goal[BOARDMAX];
/* This is the color that will get a followup value. */
int color = OTHER_COLOR(base->color_to_move);
int save_debug = debug;
memcpy(q, base, sizeof(*q));
ASSERT1(IS_STONE(q->color_to_move), move);
q->color_to_move = color;
/* We mark the saved stones and their neighbors in the goal array.
*/
for (ii = BOARDMIN; ii < BOARDMAX; ii++)
if (ON_BOARD(ii)) {
if (q->safe[ii] == INFLUENCE_SAVED_STONE)
goal[ii] = 1;
else
goal[ii] = 0;
}
/* Turn off DEBUG_INFLUENCE for influence computations we are not
* interested in.
*/
if ((move == NO_MOVE
&& !(printmoyo & PRINTMOYO_INITIAL_INFLUENCE))
|| (move != debug_influence))
debug = debug &~ DEBUG_INFLUENCE;
q->intrusion_counter = 0;
current_influence = q;
/* Match B patterns for saved stones. */
matchpat_goal_anchor(followup_influence_callback, color, &barrierspat_db,
q, goal, 1);
debug = save_debug;
/* Now add the intrusions. */
add_marked_intrusions(q);
reset_unblocked_blocks(q);
/* Spread influence for new influence sources. */
for (ii = BOARDMIN; ii < BOARDMAX; ii++)
if (ON_BOARD(ii))
if ((color == BLACK
&& q->black_strength[ii] > base->black_strength[ii])
|| (color == WHITE
&& q->white_strength[ii] > base->white_strength[ii]))
accumulate_influence(q, ii, color);
value_territory(q);
if (debug_influence && debug_influence == move)
print_influence(q, trace_message);
}
/* Compute influence based escape values and return them in the
* escape_value array.
*/
void
compute_escape_influence(int color, const signed char safe_stones[BOARDMAX],
const signed char goal[BOARDMAX],
const float strength[BOARDMAX],
signed char escape_value[BOARDMAX])
{
int k;
int ii;
int save_debug = debug;
/* IMPORTANT: The caching relies on the fact that safe_stones[] and
* strength[] will currently always be identical for identical board[]
* states. Better check for these, too.
*/
static int cached_board[BOARDMAX];
static signed char escape_values[BOARDMAX][2];
static int active_caches[2] = {0, 0};
int cache_number = (color == WHITE);
VALGRIND_MAKE_WRITABLE(&escape_influence, sizeof(escape_influence));
if (!goal) {
/* Encode the values of color and dragons_known into an integer
* between 0 and 3.
*/
int board_was_cached = 1;
/* Notice that we compare the out of board markers as well, in
* case the board size should have changed between calls.
*/
for (ii = BOARDMIN; ii < BOARDMAX; ii++) {
if (cached_board[ii] != board[ii]) {
cached_board[ii] = board[ii];
board_was_cached = 0;
}
}
if (!board_was_cached)
for (k = 0; k < 2; k++)
active_caches[k] = 0;
if (active_caches[cache_number]) {
for (ii = BOARDMIN; ii < BOARDMAX; ii++)
if (ON_BOARD(ii))
escape_value[ii] = escape_values[ii][cache_number];
return;
}
}
/* Use enhance pattern and higher attenuation for escape influence. */
escape_influence.is_territorial_influence = 0;
escape_influence.color_to_move = EMPTY;
/* Turn off DEBUG_INFLUENCE unless we are specifically interested in
* escape computations.
*/
if (!(debug & DEBUG_ESCAPE))
debug &= ~DEBUG_INFLUENCE;
do_compute_influence(safe_stones, goal, strength,
&escape_influence, -1, NULL);
debug = save_debug;
for (ii = BOARDMIN; ii < BOARDMAX; ii++)
if (ON_BOARD(ii)) {
if (whose_moyo(&escape_influence, ii) == color)
escape_value[ii] = 4;
else if (whose_area(&escape_influence, ii) == color)
escape_value[ii] = 2;
else if (whose_area(&escape_influence, ii) == EMPTY) {
if (goal) {
escape_value[ii] = 0;
if (!goal[ii]) {
int goal_proximity = 0;
for (k = 0; k < 8; k++) {
if (ON_BOARD(ii + delta[k])) {
goal_proximity += 2 * goal[ii + delta[k]];
if (k < 4 && ON_BOARD(ii + 2 * delta[k]))
goal_proximity += goal[ii + delta[k]];
}
else
goal_proximity += 1;
}
if (goal_proximity < 6)
escape_value[ii] = 1;
}
}
else
escape_value[ii] = 1;
}
else
escape_value[ii] = 0;
}
if (0 && (debug & DEBUG_ESCAPE) && verbose > 0)
print_influence(&escape_influence, "escape influence");
if (!goal) {
/* Save the computed values in the cache. */
for (ii = BOARDMIN; ii < BOARDMAX; ii++)
if (ON_BOARD(ii))
escape_values[ii][cache_number] = escape_value[ii];
active_caches[cache_number] = 1;
}
}
/* Cache of delta_territory_values. */
static float delta_territory_cache[BOARDMAX];
static float followup_territory_cache[BOARDMAX];
static Hash_data delta_territory_cache_hash[BOARDMAX];
static int territory_cache_position_number = -1;
static int territory_cache_influence_id = -1;
static int territory_cache_color = -1;
/* We cache territory computations. This avoids unnecessary re-computations
* when review_move_reasons is run a second time for the endgame patterns.
*
* (*base) points to the initial_influence data that would be used
* to make the territory computation against.
*/
int
retrieve_delta_territory_cache(int pos, int color, float *move_value,
float *followup_value,
const struct influence_data *base,
Hash_data safety_hash)
{
ASSERT_ON_BOARD1(pos);
ASSERT1(IS_STONE(color), pos);
/* We check whether the color, the board position, or the base influence
* data has changed since the cache entry got entered.
*/
if (territory_cache_position_number == position_number
&& territory_cache_color == color
&& territory_cache_influence_id == base->id
&& delta_territory_cache[pos] != NOT_COMPUTED) {
int i;
for (i = 0; i < NUM_HASHVALUES; i++)
if (delta_territory_cache_hash[pos].hashval[i]
!= safety_hash.hashval[i])
return 0;
*move_value = delta_territory_cache[pos];
*followup_value = followup_territory_cache[pos];
if (0)
gprintf("%1m: retrieved territory value from cache: %f, %f\n", pos,
*move_value, *followup_value);
return 1;
}
return 0;
}
void
store_delta_territory_cache(int pos, int color,
float move_value, float followup_value,
const struct influence_data *base,
Hash_data safety_hash)
{
int i;
ASSERT_ON_BOARD1(pos);
ASSERT1(IS_STONE(color), pos);
if (territory_cache_position_number != position_number
|| territory_cache_color != color
|| territory_cache_influence_id != base->id) {
int ii;
for (ii = BOARDMIN; ii < BOARDMAX; ii++)
delta_territory_cache[ii] = NOT_COMPUTED;
territory_cache_position_number = position_number;
territory_cache_influence_id = base->id;
territory_cache_color = color;
if (0)
gprintf("Cleared delta territory cache.\n");
}
delta_territory_cache[pos] = move_value;
followup_territory_cache[pos] = followup_value;
for (i = 0; i < NUM_HASHVALUES; i++)
delta_territory_cache_hash[pos].hashval[i] = safety_hash.hashval[i];
if (0)
gprintf("%1m: Stored delta territory cache: %f, %f\n", pos, move_value,
followup_value);
}
/* Compute the difference in territory between two influence data,
* from the point of view of (color).
* (move) is only passed for debugging output.
*/
float
influence_delta_territory(const struct influence_data *base,
const struct influence_data *q, int color,
int move)
{
int ii;
float total_delta = 0.0;
float this_delta;
ASSERT_ON_BOARD1(move);
ASSERT1(IS_STONE(color), move);
for (ii = BOARDMIN; ii < BOARDMAX; ii++)
if (ON_BOARD(ii)) {
float new_value = q->territory_value[ii];
float old_value = base->territory_value[ii];
this_delta = new_value - old_value;
/* Negate values if we are black. */
if (color == BLACK) {
new_value = -new_value;
old_value = -old_value;
this_delta = -this_delta;
}
if (move != -1
&& (this_delta > 0.02 || -this_delta > 0.02))
DEBUG(DEBUG_TERRITORY,
" %1m: - %1m territory change %f (%f -> %f)\n",
move, ii, this_delta, old_value, new_value);
total_delta += this_delta;
}
/* Finally, captured stones: */
this_delta = q->captured - base->captured;
if (color == BLACK)
this_delta = -this_delta;
if (move != -1
&& this_delta != 0.0)
DEBUG(DEBUG_TERRITORY, " %1m: - captured stones %f\n",
move, this_delta);
total_delta += this_delta;
return total_delta;
}
/* Estimate the score. A positive value means white is ahead. The
* score is estimated influence data *q, which must have been
* computed in advance.
*/
float
influence_score(const struct influence_data *q, int use_chinese_rules)
{
float score = 0.0;
int ii;
for (ii = BOARDMIN; ii < BOARDMAX; ii++)
if (ON_BOARD(ii))
score += q->territory_value[ii];
if (use_chinese_rules)
score += stones_on_board(WHITE) - stones_on_board(BLACK) + komi + handicap;
else
score += black_captured - white_captured + komi;
return score;
}
/* Uses initial_influence to estimate the game advancement (fuseki,
* chuban, yose) returned as a value between 0.0 (start) and 1.0 (game
* over)
*/
float
game_status(int color)
{
struct influence_data *iq = INITIAL_INFLUENCE(color);
struct influence_data *oq = OPPOSITE_INFLUENCE(color);
int count = 0;
int ii;
for (ii = BOARDMIN; ii < BOARDMAX; ii++)
if (ON_BOARD(ii)) {
if (iq->safe[ii])
count += WEIGHT_TERRITORY;
else if (whose_territory(iq, ii) != EMPTY
&& whose_territory(oq, ii) != EMPTY)
count += WEIGHT_TERRITORY;
else if (whose_moyo(oq, ii) != EMPTY)
count += WEIGHT_MOYO;
else if (whose_area(oq, ii) != EMPTY)
count += WEIGHT_AREA;
}
return (float) count / (WEIGHT_TERRITORY * board_size * board_size);
}
/* Print the influence map when we have computed influence for the
* move at (i, j).
*/
void
debug_influence_move(int move)
{
debug_influence = move;
}
/* One more way to export influence data. This should only be used
* for debugging.
*/
void
get_influence(const struct influence_data *q,
float white_influence[BOARDMAX],
float black_influence[BOARDMAX],
float white_strength[BOARDMAX],
float black_strength[BOARDMAX],
float white_attenuation[BOARDMAX],
float black_attenuation[BOARDMAX],
float white_permeability[BOARDMAX],
float black_permeability[BOARDMAX],
float territory_value[BOARDMAX],
int influence_regions[BOARDMAX],
int non_territory[BOARDMAX])
{
int ii;
for (ii = BOARDMIN; ii < BOARDMAX; ii++) {
white_influence[ii] = q->white_influence[ii];
black_influence[ii] = q->black_influence[ii];
white_strength[ii] = q->white_strength[ii];
black_strength[ii] = q->black_strength[ii];
white_attenuation[ii] = q->white_attenuation[ii];
black_attenuation[ii] = q->black_attenuation[ii];
white_permeability[ii] = q->white_permeability[ii];
black_permeability[ii] = q->black_permeability[ii];
territory_value[ii] = q->territory_value[ii];
non_territory[ii] = q->non_territory[ii];
if (board[ii] == EMPTY) {
if (whose_territory(q, ii) == WHITE)
influence_regions[ii] = 3;
else if (whose_territory(q, ii) == BLACK)
influence_regions[ii] = -3;
else if (whose_moyo(q, ii) == WHITE)
influence_regions[ii] = 2;
else if (whose_moyo(q, ii) == BLACK)
influence_regions[ii] = -2;
else if (whose_area(q, ii) == WHITE)
influence_regions[ii] = 1;
else if (whose_area(q, ii) == BLACK)
influence_regions[ii] = -1;
else
influence_regions[ii] = 0;
}
else if (board[ii] == WHITE)
influence_regions[ii] = 4;
else if (board[ii] == BLACK)
influence_regions[ii] = -4;
}
}
/* Print influence for debugging purposes, according to
* printmoyo bitmap (controlled by -m command line option).
*/
void
print_influence(const struct influence_data *q, const char *info_string)
{
if (printmoyo & PRINTMOYO_ATTENUATION) {
/* Print the attenuation values. */
fprintf(stderr, "white attenuation (%s):\n", info_string);
print_numeric_influence(q, q->white_attenuation, "%3.2f", 3, 0, 0);
fprintf(stderr, "black attenuation (%s):\n", info_string);
print_numeric_influence(q, q->black_attenuation, "%3.2f", 3, 0, 0);
}
if (printmoyo & PRINTMOYO_PERMEABILITY) {
/* Print the white permeability values. */
fprintf(stderr, "white permeability:\n");
print_numeric_influence(q, q->white_permeability, "%3.1f", 3, 0, 0);
/* Print the black permeability values. */
fprintf(stderr, "black permeability:\n");
print_numeric_influence(q, q->black_permeability, "%3.1f", 3, 0, 0);
}
if (printmoyo & PRINTMOYO_STRENGTH) {
/* Print the strength values. */
fprintf(stderr, "white strength:\n");
if (q->is_territorial_influence)
print_numeric_influence(q, q->white_strength, "%5.1f", 5, 0, 0);
else
print_numeric_influence(q, q->white_strength, "%3.0f", 3, 0, 1);
fprintf(stderr, "black strength:\n");
if (q->is_territorial_influence)
print_numeric_influence(q, q->black_strength, "%5.1f", 5, 0, 0);
else
print_numeric_influence(q, q->black_strength, "%3.0f", 3, 0, 1);
}
if (printmoyo & PRINTMOYO_NUMERIC_INFLUENCE) {
/* Print the white influence values. */
fprintf(stderr, "white influence (%s):\n", info_string);
print_numeric_influence(q, q->white_influence, "%5.1f", 5, 1, 0);
/* Print the black influence values. */
fprintf(stderr, "black influence (%s):\n", info_string);
print_numeric_influence(q, q->black_influence, "%5.1f", 5, 1, 0);
}
if (printmoyo & PRINTMOYO_PRINT_INFLUENCE) {
fprintf(stderr, "influence regions (%s):\n", info_string);
print_influence_areas(q);
}
if (printmoyo & PRINTMOYO_VALUE_TERRITORY) {
fprintf(stderr, "territory (%s)", info_string);
print_numeric_influence(q, q->territory_value, "%5.2f", 5, 1, 0);
}
}
/*
* Print numeric influence values.
*/
static void
print_numeric_influence(const struct influence_data *q,
const float values[BOARDMAX],
const char *format, int width,
int draw_stones, int mark_epsilon)
{
int i, j;
char ch;
char format_stone[20];
memset(format_stone, ' ', 20);
format_stone[(width + 1) / 2] = '%';
format_stone[(width + 3) / 2] = 'c';
format_stone[width + 2] = 0;
fprintf(stderr, " ");
for (i = 0, ch = 'A'; i < board_size; i++, ch++) {
if (ch == 'I')
ch++;
fprintf(stderr, format_stone, ch);
}
fprintf(stderr, "\n");
for (i = 0; i < board_size; i++) {
int ii = board_size - i;
fprintf(stderr, "%2d ", ii);
for (j = 0; j < board_size; j++) {
int ii = POS(i, j);
if (draw_stones && q->safe[ii]) {
if (board[ii] == WHITE)
fprintf(stderr, format_stone, 'O');
else
fprintf(stderr, format_stone, 'X');
}
else {
if (mark_epsilon && values[ii] > 0.0 && values[ii] < 1.0)
fprintf(stderr, "eps");
else
fprintf(stderr, format, values[ii]);
fprintf(stderr, " ");
}
}
fprintf(stderr, "%2d\n", ii);
}
fprintf(stderr, " ");
for (i = 0, ch = 'A'; i < board_size; i++, ch++) {
if (ch == 'I')
ch++;
fprintf(stderr, format_stone, ch);
}
fprintf(stderr, "\n");
}
/* Draw colored board illustrating territory, moyo, and area. */
static void
print_influence_areas(const struct influence_data *q)
{
int ii;
start_draw_board();
for (ii = BOARDMIN; ii < BOARDMAX; ii++)
if (ON_BOARD(ii)) {
int c = EMPTY;
int color = GG_COLOR_BLACK;
if (q->safe[ii]) {
color = GG_COLOR_BLACK;
if (board[ii] == WHITE)
c = 'O';
else
c = 'X';
}
else if (whose_territory(q, ii) == WHITE) {
c = 'o';
color = GG_COLOR_CYAN;
}
else if (whose_territory(q, ii) == BLACK) {
c = 'x';
color = GG_COLOR_CYAN;
}
else if (whose_moyo(q, ii) == WHITE) {
c = 'o';
color = GG_COLOR_YELLOW;
}
else if (whose_moyo(q, ii) == BLACK) {
c = 'x';
color = GG_COLOR_YELLOW;
}
else if (whose_area(q, ii) == WHITE) {
c = 'o';
color = GG_COLOR_RED;
}
else if (whose_area(q, ii) == BLACK) {
c = 'x';
color = GG_COLOR_RED;
}
draw_color_char(I(ii), J(ii), c, color);
}
end_draw_board();
}
/*
* Local Variables:
* tab-width: 8
* c-basic-offset: 2
* End:
*/