Initial commit of GNU Go v3.8.
[sgk-go] / engine / owl.c
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* This is GNU Go, a Go program. Contact gnugo@gnu.org, or see *
* http://www.gnu.org/software/gnugo/ for more information. *
* *
* Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, *
* 2008 and 2009 by the Free Software Foundation. *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation - version 3 or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License in file COPYING for more details. *
* *
* You should have received a copy of the GNU General Public *
* License along with this program; if not, write to the Free *
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, *
* Boston, MA 02111, USA. *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*
* The code in this file implements "Optics With Limit-negotiation (OWL)."
*
* The life and death code in optics.c, works reasonably well as long as the
* position is in a *terminal position*, which we define to be one where there
* are no moves left which can expand the eye space, or limit it. In
* situations where the dragon is surrounded, yet has room to thrash around a
* bit making eyes, a simple application of the graph-based analysis will not
* work. Instead, a bit of reading is needed to reach a terminal position.
* The defender tries to expand his eyespace, the attacker to limit it, and
* when neither finds an effective move, the position is evaluated. We call
* this type of life and death reading *Optics With Limit-negotiation* (OWL).
*
* (|__|)
* (@)(@))
* |:v:: |
* ( )
* \| |/
* =#===#=
* /___/
*
* The owl is noted for its keen vision
* and (purported) wisdom.
*/
#include "gnugo.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "liberty.h"
#include "readconnect.h"
#include "patterns.h"
#include "cache.h"
#include "sgftree.h"
#include "gg_utils.h"
#define MAX_MOVES 3 /* maximum number of branches at each node */
#define MAX_SEMEAI_MOVES 6 /* semeai branch factor */
#define MAX_SEMEAI_DEPTH 100 /* Don't read below this depth */
#define MAX_LUNCHES 10
#define MAX_GOAL_WORMS 15 /* maximum number of worms in a dragon to be */
/* cataloged. NOTE: Must fit in value2 in hashnode! */
#define MAX_ESCAPE 3 /* After this many escape moves, owl_determine_life is */
/* not called */
struct local_owl_data {
signed char goal[BOARDMAX];
signed char boundary[BOARDMAX];
/* Same as goal, except never anything is removed from it. */
signed char cumulative_goal[BOARDMAX];
/* FIXME: neighbors[] and escape_values[] are never recomputed.
* Consider moving these arrays from stack to a static or
* dynamic variable so it is not copied around in
* do_push_owl(). Be aware of semeai code though.
*/
signed char neighbors[BOARDMAX];
signed char escape_values[BOARDMAX];
int color;
struct eye_data my_eye[BOARDMAX];
/* array of half-eye data for use during owl reading */
struct half_eye_data half_eye[BOARDMAX];
int lunch[MAX_LUNCHES];
int lunch_attack_code[MAX_LUNCHES];
int lunch_attack_point[MAX_LUNCHES];
int lunch_defend_code[MAX_LUNCHES];
int lunch_defense_point[MAX_LUNCHES];
signed char inessential[BOARDMAX];
int lunches_are_current; /* If true, owl lunch data is current */
signed char safe_move_cache[BOARDMAX];
/* This is used to organize the owl stack. */
struct local_owl_data *restore_from;
};
static int result_certain;
/* Statistics. */
static int local_owl_node_counter;
/* Node limitation. */
static int global_owl_node_counter = 0;
static struct local_owl_data *current_owl_data;
static struct local_owl_data *other_owl_data;
static int goal_worms_computed = 0;
static int owl_goal_worm[MAX_GOAL_WORMS];
#define MAX_CUTS 5
enum same_dragon_value {
SAME_DRAGON_NOT_CONNECTED,
SAME_DRAGON_MAYBE_CONNECTED,
SAME_DRAGON_CONNECTED,
SAME_DRAGON_ALL_CONNECTED
};
struct matched_pattern_data;
struct owl_move_data {
int pos; /* move coordinate */
int value; /* value */
const char *name; /* name of the pattern suggesting the move */
/* whether the move extends the dragon or not */
enum same_dragon_value same_dragon;
int lunch; /* Position of a lunch, if applicable.*/
int escape; /* true if an escape pattern is matched */
int defense_pos; /* defense coordinate for vital owl attack patterns. */
int cuts[MAX_CUTS]; /* strings of the goal that might get cut off */
/* pointer to pattern data, used for SAME_DRAGON_ALL_CONNECTED */
struct matched_pattern_data *pattern_data;
};
#define USE_BDIST 1
struct matched_pattern_data {
int move;
int value;
int ll;
int anchor;
#if USE_BDIST
int bdist;
#endif
struct pattern *pattern;
/* To link combinable patterns in chains. */
int next_pattern_index;
};
struct matched_patterns_list_data {
int initialized;
int counter; /* Number of patterns in the list. */
int used; /* How many patterns have already been used?*/
int list_size;
struct matched_pattern_data *pattern_list;
int first_pattern_index[BOARDMAX];
int heap_num_patterns;
struct matched_pattern_data **pattern_heap;
};
void dump_pattern_list(struct matched_patterns_list_data *list);
static int do_owl_attack(int str, int *move, int *wormid,
struct local_owl_data *owl, int escape);
static int do_owl_defend(int str, int *move, int *wormid,
struct local_owl_data *owl, int escape);
static void owl_shapes(struct matched_patterns_list_data *list,
struct owl_move_data moves[MAX_MOVES], int color,
struct local_owl_data *owl, struct pattern_db *type);
static void collect_owl_shapes_callbacks(int anchor, int color,
struct pattern *pattern_db,
int ll, void *data);
static void pattern_list_prepare(struct matched_patterns_list_data *list);
static void pattern_list_build_heap(struct matched_patterns_list_data *list);
static void pattern_list_pop_heap_once(struct matched_patterns_list_data *list);
static void pattern_list_sink_heap_top_element(struct matched_patterns_list_data
*list);
static int get_next_move_from_list(struct matched_patterns_list_data *list,
int color, struct owl_move_data *moves,
int cutoff, struct local_owl_data *owl);
static void init_pattern_list(struct matched_patterns_list_data *list);
static void close_pattern_list(int color,
struct matched_patterns_list_data *list);
static void owl_shapes_callback(int anchor, int color,
struct pattern *pattern_db,
int ll, void *data);
static void owl_add_move(struct owl_move_data *moves, int move, int value,
const char *reason,
enum same_dragon_value same_dragon, int lunch,
int escape, int defense_pos, int max_moves,
struct matched_pattern_data *pattern_data);
static void owl_determine_life(struct local_owl_data *owl,
struct local_owl_data *second_owl,
int does_attack,
struct owl_move_data *moves,
struct eyevalue *probable_eyes,
int *eyemin, int *eyemax);
static void owl_find_relevant_eyespaces(struct local_owl_data *owl,
int mw[BOARDMAX], int mz[BOARDMAX]);
static int owl_estimate_life(struct local_owl_data *owl,
struct local_owl_data *second_owl,
struct owl_move_data vital_moves[MAX_MOVES],
const char **live_reason,
int does_attack,
struct eyevalue *probable_eyes,
int *eyemin, int *eyemax);
static int modify_stupid_eye_vital_point(struct local_owl_data *owl,
int *vital_point,
int is_attack_point);
static int modify_eyefilling_move(int *move, int color);
static int estimate_lunch_half_eye_bonus(int lunch,
struct half_eye_data half_eye[BOARDMAX]);
static void owl_mark_dragon(int apos, int bpos,
struct local_owl_data *owl,
int new_dragons[BOARDMAX]);
static void owl_mark_worm(int apos, int bpos,
struct local_owl_data *owl);
static void owl_mark_boundary(struct local_owl_data *owl);
static void owl_update_goal(int pos, enum same_dragon_value same_dragon,
int lunch, struct local_owl_data *owl,
int semeai_call,
struct matched_pattern_data *pattern_data);
static void owl_test_cuts(signed char goal[BOARDMAX], int color,
int cuts[MAX_CUTS]);
static void componentdump(const signed char component[BOARDMAX]);
static void owl_update_boundary_marks(int pos, struct local_owl_data *owl);
static void owl_find_lunches(struct local_owl_data *owl);
static int improve_lunch_attack(int lunch, int attack_point);
static int improve_lunch_defense(int lunch, int defense_point);
static void owl_make_domains(struct local_owl_data *owla,
struct local_owl_data *owlb);
static int owl_safe_move(int move, int color);
static void sniff_lunch(int lunch, int *min, int *probable, int *max,
struct local_owl_data *owl);
static void eat_lunch_escape_bonus(int lunch, int *min, int *probable,
int *max, struct local_owl_data *owl);
static int select_new_goal_origin(int origin, struct local_owl_data *owl);
static void compute_owl_escape_values(struct local_owl_data *owl);
static int owl_escape_route(struct local_owl_data *owl);
static void do_owl_analyze_semeai(int apos, int bpos,
struct local_owl_data *owla,
struct local_owl_data *owlb,
int *resulta, int *resultb,
int *move, int pass, int owl_phase);
static int semeai_trymove_and_recurse(int apos, int bpos,
struct local_owl_data *owla,
struct local_owl_data *owlb,
int owl_phase,
int move, int color, int ko_allowed,
int move_value, const char *move_name,
enum same_dragon_value same_dragon,
struct matched_pattern_data *pattern_data,
int lunch, int *semeai_move,
int *this_resulta, int *this_resultb);
static void semeai_add_sgf_comment(int value, int owl_phase);
static int semeai_trust_tactical_attack(int str);
static int semeai_propose_eyespace_filling_move(struct local_owl_data *owla,
struct local_owl_data *owlb);
static void semeai_review_owl_moves(struct owl_move_data owl_moves[MAX_MOVES],
struct local_owl_data *owla,
struct local_owl_data *owlb, int color,
int *safe_outside_liberty_found,
int *safe_common_liberty_found,
int *riskless_move_found,
signed char mw[BOARDMAX],
struct owl_move_data semeai_moves[MAX_SEMEAI_MOVES],
int guess_same_dragon, int value_bonus,
int *critical_semeai_worms);
static int semeai_move_value(int move, struct local_owl_data *owla,
struct local_owl_data *owlb, int raw_value,
int *critical_semeai_worms);
static int semeai_is_riskless_move(int move, struct local_owl_data *owla);
static void remove_eye_filling_moves(struct local_owl_data *our_owl,
struct owl_move_data *moves);
static int find_semeai_backfilling_move(int worm, int liberty);
static int liberty_of_goal(int pos, struct local_owl_data *owl);
static int second_liberty_of_goal(int pos, struct local_owl_data *owl);
static int matches_found;
static signed char found_matches[BOARDMAX];
static void reduced_init_owl(struct local_owl_data **owl,
int at_bottom_of_stack);
static void init_owl(struct local_owl_data **owl, int target1, int target2,
int move, int use_stack, int new_dragons[BOARDMAX]);
static struct local_owl_data *owl_stack[2 * MAXSTACK];
static int owl_stack_size = 0;
static int owl_stack_pointer = 0;
static void check_owl_stack_size(void);
static void push_owl(struct local_owl_data **owl);
static void do_push_owl(struct local_owl_data **owl);
static void pop_owl(struct local_owl_data **owl);
#if 0
static int catalog_goal(struct local_owl_data *owl,
int goal_worm[MAX_GOAL_WORMS]);
#endif
static int list_goal_worms(struct local_owl_data *owl,
int goal_worm[MAX_GOAL_WORMS]);
/* FIXME: taken from move_reasons.h */
#define MAX_DRAGONS 2 * MAX_BOARD * MAX_BOARD / 3
static int dragon_goal_worms[MAX_DRAGONS][MAX_GOAL_WORMS];
static void
prepare_goal_list(int str, struct local_owl_data *owl,
int list[MAX_GOAL_WORMS], int *flag, int *kworm,
int do_list);
static void
finish_goal_list(int *flag, int *wpos, int list[MAX_GOAL_WORMS], int index);
/* Semeai worms are worms whose capture wins the semeai. */
#define MAX_SEMEAI_WORMS 20
static int s_worms = 0;
static int semeai_worms[MAX_SEMEAI_WORMS];
static int important_semeai_worms[MAX_SEMEAI_WORMS];
/* Whether one color prefers to get a ko over a seki. */
static int prefer_ko;
/* Usually it's a bad idea to include the opponent worms involved in
* the semeai in the eyespace. For some purposes (determining a
* definite lack of eyespace, finding certain vital moves), however,
* we want to do that anyway. Then set this variable to 1 before
* calling owl_estimate_life() and reset it afterwards.
*
* FIXME: We should implement a nicer mechanism to propagate this
* information to owl_lively(), where it's used.
*/
static int include_semeai_worms_in_eyespace = 0;
static void
clear_cut_list(int cuts[MAX_CUTS])
{
int i;
for (i = 0; i < MAX_CUTS; i++)
cuts[i] = NO_MOVE;
}
/* Called when (apos) and (bpos) point to adjacent dragons
* of the opposite color, both with matcher_status DEAD or
* CRITICAL, analyzes the semeai, assuming that the player
* of the (apos) dragon moves first. The results returned
* by *resulta and *resultb are the results of the defense
* of the apos dragon and the attack of the bpos dragon,
* respectively. Thus if these results are 1 and 0,
* respectively, the usual meaning is that a move by the
* apos player produces seki.
*
* owl determines whether owl moves are being generated
* or simple liberty filling is taking place.
*
*/
void
owl_analyze_semeai(int apos, int bpos, int *resulta, int *resultb,
int *semeai_move, int owl, int *semeai_result_certain)
{
owl_analyze_semeai_after_move(PASS_MOVE, EMPTY, apos, bpos, resulta, resultb,
semeai_move, owl, semeai_result_certain, 0);
}
/* Same as the function above with the addition that an arbitrary move
* may be made before the analysis is performed.
*/
void
owl_analyze_semeai_after_move(int move, int color, int apos, int bpos,
int *resulta, int *resultb, int *semeai_move,
int owl, int *semeai_result_certain,
int recompute_dragons)
{
signed char ms[BOARDMAX];
int w1, w2;
int str;
SGFTree *save_sgf_dumptree = sgf_dumptree;
int save_verbose = verbose;
int dummy_resulta;
int dummy_resultb;
int dummy_semeai_move;
double start = 0.0;
int reading_nodes_when_called = get_reading_node_counter();
int nodes_used;
int new_dragons[BOARDMAX];
struct local_owl_data *owla;
struct local_owl_data *owlb;
Hash_data goal_hash;
if (!resulta)
resulta = &dummy_resulta;
if (!resultb)
resultb = &dummy_resultb;
if (!semeai_move)
semeai_move = &dummy_semeai_move;
if (debug & DEBUG_OWL_PERFORMANCE)
start = gg_cputime();
if (recompute_dragons) {
if (tryko(move, color, "Recompute dragons for semeai.")) {
compute_new_dragons(new_dragons);
popgo();
}
else
recompute_dragons = 0;
}
/* Look for owl substantial worms of either dragon adjoining
* the other dragon. Capturing such a worm wins the semeai.
* These are the semeai_worms. This code must come before
* the owl_init() calls because the owl_substantial
*
* FIXME: The sentence above is unfinished.
*/
s_worms = 0;
memset(ms, 0, sizeof(ms));
for (w1 = first_worm_in_dragon(apos);
w1 != NO_MOVE;
w1 = next_worm_in_dragon(w1)) {
for (w2 = first_worm_in_dragon(bpos);
w2 != NO_MOVE;
w2 = next_worm_in_dragon(w2)) {
if (adjacent_strings(w1, w2) || have_common_lib(w1, w2, NULL)) {
mark_string(w1, ms, 1);
mark_string(w2, ms, 1);
}
}
}
sgf_dumptree = NULL;
if (verbose > 0)
verbose--;
for (str = BOARDMIN; str < BOARDMAX; str++)
if (ON_BOARD(str) && ms[str] && worm[str].origin == str) {
int adj;
int adjs[MAXCHAIN];
int k;
int adjacent_to_outside = 0;
/* Is the string adjacent to a living dragon outside the semeai?
* In that case it's important to attack/defend it for the life
* of the opponent.
*
* FIXME: Checking crude_status here isn't quite appropriate but
* owl_status is not always computed and status itself is unsafe
* since it might change before later calls to this code, e.g.
* when checking for blunders.
*
* Not checking for aliveness at all gives problems in e.g.
* ld_owl:302 where S19 is a separate dragon and R19 should not
* be considered critically important. What we really would like
* to determine is whether it's outside the semeai, however.
*/
adj = chainlinks(str, adjs);
for (k = 0; k < adj; k++) {
if (!is_same_dragon(adjs[k], apos)
&& !is_same_dragon(adjs[k], bpos)
&& dragon[adjs[k]].crude_status == ALIVE)
adjacent_to_outside = 1;
}
if ((adjacent_to_outside || countstones(str) > 6)
&& s_worms < MAX_SEMEAI_WORMS) {
important_semeai_worms[s_worms] = 1;
semeai_worms[s_worms++] = str;
DEBUG(DEBUG_SEMEAI, "important semeai worm: %1m\n", str);
}
else if (owl_substantial(str) && s_worms < MAX_SEMEAI_WORMS) {
important_semeai_worms[s_worms] = 0;
semeai_worms[s_worms++] = str;
DEBUG(DEBUG_SEMEAI, "semeai worm: %1m\n", str);
}
}
verbose = save_verbose;
sgf_dumptree = save_sgf_dumptree;
ASSERT1(board[apos] == OTHER_COLOR(board[bpos]), apos);
count_variations = 1;
if (move == PASS_MOVE)
DEBUG(DEBUG_SEMEAI, "owl_analyze_semeai: %1m vs. %1m\n", apos, bpos);
else
DEBUG(DEBUG_SEMEAI, "owl_analyze_semeai_after_move %C %1m: %1m vs. %1m\n",
color, move, apos, bpos);
if (owl) {
if (recompute_dragons) {
init_owl(&owla, apos, NO_MOVE, NO_MOVE, 1, new_dragons);
init_owl(&owlb, bpos, NO_MOVE, NO_MOVE, 0, new_dragons);
}
else {
init_owl(&owla, apos, NO_MOVE, NO_MOVE, 1, NULL);
init_owl(&owlb, bpos, NO_MOVE, NO_MOVE, 0, NULL);
}
owl_make_domains(owla, owlb);
}
else {
reduced_init_owl(&owla, 1);
reduced_init_owl(&owlb, 0);
local_owl_node_counter = 0;
owl_mark_worm(apos, NO_MOVE, owla);
owl_mark_worm(bpos, NO_MOVE, owlb);
}
result_certain = 1;
{
Hash_data temp = goal_to_hashvalue(owla->goal);
goal_hash = goal_to_hashvalue(owlb->goal);
hashdata_xor(goal_hash, temp);
}
if (owl
&& search_persistent_semeai_cache(ANALYZE_SEMEAI,
apos, bpos, move, color, &goal_hash,
resulta, resultb, semeai_move,
semeai_result_certain)) {
if (move == PASS_MOVE) {
DEBUG(DEBUG_OWL_PERFORMANCE,
"analyze_semeai %1m vs. %1m, result %d %d %1m (cached)\n",
apos, bpos, *resulta, *resultb, *semeai_move);
}
else {
DEBUG(DEBUG_OWL_PERFORMANCE,
"analyze_semeai_after_move %C %1m: %1m vs. %1m, result %d %d %1m (cached)\n",
color, move, apos, bpos, *resulta, *resultb, *semeai_move);
}
return;
}
/* In some semeai situations one or both players have the option to
* choose between seki and ko for the life and death of both. In
* general this choice depends on the ko threat situation, the
* overall score, and the strategical effects on surrounding
* dragons, but we don't try to correctly estimate this. Instead we
* make the reasonable assumption that if one dragon is
* substantially smaller than the other dragon, ko is to be
* preferred for the smaller dragon and seki for the larger dragon.
*
* prefer_ko can be either WHITE, BLACK, or EMPTY and tells which
* color, if any, prefers to get ko.
*/
if (dragon[apos].size <= 5 && dragon[bpos].size > 3 * dragon[apos].size)
prefer_ko = board[apos];
else if (dragon[bpos].size <= 5 && dragon[apos].size > 3 * dragon[bpos].size)
prefer_ko = board[bpos];
else
prefer_ko = EMPTY;
if (move == PASS_MOVE)
do_owl_analyze_semeai(apos, bpos, owla, owlb,
resulta, resultb, semeai_move, 0, owl);
else {
semeai_trymove_and_recurse(bpos, apos, owlb, owla, owl,
move, color, 1, 0, "mandatory move",
SAME_DRAGON_MAYBE_CONNECTED, NULL, NO_MOVE,
semeai_move, resultb, resulta);
*resulta = REVERSE_RESULT(*resulta);
*resultb = REVERSE_RESULT(*resultb);
}
nodes_used = get_reading_node_counter() - reading_nodes_when_called;
if (move == PASS_MOVE) {
DEBUG(DEBUG_OWL_PERFORMANCE,
"analyze_semeai %1m vs. %1m, result %d %d %1m (%d, %d nodes, %f seconds)\n",
apos, bpos, *resulta, *resultb, *semeai_move, local_owl_node_counter,
nodes_used, gg_cputime() - start);
}
else {
DEBUG(DEBUG_OWL_PERFORMANCE,
"analyze_semeai_after_move %C %1m: %1m vs. %1m, result %d %d %1m (%d, %d nodes, %f seconds)\n",
color, move, apos, bpos, *resulta, *resultb, *semeai_move,
local_owl_node_counter,
nodes_used, gg_cputime() - start);
}
if (semeai_result_certain)
*semeai_result_certain = result_certain;
if (owl)
store_persistent_semeai_cache(ANALYZE_SEMEAI, apos, bpos, move, color,
&goal_hash,
*resulta, *resultb, *semeai_move,
result_certain, nodes_used,
owla->goal, owlb->goal);
}
/* It is assumed that the 'a' player moves first, and
* determines the best result for both players. The
* parameter "pass" is 1 if the opponent's last move is
* pass. In this case, if no move is found but the genus
* is less than 2, then the position is declared seki.
*
* If a move is needed to get this result, then (*move) is
* the location, otherwise this field returns PASS.
*/
static void
do_owl_analyze_semeai(int apos, int bpos,
struct local_owl_data *owla,
struct local_owl_data *owlb,
int *resulta, int *resultb,
int *move, int pass, int owl_phase)
{
int color = board[apos];
int other = OTHER_COLOR(color);
#if 0
int wormsa, wormsb;
int goal_wormsa[MAX_GOAL_WORMS], goal_wormsb[MAX_GOAL_WORMS];
#endif
struct owl_move_data vital_defensive_moves[MAX_MOVES];
struct owl_move_data vital_offensive_moves[MAX_MOVES];
struct owl_move_data shape_defensive_moves[MAX_MOVES];
struct owl_move_data shape_offensive_moves[MAX_MOVES];
struct matched_patterns_list_data shape_offensive_patterns;
struct matched_patterns_list_data shape_defensive_patterns;
struct owl_move_data moves[MAX_SEMEAI_MOVES];
struct owl_move_data outside_liberty;
struct owl_move_data common_liberty;
struct owl_move_data backfill_outside_liberty;
struct owl_move_data backfill_common_liberty;
int safe_outside_liberty_found = 0;
int safe_common_liberty_found = 0;
int riskless_move_found = 0;
signed char mw[BOARDMAX];
int k;
SGFTree *save_sgf_dumptree = sgf_dumptree;
int save_count_variations = count_variations;
int move_value;
int best_resulta = 0;
int best_resultb = 0;
int best_move = 0;
const char *best_move_name = NULL;
int this_resulta = -1;
int this_resultb = -1;
int xpos;
int value1;
int value2;
int this_variation_number = count_variations - 1;
int you_look_alive = 0;
int I_look_alive = 0;
int dummy_move;
int tested_moves;
int critical_semeai_worms[MAX_SEMEAI_WORMS];
int sworm;
int we_might_be_inessential;
struct eyevalue probable_eyes_a;
struct eyevalue probable_eyes_b;
struct eyevalue dummy_eyes;
int I_have_more_eyes;
SETUP_TRACE_INFO2("do_owl_analyze_semeai", apos, bpos);
if (!move)
move = &dummy_move;
ASSERT1(board[apos] == owla->color, apos);
ASSERT1(board[bpos] == owlb->color, bpos);
apos = find_origin(apos);
bpos = find_origin(bpos);
if (stackp <= semeai_branch_depth
&& owl_phase
&& tt_get(&ttable, SEMEAI, apos, bpos, depth - stackp, NULL,
&value1, &value2, &xpos) == 2) {
TRACE_CACHED_RESULT2(value1, value2, xpos);
*move = xpos;
*resulta = value1;
*resultb = value2;
TRACE("%oVariation %d: %1m %1m %s %s %1m (cached) ",
this_variation_number, apos, bpos,
result_to_string(*resulta),
result_to_string(*resultb),
*move);
SGFTRACE_SEMEAI(xpos, *resulta, *resultb, "cached");
return;
}
global_owl_node_counter++;
local_owl_node_counter++;
shape_offensive_patterns.initialized = 0;
shape_defensive_patterns.initialized = 0;
#if 0
wormsa = catalog_goal(owla, goal_wormsa);
wormsb = catalog_goal(owlb, goal_wormsb);
#endif
outside_liberty.pos = NO_MOVE;
common_liberty.pos = NO_MOVE;
backfill_outside_liberty.pos = NO_MOVE;
backfill_common_liberty.pos = NO_MOVE;
for (k = 0; k < MAX_SEMEAI_MOVES; k++) {
moves[k].pos = 0;
moves[k].value = -1;
moves[k].name = NULL;
moves[k].same_dragon = SAME_DRAGON_CONNECTED;
moves[k].lunch = NO_MOVE;
clear_cut_list(moves[k].cuts);
}
ASSERT1(other == board[bpos], bpos);
memset(mw, 0, sizeof(mw));
/* Turn off the sgf file and variation counting. */
sgf_dumptree = NULL;
count_variations = 0;
/* Look for a tactical attack. We seek a semeai worm of owlb which
* can be attacked. If such exists and is considered critical, we
* declare victory. If it's not considered critical we add the
* attacking move as a high priority move to try.
*/
{
int upos;
for (sworm = 0; sworm < s_worms; sworm++) {
critical_semeai_worms[sworm] = 0;
if (board[semeai_worms[sworm]] == other) {
int acode = attack(semeai_worms[sworm], &upos);
if (acode == WIN
&& semeai_trust_tactical_attack(semeai_worms[sworm])
&& important_semeai_worms[sworm]) {
*resulta = WIN;
*resultb = WIN;
*move = upos;
sgf_dumptree = save_sgf_dumptree;
count_variations = save_count_variations;
SGFTRACE_SEMEAI(upos, WIN, WIN, "tactical win found");
READ_RETURN_SEMEAI(SEMEAI, apos, bpos, depth - stackp,
move, upos, WIN, WIN);
}
else if (acode != 0
&& find_defense(semeai_worms[sworm], NULL)) {
critical_semeai_worms[sworm] = 1;
owl_add_move(moves, upos, 105, "attack semeai worm",
SAME_DRAGON_MAYBE_CONNECTED,
NO_MOVE, 0, NO_MOVE, MAX_SEMEAI_MOVES, NULL);
TRACE("Added %1m %d (-1)\n", upos, 105);
}
else if (acode == WIN
&& important_semeai_worms[sworm]) {
owl_add_move(moves, upos, 100, "attack semeai worm",
SAME_DRAGON_MAYBE_CONNECTED,
NO_MOVE, 0, NO_MOVE, MAX_SEMEAI_MOVES, NULL);
TRACE("Added %1m %d (-1)\n", upos, 100);
}
}
}
/* Look for a tactical rescue. If a semeai worm of owla is tactically
* threatened, try to save it.
*/
we_might_be_inessential = 1;
for (sworm = 0; sworm < s_worms; sworm++)
if (board[semeai_worms[sworm]] == color) {
if (important_semeai_worms[sworm])
we_might_be_inessential = 0;
if (attack(semeai_worms[sworm], NULL)
&& find_defense(semeai_worms[sworm], &upos)) {
critical_semeai_worms[sworm] = 1;
owl_add_move(moves, upos, 85, "defend semeai worm",
SAME_DRAGON_MAYBE_CONNECTED, NO_MOVE,
0, NO_MOVE, MAX_SEMEAI_MOVES, NULL);
TRACE("Added %1m %d (0)\n", upos, 85);
}
}
}
/* We generate the candidate moves. During the early stages of
* the semeai, there may be moves to expand or shrink the
* eyespaces of the two dragons. During the later stages, the
* picture is simplified and reading the semeai is a matter
* of filling liberties until one of the dragons may be removed,
* or a seki results. The first stage we call the owl phase.
*/
if (!owl_phase) {
set_eyevalue(&probable_eyes_a, 0, 0, 0, 0);
set_eyevalue(&probable_eyes_b, 0, 0, 0, 0);
I_have_more_eyes = 0;
}
else {
/* First the vital moves. These include moves to attack or
* defend the eyespace (e.g. nakade, or hane to reduce the
* number of eyes) or moves to capture a lunch.
*/
int eyemin_a;
int eyemin_b;
int eyemax_a;
int eyemax_b;
const char *live_reasona;
const char *live_reasonb;
/* We do not wish for any string of the 'b' dragon to be
* counted as a lunch of the 'a' dragon since owl_determine_life
* can give a wrong result in the case of a semeai. So we eliminate
* such lunches.
*/
owl_find_lunches(owla);
owl_find_lunches(owlb);
for (k = 0; k < MAX_LUNCHES; k++) {
if (owla->lunch[k] != NO_MOVE
&& owlb->goal[owla->lunch[k]]) {
owla->lunch[k] = NO_MOVE;
}
}
#if 1
for (k = 0; k < MAX_LUNCHES; k++) {
if (owlb->lunch[k] != NO_MOVE
&& owla->goal[owlb->lunch[k]]) {
owlb->lunch[k] = NO_MOVE;
}
}
#endif
if (owl_estimate_life(owla, owlb, vital_defensive_moves,
&live_reasona, 0, &probable_eyes_a,
&eyemin_a, &eyemax_a))
I_look_alive = 1;
else if (stackp > 2 && owl_escape_route(owla) >= 5) {
live_reasona = "escaped";
I_look_alive = 1;
}
if (owl_estimate_life(owlb, owla, vital_offensive_moves,
&live_reasonb, 1, &probable_eyes_b,
&eyemin_b, &eyemax_b))
you_look_alive = 1;
else if (stackp > 2 && owl_escape_route(owlb) >= 5) {
live_reasonb = "escaped";
you_look_alive = 1;
}
if (verbose) {
gprintf("probable_eyes_a: %s eyemin: %d eyemax: %d",
eyevalue_to_string(&probable_eyes_a), eyemin_a, eyemax_a);
if (I_look_alive)
gprintf("%o I look alive (%s)", live_reasona);
gprintf("%o\n");
gprintf("probable_eyes_b: %s eyemin: %d eyemax: %d",
eyevalue_to_string(&probable_eyes_b), eyemin_b, eyemax_b);
if (you_look_alive)
gprintf("%o you look alive(%s)", live_reasonb);
gprintf("%o\n");
}
/* Stop here if both look certain to live. */
if (I_look_alive && you_look_alive) {
*resulta = WIN;
*resultb = 0;
*move = PASS_MOVE;
sgf_dumptree = save_sgf_dumptree;
count_variations = save_count_variations;
TRACE("Both live\n");
SGFTRACE_SEMEAI(PASS_MOVE, WIN, 0, "Both live");
READ_RETURN_SEMEAI(SEMEAI, apos, bpos, depth - stackp,
move, PASS_MOVE, WIN, 0);
}
/* Next the shape moves. */
if (!I_look_alive) {
owl_shapes(&shape_defensive_patterns, shape_defensive_moves, color,
owla, &owl_defendpat_db);
for (k = 0; k < MAX_MOVES-1; k++)
if (!get_next_move_from_list(&shape_defensive_patterns, color,
shape_defensive_moves, 1, owla))
break;
}
else
shape_defensive_moves[0].pos = NO_MOVE;
if (!you_look_alive) {
owl_shapes(&shape_offensive_patterns, shape_offensive_moves, color,
owlb, &owl_attackpat_db);
for (k = 0; k < MAX_MOVES-1; k++)
if (!get_next_move_from_list(&shape_offensive_patterns, color,
shape_offensive_moves, 1, owlb))
break;
}
else
shape_offensive_moves[0].pos = NO_MOVE;
/* Filter out moves, which fill our eye (and not split it). */
if (eyemax_a > 0) {
remove_eye_filling_moves(owla, vital_defensive_moves);
remove_eye_filling_moves(owla, vital_offensive_moves);
remove_eye_filling_moves(owla, shape_defensive_moves);
remove_eye_filling_moves(owla, shape_offensive_moves);
}
/* Now we review the moves already considered, while collecting
* them into a single list.
*/
if (!I_look_alive) {
semeai_review_owl_moves(vital_defensive_moves, owla, owlb, color,
&safe_outside_liberty_found,
&safe_common_liberty_found,
&riskless_move_found,
mw, moves, 0, 30,
critical_semeai_worms);
semeai_review_owl_moves(shape_defensive_moves, owla, owlb, color,
&safe_outside_liberty_found,
&safe_common_liberty_found,
&riskless_move_found,
mw, moves, 0, 0,
critical_semeai_worms);
}
if (!you_look_alive) {
semeai_review_owl_moves(vital_offensive_moves, owla, owlb, color,
&safe_outside_liberty_found,
&safe_common_liberty_found,
&riskless_move_found,
mw, moves, 1, 30,
critical_semeai_worms);
semeai_review_owl_moves(shape_offensive_moves, owla, owlb, color,
&safe_outside_liberty_found,
&safe_common_liberty_found,
&riskless_move_found,
mw, moves, 1, 0,
critical_semeai_worms);
}
/* If no moves were found so far, also check the eyespaces when
* opponent semeai worms are allowed to be included for vital
* moves.
*/
if (moves[0].pos == NO_MOVE || we_might_be_inessential) {
include_semeai_worms_in_eyespace = 1;
if (!owl_estimate_life(owlb, owla, vital_offensive_moves,
&live_reasonb, 1, &dummy_eyes,
&eyemin_b, &eyemax_b))
semeai_review_owl_moves(vital_offensive_moves, owla, owlb, color,
&safe_outside_liberty_found,
&safe_common_liberty_found,
&riskless_move_found,
mw, moves, 1, 30,
critical_semeai_worms);
include_semeai_worms_in_eyespace = 0;
}
if (eyemin_a == eyemax_a)
/* We have stable number of eyes, so we can try to reduce
* opponent eyes.
*/
I_have_more_eyes = (eyemin_a > min_eyes(&probable_eyes_b));
else {
if (min_eyes(&probable_eyes_a) == max_eyes(&probable_eyes_a))
/* If we can't increase our number of eyes, we try to reduce
* opponent eyes.
*/
I_have_more_eyes = (max_eyes(&probable_eyes_a) > min_eyes(&probable_eyes_b));
else
/* If we can increase our number of eyes, we do it and let
* opponent to increase his.
*/
I_have_more_eyes = (max_eyes(&probable_eyes_a) > max_eyes(&probable_eyes_b));
}
if (get_level() < 8) {
/* If no owl moves were found on two consecutive moves,
* turn off the owl phase.
*/
if (moves[0].pos == NO_MOVE) {
if (owl_phase == 1)
owl_phase = 2;
else if (owl_phase == 2)
owl_phase = 0;
}
else
owl_phase = 1;
}
}
if (1 && verbose) {
showboard(0);
goaldump(owla->goal);
goaldump(owlb->goal);
}
/* Now we look for a move to fill a liberty. This is only
* interesting if the opponent doesn't already have two eyes.
* If we have more eyes, always check for a backfilling move.
*/
if (!you_look_alive
&& !safe_outside_liberty_found
&& (moves[0].value < 110 || I_have_more_eyes)) {
int pos;
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (!ON_BOARD(pos))
continue;
if (board[pos] == EMPTY && !mw[pos]) {
if (liberty_of_goal(pos, owlb)) {
if (!liberty_of_goal(pos, owla)) {
/* outside liberty */
if (safe_move(pos, color) == WIN) {
safe_outside_liberty_found = 1;
outside_liberty.pos = pos;
break;
}
else if (backfill_outside_liberty.pos == NO_MOVE)
backfill_outside_liberty.pos = find_semeai_backfilling_move(bpos,
pos);
}
else {
/* common liberty */
if (safe_move(pos, color) == WIN) {
safe_common_liberty_found = 1;
common_liberty.pos = pos;
}
else if (backfill_common_liberty.pos == NO_MOVE)
backfill_common_liberty.pos = find_semeai_backfilling_move(bpos,
pos);
}
}
}
}
}
/* Add the best liberty filling move available. We first want to
* play outer liberties, second backfilling moves required before
* filling an outer liberty. If no such moves are available we try
* to fill a mutual liberty or play a corresponding backfilling
* move.
*/
if (!you_look_alive) {
if (safe_outside_liberty_found
&& outside_liberty.pos != NO_MOVE) {
move_value = semeai_move_value(outside_liberty.pos,
owla, owlb, 50,
critical_semeai_worms);
owl_add_move(moves, outside_liberty.pos, move_value,
"safe outside liberty", SAME_DRAGON_NOT_CONNECTED,
NO_MOVE, 0, NO_MOVE, MAX_SEMEAI_MOVES, NULL);
riskless_move_found = 1;
TRACE("Added %1m %d (5)\n", outside_liberty.pos, move_value);
}
else if (backfill_outside_liberty.pos != NO_MOVE) {
move_value = semeai_move_value(backfill_outside_liberty.pos,
owla, owlb, 50,
critical_semeai_worms);
owl_add_move(moves, backfill_outside_liberty.pos, move_value,
"backfilling move", SAME_DRAGON_NOT_CONNECTED, NO_MOVE, 0,
NO_MOVE, MAX_SEMEAI_MOVES, NULL);
riskless_move_found = 1;
TRACE("Added %1m %d (6)\n", backfill_outside_liberty.pos, move_value);
}
else if (safe_common_liberty_found
&& common_liberty.pos != NO_MOVE) {
move_value = semeai_move_value(common_liberty.pos,
owla, owlb, 10,
critical_semeai_worms);
owl_add_move(moves, common_liberty.pos, move_value,
"safe common liberty", SAME_DRAGON_MAYBE_CONNECTED,
NO_MOVE, 0, NO_MOVE, MAX_SEMEAI_MOVES, NULL);
if (semeai_is_riskless_move(common_liberty.pos, owla))
riskless_move_found = 1;
TRACE("Added %1m %d (7)\n", common_liberty.pos, move_value);
}
else if (backfill_common_liberty.pos != NO_MOVE) {
move_value = semeai_move_value(backfill_common_liberty.pos,
owla, owlb, 10,
critical_semeai_worms);
owl_add_move(moves, backfill_common_liberty.pos, move_value,
"backfilling move", SAME_DRAGON_NOT_CONNECTED, NO_MOVE, 0,
NO_MOVE, MAX_SEMEAI_MOVES, NULL);
if (semeai_is_riskless_move(backfill_common_liberty.pos, owla))
riskless_move_found = 1;
TRACE("Added %1m %d (6)\n", backfill_common_liberty.pos, move_value);
}
}
if (moves[0].pos == NO_MOVE) {
/* If no move has been found yet, see if we can fill opponent's
* eye (i.e. put more stones in "bulky five" shape).
*/
if (min_eyes(&probable_eyes_b) == 1) {
int move = semeai_propose_eyespace_filling_move(owla, owlb);
if (move) {
owl_add_move(moves, move, 70, "eyespace filling",
SAME_DRAGON_NOT_CONNECTED, NO_MOVE,
0, NO_MOVE, MAX_SEMEAI_MOVES, NULL);
}
}
if (moves[0].pos == NO_MOVE)
TRACE("No move found\n");
}
/* Now we are ready to try moves. Turn on the sgf output ... */
sgf_dumptree = save_sgf_dumptree;
count_variations = save_count_variations;
tested_moves = 0;
for (k = 0; k < MAX_SEMEAI_MOVES; k++) {
int mpos = moves[k].pos;
if (mpos == NO_MOVE)
break;
if (moves[k].value == 0)
continue;
/* Do not try too many moves. */
if (tested_moves > 2
|| (stackp > semeai_branch_depth2 && tested_moves > 1)
|| (stackp > semeai_branch_depth && tested_moves > 0)) {
/* If allpats, try and pop to get the move in the sgf record. */
if (!allpats)
break;
else if (trymove(mpos, color, moves[k].name, apos)) {
semeai_add_sgf_comment(moves[k].value, owl_phase);
popgo();
}
continue;
}
if (count_variations >= semeai_node_limit
|| stackp >= MAX_SEMEAI_DEPTH)
continue;
/* Try playing the move at mpos and call ourselves recursively to
* determine the result obtained by this move.
*/
if (semeai_trymove_and_recurse(apos, bpos, owla, owlb,
owl_phase, mpos, color,
best_resulta == 0 || best_resultb == 0,
moves[k].value, moves[k].name,
moves[k].same_dragon, moves[k].pattern_data,
moves[k].lunch, NULL,
&this_resulta, &this_resultb)) {
tested_moves++;
if (this_resultb == WIN && this_resulta == WIN) {
/* Ideal result, no need to try any more moves. */
*resulta = WIN;
*resultb = WIN;
*move = mpos;
TRACE("After %1m I (%C) am alive, you are dead\n", mpos, color);
SGFTRACE_SEMEAI(mpos, WIN, WIN, moves[k].name);
close_pattern_list(color, &shape_defensive_patterns);
close_pattern_list(color, &shape_offensive_patterns);
READ_RETURN_SEMEAI(SEMEAI, apos, bpos, depth - stackp,
move, mpos, WIN, WIN);
}
/* When there is a choice between ko and seki, the prefer_ko
* variable decides policy. Thus if prefer_ko == color we
* consider attacking the opponent more important than defending
* our dragon, and vise versa otherwise.
*/
else if ((prefer_ko != color
&& (this_resulta > best_resulta
|| (this_resulta == best_resulta
&& this_resultb > best_resultb)))
|| (prefer_ko == color
&& (this_resultb > best_resultb
|| (this_resultb == best_resultb
&& this_resulta > best_resulta)))) {
best_resulta = this_resulta;
best_resultb = this_resultb;
best_move = mpos;
best_move_name = moves[k].name;
}
}
}
close_pattern_list(color, &shape_defensive_patterns);
close_pattern_list(color, &shape_offensive_patterns);
/* If we can't find a move and the opponent looks alive, we have
* lost.
*/
if (best_resulta == 0 && best_resultb == 0 && you_look_alive) {
*resulta = 0;
*resultb = 0;
*move = PASS_MOVE;
SGFTRACE_SEMEAI(PASS_MOVE, 0, 0, "You live, I die");
READ_RETURN_SEMEAI(SEMEAI, apos, bpos, depth - stackp,
move, PASS_MOVE, 0, 0);
}
/* If we didn't find a working move and we look dead even if including the
* opponent stones in our eyespace, we have lost.
*/
if (best_resulta == 0 && best_resultb == 0
&& !riskless_move_found) {
const char *live_reasona;
int eyemin_a;
int eyemax_a;
for (sworm = 0; sworm < s_worms; sworm++) {
if (board[semeai_worms[sworm]] == other) {
if (important_semeai_worms[sworm])
break;
}
}
if (sworm == s_worms) {
include_semeai_worms_in_eyespace = 1;
if (!owl_estimate_life(owla, owlb, vital_defensive_moves,
&live_reasona, 0, &dummy_eyes,
&eyemin_a, &eyemax_a)
&& eyemax_a < 2) {
include_semeai_worms_in_eyespace = 0;
*resulta = 0;
*resultb = 0;
*move = PASS_MOVE;
SGFTRACE_SEMEAI(PASS_MOVE, 0, 0, "You live, I die - 2");
READ_RETURN_SEMEAI(SEMEAI, apos, bpos, depth - stackp,
move, PASS_MOVE, 0, 0);
}
include_semeai_worms_in_eyespace = 0;
}
}
/* If we can't find a useful move and opponent passed, it's seki, unless
* one dragon has more eyes than the other.
*/
if (best_resulta == 0 && best_resultb == 0
&& !riskless_move_found) {
if (pass) {
if (max_eyes(&probable_eyes_a) < min_eyes(&probable_eyes_b)) {
*resulta = 0;
*resultb = 0;
*move = PASS_MOVE;
TRACE("You have more eyes.\n");
SGFTRACE_SEMEAI(PASS_MOVE, 0, 0, "You have more eyes");
READ_RETURN_SEMEAI(SEMEAI, apos, bpos, depth - stackp,
move, PASS_MOVE, 0, 0);
}
else if (max_eyes(&probable_eyes_b) < min_eyes(&probable_eyes_a)) {
*resulta = WIN;
*resultb = WIN;
*move = PASS_MOVE;
TRACE("I have more eyes\n");
SGFTRACE_SEMEAI(PASS_MOVE, WIN, WIN, "I have more eyes");
READ_RETURN_SEMEAI(SEMEAI, apos, bpos, depth - stackp,
move, PASS_MOVE, WIN, WIN);
}
else {
*resulta = WIN;
*resultb = 0;
*move = PASS_MOVE;
TRACE("Seki\n");
SGFTRACE_SEMEAI(PASS_MOVE, WIN, 0, "Seki");
READ_RETURN_SEMEAI(SEMEAI, apos, bpos, depth - stackp,
move, PASS_MOVE, WIN, 0);
}
}
else {
/* No working move was found, but opponent hasn't passed. Then we pass. */
do_owl_analyze_semeai(bpos, apos, owlb, owla,
resultb, resulta, NULL, 1, owl_phase);
*resulta = REVERSE_RESULT(*resulta);
*resultb = REVERSE_RESULT(*resultb);
TRACE("No move found\n");
SGFTRACE_SEMEAI(PASS_MOVE, *resulta, *resultb, "No move found");
*move = PASS_MOVE;
READ_RETURN_SEMEAI(SEMEAI, apos, bpos, depth - stackp,
move, PASS_MOVE, *resulta, *resultb);
}
}
/* There are a few selected cases where we should try to see if it
* would be better to pass rather than playing any move in the semeai.
*
* A first simple example is the case of positions where there is nothing
* left to play but common liberties. In case the above analysis concluded
* the result is seki and if the best (and only) move happens to be a
* common liberty, we attempt to pass, so that the engine considers tenuki
* as a viable option in case it actually is.
*
* Another example is related to "disturbing" kos.
*
* .OOOOOOOO. In this position (similar to semeai:130), X has just taken
* OOXXXXXXOO the ko on the left. The semeai code finds the ko recapture
* OX.XXOOXXO as the only attacking move and concludes the result is KO_B.
* OOXX.OO.XO
* ----------
*
* In such cases too, we try to pass to see if it doesn't actually yield
* a better result.
*
* FIXME: there might be more cases where passing would be valuable.
*/
if (!pass && k == 1) {
if ((best_resulta == WIN && best_resultb == 0
&& best_move != NO_MOVE
&& best_move == common_liberty.pos)
|| (best_resulta == KO_B && best_resultb == KO_B
&& is_ko(best_move, owla->color, NULL))) {
do_owl_analyze_semeai(bpos, apos, owlb, owla, &this_resultb,
&this_resulta, NULL, 1, owl_phase);
if (REVERSE_RESULT(this_resulta) >= best_resulta
&& REVERSE_RESULT(this_resultb) >= best_resultb) {
best_move = PASS_MOVE;
best_resulta = REVERSE_RESULT(this_resulta);
best_resultb = REVERSE_RESULT(this_resultb);
best_move_name = "Pass";
}
}
}
*resulta = best_resulta;
*resultb = best_resultb;
if (best_resulta == 0)
best_move = PASS_MOVE;
*move = best_move;
SGFTRACE_SEMEAI(best_move, best_resulta, best_resultb, best_move_name);
READ_RETURN_SEMEAI(SEMEAI, apos, bpos, depth - stackp,
move, best_move, best_resulta, best_resultb);
}
/* Play a move, update goal and boundaries appropriately, and call
* do_owl_analyze_semeai() recursively to determine the result of this
* move.
*/
static int
semeai_trymove_and_recurse(int apos, int bpos, struct local_owl_data *owla,
struct local_owl_data *owlb,
int owl_phase,
int move, int color, int ko_allowed,
int move_value, const char *move_name,
enum same_dragon_value same_dragon,
struct matched_pattern_data *pattern_data,
int lunch, int *semeai_move,
int *this_resulta, int *this_resultb)
{
int ko_move = 0;
gg_assert(this_resulta != NULL && this_resultb != NULL);
*this_resulta = 0;
*this_resultb = 0;
if (!komaster_trymove(move, color, move_name, apos, &ko_move, ko_allowed)) {
int kpos;
if (is_ko(move, color, &kpos)) {
/* Move was not allowed because of komaster. We want to check
* if this situation is double ko and when it is, we won semeai.
*/
int libs[MAX_LIBERTIES];
int n;
int nlib;
int sworm;
int worm_color;
int other = OTHER_COLOR(color);
for (sworm = 0; sworm < s_worms; sworm++) {
worm_color = board[semeai_worms[sworm]];
if (worm_color == color) {
/* We only check up to MAX_LIBERTIES, due to performance
* reasons. When we have more liberties we have some outside
* liberties to fill and these moves will be tried later
* (and double ko situation will be found).
*/
nlib = findlib(semeai_worms[sworm], MAX_LIBERTIES, libs);
if (nlib > MAX_LIBERTIES)
return 0;
for (n = 0; n < nlib; n++)
if (is_ko(libs[n], other, NULL)) {
/* Check if situation is not a nested ko capture. */
if (DIAGONAL_NEIGHBORS(libs[n], kpos))
return 0;
/* Our dragon has double ko, but we have to check if
* opponent dragon doesn't have outside liberties or
* double ko.
*/
*this_resulta = WIN;
*this_resultb = WIN;
}
}
else if (worm_color == other) {
if (countlib(semeai_worms[sworm]) > 2)
/* In double ko situation the opponent can have only a
* single eye and a ko outside liberty to be sure that we
* will always win double ko.
*/
return 0;
}
}
if (*this_resulta == WIN)
return 1;
}
return 0;
}
semeai_add_sgf_comment(move_value, owl_phase);
TRACE("Trying %C %1m. Current stack: ", color, move);
if (verbose) {
dump_stack();
goaldump(owla->goal);
gprintf("\n");
goaldump(owlb->goal);
gprintf("\n");
}
TRACE("%s, value %d, same_dragon %d\n", move_name, move_value, same_dragon);
push_owl(&owla);
push_owl(&owlb);
if (owla->color == color) {
owl_update_goal(move, same_dragon, lunch, owla, 1, pattern_data);
owl_update_boundary_marks(move, owlb);
}
else {
owl_update_goal(move, same_dragon, lunch, owlb, 1, pattern_data);
owl_update_boundary_marks(move, owla);
}
mark_goal_in_sgf(owla->goal);
mark_goal_in_sgf(owlb->goal);
/* Do a recursive call to read the semeai after the move we just
* tried. If dragon b was captured by the move, call
* do_owl_attack() to see whether it sufficed for us to live.
*/
if (board[bpos] == EMPTY) {
/* FIXME: Are all owl_data fields and relevant static
* variables properly set up for a call to do_owl_attack()?
*/
*this_resulta = REVERSE_RESULT(do_owl_attack(apos, semeai_move, NULL, owla, 0));
*this_resultb = *this_resulta;
}
else {
do_owl_analyze_semeai(bpos, apos, owlb, owla,
this_resultb, this_resulta, semeai_move,
0, owl_phase);
*this_resulta = REVERSE_RESULT(*this_resulta);
*this_resultb = REVERSE_RESULT(*this_resultb);
}
pop_owl(&owlb);
pop_owl(&owla);
popgo();
/* Does success require ko? */
if (ko_move) {
if (*this_resulta != 0)
*this_resulta = KO_B;
if (*this_resultb != 0)
*this_resultb = KO_B;
}
if (count_variations >= semeai_node_limit) {
TRACE("Out of nodes, claiming win.\n");
result_certain = 0;
*this_resulta = WIN;
*this_resultb = WIN;
}
return 1;
}
/* Add details in sgf file about move value and whether owl_phase is active. */
static void
semeai_add_sgf_comment(int value, int owl_phase)
{
char buf[100];
if (!sgf_dumptree)
return;
if (owl_phase)
gg_snprintf(buf, 100, "value %d, owl_phase", value);
else
gg_snprintf(buf, 100, "value %d", value);
sgftreeAddComment(sgf_dumptree, buf);
}
/* In semeai situations tactical attacks often cannot be trusted. This
* in particular holds for strings with three or more liberties. Two
* liberties can usually be trusted, but if neither liberty can be
* played immediately, the need for backfilling moves gives an
* effective liberty count of more than two, again making the attack
* untrustworthy.
*
* This function decides whether an attack should be trusted. It does
* not check whether there actually is an attack, though.
*/
static int
semeai_trust_tactical_attack(int str)
{
int liberties;
int libs[3];
int other = OTHER_COLOR(board[str]);
liberties = findlib(str, 3, libs);
if (liberties > 2)
return 0;
if (liberties < 2)
return 1;
if (!is_self_atari(libs[0], other)
|| !is_self_atari(libs[1], other))
return 1;
return 0;
}
/* A move is deemed riskless (i.e., doesn't kill ourself in a seki situation)
* if it doesn't decrease the liberty count of any goal string of our
* dragon.
*/
static int
semeai_is_riskless_move(int move, struct local_owl_data *owla)
{
int k;
int liberties = accuratelib(move, owla->color, MAXLIBS, NULL);
if (!liberty_of_goal(move, owla))
return 1;
for (k = 0; k < 4; k++) {
int pos = move + delta[k];
if (board[pos] == owla->color
&& owla->goal[pos]
&& countlib(pos) > liberties)
return 0;
}
return 1;
}
/* Review the moves in owl_moves[] and add them into semeai_moves[].
* This is used to merge multiple sets of owl moves into one move
* list, while revising the values for use in semeai reading.
*
* We also record whether the moves include an outer or common liberty
* in the semeai.
*/
static void
semeai_review_owl_moves(struct owl_move_data owl_moves[MAX_MOVES],
struct local_owl_data *owla,
struct local_owl_data *owlb, int color,
int *safe_outside_liberty_found,
int *safe_common_liberty_found,
int *riskless_move_found,
signed char mw[BOARDMAX],
struct owl_move_data semeai_moves[MAX_SEMEAI_MOVES],
int guess_same_dragon, int value_bonus,
int *critical_semeai_worms)
{
int move;
int move_value;
enum same_dragon_value same_dragon;
struct matched_pattern_data *pattern_data = NULL;
int k;
for (k = 0; k < MAX_MOVES-1; k++) {
move = owl_moves[k].pos;
if (move == NO_MOVE)
break;
if (owl_moves[k].value == 0)
continue;
/* Does the move fill a liberty in the semeai? */
if (liberty_of_goal(move, owlb)
&& safe_move(move, color)) {
if (!liberty_of_goal(move, owla))
*safe_outside_liberty_found = 1;
else
*safe_common_liberty_found = 1;
}
if (is_legal(move, color) && !is_ko(move, color, NULL)
&& semeai_is_riskless_move(move, owla))
*riskless_move_found = 1;
/* For some types of owl moves we don't have same_dragon
* information recorded and need to guess.
*/
if (guess_same_dragon) {
if (liberty_of_goal(move, owla)
|| second_liberty_of_goal(move, owla))
same_dragon = SAME_DRAGON_MAYBE_CONNECTED;
else
same_dragon = SAME_DRAGON_NOT_CONNECTED;
}
else {
same_dragon = owl_moves[k].same_dragon;
pattern_data = owl_moves[k].pattern_data;
}
mw[move] = 1;
move_value = (semeai_move_value(move, owla, owlb, owl_moves[k].value,
critical_semeai_worms)
+ value_bonus);
owl_add_move(semeai_moves, move, move_value, owl_moves[k].name,
same_dragon, NO_MOVE, owl_moves[k].escape,
NO_MOVE, MAX_SEMEAI_MOVES, pattern_data);
TRACE("Added %1m %d\n", move, move_value);
}
}
/* Propose an eyespace filling move. Such a move can, for instance,
* add a stone to opponent's "bulky five" shape. We of course choose
* a move that doesn't allow opponent to turn his dead eyeshape into a
* two eyes eyeshape. E.g. in this position, the function will
* propose the move at '*', not at the '.':
*
* XXX
* XXOX
* XOOX
* X.*X
* ----
*/
static int
semeai_propose_eyespace_filling_move(struct local_owl_data *owla,
struct local_owl_data *owlb)
{
int color = OTHER_COLOR(owlb->color);
int pos;
int mw[BOARDMAX];
int mz[BOARDMAX];
owl_find_relevant_eyespaces(owlb, mw, mz);
/* Never try to fill opponent's eyes which contain our dragon. This
* is nothing else than suicide.
*/
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (ON_BOARD(pos) && owla->goal[pos])
mw[owlb->my_eye[pos].origin] = 0;
}
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (board[pos] == EMPTY) {
int origin = owlb->my_eye[pos].origin;
if (mw[origin] > 1
&& min_eyes(&owlb->my_eye[origin].value) == 1) {
int good_move = 0;
if (trymove(pos, color, "eyespace_filling", NO_MOVE)) {
struct eyevalue new_value;
int dummy_attack;
int dummy_defense;
compute_eyes(origin, &new_value, &dummy_attack, &dummy_defense,
owlb->my_eye, owlb->half_eye, 0);
if (max_eyes(&new_value) <= 1)
good_move = 1;
popgo();
}
if (good_move)
return pos;
}
}
}
return NO_MOVE;
}
/* Try to estimate the value of a semeai move. This has two
* components. The first is the change in the total number of
* liberties for strings involved in the semeai. The second is a bonus
* for attacks and defenses of critical semeai worms.
*/
static int
semeai_move_value(int move, struct local_owl_data *owla,
struct local_owl_data *owlb,
int raw_value, int *critical_semeai_worms)
{
int pos;
int net = 0;
int color = owla->color;
int save_verbose = verbose;
int k;
int bonus = 0;
ASSERT1(board[move] == EMPTY, move);
verbose = 0;
if (safe_move(move, color)) {
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (IS_STONE(board[pos])
&& pos == find_origin(pos)) {
int count_lib = -1;
if (owla->goal[pos]) {
count_lib = countlib(pos);
net -= 75 * count_lib;
}
if (owlb->goal[pos]) {
if (count_lib < 0)
count_lib = countlib(pos);
net += 100 * count_lib;
}
}
}
if (!trymove(move, color, NULL, 0)) {
verbose = save_verbose;
return 0;
}
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (IS_STONE(board[pos])
&& pos == find_origin(pos)) {
int count_lib = -1;
if (owla->goal[pos]
|| (pos == move && liberty_of_goal(move, owla))) {
count_lib = countlib(pos);
net += 75 * count_lib;
}
if (owlb->goal[pos]) {
if (count_lib < 0)
count_lib = countlib(pos);
net -= 100 * count_lib;
}
}
}
increase_depth_values();
for (k = 0; k < s_worms; k++) {
if (!critical_semeai_worms[k])
continue;
if (board[semeai_worms[k]] == color
&& !attack(semeai_worms[k], NULL))
bonus += 50;
else if (board[semeai_worms[k]] == OTHER_COLOR(color)
&& !find_defense(semeai_worms[k], NULL))
bonus += 50;
}
decrease_depth_values();
popgo();
}
verbose = save_verbose;
if (net < 0)
net = 0;
net /= 25;
net *= 3;
return raw_value + net + bonus;
}
/* Remove all moves from the list that would fill our own eye. */
static void
remove_eye_filling_moves(struct local_owl_data *our_owl,
struct owl_move_data *moves)
{
int k;
int color = our_owl->color;
for (k = 0; k < MAX_MOVES; k++) {
if (moves[k].pos == NO_MOVE)
break;
else {
struct eye_data *eye = &our_owl->my_eye[moves[k].pos];
/* If esize==1 this eye must not be a real eye (at least one
* worm is capturable, otherwise this move would not be
* proposed).
*/
if (eye->color == color && eye->msize == 0 && eye->neighbors <= 1
&& eye->esize != 1
&& our_owl->half_eye[moves[k].pos].type != HALF_EYE
&& !has_neighbor(moves[k].pos, OTHER_COLOR(color)))
moves[k].value = 0;
}
}
}
/* Is the vertex at pos adjacent to an element of the owl goal? */
static int
liberty_of_goal(int pos, struct local_owl_data *owl)
{
int k;
for (k = 0; k < 4; k++)
if (IS_STONE(board[pos + delta[k]]) && owl->goal[pos + delta[k]])
return 1;
return 0;
}
/* Is the vertex at pos a second liberty of the owl goal? */
static int
second_liberty_of_goal(int pos, struct local_owl_data *owl)
{
int k;
for (k = 0; k < 4; k++)
if (board[pos + delta[k]] == EMPTY && liberty_of_goal(pos + delta[k], owl))
return 1;
return 0;
}
/* 'liberty' is a liberty of 'worm' which we would like to fill.
* However it is not safe to play there, so we look for a
* backfilling move. For example in this situation:
*
* ------+
* O.OaXc|
* OOOOOX|
* XXXXXb|
* ......|
*
* If 'worm' is the O string and 'liberty' is 'a', the
* function returns 'b'. To fill at 'a', X must first
* fill 'b' and 'c' and it is better to fill at 'b' first
* since that will sometimes leave fewer or smaller ko threats.
*
* Returns NO_MOVE if no move is found.
*/
static int
find_semeai_backfilling_move(int worm, int liberty)
{
int color = board[worm];
int other = OTHER_COLOR(color);
int result = NO_MOVE;
if (safe_move(liberty, other) == WIN)
return liberty;
if (is_self_atari(liberty, other)) {
int fill;
if (approxlib(liberty, other, 1, &fill) > 0
&& trymove(fill, other, "find_semeai_backfilling_move", worm)) {
if (safe_move(liberty, other))
result = fill;
else if (board[worm] != EMPTY)
result = find_semeai_backfilling_move(worm, liberty);
popgo();
}
}
if (ON_BOARD(result) && safe_move(result, other))
return result;
else
return NO_MOVE;
}
/* Some helper function for do_owl_attack/defend. */
static int
reading_limit_reached(const char **live_reason, int this_variation_number)
{
/* If (stackp > owl_reading_depth), interpret deep reading
* conservatively as escape.
*/
if (stackp > owl_reading_depth) {
TRACE("%oVariation %d: ALIVE (maximum reading depth reached)\n",
this_variation_number);
*live_reason = "max reading depth reached";
return 1;
}
/* If the owl node limit has been reached, assume the dragon has
* managed to escape.
*/
if (local_owl_node_counter >= owl_node_limit) {
result_certain = 0;
TRACE("%oVariation %d: ALIVE (owl node limit reached)\n",
this_variation_number);
*live_reason = "owl node limit reached";
return 1;
}
return 0;
}
static void
clear_owl_move_data(struct owl_move_data moves[MAX_MOVES])
{
int k;
for (k = 0; k < MAX_MOVES; k++) {
moves[k].pos = NO_MOVE;
moves[k].value = -1;
moves[k].name = NULL;
moves[k].same_dragon = SAME_DRAGON_CONNECTED;
moves[k].escape = 0;
moves[k].lunch = NO_MOVE;
moves[k].pattern_data = NULL;
clear_cut_list(moves[k].cuts);
}
}
static void
set_single_owl_move(struct owl_move_data moves[MAX_MOVES],
int pos, const char *name)
{
moves[0].pos = pos;
moves[0].value = 25;
moves[0].name = name;
moves[0].same_dragon = SAME_DRAGON_MAYBE_CONNECTED;
moves[0].escape = 0;
moves[0].lunch = NO_MOVE;
moves[0].pattern_data = NULL;
clear_cut_list(moves[0].cuts);
moves[1].value = 0;
}
/* Returns true if a move can be found to attack the dragon
* at (target), in which case (*attack_point) is the recommended move.
* (attack_point) can be a null pointer if only the result is needed.
*
* The array goal marks the extent of the dragon. This must
* be maintained during reading. Call this function only when
* stackp==0; otherwise you can call do_owl_attack but you must
* set up the goal and boundary arrays by hand first.
*
* Returns KO_A or KO_B if the position is ko:
*
* - Returns KO_A if the attack prevails provided attacker is willing to
* ignore any ko threat (the attacker makes the first ko capture).
*
* - Returns KO_B if attack succeeds provided attacker has a ko threat
* which must be answered (the defender makes the first ko capture).
*
* If GNU Go is compiled with `configure --enable-experimental-owl-ext'
* then a return codes of GAIN is also possible.
*
* - Returns GAIN if the attack fails but another worm of the
* opponent's is captured in during the failed attack. The location
* of the killed worm is returned through the *kworm field.
*
* */
int
owl_attack(int target, int *attack_point, int *certain, int *kworm)
{
int result;
struct local_owl_data *owl;
int reading_nodes_when_called = get_reading_node_counter();
double start = 0.0;
int tactical_nodes;
int move = NO_MOVE;
int wpos = NO_MOVE;
int wid = MAX_GOAL_WORMS;
result_certain = 1;
if (worm[target].unconditional_status == DEAD) {
if (attack_point)
*attack_point = NO_MOVE;
if (kworm)
*kworm = NO_MOVE;
if (certain)
*certain = 1;
return 1;
}
if (search_persistent_owl_cache(OWL_ATTACK, target, 0, 0, &result,
attack_point, kworm, certain))
return result;
if (debug & DEBUG_OWL_PERFORMANCE)
start = gg_cputime();
TRACE("owl_attack %1m\n", target);
init_owl(&owl, target, NO_MOVE, NO_MOVE, 1, NULL);
owl_make_domains(owl, NULL);
prepare_goal_list(target, owl, owl_goal_worm, &goal_worms_computed,
kworm, 1);
result = do_owl_attack(target, &move, &wid, owl, 0);
finish_goal_list(&goal_worms_computed, &wpos, owl_goal_worm, wid);
tactical_nodes = get_reading_node_counter() - reading_nodes_when_called;
DEBUG(DEBUG_OWL_PERFORMANCE,
"owl_attack %1m, result %d %1m (%d, %d nodes, %f seconds)\n",
target, result, move, local_owl_node_counter,
tactical_nodes, gg_cputime() - start);
store_persistent_owl_cache(OWL_ATTACK, target, 0, 0,
result, move, wpos,
result_certain, tactical_nodes,
owl->goal, board[target]);
if (attack_point)
*attack_point = move;
if (kworm)
*kworm = wpos;
if (certain)
*certain = result_certain;
return result;
}
/* Static function containing the main recursive code for
* owl_attack.
*/
static int
do_owl_attack(int str, int *move, int *wormid,
struct local_owl_data *owl, int escape)
{
int color = board[str];
int other = OTHER_COLOR(color);
struct owl_move_data vital_moves[MAX_MOVES];
struct owl_move_data shape_moves[MAX_MOVES];
struct owl_move_data *moves;
struct matched_patterns_list_data shape_patterns;
signed char mw[BOARDMAX];
int number_tried_moves = 0;
int pass;
int k;
int savemove = 0;
int saveworm = MAX_GOAL_WORMS;
int savecode = 0;
int eyemin = -1; /* Lower bound on the number of eyes. */
int eyemax = -1; /* Upper bound on the number of eyes. */
struct eyevalue probable_eyes; /* Best guess of eyevalue. */
const char *live_reason;
int move_cutoff;
int xpos;
int value1;
int value2;
int this_variation_number = count_variations - 1;
SETUP_TRACE_INFO("owl_attack", str);
shape_patterns.initialized = 0;
str = find_origin(str);
if (tt_get(&ttable, OWL_ATTACK, str, NO_MOVE, depth - stackp, NULL,
&value1, &value2, &xpos) == 2) {
TRACE_CACHED_RESULT(value1, xpos);
if (move)
*move = xpos;
if (value1 == GAIN) {
if (wormid) {
if (goal_worms_computed)
*wormid = value2;
else
*wormid = MAX_GOAL_WORMS;
}
}
if (value1 == WIN)
TRACE("%oVariation %d: DEAD (cached)\n", this_variation_number);
else
TRACE("%oVariation %d: ALIVE (cached)\n", this_variation_number);
SGFTRACE(xpos, value1, "cached");
return value1;
}
/* If reading goes to deep or we run out of nodes, we assume life. */
if (reading_limit_reached(&live_reason, this_variation_number)) {
SGFTRACE(0, 0, live_reason);
READ_RETURN(OWL_ATTACK, str, depth - stackp, move, 0, 0);
}
memset(mw, 0, sizeof(mw));
global_owl_node_counter++;
local_owl_node_counter++;
current_owl_data = owl;
memset(owl->safe_move_cache, 0, sizeof(owl->safe_move_cache));
/* First see whether there is any chance to kill. */
if (owl_estimate_life(owl, NULL, vital_moves, &live_reason, 1,
&probable_eyes, &eyemin, &eyemax)) {
/*
* We need to check here if there's a worm under atari. If yes,
* locate it and report a (gote) GAIN.
*/
int acode = 0;
int mpos = NO_MOVE;
if (experimental_owl_ext && goal_worms_computed) {
int size = 0;
saveworm = MAX_GOAL_WORMS;
for (k = 0; k < MAX_GOAL_WORMS; k++) {
if (owl_goal_worm[k] == NO_MOVE)
break;
if (board[owl_goal_worm[k]] == EMPTY
|| countlib(owl_goal_worm[k]) > 1)
continue;
if (worm[owl_goal_worm[k]].size > size) {
saveworm = k;
size = worm[owl_goal_worm[k]].size;
}
}
if (saveworm != MAX_GOAL_WORMS && size >= 3) {
acode = GAIN;
findlib(worm[owl_goal_worm[saveworm]].origin, 1, &mpos);
/* ASSERT1( ... */
}
}
SGFTRACE(0, acode, live_reason);
TRACE("%oVariation %d: ALIVE (%s)\n", this_variation_number, live_reason);
if (acode == 0) {
READ_RETURN(OWL_ATTACK, str, depth - stackp, move, 0, 0);
}
else {
if (wormid)
*wormid = saveworm;
READ_RETURN2(OWL_ATTACK, str, depth - stackp,
move, mpos, acode, saveworm);
}
}
/* We try moves in five passes.
* stackp==0 stackp>0
* 0. Vital moves in the interval [70..] [45..]
* 1. Shape moves
* 2. Vital moves in the interval [..69] [..44]
* 3. Tactical attack moves (except certain kos)
* 4. Moves found by the defender
* 5. Tactical ko attack moves which were not tried in pass 3
*/
for (pass = 0; pass < 6; pass++) {
moves = NULL;
move_cutoff = 1;
current_owl_data = owl;
/* Get the shape moves if we are in the right pass. */
switch (pass) {
case 1:
if (stackp > owl_branch_depth && number_tried_moves > 0)
continue;
owl_shapes(&shape_patterns, shape_moves, other, owl, &owl_attackpat_db);
moves = shape_moves;
break;
case 0:
case 2:
if (stackp > owl_branch_depth && number_tried_moves > 0)
continue;
moves = vital_moves;
if (pass == 0 || stackp > owl_distrust_depth) {
if (stackp == 0)
move_cutoff = 70;
else
move_cutoff = 45;
}
if (eyemax < 2 && stackp > 2)
move_cutoff = 99; /* Effectively disable vital moves. */
break;
case 3:
case 5:
{
/* Look for a tactical attack. This is primarily intended for
* the case where the whole dragon is a single string, therefore
* we only look at the string at the "origin".
*
* We must be wary with attacks giving ko. Unless the dragon
* otherwise looks alive, this may turn a dead dragon into one
* which can live by ko. Such moves will be tried anyway in
* pass 5. Notice though that we can only reach there if an owl
* defense was found in pass 4.
*/
int apos;
int result;
SGFTree *save_sgf_dumptree = sgf_dumptree;
int save_count_variations = count_variations;
sgf_dumptree = NULL;
count_variations = 0;
result = attack(str, &apos);
if (result == WIN
|| (result != 0 && (min_eyes(&probable_eyes) >= 2
|| pass == 5))) {
set_single_owl_move(shape_moves, apos, "tactical attack");
moves = shape_moves;
}
sgf_dumptree = save_sgf_dumptree;
count_variations = save_count_variations;
}
break;
/* If we found no move in the first four passes we ask the defender
* for a move suggestion.
*/
case 4:
if (number_tried_moves == 0) {
int dpos;
int dcode = do_owl_defend(str, &dpos, NULL, owl, escape);
/* No defense, we won. */
if (dcode == 0) {
TRACE("%oVariation %d: DEAD (no defense)\n",
this_variation_number);
SGFTRACE(0, WIN, "no defense");
close_pattern_list(other, &shape_patterns);
READ_RETURN(OWL_ATTACK, str, depth - stackp, move, 0, WIN);
}
else if (dpos != NO_MOVE) {
/* The dragon could be defended by one more move. Try to
* attack with this move.
*
* If the move is suicide for us, try to find a backfilling
* move to play instead. Do this also if the move is a
* send-two-return-one sacrifice.
*/
const char *name = "defense move";
SGFTree *save_sgf_dumptree = sgf_dumptree;
int save_count_variations = count_variations;
sgf_dumptree = NULL;
count_variations = 0;
if (is_suicide(dpos, other) || send_two_return_one(dpos, other)) {
int dpos2;
for (k = 0; k < 4; k++) {
if (board[dpos + delta[k]] == other
&& find_defense(dpos + delta[k], &dpos2)) {
dpos = dpos2;
name = "defense move (backfill)";
break;
}
}
}
sgf_dumptree = save_sgf_dumptree;
count_variations = save_count_variations;
if (dpos != NO_MOVE) {
set_single_owl_move(shape_moves, dpos, name);
moves = shape_moves;
}
}
}
break;
} /* switch (pass) */
/* FIXME: This block probably should reappear somewhere in this
* function.
*/
#if 0
/* First test whether the dragon has escaped. */
if (owl_escape_route(owl) >= 5) {
/* FIXME: We probably should make distinction in the returned
* result whether the dragon lives by making two eyes or by
* escaping.
*/
TRACE("%oVariation %d: ALIVE (escaped)\n", this_variation_number);
SGFTRACE(0, 0, "escaped");
close_pattern_list(other, &shape_patterns);
READ_RETURN0(OWL_ATTACK, str, depth - stackp);
}
#endif
if (!moves)
continue;
/* For the up to MAX_MOVES best moves with value equal to
* move_cutoff or higher, try to attack the dragon and see if it
* can then be defended.
*/
for (k = 0; k < MAX_MOVES; k++) {
int mpos;
int ko_move = -1;
int origin = NO_MOVE;
int captured;
int wid = MAX_GOAL_WORMS;
int dcode;
/* Consider only the highest scoring move if we're deeper than
* owl_branch_depth.
*
* FIXME: To behave as intended, k should be replaced by
* number_tried_moves.
*/
if (stackp > owl_branch_depth && k > 0)
break;
current_owl_data = owl;
/* Shape moves are selected on demand. */
if (pass == 1) {
if (!get_next_move_from_list(&shape_patterns, other,
shape_moves, move_cutoff, owl))
break;
}
else
if (moves[k].value < move_cutoff)
break;
mpos = moves[k].pos;
ASSERT_ON_BOARD1(mpos);
/* Have we already tested this move? */
if (mw[mpos])
continue;
captured = (color == WHITE ? white_captured : black_captured);
/* Try to make the move. */
if (!komaster_trymove(mpos, other, moves[k].name, str,
&ko_move, savecode == 0))
continue;
captured = (color == WHITE ? white_captured : black_captured) - captured;
TRACE("Trying %C %1m. Escape = %d. Current stack: ",
other, mpos, escape);
if (verbose)
dump_stack();
/* We have now made a move. Analyze the new position. */
push_owl(&owl);
mw[mpos] = 1;
number_tried_moves++;
owl_update_boundary_marks(mpos, owl);
/* If the origin of the dragon has been captured, we look
* for another string which was part of the original dragon,
* marked when stackp==0, which has not been captured. If no
* such string is found, owl_attack declares victory.
*/
if (IS_STONE(board[str]))
origin = str;
else
origin = select_new_goal_origin(NO_MOVE, owl);
/* Test whether the move cut the goal dragon apart. */
if (moves[k].cuts[0] != NO_MOVE && origin != NO_MOVE) {
owl_test_cuts(owl->goal, owl->color, moves[k].cuts);
if (!owl->goal[origin])
origin = select_new_goal_origin(origin, owl);
}
mark_goal_in_sgf(owl->goal);
if (origin == NO_MOVE)
dcode = 0;
else
dcode = do_owl_defend(origin, NULL, &wid, owl, escape);
if (!ko_move) {
if (dcode == 0) {
pop_owl(&owl);
popgo();
if (sgf_dumptree) {
const char *wintxt;
char winstr[192];
if (origin == NO_MOVE)
wintxt = "all original stones captured";
else
wintxt = "attack effective";
sprintf(winstr, "%s)\n (%d variations", wintxt,
count_variations - this_variation_number);
SGFTRACE(mpos, WIN, winstr);
}
close_pattern_list(other, &shape_patterns);
READ_RETURN(OWL_ATTACK, str, depth - stackp, move, mpos, WIN);
}
else if (experimental_owl_ext && dcode == LOSS) {
if (saveworm == MAX_GOAL_WORMS
|| worm[owl_goal_worm[wid]].size
> worm[owl_goal_worm[saveworm]].size)
saveworm = wid;
}
/* The conditions here are set so that this code doesn't get
* triggered when the capture is immediate (the tactical
* reading code should take care of these).
*/
else if (experimental_owl_ext && goal_worms_computed
#if 0
&& stackp > 1
#endif
&& captured >= 3) {
int w = MAX_GOAL_WORMS;
int size = 0;
int l;
/* locate the biggest captured worm */
for (l = 0; l < MAX_GOAL_WORMS; l++) {
if (owl_goal_worm[l] == NO_MOVE)
break;
if (board[owl_goal_worm[l]] == EMPTY)
if (size == 0 || worm[owl_goal_worm[l]].size > size) {
w = l;
size = worm[owl_goal_worm[l]].size;
}
}
if (w != MAX_GOAL_WORMS) {
if (GAIN > savecode) {
/* if new result better, just update */
dcode = LOSS;
saveworm = w;
}
else if (GAIN == savecode) {
/* bigger ? */
int wpos = owl_goal_worm[saveworm];
if (size > worm[wpos].size)
saveworm = w;
}
}
}
UPDATE_SAVED_KO_RESULT(savecode, savemove, dcode, mpos);
}
else { /* ko_move */
if (dcode != WIN) {
if (mpos == 0) {
SGFTRACE(mpos, KO_B, "all original stones captured with ko");
}
else {
SGFTRACE(mpos, KO_B, "attack effective - ko");
}
/* We already know the savecode was previously 0. */
savemove = mpos;
savecode = KO_B;
/* It's possible that the defender has no defense even if we
* give up the ko. In order to force a test of this,
* assuming this was our only move, we decrease the number
* of tried moves counter, disregarding this move.
*/
number_tried_moves--;
}
}
pop_owl(&owl);
popgo();
}
}
close_pattern_list(other, &shape_patterns);
if (savecode) {
if (savecode == GAIN) {
SGFTRACE(savemove, savecode, "attack effective (gain) - E");
if (wormid)
*wormid = saveworm;
READ_RETURN2(OWL_ATTACK, str, depth - stackp,
move, savemove, savecode, saveworm);
}
else {
SGFTRACE(savemove, savecode, "attack effective (ko) - E");
READ_RETURN(OWL_ATTACK, str, depth - stackp, move, savemove, savecode);
}
}
if (sgf_dumptree) {
char winstr[128];
sprintf(winstr, "attack failed)\n (%d variations",
count_variations - this_variation_number);
SGFTRACE(0, 0, winstr);
}
READ_RETURN0(OWL_ATTACK, str, depth - stackp);
}
/* Returns true if the dragon at (target) can be captured given
* two moves in a row. The first two moves to capture the
* dragon are given as (*attack1) and (*attack2).
*/
int
owl_threaten_attack(int target, int *attack1, int *attack2)
{
struct owl_move_data moves[MAX_MOVES];
int k;
int other = OTHER_COLOR(board[target]);
struct local_owl_data *owl;
int result = 0;
int reading_nodes_when_called = get_reading_node_counter();
signed char saved_boundary[BOARDMAX];
double start = 0.0;
int tactical_nodes;
int move = 0;
int move2 = 0;
struct matched_patterns_list_data shape_patterns;
shape_patterns.initialized = 0;
result_certain = 1;
if (search_persistent_owl_cache(OWL_THREATEN_ATTACK, target, 0, 0,
&result, attack1, attack2, NULL))
return result;
if (debug & DEBUG_OWL_PERFORMANCE)
start = gg_cputime();
gg_assert(stackp == 0);
TRACE("owl_threaten_attack %1m\n", target);
init_owl(&owl, target, NO_MOVE, NO_MOVE, 1, NULL);
memcpy(saved_boundary, owl->boundary, sizeof(saved_boundary));
owl_make_domains(owl, NULL);
owl_shapes(&shape_patterns, moves, other, owl, &owl_attackpat_db);
for (k = 0; k < MAX_MOVES; k++) {
current_owl_data = owl;
if (!get_next_move_from_list(&shape_patterns, other, moves, 1, owl))
break;
else {
int mpos = moves[k].pos;
if (mpos != NO_MOVE && moves[k].value > 0)
if (trymove(mpos, other, moves[k].name, target)) {
int pos;
int origin = NO_MOVE;
owl->lunches_are_current = 0;
owl_update_boundary_marks(mpos, owl);
/* If the origin of the dragon has been captured, we look
* for another string which was part of the original dragon,
* marked when stackp==0, which has not been captured. If no
* such string is found, owl_attack declares victory.
*/
if (board[target] == EMPTY) {
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (IS_STONE(board[pos]) && owl->goal[pos] == 1) {
origin = find_origin(pos);
break;
}
}
if (origin == NO_MOVE
|| do_owl_attack(origin, NULL, NULL, owl, 0)) {
/* probably this can't happen */
popgo();
gg_assert(stackp == 0);
result = 1;
break;
}
}
else if (do_owl_attack(target, &move2, NULL, owl, 0) == WIN) {
move = moves[k].pos;
popgo();
gg_assert(stackp == 0);
result = 1;
break;
}
popgo();
memcpy(owl->boundary, saved_boundary, sizeof(saved_boundary));
}
}
}
tactical_nodes = get_reading_node_counter() - reading_nodes_when_called;
gg_assert(stackp == 0);
DEBUG(DEBUG_OWL_PERFORMANCE,
"owl_threaten_attack %1m %1m %1m, result %d (%d, %d nodes, %f seconds)\n",
target, move, move2, result, local_owl_node_counter,
tactical_nodes, gg_cputime() - start);
store_persistent_owl_cache(OWL_THREATEN_ATTACK, target, 0, 0,
result, move, move2, 0,
tactical_nodes, owl->goal, board[target]);
if (attack1)
*attack1 = move;
if (attack2)
*attack2 = move2;
close_pattern_list(other, &shape_patterns);
return result;
}
/* Returns true if a move can be found to defend the dragon
* at (target), in which case (*defense_point) is the recommended move.
* (defense_point) can be a null pointer if the result is not needed.
*
* The array goal marks the extent of the dragon. This must
* be maintained during reading. Call this function only when
* stackp==0; otherwise you can call do_owl_attack but you must
* set up the goal and boundary arrays by hand first.
*
* Returns KO_A or KO_B if the position is ko:
*
* - Returns KO_A if the defendse succeeds provided the defender is willing to
* ignore any ko threat (the defender makes the first ko capture).
* - Returns KO_B if the defense succeeds provided the defender has a ko threat
* which must be answered (the attacker makes the first ko capture).
*
* If GNU Go is compiled with `configure --enable-experimental-owl-ext'
* then a return codes of GAIN is also possible.
*
* - Returns LOSS if the defense succeeds but another worm of the
* defender's is captured in during the defense. The location
* of the killed worm is returned through the *kworm field.
*
* The array goal marks the extent of the dragon. This must
* be maintained during reading.
*/
int
owl_defend(int target, int *defense_point, int *certain, int *kworm)
{
int result;
static struct local_owl_data *owl;
int reading_nodes_when_called = get_reading_node_counter();
double start = 0.0;
int tactical_nodes;
int move = NO_MOVE;
int wpos = NO_MOVE;
int wid = MAX_GOAL_WORMS;
result_certain = 1;
if (worm[target].unconditional_status == DEAD)
return 0;
if (search_persistent_owl_cache(OWL_DEFEND, target, 0, 0, &result,
defense_point, kworm, certain))
return result;
if (debug & DEBUG_OWL_PERFORMANCE)
start = gg_cputime();
TRACE("owl_defend %1m\n", target);
init_owl(&owl, target, NO_MOVE, NO_MOVE, 1, NULL);
owl_make_domains(owl, NULL);
prepare_goal_list(target, owl, owl_goal_worm, &goal_worms_computed,
kworm, 1);
result = do_owl_defend(target, &move, &wid, owl, 0);
finish_goal_list(&goal_worms_computed, &wpos, owl_goal_worm, wid);
tactical_nodes = get_reading_node_counter() - reading_nodes_when_called;
DEBUG(DEBUG_OWL_PERFORMANCE,
"owl_defend %1m, result %d %1m (%d, %d nodes, %f seconds)\n",
target, result, move, local_owl_node_counter,
tactical_nodes, gg_cputime() - start);
store_persistent_owl_cache(OWL_DEFEND, target, 0, 0, result, move, wpos,
result_certain, tactical_nodes, owl->goal,
board[target]);
if (defense_point)
*defense_point = move;
if (kworm)
*kworm = wpos;
if (certain)
*certain = result_certain;
return result;
}
/* Static function containing the main recursive code for owl_defend.
*/
static int
do_owl_defend(int str, int *move, int *wormid, struct local_owl_data *owl,
int escape)
{
int color = board[str];
struct owl_move_data shape_moves[MAX_MOVES];
struct owl_move_data vital_moves[MAX_MOVES];
struct owl_move_data *moves;
struct matched_patterns_list_data shape_patterns;
signed char mw[BOARDMAX];
int number_tried_moves = 0;
int pass;
int k;
int savemove = 0;
int saveworm = MAX_GOAL_WORMS;
int savecode = 0;
int eyemin = -1; /* Lower bound on the number of eyes. */
int eyemax = -1; /* Upper bound on the number of eyes. */
struct eyevalue probable_eyes; /* Best guess of eyevalue. */
int escape_route;
const char *live_reason;
int move_cutoff;
int xpos;
int value1;
int value2;
int this_variation_number = count_variations - 1;
SETUP_TRACE_INFO("owl_defend", str);
shape_patterns.initialized = 0;
str = find_origin(str);
if (tt_get(&ttable, OWL_DEFEND, str, NO_MOVE, depth - stackp, NULL,
&value1, &value2, &xpos) == 2) {
TRACE_CACHED_RESULT(value1, xpos);
if (move)
*move = xpos;
if (value1 == LOSS) {
if (wormid) {
if (goal_worms_computed)
*wormid = value2;
else
*wormid = MAX_GOAL_WORMS;
}
}
if (value1 == WIN || value1 == LOSS)
TRACE("%oVariation %d: ALIVE (cached)\n", this_variation_number);
else
TRACE("%oVariation %d: DEAD (cached)\n", this_variation_number);
SGFTRACE(xpos, value1, "cached");
return value1;
}
/* In order to get a defense move even if we seem to already have
* escaped and to reduce the impact of overestimated escape
* possibilities, we don't declare escape victory on the first move.
*
* FIXME: Should introduce a new owl depth value rather than having
* this hardwired value.
*/
escape_route = owl_escape_route(owl);
if (stackp > 2 && escape_route >= 5) {
/* FIXME: We probably should make distinction in the returned
* result whether the dragon lives by making two eyes or by
* escaping.
*/
TRACE("%oVariation %d: ALIVE (escaped)\n", this_variation_number);
SGFTRACE(0, WIN, "escaped");
READ_RETURN(OWL_DEFEND, str, depth - stackp, move, 0, WIN);
}
/* If reading goes to deep or we run out of nodes, we assume life. */
if (reading_limit_reached(&live_reason, this_variation_number)) {
SGFTRACE(0, WIN, live_reason);
READ_RETURN(OWL_DEFEND, str, depth - stackp, move, 0, WIN);
}
memset(mw, 0, sizeof(mw));
local_owl_node_counter++;
global_owl_node_counter++;
current_owl_data = owl;
memset(owl->safe_move_cache, 0, sizeof(owl->safe_move_cache));
/* First see whether we might already be alive. */
if (escape < MAX_ESCAPE) {
if (owl_estimate_life(owl, NULL, vital_moves, &live_reason, 0,
&probable_eyes, &eyemin, &eyemax)) {
SGFTRACE(0, WIN, live_reason);
TRACE("%oVariation %d: ALIVE (%s)\n",
this_variation_number, live_reason);
READ_RETURN(OWL_DEFEND, str, depth - stackp, move, 0, WIN);
}
}
else {
/* In this case we don't recompute eyes. However, to avoid accessing
* partially-random data left on stack, we copy eye data from the
* previous depth level. It should be reasonably close to the actual
* state of eyes.
*/
memcpy(owl->my_eye, owl->restore_from->my_eye, sizeof(owl->my_eye));
memcpy(owl->half_eye, owl->restore_from->half_eye, sizeof(owl->half_eye));
vital_moves[0].pos = 0;
vital_moves[0].value = -1;
set_eyevalue(&probable_eyes, 0, 0, 0, 0);
}
/* We try moves in four passes.
* stackp==0 stackp>0
* 0. Vital moves in the interval [70..] [45..]
* 1. Shape moves
* 2. Vital moves in the interval [..69] [..44]
* 3. Tactical defense moves
*/
for (pass = 0; pass < 4; pass++) {
moves = NULL;
move_cutoff = 1;
current_owl_data = owl;
switch (pass) {
/* Get the shape moves if we are in the right pass. */
case 1:
if (stackp > owl_branch_depth && number_tried_moves > 0)
continue;
owl_shapes(&shape_patterns, shape_moves, color, owl, &owl_defendpat_db);
moves = shape_moves;
break;
case 0:
case 2:
if (stackp > owl_branch_depth && number_tried_moves > 0)
continue;
moves = vital_moves;
if (pass == 0 || stackp > owl_distrust_depth) {
if (stackp == 0)
move_cutoff = 70;
else if (eyemin + min_eyes(&probable_eyes) > 3)
move_cutoff = 25;
else if (eyemin + min_eyes(&probable_eyes) >= 3)
move_cutoff = 35;
else
move_cutoff = 45;
}
if (eyemax < 2 && stackp > 2)
move_cutoff = 99; /* Effectively disable vital moves. */
break;
case 3:
{
int goalcount = 0;
/* If the goal is small, try a tactical defense. */
for (k = BOARDMIN; k < BOARDMAX; k++)
if (ON_BOARD(k))
goalcount += owl->goal[k];
if (goalcount < 5) {
/* Look for a tactical defense. This is primarily intended for
* the case where the whole dragon is a single string, therefore
* we only look at the string at the "origin".
*
* We only accept clearly effective tactical defenses here,
* using a liberty heuristic. The reason for this is problems
* with ineffective self ataris which do defend tactically but
* have no strategical effect other than wasting owl nodes or
* confusing the eye analysis.
*/
int dpos;
SGFTree *save_sgf_dumptree = sgf_dumptree;
int save_count_variations = count_variations;
sgf_dumptree = NULL;
count_variations = 0;
if (attack_and_defend(str, NULL, NULL, NULL, &dpos)
&& (approxlib(dpos, color, 2, NULL) > 1
|| does_capture_something(dpos, color))) {
TRACE("Found tactical defense for %1m at %1m.\n", str, dpos);
set_single_owl_move(shape_moves, dpos, "tactical_defense");
moves = shape_moves;
}
sgf_dumptree = save_sgf_dumptree;
count_variations = save_count_variations;
}
if (!moves)
continue;
}
} /* switch (pass) */
/* For the up to MAX_MOVES best moves with value equal to
* move_cutoff or higher, try to defend the dragon and see if it
* can then be attacked.
*/
for (k = 0; k < MAX_MOVES; k++) {
int mpos;
int ko_move = -1;
int new_escape;
int wid = MAX_GOAL_WORMS;
/* Consider only the highest scoring move if we're deeper than
* owl_branch_depth.
*
* FIXME: To behave as intended, k should be replaced by
* number_tried_moves.
*/
if (stackp > owl_branch_depth && k > 0)
break;
current_owl_data = owl;
if (pass == 1) {
if (!get_next_move_from_list(&shape_patterns, color, shape_moves,
move_cutoff, owl))
break;
}
else
if (moves[k].value < move_cutoff)
break;
mpos = moves[k].pos;
modify_eyefilling_move(&mpos, color);
ASSERT_ON_BOARD1(mpos);
/* Have we already tested this move? */
if (mw[mpos])
continue;
/* Try to make the move. */
if (!komaster_trymove(mpos, color, moves[k].name, str,
&ko_move, savecode == 0))
continue;
new_escape = escape;
if (moves[k].escape)
new_escape++;
TRACE("Trying %C %1m. Escape = %d. Current stack: ",
color, mpos, escape);
if (verbose)
dump_stack();
/* We have now made a move. Analyze the new position. */
push_owl(&owl);
mw[mpos] = 1;
number_tried_moves++;
/* Add the stone just played to the goal dragon, unless the
* pattern explicitly asked for not doing this.
*/
owl_update_goal(mpos, moves[k].same_dragon, moves[k].lunch, owl, 0,
moves[k].pattern_data);
mark_goal_in_sgf(owl->goal);
if (!ko_move) {
int acode = do_owl_attack(str, NULL, &wid, owl, new_escape);
if (!acode) {
pop_owl(&owl);
popgo();
if (sgf_dumptree) {
char winstr[192];
sprintf(winstr, "defense effective)\n (%d variations",
count_variations - this_variation_number);
SGFTRACE(mpos, WIN, winstr);
}
close_pattern_list(color, &shape_patterns);
READ_RETURN(OWL_DEFEND, str, depth - stackp, move, mpos, WIN);
}
if (acode == GAIN)
saveworm = wid;
UPDATE_SAVED_KO_RESULT(savecode, savemove, acode, mpos);
}
else {
if (do_owl_attack(str, NULL, NULL, owl, new_escape) != WIN) {
savemove = mpos;
savecode = KO_B;
}
}
/* Undo the tested move. */
pop_owl(&owl);
popgo();
}
}
close_pattern_list(color, &shape_patterns);
if (savecode) {
if (savecode == LOSS) {
SGFTRACE(savemove, savecode, "defense effective (loss) - B");
if (wormid)
*wormid = saveworm;
READ_RETURN2(OWL_DEFEND, str, depth - stackp,
move, savemove, savecode, saveworm);
}
else {
SGFTRACE(savemove, savecode, "defense effective (ko) - B");
READ_RETURN(OWL_DEFEND, str, depth - stackp, move, savemove, savecode);
}
}
if (number_tried_moves == 0 && min_eyes(&probable_eyes) >= 2) {
SGFTRACE(0, WIN, "genus probably >= 2");
READ_RETURN(OWL_DEFEND, str, depth - stackp, move, 0, WIN);
}
if (sgf_dumptree) {
char winstr[196];
int print_genus = eyemin == 1 ? 1 : 0;
sprintf(winstr, "defense failed - genus %d)\n (%d variations",
print_genus, count_variations - this_variation_number);
SGFTRACE(0, 0, winstr);
}
READ_RETURN0(OWL_DEFEND, str, depth - stackp);
}
/* Returns true if the dragon at (target) can be defended given
* two moves in a row. The first two moves to defend the
* dragon are given as (*defend1) and (*defend2).
*/
int
owl_threaten_defense(int target, int *defend1, int *defend2)
{
struct owl_move_data moves[MAX_MOVES];
int k;
int color = board[target];
int result = 0;
struct local_owl_data *owl;
int reading_nodes_when_called = get_reading_node_counter();
signed char saved_goal[BOARDMAX];
double start = 0.0;
int tactical_nodes;
int move = 0;
int move2 = 0;
struct matched_patterns_list_data shape_patterns;
shape_patterns.initialized = 0;
result_certain = 1;
if (worm[target].unconditional_status == DEAD)
return 0;
if (search_persistent_owl_cache(OWL_THREATEN_DEFENSE, target, 0, 0,
&result, defend1, defend2, NULL))
return result;
if (debug & DEBUG_OWL_PERFORMANCE)
start = gg_cputime();
TRACE("owl_threaten_defense %1m\n", target);
init_owl(&owl, target, NO_MOVE, NO_MOVE, 1, NULL);
memcpy(saved_goal, owl->goal, sizeof(saved_goal));
owl_make_domains(owl, NULL);
owl_shapes(&shape_patterns, moves, color, owl, &owl_defendpat_db);
for (k = 0; k < MAX_MOVES; k++) {
current_owl_data = owl;
if (!get_next_move_from_list(&shape_patterns, color, moves, 1, owl))
break;
else {
if (moves[k].pos != NO_MOVE && moves[k].value > 0)
if (trymove(moves[k].pos, color, moves[k].name, target)) {
owl->lunches_are_current = 0;
owl_update_goal(moves[k].pos, moves[k].same_dragon,
moves[k].lunch, owl, 0, moves[k].pattern_data);
if (do_owl_defend(target, &move2, NULL, owl, 0) == WIN) {
move = moves[k].pos;
popgo();
/* Don't return the second move if occupied before trymove */
if (move2 != NO_MOVE && IS_STONE(board[move2]))
move2 = NO_MOVE;
result = WIN;
break;
}
else
popgo();
memcpy(owl->goal, saved_goal, sizeof(saved_goal));
}
}
}
tactical_nodes = get_reading_node_counter() - reading_nodes_when_called;
gg_assert(stackp == 0);
DEBUG(DEBUG_OWL_PERFORMANCE,
"owl_threaten_defense %1m %1m %1m, result %d (%d, %d nodes, %f seconds)\n",
target, move, move2, result, local_owl_node_counter,
tactical_nodes, gg_cputime() - start);
store_persistent_owl_cache(OWL_THREATEN_DEFENSE, target, 0, 0,
result, move, move2, 0,
tactical_nodes, owl->goal, board[target]);
if (defend1)
*defend1 = move;
if (defend2)
*defend2 = move2;
close_pattern_list(color, &shape_patterns);
return result;
}
/*
* This function calls owl_determine_life() to get an eye estimate,
* and matchpat() for vital attack moves, and decides according to
* various policies (depth-dependant) whether the dragon should thus
* be considered alive.
*/
static int
owl_estimate_life(struct local_owl_data *owl,
struct local_owl_data *second_owl,
struct owl_move_data vital_moves[MAX_MOVES],
const char **live_reason, int does_attack,
struct eyevalue *probable_eyes, int *eyemin, int *eyemax)
{
SGFTree *save_sgf_dumptree = sgf_dumptree;
int save_count_variations = count_variations;
struct owl_move_data dummy_moves[MAX_MOVES];
int other = OTHER_COLOR(owl->color);
sgf_dumptree = NULL;
count_variations = 0;
owl_determine_life(owl, second_owl, does_attack, vital_moves,
probable_eyes, eyemin, eyemax);
matches_found = 0;
memset(found_matches, 0, sizeof(found_matches));
if (get_level() >= 8) {
memset(owl->safe_move_cache, 0, sizeof(owl->safe_move_cache));
if (!does_attack) {
clear_owl_move_data(dummy_moves);
matchpat(owl_shapes_callback, other,
&owl_vital_apat_db, dummy_moves, owl->goal);
}
else if (max_eyes(probable_eyes) >= 2)
matchpat(owl_shapes_callback, other,
&owl_vital_apat_db, vital_moves, owl->goal);
}
if ((debug & DEBUG_EYES) && (debug & DEBUG_OWL))
gprintf("owl: eyemin=%d matches_found=%d\n", *eyemin, matches_found);
if (*eyemin >= matches_found)
*eyemin -= matches_found;
else
*eyemin = 0;
sgf_dumptree = save_sgf_dumptree;
count_variations = save_count_variations;
if (*eyemin >= 2
|| (*eyemin == 1 && min_eyes(probable_eyes) >= 4)
|| (stackp > owl_distrust_depth
&& min_eyes(probable_eyes) >= 2
&& !matches_found)) {
if (*eyemin >= 2)
*live_reason = "2 or more secure eyes";
else if (*eyemin == 1 && min_eyes(probable_eyes) >= 4)
*live_reason = "1 secure eye, likely >= 4";
else if (stackp > owl_distrust_depth
&& min_eyes(probable_eyes) >= 2
&& !matches_found)
*live_reason = "getting deep, looks lively";
else
gg_assert(0);
return 1;
}
if (!does_attack
&& (*eyemin + matches_found >= 2
|| (*eyemin + matches_found == 1 && min_eyes(probable_eyes) >= 4)
|| (stackp > owl_distrust_depth
&& min_eyes(probable_eyes) >= 2))) {
/* We are not yet alive only due to owl vital attack patterns matching.
* Let's try to defend against it.
*/
owl_add_move(vital_moves, dummy_moves[0].defense_pos,
dummy_moves[0].value, dummy_moves[0].name,
SAME_DRAGON_CONNECTED, NO_MOVE, 0, NO_MOVE, MAX_MOVES, NULL);
}
return 0;
}
/*
* This function is invoked from do_owl_attack() and do_owl_defend()
* for each node to determine whether the the dragon has sufficient
* eye potential to live. It also generates vital moves to attack or
* defend the eyes. There are two distinct sources for eyes. The first
* is the eyespaces found by make_domains() and evaluated by
* compute_eyes_pessimistic(). The second is the lunches found by
* owl_find_lunches() and evaluated by sniff_lunch().
*
* The best guess of the eye potential is stored as an eyevalue in
* *probable_eyes. This is not entirely reliable though since the
* graph matching techniques in optics.c fail to understand subtleties
* like atari inside the eyespace, cutting points in the wall, and
* shortage of outside liberties. (The patterns in owl_vital_apats.db
* are used to compensate for this. See do_owl_attack() and
* do_owl_defend() for how these are used.) Also the estimates from
* sniff_lunch() are fairly unreliable.
*
* A lower and upper bound on the number of eyes are returned in
* *eyemin and *eyemax. The value of *eyemin must be offset by the
* matches of owl_vital_apats.db. If that number is 2 or larger, we
* should be certain of life.
*
* Vital moves to attack or defend eyes are returned in the moves[]
* array. Also moves to reduce the uncertainty of the eye estimates
* are added to this array, but with smaller move values. The
* parameter does_attack determines whether to generate vital attack
* moves or vital defense moves.
*
* The dragon is specified by the information in the owl struct. The
* color of the dragon is passed in the color parameter.
*
* For use in the semeai code, a second dragon can be provided. Set
* this to NULL when only one dragon is involved.
*/
static void
owl_determine_life(struct local_owl_data *owl,
struct local_owl_data *second_owl,
int does_attack,
struct owl_move_data *moves,
struct eyevalue *probable_eyes, int *eyemin, int *eyemax)
{
int color = owl->color;
struct eye_data *eye = owl->my_eye;
int mw[BOARDMAX]; /* mark relevant eye origins */
int mz[BOARDMAX]; /* mark potentially irrelevant eye origins */
int vital_values[BOARDMAX];
int dummy_eyemin = 0;
int dummy_eyemax = 0;
struct eyevalue eyevalue;
struct eyevalue eyevalue_list[BOARDMAX/2];
int eyes_attack_points[BOARDMAX/2];
int pessimistic_min;
int attack_point;
int defense_point;
int pos;
int k;
int lunch;
int num_eyes = 0;
int num_lunches = 0;
int save_debug = debug;
memset(vital_values, 0, sizeof(vital_values));
if (!eyemin)
eyemin = &dummy_eyemin;
if (!eyemax)
eyemax = &dummy_eyemax;
*eyemin = 0;
*eyemax = 0;
/* Turn off eye debugging if we're not also debugging owl. */
if (!(debug & DEBUG_OWL))
debug &= ~DEBUG_EYES;
clear_owl_move_data(moves);
if (!owl->lunches_are_current)
owl_find_lunches(owl);
if (0) {
for (k = 0; k < MAX_LUNCHES; k++)
if (owl->lunch[k] != NO_MOVE)
gprintf("owl lunch %1m, attack %1m, defend %1m\n",
owl->lunch[k],
owl->lunch_attack_point[k],
owl->lunch_defense_point[k]);
}
owl_make_domains(owl, second_owl);
owl_find_relevant_eyespaces(owl, mw, mz);
/* Reset halfeye data. Set topological eye value to something big. */
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (ON_BOARD(pos)) {
owl->half_eye[pos].type = 0;
owl->half_eye[pos].value = 10.0;
}
}
/* Find topological half eyes and false eyes. */
find_half_and_false_eyes(color, eye, owl->half_eye, mw);
/* The eyespaces may have been split or changed in other ways by the
* topological analysis, so we need to regenerate them and once more
* determine which ones are relevant.
*/
partition_eyespaces(owl->my_eye, owl->color);
owl_find_relevant_eyespaces(owl, mw, mz);
set_eyevalue(probable_eyes, 0, 0, 0, 0);
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (ON_BOARD(pos) && mw[pos] > 1) {
int value = 0;
const char *reason = "";
compute_eyes_pessimistic(pos, &eyevalue, &pessimistic_min,
&attack_point, &defense_point,
eye, owl->half_eye);
/* If the eyespace is more in contact with own stones not in the goal,
* than with ones in the goal, there is a risk that we can be cut off
* from a major part of the eyespace. Thus we can't trust the opinion
* of compute_eyes().
*
* (Obviously this is a quite fuzzy heuristic. With more accurate
* connection analysis in the owl code we could do this more robustly.)
*/
if (mw[pos] < mz[pos]
|| (mw[pos] < 3 * mz[pos] && mz[pos] > 5))
pessimistic_min = 0;
/* It appears that this policy is needed no longer. */
#if 0
/* If this eyespace includes an owl inessential string, we must assume
* that the pessimistic min is 0.
*/
if (pessimistic_min > 0) {
for (pos2 = BOARDMIN; pos2 < BOARDMAX; pos2++) {
if (ON_BOARD(pos2)
&& eye[pos2].origin == pos
&& owl->inessential[pos2]) {
pessimistic_min = 0;
break;
}
}
}
#endif
eyes_attack_points[num_eyes] = NO_MOVE;
eyevalue_list[num_eyes] = eyevalue;
*eyemin += pessimistic_min;
/* Fill in the value field for use by the owl_eyespace() function. */
eye[pos].value = eyevalue;
/* This shortcut has been disabled for two reasons:
* 1. Due to the vital attack moves being able to later reduce
* the *eyemin, we can't say that a certain *eyemin is
* sufficient.
* 2. This part of the code is in no way time critical.
*/
#if 0
/* Found two certain eyes---look no further. */
if (*eyemin >= 2) {
debug = save_debug;
return 2;
}
#endif
if (eye_move_urgency(&eyevalue)) {
value = 50;
if (max_eyes(&eyevalue) - min_eyes(&eyevalue) == 2)
value = 70;
else if (max_eyes(&eyevalue) - pessimistic_min == 2)
value = 60;
reason = "vital move";
}
else if (max_eyes(&eyevalue) != pessimistic_min) {
if (max_eyes(&eyevalue) - pessimistic_min == 2)
value = 40;
else
value = 30;
reason = "marginal eye space";
}
if (value > 0) {
if (does_attack && attack_point != NO_MOVE) {
if (vital_values[attack_point] > 0) {
value += vital_values[attack_point];
if (value > 98)
value = 98; /* Higher values may get special interpretation. */
}
TRACE("%s at %1m, score %d (eye at %1m, value %s, pessimistic_min %d)\n",
reason, attack_point, value,
pos, eyevalue_to_string(&eyevalue), pessimistic_min);
if (eye[attack_point].marginal
&& modify_stupid_eye_vital_point(owl, &attack_point, 1))
TRACE("vital point looked stupid, moved it to %1m\n",
attack_point);
if (attack_point != NO_MOVE) {
owl_add_move(moves, attack_point, value, reason,
SAME_DRAGON_MAYBE_CONNECTED, NO_MOVE,
0, NO_MOVE, MAX_MOVES, NULL);
vital_values[attack_point] = value;
eyes_attack_points[num_eyes] = attack_point;
}
}
/* The reason for the last set of tests is that we don't
* want to play a self atari in e.g. this position
*
* |XXX.
* |OOX.
* |.OX.
* |XOXX
* |XOOX
* |O*OX
* +----
*
* but it's okay in this position
*
* |XXXXX
* |....X
* |OOOOX
* |.XXOX
* |.*XOX
* +-----
*
* In both cases * is the vital point according to the graph
* matching. The significant difference is that in the first
* case the vital point is adjacent to stones in the goal.
*/
else if (!does_attack
&& defense_point != NO_MOVE
&& board[defense_point] == EMPTY
&& (!liberty_of_goal(defense_point, owl)
|| !is_self_atari(defense_point, color)
|| is_ko(defense_point, color, NULL)
|| safe_move(defense_point, color) != 0)) {
if (vital_values[defense_point] > 0) {
value += vital_values[defense_point];
if (value > 98)
value = 98; /* Higher values may get special interpretation. */
}
TRACE("%s at %1m, score %d (eye at %1m, value %s, pessimistic_min %d)\n",
reason, defense_point, value, pos,
eyevalue_to_string(&eyevalue), pessimistic_min);
if ((eye[defense_point].marginal
|| eye[defense_point].origin != pos)
&& modify_stupid_eye_vital_point(owl, &defense_point, 0))
TRACE("vital point looked stupid, moved it to %1m\n",
defense_point);
if (defense_point != NO_MOVE) {
owl_add_move(moves, defense_point, value, reason,
SAME_DRAGON_MAYBE_CONNECTED, NO_MOVE,
0, NO_MOVE, MAX_MOVES, NULL);
vital_values[defense_point] = value;
}
}
}
num_eyes++;
}
}
/* Sniff each lunch for nutritional value. The assumption is that
* capturing the lunch is gote, therefore the number of half eyes
* equals the MINIMUM number of eyes yielded by the resulting eye
* space.
*/
{
for (lunch = 0; (lunch < MAX_LUNCHES); lunch++)
if (owl->lunch[lunch] != NO_MOVE
&& owl->lunch_defense_point[lunch] != NO_MOVE) {
int value = 0;
int lunch_min;
int lunch_probable;
int lunch_max;
struct eyevalue e;
sniff_lunch(owl->lunch[lunch],
&lunch_min, &lunch_probable, &lunch_max, owl);
set_eyevalue(&e, 0, 0, lunch_probable, lunch_probable);
*eyemax += lunch_max;
if (lunch_probable == 0) {
if (countstones(owl->lunch[lunch]) == 1)
continue;
value = 20;
}
else if (lunch_probable == 1 && lunch_max == 1)
value = 60 + countstones(owl->lunch[lunch]);
else if (lunch_probable == 1 && lunch_max == 2)
value = 70 + countstones(owl->lunch[lunch]);
else
value = 75 + countstones(owl->lunch[lunch]);
if (owl->lunch_attack_code[lunch] != WIN)
value -= 10;
if (does_attack) {
defense_point = improve_lunch_defense(owl->lunch[lunch],
owl->lunch_defense_point[lunch]);
if (vital_values[defense_point]) {
/* The point here is that the move which saves the lunch also
* attacks an eye. So this attack move reduces the global eye
* potential. The eyes arithmetic for probable_eyes has then
* to be adapted accordingly.
*/
int ne;
for (ne = 0; ne < num_eyes - num_lunches; ne++)
if (eyes_attack_points[ne] == defense_point)
break;
gg_assert(ne < num_eyes - num_lunches);
/* merge eye values */
add_eyevalues(&eyevalue_list[ne], &e, &eyevalue_list[ne]);
/* and adjust */
eyevalue_list[ne].a = 0;
eyevalue_list[ne].b = 0;
}
else {
num_lunches++;
eyevalue_list[num_eyes++] = e;
}
TRACE("save lunch at %1m with %1m, score %d, probable eye %d, max eye %d\n",
owl->lunch[lunch], defense_point, value,
lunch_probable, lunch_max);
owl_add_move(moves, defense_point, value,
"save lunch", SAME_DRAGON_MAYBE_CONNECTED,
NO_MOVE, 0, NO_MOVE, MAX_MOVES, NULL);
}
else {
attack_point = improve_lunch_attack(owl->lunch[lunch],
owl->lunch_attack_point[lunch]);
TRACE("eat lunch at %1m with %1m, score %d, probable eye %d, max eye %d\n",
owl->lunch[lunch], attack_point, value,
lunch_probable, lunch_max);
/* We only remember the lunch for owl_update_goal() if the lunch
* cannot be defended with ko after the move.
* If we capture the lunch by an illegal ko capture, we become
* ko master with this move, and hence the above is true.
*/
if (owl->lunch_attack_code[lunch] == WIN
|| is_illegal_ko_capture(attack_point, owl->color))
owl_add_move(moves, attack_point, value, "eat lunch",
SAME_DRAGON_MAYBE_CONNECTED, owl->lunch[lunch],
0, NO_MOVE, MAX_MOVES, NULL);
else
owl_add_move(moves, attack_point, value, "eat lunch",
SAME_DRAGON_MAYBE_CONNECTED, NO_MOVE, 0, NO_MOVE,
MAX_MOVES, NULL);
num_lunches++;
eyevalue_list[num_eyes++] = e;
}
}
}
/* now, totalize the eye potential */
{
int ne;
for (ne = 0; ne < num_eyes - num_lunches; ne++)
add_eyevalues(probable_eyes, &eyevalue_list[ne], probable_eyes);
*eyemax += max_eyes(probable_eyes);
/* If we have at least two different eyespaces and can create one eye
* in sente, we assume there's a chance to create another one. This is
* needed because optics code don't know about eyespaces influencing
* each other and combination moves (i.e. double threats to create an
* eye).
*/
if (num_eyes - num_lunches > 1 && max_eye_threat(probable_eyes) > 1)
*eyemax += 1;
for (; ne < num_eyes; ne++)
add_eyevalues(probable_eyes, &eyevalue_list[ne], probable_eyes);
}
debug = save_debug;
}
/* The eyespaces we want to evaluate are the ones which
* are adjacent to the dragon (whose stones comprise the
* support of goal) which are not GRAY bordered. These
* are the eyespaces of the dragon. Now we find their
* origins.
*
* It is required that there are at least two distinct connections,
* adjacent or diagonal, between non-marginal eyespace vertices and
* stones of the goal dragon. Otherwise there is a risk that we
* include irrelevant eye spaces.
*/
static void
owl_find_relevant_eyespaces(struct local_owl_data *owl,
int mw[BOARDMAX], int mz[BOARDMAX])
{
int pos;
int eye_color;
int k;
struct eye_data *eye = owl->my_eye;
if (owl->color == WHITE)
eye_color = WHITE;
else
eye_color = BLACK;
memset(mw, 0, BOARDMAX * sizeof(mw[0]));
memset(mz, 0, BOARDMAX * sizeof(mz[0]));
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (board[pos] == owl->color) {
for (k = 0; k < 8; k++) {
int pos2 = pos + delta[k];
if (ON_BOARD(pos2)
&& eye[pos2].color == eye_color
&& !eye[pos2].marginal) {
if (owl->goal[pos])
mw[eye[pos2].origin]++;
else
mz[eye[pos2].origin]++;
}
}
}
}
}
/* Case 1.
*
* The optics code occasionally comes up with stupid vital moves, like
* a in this position:
*
* ----+
* O...|
* OX..|
* OX..|
* O.X.|
* .O.a|
* ....|
*
* This function moves such moves to the second line.
*
* Case 2.
*
* In this position the optics code can suggest the empty 1-2 point as
* vital move for the eyespace on the right edge. That is okay for attack
* but obviously not for defense.
*
* ----+
* XO.O|
* XOOX|
* XXO.|
* .XOO|
* .XXX|
*
* Case 3.
*
* Playing into a snapback is usually not an effective way to destroy
* an eye.
*
* XOOO|
* XOXX|
* XXO.|
* .XXO|
* ....|
*
* This function changes the attack point to NO_MOVE (i.e. removes it).
*/
static int
modify_stupid_eye_vital_point(struct local_owl_data *owl, int *vital_point,
int is_attack_point)
{
int up;
int right;
int k;
int libs[2];
/* Case 1. */
for (k = 0; k < 4; k++) {
up = delta[k];
if (ON_BOARD(*vital_point - up))
continue;
if (board[*vital_point + up] != EMPTY)
continue;
right = delta[(k+1) % 4];
if (board[*vital_point + right] != EMPTY
|| board[*vital_point - right] != EMPTY)
continue;
if (board[*vital_point + 2 * up] != EMPTY
|| board[*vital_point + up + right] != EMPTY
|| board[*vital_point + up - right] != EMPTY) {
*vital_point += up;
return 1;
}
}
/* Case 2. */
if (!is_attack_point) {
if (approxlib(*vital_point, OTHER_COLOR(owl->color), 1, NULL) == 0) {
for (k = 4; k < 8; k++) {
int pos = *vital_point + delta[k];
if (board[pos] == OTHER_COLOR(owl->color)
&& countlib(pos) == 1) {
findlib(pos, 1, vital_point);
return 1;
}
}
}
}
/* Case 3. */
if (is_attack_point
&& does_capture_something(*vital_point, OTHER_COLOR(owl->color))
&& accuratelib(*vital_point, OTHER_COLOR(owl->color), 2, libs) == 1
&& !attack(libs[0], NULL)) {
*vital_point = NO_MOVE;
return 1;
}
return 0;
}
/* The purpose of this function is to avoid moves which needlessly
* fill in an eye. A typical example, from ld_owl:188, is
*
* -----+
* .O.OX|
* XOOXX|
* XXOOX|
* .XXO.|
* ..XOO|
* ..XXX|
*
* where various patterns manage to propose the eye-filling move on
* the top edge instead of capturing the opponent stones and get two
* solid eyes. This function modifies the move accordingly.
*/
static int
modify_eyefilling_move(int *move, int color)
{
int k;
int r;
int other = OTHER_COLOR(color);
/* Only do this for a small eye. */
for (k = 0; k < 4; k++)
if (ON_BOARD(*move + delta[k]) && board[*move + delta[k]] != color)
return 0;
for (r = 4; r < 8; r++)
if (board[*move + delta[r]] == other
&& countlib(*move + delta[r]) == 1) {
for (k = 0; k < 4; k++)
if (board[*move + delta[k]] == color
&& countlib(*move + delta[k]) == 1
&& !adjacent_strings(*move + delta[r], *move + delta[k]))
break;
if (k == 4) {
int new_move;
findlib(*move + delta[r], 1, &new_move);
TRACE("Changing eyefilling move at %1m to capture at %1m.\n",
*move, new_move);
*move = new_move;
return 1;
}
}
return 0;
}
/*
* Generates up to max_moves moves, attempting to attack or defend the goal
* dragon. The found moves are put in moves, an array of owl_move_data
* structs, starting in the position 'initial'. The entries in the array are
* sorted by value with moves[initial] having highest priority. When no more
* moves are available this is indicated by value and coordinates in the array
* being -1.
*
* This function automatically initializes the owl_safe_move cache the
* pattern list. WATCH OUT: This has to be matched with a call to
* close_pattern_list(pattern_list)!!!
*
* Returns 1 if at least one move is found, or 0 if no move is found.
*/
static void
owl_shapes(struct matched_patterns_list_data *pattern_list,
struct owl_move_data moves[MAX_MOVES],
int color, struct local_owl_data *owl, struct pattern_db *type)
{
SGFTree *save_sgf_dumptree = sgf_dumptree;
int save_count_variations = count_variations;
sgf_dumptree = NULL;
count_variations = 0;
current_owl_data = owl;
clear_owl_move_data(moves);
/* We must reset the owl safe_move_cache before starting the
* pattern matching. The cache is used by owl_shapes_callback().
*/
memset(owl->safe_move_cache, 0, sizeof(owl->safe_move_cache));
init_pattern_list(pattern_list);
matchpat(collect_owl_shapes_callbacks, color, type, pattern_list, owl->goal);
sgf_dumptree = save_sgf_dumptree;
count_variations = save_count_variations;
}
/* This function contains all the expensive checks for a matched pattern. */
static int
check_pattern_hard(int move, int color, struct pattern *pattern, int ll)
{
int constraint_checked = 0;
int safe_move_checked = 0;
/* The very first check is whether we can disregard the pattern due
* due to an owl safe_move_cache lookup.
*/
if (!(pattern->class & CLASS_s))
if (current_owl_data->safe_move_cache[move]) {
if (current_owl_data->safe_move_cache[move] == 1)
return 0;
else
safe_move_checked = 1;
}
/* If the constraint is cheap to check, we do this first. */
if ((pattern->autohelper_flag & HAVE_CONSTRAINT)
&& pattern->constraint_cost < 0.45) {
if (!pattern->autohelper(ll, move, color, 0))
return 0;
constraint_checked = 1;
}
/* For sacrifice patterns, the survival of the stone to be played is
* not checked. Otherwise we discard moves which can be captured.
* Illegal ko captures are accepted for ko analysis.
*/
if (!(pattern->class & CLASS_s) && !safe_move_checked) {
if (!owl_safe_move(move, color)) {
if (0)
TRACE(" move at %1m wasn't safe, discarded\n", move);
return 0;
}
if (!is_legal(move, color)) {
if (0)
TRACE(" move at %1m wasn't legal, discarded\n", move);
return 0;
}
}
/* For class n patterns, the pattern is contingent on an opponent
* move at * not being captured.
*
* We can't use owl_safe_move() here because we would try the wrong color.
*/
if (pattern->class & CLASS_n) {
if (safe_move(move, OTHER_COLOR(color)) == 0) {
if (0)
TRACE(" opponent can't play safely at %1m, move discarded\n", move);
return 0;
}
}
/* If the pattern has a constraint, call the autohelper to see
* if the pattern must be rejected.
*/
if ((pattern->autohelper_flag & HAVE_CONSTRAINT) && !constraint_checked)
if (!pattern->autohelper(ll, move, color, 0))
return 0;
return 1;
}
/* This initializes a pattern list, allocating memory for 200 patterns.
* If more patterns need to be stored, collect_owl_shapes_callbacks will
* dynamically reallocate additional memory.
* The space for list->pattern_list is allocated here.
*
* This function is automatically called from owl_shapes. Every call here
* has to be matched by a call to close_pattern_list below.
*/
static void
init_pattern_list(struct matched_patterns_list_data *list)
{
gg_assert(!list->initialized);
list->counter = 0;
list->used = 0;
list->pattern_list = malloc(200 * sizeof(list->pattern_list[0]));
list->list_size = 200;
gg_assert(list->pattern_list != NULL);
list->pattern_heap = NULL;
if (0)
gprintf("List at %x has new array at %x\n", list, list->pattern_list);
list->initialized = 1;
}
/* This function has to get called before the memory of *list is freed
* in the calling function.
*/
static void
close_pattern_list(int color, struct matched_patterns_list_data *list)
{
if (list->initialized) {
if (0)
gprintf("%d patterns matched, %d patterns checked\n", list->counter,
list->used);
if (0)
gprintf("Pattern list at %x freed for list at %x\n",
list->pattern_list, list);
if (allpats && verbose) {
int i;
int found_one = 0;
SGFTree *save_sgf_dumptree = sgf_dumptree;
int save_count_variations = count_variations;
sgf_dumptree = NULL;
count_variations = 0;
if (!current_owl_data->lunches_are_current)
owl_find_lunches(current_owl_data);
if (!list->pattern_heap)
pattern_list_build_heap(list);
for (i = 0; i < list->heap_num_patterns; i++)
if (check_pattern_hard(list->pattern_heap[i]->move, color,
list->pattern_heap[i]->pattern,
list->pattern_heap[i]->ll)) {
if (!found_one) {
TRACE("Remaining valid (but unused) patterns at stack: ");
dump_stack();
found_one = 1;
}
TRACE("Pattern %s found at %1m with value %d\n",
list->pattern_heap[i]->pattern->name,
list->pattern_heap[i]->move,
(int) list->pattern_heap[i]->pattern->value);
}
sgf_dumptree = save_sgf_dumptree;
count_variations = save_count_variations;
}
free(list->pattern_list);
free(list->pattern_heap);
}
list->counter = -1;
}
/* Can be called from gdb for debugging:
* (gdb) set dump_pattern_list(&shape_patterns)
*/
void
dump_pattern_list(struct matched_patterns_list_data *list)
{
int i;
struct matched_pattern_data *matched_pattern;
if (!list->initialized)
return;
gprintf("%oList size %d. %d Patterns in list, %d have been used.",
list->list_size, list->counter, list->used);
for (i = 0; i < list->counter; i++) {
matched_pattern = &list->pattern_list[i];
gprintf("%o\n Pattern %s (orient. %d) at %1m, value %f.",
matched_pattern->pattern->name, matched_pattern->ll,
matched_pattern->move, matched_pattern->pattern->value);
if (matched_pattern->next_pattern_index != -1)
gprintf("%o * ");
}
gprintf("%o\n");
gprintf("%oCurrent heap ordering: \n");
for (i = 0; i < list->heap_num_patterns; i++) {
matched_pattern = list->pattern_heap[i];
gprintf("%o %s (%1m), %f; ", matched_pattern->pattern->name,
matched_pattern->move, matched_pattern->pattern->value);
}
gprintf("\n");
}
/* This function stores a found pattern in the list for later evaluation.
* The only processing done is computing the position of the move, and
* forgetting the color.
*/
static void
collect_owl_shapes_callbacks(int anchor, int color, struct pattern *pattern,
int ll, void *data)
{
struct matched_patterns_list_data *matched_patterns = data;
struct matched_pattern_data *next_pattern;
UNUSED(color); /* The calling function has to remember that. */
if (matched_patterns->counter >= matched_patterns->list_size) {
matched_patterns->list_size += 100;
matched_patterns->pattern_list
= realloc(matched_patterns->pattern_list,
matched_patterns->list_size
* sizeof(matched_patterns->pattern_list[0]));
}
next_pattern = &matched_patterns->pattern_list[matched_patterns->counter];
next_pattern->move = AFFINE_TRANSFORM(pattern->move_offset, ll, anchor);
next_pattern->value = pattern->value;
next_pattern->ll = ll;
next_pattern->anchor = anchor;
next_pattern->pattern = pattern;
next_pattern->next_pattern_index = -1;
matched_patterns->counter++;
}
#define MAX_STORED_REASONS 4
static int
valuate_combinable_pattern_chain(struct matched_patterns_list_data *list,
int pos)
{
/* FIXME: This is just a first attempt at pattern combination.
* Improve it. The first idea is to differentiate between
* move reason types. For instance, when there is a secure
* eye already, a threat to create another is more severe.
*
* This will certainly involve splitting the function into
* attack and defense versions.
*/
int pattern_index = list->first_pattern_index[pos];
int num_capture_threats = 0;
int capture_threats[MAX_STORED_REASONS];
int num_eye_threats = 0;
int eye_threats[MAX_STORED_REASONS];
int num_reverse_sente = 0;
int reverse_sente_against[MAX_STORED_REASONS];
int num_move_reasons;
float full_value = 0.0;
ASSERT1(pattern_index != -1, pos);
do {
struct matched_pattern_data *pattern_data = (list->pattern_list
+ pattern_index);
struct pattern_attribute *attribute;
/* Skip patterns that haven't passed constraint validation. */
if (pattern_data->pattern) {
for (attribute = pattern_data->pattern->attributes;
attribute->type != LAST_ATTRIBUTE;
attribute++) {
int k;
int target = AFFINE_TRANSFORM(attribute->offset, pattern_data->ll,
pattern_data->move);
switch (attribute->type) {
case THREATENS_TO_CAPTURE:
if (num_capture_threats < MAX_STORED_REASONS) {
ASSERT1(IS_STONE(board[target]), target);
target = find_origin(target);
for (k = 0; k < num_capture_threats; k++) {
if (capture_threats[k] == target)
break;
}
if (k == num_capture_threats) {
capture_threats[num_capture_threats++] = target;
full_value += pattern_data->pattern->value;
}
}
break;
case THREATENS_EYE:
if (num_eye_threats < MAX_STORED_REASONS) {
target = current_owl_data->my_eye[target].origin;
for (k = 0; k < num_eye_threats; k++) {
if (eye_threats[k] == target)
break;
}
if (k == num_eye_threats) {
eye_threats[num_eye_threats++] = target;
full_value += pattern_data->pattern->value;
}
}
break;
case REVERSE_SENTE:
if (num_reverse_sente < MAX_STORED_REASONS) {
ASSERT1(board[target] == EMPTY, target);
for (k = 0; k < num_reverse_sente; k++) {
if (reverse_sente_against[k] == target)
break;
}
if (k == num_reverse_sente) {
reverse_sente_against[num_reverse_sente++] = target;
full_value += pattern_data->pattern->value;
}
}
break;
default:
gg_assert(0);
}
}
}
pattern_index = pattern_data->next_pattern_index;
} while (pattern_index >= 0);
num_move_reasons = num_capture_threats + num_eye_threats + num_reverse_sente;
if (num_move_reasons <= 1) {
/* Not much to combine, eh? */
return 0;
}
if (num_move_reasons == 2)
return gg_min(gg_normalize_float2int(full_value, 1.0), 75);
if (num_move_reasons == 3)
return gg_min(gg_normalize_float2int(full_value * 0.85, 1.0), 90);
return gg_min(gg_normalize_float2int(full_value * 0.75, 1.0), 99);
}
#if USE_BDIST
/* Compute the squared of the distance of a point on the board to the
* center of the board.
*/
static int
bdist(int move)
{
/* i = 0: idist = - (board_size - 1)
* i = board_size -1 : idist = board_size - 1
*/
int idist = 2*I(move) - board_size + 1;
int jdist = 2*J(move) - board_size + 1;
return idist*idist + jdist*jdist;
}
/* NOTICE : In order to stabilize the regression test results,
* arbitrary parameters like pattern memory address and move position
* have been included in the sorting algorithm.
*/
#define BETTER_PATTERN(a, b) \
((a)->value > (b)->value \
|| ((a)->value == (b)->value \
&& ((a)->pattern < (b)->pattern \
|| ((a)->pattern == (b)->pattern \
&& ((a)->bdist < (b)->bdist \
|| ((a)->bdist == (b)->bdist \
&& (a)->move < (b)->move))))))
#else /* not USE_BDIST */
#define BETTER_PATTERN(a, b) \
((a)->value > (b)->value \
|| ((a)->value == (b)->value \
&& ((a)->pattern < (b)->pattern \
|| ((a)->pattern == (b)->pattern \
&& (a)->move < (b)->move))))
#endif /* not USE_BDIST */
static void
pattern_list_prepare(struct matched_patterns_list_data *list)
{
int k;
int pos;
list->heap_num_patterns = 0;
/* This is more than needed in case of (combinable) pattern chains,
* but it is easier to allocate more than to count real number of
* heap elements first.
*/
if (list->counter > 0) { /* avoid malloc(0) */
list->pattern_heap = malloc(list->counter * sizeof(*(list->pattern_heap)));
gg_assert(list->pattern_heap != NULL);
}
else {
/* free() has defined behaviour for NULL pointer */
list->pattern_heap = NULL;
}
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
list->first_pattern_index[pos] = -1;
for (k = 0; k < list->counter; k++) {
int move = list->pattern_list[k].move;
#if USE_BDIST
list->pattern_list[k].bdist = bdist(move);
#endif
/* Allocate heap elements for normal patterns. Link combinable
* patterns in chains.
*/
if (!(list->pattern_list[k].pattern->class & CLASS_c))
list->pattern_heap[list->heap_num_patterns++] = &list->pattern_list[k];
else {
list->pattern_list[k].next_pattern_index = list->first_pattern_index[move];
list->first_pattern_index[move] = k;
}
}
/* Allocate one heap element for each chain of combinable patterns
* and calculate initial chain values (as if all patterns passed
* constraint validation).
*/
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (list->first_pattern_index[pos] != -1) {
struct matched_pattern_data *pattern_data
= &list->pattern_list[list->first_pattern_index[pos]];
pattern_data->value = valuate_combinable_pattern_chain(list, pos);
list->pattern_heap[list->heap_num_patterns++] = pattern_data;
}
}
if (list->heap_num_patterns > 0)
pattern_list_build_heap(list);
}
/* Fast heap building. Takes O(n) only. */
static void
pattern_list_build_heap(struct matched_patterns_list_data *list)
{
int k;
int limit = list->heap_num_patterns / 2;
for (k = limit; --k >= 0;) {
int parent;
int child;
struct matched_pattern_data *pattern_data = list->pattern_heap[k];
for (parent = k; parent < limit; parent = child) {
child = 2 * parent + 1;
if (child + 1 < list->heap_num_patterns
&& BETTER_PATTERN(list->pattern_heap[child + 1],
list->pattern_heap[child]))
child++;
if (BETTER_PATTERN(pattern_data, list->pattern_heap[child]))
break;
list->pattern_heap[parent] = list->pattern_heap[child];
}
list->pattern_heap[parent] = pattern_data;
}
}
/* Pops patterns list's heap once. */
static void
pattern_list_pop_heap_once(struct matched_patterns_list_data *list)
{
int parent;
int child;
list->heap_num_patterns--;
for (parent = 0; 2 * parent + 1 < list->heap_num_patterns; parent = child) {
child = 2 * parent + 1;
if (BETTER_PATTERN(list->pattern_heap[child + 1],
list->pattern_heap[child]))
child++;
if (BETTER_PATTERN(list->pattern_heap[list->heap_num_patterns],
list->pattern_heap[child]))
break;
list->pattern_heap[parent] = list->pattern_heap[child];
}
list->pattern_heap[parent] = list->pattern_heap[list->heap_num_patterns];
}
/* Sink top element of heap because it got devalued. This happens
* when a combinable pattern doesn't pass check_pattern_hard() -- it
* is no longer counted and its whole chain's value is reduced.
*/
static void
pattern_list_sink_heap_top_element(struct matched_patterns_list_data *list)
{
int parent;
int child;
struct matched_pattern_data *heap_top_element = list->pattern_heap[0];
for (parent = 0; 2 * parent + 1 < list->heap_num_patterns; parent = child) {
child = 2 * parent + 1;
if (child + 1 < list->heap_num_patterns
&& BETTER_PATTERN(list->pattern_heap[child + 1],
list->pattern_heap[child]))
child++;
if (BETTER_PATTERN(heap_top_element,
list->pattern_heap[child]))
break;
list->pattern_heap[parent] = list->pattern_heap[child];
}
list->pattern_heap[parent] = heap_top_element;
}
/* Adds all goal strings in the pattern area to the cuts[] list, if there
* is more than one.
*/
static void
generate_cut_list(struct pattern *pattern, int ll, int anchor,
int cuts[MAX_CUTS], struct local_owl_data *owl)
{
int k;
int num = 0;
signed char mark[BOARDMAX];
memset(mark, 0, BOARDMAX);
for (k = 0; k < pattern->patlen; k++) {
int pos = AFFINE_TRANSFORM(pattern->patn[k].offset, ll, anchor);
if (!IS_STONE(board[pos]))
continue;
pos = find_origin(pos);
if (!mark[pos] && board[pos] == owl->color && owl->goal[pos]) {
cuts[num++] = pos;
mark[pos] = 1;
if (num == MAX_CUTS)
return;
}
}
if (num == 1)
cuts[0] = NO_MOVE;
else if ((debug & DEBUG_SPLIT_OWL) && num > 1)
gprintf("Move provokes %d cuts, among them %1m and %1m.\n", num,
cuts[0], cuts[1]);
}
/* This function searches in the previously stored list of matched
* patterns for the highest valued unused patterns that have a valid
* constraint. It returns the moves at the next empty positions in
* the array moves[]. Empty positions in the moves array are marked
* by having value <= 0. There must be enough empty positions in the
* list.
*
* If the highest valued pattern found has a value less than cutoff,
* no move is returned. Returns 1 if a move is found, 0 otherwise.
*
* This function also dispatches constraint validation of combinable
* pattern chains. Whenever a pattern from a chain fails constraints,
* the chain is reevaluated and most likely drops in value enough to
* let other patterns (or chains) climb to the top of pattern heap.
*
* This function loops until enough moves are found or the end of the
* list is reached.
*/
static int
get_next_move_from_list(struct matched_patterns_list_data *list, int color,
struct owl_move_data *moves, int cutoff,
struct local_owl_data *owl)
{
int move_found = 0;
SGFTree *save_sgf_dumptree = sgf_dumptree;
int save_count_variations = count_variations;
sgf_dumptree = NULL;
count_variations = 0;
/* Prepare pattern list if needed. */
if (!list->pattern_heap)
pattern_list_prepare(list);
while (list->heap_num_patterns > 0) {
int k;
struct matched_pattern_data *pattern_data;
struct pattern *pattern;
int move;
int value;
int ll;
int anchor;
int next_pattern_index;
/* Peek top element of heap associated with pattern list. */
if (list->pattern_heap[0]->value < cutoff)
break;
pattern_data = list->pattern_heap[0];
pattern = list->pattern_heap[0]->pattern;
move = list->pattern_heap[0]->move;
value = list->pattern_heap[0]->value;
ll = list->pattern_heap[0]->ll;
anchor = list->pattern_heap[0]->anchor;
next_pattern_index = list->pattern_heap[0]->next_pattern_index;
list->used++;
ASSERT_ON_BOARD1(move);
for (k = 0; k < MAX_MOVES; k++) {
if (moves[k].pos == move || moves[k].value <= 0)
break;
}
if (moves[k].pos == move) {
/* No point in testing this pattern/chain. Throw it out. */
pattern_list_pop_heap_once(list);
continue;
}
/* There has to be an empty space. */
gg_assert(k < MAX_MOVES);
/* If a pattern chain was devalued because its last pattern didn't
* pass constraint validation, `pattern' is set NULL (i.e. nothing
* more to test). Note that devalued chains might still be
* useful, i.e. if 2 of 3 patterns passed check_pattern_hard().
*/
if (pattern == NULL
|| check_pattern_hard(move, color, pattern, ll)) {
if (next_pattern_index == -1) {
/* Normal pattern or last one in a chain. */
pattern_list_pop_heap_once(list);
}
else {
/* We just validated a non-last pattern in a chain. Since the
* chain remains at the same value, we keep the heap structure
* untouched. However, we need to set heap's top to point to
* next pattern of the chain.
*/
list->pattern_heap[0] = list->pattern_list + next_pattern_index;
list->pattern_heap[0]->value = value;
continue;
}
moves[k].pos = move;
moves[k].value = value;
clear_cut_list(moves[k].cuts);
move_found = 1;
if (pattern && !(pattern->class & CLASS_c)) {
moves[k].name = pattern->name;
TRACE("Pattern %s found at %1m with value %d\n",
pattern->name, move, moves[k].value);
if (pattern->class & CLASS_C) {
/* Cut possible. (Only used in attack patterns). Try to find
* goal strings in the pattern area and store them in the cut list
* if there is more than one.
*/
DEBUG(DEBUG_SPLIT_OWL,
"Generating cut list for move at %1m.\n", move);
generate_cut_list(pattern, ll, anchor, moves[k].cuts, owl);
}
if (pattern->class & CLASS_B)
moves[k].same_dragon = SAME_DRAGON_NOT_CONNECTED;
else if (pattern->class & CLASS_a) {
moves[k].same_dragon = SAME_DRAGON_ALL_CONNECTED;
moves[k].pattern_data = pattern_data;
}
else if (!(pattern->class & CLASS_b))
moves[k].same_dragon = SAME_DRAGON_CONNECTED;
else {
int i;
enum same_dragon_value same_dragon = SAME_DRAGON_MAYBE_CONNECTED;
/* If we do not yet know whether the move belongs to the
* same dragon, we see whether another pattern can clarify.
*/
for (i = 0; i < list->heap_num_patterns; i++) {
pattern_data = list->pattern_heap[i];
if (pattern_data->pattern
&& pattern_data->move == move
&& ((pattern_data->pattern->class & CLASS_B)
|| !(pattern_data->pattern->class & CLASS_b))) {
if (check_pattern_hard(move, color, pattern_data->pattern,
pattern_data->ll)) {
TRACE("Additionally pattern %s found at %1m\n",
pattern_data->pattern->name, move);
if (pattern_data->pattern->class & CLASS_B)
same_dragon = SAME_DRAGON_NOT_CONNECTED;
else if (pattern_data->pattern->class & CLASS_a) {
same_dragon = SAME_DRAGON_ALL_CONNECTED;
moves[k].pattern_data = pattern_data;
}
else
same_dragon = SAME_DRAGON_CONNECTED;
break;
}
}
}
moves[k].same_dragon = same_dragon;
}
}
else {
moves[k].name = "Pattern combination";
if (verbose) {
/* FIXME: write names of all patterns in chain. */
}
/* FIXME: Add handling of CLASS_b.
*
* FIXME: It is silently assumed that all patterns in the
* chain have the same class. When the last pattern in
* chain didn't match, this will not work at all.
*/
if (pattern && pattern->class & CLASS_B)
moves[k].same_dragon = SAME_DRAGON_NOT_CONNECTED;
else if (pattern && pattern->class & CLASS_a) {
moves[k].same_dragon = SAME_DRAGON_ALL_CONNECTED;
moves[k].pattern_data = list->pattern_heap[0];
}
else
moves[k].same_dragon = SAME_DRAGON_CONNECTED;
}
if (pattern && pattern->class & CLASS_E)
moves[k].escape = 1;
else
moves[k].escape = 0;
break;
}
else { /* !check_pattern_hard(...) */
if (!(pattern->class & CLASS_c)) {
/* Just forget about it. */
pattern_list_pop_heap_once(list);
}
else {
/* Set this pattern to not matched and advance to next one in
* the chain, if any.
*/
list->pattern_heap[0]->pattern = NULL;
if (next_pattern_index != -1)
list->pattern_heap[0] = list->pattern_list + next_pattern_index;
/* Reevaluate chain and adjust heap structure accordingly. */
list->pattern_heap[0]->value = valuate_combinable_pattern_chain(list,
move);
pattern_list_sink_heap_top_element(list);
}
}
}
sgf_dumptree = save_sgf_dumptree;
count_variations = save_count_variations;
return move_found;
}
/* This function takes an array of already found moves (passed as
* 'data') and looks for moves to replace these. Only moves near
* the goal dragon are considered.
*/
static void
owl_shapes_callback(int anchor, int color, struct pattern *pattern,
int ll, void *data)
{
int tval; /* trial move and its value */
int move;
struct owl_move_data *moves = data; /* considered moves passed as data */
enum same_dragon_value same_dragon = SAME_DRAGON_MAYBE_CONNECTED;
int escape = 0;
int defense_pos;
/* Pick up the location of the move */
move = AFFINE_TRANSFORM(pattern->move_offset, ll, anchor);
/* Before we do any expensive reading, check whether this move
* already is known with a higher value or if there are too many
* other moves with higher value.
*/
if (!allpats) {
int k;
for (k = 0; k < MAX_MOVES; k++) {
if (moves[k].value == -1)
break;
if (moves[k].pos == move) {
if (moves[k].value >= pattern->value)
return;
else
break;
}
}
if (k == MAX_MOVES && moves[MAX_MOVES - 1].value >= pattern->value)
return;
}
if (!check_pattern_hard(move, color, pattern, ll))
return;
/* and work out the value of this move */
if (pattern->helper) {
/* ask helper function to consider the move */
gg_assert(0);
DEBUG(DEBUG_HELPER, " asking helper to consider '%s'+%d at %1m\n",
pattern->name, ll, move);
tval = pattern->helper(pattern, ll, move, color);
if (tval > 0) {
DEBUG(DEBUG_HELPER, "helper likes pattern '%s' value %d at %1m\n",
pattern->name, tval, move);
}
else {
DEBUG(DEBUG_HELPER, " helper does not like pattern '%s' at %1m\n",
pattern->name, move);
return; /* pattern matcher does not like it */
}
}
else { /* no helper */
tval = (int) pattern->value;
}
/* having made it here, we have made it through all the extra checks */
TRACE("Pattern %s found at %1m with value %d\n", pattern->name, move, tval);
if (pattern->class & CLASS_B)
same_dragon = SAME_DRAGON_NOT_CONNECTED;
else if (pattern->class & CLASS_b)
same_dragon = SAME_DRAGON_MAYBE_CONNECTED;
else if (pattern->class & CLASS_a) {
same_dragon = SAME_DRAGON_ALL_CONNECTED;
/* FIXME: Currently this code is only used with vital attack
* moves, so there is no use for the "a" classification. If it
* would be needed in the future it's necessary to set up a struct
* matched_pattern_data here to be passed to owl_add_move(). This
* is not all that simple with respect to memory management
* however. Notice that a local variable in this function would go
* out of scope too early.
*/
gg_assert(0);
}
else
same_dragon = SAME_DRAGON_CONNECTED;
if (pattern->class & CLASS_E)
escape = 1;
else
escape = 0;
/* Finally, check for position of defense move. */
{
int k;
defense_pos = move;
for (k = 0; k < pattern->patlen; k++)
if (pattern->patn[k].att == ATT_not)
defense_pos = AFFINE_TRANSFORM(pattern->patn[k].offset, ll, anchor);
}
owl_add_move(moves, move, tval, pattern->name, same_dragon, NO_MOVE,
escape, defense_pos, MAX_MOVES, NULL);
}
/* Add a move to the list of candidate moves */
static void
owl_add_move(struct owl_move_data *moves, int move, int value,
const char *reason, enum same_dragon_value same_dragon, int lunch,
int escape, int defense_pos, int max_moves,
struct matched_pattern_data *pattern_data)
{
int k;
if (!found_matches[move]) {
found_matches[move] = 1;
matches_found++;
}
/* Add the new move to the list of already found moves, if the value
* is sufficently large. We keep the list sorted.
*
* First we must see if this move already is in the list.
*/
for (k = 0; k < max_moves; k++) {
if (moves[k].value == -1)
break;
if (moves[k].pos == move) {
if (same_dragon > moves[k].same_dragon) {
moves[k].same_dragon = same_dragon;
moves[k].pattern_data = pattern_data;
}
if (!moves[k].escape)
escape = 0;
break;
}
}
/* Did we already have this move in the list with a higher value? */
if (k < max_moves && moves[k].value >= value)
return;
/* Insert the move at the right place in the list and adjust other
* entries as needed.
*/
for (; k >= 0; k--) {
if (k == 0 || value <= moves[k-1].value) {
/* Can't get higher. Insert the move below this point and quit
* looping.
*/
if (k < max_moves) {
moves[k].pos = move;
moves[k].value = value;
moves[k].name = reason;
/* If B or b class pattern, this move shouldn't be added to the
* dragon under consideration.
*/
moves[k].same_dragon = same_dragon;
moves[k].pattern_data = pattern_data;
moves[k].lunch = lunch;
moves[k].escape = escape;
moves[k].defense_pos = defense_pos;
}
break;
}
/* Shuffle the passed move one step downwards. */
if (k < max_moves)
moves[k] = moves[k-1]; /* struct copy */
}
/* Assert that the list contains unique moves. */
if (0) {
int l;
for (k = 0; k < max_moves; k++)
for (l = k+1; l < max_moves; l++)
gg_assert(moves[k].pos == 0
|| moves[k].pos != moves[l].pos);
}
}
/* Marks the dragons at apos and bpos. If only one dragon
* needs marking, bpos should be passed as NO_MOVE.
*/
static void
owl_mark_dragon(int apos, int bpos, struct local_owl_data *owl,
int new_dragons[BOARDMAX])
{
int pos;
int color = board[apos];
ASSERT1(bpos == NO_MOVE || board[bpos] == color, bpos);
if (new_dragons == NULL) {
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
if (ON_BOARD(pos)) {
if (is_same_dragon(pos, apos) || is_same_dragon(pos, bpos))
owl->goal[pos] = 1;
else
owl->goal[pos] = 0;
}
}
else {
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
if (ON_BOARD(pos)) {
if (IS_STONE(board[pos])
&& (new_dragons[pos] == new_dragons[apos]
|| new_dragons[pos] == new_dragons[bpos]))
owl->goal[pos] = 1;
else
owl->goal[pos] = 0;
}
}
memcpy(owl->cumulative_goal, owl->goal, sizeof(owl->goal));
owl->color = color;
owl_mark_boundary(owl);
}
/* Marks the worms at apos and bpos. If only one worm
* needs marking, bpos should be passed as NO_MOVE.
*/
static void
owl_mark_worm(int apos, int bpos, struct local_owl_data *owl)
{
int pos;
int color = board[apos];
ASSERT1(bpos == NO_MOVE || board[bpos] == color, bpos);
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
if (ON_BOARD(pos)) {
if (is_same_worm(pos, apos) || is_same_worm(pos, bpos))
owl->goal[pos] = 1;
else
owl->goal[pos] = 0;
}
owl->color = color;
}
/* Mark the boundary strings of the dragon. A boundary string is marked 2
* if it adjoins a friendly live dragon, 1 otherwise.
*/
static void
owl_mark_boundary(struct local_owl_data *owl)
{
int k;
int pos;
int color = owl->color;
int other = OTHER_COLOR(color);
memset(owl->boundary, 0, sizeof(owl->boundary));
memset(owl->neighbors, 0, sizeof(owl->neighbors));
/* Find all friendly neighbors of the dragon in goal. */
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (board[pos] == color && owl->goal[pos]) {
for (k = 0; k < 4; k++) {
if (board[pos + delta[k]] == EMPTY
&& board[pos + 2 * delta[k]] == color
&& !owl->neighbors[pos + 2 * delta[k]])
mark_string(pos + 2 * delta[k], owl->neighbors, 1);
}
for (; k < 8; k++) {
int pos2 = pos + delta[k];
if (board[pos2] == color
&& !owl->neighbors[pos2]
&& (board[SOUTH(gg_min(pos, pos2))] == EMPTY
|| board[NORTH(gg_max(pos, pos2))] == EMPTY))
mark_string(pos2, owl->neighbors, 1);
}
}
}
/* First find all boundary strings (including those adjacent not to
* the goal dragon, but one of its neighbors).
*/
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
if (board[pos] == other && !owl->boundary[pos]) {
for (k = 0; k < 8; k++)
if (ON_BOARD(pos + delta[k])
&& (owl->goal[pos + delta[k]] || owl->neighbors[pos + delta[k]])) {
mark_string(pos, owl->boundary, 1);
break;
}
}
/* Upgrade the mark of a boundary string if it adjoins a safe
* friendly dragon.
*/
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
if (owl->boundary[pos] == 1) {
for (k = 0; k < 8; k++) {
int pos2 = pos + delta[k];
if (board[pos2] == color
&& !owl->goal[pos2]
&& !owl->neighbors[pos2]
&& ((dragon[pos2].crude_status != DEAD && countstones(pos2) > 2)
|| dragon[pos2].crude_status == ALIVE)) {
mark_string(pos, owl->boundary, 2);
break;
}
}
}
/* During the owl reading, stones farther away may become parts of
* the boundary. We mark those strings neighboring some other
* friendly dragon with boundary value 2 right away, since we have
* no mechanism for detecting this later.
*/
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
if (board[pos] == other && owl->boundary[pos] == 0) {
/* If a lunch has been amalgamated into a larger dragon, we
* have to back out now.
*
* Notice that we assume that no stone of the attacking color
* has been placed on the board with trymove() when this
* function is called. Thus we can (mostly) trust the worm data for
* stones of this color.
*/
if (worm[pos].attack_codes[0] != 0
&& worm[pos].size != dragon[pos].size)
continue;
/* This can happen if called when stackp > 0 */
if (dragon[pos].id == -1)
continue;
for (k = 0; k < DRAGON2(pos).neighbors; k++) {
int d = DRAGON2(pos).adjacent[k];
int apos = dragon2[d].origin;
if (board[apos] == color && !owl->goal[apos]) {
owl->boundary[pos] = 2;
break;
}
}
}
}
/* Add the stone just played to the goal dragon if same_dragon is
* SAME_DRAGON_CONNECTED. We also add all stones belonging to the same
* generalized string to the goal. If same_dragon is
* SAME_DRAGON_MAYBE_CONNECTED, we only add the stones if at least one
* stone of the generalized string already was part of the goal. If
* same_dragon is SAME_DRAGON_NOT_CONNECTED, we don't add any stones
* at all.
*
* The SAME_DRAGON_ALL_CONNECTED case is like SAME_DRAGON_CONNECTED
* but additionally all other own stones in the pattern suggesting the
* move are also added to the goal.
*/
static void
owl_update_goal(int pos, enum same_dragon_value same_dragon, int lunch,
struct local_owl_data *owl, int semeai_call,
struct matched_pattern_data *pattern_data)
{
int stones[MAX_BOARD * MAX_BOARD];
int num_stones;
int k;
int do_add = 1;
SGFTree *save_sgf_dumptree = sgf_dumptree;
int save_count_variations = count_variations;
/* Turn off sgf output during find_superstring(). */
sgf_dumptree = NULL;
count_variations = 0;
if (same_dragon == SAME_DRAGON_NOT_CONNECTED)
num_stones = findstones(pos, MAX_BOARD * MAX_BOARD, stones);
else if (semeai_call)
find_superstring_conservative(pos, &num_stones, stones);
else
find_superstring(pos, &num_stones, stones);
/* Turn sgf output back on. */
sgf_dumptree = save_sgf_dumptree;
count_variations = save_count_variations;
/* If same_dragon field is 1, only add if the played stone
* clearly is in contact with the goal dragon.
*/
if (same_dragon <= SAME_DRAGON_MAYBE_CONNECTED) {
do_add = 0;
for (k = 0; k < num_stones; k++)
if (owl->goal[stones[k]] != 0) {
do_add = 1;
break;
}
}
if (do_add)
for (k = 0; k < num_stones; k++) {
if (owl->goal[stones[k]] == 0) {
if (0)
TRACE("Added %1m to goal.\n", stones[k]);
owl->goal[stones[k]] = 2;
owl->cumulative_goal[stones[k]] = 1;
}
}
/* If this move captures a lunch, we add all it's direct neighbours to the
* goal.
*/
if (!semeai_call && lunch != NO_MOVE && board[lunch] != EMPTY) {
int adj, adjs[MAXCHAIN];
int k;
adj = chainlinks(lunch, adjs);
for (k = 0; k < adj; k++)
if (!owl->goal[adjs[k]]) {
mark_string(adjs[k], owl->goal, 2);
mark_string(adjs[k], owl->cumulative_goal, 2);
}
}
/* Now we handle the SAME_DRAGON_ALL_CONNECTED case. The move has
* already been added to the goal above, so it remains to find all
* other friendly stones in the pattern and add them too. We do that
* by a recursive call to this function in SAME_DRAGON_CONNECTED mode.
* This is maybe not the most elegant technique, however.
*/
if (same_dragon == SAME_DRAGON_ALL_CONNECTED) {
gg_assert(pattern_data != NULL);
for (k = 0; k < pattern_data->pattern->patlen; k++) {
int pos2;
/* all the following stuff (currently) applies only at occupied cells */
if (pattern_data->pattern->patn[k].att != ATT_O)
continue;
/* transform pattern real coordinate */
pos2 = AFFINE_TRANSFORM(pattern_data->pattern->patn[k].offset,
pattern_data->ll, pattern_data->anchor);
if (!owl->goal[pos2])
owl_update_goal(pos2, SAME_DRAGON_CONNECTED, NO_MOVE, owl, semeai_call,
pattern_data);
}
}
if (1 && verbose)
goaldump(owl->goal);
}
/* Computes the connected components of a the graph that is given by
* having graph[i][j] = 1 if i and j are connected, and that has size
* graph_size.
*
* This function is generic, but without having the fixed MAX_CUTS
* array size it is ugly to write in ANSI C89 (no variably sized arrays),
* so we leave it here for now.
*/
static int
connected_components(signed char graph[MAX_CUTS][MAX_CUTS], int graph_size,
signed char component[MAX_CUTS])
{
int num_components = 0;
int k, j;
if (graph_size <= 0)
return 0;
memset(component, -1, MAX_CUTS);
for (;;) {
int found_one;
/* Find unidentified string. */
for (k = 0; k < graph_size; k++)
if (component[k] == -1)
break;
if (k == graph_size)
break; /* All are identified. */
component[k] = num_components; /* Start new component. */
do { /* Spread new component. */
found_one = 0;
for (j = k+1; j < graph_size; j++)
if (graph[k][j] && component[j] == -1) {
component[j] = num_components;
found_one = 1;
}
} while (found_one);
num_components++;
}
gg_assert(num_components > 0);
return num_components;
}
/* This functions gets called after a move has been made that threatens
* to cut the owl goal dragon. It cuts the goal if necessary, and sets it
* to the biggest remaining component.
*/
static void
owl_test_cuts(signed char goal[BOARDMAX], int color, int cuts[MAX_CUTS])
{
int k, j;
signed char connected[MAX_CUTS][MAX_CUTS];
/* int connect_move[MAX_CUTS][MAX_CUTS]; */
int num_cuts;
int found_cut = 0;
SGFTree *save_sgf_dumptree = sgf_dumptree;
int save_count_variations = count_variations;
sgf_dumptree = NULL;
count_variations = 0;
memset(connected, 1, MAX_CUTS*MAX_CUTS);
if (debug & DEBUG_SPLIT_OWL) {
gprintf("Called for this goal: ");
goaldump(goal);
gprintf("At this position:\n");
showboard(0);
}
/* Delete captured strings from list. */
for (k = 0; k < MAX_CUTS; k++) {
if (cuts[k] == NO_MOVE)
break;
if (board[cuts[k]] == EMPTY) {
for (j = k + 1; j < MAX_CUTS; j++) {
if (cuts[j] == NO_MOVE)
break;
cuts[j-1] = cuts[j];
}
cuts[k] = NO_MOVE;
k--;
}
}
num_cuts = k;
/* Test for each pair of strings in cuts[] whether it can now be
* disconnected.
*/
for (k = 0; k < num_cuts; k++) {
ASSERT1(board[cuts[k]] == color, cuts[k]);
for (j = k + 1; j < num_cuts; j++)
if (fast_disconnect(cuts[k], cuts[j], NULL) == WIN) {
found_cut = 1;
connected[k][j] = 0;
connected[j][k] = 0;
}
}
if (found_cut) {
signed char component[MAX_CUTS];
signed char component2[BOARDMAX];
int component_size[MAX_CUTS];
int num_components;
int biggest_component = -1;
struct connection_data *conn_data;
int c_id;
int pos;
/* Start by computing the connected components among the strings
* listed in cuts[].
*/
num_components = connected_components(connected, num_cuts, component);
if (num_components <= 1) {
sgf_dumptree = save_sgf_dumptree;
count_variations = save_count_variations;
return;
}
/* Now break up the goal by associating each goal stone to one of
* the connected components.
*
* First we compute the connection distances from each of the
* partial goals we have found.
*/
memset(component2, -1, BOARDMAX);
memset(component_size, 0, sizeof(int) * num_components);
conn_data = malloc(sizeof(struct connection_data) * num_components);
for (c_id = 0; c_id < num_components; c_id++) {
signed char this_goal[BOARDMAX];
memset(this_goal, 0, BOARDMAX);
for (k = 0; k < num_cuts; k++)
if (component[k] == c_id) {
mark_string(cuts[k], this_goal, 1);
mark_string(cuts[k], component2, (signed char) c_id);
}
init_connection_data(color, this_goal, NO_MOVE, FP(3.01),
conn_data + c_id, 1);
spread_connection_distances(color, conn_data + c_id);
}
/* Now put each goal string to the component to which it has the
* smallest distance.
*/
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
int closest_dist = HUGE_CONNECTION_DISTANCE;
int closest_component = -1;
if (board[pos] != color || !goal[pos])
continue;
if (pos != find_origin(pos))
continue;
for (c_id = 0; c_id < num_components; c_id++) {
if (conn_data[c_id].distances[pos] < closest_dist) {
closest_dist = conn_data[c_id].distances[pos];
closest_component = c_id;
}
}
/* FIXME: What to do if no close component found? */
if (closest_component != -1) {
mark_string(pos, component2, (signed char) closest_component);
component_size[closest_component] += countstones(pos);
}
}
/* Now find the biggest_component. */
{
int biggest_size = 0;
for (c_id = 0; c_id < num_components; c_id++)
if (component_size[c_id] > biggest_size) {
biggest_size = component_size[c_id];
biggest_component = c_id;
}
gg_assert(biggest_component != -1);
}
/* Now delete everything except the biggest component from the goal. */
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
if (component2[pos] != biggest_component)
goal[pos] = 0;
if (debug & DEBUG_SPLIT_OWL) {
gprintf("Split dragon. Biggest component is %d (of %d).\n",
biggest_component, num_components);
showboard(0);
componentdump(component2);
}
free(conn_data);
}
sgf_dumptree = save_sgf_dumptree;
count_variations = save_count_variations;
}
/* We update the boundary marks. The boundary mark must be
* constant on each string. It is nonzero if the string
* adjoins the goal dragon, or if the string includes a
* stone played in the course of analysis. If the string
* adjoins a live friendly dragon, the boundary mark is 2.
*/
static void
owl_update_boundary_marks(int pos, struct local_owl_data *owl)
{
signed char boundary_mark = 0;
int k;
for (k = 0; k < 4; k++) {
int pos2 = pos + delta[k];
if (ON_BOARD(pos2) && owl->boundary[pos2] > boundary_mark)
boundary_mark = owl->boundary[pos2];
if (board[pos2] == owl->color
&& dragon[pos2].color == owl->color
&& dragon[pos2].status == ALIVE
&& !owl->goal[pos2]
&& !owl->neighbors[pos2])
boundary_mark = 2;
}
mark_string(pos, owl->boundary, boundary_mark);
}
/* Lists the goal array. For use in GDB:
* (gdb) set goaldump(goal).
*/
void
goaldump(const signed char goal[BOARDMAX])
{
int pos;
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
if (ON_BOARD(pos) && goal[pos])
gprintf("%o%1m (%d) ", pos, (int) goal[pos]);
gprintf("\n");
}
void
componentdump(const signed char component[BOARDMAX])
{
int pos;
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
if (ON_BOARD(pos) && component[pos] != -1)
gprintf("%o%1m (%d) ", pos, (int) component[pos]);
gprintf("\n");
}
/*
* Owl attack moves are ineffective when the dragon can still live in a
* semeai. This function tests whether an owl attack move has this problem.
* If not, an owl attack move reason is added, otherwise we treat the
* move as a strategic attack.
*/
static void
test_owl_attack_move(int pos, int dr, int kworm, int acode)
{
int color = OTHER_COLOR(board[dr]);
if (DRAGON2(dr).semeais == 0
|| DRAGON2(dr).semeai_defense_point == NO_MOVE
|| (DRAGON2(dr).semeais == 1 && semeai_move_reason_known(pos, dr))
|| acode == GAIN) {
add_owl_attack_move(pos, dr, kworm, acode);
DEBUG(DEBUG_OWL, "owl: %1m attacks %1m (%s) at move %d\n",
pos, dr, result_to_string(DRAGON2(dr).owl_attack_code),
movenum+1);
}
else {
int dr2 = DRAGON2(dr).semeai_defense_target;
int semeai_result, certain;
int save_verbose = verbose;
if (verbose > 0)
verbose--;
owl_analyze_semeai_after_move(pos, color, dr, dr2, &semeai_result,
NULL, NULL, 1, &certain, 0);
verbose = save_verbose;
if (certain >= DRAGON2(dr).semeai_defense_certain
&& (semeai_result >= REVERSE_RESULT(acode))) {
/* Demote the move reasons. */
DEBUG(DEBUG_OWL, "owl: %1m ineffective owl attack on %1m (can live in semeai with %1m)\n", pos, dr, dr2);
add_strategical_attack_move(pos, dr);
}
else {
add_owl_attack_move(pos, dr, kworm, acode);
DEBUG(DEBUG_OWL, "owl: %1m attacks %1m (%s) at move %d\n",
pos, dr, result_to_string(DRAGON2(dr).owl_attack_code),
movenum+1);
}
}
}
/* Add owl move reasons. This function should be called once during
* genmove. It has to be called after semeai_move_reasons().
*/
void
owl_reasons(int color)
{
int pos;
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (!IS_STONE(board[pos])
|| dragon[pos].origin != pos)
continue;
if (dragon[pos].status == CRITICAL
&& DRAGON2(pos).owl_attack_point != NO_MOVE) {
if (board[pos] == color) {
if (DRAGON2(pos).owl_defense_point != NO_MOVE) {
if (DRAGON2(pos).owl_defense_code == LOSS) {
add_loss_move(DRAGON2(pos).owl_defense_point, pos,
DRAGON2(pos).owl_defense_kworm);
DEBUG(DEBUG_OWL, "owl: %1m defends %1m with loss at move %d\n",
DRAGON2(pos).owl_defense_point, pos, movenum+1);
}
else {
add_owl_defense_move(DRAGON2(pos).owl_defense_point, pos,
DRAGON2(pos).owl_defense_code);
DEBUG(DEBUG_OWL, "owl: %1m defends %1m at move %d\n",
DRAGON2(pos).owl_defense_point, pos, movenum+1);
}
}
}
else { /* opponent's dragon */
/* We don't want to add this move reason if the attacker
* dies because the victim only formed a nakade shape.
*
* FIXME: This code overlaps heavily with some code in
* examine_move_safety() in move_reasons.c. The caching
* scheme should minimize the performance hit, but of course
* it's unfortunate to have the code duplication.
*/
int move = DRAGON2(pos).owl_attack_point;
/* No worries if we catch something big. */
if (dragon[pos].effective_size < 8) {
/* Look through the neighbors of the victim for dragons of
* our color. If we find at least one being thought alive
* everything is ok. Otherwise we keep track of the
* largest one for further examination.
*/
int largest = 0;
int k;
int bpos = NO_MOVE;
int kworm = NO_MOVE;
int safe = 0;
for (k = 0; k < DRAGON2(pos).neighbors; k++) {
int d = DRAGON2(pos).adjacent[k];
if (DRAGON(d).color == color) {
if (DRAGON(d).status == ALIVE) {
safe = 1;
break;
}
if (DRAGON(d).size > largest) {
bpos = dragon2[d].origin;
largest = DRAGON(d).size;
}
}
}
/* It may occasionally happen that no neighbor of our
* color was found. Assume safe in that case.
*/
if (bpos == NO_MOVE)
safe = 1;
/* If not yet thought safe, ask the owl code whether the
* owl attack defends the (largest) attacker.
*/
if (!safe && owl_does_defend(move, bpos, &kworm) != WIN) {
DEBUG(DEBUG_OWL,
"owl: %1m attacks %1m at move %d, but the attacker dies.\n",
move, pos, movenum+1);
DRAGON2(pos).safety = INESSENTIAL;
continue;
}
}
/* If we've reached this far, it only remains to check the move
* against semeai complications. */
test_owl_attack_move(move, pos, DRAGON2(pos).owl_attack_kworm,
DRAGON2(pos).owl_attack_code);
}
}
else if (DRAGON2(pos).owl_status == DEAD
&& DRAGON2(pos).owl_threat_status == CAN_THREATEN_DEFENSE) {
if (board[pos] == color
&& DRAGON2(pos).owl_defense_point != NO_MOVE) {
add_owl_defense_threat_move(DRAGON2(pos).owl_defense_point, pos, WIN);
DEBUG(DEBUG_OWL, "owl: %1m threatens to defend %1m at move %d\n",
DRAGON2(pos).owl_defense_point, pos, movenum+1);
}
if (board[pos] == color
&& DRAGON2(pos).owl_second_defense_point != NO_MOVE
&& is_legal(DRAGON2(pos).owl_second_defense_point, color)) {
add_owl_defense_threat_move(DRAGON2(pos).owl_second_defense_point,
pos, WIN);
DEBUG(DEBUG_OWL, "owl: %1m threatens to defend %1m at move %d\n",
DRAGON2(pos).owl_second_defense_point, pos, movenum+1);
}
/* If the opponent can threaten to live, an attacking
* move gets a small value to make sure it's really dead.
*/
if (board[pos] == OTHER_COLOR(color)
&& DRAGON2(pos).owl_threat_status == CAN_THREATEN_DEFENSE
&& DRAGON2(pos).owl_attack_point != NO_MOVE) {
add_owl_prevent_threat_move(DRAGON2(pos).owl_attack_point, pos);
DEBUG(DEBUG_OWL, "owl: %1m prevents a threat against %1m at move %d\n",
DRAGON2(pos).owl_attack_point, pos, movenum+1);
}
}
else if (DRAGON2(pos).owl_status == ALIVE) {
if (board[pos] == OTHER_COLOR(color)
&& DRAGON2(pos).owl_threat_status == CAN_THREATEN_ATTACK) {
if (DRAGON2(pos).owl_attack_point != NO_MOVE) {
add_owl_attack_threat_move(DRAGON2(pos).owl_attack_point, pos, WIN);
DEBUG(DEBUG_OWL, "owl: %1m threatens %1m at move %d\n",
DRAGON2(pos).owl_attack_point, pos, movenum+1);
}
if (DRAGON2(pos).owl_second_attack_point != NO_MOVE
&& is_legal(DRAGON2(pos).owl_second_attack_point, color)) {
add_owl_attack_threat_move(DRAGON2(pos).owl_second_attack_point, pos,
WIN);
DEBUG(DEBUG_OWL, "owl: %1m threatens %1m at move %d\n",
DRAGON2(pos).owl_second_attack_point, pos, movenum+1);
}
}
else if (board[pos] == OTHER_COLOR(color)
&& DRAGON2(pos).owl_attack_point != NO_MOVE
&& DRAGON2(pos).owl_attack_code == GAIN) {
add_owl_attack_move(DRAGON2(pos).owl_attack_point, pos,
DRAGON2(pos).owl_attack_kworm, GAIN);
DEBUG(DEBUG_OWL, "owl: %1m attacks %1m with gain at move %d\n",
DRAGON2(pos).owl_attack_point, pos, movenum+1);
}
else if (board[pos] == color
&& DRAGON2(pos).owl_defense_point != NO_MOVE
&& DRAGON2(pos).owl_defense_code == LOSS) {
add_loss_move(DRAGON2(pos).owl_defense_point, pos,
DRAGON2(pos).owl_defense_kworm);
DEBUG(DEBUG_OWL, "owl: %1m defends %1m with loss at move %d\n",
DRAGON2(pos).owl_defense_point, pos, movenum+1);
}
else if (board[pos] == color
&& DRAGON2(pos).owl_attack_point != NO_MOVE
&& DRAGON2(pos).owl_attack_code == GAIN
&& DRAGON2(pos).owl_defense_code == WIN
&& DRAGON2(pos).owl_defense_point != NO_MOVE) {
add_owl_defense_move(DRAGON2(pos).owl_defense_point, pos,
DRAGON2(pos).owl_defense_code);
DEBUG(DEBUG_OWL, "owl: %1m defends %1m against possible loss at move %d\n",
DRAGON2(pos).owl_defense_point, pos, movenum+1);
}
/* The owl code found the friendly dragon alive, but was uncertain,
* and an extra point of defense was found, so this might
* be a good place to play.
*/
else if (board[pos] == color
&& !DRAGON2(pos).owl_attack_certain
&& DRAGON2(pos).owl_defense_certain
&& ON_BOARD(DRAGON2(pos).owl_defense_point)) {
add_owl_uncertain_defense_move(DRAGON2(pos).owl_defense_point, pos);
DEBUG(DEBUG_OWL,
"owl: %1m defends the uncertain dragon at %1m at move %d\n",
DRAGON2(pos).owl_defense_point, pos, movenum+1);
}
}
/* The owl code found the dragon dead, but was uncertain,
* and an extra point of attack was found, so this might
* be a good place to play.
*/
else if (DRAGON2(pos).owl_status == DEAD
&& board[pos] == OTHER_COLOR(color)
&& !DRAGON2(pos).owl_attack_certain
&& ON_BOARD(DRAGON2(pos).owl_attack_point)) {
add_owl_uncertain_defense_move(DRAGON2(pos).owl_attack_point, pos);
DEBUG(DEBUG_OWL,
"owl: %1m might defend the uncertain dragon at %1m at move %d\n",
DRAGON2(pos).owl_attack_point, pos, movenum+1);
}
}
}
/* Use the owl code to determine whether the move at (move) makes
* the dragon at (target) owl safe. This is used to test whether
* tactical defenses are strategically viable and whether a vital eye
* point does kill an owl critical dragon.
*
* Should be called only when stackp==0.
*/
int
owl_does_defend(int move, int target, int *kworm)
{
int color = board[target];
int result = 0;
struct local_owl_data *owl;
int reading_nodes_when_called = get_reading_node_counter();
int tactical_nodes;
int origin;
int acode;
int wpos = NO_MOVE;
int wid = MAX_GOAL_WORMS;
double start = 0.0;
if (debug & DEBUG_OWL_PERFORMANCE)
start = gg_cputime();
if (worm[target].unconditional_status == DEAD)
return 0;
origin = dragon[target].origin;
TRACE("owl_does_defend %1m %1m(%1m)\n", move, target, origin);
if (search_persistent_owl_cache(OWL_DOES_DEFEND, move, target, 0,
&result, kworm, NULL, NULL))
return result;
if (trymove(move, color, "owl_does_defend", target)) {
/* Check if a compatible owl_attack() is cached. */
if (search_persistent_owl_cache(OWL_ATTACK, origin, 0, 0,
&result, NULL, kworm, NULL)) {
popgo();
return REVERSE_RESULT(result);
}
/*
* FIXME: (move) will be added to the goal dragon although we
* do not know whether it is really connected.
*/
init_owl(&owl, target, NO_MOVE, move, 1, NULL);
prepare_goal_list(target, owl, owl_goal_worm, &goal_worms_computed,
kworm, 0);
acode = do_owl_attack(target, NULL, &wid, owl, 0);
finish_goal_list(&goal_worms_computed, &wpos, owl_goal_worm, wid);
result = REVERSE_RESULT(acode);
popgo();
}
else
return 0; /* Don't cache anything in this case. */
tactical_nodes = get_reading_node_counter() - reading_nodes_when_called;
DEBUG(DEBUG_OWL_PERFORMANCE,
"owl_does_defend %1m %1m(%1m), result %d (%d, %d nodes, %f seconds)\n",
move, target, origin, result, local_owl_node_counter,
tactical_nodes, gg_cputime() - start);
store_persistent_owl_cache(OWL_DOES_DEFEND, move, target, 0,
result, wpos, 0, 0,
tactical_nodes, owl->goal, board[target]);
if (kworm)
*kworm = wpos;
return result;
}
/* Use the owl code to determine whether the dragon at (target) is owl
* safe after an own move at (move). This is used to detect
* blunders. In case the dragon is not safe, it also tries to find a
* defense point making (target) safe in a later move.
*
* Should be called only when stackp==0.
*/
int
owl_confirm_safety(int move, int target, int *defense_point, int *kworm)
{
int color = board[target];
int result = 0;
struct local_owl_data *owl;
int reading_nodes_when_called = get_reading_node_counter();
int tactical_nodes;
int origin;
int defense = 0;
double start = 0.0;
int acode;
int wpos = NO_MOVE;
int wid = MAX_GOAL_WORMS;
if (debug & DEBUG_OWL_PERFORMANCE)
start = gg_cputime();
if (worm[target].unconditional_status == DEAD)
return 0;
origin = dragon[target].origin;
TRACE("owl_confirm_safety %1m %1m(%1m)\n", move, target, origin);
if (search_persistent_owl_cache(OWL_CONFIRM_SAFETY, move, target, 0,
&result, defense_point, kworm, NULL))
return result;
if (trymove(move, color, "owl_confirm_safety", target)) {
/* Check if a compatible owl_attack() is cached. */
if (search_persistent_owl_cache(OWL_ATTACK, origin, 0, 0,
&result, defense_point, kworm, NULL)) {
popgo();
if (result == 0)
return WIN;
else if (result == GAIN)
return LOSS;
else
return 0;
}
init_owl(&owl, target, NO_MOVE, move, 1, NULL);
prepare_goal_list(target, owl, owl_goal_worm, &goal_worms_computed,
kworm, 0);
acode = do_owl_attack(target, &defense, &wid, owl, 0);
finish_goal_list(&goal_worms_computed, &wpos, owl_goal_worm, wid);
if (acode == 0)
result = WIN;
else if (acode == GAIN)
result = LOSS;
popgo();
}
else
return 0; /* Don't cache anything in this case. */
tactical_nodes = get_reading_node_counter() - reading_nodes_when_called;
DEBUG(DEBUG_OWL_PERFORMANCE,
"owl_confirm_safety %1m %1m(%1m), result %d %1m (%d, %d nodes, %f seconds)\n",
move, target, origin, result, defense,
local_owl_node_counter, tactical_nodes,
gg_cputime() - start);
store_persistent_owl_cache(OWL_CONFIRM_SAFETY, move, target, 0,
result, defense, wpos, 0,
tactical_nodes, owl->goal, board[target]);
if (defense_point)
*defense_point = defense;
if (kworm)
*kworm = wpos;
return result;
}
/* Use the owl code to determine whether the attack move at (move) of
* the dragon (target) is effective, i.e. whether it kills the stones.
*
* Should be called only when stackp==0.
*/
int
owl_does_attack(int move, int target, int *kworm)
{
int color = board[target];
int other = OTHER_COLOR(color);
int result = 0;
struct local_owl_data *owl;
int reading_nodes_when_called = get_reading_node_counter();
int tactical_nodes;
int origin;
int dcode;
int wpos = NO_MOVE;
int wid = MAX_GOAL_WORMS;
double start = 0.0;
if (debug & DEBUG_OWL_PERFORMANCE)
start = gg_cputime();
if (worm[target].unconditional_status == ALIVE)
return 0;
origin = dragon[target].origin;
TRACE("owl_does_attack %1m %1m(%1m)\n", move, target, origin);
if (search_persistent_owl_cache(OWL_DOES_ATTACK, move, target, 0,
&result, kworm, NULL, NULL))
return result;
/* FIXME: We want to do this after the trymove(), but currently
* owl_mark_dragon() may crash if the trymove() happens to remove
* some stones of the goal dragon from the board.
*/
#if 1
init_owl(&owl, target, NO_MOVE, NO_MOVE, 1, NULL);
#endif
if (trymove(move, other, "owl_does_attack", target)) {
/* Check if a compatible owl_defend() is cached. */
if (search_persistent_owl_cache(OWL_DEFEND, origin, 0, 0,
&result, NULL, kworm, NULL)) {
popgo();
return REVERSE_RESULT(result);
}
#if 0
local_owl_node_counter = 0;
owl->lunches_are_current = 0;
owl_mark_dragon(target, NO_MOVE, owl);
#endif
owl_update_boundary_marks(move, owl);
#if 0
compute_owl_escape_values(owl);
#endif
/* FIXME: Should also check if part of the dragon was captured,
* like do_owl_attack() does.
*/
if (board[target] == EMPTY)
dcode = 0;
else {
prepare_goal_list(target, owl, owl_goal_worm, &goal_worms_computed,
kworm, 0);
dcode = do_owl_defend(target, NULL, &wid, owl, 0);
finish_goal_list(&goal_worms_computed, &wpos, owl_goal_worm, wid);
}
result = REVERSE_RESULT(dcode);
owl->lunches_are_current = 0;
popgo();
}
else
return 0; /* Don't cache anything in this case. */
tactical_nodes = get_reading_node_counter() - reading_nodes_when_called;
DEBUG(DEBUG_OWL_PERFORMANCE,
"owl_does_attack %1m %1m(%1m), result %d (%d, %d nodes, %f seconds)\n",
move, target, origin, result, local_owl_node_counter,
tactical_nodes, gg_cputime() - start);
store_persistent_owl_cache(OWL_DOES_ATTACK, move, target, 0,
result, wpos, 0, 0,
tactical_nodes, owl->goal, board[target]);
if (kworm)
*kworm = wpos;
return result;
}
/* Use the owl code to determine whether connecting the two dragons
* (target1) and (target2) by playing at (move) results in a living
* dragon. Should be called only when stackp==0.
*/
int
owl_connection_defends(int move, int target1, int target2)
{
int color = board[target1];
int result = 0;
int reading_nodes_when_called = get_reading_node_counter();
int tactical_nodes;
double start = 0.0;
struct local_owl_data *owl;
if (debug & DEBUG_OWL_PERFORMANCE)
start = gg_cputime();
ASSERT1(board[target2] == color, target2);
TRACE("owl_connection_defends %1m %1m %1m\n", move, target1, target2);
if (worm[target1].unconditional_status == DEAD)
return 0;
if (worm[target2].unconditional_status == DEAD)
return 0;
if (search_persistent_owl_cache(OWL_CONNECTION_DEFENDS, move, target1,
target2, &result, NULL, NULL, NULL))
return result;
init_owl(&owl, target1, target2, NO_MOVE, 1, NULL);
if (trymove(move, color, "owl_connection_defends", target1)) {
owl_update_goal(move, SAME_DRAGON_MAYBE_CONNECTED, NO_MOVE, owl, 0, NULL);
if (!do_owl_attack(move, NULL, NULL, owl, 0))
result = WIN;
owl->lunches_are_current = 0;
popgo();
}
tactical_nodes = get_reading_node_counter() - reading_nodes_when_called;
DEBUG(DEBUG_OWL_PERFORMANCE,
"owl_conn_defends %1m %1m %1m, result %d (%d, %d nodes, %f seconds)\n",
move, target1, target2, result, local_owl_node_counter,
tactical_nodes, gg_cputime() - start);
store_persistent_owl_cache(OWL_CONNECTION_DEFENDS, move, target1, target2,
result, 0, 0, 0, tactical_nodes,
owl->goal, color);
return result;
}
/* This function attempts to make a list of dead strings
* which may be relevant to the life of the goal dragon.
* Such strings are called owl lunches. They are ignored
* (treated as invisible) during the running of make_domains.
*
* In certain cases we also need to identify tactically safe strings
* which should be included in the eyespace, e.g. in this position:
*
* -------
* OXXOOXO
* OX.O.XO
* OXX.XXO
* OOXXXOO
* .OOOOO.
*
* The three O stones cannot be captured, but they can't live
* independently without capturing the surrounding stones. We call
* such stones INESSENTIAL and identify them by the condition that for
* each liberty of the corresponding superstring, the following must
* hold:
*
* 1. At least one neighbor of the liberty is the goal dragon.
* 2. No neighbor of the liberty is the same color as the tested string,
* unless part of the same superstring.
* 3. No neighbor of the liberty of the same color as the goal dragon
* does not belong to the goal dragon.
* 4. No neighbor of the liberty belonging to the goal dragon can be
* tactically captured.
*
* There is a weakness with this characterization though, which can be
* seen in this position:
*
* --------
* OX..OOX.
* OX.X.XOO
* OX.XX.O.
* O.XXOOO.
* .OOOO...
*
* The two O stones intruding in X's eyespace cannot be tactically
* captured and their liberties satisfy the requirements above. Still
* it doesn't make any sense to count those stones as
* inessential. Therefore we add another requirement on the stones
* themself:
*
* 5. No neighbor of the stones does not belong to the goal or can be
* tactically captured.
*
* A second weakness can be noticed in this position:
*
* |OOOO.
* |XXXO.
* |O.XOO
* |OXXXO
* |.O.XO
* +-----
*
* The white stones in the corner should qualify as inessential but
* the corner liberty doesn't satisfy requirement 1. Therefore we add
* an alternative requirement:
*
* 1b. The liberty is a topologically false eye with respect to the
* goal dragon.
*
* This is not quite good enough though, as shown in this position:
*
* ----------
* OX.X.OO...
* OXX.OOX.O.
* O.XXXXX.O.
* OOOOOOOOO.
*
* The four O stones are regarded as inessential after inclusion of
* rule 1b, which is clearly inappropriate. To solve this problem we
* modify the rule:
*
* 1b'. The liberty is a topologically false eye with respect to the
* goal dragon and is adjacent to no empty vertex.
*/
static void
owl_find_lunches(struct local_owl_data *owl)
{
int k;
int pos;
int lunches = 0;
int prevlunch;
int lunch;
int acode;
int apos;
int dcode;
int dpos;
int color = owl->color;
int other = OTHER_COLOR(color);
signed char already_checked[BOARDMAX];
SGFTree *save_sgf_dumptree = sgf_dumptree;
int save_count_variations = count_variations;
sgf_dumptree = NULL;
count_variations = 0;
for (prevlunch = 0; prevlunch < MAX_LUNCHES; prevlunch++)
owl->lunch[prevlunch] = NO_MOVE;
memset(owl->inessential, 0, sizeof(owl->inessential));
memset(already_checked, 0, sizeof(already_checked));
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (board[pos] == color && owl->goal[pos]) {
/* Loop over the eight neighbors. */
for (k = 0; k < 8; k++) {
int pos2 = pos + delta[k];
/* If the immediate neighbor is empty, we look two steps away. */
if (k < 4 && board[pos2] == EMPTY)
pos2 += delta[k];
if (board[pos2] != other)
continue;
lunch = find_origin(pos2);
if (already_checked[lunch])
continue;
already_checked[lunch] = 1;
attack_and_defend(lunch, &acode, &apos, &dcode, &dpos);
if (acode != 0) {
owl->lunch[lunches] = lunch;
owl->lunch_attack_code[lunches] = acode;
owl->lunch_attack_point[lunches] = apos;
owl->lunch_defend_code[lunches] = dcode;
ASSERT1(board[apos] == EMPTY, lunch);
if (dcode != 0) {
owl->lunch_defense_point[lunches] = dpos;
ASSERT1(board[dpos] == EMPTY, lunch);
}
else
owl->lunch_defense_point[lunches] = NO_MOVE;
lunches++;
if (lunches == MAX_LUNCHES) {
sgf_dumptree = save_sgf_dumptree;
count_variations = save_count_variations;
owl->lunches_are_current = 1;
return;
}
}
else if (!owl->inessential[lunch]) {
/* Test for inessentiality. */
int adj;
int adjs[MAXCHAIN];
int num_stones;
int stones[MAX_BOARD * MAX_BOARD];
int liberties;
int libs[MAXLIBS];
int r;
int essential = 0;
int superstring[BOARDMAX];
/* First check the neighbors of the string. */
adj = chainlinks(lunch, adjs);
for (r = 0; r < adj; r++) {
if (!owl->goal[adjs[r]] || attack(adjs[r], NULL) != 0) {
essential = 1;
break;
}
}
if (essential)
continue;
find_superstring_stones_and_liberties(lunch, &num_stones, stones,
&liberties, libs, 0);
memset(superstring, 0, sizeof(superstring));
for (r = 0; r < num_stones; r++)
superstring[stones[r]] = 1;
for (r = 0; r < liberties; r++) {
int bpos = libs[r];
int goal_found = 0;
int s;
for (s = 0; s < 4; s++) {
int cpos = bpos + delta[s];
if (!ON_BOARD(cpos))
continue;
if (board[cpos] == color) {
if (attack(cpos, NULL) != 0) {
essential = 1;
break;
}
else if (owl->goal[cpos])
goal_found = 1;
else {
essential = 1;
break;
}
}
else if (board[cpos] == other
&& !superstring[cpos]) {
essential = 1;
break;
}
}
if (!goal_found) {
/* Requirement 1 not satisfied. Test requirement 1b.
* N.B. This is a simplified topological eye test.
* The simplification may be good, bad, or neutral.
*/
int off_board = 0;
int diagonal_goal = 0;
for (s = 4; s < 8; s++) {
if (!ON_BOARD(bpos + delta[s]))
off_board++;
else if (owl->goal[bpos + delta[s]])
diagonal_goal++;
}
if (diagonal_goal + (off_board >= 2) < 2)
essential = 1;
else {
/* Check that the liberty is adjacent to no empty
* vertex, as required by 1b'.
*/
for (s = 0; s < 4; s++) {
if (board[bpos + delta[s]] == EMPTY) {
essential = 1;
break;
}
}
}
}
if (essential)
break;
}
if (!essential) {
TRACE("Inessential string found at %1m.\n", lunch);
for (r = 0; r < num_stones; r++)
owl->inessential[stones[r]] = 1;
}
}
}
}
}
owl->lunches_are_current = 1;
sgf_dumptree = save_sgf_dumptree;
count_variations = save_count_variations;
}
/* Try to improve the move to attack a lunch. Essentially we try to avoid
* unsafe moves when there are less risky ways to attack.
*
* This function also improves lunch attack point in a special case when
* we capture a one- or two-stone lunch on the first line. If we eat it
* with a first line move, there is a huge risk we'll end up with a false
* eye. Therefore, we move the attack to the second line when it works.
*
* .*OO .*OOO .*OOOO
* .,XO .,X.O .,XX.O
* ---- ----- ------
*
* In all these position the attack point is moved from ',' to '*'.
*/
static int
improve_lunch_attack(int lunch, int attack_point)
{
int color = OTHER_COLOR(board[lunch]);
int defense_point;
int k;
if (safe_move(attack_point, color)) {
if (is_edge_vertex(lunch)
&& is_edge_vertex(attack_point)
&& neighbor_of_string(attack_point, lunch)) {
int stones = countstones(lunch);
int libs[2];
if (stones == 1
|| (stones == 2
&& findlib(lunch, 2, libs) == 2
&& is_edge_vertex(libs[0])
&& is_edge_vertex(libs[1]))) {
for (k = 0; k < 4; k++) {
int apos = attack_point + delta[k];
if (!ON_BOARD(attack_point - delta[k]) && board[apos] == EMPTY) {
if (does_attack(apos, lunch) && safe_move(apos, color))
return apos;
break;
}
}
}
}
return attack_point;
}
for (k = 0; k < 4; k++) {
int pos = attack_point + delta[k];
if (board[pos] == color
&& attack(pos, NULL)
&& find_defense(pos, &defense_point)
&& defense_point != NO_MOVE
&& does_attack(defense_point, lunch)) {
TRACE("Moved attack of lunch %1m from %1m to %1m.\n",
lunch, attack_point, defense_point);
return defense_point;
}
}
return attack_point;
}
/* Try to improve the move to defend a lunch.
*
* An example where this is useful is the position below, where the
* defense of A is moved from b to c. This is a possible variation in
* ld_owl:182.
*
* ...X..| ...X..|
* ...X..| ...Xc.|
* ..XXO.| ..XXOb|
* XXXOOX| XXXOOA|
* XOOOX.| XOOOX.|
* .XOX.X| .XOX.X|
* ------+ ------+
*/
static int
improve_lunch_defense(int lunch, int defense_point)
{
int color = board[lunch];
int k;
for (k = 0; k < 4; k++) {
int pos = defense_point + delta[k];
if (board[pos] == OTHER_COLOR(color)
&& countlib(pos) == 2) {
int libs[2];
int pos2;
findlib(pos, 2, libs);
if (libs[0] == defense_point)
pos2 = libs[1];
else
pos2 = libs[0];
if (accuratelib(pos2, color, MAXLIBS, NULL)
> accuratelib(defense_point, color, MAXLIBS, NULL)
&& does_defend(pos2, lunch)) {
TRACE("Moved defense of lunch %1m from %1m to %1m.\n",
lunch, defense_point, pos2);
return pos2;
}
}
}
return defense_point;
}
/* Wrapper for make domains. The second set of owl data is optional.
* Use a null pointer if it is not needed. Otherwise, make_domains
* is run separately for the two owl data, but information about
* tactically dead lunches is used from *both* sources through
* the owl_lively() calls.
*/
static void
owl_make_domains(struct local_owl_data *owla, struct local_owl_data *owlb)
{
/* We need to set this so that owl_lively() can be used. */
struct eye_data *black_eye = NULL;
struct eye_data *white_eye = NULL;
current_owl_data = owla;
other_owl_data = owlb;
if (!owla->lunches_are_current)
owl_find_lunches(owla);
if (owla->color == BLACK)
black_eye = owla->my_eye;
else
white_eye = owla->my_eye;
if (owlb) {
gg_assert(owla->color == OTHER_COLOR(owlb->color));
if (!owlb->lunches_are_current)
owl_find_lunches(owlb);
if (owlb->color == BLACK)
black_eye = owlb->my_eye;
else
white_eye = owlb->my_eye;
}
make_domains(black_eye, white_eye, 1);
}
/* True unless (pos) is EMPTY or occupied by a lunch for the goal dragon.
* Used during make_domains (see optics.c: lively macro). A ``lively''
* worm is one that might be alive, hence cannot be ignored in
* determining eye spaces.
*/
int
owl_lively(int pos)
{
int origin;
int lunch;
ASSERT_ON_BOARD1(pos);
if (board[pos] == EMPTY)
return 0;
origin = find_origin(pos);
/* When reading a semeai there is a second set of owl data to consider.
* Strings of the second owl are considered lively no matter what,
* since declaring such a string dead prematurely can prevent the
* semeai code from finishing its job.
*
* On the other hand a friendly string which is a lunch of the
* other dragon and can't be saved is not lively.
*/
if (other_owl_data) {
if (include_semeai_worms_in_eyespace && other_owl_data->goal[pos])
return 0;
if (other_owl_data->goal[pos] && !semeai_trust_tactical_attack(pos))
return 1;
/* FIXME: Shouldn't we check other_owl_data->inessential[origin] here? */
for (lunch = 0; lunch < MAX_LUNCHES; lunch++)
if (other_owl_data->lunch[lunch] == origin
&& other_owl_data->lunch_defense_point[lunch] == NO_MOVE)
return 0;
}
/* Inessential stones are not lively. */
if (current_owl_data->inessential[origin])
return 0;
/* Lunches that can't be saved are dead, so don't report them as lively. */
for (lunch = 0; lunch < MAX_LUNCHES; lunch++)
if (current_owl_data->lunch[lunch] == origin
&& current_owl_data->lunch_defense_point[lunch] == NO_MOVE)
return 0;
return 1;
}
/* Caching version of safe_move for the callback. This function has
* its own cache, separate from the global safe move cache. Note that
* since the cache is reset by owl_shapes before starting pattern
* matching, and since (unlike safe_move) this function is always
* called from the same place in owl_shapes_callback, the color will
* be the same each time it is called. So there is no need to have
* separate caches for B and W.
*/
static int
owl_safe_move(int move, int color)
{
int acode, safe = 0;
if (trymove(move, color, "owl_safe_move", 0)) {
acode = attack(move, NULL);
if (acode != WIN)
safe = 1;
else
safe = 0;
popgo();
}
current_owl_data->safe_move_cache[move] = safe+1;
return safe;
}
/* This function, called when stackp==0, returns true if capturing
* the string at (str) results in a live group.
*/
#define MAX_SUBSTANTIAL_LIBS 10
int
owl_substantial(int str)
{
int k;
int libs[MAX_SUBSTANTIAL_LIBS + 1];
int liberties = findlib(str, MAX_SUBSTANTIAL_LIBS+1, libs);
int reading_nodes_when_called = get_reading_node_counter();
int tactical_nodes;
int result;
double start = 0.0;
struct local_owl_data *owl;
int num_moves = 0;
if (debug & DEBUG_OWL_PERFORMANCE)
start = gg_cputime();
/* FIXME: We want to use the full init_owl here too (cf. similar
* remark below).
*/
reduced_init_owl(&owl, 1);
owl->color = OTHER_COLOR(board[str]);
local_owl_node_counter = 0;
/* Big strings are always substantial since the biggest nakade is
* six stones. (There are probably rare exceptions to this
* rule, but they are unlikely to come up in a game.)
*/
if (countstones(str) > 6)
return 1;
if (liberties > MAX_SUBSTANTIAL_LIBS)
return 0;
memset(owl->goal, 0, sizeof(owl->goal));
/* Mark the neighbors of the string. If one is found which is alive, return
* true. */
{
int adjs[MAXCHAIN];
int adj;
adj = chainlinks(str, adjs);
for (k = 0; k < adj; k++) {
if (dragon[adjs[k]].status == ALIVE)
return 1;
mark_dragon(adjs[k], owl->goal, 1);
}
}
/* We must check the cache while stackp == 0, but we wait until the
* trivial tests have been done.
*/
if (search_persistent_owl_cache(OWL_SUBSTANTIAL, str, 0, 0,
&result, NULL, NULL, NULL))
return result;
/* fill all the liberties */
for (k = 0; k < liberties; k++) {
if (trymove(libs[k], owl->color, NULL, 0)) {
if (get_level() >= 8)
increase_depth_values();
owl->goal[libs[k]] = 1;
num_moves++;
}
else {
/* if we can't fill, try swapping with the next liberty */
if (k < liberties-1
&& trymove(libs[k+1], owl->color, NULL, 0)) {
if (get_level() >= 8)
increase_depth_values();
owl->goal[libs[k+1]] = 1;
libs[k+1] = libs[k];
num_moves++;
}
else {
/* Can't fill the liberties. Give up! */
while (num_moves-- > 0) {
if (get_level() >= 8)
decrease_depth_values();
popgo();
}
return 0;
}
}
}
/* FIXME: We would want to use init_owl() here too, but it doesn't
* fit very well with the construction of the goal array above.
*/
memcpy(owl->cumulative_goal, owl->goal, BOARDMAX);
compute_owl_escape_values(owl);
owl_mark_boundary(owl);
owl->lunches_are_current = 0;
if (do_owl_attack(libs[0], NULL, NULL, owl, 0))
result = 0;
else
result = 1;
while (num_moves-- > 0) {
if (get_level() >= 8)
decrease_depth_values();
popgo();
}
tactical_nodes = get_reading_node_counter() - reading_nodes_when_called;
DEBUG(DEBUG_OWL_PERFORMANCE,
"owl_substantial %1m, result %d (%d, %d nodes, %f seconds)\n",
str, result, local_owl_node_counter,
tactical_nodes, gg_cputime() - start);
store_persistent_owl_cache(OWL_SUBSTANTIAL, str, 0, 0, result, 0, 0, 0,
tactical_nodes, owl->goal, owl->color);
return result;
}
/* Returns true if and only if (i, j) is a 1-2 vertex, i.e. next to a
* corner.
*/
static int
one_two_point(int pos)
{
int i = I(pos);
int j = J(pos);
if ((i == 0 || i == board_size-1 || j == 0 || j == board_size-1)
&& (i == 1 || i == board_size-2 || j == 1 || j == board_size-2))
return 1;
return 0;
}
/* Reports the number of eyes gotten by capturing a boundary string.
* This implementation tends to give an optimistic view of the
* chances, so if it tells that the lunch is worthless, it truly
* should be. The converse is not true.
*/
static void
sniff_lunch(int lunch, int *min, int *probable, int *max,
struct local_owl_data *owl)
{
int other = OTHER_COLOR(board[lunch]);
int libs[MAXLIBS];
int liberties;
int r;
ASSERT1(IS_STONE(board[lunch]), lunch);
if (owl->boundary[lunch] == 2) {
*min = 2;
*probable = 2;
*max = 2;
return;
}
/* Do we believe this capture would help escaping? */
liberties = findlib(lunch, MAXLIBS, libs);
for (r = 0; r < liberties; r++) {
if (owl->escape_values[libs[r]] > 0
&& !is_self_atari(libs[r], other)) {
int k;
for (k = 0; k < 8; k++)
if (ON_BOARD(libs[r] + delta[k]) && owl->goal[libs[r] + delta[k]])
break;
if (k == 8) {
*min = 2;
*probable = 2;
*max = 2;
return;
}
}
}
estimate_lunch_eye_value(lunch, min, probable, max, 1);
if (*min < 2) {
int bonus = estimate_lunch_half_eye_bonus(lunch, owl->half_eye);
*min += bonus/2;
*probable += bonus;
*max += (bonus + 1)/2;
}
if (*probable < 2)
eat_lunch_escape_bonus(lunch, min, probable, max, owl);
}
/* Capturing a lunch can give eyes by turning a false eye into a proper one,
* etc. This function returns the likely increase in half eyes
* by capturing the string at (lunch).
*/
static int
estimate_lunch_half_eye_bonus(int lunch,
struct half_eye_data half_eye[BOARDMAX])
{
int stones[10];
int k;
int size = findstones(lunch, 10, stones);
int half_eyes = 0;
ASSERT1(size < 10, lunch);
for (k = 0; k < size; k++) {
int stone = stones[k];
int d;
for (d = 4; d < 8; d++) {
int pos = stone + delta[d];
if (ON_BOARD(pos)
&& (is_halfeye(half_eye, pos) || is_false_eye(half_eye, pos)))
half_eyes++;
}
}
return half_eyes;
}
void
estimate_lunch_eye_value(int lunch, int *min, int *probable, int *max,
int appreciate_one_two_lunches)
{
int other = OTHER_COLOR(board[lunch]);
int size = countstones(lunch);
if (size > 6) {
*min = 2;
*probable = 2;
*max = 2;
}
else if (size > 4) {
*min = 1;
*probable = 2;
*max = 2;
}
else if (size > 2) {
*min = 0;
*probable = 1;
*max = 2;
}
else if (size == 2) {
int stones[2];
findstones(lunch, 2, stones);
/* A lunch on a 1-2 point tends always to be worth contesting. */
if ((obvious_false_eye(stones[0], other)
|| obvious_false_eye(stones[1], other))
&& (!appreciate_one_two_lunches
|| !(one_two_point(stones[0]) || one_two_point(stones[1])))) {
*min = 0;
*probable = 0;
*max = 0;
}
else {
*min = 0;
*probable = 1;
*max = 1;
}
}
else if (size == 1) {
if (!obvious_false_eye(lunch, other)) {
*min = 0;
*probable = 1;
*max = 1;
}
else {
*min = 0;
*probable = 0;
*max = 0;
}
}
}
/* Gives a bonus for a lunch capture which joins a (or some) friendly
* string(s) to the goal dragon and improves the escape potential at
* the same time. This is indicated in some situations where the owl
* code would stop the analysis because of various cutoffs. See
* do_owl_defend()
*
* The following implementation tries to get a precise idea of the
* escape potential improvement by calling dragon_escape() twice.
*/
static void
eat_lunch_escape_bonus(int lunch, int *min, int *probable, int *max,
struct local_owl_data *owl)
{
int adjacent[MAXCHAIN];
int neighbors;
int adjoins = 0;
int n;
/* Be very careful before touching this value.
* See owl_estimate_life() for details.
*/
UNUSED(min);
/* Don't mess up with kos */
if (is_ko_point(lunch))
return;
neighbors = chainlinks(lunch, adjacent);
for (n = 0; n < neighbors; n++)
adjoins |= !owl->goal[adjacent[n]];
if (adjoins) {
int before, after;
before = dragon_escape(owl->goal, owl->color, owl->escape_values);
/* if the escape route is already large enough to be considered
* a WIN by the owl code, then no need for more */
if (before < 5) {
signed char new_goal[BOARDMAX];
memcpy(new_goal, owl->goal, sizeof(new_goal));
for (n = 0; n < neighbors; n++)
if (!owl->goal[adjacent[n]])
mark_string(adjacent[n], new_goal, 2);
after = dragon_escape(new_goal, owl->color, owl->escape_values);
/* Following is completely ad hoc. Another set of tests might
* very well get better results. */
if (after - before >= 3) {
if (after >= 8 || (before == 0 && after >= 5)) {
*probable = 2;
*max = 2;
}
else if (*max < 2)
(*max)++;
}
}
}
}
/* Find a new origin when it has been captured or cut out of the
* goal. Used in do_owl_attack()
*/
static int
select_new_goal_origin(int origin, struct local_owl_data *owl)
{
int pos;
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
if (board[pos] == owl->color && owl->goal[pos] == 1)
return find_origin(pos);
return origin;
}
/* Retrieve topological eye values stored in the half_eye[] array of
* the current owl data.
*
* FIXME: Sooner or later we'll want this to return a non-rounded
* value. When we change this, we have to review all patterns using
* the autohelper owl_topological_eye().
*/
int
owl_topological_eye(int pos, int color)
{
float value;
UNUSED(color);
value = current_owl_data->half_eye[pos].value;
if (value > 2.0 && value < 4.0)
return 3;
else if (value <= 2.0)
return (int) (value + 0.99); /* Round up. */
else
return (int) value; /* Round down. */
}
/* This function returns true if it is judged that the capture of the
* string at (pos) is sufficient to create one eye.
*
* Update: Now it instead returns the max number of eyes.
*/
int
vital_chain(int pos)
{
int min;
int probable;
int max;
sniff_lunch(pos, &min, &probable, &max, current_owl_data);
return max;
}
static void
compute_owl_escape_values(struct local_owl_data *owl)
{
int pos;
int m, n;
signed char safe_stones[BOARDMAX];
SGFTree *save_sgf_dumptree = sgf_dumptree;
int save_count_variations = count_variations;
signed char mx[BOARDMAX];
memset(mx, 0, sizeof(mx));
sgf_dumptree = NULL;
count_variations = 0;
get_lively_stones(OTHER_COLOR(owl->color), safe_stones);
sgf_dumptree = save_sgf_dumptree;
count_variations = save_count_variations;
compute_escape_influence(owl->color, safe_stones, NULL, NULL,
owl->escape_values);
DEBUG(DEBUG_ESCAPE, "Owl escape values:\n");
for (m = 0; m < board_size; m++) {
for (n = 0; n < board_size; n++) {
pos = POS(m, n);
if (dragon[pos].color == owl->color && !owl->goal[pos]) {
if (dragon[pos].crude_status == ALIVE)
owl->escape_values[pos] = 6;
else if (dragon[pos].crude_status == UNKNOWN) {
if (DRAGON2(pos).moyo_size > 5)
owl->escape_values[pos] = 4;
else if (DRAGON2(pos).escape_route > 5) {
if (mx[dragon[pos].origin])
owl->escape_values[pos] = owl->escape_values[dragon[pos].origin];
else {
int pos2;
signed char escape_values[BOARDMAX];
signed char dragon_stones[BOARDMAX];
compute_escape_influence(owl->color, safe_stones, owl->goal,
NULL, escape_values);
/* mark_dragon() can't be used here in case a string of
* the dragon was captured by the initial move in
* owl_does_attack(). Actually it isn't really proper to
* use is_same_dragon() at stackp>0 either but it's more
* robust at least.
*/
for (pos2 = BOARDMIN; pos2 < BOARDMAX; pos2++) {
if (ON_BOARD(pos2))
dragon_stones[pos2] = is_same_dragon(pos2, pos);
}
if (dragon_escape(dragon_stones, owl->color, escape_values) > 5)
owl->escape_values[dragon[pos].origin] = 4;
mx[dragon[pos].origin] = 1;
}
}
}
}
DEBUG(DEBUG_ESCAPE, "%o%d", owl->escape_values[pos]);
}
DEBUG(DEBUG_ESCAPE, "%o\n");
}
}
/* Used by autohelpers. */
int
owl_escape_value(int pos)
{
/* FIXME: Should have a more robust mechanism to avoid
* escaping inwards. Returning a negative value is just a kludge.
*/
int k;
ASSERT_ON_BOARD1(pos);
if (current_owl_data->goal[pos])
return -10;
if (board[pos] == EMPTY)
for (k = 0; k < 8; k++)
if (ON_BOARD(pos + delta[k]) && current_owl_data->goal[pos + delta[k]])
return -10;
return current_owl_data->escape_values[pos];
}
/* Used by autohelpers. */
int
owl_goal_dragon(int pos)
{
return current_owl_data->goal[pos] != 0;
}
/* Used by autohelpers.
* Returns 1 if (pos) is an eyespace for the color of the dragon currently
* under owl investigation.
*/
int
owl_eyespace(int pos)
{
int origin;
ASSERT_ON_BOARD1(pos);
origin = current_owl_data->my_eye[pos].origin;
return (ON_BOARD(origin)
&& (current_owl_data->my_eye[origin].color
== current_owl_data->color)
&& max_eyes(&current_owl_data->my_eye[origin].value) > 0);
}
/* Used by autohelpers.
* Returns 1 if (pos) is an eyespace for the color of the dragon currently
* under owl investigation, which is possibly worth (at least) 2 eyes.
*/
int
owl_big_eyespace(int pos)
{
int origin;
ASSERT_ON_BOARD1(pos);
origin = current_owl_data->my_eye[pos].origin;
return (ON_BOARD(origin)
&& (current_owl_data->my_eye[origin].color
== current_owl_data->color)
&& max_eyes(&current_owl_data->my_eye[origin].value) >= 2);
}
/* Used by autohelpers.
* Returns 1 if (pos) is an eyespace for the color of the dragon currently
* under owl investigation.
*/
int
owl_mineye(int pos)
{
int origin;
ASSERT_ON_BOARD1(pos);
origin = current_owl_data->my_eye[pos].origin;
if (!ON_BOARD(origin)
|| (current_owl_data->my_eye[origin].color
!= current_owl_data->color))
return 0;
return min_eyes(&current_owl_data->my_eye[origin].value);
}
/* Used by autohelpers.
* Returns 1 if (pos) is an eyespace for the color of the dragon currently
* under owl investigation.
*/
int
owl_maxeye(int pos)
{
int origin;
ASSERT_ON_BOARD1(pos);
origin = current_owl_data->my_eye[pos].origin;
if (!ON_BOARD(origin)
|| (current_owl_data->my_eye[origin].color
!= current_owl_data->color))
return 0;
return max_eyes(&current_owl_data->my_eye[origin].value);
}
/* Used by autohelpers.
* Returns 1 if (pos) is a non-marginal eyespace for the color of the
* dragon currently under owl investigation.
*/
int
owl_proper_eye(int pos)
{
ASSERT_ON_BOARD1(pos);
return ((current_owl_data->my_eye[pos].color
== current_owl_data->color)
&& !current_owl_data->my_eye[pos].marginal);
}
/* Used by autohelpers.
* Returns the effective size of the eyespace at pos.
*/
int
owl_eye_size(int pos)
{
int origin;
ASSERT_ON_BOARD1(pos);
origin = current_owl_data->my_eye[pos].origin;
return current_owl_data->my_eye[origin].esize
- current_owl_data->my_eye[origin].msize;
}
/* Used by autohelpers.
* Returns whether str is a lunch.
*/
int
owl_lunch(int str)
{
int k;
int origin;
ASSERT_ON_BOARD1(str);
ASSERT1(current_owl_data->lunches_are_current, str);
origin = find_origin(str);
for (k = 0; k < MAX_LUNCHES; k++) {
if (current_owl_data->lunch[k] == NO_MOVE)
break;
if (current_owl_data->lunch[k] == origin)
return 1;
}
return 0;
}
/* Used by autohelpers.
* Returns 1 if (pos) is considered to be a strong dragon. This is
* intended to be used to decide whether connecting to some external
* stones is an easy way to live. The current implementation is fairly
* conservative, requiring that (pos) was part of a dragon with two
* eyes according to the static analysis. This requirement may be
* relaxed considerably in the future.
*
* (pos) must not be part of the goal dragon.
*/
int
owl_strong_dragon(int pos)
{
ASSERT_ON_BOARD1(pos);
ASSERT1(IS_STONE(board[pos]), pos);
return (!current_owl_data->goal[pos]
&& dragon[pos].color == board[pos]
&& dragon[pos].crude_status == ALIVE);
}
static int
owl_escape_route(struct local_owl_data *owl)
{
signed char modified_escape[BOARDMAX];
int pos;
memcpy(modified_escape, owl->escape_values, sizeof(modified_escape));
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
if (ON_BOARD(pos) && owl->cumulative_goal[pos])
modified_escape[pos] = 0;
return dragon_escape(owl->goal, owl->color, modified_escape);
}
/****************************
* Initialization of owl data
****************************/
/* This is a temporary solution. We want to be able to use the full
* init_owl() also in owl_substantial.
*/
static void
reduced_init_owl(struct local_owl_data **owl, int at_bottom_of_stack)
{
if (at_bottom_of_stack)
owl_stack_pointer = 0;
else
owl_stack_pointer++;
check_owl_stack_size();
*owl = owl_stack[owl_stack_pointer];
VALGRIND_MAKE_WRITABLE(*owl, sizeof(struct local_owl_data));
}
/* Initialize owl data. Set at_bottom_of_stack to 1 the first time you
* call init_owl() and to 0 any following time (only relevant if you
* need more than one set of owl data).
*/
static void
init_owl(struct local_owl_data **owl, int target1, int target2, int move,
int at_bottom_of_stack, int new_dragons[BOARDMAX])
{
reduced_init_owl(owl, at_bottom_of_stack);
local_owl_node_counter = 0;
(*owl)->lunches_are_current = 0;
owl_mark_dragon(target1, target2, *owl, new_dragons);
if (move != NO_MOVE)
owl_update_goal(move, SAME_DRAGON_MAYBE_CONNECTED, NO_MOVE, *owl, 0, NULL);
compute_owl_escape_values(*owl);
}
/***********************
* Storage of owl data
***********************/
/* Check the size of the owl stack and extend it if too small. */
static void
check_owl_stack_size(void)
{
while (owl_stack_size <= owl_stack_pointer) {
owl_stack[owl_stack_size] = malloc(sizeof(*owl_stack[0]));
gg_assert(owl_stack[owl_stack_size] != NULL);
owl_stack_size++;
}
}
/* Push owl data one step upwards in the stack. Gets called from
* push_owl.
*/
static void
do_push_owl(struct local_owl_data **owl)
{
struct local_owl_data *new_owl = owl_stack[owl_stack_pointer];
/* Mark all the data in *new_owl as uninitialized. */
VALGRIND_MAKE_WRITABLE(new_owl, sizeof(struct local_owl_data));
/* Copy the owl data. */
memcpy(new_owl->goal, (*owl)->goal, sizeof(new_owl->goal));
memcpy(new_owl->cumulative_goal, (*owl)->cumulative_goal,
sizeof(new_owl->cumulative_goal));
memcpy(new_owl->boundary, (*owl)->boundary, sizeof(new_owl->boundary));
memcpy(new_owl->neighbors, (*owl)->neighbors, sizeof(new_owl->neighbors));
memcpy(new_owl->escape_values, (*owl)->escape_values,
sizeof(new_owl->escape_values));
new_owl->color = (*owl)->color;
new_owl->lunches_are_current = 0;
/* Needed for stack organization. Since there may be one or two sets
* of owl data active at we don't know whether to restore from the
* previos stack entry or two steps back.
*/
new_owl->restore_from = *owl;
/* Finally move the *owl pointer. */
*owl = new_owl;
}
/* Push owl data one step upwards in the stack. The stack is extended
* with dynamically allocated memory if it is too small.
*
* This function no longer may move existing owl data around, so
* existing pointers do not risk becoming invalid.
*/
static void
push_owl(struct local_owl_data **owl)
{
owl_stack_pointer++;
check_owl_stack_size();
do_push_owl(owl);
}
/* Retrieve owl data from the stack. */
static void
pop_owl(struct local_owl_data **owl)
{
*owl = (*owl)->restore_from;
owl_stack_pointer--;
}
/*
* List worms in order to track captures during owl reading
* (GAIN/LOSS codes)
*/
static int
list_goal_worms(struct local_owl_data *owl, int goal_worm[MAX_GOAL_WORMS])
{
int pos, k;
int w = 0;
for (k = 0; k < MAX_GOAL_WORMS; k++)
goal_worm[k] = NO_MOVE;
for (pos = BOARDMIN; pos < BOARDMAX && w < MAX_GOAL_WORMS; pos++) {
if (ON_BOARD(pos)
&& board[pos]
&& owl->goal[pos] == 1) {
int origin = find_origin(pos);
for (k = 0; k < w; k++)
if (goal_worm[k] == origin)
break;
if (k == w)
goal_worm[w++] = pos;
}
}
/* experimental: let's try to fill up the array with other neighboring
* opponent worms
*/
if (1 && (w > 0) && (w < MAX_GOAL_WORMS)) {
pos = goal_worm[0];
for (k = 0; k < DRAGON2(pos).neighbors && w < MAX_GOAL_WORMS; k++) {
int ii;
int d = DRAGON2(pos).adjacent[k];
if (DRAGON(d).color != owl->color)
continue;
for (ii = BOARDMIN; ii < BOARDMAX && w < MAX_GOAL_WORMS; ii++)
if (ON_BOARD(ii) && board[ii] && worm[ii].origin == ii
&& worm[ii].size >= 3 && dragon[ii].id == d)
goal_worm[w++] = ii;
}
}
return w;
}
static void
prepare_goal_list(int str, struct local_owl_data *owl,
int list[MAX_GOAL_WORMS], int *flag,
int *kworm, int do_list)
{
gg_assert(flag != NULL);
if (kworm) {
if (do_list)
list_goal_worms(owl, list);
/* N.B. We cannot use sizeof(list) below because a formal array
* parameter implicitly is converted to a pointer and sizeof(list)
* thus equals sizeof(int *), which is not what we want.
*/
memcpy(dragon_goal_worms[dragon[str].id], list,
sizeof(dragon_goal_worms[dragon[str].id]));
*flag = 1;
}
else
*flag = 0;
}
static void
finish_goal_list(int *flag, int *wpos, int list[MAX_GOAL_WORMS], int index)
{
gg_assert(flag != NULL);
gg_assert(wpos != NULL);
*flag = 0;
if (index == MAX_GOAL_WORMS)
*wpos = NO_MOVE;
else
*wpos = list[index];
}
/* Returns the number of worms in the goal dragon, and a pointer to each */
#if 0
static int
catalog_goal(struct local_owl_data *owl, int goal_worm[MAX_GOAL_WORMS])
{
int pos;
int worms = 0;
int k;
for (k = 0; k < MAX_WORMS; k++)
goal_worm[k] = NO_MOVE;
for (pos = BOARDMIN; pos < BOARDMAX && worms < MAX_WORMS; pos++)
if (ON_BOARD(pos)
&& board[pos]
&& (owl->goal)[pos]) {
int origin = find_origin(pos);
if (pos == origin) {
if (0) {
DEBUG(DEBUG_SEMEAI, "goal worm: %1m\n", pos);
}
goal_worm[worms++] = pos;
}
}
return worms;
}
#endif
/***********************/
/* Clear statistics. */
void
reset_owl_node_counter()
{
global_owl_node_counter = 0;
}
/* Retrieve statistics. */
int
get_owl_node_counter()
{
return global_owl_node_counter;
}
/*
* Local Variables:
* tab-width: 8
* c-basic-offset: 2
* End:
*/