ELO: Use probability distribution over available moves, not whole board
[pachi/json.git] / patternscan / patternscan.c
blob243aab17ab68bb24542b9fd879e43743d5843c77
1 #include <assert.h>
2 #include <stdio.h>
3 #include <stdlib.h>
5 #include "board.h"
6 #include "engine.h"
7 #include "move.h"
8 #include "patternscan/patternscan.h"
9 #include "pattern.h"
10 #include "patternsp.h"
13 /* Internal engine state. */
14 struct patternscan {
15 int debug_level;
17 struct pattern_config pc;
18 pattern_spec ps;
19 bool competition;
21 bool no_pattern_match;
22 bool gen_spat_dict;
23 /* Minimal number of occurences for spatial to be saved. */
24 int spat_threshold;
25 /* Number of loaded spatials; checkpoint for saving new sids
26 * in case gen_spat_dict is enabled. */
27 int loaded_spatials;
29 /* Book-keeping of spatial occurence count. */
30 int nscounts;
31 int *scounts;
35 static void
36 process_pattern(struct patternscan *ps, struct board *b, struct move *m, char **str)
38 /* First, store the spatial configuration in dictionary
39 * if applicable. */
40 if (ps->gen_spat_dict && !is_pass(m->coord)) {
41 struct spatial s;
42 spatial_from_board(&ps->pc, &s, b, m);
43 int dmax = s.dist;
44 for (int d = ps->pc.spat_min; d <= dmax; d++) {
45 s.dist = d;
46 int sid = spatial_dict_put(ps->pc.spat_dict, &s, spatial_hash(0, &s));
47 assert(sid > 0);
48 /* Allocate space in 1024 blocks. */
49 #define SCOUNTS_ALLOC 1024
50 if (sid >= ps->nscounts) {
51 int newnsc = (sid / SCOUNTS_ALLOC + 1) * SCOUNTS_ALLOC;
52 ps->scounts = realloc(ps->scounts, newnsc * sizeof(*ps->scounts));
53 memset(&ps->scounts[ps->nscounts], 0, (newnsc - ps->nscounts) * sizeof(*ps->scounts));
54 ps->nscounts = newnsc;
56 ps->scounts[sid]++;
60 /* Now, match the pattern. */
61 if (!ps->no_pattern_match) {
62 struct pattern p;
63 pattern_match(&ps->pc, ps->ps, &p, b, m);
64 *str = pattern2str(*str, &p);
68 static char *
69 patternscan_play(struct engine *e, struct board *b, struct move *m)
71 struct patternscan *ps = e->data;
73 if (is_resign(m->coord))
74 return NULL;
76 static char str[1048576]; // XXX
77 char *strp = str;
78 *str = 0;
80 /* Scan for supported features. */
81 /* For specifiation of features and their payloads,
82 * please refer to pattern.h. */
83 process_pattern(ps, b, m, &strp);
85 if (ps->competition) {
86 /* Look at other possible moves as well. */
87 for (int f = 0; f < b->flen; f++) {
88 struct move mo = { .coord = b->f[f], .color = m->color };
89 if (mo.coord == m->coord)
90 continue;
91 if (!board_is_valid_move(b, &mo))
92 continue;
93 *strp++ = ' ';
94 process_pattern(ps, b, &mo, &strp);
98 return ps->no_pattern_match ? NULL : str;
101 static coord_t *
102 patternscan_genmove(struct engine *e, struct board *b, enum stone color)
104 fprintf(stderr, "genmove command not available during patternscan!\n");
105 exit(EXIT_FAILURE);
108 void
109 patternscan_finish(struct engine *e)
111 struct patternscan *ps = e->data;
112 if (!ps->gen_spat_dict)
113 return;
115 /* Save newly found patterns. */
117 bool newfile = true;
118 FILE *f = fopen(spatial_dict_filename, "r");
119 if (f) { fclose(f); newfile = false; }
120 f = fopen(spatial_dict_filename, "a");
121 if (newfile)
122 spatial_dict_writeinfo(ps->pc.spat_dict, f);
124 for (int i = ps->loaded_spatials; i < ps->pc.spat_dict->nspatials; i++) {
125 /* By default, threshold is 0 and condition is always true. */
126 assert(i < ps->nscounts && ps->scounts[i] > 0);
127 if (ps->scounts[i] >= ps->spat_threshold)
128 spatial_write(&ps->pc.spat_dict->spatials[i], i, f);
130 fclose(f);
134 struct patternscan *
135 patternscan_state_init(char *arg)
137 struct patternscan *ps = calloc(1, sizeof(struct patternscan));
138 int xspat = -1;
140 ps->debug_level = 1;
141 ps->pc = DEFAULT_PATTERN_CONFIG;
142 memcpy(&ps->ps, PATTERN_SPEC_MATCHALL, sizeof(pattern_spec));
144 if (arg) {
145 char *optspec, *next = arg;
146 while (*next) {
147 optspec = next;
148 next += strcspn(next, ",");
149 if (*next) { *next++ = 0; } else { *next = 0; }
151 char *optname = optspec;
152 char *optval = strchr(optspec, '=');
153 if (optval) *optval++ = 0;
155 if (!strcasecmp(optname, "debug")) {
156 if (optval)
157 ps->debug_level = atoi(optval);
158 else
159 ps->debug_level++;
161 } else if (!strcasecmp(optname, "gen_spat_dict")) {
162 /* If set, re-generate the spatial patterns
163 * dictionary; you need to have a dictionary
164 * of spatial stone configurations in order
165 * to match any spatial features. */
166 ps->gen_spat_dict = !optval || atoi(optval);
168 } else if (!strcasecmp(optname, "no_pattern_match")) {
169 /* If set, do not actually match patterns.
170 * Useful only together with gen_spat_dict
171 * when just building spatial dictionary. */
172 ps->no_pattern_match = !optval || atoi(optval);
174 } else if (!strcasecmp(optname, "spat_threshold") && optval) {
175 /* Minimal number of times new spatial
176 * feature must occur in this run (!) to
177 * be included in the dictionary. Note that
178 * this will produce discontinuous dictionary
179 * that you should renumber. */
180 ps->spat_threshold = atoi(optval);
182 } else if (!strcasecmp(optname, "competition")) {
183 /* In competition mode, first the played
184 * pattern is printed, then all other patterns
185 * that could be played but weren't. */
186 ps->competition = !optval || atoi(optval);
188 } else if (!strcasecmp(optname, "matchfast")) {
189 /* Limit the matched features only to the
190 * set used in MC simulations. */
191 ps->pc = FAST_PATTERN_CONFIG;
192 memcpy(&ps->ps, PATTERN_SPEC_MATCHFAST, sizeof(pattern_spec));
194 } else if (!strcasecmp(optname, "xspat") && optval) {
195 /* xspat==0: don't match spatial features
196 * xspat==1: match *only* spatial features */
197 xspat = atoi(optval);
199 /* See pattern.h:pattern_config for description and
200 * pattern.c:DEFAULT_PATTERN_CONFIG for default values
201 * of the following options. */
202 } else if (!strcasecmp(optname, "spat_min") && optval) {
203 ps->pc.spat_min = atoi(optval);
204 } else if (!strcasecmp(optname, "spat_max") && optval) {
205 ps->pc.spat_max = atoi(optval);
206 } else if (!strcasecmp(optname, "bdist_max") && optval) {
207 ps->pc.bdist_max = atoi(optval);
208 } else if (!strcasecmp(optname, "ldist_min") && optval) {
209 ps->pc.ldist_min = atoi(optval);
210 } else if (!strcasecmp(optname, "ldist_max") && optval) {
211 ps->pc.ldist_max = atoi(optval);
212 } else if (!strcasecmp(optname, "mcsims") && optval) {
213 ps->pc.mcsims = atoi(optval);
215 } else {
216 fprintf(stderr, "patternscan: Invalid engine argument %s or missing value\n", optname);
217 exit(EXIT_FAILURE);
221 for (int i = 0; i < FEAT_MAX; i++) if ((xspat == 0 && i == FEAT_SPATIAL) || (xspat == 1 && i != FEAT_SPATIAL)) ps->ps[i] = 0;
222 ps->pc.spat_dict = spatial_dict_init(ps->gen_spat_dict);
223 ps->loaded_spatials = ps->pc.spat_dict->nspatials;
225 return ps;
228 struct engine *
229 engine_patternscan_init(char *arg)
231 struct patternscan *ps = patternscan_state_init(arg);
232 struct engine *e = calloc(1, sizeof(struct engine));
233 e->name = "PatternScan Engine";
234 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.";
235 e->genmove = patternscan_genmove;
236 e->notify_play = patternscan_play;
237 e->finish = patternscan_finish;
238 e->data = ps;
240 return e;