Merge branch 'patterns' of ssh://repo.or.cz/srv/git/pachi into patterns
[pachi.git] / patternscan / patternscan.c
blob222442eb271f6dfcb25690154d7d7342261e4790
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_config pc;
35 pattern_spec ps;
36 bool competition;
37 bool spat_split_sizes;
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 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->pc, &s, b, m);
63 int dmax = s.dist;
64 for (int d = ps->pc.spat_min; d <= dmax; d++) {
65 s.dist = d;
66 int sid = spatial_dict_put(ps->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->pc.spat_dict->nspatials, ps->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->pc, ps->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)
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 == 1)
132 ps->gameno++;
134 static char str[1048576]; // XXX
135 char *strp = str;
136 *str = 0;
138 /* Scan for supported features. */
139 /* For specifiation of features and their payloads,
140 * please refer to pattern.h. */
141 *strp++ = '[';
142 process_pattern(ps, b, m, &strp);
143 *strp++ = ']';
145 if (ps->competition) {
146 /* Look at other possible moves as well. */
147 *strp++ = ' ';
148 *strp++ = '[';
149 for (int f = 0; f < b->flen; f++) {
150 struct move mo = { .coord = b->f[f], .color = m->color };
151 if (is_pass(mo.coord))
152 continue;
153 if (!board_is_valid_move(b, &mo))
154 continue;
155 if (strp[-1] != '[')
156 *strp++ = ' ';
157 process_pattern(ps, b, &mo, &strp);
159 *strp++ = ']';
161 *strp++ = 0;
163 return ps->no_pattern_match ? NULL : str;
166 static coord_t *
167 patternscan_genmove(struct engine *e, struct board *b, struct time_info *ti, enum stone color, bool pass_all_alive)
169 fprintf(stderr, "genmove command not available during patternscan!\n");
170 exit(EXIT_FAILURE);
173 void
174 patternscan_done(struct engine *e)
176 struct patternscan *ps = e->data;
177 if (!ps->gen_spat_dict)
178 return;
180 /* Save newly found patterns. */
182 bool newfile = true;
183 FILE *f = fopen(spatial_dict_filename, "r");
184 if (f) { fclose(f); newfile = false; }
185 f = fopen(spatial_dict_filename, "a");
186 if (newfile)
187 spatial_dict_writeinfo(ps->pc.spat_dict, f);
189 for (int i = ps->loaded_spatials; i < ps->pc.spat_dict->nspatials; i++) {
190 /* By default, threshold is 0 and condition is always true. */
191 assert(i < ps->nscounts && ps->scounts[i] > 0);
192 if (ps->scounts[i] >= ps->spat_threshold)
193 spatial_write(ps->pc.spat_dict, &ps->pc.spat_dict->spatials[i], i, f);
195 fclose(f);
199 struct patternscan *
200 patternscan_state_init(char *arg)
202 struct patternscan *ps = calloc2(1, sizeof(struct patternscan));
203 int xspat = -1;
205 ps->debug_level = 1;
206 ps->pc = DEFAULT_PATTERN_CONFIG;
207 memcpy(&ps->ps, PATTERN_SPEC_MATCH_DEFAULT, sizeof(pattern_spec));
209 if (arg) {
210 char *optspec, *next = arg;
211 while (*next) {
212 optspec = next;
213 next += strcspn(next, ",");
214 if (*next) { *next++ = 0; } else { *next = 0; }
216 char *optname = optspec;
217 char *optval = strchr(optspec, '=');
218 if (optval) *optval++ = 0;
220 if (!strcasecmp(optname, "debug")) {
221 if (optval)
222 ps->debug_level = atoi(optval);
223 else
224 ps->debug_level++;
226 } else if (!strcasecmp(optname, "gen_spat_dict")) {
227 /* If set, re-generate the spatial patterns
228 * dictionary; you need to have a dictionary
229 * of spatial stone configurations in order
230 * to match any spatial features. */
231 ps->gen_spat_dict = !optval || atoi(optval);
233 } else if (!strcasecmp(optname, "no_pattern_match")) {
234 /* If set, do not actually match patterns.
235 * Useful only together with gen_spat_dict
236 * when just building spatial dictionary. */
237 ps->no_pattern_match = !optval || atoi(optval);
239 } else if (!strcasecmp(optname, "spat_threshold") && optval) {
240 /* Minimal number of times new spatial
241 * feature must occur in this run (!) to
242 * be included in the dictionary. Note that
243 * this will produce discontinuous dictionary
244 * that you should renumber. Also note that
245 * 3x3 patterns are always saved. */
246 ps->spat_threshold = atoi(optval);
248 } else if (!strcasecmp(optname, "competition")) {
249 /* In competition mode, first the played
250 * pattern is printed, then all patterns
251 * that could be played (including the played
252 * one). */
253 ps->competition = !optval || atoi(optval);
255 } else if (!strcasecmp(optname, "spat_split_sizes")) {
256 /* Generate a separate pattern for each
257 * spatial size. This is important to
258 * preserve good generalization in unknown
259 * situations where the largest pattern
260 * might not match. */
261 ps->spat_split_sizes = 1;
262 ps->pc.spat_largest = 0;
264 } else if (!strcasecmp(optname, "xspat") && optval) {
265 /* xspat==0: don't match spatial features
266 * xspat==1: match *only* spatial features */
267 xspat = atoi(optval);
269 /* See pattern.h:pattern_config for description and
270 * pattern.c:DEFAULT_PATTERN_CONFIG for default values
271 * of the following options. */
272 } else if (!strcasecmp(optname, "bdist_max") && optval) {
273 ps->pc.bdist_max = atoi(optval);
274 } else if (!strcasecmp(optname, "spat_min") && optval) {
275 ps->pc.spat_min = atoi(optval);
276 } else if (!strcasecmp(optname, "spat_max") && optval) {
277 ps->pc.spat_max = atoi(optval);
278 } else if (!strcasecmp(optname, "spat_largest")) {
279 ps->pc.spat_largest = !optval || atoi(optval);
281 } else {
282 fprintf(stderr, "patternscan: Invalid engine argument %s or missing value\n", optname);
283 exit(EXIT_FAILURE);
287 for (int i = 0; i < FEAT_MAX; i++) if ((xspat == 0 && i == FEAT_SPATIAL) || (xspat == 1 && i != FEAT_SPATIAL)) ps->ps[i] = 0;
288 ps->pc.spat_dict = spatial_dict_init(ps->gen_spat_dict, true);
289 ps->loaded_spatials = ps->pc.spat_dict->nspatials;
291 return ps;
294 struct engine *
295 engine_patternscan_init(char *arg, struct board *b)
297 struct patternscan *ps = patternscan_state_init(arg);
298 struct engine *e = calloc2(1, sizeof(struct engine));
299 e->name = "PatternScan Engine";
300 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.";
301 e->genmove = patternscan_genmove;
302 e->notify_play = patternscan_play;
303 e->done = patternscan_done;
304 e->data = ps;
305 // clear_board does not concern us, we like to work over many games
306 e->keep_on_clear = true;
308 return e;