/* Change the status and safety of a dragon. In addition, if the new * status is not DEAD, make all worms of the dragon essential, so that * results found by semeai code don't get ignored. */ static void update_status(int dr, enum dragon_status new_status, enum dragon_status new_safety) { int pos; if (dragon[dr].status != new_status && (dragon[dr].status != CRITICAL || new_status != DEAD)) { DEBUG(DEBUG_SEMEAI, "Changing status of %1m from %s to %s.\n", dr, status_to_string(dragon[dr].status), status_to_string(new_status)); for (pos = BOARDMIN; pos < BOARDMAX; pos++) if (IS_STONE(board[pos]) && is_same_dragon(dr, pos)) { dragon[pos].status = new_status; if (new_status != DEAD) worm[pos].inessential = 0; } } if (DRAGON2(dr).safety != new_safety && (DRAGON2(dr).safety != CRITICAL || new_safety != DEAD)) { DEBUG(DEBUG_SEMEAI, "Changing safety of %1m from %s to %s.\n", dr, status_to_string(DRAGON2(dr).safety), status_to_string(new_safety)); DRAGON2(dr).safety = new_safety; } }
int does_surround(int move, int dr) { if (DRAGON2(dr).surround_status) return 0; return compute_surroundings(dr, move, 0, NULL); }
int compute_surroundings(int pos, int apos, int showboard, int *surround_size) { int i, j; int m, n; int k; int dpos; int surrounded; int left_corner[MAX_BOARD]; int right_corner[MAX_BOARD]; int corner[BOARDMAX]; int left_corners = 0, right_corners = 0; int corners = 0; int top_row, bottom_row; int color = board[pos]; int other = OTHER_COLOR(color); int gi = 0; int gj = 0; int stones = 0; int found_some; signed char mf[BOARDMAX]; /* friendly dragon */ signed char mn[BOARDMAX]; /* neighbor dragons */ int sd[BOARDMAX]; /* distances to the goal */ if (DRAGON2(pos).hostile_neighbors == 0) return(0); memset(mf, 0, sizeof(mf)); memset(mn, 0, sizeof(mn)); memset(sd, 0, sizeof(sd)); mark_dragon(pos, mf, 1); /* mark hostile neighbors */ for (k = 0; k < DRAGON2(pos).neighbors; k++) { int nd = DRAGON(DRAGON2(pos).adjacent[k]).origin; if (board[nd] != color) { if (0) gprintf("neighbor: %1m\n", nd); mark_dragon(nd, mn, 1); } } /* descend markings from stones lying on the 2nd and third lines */ for (dpos = BOARDMIN; dpos < BOARDMAX; dpos++) if (ON_BOARD(dpos) && mn[dpos]) { for (k = 0; k < 4; k++) { int d = delta[k]; if (!ON_BOARD(dpos + d)) continue; if (!ON_BOARD(dpos + 2*d)) { if (board[dpos + d] == EMPTY) mn[dpos + d] = 1; } else if (!ON_BOARD(dpos + 3*d)) { if (board[dpos + d] == EMPTY && board[dpos + 2*d] == EMPTY) mn[dpos + 2*d] = 1; } } } /* compute minimum distances to the goal */ for (dpos = BOARDMIN; dpos < BOARDMAX; dpos++) if (ON_BOARD(dpos) && mn[dpos]) sd[dpos] = goal_dist(dpos, mf); /* revise markings */ do { found_some = 0; for (dpos = BOARDMIN; dpos < BOARDMAX; dpos++) if (ON_BOARD(dpos) && mn[dpos] && sd[dpos] > 8) { /* discard markings if we can find 2 stones * that verify : * - it is closer to the goal than we are * - it is closer to us than the goal is * - they are closer to each other than we are to the goal */ for (i = BOARDMIN; i < BOARDMAX; i++) if (ON_BOARD(i) && mn[i] && i != dpos && sd[i] < sd[dpos] && square_dist(i, dpos) < sd[dpos]) { for (j = i + 1; j < BOARDMAX; j++) if (ON_BOARD(j) && mn[j] && j != dpos && sd[j] < sd[dpos] && square_dist(j, dpos) < sd[dpos] && square_dist(i, j) < sd[dpos]) { mn[dpos] = 0; found_some = 1; break; } if (mn[dpos] == 0) break; } } } while (found_some); /* prepare corner array */ for (dpos = BOARDMIN; dpos < BOARDMAX; dpos++) if (ON_BOARD(dpos) && mn[dpos]) corner[corners++] = dpos; /* compute gravity center of the goal */ for (dpos = BOARDMIN; dpos < BOARDMAX; dpos++) if (ON_BOARD(dpos) && mf[dpos]) { gi += I(dpos); gj += J(dpos); stones++; } gi /= stones; gj /= stones; gg = POS(gi, gj); /* sort the corner array */ gg_sort(corner, corners, sizeof(int), compare_angles); /* if apos is not NO_MOVE, mark it. */ if (apos != NO_MOVE) { ASSERT_ON_BOARD1(apos); mn[apos] = 1; } if (showboard == 1) { show_surround_map(mf, mn); } /* find top row of surrounding polyhedron */ top_row = -1; for (m = 0; m < board_size; m++) { if (top_row != -1) break; for (n = 0; n < board_size; n++) if (mn[POS(m, n)]) { left_corner[0] = POS(m, n); top_row = m; break; } } /* find bottom row */ bottom_row = -1; for (m = board_size - 1; m >= 0; m--) { if (bottom_row != -1) break; for (n = 0; n < board_size; n++) if (mn[POS(m, n)]) { bottom_row = m; break; } } /* find the corners on the left side */ for (left_corners = 1; I(left_corner[left_corners-1]) < bottom_row; left_corners++) { int best_found = 0; float best_slope = 0.; int m = I(left_corner[left_corners-1]); int n = J(left_corner[left_corners-1]); for (i = m + 1; i <= bottom_row; i++) for (j = 0; j < board_size; j++) if (mn[POS(i, j)]) { float slope = ((float) (j - n))/((float) (i - m)); if (0) gprintf("(left) at %m, last %m, slope=%f\n", i, j, m, n, slope); if (!best_found || slope < best_slope) { best_found = POS(i, j); best_slope = slope; } } ASSERT_ON_BOARD1(best_found); left_corner[left_corners] = best_found; } for (n = board_size-1; n >= 0; n--) if (mn[POS(top_row, n)]) { right_corner[0] = POS(top_row, n); break; } /* find the corners on the right side */ for (right_corners = 1; I(right_corner[right_corners-1]) < bottom_row; right_corners++) { int best_found = 0; float best_slope = 0.; int m = I(right_corner[right_corners-1]); int n = J(right_corner[right_corners-1]); for (i = m + 1; i <= bottom_row; i++) { for (j = board_size - 1; j >= 0; j--) { if (mn[POS(i, j)]) { float slope = ((float) (j - n))/((float) (i - m)); if (0) gprintf("(right) at %m, last %m, slope=%f\n", i, j, m, n, slope); if (!best_found || slope > best_slope) { best_found = POS(i, j); best_slope = slope; } } } } ASSERT_ON_BOARD1(best_found); right_corner[right_corners] = best_found; } if (0) { for (k = 0; k < left_corners; k++) gprintf("left corner %d: %1m\n", k, left_corner[k]); for (k = 0; k < right_corners; k++) gprintf("right corner %d: %1m\n", k, right_corner[k]); } /* Now mark the interior of the convex hull */ for (n = J(left_corner[0]); n <= J(right_corner[0]); n++) mn[POS(top_row, n)] = 1; for (n = J(left_corner[left_corners-1]); n <= J(right_corner[right_corners-1]); n++) mn[POS(bottom_row, n)] = 1; for (m = top_row+1; m < bottom_row; m++) { int left_boundary = -1, right_boundary = -1; for (k = 1; k < left_corners; k++) { if (I(left_corner[k]) > m) { float ti = I(left_corner[k-1]); float tj = J(left_corner[k-1]); float bi = I(left_corner[k]); float bj = J(left_corner[k]); if (0) gprintf("(left) %d: %1m %1m\n", m, left_corner[k-1], left_corner[k]); /* left edge in this row is on segment (ti,tj) -> (bi, bj) */ /* FIXME: Rewrite this to avoid floating point arithmetic */ left_boundary = ceil(tj + (m - ti) * (bj - tj) / (bi - ti)); break; } } for (k = 1; k < right_corners; k++) { if (I(right_corner[k]) > m) { float ti = I(right_corner[k-1]); float tj = J(right_corner[k-1]); float bi = I(right_corner[k]); float bj = J(right_corner[k]); if (0) gprintf("(right) %d: %1m %1m\n", m, right_corner[k-1], right_corner[k]); /* FIXME: Rewrite this to avoid floating point arithmetic */ right_boundary = floor(tj + (m - ti) * (bj - tj) / (bi - ti)); break; } } for (n = left_boundary; n <= right_boundary; n++) mn[POS(m, n)] = 1; } /* mark the expanded region */ for (dpos = BOARDMIN; dpos < BOARDMAX; dpos++) if (ON_BOARD(dpos) && mn[dpos] == 1) for (k = 0; k < 4; k++) if (ON_BOARD(dpos + delta[k]) && !mn[dpos + delta[k]]) mn[dpos + delta[k]] = 2; /* Mark allied dragons that intersect the (unexpanded) hull. * These must all lie entirely within the hull for the * dragon to be considered surrounded. * * Only neighbor dragons are considered since dragons that * are not neighbors are less likely to be helpful. */ for (dpos = BOARDMIN; dpos < BOARDMAX; dpos++) { int mpos; if (ON_BOARD(dpos) && mn[dpos] == 1 && board[dpos] == color && are_neighbor_dragons(pos, dpos) && !mf[dpos]) { for (mpos = BOARDMIN; mpos < BOARDMAX; mpos++) if (ON_BOARD(mpos) && is_same_dragon(mpos, dpos)) mf[mpos] = 2; } /* A special case * * . X X . * X O . X * X . O O * . O . . * * The O stone hasn't been amalgamated and the surround computations * might think this single stone dragon is surrounded, which in turn * can generate overvaluation of moves around this stone. * Consequently, we allow inclusion of the stones at kosumi distance * in the mf (friendly) array. */ if (ON_BOARD(dpos) && mn[dpos] == 2 && board[dpos] == color && are_neighbor_dragons(pos, dpos) && !mf[dpos]) { for (k = 4; k < 8; k++) if (ON_BOARD(dpos + delta[k]) && board[dpos + delta[k]] == color && mn[dpos + delta[k]] == 1 && board[dpos + delta[k-4]] == EMPTY && board[dpos + delta[(k-3)%4]] == EMPTY) { for (mpos = BOARDMIN; mpos < BOARDMAX; mpos++) if (ON_BOARD(mpos) && is_same_dragon(mpos, dpos)) mf[mpos] = 2; } } } /* determine the surround status of the dragon */ surrounded = SURROUNDED; /* Compute the maximum surround status awarded * If distances between enclosing stones are large, reduce to * WEAKLY_SURROUNDED. If (really) too large, then reduce to 0 * FIXME: constants chosen completely ad hoc. Possibly better tunings * can be found. */ for (k = 0; k < corners - 1; k++) { if (is_edge_vertex(corner[k]) && is_edge_vertex(corner[k+1])) continue; if (square_dist(corner[k], corner[k+1]) > 60) { surrounded = 0; break; } else if (square_dist(corner[k], corner[k+1]) > 27) surrounded = WEAKLY_SURROUNDED; } if (surrounded && (!is_edge_vertex(corner[0]) || !is_edge_vertex(corner[corners-1]))) { if (square_dist(corner[0], corner[corners-1]) > 60) surrounded = 0; else if (square_dist(corner[0], corner[corners-1]) > 27) surrounded = WEAKLY_SURROUNDED; } if (surrounded) for (dpos = BOARDMIN; dpos < BOARDMAX; dpos++) if (mf[dpos]) { if (mn[dpos] == 0) { surrounded = 0; break; } else if (mn[dpos] == 2) surrounded = WEAKLY_SURROUNDED; } /* revise the status for single stone dragons. */ if (stones == 1 && surrounded == WEAKLY_SURROUNDED && mn[pos] == 2) surrounded = 0; /* revise the status if an ikken tobi jumps out. */ if (surrounded) { for (dpos = BOARDMIN; dpos < BOARDMAX && surrounded; dpos++) { if (!ON_BOARD(dpos) || !mf[dpos]) continue; for (k = 0; k < 4; k++) { int up = delta[k]; int right = delta[(k + 1) % 4]; if (board[dpos + up] == EMPTY && board[dpos + 2*up] == color && mn[dpos + 2*up] != 1 && ON_BOARD(dpos + up + right) && board[dpos + up + right] != other && ON_BOARD(dpos + up - right) && board[dpos + up - right] != other) { surrounded = 0; break; } } } } if (showboard == 1 || (showboard == 2 && surrounded)) { show_surround_map(mf, mn); } if (!apos && surrounded && surround_pointer < MAX_SURROUND) { memcpy(surroundings[surround_pointer].surround_map, mn, sizeof(mn)); surroundings[surround_pointer].dragon_number = dragon[pos].id; surround_pointer++; } if (surround_size) { int pos; *surround_size = 0; for (pos = BOARDMIN; pos < BOARDMAX; pos++) if (ON_BOARD(pos) && mn[pos] == 1) (*surround_size)++; } return surrounded; }
int is_surrounded(int dr) { return(DRAGON2(dr).surround_status); }
void decide_position(int color) { int pos; int move = NO_MOVE; int acode = 0, dcode = 0; int kworm; static const char *snames[] = {"dead", "alive", "critical", "unknown"}; SGFTree tree; /* Prepare pattern matcher and reading code. */ reset_engine(); silent_examine_position(color, EXAMINE_DRAGONS_WITHOUT_OWL); /* We want to see the reading performed, not just a result picked * from the cache. Thus we clear the cache here. */ reading_cache_clear(); if (*outfilename) sgffile_begindump(&tree); count_variations = 1; for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (!ON_BOARD(pos) || dragon[pos].origin != pos || board[pos] == EMPTY || DRAGON2(pos).escape_route >= 6) continue; gprintf("\nanalyzing %1m\n", pos); gprintf("status=%s, escape=%d\n", snames[dragon[pos].crude_status], DRAGON2(pos).escape_route); acode = owl_attack(pos, &move, NULL, &kworm); if (acode) { if (acode == WIN) { if (move == NO_MOVE) gprintf("%1m is dead as it stands\n", pos); else gprintf("%1m can be attacked at %1m (%d variations)\n", pos, move, count_variations); } else if (acode == KO_A) gprintf("%1m can be attacked with ko (good) at %1m (%d variations)\n", pos, move, count_variations); else if (acode == KO_B) gprintf("%1m can be attacked with ko (bad) at %1m (%d variations)\n", pos, move, count_variations); else if (acode == GAIN) gprintf("%1m can be attacked with gain (captures %1m) at %1m (%d variations)", pos, kworm, move, count_variations); count_variations = 1; dcode = owl_defend(pos, &move, NULL, &kworm); if (dcode) { if (dcode == WIN) { if (move == NO_MOVE) gprintf("%1m is alive as it stands\n", pos); else gprintf("%1m can be defended at %1m (%d variations)\n", pos, move, count_variations); } else if (dcode == KO_A) gprintf("%1m can be defended with ko (good) at %1m (%d variations)\n", pos, move, count_variations); else if (dcode == KO_B) gprintf("%1m can be defended with ko (bad) at %1m (%d variations)\n", pos, move, count_variations); else if (dcode == LOSS) gprintf("%1m can be defended with loss (loses %1m) at %1m (%d variations)", pos, kworm, move, count_variations); } else gprintf("%1m cannot be defended (%d variations)\n", pos, count_variations); } else gprintf("%1m cannot be attacked (%d variations)\n", pos, count_variations); if (acode) { if (dcode) gprintf("status of %1m revised to CRITICAL\n", pos); else gprintf("status of %1m revised to DEAD\n", pos); } else gprintf("status of %1m revised to ALIVE\n", pos); } sgffile_enddump(outfilename); count_variations = 0; }
void semeai() { int semeai_results_first[MAX_DRAGONS][MAX_DRAGONS]; int semeai_results_second[MAX_DRAGONS][MAX_DRAGONS]; int semeai_move[MAX_DRAGONS][MAX_DRAGONS]; char semeai_certain[MAX_DRAGONS][MAX_DRAGONS]; int d1, d2; int k; int num_dragons = number_of_dragons; if (num_dragons > MAX_DRAGONS) { TRACE("Too many dragons!!! Might disregard some semeais."); num_dragons = MAX_DRAGONS; } for (d1 = 0; d1 < num_dragons; d1++) for (d2 = 0; d2 < num_dragons; d2++) { semeai_results_first[d1][d2] = -1; semeai_results_second[d1][d2] = -1; } for (d1 = 0; d1 < num_dragons; d1++) for (k = 0; k < dragon2[d1].neighbors; k++) { int apos = DRAGON(d1).origin; int bpos = DRAGON(dragon2[d1].adjacent[k]).origin; int result_certain; d2 = dragon[bpos].id; /* Look for semeais */ if (dragon[apos].color == dragon[bpos].color || (dragon[apos].status != DEAD && dragon[apos].status != CRITICAL) || (dragon[bpos].status != DEAD && dragon[bpos].status != CRITICAL)) continue; /* Ignore inessential worms or dragons */ if (worm[apos].inessential || DRAGON2(apos).safety == INESSENTIAL || worm[bpos].inessential || DRAGON2(bpos).safety == INESSENTIAL) continue; /* Sometimes the dragons are considered neighbors but are too * distant to constitute a proper semeai, e.g. in nngs4:650, P2 * vs. R3. Then the result of semeai reading may be meaningless * and can confuse the analysis. In order to avoid this we check * that the dragons either are directly adjacent or at least * have one common liberty. */ if (!close_enough_for_proper_semeai(apos, bpos)) continue; /* The array semeai_results_first[d1][d2] will contain the status * of d1 after the d1 d2 semeai, giving d1 the first move. * The array semeai_results_second[d1][d2] will contain the status * of d1 after the d1 d2 semeai, giving d2 the first move. */ DEBUG(DEBUG_SEMEAI, "Considering semeai between %1m and %1m\n", apos, bpos); owl_analyze_semeai(apos, bpos, &(semeai_results_first[d1][d2]), &(semeai_results_second[d1][d2]), &(semeai_move[d1][d2]), 1, &result_certain); DEBUG(DEBUG_SEMEAI, "results if %s moves first: %s %s, %1m%s\n", board[apos] == BLACK ? "black" : "white", result_to_string(semeai_results_first[d1][d2]), result_to_string(semeai_results_second[d1][d2]), semeai_move[d1][d2], result_certain ? "" : " (uncertain)"); semeai_certain[d1][d2] = result_certain; } /* Look for dragons which lose all their semeais outright. The * winners in those semeais are considered safe and further semeais * they are involved in are disregarded. See semeai:81-86 and * nicklas5:1211 for examples of where this is useful. * * Note: To handle multiple simultaneous semeais properly we would * have to make simultaneous semeai reading. Lacking that we can * only get rough guesses of the correct status of the involved * dragons. This code is not guaranteed to be correct in all * situations but should usually be an improvement. */ for (d1 = 0; d1 < num_dragons; d1++) { int involved_in_semeai = 0; int all_lost = 1; for (d2 = 0; d2 < num_dragons; d2++) { if (semeai_results_first[d1][d2] != -1) { involved_in_semeai = 1; if (semeai_results_first[d1][d2] != 0) { all_lost = 0; break; } } } if (involved_in_semeai && all_lost) { /* Leave the status changes to the main loop below. Here we just * remove the presumably irrelevant semeai results. */ for (d2 = 0; d2 < num_dragons; d2++) { if (semeai_results_first[d1][d2] == 0) { int d3; for (d3 = 0; d3 < num_dragons; d3++) { if (semeai_results_second[d3][d2] > 0) { semeai_results_first[d3][d2] = -1; semeai_results_second[d3][d2] = -1; semeai_results_first[d2][d3] = -1; semeai_results_second[d2][d3] = -1; } } } } } } for (d1 = 0; d1 < num_dragons; d1++) { int semeais_found = 0; int best_defense = 0; int best_attack = 0; int defense_move = PASS_MOVE; int attack_move = PASS_MOVE; int defense_certain = 0; int attack_certain = 0; int semeai_attack_target = NO_MOVE; int semeai_defense_target = NO_MOVE; for (d2 = 0; d2 < num_dragons; d2++) { if (semeai_results_first[d1][d2] == -1) continue; gg_assert(semeai_results_second[d1][d2] != -1); semeais_found++; if (best_defense < semeai_results_first[d1][d2] || (best_defense == semeai_results_first[d1][d2] && defense_certain < semeai_certain[d1][d2])) { best_defense = semeai_results_first[d1][d2]; defense_move = semeai_move[d1][d2]; defense_certain = semeai_certain[d1][d2]; semeai_defense_target = dragon2[d2].origin; } if (best_attack < semeai_results_second[d2][d1] || (best_attack == semeai_results_second[d2][d1] && attack_certain < semeai_certain[d2][d1])) { best_attack = semeai_results_second[d2][d1]; attack_move = semeai_move[d2][d1]; attack_certain = semeai_certain[d2][d1]; semeai_attack_target = dragon2[d2].origin; } } if (semeais_found) { dragon2[d1].semeais = semeais_found; if (best_defense != 0 && best_attack != 0) update_status(DRAGON(d1).origin, CRITICAL, CRITICAL); else if (best_attack == 0 && attack_certain) update_status(DRAGON(d1).origin, ALIVE, ALIVE); dragon2[d1].semeai_defense_point = defense_move; dragon2[d1].semeai_defense_certain = defense_certain; dragon2[d1].semeai_defense_target = semeai_defense_target; dragon2[d1].semeai_attack_point = attack_move; dragon2[d1].semeai_attack_certain = attack_certain; dragon2[d1].semeai_attack_target = semeai_attack_target; } } find_moves_to_make_seki(); }
/* Find moves turning supposed territory into seki. This is not * detected above since it either involves an ALIVE dragon adjacent to * a CRITICAL dragon, or an ALIVE dragon whose eyespace can be invaded * and turned into a seki. * * Currently we only search for tactically critical strings with * dragon status dead, which are neighbors of only one opponent * dragon, which is alive. Through semeai analysis we then determine * whether such a string can in fact live in seki. Relevant testcases * include gunnar:42 and gifu03:2. */ static void find_moves_to_make_seki() { int str; int defend_move; int resulta, resultb; for (str = BOARDMIN; str < BOARDMAX; str++) { if (IS_STONE(board[str]) && is_worm_origin(str, str) && attack_and_defend(str, NULL, NULL, NULL, &defend_move) && dragon[str].status == DEAD && DRAGON2(str).hostile_neighbors == 1) { int k; int color = board[str]; int opponent = NO_MOVE; int certain; struct eyevalue reduced_genus; for (k = 0; k < DRAGON2(str).neighbors; k++) { opponent = dragon2[DRAGON2(str).adjacent[k]].origin; if (board[opponent] != color) break; } ASSERT1(opponent != NO_MOVE, opponent); if (dragon[opponent].status != ALIVE) continue; /* FIXME: These heuristics are used for optimization. We don't * want to call expensive semeai code if the opponent * dragon has more than one eye elsewhere. However, the * heuristics might still need improvement. */ compute_dragon_genus(opponent, &reduced_genus, str); if (DRAGON2(opponent).moyo_size > 10 || min_eyes(&reduced_genus) > 1) continue; owl_analyze_semeai_after_move(defend_move, color, opponent, str, &resulta, &resultb, NULL, 1, &certain, 0); /* Do not trust uncertain results. In fact it should only take a * few nodes to determine the semeai result, if it is a proper * potential seki position. */ if (resultb != WIN && certain) { int d = dragon[str].id; DEBUG(DEBUG_SEMEAI, "Move to make seki at %1m (%1m vs %1m)\n", defend_move, str, opponent); dragon2[d].semeais++; update_status(str, CRITICAL, CRITICAL); dragon2[d].semeai_defense_point = defend_move; dragon2[d].semeai_defense_certain = certain; dragon2[d].semeai_defense_target = opponent; /* We need to determine a proper attack move (the one that * prevents seki). Currently we try the defense move first, * and if it doesn't work -- all liberties of the string. */ owl_analyze_semeai_after_move(defend_move, OTHER_COLOR(color), str, opponent, &resulta, NULL, NULL, 1, NULL, 0); if (resulta != WIN) dragon2[d].semeai_attack_point = defend_move; else { int k; int libs[MAXLIBS]; int liberties = findlib(str, MAXLIBS, libs); for (k = 0; k < liberties; k++) { owl_analyze_semeai_after_move(libs[k], OTHER_COLOR(color), str, opponent, &resulta, NULL, NULL, 1, NULL, 0); if (resulta != WIN) { dragon2[d].semeai_attack_point = libs[k]; break; } } /* FIXME: What should we do if none of the tried attacks worked? */ if (k == liberties) dragon2[d].semeai_attack_point = defend_move; } DEBUG(DEBUG_SEMEAI, "Move to prevent seki at %1m (%1m vs %1m)\n", dragon2[d].semeai_attack_point, opponent, str); dragon2[d].semeai_attack_certain = certain; dragon2[d].semeai_attack_target = opponent; } } } }
/* Find moves turning supposed territory into seki. This is not * detected above since it either involves an ALIVE dragon adjacent to * a CRITICAL dragon, or an ALIVE dragon whose eyespace can be invaded * and turned into a seki. * * Currently we only search for tactically critical strings with * dragon status dead, which are neighbors of only one opponent * dragon, which is alive. Through semeai analysis we then determine * whether such a string can in fact live in seki. Relevant testcases * include gunnar:42 and gifu03:2. */ static void find_moves_to_make_seki() { int str; int defend_move; int resulta, resultb; for (str = BOARDMIN; str < BOARDMAX; str++) { if (IS_STONE(board[str]) && is_worm_origin(str, str) && attack_and_defend(str, NULL, NULL, NULL, &defend_move) && dragon[str].status == DEAD && DRAGON2(str).hostile_neighbors == 1) { int k; int color = board[str]; int opponent = NO_MOVE; int certain; struct eyevalue reduced_genus; for (k = 0; k < DRAGON2(str).neighbors; k++) { opponent = dragon2[DRAGON2(str).adjacent[k]].origin; if (board[opponent] != color) break; } ASSERT1(opponent != NO_MOVE, opponent); if (dragon[opponent].status != ALIVE) continue; /* FIXME: These heuristics are used for optimization. We don't * want to call expensive semeai code if the opponent * dragon has more than one eye elsewhere. However, the * heuristics might still need improvement. */ compute_dragon_genus(opponent, &reduced_genus, str); if (min_eyes(&reduced_genus) > 1 || DRAGON2(opponent).moyo_size > 10 || DRAGON2(opponent).moyo_territorial_value > 2.999 || DRAGON2(opponent).escape_route > 0 || DRAGON2(str).escape_route > 0) continue; owl_analyze_semeai_after_move(defend_move, color, opponent, str, &resulta, &resultb, NULL, 1, &certain, 0); if (resultb == WIN) { owl_analyze_semeai(str, opponent, &resultb, &resulta, &defend_move, 1, &certain); resulta = REVERSE_RESULT(resulta); resultb = REVERSE_RESULT(resultb); } /* Do not trust uncertain results. In fact it should only take a * few nodes to determine the semeai result, if it is a proper * potential seki position. */ if (resultb != WIN && certain) { int d = dragon[str].id; DEBUG(DEBUG_SEMEAI, "Move to make seki at %1m (%1m vs %1m)\n", defend_move, str, opponent); dragon2[d].semeais++; update_status(str, CRITICAL, CRITICAL); dragon2[d].semeai_defense_code = REVERSE_RESULT(resultb); dragon2[d].semeai_defense_point = defend_move; dragon2[d].semeai_defense_certain = certain; gg_assert(board[opponent] == OTHER_COLOR(board[dragon2[d].origin])); dragon2[d].semeai_defense_target = opponent; /* We need to determine a proper attack move (the one that * prevents seki). Currently we try the defense move first, * and if it doesn't work -- all liberties of the string. */ owl_analyze_semeai_after_move(defend_move, OTHER_COLOR(color), str, opponent, &resulta, NULL, NULL, 1, NULL, 0); if (resulta != WIN) { dragon2[d].semeai_attack_code = REVERSE_RESULT(resulta); dragon2[d].semeai_attack_point = defend_move; } else { int k; int libs[MAXLIBS]; int liberties = findlib(str, MAXLIBS, libs); for (k = 0; k < liberties; k++) { owl_analyze_semeai_after_move(libs[k], OTHER_COLOR(color), str, opponent, &resulta, NULL, NULL, 1, NULL, 0); if (resulta != WIN) { dragon2[d].semeai_attack_code = REVERSE_RESULT(resulta); dragon2[d].semeai_attack_point = libs[k]; break; } } if (k == liberties) { DEBUG(DEBUG_SEMEAI, "No move to attack in semeai (%1m vs %1m), seki assumed.\n", str, opponent); dragon2[d].semeai_attack_code = 0; dragon2[d].semeai_attack_point = NO_MOVE; update_status(str, ALIVE, ALIVE_IN_SEKI); } } DEBUG(DEBUG_SEMEAI, "Move to prevent seki at %1m (%1m vs %1m)\n", dragon2[d].semeai_attack_point, opponent, str); dragon2[d].semeai_attack_certain = certain; dragon2[d].semeai_attack_target = opponent; } } } /* Now look for dead strings inside a single eyespace of a living dragon. * * FIXME: Clearly this loop should share most of its code with the * one above. It would also be good to reimplement so that * moves invading a previously empty single eyespace to make * seki can be found. */ for (str = BOARDMIN; str < BOARDMAX; str++) { if (IS_STONE(board[str]) && is_worm_origin(str, str) && !find_defense(str, NULL) && dragon[str].status == DEAD && DRAGON2(str).hostile_neighbors == 1) { int k; int color = board[str]; int opponent = NO_MOVE; int certain; struct eyevalue reduced_genus; for (k = 0; k < DRAGON2(str).neighbors; k++) { opponent = dragon2[DRAGON2(str).adjacent[k]].origin; if (board[opponent] != color) break; } ASSERT1(opponent != NO_MOVE, opponent); if (dragon[opponent].status != ALIVE) continue; /* FIXME: These heuristics are used for optimization. We don't * want to call expensive semeai code if the opponent * dragon has more than one eye elsewhere. However, the * heuristics might still need improvement. */ compute_dragon_genus(opponent, &reduced_genus, str); if (DRAGON2(opponent).moyo_size > 10 || min_eyes(&reduced_genus) > 1) continue; owl_analyze_semeai(str, opponent, &resulta, &resultb, &defend_move, 1, &certain); /* Do not trust uncertain results. In fact it should only take a * few nodes to determine the semeai result, if it is a proper * potential seki position. */ if (resulta != 0 && certain) { int d = dragon[str].id; DEBUG(DEBUG_SEMEAI, "Move to make seki at %1m (%1m vs %1m)\n", defend_move, str, opponent); dragon2[d].semeais++; update_status(str, CRITICAL, CRITICAL); dragon2[d].semeai_defense_code = resulta; dragon2[d].semeai_defense_point = defend_move; dragon2[d].semeai_defense_certain = certain; gg_assert(board[opponent] == OTHER_COLOR(board[dragon2[d].origin])); dragon2[d].semeai_defense_target = opponent; /* We need to determine a proper attack move (the one that * prevents seki). Currently we try the defense move first, * and if it doesn't work -- all liberties of the string. */ owl_analyze_semeai_after_move(defend_move, OTHER_COLOR(color), str, opponent, &resulta, NULL, NULL, 1, NULL, 0); if (resulta != WIN) { dragon2[d].semeai_attack_code = REVERSE_RESULT(resulta); dragon2[d].semeai_attack_point = defend_move; } else { int k; int libs[MAXLIBS]; int liberties = findlib(str, MAXLIBS, libs); for (k = 0; k < liberties; k++) { owl_analyze_semeai_after_move(libs[k], OTHER_COLOR(color), str, opponent, &resulta, NULL, NULL, 1, NULL, 0); if (resulta != WIN) { dragon2[d].semeai_attack_code = REVERSE_RESULT(resulta); dragon2[d].semeai_attack_point = libs[k]; break; } } if (k == liberties) { DEBUG(DEBUG_SEMEAI, "No move to attack in semeai (%1m vs %1m), seki assumed.\n", str, opponent); dragon2[d].semeai_attack_code = 0; dragon2[d].semeai_attack_point = NO_MOVE; update_status(str, ALIVE, ALIVE_IN_SEKI); } } DEBUG(DEBUG_SEMEAI, "Move to prevent seki at %1m (%1m vs %1m)\n", dragon2[d].semeai_attack_point, opponent, str); dragon2[d].semeai_attack_certain = certain; dragon2[d].semeai_attack_target = opponent; } } } }
/* 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: * 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. * 1. Capture an opponent string in atari and adjacent to own * invincible string. Moves leading to ko or snapback are excluded. * 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. * 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. */ int aftermath_genmove(int *aftermath_move, 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 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) { *aftermath_move = best_scoring_move; DEBUG(DEBUG_AFTERMATH, "Closing edge at %1m\n", best_scoring_move); return 1; } /* Case 1. */ 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)) { *aftermath_move = lib; return 1; } } } /* Cases 2--4. */ if (closest_opponent != NO_MOVE || closest_own != NO_MOVE) { if (closest_own == NO_MOVE) 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) { char mx[BOARDMAX]; 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; } } } *aftermath_move = move; return 1; } /* 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; 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; 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 { *aftermath_move = move; DEBUG(DEBUG_AFTERMATH, "Splitting eyespace at %1m\n", move); return 1; } } } /* 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 < 4; k++) { int pos2 = pos + delta[k]; if (!ON_BOARD(pos2)) continue; if (board[pos2] == other && dragon[pos2].status != ALIVE && (do_capture_dead_stones || worm[pos2].unconditional_status != DEAD) && DRAGON2(pos2).safety != INESSENTIAL) { 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). */ if (!self_atari_ok && 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)) 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; *aftermath_move = move; DEBUG(DEBUG_AFTERMATH, "Filling opponent liberty at %1m\n", move); return 1; } } /* 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)) { *aftermath_move = worm[pos].attack_points[0]; DEBUG(DEBUG_AFTERMATH, "Tactically attack %1m at %1m\n", pos, *aftermath_move); return 1; } } /* No move found. */ return -1; }