9 #include "patternscan/patternscan.h"
11 #include "patternsp.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
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. */
34 struct pattern_setup pat
;
36 bool spat_split_sizes
;
39 bool no_pattern_match
;
41 /* Minimal number of occurences for spatial to be saved. */
43 /* Number of loaded spatials; checkpoint for saving new sids
44 * in case gen_spat_dict is enabled. */
47 /* Book-keeping of spatial occurence count. */
49 unsigned int nscounts
;
56 process_pattern(struct patternscan
*ps
, struct board
*b
, struct move
*m
, char **str
)
58 /* First, store the spatial configuration in dictionary
60 if (ps
->gen_spat_dict
&& !is_pass(m
->coord
)) {
62 spatial_from_board(&ps
->pat
.pc
, &s
, b
, m
);
64 for (int d
= ps
->pat
.pc
.spat_min
; d
<= dmax
; d
++) {
66 unsigned int sid
= spatial_dict_put(ps
->pat
.pc
.spat_dict
, &s
, spatial_hash(0, &s
));
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
->pat
.pc
.spat_dict
->nspatials
, ps
->pat
.pc
.spat_dict
->collisions
);
80 if (ps
->sgameno
[sid
] != ps
->gameno
) {
82 ps
->sgameno
[sid
] = ps
->gameno
;
87 /* Now, match the pattern. */
88 if (!ps
->no_pattern_match
) {
90 pattern_match(&ps
->pat
.pc
, ps
->pat
.ps
, &p
, b
, m
);
92 if (!ps
->spat_split_sizes
) {
93 *str
= pattern2str(*str
, &p
);
95 /* XXX: We assume that FEAT_SPATIAL items
99 while (i
< p
.n
&& p
.f
[i
].id
!= FEAT_SPATIAL
) {
105 *str
= pattern2str(*str
, &p2
);
108 for (int j
= i
; j
< p
.n
; j
++) {
109 assert(p
.f
[j
].id
== FEAT_SPATIAL
);
111 if ((*str
)[-1] == ')')
113 *str
= pattern2str(*str
, &p2
);
121 patternscan_play(struct engine
*e
, struct board
*b
, struct move
*m
, char *enginearg
)
123 struct patternscan
*ps
= e
->data
;
125 if (is_resign(m
->coord
))
127 /* Deal with broken game records that sometimes get fed in. */
128 if (board_at(b
, m
->coord
) != S_NONE
)
131 if (b
->moves
== (b
->handicap
? b
->handicap
* 2 : 1))
134 if (!(m
->color
& ps
->color_mask
))
136 /* The user can request this play to be "silent", to get patterns
137 * only for a single specific situation. */
138 if (enginearg
&& *enginearg
== '0')
141 static char str
[1048576]; // XXX
145 /* Scan for supported features. */
146 /* For specifiation of features and their payloads,
147 * please refer to pattern.h. */
149 process_pattern(ps
, b
, m
, &strp
);
152 if (ps
->competition
) {
153 /* Look at other possible moves as well. */
156 for (int f
= 0; f
< b
->flen
; f
++) {
157 struct move mo
= { .coord
= b
->f
[f
], .color
= m
->color
};
158 if (is_pass(mo
.coord
))
160 if (!board_is_valid_move(b
, &mo
))
164 process_pattern(ps
, b
, &mo
, &strp
);
170 return ps
->no_pattern_match
? NULL
: str
;
174 patternscan_genmove(struct engine
*e
, struct board
*b
, struct time_info
*ti
, enum stone color
, bool pass_all_alive
)
176 fprintf(stderr
, "genmove command not available during patternscan!\n");
181 patternscan_done(struct engine
*e
)
183 struct patternscan
*ps
= e
->data
;
184 if (!ps
->gen_spat_dict
)
187 /* Save newly found patterns. */
190 FILE *f
= fopen(spatial_dict_filename
, "r");
191 if (f
) { fclose(f
); newfile
= false; }
192 f
= fopen(spatial_dict_filename
, "a");
194 spatial_dict_writeinfo(ps
->pat
.pc
.spat_dict
, f
);
196 for (unsigned int i
= ps
->loaded_spatials
; i
< ps
->pat
.pc
.spat_dict
->nspatials
; i
++) {
197 /* By default, threshold is 0 and condition is always true. */
198 assert(i
< ps
->nscounts
&& ps
->scounts
[i
] > 0);
199 if (ps
->scounts
[i
] >= ps
->spat_threshold
)
200 spatial_write(ps
->pat
.pc
.spat_dict
, &ps
->pat
.pc
.spat_dict
->spatials
[i
], i
, f
);
207 patternscan_state_init(char *arg
)
209 struct patternscan
*ps
= calloc2(1, sizeof(struct patternscan
));
210 bool pat_setup
= false;
214 ps
->color_mask
= S_BLACK
| S_WHITE
;
217 char *optspec
, *next
= arg
;
220 next
+= strcspn(next
, ",");
221 if (*next
) { *next
++ = 0; } else { *next
= 0; }
223 char *optname
= optspec
;
224 char *optval
= strchr(optspec
, '=');
225 if (optval
) *optval
++ = 0;
227 if (!strcasecmp(optname
, "debug")) {
229 ps
->debug_level
= atoi(optval
);
233 } else if (!strcasecmp(optname
, "gen_spat_dict")) {
234 /* If set, re-generate the spatial patterns
235 * dictionary; you need to have a dictionary
236 * of spatial stone configurations in order
237 * to match any spatial features. */
238 /* XXX: If you specify the 'patterns' option,
239 * this must come first! */
240 ps
->gen_spat_dict
= !optval
|| atoi(optval
);
242 } else if (!strcasecmp(optname
, "no_pattern_match")) {
243 /* If set, do not actually match patterns.
244 * Useful only together with gen_spat_dict
245 * when just building spatial dictionary. */
246 ps
->no_pattern_match
= !optval
|| atoi(optval
);
248 } else if (!strcasecmp(optname
, "spat_threshold") && optval
) {
249 /* Minimal number of times new spatial
250 * feature must occur in this run (!) to
251 * be included in the dictionary. Note that
252 * this will produce discontinuous dictionary
253 * that you should renumber. Also note that
254 * 3x3 patterns are always saved. */
255 ps
->spat_threshold
= atoi(optval
);
257 } else if (!strcasecmp(optname
, "competition")) {
258 /* In competition mode, first the played
259 * pattern is printed, then all patterns
260 * that could be played (including the played
262 ps
->competition
= !optval
|| atoi(optval
);
264 } else if (!strcasecmp(optname
, "spat_split_sizes")) {
265 /* Generate a separate pattern for each
266 * spatial size. This is important to
267 * preserve good generalization in unknown
268 * situations where the largest pattern
269 * might not match. */
270 ps
->spat_split_sizes
= 1;
272 } else if (!strcasecmp(optname
, "color_mask") && optval
) {
273 /* Bitmask of move colors to match. Set this
274 * to 2 if you want to match only white moves,
275 * for example. (Useful for processing
276 * handicap games.) */
277 ps
->color_mask
= atoi(optval
);
279 } else if (!strcasecmp(optname
, "xspat") && optval
) {
280 /* xspat==0: don't match spatial features
281 * xspat==1: match *only* spatial features */
282 xspat
= atoi(optval
);
284 } else if (!strcasecmp(optname
, "patterns") && optval
) {
285 patterns_init(&ps
->pat
, optval
, ps
->gen_spat_dict
, false);
289 fprintf(stderr
, "patternscan: Invalid engine argument %s or missing value\n", optname
);
296 patterns_init(&ps
->pat
, NULL
, ps
->gen_spat_dict
, false);
297 if (ps
->spat_split_sizes
)
298 ps
->pat
.pc
.spat_largest
= 0;
300 for (int i
= 0; i
< FEAT_MAX
; i
++) if ((xspat
== 0 && i
== FEAT_SPATIAL
) || (xspat
== 1 && i
!= FEAT_SPATIAL
)) ps
->pat
.ps
[i
] = 0;
301 ps
->loaded_spatials
= ps
->pat
.pc
.spat_dict
->nspatials
;
309 engine_patternscan_init(char *arg
, struct board
*b
)
311 struct patternscan
*ps
= patternscan_state_init(arg
);
312 struct engine
*e
= calloc2(1, sizeof(struct engine
));
313 e
->name
= "PatternScan Engine";
314 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.";
315 e
->genmove
= patternscan_genmove
;
316 e
->notify_play
= patternscan_play
;
317 e
->done
= patternscan_done
;
319 // clear_board does not concern us, we like to work over many games
320 e
->keep_on_clear
= true;