/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ * 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 "gnugo.h" #include #include #include #include #include "liberty.h" #include "gg_utils.h" #include "random.h" #include "move_reasons.h" /* All these data structures are declared in move_reasons.h */ struct move_data move[BOARDMAX]; struct move_reason move_reasons[MAX_MOVE_REASONS]; int next_reason; /* Connections */ int conn_worm1[MAX_CONNECTIONS]; int conn_worm2[MAX_CONNECTIONS]; int next_connection; /* Potential semeai moves. */ int semeai_target1[MAX_POTENTIAL_SEMEAI]; int semeai_target2[MAX_POTENTIAL_SEMEAI]; static int next_semeai; /* Unordered sets (currently pairs) of move reasons / targets */ Reason_set either_data[MAX_EITHER]; int next_either; Reason_set all_data[MAX_ALL]; int next_all; /* Eye shapes */ int eyes[MAX_EYES]; int eyecolor[MAX_EYES]; int next_eye; /* Lunches */ int lunch_dragon[MAX_LUNCHES]; /* eater */ int lunch_worm[MAX_LUNCHES]; /* food */ int next_lunch; /* Point redistribution */ int replacement_map[BOARDMAX]; /* The color for which we are evaluating moves. */ int current_color; /* Attack threats that are known to be sente locally. */ static int known_good_attack_threats[BOARDMAX][MAX_ATTACK_THREATS]; /* Moves that are known to be safe (in the sense that played stones can * be captured, but opponent loses much more when attempting to do so) */ static int known_safe_moves[BOARDMAX]; /* Helper functions to check conditions in discard rules. */ typedef int (*discard_condition_fn_ptr)(int pos, int what); struct discard_rule { int reason_type[MAX_REASONS]; discard_condition_fn_ptr condition; int flags; char trace_message[MAX_TRACE_LENGTH]; }; /* Initialize move reason data structures. */ void clear_move_reasons(void) { int pos; int k; next_reason = 0; next_connection = 0; next_semeai = 0; next_either = 0; next_all = 0; next_eye = 0; next_lunch = 0; for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (ON_BOARD(pos)) { move[pos].value = 0.0; move[pos].final_value = 0.0; move[pos].additional_ko_value = 0.0; move[pos].territorial_value = 0.0; move[pos].strategical_value = 0.0; move[pos].maxpos_shape = 0.0; move[pos].numpos_shape = 0; move[pos].maxneg_shape = 0.0; move[pos].numneg_shape = 0; move[pos].followup_value = 0.0; move[pos].influence_followup_value = 0.0; move[pos].reverse_followup_value = 0.0; move[pos].secondary_value = 0.0; move[pos].min_value = 0.0; move[pos].max_value = HUGE_MOVE_VALUE; move[pos].min_territory = 0.0; move[pos].max_territory = HUGE_MOVE_VALUE; for (k = 0; k < MAX_REASONS; k++) move[pos].reason[k] = -1; move[pos].move_safety = 0; move[pos].worthwhile_threat = 0; move[pos].randomness_scaling = 1.0; /* The reason we assign a random number to each move immediately * is to avoid dependence on which moves are evaluated when it * comes to choosing between multiple moves of the same value. * In this way we can get consistent results for use in the * regression tests. */ move[pos].random_number = gg_drand(); /* Do not send away the points (yet). */ replacement_map[pos] = NO_MOVE; } } for (pos = BOARDMIN; pos < BOARDMAX; pos++) { known_safe_moves[pos] = 0; for (k = 0; k < MAX_ATTACK_THREATS; k++) known_good_attack_threats[pos][k] = NO_MOVE; } } /* * Find the index of a connection in the list of connections. * If necessary, add a new entry. */ int find_connection(int worm1, int worm2) { int k; if (worm1 > worm2) { /* Swap to canonical order. */ int tmp = worm1; worm1 = worm2; worm2 = tmp; } for (k = 0; k < next_connection; k++) if (conn_worm1[k] == worm1 && conn_worm2[k] == worm2) return k; /* Add a new entry. */ gg_assert(next_connection < MAX_CONNECTIONS); conn_worm1[next_connection] = worm1; conn_worm2[next_connection] = worm2; next_connection++; return next_connection - 1; } static int find_either_data(int reason1, int what1, int reason2, int what2) { int k; /* Make sure the worms are ordered canonically. */ if (what1 > what2) { int tmp = what1; what1 = what2; what2 = tmp; } for (k = 0; k < next_either; k++) if (either_data[k].reason1 == reason1 && either_data[k].what1 == what1 && either_data[k].reason2 == reason2 && either_data[k].what2 == what2) return k; /* Add a new entry. */ gg_assert(next_either < MAX_EITHER); either_data[next_either].reason1 = reason1; either_data[next_either].what1 = what1; either_data[next_either].reason2 = reason2; either_data[next_either].what2 = what2; next_either++; return next_either - 1; } static int find_all_data(int reason1, int what1, int reason2, int what2) { int k; /* Make sure the worms are ordered canonically. */ if (what1 > what2) { int tmp = what1; what1 = what2; what2 = tmp; } for (k = 0; k < next_all; k++) if (all_data[k].reason1 == reason1 && all_data[k].what1 == what1 && all_data[k].reason2 == reason2 && all_data[k].what2 == what2) return k; /* Add a new entry. */ gg_assert(next_all < MAX_ALL); all_data[next_all].reason1 = reason1; all_data[next_all].what1 = what1; all_data[next_all].reason2 = reason2; all_data[next_all].what2 = what2; next_all++; return next_all - 1; } static int find_pair_data(int what1, int what2) { int k; for (k = 0; k < next_either; k++) if (either_data[k].what1 == what1 && either_data[k].what2 == what2) return k; /* Add a new entry. */ gg_assert(next_either < MAX_EITHER); either_data[next_either].what1 = what1; either_data[next_either].what2 = what2; next_either++; return next_either - 1; } /* Interprets the object of a reason and returns its position. * If the object is a pair (of worms or dragons), the position of the first * object is returned. (This is only used for trace outputs.) Returns * NO_MOVE if move does not point to a location. * FIXME: This new function produces some code duplication with other * trace output function. Do some code cleanup here. */ static int get_pos(int reason, int what) { switch (reason) { case ATTACK_MOVE: case DEFEND_MOVE: case ATTACK_THREAT: case DEFEND_THREAT: case ATTACK_MOVE_GOOD_KO: case ATTACK_MOVE_BAD_KO: case DEFEND_MOVE_GOOD_KO: case DEFEND_MOVE_BAD_KO: return what; case SEMEAI_MOVE: case SEMEAI_THREAT: case STRATEGIC_ATTACK_MOVE: case STRATEGIC_DEFEND_MOVE: case OWL_ATTACK_MOVE: case OWL_DEFEND_MOVE: case OWL_ATTACK_THREAT: case OWL_DEFEND_THREAT: case OWL_PREVENT_THREAT: case UNCERTAIN_OWL_ATTACK: case UNCERTAIN_OWL_DEFENSE: case OWL_ATTACK_MOVE_GOOD_KO: case OWL_ATTACK_MOVE_BAD_KO: case OWL_DEFEND_MOVE_GOOD_KO: case OWL_DEFEND_MOVE_BAD_KO: return what; case EITHER_MOVE: /* FIXME: What should we return here? */ return either_data[what].what1; case ALL_MOVE: /* FIXME: What should we return here? */ return all_data[what].what1; case CONNECT_MOVE: case CUT_MOVE: return conn_worm1[what]; case ANTISUJI_MOVE: case EXPAND_TERRITORY_MOVE: case EXPAND_MOYO_MOVE: case INVASION_MOVE: case MY_ATARI_ATARI_MOVE: case YOUR_ATARI_ATARI_MOVE: return NO_MOVE; case OWL_ATTACK_MOVE_GAIN: case OWL_DEFEND_MOVE_LOSS: /* FIXME: What should we return here? */ return either_data[what].what1; default: /* We should never get here: */ gg_assert(0); return 0; /* To keep gcc happy. */ } } /* * See if a lunch is already in the list of lunches, otherwise add a new * entry. A lunch is in this context a pair of eater (a dragon) and food * (a worm). */ void add_lunch(int eater, int food) { int k; int dragon1 = dragon[eater].origin; int worm1 = worm[food].origin; ASSERT_ON_BOARD1(eater); ASSERT_ON_BOARD1(food); for (k = 0; k < next_lunch; k++) if ((lunch_dragon[k] == dragon1) && (lunch_worm[k] == worm1)) return; /* Add a new entry. */ gg_assert(next_lunch < MAX_LUNCHES); lunch_dragon[next_lunch] = dragon1; lunch_worm[next_lunch] = worm1; next_lunch++; return; } /* ---------------------------------------------------------------- */ /* * Add a move reason for (pos) if it's not already there or the * table is full. */ static void add_move_reason(int pos, int type, int what) { int k; ASSERT_ON_BOARD1(pos); if (stackp == 0) { ASSERT1(board[pos] == EMPTY, pos); } for (k = 0; k < MAX_REASONS; k++) { int r = move[pos].reason[k]; if (r < 0) break; if (move_reasons[r].type == type && move_reasons[r].what == what) return; /* Reason already listed. */ } /* Reason not found, add it if there is place left in both lists. * Otherwise drop it. */ if (k >= MAX_REASONS) { DEBUG(DEBUG_MOVE_REASONS, "Move reason at %1m (type=%d, what=%d) dropped because list full.\n", pos, type, what); return; } if (next_reason >= MAX_MOVE_REASONS) { DEBUG(DEBUG_MOVE_REASONS, "Move reason at %1m (type=%d, what=%d) dropped because global list full.\n", pos, type, what); return; } /* Add a new entry. */ move[pos].reason[k] = next_reason; move_reasons[next_reason].type = type; move_reasons[next_reason].what = what; move_reasons[next_reason].status = ACTIVE; next_reason++; } /* * Remove a move reason for (pos). Ignore silently if the reason * wasn't there. */ static void remove_move_reason(int pos, int type, int what) { int k; int n = -1; /* Position of the move reason to be deleted. */ ASSERT_ON_BOARD1(pos); for (k = 0; k < MAX_REASONS; k++) { int r = move[pos].reason[k]; if (r < 0) break; if (move_reasons[r].type == type && move_reasons[r].what == what) n = k; } if (n == -1) return; /* Move reason wasn't there. */ /* Now move the last move reason to position n, thereby removing the * one we were looking for. */ k--; move[pos].reason[n] = move[pos].reason[k]; move[pos].reason[k] = -1; } /* * Check whether a move reason already is recorded for a move. * A negative value for 'what' means only match 'type'. */ int move_reason_known(int pos, int type, int what) { int k; int r; ASSERT_ON_BOARD1(pos); for (k = 0; k < MAX_REASONS; k++) { r = move[pos].reason[k]; if (r < 0) break; if (move_reasons[r].type == type && (what < 0 || move_reasons[r].what == what)) return 1; } return 0; } /* ---------------------------------------------------------------- */ /* Functions used in discard_rules follow below. */ /* * Check whether an attack move reason already is recorded for a move. * A negative value for 'what' means only match 'type'. */ int attack_move_reason_known(int pos, int what) { ASSERT1(what < 0 || IS_STONE(board[what]), what); what = worm[what].origin; if (move_reason_known(pos, ATTACK_MOVE, what)) return WIN; if (move_reason_known(pos, ATTACK_MOVE_GOOD_KO, what)) return KO_A; if (move_reason_known(pos, ATTACK_MOVE_BAD_KO, what)) return KO_B; return 0; } /* * Check whether a defense move reason already is recorded for a move. * A negative value for 'what' means only match 'type'. */ int defense_move_reason_known(int pos, int what) { ASSERT1(what < 0 || IS_STONE(board[what]), what); what = worm[what].origin; if (move_reason_known(pos, DEFEND_MOVE, what)) return WIN; if (move_reason_known(pos, DEFEND_MOVE_GOOD_KO, what)) return KO_A; if (move_reason_known(pos, DEFEND_MOVE_BAD_KO, what)) return KO_B; return 0; } /* Check whether a dragon consists of only one worm. If so, check * whether we know of a tactical attack or defense move. */ static int tactical_move_vs_whole_dragon_known(int pos, int what) { return ((worm[what].size == dragon[what].size) && (attack_move_reason_known(pos, what) || defense_move_reason_known(pos, what))); } /* * Check whether an owl attack move reason already is recorded for a move. * A negative value for 'what' means only match 'type'. */ int owl_attack_move_reason_known(int pos, int what) { if (move_reason_known(pos, OWL_ATTACK_MOVE, what)) return WIN; if (move_reason_known(pos, OWL_ATTACK_MOVE_GOOD_KO, what)) return KO_A; if (move_reason_known(pos, OWL_ATTACK_MOVE_BAD_KO, what)) return KO_B; return 0; } /* * Check whether an owl defense move reason already is recorded for a move. * A negative value for 'what' means only match 'type'. */ int owl_defense_move_reason_known(int pos, int what) { if (move_reason_known(pos, OWL_DEFEND_MOVE, what)) return WIN; if (move_reason_known(pos, OWL_DEFEND_MOVE_GOOD_KO, what)) return KO_A; if (move_reason_known(pos, OWL_DEFEND_MOVE_BAD_KO, what)) return KO_B; return 0; } /* * Check whether an owl attack/defense move reason is recorded for a move. * A negative value for 'what' means only match 'type'. */ int owl_move_reason_known(int pos, int what) { return (owl_attack_move_reason_known(pos, what) || owl_defense_move_reason_known(pos, what)); } /* * Check whether we have an owl attack/defense reason for a move that * involves a specific worm. */ static int owl_move_vs_worm_known(int pos, int what) { return owl_move_reason_known(pos, dragon[what].origin); } int semeai_move_reason_known(int pos, int what) { return move_reason_known(pos, SEMEAI_MOVE, what); } /* Check whether a worm is inessential */ static int concerns_inessential_worm(int pos, int what) { UNUSED(pos); return DRAGON2(what).safety == INESSENTIAL || worm[what].inessential; } /* Check whether a dragon is inessential */ static int concerns_inessential_dragon(int pos, int what) { UNUSED(pos); return DRAGON2(what).safety == INESSENTIAL; } static int move_is_marked_unsafe(int pos, int what) { UNUSED(what); return (!move[pos].move_safety && !adjacent_to_nondead_stone(pos, current_color)); } /* Check whether a dragon is non-critical. */ static int concerns_noncritical_dragon(int pos, int what) { UNUSED(pos); return (dragon[what].status != CRITICAL && worm[what].attack_codes[0] == 0); } /* (what) points to two worms listed in either_data. Returns true if * this is a "attack either" move reason, and one of the worms attackable. * FIXME: Ko? */ static int either_worm_attackable(int pos, int what) { UNUSED(pos); return (either_data[what].reason1 == ATTACK_STRING && either_data[what].reason2 == ATTACK_STRING && (worm[either_data[what].what1].attack_codes[0] != 0 || worm[either_data[what].what2].attack_codes[0] != 0)); } /* (what) points to two worms via all_data. Returns true if this is * a "defend both" move reason, and one of the worms is attackable. * FIXME: Ko? */ static int one_of_both_attackable(int pos, int what) { UNUSED(pos); return (all_data[what].reason1 == DEFEND_STRING && all_data[what].reason2 == DEFEND_STRING && (worm[all_data[what].what1].attack_codes[0] != 0 || worm[all_data[what].what2].attack_codes[0] != 0)); } /* ---------------------------------------------------------------- */ /* * Add to the reasons for the move at (pos) that it attacks the worm * at (ww). */ void add_attack_move(int pos, int ww, int code) { ASSERT_ON_BOARD1(ww); ww = worm[ww].origin; if (code == WIN) add_move_reason(pos, ATTACK_MOVE, ww); else if (code == KO_A) add_move_reason(pos, ATTACK_MOVE_GOOD_KO, ww); else if (code == KO_B) add_move_reason(pos, ATTACK_MOVE_BAD_KO, ww); } /* * Add to the reasons for the move at (pos) that it defends the worm * at (ww). */ void add_defense_move(int pos, int ww, int code) { ASSERT_ON_BOARD1(ww); ww = worm[ww].origin; if (code == WIN) add_move_reason(pos, DEFEND_MOVE, ww); else if (code == KO_A) add_move_reason(pos, DEFEND_MOVE_GOOD_KO, ww); else if (code == KO_B) add_move_reason(pos, DEFEND_MOVE_BAD_KO, ww); } /* * Add to the reasons for the move at (pos) that it threatens to * attack the worm at (ww). */ void add_attack_threat_move(int pos, int ww, int code) { UNUSED(code); ASSERT_ON_BOARD1(ww); add_move_reason(pos, ATTACK_THREAT, worm[ww].origin); } /* Remove an attack threat move reason. */ void remove_attack_threat_move(int pos, int ww) { ASSERT_ON_BOARD1(ww); remove_move_reason(pos, ATTACK_THREAT, worm[ww].origin); } /* * Add to the reasons for the move at (pos) that it defends the worm * at (ww). */ void add_defense_threat_move(int pos, int ww, int code) { UNUSED(code); ASSERT_ON_BOARD1(ww); add_move_reason(pos, DEFEND_THREAT, worm[ww].origin); } /* Report all, or up to max_strings, strings that are threatened * at (pos). */ int get_attack_threats(int pos, int max_strings, int strings[]) { int k; int num_strings; num_strings = 0; for (k = 0; k < MAX_REASONS; k++) { int r = move[pos].reason[k]; if (r < 0) break; if (move_reasons[r].type == ATTACK_THREAT) strings[num_strings++] = move_reasons[r].what; if (num_strings == max_strings) break; } return num_strings; } /* Report all, or up to max_strings, strings that might be defended * at (pos). */ int get_defense_threats(int pos, int max_strings, int strings[]) { int k; int num_strings; num_strings = 0; for (k = 0; k < MAX_REASONS; k++) { int r = move[pos].reason[k]; if (r < 0) break; if (move_reasons[r].type == DEFEND_THREAT) strings[num_strings++] = move_reasons[r].what; if (num_strings == max_strings) break; } return num_strings; } /* Report the biggest dragon that is owl-affected (possibily with ko) * by a move at (pos). */ int get_biggest_owl_target(int pos) { int k; int biggest_target = -1; float target_size = 0.0; for (k = 0; k < MAX_REASONS; k++) { int r = move[pos].reason[k]; if (r < 0) break; switch (move_reasons[r].type) { case OWL_ATTACK_MOVE: case OWL_ATTACK_MOVE_GOOD_KO: case OWL_ATTACK_MOVE_BAD_KO: case OWL_ATTACK_THREAT: case OWL_DEFEND_MOVE: case OWL_DEFEND_MOVE_GOOD_KO: case OWL_DEFEND_MOVE_BAD_KO: case OWL_DEFEND_THREAT: case OWL_PREVENT_THREAT: if (dragon[move_reasons[r].what].effective_size > target_size) { biggest_target = move_reasons[r].what; target_size = dragon[move_reasons[r].what].effective_size; } break; } } return biggest_target; } /* * Add to the reasons for the move at (pos) that it connects the * dragons at (dr1) and (dr2). Require that the dragons are * distinct. */ void add_connection_move(int pos, int w1, int w2) { int connection; ASSERT_ON_BOARD1(w1); ASSERT_ON_BOARD1(w2); ASSERT1(worm[w1].color == worm[w2].color, w1); if (worm[w1].origin == worm[w2].origin) return; connection = find_connection(worm[w1].origin, worm[w2].origin); add_move_reason(pos, CONNECT_MOVE, connection); } /* * Add to the reasons for the move at (pos) that it cuts the * dragons at (dr1) and (dr2). Require that the dragons are * distinct. */ void add_cut_move(int pos, int w1, int w2) { int connection; ASSERT_ON_BOARD1(w1); ASSERT_ON_BOARD1(w2); ASSERT1(worm[w1].color == worm[w2].color, w1); if (worm[w1].origin == worm[w2].origin) return; connection = find_connection(worm[w1].origin, worm[w2].origin); /* * Ignore the cut or connection if either (w1) or (w2) * points to a tactically captured worm. */ if ((worm[w1].attack_codes[0] != 0 && worm[w1].defense_codes[0] == 0) || (worm[w2].attack_codes[0] != 0 && worm[w2].defense_codes[0] == 0)) return; add_move_reason(pos, CUT_MOVE, connection); } /* * Add to the reasons for the move at (pos) that it is an anti-suji. * This means that it's a locally inferior move or for some other reason * must *not* be played. */ void add_antisuji_move(int pos) { add_move_reason(pos, ANTISUJI_MOVE, 0); } /* * Add to the reasons for the move at (pos) that it wins the * dragon (friendly or not) at (dr) in semeai. Since it is * possible that in some semeai one player can kill but the * other can only make seki, it is possible that one dragon * is already alive in seki. Therefore separate move reasons * must be added for the two dragons. */ void add_semeai_move(int pos, int dr) { ASSERT_ON_BOARD1(dr); add_move_reason(pos, SEMEAI_MOVE, dragon[dr].origin); } /* * Add to the reasons for the move at (pos) that it might * kill/save the dragon at (dr1) in the semeai against (dr2). */ static void add_potential_semeai_move(int pos, int type, int dr1, int dr2) { ASSERT1(ON_BOARD(dr1), pos); ASSERT1(ON_BOARD(dr2), pos); if (next_semeai >= MAX_POTENTIAL_SEMEAI) DEBUG(DEBUG_MOVE_REASONS, "Potential semeai move at %1m dropped as list was full\n", pos); else { semeai_target1[next_semeai] = dr1; semeai_target2[next_semeai] = dr2; add_move_reason(pos, type, next_semeai); next_semeai++; } } /* * Add to the reasons for the move at (pos) that it might * kill the dragon at (dr1) in the semeai against (dr2). */ void add_potential_semeai_attack(int pos, int dr1, int dr2) { add_potential_semeai_move(pos, POTENTIAL_SEMEAI_ATTACK, dr1, dr2); } /* * Add to the reasons for the move at (pos) that it might * save the dragon at (dr1) in the semeai against (dr2). */ void add_potential_semeai_defense(int pos, int dr1, int dr2) { add_potential_semeai_move(pos, POTENTIAL_SEMEAI_DEFENSE, dr1, dr2); } /* * Add to the reasons for the move at (pos) that given two * moves in a row a move here can win the dragon (friendly or * not) at (dr) in semeai. Such a move can be used as a * ko threat, and it is also given some value due to uncertainty * in the counting of liberties. */ void add_semeai_threat(int pos, int dr) { ASSERT_ON_BOARD1(dr); add_move_reason(pos, SEMEAI_THREAT, dragon[dr].origin); } /* * Add to the reasons for the move at (pos) that it will accomplish * one of two things: either (reason1) on (target1) or (reason2) on * (target2). * * At this time, (reason) can only be ATTACK_STRING. * However, more reasons will be implemented in the future. * * FIXME: Implement at least ATTACK_MOVE_GOOD_KO, ATTACK_MOVE_BAD_KO, * DEFEND_MOVE and associates, CONNECT_MOVE, OWL_ATTACK_MOVE, * OWL_DEFEND_MOVE, and possibly more. * * FIXME: Generalize to more than 2 parameters. * When that is done, this will be a good way to add * atari_atari moves. */ void add_either_move(int pos, int reason1, int target1, int reason2, int target2) { int what1 = 0; int what2 = 0; int index; ASSERT_ON_BOARD1(target1); ASSERT_ON_BOARD1(target2); if (reason1 == reason2 && target1 == target2) return; /* For now. */ gg_assert(reason1 == ATTACK_STRING); gg_assert(reason2 == ATTACK_STRING); switch (reason1) { case ATTACK_STRING: { what1 = worm[target1].origin; /* If this string is already attacked, and with no defense, then * there is no additional value of this move reason. */ if (worm[target1].attack_codes[0] != 0 && worm[target1].defense_codes[0] == 0) return; } break; default: break; } switch (reason2) { case ATTACK_STRING: { what2 = worm[target2].origin; /* If this string is already attacked, and with no defense, then * there is no additional value of this move reason. */ if (worm[target2].attack_codes[0] != 0 && worm[target2].defense_codes[0] == 0) return; } break; default: break; } index = find_either_data(reason1, what1, reason2, what2); add_move_reason(pos, EITHER_MOVE, index); } /* * Add to the reasons for the move at (pos) that it will accomplish * both of two things: (reason1) on (target1) and (reason2) on * (target2). * * At this time, (reason) can only be DEFEND_STRING. * However, more reasons will be implemented in the future. * * FIXME: Implement at least ATTACK_MOVE_GOOD_KO, ATTACK_MOVE_BAD_KO, * DEFEND_MOVE and associates, CONNECT_MOVE, OWL_ATTACK_MOVE, * OWL_DEFEND_MOVE, and possibly more. * * FIXME: Generalize to more than 2 parameters. * When that is done, this will be a good way to add * atari_atari moves. */ void add_all_move(int pos, int reason1, int target1, int reason2, int target2) { int what1 = 0; int what2 = 0; int index; ASSERT_ON_BOARD1(target1); ASSERT_ON_BOARD1(target2); if (reason1 == reason2 && target1 == target2) return; /* For now. */ gg_assert(reason1 == DEFEND_STRING); gg_assert(reason2 == DEFEND_STRING); switch (reason1) { case DEFEND_STRING: what1 = worm[target1].origin; break; default: break; } switch (reason2) { case DEFEND_STRING: what2 = worm[target2].origin; break; default: break; } index = find_all_data(reason1, what1, reason2, what2); add_move_reason(pos, ALL_MOVE, index); } void add_loss_move(int pos, int target1, int target2) { int what1 = dragon[target1].origin; int what2 = worm[target2].origin; int index = find_pair_data(what1, what2); ASSERT1(target2 != NO_MOVE, pos); add_move_reason(pos, OWL_DEFEND_MOVE_LOSS, index); } /* * Add to the reasons for the move at (pos) that it expands * territory. */ void add_expand_territory_move(int pos) { add_move_reason(pos, EXPAND_TERRITORY_MOVE, 0); } /* * Add to the reasons for the move at (pos) that it expands * moyo. */ void add_expand_moyo_move(int pos) { add_move_reason(pos, EXPAND_MOYO_MOVE, 0); } /* * Add to the reasons for the move at (pos) that it is an invasion. */ void add_invasion_move(int pos) { add_move_reason(pos, INVASION_MOVE, 0); } /* * This function is called when a shape value for the move at (pos) * is found. * * We keep track of the largest positive shape value found, and the * total number of positive contributions, as well as the largest * negative shape value found, and the total number of negative * shape contributions. */ void add_shape_value(int pos, float value) { ASSERT_ON_BOARD1(pos); if (value > 0.0) { if (value > move[pos].maxpos_shape) move[pos].maxpos_shape = value; move[pos].numpos_shape += 1; } else if (value < 0.0) { value = -value; if (value > move[pos].maxneg_shape) move[pos].maxneg_shape = value; move[pos].numneg_shape += 1; } } /* * Flag that this move is worthwhile to play as a pure threat move. */ void add_worthwhile_threat_move(int pos) { move[pos].worthwhile_threat = 1; } /* * Add to the reasons for the move at (pos) that it attacks * the dragon (dr) on a strategical level. */ void add_strategical_attack_move(int pos, int dr) { dr = dragon[dr].origin; ASSERT_ON_BOARD1(dr); add_move_reason(pos, STRATEGIC_ATTACK_MOVE, dr); } /* * Add to the reasons for the move at (pos) that it defends * the dragon (dr) on a strategical level. */ void add_strategical_defense_move(int pos, int dr) { dr = dragon[dr].origin; ASSERT_ON_BOARD1(dr); add_move_reason(pos, STRATEGIC_DEFEND_MOVE, dr); } /* * Add to the reasons for the move at (pos) that the owl * code reports an attack on the dragon (dr). */ void add_owl_attack_move(int pos, int dr, int kworm, int code) { dr = dragon[dr].origin; ASSERT_ON_BOARD1(dr); if (code == WIN) add_move_reason(pos, OWL_ATTACK_MOVE, dr); else if (code == KO_A) add_move_reason(pos, OWL_ATTACK_MOVE_GOOD_KO, dr); else if (code == KO_B) add_move_reason(pos, OWL_ATTACK_MOVE_BAD_KO, dr); else if (code == GAIN) { ASSERT_ON_BOARD1(kworm); add_move_reason(pos, OWL_ATTACK_MOVE_GAIN, find_pair_data(dr, kworm)); } } /* * Add to the reasons for the move at (pos) that the owl * code reports a defense of the dragon (dr). */ void add_owl_defense_move(int pos, int dr, int code) { dr = dragon[dr].origin; ASSERT_ON_BOARD1(dr); if (code == WIN) add_move_reason(pos, OWL_DEFEND_MOVE, dr); else if (code == KO_A) add_move_reason(pos, OWL_DEFEND_MOVE_GOOD_KO, dr); else if (code == KO_B) add_move_reason(pos, OWL_DEFEND_MOVE_BAD_KO, dr); } /* * Add to the reasons for the move at (pos) that the owl * code reports a move threatening to attack the dragon enemy (dr). * That is, if the attacker is given two moves in a row, (pos) * can be the first move. */ void add_owl_attack_threat_move(int pos, int dr, int code) { UNUSED(code); dr = dragon[dr].origin; ASSERT_ON_BOARD1(dr); add_move_reason(pos, OWL_ATTACK_THREAT, dragon[dr].origin); add_worthwhile_threat_move(pos); } /* The owl code found the friendly dragon alive, or the unfriendly dragon * dead, and an extra point of attack or defense was found, so this might be a * good place to play. */ void add_owl_uncertain_defense_move(int pos, int dr) { dr = dragon[dr].origin; ASSERT_ON_BOARD1(dr); add_move_reason(pos, UNCERTAIN_OWL_DEFENSE, dragon[dr].origin); } /* The owl code found the opponent dragon alive, or the friendly * dragon dead, but was uncertain, and this move reason propose * an attack or defense which is expected to fail but might succeed. */ void add_owl_uncertain_attack_move(int pos, int dr) { dr = dragon[dr].origin; ASSERT_ON_BOARD1(dr); add_move_reason(pos, UNCERTAIN_OWL_ATTACK, dragon[dr].origin); } /* * Add to the reasons for the move at (pos) that the owl * code reports a move threatening to rescue the dragon (dr). * That is, if the defender is given two moves in a row, (pos) * can be the first move. */ void add_owl_defense_threat_move(int pos, int dr, int code) { UNUSED(code); dr = dragon[dr].origin; ASSERT_ON_BOARD1(dr); add_move_reason(pos, OWL_DEFEND_THREAT, dragon[dr].origin); add_worthwhile_threat_move(pos); } /* Add to the reasons for the move at (pos) that it captures * at least one of a set of worms which individually are tactically * safe (such as a double atari). Only one such move reason is * permitted per move. */ void add_my_atari_atari_move(int pos, int size) { add_move_reason(pos, MY_ATARI_ATARI_MOVE, size); } /* Add to the reasons for the move at (pos) that it stops a * combination attack for the opponent. */ void add_your_atari_atari_move(int pos, int size) { add_move_reason(pos, YOUR_ATARI_ATARI_MOVE, size); } /* * Add to the reasons for the move at (pos) that the owl * code reports a move threatening to defend the dragon enemy (dr), * and that (pos) is a move which attacks the dragon. * That is, if the defender is given two moves in a row, (pos) * can be the first move. Hopefully playing at (pos) makes it harder * for the dragon to live. */ void add_owl_prevent_threat_move(int pos, int dr) { ASSERT_ON_BOARD1(dr); add_move_reason(pos, OWL_PREVENT_THREAT, dragon[dr].origin); } /* * Add value of followup moves. */ void add_followup_value(int pos, float value) { ASSERT_ON_BOARD1(pos); if (value > move[pos].followup_value) move[pos].followup_value = value; } /* * Add value of reverse followup moves. */ void add_reverse_followup_value(int pos, float value) { ASSERT_ON_BOARD1(pos); if (value > move[pos].reverse_followup_value) move[pos].reverse_followup_value = value; } /* * Set a minimum allowed value for the move. */ int set_minimum_move_value(int pos, float value) { ASSERT_ON_BOARD1(pos); if (value > move[pos].min_value) { move[pos].min_value = value; return 1; } return 0; } /* * Set a maximum allowed value for the move. */ void set_maximum_move_value(int pos, float value) { ASSERT_ON_BOARD1(pos); if (value < move[pos].max_value) move[pos].max_value = value; } /* * Set a minimum allowed territorial value for the move. */ void set_minimum_territorial_value(int pos, float value) { ASSERT_ON_BOARD1(pos); if (value > move[pos].min_territory) move[pos].min_territory = value; } /* * Set a maximum allowed territorial value for the move. */ void set_maximum_territorial_value(int pos, float value) { ASSERT_ON_BOARD1(pos); if (value < move[pos].max_territory) move[pos].max_territory = value; } /* * Add a point redistribution rule, sending the points from (from) * to (to). */ void add_replacement_move(int from, int to, int color) { int cc; int pos; int dummy; ASSERT_ON_BOARD1(from); ASSERT_ON_BOARD1(to); if (board[from] != EMPTY) return; ASSERT1(board[to] == EMPTY, to); cc = replacement_map[to]; if (unconditionally_meaningless_move(to, color, &dummy)) { /* Silently ignore replacement patterns which conflict with the * unconditional analysis since the latter is always correct and * it's difficult to anticipate such situations for the patterns. */ return; } /* First check for an incompatible redistribution rule. */ if (replacement_map[from] != NO_MOVE) { int dd = replacement_map[from]; /* Abort if the old rule isn't compatible with the new one. * (But not in the stable release.) */ if (0) { ASSERT1(dd == to || to == replacement_map[dd], from); } /* There already is a redistribution in effect so we * have nothing more to do. */ return; } TRACE("Move at %1m is replaced by %1m.\n", from, to); /* Verify that we don't introduce a cyclic redistribution. */ if (cc == from) { gprintf("Cyclic point redistribution detected.\n"); ASSERT1(0, from); } /* Update the replacement map. Make sure that all replacements * always are directed immediately to the final destination. */ if (cc != NO_MOVE) replacement_map[from] = cc; else replacement_map[from] = to; for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (ON_BOARD(pos) && replacement_map[pos] == from) replacement_map[pos] = replacement_map[from]; } } /* Find worms rescued by a move at (pos). */ void get_saved_worms(int pos, signed char saved[BOARDMAX]) { int k; memset(saved, 0, sizeof(saved[0]) * BOARDMAX); for (k = 0; k < MAX_REASONS; k++) { int r = move[pos].reason[k]; int what; if (r < 0) break; what = move_reasons[r].what; /* We exclude the ko contingent defenses, to avoid that the * confirm_safety routines spot an attack with ko and thinks the * move is unsafe. */ if (move_reasons[r].type == DEFEND_MOVE) mark_string(worm[what].origin, saved, 1); else if (move_reasons[r].type == OWL_DEFEND_MOVE_LOSS) { int origin = dragon[what].origin; int kworm = worm[what].origin; int ii; for (ii = BOARDMIN; ii < BOARDMAX; ii++) if (IS_STONE(board[ii]) && dragon[ii].origin == origin && worm[ii].origin != kworm) mark_string(worm[ii].origin, saved, 1); } } } /* This function marks all stones whose status is changed by an owl move * reason according to the following rules: * 1. For an owl attack, all stones belonging to the attacked dragon are * marked as INFLUENCE_CAPTURED_STONE * 2. For an owl defense, all stones belonging to the defended dragon are * markes as INFLUENCE_SAVED_STONE if they are also sufficiently * tactically stable. * * In effective_size, the sum of the effective size of the changed worms * is returned (unless it is a NULL pointer). */ void mark_changed_dragon(int pos, int color, int affected, int affected2, int move_reason_type, signed char safe_stones[BOARDMAX], float strength[BOARDMAX], float *effective_size) { int ii; signed char new_status = INFLUENCE_SAVED_STONE; int result_to_beat = 0; ASSERT1(board[pos] == EMPTY, pos); ASSERT1(IS_STONE(board[affected]), pos); if (effective_size) *effective_size = 0.0; /* For attack moves, we immediately can set the effective size. * For defense moves, it will be calculated in the course of * updating the worms' status. */ switch (move_reason_type) { case OWL_ATTACK_MOVE: case OWL_ATTACK_MOVE_GOOD_KO: case OWL_ATTACK_MOVE_BAD_KO: ASSERT1(board[affected] == OTHER_COLOR(color), pos); new_status = 0; if (effective_size) *effective_size = dragon[affected].effective_size; break; case OWL_DEFEND_MOVE: ASSERT1(board[affected] == color, pos); result_to_beat = WIN; break; case OWL_DEFEND_MOVE_GOOD_KO: ASSERT1(board[affected] == color, pos); result_to_beat = KO_A; break; case OWL_DEFEND_MOVE_BAD_KO: ASSERT1(board[affected] == color, pos); result_to_beat = KO_B; break; case OWL_ATTACK_MOVE_GAIN: ASSERT1(board[affected] == OTHER_COLOR(color), pos); new_status = 0; if (effective_size) *effective_size = worm[affected2].effective_size; break; case OWL_DEFEND_MOVE_LOSS: ASSERT1(board[affected] == color, pos); if (effective_size) *effective_size = dragon[affected].effective_size - worm[affected2].effective_size; result_to_beat = WIN; break; case SEMEAI_MOVE: ASSERT1(IS_STONE(board[affected]), pos); if (board[affected] == color) result_to_beat = WIN; else { new_status = 0; if (effective_size) *effective_size = dragon[affected].effective_size; } break; default: /* mark_changed_dragon() called with invalid move reason. */ ASSERT1(0, pos); } if (move_reason_type == OWL_ATTACK_MOVE_GAIN) mark_changed_string(affected2, safe_stones, strength, new_status); else { for (ii = first_worm_in_dragon(affected); ii != NO_MOVE; ii = next_worm_in_dragon(ii)) if (new_status == 0) mark_changed_string(ii, safe_stones, strength, new_status); else { int worm_is_safe = 0; if (worm[ii].attack_codes[0] == NO_MOVE || defense_move_reason_known(pos, ii)) worm_is_safe = 1; else if (trymove(pos, color, "mark-changed-dragon", ii)) { if (REVERSE_RESULT(attack(ii, NULL)) >= result_to_beat) worm_is_safe = 1; popgo(); } if (worm_is_safe || move_reason_type == SEMEAI_MOVE) { /* This string can now be considered safe. Hence we mark the * whole string as such: */ mark_changed_string(ii, safe_stones, strength, new_status); if (effective_size) *effective_size += worm[ii].effective_size; } } if (move_reason_type == OWL_DEFEND_MOVE_LOSS) { new_status = 0; mark_changed_string(affected2, safe_stones, strength, new_status); } } } /* Marks the string at (affected) with the new status and accordingly * with the new strength. */ void mark_changed_string(int affected, signed char safe_stones[BOARDMAX], float strength[BOARDMAX], signed char new_status) { float new_strength; int ii; ASSERT1(IS_STONE(board[affected]), affected); if (new_status == 0) new_strength = 0.0; else { gg_assert(new_status == INFLUENCE_SAVED_STONE); new_strength = DEFAULT_STRENGTH; } for (ii = BOARDMIN; ii < BOARDMAX; ii++) if (board[ii] == board[affected] && same_string(ii, affected)) { strength[ii] = new_strength; safe_stones[ii] = new_status; } } /* Find dragons rescued by a move at (pos). */ void get_saved_dragons(int pos, signed char saved[BOARDMAX]) { int k; memset(saved, 0, sizeof(saved[0]) * BOARDMAX); for (k = 0; k < MAX_REASONS; k++) { int r = move[pos].reason[k]; int what; if (r < 0) break; what = move_reasons[r].what; /* We exclude the ko contingent defenses, to avoid that the * confirm_safety routines spot an attack with ko and thinks the * move is unsafe. */ if (move_reasons[r].type == OWL_DEFEND_MOVE) mark_dragon(what, saved, 1); } } /* If a move has saved the dragons in saved_dragons[] and worms in * saved_worms[], this functions writes the stones now supposedly safe * in the array safe_stones[]. * * The safety of the played move itself is set according to * move[pos].move_safety. */ void mark_safe_stones(int color, int move_pos, const signed char saved_dragons[BOARDMAX], const signed char saved_worms[BOARDMAX], signed char safe_stones[BOARDMAX]) { int pos; for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] == OTHER_COLOR(color)) { if (dragon[pos].status == DEAD || (worm[pos].attack_codes[0] != 0 && worm[pos].defense_codes[0] == 0)) safe_stones[pos] = 0; else safe_stones[pos] = SAFE_STONE; } else if (board[pos] == color) { if ((worm[pos].attack_codes[0] != 0 && (worm[pos].defense_codes[0] == 0 || !saved_worms[pos])) || dragon[pos].status == DEAD) safe_stones[pos] = 0; else if (saved_dragons[pos]) safe_stones[pos] = OWL_SAVED_STONE; else if (dragon[pos].status == CRITICAL) safe_stones[pos] = 0; else safe_stones[pos] = SAFE_STONE; } else safe_stones[pos] = 0; } safe_stones[move_pos] = move[move_pos].move_safety && safe_move(move_pos, color) == WIN; } /* List the move reasons for (color)'s move at (pos). Return the * number of move reasons. */ int list_move_reasons(FILE *out, int move_pos) { int m; int n; int pos; int k; int reason1; int reason2; int aa = NO_MOVE; int bb = NO_MOVE; int worm1 = -1; int worm2 = -1; int num_move_reasons = 0; gprintf("\nMove reasons:\n"); for (n = 0; n < board_size; n++) for (m = board_size-1; m >= 0; m--) { pos = POS(m, n); if (move_pos != NO_MOVE && move_pos != pos) continue; for (k = 0; k < MAX_REASONS; k++) { int r = move[pos].reason[k]; if (r < 0) break; num_move_reasons++; switch (move_reasons[r].type) { case ATTACK_MOVE: aa = move_reasons[r].what; gfprintf(out, "Move at %1m attacks %1m%s\n", pos, aa, (worm[aa].defense_codes[0] == 0) ? " (defenseless)" : ""); break; case ATTACK_MOVE_GOOD_KO: aa = move_reasons[r].what; gfprintf(out, "Move at %1m attacks %1m%s with good ko\n", pos, aa, (worm[aa].defense_codes[0] == 0) ? " (defenseless)" : ""); break; case ATTACK_MOVE_BAD_KO: aa = move_reasons[r].what; gfprintf(out, "Move at %1m attacks %1m%s with bad ko\n", pos, aa, (worm[aa].defense_codes[0] == 0) ? " (defenseless)" : ""); break; case DEFEND_MOVE: aa = move_reasons[r].what; gfprintf(out, "Move at %1m defends %1m\n", pos, aa); break; case DEFEND_MOVE_GOOD_KO: aa = move_reasons[r].what; gfprintf(out, "Move at %1m defends %1m with good ko\n", pos, aa); break; case DEFEND_MOVE_BAD_KO: aa = move_reasons[r].what; gfprintf(out, "Move at %1m defends %1m with bad ko\n", pos, aa); break; case ATTACK_THREAT: case DEFEND_THREAT: aa = move_reasons[r].what; if (move_reasons[r].type == ATTACK_THREAT) gfprintf(out, "Move at %1m threatens to attack %1m\n", pos, aa); else if (move_reasons[r].type == DEFEND_THREAT) gfprintf(out, "Move at %1m threatens to defend %1m\n", pos, aa); break; case UNCERTAIN_OWL_DEFENSE: aa = move_reasons[r].what; if (board[aa] == current_color) gfprintf(out, "%1m found alive but not certainly, %1m defends it again\n", aa, pos); else gfprintf(out, "%1m found dead but not certainly, %1m attacks it again\n", aa, pos); break; case CONNECT_MOVE: case CUT_MOVE: worm1 = conn_worm1[move_reasons[r].what]; worm2 = conn_worm2[move_reasons[r].what]; if (move_reasons[r].type == CONNECT_MOVE) gfprintf(out, "Move at %1m connects %1m and %1m\n", pos, worm1, worm2); else gfprintf(out, "Move at %1m cuts %1m and %1m\n", pos, worm1, worm2); break; case ANTISUJI_MOVE: gfprintf(out, "Move at %1m is an antisuji\n", pos); break; case SEMEAI_MOVE: aa = move_reasons[r].what; gfprintf(out, "Move at %1m wins semeai for %1m\n", pos, aa); break; case SEMEAI_THREAT: aa = move_reasons[r].what; gfprintf(out, "Move at %1m threatens to win semeai for %1m\n", pos, aa); break; case EITHER_MOVE: reason1 = either_data[move_reasons[r].what].reason1; reason2 = either_data[move_reasons[r].what].reason2; worm1 = either_data[move_reasons[r].what].what1; worm2 = either_data[move_reasons[r].what].what2; gfprintf(out, "Move at %1m either %s %1m or %s %1m\n", pos, reason1 == ATTACK_STRING ? "attacks" : "defends", worm1, reason2 == ATTACK_STRING ? "attacks" : "defends", worm2); break; case ALL_MOVE: reason1 = all_data[move_reasons[r].what].reason1; reason2 = all_data[move_reasons[r].what].reason2; worm1 = all_data[move_reasons[r].what].what1; worm2 = all_data[move_reasons[r].what].what2; gfprintf(out, "Move at %1m both %s %1m and %s %1m\n", pos, reason1 == ATTACK_STRING ? "attacks" : "defends", worm1, reason2 == ATTACK_STRING ? "attacks" : "defends", worm2); break; case OWL_ATTACK_MOVE: aa = move_reasons[r].what; gfprintf(out, "Move at %1m owl-attacks %1m\n", pos, aa); break; case OWL_ATTACK_MOVE_GOOD_KO: aa = move_reasons[r].what; gfprintf(out, "Move at %1m owl-attacks %1m with good ko\n", pos, aa); break; case OWL_ATTACK_MOVE_BAD_KO: aa = move_reasons[r].what; gfprintf(out, "Move at %1m owl-attacks %1m with bad ko\n", pos, aa); break; case OWL_ATTACK_MOVE_GAIN: aa = either_data[move_reasons[r].what].what1; bb = either_data[move_reasons[r].what].what2; gfprintf(out, "Move at %1m owl-attacks %1m (captures %1m)\n", pos, aa, bb); break; case OWL_DEFEND_MOVE: aa = move_reasons[r].what; gfprintf(out, "Move at %1m owl-defends %1m\n", pos, aa); break; case OWL_DEFEND_MOVE_GOOD_KO: aa = move_reasons[r].what; gfprintf(out, "Move at %1m owl-defends %1m with good ko\n", pos, aa); break; case OWL_DEFEND_MOVE_BAD_KO: aa = move_reasons[r].what; gfprintf(out, "Move at %1m owl-defends %1m with bad ko\n", pos, aa); break; case OWL_DEFEND_MOVE_LOSS: aa = either_data[move_reasons[r].what].what1; bb = either_data[move_reasons[r].what].what2; gfprintf(out, "Move at %1m owl-defends %1m (loses %1m)\n", pos, aa, bb); break; case OWL_ATTACK_THREAT: aa = move_reasons[r].what; gfprintf(out, "Move at %1m owl-threatens to attack %1m\n", pos, aa); break; case OWL_DEFEND_THREAT: aa = move_reasons[r].what; gfprintf(out, "Move at %1m owl-threatens to defend %1m\n", pos, aa); break; case OWL_PREVENT_THREAT: aa = move_reasons[r].what; gfprintf(out, "Move at %1m owl-prevents a threat to attack or defend %1m\n", pos, aa); break; case EXPAND_TERRITORY_MOVE: gfprintf(out, "Move at %1m expands territory\n", pos); break; case EXPAND_MOYO_MOVE: gfprintf(out, "Move at %1m expands moyo\n", pos); break; case INVASION_MOVE: gfprintf(out, "Move at %1m is an invasion\n", pos); break; case STRATEGIC_ATTACK_MOVE: case STRATEGIC_DEFEND_MOVE: aa = move_reasons[r].what; if (move_reasons[r].type == STRATEGIC_ATTACK_MOVE) gfprintf(out, "Move at %1m strategically attacks %1m\n", pos, aa); else gfprintf(out, "Move at %1m strategically defends %1m\n", pos, aa); break; case MY_ATARI_ATARI_MOVE: gfprintf(out, "Move at %1m captures something\n", pos); case YOUR_ATARI_ATARI_MOVE: gfprintf(out, "Move at %1m defends against combination attack\n", pos); } } if (k > 0 && move[pos].move_safety == 0) gfprintf(out, "Move at %1m strategically or tactically unsafe\n", pos); } return num_move_reasons; } /* This array lists rules according to which we set the status * flags of a move reasons. * The format is: * { List of reasons to which the rule applies, condition of the rule, * flags to be set, trace message } * The condition must be of type discard_condition_fn_ptr, that is a pointer * to a function with parameters (pos, what). * * FIXME: Add handling of ALL and EITHER moves for inessential worms. */ static struct discard_rule discard_rules[] = { { { ATTACK_MOVE, ATTACK_MOVE_GOOD_KO, ATTACK_MOVE_BAD_KO, ATTACK_THREAT, DEFEND_MOVE, DEFEND_MOVE_GOOD_KO, DEFEND_MOVE_BAD_KO, DEFEND_THREAT, -1 }, owl_move_vs_worm_known, TERRITORY_REDUNDANT, " %1m: 0.0 - (threat of) attack/defense of %1m (owl attack/defense as well)\n" }, { { SEMEAI_MOVE, SEMEAI_THREAT, -1 }, owl_move_reason_known, REDUNDANT, " %1m: 0.0 - (threat to) win semeai involving %1m (owl move as well)\n"}, { { SEMEAI_MOVE, SEMEAI_THREAT, -1 }, tactical_move_vs_whole_dragon_known, REDUNDANT, " %1m: 0.0 - (threat to) win semeai involving %1m (tactical move as well)\n"}, { { EITHER_MOVE, -1 }, either_worm_attackable, REDUNDANT, " %1m: 0.0 - 'attack either' is redundant at %1m (direct att./def. as well)\n"}, { { ALL_MOVE, -1 }, one_of_both_attackable, REDUNDANT, " %1m: 0.0 - 'defend both' is redundant at %1m (direct att./def. as well)\n"}, { { ATTACK_THREAT, DEFEND_THREAT, -1 }, concerns_inessential_worm, TERRITORY_REDUNDANT, " %1m: 0.0 - attack/defense threat of %1m (inessential)\n"}, { { OWL_ATTACK_THREAT, UNCERTAIN_OWL_DEFENSE, -1 }, concerns_inessential_dragon, REDUNDANT, " %1m: 0.0 - (uncertain) owl attack/defense of %1m (inessential)\n"}, { { ATTACK_MOVE, ATTACK_MOVE_GOOD_KO, ATTACK_MOVE_BAD_KO, DEFEND_MOVE, DEFEND_MOVE_GOOD_KO, DEFEND_MOVE_BAD_KO, -1}, move_is_marked_unsafe, REDUNDANT, " %1m: 0.0 - tactical move vs %1m (unsafe move)\n"}, { { OWL_ATTACK_MOVE, OWL_ATTACK_MOVE_GOOD_KO, OWL_ATTACK_MOVE_BAD_KO, OWL_DEFEND_MOVE, OWL_DEFEND_MOVE_GOOD_KO, OWL_DEFEND_MOVE_BAD_KO, -1}, concerns_noncritical_dragon, REDUNDANT, " %1m: 0.0 - owl move vs %1m (non-critical)\n"}, { { -1 }, NULL, 0, ""} /* Keep this entry at end of the list. */ }; /* This function checks the list of move reasons for redundant move * reasons and marks them accordingly in their status field. */ void discard_redundant_move_reasons(int pos) { int k1, k2; int l; for (k1 = 0; !(discard_rules[k1].reason_type[0] == -1); k1++) { for (k2 = 0; !(discard_rules[k1].reason_type[k2] == -1); k2++) { for (l = 0; l < MAX_REASONS; l++) { int r = move[pos].reason[l]; if (r < 0) break; if ((move_reasons[r].type == discard_rules[k1].reason_type[k2]) && (discard_rules[k1].condition(pos, move_reasons[r].what))) { DEBUG(DEBUG_MOVE_REASONS, discard_rules[k1].trace_message, pos, get_pos(move_reasons[r].type, move_reasons[r].what)); move_reasons[r].status |= discard_rules[k1].flags; } } } } } /* Look through the move reasons to see whether (pos) is an antisuji move. */ int is_antisuji_move(int pos) { int k; for (k = 0; k < MAX_REASONS; k++) { int r = move[pos].reason[k]; if (r < 0) break; if (move_reasons[r].type == ANTISUJI_MOVE) return 1; /* This move must not be played. End of story. */ } return 0; } /* Increase the randomness scaling factor. * This causes the move value to be more random. */ void scale_randomness(int pos, float scaling) { if (scaling > move[pos].randomness_scaling) move[pos].randomness_scaling = scaling; } /* Register the given `move' as a good attack threat against `target'. By * "good" we mean a threat which is effectively a sente for the player. * E.g. in this position the threat is good, because it results in four * sente moves locally (trevord:950): * * ..OX.. * .O.*.. * .OXX.. * .OOX.. * ------ * * We use this list of good threats for performance reasons so that * estimate_territorial_value() in valuemoves.c doesn't have to read * through all the moves. Such threats are found with patterns. */ void register_good_attack_threat(int move, int target) { int k; ASSERT_ON_BOARD1(move); ASSERT_ON_BOARD1(target); ASSERT1(IS_STONE(worm[target].color), move); target = worm[target].origin; for (k = 0; k < MAX_ATTACK_THREATS; k++) { if (known_good_attack_threats[move][k] == target) break; if (known_good_attack_threats[move][k] == NO_MOVE) { known_good_attack_threats[move][k] = target; break; } } } /* Determine if an attack threat is registered as good (see above). */ int is_known_good_attack_threat(int move, int target) { int k; ASSERT_ON_BOARD1(move); ASSERT_ON_BOARD1(target); ASSERT1(IS_STONE(worm[target].color), move); target = worm[target].origin; for (k = 0; k < MAX_ATTACK_THREATS; k++) { if (known_good_attack_threats[move][k] == target) return 1; if (known_good_attack_threats[move][k] == NO_MOVE) break; } return 0; } /* Like documented in endgame:980, there are also moves which aren't * safe by themselves, but attempting to capture these stones would * result in a loss for the opponent (typically, by damezumari). * Simple examples include snapbacks, but more complicated ones do * exist. Following functions are helpers for the valuation processing * which deal with such special cases. */ void register_known_safe_move(int move) { ASSERT_ON_BOARD1(move); known_safe_moves[move] = 1; } int is_known_safe_move(int move) { ASSERT_ON_BOARD1(move); return known_safe_moves[move]; } /* * Local Variables: * tab-width: 8 * c-basic-offset: 2 * End: */