Patternscan: Change output format, clearly identify winner and witnesses
[pachi/t.git] / patternscan / patternscan.c
blob5ee8dc4b2ebdf04bbeb9602a0e912b084765ee63
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) ...]
28 /* Internal engine state. */
29 struct patternscan {
30 int debug_level;
32 struct pattern_config pc;
33 pattern_spec ps;
34 bool competition;
36 bool no_pattern_match;
37 bool gen_spat_dict;
38 /* Minimal number of occurences for spatial to be saved. */
39 int spat_threshold;
40 /* Number of loaded spatials; checkpoint for saving new sids
41 * in case gen_spat_dict is enabled. */
42 int loaded_spatials;
44 /* Book-keeping of spatial occurence count. */
45 int gameno;
46 int nscounts;
47 int *scounts;
48 int *sgameno;
52 static void
53 process_pattern(struct patternscan *ps, struct board *b, struct move *m, char **str)
55 /* First, store the spatial configuration in dictionary
56 * if applicable. */
57 if (ps->gen_spat_dict && !is_pass(m->coord)) {
58 struct spatial s;
59 spatial_from_board(&ps->pc, &s, b, m);
60 int dmax = s.dist;
61 for (int d = ps->pc.spat_min; d <= dmax; d++) {
62 s.dist = d;
63 int sid = spatial_dict_put(ps->pc.spat_dict, &s, spatial_hash(0, &s));
64 assert(sid > 0);
65 #define SCOUNTS_ALLOC 1048576 // Allocate space in 1M*4 blocks.
66 if (sid >= ps->nscounts) {
67 int newnsc = (sid / SCOUNTS_ALLOC + 1) * SCOUNTS_ALLOC;
68 ps->scounts = realloc(ps->scounts, newnsc * sizeof(*ps->scounts));
69 memset(&ps->scounts[ps->nscounts], 0, (newnsc - ps->nscounts) * sizeof(*ps->scounts));
70 ps->sgameno = realloc(ps->sgameno, newnsc * sizeof(*ps->sgameno));
71 memset(&ps->sgameno[ps->nscounts], 0, (newnsc - ps->nscounts) * sizeof(*ps->sgameno));
72 ps->nscounts = newnsc;
74 if (ps->debug_level > 1 && !fast_random(65536) && !fast_random(32)) {
75 fprintf(stderr, "%d spatials, %d collisions\n", ps->pc.spat_dict->nspatials, ps->pc.spat_dict->collisions);
77 if (ps->sgameno[sid] != ps->gameno) {
78 ps->scounts[sid]++;
79 ps->sgameno[sid] = ps->gameno;
84 /* Now, match the pattern. */
85 if (!ps->no_pattern_match) {
86 struct pattern p;
87 pattern_match(&ps->pc, ps->ps, &p, b, m);
88 *str = pattern2str(*str, &p);
92 static char *
93 patternscan_play(struct engine *e, struct board *b, struct move *m)
95 struct patternscan *ps = e->data;
97 if (is_resign(m->coord))
98 return NULL;
99 /* Deal with broken game records that sometimes get fed in. */
100 if (board_at(b, m->coord) != S_NONE)
101 return NULL;
103 if (b->moves == 1)
104 ps->gameno++;
106 static char str[1048576]; // XXX
107 char *strp = str;
108 *str = 0;
110 /* Scan for supported features. */
111 /* For specifiation of features and their payloads,
112 * please refer to pattern.h. */
113 *strp++ = '[';
114 process_pattern(ps, b, m, &strp);
115 *strp++ = ']';
117 if (ps->competition) {
118 /* Look at other possible moves as well. */
119 *strp++ = ' ';
120 *strp++ = '[';
121 for (int f = 0; f < b->flen; f++) {
122 struct move mo = { .coord = b->f[f], .color = m->color };
123 if (is_pass(mo.coord))
124 continue;
125 if (!board_is_valid_move(b, &mo))
126 continue;
127 if (strp[-1] != '[')
128 *strp++ = ' ';
129 process_pattern(ps, b, &mo, &strp);
131 *strp++ = ']';
133 *strp++ = 0;
135 return ps->no_pattern_match ? NULL : str;
138 static coord_t *
139 patternscan_genmove(struct engine *e, struct board *b, struct time_info *ti, enum stone color, bool pass_all_alive)
141 fprintf(stderr, "genmove command not available during patternscan!\n");
142 exit(EXIT_FAILURE);
145 void
146 patternscan_done(struct engine *e)
148 struct patternscan *ps = e->data;
149 if (!ps->gen_spat_dict)
150 return;
152 /* Save newly found patterns. */
154 bool newfile = true;
155 FILE *f = fopen(spatial_dict_filename, "r");
156 if (f) { fclose(f); newfile = false; }
157 f = fopen(spatial_dict_filename, "a");
158 if (newfile)
159 spatial_dict_writeinfo(ps->pc.spat_dict, f);
161 for (int i = ps->loaded_spatials; i < ps->pc.spat_dict->nspatials; i++) {
162 /* By default, threshold is 0 and condition is always true. */
163 assert(i < ps->nscounts && ps->scounts[i] > 0);
164 if (ps->scounts[i] >= ps->spat_threshold)
165 spatial_write(ps->pc.spat_dict, &ps->pc.spat_dict->spatials[i], i, f);
167 fclose(f);
171 struct patternscan *
172 patternscan_state_init(char *arg)
174 struct patternscan *ps = calloc2(1, sizeof(struct patternscan));
175 int xspat = -1;
177 ps->debug_level = 1;
178 ps->pc = DEFAULT_PATTERN_CONFIG;
179 memcpy(&ps->ps, PATTERN_SPEC_MATCH_DEFAULT, sizeof(pattern_spec));
181 if (arg) {
182 char *optspec, *next = arg;
183 while (*next) {
184 optspec = next;
185 next += strcspn(next, ",");
186 if (*next) { *next++ = 0; } else { *next = 0; }
188 char *optname = optspec;
189 char *optval = strchr(optspec, '=');
190 if (optval) *optval++ = 0;
192 if (!strcasecmp(optname, "debug")) {
193 if (optval)
194 ps->debug_level = atoi(optval);
195 else
196 ps->debug_level++;
198 } else if (!strcasecmp(optname, "gen_spat_dict")) {
199 /* If set, re-generate the spatial patterns
200 * dictionary; you need to have a dictionary
201 * of spatial stone configurations in order
202 * to match any spatial features. */
203 ps->gen_spat_dict = !optval || atoi(optval);
205 } else if (!strcasecmp(optname, "no_pattern_match")) {
206 /* If set, do not actually match patterns.
207 * Useful only together with gen_spat_dict
208 * when just building spatial dictionary. */
209 ps->no_pattern_match = !optval || atoi(optval);
211 } else if (!strcasecmp(optname, "spat_threshold") && optval) {
212 /* Minimal number of times new spatial
213 * feature must occur in this run (!) to
214 * be included in the dictionary. Note that
215 * this will produce discontinuous dictionary
216 * that you should renumber. Also note that
217 * 3x3 patterns are always saved. */
218 ps->spat_threshold = atoi(optval);
220 } else if (!strcasecmp(optname, "competition")) {
221 /* In competition mode, first the played
222 * pattern is printed, then all patterns
223 * that could be played (including the played
224 * one). */
225 ps->competition = !optval || atoi(optval);
227 } else if (!strcasecmp(optname, "xspat") && optval) {
228 /* xspat==0: don't match spatial features
229 * xspat==1: match *only* spatial features */
230 xspat = atoi(optval);
232 /* See pattern.h:pattern_config for description and
233 * pattern.c:DEFAULT_PATTERN_CONFIG for default values
234 * of the following options. */
235 } else if (!strcasecmp(optname, "bdist_max") && optval) {
236 ps->pc.bdist_max = atoi(optval);
237 } else if (!strcasecmp(optname, "spat_min") && optval) {
238 ps->pc.spat_min = atoi(optval);
239 } else if (!strcasecmp(optname, "spat_max") && optval) {
240 ps->pc.spat_max = atoi(optval);
241 } else if (!strcasecmp(optname, "spat_largest")) {
242 ps->pc.spat_largest = !optval || atoi(optval);
244 } else {
245 fprintf(stderr, "patternscan: Invalid engine argument %s or missing value\n", optname);
246 exit(EXIT_FAILURE);
250 for (int i = 0; i < FEAT_MAX; i++) if ((xspat == 0 && i == FEAT_SPATIAL) || (xspat == 1 && i != FEAT_SPATIAL)) ps->ps[i] = 0;
251 ps->pc.spat_dict = spatial_dict_init(ps->gen_spat_dict, true);
252 ps->loaded_spatials = ps->pc.spat_dict->nspatials;
254 return ps;
257 struct engine *
258 engine_patternscan_init(char *arg, struct board *b)
260 struct patternscan *ps = patternscan_state_init(arg);
261 struct engine *e = calloc2(1, sizeof(struct engine));
262 e->name = "PatternScan Engine";
263 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.";
264 e->genmove = patternscan_genmove;
265 e->notify_play = patternscan_play;
266 e->done = patternscan_done;
267 e->data = ps;
268 // clear_board does not concern us, we like to work over many games
269 e->keep_on_clear = true;
271 return e;