ref: 1af1204b9c33c9c03b2e0fe66c2f07d9729cbc72
dir: /auxiliary/hatgen.c/
/* * Generate patches of tiling by the 'hat' aperiodic monotile * discovered in 2023. * * This implementation of hat-tiling generation was intended to be the * basis for generating hat grids for Loopy. However, it turned out * that I found a better strategy, so this source file isn't used by * the main puzzle system. I've kept it anyway because I ended up * adapting it to generate the file hat-tables.h containing the lookup * tables for the algorithm I _did_ end up using. It also generates * diagrams that are useful for understanding that algorithm, and for * debugging it if anything still turns out to be wrong with it. * * Discoverers' website: https://cs.uwaterloo.ca/~csk/hat/ * Preprint of paper: https://arxiv.org/abs/2303.10798 */ #include <assert.h> #include <math.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "puzzles.h" #include "tree234.h" #include "hat.h" /* * General strategy: * * We construct the hat tiling by means of a substitution system of * 'metatiles' which come in four types, called H,T,P,F. A (valid) * tiling of these metatiles can be expanded to a larger one by a set * of recursive subdivision rules. Once we have a large enough patch * of metatiles, we apply a final transformation that converts each * metatile into 1, 2 or 4 instances of the aperiodically tiling * 'hat'. * * Unlike the similar substitution system for Penrose tilings, the * expansion rules are not geometrically precise: the larger versions * of each metatile fit together combinatorially in the same way, but * their shapes are distorted slightly. So we must construct our * expanded meta-tiling by breadth-first search out from a starting * metatile, because we won't quite know the coordinates of the * expanded version of each metatile until we know one of the ones * next to it. */ /* * Coordinate system: * * Everything in this code lives on the tiling known to grid.c as * 'Kites', which can be viewed as a tiling of hexagons each of which * is subdivided into six kites sharing their pointy vertex, or * (equivalently) a tiling of equilateral triangles each subdivided * into three kits sharing their blunt vertex. * * We express coordinates in this system relative to the basis (1, r) * where r = (1 + sqrt(3)i) / 2 is a primitive 6th root of unity. This * gives us a system in which two integer coordinates can address any * grid point, provided we scale up so that the side length of the * equilateral triangles in the tiling is 6. */ typedef struct Point { int x, y; /* represents x + yr */ } Point; static inline Point left6(Point p) { /* r satisfies the equation r^2 = r-1. Hence, multiplying by r * (achieving a rotation anticlockwise by 1/6 turn) transforms * x+yr into xr+yr^2 = xr+y(r-1) = (-y) + (x+y)r. * * It's easy to check that iterating this transformation six times * gives you back the same coordinates you started with. */ Point q = { -p.y, p.x + p.y }; return q; } static inline Point right6(Point p) { /* Conversely, 1/r = 1 - r, so dividing by r turns x+yr into x/r+y * = x(1-r) + y = (x+y) + (-x)r. */ Point q = { p.x + p.y, -p.x }; return q; } typedef enum MetatileType { MT_H, MT_T, MT_P, MT_F } MetatileType; typedef struct Metatile Metatile; typedef struct MetaCoord { Metatile *parent; int index; } MetaCoord; struct Metatile { /* Data fields describing the metatile and its position. */ MetatileType type; Point start, orientation; MetaCoord coords[4]; size_t ncoords; /* Auxiliary fields used to store the progress of the expansion * algorithm. */ bool queued; Metatile *qnext; }; #define MT_MAXVERT 6 /* largest number of vertices of any metatile */ #define MT_MAXVDEGREE 3 /* largest degree of any vertex of a meta-tiling */ #define MT_MAXEXPAND 13 /* largest number of metatiles in any expansion */ #define MT_MAXHAT 4 /* largest number of hats in a metatile */ #define HAT_NVERT 14 /* vertices of a single hat (counting the straight one) */ #define HAT_NKITE 8 /* kites in a single hat */ static int metatile_cmp(void *av, void *bv) { Metatile *a = (Metatile *)av, *b = (Metatile *)bv; if (a->type < b->type) return -1; if (a->type > b->type) return +1; if (a->start.x < b->start.x) return -1; if (a->start.x > b->start.x) return +1; if (a->start.y < b->start.y) return -1; if (a->start.y > b->start.y) return +1; if (a->orientation.x < b->orientation.x) return -1; if (a->orientation.x > b->orientation.x) return +1; if (a->orientation.y < b->orientation.y) return -1; if (a->orientation.y > b->orientation.y) return +1; return 0; } /* * Return the coordinates of the vertices of a metatile, given the * coordinates of the vertex we deem to be distinguished, and a vector * of Euclidean length 1 showing its direction. * * If 'expanded' is true, we instead return the coordinates of the * corresponding vertices of the expanded version of the same * metatile. */ static size_t metatile_vertices(Metatile m, Point *out, bool expanded) { static const Point vertices_H[] = { {0, 0}, {4, -2}, {12, 6}, {10, 10}, {-6, 18}, {-8, 16}, }; static const Point vertices_T[] = { {0, 0}, {6, 6}, {-6, 12}, }; static const Point vertices_P[] = { {0, 0}, {4, 4}, {-4, 20}, {-8, 16}, }; static const Point vertices_F[] = { {0, 0}, {4, -2}, {6, 0}, {-2, 16}, {-6, 12}, }; static const Point expanded_H[] = { {0, 0}, {12, -6}, {30, 12}, {24, 24}, {-12, 42}, {-18, 36}, }; static const Point expanded_T[] = { {0, 0}, {12, 12}, {-12, 24}, }; static const Point expanded_P[] = { {0, 0}, {14, 8}, {-4, 44}, {-18, 36}, }; static const Point expanded_F[] = { {0, 0}, {14, -4}, {18, 6}, {0, 42}, {-14, 34}, }; const Point *vertices; size_t nvertices; size_t i; switch (m.type) { case MT_H: vertices = expanded ? expanded_H : vertices_H; nvertices = lenof(vertices_H); break; case MT_T: vertices = expanded ? expanded_T : vertices_T; nvertices = lenof(vertices_T); break; case MT_P: vertices = expanded ? expanded_P : vertices_P; nvertices = lenof(vertices_P); break; default /* case MT_F */: vertices = expanded ? expanded_F : vertices_F; nvertices = lenof(vertices_F); break; } assert(nvertices <= MT_MAXVERT); Point orientation_r = left6(m.orientation); for (i = 0; i < nvertices; i++) { Point v = vertices[i]; out[i].x = m.start.x + v.x * m.orientation.x + v.y * orientation_r.x; out[i].y = m.start.y + v.x * m.orientation.y + v.y * orientation_r.y; } return nvertices; } /* * Return a list of metatiles that arise from expanding a given tile. */ static size_t metatile_expand(Metatile m, Metatile *out) { static const Metatile tiles_H[] = { {MT_H, {-4, 20}, {1, 0}}, {MT_H, {2, 2}, {1, 0}}, {MT_H, {8, 26}, {0, -1}}, {MT_T, {6, 24}, {-1, 0}}, {MT_P, {-8, 16}, {1, 0}}, {MT_P, {4, 34}, {0, -1}}, {MT_P, {6, 0}, {1, -1}}, {MT_F, {-10, 38}, {-1, 1}}, {MT_F, {-10, 44}, {0, -1}}, {MT_F, {-4, 2}, {1, 0}}, {MT_F, {2, 2}, {0, -1}}, {MT_F, {26, 14}, {1, 0}}, {MT_F, {32, 8}, {-1, 1}}, }; static const Metatile tiles_T[] = { {MT_H, {10, 10}, {-1, 1}}, {MT_P, {-6, 0}, {1, 0}}, {MT_P, {8, 14}, {0, 1}}, {MT_P, {18, 6}, {-1, 1}}, {MT_F, {-14, 34}, {-1, 0}}, {MT_F, {-8, -2}, {1, -1}}, {MT_F, {22, 4}, {0, 1}}, }; static const Metatile tiles_P[] = { {MT_H, {4, 22}, {0, 1}}, {MT_H, {10, 10}, {-1, 1}}, {MT_P, {-6, 0}, {1, 0}}, {MT_P, {6, 24}, {1, 0}}, {MT_P, {8, 14}, {0, 1}}, {MT_F, {-20, 40}, {1, -1}}, {MT_F, {-14, 34}, {-1, 0}}, {MT_F, {-8, -2}, {1, -1}}, {MT_F, {4, 46}, {-1, 1}}, {MT_F, {10, 10}, {1, 0}}, {MT_F, {16, 4}, {-1, 1}}, }; static const Metatile tiles_F[] = { {MT_H, {8, 20}, {0, 1}}, {MT_H, {14, 8}, {-1, 1}}, {MT_P, {10, 22}, {1, 0}}, {MT_P, {12, 12}, {0, 1}}, {MT_F, {-16, 38}, {1, -1}}, {MT_F, {-10, 32}, {-1, 0}}, {MT_F, {-4, 2}, {1, 0}}, {MT_F, {2, 2}, {0, -1}}, {MT_F, {8, 44}, {-1, 1}}, {MT_F, {14, 8}, {1, 0}}, {MT_F, {20, 2}, {-1, 1}}, }; const Metatile *tiles; size_t ntiles; size_t i; switch (m.type) { case MT_H: tiles = tiles_H; ntiles = lenof(tiles_H); break; case MT_T: tiles = tiles_T; ntiles = lenof(tiles_T); break; case MT_P: tiles = tiles_P; ntiles = lenof(tiles_P); break; default /* case MT_F */: tiles = tiles_F; ntiles = lenof(tiles_F); break; } assert(ntiles <= MT_MAXEXPAND); Point orientation_r = left6(m.orientation); for (i = 0; i < ntiles; i++) { Metatile t = tiles[i]; out[i].type = t.type; out[i].start.x = (m.start.x + t.start.x * m.orientation.x + t.start.y * orientation_r.x); out[i].start.y = (m.start.y + t.start.x * m.orientation.y + t.start.y * orientation_r.y); out[i].orientation.x = (t.orientation.x * m.orientation.x + t.orientation.y * orientation_r.x); out[i].orientation.y = (t.orientation.x * m.orientation.y + t.orientation.y * orientation_r.y); } return ntiles; } /* Store data about each vertex during an expansion. */ typedef struct VertexMapping { Point in; /* Metatiles sharing this vertex */ Metatile *tiles[MT_MAXVDEGREE]; size_t ntiles; /* The expanded coordinates of this vertex, if known */ bool mapped; Point out; } VertexMapping; static int vertexmapping_cmp(void *av, void *bv) { VertexMapping *a = (VertexMapping *)av, *b = (VertexMapping *)bv; if (a->in.x < b->in.x) return -1; if (a->in.x > b->in.x) return +1; if (a->in.y < b->in.y) return -1; if (a->in.y > b->in.y) return +1; return 0; } static int vertexmapping_find(void *av, void *bv) { Point *a = (Point *)av; VertexMapping *b = (VertexMapping *)bv; if (a->x < b->in.x) return -1; if (a->x > b->in.x) return +1; if (a->y < b->in.y) return -1; if (a->y > b->in.y) return +1; return 0; } typedef struct MetatileSet { /* The tiles in the set */ tree234 *tiles; /* * Bounding box of a rectangular region within the original single * tile this set was expanded from. We need this in order to pick * a random chunk out of the tiling to return to our client: this * box is the limit of where we might select our chunk from. * * The box is obtained by starting from the two obtuse vertices of * the starting P metatile, and then mapping those two vertices * through each expansion pass. This wouldn't work for the _other_ * two vertices of the P metatile, which end up in the middle of * another metatile after the first expansion, so that the next * expansion wouldn't find that point in its VertexMapping. But * luckily the two inner P vertices do continue working: they * alternate in subsequent expansions between vertex 1 and vertex * 4 of an F metatile. And those are the ones we need to define a * reliable bounding box - phew! */ Point vertices[2]; size_t nvertices; } MetatileSet; static MetatileSet *metatile_initial_set(MetatileType type) { MetatileSet *s; Metatile *m; Point vertices[MT_MAXVERT]; size_t nv; s = snew(MetatileSet); s->tiles = newtree234(metatile_cmp); m = snew(Metatile); m->type = type; m->start.x = 0; m->start.y = 0; m->orientation.x = 1; m->orientation.y = 0; m->ncoords = 0; add234(s->tiles, m); if (type == MT_P) { nv = metatile_vertices(*m, vertices, false); assert(nv == 4); s->vertices[0] = vertices[1]; s->vertices[1] = vertices[3]; s->nvertices = 2; } else { s->nvertices = 0; } return s; } static void metatile_free_set(MetatileSet *s) { Metatile *m; while ((m = delpos234(s->tiles, 0)) != NULL) sfree(m); freetree234(s->tiles); sfree(s); } typedef struct Queue { Metatile *head, *tail; } Queue; static void map_vertex(VertexMapping *vm, Point out, Queue *queue) { size_t i; debug(("map_vertex %d,%d -> %d,%d", vm->in.x, vm->in.y, out.x, out.y)); if (vm->mapped) { debug((" (already done)\n")); return; } debug(("\n")); vm->mapped = true; vm->out = out; for (i = 0; i < vm->ntiles; i++) { Metatile *t = vm->tiles[i]; if (!t->queued) { t->queued = true; t->qnext = NULL; if (queue->tail) queue->tail->qnext = t; else queue->head = t; queue->tail = t; debug(("queued %c @ %d,%d d=%d,%d\n", "HTPF"[t->type], t->start.x, t->start.y, t->orientation.x, t->orientation.y)); } } } /* * Expand a set of metatiles into its next-generation set. Returns the * new set. The old set is not freed, but the auxiliary fields of its * Metatile structures will be used as intermediate storage. */ static MetatileSet *metatile_set_expand(MetatileSet *si) { tree234 *vmap; VertexMapping *vm; Metatile *m; Queue queue = { NULL, NULL }; size_t i, j; MetatileSet *so = snew(MetatileSet); so->tiles = newtree234(metatile_cmp); /* * Enumerate all the vertices in our tiling, and store the set of * tiles they belong to. */ vmap = newtree234(vertexmapping_cmp); for (i = 0; (m = index234(si->tiles, i)) != NULL; i++) { Point vertices[MT_MAXVERT]; size_t nv = metatile_vertices(*m, vertices, false); for (j = 0; j < nv; j++) { VertexMapping *newvm = snew(VertexMapping); newvm->in = vertices[j]; newvm->ntiles = 0; newvm->mapped = false; vm = add234(vmap, newvm); if (vm != newvm) sfree(newvm); assert(vm->ntiles < MT_MAXVDEGREE); vm->tiles[vm->ntiles++] = m; } m->queued = false; } for (i = 0; (vm = index234(vmap, i)) != NULL; i++) { debug(("vertex @ %d,%d {", vm->in.x, vm->in.y)); for (j = 0; j < vm->ntiles; j++) { m = vm->tiles[j]; debug(("%s%c @ %d,%d d=%d,%d", j?", ":"", "HTPF"[m->type], m->start.x, m->start.y, m->orientation.x, m->orientation.y)); } debug(("}\n")); } /* * Initialise an arbitrary vertex to a known location. */ { Point p = {0, 0}; m = index234(si->tiles, 0); vm = find234(vmap, &m->start, vertexmapping_find); map_vertex(vm, p, &queue); } /* * Now process the queue of tiles to be expanded. */ debug(("-- start\n")); while (queue.head) { Metatile *m, m_moved; Metatile t[MT_MAXEXPAND]; Point vi[MT_MAXVERT], vo[MT_MAXVERT]; size_t nv, nt; m = queue.head; queue.head = queue.head->qnext; if (!queue.head) queue.tail = NULL; debug(("unqueued %c @ %d,%d d=%d,%d\n", "HTPF"[m->type], m->start.x, m->start.y, m->orientation.x, m->orientation.y)); nv = metatile_vertices(*m, vi, false); metatile_vertices(*m, vo, true); /* Find a vertex of this tile that's already mapped, and use * it to determine the placement. */ int dx, dy; for (i = 0; i < nv; i++) { vm = find234(vmap, &vi[i], vertexmapping_find); assert(vm); if (vm->mapped) { dx = vm->out.x - vo[i].x; dy = vm->out.y - vo[i].y; debug(("found mapped vertex %d,%d -> %d,%d: " "offset=%d,%d\n", vm->in.x, vm->in.y, vm->out.x, vm->out.y, dx, dy)); break; } } assert(i < nv && "Why was this tile queued without a mapped vertex?"); /* Now map all the rest of the vertices of the tile. */ for (i = 0; i < nv; i++) { vm = find234(vmap, &vi[i], vertexmapping_find); vo[i].x += dx; vo[i].y += dy; map_vertex(vm, vo[i], &queue); } /* And expand it, substituting in its new starting coordinate. */ m_moved = *m; /* structure copy */ m_moved.start = vo[0]; nt = metatile_expand(m_moved, t); for (i = 0; i < nt; i++) { Metatile *newmt = snew(Metatile); *newmt = t[i]; /* structure copy */ newmt->ncoords = 0; debug(("expanded %c @ %d,%d d=%d,%d\n", "HTPF"[newmt->type], newmt->start.x, newmt->start.y, newmt->orientation.x, newmt->orientation.y)); Metatile *added = add234(so->tiles, newmt); if (added != newmt) sfree(newmt); assert(added->ncoords < lenof(added->coords)); added->coords[added->ncoords].parent = m; added->coords[added->ncoords].index = i; added->ncoords++; } } for (i = 0; (m = index234(si->tiles, i)) != NULL; i++) { if (!m->queued) debug(("OMITTED %c @ %d,%d d=%d,%d\n", "HTPF"[m->type], m->start.x, m->start.y, m->orientation.x, m->orientation.y)); } /* * Write out the remapped versions of the tile set's bounding * vertices. */ for (i = 0; i < si->nvertices; i++) { vm = find234(vmap, &si->vertices[i], vertexmapping_find); so->vertices[i] = vm->out; } so->nvertices = si->nvertices; while ((vm = delpos234(vmap, 0)) != NULL) sfree(vm); freetree234(vmap); return so; } typedef struct Hat { Point start, orientation; bool reversed; const Metatile *parent; int index; } Hat; static size_t metatile_hats(const Metatile *m, Hat *out) { static const Hat hats_H[] = { {{6, 0}, {1, 0}, false}, {{6, 6}, {0, -1}, false}, {{0, 12}, {1, 0}, false}, {{0, 6}, {-1, 0}, true}, }; static const Hat hats_T[] = { {{-2, 10}, {-1, 1}, false}, }; static const Hat hats_P[] = { {{-2, 10}, {-1, 1}, false}, {{-2, 16}, {0, 1}, false}, }; static const Hat hats_F[] = { {{0, 6}, {-1, 1}, false}, {{0, 12}, {0, 1}, false}, }; const Hat *hats; size_t nhats; size_t i; switch (m->type) { case MT_H: hats = hats_H; nhats = lenof(hats_H); break; case MT_T: hats = hats_T; nhats = lenof(hats_T); break; case MT_P: hats = hats_P; nhats = lenof(hats_P); break; default /* case MT_F */: hats = hats_F; nhats = lenof(hats_F); break; } assert(nhats <= MT_MAXHAT); Point orientation_r = left6(m->orientation); for (i = 0; i < nhats; i++) { Hat h = hats[i]; out[i].parent = m; out[i].index = i; out[i].start.x = (m->start.x + h.start.x * m->orientation.x + h.start.y * orientation_r.x); out[i].start.y = (m->start.y + h.start.x * m->orientation.y + h.start.y * orientation_r.y); out[i].orientation.x = (h.orientation.x * m->orientation.x + h.orientation.y * orientation_r.x); out[i].orientation.y = (h.orientation.x * m->orientation.y + h.orientation.y * orientation_r.y); out[i].reversed = h.reversed; } return nhats; } static size_t hat_vertices(Hat h, Point *out) { static const Point reference_hat[] = { {0, 0}, {3, 0}, {2, 2}, {0, 3}, {-2, 4}, {-3, 3}, {-6, 6}, {-9, 6}, {-8, 4}, {-6, 3}, {-6, 0}, {-3, -3}, {-2, -2}, {0, -3}, }; size_t i; Point orientation_r; if (h.reversed) orientation_r = right6(h.orientation); else orientation_r = left6(h.orientation); assert(lenof(reference_hat) == HAT_NVERT); for (i = 0; i < lenof(reference_hat); i++) { Point v = reference_hat[h.reversed ? HAT_NVERT-1-i : i]; out[i].x = h.start.x + v.x * h.orientation.x + v.y * orientation_r.x; out[i].y = h.start.y + v.x * h.orientation.y + v.y * orientation_r.y; } return lenof(reference_hat); } typedef struct BoundingBox { Point bl, tr; } BoundingBox; static bool point_in_bbox(Point p, const BoundingBox *bbox) { int xl, xr, x; if (!bbox) return true; /* * Bounding boxes have vertical edges, not aligned with our basis * vector r. So the 'true' x coordinate of (x,y) is proportional * to 2x+y. */ if (p.y < bbox->bl.y || p.y > bbox->tr.y) return false; xl = 2*bbox->bl.x + bbox->bl.y; xr = 2*bbox->tr.x + bbox->tr.y; x = 2*p.x + p.y; if (x < xl || x > xr) return false; return true; } static bool hat_in_bbox(Hat h, const BoundingBox *bbox) { Point p[HAT_NVERT]; size_t i, np; if (!bbox) return true; np = hat_vertices(h, p); for (i = 0; i < np; i++) if (!point_in_bbox(p[i], bbox)) return false; return true; } static Hat *metatile_set_to_hats(MetatileSet *s, size_t *nhats, const BoundingBox *bbox) { Metatile *m; size_t i, j, k, n; Hat *h; n = 0; for (i = 0; (m = index234(s->tiles, i)) != NULL; i++) { Hat htmp[MT_MAXHAT]; size_t nthis = metatile_hats(m, htmp); for (k = 0; k < nthis; k++) if (hat_in_bbox(htmp[k], bbox)) n++; } *nhats = n; h = snewn(n, Hat); j = 0; for (i = 0; (m = index234(s->tiles, i)) != NULL; i++) { Hat htmp[MT_MAXHAT]; size_t nthis = metatile_hats(m, htmp); for (k = 0; k < nthis; k++) { if (hat_in_bbox(htmp[k], bbox)) { assert(j < n); h[j++] = htmp[k]; /* structure copy */ } } } assert(j == n); return h; } #if 0 void hat_tiling_randomise(struct HatPatchParams *params, random_state *rs) { MetatileSet *s, *s2; int x0, x1, y0, y1; /* * Iterate until we have a good-sized patch to select a rectangle * from. */ s = metatile_initial_set(MT_P); params->iterations = 0; while (true) { x0 = 2 * s->vertices[0].x + s->vertices[0].y; x1 = 2 * s->vertices[1].x + s->vertices[1].y; if (x1 < x0) { int t = x1; x1 = x0; x0 = t; } y0 = s->vertices[0].y; y1 = s->vertices[1].y; if (y1 < y0) { int t = y1; y1 = y0; y0 = t; } if (50*params->w <= x1-x0 && 50*params->h <= y1-y0) break; params->iterations++; s2 = metatile_set_expand(s); metatile_free_set(s); s = s2; } /* * Now select that rectangle. */ params->x = x0 + random_upto(rs, x1 - x0 - params->w + 1); params->y = y0 + random_upto(rs, y1 - y0 - params->h + 1); } void hat_tiling_generate(struct HatPatchParams *params, hat_tile_callback_fn cb, void *cbctx) { MetatileSet *s, *s2; unsigned i; size_t j, nh; BoundingBox bbox; Hat *hats; s = metatile_initial_set(MT_P); for (i = 0; i < params->iterations; i++) { s2 = metatile_set_expand(s); metatile_free_set(s); s = s2; } bbox.bl.x = (params->x - params->y) / 2; bbox.tr.x = ((params->x + params->w) - (params->y + params->h)) / 2; bbox.bl.y = params->y; bbox.tr.y = params->y + params->h; hats = metatile_set_to_hats(s, &nh, &bbox); for (j = 0; j < nh; j++) { Point vertices[HAT_NVERT]; size_t nv = hat_vertices(hats[j], vertices); int out[2 * HAT_NVERT]; size_t k; for (k = 0; k < nv; k++) { out[2*k] = 2 * vertices[k].x + vertices[k].y; out[2*k+1] = vertices[k].y; } cb(cbctx, nv, out); } sfree(hats); metatile_free_set(s); } #endif #ifdef TEST_HAT /* * Assortment of test modes that output Postscript diagrams. */ static size_t hat_kite_centres(Hat h, Point *out) { static const Point reference_hat[] = { {-7,5},{-5,4},{-5,1},{-4,-1},{-1,-1},{-2,1},{-1,2},{1,1}, }; size_t i; Point orientation_r; if (h.reversed) orientation_r = right6(h.orientation); else orientation_r = left6(h.orientation); assert(lenof(reference_hat) == HAT_NKITE); for (i = 0; i < lenof(reference_hat); i++) { Point v = reference_hat[i]; out[i].x = h.start.x + v.x * h.orientation.x + v.y * orientation_r.x; out[i].y = h.start.y + v.x * h.orientation.y + v.y * orientation_r.y; } return lenof(reference_hat); } static inline int round6(int x) { int sign = x<0 ? -1 : +1; x *= sign; x += 3; x /= 6; x *= 6; x *= sign; return x; } static inline Point kite_left(Point k) { Point centre = { round6(k.x), round6(k.y) }; Point offset = { k.x - centre.x, k.y - centre.y }; offset = left6(offset); Point r = { centre.x + offset.x, centre.y + offset.y }; return r; } static inline Point kite_right(Point k) { Point centre = { round6(k.x), round6(k.y) }; Point offset = { k.x - centre.x, k.y - centre.y }; offset = right6(offset); Point r = { centre.x + offset.x, centre.y + offset.y }; return r; } static inline Point kite_forward_left(Point k) { Point centre = { round6(k.x), round6(k.y) }; Point offset = { k.x - centre.x, k.y - centre.y }; Point rotate = left6(offset); Point r = { k.x + rotate.x + offset.x, k.y + rotate.y + offset.y }; return r; } static inline Point kite_forward_right(Point k) { Point centre = { round6(k.x), round6(k.y) }; Point offset = { k.x - centre.x, k.y - centre.y }; Point rotate = right6(offset); Point r = { k.x + rotate.x + offset.x, k.y + rotate.y + offset.y }; return r; } typedef struct pspoint { float x, y; } pspoint; static inline pspoint pscoords(Point p) { pspoint q = { p.x + p.y / 2.0F, p.y * sqrt(0.75) }; return q; } typedef struct psbbox { bool started; pspoint bl, tr; } psbbox; static inline void psbbox_add(psbbox *bbox, pspoint p) { if (!bbox->started || bbox->bl.x > p.x) bbox->bl.x = p.x; if (!bbox->started || bbox->tr.x < p.x) bbox->tr.x = p.x; if (!bbox->started || bbox->bl.y > p.y) bbox->bl.y = p.y; if (!bbox->started || bbox->tr.y < p.y) bbox->tr.y = p.y; bbox->started = true; } static void draw_metatiles_svg(const Metatile *tiles, size_t n, const Point *bounds, bool coords) { size_t i, j; psbbox bbox = { false }; for (i = 0; i < n; i++) { Point vertices[MT_MAXVERT]; size_t nv = metatile_vertices(tiles[i], vertices, false); for (j = 0; j < nv; j++) psbbox_add(&bbox, pscoords(vertices[j])); } float ascale = 10, xscale = ascale, yscale = -ascale; float border = 0.2 * ascale; /* leave room for strokes at the edges */ float ox = -xscale * bbox.bl.x + border; float oy = -yscale * bbox.tr.y + border; printf("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"); printf("<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" " "width=\"%g\" height=\"%g\">\n", ceil(ox + xscale * bbox.tr.x + 2*border), ceil(oy + yscale * bbox.bl.y + 2*border)); for (i = 0; i < n; i++) { Point vertices[MT_MAXVERT]; size_t nv = metatile_vertices(tiles[i], vertices, false); pspoint pp[MT_MAXVERT]; for (j = 0; j < nv; j++) { pp[j] = pscoords(vertices[j]); pp[j].x = ox + xscale * pp[j].x; pp[j].y = oy + yscale * pp[j].y; } printf("<path style=\"" "fill: none; " "stroke: black; " "stroke-width: %f; " "stroke-linejoin: round; " "stroke-linecap: round; " "\" d=\"", 0.2 * ascale); for (j = 0; j < nv; j++) printf("%s %f %f \n", j ? "L" : "M", pp[j].x, pp[j].y); printf("z\" />\n"); if (tiles[i].type != MT_F) { /* * Mark arrows on three of the metatile types (H, T, P), * following the diagrams in the paper. (The metatile * shapes other than F each have some symmetry, but their * roles in the metatile substitution system are not * similarly symmetric, so for diagnostic diagrams you * want to mark their orientation.) */ pspoint lstart, lend; pspoint astart, aend, aforward, aleft; double d; /* * Determine endpoints of a line crossing the polygon in * the appropriate direction, by case analysis on the * individual tile types. */ switch (tiles[i].type) { case MT_H: lstart.x = (pp[4].x + pp[5].x) / 2; lstart.y = (pp[4].y + pp[5].y) / 2; lend.x = (pp[1].x + pp[2].x) / 2; lend.y = (pp[1].y + pp[2].y) / 2; break; case MT_T: lstart = pp[0]; lend.x = (pp[1].x + pp[2].x) / 2; lend.y = (pp[1].y + pp[2].y) / 2; break; default /* case MT_P */: lstart.x = (5*pp[3].x + 3*pp[0].x) / 8; lstart.y = (5*pp[3].y + 3*pp[0].y) / 8; lend.x = (5*pp[1].x + 3*pp[2].x) / 8; lend.y = (5*pp[1].y + 3*pp[2].y) / 8; break; } /* * Now shorten that line a little and give it an arrowhead. */ astart.x = (4 * lstart.x + lend.x) / 5; astart.y = (4 * lstart.y + lend.y) / 5; aend.x = (lstart.x + 4 * lend.x) / 5; aend.y = (lstart.y + 4 * lend.y) / 5; aforward.x = aend.x - astart.x; aforward.y = aend.y - astart.y; d = sqrt(aforward.x*aforward.x + aforward.y*aforward.y); aforward.x /= d; aforward.y /= d; aleft.x = -aforward.y; aleft.y = +aforward.x; printf("<path style=\"" "fill: none; " "stroke: black; " "stroke-width: %f; " "stroke-opacity: 0.2; " "stroke-linejoin: round; " "stroke-linecap: round; " "\" d=\"", 0.9 * ascale); printf("M %f %f L %f %f ", astart.x, astart.y, aend.x, aend.y); printf("L %f %f ", aend.x - 1.2 * ascale * (aforward.x + aleft.x), aend.y - 1.2 * ascale * (aforward.y + aleft.y)); printf("M %f %f L %f %f ", aend.x, aend.y, aend.x - 1.2 * ascale * (aforward.x - aleft.x), aend.y - 1.2 * ascale * (aforward.y - aleft.y)); printf("\" />\n"); } if (coords) { /* * Print each tile's coordinates. */ pspoint centre; size_t j; switch (tiles[i].type) { case MT_H: centre.x = (pp[0].x + pp[2].x + pp[4].x) / 3; centre.y = (pp[0].y + pp[2].y + pp[4].y) / 3; break; case MT_T: centre.x = (pp[0].x + pp[1].x + pp[2].x) / 3; centre.y = (pp[0].y + pp[1].y + pp[2].y) / 3; break; case MT_P: centre.x = (pp[0].x + pp[2].x) / 2; centre.y = (pp[0].y + pp[2].y) / 2; break; default /* case MT_F */: centre.x = (pp[2].x + pp[4].x) / 2; centre.y = (pp[2].y + pp[4].y) / 2; break; } double lineheight = ascale * 1.5; double charheight = lineheight * 0.6; /* close enough */ double allheight = lineheight * (tiles[i].ncoords-1) + charheight; for (j = 0; j < tiles[i].ncoords; j++) { const Metatile *it; unsigned cindex; printf("<text style=\"" "fill: black; " "font-family: Sans; " "font-size: %g; " "text-anchor: middle; " "text-align: center; " "\" x=\"%g\" y=\"%g\">", lineheight, centre.x, centre.y - allheight/2 + charheight + lineheight*j); it = &tiles[i]; cindex = j; while (cindex < it->ncoords) { if (it != &tiles[i]) printf("."); printf("%d", (int)it->coords[cindex].index); it = it->coords[cindex].parent; cindex = 0; /* BODGING AHOY */ } printf("</text>\n"); } } } printf("</svg>\n"); } static void draw_metatile_set_svg( MetatileSet *tiles, const Point *bounds, bool coords) { /* * Slurp the tree234 of tiles into an array for display. * Tedious, but this test code doesn't have to be particularly * efficient. */ size_t nt = count234(tiles->tiles); Metatile *t = snewn(nt, Metatile); size_t i; for (i = 0; i < nt; i++) { Metatile *m = index234(tiles->tiles, i); t[i] = *m; /* structure copy */ } draw_metatiles_svg(t, nt, bounds, coords); sfree(t); } static void draw_hats_svg(const Hat *hats, size_t n, const Point *bounds, bool kites, char coordtype) { size_t i, j; psbbox bbox = { false }; for (i = 0; i < n; i++) { Point vertices[HAT_NVERT]; size_t nv = hat_vertices(hats[i], vertices); for (j = 0; j < nv; j++) psbbox_add(&bbox, pscoords(vertices[j])); } float ascale = (coordtype == 'k' || coordtype == 'K' ? 20 : 10); float xscale = ascale, yscale = -ascale; float border = 0.2 * ascale; /* leave room for strokes at the edges */ float ox = -xscale * bbox.bl.x + border; float oy = -yscale * bbox.tr.y + border; printf("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"); printf("<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" " "width=\"%g\" height=\"%g\">\n", ceil(ox + xscale * bbox.tr.x + 2*border), ceil(oy + yscale * bbox.bl.y + 2*border)); for (i = 0; i < n; i++) { Point vertices[HAT_NVERT]; pspoint psvs[HAT_NVERT]; size_t nv = hat_vertices(hats[i], vertices); int is = hats[i].reversed ? -1 : +1; int io = hats[i].reversed ? 13 : 0; printf("<path style=\"" "fill: %s; " "stroke: black; " "stroke-width: %f; " "stroke-linejoin: round; " "stroke-linecap: round; " "\" d=\"", hats[i].reversed ? "rgba(0,0,0,0.2)" : "none", 0.2 * ascale); for (j = 0; j < nv; j++) { psvs[j] = pscoords(vertices[j]); printf("%s %f %f\n", j ? "L" : "M", ox + xscale * psvs[j].x, oy + yscale * psvs[j].y); } printf("z\" />\n"); if (kites) { /* * Draw internal lines within each hat dividing it into * kites. This is done in a rather bodgy way, sorry. */ const char *fmt = "<path style=\"" "fill: none; " "stroke: rgba(0,0,0,0.2); " "stroke-width: %f; " "stroke-linejoin: round; " "stroke-linecap: round; " "\" d=\"M %f %f L %f %f\" />\n"; float strokewidth = 0.1 * ascale; printf(fmt, strokewidth, ox + xscale * psvs[io+is*0].x, oy + yscale * psvs[io+is*0].y, ox + xscale * psvs[io+is*3].x, oy + yscale * psvs[io+is*3].y); printf(fmt, strokewidth, ox + xscale * psvs[io+is*0].x, oy + yscale * psvs[io+is*0].y, ox + xscale * psvs[io+is*5].x, oy + yscale * psvs[io+is*5].y); printf(fmt, strokewidth, ox + xscale * psvs[io+is*6].x, oy + yscale * psvs[io+is*6].y, ox + xscale * psvs[io+is*9].x, oy + yscale * psvs[io+is*9].y); printf(fmt, strokewidth, ox + xscale * psvs[io+is*0].x, oy + yscale * psvs[io+is*0].y, ox + xscale * psvs[io+is*10].x, oy + yscale * psvs[io+is*10].y); printf(fmt, strokewidth, ox + xscale * psvs[io+is*9].x, oy + yscale * psvs[io+is*9].y, ox + xscale * (psvs[io+is*6].x + psvs[io+is*12].x) / 2, oy + yscale * (psvs[io+is*6].y + psvs[io+is*12].y) / 2); printf(fmt, strokewidth, ox + xscale * psvs[io+is*5].x, oy + yscale * psvs[io+is*5].y, ox + xscale * (psvs[io+is*6].x + psvs[io+is*12].x) / 2, oy + yscale * (psvs[io+is*6].y + psvs[io+is*12].y) / 2); printf(fmt, strokewidth, ox + xscale * psvs[io+is*12].x, oy + yscale * psvs[io+is*12].y, ox + xscale * (psvs[io+is*6].x + psvs[io+is*12].x) / 2, oy + yscale * (psvs[io+is*6].y + psvs[io+is*12].y) / 2); } if (coordtype == 'h') { double lineheight = ascale * 2; double charheight = lineheight * 0.6; /* close enough */ printf("<text style=\"" "fill: black; " "font-family: Sans; " "font-size: %gpx; " "text-anchor: middle; " "text-align: center; " "\" x=\"%g\" y=\"%g\">", lineheight, ox + xscale * (psvs[io+is*0].x + psvs[io+is*10].x) / 2, oy + yscale * (psvs[io+is*0].y + psvs[io+is*10].y) / 2 + charheight/2); printf("%d", (int)i); printf("</text>\n"); } else if (coordtype == 'k') { Point kites[HAT_NKITE]; size_t nk = hat_kite_centres(hats[i], kites); double lineheight = ascale * 0.5; double charheight = lineheight * 0.6; /* close enough */ for (j = 0; j < nk; j++) { pspoint p = pscoords(kites[j]); printf("<text style=\"" "fill: black; " "font-family: Sans; " "font-size: %gpx; " "text-anchor: middle; " "text-align: center; " "\" x=\"%g\" y=\"%g\">", lineheight, ox + xscale * p.x, oy + yscale * p.y + charheight/2); printf("%d.%d.%d", (int)j, hats[i].index, hats[i].parent->coords[0].index); printf("</text>\n"); } } else if (coordtype == 'K') { Point kites[HAT_NKITE]; size_t nk = hat_kite_centres(hats[i], kites); double lineheight = ascale * 1.1; double charheight = lineheight * 0.6; /* close enough */ for (j = 0; j < nk; j++) { pspoint p = pscoords(kites[j]); printf("<text style=\"" "fill: black; " "font-family: Sans; " "font-size: %gpx; " "text-anchor: middle; " "text-align: center; " "\" x=\"%g\" y=\"%g\">", lineheight, ox + xscale * p.x, oy + yscale * p.y + charheight/2); printf("%d", (int)j); printf("</text>\n"); } } } printf("</svg>\n"); } typedef enum KiteStep { KS_LEFT, KS_RIGHT, KS_F_LEFT, KS_F_RIGHT } KiteStep; static inline Point kite_step(Point k, KiteStep step) { switch (step) { case KS_LEFT: return kite_left(k); case KS_RIGHT: return kite_right(k); case KS_F_LEFT: return kite_forward_left(k); default /* case KS_F_RIGHT */: return kite_forward_right(k); } } int main(int argc, char **argv) { if (argc <= 1) { printf("usage: hat-test <mode>\n"); printf("modes: H,T,P,F display a single unexpanded tile\n"); printf(" xH,xT,xP,xF display the expansion of one tile\n"); printf(" cH,cT,cP,cF display expansion with tile coords\n"); printf(" CH,CT,CP,CF display double expansion with coords\n"); printf(" hH,hT,hP,hF display the hats from one tile\n"); printf(" HH,HT,HP,HF hats from an expansion, with coords\n"); printf(" m1, m2, ... nth expansion of one H metatile\n"); printf(" M1, M2, ... nth expansion turned into real hats\n"); printf(" --hat show the kites in a single hat\n"); printf(" --tables generate hat-tables.h for hat.c\n"); return 0; } if (!strcmp(argv[1], "H") || !strcmp(argv[1], "T") || !strcmp(argv[1], "P") || !strcmp(argv[1], "F")) { MetatileType type = (argv[1][0] == 'H' ? MT_H : argv[1][0] == 'T' ? MT_T : argv[1][0] == 'P' ? MT_P : MT_F); Metatile m = {type, {0, 0}, {1, 0}}; draw_metatiles_svg(&m, 1, NULL, false); return 0; } if (!strcmp(argv[1], "xH") || !strcmp(argv[1], "xT") || !strcmp(argv[1], "xP") || !strcmp(argv[1], "xF")) { MetatileType type = (argv[1][1] == 'H' ? MT_H : argv[1][1] == 'T' ? MT_T : argv[1][1] == 'P' ? MT_P : MT_F); Metatile m = {type, {0, 0}, {1, 0}}; Metatile t[MT_MAXEXPAND]; size_t nt = metatile_expand(m, t); draw_metatiles_svg(t, nt, NULL, false); return 0; } if (argv[1][0] && argv[1][1] && strchr("cC", argv[1][0]) && strchr("HTPF", argv[1][1])) { MetatileType type = (argv[1][1] == 'H' ? MT_H : argv[1][1] == 'T' ? MT_T : argv[1][1] == 'P' ? MT_P : MT_F); MetatileSet *t[3]; int nsets = (argv[1][0] == 'c' ? 2 : 3); int i; t[0] = metatile_initial_set(type); for (i = 1; i < nsets; i++) t[i] = metatile_set_expand(t[i-1]); draw_metatile_set_svg(t[nsets-1], NULL, true); for (i = 0; i < nsets; i++) metatile_free_set(t[i]); return 0; } if (!strcmp(argv[1], "hH") || !strcmp(argv[1], "hT") || !strcmp(argv[1], "hP") || !strcmp(argv[1], "hF")) { MetatileType type = ( !strcmp(argv[1], "hH") ? MT_H : !strcmp(argv[1], "hT") ? MT_T : !strcmp(argv[1], "hP") ? MT_P : MT_F); Metatile m = {type, {0, 0}, {1, 0}}; Hat h[MT_MAXHAT]; size_t nh = metatile_hats(&m, h); draw_hats_svg(h, nh, NULL, false, 'h'); return 0; } if (!strcmp(argv[1], "--hat")) { Hat h = { { 0, 0 }, { 1, 0 }, false, NULL, 0 }; draw_hats_svg(&h, 1, NULL, true, 'K'); return 0; } if (argv[1][0] == 'H' && argv[1][1] && strchr("HTPF", argv[1][1])) { MetatileType type = (argv[1][1] == 'H' ? MT_H : argv[1][1] == 'T' ? MT_T : argv[1][1] == 'P' ? MT_P : MT_F); MetatileSet *t[2]; size_t i, nh; t[0] = metatile_initial_set(type); t[1] = metatile_set_expand(t[0]); Hat *h = metatile_set_to_hats(t[1], &nh, NULL); draw_hats_svg(h, nh, NULL, true, 'k'); sfree(h); for (i = 0; i < 2; i++) metatile_free_set(t[i]); return 0; } if (argv[1][0] == 'm' || argv[1][0] == 'M') { int niter = atoi(argv[1] + 1); MetatileSet *tiles = metatile_initial_set(MT_P); while (niter-- > 0) { MetatileSet *t2 = metatile_set_expand(tiles); metatile_free_set(tiles); tiles = t2; } if (argv[1][0] == 'M') { size_t nh; Hat *h = metatile_set_to_hats(tiles, &nh, NULL); draw_hats_svg(h, nh, tiles->vertices, false, 0); sfree(h); } else { draw_metatile_set_svg(tiles, tiles->vertices, false); } metatile_free_set(tiles); return 0; } if (!strcmp(argv[1], "--tables")) { size_t i, j, k; printf("/*\n" " * Header file autogenerated by auxiliary/hatgen.c\n" " *\n" " * To regenerate, run 'hatgen --tables > hat-tables.h'\n" " */\n\n"); static const char HTPF[] = "HTPF"; printf("static const unsigned hats_in_metatile[] = {"); for (i = 0; i < 4; i++) { Metatile m = {i, {0, 0}, {1, 0}}; Hat h[MT_MAXHAT]; size_t nh = metatile_hats(&m, h); printf(" %zu,", nh); } printf(" };\n\n"); { size_t psizes[4] = {0, 0, 0, 0}; size_t csizes[4] = {0, 0, 0, 0}; for (i = 0; i < 4; i++) { Metatile m = {i, {0, 0}, {1, 0}}; Metatile t[MT_MAXEXPAND]; size_t nt = metatile_expand(m, t); printf("static const TileType children_%c[] = {\n" " ", HTPF[i]); for (j = 0; j < nt; j++) { MetatileType c = t[j].type; psizes[c]++; csizes[i]++; printf(" TT_%c,", HTPF[c]); } printf("\n};\n"); } printf("static const TileType *const children[] = {\n"); for (i = 0; i < 4; i++) printf(" children_%c,\n", HTPF[i]); printf("};\n"); printf("static const size_t nchildren[] = {\n"); for (i = 0; i < 4; i++) printf(" %u,\n", (unsigned)csizes[i]); printf("};\n\n"); } { for (i = 0; i < 4; i++) { MetatileSet *t[2]; size_t j, k, nh, nmeta, ti; struct list { Point kite; unsigned ik, ih, im; } list[8*MT_MAXHAT*MT_MAXEXPAND]; size_t len = 0; t[0] = metatile_initial_set(i); t[1] = metatile_set_expand(t[0]); Hat *h = metatile_set_to_hats(t[1], &nh, NULL); printf("static const KitemapEntry kitemap_%c[] = {\n", HTPF[i]); Point origin = h[0].start; for (j = 0; j < nh; j++) { Point kites[HAT_NKITE]; size_t nk = hat_kite_centres(h[j], kites); for (k = 0; k < nk; k++) { struct list *le = &list[len++]; le->kite.x = kites[k].x - origin.x; le->kite.y = kites[k].y - origin.y; le->ik = k; le->ih = h[j].index; le->im = h[j].parent->coords[0].index; #if 0 printf("// %d,%d = %u.%u.%u\n", le->kite.x, le->kite.y, le->ik, le->ih, le->im); #endif } } nmeta = count234(t[1]->tiles); for (ti = 0; ti < 8 * 4 * nmeta; ti++) { unsigned ik = ti % 8; unsigned ih = ti / 8 % 4; unsigned im = ti / (8*4); struct list *src = NULL, *dst = NULL; int istep; for (j = 0; j < len; j++) { struct list *tmp = &list[j]; if (tmp->ik == ik && tmp->ih == ih && tmp->im == im) { src = tmp; break; } } if (ik == 0) { printf(" /* hat #%u in metatile #%u (type %c)", ih, im, HTPF[((Metatile *)index234( t[1]->tiles, im))->type]); if (!src) printf(" does not exist"); printf(" */\n"); } #if 0 if (src) printf(" // src=%d,%d\n", src->kite.x, src->kite.y); #endif printf(" "); for (istep = 0; istep < 4; istep++) { KiteStep step = istep; dst = NULL; if (src) { Point pdst = kite_step(src->kite, step); #if 0 printf(" /* dst=%d,%d */", pdst.x, pdst.y); #endif for (k = 0; k < len; k++) { struct list *tmp = &list[k]; if (tmp->kite.x == pdst.x && tmp->kite.y == pdst.y) { dst = tmp; break; } } } if (!dst) { printf(" {-1,-1,-1},"); } else { printf(" {%u,%u,%u},", dst->ik, dst->ih, dst->im); } } printf("\n"); } printf("};\n"); sfree(h); for (j = 0; j < 2; j++) metatile_free_set(t[j]); } printf("static const KitemapEntry *const " "kitemap[] = {\n"); for (i = 0; i < 4; i++) printf(" kitemap_%c,\n", HTPF[i]); printf("};\n\n"); } { for (i = 0; i < 4; i++) { MetatileSet *t[3]; Metatile *m; int map[MT_MAXEXPAND * MT_MAXEXPAND]; size_t maplen; for (j = 0; j < lenof(map); j++) map[j] = -1; t[0] = metatile_initial_set(i); for (j = 1; j < 3; j++) t[j] = metatile_set_expand(t[j-1]); for (j = 0; (m = index234(t[2]->tiles, j)) != NULL; j++) { unsigned coords[4]; size_t ncoords = 0; int cindex; #if 0 printf("// ***\n"); #endif for (cindex = 0; cindex < m->ncoords; cindex++) { #if 0 printf("// %d.%d\n", (int)m->coords[cindex].index, (int)m->coords[cindex].parent->coords[0].index); #endif coords[ncoords++] = ( m->coords[cindex].index + MT_MAXEXPAND * m->coords[cindex].parent->coords[0].index); } unsigned prev = ncoords-1; for (k = 0; k < ncoords; k++) { map[coords[prev]] = coords[k]; prev = k; } } printf("static const MetamapEntry metamap_%c[] = {\n", HTPF[i]); maplen = MT_MAXEXPAND * count234(t[1]->tiles); for (j = 0; j < maplen; j++) { printf(" /* %u, %u -> */ ", (unsigned)(j % MT_MAXEXPAND), (unsigned)(j / MT_MAXEXPAND)); if (map[j] == -1) { printf("{-1,-1}, /* does not exist */\n"); } else { printf("{%u, %u},", (unsigned)(map[j] % MT_MAXEXPAND), (unsigned)(map[j] / MT_MAXEXPAND)); if (map[j] == j) printf(" /* no alternatives */"); printf("\n"); } } printf("};\n"); for (j = 0; j < 3; j++) metatile_free_set(t[j]); } printf("static const MetamapEntry *const " "metamap[] = {\n"); for (i = 0; i < 4; i++) printf(" metamap_%c,\n", HTPF[i]); printf("};\n"); } return 0; } fprintf(stderr, "unknown test mode '%s'\n", argv[1]); return 1; } #endif