Initial commit of GNU Go v3.8.
[sgk-go] / engine / move_reasons.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 "random.h"
#include "move_reasons.h"
/* All these data structures are declared in move_reasons.h */
struct move_data move[BOARDMAX];
struct move_reason move_reasons[MAX_MOVE_REASONS];
int next_reason;
/* Connections */
int conn_worm1[MAX_CONNECTIONS];
int conn_worm2[MAX_CONNECTIONS];
int next_connection;
/* Potential semeai moves. */
int semeai_target1[MAX_POTENTIAL_SEMEAI];
int semeai_target2[MAX_POTENTIAL_SEMEAI];
static int next_semeai;
/* Unordered sets (currently pairs) of move reasons / targets */
Reason_set either_data[MAX_EITHER];
int next_either;
Reason_set all_data[MAX_ALL];
int next_all;
/* Eye shapes */
int eyes[MAX_EYES];
int eyecolor[MAX_EYES];
int next_eye;
/* Lunches */
int lunch_dragon[MAX_LUNCHES]; /* eater */
int lunch_worm[MAX_LUNCHES]; /* food */
int next_lunch;
/* Point redistribution */
int replacement_map[BOARDMAX];
/* The color for which we are evaluating moves. */
int current_color;
/* Attack threats that are known to be sente locally. */
static int known_good_attack_threats[BOARDMAX][MAX_ATTACK_THREATS];
/* Moves that are known to be safe (in the sense that played stones can
* be captured, but opponent loses much more when attempting to do so)
*/
static int known_safe_moves[BOARDMAX];
/* Helper functions to check conditions in discard rules. */
typedef int (*discard_condition_fn_ptr)(int pos, int what);
struct discard_rule {
int reason_type[MAX_REASONS];
discard_condition_fn_ptr condition;
int flags;
char trace_message[MAX_TRACE_LENGTH];
};
/* Initialize move reason data structures. */
void
clear_move_reasons(void)
{
int pos;
int k;
next_reason = 0;
next_connection = 0;
next_semeai = 0;
next_either = 0;
next_all = 0;
next_eye = 0;
next_lunch = 0;
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (ON_BOARD(pos)) {
move[pos].value = 0.0;
move[pos].final_value = 0.0;
move[pos].additional_ko_value = 0.0;
move[pos].territorial_value = 0.0;
move[pos].strategical_value = 0.0;
move[pos].maxpos_shape = 0.0;
move[pos].numpos_shape = 0;
move[pos].maxneg_shape = 0.0;
move[pos].numneg_shape = 0;
move[pos].followup_value = 0.0;
move[pos].influence_followup_value = 0.0;
move[pos].reverse_followup_value = 0.0;
move[pos].secondary_value = 0.0;
move[pos].min_value = 0.0;
move[pos].max_value = HUGE_MOVE_VALUE;
move[pos].min_territory = 0.0;
move[pos].max_territory = HUGE_MOVE_VALUE;
for (k = 0; k < MAX_REASONS; k++)
move[pos].reason[k] = -1;
move[pos].move_safety = 0;
move[pos].worthwhile_threat = 0;
move[pos].randomness_scaling = 1.0;
/* The reason we assign a random number to each move immediately
* is to avoid dependence on which moves are evaluated when it
* comes to choosing between multiple moves of the same value.
* In this way we can get consistent results for use in the
* regression tests.
*/
move[pos].random_number = gg_drand();
/* Do not send away the points (yet). */
replacement_map[pos] = NO_MOVE;
}
}
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
known_safe_moves[pos] = 0;
for (k = 0; k < MAX_ATTACK_THREATS; k++)
known_good_attack_threats[pos][k] = NO_MOVE;
}
}
/*
* Find the index of a connection in the list of connections.
* If necessary, add a new entry.
*/
int
find_connection(int worm1, int worm2)
{
int k;
if (worm1 > worm2) {
/* Swap to canonical order. */
int tmp = worm1;
worm1 = worm2;
worm2 = tmp;
}
for (k = 0; k < next_connection; k++)
if (conn_worm1[k] == worm1 && conn_worm2[k] == worm2)
return k;
/* Add a new entry. */
gg_assert(next_connection < MAX_CONNECTIONS);
conn_worm1[next_connection] = worm1;
conn_worm2[next_connection] = worm2;
next_connection++;
return next_connection - 1;
}
static int
find_either_data(int reason1, int what1, int reason2, int what2)
{
int k;
/* Make sure the worms are ordered canonically. */
if (what1 > what2) {
int tmp = what1;
what1 = what2;
what2 = tmp;
}
for (k = 0; k < next_either; k++)
if (either_data[k].reason1 == reason1
&& either_data[k].what1 == what1
&& either_data[k].reason2 == reason2
&& either_data[k].what2 == what2)
return k;
/* Add a new entry. */
gg_assert(next_either < MAX_EITHER);
either_data[next_either].reason1 = reason1;
either_data[next_either].what1 = what1;
either_data[next_either].reason2 = reason2;
either_data[next_either].what2 = what2;
next_either++;
return next_either - 1;
}
static int
find_all_data(int reason1, int what1, int reason2, int what2)
{
int k;
/* Make sure the worms are ordered canonically. */
if (what1 > what2) {
int tmp = what1;
what1 = what2;
what2 = tmp;
}
for (k = 0; k < next_all; k++)
if (all_data[k].reason1 == reason1
&& all_data[k].what1 == what1
&& all_data[k].reason2 == reason2
&& all_data[k].what2 == what2)
return k;
/* Add a new entry. */
gg_assert(next_all < MAX_ALL);
all_data[next_all].reason1 = reason1;
all_data[next_all].what1 = what1;
all_data[next_all].reason2 = reason2;
all_data[next_all].what2 = what2;
next_all++;
return next_all - 1;
}
static int
find_pair_data(int what1, int what2)
{
int k;
for (k = 0; k < next_either; k++)
if (either_data[k].what1 == what1
&& either_data[k].what2 == what2)
return k;
/* Add a new entry. */
gg_assert(next_either < MAX_EITHER);
either_data[next_either].what1 = what1;
either_data[next_either].what2 = what2;
next_either++;
return next_either - 1;
}
/* Interprets the object of a reason and returns its position.
* If the object is a pair (of worms or dragons), the position of the first
* object is returned. (This is only used for trace outputs.) Returns
* NO_MOVE if move does not point to a location.
* FIXME: This new function produces some code duplication with other
* trace output function. Do some code cleanup here.
*/
static int
get_pos(int reason, int what)
{
switch (reason) {
case ATTACK_MOVE:
case DEFEND_MOVE:
case ATTACK_THREAT:
case DEFEND_THREAT:
case ATTACK_MOVE_GOOD_KO:
case ATTACK_MOVE_BAD_KO:
case DEFEND_MOVE_GOOD_KO:
case DEFEND_MOVE_BAD_KO:
return what;
case SEMEAI_MOVE:
case SEMEAI_THREAT:
case STRATEGIC_ATTACK_MOVE:
case STRATEGIC_DEFEND_MOVE:
case OWL_ATTACK_MOVE:
case OWL_DEFEND_MOVE:
case OWL_ATTACK_THREAT:
case OWL_DEFEND_THREAT:
case OWL_PREVENT_THREAT:
case UNCERTAIN_OWL_ATTACK:
case UNCERTAIN_OWL_DEFENSE:
case OWL_ATTACK_MOVE_GOOD_KO:
case OWL_ATTACK_MOVE_BAD_KO:
case OWL_DEFEND_MOVE_GOOD_KO:
case OWL_DEFEND_MOVE_BAD_KO:
return what;
case EITHER_MOVE:
/* FIXME: What should we return here? */
return either_data[what].what1;
case ALL_MOVE:
/* FIXME: What should we return here? */
return all_data[what].what1;
case CONNECT_MOVE:
case CUT_MOVE:
return conn_worm1[what];
case ANTISUJI_MOVE:
case EXPAND_TERRITORY_MOVE:
case EXPAND_MOYO_MOVE:
case INVASION_MOVE:
case MY_ATARI_ATARI_MOVE:
case YOUR_ATARI_ATARI_MOVE:
return NO_MOVE;
case OWL_ATTACK_MOVE_GAIN:
case OWL_DEFEND_MOVE_LOSS:
/* FIXME: What should we return here? */
return either_data[what].what1;
default:
/* We should never get here: */
gg_assert(0);
return 0; /* To keep gcc happy. */
}
}
/*
* See if a lunch is already in the list of lunches, otherwise add a new
* entry. A lunch is in this context a pair of eater (a dragon) and food
* (a worm).
*/
void
add_lunch(int eater, int food)
{
int k;
int dragon1 = dragon[eater].origin;
int worm1 = worm[food].origin;
ASSERT_ON_BOARD1(eater);
ASSERT_ON_BOARD1(food);
for (k = 0; k < next_lunch; k++)
if ((lunch_dragon[k] == dragon1) && (lunch_worm[k] == worm1))
return;
/* Add a new entry. */
gg_assert(next_lunch < MAX_LUNCHES);
lunch_dragon[next_lunch] = dragon1;
lunch_worm[next_lunch] = worm1;
next_lunch++;
return;
}
/* ---------------------------------------------------------------- */
/*
* Add a move reason for (pos) if it's not already there or the
* table is full.
*/
static void
add_move_reason(int pos, int type, int what)
{
int k;
ASSERT_ON_BOARD1(pos);
if (stackp == 0) {
ASSERT1(board[pos] == EMPTY, pos);
}
for (k = 0; k < MAX_REASONS; k++) {
int r = move[pos].reason[k];
if (r < 0)
break;
if (move_reasons[r].type == type
&& move_reasons[r].what == what)
return; /* Reason already listed. */
}
/* Reason not found, add it if there is place left in both lists.
* Otherwise drop it.
*/
if (k >= MAX_REASONS) {
DEBUG(DEBUG_MOVE_REASONS,
"Move reason at %1m (type=%d, what=%d) dropped because list full.\n",
pos, type, what);
return;
}
if (next_reason >= MAX_MOVE_REASONS) {
DEBUG(DEBUG_MOVE_REASONS,
"Move reason at %1m (type=%d, what=%d) dropped because global list full.\n",
pos, type, what);
return;
}
/* Add a new entry. */
move[pos].reason[k] = next_reason;
move_reasons[next_reason].type = type;
move_reasons[next_reason].what = what;
move_reasons[next_reason].status = ACTIVE;
next_reason++;
}
/*
* Remove a move reason for (pos). Ignore silently if the reason
* wasn't there.
*/
static void
remove_move_reason(int pos, int type, int what)
{
int k;
int n = -1; /* Position of the move reason to be deleted. */
ASSERT_ON_BOARD1(pos);
for (k = 0; k < MAX_REASONS; k++) {
int r = move[pos].reason[k];
if (r < 0)
break;
if (move_reasons[r].type == type
&& move_reasons[r].what == what)
n = k;
}
if (n == -1)
return; /* Move reason wasn't there. */
/* Now move the last move reason to position n, thereby removing the
* one we were looking for.
*/
k--;
move[pos].reason[n] = move[pos].reason[k];
move[pos].reason[k] = -1;
}
/*
* Check whether a move reason already is recorded for a move.
* A negative value for 'what' means only match 'type'.
*/
int
move_reason_known(int pos, int type, int what)
{
int k;
int r;
ASSERT_ON_BOARD1(pos);
for (k = 0; k < MAX_REASONS; k++) {
r = move[pos].reason[k];
if (r < 0)
break;
if (move_reasons[r].type == type
&& (what < 0
|| move_reasons[r].what == what))
return 1;
}
return 0;
}
/* ---------------------------------------------------------------- */
/* Functions used in discard_rules follow below. */
/*
* Check whether an attack move reason already is recorded for a move.
* A negative value for 'what' means only match 'type'.
*/
int
attack_move_reason_known(int pos, int what)
{
ASSERT1(what < 0 || IS_STONE(board[what]), what);
what = worm[what].origin;
if (move_reason_known(pos, ATTACK_MOVE, what))
return WIN;
if (move_reason_known(pos, ATTACK_MOVE_GOOD_KO, what))
return KO_A;
if (move_reason_known(pos, ATTACK_MOVE_BAD_KO, what))
return KO_B;
return 0;
}
/*
* Check whether a defense move reason already is recorded for a move.
* A negative value for 'what' means only match 'type'.
*/
int
defense_move_reason_known(int pos, int what)
{
ASSERT1(what < 0 || IS_STONE(board[what]), what);
what = worm[what].origin;
if (move_reason_known(pos, DEFEND_MOVE, what))
return WIN;
if (move_reason_known(pos, DEFEND_MOVE_GOOD_KO, what))
return KO_A;
if (move_reason_known(pos, DEFEND_MOVE_BAD_KO, what))
return KO_B;
return 0;
}
/* Check whether a dragon consists of only one worm. If so, check
* whether we know of a tactical attack or defense move.
*/
static int
tactical_move_vs_whole_dragon_known(int pos, int what)
{
return ((worm[what].size == dragon[what].size)
&& (attack_move_reason_known(pos, what)
|| defense_move_reason_known(pos, what)));
}
/*
* Check whether an owl attack move reason already is recorded for a move.
* A negative value for 'what' means only match 'type'.
*/
int
owl_attack_move_reason_known(int pos, int what)
{
if (move_reason_known(pos, OWL_ATTACK_MOVE, what))
return WIN;
if (move_reason_known(pos, OWL_ATTACK_MOVE_GOOD_KO, what))
return KO_A;
if (move_reason_known(pos, OWL_ATTACK_MOVE_BAD_KO, what))
return KO_B;
return 0;
}
/*
* Check whether an owl defense move reason already is recorded for a move.
* A negative value for 'what' means only match 'type'.
*/
int
owl_defense_move_reason_known(int pos, int what)
{
if (move_reason_known(pos, OWL_DEFEND_MOVE, what))
return WIN;
if (move_reason_known(pos, OWL_DEFEND_MOVE_GOOD_KO, what))
return KO_A;
if (move_reason_known(pos, OWL_DEFEND_MOVE_BAD_KO, what))
return KO_B;
return 0;
}
/*
* Check whether an owl attack/defense move reason is recorded for a move.
* A negative value for 'what' means only match 'type'.
*/
int
owl_move_reason_known(int pos, int what)
{
return (owl_attack_move_reason_known(pos, what)
|| owl_defense_move_reason_known(pos, what));
}
/*
* Check whether we have an owl attack/defense reason for a move that
* involves a specific worm.
*/
static int
owl_move_vs_worm_known(int pos, int what)
{
return owl_move_reason_known(pos, dragon[what].origin);
}
int
semeai_move_reason_known(int pos, int what)
{
return move_reason_known(pos, SEMEAI_MOVE, what);
}
/* Check whether a worm is inessential */
static int
concerns_inessential_worm(int pos, int what)
{
UNUSED(pos);
return DRAGON2(what).safety == INESSENTIAL
|| worm[what].inessential;
}
/* Check whether a dragon is inessential */
static int
concerns_inessential_dragon(int pos, int what)
{
UNUSED(pos);
return DRAGON2(what).safety == INESSENTIAL;
}
static int
move_is_marked_unsafe(int pos, int what)
{
UNUSED(what);
return (!move[pos].move_safety
&& !adjacent_to_nondead_stone(pos, current_color));
}
/* Check whether a dragon is non-critical. */
static int
concerns_noncritical_dragon(int pos, int what)
{
UNUSED(pos);
return (dragon[what].status != CRITICAL
&& worm[what].attack_codes[0] == 0);
}
/* (what) points to two worms listed in either_data. Returns true if
* this is a "attack either" move reason, and one of the worms attackable.
* FIXME: Ko?
*/
static int
either_worm_attackable(int pos, int what)
{
UNUSED(pos);
return (either_data[what].reason1 == ATTACK_STRING
&& either_data[what].reason2 == ATTACK_STRING
&& (worm[either_data[what].what1].attack_codes[0] != 0
|| worm[either_data[what].what2].attack_codes[0] != 0));
}
/* (what) points to two worms via all_data. Returns true if this is
* a "defend both" move reason, and one of the worms is attackable.
* FIXME: Ko?
*/
static int
one_of_both_attackable(int pos, int what)
{
UNUSED(pos);
return (all_data[what].reason1 == DEFEND_STRING
&& all_data[what].reason2 == DEFEND_STRING
&& (worm[all_data[what].what1].attack_codes[0] != 0
|| worm[all_data[what].what2].attack_codes[0] != 0));
}
/* ---------------------------------------------------------------- */
/*
* Add to the reasons for the move at (pos) that it attacks the worm
* at (ww).
*/
void
add_attack_move(int pos, int ww, int code)
{
ASSERT_ON_BOARD1(ww);
ww = worm[ww].origin;
if (code == WIN)
add_move_reason(pos, ATTACK_MOVE, ww);
else if (code == KO_A)
add_move_reason(pos, ATTACK_MOVE_GOOD_KO, ww);
else if (code == KO_B)
add_move_reason(pos, ATTACK_MOVE_BAD_KO, ww);
}
/*
* Add to the reasons for the move at (pos) that it defends the worm
* at (ww).
*/
void
add_defense_move(int pos, int ww, int code)
{
ASSERT_ON_BOARD1(ww);
ww = worm[ww].origin;
if (code == WIN)
add_move_reason(pos, DEFEND_MOVE, ww);
else if (code == KO_A)
add_move_reason(pos, DEFEND_MOVE_GOOD_KO, ww);
else if (code == KO_B)
add_move_reason(pos, DEFEND_MOVE_BAD_KO, ww);
}
/*
* Add to the reasons for the move at (pos) that it threatens to
* attack the worm at (ww).
*/
void
add_attack_threat_move(int pos, int ww, int code)
{
UNUSED(code);
ASSERT_ON_BOARD1(ww);
add_move_reason(pos, ATTACK_THREAT, worm[ww].origin);
}
/* Remove an attack threat move reason. */
void
remove_attack_threat_move(int pos, int ww)
{
ASSERT_ON_BOARD1(ww);
remove_move_reason(pos, ATTACK_THREAT, worm[ww].origin);
}
/*
* Add to the reasons for the move at (pos) that it defends the worm
* at (ww).
*/
void
add_defense_threat_move(int pos, int ww, int code)
{
UNUSED(code);
ASSERT_ON_BOARD1(ww);
add_move_reason(pos, DEFEND_THREAT, worm[ww].origin);
}
/* Report all, or up to max_strings, strings that are threatened
* at (pos).
*/
int
get_attack_threats(int pos, int max_strings, int strings[])
{
int k;
int num_strings;
num_strings = 0;
for (k = 0; k < MAX_REASONS; k++) {
int r = move[pos].reason[k];
if (r < 0)
break;
if (move_reasons[r].type == ATTACK_THREAT)
strings[num_strings++] = move_reasons[r].what;
if (num_strings == max_strings)
break;
}
return num_strings;
}
/* Report all, or up to max_strings, strings that might be defended
* at (pos).
*/
int
get_defense_threats(int pos, int max_strings, int strings[])
{
int k;
int num_strings;
num_strings = 0;
for (k = 0; k < MAX_REASONS; k++) {
int r = move[pos].reason[k];
if (r < 0)
break;
if (move_reasons[r].type == DEFEND_THREAT)
strings[num_strings++] = move_reasons[r].what;
if (num_strings == max_strings)
break;
}
return num_strings;
}
/* Report the biggest dragon that is owl-affected (possibily with ko)
* by a move at (pos).
*/
int
get_biggest_owl_target(int pos)
{
int k;
int biggest_target = -1;
float target_size = 0.0;
for (k = 0; k < MAX_REASONS; k++) {
int r = move[pos].reason[k];
if (r < 0)
break;
switch (move_reasons[r].type) {
case OWL_ATTACK_MOVE:
case OWL_ATTACK_MOVE_GOOD_KO:
case OWL_ATTACK_MOVE_BAD_KO:
case OWL_ATTACK_THREAT:
case OWL_DEFEND_MOVE:
case OWL_DEFEND_MOVE_GOOD_KO:
case OWL_DEFEND_MOVE_BAD_KO:
case OWL_DEFEND_THREAT:
case OWL_PREVENT_THREAT:
if (dragon[move_reasons[r].what].effective_size > target_size) {
biggest_target = move_reasons[r].what;
target_size = dragon[move_reasons[r].what].effective_size;
}
break;
}
}
return biggest_target;
}
/*
* Add to the reasons for the move at (pos) that it connects the
* dragons at (dr1) and (dr2). Require that the dragons are
* distinct.
*/
void
add_connection_move(int pos, int w1, int w2)
{
int connection;
ASSERT_ON_BOARD1(w1);
ASSERT_ON_BOARD1(w2);
ASSERT1(worm[w1].color == worm[w2].color, w1);
if (worm[w1].origin == worm[w2].origin)
return;
connection = find_connection(worm[w1].origin, worm[w2].origin);
add_move_reason(pos, CONNECT_MOVE, connection);
}
/*
* Add to the reasons for the move at (pos) that it cuts the
* dragons at (dr1) and (dr2). Require that the dragons are
* distinct.
*/
void
add_cut_move(int pos, int w1, int w2)
{
int connection;
ASSERT_ON_BOARD1(w1);
ASSERT_ON_BOARD1(w2);
ASSERT1(worm[w1].color == worm[w2].color, w1);
if (worm[w1].origin == worm[w2].origin)
return;
connection = find_connection(worm[w1].origin, worm[w2].origin);
/*
* Ignore the cut or connection if either (w1) or (w2)
* points to a tactically captured worm.
*/
if ((worm[w1].attack_codes[0] != 0 && worm[w1].defense_codes[0] == 0)
|| (worm[w2].attack_codes[0] != 0 && worm[w2].defense_codes[0] == 0))
return;
add_move_reason(pos, CUT_MOVE, connection);
}
/*
* Add to the reasons for the move at (pos) that it is an anti-suji.
* This means that it's a locally inferior move or for some other reason
* must *not* be played.
*/
void
add_antisuji_move(int pos)
{
add_move_reason(pos, ANTISUJI_MOVE, 0);
}
/*
* Add to the reasons for the move at (pos) that it wins the
* dragon (friendly or not) at (dr) in semeai. Since it is
* possible that in some semeai one player can kill but the
* other can only make seki, it is possible that one dragon
* is already alive in seki. Therefore separate move reasons
* must be added for the two dragons.
*/
void
add_semeai_move(int pos, int dr)
{
ASSERT_ON_BOARD1(dr);
add_move_reason(pos, SEMEAI_MOVE, dragon[dr].origin);
}
/*
* Add to the reasons for the move at (pos) that it might
* kill/save the dragon at (dr1) in the semeai against (dr2).
*/
static void
add_potential_semeai_move(int pos, int type, int dr1, int dr2)
{
ASSERT1(ON_BOARD(dr1), pos);
ASSERT1(ON_BOARD(dr2), pos);
if (next_semeai >= MAX_POTENTIAL_SEMEAI)
DEBUG(DEBUG_MOVE_REASONS,
"Potential semeai move at %1m dropped as list was full\n", pos);
else {
semeai_target1[next_semeai] = dr1;
semeai_target2[next_semeai] = dr2;
add_move_reason(pos, type, next_semeai);
next_semeai++;
}
}
/*
* Add to the reasons for the move at (pos) that it might
* kill the dragon at (dr1) in the semeai against (dr2).
*/
void
add_potential_semeai_attack(int pos, int dr1, int dr2)
{
add_potential_semeai_move(pos, POTENTIAL_SEMEAI_ATTACK, dr1, dr2);
}
/*
* Add to the reasons for the move at (pos) that it might
* save the dragon at (dr1) in the semeai against (dr2).
*/
void
add_potential_semeai_defense(int pos, int dr1, int dr2)
{
add_potential_semeai_move(pos, POTENTIAL_SEMEAI_DEFENSE, dr1, dr2);
}
/*
* Add to the reasons for the move at (pos) that given two
* moves in a row a move here can win the dragon (friendly or
* not) at (dr) in semeai. Such a move can be used as a
* ko threat, and it is also given some value due to uncertainty
* in the counting of liberties.
*/
void
add_semeai_threat(int pos, int dr)
{
ASSERT_ON_BOARD1(dr);
add_move_reason(pos, SEMEAI_THREAT, dragon[dr].origin);
}
/*
* Add to the reasons for the move at (pos) that it will accomplish
* one of two things: either (reason1) on (target1) or (reason2) on
* (target2).
*
* At this time, (reason) can only be ATTACK_STRING.
* However, more reasons will be implemented in the future.
*
* FIXME: Implement at least ATTACK_MOVE_GOOD_KO, ATTACK_MOVE_BAD_KO,
* DEFEND_MOVE and associates, CONNECT_MOVE, OWL_ATTACK_MOVE,
* OWL_DEFEND_MOVE, and possibly more.
*
* FIXME: Generalize to more than 2 parameters.
* When that is done, this will be a good way to add
* atari_atari moves.
*/
void
add_either_move(int pos, int reason1, int target1, int reason2, int target2)
{
int what1 = 0;
int what2 = 0;
int index;
ASSERT_ON_BOARD1(target1);
ASSERT_ON_BOARD1(target2);
if (reason1 == reason2 && target1 == target2)
return;
/* For now. */
gg_assert(reason1 == ATTACK_STRING);
gg_assert(reason2 == ATTACK_STRING);
switch (reason1) {
case ATTACK_STRING:
{
what1 = worm[target1].origin;
/* If this string is already attacked, and with no defense, then
* there is no additional value of this move reason. */
if (worm[target1].attack_codes[0] != 0
&& worm[target1].defense_codes[0] == 0)
return;
}
break;
default:
break;
}
switch (reason2) {
case ATTACK_STRING:
{
what2 = worm[target2].origin;
/* If this string is already attacked, and with no defense, then
* there is no additional value of this move reason. */
if (worm[target2].attack_codes[0] != 0
&& worm[target2].defense_codes[0] == 0)
return;
}
break;
default:
break;
}
index = find_either_data(reason1, what1, reason2, what2);
add_move_reason(pos, EITHER_MOVE, index);
}
/*
* Add to the reasons for the move at (pos) that it will accomplish
* both of two things: (reason1) on (target1) and (reason2) on
* (target2).
*
* At this time, (reason) can only be DEFEND_STRING.
* However, more reasons will be implemented in the future.
*
* FIXME: Implement at least ATTACK_MOVE_GOOD_KO, ATTACK_MOVE_BAD_KO,
* DEFEND_MOVE and associates, CONNECT_MOVE, OWL_ATTACK_MOVE,
* OWL_DEFEND_MOVE, and possibly more.
*
* FIXME: Generalize to more than 2 parameters.
* When that is done, this will be a good way to add
* atari_atari moves.
*/
void
add_all_move(int pos, int reason1, int target1, int reason2, int target2)
{
int what1 = 0;
int what2 = 0;
int index;
ASSERT_ON_BOARD1(target1);
ASSERT_ON_BOARD1(target2);
if (reason1 == reason2 && target1 == target2)
return;
/* For now. */
gg_assert(reason1 == DEFEND_STRING);
gg_assert(reason2 == DEFEND_STRING);
switch (reason1) {
case DEFEND_STRING:
what1 = worm[target1].origin;
break;
default:
break;
}
switch (reason2) {
case DEFEND_STRING:
what2 = worm[target2].origin;
break;
default:
break;
}
index = find_all_data(reason1, what1, reason2, what2);
add_move_reason(pos, ALL_MOVE, index);
}
void
add_loss_move(int pos, int target1, int target2)
{
int what1 = dragon[target1].origin;
int what2 = worm[target2].origin;
int index = find_pair_data(what1, what2);
ASSERT1(target2 != NO_MOVE, pos);
add_move_reason(pos, OWL_DEFEND_MOVE_LOSS, index);
}
/*
* Add to the reasons for the move at (pos) that it expands
* territory.
*/
void
add_expand_territory_move(int pos)
{
add_move_reason(pos, EXPAND_TERRITORY_MOVE, 0);
}
/*
* Add to the reasons for the move at (pos) that it expands
* moyo.
*/
void
add_expand_moyo_move(int pos)
{
add_move_reason(pos, EXPAND_MOYO_MOVE, 0);
}
/*
* Add to the reasons for the move at (pos) that it is an invasion.
*/
void
add_invasion_move(int pos)
{
add_move_reason(pos, INVASION_MOVE, 0);
}
/*
* This function is called when a shape value for the move at (pos)
* is found.
*
* We keep track of the largest positive shape value found, and the
* total number of positive contributions, as well as the largest
* negative shape value found, and the total number of negative
* shape contributions.
*/
void
add_shape_value(int pos, float value)
{
ASSERT_ON_BOARD1(pos);
if (value > 0.0) {
if (value > move[pos].maxpos_shape)
move[pos].maxpos_shape = value;
move[pos].numpos_shape += 1;
}
else if (value < 0.0) {
value = -value;
if (value > move[pos].maxneg_shape)
move[pos].maxneg_shape = value;
move[pos].numneg_shape += 1;
}
}
/*
* Flag that this move is worthwhile to play as a pure threat move.
*/
void
add_worthwhile_threat_move(int pos)
{
move[pos].worthwhile_threat = 1;
}
/*
* Add to the reasons for the move at (pos) that it attacks
* the dragon (dr) on a strategical level.
*/
void
add_strategical_attack_move(int pos, int dr)
{
dr = dragon[dr].origin;
ASSERT_ON_BOARD1(dr);
add_move_reason(pos, STRATEGIC_ATTACK_MOVE, dr);
}
/*
* Add to the reasons for the move at (pos) that it defends
* the dragon (dr) on a strategical level.
*/
void
add_strategical_defense_move(int pos, int dr)
{
dr = dragon[dr].origin;
ASSERT_ON_BOARD1(dr);
add_move_reason(pos, STRATEGIC_DEFEND_MOVE, dr);
}
/*
* Add to the reasons for the move at (pos) that the owl
* code reports an attack on the dragon (dr).
*/
void
add_owl_attack_move(int pos, int dr, int kworm, int code)
{
dr = dragon[dr].origin;
ASSERT_ON_BOARD1(dr);
if (code == WIN)
add_move_reason(pos, OWL_ATTACK_MOVE, dr);
else if (code == KO_A)
add_move_reason(pos, OWL_ATTACK_MOVE_GOOD_KO, dr);
else if (code == KO_B)
add_move_reason(pos, OWL_ATTACK_MOVE_BAD_KO, dr);
else if (code == GAIN) {
ASSERT_ON_BOARD1(kworm);
add_move_reason(pos, OWL_ATTACK_MOVE_GAIN, find_pair_data(dr, kworm));
}
}
/*
* Add to the reasons for the move at (pos) that the owl
* code reports a defense of the dragon (dr).
*/
void
add_owl_defense_move(int pos, int dr, int code)
{
dr = dragon[dr].origin;
ASSERT_ON_BOARD1(dr);
if (code == WIN)
add_move_reason(pos, OWL_DEFEND_MOVE, dr);
else if (code == KO_A)
add_move_reason(pos, OWL_DEFEND_MOVE_GOOD_KO, dr);
else if (code == KO_B)
add_move_reason(pos, OWL_DEFEND_MOVE_BAD_KO, dr);
}
/*
* Add to the reasons for the move at (pos) that the owl
* code reports a move threatening to attack the dragon enemy (dr).
* That is, if the attacker is given two moves in a row, (pos)
* can be the first move.
*/
void
add_owl_attack_threat_move(int pos, int dr, int code)
{
UNUSED(code);
dr = dragon[dr].origin;
ASSERT_ON_BOARD1(dr);
add_move_reason(pos, OWL_ATTACK_THREAT, dragon[dr].origin);
add_worthwhile_threat_move(pos);
}
/* The owl code found the friendly dragon alive, or the unfriendly dragon
* dead, and an extra point of attack or defense was found, so this might be a
* good place to play.
*/
void
add_owl_uncertain_defense_move(int pos, int dr)
{
dr = dragon[dr].origin;
ASSERT_ON_BOARD1(dr);
add_move_reason(pos, UNCERTAIN_OWL_DEFENSE, dragon[dr].origin);
}
/* The owl code found the opponent dragon alive, or the friendly
* dragon dead, but was uncertain, and this move reason propose
* an attack or defense which is expected to fail but might succeed.
*/
void
add_owl_uncertain_attack_move(int pos, int dr)
{
dr = dragon[dr].origin;
ASSERT_ON_BOARD1(dr);
add_move_reason(pos, UNCERTAIN_OWL_ATTACK, dragon[dr].origin);
}
/*
* Add to the reasons for the move at (pos) that the owl
* code reports a move threatening to rescue the dragon (dr).
* That is, if the defender is given two moves in a row, (pos)
* can be the first move.
*/
void
add_owl_defense_threat_move(int pos, int dr, int code)
{
UNUSED(code);
dr = dragon[dr].origin;
ASSERT_ON_BOARD1(dr);
add_move_reason(pos, OWL_DEFEND_THREAT, dragon[dr].origin);
add_worthwhile_threat_move(pos);
}
/* Add to the reasons for the move at (pos) that it captures
* at least one of a set of worms which individually are tactically
* safe (such as a double atari). Only one such move reason is
* permitted per move.
*/
void
add_my_atari_atari_move(int pos, int size)
{
add_move_reason(pos, MY_ATARI_ATARI_MOVE, size);
}
/* Add to the reasons for the move at (pos) that it stops a
* combination attack for the opponent.
*/
void
add_your_atari_atari_move(int pos, int size)
{
add_move_reason(pos, YOUR_ATARI_ATARI_MOVE, size);
}
/*
* Add to the reasons for the move at (pos) that the owl
* code reports a move threatening to defend the dragon enemy (dr),
* and that (pos) is a move which attacks the dragon.
* That is, if the defender is given two moves in a row, (pos)
* can be the first move. Hopefully playing at (pos) makes it harder
* for the dragon to live.
*/
void
add_owl_prevent_threat_move(int pos, int dr)
{
ASSERT_ON_BOARD1(dr);
add_move_reason(pos, OWL_PREVENT_THREAT, dragon[dr].origin);
}
/*
* Add value of followup moves.
*/
void
add_followup_value(int pos, float value)
{
ASSERT_ON_BOARD1(pos);
if (value > move[pos].followup_value)
move[pos].followup_value = value;
}
/*
* Add value of reverse followup moves.
*/
void
add_reverse_followup_value(int pos, float value)
{
ASSERT_ON_BOARD1(pos);
if (value > move[pos].reverse_followup_value)
move[pos].reverse_followup_value = value;
}
/*
* Set a minimum allowed value for the move.
*/
int
set_minimum_move_value(int pos, float value)
{
ASSERT_ON_BOARD1(pos);
if (value > move[pos].min_value) {
move[pos].min_value = value;
return 1;
}
return 0;
}
/*
* Set a maximum allowed value for the move.
*/
void
set_maximum_move_value(int pos, float value)
{
ASSERT_ON_BOARD1(pos);
if (value < move[pos].max_value)
move[pos].max_value = value;
}
/*
* Set a minimum allowed territorial value for the move.
*/
void
set_minimum_territorial_value(int pos, float value)
{
ASSERT_ON_BOARD1(pos);
if (value > move[pos].min_territory)
move[pos].min_territory = value;
}
/*
* Set a maximum allowed territorial value for the move.
*/
void
set_maximum_territorial_value(int pos, float value)
{
ASSERT_ON_BOARD1(pos);
if (value < move[pos].max_territory)
move[pos].max_territory = value;
}
/*
* Add a point redistribution rule, sending the points from (from)
* to (to).
*/
void
add_replacement_move(int from, int to, int color)
{
int cc;
int pos;
int dummy;
ASSERT_ON_BOARD1(from);
ASSERT_ON_BOARD1(to);
if (board[from] != EMPTY)
return;
ASSERT1(board[to] == EMPTY, to);
cc = replacement_map[to];
if (unconditionally_meaningless_move(to, color, &dummy)) {
/* Silently ignore replacement patterns which conflict with the
* unconditional analysis since the latter is always correct and
* it's difficult to anticipate such situations for the patterns.
*/
return;
}
/* First check for an incompatible redistribution rule. */
if (replacement_map[from] != NO_MOVE) {
int dd = replacement_map[from];
/* Abort if the old rule isn't compatible with the new one.
* (But not in the stable release.)
*/
if (0) {
ASSERT1(dd == to || to == replacement_map[dd], from);
}
/* There already is a redistribution in effect so we
* have nothing more to do.
*/
return;
}
TRACE("Move at %1m is replaced by %1m.\n", from, to);
/* Verify that we don't introduce a cyclic redistribution. */
if (cc == from) {
gprintf("Cyclic point redistribution detected.\n");
ASSERT1(0, from);
}
/* Update the replacement map. Make sure that all replacements
* always are directed immediately to the final destination.
*/
if (cc != NO_MOVE)
replacement_map[from] = cc;
else
replacement_map[from] = to;
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (ON_BOARD(pos) && replacement_map[pos] == from)
replacement_map[pos] = replacement_map[from];
}
}
/* Find worms rescued by a move at (pos). */
void
get_saved_worms(int pos, signed char saved[BOARDMAX])
{
int k;
memset(saved, 0, sizeof(saved[0]) * BOARDMAX);
for (k = 0; k < MAX_REASONS; k++) {
int r = move[pos].reason[k];
int what;
if (r < 0)
break;
what = move_reasons[r].what;
/* We exclude the ko contingent defenses, to avoid that the
* confirm_safety routines spot an attack with ko and thinks the
* move is unsafe.
*/
if (move_reasons[r].type == DEFEND_MOVE)
mark_string(worm[what].origin, saved, 1);
else if (move_reasons[r].type == OWL_DEFEND_MOVE_LOSS) {
int origin = dragon[what].origin;
int kworm = worm[what].origin;
int ii;
for (ii = BOARDMIN; ii < BOARDMAX; ii++)
if (IS_STONE(board[ii]) && dragon[ii].origin == origin
&& worm[ii].origin != kworm)
mark_string(worm[ii].origin, saved, 1);
}
}
}
/* This function marks all stones whose status is changed by an owl move
* reason according to the following rules:
* 1. For an owl attack, all stones belonging to the attacked dragon are
* marked as INFLUENCE_CAPTURED_STONE
* 2. For an owl defense, all stones belonging to the defended dragon are
* markes as INFLUENCE_SAVED_STONE if they are also sufficiently
* tactically stable.
*
* In effective_size, the sum of the effective size of the changed worms
* is returned (unless it is a NULL pointer).
*/
void
mark_changed_dragon(int pos, int color, int affected, int affected2,
int move_reason_type, signed char safe_stones[BOARDMAX],
float strength[BOARDMAX], float *effective_size)
{
int ii;
signed char new_status = INFLUENCE_SAVED_STONE;
int result_to_beat = 0;
ASSERT1(board[pos] == EMPTY, pos);
ASSERT1(IS_STONE(board[affected]), pos);
if (effective_size)
*effective_size = 0.0;
/* For attack moves, we immediately can set the effective size.
* For defense moves, it will be calculated in the course of
* updating the worms' status.
*/
switch (move_reason_type) {
case OWL_ATTACK_MOVE:
case OWL_ATTACK_MOVE_GOOD_KO:
case OWL_ATTACK_MOVE_BAD_KO:
ASSERT1(board[affected] == OTHER_COLOR(color), pos);
new_status = 0;
if (effective_size)
*effective_size = dragon[affected].effective_size;
break;
case OWL_DEFEND_MOVE:
ASSERT1(board[affected] == color, pos);
result_to_beat = WIN;
break;
case OWL_DEFEND_MOVE_GOOD_KO:
ASSERT1(board[affected] == color, pos);
result_to_beat = KO_A;
break;
case OWL_DEFEND_MOVE_BAD_KO:
ASSERT1(board[affected] == color, pos);
result_to_beat = KO_B;
break;
case OWL_ATTACK_MOVE_GAIN:
ASSERT1(board[affected] == OTHER_COLOR(color), pos);
new_status = 0;
if (effective_size)
*effective_size = worm[affected2].effective_size;
break;
case OWL_DEFEND_MOVE_LOSS:
ASSERT1(board[affected] == color, pos);
if (effective_size)
*effective_size = dragon[affected].effective_size
- worm[affected2].effective_size;
result_to_beat = WIN;
break;
case SEMEAI_MOVE:
ASSERT1(IS_STONE(board[affected]), pos);
if (board[affected] == color)
result_to_beat = WIN;
else {
new_status = 0;
if (effective_size)
*effective_size = dragon[affected].effective_size;
}
break;
default:
/* mark_changed_dragon() called with invalid move reason. */
ASSERT1(0, pos);
}
if (move_reason_type == OWL_ATTACK_MOVE_GAIN)
mark_changed_string(affected2, safe_stones, strength, new_status);
else {
for (ii = first_worm_in_dragon(affected); ii != NO_MOVE;
ii = next_worm_in_dragon(ii))
if (new_status == 0)
mark_changed_string(ii, safe_stones, strength, new_status);
else {
int worm_is_safe = 0;
if (worm[ii].attack_codes[0] == NO_MOVE
|| defense_move_reason_known(pos, ii))
worm_is_safe = 1;
else if (trymove(pos, color, "mark-changed-dragon", ii)) {
if (REVERSE_RESULT(attack(ii, NULL)) >= result_to_beat)
worm_is_safe = 1;
popgo();
}
if (worm_is_safe || move_reason_type == SEMEAI_MOVE) {
/* This string can now be considered safe. Hence we mark the
* whole string as such:
*/
mark_changed_string(ii, safe_stones, strength, new_status);
if (effective_size)
*effective_size += worm[ii].effective_size;
}
}
if (move_reason_type == OWL_DEFEND_MOVE_LOSS) {
new_status = 0;
mark_changed_string(affected2, safe_stones, strength, new_status);
}
}
}
/* Marks the string at (affected) with the new status and accordingly
* with the new strength.
*/
void
mark_changed_string(int affected, signed char safe_stones[BOARDMAX],
float strength[BOARDMAX], signed char new_status)
{
float new_strength;
int ii;
ASSERT1(IS_STONE(board[affected]), affected);
if (new_status == 0)
new_strength = 0.0;
else {
gg_assert(new_status == INFLUENCE_SAVED_STONE);
new_strength = DEFAULT_STRENGTH;
}
for (ii = BOARDMIN; ii < BOARDMAX; ii++)
if (board[ii] == board[affected]
&& same_string(ii, affected)) {
strength[ii] = new_strength;
safe_stones[ii] = new_status;
}
}
/* Find dragons rescued by a move at (pos). */
void
get_saved_dragons(int pos, signed char saved[BOARDMAX])
{
int k;
memset(saved, 0, sizeof(saved[0]) * BOARDMAX);
for (k = 0; k < MAX_REASONS; k++) {
int r = move[pos].reason[k];
int what;
if (r < 0)
break;
what = move_reasons[r].what;
/* We exclude the ko contingent defenses, to avoid that the
* confirm_safety routines spot an attack with ko and thinks the
* move is unsafe.
*/
if (move_reasons[r].type == OWL_DEFEND_MOVE)
mark_dragon(what, saved, 1);
}
}
/* If a move has saved the dragons in saved_dragons[] and worms in
* saved_worms[], this functions writes the stones now supposedly safe
* in the array safe_stones[].
*
* The safety of the played move itself is set according to
* move[pos].move_safety.
*/
void
mark_safe_stones(int color, int move_pos,
const signed char saved_dragons[BOARDMAX],
const signed char saved_worms[BOARDMAX],
signed char safe_stones[BOARDMAX])
{
int pos;
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (board[pos] == OTHER_COLOR(color)) {
if (dragon[pos].status == DEAD
|| (worm[pos].attack_codes[0] != 0
&& worm[pos].defense_codes[0] == 0))
safe_stones[pos] = 0;
else
safe_stones[pos] = SAFE_STONE;
}
else if (board[pos] == color) {
if ((worm[pos].attack_codes[0] != 0
&& (worm[pos].defense_codes[0] == 0 || !saved_worms[pos]))
|| dragon[pos].status == DEAD)
safe_stones[pos] = 0;
else if (saved_dragons[pos])
safe_stones[pos] = OWL_SAVED_STONE;
else if (dragon[pos].status == CRITICAL)
safe_stones[pos] = 0;
else
safe_stones[pos] = SAFE_STONE;
}
else
safe_stones[pos] = 0;
}
safe_stones[move_pos]
= move[move_pos].move_safety && safe_move(move_pos, color) == WIN;
}
/* List the move reasons for (color)'s move at (pos). Return the
* number of move reasons.
*/
int
list_move_reasons(FILE *out, int move_pos)
{
int m;
int n;
int pos;
int k;
int reason1;
int reason2;
int aa = NO_MOVE;
int bb = NO_MOVE;
int worm1 = -1;
int worm2 = -1;
int num_move_reasons = 0;
gprintf("\nMove reasons:\n");
for (n = 0; n < board_size; n++)
for (m = board_size-1; m >= 0; m--) {
pos = POS(m, n);
if (move_pos != NO_MOVE && move_pos != pos)
continue;
for (k = 0; k < MAX_REASONS; k++) {
int r = move[pos].reason[k];
if (r < 0)
break;
num_move_reasons++;
switch (move_reasons[r].type) {
case ATTACK_MOVE:
aa = move_reasons[r].what;
gfprintf(out, "Move at %1m attacks %1m%s\n", pos, aa,
(worm[aa].defense_codes[0] == 0) ? " (defenseless)" : "");
break;
case ATTACK_MOVE_GOOD_KO:
aa = move_reasons[r].what;
gfprintf(out, "Move at %1m attacks %1m%s with good ko\n", pos, aa,
(worm[aa].defense_codes[0] == 0) ? " (defenseless)" : "");
break;
case ATTACK_MOVE_BAD_KO:
aa = move_reasons[r].what;
gfprintf(out, "Move at %1m attacks %1m%s with bad ko\n", pos, aa,
(worm[aa].defense_codes[0] == 0) ? " (defenseless)" : "");
break;
case DEFEND_MOVE:
aa = move_reasons[r].what;
gfprintf(out, "Move at %1m defends %1m\n", pos, aa);
break;
case DEFEND_MOVE_GOOD_KO:
aa = move_reasons[r].what;
gfprintf(out, "Move at %1m defends %1m with good ko\n", pos, aa);
break;
case DEFEND_MOVE_BAD_KO:
aa = move_reasons[r].what;
gfprintf(out, "Move at %1m defends %1m with bad ko\n", pos, aa);
break;
case ATTACK_THREAT:
case DEFEND_THREAT:
aa = move_reasons[r].what;
if (move_reasons[r].type == ATTACK_THREAT)
gfprintf(out, "Move at %1m threatens to attack %1m\n", pos, aa);
else if (move_reasons[r].type == DEFEND_THREAT)
gfprintf(out, "Move at %1m threatens to defend %1m\n", pos, aa);
break;
case UNCERTAIN_OWL_DEFENSE:
aa = move_reasons[r].what;
if (board[aa] == current_color)
gfprintf(out, "%1m found alive but not certainly, %1m defends it again\n",
aa, pos);
else
gfprintf(out, "%1m found dead but not certainly, %1m attacks it again\n",
aa, pos);
break;
case CONNECT_MOVE:
case CUT_MOVE:
worm1 = conn_worm1[move_reasons[r].what];
worm2 = conn_worm2[move_reasons[r].what];
if (move_reasons[r].type == CONNECT_MOVE)
gfprintf(out, "Move at %1m connects %1m and %1m\n",
pos, worm1, worm2);
else
gfprintf(out, "Move at %1m cuts %1m and %1m\n", pos, worm1, worm2);
break;
case ANTISUJI_MOVE:
gfprintf(out, "Move at %1m is an antisuji\n", pos);
break;
case SEMEAI_MOVE:
aa = move_reasons[r].what;
gfprintf(out, "Move at %1m wins semeai for %1m\n", pos, aa);
break;
case SEMEAI_THREAT:
aa = move_reasons[r].what;
gfprintf(out, "Move at %1m threatens to win semeai for %1m\n",
pos, aa);
break;
case EITHER_MOVE:
reason1 = either_data[move_reasons[r].what].reason1;
reason2 = either_data[move_reasons[r].what].reason2;
worm1 = either_data[move_reasons[r].what].what1;
worm2 = either_data[move_reasons[r].what].what2;
gfprintf(out, "Move at %1m either %s %1m or %s %1m\n", pos,
reason1 == ATTACK_STRING ? "attacks" : "defends", worm1,
reason2 == ATTACK_STRING ? "attacks" : "defends", worm2);
break;
case ALL_MOVE:
reason1 = all_data[move_reasons[r].what].reason1;
reason2 = all_data[move_reasons[r].what].reason2;
worm1 = all_data[move_reasons[r].what].what1;
worm2 = all_data[move_reasons[r].what].what2;
gfprintf(out, "Move at %1m both %s %1m and %s %1m\n", pos,
reason1 == ATTACK_STRING ? "attacks" : "defends", worm1,
reason2 == ATTACK_STRING ? "attacks" : "defends", worm2);
break;
case OWL_ATTACK_MOVE:
aa = move_reasons[r].what;
gfprintf(out, "Move at %1m owl-attacks %1m\n", pos, aa);
break;
case OWL_ATTACK_MOVE_GOOD_KO:
aa = move_reasons[r].what;
gfprintf(out, "Move at %1m owl-attacks %1m with good ko\n", pos, aa);
break;
case OWL_ATTACK_MOVE_BAD_KO:
aa = move_reasons[r].what;
gfprintf(out, "Move at %1m owl-attacks %1m with bad ko\n", pos, aa);
break;
case OWL_ATTACK_MOVE_GAIN:
aa = either_data[move_reasons[r].what].what1;
bb = either_data[move_reasons[r].what].what2;
gfprintf(out, "Move at %1m owl-attacks %1m (captures %1m)\n",
pos, aa, bb);
break;
case OWL_DEFEND_MOVE:
aa = move_reasons[r].what;
gfprintf(out, "Move at %1m owl-defends %1m\n", pos, aa);
break;
case OWL_DEFEND_MOVE_GOOD_KO:
aa = move_reasons[r].what;
gfprintf(out, "Move at %1m owl-defends %1m with good ko\n", pos, aa);
break;
case OWL_DEFEND_MOVE_BAD_KO:
aa = move_reasons[r].what;
gfprintf(out, "Move at %1m owl-defends %1m with bad ko\n", pos, aa);
break;
case OWL_DEFEND_MOVE_LOSS:
aa = either_data[move_reasons[r].what].what1;
bb = either_data[move_reasons[r].what].what2;
gfprintf(out, "Move at %1m owl-defends %1m (loses %1m)\n",
pos, aa, bb);
break;
case OWL_ATTACK_THREAT:
aa = move_reasons[r].what;
gfprintf(out, "Move at %1m owl-threatens to attack %1m\n", pos, aa);
break;
case OWL_DEFEND_THREAT:
aa = move_reasons[r].what;
gfprintf(out, "Move at %1m owl-threatens to defend %1m\n", pos, aa);
break;
case OWL_PREVENT_THREAT:
aa = move_reasons[r].what;
gfprintf(out, "Move at %1m owl-prevents a threat to attack or defend %1m\n",
pos, aa);
break;
case EXPAND_TERRITORY_MOVE:
gfprintf(out, "Move at %1m expands territory\n", pos);
break;
case EXPAND_MOYO_MOVE:
gfprintf(out, "Move at %1m expands moyo\n", pos);
break;
case INVASION_MOVE:
gfprintf(out, "Move at %1m is an invasion\n", pos);
break;
case STRATEGIC_ATTACK_MOVE:
case STRATEGIC_DEFEND_MOVE:
aa = move_reasons[r].what;
if (move_reasons[r].type == STRATEGIC_ATTACK_MOVE)
gfprintf(out, "Move at %1m strategically attacks %1m\n", pos, aa);
else
gfprintf(out, "Move at %1m strategically defends %1m\n", pos, aa);
break;
case MY_ATARI_ATARI_MOVE:
gfprintf(out, "Move at %1m captures something\n", pos);
case YOUR_ATARI_ATARI_MOVE:
gfprintf(out, "Move at %1m defends against combination attack\n",
pos);
}
}
if (k > 0 && move[pos].move_safety == 0)
gfprintf(out, "Move at %1m strategically or tactically unsafe\n", pos);
}
return num_move_reasons;
}
/* This array lists rules according to which we set the status
* flags of a move reasons.
* The format is:
* { List of reasons to which the rule applies, condition of the rule,
* flags to be set, trace message }
* The condition must be of type discard_condition_fn_ptr, that is a pointer
* to a function with parameters (pos, what).
*
* FIXME: Add handling of ALL and EITHER moves for inessential worms.
*/
static struct discard_rule discard_rules[] =
{
{ { ATTACK_MOVE, ATTACK_MOVE_GOOD_KO,
ATTACK_MOVE_BAD_KO, ATTACK_THREAT,
DEFEND_MOVE, DEFEND_MOVE_GOOD_KO,
DEFEND_MOVE_BAD_KO, DEFEND_THREAT, -1 },
owl_move_vs_worm_known, TERRITORY_REDUNDANT,
" %1m: 0.0 - (threat of) attack/defense of %1m (owl attack/defense as well)\n" },
{ { SEMEAI_MOVE, SEMEAI_THREAT, -1 },
owl_move_reason_known, REDUNDANT,
" %1m: 0.0 - (threat to) win semeai involving %1m (owl move as well)\n"},
{ { SEMEAI_MOVE, SEMEAI_THREAT, -1 },
tactical_move_vs_whole_dragon_known, REDUNDANT,
" %1m: 0.0 - (threat to) win semeai involving %1m (tactical move as well)\n"},
{ { EITHER_MOVE, -1 },
either_worm_attackable, REDUNDANT,
" %1m: 0.0 - 'attack either' is redundant at %1m (direct att./def. as well)\n"},
{ { ALL_MOVE, -1 },
one_of_both_attackable, REDUNDANT,
" %1m: 0.0 - 'defend both' is redundant at %1m (direct att./def. as well)\n"},
{ { ATTACK_THREAT, DEFEND_THREAT, -1 },
concerns_inessential_worm, TERRITORY_REDUNDANT,
" %1m: 0.0 - attack/defense threat of %1m (inessential)\n"},
{ { OWL_ATTACK_THREAT, UNCERTAIN_OWL_DEFENSE, -1 },
concerns_inessential_dragon, REDUNDANT,
" %1m: 0.0 - (uncertain) owl attack/defense of %1m (inessential)\n"},
{ { ATTACK_MOVE, ATTACK_MOVE_GOOD_KO, ATTACK_MOVE_BAD_KO,
DEFEND_MOVE, DEFEND_MOVE_GOOD_KO, DEFEND_MOVE_BAD_KO, -1},
move_is_marked_unsafe, REDUNDANT,
" %1m: 0.0 - tactical move vs %1m (unsafe move)\n"},
{ { OWL_ATTACK_MOVE, OWL_ATTACK_MOVE_GOOD_KO, OWL_ATTACK_MOVE_BAD_KO,
OWL_DEFEND_MOVE, OWL_DEFEND_MOVE_GOOD_KO, OWL_DEFEND_MOVE_BAD_KO, -1},
concerns_noncritical_dragon, REDUNDANT,
" %1m: 0.0 - owl move vs %1m (non-critical)\n"},
{ { -1 }, NULL, 0, ""} /* Keep this entry at end of the list. */
};
/* This function checks the list of move reasons for redundant move
* reasons and marks them accordingly in their status field.
*/
void
discard_redundant_move_reasons(int pos)
{
int k1, k2;
int l;
for (k1 = 0; !(discard_rules[k1].reason_type[0] == -1); k1++) {
for (k2 = 0; !(discard_rules[k1].reason_type[k2] == -1); k2++) {
for (l = 0; l < MAX_REASONS; l++) {
int r = move[pos].reason[l];
if (r < 0)
break;
if ((move_reasons[r].type == discard_rules[k1].reason_type[k2])
&& (discard_rules[k1].condition(pos, move_reasons[r].what))) {
DEBUG(DEBUG_MOVE_REASONS, discard_rules[k1].trace_message,
pos, get_pos(move_reasons[r].type, move_reasons[r].what));
move_reasons[r].status |= discard_rules[k1].flags;
}
}
}
}
}
/* Look through the move reasons to see whether (pos) is an antisuji move. */
int
is_antisuji_move(int pos)
{
int k;
for (k = 0; k < MAX_REASONS; k++) {
int r = move[pos].reason[k];
if (r < 0)
break;
if (move_reasons[r].type == ANTISUJI_MOVE)
return 1; /* This move must not be played. End of story. */
}
return 0;
}
/* Increase the randomness scaling factor.
* This causes the move value to be more random.
*/
void
scale_randomness(int pos, float scaling)
{
if (scaling > move[pos].randomness_scaling)
move[pos].randomness_scaling = scaling;
}
/* Register the given `move' as a good attack threat against `target'. By
* "good" we mean a threat which is effectively a sente for the player.
* E.g. in this position the threat is good, because it results in four
* sente moves locally (trevord:950):
*
* ..OX..
* .O.*..
* .OXX..
* .OOX..
* ------
*
* We use this list of good threats for performance reasons so that
* estimate_territorial_value() in valuemoves.c doesn't have to read
* through all the moves. Such threats are found with patterns.
*/
void
register_good_attack_threat(int move, int target)
{
int k;
ASSERT_ON_BOARD1(move);
ASSERT_ON_BOARD1(target);
ASSERT1(IS_STONE(worm[target].color), move);
target = worm[target].origin;
for (k = 0; k < MAX_ATTACK_THREATS; k++) {
if (known_good_attack_threats[move][k] == target)
break;
if (known_good_attack_threats[move][k] == NO_MOVE) {
known_good_attack_threats[move][k] = target;
break;
}
}
}
/* Determine if an attack threat is registered as good (see above). */
int
is_known_good_attack_threat(int move, int target)
{
int k;
ASSERT_ON_BOARD1(move);
ASSERT_ON_BOARD1(target);
ASSERT1(IS_STONE(worm[target].color), move);
target = worm[target].origin;
for (k = 0; k < MAX_ATTACK_THREATS; k++) {
if (known_good_attack_threats[move][k] == target)
return 1;
if (known_good_attack_threats[move][k] == NO_MOVE)
break;
}
return 0;
}
/* Like documented in endgame:980, there are also moves which aren't
* safe by themselves, but attempting to capture these stones would
* result in a loss for the opponent (typically, by damezumari).
* Simple examples include snapbacks, but more complicated ones do
* exist. Following functions are helpers for the valuation processing
* which deal with such special cases.
*/
void
register_known_safe_move(int move)
{
ASSERT_ON_BOARD1(move);
known_safe_moves[move] = 1;
}
int
is_known_safe_move(int move)
{
ASSERT_ON_BOARD1(move);
return known_safe_moves[move];
}
/*
* Local Variables:
* tab-width: 8
* c-basic-offset: 2
* End:
*/