// group must be base of a group inline static bool try_delete_group(board_t *b, index_t group) { if (b->pseudo_liberties[group] != 0) { maybe_in_atari_now(b, group); return false; } index_t ptr = group; index_swap(b, b->list_pos[ptr], b->group_ptr++); do { clear_atari_bits_3x3(b->nbr3x3[ptr]); index_swap(b, b->list_pos[ptr], b->empty_ptr++); b->hash -= (hash_t)b->stones[ptr] * p4423[ptr]; b->stones[ptr] = STONE_EMPTY; delete_stone_update_liberties(b, ptr); delete_stone_update_3x3(b, ptr); ptr = b->next_in_group[ptr]; } while (ptr != group); do { if (IS_STONE(b->stones[N(b, ptr)])) { maybe_not_in_atari_now(b, b->base_of_group[N(b, ptr)]); } if (IS_STONE(b->stones[S(b, ptr)])) { maybe_not_in_atari_now(b, b->base_of_group[S(b, ptr)]); } if (IS_STONE(b->stones[W(b, ptr)])) { maybe_not_in_atari_now(b, b->base_of_group[W(b, ptr)]); } if (IS_STONE(b->stones[E(b, ptr)])) { maybe_not_in_atari_now(b, b->base_of_group[E(b, ptr)]); } ptr = b->next_in_group[ptr]; } while (ptr != group); return true; }
/* Helper for the owl_hotspots() function below. */ static void mark_dragon_hotspot_values(float values[BOARDMAX], int dr, float contribution, char active_board[BOARDMAX]) { int pos; int k; ASSERT1(IS_STONE(board[dr]), dr); for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] != EMPTY) continue; for (k = 0; k < 8; k++) { int pos2 = pos + delta[k]; if (IS_STONE(board[pos2]) && (is_same_dragon(pos2, dr) || (are_neighbor_dragons(pos2, dr) && board[pos2] == board[dr])) && (countlib(pos2) <= 4 || is_edge_vertex(pos))) { if (k < 4) { if (is_same_dragon(pos2, dr)) values[pos] += contribution; else values[pos] += 0.5 * contribution; break; } else { /* If pos2 = SOUTHWEST(pos), this construction makes * pos3 = SOUTH(pos) and * pos4 = WEST(pos) * and corresponding for all other diagonal movements. */ int pos3 = pos + delta[k % 4]; int pos4 = pos + delta[(k+1) % 4]; if (board[pos3] == EMPTY || countlib(pos3) <= 2 || board[pos4] == EMPTY || countlib(pos4) <= 2) values[pos] += 0.5 * contribution; break; } } } /* If not close to the dragon, but within the active area, give * negative hotspot contribution. */ if (k == 8 && active_board[pos] == EMPTY) { values[pos] -= 0.5 * contribution; } } }
/* 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; } }
void decide_eye(int pos) { int color; struct eyevalue value; int attack_point; int defense_point; int eyepos; SGFTree tree; reset_engine(); silent_examine_position(EXAMINE_DRAGONS_WITHOUT_OWL); color = black_eye[pos].color; if (!IS_STONE(color)) { gprintf("The eye at %1m is not of a single color.\n", pos); return; } if (printboard) showboard(0); /* Enable sgf output. */ if (*outfilename) sgffile_begindump(&tree); count_variations = 1; if (black_eye[pos].color == BLACK) { eyepos = black_eye[pos].origin; compute_eyes(eyepos, &value, &attack_point, &defense_point, black_eye, half_eye, 0); gprintf("Black eyespace at %1m: %s\n", eyepos, eyevalue_to_string(&value)); if (eye_move_urgency(&value) > 0) { gprintf(" vital points: %1m (attack) %1m (defense)\n", attack_point, defense_point); } } if (white_eye[pos].color == WHITE) { eyepos = white_eye[pos].origin; compute_eyes(eyepos, &value, &attack_point, &defense_point, white_eye, half_eye, 0); gprintf("White eyespace at %1m: %s\n", eyepos, eyevalue_to_string(&value)); if (eye_move_urgency(&value) > 0) { gprintf(" vital points: %1m (attack) %1m (defense)\n", attack_point, defense_point); } } /* Finish sgf output. */ sgffile_enddump(outfilename); count_variations = 0; }
inline static void delete_stone_update_liberties(board_t *b, index_t pos) { index_t z, pos2 = pos * pos; if (IS_STONE(b->stones[N(b, pos)])) { b->pseudo_liberties[z=b->base_of_group[N(b, pos)]] ++; b->group_liberties_sum[z] += pos; b->group_liberties_sum_squared[z] += pos2; } if (IS_STONE(b->stones[S(b, pos)])) { b->pseudo_liberties[z=b->base_of_group[S(b, pos)]] ++; b->group_liberties_sum[z] += pos; b->group_liberties_sum_squared[z] += pos2; } if (IS_STONE(b->stones[W(b, pos)])) { b->pseudo_liberties[z=b->base_of_group[W(b, pos)]] ++; b->group_liberties_sum[z] += pos; b->group_liberties_sum_squared[z] += pos2; } if (IS_STONE(b->stones[E(b, pos)])) { b->pseudo_liberties[z=b->base_of_group[E(b, pos)]] ++; b->group_liberties_sum[z] += pos; b->group_liberties_sum_squared[z] += pos2; } }
/* Check whether two dragons are directly adjacent or have at least * one common liberty. */ static int close_enough_for_proper_semeai(int apos, int bpos) { int pos; for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] == EMPTY && neighbor_of_dragon(pos, apos) && neighbor_of_dragon(pos, bpos)) return 1; else if (IS_STONE(board[pos])) { if (is_same_dragon(pos, apos) && neighbor_of_dragon(pos, bpos)) return 1; if (is_same_dragon(pos, bpos) && neighbor_of_dragon(pos, apos)) return 1; } } return 0; }
/* Helper for the reading_hotspots() function below. */ static void mark_string_hotspot_values(float values[BOARDMAX], int m, int n, float contribution) { int i, j, k; /* If p[m][n] is EMPTY, we just give the contribution to close empty * vertices. This is a rough simplification. */ if (BOARD(m, n) == EMPTY) { for (i = -1; i <= 1; i++) for (j = -1; j <= 1; j++) if (BOARD(m+i, n+j) == EMPTY) values[POS(m+i, n+j)] += contribution; return; } /* Otherwise we give contribution to liberties and diagonal * neighbors of the string at (m, n). */ for (i = 0; i < board_size; i++) for (j = 0; j < board_size; j++) { if (BOARD(i, j) != EMPTY) continue; for (k = 0; k < 8; k++) { int di = deltai[k]; int dj = deltaj[k]; if (IS_STONE(BOARD(i+di, j+dj)) && same_string(POS(i+di, j+dj), POS(m, n))) { if (k < 4) { values[POS(i, j)] += contribution; break; } else { if (BOARD(i+di, j) == EMPTY || countlib(POS(i+di, j)) <= 2 || BOARD(i, j+dj) == EMPTY || countlib(POS(i, j+dj)) <= 2) values[POS(i, j)] += contribution; break; } } } } }
/* 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; } } } }
bool check_board(board_t *b) { static bool flag[max_len]; memset(flag, false, sizeof(bool) * max_len); for (index_t i = 0; i < b->len; i++) { if (IS_STONE(b->stones[i])) { flag[b->next_in_group[i]] = true; //check next[i] in the same group if (b->base_of_group[i] != b->base_of_group[b->next_in_group[i]]) return false; //check same color if (b->stones[i] != b->stones[b->next_in_group[i]]) return false; } } // check uniqueness of next[i] for (index_t i = 0; i < b->len; i++) { if (IS_STONE(b->stones[i]) != flag[i]) { return false; } } //check pseudo_liberties information board_t *b2 = new board_t; fork_board(b2, b); memset(b2->group_size, 0, sizeof(index_t) * b->len); for (index_t i = 0; i < b2->len; i++) { if (IS_STONE(b2->stones[i])) { index_t j = b2->base_of_group[i]; b2->group_size[j] ++; b2->pseudo_liberties[j] = 0; b2->group_liberties_sum[j] = 0; b2->group_liberties_sum_squared[j] = 0; } } for (index_t i = 0; i < b2->len; i++) { if (IS_STONE(b2->stones[i])) { update_empty_neighbour(b2, i); } } for (index_t i = 0; i < b2->len; i++) { if (IS_STONE(b2->stones[i])) { index_t p = b->base_of_group[i]; index_t q = b2->base_of_group[i]; if (b->group_size[p] != b2->group_size[q] || b->pseudo_liberties[p] != b2->pseudo_liberties[q]) { delete b2; return false; } if (b->group_liberties_sum[p] != b2->group_liberties_sum[q] || b->group_liberties_sum_squared[p] != b2->group_liberties_sum_squared[q]) { delete b2; return false; } } } delete b2; // check hash value hash_t nhash = 0; for (index_t i = 0; i < b->len; i++) { if (IS_STONE(b->stones[i])) nhash += (hash_t)b->stones[i] * p4423[i]; } if (nhash != b->hash) return false; // check list for (index_t i = 0; i < b->len; i++) { if (b->stones[i] != STONE_BORDER) { index_t j = b->list_pos[i]; if (j < 0 || j >= b->size * b->size) return false; if (b->list[j] != i) return false; int tA = b->stones[i] == STONE_EMPTY ? 0 : i == b->base_of_group[i] ? 2 : 1; int tB = j < b->empty_ptr ? 0 : j < b->group_ptr ? 1 : 2; if (tA != tB) return false; } } // check nbr3x3 nbr3x3_t new_nbr[max_len]; for (index_t i = 0; i < b->len; i++) { if (b->stones[i] != STONE_BORDER) { new_nbr[i] = recalc_nbr3x3(b, i); } } for (index_t i = 0; i < b->len; i++) { if (IS_STONE(b->stones[i]) && b->base_of_group[i] == i && find_atari(b, i) >= 0) { index_t av = find_atari(b, i); if (av < 0) return false; set_atari_bits_3x3(new_nbr[av], IN_GROUP(b, N(b, av), i), IN_GROUP(b, S(b, av), i), IN_GROUP(b, W(b, av), i), IN_GROUP(b, E(b, av), i)); } } for (index_t i = 0; i < b->len; i++) { if (b->stones[i] != STONE_BORDER) { if (new_nbr[i] != b->nbr3x3[i]) return false; } } // check prob for (int i = 0; i < 2; i++) for (int j = 0; j < b->len; j++) { double diff = get_prob(b, j, (stone_t)(i+1)) - b->prob_sum2[i][j]; if (diff > epsilon || diff < -epsilon) return false; } return true; }
/* Computes the active area for the current board position and the * read result that has just been stored in *entry. */ static void compute_active_breakin_area(struct persistent_cache_entry *entry, const char breakin_shadow[BOARDMAX], int dummy) { int pos; int k, r; signed char active[BOARDMAX]; int other = OTHER_COLOR(board[entry->apos]); UNUSED(dummy); /* We let the active area be * the string to connect + * the breakin shadow (which contains the goal) + * distance two expansion through empty intersections and own stones + * adjacent opponent strings + * liberties and neighbors of adjacent opponent strings with less than * five liberties + * liberties and neighbors of low liberty neighbors of adjacent opponent * strings with less than five liberties. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) active[pos] = breakin_shadow[pos]; signed_mark_string(entry->apos, active, 1); /* To be safe, also add the successful move. */ if (entry->result != 0 && entry->move != 0) active[entry->move] = 1; /* Distance two expansion through empty intersections and own stones. */ for (k = 1; k < 3; k++) { for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (!ON_BOARD(pos) || board[pos] == other || active[pos] != 0) continue; if ((ON_BOARD(SOUTH(pos)) && active[SOUTH(pos)] == k) || (ON_BOARD(WEST(pos)) && active[WEST(pos)] == k) || (ON_BOARD(NORTH(pos)) && active[NORTH(pos)] == k) || (ON_BOARD(EAST(pos)) && active[EAST(pos)] == k)) { if (board[pos] == EMPTY) active[pos] = k + 1; else signed_mark_string(pos, active, (signed char) (k + 1)); } } } /* Adjacent opponent strings. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] != other || active[pos] != 0) continue; for (r = 0; r < 4; r++) { int pos2 = pos + delta[r]; if (ON_BOARD(pos2) && board[pos2] != other && active[pos2] && active[pos2] <= 2) { signed_mark_string(pos, active, 1); break; } } } /* Liberties of adjacent opponent strings with less than four liberties + * liberties of low liberty neighbors of adjacent opponent strings * with less than five liberties. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] == other && active[pos] > 0 && countlib(pos) < 4) { int libs[4]; int liberties = findlib(pos, 3, libs); int adjs[MAXCHAIN]; int adj; for (r = 0; r < liberties; r++) active[libs[r]] = 1; /* Also add liberties of neighbor strings if these are three * or less. */ adj = chainlinks(pos, adjs); for (r = 0; r < adj; r++) { signed_mark_string(adjs[r], active, -1); if (countlib(adjs[r]) <= 3) { int s; int adjs2[MAXCHAIN]; int adj2; liberties = findlib(adjs[r], 3, libs); for (s = 0; s < liberties; s++) active[libs[s]] = 1; adj2 = chainlinks(pos, adjs2); for (s = 0; s < adj2; s++) signed_mark_string(adjs2[s], active, -1); } } } } for (pos = BOARDMIN; pos < BOARDMAX; pos++) { char value = board[pos]; if (!ON_BOARD(pos)) continue; if (!active[pos]) value = GRAY; else if (IS_STONE(board[pos]) && countlib(pos) > 3 && active[pos] > 0) value |= HIGH_LIBERTY_BIT2; entry->board[pos] = value; } }
static void compute_active_reading_area(struct persistent_cache_entry *entry, const char goal[BOARDMAX], int dummy) { signed char active[BOARDMAX]; int pos, r; UNUSED(dummy); /* Remains to set the board. We let the active area be the contested * string and reading shadow + adjacent empty and strings + * neighbors of active area so far + one more expansion from empty * to empty. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) active[pos] = goal[pos]; signed_mark_string(entry->apos, active, 1); /* To be safe, also add the successful move. */ if (entry->result != 0 && entry->move != 0) active[entry->move] = 1; /* Add adjacent strings and empty. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (!ON_BOARD(pos)) continue; if (active[pos] != 0) continue; if ((ON_BOARD(SOUTH(pos)) && active[SOUTH(pos)] == 1) || (ON_BOARD(WEST(pos)) && active[WEST(pos)] == 1) || (ON_BOARD(NORTH(pos)) && active[NORTH(pos)] == 1) || (ON_BOARD(EAST(pos)) && active[EAST(pos)] == 1)) { if (IS_STONE(board[pos])) signed_mark_string(pos, active, 2); else active[pos] = 2; } } /* Remove invincible strings. No point adding their liberties and * neighbors. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (!ON_BOARD(pos)) continue; if (IS_STONE(board[pos]) && worm[pos].invincible) active[pos] = 0; } /* Expand empty to empty. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (IS_STONE(board[pos]) || active[pos] != 0) continue; if ((board[SOUTH(pos)] == EMPTY && active[SOUTH(pos)] == 2) || (board[WEST(pos)] == EMPTY && active[WEST(pos)] == 2) || (board[NORTH(pos)] == EMPTY && active[NORTH(pos)] == 2) || (board[EAST(pos)] == EMPTY && active[EAST(pos)] == 2)) active[pos] = 3; } /* Add neighbors of active area so far. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (!ON_BOARD(pos)) continue; if (active[pos] != 0) continue; if ((ON_BOARD(SOUTH(pos)) && active[SOUTH(pos)] > 0 && active[SOUTH(pos)] < 4) || (ON_BOARD(WEST(pos)) && active[WEST(pos)] > 0 && active[WEST(pos)] < 4) || (ON_BOARD(NORTH(pos)) && active[NORTH(pos)] > 0 && active[NORTH(pos)] < 4) || (ON_BOARD(EAST(pos)) && active[EAST(pos)] > 0 && active[EAST(pos)] < 4)) active[pos] = 4; } /* Also add the previously played stones to the active area. */ for (r = 0; r < stackp; r++) active[entry->stack[r]] = 5; for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (!ON_BOARD(pos)) continue; entry->board[pos] = active[pos] != 0 ? board[pos] : GRAY; } }
static void compute_active_owl_area(struct persistent_cache_entry *entry, const char goal[BOARDMAX], int goal_color) { int k, r; int pos; int other = OTHER_COLOR(goal_color); signed char active[BOARDMAX]; /* We let the active area be the goal + * distance four expansion through empty intersections and own stones + * adjacent opponent strings + * liberties and neighbors of adjacent opponent strings with less than * five liberties + * liberties and neighbors of low liberty neighbors of adjacent opponent * strings with less than five liberties. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) if (ON_BOARD(pos)) active[pos] = (goal[pos] != 0); /* Also add critical moves to the active area. */ if (ON_BOARD1(entry->move)) active[entry->move] = 1; if (ON_BOARD1(entry->move2)) active[entry->move2] = 1; /* Distance four expansion through empty intersections and own stones. */ for (k = 1; k < 5; k++) { for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (!ON_BOARD(pos) || board[pos] == other || active[pos] > 0) continue; if ((ON_BOARD(SOUTH(pos)) && active[SOUTH(pos)] == k) || (ON_BOARD(WEST(pos)) && active[WEST(pos)] == k) || (ON_BOARD(NORTH(pos)) && active[NORTH(pos)] == k) || (ON_BOARD(EAST(pos)) && active[EAST(pos)] == k)) { if (board[pos] == EMPTY) active[pos] = k + 1; else signed_mark_string(pos, active, (signed char) (k + 1)); } } } /* Adjacent opponent strings. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] != other || active[pos] != 0) continue; for (r = 0; r < 4; r++) { int pos2 = pos + delta[r]; if (ON_BOARD(pos2) && board[pos2] != other && active[pos2] != 0) { signed_mark_string(pos, active, 1); break; } } } /* Liberties of adjacent opponent strings with less than five liberties + * liberties of low liberty neighbors of adjacent opponent strings * with less than five liberties. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] == other && active[pos] > 0 && countlib(pos) < 5) { int libs[4]; int liberties = findlib(pos, 4, libs); int adjs[MAXCHAIN]; int adj; for (r = 0; r < liberties; r++) active[libs[r]] = 1; /* Also add liberties of neighbor strings if these are three * or less. */ adj = chainlinks(pos, adjs); for (r = 0; r < adj; r++) { signed_mark_string(adjs[r], active, -1); if (countlib(adjs[r]) <= 3) { int s; int adjs2[MAXCHAIN]; int adj2; liberties = findlib(adjs[r], 3, libs); for (s = 0; s < liberties; s++) active[libs[s]] = 1; adj2 = chainlinks(pos, adjs2); for (s = 0; s < adj2; s++) signed_mark_string(adjs2[s], active, -1); } } } } for (pos = BOARDMIN; pos < BOARDMAX; pos++) { int value = board[pos]; if (!ON_BOARD(pos)) continue; if (!active[pos]) value = GRAY; else if (IS_STONE(board[pos]) && countlib(pos) > 4 && active[pos] > 0) value |= HIGH_LIBERTY_BIT; entry->board[pos] = value; } }
void store_persistent_owl_cache(int routine, int apos, int bpos, int cpos, int result, int move, int move2, int certain, int tactical_nodes, char goal[BOARDMAX], int goal_color) { char active[BOARDMAX]; int pos; int k; int r; int other = OTHER_COLOR(goal_color); gg_assert(stackp == 0); /* If cache is full, first try to purge it. */ if (persistent_owl_cache_size == MAX_OWL_CACHE_SIZE) purge_persistent_owl_cache(); /* FIXME: Kick out oldest or least expensive entry instead of giving up. */ if (persistent_owl_cache_size == MAX_OWL_CACHE_SIZE) { TRACE_OWL_PERFORMANCE("Persistent owl cache full.\n"); return; } persistent_owl_cache[persistent_owl_cache_size].boardsize = board_size; persistent_owl_cache[persistent_owl_cache_size].routine = routine; persistent_owl_cache[persistent_owl_cache_size].apos = apos; persistent_owl_cache[persistent_owl_cache_size].bpos = bpos; persistent_owl_cache[persistent_owl_cache_size].cpos = cpos; persistent_owl_cache[persistent_owl_cache_size].result = result; persistent_owl_cache[persistent_owl_cache_size].result_certain = certain; persistent_owl_cache[persistent_owl_cache_size].move = move; persistent_owl_cache[persistent_owl_cache_size].move2 = move2; persistent_owl_cache[persistent_owl_cache_size].tactical_nodes = tactical_nodes; persistent_owl_cache[persistent_owl_cache_size].movenum = movenum; /* Remains to set the board. We let the active area be * the goal + * distance four expansion through empty intersections and own stones + * adjacent opponent strings + * liberties of adjacent opponent strings with less than five liberties + * liberties of low liberty neighbors of adjacent opponent strings * with less than five liberties. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) if (ON_BOARD(pos)) active[pos] = (goal[pos] != 0); /* Also add critical moves to the active area. */ if (ON_BOARD1(move)) active[move] = 1; if (ON_BOARD1(move2)) active[move2] = 1; /* Distance four expansion through empty intersections and own stones. */ for (k = 1; k < 5; k++) { for (pos = BOARDMIN; pos < BOARDMAX; pos++){ if (!ON_BOARD(pos) || board[pos] == other || active[pos] != 0) continue; if ((ON_BOARD(SOUTH(pos)) && active[SOUTH(pos)] == k) || (ON_BOARD(WEST(pos)) && active[WEST(pos)] == k) || (ON_BOARD(NORTH(pos)) && active[NORTH(pos)] == k) || (ON_BOARD(EAST(pos)) && active[EAST(pos)] == k)) { if (board[pos] == EMPTY) active[pos] = k + 1; else mark_string(pos, active, (char) (k + 1)); } } } /* Adjacent opponent strings. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] != other || active[pos] != 0) continue; for (r = 0; r < 4; r++) { int pos2 = pos + delta[r]; if (ON_BOARD(pos2) && board[pos2] != other && active[pos2] != 0) { mark_string(pos, active, (char) 1); break; } } } /* Liberties of adjacent opponent strings with less than five liberties + * liberties of low liberty neighbors of adjacent opponent strings * with less than five liberties. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] == other && active[pos] != 0 && countlib(pos) < 5) { int libs[4]; int liberties = findlib(pos, 4, libs); int adjs[MAXCHAIN]; int adj; for (r = 0; r < liberties; r++) active[libs[r]] = 1; /* Also add liberties of neighbor strings if these are three * or less. */ adj = chainlinks(pos, adjs); for (r = 0; r < adj; r++) { if (countlib(adjs[r]) <= 3) { int s; liberties = findlib(adjs[r], 3, libs); for (s = 0; s < liberties; s++) active[libs[s]] = 1; } } } } for (pos = BOARDMIN; pos < BOARDMAX; pos++) { int value = board[pos]; if (!ON_BOARD(pos)) continue; if (!active[pos]) value = GRAY; else if (IS_STONE(board[pos]) && countlib(pos) > 4) value |= HIGH_LIBERTY_BIT; persistent_owl_cache[persistent_owl_cache_size].board[pos] = value; } if (debug & DEBUG_OWL_PERSISTENT_CACHE) { gprintf("%o Stored result in cache (entry %d):\n", persistent_owl_cache_size); print_persistent_owl_cache_entry(persistent_owl_cache_size); } persistent_owl_cache_size++; }
/* Store a new read result in the persistent cache. */ void store_persistent_reading_cache(int routine, int str, int result, int move, int nodes) { char active[BOARDMAX]; int k; int r; int score = nodes; struct reading_cache *entry; ASSERT1(result == 0 || (move == 0) || ON_BOARD(move), move); /* Never cache results at too great depth. */ if (stackp > MAX_READING_CACHE_DEPTH) return; /* If cache is still full, consider kicking out an old entry. */ if (persistent_reading_cache_size == MAX_READING_CACHE_SIZE) { int worst_entry = -1; int worst_score = score; for (k = 1; k < persistent_reading_cache_size; k++) { if (persistent_reading_cache[k].score < worst_score) { worst_score = persistent_reading_cache[k].score; worst_entry = k; } } if (worst_entry != -1) { /* Move the last entry in the cache here to make space. */ if (worst_entry < persistent_reading_cache_size - 1) persistent_reading_cache[worst_entry] = persistent_reading_cache[persistent_reading_cache_size - 1]; persistent_reading_cache_size--; } else return; } entry = &(persistent_reading_cache[persistent_reading_cache_size]); entry->boardsize = board_size; entry->movenum = movenum; entry->nodes = nodes; entry->score = score; entry->remaining_depth = depth - stackp; entry->routine = routine; entry->str = str; entry->result = result; entry->move = move; for (r = 0; r < MAX_READING_CACHE_DEPTH; r++) { if (r < stackp) get_move_from_stack(r, &(entry->stack[r]), &(entry->move_color[r])); else { entry->stack[r] = 0; entry->move_color[r] = EMPTY; } } /* Remains to set the board. We let the active area be the contested * string and reading shadow + adjacent empty and strings + * neighbors of active area so far + one more expansion from empty * to empty. */ for (k = BOARDMIN; k < BOARDMAX; k++) active[k] = shadow[k]; mark_string(str, active, 1); /* To be safe, also add the successful move. */ if (result != 0 && move != 0) active[move] = 1; /* Add adjacent strings and empty. */ for (k = BOARDMIN; k < BOARDMAX; k++) { if (!ON_BOARD(k)) continue; if (active[k] != 0) continue; if ((ON_BOARD(SOUTH(k)) && active[SOUTH(k)] == 1) || (ON_BOARD(WEST(k)) && active[WEST(k)] == 1) || (ON_BOARD(NORTH(k)) && active[NORTH(k)] == 1) || (ON_BOARD(EAST(k)) && active[EAST(k)] == 1)) { if (IS_STONE(board[k])) mark_string(k, active, 2); else active[k] = 2; } } /* Remove invincible strings. No point adding their liberties and * neighbors. */ for (k = BOARDMIN; k < BOARDMAX; k++) { if (!ON_BOARD(k)) continue; if (IS_STONE(board[k]) && worm[k].invincible) active[k] = 0; } /* Expand empty to empty. */ for (k = BOARDMIN; k < BOARDMAX; k++) { if (IS_STONE(board[k]) || active[k] != 0) continue; if ((board[SOUTH(k)] == EMPTY && active[SOUTH(k)] == 2) || (board[WEST(k)] == EMPTY && active[WEST(k)] == 2) || (board[NORTH(k)] == EMPTY && active[NORTH(k)] == 2) || (board[EAST(k)] == EMPTY && active[EAST(k)] == 2)) active[k] = 3; } /* Add neighbors of active area so far. */ for (k = BOARDMIN; k < BOARDMAX; k++) { if (!ON_BOARD(k)) continue; if (active[k] != 0) continue; if ((ON_BOARD(SOUTH(k)) && active[SOUTH(k)] > 0 && active[SOUTH(k)] < 4) || (ON_BOARD(WEST(k)) && active[WEST(k)] > 0 && active[WEST(k)] < 4) || (ON_BOARD(NORTH(k)) && active[NORTH(k)] > 0 && active[NORTH(k)] < 4) || (ON_BOARD(EAST(k)) && active[EAST(k)] > 0 && active[EAST(k)] < 4)) active[k] = 4; } /* Also add the previously played stones to the active area. */ for (r = 0; r < stackp; r++) active[entry->stack[r]] = 5; for (k = BOARDMIN; k < BOARDMAX; k++) { if (!ON_BOARD(k)) continue; entry->board[k] = active[k] != 0 ? board[k] : GRAY; } if (0) { gprintf("%o Stored result in cache (entry %d):\n", persistent_reading_cache_size); print_persistent_reading_cache_entry(persistent_reading_cache_size); gprintf("%o Reading shadow was:\n"); draw_reading_shadow(); } persistent_reading_cache_size++; }