Probability pattern ditionary: Hash spatials only when adding their patterns
[pachi.git] / patternscan / patternscan.c
blobfffe8d5b1937724b6eb2547b6b5353986392ffef
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 /* Internal engine state. */
16 struct patternscan {
17 int debug_level;
19 struct pattern_config pc;
20 pattern_spec ps;
21 bool competition;
23 bool no_pattern_match;
24 bool gen_spat_dict;
25 /* Minimal number of occurences for spatial to be saved. */
26 int spat_threshold;
27 /* Number of loaded spatials; checkpoint for saving new sids
28 * in case gen_spat_dict is enabled. */
29 int loaded_spatials;
31 /* Book-keeping of spatial occurence count. */
32 int gameno;
33 int nscounts;
34 int *scounts;
35 int *sgameno;
39 static void
40 process_pattern(struct patternscan *ps, struct board *b, struct move *m, char **str)
42 /* First, store the spatial configuration in dictionary
43 * if applicable. */
44 if (ps->gen_spat_dict && !is_pass(m->coord)) {
45 struct spatial s;
46 spatial_from_board(&ps->pc, &s, b, m);
47 int dmax = s.dist;
48 for (int d = ps->pc.spat_min; d <= dmax; d++) {
49 s.dist = d;
50 int sid = spatial_dict_put(ps->pc.spat_dict, &s, spatial_hash(0, &s));
51 assert(sid > 0);
52 #define SCOUNTS_ALLOC 1048576 // Allocate space in 1M*4 blocks.
53 if (sid >= ps->nscounts) {
54 int newnsc = (sid / SCOUNTS_ALLOC + 1) * SCOUNTS_ALLOC;
55 ps->scounts = realloc(ps->scounts, newnsc * sizeof(*ps->scounts));
56 memset(&ps->scounts[ps->nscounts], 0, (newnsc - ps->nscounts) * sizeof(*ps->scounts));
57 ps->sgameno = realloc(ps->sgameno, newnsc * sizeof(*ps->sgameno));
58 memset(&ps->sgameno[ps->nscounts], 0, (newnsc - ps->nscounts) * sizeof(*ps->sgameno));
59 ps->nscounts = newnsc;
61 if (ps->debug_level > 1 && !fast_random(65536) && !fast_random(32)) {
62 fprintf(stderr, "%d spatials, %d collisions\n", ps->pc.spat_dict->nspatials, ps->pc.spat_dict->collisions);
64 if (ps->sgameno[sid] != ps->gameno) {
65 ps->scounts[sid]++;
66 ps->sgameno[sid] = ps->gameno;
71 /* Now, match the pattern. */
72 if (!ps->no_pattern_match) {
73 struct pattern p;
74 pattern_match(&ps->pc, ps->ps, &p, b, m);
75 *str = pattern2str(*str, &p);
79 static char *
80 patternscan_play(struct engine *e, struct board *b, struct move *m)
82 struct patternscan *ps = e->data;
84 if (is_resign(m->coord))
85 return NULL;
87 if (b->moves == 1)
88 ps->gameno++;
90 static char str[1048576]; // XXX
91 char *strp = str;
92 *str = 0;
94 /* Scan for supported features. */
95 /* For specifiation of features and their payloads,
96 * please refer to pattern.h. */
97 process_pattern(ps, b, m, &strp);
99 if (ps->competition) {
100 /* Look at other possible moves as well. */
101 for (int f = 0; f < b->flen; f++) {
102 struct move mo = { .coord = b->f[f], .color = m->color };
103 if (is_pass(mo.coord))
104 continue;
105 if (!board_is_valid_move(b, &mo))
106 continue;
107 *strp++ = ' ';
108 process_pattern(ps, b, &mo, &strp);
112 return ps->no_pattern_match ? NULL : str;
115 static coord_t *
116 patternscan_genmove(struct engine *e, struct board *b, struct time_info *ti, enum stone color, bool pass_all_alive)
118 fprintf(stderr, "genmove command not available during patternscan!\n");
119 exit(EXIT_FAILURE);
122 void
123 patternscan_done(struct engine *e)
125 struct patternscan *ps = e->data;
126 if (!ps->gen_spat_dict)
127 return;
129 /* Save newly found patterns. */
131 bool newfile = true;
132 FILE *f = fopen(spatial_dict_filename, "r");
133 if (f) { fclose(f); newfile = false; }
134 f = fopen(spatial_dict_filename, "a");
135 if (newfile)
136 spatial_dict_writeinfo(ps->pc.spat_dict, f);
138 for (int i = ps->loaded_spatials; i < ps->pc.spat_dict->nspatials; i++) {
139 /* By default, threshold is 0 and condition is always true. */
140 assert(i < ps->nscounts && ps->scounts[i] > 0);
141 if (ps->scounts[i] >= ps->spat_threshold)
142 spatial_write(ps->pc.spat_dict, &ps->pc.spat_dict->spatials[i], i, f);
144 fclose(f);
148 struct patternscan *
149 patternscan_state_init(char *arg)
151 struct patternscan *ps = calloc2(1, sizeof(struct patternscan));
152 int xspat = -1;
154 ps->debug_level = 1;
155 ps->pc = DEFAULT_PATTERN_CONFIG;
156 memcpy(&ps->ps, PATTERN_SPEC_MATCH_DEFAULT, sizeof(pattern_spec));
158 if (arg) {
159 char *optspec, *next = arg;
160 while (*next) {
161 optspec = next;
162 next += strcspn(next, ",");
163 if (*next) { *next++ = 0; } else { *next = 0; }
165 char *optname = optspec;
166 char *optval = strchr(optspec, '=');
167 if (optval) *optval++ = 0;
169 if (!strcasecmp(optname, "debug")) {
170 if (optval)
171 ps->debug_level = atoi(optval);
172 else
173 ps->debug_level++;
175 } else if (!strcasecmp(optname, "gen_spat_dict")) {
176 /* If set, re-generate the spatial patterns
177 * dictionary; you need to have a dictionary
178 * of spatial stone configurations in order
179 * to match any spatial features. */
180 ps->gen_spat_dict = !optval || atoi(optval);
182 } else if (!strcasecmp(optname, "no_pattern_match")) {
183 /* If set, do not actually match patterns.
184 * Useful only together with gen_spat_dict
185 * when just building spatial dictionary. */
186 ps->no_pattern_match = !optval || atoi(optval);
188 } else if (!strcasecmp(optname, "spat_threshold") && optval) {
189 /* Minimal number of times new spatial
190 * feature must occur in this run (!) to
191 * be included in the dictionary. Note that
192 * this will produce discontinuous dictionary
193 * that you should renumber. Also note that
194 * 3x3 patterns are always saved. */
195 ps->spat_threshold = atoi(optval);
197 } else if (!strcasecmp(optname, "competition")) {
198 /* In competition mode, first the played
199 * pattern is printed, then all patterns
200 * that could be played (including the played
201 * one). */
202 ps->competition = !optval || atoi(optval);
204 } else if (!strcasecmp(optname, "xspat") && optval) {
205 /* xspat==0: don't match spatial features
206 * xspat==1: match *only* spatial features */
207 xspat = atoi(optval);
209 /* See pattern.h:pattern_config for description and
210 * pattern.c:DEFAULT_PATTERN_CONFIG for default values
211 * of the following options. */
212 } else if (!strcasecmp(optname, "bdist_max") && optval) {
213 ps->pc.bdist_max = atoi(optval);
214 } else if (!strcasecmp(optname, "spat_min") && optval) {
215 ps->pc.spat_min = atoi(optval);
216 } else if (!strcasecmp(optname, "spat_max") && optval) {
217 ps->pc.spat_max = atoi(optval);
218 } else if (!strcasecmp(optname, "spat_largest")) {
219 ps->pc.spat_largest = !optval || atoi(optval);
221 } else {
222 fprintf(stderr, "patternscan: Invalid engine argument %s or missing value\n", optname);
223 exit(EXIT_FAILURE);
227 for (int i = 0; i < FEAT_MAX; i++) if ((xspat == 0 && i == FEAT_SPATIAL) || (xspat == 1 && i != FEAT_SPATIAL)) ps->ps[i] = 0;
228 ps->pc.spat_dict = spatial_dict_init(ps->gen_spat_dict, true);
229 ps->loaded_spatials = ps->pc.spat_dict->nspatials;
231 return ps;
234 struct engine *
235 engine_patternscan_init(char *arg, struct board *b)
237 struct patternscan *ps = patternscan_state_init(arg);
238 struct engine *e = calloc2(1, sizeof(struct engine));
239 e->name = "PatternScan Engine";
240 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.";
241 e->genmove = patternscan_genmove;
242 e->notify_play = patternscan_play;
243 e->done = patternscan_done;
244 e->data = ps;
245 // clear_board does not concern us, we like to work over many games
246 e->keep_on_clear = true;
248 return e;