/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ * This is GNU Go, a Go program. Contact gnugo@gnu.org, or see * * http://www.gnu.org/software/gnugo/ for more information. * * * * Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, * * 2008 and 2009 by the Free Software Foundation. * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation - version 3 or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License in file COPYING for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02111, USA. * \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* * The code in this file implements persistent caching. * * The idea is that reading results are stored together with an * "active area", i.e. the part of the board having an effect on the * reading result. Thus if only moves outside of the active area has * been played since the result was stored, it can be reused. * * The active areas are not known exactly but are estimated * heuristically. The effects are that too large an active area * reduces the efficiency of the caching scheme while too small an * active area may cause an incorrect read result to be retrieved from * the cache. * * Persistent caching has so far been implemented for tactical reading, * owl reading, connection reading and break-in reading (with semeai * reading planned for the future). * * The hotspot functions are intended to locate where the most * expensive reading of either type is going on. This information can * be estimated from the contents of the persistent caches since the * most expensive readings are stored there with full information of * spent reading nodes, involved strings or dragons, and active areas. */ #include "gnugo.h" #include #include #include #include "liberty.h" #include "cache.h" /* ================================================================ */ /* Data structures */ /* ================================================================ */ /* Used in active area. */ #define HIGH_LIBERTY_BIT 4 #define HIGH_LIBERTY_BIT2 8 #define MAX_READING_CACHE_DEPTH 5 #define MAX_READING_CACHE_SIZE 100 #define MAX_OWL_CACHE_DEPTH 0 #define MAX_OWL_CACHE_SIZE 150 #define MAX_CONNECTION_CACHE_DEPTH 5 #define MAX_CONNECTION_CACHE_SIZE 100 #define MAX_BREAKIN_CACHE_DEPTH 1 #define MAX_BREAKIN_CACHE_SIZE 150 #define MAX_SEMEAI_CACHE_DEPTH 0 #define MAX_SEMEAI_CACHE_SIZE 150 #define MAX_CACHE_DEPTH 5 /* We use the same data structure for all of the caches. Some of the entries * below are unused for some of the caches. */ struct persistent_cache_entry { int boardsize; int movenum; Intersection board[BOARDMAX]; int stack[MAX_CACHE_DEPTH]; int move_color[MAX_CACHE_DEPTH]; enum routine_id routine; int apos; /* first input coordinate */ int bpos; /* second input coordinate */ int cpos; /* third input coordinate */ int color; /* Move at (cpos) by (color) in analyze_semeai_after_move() */ Hash_data goal_hash; /* hash of the goals in break-in and semeai reading */ int result; int result2; int result_certain; int remaining_depth; int node_limit; int move; /* first result coordinate */ int move2;/* second result coordinate */ int cost; /* Usually no. of tactical nodes spent on this reading result. */ int score; /* Heuristic guess of the worth of the cache entry. */ }; /* Callback function that implements the computation of the active area. * This function has to be provided by each cache. */ typedef void (*compute_active_area_fn)(struct persistent_cache_entry *entry, const signed char goal[BOARDMAX], int goal_color); struct persistent_cache { const int max_size; /* Size of above array. */ const int max_stackp; /* Don't store positions with stackp > max_stackp. */ const float age_factor; /* Reduce value of old entries with this factor. */ const char *name; /* For debugging purposes. */ const compute_active_area_fn compute_active_area; struct persistent_cache_entry *table; /* Array of actual results. */ int current_size; /* Current number of entries. */ int last_purge_position_number; }; static void compute_active_owl_area(struct persistent_cache_entry *entry, const signed char goal[BOARDMAX], int goal_color); static void compute_active_semeai_area(struct persistent_cache_entry *entry, const signed char goal[BOARDMAX], int dummy); static void compute_active_reading_area(struct persistent_cache_entry *entry, const signed char reading_shadow[BOARDMAX], int dummy); static void compute_active_connection_area(struct persistent_cache_entry *entry, const signed char connection_shadow[BOARDMAX], int goal_color); static void compute_active_breakin_area(struct persistent_cache_entry *entry, const signed char breakin_shadow[BOARDMAX], int dummy); static struct persistent_cache reading_cache = { MAX_READING_CACHE_SIZE, MAX_READING_CACHE_DEPTH, 1.0, "reading cache", compute_active_reading_area, NULL, 0, -1 }; static struct persistent_cache connection_cache = { MAX_CONNECTION_CACHE_SIZE, MAX_CONNECTION_CACHE_DEPTH, 1.0, "connection cache", compute_active_connection_area, NULL, 0, -1 }; static struct persistent_cache breakin_cache = { MAX_BREAKIN_CACHE_SIZE, MAX_BREAKIN_CACHE_DEPTH, 0.75, "breakin cache", compute_active_breakin_area, NULL, 0, -1 }; static struct persistent_cache owl_cache = { MAX_OWL_CACHE_SIZE, MAX_OWL_CACHE_DEPTH, 1.0, "owl cache", compute_active_owl_area, NULL, 0, -1 }; static struct persistent_cache semeai_cache = { MAX_SEMEAI_CACHE_SIZE, MAX_SEMEAI_CACHE_DEPTH, 0.75, "semeai cache", compute_active_semeai_area, NULL, 0, -1 }; /* ================================================================ */ /* Common helper functions. */ static void draw_active_area(Intersection board[BOARDMAX], int apos) { int i, j, ii; int c = ' '; int cw = (apos == NO_MOVE) ? 'O' : 'o'; int cb = (apos == NO_MOVE) ? 'X' : 'x'; start_draw_board(); for (i = 0; i < board_size; i++) { ii = board_size - i; fprintf(stderr, "\n%2d", ii); for (j = 0; j < board_size; j++) { int pos = POS(i, j); if (board[pos] == EMPTY) c = '.'; else if (board[pos] == WHITE) c = cw; else if ((board[pos] & 3) == WHITE) c = 'O'; else if (board[pos] == BLACK) c = cb; else if ((board[pos] & 3) == BLACK) c = 'X'; if (board[pos] == GRAY) c = '?'; if (pos == apos) fprintf(stderr, "[%c", c); else if (j > 0 && POS(i, j-1) == apos) fprintf(stderr, "]%c", c); else fprintf(stderr, " %c", c); } fprintf(stderr, " %d", ii); } end_draw_board(); } /* Returns 1 if the stored board is compatible with the current board, * 0 otherwise. */ static int verify_stored_board(Intersection p[BOARDMAX]) { int pos; for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (!ON_BOARD(pos)) continue; else if (p[pos] == GRAY) continue; else if ((p[pos] & 3) != board[pos]) return 0; else if (!(p[pos] & (HIGH_LIBERTY_BIT | HIGH_LIBERTY_BIT2))) continue; else if (((p[pos] & HIGH_LIBERTY_BIT) && countlib(pos) <= 4) || (p[pos] & HIGH_LIBERTY_BIT2 && countlib(pos) <= 3)) return 0; } return 1; } /* Prints out all relevant information for a cache entry, and prints * a board showing the active area. */ static void print_persistent_cache_entry(struct persistent_cache_entry *entry) { int r; gprintf("%omovenum = %d\n", entry->movenum); gprintf("%oscore = %d\n", entry->score); gprintf("%ocost = %d\n", entry->cost); gprintf("%oroutine = %s\n", routine_id_to_string(entry->routine)); gprintf("%oapos = %1m\n", entry->apos); if (entry->bpos != NO_MOVE) gprintf("%obpos = %1m\n", entry->bpos); if (entry->cpos != NO_MOVE) gprintf("%ocpos = %1m\n", entry->cpos); gprintf("%oresult = %s\n", result_to_string(entry->result)); if (entry->result2 != 0) gprintf("%oresult2 = %s\n", result_to_string(entry->result2)); if (entry->result_certain != -1) gprintf("%oresult_certain = %d\n", entry->result_certain); if (entry->node_limit != -1) gprintf("%onode_limit = %d\n", entry->node_limit); if (entry->move != NO_MOVE) gprintf("%omove = %1m\n", entry->move); if (entry->move2 != NO_MOVE) gprintf("%omove2 = %1m\n", entry->move2); for (r = 0; r < MAX_CACHE_DEPTH; r++) { if (entry->stack[r] == 0) break; gprintf("%ostack[%d] = %C %1m\n", r, entry->move_color[r], entry->stack[r]); } draw_active_area(entry->board, entry->apos); } /* To keep GCC happy and have the function included in the * gnugo executable. Can be used from gdb. */ void print_persistent_cache(struct persistent_cache *cache); /* Can be used from gdb. */ void print_persistent_cache(struct persistent_cache *cache) { int k; gprintf("Entire content of %s:\n", cache->name); for (k = 0; k < cache->current_size; k++) print_persistent_cache_entry(cache->table + k); } /* ================================================================ */ /* Core functions. */ /* ================================================================ */ /* The static functions below implement the core infrastructure of the * persistent caches. Each cache only has to provide a function * computing the active area, and wrappers around the search_.. and store_.. * function below. */ /* 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--; } } } /* Find a cache entry matching the data given in the parameters. * Important: We assume that unused parameters are normalized to NO_MOVE * when storing or retrieving, so that we can ignore them here. */ static struct persistent_cache_entry * find_persistent_cache_entry(struct persistent_cache *cache, enum routine_id routine, int apos, int bpos, int cpos, int color, Hash_data *goal_hash, int node_limit) { int k; for (k = 0; k < cache->current_size; k++) { struct persistent_cache_entry *entry = cache->table + k; if (entry->routine == routine && entry->apos == apos && entry->bpos == bpos && entry->cpos == cpos && entry->color == color && depth - stackp <= entry->remaining_depth && (entry->node_limit >= node_limit || entry->result_certain) && (goal_hash == NULL || hashdata_is_equal(entry->goal_hash, *goal_hash)) && verify_stored_board(entry->board)) return entry; } return NULL; } /* Search through a persistent cache. Returns 0 if no matching entry was * found; returns 1 and sets the relevant return values otherwise. See * comment above find_persistent_cache_entry() about unused parameters. */ static int search_persistent_cache(struct persistent_cache *cache, enum routine_id routine, int apos, int bpos, int cpos, int color, Hash_data *goal_hash, int node_limit, int *result, int *result2, int *move, int *move2, int *certain) { /* Try to find entry. */ struct persistent_cache_entry *entry; entry = find_persistent_cache_entry(cache, routine, apos, bpos, cpos, color, goal_hash, node_limit); if (entry == NULL) return 0; /* Set return values. */ *result = entry->result; if (result2) *result2 = entry->result2; if (move) *move = entry->move; if (move2) *move2 = entry->move2; if (certain) *certain = entry->result_certain; /* Increase score for entry. */ entry->score += entry->cost; if (debug & DEBUG_PERSISTENT_CACHE) { gprintf("%oRetrieved position from %s:\n", cache->name); print_persistent_cache_entry(entry); } /* FIXME: This is an ugly hack. */ if (strcmp(cache->name, "reading cache") == 0 && (debug & DEBUG_READING_PERFORMANCE) && entry->cost >= MIN_READING_NODES_TO_REPORT) { if (entry->result != 0) gprintf("%o%s %1m = %d %1m, cached (%d nodes) ", routine == ATTACK ? "attack" : "defend", apos, entry->result, entry->move, entry->cost); else gprintf("%o%s %1m = %d, cached (%d nodes) ", routine == ATTACK ? "attack" : "defend", apos, entry->result, entry->cost); dump_stack(); } return 1; } /* Generic function that tries to store a cache entry. If the cache * is full, we delete the lowest scoring entry. * * Unused parameters have to be normalized to NO_MOVE by the calling * function. */ static void store_persistent_cache(struct persistent_cache *cache, enum routine_id routine, int apos, int bpos, int cpos, int color, Hash_data *goal_hash, int result, int result2, int move, int move2, int certain, int node_limit, int cost, const signed char goal[BOARDMAX], int goal_color) { int r; struct persistent_cache_entry *entry; if (stackp > cache->max_stackp) return; /* If cache is still full, consider kicking out an old entry. */ if (cache->current_size == cache->max_size) { int worst_entry = -1; int worst_score = cost; int k; for (k = 0; k < cache->current_size; k++) { if (cache->table[k].score < worst_score) { worst_score = cache->table[k].score; worst_entry = k; } } if (worst_entry != -1) { /* Move the last entry in the cache here to make space. */ if (worst_entry < cache->current_size - 1) cache->table[worst_entry] = cache->table[cache->current_size - 1]; cache->current_size--; } else return; } entry = &(cache->table[cache->current_size]); entry->boardsize = board_size; entry->routine = routine; entry->apos = apos; entry->bpos = bpos; entry->cpos = cpos; entry->color = color; if (goal_hash) entry->goal_hash = *goal_hash; entry->result = result; entry->result2 = result2; entry->result_certain = certain; entry->node_limit = node_limit; entry->remaining_depth = depth - stackp; entry->move = move; entry->move2 = move2; entry->score = cost; entry->cost = cost; entry->movenum = movenum; for (r = 0; r < MAX_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. */ cache->compute_active_area(&(cache->table[cache->current_size]), goal, goal_color); cache->current_size++; if (debug & DEBUG_PERSISTENT_CACHE) { gprintf("%oEntered position in %s:\n", cache->name); print_persistent_cache_entry(entry); gprintf("%oCurrent size: %d\n", cache->current_size); } } /* ================================================================ */ /* Interface functions relevant to all caches. */ /* ================================================================ */ /* 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); } /* Initializes all persistent caches. * Needs to be called only once at startup. */ void persistent_cache_init() { init_cache(&reading_cache); init_cache(&breakin_cache); init_cache(&connection_cache); init_cache(&owl_cache); init_cache(&semeai_cache); } /* Discards all persistent cache entries. */ void clear_persistent_caches() { reading_cache.current_size = 0; connection_cache.current_size = 0; breakin_cache.current_size = 0; owl_cache.current_size = 0; semeai_cache.current_size = 0; } /* Discards all persistent cache entries that are no longer useful. * Should be called once per move for optimal performance (but is not * necessary for proper operation). */ void purge_persistent_caches() { purge_persistent_cache(&reading_cache); purge_persistent_cache(&connection_cache); purge_persistent_cache(&breakin_cache); purge_persistent_cache(&owl_cache); purge_persistent_cache(&semeai_cache); } /* ================================================================ */ /* Tactical reading functions */ /* ================================================================ */ /* Look for a valid read result in the persistent cache. * Return 1 if found, 0 otherwise. */ int search_persistent_reading_cache(enum routine_id routine, int str, int *result, int *move) { return search_persistent_cache(&reading_cache, routine, str, NO_MOVE, NO_MOVE, EMPTY, NULL, -1, result, NULL, move, NULL, NULL); } /* Store a new read result in the persistent cache. */ void store_persistent_reading_cache(enum routine_id routine, int str, int result, int move, int nodes) { store_persistent_cache(&reading_cache, routine, str, NO_MOVE, NO_MOVE, EMPTY, NULL, result, NO_MOVE, move, NO_MOVE, -1, -1, nodes, shadow, EMPTY); } static void compute_active_reading_area(struct persistent_cache_entry *entry, const signed 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]; 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])) 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; } } /* 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; } } } } } /* 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; } } } /* ================================================================ */ /* Connection reading functions */ /* ================================================================ */ /* Look for a valid read result in the persistent connection cache. * Return 1 if found, 0 otherwise. */ int search_persistent_connection_cache(enum routine_id routine, int str1, int str2, int *result, int *move) { return search_persistent_cache(&connection_cache, routine, str1, str2, NO_MOVE, EMPTY, NULL, connection_node_limit, result, NULL, move, NULL, NULL); } /* Store a new connection result in the persistent cache. */ void store_persistent_connection_cache(enum routine_id routine, int str1, int str2, int result, int move, int tactical_nodes, signed char connection_shadow[BOARDMAX]) { store_persistent_cache(&connection_cache, routine, str1, str2, NO_MOVE, EMPTY, NULL, result, NO_MOVE, move, NO_MOVE, -1, connection_node_limit, tactical_nodes, connection_shadow, EMPTY); } /* Computes the active area for the current board position and the * connection read result that has just been stored in *entry. */ static void compute_active_connection_area(struct persistent_cache_entry *entry, const signed char connection_shadow[BOARDMAX], int dummy) { int pos; int k, r; signed char active[BOARDMAX]; int other = OTHER_COLOR(board[entry->apos]); UNUSED(dummy); /* Remains to set the board. We let the active area be * the two strings to connect + * the connection shadow + * 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] = connection_shadow[pos]; mark_string(entry->apos, active, 1); mark_string(entry->bpos, 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 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) { 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++) { 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++) mark_string(adjs2[s], active, -1); } } } } /* Also add the previously played stones to the active area. */ for (r = 0; r < stackp; r++) active[entry->stack[r]] = 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; } } /* ================================================================ */ /* Break-in reading functions */ /* ================================================================ */ /* Look for a valid read result in the persistent breakin cache. * Return 1 if found, 0 otherwise. */ int search_persistent_breakin_cache(enum routine_id routine, int str, Hash_data *goal_hash, int node_limit, int *result, int *move) { return search_persistent_cache(&breakin_cache, routine, str, NO_MOVE, NO_MOVE, EMPTY, goal_hash, node_limit, result, NULL, move, NULL, NULL); } /* Store a new breakin result in the persistent cache. */ void store_persistent_breakin_cache(enum routine_id routine, int str, Hash_data *goal_hash, int result, int move, int tactical_nodes, int breakin_node_limit, signed char breakin_shadow[BOARDMAX]) { store_persistent_cache(&breakin_cache, routine, str, NO_MOVE, NO_MOVE, EMPTY, goal_hash, result, NO_MOVE, move, NO_MOVE, -1, breakin_node_limit, tactical_nodes, breakin_shadow, EMPTY); } /* 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 signed 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]; 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 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) { 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++) { 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++) mark_string(adjs2[s], active, -1); } } } } for (pos = BOARDMIN; pos < BOARDMAX; pos++) { Intersection 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; } } /* ================================================================ */ /* Owl reading functions */ /* ================================================================ */ int search_persistent_owl_cache(enum routine_id routine, int apos, int bpos, int cpos, int *result, int *move, int *move2, int *certain) { return search_persistent_cache(&owl_cache, routine, apos, bpos, cpos, EMPTY, NULL, owl_node_limit, result, NULL, move, move2, certain); } void store_persistent_owl_cache(enum routine_id routine, int apos, int bpos, int cpos, int result, int move, int move2, int certain, int tactical_nodes, signed char goal[BOARDMAX], int goal_color) { store_persistent_cache(&owl_cache, routine, apos, bpos, cpos, EMPTY, NULL, result, NO_MOVE, move, move2, certain, owl_node_limit, tactical_nodes, goal, goal_color); } /* This function is used by owl and semai active area computation. We assume * that (goal) marks a dragon of color (goal_color), i.e. all intersections * in the goal that are not a stone of this color are ignored. The calling * functions must have zeroed the active area, and is allowed to preset * some intersection to be active. */ static void compute_active_owl_type_area(const signed char goal[BOARDMAX], int goal_color, signed char active[BOARDMAX]) { int k, r; int pos; int other = OTHER_COLOR(goal_color); /* 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) && goal[pos]) active[pos] = 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, (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) { 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++) { 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++) mark_string(adjs2[s], active, -1); } } } } } static void compute_active_owl_area(struct persistent_cache_entry *entry, const signed char goal[BOARDMAX], int goal_color) { int pos; signed char active[BOARDMAX]; memset(active, 0, BOARDMAX); /* 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; compute_active_owl_type_area(goal, goal_color, active); 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; } } /* ================================================================ */ /* Semeai reading functions */ /* ================================================================ */ /* Look for stored result in semeai cache. Returns 1 if result found, 0 * otherwise. */ int search_persistent_semeai_cache(enum routine_id routine, int apos, int bpos, int cpos, int color, Hash_data *goal_hash, int *resulta, int *resultb, int *move, int *certain) { return search_persistent_cache(&semeai_cache, routine, apos, bpos, cpos, color, goal_hash, semeai_node_limit, resulta, resultb, move, NULL, certain); } /* Store a new read result in the persistent semeai cache. */ void store_persistent_semeai_cache(enum routine_id routine, int apos, int bpos, int cpos, int color, Hash_data *goal_hash, int resulta, int resultb, int move, int certain, int tactical_nodes, signed char goala[BOARDMAX], signed char goalb[BOARDMAX]) { signed char goal[BOARDMAX]; int pos; for (pos = BOARDMIN; pos < BOARDMAX; pos++) if (ON_BOARD(pos)) goal[pos] = goala[pos] || goalb[pos]; store_persistent_cache(&semeai_cache, routine, apos, bpos, cpos, color, goal_hash, resulta, resultb, move, NO_MOVE, certain, semeai_node_limit, tactical_nodes, goal, EMPTY); } static void compute_active_semeai_area(struct persistent_cache_entry *entry, const signed char goal[BOARDMAX], int dummy) { int pos; signed char active_b[BOARDMAX]; signed char active_w[BOARDMAX]; UNUSED(dummy); memset(active_b, 0, BOARDMAX); memset(active_w, 0, BOARDMAX); /* Add critical move to the active area. */ if (ON_BOARD1(entry->move)) { active_b[entry->move] = 1; active_w[entry->move] = 1; } if (ON_BOARD1(entry->cpos)) { active_b[entry->cpos] = 1; active_w[entry->cpos] = 1; } compute_active_owl_type_area(goal, BLACK, active_b); compute_active_owl_type_area(goal, WHITE, active_w); for (pos = BOARDMIN; pos < BOARDMAX; pos++) { int value = board[pos]; if (!ON_BOARD(pos)) continue; if (!active_b[pos] && !active_w[pos]) value = GRAY; else if (IS_STONE(board[pos]) && countlib(pos) > 4 && (active_b[pos] > 0 || active_w[pos] > 0)) value |= HIGH_LIBERTY_BIT; entry->board[pos] = value; } } /* Helper for the owl_hotspots() function below. */ static void mark_dragon_hotspot_values(float values[BOARDMAX], int dr, float contribution, Intersection active_board[BOARDMAX]) { int pos; int k; if (!IS_STONE(board[dr])) return; 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; } } } /* 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). */ if (!IS_STONE(board[entry->apos])) continue; 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; } } } /* * Local Variables: * tab-width: 8 * c-basic-offset: 2 * End: */