/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ * 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 "liberty.h" static int do_aftermath_genmove(int color, int under_control[BOARDMAX], int do_capture_dead_stones); static int all_own_neighbors_inessential(int pos, int color) { int k; for (k = 0; k < 4; k++) if (board[pos + delta[k]] == color && DRAGON2(pos + delta[k]).safety != INESSENTIAL && (DRAGON2(pos + delta[k]).safety != ALIVE || DRAGON2(pos + delta[k]).owl_status != DEAD)) return 0; return 1; } /* Does a move by color at pos make one of the neighboring points into * a solid one-point eye? */ static int make_solid_eye(int pos, int color) { int k; int r; for (k = 0; k < 4; k++) { int eyepos = pos + delta[k]; if (board[eyepos] == EMPTY || (board[eyepos] == OTHER_COLOR(color) && countlib(eyepos) == 1)) { /* For a solid one-point eye all four neighbors must be own * stones. But one is about to be played so we need three in the * center, two on the edge and one in the corner. * * We also need a sufficient number of own diagonals; three in * the center, two on the edge, and one in the corner. * * Notice that the same numbers are needed for both neighbors * and diagonals and if we start counting at 2 in the corner and * at 1 on the edge, we need to reach 3 everywhere on the board. */ int own_neighbors = is_edge_vertex(pos) + is_corner_vertex(pos); int own_diagonals = own_neighbors; for (r = 0; r < 8; r++) { if (board[eyepos + delta[r]] == color) { if (r < 4) own_neighbors++; else own_diagonals++; } } if (own_neighbors == 3 && own_diagonals >= 3) return 1; } } return 0; } /* External interface to do_aftermath_genmove(). * * If the suggested move turns out not to be allowed we just return * pass. This is not ideal but also not a big deal. If * do_aftermath_genmove() is ever redesigned that would be a good time * to integrate allowed_moves. */ int aftermath_genmove(int color, int do_capture_dead_stones, int allowed_moves[BOARDMAX]) { int move = do_aftermath_genmove(color, NULL, do_capture_dead_stones); if (move != PASS_MOVE && allowed_moves && !allowed_moves[move]) move = PASS_MOVE; return move; } /* Generate a move to definitely settle the position after the game * has been finished. The purpose of this is to robustly determine * life and death status and to distinguish between life in seki and * life with territory. * * The strategy is basically to turn all own living stones into * invincible ones and remove from the board all dead opponent stones. * Stones which cannot be removed, nor turned invincible, are alive in * seki. * * If do_capture_dead_stones is 0, opponent stones are not necessarily * removed from the board. This happens if they become unconditionally * dead anyway. * * Moves are generated in the following order of priority: * -1. Play a move which is listed as a replacement for an * unconditionally meaningless move. This is guaranteed to extend * the unconditionally settled part of the board. Only do this if * the meaningless move is not connected through open space to an * invincible string. * 0. Play edge liberties in certain positions. This is not really * necessary, but often it can simplify the tactical and strategical * reading substantially, making subsequent moves faster to generate. * 1a. Capture an opponent string in atari and adjacent to own invincible * string. Moves leading to ko or snapback are excluded. * 1b. If do_capture_dead_stones, play a non-self-atari move adjacent * to an unconditionally dead opponent string. * 1c. If do_capture_dead_stones, play a liberty of an opponent string * where the liberty is adjacent to own invincible string. * 2. Extend an invincible string to a liberty of an opponent string. * 3. Connect a non-invincible string to an invincible string. * 4. Extend an invincible string towards an opponent string or an own * non-invincible string. * 5. Split a big eyespace of an alive own dragon without invincible * strings into smaller pieces. Do not play self-atari here. * 6. Play a liberty of a dead opponent dragon. * * Steps 2--4 are interleaved to try to optimize the efficiency of the * moves. In step 5 too, efforts are made to play efficient moves. By * efficient we here mean moves which are effectively settling the * position and simplify the tactical and strategical reading for * subsequent moves. * * Steps 1--4 are guaranteed to be completely safe. Step 0 and 5 * should also be risk-free. Step 6 on the other hand definitely * isn't. Consider for example this position: * * .XXXXX. * XXOOOXX * XOO.OOX * XOXXXOX * XO.XXOX * ------- * * In order to remove the O stones, it is necessary to play on one of * the inner liberties, but one of them lets O live. Thus we have to * check carefully for blunders at this step. * * Update: Step 0 is only safe against blunders if care is taken not * to get into a shortage of liberties. * Step 5 also has some risks. Consider this position: * * |XXXXX. * |OOOOXX * |..O.OX * |OX*OOX * +------ * * Playing at * allows X to make seki. * * IMPORTANT RESTRICTION: * Before calling this function it is mandatory to call genmove() or * genmove_conservative(). For this function to be meaningful, the * genmove() call should return pass. */ static int do_aftermath_genmove(int color, int under_control[BOARDMAX], int do_capture_dead_stones) { int k; int other = OTHER_COLOR(color); int distance[BOARDMAX]; int score[BOARDMAX]; float owl_hotspot[BOARDMAX]; float reading_hotspot[BOARDMAX]; int dragons[BOARDMAX]; int something_found; int closest_opponent = NO_MOVE; int closest_own = NO_MOVE; int d; int move = NO_MOVE; int pos = NO_MOVE; int best_score; int best_scoring_move; owl_hotspots(owl_hotspot); reading_hotspots(reading_hotspot); /* As a preparation we compute a distance map to the invincible strings. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (!ON_BOARD(pos)) continue; else if (board[pos] == color && worm[pos].invincible) distance[pos] = 0; else if (!do_capture_dead_stones && ((board[pos] == other && worm[pos].unconditional_status == DEAD) || (board[pos] == color && worm[pos].unconditional_status == ALIVE))) distance[pos] = 0; else distance[pos] = -1; } d = 0; do { something_found = 0; for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (ON_BOARD(pos) && distance[pos] == -1) { for (k = 0; k < 4; k++) { int pos2 = pos + delta[k]; if (!ON_BOARD(pos2)) continue; if ((d == 0 || board[pos2] == EMPTY) && distance[pos2] == d) { if (d > 0 && board[pos] == other) { distance[pos] = d + 1; if (closest_opponent == NO_MOVE) closest_opponent = pos; } else if (d > 0 && board[pos] == color) { distance[pos] = d + 1; if (closest_own == NO_MOVE) closest_own = pos; } else if (board[pos] == EMPTY) { distance[pos] = d + 1; something_found = 1; } break; } } } } d++; } while (something_found); if (under_control) { for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (!ON_BOARD(pos)) continue; else if (distance[pos] == -1) under_control[pos] = 0; else under_control[pos] = 1; } } if (debug & DEBUG_AFTERMATH) { int m, n; for (m = 0; m < board_size; m++) { for (n = 0; n < board_size; n++) { pos = POS(m, n); if (distance[pos] > 0) fprintf(stderr, "%2d", distance[pos]); else if (distance[pos] == 0) { if (board[pos] == WHITE) gprintf(" o"); else if (board[pos] == BLACK) gprintf(" x"); else gprintf(" ?"); } else { if (board[pos] == WHITE) gprintf(" O"); else if (board[pos] == BLACK) gprintf(" X"); else gprintf(" ."); } } gprintf("\n"); } gprintf("Closest opponent %1m", closest_opponent); if (closest_opponent != NO_MOVE) gprintf(", distance %d\n", distance[closest_opponent]); else gprintf("\n"); gprintf("Closest own %1m", closest_own); if (closest_own != NO_MOVE) gprintf(", distance %d\n", distance[closest_own]); else gprintf("\n"); } /* Case -1. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { int replacement_move; if (board[pos] == EMPTY && distance[pos] == -1 && unconditionally_meaningless_move(pos, color, &replacement_move) && replacement_move != NO_MOVE) { DEBUG(DEBUG_AFTERMATH, "Replacement move for %1m at %1m\n", pos, replacement_move); return replacement_move; } } /* Case 0. This is a special measure to avoid a certain kind of * tactical reading inefficiency. * * Here we play on edge liberties in the configuration * * XO. * .*. * --- * * to stop X from "leaking" out along the edge. Sometimes this can * save huge amounts of tactical reading for later moves. */ best_scoring_move = NO_MOVE; best_score = 5; for (pos = BOARDMIN; pos < BOARDMAX; pos++) { int libs; if (board[pos] != EMPTY || distance[pos] == 0) continue; libs = approxlib(pos, color, 3, NULL); if (libs < 3) continue; if (is_self_atari(pos, other)) continue; for (k = 0; k < 4; k++) { int dir = delta[k]; int right = delta[(k+1)%4]; if (!ON_BOARD(pos - dir) && board[pos + dir] == color && board[pos + dir + right] == other && board[pos + dir - right] == other && (libs > countlib(pos + dir) || (libs > 4 && libs == countlib(pos + dir))) && (DRAGON2(pos + dir).safety == INVINCIBLE || DRAGON2(pos + dir).safety == STRONGLY_ALIVE)) { int this_score = 20 * (owl_hotspot[pos] + reading_hotspot[pos]); if (this_score > best_score) { best_score = this_score; best_scoring_move = pos; } } } } if (best_scoring_move != NO_MOVE && safe_move(best_scoring_move, color) == WIN) { DEBUG(DEBUG_AFTERMATH, "Closing edge at %1m\n", best_scoring_move); return best_scoring_move; } /* Case 1a. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { int lib; if (board[pos] == other && worm[pos].unconditional_status != DEAD && countlib(pos) == 1 && ((ON_BOARD(SOUTH(pos)) && distance[SOUTH(pos)] == 0) || (ON_BOARD(WEST(pos)) && distance[WEST(pos)] == 0) || (ON_BOARD(NORTH(pos)) && distance[NORTH(pos)] == 0) || (ON_BOARD(EAST(pos)) && distance[EAST(pos)] == 0))) { findlib(pos, 1, &lib); /* Make sure we don't play into a ko or a (proper) snapback. */ if (countstones(pos) > 1 || !is_self_atari(lib, color)) { return lib; } } } /* Case 1b. Play liberties of unconditionally dead stones, but never * self-atari. For efficiency against stubborn opponents, we want to * split up the empty space as much as possible. Therefore we look * among this class of moves for one with a maximum number of * adjacent empty spaces and opponent stones. */ if (do_capture_dead_stones) { best_score = 0; best_scoring_move = NO_MOVE; for (pos = BOARDMIN; pos < BOARDMAX; pos++) { /* Look at empty points which are connectable to some invincible * string through empty space. */ if (board[pos] == EMPTY && distance[pos] >= 0) { int valid_move = 0; int this_score = 0; for (k = 0; k < 4; k++) { int pos2 = pos + delta[k]; if (board[pos2] == EMPTY) this_score += 2; else if (board[pos2] == other && worm[pos2].unconditional_status == DEAD) { this_score++; valid_move = 1; } } if (valid_move && this_score > best_score && !is_self_atari(pos, color)) { best_score = this_score; best_scoring_move = pos; } } } if (best_score > 0) return best_scoring_move; } /* Case 1c. */ if (do_capture_dead_stones) { for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] == EMPTY && distance[pos] == 1 && has_neighbor(pos, other)) { return pos; } } } /* Cases 2--4. */ if (closest_opponent != NO_MOVE || closest_own != NO_MOVE) { if (closest_own == NO_MOVE || (capture_all_dead && closest_opponent != NO_MOVE && distance[closest_opponent] < distance[closest_own])) move = closest_opponent; else move = closest_own; /* if we're about to play at distance 1, try to optimize the move. */ if (distance[move] == 2) { signed char mx[BOARDMAX]; signed char mark = 0; memset(mx, 0, sizeof(mx)); best_score = 0; best_scoring_move = move; for (pos = BOARDMIN; pos < BOARDMAX; pos++) { int score = 0; int move_ok = 0; if (!ON_BOARD(pos) || distance[pos] != 1) continue; mark++; for (k = 0; k < 4; k++) { int pos2 = pos + delta[k]; if (!ON_BOARD(pos2)) continue; if (distance[pos2] < 1) score--; else if (board[pos2] == EMPTY) score++; else if (mx[pos2] == mark) score--; else { if (board[pos2] == color) { move_ok = 1; score += 7; if (countstones(pos2) > 2) score++; if (countstones(pos2) > 4) score++; if (countlib(pos2) < 4) score++; if (countlib(pos2) < 3) score++; } else { int deltalib = (approxlib(pos, other, MAXLIBS, NULL) - countlib(pos2)); move_ok = 1; score++; if (deltalib >= 0) score++; if (deltalib > 0) score++; } mark_string(pos2, mx, mark); } } if (is_suicide(pos, other)) score -= 3; if (0) gprintf("Score %1m = %d\n", pos, score); if (move_ok && score > best_score) { best_score = score; best_scoring_move = pos; } } move = best_scoring_move; } while (distance[move] > 1) { for (k = 0; k < 4; k++) { int pos2 = move + delta[k]; if (ON_BOARD(pos2) && board[pos2] == EMPTY && distance[pos2] == distance[move] - 1) { move = pos2; break; } } } return move; } /* Case 5. * If we reach here, either all strings of a dragon are invincible * or no string is. Next we try to make alive dragons invincible by * splitting big eyes into smaller ones. Our strategy is to search * for an empty vertex with as many eye points as possible adjacent * and with at least one alive but not invincible stone adjacent or * diagonal. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { int eyespace_neighbors = 0; int own_neighbors = 0; int own_diagonals = 0; int opponent_dragons = 0; int own_worms = 0; int safety = UNKNOWN; int bonus = 0; int mx[BOARDMAX]; score[pos] = 0; if (board[pos] != EMPTY || distance[pos] != -1) continue; /* Do not play self-atari here. */ if (is_self_atari(pos, color)) continue; memset(mx, 0, sizeof(mx)); for (k = 0; k < 8; k++) { int pos2 = pos + delta[k]; if (!ON_BOARD(pos2)) continue; if (board[pos2] == EMPTY) { if (k < 4) eyespace_neighbors++; continue; } if (board[pos2] == other) { int origin = dragon[pos2].origin; if (k < 4) { if (dragon[pos2].status == ALIVE) { safety = DEAD; break; } else if (!mx[origin]) { eyespace_neighbors++; opponent_dragons++; } } if (!mx[origin] && dragon[pos2].status == DEAD) { bonus++; if (k < 4 && countlib(pos2) <= 2 && countstones(pos2) >= 3) bonus++; if (k < 4 && countlib(pos2) == 1) bonus += 3; } mx[origin] = 1; } else if (board[pos2] == color) { dragons[pos] = pos2; if (safety == UNKNOWN && dragon[pos2].status == ALIVE) safety = ALIVE; if (DRAGON2(pos2).safety == INVINCIBLE) safety = INVINCIBLE; if (k < 4) { int apos = worm[pos2].origin; if (!mx[apos]) { own_worms++; if (countstones(apos) == 1) bonus += 2; if (countlib(apos) < 6 && approxlib(pos, color, 5, NULL) < countlib(apos)) bonus -= 5; mx[apos] = 1; } if (countlib(apos) <= 2) { int r; int important = 0; int safe_atari = 0; for (r = 0; r < 4; r++) { d = delta[r]; if (!ON_BOARD(apos+d)) continue; if (board[apos+d] == other && dragon[apos+d].status == DEAD) important = 1; else if (board[apos+d] == EMPTY && !is_self_atari(apos+d, other)) safe_atari = 1; } if (approxlib(pos, color, 3, NULL) > 2) { bonus++; if (important) { bonus += 2; if (safe_atari) bonus += 2; } } } own_neighbors++; } else own_diagonals++; } } if (safety == DEAD || safety == UNKNOWN || eyespace_neighbors == 0 || (own_neighbors + own_diagonals) == 0) continue; if (bonus < 0) bonus = 0; /* Big bonus for making a small solid eye while splitting the * eyespace. Don't bother optimizing for making two solid eyes, * unconditional replacement moves (case -1) will take care of * that. * * Additional bonus if adjacent to an opponent dragon and we are * asked to remove all dead opponent stones. */ if (eyespace_neighbors >= 2) if (make_solid_eye(pos, color)) { bonus += 20; if (do_capture_dead_stones && opponent_dragons > 0) bonus += 10; } score[pos] = 4 * eyespace_neighbors + bonus; if (safety == INVINCIBLE) { score[pos] += own_neighbors; if (own_neighbors < 2) score[pos] += own_diagonals; if (own_worms > 1 && eyespace_neighbors >= 1) score[pos] += 10 + 5 * (own_worms - 2); } else if (eyespace_neighbors > 2) score[pos] += own_diagonals; /* Splitting bonus. */ if (opponent_dragons > 1) score[pos] += 10 * (opponent_dragons - 1); /* Hotspot bonus. */ { int owl_hotspot_bonus = (int) (20.0 * owl_hotspot[pos]); int reading_hotspot_bonus = (int) (20.0 * reading_hotspot[pos]); int hotspot_bonus = owl_hotspot_bonus + reading_hotspot_bonus; /* Don't allow the hotspot bonus to turn a positive score into * a non-positive one. */ if (score[pos] > 0 && score[pos] + hotspot_bonus <= 0) hotspot_bonus = 1 - score[pos]; score[pos] += hotspot_bonus; if (1 && (debug & DEBUG_AFTERMATH)) gprintf("Score %1M = %d (hotspot bonus %d + %d)\n", pos, score[pos], owl_hotspot_bonus, reading_hotspot_bonus); } /* Avoid taking ko. */ if (is_ko(pos, color, NULL)) score[pos] = (score[pos] + 1) / 2; } while (1) { int bb; best_score = 0; move = NO_MOVE; for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (ON_BOARD(pos) && score[pos] > best_score) { best_score = score[pos]; move = pos; } } if (move == NO_MOVE) break; bb = dragons[move]; if (is_illegal_ko_capture(move, color) || !safe_move(move, color) || (DRAGON2(bb).safety != INVINCIBLE && DRAGON2(bb).safety != STRONGLY_ALIVE && owl_does_defend(move, bb, NULL) != WIN) || (!confirm_safety(move, color, NULL, NULL))) { score[move] = 0; } else { /* If we're getting short of liberties, we must be more careful. * Check that no adjacent string or dragon gets more alive by * the move. */ int libs = approxlib(move, color, 5, NULL); int move_ok = 1; if (libs < 5) { for (k = 0; k < 4; k++) { if (board[move + delta[k]] == color && countlib(move + delta[k]) > libs) break; } if (k < 4) { if (trymove(move, color, "aftermath-B", move + delta[k])) { int adjs[MAXCHAIN]; int neighbors; int r; neighbors = chainlinks(move, adjs); for (r = 0; r < neighbors; r++) { if (worm[adjs[r]].attack_codes[0] != 0 && (find_defense(adjs[r], NULL) > worm[adjs[r]].defense_codes[0])) { DEBUG(DEBUG_AFTERMATH, "Blunder: %1m becomes tactically safer after %1m\n", adjs[r], move); move_ok = 0; } } popgo(); for (r = 0; r < neighbors && move_ok; r++) { if (dragon[adjs[r]].status == DEAD && !owl_does_attack(move, adjs[r], NULL)) { DEBUG(DEBUG_AFTERMATH, "Blunder: %1m becomes more alive after %1m\n", adjs[r], move); move_ok = 0; } } } } } if (!move_ok) score[move] = 0; else { DEBUG(DEBUG_AFTERMATH, "Splitting eyespace at %1m\n", move); return move; } } } /* Case 6. * Finally we try to play on liberties of remaining DEAD opponent * dragons, carefully checking against mistakes. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { int target; int cc = NO_MOVE; int self_atari_ok = 0; if (board[pos] != EMPTY || distance[pos] != -1) continue; target = NO_MOVE; for (k = 0; k < 8; k++) { int pos2 = pos + delta[k]; if (!ON_BOARD(pos2)) continue; if (board[pos2] == other && dragon[pos2].status != ALIVE && dragon[pos2].status != UNKNOWN && (do_capture_dead_stones || worm[pos2].unconditional_status != DEAD) && DRAGON2(pos2).safety != INESSENTIAL) { if (k < 4 || all_own_neighbors_inessential(pos, color)) { target = pos2; break; } } } if (target == NO_MOVE) continue; /* At this point, (pos) is a move that potentially may capture * a dead opponent string at (target). */ if (!trymove(pos, color, "aftermath-A", target)) continue; /* It is frequently necessary to sacrifice own stones in order * to force the opponent's stones to be removed from the board, * e.g. by adding stones to fill up a nakade shape. However, we * should only play into a self atari if the sacrificed stones * are classified as INESSENTIAL. Thus it would be ok for O to * try a self atari in this position: * * |OOOO * |XXXO * |..XO * |OOXO * +---- * * but not in this one: * * |XXX.. * |OOXX. * |.OOXX * |XXOOX * |.O.OX * +----- */ self_atari_ok = 1; for (k = 0; k < 4; k++) { if (board[pos + delta[k]] == color && DRAGON2(pos + delta[k]).safety != INESSENTIAL) { self_atari_ok = 0; cc = pos + delta[k]; break; } } /* Copy the potential move to (move). */ move = pos; /* If the move is a self atari, but that isn't okay, try to * recursively find a backfilling move which later makes the * potential move possible. */ if (!self_atari_ok) { while (countlib(pos) == 1) { int lib; findlib(pos, 1, &lib); move = lib; if (!trymove(move, color, "aftermath-B", target)) break; } if (countlib(pos) == 1) move = NO_MOVE; } while (stackp > 0) popgo(); if (move == NO_MOVE) continue; /* Make sure that the potential move really isn't a self * atari. In the case of a move found after backfilling this * could happen (because the backfilling moves happened to * capture some stones). The position of the move may even be * occupied. */ if (!self_atari_ok && (board[move] != EMPTY || is_self_atari(move, color))) continue; /* Consult the owl code to determine whether the considered move * really is effective. Blunders should be detected here. */ if (owl_does_attack(move, target, NULL) == WIN) { /* If we have an adjacent own dragon, which is not inessential, * verify that it remains safe. */ if (cc != NO_MOVE && !owl_does_defend(move, cc, NULL)) { int resulta, resultb; owl_analyze_semeai_after_move(move, color, target, cc, &resulta, &resultb, NULL, 1, NULL, 1); if (resulta != 0) continue; } /* If we don't allow self atari, also call confirm safety to * avoid setting up combination attacks. */ if (!self_atari_ok && !confirm_safety(move, color, NULL, NULL)) continue; DEBUG(DEBUG_AFTERMATH, "Filling opponent liberty at %1m\n", move); return move; } } /* Case 7. * In very rare cases it turns out we need yet another pass. An * example is this position: * * |..... * |OOOO. * |XXXO. * |.OXO. * |O.XO. * +----- * * Here the X stones are found tactically dead and therefore the * corner O stones have been amalgamated with the surrounding * stones. Since the previous case only allows sacrificing * INESSENTIAL stones, it fails to take X off the board. * * The solution is to look for tactically attackable opponent stones * that still remain on the board but should be removed. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] == other && (worm[pos].unconditional_status == UNKNOWN || do_capture_dead_stones) && (DRAGON2(pos).safety == DEAD || DRAGON2(pos).safety == TACTICALLY_DEAD) && worm[pos].attack_codes[0] != 0 && !is_illegal_ko_capture(worm[pos].attack_points[0], color)) { DEBUG(DEBUG_AFTERMATH, "Tactically attack %1m at %1m\n", pos, worm[pos].attack_points[0]); return worm[pos].attack_points[0]; } } /* No move found. */ return PASS_MOVE; } /* This is a substitute for genmove_conservative() which only does * what is required when doing the aftermath. Notice though that this * generates an "ordinary" move, in contrast to aftermath_genmove(). * Usually this should turn up a pass, but when it doesn't it's * important not to miss the move. */ static int reduced_genmove(int color) { float value; int save_verbose; float our_score; int move; /* no move is found yet. */ move = PASS_MOVE; value = 0.0; /* Prepare pattern matcher and reading code. */ reset_engine(); /* Find out information about the worms and dragons. */ examine_position(EXAMINE_ALL, 1); /* The score will be used to determine when we are safely * ahead. So we want the most conservative score. */ if (color == WHITE) our_score = black_score; else our_score = -white_score; gg_assert(stackp == 0); /* * Ok, information gathering is complete. Now start to find some moves! */ /* Pick up moves that we know of already. */ save_verbose = verbose; if (verbose > 0) verbose--; collect_move_reasons(color); verbose = save_verbose; /* Look for combination attacks and defenses against them. */ combinations(color); gg_assert(stackp == 0); /* Review the move reasons and estimate move values. */ if (review_move_reasons(&move, &value, color, 0.0, our_score, NULL, 0)) TRACE("Move generation likes %1m with value %f\n", move, value); gg_assert(stackp == 0); /* If no move is found then pass. */ if (move == PASS_MOVE) TRACE("I pass.\n"); else TRACE("reduced_genmove() recommends %1m with value %f\n", move, value); return move; } /* Preliminary function for playing through the aftermath. */ static void do_play_aftermath(int color, struct aftermath_data *a, SGFTree *aftermath_sgftree) { int move; int pass = 0; int moves = 0; int color_to_play = color; DEBUG(DEBUG_AFTERMATH, "The aftermath starts.\n"); /* Disable computing worm and owl threats. */ disable_threat_computation = 1; /* Disable matching of endgame patterns. */ disable_endgame_patterns = 1; while (pass < 2 && moves < board_size * board_size) { int reading_nodes = get_reading_node_counter(); int owl_nodes = get_owl_node_counter(); move = reduced_genmove(color_to_play); if (move == PASS_MOVE) { int save_verbose = verbose; if (verbose > 0) verbose--; move = do_aftermath_genmove(color_to_play, (color_to_play == WHITE ? a->white_control : a->black_control), 0); verbose = save_verbose; } play_move(move, color_to_play); if (aftermath_sgftree) sgftreeAddPlay(aftermath_sgftree, color_to_play, I(move), J(move)); moves++; DEBUG(DEBUG_AFTERMATH, "%d %C move %1m (nodes %d, %d total %d, %d)\n", movenum, color_to_play, move, get_owl_node_counter() - owl_nodes, get_reading_node_counter() - reading_nodes, get_owl_node_counter(), get_reading_node_counter()); if (move != PASS_MOVE) pass = 0; else pass++; color_to_play = OTHER_COLOR(color_to_play); } /* Reenable worm and dragon threats and endgame patterns. */ disable_threat_computation = 0; disable_endgame_patterns = 0; } static struct aftermath_data aftermath; static void play_aftermath(int color, SGFTree *aftermath_sgftree) { int pos; struct board_state saved_board; struct aftermath_data *a = &aftermath; static int current_board[BOARDMAX]; static int current_color = EMPTY; int cached_board = 1; gg_assert(color == BLACK || color == WHITE); if (current_color != color) { current_color = color; cached_board = 0; } for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (ON_BOARD(pos) && board[pos] != current_board[pos]) { current_board[pos] = board[pos]; cached_board = 0; } } /* If this is exactly the same position as the one we analyzed the * last time, the content of the aftermath struct is up to date. */ if (cached_board) return; a->white_captured = white_captured; a->black_captured = black_captured; a->white_prisoners = 0; a->black_prisoners = 0; a->white_territory = 0; a->black_territory = 0; a->white_area = 0; a->black_area = 0; store_board(&saved_board); do_play_aftermath(color, a, aftermath_sgftree); restore_board(&saved_board); for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (!ON_BOARD(pos)) continue; if (a->black_control[pos]) { a->black_area++; if (board[pos] == WHITE) { a->black_territory++; a->white_prisoners++; a->final_status[pos] = DEAD; } else if (board[pos] == EMPTY) { a->black_territory++; a->final_status[pos] = BLACK_TERRITORY; } else a->final_status[pos] = ALIVE; } else if (a->white_control[pos]) { a->white_area++; if (board[pos] == BLACK) { a->white_territory++; a->black_prisoners++; a->final_status[pos] = DEAD; } else if (board[pos] == EMPTY) { a->white_territory++; a->final_status[pos] = WHITE_TERRITORY; } else a->final_status[pos] = ALIVE; } else { if (board[pos] == EMPTY) a->final_status[pos] = DAME; else { a->final_status[pos] = ALIVE_IN_SEKI; if (board[pos] == WHITE) a->white_area++; else a->black_area++; } } } if (debug & DEBUG_AFTERMATH) { gprintf("White captured: %d\n", a->white_captured); gprintf("Black captured: %d\n", a->black_captured); gprintf("White prisoners: %d\n", a->white_prisoners); gprintf("Black prisoners: %d\n", a->black_prisoners); gprintf("White territory: %d\n", a->white_territory); gprintf("Black territory: %d\n", a->black_territory); gprintf("White area: %d\n", a->white_area); gprintf("Black area: %d\n", a->black_area); } } float aftermath_compute_score(int color, SGFTree *tree) { struct aftermath_data *a = &aftermath; play_aftermath(color, tree); if (chinese_rules) return (a->white_area - a->black_area + komi + handicap); else return (a->white_territory + a->black_captured + a->black_prisoners - (a->black_territory + a->white_captured + a->white_prisoners) + komi); } /* Report the final status of a vertex on the board. * Possible results are ALIVE, DEAD, ALIVE_IN_SEKI, WHITE_TERRITORY, * BLACK_TERRITORY, and DAME. */ enum dragon_status aftermath_final_status(int color, int pos) { ASSERT_ON_BOARD1(pos); play_aftermath(color, NULL); return aftermath.final_status[pos]; } /* * Local Variables: * tab-width: 8 * c-basic-offset: 2 * End: */