GTP play: Add support for a trailing engine-specific argument
[pachi.git] / patternscan / patternscan.c
blob441617dded112ad5d1cb4adb78df1f7df69534ca
1 #include <assert.h>
2 #include <stdio.h>
3 #include <stdlib.h>
5 #include "board.h"
6 #include "debug.h"
7 #include "engine.h"
8 #include "move.h"
9 #include "patternscan/patternscan.h"
10 #include "pattern.h"
11 #include "patternsp.h"
12 #include "random.h"
15 /* The engine has two modes:
17 * * gen_spat_dict=1: patterns.spat file is generated with a list of all
18 * encountered spatials
20 * * gen_spat_dict=0,no_pattern_match=1: all encountered patterns are
21 * listed on output on each move; the general format is
22 * [(winpattern)]
23 * but with competition=1 it is
24 * [(winpattern)] [(witnesspattern0) (witnesspattern1) ...]
25 * and with spat_split_sizes=1 even
26 * [(winpattern0) (winpattern1) ...] [(witpattern0) (witpattern1) ...]
30 /* Internal engine state. */
31 struct patternscan {
32 int debug_level;
34 struct pattern_setup pat;
35 bool competition;
36 bool spat_split_sizes;
37 int color_mask;
39 bool no_pattern_match;
40 bool gen_spat_dict;
41 /* Minimal number of occurences for spatial to be saved. */
42 int spat_threshold;
43 /* Number of loaded spatials; checkpoint for saving new sids
44 * in case gen_spat_dict is enabled. */
45 int loaded_spatials;
47 /* Book-keeping of spatial occurence count. */
48 int gameno;
49 unsigned int nscounts;
50 int *scounts;
51 int *sgameno;
55 static void
56 process_pattern(struct patternscan *ps, struct board *b, struct move *m, char **str)
58 /* First, store the spatial configuration in dictionary
59 * if applicable. */
60 if (ps->gen_spat_dict && !is_pass(m->coord)) {
61 struct spatial s;
62 spatial_from_board(&ps->pat.pc, &s, b, m);
63 int dmax = s.dist;
64 for (int d = ps->pat.pc.spat_min; d <= dmax; d++) {
65 s.dist = d;
66 unsigned int sid = spatial_dict_put(ps->pat.pc.spat_dict, &s, spatial_hash(0, &s));
67 assert(sid > 0);
68 #define SCOUNTS_ALLOC 1048576 // Allocate space in 1M*4 blocks.
69 if (sid >= ps->nscounts) {
70 int newnsc = (sid / SCOUNTS_ALLOC + 1) * SCOUNTS_ALLOC;
71 ps->scounts = realloc(ps->scounts, newnsc * sizeof(*ps->scounts));
72 memset(&ps->scounts[ps->nscounts], 0, (newnsc - ps->nscounts) * sizeof(*ps->scounts));
73 ps->sgameno = realloc(ps->sgameno, newnsc * sizeof(*ps->sgameno));
74 memset(&ps->sgameno[ps->nscounts], 0, (newnsc - ps->nscounts) * sizeof(*ps->sgameno));
75 ps->nscounts = newnsc;
77 if (ps->debug_level > 1 && !fast_random(65536) && !fast_random(32)) {
78 fprintf(stderr, "%d spatials, %d collisions\n", ps->pat.pc.spat_dict->nspatials, ps->pat.pc.spat_dict->collisions);
80 if (ps->sgameno[sid] != ps->gameno) {
81 ps->scounts[sid]++;
82 ps->sgameno[sid] = ps->gameno;
87 /* Now, match the pattern. */
88 if (!ps->no_pattern_match) {
89 struct pattern p;
90 pattern_match(&ps->pat.pc, ps->pat.ps, &p, b, m);
92 if (!ps->spat_split_sizes) {
93 *str = pattern2str(*str, &p);
94 } else {
95 /* XXX: We assume that FEAT_SPATIAL items
96 * are at the end. */
97 struct pattern p2;
98 int i = 0;
99 while (i < p.n && p.f[i].id != FEAT_SPATIAL) {
100 p2.f[i] = p.f[i];
101 i++;
103 if (i == p.n) {
104 p2.n = i;
105 *str = pattern2str(*str, &p2);
106 } else {
107 p2.n = i + 1;
108 for (int j = i; j < p.n; j++) {
109 assert(p.f[j].id == FEAT_SPATIAL);
110 p2.f[i] = p.f[j];
111 if ((*str)[-1] == ')')
112 *(*str)++ = ' ';
113 *str = pattern2str(*str, &p2);
120 static char *
121 patternscan_play(struct engine *e, struct board *b, struct move *m, char *enginearg)
123 struct patternscan *ps = e->data;
125 if (is_resign(m->coord))
126 return NULL;
127 /* Deal with broken game records that sometimes get fed in. */
128 if (board_at(b, m->coord) != S_NONE)
129 return NULL;
131 if (b->moves == (b->handicap ? b->handicap * 2 : 1))
132 ps->gameno++;
134 if (!(m->color & ps->color_mask))
135 return NULL;
137 static char str[1048576]; // XXX
138 char *strp = str;
139 *str = 0;
141 /* Scan for supported features. */
142 /* For specifiation of features and their payloads,
143 * please refer to pattern.h. */
144 *strp++ = '[';
145 process_pattern(ps, b, m, &strp);
146 *strp++ = ']';
148 if (ps->competition) {
149 /* Look at other possible moves as well. */
150 *strp++ = ' ';
151 *strp++ = '[';
152 for (int f = 0; f < b->flen; f++) {
153 struct move mo = { .coord = b->f[f], .color = m->color };
154 if (is_pass(mo.coord))
155 continue;
156 if (!board_is_valid_move(b, &mo))
157 continue;
158 if (strp[-1] != '[')
159 *strp++ = ' ';
160 process_pattern(ps, b, &mo, &strp);
162 *strp++ = ']';
164 *strp++ = 0;
166 return ps->no_pattern_match ? NULL : str;
169 static coord_t *
170 patternscan_genmove(struct engine *e, struct board *b, struct time_info *ti, enum stone color, bool pass_all_alive)
172 fprintf(stderr, "genmove command not available during patternscan!\n");
173 exit(EXIT_FAILURE);
176 void
177 patternscan_done(struct engine *e)
179 struct patternscan *ps = e->data;
180 if (!ps->gen_spat_dict)
181 return;
183 /* Save newly found patterns. */
185 bool newfile = true;
186 FILE *f = fopen(spatial_dict_filename, "r");
187 if (f) { fclose(f); newfile = false; }
188 f = fopen(spatial_dict_filename, "a");
189 if (newfile)
190 spatial_dict_writeinfo(ps->pat.pc.spat_dict, f);
192 for (unsigned int i = ps->loaded_spatials; i < ps->pat.pc.spat_dict->nspatials; i++) {
193 /* By default, threshold is 0 and condition is always true. */
194 assert(i < ps->nscounts && ps->scounts[i] > 0);
195 if (ps->scounts[i] >= ps->spat_threshold)
196 spatial_write(ps->pat.pc.spat_dict, &ps->pat.pc.spat_dict->spatials[i], i, f);
198 fclose(f);
202 struct patternscan *
203 patternscan_state_init(char *arg)
205 struct patternscan *ps = calloc2(1, sizeof(struct patternscan));
206 bool pat_setup = false;
207 int xspat = -1;
209 ps->debug_level = 1;
210 ps->color_mask = S_BLACK | S_WHITE;
212 if (arg) {
213 char *optspec, *next = arg;
214 while (*next) {
215 optspec = next;
216 next += strcspn(next, ",");
217 if (*next) { *next++ = 0; } else { *next = 0; }
219 char *optname = optspec;
220 char *optval = strchr(optspec, '=');
221 if (optval) *optval++ = 0;
223 if (!strcasecmp(optname, "debug")) {
224 if (optval)
225 ps->debug_level = atoi(optval);
226 else
227 ps->debug_level++;
229 } else if (!strcasecmp(optname, "gen_spat_dict")) {
230 /* If set, re-generate the spatial patterns
231 * dictionary; you need to have a dictionary
232 * of spatial stone configurations in order
233 * to match any spatial features. */
234 /* XXX: If you specify the 'patterns' option,
235 * this must come first! */
236 ps->gen_spat_dict = !optval || atoi(optval);
238 } else if (!strcasecmp(optname, "no_pattern_match")) {
239 /* If set, do not actually match patterns.
240 * Useful only together with gen_spat_dict
241 * when just building spatial dictionary. */
242 ps->no_pattern_match = !optval || atoi(optval);
244 } else if (!strcasecmp(optname, "spat_threshold") && optval) {
245 /* Minimal number of times new spatial
246 * feature must occur in this run (!) to
247 * be included in the dictionary. Note that
248 * this will produce discontinuous dictionary
249 * that you should renumber. Also note that
250 * 3x3 patterns are always saved. */
251 ps->spat_threshold = atoi(optval);
253 } else if (!strcasecmp(optname, "competition")) {
254 /* In competition mode, first the played
255 * pattern is printed, then all patterns
256 * that could be played (including the played
257 * one). */
258 ps->competition = !optval || atoi(optval);
260 } else if (!strcasecmp(optname, "spat_split_sizes")) {
261 /* Generate a separate pattern for each
262 * spatial size. This is important to
263 * preserve good generalization in unknown
264 * situations where the largest pattern
265 * might not match. */
266 ps->spat_split_sizes = 1;
268 } else if (!strcasecmp(optname, "color_mask") && optval) {
269 /* Bitmask of move colors to match. Set this
270 * to 2 if you want to match only white moves,
271 * for example. (Useful for processing
272 * handicap games.) */
273 ps->color_mask = atoi(optval);
275 } else if (!strcasecmp(optname, "xspat") && optval) {
276 /* xspat==0: don't match spatial features
277 * xspat==1: match *only* spatial features */
278 xspat = atoi(optval);
280 } else if (!strcasecmp(optname, "patterns") && optval) {
281 patterns_init(&ps->pat, optval, ps->gen_spat_dict, false);
282 pat_setup = true;
284 } else {
285 fprintf(stderr, "patternscan: Invalid engine argument %s or missing value\n", optname);
286 exit(EXIT_FAILURE);
291 if (!pat_setup)
292 patterns_init(&ps->pat, NULL, ps->gen_spat_dict, false);
293 if (ps->spat_split_sizes)
294 ps->pat.pc.spat_largest = 0;
296 for (int i = 0; i < FEAT_MAX; i++) if ((xspat == 0 && i == FEAT_SPATIAL) || (xspat == 1 && i != FEAT_SPATIAL)) ps->pat.ps[i] = 0;
297 ps->loaded_spatials = ps->pat.pc.spat_dict->nspatials;
299 return ps;
302 struct engine *
303 engine_patternscan_init(char *arg, struct board *b)
305 struct patternscan *ps = patternscan_state_init(arg);
306 struct engine *e = calloc2(1, sizeof(struct engine));
307 e->name = "PatternScan Engine";
308 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.";
309 e->genmove = patternscan_genmove;
310 e->notify_play = patternscan_play;
311 e->done = patternscan_done;
312 e->data = ps;
313 // clear_board does not concern us, we like to work over many games
314 e->keep_on_clear = true;
316 return e;