/* Remove persistent cache entries which are no longer compatible with * the board. For efficient use of the cache, it's recommended to call * this function once per move, before starting the owl reading. It's * not required for correct operation though. */ void purge_persistent_owl_cache() { int k; static int last_purge_position_number = -1; gg_assert(stackp == 0); /* Never do this more than once per move. */ if (last_purge_position_number == position_number) return; else last_purge_position_number = position_number; for (k = 0; k < persistent_owl_cache_size; k++) { if (persistent_owl_cache[k].boardsize != board_size || !verify_stored_board(persistent_owl_cache[k].board)) { /* Move the last entry in the cache here and back up the loop * counter to redo the test at this position in the cache. */ if (k < persistent_owl_cache_size - 1) persistent_owl_cache[k] = persistent_owl_cache[persistent_owl_cache_size - 1]; k--; persistent_owl_cache_size--; } } }
int search_persistent_owl_cache(int routine, int apos, int bpos, int cpos, int *result, int *move, int *move2, int *certain) { int k; gg_assert(stackp == 0 || stackp == 1); for (k = 0; k < persistent_owl_cache_size; k++) { if (persistent_owl_cache[k].routine == routine && persistent_owl_cache[k].apos == apos && persistent_owl_cache[k].bpos == bpos && persistent_owl_cache[k].cpos == cpos && verify_stored_board(persistent_owl_cache[k].board)) { *result = persistent_owl_cache[k].result; if (move) *move = persistent_owl_cache[k].move; if (move2) *move2 = persistent_owl_cache[k].move2; if (certain) *certain = persistent_owl_cache[k].result_certain; TRACE_OWL_PERSISTENT_CACHE( "persistent owl cache hit: routine %s at %1m result %d\n", routine_to_string(routine), apos, bpos, cpos, result_to_string(persistent_owl_cache[k].result)); return 1; } } return 0; }
/* Full board matching in database for fuseki moves. Return 1 if any * pattern found. */ static int search_fuseki_database(int color) { struct fullboard_pattern *database; int q; int k; int best_fuseki_value; /* Disable matching after a certain number of stones are placed on * the board. */ if (stones_on_board(BLACK | WHITE) > MAX_FUSEKI_DATABASE_STONES) return 0; /* We only have databases for 9x9, 13x13 and 19x19. */ if (board_size == 9) database = fuseki9; else if (board_size == 13) database = fuseki13; else if (board_size == 19) database = fuseki19; else return 0; /* Do the matching. */ num_fuseki_moves = 0; fuseki_total_value = 0; fullboard_matchpat(fuseki_callback, color, database); /* No match. */ if (num_fuseki_moves == 0) return 0; /* Choose randomly with respect to relative weights for matched moves. */ /* Do not choose moves with less value than 20% of the best move */ best_fuseki_value = fuseki_value[0]; q = gg_rand() % fuseki_total_value; for (k = 0; k < num_fuseki_moves; k++) { if (fuseki_value[k] < (best_fuseki_value / 5)) break; q -= fuseki_value[k]; if (q < 0) break; } gg_assert(k < num_fuseki_moves); /* Give this move an arbitrary value of 75. The actual value doesn't * matter much since the intention is that we should play this move * whatever the rest of the analysis thinks. */ announce_move(fuseki_moves[k], 75, color); /* Also make sure the other considered moves can be seen in the * traces and in the output file. */ for (k = 0; k < num_fuseki_moves; k++) set_minimum_move_value(fuseki_moves[k], 74); return 1; }
/* Remove persistent cache entries which are no longer compatible with * the board. For efficient use of the cache, it's recommended to call * this function once per move, before starting the owl reading. It's * not required for correct operation though. */ static void purge_persistent_cache(struct persistent_cache *cache) { int k; int r; gg_assert(stackp == 0); /* Never do this more than once per move. */ if (cache->last_purge_position_number == position_number) return; else cache->last_purge_position_number = position_number; for (k = 0; k < cache->current_size; k++) { int played_moves = 0; int entry_ok = 1; struct persistent_cache_entry *entry = &(cache->table[k]); if (entry->boardsize != board_size) entry_ok = 0; else { for (r = 0; r < MAX_CACHE_DEPTH; r++) { int apos = entry->stack[r]; int color = entry->move_color[r]; if (apos == 0) break; if (board[apos] == EMPTY && trymove(apos, color, "purge_persistent_cache", 0)) played_moves++; else { entry_ok = 0; break; } } } if (!entry_ok || !verify_stored_board(entry->board)) { /* Move the last entry in the cache here and back up the loop * counter to redo the test at this position in the cache. */ if (0) gprintf("Purging entry %d from cache.\n", k); if (k < cache->current_size - 1) *entry = cache->table[cache->current_size - 1]; k--; cache->current_size--; } else { /* Reduce score here to penalize entries getting old. */ entry->score *= cache->age_factor; } while (played_moves > 0) { popgo(); played_moves--; } } }
/* Remove persistent cache entries which are no longer current. */ void purge_persistent_reading_cache() { int k; int r; static int last_purge_position_number = -1; gg_assert(stackp == 0); /* Never do this more than once per move. */ if (last_purge_position_number == position_number) return; else last_purge_position_number = position_number; for (k = 0; k < persistent_reading_cache_size; k++) { int played_moves = 0; int entry_ok = 1; if (persistent_reading_cache[k].boardsize != board_size) entry_ok = 0; else { for (r = 0; r < MAX_READING_CACHE_DEPTH; r++) { int apos = persistent_reading_cache[k].stack[r]; int color = persistent_reading_cache[k].move_color[r]; if (apos == 0) break; if (board[apos] == EMPTY && trymove(apos, color, "purge_persistent_reading_cache", 0, EMPTY, 0)) played_moves++; else { entry_ok = 0; break; } } } if (!entry_ok || !verify_stored_board(persistent_reading_cache[k].board)) { /* Move the last entry in the cache here and back up the loop * counter to redo the test at this position in the cache. */ if (0) gprintf("Purging entry %d from cache.\n", k); if (k < persistent_reading_cache_size - 1) persistent_reading_cache[k] = persistent_reading_cache[persistent_reading_cache_size - 1]; k--; persistent_reading_cache_size--; } while (played_moves > 0) { popgo(); played_moves--; } } }
static void free_handicap_callback(int anchor, int color, struct pattern *pattern, int ll, void *data) { int r = -1; int k; int number_of_stones = 1; /* Pick up the location of the move */ int move = AFFINE_TRANSFORM(pattern->move_offset, ll, anchor); UNUSED(data); /* Check how many stones are placed by the pattern. This must not be * larger than the number of remaining handicap stones. */ for (k = 0; k < pattern->patlen; k++) { if (pattern->patn[k].att == ATT_not) number_of_stones++; } if (number_of_stones > remaining_handicap_stones) return; /* If the pattern has a constraint, call the autohelper to see * if the pattern must be rejected. */ if (pattern->autohelper_flag & HAVE_CONSTRAINT) { if (!pattern->autohelper(ll, move, color, 0)) return; } if (number_of_matches < MAX_HANDICAP_MATCHES) { r = number_of_matches; number_of_matches++; } else { int least_value = handicap_matches[0].value + 1; for (k = 0; k < number_of_matches; k++) { if (handicap_matches[k].value < least_value) { r = k; least_value = handicap_matches[k].value; } } } gg_assert(r >= 0 && r < MAX_HANDICAP_MATCHES); handicap_matches[r].value = pattern->value; handicap_matches[r].anchor = anchor; handicap_matches[r].pattern = pattern; handicap_matches[r].ll = ll; }
char * hashdata_to_string(Hash_data *hashdata) { static char buffer[BUFFER_SIZE]; int n = 0; int k; for (k = 0; k < NUM_HASHVALUES; k++) { n += sprintf(buffer + n, HASHVALUE_PRINT_FORMAT, HASHVALUE_NUM_DIGITS, hashdata->hashval[k]); gg_assert(n < BUFFER_SIZE); } return buffer; }
static void showcapture(char *line) { int str; int move; gg_assert(line); str = string_to_location(board_size, line); if (str == NO_MOVE || board[str] == EMPTY) { printf("\ninvalid point!\n"); return; } if (attack(str, &move)) mprintf("\nSuccessful attack of %1m at %1m\n", str, move); else mprintf("\n%1m cannot be attacked\n", str); }
static void showdefense(char *line) { int str; int move; gg_assert(line); str = string_to_location(board_size, line); if (str == NO_MOVE || board[str] == EMPTY) { printf("\ninvalid point!\n"); return; } if (attack(str, NULL)) { if (find_defense(str, &move)) mprintf("\nSuccessful defense of %1m at %1m\n", str, move); else mprintf("\n%1m cannot be defended\n", str); } else mprintf("\nThere is no need to defend %1m\n", str); }
/* Based on the entries in the reading cache and their nodes field, * compute where the relatively most expensive tactical reading is * going on. */ void reading_hotspots(float values[BOARDMAX]) { int pos; int k; int sum_nodes = 0; for (pos = BOARDMIN; pos < BOARDMAX; pos++) values[pos] = 0.0; /* Compute the total number of nodes for the cached entries. */ for (k = 0; k < reading_cache.current_size; k++) sum_nodes += reading_cache.table[k].cost; if (sum_nodes <= 100) return; /* Loop over all entries and increase the value of vertices adjacent * to dragons involving expensive tactical reading. */ for (k = 0; k < reading_cache.current_size; k++) { struct persistent_cache_entry *entry = &(reading_cache.table[k]); float contribution = entry->cost / (float) sum_nodes; if (0) { gprintf("Reading hotspots: %d %1m %f\n", entry->routine, entry->apos, contribution); } switch (entry->routine) { case ATTACK: case FIND_DEFENSE: mark_string_hotspot_values(values, I(entry->apos), J(entry->apos), contribution); break; default: gg_assert(0); /* Shouldn't happen. */ break; } } }
/* Based on the entries in the reading cache and their nodes field, * compute where the relatively most expensive tactical reading is * going on. */ void reading_hotspots(float values[BOARDMAX]) { int m, n, k; int sum_nodes = 0; for (m = 0; m < board_size; m++) for (n = 0; n < board_size; n++) values[POS(m, n)] = 0.0; /* Compute the total number of nodes for the cached entries. */ for (k = 0; k < persistent_reading_cache_size; k++) sum_nodes += persistent_reading_cache[k].nodes; if (sum_nodes <= 100) return; /* Loop over all entries and increase the value of vertices adjacent * to dragons involving expensive tactical reading. */ for (k = 0; k < persistent_reading_cache_size; k++) { struct reading_cache *entry = &(persistent_reading_cache[k]); float contribution = entry->nodes / (float) sum_nodes; if (0) { gprintf("Reading hotspots: %d %1m %f\n", entry->routine, entry->str, contribution); } switch (entry->routine) { case ATTACK: case FIND_DEFENSE: mark_string_hotspot_values(values, I(entry->str), J(entry->str), contribution); break; default: gg_assert(0); /* Shouldn't happen. */ break; } } }
/* * Sets up free placement handicap stones, returning the number of * placed handicap stones and also setting the global variable * handicap to the same value. */ int place_free_handicap(int desired_handicap) { gg_assert(desired_handicap == 0 || desired_handicap >= 2); if (desired_handicap == 0) { handicap = 0; return 0; } total_handicap_stones = desired_handicap; remaining_handicap_stones = desired_handicap; /* First place black stones in the four corners to enable the * pattern matching scheme. */ add_stone(POS(0, 0), BLACK); add_stone(POS(0, board_size - 1), BLACK); add_stone(POS(board_size - 1, 0), BLACK); add_stone(POS(board_size - 1, board_size - 1), BLACK); /* Find and place free handicap stones by pattern matching. */ while (remaining_handicap_stones > 0) { if (!find_free_handicap_pattern()) break; } /* Remove the artificial corner stones. */ remove_stone(POS(0, 0)); remove_stone(POS(0, board_size - 1)); remove_stone(POS(board_size - 1, 0)); remove_stone(POS(board_size - 1, board_size - 1)); /* Find and place additional free handicap stones by the aftermath * algorithm. */ while (remaining_handicap_stones > 0) { int move; /* Call genmove_conservative() in order to prepare the engine for * an aftermath_genmove() call. We discard the genmove result. */ genmove_conservative(BLACK, NULL); move = aftermath_genmove(BLACK, 0, NULL); if (move != PASS_MOVE) { add_stone(move, BLACK); remaining_handicap_stones--; } else break; } /* Set handicap to the number of actually placed stones. */ handicap = desired_handicap - remaining_handicap_stones; /* Reset these to invalid values, so that improper use of handicap * helper functions can be detected. */ total_handicap_stones = -1; remaining_handicap_stones = -1; return handicap; }
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 (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; } } } }
void tt_update(Transposition_table *table, enum routine_id routine, int target1, int target2, int remaining_depth, Hash_data *extra_hash, int value1, int value2, int move) { Hash_data hashval; Hashentry *entry; Hashnode *deepest; Hashnode *newest; unsigned int data; /* Get routine costs definitions from liberty.h. */ static const int routine_costs[] = { ROUTINE_COSTS }; gg_assert(routine_costs[NUM_CACHE_ROUTINES] == -1); /* Sanity check. */ if (remaining_depth < 0 || remaining_depth > HN_MAX_REMAINING_DEPTH) return; /* Get the combined hash value. */ calculate_hashval_for_tt(&hashval, routine, target1, target2, extra_hash); data = hn_create_data(remaining_depth, value1, value2, move, routine_costs[routine]); /* Get the entry and nodes. */ entry = &table->entries[hashdata_remainder(hashval, table->num_entries)]; deepest = &entry->deepest; newest = &entry->newest; /* See if we found an already existing node. */ if (hashdata_is_equal(hashval, deepest->key) && remaining_depth >= (int) hn_get_remaining_depth(deepest->data)) { /* Found deepest */ deepest->data = data; } else if (hashdata_is_equal(hashval, newest->key) && remaining_depth >= (int) hn_get_remaining_depth(newest->data)) { /* Found newest */ newest->data = data; /* If newest has become deeper than deepest, then switch them. */ if (hn_get_remaining_depth(newest->data) > hn_get_remaining_depth(deepest->data)) { Hashnode temp; temp = *deepest; *deepest = *newest; *newest = temp; } } else if (hn_get_total_cost(data) > hn_get_total_cost(deepest->data)) { if (hn_get_total_cost(newest->data) < hn_get_total_cost(deepest->data)) *newest = *deepest; deepest->key = hashval; deepest->data = data; } else { /* Replace newest. */ newest->key = hashval; newest->data = data; } stats.read_result_entered++; table->is_clean = 0; }
static void play_aftermath(int color) { 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); 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); } }
/* 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 *move, int color) { float val; int save_verbose; float upper_bound, lower_bound; float our_score; /* no move is found yet. */ *move = NO_MOVE; val = -1; /* Prepare pattern matcher and reading code. */ reset_engine(); /* Find out information about the worms and dragons. */ examine_position(EXAMINE_ALL); /* Make a score estimate. This can be used in later stages of the * move generation. If we are ahead, we can play safely and if * we are behind, we have to play more daringly. */ estimate_score(&upper_bound, &lower_bound); if (verbose || showscore) { if (lower_bound == upper_bound) gprintf("\nScore estimate: %s %f\n", lower_bound > 0 ? "W " : "B ", gg_abs(lower_bound)); else gprintf("\nScore estimate: %s %f to %s %f\n", lower_bound > 0 ? "W " : "B ", gg_abs(lower_bound), upper_bound > 0 ? "W " : "B ", gg_abs(upper_bound)); fflush(stderr); } /* The score will be used to determine when we are safely * ahead. So we want the most conservative score. */ if (color == WHITE) our_score = lower_bound; else our_score = -upper_bound; 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, &val, color, 0.0, our_score, NULL)) TRACE("Move generation likes %1m with value %f\n", *move, val); gg_assert(stackp == 0); /* If no move is found then pass. */ if (val < 0.0) { TRACE("I pass.\n"); *move = NO_MOVE; } else TRACE("reduced_genmove() recommends %1m with value %f\n", *move, val); return val; }
int free_handicap_remaining_stones() { gg_assert(remaining_handicap_stones >= 0); return remaining_handicap_stones; }
/* Based on the entries in the owl cache and their tactical_nodes * field, compute where the relatively most expensive owl reading is * going on. */ void owl_hotspots(float values[BOARDMAX]) { int pos; int k, r; int libs[MAXLIBS]; int liberties; int sum_tactical_nodes = 0; /* Don't bother checking out of board. Set values[] to zero there too. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) values[pos] = 0.0; /* Compute the total number of tactical nodes for the cached entries. */ for (k = 0; k < owl_cache.current_size; k++) sum_tactical_nodes += owl_cache.table[k].score; if (sum_tactical_nodes <= 100) return; /* Loop over all entries and increase the value of vertices adjacent * to dragons involving expensive owl reading. */ for (k = 0; k < owl_cache.current_size; k++) { struct persistent_cache_entry *entry = &(owl_cache.table[k]); float contribution = entry->score / (float) sum_tactical_nodes; if (debug & DEBUG_PERSISTENT_CACHE) { gprintf("Owl hotspots: %d %1m %f\n", entry->routine, entry->apos, contribution); } switch (entry->routine) { case OWL_ATTACK: case OWL_THREATEN_ATTACK: case OWL_DEFEND: case OWL_THREATEN_DEFENSE: mark_dragon_hotspot_values(values, entry->apos, contribution, entry->board); break; case OWL_DOES_DEFEND: case OWL_DOES_ATTACK: case OWL_CONFIRM_SAFETY: mark_dragon_hotspot_values(values, entry->bpos, contribution, entry->board); break; case OWL_CONNECTION_DEFENDS: mark_dragon_hotspot_values(values, entry->bpos, contribution, entry->board); mark_dragon_hotspot_values(values, entry->cpos, contribution, entry->board); break; case OWL_SUBSTANTIAL: /* Only consider the liberties of (apos). */ liberties = findlib(entry->apos, MAXLIBS, libs); for (r = 0; r < liberties; r++) values[libs[r]] += contribution; break; default: gg_assert(0); /* Shouldn't happen. */ break; } } }
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++; }
/* Capture as many strings of the given color as we can. Played stones * are left on the board and the number of played stones is returned. * Strings marked in the exceptions array are excluded from capturing * attempts. If all non-excepted strings are successfully captured, * *none_invincible is set to one. Set none_invincible to NULL if you * don't need that information. */ static int capture_non_invincible_strings(int color, int exceptions[BOARDMAX], int *none_invincible) { int other = OTHER_COLOR(color); int something_captured = 1; /* To get into the first turn of the loop. */ int string_found = 0; int moves_played = 0; int save_moves; int libs[MAXLIBS]; int liberties; int pos; int k; while (something_captured) { /* Nothing captured so far in this turn of the loop. */ something_captured = 0; /* Is there something left to try to capture? */ string_found = 0; /* Visit all friendly strings on the board. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] != color || find_origin(pos) != pos) continue; if (exceptions && exceptions[pos]) continue; string_found = 1; /* Try to capture the string at pos. */ liberties = findlib(pos, MAXLIBS, libs); save_moves = moves_played; for (k = 0; k < liberties; k++) { if (trymove(libs[k], other, "unconditional_life", pos)) moves_played++; } /* Successful if already captured or a single liberty remains. * Otherwise we must rewind and take back the last batch of moves. */ if (board[pos] == EMPTY) something_captured = 1; else if (findlib(pos, 2, libs) == 1) { /* Need to use tryko as a defense against the extreme case * when the only opponent liberty that is not suicide is an * illegal ko capture, like in this 5x5 position: * +-----+ * |.XO.O| * |XXOO.| * |X.XOO| * |XXOO.| * |.XO.O| * +-----+ */ int success = tryko(libs[0], other, "unconditional_life"); gg_assert(success); moves_played++; something_captured = 1; } else while (moves_played > save_moves) { popgo(); moves_played--; } } } if (none_invincible) *none_invincible = !string_found; return moves_played; }
/* By unconditional status analysis we can statically find some moves * which there is never any need to play. Those belong to three * different categories: * * 1. A move on a vertex which is already unconditional territory for * either color. * 2. A move which after having been made ends up as unconditional * territory for the opponent. * 3. If a move at vertex A makes vertex B become unconditional * territory, there is no need to consider a move at B, since A has * all the positive effects that B would have. * * Moves in categories 1 and 2 are never any better than passing and * often worse (with territory scoring always worse). Moves in * category three can be either better or worse than passing, but it's * always true that a move at A is at least as good as a move at B. * Occasionally they are identically good (A makes B unconditional * territory and B makes A unconditional territory) but there is never * any need to analyze both. * * In meaningless_black_moves[] and meaningless_white_moves[] a value * of -1 means it is not meaningless, 0 (NO_MOVE) means it belongs to * category 1 or 2, and a value greater than zero points to the * preferred move in category 3. * * The parameter unconditional_territory should contain the result of * calling unconditional_life() in the original position. Meaningless * moves are computed for the given color. */ void find_unconditionally_meaningless_moves(int unconditional_territory[BOARDMAX], int color) { int *meaningless_moves; int other = OTHER_COLOR(color); int friendly_unconditional[BOARDMAX]; int opponent_unconditional[BOARDMAX]; int pos; int pos2; gg_assert(color == BLACK || color == WHITE); if (color == BLACK) meaningless_moves = meaningless_black_moves; else meaningless_moves = meaningless_white_moves; /* Initialize meaningless_moves and detect moves of category 1, but * only for own unconditional territory. * * FIXME: We would save some time by detecting all category 1 moves * here but then we would need to have the initial unconditional * territory for the opponent as well. This can of course be done, * the question is how we get it in the nicest way. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) if (board[pos] == EMPTY) { if (unconditional_territory[pos]) meaningless_moves[pos] = NO_MOVE; else meaningless_moves[pos] = -1; } for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] != EMPTY || meaningless_moves[pos] != -1) continue; if (!tryko(pos, color, "find_unconditionally_meaningless_moves")) continue; unconditional_life(opponent_unconditional, other); if (opponent_unconditional[pos]) { /* Move of category 1 or 2. */ meaningless_moves[pos] = NO_MOVE; } else { unconditional_life(friendly_unconditional, color); if (friendly_unconditional[pos]) for (pos2 = BOARDMIN; pos2 < BOARDMAX; pos2++) if (board[pos2] == EMPTY && meaningless_moves[pos2] == -1 && friendly_unconditional[pos2]) { /* Move of category 3. */ meaningless_moves[pos2] = pos; } } popgo(); } /* Meaningless moves of category 3 may have been found in multiple * steps. Normalize to the final replacement move. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) if (board[pos] == EMPTY && meaningless_moves[pos] > 0) while (meaningless_moves[meaningless_moves[pos]] > 0) meaningless_moves[pos] = meaningless_moves[meaningless_moves[pos]]; }
int free_handicap_total_stones() { gg_assert(total_handicap_stones >= 0); return total_handicap_stones; }
/* Allocate the actual cache table. */ static void init_cache(struct persistent_cache *cache) { cache->table = malloc(cache->max_size*sizeof(struct persistent_cache_entry)); gg_assert(cache->table); }