/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ * 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 #include #include #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(¤t_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(¤t_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(¤t_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(¤t_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: */