/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ * This is GNU Go, a Go program. Contact gnugo@gnu.org, or see * * http://www.gnu.org/software/gnugo/ for more information. * * * * Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, * * 2008 and 2009 by the Free Software Foundation. * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation - version 3 or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License in file COPYING for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02111, USA. * \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include #include "liberty.h" #include "patterns.h" #define TRYMOVE(pos, color) trymove(pos, color, "helper", NO_MOVE) #define OFFSET_BY(x, y) AFFINE_TRANSFORM(OFFSET(x, y), trans, move) #define ARGS struct pattern *pattern, int trans, int move, int color /* This file contains helper functions which assist the pattern matcher. * They are invoked with (move) = the position on the board marked with '*'. * They are invoked with color = WHITE or BLACK: any pieces on the * board marked with 'O' in the pattern will always contain color, * and 'X's contain OTHER_COLOR(color) * * The helper must return 0 if the pattern is rejected and 1 otherwise. */ /* Jump out into nothingness. To avoid jumping into our own territory, * we use the "area" measure. Also we never ever jump into our own * established eyespace. */ int jump_out_helper(ARGS) { int own_eyespace; UNUSED(trans); UNUSED(pattern); own_eyespace = (white_eye[move].color == color); if (whose_area(OPPOSITE_INFLUENCE(color), move) != color && !own_eyespace) return 1; else return 0; } /* Make a long jump into nothingness. Since these jumps are not * securely connected we don't use them to jump into the opponent's * zone of control. */ int jump_out_far_helper(ARGS) { if (whose_area(OPPOSITE_INFLUENCE(color), move) != OTHER_COLOR(color)) return jump_out_helper(pattern, trans, move, color); else return 0; } /* Active until the opponent has played his first stone. */ int high_handicap_helper(ARGS) { UNUSED(trans); UNUSED(pattern); UNUSED(move); return !doing_scoring && stones_on_board(OTHER_COLOR(color)) == 0; } /* Active when the opponent is thought to be everywhere dead. This * typically happens early in high handicap games on small boards. * This helper is used by patterns intended to reinforce possible * weaknesses in the position. */ int reinforce_helper(ARGS) { UNUSED(trans); UNUSED(pattern); return (!doing_scoring && !lively_dragon_exists(OTHER_COLOR(color)) && safe_move(move, color)); } /* * * XXO XXc decrease eye space in sente (unless it kills) * .*X e*a * --- --- * * or * * XXO XXc decrease eye space in sente (unless it kills) * .*X e*a * XXO XXd * * or * * |XXO |XXc decrease eye space in sente (unless it kills) * |.*X |e*a * |XXO |XXd * * or * * |XXO |XXc decrease eye space in sente (unless it kills) * |.*X |e*a * +--- +--- * */ int throw_in_atari_helper(ARGS) { int apos, bpos, cpos, dpos; int success = 0; int other = OTHER_COLOR(color); int libs[2]; UNUSED(pattern); apos = OFFSET_BY(0, 1); cpos = OFFSET_BY(-1, 1); dpos = OFFSET_BY(1, 1); /* Find second liberty of the stone a. */ findlib(apos, 2, libs); if (libs[0] != move) bpos = libs[0]; else bpos = libs[1]; if (TRYMOVE(move, color)) { if (!attack(cpos, NULL) && !(ON_BOARD(dpos) && attack(dpos, NULL))) { if (TRYMOVE(bpos, other)) { if (attack(apos, NULL)) success = 1; popgo(); } else { success = 1; /* X move at (bpos) would have been suicide */ } } popgo(); } /* The followup is to capture the "a" string. Estimate the value to * twice the size. */ add_followup_value(move, 2 * worm[apos].effective_size); TRACE("...followup value %f\n", 2 * worm[apos].effective_size); return success; } /* This is intended for use in autohelpers. */ /* Check whether the string at (str) can attack any surrounding * string. If so, return false as the move to create a seki (probably) * wouldn't work. */ int seki_helper(int str) { int r; int adj; int adjs[MAXCHAIN]; adj = chainlinks(str, adjs); for (r = 0; r < adj; r++) if (worm[adjs[r]].attack_codes[0] != 0) return 0; return 1; } /* * XO aO * O* O* * * Increase the cutstone2 field if * is a potential cutting point, * i.e. if it does work as a cutting point once 'a' has been * defended. This helper is expected to always return 0. */ int cutstone2_helper(ARGS) { int apos; int bpos; int cpos; int dpos; UNUSED(pattern); UNUSED(color); if (stackp > 0) return 0; apos = OFFSET_BY(-1, -1); bpos = OFFSET_BY(-1, 0); cpos = OFFSET_BY( 0, -1); if (worm[apos].defense_codes[0] == 0) return 0; dpos = worm[apos].defense_points[0]; if (TRYMOVE(dpos, board[apos])) { if (!board[bpos] || attack(bpos, NULL) || !board[cpos] || attack(cpos, NULL) || safe_move(move, board[apos]) != 0) { popgo(); worm[worm[apos].origin].cutstone2++; propagate_worm(worm[apos].origin); return 0; } popgo(); } return 0; } /* * ?x?? ?x?? * ?X.. ?Xb. * O*.. c*a. * * Is the push at * sente? c must have exactly two liberties. This is * called edge_double_sente_helper because it mainly called for edge * hanes, but it can be used anywhere on the board. */ int edge_double_sente_helper(int move, int apos, int bpos, int cpos) { int color = board[cpos]; int success = 0; ASSERT1((color == BLACK || color == WHITE), move); if (TRYMOVE(move, color)) { ASSERT1(countlib(move) == 2, move); success = connect_and_cut_helper(move, apos, bpos); popgo(); } return success; } /* * This is intended for use in autohelpers. * * Give a conservative estimate of the value of saving the string (str) * by capturing one opponent stone. */ void threaten_to_save_helper(int move, int str) { add_followup_value(move, 2.0 + 2.0 * worm[str].effective_size); TRACE("...followup value %f\n", 2.0 + 2.0 * worm[str].effective_size); } /* For use in autohelpers. * * Adds a reverse followup value if the opponent's move here would threaten * to capture (str). */ void prevent_attack_threat_helper(int move, int str) { add_reverse_followup_value(move, 2.0 * worm[str].effective_size); TRACE("...reverse followup value %f\n", 2.0 * worm[str].effective_size); } /* This function is obsolete. Code in `value_moves.c' is more general. */ #if 0 /* * This is intended for use in autohelpers. * * Estimate the value of capturing the string (str) and add this as * a followup value. We don't do this for too stupid looking threats, * however, e.g. in a position like * * OOOO.. * XXX.*. * XOOOX. * XXXXO. * * where X can get out of atari with profit by capturing three O stones. * * Another case where we don't award the followup value is when the * opponent can defend with a threat against our move, e.g. in this * position: * * .OOOXX. * .OXXO.X * ..*.X.. * ..XX... * */ void threaten_to_capture_helper(int move, int str) { int adj, adjs[MAXCHAIN]; int defense_move; int k; adj = chainlinks2(str, adjs, 1); for (k = 0; k < adj; k++) if (worm[adjs[k]].defense_codes[0] != 0 && !does_defend(move, adjs[k])) return; if (!TRYMOVE(move, OTHER_COLOR(board[str]))) return; if (find_defense(str, &defense_move) != 0 && defense_move != NO_MOVE && TRYMOVE(defense_move, board[str])) { if (board[move] == EMPTY || attack(move, NULL) != 0) { popgo(); popgo(); return; } popgo(); } /* In addition to the move found by find_defense(), also try all * chain breaking moves in the same way. */ adj = chainlinks2(str, adjs, 1); for (k = 0; k < adj; k++) { int lib; findlib(adjs[k], 1, &lib); if (TRYMOVE(lib, board[str])) { if (!attack(str, NULL) && (board[move] == EMPTY || attack(move, NULL) != 0)) { popgo(); popgo(); return; } popgo(); } } popgo(); add_followup_value(move, 2.0 * worm[str].effective_size); TRACE("...followup value %f\n", 2.0 * worm[str].effective_size); } #endif /* * This is intended for use in autohelpers. * * Estimate the value of defending a string which can be put into * atari and add this as a reverse followup value. */ void defend_against_atari_helper(int move, int str) { int adj, adjs[MAXCHAIN]; int libs[2]; int k; ASSERT1(countlib(str) == 2, str); /* No value if the string can capture out of atari. */ adj = chainlinks2(str, adjs, 1); for (k = 0; k < adj; k++) if (worm[adjs[k]].defense_codes[0] != 0 && !does_defend(move, adjs[k])) return; /* No value if opponent has no safe atari. */ findlib(str, 2, libs); if (!safe_move(libs[0], OTHER_COLOR(board[str])) && !safe_move(libs[1], OTHER_COLOR(board[str]))) return; TRACE("...reverse followup value %f\n", 2.0 * worm[str].effective_size); add_reverse_followup_value(move, 2.0 * worm[str].effective_size); } /* * This is intended for use in conn.db autohelpers. * * Amalgamate either a with b or c with b, depending on which of the * two dragons a and c is largest. * * If either of these pairs already have been amalgamated somehow, * do nothing. */ void amalgamate_most_valuable_helper(int apos, int bpos, int cpos) { if (!is_same_dragon(apos, bpos) && !is_same_dragon(bpos, cpos)) { if (dragon[apos].effective_size >= dragon[cpos].effective_size) join_dragons(apos, bpos); else join_dragons(bpos, cpos); } } /* * This is intended for use in autohelpers. * * Returns 1 if (pos) is adjacent to a stone which can be captured by ko. */ int finish_ko_helper(int pos) { int adj, adjs[MAXCHAIN]; int lib; int k; adj = chainlinks2(pos, adjs, 1); for (k = 0; k < adj; k++) { if (countstones(adjs[k]) == 1) { findlib(adjs[k], 1, &lib); if (is_ko(lib, board[pos], NULL)) return 1; } } return 0; } /* * This is intended for use in autohelpers. * * Returns 1 if (ai, aj) is next to a ko point. */ int squeeze_ko_helper(int pos) { int libs[2]; int liberties; int k; liberties = findlib(pos, 2, libs); ASSERT1(liberties == 2, pos); for (k = 0; k < liberties; k++) { int aa = libs[k]; if (is_ko(aa, OTHER_COLOR(board[pos]), NULL)) return 1; } return 0; } /* * This is intended for use in autohelpers. * * If after playing a and b, the string at c can be attacked, this * function adds a small fixed move value for a move which defends * c. */ int backfill_helper(int apos, int bpos, int cpos) { int color = board[cpos]; int other = OTHER_COLOR(color); int dpos = NO_MOVE; if (TRYMOVE(apos, color)) { if (TRYMOVE(bpos, other)) { if (attack(cpos, NULL) && find_defense(cpos, &dpos)) { set_minimum_move_value(dpos, 0.1); TRACE("%o...setting min move value of %1m to 0.1\n", dpos); } popgo(); } popgo(); } return 0; } /* Returns true if (apos) kills or threatens to kill (bpos). */ int owl_threatens_attack(int apos, int bpos) { if (DRAGON2(bpos).owl_status == CRITICAL && DRAGON2(bpos).owl_attack_point == apos) return 1; if (DRAGON2(bpos).owl_threat_status == CAN_THREATEN_ATTACK) if (DRAGON2(bpos).owl_attack_point == apos || DRAGON2(bpos).owl_second_attack_point == apos) return 1; return 0; } /* Returns true if O needs to connect at c in the position below after * O at b and X at d, because X can cut at c. In general d is the * second liberty of A, which must have exactly two liberties. * * |.X |dX * |XO |AO * |XO |Ae * |.. |bc */ int connect_and_cut_helper(int Apos, int bpos, int cpos) { int dpos; int epos = NO_MOVE; int other = board[Apos]; int color = OTHER_COLOR(other); int libs[2]; int liberties = findlib(Apos, 2, libs); int result = 0; int k; gg_assert(IS_STONE(color)); gg_assert(liberties == 2); if (libs[0] == bpos) dpos = libs[1]; else dpos = libs[0]; for (k = 0; k < 4; k++) if (board[cpos + delta[k]] == color && neighbor_of_string(cpos + delta[k], Apos)) { epos = cpos + delta[k]; break; } gg_assert(epos != NO_MOVE); if (TRYMOVE(bpos, color)) { if (TRYMOVE(dpos, other)) { if (TRYMOVE(cpos, other)) { if (board[bpos] == EMPTY || board[epos] == EMPTY || !defend_both(bpos, epos)) result = 1; popgo(); } popgo(); } popgo(); } return result; } /* * This is similar to connect_and_cut_helper(), except it starts with * a move at A and that d is found as a general defense point for A. A * is no longer restricted to two liberties. * * |.X |dX * |XO |XO * |.O |Ae * |.. |bc */ int connect_and_cut_helper2(int Apos, int bpos, int cpos, int color) { int dpos; int epos = NO_MOVE; int other = OTHER_COLOR(color); int result = 0; int k; gg_assert(IS_STONE(color)); if (TRYMOVE(Apos, color)) { for (k = 0; k < 4; k++) if (board[cpos + delta[k]] == other && neighbor_of_string(cpos + delta[k], Apos)) { epos = cpos + delta[k]; break; } gg_assert(epos != NO_MOVE); if (TRYMOVE(bpos, other)) { if (!find_defense(Apos, &dpos) || dpos == NO_MOVE) { popgo(); popgo(); return 0; } if (TRYMOVE(dpos, color)) { if (TRYMOVE(cpos, color)) { if (board[bpos] == EMPTY || board[epos] == EMPTY || !defend_both(bpos, epos)) result = 1; popgo(); } popgo(); } popgo(); } popgo(); } return result; } void test_attack_either_move(int move, int color, int worma, int wormb) { ASSERT_ON_BOARD1(move); ASSERT1(board[move] == EMPTY, move); ASSERT1(board[worma] == OTHER_COLOR(color) && board[wormb] == OTHER_COLOR(color), move); if (!defend_both(worma, wormb)) { if (0) gprintf("%1m: Reject attack_either_move for %1m, %1m (can't defend both)\n", move, worma, wormb); return; } if (trymove(move, color, "test_attack_either_move", worma)) { if (board[worma] == OTHER_COLOR(color) && board[wormb] == OTHER_COLOR(color)) { if (!find_defense(worma, NULL) || !find_defense(wormb, NULL)) { if (0) gprintf("%1m: Rej. attack_either_move for %1m & %1m (regular attack)\n", move, worma, wormb); } else if (!defend_both(worma, wormb)) add_either_move(move, ATTACK_STRING, worma, ATTACK_STRING, wormb); else { if (0) gprintf("%1m: Rej. attack_either_move for %1m & %1m (doesn't work)\n", move, worma, wormb); } } else if (0) gprintf("%1m: Rej. attack_either_move for %1m & %1m (captured directly)\n", move, worma, wormb); popgo(); } } /* True if str is adjacent to a stone in atari, which is tactically * attackable (to exclude pointless captures of snapback stones). */ int adjacent_to_stone_in_atari(int str) { int adj; int adjs[MAXCHAIN]; int k; adj = chainlinks2(str, adjs, 1); for (k = 0; k < adj; k++) if (attack(adjs[k], NULL)) return 1; return 0; } /* True if str is adjacent to a stone in atari, which is tactically * defendable. */ int adjacent_to_defendable_stone_in_atari(int str) { int adj; int adjs[MAXCHAIN]; int k; adj = chainlinks2(str, adjs, 1); for (k = 0; k < adj; k++) if (attack_and_defend(adjs[k], NULL, NULL, NULL, NULL)) return 1; return 0; } void backfill_replace(int move, int str) { int defense_move = NO_MOVE; if (TRYMOVE(move, OTHER_COLOR(board[str]))) { if (attack_and_defend(str, NULL, NULL, NULL, &defense_move)) { /* Must undo the trymove before adding the replacement move. */ popgo(); add_replacement_move(move, defense_move, board[str]); } else popgo(); } } /* True if * 1. White to move. * 2. All white stones look dead. * 3. Less than 40% of the board is filled or less than 20% of the * board is filled with white stones. * * This is intended for patterns forcing white to thrash around early * in high handicap games, instead of passing because it looks like no * stones can live. */ int thrash_around_helper(ARGS) { UNUSED(pattern); UNUSED(trans); UNUSED(move); /* Do not generate these moves when doing scoring or if fuseki move * generation is disabled (typically used when solving life and * death problems embedded on a big board). */ if (doing_scoring || disable_fuseki || (stones_on_board(BLACK | WHITE) > board_size * board_size * 2 / 5 && stones_on_board(WHITE) > board_size * board_size / 5) || color == BLACK || lively_dragon_exists(WHITE)) return 0; return 1; } /* Returns true if * * 1. The board size is odd. * 2. A white move is being generated. * 3. The komi is less than or equal to zero. * 4. str is placed at tengen. * 5. At least 10 moves have been played. * 6. The board is currently mirror symmetric. * * This is intended for patterns to break mirror go when black starts at * tengen and then mirrors white. We only care about breaking the mirroring * if komi is non-positive, otherwise the mirroring is to our advantage. */ int break_mirror_helper(int str, int color) { if (board_size % 2 == 1 && color == WHITE && komi <= 0.0 && I(str) == (board_size - 1) / 2 && J(str) == (board_size - 1) / 2 && stones_on_board(BLACK | WHITE) > 10 && test_symmetry_after_move(PASS_MOVE, EMPTY, 1)) return 1; return 0; } /* This helper is intended to detect semeai kind of positions where * the tactical reading can't be trusted enough to allow amalgamation * over presumably tactically dead strings. * * It has turned out to be best not to trust tactical reading of three * and four liberty strings at all. Not trusting two liberty strings * leads to an underamalgamation and unnecessarily many dragons on the * board. Therefore we try to detect two liberty strings with an * enclosed nakade, which after capturing leads to an unreliable * reading at three or four liberties. * * More specifically we check whether the string has a neighbor with * the following properties: * 1. At least three stones in size. * 2. All its liberties are common liberties with the string. * 3. It has no second order liberties. * 4. Its liberties are adjacent to no other strings than itself and * the primary string. * * If we find such a neighbor 1 is returned, otherwise 0. */ int distrust_tactics_helper(int str) { int color = board[str]; int adj; int adjs[MAXCHAIN]; int k; int r; int s; int lib = countlib(str); ASSERT1(IS_STONE(board[str]), str); if (lib > 2) return 1; else if (lib == 1) return 0; adj = chainlinks3(str, adjs, lib); for (r = 0; r < adj; r++) { int nakade = 1; int adjlib; int adjlibs[3]; if (countstones(adjs[r]) < 3) continue; adjlib = findlib(adjs[r], 3, adjlibs); for (s = 0; s < adjlib; s++) { int str_found = 0; for (k = 0; k < 4; k++) { int pos = adjlibs[s] + delta[k]; if (board[pos] == EMPTY && !liberty_of_string(pos, adjs[r])) nakade = 0; else if (board[pos] == color) { if (same_string(pos, str)) str_found = 1; else nakade = 0; } else if (board[pos] == OTHER_COLOR(color) && !same_string(pos, adjs[r])) nakade = 0; } if (!str_found) nakade = 0; } if (nakade) return 1; } return 0; } /* * LOCAL Variables: * tab-width: 8 * c-basic-offset: 2 * End: */