pachi_py/pachi/patternscan/patternscan.c (214 lines of code) (raw):
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include "board.h"
#include "debug.h"
#include "engine.h"
#include "move.h"
#include "patternscan/patternscan.h"
#include "pattern.h"
#include "patternsp.h"
#include "random.h"
/* The engine has two modes:
*
* * gen_spat_dict=1: patterns.spat file is generated with a list of all
* encountered spatials
*
* * gen_spat_dict=0,no_pattern_match=1: all encountered patterns are
* listed on output on each move; the general format is
* [(winpattern)]
* but with competition=1 it is
* [(winpattern)] [(witnesspattern0) (witnesspattern1) ...]
* and with spat_split_sizes=1 even
* [(winpattern0) (winpattern1) ...] [(witpattern0) (witpattern1) ...]
*/
/* Internal engine state. */
struct patternscan {
int debug_level;
struct pattern_setup pat;
bool competition;
bool spat_split_sizes;
int color_mask;
bool no_pattern_match;
bool gen_spat_dict;
/* Minimal number of occurences for spatial to be saved. */
int spat_threshold;
/* Number of loaded spatials; checkpoint for saving new sids
* in case gen_spat_dict is enabled. */
int loaded_spatials;
/* Book-keeping of spatial occurence count. */
int gameno;
unsigned int nscounts;
int *scounts;
int *sgameno;
};
static void
process_pattern(struct patternscan *ps, struct board *b, struct move *m, char **str)
{
/* First, store the spatial configuration in dictionary
* if applicable. */
if (ps->gen_spat_dict && !is_pass(m->coord)) {
struct spatial s;
spatial_from_board(&ps->pat.pc, &s, b, m);
int dmax = s.dist;
for (int d = ps->pat.pc.spat_min; d <= dmax; d++) {
s.dist = d;
unsigned int sid = spatial_dict_put(ps->pat.pc.spat_dict, &s, spatial_hash(0, &s));
assert(sid > 0);
#define SCOUNTS_ALLOC 1048576 // Allocate space in 1M*4 blocks.
if (sid >= ps->nscounts) {
int newnsc = (sid / SCOUNTS_ALLOC + 1) * SCOUNTS_ALLOC;
ps->scounts = realloc(ps->scounts, newnsc * sizeof(*ps->scounts));
memset(&ps->scounts[ps->nscounts], 0, (newnsc - ps->nscounts) * sizeof(*ps->scounts));
ps->sgameno = realloc(ps->sgameno, newnsc * sizeof(*ps->sgameno));
memset(&ps->sgameno[ps->nscounts], 0, (newnsc - ps->nscounts) * sizeof(*ps->sgameno));
ps->nscounts = newnsc;
}
if (ps->debug_level > 1 && !fast_random(65536) && !fast_random(32)) {
fprintf(stderr, "%d spatials, %d collisions\n", ps->pat.pc.spat_dict->nspatials, ps->pat.pc.spat_dict->collisions);
}
if (ps->sgameno[sid] != ps->gameno) {
ps->scounts[sid]++;
ps->sgameno[sid] = ps->gameno;
}
}
}
/* Now, match the pattern. */
if (!ps->no_pattern_match) {
struct pattern p;
pattern_match(&ps->pat.pc, ps->pat.ps, &p, b, m);
if (!ps->spat_split_sizes) {
*str = pattern2str(*str, &p);
} else {
/* XXX: We assume that FEAT_SPATIAL items
* are at the end. */
struct pattern p2;
int i = 0;
while (i < p.n && p.f[i].id != FEAT_SPATIAL) {
p2.f[i] = p.f[i];
i++;
}
if (i == p.n) {
p2.n = i;
*str = pattern2str(*str, &p2);
} else {
p2.n = i + 1;
for (int j = i; j < p.n; j++) {
assert(p.f[j].id == FEAT_SPATIAL);
p2.f[i] = p.f[j];
if ((*str)[-1] == ')')
*(*str)++ = ' ';
*str = pattern2str(*str, &p2);
}
}
}
}
}
static char *
patternscan_play(struct engine *e, struct board *b, struct move *m, char *enginearg)
{
struct patternscan *ps = e->data;
if (is_resign(m->coord))
return NULL;
/* Deal with broken game records that sometimes get fed in. */
if (board_at(b, m->coord) != S_NONE)
return NULL;
if (b->moves == (b->handicap ? b->handicap * 2 : 1))
ps->gameno++;
if (!(m->color & ps->color_mask))
return NULL;
/* The user can request this play to be "silent", to get patterns
* only for a single specific situation. */
if (enginearg && *enginearg == '0')
return NULL;
static char str[1048576]; // XXX
char *strp = str;
*str = 0;
/* Scan for supported features. */
/* For specifiation of features and their payloads,
* please refer to pattern.h. */
*strp++ = '[';
process_pattern(ps, b, m, &strp);
*strp++ = ']';
if (ps->competition) {
/* Look at other possible moves as well. */
*strp++ = ' ';
*strp++ = '[';
for (int f = 0; f < b->flen; f++) {
struct move mo = { .coord = b->f[f], .color = m->color };
if (is_pass(mo.coord))
continue;
if (!board_is_valid_move(b, &mo))
continue;
if (strp[-1] != '[')
*strp++ = ' ';
process_pattern(ps, b, &mo, &strp);
}
*strp++ = ']';
}
*strp++ = 0;
return ps->no_pattern_match ? NULL : str;
}
static coord_t *
patternscan_genmove(struct engine *e, struct board *b, struct time_info *ti, enum stone color, bool pass_all_alive)
{
fprintf(stderr, "genmove command not available during patternscan!\n");
exit(EXIT_FAILURE);
}
void
patternscan_done(struct engine *e)
{
struct patternscan *ps = e->data;
if (!ps->gen_spat_dict)
return;
/* Save newly found patterns. */
bool newfile = true;
FILE *f = fopen(spatial_dict_filename, "r");
if (f) { fclose(f); newfile = false; }
f = fopen(spatial_dict_filename, "a");
if (newfile)
spatial_dict_writeinfo(ps->pat.pc.spat_dict, f);
for (unsigned int i = ps->loaded_spatials; i < ps->pat.pc.spat_dict->nspatials; i++) {
/* By default, threshold is 0 and condition is always true. */
assert(i < ps->nscounts && ps->scounts[i] > 0);
if (ps->scounts[i] >= ps->spat_threshold)
spatial_write(ps->pat.pc.spat_dict, &ps->pat.pc.spat_dict->spatials[i], i, f);
}
fclose(f);
}
struct patternscan *
patternscan_state_init(char *arg)
{
struct patternscan *ps = calloc2(1, sizeof(struct patternscan));
bool pat_setup = false;
int xspat = -1;
ps->debug_level = 1;
ps->color_mask = S_BLACK | S_WHITE;
if (arg) {
char *optspec, *next = arg;
while (*next) {
optspec = next;
next += strcspn(next, ",");
if (*next) { *next++ = 0; } else { *next = 0; }
char *optname = optspec;
char *optval = strchr(optspec, '=');
if (optval) *optval++ = 0;
if (!strcasecmp(optname, "debug")) {
if (optval)
ps->debug_level = atoi(optval);
else
ps->debug_level++;
} else if (!strcasecmp(optname, "gen_spat_dict")) {
/* If set, re-generate the spatial patterns
* dictionary; you need to have a dictionary
* of spatial stone configurations in order
* to match any spatial features. */
/* XXX: If you specify the 'patterns' option,
* this must come first! */
ps->gen_spat_dict = !optval || atoi(optval);
} else if (!strcasecmp(optname, "no_pattern_match")) {
/* If set, do not actually match patterns.
* Useful only together with gen_spat_dict
* when just building spatial dictionary. */
ps->no_pattern_match = !optval || atoi(optval);
} else if (!strcasecmp(optname, "spat_threshold") && optval) {
/* Minimal number of times new spatial
* feature must occur in this run (!) to
* be included in the dictionary. Note that
* this will produce discontinuous dictionary
* that you should renumber. Also note that
* 3x3 patterns are always saved. */
ps->spat_threshold = atoi(optval);
} else if (!strcasecmp(optname, "competition")) {
/* In competition mode, first the played
* pattern is printed, then all patterns
* that could be played (including the played
* one). */
ps->competition = !optval || atoi(optval);
} else if (!strcasecmp(optname, "spat_split_sizes")) {
/* Generate a separate pattern for each
* spatial size. This is important to
* preserve good generalization in unknown
* situations where the largest pattern
* might not match. */
ps->spat_split_sizes = 1;
} else if (!strcasecmp(optname, "color_mask") && optval) {
/* Bitmask of move colors to match. Set this
* to 2 if you want to match only white moves,
* for example. (Useful for processing
* handicap games.) */
ps->color_mask = atoi(optval);
} else if (!strcasecmp(optname, "xspat") && optval) {
/* xspat==0: don't match spatial features
* xspat==1: match *only* spatial features */
xspat = atoi(optval);
} else if (!strcasecmp(optname, "patterns") && optval) {
patterns_init(&ps->pat, optval, ps->gen_spat_dict, false);
pat_setup = true;
} else {
fprintf(stderr, "patternscan: Invalid engine argument %s or missing value\n", optname);
exit(EXIT_FAILURE);
}
}
}
if (!pat_setup)
patterns_init(&ps->pat, NULL, ps->gen_spat_dict, false);
if (ps->spat_split_sizes)
ps->pat.pc.spat_largest = 0;
for (int i = 0; i < FEAT_MAX; i++) if ((xspat == 0 && i == FEAT_SPATIAL) || (xspat == 1 && i != FEAT_SPATIAL)) ps->pat.ps[i] = 0;
ps->loaded_spatials = ps->pat.pc.spat_dict->nspatials;
ps->gameno = 1;
return ps;
}
struct engine *
engine_patternscan_init(char *arg, struct board *b)
{
struct patternscan *ps = patternscan_state_init(arg);
struct engine *e = calloc2(1, sizeof(struct engine));
e->name = "PatternScan Engine";
e->comment = "You cannot play Pachi with this engine, it is intended for special development use - scanning of games fed to it as GTP streams for various pattern features.";
e->genmove = patternscan_genmove;
e->notify_play = patternscan_play;
e->done = patternscan_done;
e->data = ps;
// clear_board does not concern us, we like to work over many games
e->keep_on_clear = true;
return e;
}