/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* 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 "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 conn_worm1
[MAX_CONNECTIONS
];
int conn_worm2
[MAX_CONNECTIONS
];
/* Potential semeai moves. */
int semeai_target1
[MAX_POTENTIAL_SEMEAI
];
int semeai_target2
[MAX_POTENTIAL_SEMEAI
];
/* Unordered sets (currently pairs) of move reasons / targets */
Reason_set either_data
[MAX_EITHER
];
Reason_set all_data
[MAX_ALL
];
int lunch_dragon
[MAX_LUNCHES
]; /* eater */
int lunch_worm
[MAX_LUNCHES
]; /* food */
/* Point redistribution */
int replacement_map
[BOARDMAX
];
/* The color for which we are evaluating moves. */
/* 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
);
int reason_type
[MAX_REASONS
];
discard_condition_fn_ptr condition
;
char trace_message
[MAX_TRACE_LENGTH
];
/* Initialize move reason data structures. */
for (pos
= BOARDMIN
; pos
< BOARDMAX
; pos
++) {
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
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.
find_connection(int worm1
, int worm2
)
/* Swap to canonical order. */
for (k
= 0; k
< next_connection
; k
++)
if (conn_worm1
[k
] == worm1
&& conn_worm2
[k
] == worm2
)
gg_assert(next_connection
< MAX_CONNECTIONS
);
conn_worm1
[next_connection
] = worm1
;
conn_worm2
[next_connection
] = worm2
;
return next_connection
- 1;
find_either_data(int reason1
, int what1
, int reason2
, int what2
)
/* Make sure the worms are ordered canonically. */
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
)
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
;
find_all_data(int reason1
, int what1
, int reason2
, int what2
)
/* Make sure the worms are ordered canonically. */
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
)
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
;
find_pair_data(int what1
, int what2
)
for (k
= 0; k
< next_either
; k
++)
if (either_data
[k
].what1
== what1
&& either_data
[k
].what2
== what2
)
gg_assert(next_either
< MAX_EITHER
);
either_data
[next_either
].what1
= what1
;
either_data
[next_either
].what2
= what2
;
/* 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.
get_pos(int reason
, int what
)
case ATTACK_MOVE_GOOD_KO
:
case DEFEND_MOVE_GOOD_KO
:
case STRATEGIC_ATTACK_MOVE
:
case STRATEGIC_DEFEND_MOVE
:
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
:
/* FIXME: What should we return here? */
return either_data
[what
].what1
;
/* FIXME: What should we return here? */
return all_data
[what
].what1
;
case EXPAND_TERRITORY_MOVE
:
case MY_ATARI_ATARI_MOVE
:
case YOUR_ATARI_ATARI_MOVE
:
case OWL_ATTACK_MOVE_GAIN
:
case OWL_DEFEND_MOVE_LOSS
:
/* FIXME: What should we return here? */
return either_data
[what
].what1
;
/* We should never get here: */
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
add_lunch(int eater
, int food
)
int dragon1
= dragon
[eater
].origin
;
int worm1
= worm
[food
].origin
;
for (k
= 0; k
< next_lunch
; k
++)
if ((lunch_dragon
[k
] == dragon1
) && (lunch_worm
[k
] == worm1
))
gg_assert(next_lunch
< MAX_LUNCHES
);
lunch_dragon
[next_lunch
] = dragon1
;
lunch_worm
[next_lunch
] = worm1
;
/* ---------------------------------------------------------------- */
* Add a move reason for (pos) if it's not already there or the
add_move_reason(int pos
, int type
, int what
)
ASSERT1(board
[pos
] == EMPTY
, pos
);
for (k
= 0; k
< MAX_REASONS
; k
++) {
int r
= move
[pos
].reason
[k
];
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.
DEBUG(DEBUG_MOVE_REASONS
,
"Move reason at %1m (type=%d, what=%d) dropped because list full.\n",
if (next_reason
>= MAX_MOVE_REASONS
) {
DEBUG(DEBUG_MOVE_REASONS
,
"Move reason at %1m (type=%d, what=%d) dropped because global list full.\n",
move
[pos
].reason
[k
] = next_reason
;
move_reasons
[next_reason
].type
= type
;
move_reasons
[next_reason
].what
= what
;
move_reasons
[next_reason
].status
= ACTIVE
;
* Remove a move reason for (pos). Ignore silently if the reason
remove_move_reason(int pos
, int type
, int what
)
int n
= -1; /* Position of the move reason to be deleted. */
for (k
= 0; k
< MAX_REASONS
; k
++) {
int r
= move
[pos
].reason
[k
];
if (move_reasons
[r
].type
== type
&& move_reasons
[r
].what
== what
)
return; /* Move reason wasn't there. */
/* Now move the last move reason to position n, thereby removing the
* one we were looking for.
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'.
move_reason_known(int pos
, int type
, int what
)
for (k
= 0; k
< MAX_REASONS
; k
++) {
if (move_reasons
[r
].type
== type
|| move_reasons
[r
].what
== what
))
/* ---------------------------------------------------------------- */
/* 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'.
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
))
if (move_reason_known(pos
, ATTACK_MOVE_GOOD_KO
, what
))
if (move_reason_known(pos
, ATTACK_MOVE_BAD_KO
, what
))
* Check whether a defense move reason already is recorded for a move.
* A negative value for 'what' means only match 'type'.
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
))
if (move_reason_known(pos
, DEFEND_MOVE_GOOD_KO
, what
))
if (move_reason_known(pos
, DEFEND_MOVE_BAD_KO
, what
))
/* Check whether a dragon consists of only one worm. If so, check
* whether we know of a tactical attack or defense move.
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'.
owl_attack_move_reason_known(int pos
, int what
)
if (move_reason_known(pos
, OWL_ATTACK_MOVE
, what
))
if (move_reason_known(pos
, OWL_ATTACK_MOVE_GOOD_KO
, what
))
if (move_reason_known(pos
, OWL_ATTACK_MOVE_BAD_KO
, what
))
* Check whether an owl defense move reason already is recorded for a move.
* A negative value for 'what' means only match 'type'.
owl_defense_move_reason_known(int pos
, int what
)
if (move_reason_known(pos
, OWL_DEFEND_MOVE
, what
))
if (move_reason_known(pos
, OWL_DEFEND_MOVE_GOOD_KO
, what
))
if (move_reason_known(pos
, OWL_DEFEND_MOVE_BAD_KO
, what
))
* Check whether an owl attack/defense move reason is recorded for a move.
* A negative value for 'what' means only match 'type'.
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.
owl_move_vs_worm_known(int pos
, int what
)
return owl_move_reason_known(pos
, dragon
[what
].origin
);
semeai_move_reason_known(int pos
, int what
)
return move_reason_known(pos
, SEMEAI_MOVE
, what
);
/* Check whether a worm is inessential */
concerns_inessential_worm(int pos
, int what
)
return DRAGON2(what
).safety
== INESSENTIAL
|| worm
[what
].inessential
;
/* Check whether a dragon is inessential */
concerns_inessential_dragon(int pos
, int what
)
return DRAGON2(what
).safety
== INESSENTIAL
;
move_is_marked_unsafe(int pos
, int what
)
return (!move
[pos
].move_safety
&& !adjacent_to_nondead_stone(pos
, current_color
));
/* Check whether a dragon is non-critical. */
concerns_noncritical_dragon(int pos
, int what
)
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.
either_worm_attackable(int pos
, int what
)
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.
one_of_both_attackable(int pos
, int what
)
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
add_attack_move(int pos
, int ww
, int code
)
add_move_reason(pos
, ATTACK_MOVE
, ww
);
add_move_reason(pos
, ATTACK_MOVE_GOOD_KO
, ww
);
add_move_reason(pos
, ATTACK_MOVE_BAD_KO
, ww
);
* Add to the reasons for the move at (pos) that it defends the worm
add_defense_move(int pos
, int ww
, int code
)
add_move_reason(pos
, DEFEND_MOVE
, ww
);
add_move_reason(pos
, DEFEND_MOVE_GOOD_KO
, ww
);
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).
add_attack_threat_move(int pos
, int ww
, int code
)
add_move_reason(pos
, ATTACK_THREAT
, worm
[ww
].origin
);
/* Remove an attack threat move reason. */
remove_attack_threat_move(int pos
, int ww
)
remove_move_reason(pos
, ATTACK_THREAT
, worm
[ww
].origin
);
* Add to the reasons for the move at (pos) that it defends the worm
add_defense_threat_move(int pos
, int ww
, int code
)
add_move_reason(pos
, DEFEND_THREAT
, worm
[ww
].origin
);
/* Report all, or up to max_strings, strings that are threatened
get_attack_threats(int pos
, int max_strings
, int strings
[])
for (k
= 0; k
< MAX_REASONS
; k
++) {
int r
= move
[pos
].reason
[k
];
if (move_reasons
[r
].type
== ATTACK_THREAT
)
strings
[num_strings
++] = move_reasons
[r
].what
;
if (num_strings
== max_strings
)
/* Report all, or up to max_strings, strings that might be defended
get_defense_threats(int pos
, int max_strings
, int strings
[])
for (k
= 0; k
< MAX_REASONS
; k
++) {
int r
= move
[pos
].reason
[k
];
if (move_reasons
[r
].type
== DEFEND_THREAT
)
strings
[num_strings
++] = move_reasons
[r
].what
;
if (num_strings
== max_strings
)
/* Report the biggest dragon that is owl-affected (possibily with ko)
get_biggest_owl_target(int pos
)
for (k
= 0; k
< MAX_REASONS
; k
++) {
int r
= move
[pos
].reason
[k
];
switch (move_reasons
[r
].type
) {
case OWL_ATTACK_MOVE_GOOD_KO
:
case OWL_ATTACK_MOVE_BAD_KO
:
case OWL_DEFEND_MOVE_GOOD_KO
:
case OWL_DEFEND_MOVE_BAD_KO
:
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
;
* Add to the reasons for the move at (pos) that it connects the
* dragons at (dr1) and (dr2). Require that the dragons are
add_connection_move(int pos
, int w1
, int w2
)
ASSERT1(worm
[w1
].color
== worm
[w2
].color
, w1
);
if (worm
[w1
].origin
== worm
[w2
].origin
)
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
add_cut_move(int pos
, int w1
, int w2
)
ASSERT1(worm
[w1
].color
== worm
[w2
].color
, w1
);
if (worm
[w1
].origin
== worm
[w2
].origin
)
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))
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
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.
add_semeai_move(int pos
, int 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).
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
);
semeai_target1
[next_semeai
] = dr1
;
semeai_target2
[next_semeai
] = dr2
;
add_move_reason(pos
, type
, next_semeai
);
* Add to the reasons for the move at (pos) that it might
* kill the dragon at (dr1) in the semeai against (dr2).
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).
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.
add_semeai_threat(int pos
, int 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
* 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
add_either_move(int pos
, int reason1
, int target1
, int reason2
, int target2
)
ASSERT_ON_BOARD1(target1
);
ASSERT_ON_BOARD1(target2
);
if (reason1
== reason2
&& target1
== target2
)
gg_assert(reason1
== ATTACK_STRING
);
gg_assert(reason2
== 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)
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)
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
* 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
add_all_move(int pos
, int reason1
, int target1
, int reason2
, int target2
)
ASSERT_ON_BOARD1(target1
);
ASSERT_ON_BOARD1(target2
);
if (reason1
== reason2
&& target1
== target2
)
gg_assert(reason1
== DEFEND_STRING
);
gg_assert(reason2
== DEFEND_STRING
);
what1
= worm
[target1
].origin
;
what2
= worm
[target2
].origin
;
index
= find_all_data(reason1
, what1
, reason2
, what2
);
add_move_reason(pos
, ALL_MOVE
, index
);
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
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
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.
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)
* 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
add_shape_value(int pos
, float value
)
if (value
> move
[pos
].maxpos_shape
)
move
[pos
].maxpos_shape
= value
;
move
[pos
].numpos_shape
+= 1;
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.
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.
add_strategical_attack_move(int pos
, int 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.
add_strategical_defense_move(int pos
, int 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).
add_owl_attack_move(int pos
, int dr
, int kworm
, int code
)
add_move_reason(pos
, OWL_ATTACK_MOVE
, dr
);
add_move_reason(pos
, OWL_ATTACK_MOVE_GOOD_KO
, dr
);
add_move_reason(pos
, OWL_ATTACK_MOVE_BAD_KO
, dr
);
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).
add_owl_defense_move(int pos
, int dr
, int code
)
add_move_reason(pos
, OWL_DEFEND_MOVE
, dr
);
add_move_reason(pos
, OWL_DEFEND_MOVE_GOOD_KO
, dr
);
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)
add_owl_attack_threat_move(int pos
, int dr
, int code
)
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
add_owl_uncertain_defense_move(int pos
, int 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.
add_owl_uncertain_attack_move(int pos
, int 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)
add_owl_defense_threat_move(int pos
, int dr
, int code
)
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
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.
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.
add_owl_prevent_threat_move(int pos
, int dr
)
add_move_reason(pos
, OWL_PREVENT_THREAT
, dragon
[dr
].origin
);
* Add value of followup moves.
add_followup_value(int pos
, float value
)
if (value
> move
[pos
].followup_value
)
move
[pos
].followup_value
= value
;
* Add value of reverse followup moves.
add_reverse_followup_value(int pos
, float value
)
if (value
> move
[pos
].reverse_followup_value
)
move
[pos
].reverse_followup_value
= value
;
* Set a minimum allowed value for the move.
set_minimum_move_value(int pos
, float value
)
if (value
> move
[pos
].min_value
) {
move
[pos
].min_value
= value
;
* Set a maximum allowed value for the move.
set_maximum_move_value(int pos
, float value
)
if (value
< move
[pos
].max_value
)
move
[pos
].max_value
= value
;
* Set a minimum allowed territorial value for the move.
set_minimum_territorial_value(int pos
, float value
)
if (value
> move
[pos
].min_territory
)
move
[pos
].min_territory
= value
;
* Set a maximum allowed territorial value for the move.
set_maximum_territorial_value(int pos
, float value
)
if (value
< move
[pos
].max_territory
)
move
[pos
].max_territory
= value
;
* Add a point redistribution rule, sending the points from (from)
add_replacement_move(int from
, int to
, int color
)
if (board
[from
] != EMPTY
)
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.
/* 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.)
ASSERT1(dd
== to
|| to
== replacement_map
[dd
], from
);
/* There already is a redistribution in effect so we
* have nothing more to do.
TRACE("Move at %1m is replaced by %1m.\n", from
, to
);
/* Verify that we don't introduce a cyclic redistribution. */
gprintf("Cyclic point redistribution detected.\n");
/* Update the replacement map. Make sure that all replacements
* always are directed immediately to the final destination.
replacement_map
[from
] = cc
;
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). */
get_saved_worms(int pos
, signed char saved
[BOARDMAX
])
memset(saved
, 0, sizeof(saved
[0]) * BOARDMAX
);
for (k
= 0; k
< MAX_REASONS
; k
++) {
int r
= move
[pos
].reason
[k
];
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
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
;
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
* In effective_size, the sum of the effective size of the changed worms
* is returned (unless it is a NULL pointer).
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
)
signed char new_status
= INFLUENCE_SAVED_STONE
;
ASSERT1(board
[pos
] == EMPTY
, pos
);
ASSERT1(IS_STONE(board
[affected
]), pos
);
/* 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_GOOD_KO
:
case OWL_ATTACK_MOVE_BAD_KO
:
ASSERT1(board
[affected
] == OTHER_COLOR(color
), pos
);
*effective_size
= dragon
[affected
].effective_size
;
ASSERT1(board
[affected
] == color
, pos
);
case OWL_DEFEND_MOVE_GOOD_KO
:
ASSERT1(board
[affected
] == color
, pos
);
case OWL_DEFEND_MOVE_BAD_KO
:
ASSERT1(board
[affected
] == color
, pos
);
case OWL_ATTACK_MOVE_GAIN
:
ASSERT1(board
[affected
] == OTHER_COLOR(color
), pos
);
*effective_size
= worm
[affected2
].effective_size
;
case OWL_DEFEND_MOVE_LOSS
:
ASSERT1(board
[affected
] == color
, pos
);
*effective_size
= dragon
[affected
].effective_size
- worm
[affected2
].effective_size
;
ASSERT1(IS_STONE(board
[affected
]), pos
);
if (board
[affected
] == color
)
*effective_size
= dragon
[affected
].effective_size
;
/* mark_changed_dragon() called with invalid move reason. */
if (move_reason_type
== OWL_ATTACK_MOVE_GAIN
)
mark_changed_string(affected2
, safe_stones
, strength
, new_status
);
for (ii
= first_worm_in_dragon(affected
); ii
!= NO_MOVE
;
ii
= next_worm_in_dragon(ii
))
mark_changed_string(ii
, safe_stones
, strength
, new_status
);
if (worm
[ii
].attack_codes
[0] == NO_MOVE
|| defense_move_reason_known(pos
, ii
))
else if (trymove(pos
, color
, "mark-changed-dragon", ii
)) {
if (REVERSE_RESULT(attack(ii
, NULL
)) >= result_to_beat
)
if (worm_is_safe
|| move_reason_type
== SEMEAI_MOVE
) {
/* This string can now be considered safe. Hence we mark the
mark_changed_string(ii
, safe_stones
, strength
, new_status
);
*effective_size
+= worm
[ii
].effective_size
;
if (move_reason_type
== OWL_DEFEND_MOVE_LOSS
) {
mark_changed_string(affected2
, safe_stones
, strength
, new_status
);
/* Marks the string at (affected) with the new status and accordingly
mark_changed_string(int affected
, signed char safe_stones
[BOARDMAX
],
float strength
[BOARDMAX
], signed char new_status
)
ASSERT1(IS_STONE(board
[affected
]), affected
);
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). */
get_saved_dragons(int pos
, signed char saved
[BOARDMAX
])
memset(saved
, 0, sizeof(saved
[0]) * BOARDMAX
);
for (k
= 0; k
< MAX_REASONS
; k
++) {
int r
= move
[pos
].reason
[k
];
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
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
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
])
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
] = 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
)
else if (saved_dragons
[pos
])
safe_stones
[pos
] = OWL_SAVED_STONE
;
else if (dragon
[pos
].status
== CRITICAL
)
safe_stones
[pos
] = SAFE_STONE
;
= 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.
list_move_reasons(FILE *out
, int move_pos
)
int num_move_reasons
= 0;
gprintf("\nMove reasons:\n");
for (n
= 0; n
< board_size
; n
++)
for (m
= board_size
-1; m
>= 0; m
--) {
if (move_pos
!= NO_MOVE
&& move_pos
!= pos
)
for (k
= 0; k
< MAX_REASONS
; k
++) {
int r
= move
[pos
].reason
[k
];
switch (move_reasons
[r
].type
) {
aa
= move_reasons
[r
].what
;
gfprintf(out
, "Move at %1m attacks %1m%s\n", pos
, aa
,
(worm
[aa
].defense_codes
[0] == 0) ? " (defenseless)" : "");
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)" : "");
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)" : "");
aa
= move_reasons
[r
].what
;
gfprintf(out
, "Move at %1m defends %1m\n", pos
, aa
);
case DEFEND_MOVE_GOOD_KO
:
aa
= move_reasons
[r
].what
;
gfprintf(out
, "Move at %1m defends %1m with good ko\n", pos
, aa
);
aa
= move_reasons
[r
].what
;
gfprintf(out
, "Move at %1m defends %1m with bad ko\n", pos
, aa
);
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
);
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",
gfprintf(out
, "%1m found dead but not certainly, %1m attacks it again\n",
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",
gfprintf(out
, "Move at %1m cuts %1m and %1m\n", pos
, worm1
, worm2
);
gfprintf(out
, "Move at %1m is an antisuji\n", pos
);
aa
= move_reasons
[r
].what
;
gfprintf(out
, "Move at %1m wins semeai for %1m\n", pos
, aa
);
aa
= move_reasons
[r
].what
;
gfprintf(out
, "Move at %1m threatens to win semeai for %1m\n",
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
);
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
);
aa
= move_reasons
[r
].what
;
gfprintf(out
, "Move at %1m owl-attacks %1m\n", pos
, aa
);
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
);
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
);
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",
aa
= move_reasons
[r
].what
;
gfprintf(out
, "Move at %1m owl-defends %1m\n", pos
, aa
);
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
);
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
);
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",
aa
= move_reasons
[r
].what
;
gfprintf(out
, "Move at %1m owl-threatens to attack %1m\n", pos
, aa
);
aa
= move_reasons
[r
].what
;
gfprintf(out
, "Move at %1m owl-threatens to defend %1m\n", pos
, aa
);
aa
= move_reasons
[r
].what
;
gfprintf(out
, "Move at %1m owl-prevents a threat to attack or defend %1m\n",
case EXPAND_TERRITORY_MOVE
:
gfprintf(out
, "Move at %1m expands territory\n", pos
);
gfprintf(out
, "Move at %1m expands moyo\n", pos
);
gfprintf(out
, "Move at %1m is an invasion\n", pos
);
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
);
gfprintf(out
, "Move at %1m strategically defends %1m\n", pos
, aa
);
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",
if (k
> 0 && move
[pos
].move_safety
== 0)
gfprintf(out
, "Move at %1m strategically or tactically unsafe\n", pos
);
/* This array lists rules according to which we set the status
* flags of a move reasons.
* { 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_worm_attackable
, REDUNDANT
,
" %1m: 0.0 - 'attack either' is redundant at %1m (direct att./def. as well)\n"},
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.
discard_redundant_move_reasons(int pos
)
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 ((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. */
is_antisuji_move(int pos
)
for (k
= 0; k
< MAX_REASONS
; k
++) {
int r
= move
[pos
].reason
[k
];
if (move_reasons
[r
].type
== ANTISUJI_MOVE
)
return 1; /* This move must not be played. End of story. */
/* Increase the randomness scaling factor.
* This causes the move value to be more random.
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):
* 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.
register_good_attack_threat(int move
, int target
)
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
)
if (known_good_attack_threats
[move
][k
] == NO_MOVE
) {
known_good_attack_threats
[move
][k
] = target
;
/* Determine if an attack threat is registered as good (see above). */
is_known_good_attack_threat(int move
, int target
)
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
)
if (known_good_attack_threats
[move
][k
] == NO_MOVE
)
/* 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.
register_known_safe_move(int move
)
known_safe_moves
[move
] = 1;
is_known_safe_move(int move
)
return known_safe_moves
[move
];