/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ * 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. * \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* This file contains functions that deals with threats and, * especially, combinations of threats. */ #include "gnugo.h" #include #include "liberty.h" #include "gg_utils.h" #include "patterns.h" static void find_double_threats(int color); /* Generate move reasons for combination attacks and defenses against * them. * * This is one of the move generators called from genmove(). */ void combinations(int color) { int save_verbose; int attack_point; signed char defense_points[BOARDMAX]; int other = OTHER_COLOR(color); int aa_val; /* Find intersections with multiple threats. */ find_double_threats(color); save_verbose = verbose; if (verbose > 0) verbose--; if (save_verbose) gprintf("\nlooking for combination attacks ...\n"); aa_val = atari_atari(color, &attack_point, NULL, save_verbose); if (aa_val > 0) { if (save_verbose) gprintf("Combination attack for %C with size %d found at %1m\n", color, aa_val, attack_point); add_my_atari_atari_move(attack_point, aa_val); } aa_val = atari_atari(other, &attack_point, defense_points, save_verbose); if (aa_val > 0) { int pos; if (save_verbose) gprintf("Combination attack for %C with size %d found at %1m\n", other, aa_val, attack_point); for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (ON_BOARD(pos) && defense_points[pos]) { add_your_atari_atari_move(pos, aa_val); if (save_verbose) gprintf("- defense at %1m\n", pos); } } } verbose = save_verbose; } #define MAX_THREATENED_STRINGS 10 /* Should be enough for one intersection */ static void find_double_threats(int color) { int ii; int k; int l; for (ii = BOARDMIN; ii < BOARDMAX; ii++) { int num_a_threatened_groups; int a_threatened_groups[MAX_THREATENED_STRINGS]; #if 0 int num_d_threatened_groups; int d_threatened_groups[MAX_THREATENED_STRINGS]; #endif if (!ON_BOARD(ii)) continue; /* Generate an EITHER_MOVE move reasons for each pair of the * threatened strings. We must also remove the threats, because * otherwise we would get followup points for them as well. * * FIXME: * - This is perhaps not the best way to do it, but realistically * it will be seldom that more than two strings are threatened * at the same point. Still, we should find a better way. * - EITHER_MOVE should be generalized to more than two strings. */ num_a_threatened_groups = get_attack_threats(ii, MAX_THREATENED_STRINGS, a_threatened_groups); if (num_a_threatened_groups > 1) { if (trymove(ii, color, "find_double_threats-A", ii)) { for (k = 0; k < num_a_threatened_groups - 1; ++k) for (l = k + 1; l < num_a_threatened_groups; ++l) { /* Note: If we used attack_either() here instead of trymove() * and !defend_both(), we would not make use of the fact * that we already know of a common threat point for * the two strings. * Besides, attack_either is currently (3.1.11) not very good. * * The call to attack() is intended to detect the case * where the move at ii is a snapback capture. */ if (board[a_threatened_groups[k]] == EMPTY || board[a_threatened_groups[l]] == EMPTY) { if (!attack(ii, NULL)) { TRACE("Double threat at %1m, either %1m or %1m attacked.\n", ii, a_threatened_groups[k], a_threatened_groups[l]); add_either_move(ii, ATTACK_STRING, a_threatened_groups[k], ATTACK_STRING, a_threatened_groups[l]); remove_attack_threat_move(ii, a_threatened_groups[k]); remove_attack_threat_move(ii, a_threatened_groups[l]); } } else if (!defend_both(a_threatened_groups[k], a_threatened_groups[l])) { TRACE("Double threat at %1m, either %1m or %1m attacked.\n", ii, a_threatened_groups[k], a_threatened_groups[l]); add_either_move(ii, ATTACK_STRING, a_threatened_groups[k], ATTACK_STRING, a_threatened_groups[l]); remove_attack_threat_move(ii, a_threatened_groups[k]); remove_attack_threat_move(ii, a_threatened_groups[l]); } } popgo(); } } } /* FIXME: * TODO: * - defense threats * - combinations of owl threats and other threats * - combinations of threats to cut and connect * - combinations of breakins into enemy territory */ } /* ================================================================ */ /* Combination attacks */ /* ================================================================ */ /* atari_atari(color, *move) looks for a series of ataris on * strings of the other color culminating in the capture of * a string which is thought to be invulnerable by the reading * code. Such a move can be missed since it may be that each * string involved individually can be rescued, but nevertheless * one of them can be caught. The simplest example is a double * atari. The return value is the size of the smallest opponent * worm. * * One danger with this scheme is that the first atari * tried might be irrelevant to the actual combination. * To detect this possibility, once we've found a combination, * we mark that first move as forbidden, then try again. If * no combination of the same size or larger turns up, then * the first move was indeed essential. * * For the purpose of the move generation, returns the * size of the smallest of the worms under attack. */ /* Local struct to keep track of atari_atari attack moves and what * they threat. */ #define AA_MAX_TARGETS_PER_MOVE 4 #define MAX_AA_DIST 5 struct aa_move { int move; int target[AA_MAX_TARGETS_PER_MOVE]; }; #define AA_MAX_MOVES MAX_BOARD * MAX_BOARD static int aa_status[BOARDMAX]; /* ALIVE, DEAD or CRITICAL */ static int forbidden[BOARDMAX]; static int aa_values[BOARDMAX]; static void compute_aa_status(int color, const signed char safe_stones[BOARDMAX]); static void compute_aa_values(int color); static int get_aa_status(int pos); static int do_atari_atari(int color, int *attack_point, int *defense_point, signed char all_potential_defenses[BOARDMAX], int last_friendly, int save_verbose, int minsize, signed char goal[BOARDMAX]); static int atari_atari_succeeded(int color, int *attack_point, int *defense_point, int last_friendly, int save_verbose, int minsize); static void atari_atari_find_attack_moves(int color, int minsize, struct aa_move attacks[AA_MAX_MOVES], signed char goal[BOARDMAX]); static void atari_atari_attack_patterns(int color, int minsize, struct aa_move attacks[AA_MAX_MOVES], signed char goal[BOARDMAX]); static void atari_atari_attack_callback(int anchor, int color, struct pattern *pattern, int ll, void *data); static int atari_atari_find_defense_moves(int targets[AA_MAX_TARGETS_PER_MOVE], int moves[AA_MAX_MOVES]); static int get_aa_value(int str); static int update_aa_goal(signed char goal[BOARDMAX], signed char new_goal[BOARDMAX], int apos, int color); static void aa_init_moves(struct aa_move attacks[AA_MAX_MOVES]); static void aa_add_move(struct aa_move attacks[AA_MAX_MOVES], int move, int target); static int aa_move_known(struct aa_move attacks[AA_MAX_MOVES], int move, int target); static void aa_sort_moves(struct aa_move attacks[AA_MAX_MOVES]); /* Set to 1 if you want verbose traces from this function. */ int atari_atari(int color, int *attack_move, signed char defense_moves[BOARDMAX], int save_verbose) { int other = OTHER_COLOR(color); int apos; int dpos; int aa_val; signed char saved_defense_moves[BOARDMAX]; /* Collect worm statuses of opponent's worms. We need to * know this because we only want to report unexpected * results. For example, we do not want to report success * if we find we can kill a worm which is already dead. * The worm status of empty points is set to UNKNOWN to signal * that stones added along the way need special attention. */ if (aa_depth < 2) return 0; memset(forbidden, 0, sizeof(forbidden)); compute_aa_status(color, NULL); compute_aa_values(color); if (defense_moves) memset(defense_moves, 0, BOARDMAX); aa_val = do_atari_atari(color, &apos, &dpos, defense_moves, NO_MOVE, save_verbose, 0, NULL); if (aa_val == 0) return 0; /* We try excluding the first atari found and see if the * combination still works. Repeat until failure. */ while (1) { int new_aa_val; if (attack_move) *attack_move = apos; forbidden[apos] = 1; if (defense_moves) { memcpy(saved_defense_moves, defense_moves, BOARDMAX); memset(defense_moves, 0, BOARDMAX); } new_aa_val = do_atari_atari(color, &apos, &dpos, defense_moves, NO_MOVE, save_verbose, aa_val, NULL); /* The last do_atari_atari call fails. When do_atari_atari fails, * it does not change the value of (apos), so these correspond * to a move that works and is necessary. */ if (new_aa_val == 0) break; else aa_val = new_aa_val; } if (defense_moves) { int pos; memcpy(defense_moves, saved_defense_moves, BOARDMAX); /* defense_moves[] contains potential defense moves. Now we * examine which of them really work. */ forbidden[apos] = 0; for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (!ON_BOARD(pos) || !defense_moves[pos]) continue; if (!trymove(pos, other, "atari_atari", NO_MOVE)) { defense_moves[pos] = 0; if (save_verbose) gprintf("%1m deleted defense point, illegal\n", pos); continue; } if (attack(pos, NULL)) { defense_moves[pos] = 0; popgo(); if (save_verbose) gprintf("%1m deleted defense point, unsafe\n", pos); continue; } if (do_atari_atari(color, &apos, &dpos, NULL, NO_MOVE, save_verbose, aa_val, NULL) > 0) { if (save_verbose) gprintf("%1m deleted defense point, didn't work\n", pos); defense_moves[pos] = 0; } popgo(); } } return aa_val; } /* Wrapper around atari_atari_blunder_size. Check whether a * combination attack of size at least minsize appears after move * at (move) has been made. * The arrays saved_dragons[] and saved_worms[] should be one for * stones belonging to dragons or worms respectively, which are * supposedly saved by (move). * * FIXME: We probably want to change the calling convention of this * function to return all defense moves. */ int atari_atari_confirm_safety(int color, int move, int *defense, int minsize, const signed char saved_dragons[BOARDMAX], const signed char saved_worms[BOARDMAX]) { signed char safe_stones[BOARDMAX]; signed char defense_moves[BOARDMAX]; int pos; int blunder_size; mark_safe_stones(color, move, saved_dragons, saved_worms, safe_stones); blunder_size = atari_atari_blunder_size(color, move, defense_moves, safe_stones); if (defense) { /* Return one arbitrary defense move. */ *defense = NO_MOVE; for (pos = BOARDMIN; pos < BOARDMAX; pos++) if (ON_BOARD(pos) && defense_moves[pos]) { *defense = pos; break; } } return blunder_size >= minsize; } /* This function checks whether any new combination attack appears after * move at (move) has been made, and returns its size (in points). * safe_stones marks which of our stones are supposedly safe after this move. */ int atari_atari_blunder_size(int color, int move, signed char defense_moves[BOARDMAX], const signed char safe_stones[BOARDMAX]) { int apos; int defense_point = NO_MOVE; int aa_val, after_aa_val; int other = OTHER_COLOR(color); signed char defense_points[BOARDMAX]; int last_forbidden = NO_MOVE; /* If aa_depth is too small, we can't see any combination attacks, * so in this respect the move is safe enough. */ if (aa_depth < 2) return 0; memset(forbidden, 0, sizeof(forbidden)); memset(defense_points, 0, sizeof(defense_points)); compute_aa_status(other, safe_stones); compute_aa_values(other); /* Accept illegal ko capture here. */ if (!tryko(move, color, NULL)) /* Really shouldn't happen. */ abortgo(__FILE__, __LINE__, "trymove", move); increase_depth_values(); aa_val = do_atari_atari(other, &apos, &defense_point, defense_points, NO_MOVE, 0, 0, NULL); after_aa_val = aa_val; if (aa_val == 0 || defense_point == NO_MOVE) { /* No sufficiently large combination attack, so the move is safe from * this danger. * * On rare occasions do_atari_atari might find a combination * but no defense. In this case we assume that the combination * is illusory. */ popgo(); decrease_depth_values(); return 0; } while (aa_val >= after_aa_val && defense_point != NO_MOVE) { /* Try dropping moves from the combination and see if it still * works. What we really want is to get the proper defense move * into (*defense). */ forbidden[apos] = 1; last_forbidden = apos; aa_val = do_atari_atari(other, &apos, &defense_point, NULL, NO_MOVE, 0, aa_val, NULL); } popgo(); decrease_depth_values(); /* We know that a combination exists, but we don't know if * the original move at (aa) was really relevant. So we * try omitting it and see if a combination is still found. */ compute_aa_status(other, NULL); compute_aa_values(other); forbidden[last_forbidden] = 0; aa_val = do_atari_atari(other, NULL, NULL, NULL, NO_MOVE, 0, 0, NULL); if (aa_val >= after_aa_val) return 0; /* Try the potential defense moves to see which are effective. */ if (defense_moves) { int pos; /* defense_points[] contains potential defense moves. Now we * examine which of them really work. */ /* FIXME: Maybe these should be moved after the tryko() below? */ compute_aa_status(other, safe_stones); compute_aa_values(other); memcpy(defense_moves, defense_points, sizeof(defense_points)); for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (!ON_BOARD(pos) || !defense_moves[pos] || pos == move) continue; if (!trymove(pos, color, "atari_atari", NO_MOVE)) { defense_moves[pos] = 0; continue; } increase_depth_values(); if (attack(pos, NULL)) { defense_moves[pos] = 0; decrease_depth_values(); popgo(); continue; } /* Accept illegal ko capture here. */ if (!tryko(move, color, NULL)) /* Really shouldn't happen. */ abortgo(__FILE__, __LINE__, "trymove", move); increase_depth_values(); if (do_atari_atari(other, &apos, &defense_point, NULL, NO_MOVE, 0, after_aa_val, NULL) >= after_aa_val) defense_moves[pos] = 0; decrease_depth_values(); popgo(); decrease_depth_values(); popgo(); } } return after_aa_val - aa_val; } /* ---------------------------------------------------------------- */ /* Helper functions for atari_atari. */ /* ---------------------------------------------------------------- */ /* Helper function for computing the aa_status for all opponent's strings. * If safe_stones is given, we just copy the information from there. * If called at stackp > 0, safe_stones must be provided since the * dragon_data is not valid then. */ static void compute_aa_status(int color, const signed char safe_stones[BOARDMAX]) { int other = OTHER_COLOR(color); int pos; SGFTree *save_sgf_dumptree = sgf_dumptree; int save_count_variations = count_variations; int save_verbose = verbose; gg_assert(safe_stones || stackp == 0); sgf_dumptree = NULL; count_variations = 0; if (verbose) verbose--; /* Collect worm statuses of opponent's worms. We need to * know this because we only want to report unexpected * results. For example, we do not want to report success * if we find we can kill a worm which is already dead. * The worm status of empty points is set to UNKNOWN to signal * that stones added along the way need special attention. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] == other) { if (safe_stones) { if (safe_stones[pos]) aa_status[pos] = ALIVE; else aa_status[pos] = DEAD; } else { if (dragon[pos].status == DEAD) aa_status[pos] = DEAD; else if (dragon[pos].status == CRITICAL) aa_status[pos] = CRITICAL; else if (worm[pos].attack_codes[0] != 0) { if (worm[pos].defense_codes[0] != 0) aa_status[pos] = CRITICAL; else aa_status[pos] = DEAD; } else aa_status[pos] = ALIVE; } } else if (ON_BOARD(pos)) aa_status[pos] = UNKNOWN; } /* reclassify a worm with 2 liberties as INSUBSTANTIAL if capturing * it does not result in a live group. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] == other && find_origin(pos) == pos && countlib(pos) == 2 && aa_status[pos] == ALIVE) { int libs[2]; findlib(pos, 2, libs); /* Don't waste time running owl_substantial() if we can't safely * atari anyway. */ if (is_self_atari(libs[0], color) && is_self_atari(libs[1], color)) continue; if (!owl_substantial(pos)) { int pos2; for (pos2 = BOARDMIN; pos2 < BOARDMAX; pos2++) if (board[pos2] == other && find_origin(pos2) == pos) aa_status[pos2] = INSUBSTANTIAL; } } } if (debug & DEBUG_ATARI_ATARI) { gprintf("compute_aa_status() for %C\n", color); gprintf("aa_status: (ALIVE worms not listed)\n"); for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] == other && is_worm_origin(pos, pos)) { const char *status = "UNKNOWN (shouldn't happen)"; if (aa_status[pos] == DEAD) status = "DEAD"; else if (aa_status[pos] == CRITICAL) status = "CRITICAL"; else if (aa_status[pos] == INSUBSTANTIAL) status = "INSUBSTANTIAL"; if (aa_status[pos] != ALIVE) gprintf("%1M: %s\n", pos, status); } } } sgf_dumptree = save_sgf_dumptree; count_variations = save_count_variations; verbose = save_verbose; } /* Helper function for retrieving the aa_status for a string. We can't * reliably do this simply by looking up aa_status[pos] since this is * only valid at vertices which were non-empty at the start of the * reading. For later added stones, we need to find their aa_status by * locating a part of the string which was a worm at the beginning of * the reading. */ static int get_aa_status(int pos) { int stones[MAX_BOARD * MAX_BOARD]; int num_stones; int k; if (aa_status[pos] != UNKNOWN) return aa_status[pos]; num_stones = findstones(pos, MAX_BOARD * MAX_BOARD, stones); for (k = 0; k < num_stones; k++) if (aa_status[stones[k]] != UNKNOWN) return aa_status[stones[k]]; return UNKNOWN; } /* Helper function for atari_atari. Here worms is the number of * opponent worms involved in the combination, and (last_friendly) is * the location of the last friendly move played. Moves marked * with the forbidden array are not tried. If no move is found, * the values of *attack_point and *defense_point are not changed. * * If not NULL, *attack_point is left pointing to the location of the * attacking move, and *defense_point points to a move defending the * combination. In rare cases a defensive move might not be found. If * a non-static function calling do_atari_atari gets a return value of * 1 but NO_MOVE as the defense point, this should be treated as * equivalent to a return value of 0. * * The goal array limits where we are allowed to consider threats. * Only strings for which goal is set to 1 may be threatened. If goal * is NULL, anything may be attacked. Thus goal is typically NULL when * do_atari_atari() is called from an external function. After the * first threat has been made, the goal array is set to one in a * neighborhood of the move and after subsequent threats it is * expanded with neighborhoods of those moves. The details of this can * be found in the function update_aa_goal(). */ static int do_atari_atari(int color, int *attack_point, int *defense_point, signed char all_potential_defenses[BOARDMAX], int last_friendly, int save_verbose, int minsize, signed char goal[BOARDMAX]) { int other = OTHER_COLOR(color); int k; struct aa_move attacks[AA_MAX_MOVES]; int num_defense_moves; int defense_moves[AA_MAX_MOVES]; int pos; SGFTree *save_sgf_dumptree; int save_count_variations; if (debug & DEBUG_ATARI_ATARI) { gprintf("%odo_atari_atari: "); dump_stack(); gprintf("%oforbidden moves: "); for (pos = BOARDMIN; pos < BOARDMAX; pos++) if (ON_BOARD(pos) && forbidden[pos]) gprintf("%o%1m ", pos); gprintf("\n"); gprintf("%ogoal: "); if (!goal) gprintf("none"); else { for (pos = BOARDMIN; pos < BOARDMAX; pos++) if (ON_BOARD(pos) && goal[pos]) gprintf("%o%1m ", pos); } gprintf("\n"); } /* First look for strings adjacent to the last friendly move played * (or to another stone in the same string) which can be * unexpectedly attacked. If so, the combination attack * has succeeded. */ if (last_friendly != NO_MOVE) { int retval; save_sgf_dumptree = sgf_dumptree; save_count_variations = count_variations; sgf_dumptree = NULL; count_variations = 0; retval = atari_atari_succeeded(color, attack_point, defense_point, last_friendly, save_verbose, minsize); sgf_dumptree = save_sgf_dumptree; count_variations = save_count_variations; if (retval != 0) { if (sgf_dumptree) /* FIXME: Better message. */ sgftreeAddComment(sgf_dumptree, "attack found"); return retval; } } if (stackp > aa_depth) return 0; /* Find attack moves. These are typically ataris but may also be * more general. */ save_sgf_dumptree = sgf_dumptree; save_count_variations = count_variations; sgf_dumptree = NULL; count_variations = 0; atari_atari_find_attack_moves(color, minsize, attacks, goal); sgf_dumptree = save_sgf_dumptree; count_variations = save_count_variations; /* Try the attacking moves and let the opponent defend. Then call * ourselves recursively. */ for (k = 0; attacks[k].move != NO_MOVE; k++) { int aa_val; int str = attacks[k].target[0]; int apos = attacks[k].move; int bpos; int r; if (!trymove(apos, color, "do_atari_atari-A", str)) continue; if (all_potential_defenses) { all_potential_defenses[apos] = 1; if (countlib(apos) <= 2) { int libs[2]; int num_libs = findlib(apos, 2, libs); all_potential_defenses[libs[0]] = 1; if (num_libs == 2) all_potential_defenses[libs[1]] = 1; } } if (!IS_STONE(board[str])) { /* Error situation. This could be caused by a wrong matcher status. */ if (save_verbose || (debug & DEBUG_ATARI_ATARI)) gprintf("%oError condition found by atari_atari\n"); popgo(); return 0; } /* Try to defend the stone (str) which is threatened. */ aa_val = get_aa_value(str); /* Pick up defense moves. */ save_sgf_dumptree = sgf_dumptree; save_count_variations = count_variations; sgf_dumptree = NULL; count_variations = 0; num_defense_moves = atari_atari_find_defense_moves(attacks[k].target, defense_moves); sgf_dumptree = save_sgf_dumptree; count_variations = save_count_variations; for (r = 0; r < num_defense_moves; r++) { bpos = defense_moves[r]; if (all_potential_defenses) all_potential_defenses[bpos] = 1; if (trymove(bpos, other, "do_atari_atari-B", str)) { int new_aa_val; signed char new_goal[BOARDMAX]; /* These moves may have been irrelevant for later * reading, so in order to avoid horizon problems, we * need to temporarily increase the depth values. */ modify_depth_values(2); update_aa_goal(goal, new_goal, apos, color); new_aa_val = do_atari_atari(color, NULL, defense_point, all_potential_defenses, apos, save_verbose, minsize, new_goal); modify_depth_values(-2); if (new_aa_val < aa_val) aa_val = new_aa_val; popgo(); } /* Defense successful, no need to try any further. */ if (aa_val == 0) break; } /* Undo the attacking move. */ popgo(); if (aa_val == 0) continue; /* atari_atari successful */ if (num_defense_moves == 0) { if (save_verbose || (debug & DEBUG_ATARI_ATARI)) { gprintf("%oThe worm %1m can be attacked at %1m after ", str, apos); dump_stack(); } if (sgf_dumptree) /* FIXME: Better message. */ sgftreeAddComment(sgf_dumptree, "attack found"); } if (attack_point) *attack_point = apos; if (defense_point) { save_sgf_dumptree = sgf_dumptree; save_count_variations = count_variations; sgf_dumptree = NULL; count_variations = 0; if (!find_defense(str, defense_point)) *defense_point = NO_MOVE; /* If no defense point is known and (apos) is a safe * move for other, it probably defends the combination. */ if ((*defense_point == NO_MOVE || !safe_move(*defense_point, other)) && safe_move(apos, other)) *defense_point = apos; sgf_dumptree = save_sgf_dumptree; count_variations = save_count_variations; } DEBUG(DEBUG_ATARI_ATARI, "%oreturn value:%d (%1m)\n", aa_val, str); return aa_val; } /* No atari_atari attack. */ return 0; } static int atari_atari_succeeded(int color, int *attack_point, int *defense_point, int last_friendly, int save_verbose, int minsize) { int pos; int apos; int other = OTHER_COLOR(color); for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] != other) continue; if (pos != find_origin(pos)) continue; if (minsize > 0 && get_aa_value(pos) < minsize) continue; if (get_aa_status(pos) != ALIVE) continue; if (board[last_friendly] != EMPTY && !adjacent_strings(last_friendly, pos)) continue; if (board[last_friendly] == EMPTY && !liberty_of_string(last_friendly, pos)) continue; if (debug & DEBUG_ATARI_ATARI) gprintf("Considering attack of %1m. depth = %d.\n", pos, depth); if (attack(pos, &apos) && !forbidden[apos]) { if (save_verbose || (debug & DEBUG_ATARI_ATARI)) { gprintf("%oThe worm %1m can be attacked at %1m after ", pos, apos); dump_stack(); } if (attack_point) *attack_point = apos; /* We look for a move defending the combination. * Normally this is found by find_defense but failing * that, if the attacking move is a safe move for color, * it probably defends. */ if (defense_point) { if (!find_defense(pos, defense_point)) { if (safe_move(apos, other)) *defense_point = apos; else *defense_point = NO_MOVE; } } DEBUG(DEBUG_ATARI_ATARI, "%oreturn value:%d (%1m)\n", get_aa_value(pos), pos); return get_aa_value(pos); } } return 0; } #define MAX_THREAT_MOVES MAX_TACTICAL_POINTS static void atari_atari_find_attack_moves(int color, int minsize, struct aa_move attacks[AA_MAX_MOVES], signed char goal[BOARDMAX]) { int k; int r; aa_init_moves(attacks); atari_atari_attack_patterns(color, minsize, attacks, goal); /* Sort the attack moves. */ aa_sort_moves(attacks); if (debug & DEBUG_ATARI_ATARI) { gprintf("Attack moves:"); for (k = 0; k < AA_MAX_MOVES && attacks[k].move != NO_MOVE; k++) { gprintf("%o %1m(", attacks[k].move); for (r = 0; r < AA_MAX_TARGETS_PER_MOVE; r++) { if (attacks[k].target[r] == NO_MOVE) break; gprintf("%o%s%1m", r == 0 ? "" : ",", attacks[k].target[r]); } gprintf("%o)"); } gprintf("%o\n"); } } /* FIXME: Move these to a struct and pass to callback through the * *data parameter. */ static int current_minsize; static struct aa_move *current_attacks; static int conditional_attack_point[BOARDMAX]; static void atari_atari_attack_patterns(int color, int minsize, struct aa_move attacks[AA_MAX_MOVES], signed char goal[BOARDMAX]) { signed char revised_goal[BOARDMAX]; current_minsize = minsize; current_attacks = attacks; memset(conditional_attack_point, 0, sizeof(conditional_attack_point)); /* If goal is NULL and there are forbidden moves we need to compute * a new goal around the forbidden moves. */ if (goal == NULL && update_aa_goal(goal, revised_goal, NO_MOVE, color)) goal = revised_goal; #if 0 if (goal != NULL) { int pos; gprintf("goal:"); for (pos = BOARDMIN; pos < BOARDMAX; pos++) if (ON_BOARD(pos) && goal[pos]) gprintf("%o %1m", pos); gprintf("%o\n"); } #endif matchpat(atari_atari_attack_callback, color, &aa_attackpat_db, NULL, goal); } /* Try to attack every X string in the pattern, whether there is an attack * before or not. Only exclude already known attacking moves. */ static void atari_atari_attack_callback(int anchor, int color, struct pattern *pattern, int ll, void *data) { int move; int k; UNUSED(data); move = AFFINE_TRANSFORM(pattern->move_offset, ll, anchor); if (forbidden[move]) return; /* If the pattern has a constraint, call the autohelper to see * if the pattern must be rejected. */ if (pattern->autohelper_flag & HAVE_CONSTRAINT) if (!pattern->autohelper(ll, move, color, 0)) return; /* If the pattern has a helper, call it to see if the pattern must * be rejected. */ if (pattern->helper) if (!pattern->helper(pattern, ll, move, color)) return; /* Loop through pattern elements in search of X strings to * threaten to attack. */ for (k = 0; k < pattern->patlen; ++k) { /* match each point */ if (pattern->patn[k].att == ATT_X) { /* transform pattern real coordinate */ int str = find_origin(AFFINE_TRANSFORM(pattern->patn[k].offset, ll, anchor)); if (current_minsize > 0 && get_aa_value(str) < current_minsize) continue; if (aa_move_known(current_attacks, move, str)) continue; if (get_aa_status(str) != ALIVE) continue; /* Usually we don't want to play self atari. However, if we * capture in snapback it's okay. For s class patterns we don't * have this requirement. */ if (!(pattern->class & CLASS_s) && is_self_atari(move, color)) { if (countlib(str) > 2) continue; if (!safe_move(move, color)) continue; } /* * Play (move) and see if there is an attack. */ if (trymove(move, color, "attack_callback", str)) { int acode; int attack_point = NO_MOVE; if (!board[str]) acode = WIN; else acode = attack(str, &attack_point); popgo(); if (acode != 0) { if ((pattern->class & CLASS_c) && !aa_move_known(current_attacks, move, NO_MOVE)) { /* Conditional pattern. */ DEBUG(DEBUG_ATARI_ATARI, "aa_attack pattern %s+%d (conditional) found threat on %1m at %1m with code %d\n", pattern->name, ll, str, move, acode); if (conditional_attack_point[move] == NO_MOVE) conditional_attack_point[move] = str; else if (conditional_attack_point[move] != str) { aa_add_move(current_attacks, move, conditional_attack_point[move]); aa_add_move(current_attacks, move, str); } } else { aa_add_move(current_attacks, move, str); DEBUG(DEBUG_ATARI_ATARI, "aa_attack pattern %s+%d found threat on %1m at %1m with code %d\n", pattern->name, ll, str, move, acode); } } } } } } static int atari_atari_find_defense_moves(int targets[AA_MAX_TARGETS_PER_MOVE], int moves[AA_MAX_MOVES]) { int num_moves = 0; int move; int k; int liberties; int libs[4]; int neighbors; int adjs[MAXCHAIN]; int mx[BOARDMAX]; int r, s; memset(mx, 0, sizeof(mx)); for (r = 0; r < AA_MAX_TARGETS_PER_MOVE && targets[r] != NO_MOVE; r++) { int str = targets[r]; /* If the attack move happened to remove (str), there's no defense. */ if (board[str] == EMPTY) continue; /* Because we know (str) is threatened there is an * attack and we can be sure find_defense() will give a * useful defense point if it returns non-zero. Usually we * would need to call attack_and_defend() to be certain of * this. */ if (!find_defense(str, &move)) continue; moves[num_moves++] = move; if (num_moves == AA_MAX_MOVES) return num_moves; mx[move] = 1; /* Consider all moves to attack a neighbor or to play on a liberty. */ liberties = findlib(str, 4, libs); for (k = 0; k < liberties; k++) { if (!mx[libs[k]] && trymove(libs[k], board[str], "aa_defend-A", str)) { if (attack(str, NULL) == 0) { moves[num_moves++] = libs[k]; mx[libs[k]] = 1; } popgo(); if (num_moves == AA_MAX_MOVES) return num_moves; } } neighbors = chainlinks(str, adjs); for (k = 0; k < neighbors; k++) { int attack_point; if (attack(adjs[k], &attack_point) == WIN && !mx[attack_point]) { moves[num_moves++] = attack_point; if (num_moves == AA_MAX_MOVES) return num_moves; mx[attack_point] = 1; } /* If the neighbor has at most three liberties, try all of them * for defense, except self-ataris. */ liberties = findlib(adjs[k], 3, libs); if (liberties <= 3) { for (s = 0; s < liberties; s++) { if (!mx[libs[s]] && !is_self_atari(libs[s], board[str]) && trymove(libs[s], board[str], "aa_defend-B", str)) { if (attack(str, NULL) == 0) { moves[num_moves++] = libs[s]; mx[libs[s]] = 1; } popgo(); if (num_moves == AA_MAX_MOVES) return num_moves; } } } } if (debug & DEBUG_ATARI_ATARI) { gprintf("Defense moves for %1m:", str); for (k = 0; k < num_moves; k++) gprintf("%o %1m", moves[k]); gprintf("%o\n"); } } return num_moves; } /* Try to guess the value of the strings. We do this by adding twice * the number of stones to the number of liberties and second order * liberties within the moyo around the string. This is of course * quite crude since it doesn't take into account any strategic * effects, e.g. a string being cutting stones. */ static void compute_aa_values(int color) { int other = OTHER_COLOR(color); int pos; int value; int liberties; int libs[MAXLIBS]; int mx[BOARDMAX]; int r, k; for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] != other || pos != find_origin(pos) || aa_status[pos] != ALIVE) { aa_values[pos] = 0; continue; } memset(mx, 0, sizeof(mx)); liberties = findlib(pos, MAXLIBS, libs); value = 2 * countstones(pos); for (r = 0; r < liberties; r++) { if (!mx[libs[r]] && (whose_moyo(&initial_black_influence, libs[r]) == other || whose_moyo(&initial_white_influence, libs[r]) == other)) { mx[libs[r]] = 1; value++; } for (k = 0; k < 4; k++) { int librd = libs[r] + delta[k]; if (!ON_BOARD1(librd) || mx[librd]) continue; mx[librd] = 1; if (board[librd] == EMPTY && (whose_moyo(&initial_black_influence, librd) == other || (whose_moyo(&initial_white_influence, librd) == other))) value++; } } aa_values[pos] = value; if (1) DEBUG(DEBUG_ATARI_ATARI, "aa_value for %1m = %d\n", pos, value); } } /* The aa_value for a string is the sum of the aa_values for all * included strings in the original position. This will systematically * overvalue strings which consist of multiple original strings, but * this is okay since the defender very rarely should defend a string * first and then sacrifice it later. */ static int get_aa_value(int str) { int stones[MAX_BOARD * MAX_BOARD]; int k; int num_stones = findstones(str, MAX_BOARD * MAX_BOARD, stones); int value = 0; for (k = 0; k < num_stones; k++) value += aa_values[stones[k]]; return value; } /* update_aa_goal(goal, new_goal, apos, color) extends the goal array * with vertices in a neighborhood of apos. The algorithm is that * starting at apos, a distance measure is computed to nearby * vertices. The distance increases with one for each step through * empty vertices and by a liberty depending number when passing * through strings of the attacked color. Strings with 3 or fewer * liberties are free to pass through while strings with more * liberties cost (libs - 3) to pass through. Stones with a distance * of 5 or less are included in the goal. * * Additionally neighborhoods of the moves in the forbidden array are * included in the goal, to make it possible to limit the goal to a * specific area from the beginning. This is needed when trying to * decide which moves are relevant to the combination. */ #define ENQUEUE(pos, dist) \ do { \ if ((dist) <= MAX_AA_DIST) { \ if (dists[pos] == 0) { \ queue[queue_end++] = (pos); \ dists[pos] = (dist); \ } \ else if (dists[pos] < (dist)) \ dists[pos] = (dist); \ } \ } while (0); static int update_aa_goal(signed char goal[BOARDMAX], signed char new_goal[BOARDMAX], int apos, int color) { int other = OTHER_COLOR(color); int dists[BOARDMAX]; int queue[MAX_BOARD * MAX_BOARD]; int queue_end = 0; int k, r, s; int pos; if (goal == NULL) memset(new_goal, 0, BOARDMAX); else memcpy(new_goal, goal, BOARDMAX); memset(dists, 0, sizeof(dists)); if (apos != NO_MOVE) { dists[apos] = 1; queue[queue_end++] = apos; } #if 0 /* Disabled for now, since it does nothing but break atari_atari:16 * and trevorc:1540. It could be reactivated when the rest of the * function would be modified in order to garanty that a forbidden * move is strictly equivalent to a played move in terms of goal * mapping. I doubt it would be anything worth though... */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (ON_BOARD(pos) && forbidden[pos]) { dists[pos] = 1; queue[queue_end++] = pos; } } #endif if (queue_end == 0) return 0; for (r = 0; r < queue_end; r++) { int smallest_dist = MAX_BOARD * MAX_BOARD; int best_index = -1; gg_assert(queue_end < MAX_BOARD * MAX_BOARD); for (k = r; k < queue_end; k++) { if (dists[queue[k]] < smallest_dist) { smallest_dist = dists[queue[k]]; best_index = k; } } if (best_index != r) { int tmp = queue[r]; queue[r] = queue[best_index]; queue[best_index] = tmp; } pos = queue[r]; if (board[pos] == other) new_goal[pos] = 1; /* FIXME: We shouldn't let dead opponent stones stop the * propagation of distance. * * As a partial fix we include pos == apos in a test below. */ for (k = 0; k < 4; k++) { int pos2 = pos + delta[k]; if (!ON_BOARD(pos2)) continue; if ((board[pos] != color || pos == apos) && board[pos2] == EMPTY) { ENQUEUE(pos2, dists[pos] + 1); } else if (board[pos] != other && board[pos2] == other) { int stones[MAX_BOARD * MAX_BOARD]; int size = findstones(pos2, MAX_BOARD * MAX_BOARD, stones); int libs = countlib(pos2); int deltadist = libs - 3; if (deltadist < 0) deltadist = 0; for (s = 0; s < size; s++) ENQUEUE(stones[s], dists[pos] + deltadist); } } } return 1; } /* Initialize an array with atari_atari attacks. The convention is that * the array ends when a NO_MOVE is encountered in the move field. */ static void aa_init_moves(struct aa_move attacks[AA_MAX_MOVES]) { attacks[0].move = NO_MOVE; } /* Add an atari_atari attack move to a struct aa_move array. If the * move already is included in the array, we check whether the target * also is known for that move and add it if not. */ static void aa_add_move(struct aa_move attacks[AA_MAX_MOVES], int move, int target) { int k; int r; for (k = 0; k < AA_MAX_MOVES; k++) if (attacks[k].move == move || attacks[k].move == NO_MOVE) break; /* If the array is full, give up. */ if (k == AA_MAX_MOVES) return; target = find_origin(target); /* New move. */ if (attacks[k].move == NO_MOVE) { attacks[k].move = move; attacks[k].target[0] = target; if (AA_MAX_TARGETS_PER_MOVE > 0) attacks[k].target[1] = NO_MOVE; if (k < AA_MAX_MOVES - 1) attacks[k+1].move = NO_MOVE; return; } /* Known move, maybe new target. */ for (r = 0; r < AA_MAX_TARGETS_PER_MOVE; r++) if (attacks[k].target[r] == target || attacks[k].target[r] == NO_MOVE) break; /* No place for more targets. */ if (r == AA_MAX_TARGETS_PER_MOVE) return; /* Target known. */ if (attacks[k].target[r] == target) return; /* Add target. */ attacks[k].target[r] = target; if (r < AA_MAX_TARGETS_PER_MOVE - 1) attacks[k].target[r + 1] = NO_MOVE; } /* Check whether an atari_atari attack move is included in an struct * aa_move array. If target is not NO_MOVE, we also require that the * target is known for the move. */ static int aa_move_known(struct aa_move attacks[AA_MAX_MOVES], int move, int target) { int k; int r; for (k = 0; k < AA_MAX_MOVES; k++) if (attacks[k].move == move || attacks[k].move == NO_MOVE) break; /* If the array is full, give up and claim the move to be known. */ if (k == AA_MAX_MOVES) return 1; /* Unknown move. */ if (attacks[k].move == NO_MOVE) return 0; /* Move known, but how about the target? * If no target specified, just return 1. */ if (target == NO_MOVE) return 1; target = find_origin(target); for (r = 0; r < AA_MAX_TARGETS_PER_MOVE; r++) if (attacks[k].target[r] == target || attacks[k].target[r] == NO_MOVE) break; /* No place for more targets. Give up and claim the target to be known. */ if (r == AA_MAX_TARGETS_PER_MOVE) return 1; /* Target known. */ if (attacks[k].target[r] == target) return 1; /* Unknown target. */ return 0; } /* Auxiliary function for aa_sort_moves(). */ static int target_comp_func(const void *a, const void *b) { int asize = get_aa_value(*((const int *) a)); int bsize = get_aa_value(*((const int *) b)); return asize - bsize; } /* Auxiliary function for aa_sort_moves(). */ static int move_comp_func(const void *a, const void *b) { const struct aa_move *aa = a; const struct aa_move *bb = b; int asize = get_aa_value(aa->target[0]); int bsize = get_aa_value(bb->target[0]); return asize - bsize; } /* Sort the attack moves. For each move the targets are sorted in * decreasing size. Then the moves are sorted with increasing size * of their first target. */ static void aa_sort_moves(struct aa_move attacks[AA_MAX_MOVES]) { int k; int r; int number_of_attacks; int number_of_targets; for (k = 0; k < AA_MAX_MOVES && attacks[k].move != NO_MOVE; k++) { for (r = 0; r < AA_MAX_TARGETS_PER_MOVE; r++) if (attacks[k].target[r] == NO_MOVE) break; number_of_targets = r; gg_sort(attacks[k].target, number_of_targets, sizeof(attacks[k].target[0]), target_comp_func); } number_of_attacks = k; gg_sort(attacks, number_of_attacks, sizeof(attacks[0]), move_comp_func); } #if 0 /* Returns true if a move by (color) at (pos) is atari on something. * Currently unused. */ static int is_atari(int pos, int color) { int other = OTHER_COLOR(color); if (!is_legal(pos, color)) return 0; if (board[SOUTH(pos)] == other && countlib(SOUTH(pos)) == 2) return 1; if (board[WEST(pos)] == other && countlib(WEST(pos)) == 2) return 1; if (board[NORTH(pos)] == other && countlib(NORTH(pos)) == 2) return 1; if (board[EAST(pos)] == other && countlib(EAST(pos)) == 2) return 1; return 0; } #endif /* * Local Variables: * tab-width: 8 * c-basic-offset: 2 * End: */