9 #include "patternscan/patternscan.h"
11 #include "patternsp.h"
14 /* Internal engine state. */
18 struct pattern_config pc
;
23 bool no_pattern_match
;
25 /* Minimal number of occurences for spatial to be saved;
26 * 3x3 spatials are always saved. */
28 /* Number of loaded spatials; checkpoint for saving new sids
29 * in case gen_spat_dict is enabled. */
32 /* Book-keeping of spatial occurence count. */
38 /* Starting gamma number of each feature. */
39 static int gammaid
[FEAT_MAX
+ MAX_PATTERN_DIST
+ 1];
40 /* For each spatial id, its gamma value. */
41 static int spatg
[65536];
42 /* Pattern3 hashes sorted by their gamma. */
43 static hash3_t pat3g
[65536/8];
45 /* Print MM-format header - summary of features. Also create patterns.fdict
46 * containing mapping from gamma numbers to feature:payload pairs. */
48 mm_header(struct patternscan
*ps
)
50 FILE *fdict
= fopen("patterns.fdict", "w");
52 perror("patterns.fdict");
59 for (int i
= 0; i
< FEAT_MAX
; i
++) {
61 /* Feature disabled. */
62 /* XXX: Accurately account for payload count
63 * of partially disabled features? */
64 gammaid
[i
+ 1] = gammaid
[i
];
67 if (i
== FEAT_SPATIAL
) {
68 /* Special handling. */
69 gammaid
[i
+ 1] = gammaid
[i
];
75 if (i
== FEAT_PATTERN3
) {
76 /* We need to dynamically allocate gamma numbers
77 * of pattern3 hashes, 65536 extra gammas is
79 for (int s
= 0; s
< ps
->pc
.spat_dict
->nspatials
; s
++) {
80 if (ps
->pc
.spat_dict
->spatials
[s
].dist
!= 3)
82 hash3_t pat
= spatial_to_pattern3(&ps
->pc
.spat_dict
->spatials
[s
]);
83 struct feature f
= { .id
= i
, .payload
= pat
};
84 char buf
[256] = ""; feature2str(buf
, &f
);
85 fprintf(fdict
, "%d %s\n", g
, buf
);
86 pat3g
[g
++ - gammaid
[i
]] = pat
;
92 gammaid
[i
+ 1] = gammaid
[i
] + feature_payloads(&ps
->pc
, i
);
94 for (int p
= 0; p
< feature_payloads(&ps
->pc
, i
); p
++) {
95 struct feature f
= { .id
= i
, .payload
= p
};
96 char buf
[256] = ""; feature2str(buf
, &f
);
97 fprintf(fdict
, "%d %s\n", g
++, buf
);
99 assert(g
== gammaid
[i
+ 1]);
102 /* XXX: We rely on pattern3 being last feature before FEAT_MAX. */
104 /* We need to break down spatials by their radius, since payloads
105 * of single feature must be independent. */
106 if (ps
->ps
[FEAT_SPATIAL
] != 0) {
107 for (int d
= 0; d
<= ps
->pc
.spat_max
- ps
->pc
.spat_min
; d
++) {
108 for (int i
= 0; i
< ps
->pc
.spat_dict
->nspatials
; i
++) {
109 if (ps
->pc
.spat_dict
->spatials
[i
].dist
!= ps
->pc
.spat_min
+ d
)
112 struct feature f
= { .id
= FEAT_SPATIAL
, .payload
= i
};
113 char buf
[256] = ""; feature2str(buf
, &f
);
114 fprintf(fdict
, "%d %s\n", spatg
[i
], buf
);
117 gammaid
[FEAT_MAX
+ d
+ 1] = g
;
123 printf("! %d\n", g
); // Number of gammas.
124 printf("%d\n", features
); // Number of features.
125 for (int i
= 0; i
< FEAT_MAX
; i
++) {
126 if (ps
->ps
[i
] == 0) continue;
127 if (i
== FEAT_SPATIAL
) continue;
128 // Number of gammas per feature.
129 printf("%d %s\n", gammaid
[i
+ 1] - gammaid
[i
], feature_name(i
));
131 if (ps
->ps
[FEAT_SPATIAL
] != 0) {
132 for (int d
= 0; d
<= ps
->pc
.spat_max
- ps
->pc
.spat_min
; d
++) {
133 printf("%d %s.%d\n", gammaid
[FEAT_MAX
+ d
+ 1] - gammaid
[FEAT_MAX
+ d
],
134 feature_name(FEAT_SPATIAL
), ps
->pc
.spat_min
+ d
);
141 mm_pattern(struct patternscan
*ps
, char *str
, struct pattern
*p
)
143 for (int i
= 0; i
< p
->n
; i
++) {
144 if (i
> 0) str
= stpcpy(str
, " ");
145 switch (p
->f
[i
].id
) {
147 str
+= sprintf(str
, "%d", spatg
[p
->f
[i
].payload
]);
150 for (int g
= 0; g
< gammaid
[FEAT_PATTERN3
+ 1] - gammaid
[FEAT_PATTERN3
]; g
++)
151 if (pat3g
[g
] == p
->f
[i
].payload
) {
152 str
+= sprintf(str
, "%d", gammaid
[p
->f
[i
].id
] + g
);
157 str
+= sprintf(str
, "%d", gammaid
[p
->f
[i
].id
] + p
->f
[i
].payload
);
161 return stpcpy(str
, "\n");
165 /* FEAT_PATTERN3-matched patterns do not account for rotations
166 * and transpositions, but we shold emit only pattern hashes
167 * in "canonical" form that is the same no matter the rotation.
168 * To achieve this, we use a trick - the canonical form is the
169 * one recorded in the spatial dictionary! The dictionary is
170 * already closed on rotation and transposition. */
172 pattern_p3_normalize(struct patternscan
*ps
, struct pattern
*p
)
174 for (int i
= 0; i
< p
->n
; i
++) {
175 if (p
->f
[i
].id
!= FEAT_PATTERN3
)
177 /* Normalize the pattern hash across rotation and
178 * transposition space. */
179 p
->f
[i
].payload
= pattern3_by_spatial(ps
->pc
.spat_dict
, p
->f
[i
].payload
);
185 process_pattern(struct patternscan
*ps
, struct board
*b
, struct move
*m
, char **str
)
187 /* First, store the spatial configuration in dictionary
189 if (ps
->gen_spat_dict
&& !is_pass(m
->coord
)) {
191 spatial_from_board(&ps
->pc
, &s
, b
, m
);
193 for (int d
= ps
->pc
.spat_min
; d
<= dmax
; d
++) {
195 int sid
= spatial_dict_put(ps
->pc
.spat_dict
, &s
, spatial_hash(0, &s
));
197 /* Allocate space in 1024 blocks. */
198 #define SCOUNTS_ALLOC 1024
199 if (sid
>= ps
->nscounts
) {
200 int newnsc
= (sid
/ SCOUNTS_ALLOC
+ 1) * SCOUNTS_ALLOC
;
201 ps
->scounts
= realloc(ps
->scounts
, newnsc
* sizeof(*ps
->scounts
));
202 memset(&ps
->scounts
[ps
->nscounts
], 0, (newnsc
- ps
->nscounts
) * sizeof(*ps
->scounts
));
203 ps
->nscounts
= newnsc
;
209 /* Now, match the pattern. */
210 if (!ps
->no_pattern_match
) {
212 pattern_match(&ps
->pc
, ps
->ps
, &p
, b
, m
);
213 pattern_p3_normalize(ps
, &p
);
214 *str
= ps
->mm
? mm_pattern(ps
, *str
, &p
) : pattern2str(*str
, &p
);
219 patternscan_play(struct engine
*e
, struct board
*b
, struct move
*m
)
221 struct patternscan
*ps
= e
->data
;
223 if (is_resign(m
->coord
))
226 static char str
[1048576]; // XXX
230 /* Scan for supported features. */
231 /* For specifiation of features and their payloads,
232 * please refer to pattern.h. */
233 process_pattern(ps
, b
, m
, &strp
);
235 if (ps
->competition
) {
236 /* Look at other possible moves as well. */
237 for (int f
= 0; f
< b
->flen
; f
++) {
238 struct move mo
= { .coord
= b
->f
[f
], .color
= m
->color
};
239 if (is_pass(mo
.coord
))
242 /* We want to list again the played move too. This is
243 * required by the MM tool. */
244 if (mo
.coord
== m
->coord
)
247 if (!board_is_valid_move(b
, &mo
))
249 if (!ps
->mm
) *strp
++ = ' ';
250 process_pattern(ps
, b
, &mo
, &strp
);
254 return ps
->no_pattern_match
? NULL
: str
;
258 patternscan_genmove(struct engine
*e
, struct board
*b
, struct time_info
*ti
, enum stone color
, bool pass_all_alive
)
260 fprintf(stderr
, "genmove command not available during patternscan!\n");
265 patternscan_done(struct engine
*e
)
267 struct patternscan
*ps
= e
->data
;
268 if (!ps
->gen_spat_dict
)
271 /* Save newly found patterns. */
274 FILE *f
= fopen(spatial_dict_filename
, "r");
275 if (f
) { fclose(f
); newfile
= false; }
276 f
= fopen(spatial_dict_filename
, "a");
278 spatial_dict_writeinfo(ps
->pc
.spat_dict
, f
);
280 for (int i
= ps
->loaded_spatials
; i
< ps
->pc
.spat_dict
->nspatials
; i
++) {
281 /* By default, threshold is 0 and condition is always true. */
282 assert(i
< ps
->nscounts
&& ps
->scounts
[i
] > 0);
283 if (ps
->scounts
[i
] >= ps
->spat_threshold
284 || ps
->pc
.spat_dict
->spatials
[i
].dist
== 3)
285 spatial_write(ps
->pc
.spat_dict
, &ps
->pc
.spat_dict
->spatials
[i
], i
, f
);
292 patternscan_state_init(char *arg
)
294 struct patternscan
*ps
= calloc2(1, sizeof(struct patternscan
));
298 ps
->pc
= DEFAULT_PATTERN_CONFIG
;
299 memcpy(&ps
->ps
, PATTERN_SPEC_MATCHALL
, sizeof(pattern_spec
));
302 char *optspec
, *next
= arg
;
305 next
+= strcspn(next
, ",");
306 if (*next
) { *next
++ = 0; } else { *next
= 0; }
308 char *optname
= optspec
;
309 char *optval
= strchr(optspec
, '=');
310 if (optval
) *optval
++ = 0;
312 if (!strcasecmp(optname
, "debug")) {
314 ps
->debug_level
= atoi(optval
);
318 } else if (!strcasecmp(optname
, "gen_spat_dict")) {
319 /* If set, re-generate the spatial patterns
320 * dictionary; you need to have a dictionary
321 * of spatial stone configurations in order
322 * to match any spatial features. */
323 ps
->gen_spat_dict
= !optval
|| atoi(optval
);
325 } else if (!strcasecmp(optname
, "no_pattern_match")) {
326 /* If set, do not actually match patterns.
327 * Useful only together with gen_spat_dict
328 * when just building spatial dictionary. */
329 ps
->no_pattern_match
= !optval
|| atoi(optval
);
331 } else if (!strcasecmp(optname
, "spat_threshold") && optval
) {
332 /* Minimal number of times new spatial
333 * feature must occur in this run (!) to
334 * be included in the dictionary. Note that
335 * this will produce discontinuous dictionary
336 * that you should renumber. Also note that
337 * 3x3 patterns are always saved. */
338 ps
->spat_threshold
= atoi(optval
);
340 } else if (!strcasecmp(optname
, "competition")) {
341 /* In competition mode, first the played
342 * pattern is printed, then all patterns
343 * that could be played (including the played
345 ps
->competition
= !optval
|| atoi(optval
);
347 } else if (!strcasecmp(optname
, "matchfast")) {
348 /* Limit the matched features only to the
349 * set used in MC simulations. */
350 ps
->pc
= FAST_PATTERN_CONFIG
;
351 memcpy(&ps
->ps
, PATTERN_SPEC_MATCHFAST
, sizeof(pattern_spec
));
353 } else if (!strcasecmp(optname
, "precisesa")) {
354 /* Use precise self-atari detection instead
355 * of quick one; makes sense ONLY with
356 * matchfast, otherwise we always do this. */
357 ps
->ps
[FEAT_SELFATARI
] &= ~(1<<PF_SELFATARI_STUPID
);
358 ps
->ps
[FEAT_SELFATARI
] |= (1<<PF_SELFATARI_SMART
);
360 } else if (!strcasecmp(optname
, "mm")) {
361 /* Generate output almost suitable for the
362 * Remi Coulom's MM tool, and auxiliar file
363 * "patterns.fdict" with mapping from gamma ids
364 * back to feature,payload pairs. You will need
365 * to post-process the output, substituting
367 ps
->mm
= !optval
|| atoi(optval
);
369 } else if (!strcasecmp(optname
, "xspat") && optval
) {
370 /* xspat==0: don't match spatial features
371 * xspat==1: match *only* spatial features */
372 xspat
= atoi(optval
);
374 /* See pattern.h:pattern_config for description and
375 * pattern.c:DEFAULT_PATTERN_CONFIG for default values
376 * of the following options. */
377 } else if (!strcasecmp(optname
, "spat_min") && optval
) {
378 ps
->pc
.spat_min
= atoi(optval
);
379 } else if (!strcasecmp(optname
, "spat_max") && optval
) {
380 ps
->pc
.spat_max
= atoi(optval
);
381 } else if (!strcasecmp(optname
, "bdist_max") && optval
) {
382 ps
->pc
.bdist_max
= atoi(optval
);
383 } else if (!strcasecmp(optname
, "ldist_min") && optval
) {
384 ps
->pc
.ldist_min
= atoi(optval
);
385 } else if (!strcasecmp(optname
, "ldist_max") && optval
) {
386 ps
->pc
.ldist_max
= atoi(optval
);
387 } else if (!strcasecmp(optname
, "mcsims") && optval
) {
388 ps
->pc
.mcsims
= atoi(optval
);
391 fprintf(stderr
, "patternscan: Invalid engine argument %s or missing value\n", optname
);
396 for (int i
= 0; i
< FEAT_MAX
; i
++) if ((xspat
== 0 && i
== FEAT_SPATIAL
) || (xspat
== 1 && i
!= FEAT_SPATIAL
)) ps
->ps
[i
] = 0;
397 ps
->pc
.spat_dict
= spatial_dict_init(ps
->gen_spat_dict
);
398 ps
->loaded_spatials
= ps
->pc
.spat_dict
->nspatials
;
407 engine_patternscan_init(char *arg
, struct board
*b
)
409 struct patternscan
*ps
= patternscan_state_init(arg
);
410 struct engine
*e
= calloc2(1, sizeof(struct engine
));
411 e
->name
= "PatternScan Engine";
412 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.";
413 e
->genmove
= patternscan_genmove
;
414 e
->notify_play
= patternscan_play
;
415 e
->done
= patternscan_done
;
417 // clear_board does not concern us, we like to work over many games
418 e
->keep_on_clear
= true;