1463 lines
44 KiB
C
1463 lines
44 KiB
C
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||
|
* 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 <stdio.h>
|
||
|
#include <string.h>
|
||
|
#include <stdlib.h>
|
||
|
#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:
|
||
|
*/
|