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