UCT dynkomi: Clean up helper routine definitions
[pachi.git] / patternsp.c
blob77d21e50617f39844d824577dc1238dbb9d80dfa
1 #define DEBUG
2 #include <assert.h>
3 #include <ctype.h>
4 #include <inttypes.h>
5 #include <stdio.h>
6 #include <stdlib.h>
8 #include "board.h"
9 #include "debug.h"
10 #include "pattern.h"
11 #include "patternsp.h"
12 #include "tactics.h"
14 /* Mapping from point sequence to coordinate offsets (to determine
15 * coordinates relative to pattern center). The array is ordered
16 * in the gridcular metric order so that we can go through it
17 * and incrementally match spatial features in nested circles.
18 * Within one circle, coordinates are ordered by rows to keep
19 * good cache behavior. */
20 struct ptcoord ptcoords[MAX_PATTERN_AREA];
22 /* For each radius, starting index in ptcoords[]. */
23 int ptind[MAX_PATTERN_DIST + 2];
25 /* ptcoords[], ptind[] setup */
26 static void
27 ptcoords_init(void)
29 int i = 0; /* Indexing ptcoords[] */
31 /* First, center point. */
32 ptind[0] = ptind[1] = 0;
33 ptcoords[i].x = ptcoords[i].y = 0; i++;
35 for (int d = 2; d <= MAX_PATTERN_DIST; d++) {
36 ptind[d] = i;
37 /* For each y, examine all integer solutions
38 * of d = |x| + |y| + max(|x|, |y|). */
39 /* TODO: (Stern, 2006) uses a hand-modified
40 * circles that are finer for small d. */
41 for (short y = d / 2; y >= 0; y--) {
42 short x;
43 if (y > d / 3) {
44 /* max(|x|, |y|) = |y|, non-zero x */
45 x = d - y * 2;
46 if (x + y * 2 != d) continue;
47 } else {
48 /* max(|x|, |y|) = |x| */
49 /* Or, max(|x|, |y|) = |y| and x is zero */
50 x = (d - y) / 2;
51 if (x * 2 + y != d) continue;
54 assert((x > y ? x : y) + x + y == d);
56 ptcoords[i].x = x; ptcoords[i].y = y; i++;
57 if (x != 0) { ptcoords[i].x = -x; ptcoords[i].y = y; i++; }
58 if (y != 0) { ptcoords[i].x = x; ptcoords[i].y = -y; i++; }
59 if (x != 0 && y != 0) { ptcoords[i].x = -x; ptcoords[i].y = -y; i++; }
62 ptind[MAX_PATTERN_DIST + 1] = i;
64 #if 0
65 for (int d = 0; d <= MAX_PATTERN_DIST; d++) {
66 fprintf(stderr, "d=%d (%d) ", d, ptind[d]);
67 for (int j = ptind[d]; j < ptind[d + 1]; j++) {
68 fprintf(stderr, "%d,%d ", ptcoords[j].x, ptcoords[j].y);
70 fprintf(stderr, "\n");
72 #endif
76 /* Zobrist hashes used for points in patterns. */
77 hash_t pthashes[PTH__ROTATIONS][MAX_PATTERN_AREA][S_MAX];
79 static void
80 pthashes_init(void)
82 /* We need fixed hashes for all pattern-relative in
83 * all pattern users! This is a simple way to generate
84 * hopefully good ones. Park-Miller powa. :) */
86 /* We create a virtual board (centered at the sequence start),
87 * plant the hashes there, then pick them up into the sequence
88 * with correct coordinates. It would be possible to generate
89 * the sequence point hashes directly, but the rotations would
90 * make for enormous headaches. */
91 hash_t pthboard[MAX_PATTERN_AREA][4];
92 int pthbc = MAX_PATTERN_AREA / 2; // tengen coord
94 /* The magic numbers are tuned for minimal collisions. */
95 hash_t h = 0x313131;
96 for (int i = 0; i < MAX_PATTERN_AREA; i++) {
97 pthboard[i][S_NONE] = (h = h * 16803 - 7);
98 pthboard[i][S_BLACK] = (h = h * 16805 + 7);
99 pthboard[i][S_WHITE] = (h = h * 16807 + 3);
100 pthboard[i][S_OFFBOARD] = (h = h * 16809 - 3);
103 /* Virtual board with hashes created, now fill
104 * pthashes[] with hashes for points in actual
105 * sequences, also considering various rotations. */
106 #define PTH_VMIRROR 1
107 #define PTH_HMIRROR 2
108 #define PTH_90ROT 4
109 for (int r = 0; r < PTH__ROTATIONS; r++) {
110 for (int i = 0; i < MAX_PATTERN_AREA; i++) {
111 /* Rotate appropriately. */
112 int rx = ptcoords[i].x;
113 int ry = ptcoords[i].y;
114 if (r & PTH_VMIRROR) ry = -ry;
115 if (r & PTH_HMIRROR) rx = -rx;
116 if (r & PTH_90ROT) {
117 int rs = rx; rx = -ry; ry = rs;
119 int bi = pthbc + ry * MAX_PATTERN_DIST + rx;
121 /* Copy info. */
122 pthashes[r][i][S_NONE] = pthboard[bi][S_NONE];
123 pthashes[r][i][S_BLACK] = pthboard[bi][S_BLACK];
124 pthashes[r][i][S_WHITE] = pthboard[bi][S_WHITE];
125 pthashes[r][i][S_OFFBOARD] = pthboard[bi][S_OFFBOARD];
130 static void __attribute__((constructor))
131 spatial_init(void)
133 /* Initialization of various static data structures for
134 * fast pattern processing. */
135 ptcoords_init();
136 pthashes_init();
139 inline hash_t
140 spatial_hash(int rotation, struct spatial *s)
142 hash_t h = 0;
143 for (int i = 0; i < ptind[s->dist + 1]; i++) {
144 h ^= pthashes[rotation][i][spatial_point_at(*s, i)];
146 return h & spatial_hash_mask;
149 char *
150 spatial2str(struct spatial *s)
152 static char buf[1024];
153 for (int i = 0; i < ptind[s->dist + 1]; i++) {
154 buf[i] = stone2char(spatial_point_at(*s, i));
156 buf[ptind[s->dist + 1]] = 0;
157 return buf;
160 void
161 spatial_from_board(struct pattern_config *pc, struct spatial *s,
162 struct board *b, struct move *m)
164 assert(pc->spat_min > 0);
166 /* We record all spatial patterns black-to-play; simply
167 * reverse all colors if we are white-to-play. */
168 static enum stone bt_black[4] = { S_NONE, S_BLACK, S_WHITE, S_OFFBOARD };
169 static enum stone bt_white[4] = { S_NONE, S_WHITE, S_BLACK, S_OFFBOARD };
170 enum stone (*bt)[4] = m->color == S_WHITE ? &bt_white : &bt_black;
172 memset(s, 0, sizeof(*s));
173 for (int j = 0; j < ptind[pc->spat_max + 1]; j++) {
174 ptcoords_at(x, y, m->coord, b, j);
175 s->points[j / 4] |= (*bt)[board_atxy(b, x, y)] << ((j % 4) * 2);
177 s->dist = pc->spat_max;
180 /* Compare two spatials, allowing for differences up to isomorphism.
181 * True means the spatials are equivalent. */
182 static bool
183 spatial_cmp(struct spatial *s1, struct spatial *s2)
185 /* Quick preliminary check. */
186 if (s1->dist != s2->dist)
187 return false;
189 /* We could create complex transposition tables, but it seems most
190 * foolproof to just check if the sets of rotation hashes are the
191 * same for both. */
192 hash_t s1r[PTH__ROTATIONS];
193 for (int r = 0; r < PTH__ROTATIONS; r++)
194 s1r[r] = spatial_hash(r, s1);
195 for (int r = 0; r < PTH__ROTATIONS; r++) {
196 hash_t s2r = spatial_hash(r, s2);
197 for (int p = 0; p < PTH__ROTATIONS; p++)
198 if (s2r == s1r[p])
199 goto found_rot;
200 /* Rotation hash s2r does not correspond to s1r. */
201 return false;
202 found_rot:;
205 /* All rotation hashes of s2 occur in s1. Hopefully that
206 * indicates something. */
207 return true;
211 /* Spatial dict manipulation. */
213 static int
214 spatial_dict_addc(struct spatial_dict *dict, struct spatial *s)
216 /* Allocate space in 1024 blocks. */
217 #define SPATIALS_ALLOC 1024
218 if (!(dict->nspatials % SPATIALS_ALLOC)) {
219 dict->spatials = realloc(dict->spatials,
220 (dict->nspatials + SPATIALS_ALLOC)
221 * sizeof(*dict->spatials));
223 dict->spatials[dict->nspatials] = *s;
224 return dict->nspatials++;
227 static bool
228 spatial_dict_addh(struct spatial_dict *dict, hash_t hash, int id)
230 if (dict->hash[hash] && dict->hash[hash] != id)
231 dict->collisions++;
232 dict->hash[hash] = id;
233 return true;
236 /* Spatial dictionary file format:
237 * /^#/ - comments
238 * INDEX RADIUS STONES HASH...
239 * INDEX: index in the spatial table
240 * RADIUS: @d of the pattern
241 * STONES: string of ".XO#" chars
242 * HASH...: space-separated 18bit hash-table indices for the pattern */
244 static void
245 spatial_dict_read(struct spatial_dict *dict, char *buf)
247 /* XXX: We trust the data. Bad data will crash us. */
248 char *bufp = buf;
250 int index, radius;
251 index = strtol(bufp, &bufp, 10);
252 radius = strtol(bufp, &bufp, 10);
253 while (isspace(*bufp)) bufp++;
255 /* Load the stone configuration. */
256 struct spatial s = { .dist = radius };
257 int sl = 0;
258 while (!isspace(*bufp)) {
259 s.points[sl / 4] |= char2stone(*bufp++) << ((sl % 4)*2);
260 sl++;
262 while (isspace(*bufp)) bufp++;
264 /* Sanity check. */
265 if (sl != ptind[s.dist + 1]) {
266 fprintf(stderr, "Spatial dictionary: Invalid number of stones (%d != %d) on this line: %s\n",
267 sl, ptind[radius + 1] - 1, buf);
268 exit(EXIT_FAILURE);
271 /* Add to collection. */
272 int id = spatial_dict_addc(dict, &s);
274 /* Add to specified hash places. */
275 while (*bufp) {
276 int hash = strtol(bufp, &bufp, 16);
277 while (isspace(*bufp)) bufp++;
278 spatial_dict_addh(dict, hash & spatial_hash_mask, id);
282 void
283 spatial_write(struct spatial_dict *dict, struct spatial *s, int id, FILE *f)
285 fprintf(f, "%d %d ", id, s->dist);
286 fputs(spatial2str(s), f);
287 for (int r = 0; r < PTH__ROTATIONS; r++) {
288 hash_t rhash = spatial_hash(r, s);
289 int id2 = dict->hash[rhash];
290 if (id2 != id) {
291 /* This hash does not belong to us. Decide whether
292 * we or the current owner is better owner. */
293 /* TODO: Compare also # of patternscan encounters? */
294 struct spatial *s2 = &dict->spatials[id2];
295 if (s2->dist < s->dist)
296 continue;
297 if (s2->dist == s->dist && id2 < id)
298 continue;
300 fprintf(f, " %"PRIhash"", spatial_hash(r, s));
302 fputc('\n', f);
305 static void
306 spatial_dict_load(struct spatial_dict *dict, FILE *f)
308 char buf[1024];
309 while (fgets(buf, sizeof(buf), f)) {
310 if (buf[0] == '#') continue;
311 spatial_dict_read(dict, buf);
315 void
316 spatial_dict_writeinfo(struct spatial_dict *dict, FILE *f)
318 /* New file. First, create a comment describing order
319 * of points in the array. This is just for purposes
320 * of external tools, Pachi never interprets it itself. */
321 fprintf(f, "# Pachi spatial patterns dictionary v1.0 maxdist %d\n",
322 MAX_PATTERN_DIST);
323 for (int d = 0; d <= MAX_PATTERN_DIST; d++) {
324 fprintf(f, "# Point order: d=%d ", d);
325 for (int j = ptind[d]; j < ptind[d + 1]; j++) {
326 fprintf(f, "%d,%d ", ptcoords[j].x, ptcoords[j].y);
328 fprintf(f, "\n");
332 const char *spatial_dict_filename = "patterns.spat";
333 struct spatial_dict *
334 spatial_dict_init(bool will_append)
336 FILE *f = fopen(spatial_dict_filename, "r");
337 if (!f && !will_append) {
338 if (DEBUGL(1))
339 fprintf(stderr, "No spatial dictionary, will not match spatial pattern features.\n");
340 return NULL;
343 struct spatial_dict *dict = calloc(1, sizeof(*dict));
344 /* We create a dummy record for index 0 that we will
345 * never reference. This is so that hash value 0 can
346 * represent "no value". */
347 struct spatial dummy = { .dist = 0 };
348 spatial_dict_addc(dict, &dummy);
350 if (f) {
351 spatial_dict_load(dict, f);
352 fclose(f); f = NULL;
353 } else {
354 assert(will_append);
357 return dict;
361 spatial_dict_put(struct spatial_dict *dict, struct spatial *s, hash_t h)
363 /* We avoid spatial_dict_get() here, since we want to ignore radius
364 * differences - we have custom collision detection. */
365 int id = dict->hash[h];
366 if (id > 0) {
367 /* Is this the same or isomorphous spatial? */
368 if (spatial_cmp(s, &dict->spatials[id]))
369 return id;
371 /* Look a bit harder - perhaps one of our rotations still
372 * points at the correct spatial. */
373 for (int r = 0; r < PTH__ROTATIONS; r++) {
374 hash_t rhash = spatial_hash(r, s);
375 int rid = dict->hash[rhash];
376 /* No match means we definitely aren't stored yet. */
377 if (!rid)
378 break;
379 if (id != rid && spatial_cmp(s, &dict->spatials[rid])) {
380 /* Yay, this is us! */
381 if (DEBUGL(3))
382 fprintf(stderr, "Repeated collision %d vs %d\n", id, rid);
383 id = rid;
384 /* Point the hashes back to us. */
385 goto hash_store;
389 if (DEBUGL(1))
390 fprintf(stderr, "Collision %d vs %d\n", id, dict->nspatials);
391 id = 0;
392 /* dict->collisions++; gets done by addh */
395 /* Add new pattern! */
396 id = spatial_dict_addc(dict, s);
397 if (DEBUGL(4)) {
398 fprintf(stderr, "new spat %d(%d) %s <%"PRIhash"> ", id, s->dist, spatial2str(s), h);
399 for (int r = 0; r < 8; r++)
400 fprintf(stderr,"[%"PRIhash"] ", spatial_hash(r, s));
401 fprintf(stderr, "\n");
404 /* Store new pattern in the hash. */
405 hash_store:
406 for (int r = 0; r < PTH__ROTATIONS; r++)
407 spatial_dict_addh(dict, spatial_hash(r, s), id);
409 return id;
413 /** Pattern3 helpers */
415 /* XXX: We have hard-coded this point order:
416 * # Point order: d=1 0,0
417 * # Point order: d=2 0,1 0,-1 1,0 -1,0
418 * # Point order: d=3 1,1 -1,1 1,-1 -1,-1
420 /* p3bits describe location of given point in the
421 * pattern3 hash word. */
422 static const int p3bits[] = { -1, 1, 6, 3, 4, 0, 2, 5, 7 };
425 static hash_t
426 pattern3_to_spatial(int r, int pat3)
428 hash_t h = pthashes[r][0][S_NONE];
429 for (int i = 1; i < 9; i++)
430 h ^= pthashes[r][i][(pat3 >> (p3bits[i] * 2)) & 0x3];
431 return h & spatial_hash_mask;
435 spatial_to_pattern3(struct spatial *s)
437 assert(s->dist == 3);
438 int pat3 = 0;
439 for (int i = 1; i < 9; i++)
440 pat3 |= spatial_point_at(*s, i) << (p3bits[i] * 2);
441 return pat3;
445 pattern3_by_spatial(struct spatial_dict *dict, int pat3)
447 /* Just pull pat3 through the spatial database to generate
448 * hash of its canonical form. */
449 int s;
450 for (int r = 0; r < PTH__ROTATIONS; r++) {
451 hash_t h = pattern3_to_spatial(r, pat3);
452 s = spatial_dict_get(dict, 3, h);
453 if (s > 0) break;
454 /* We might need to try another rotation in case
455 * of a collision. */
457 /* XXX: We assume our spatial dictionary is _sane_, that is,
458 * all valid 3x3 patterns we could encounter are in the
459 * dictionary. If you hit this assert(), you probably
460 * generated the spatial dict over too few games; it is best
461 * to generate it over the same set of games as you match
462 * patterns on afterwards. */
463 assert(s > 0);
464 return spatial_to_pattern3(&dict->spatials[s]);