From 71e1776094aa9240e9772b7fbc99dd5e2f4e1acb Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 2 Apr 2023 10:20:37 +0100 Subject: [PATCH] Move hat-test into its own source file. I noticed while hacking on hat-test recently that it's quite awkward to be compiling a test main() program that lives in a source file also built into the Puzzles support library, because every modification to main() also triggers a rebuild of the library, and thence of all the actual puzzles. So it's better if such a test main() has its own source file. In order to make hat-test work standalone, I've had to move a lot of hat.c's internal declarations out into a second header file. This also means making a bunch of internal functions global, which means they're also in the namespace of programs other than hat-test, which means in turn that they should have names with less implicit context. --- CMakeLists.txt | 1 - auxiliary/CMakeLists.txt | 1 + auxiliary/hat-test.c | 622 +++++++++++++++++++++++++ hat-internal.h | 271 +++++++++++ hat.c | 984 +++------------------------------------ 5 files changed, 956 insertions(+), 923 deletions(-) create mode 100644 auxiliary/hat-test.c create mode 100644 hat-internal.h diff --git a/CMakeLists.txt b/CMakeLists.txt index df942c9..a70532c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -271,7 +271,6 @@ cliprogram(latincheck latin.c COMPILE_DEFINITIONS STANDALONE_LATIN_TEST) cliprogram(matching matching.c COMPILE_DEFINITIONS STANDALONE_MATCHING_TEST) cliprogram(combi combi.c COMPILE_DEFINITIONS STANDALONE_COMBI_TEST) cliprogram(divvy divvy.c COMPILE_DEFINITIONS TESTMODE) -cliprogram(hat-test hat.c COMPILE_DEFINITIONS TEST_HAT) cliprogram(penrose-test penrose.c COMPILE_DEFINITIONS TEST_PENROSE) cliprogram(penrose-vector-test penrose.c COMPILE_DEFINITIONS TEST_VECTORS) cliprogram(sort-test sort.c COMPILE_DEFINITIONS SORT_TEST) diff --git a/auxiliary/CMakeLists.txt b/auxiliary/CMakeLists.txt index 82e8c82..d78b60a 100644 --- a/auxiliary/CMakeLists.txt +++ b/auxiliary/CMakeLists.txt @@ -1 +1,2 @@ cliprogram(hatgen hatgen.c COMPILE_DEFINITIONS TEST_HAT) +cliprogram(hat-test hat-test.c) diff --git a/auxiliary/hat-test.c b/auxiliary/hat-test.c new file mode 100644 index 0000000..8104161 --- /dev/null +++ b/auxiliary/hat-test.c @@ -0,0 +1,622 @@ +/* + * Standalone test program for hat.c, which generates patches of hat + * tiling in multiple output formats without also generating a Loopy + * puzzle around them. + */ + +#include +#include +#include +#include +#include + +#include "hat-internal.h" + +static HatCoords *hat_coords_construct_v(TileType type, va_list ap) +{ + HatCoords *hc = hat_coords_new(); + while (true) { + int index = va_arg(ap, int); + + hat_coords_make_space(hc, hc->nc + 1); + hc->c[hc->nc].type = type; + hc->c[hc->nc].index = index; + hc->nc++; + + if (index < 0) + return hc; + + type = va_arg(ap, TileType); + } +} + +static HatCoords *hat_coords_construct(TileType type, ...) +{ + HatCoords *hc; + va_list ap; + + va_start(ap, type); + hc = hat_coords_construct_v(type, ap); + va_end(ap); + + return hc; +} + +static bool hat_coords_equal(HatCoords *hc1, HatCoords *hc2) +{ + size_t i; + + if (hc1->nc != hc2->nc) + return false; + + for (i = 0; i < hc1->nc; i++) { + if (hc1->c[i].type != hc2->c[i].type || + hc1->c[i].index != hc2->c[i].index) + return false; + } + + return true; +} + +static bool hat_coords_expect(const char *file, int line, HatCoords *hc, + TileType type, ...) +{ + bool equal; + va_list ap; + HatCoords *hce; + + va_start(ap, type); + hce = hat_coords_construct_v(type, ap); + va_end(ap); + + equal = hat_coords_equal(hc, hce); + + if (!equal) { + fprintf(stderr, "%s:%d: coordinate mismatch\n", file, line); + hat_coords_debug(" expected: ", hce, "\n"); + hat_coords_debug(" actual: ", hc, "\n"); + } + + hat_coords_free(hce); + return equal; +} + +#define EXPECT(hc, ...) do { \ + if (!hat_coords_expect(__FILE__, __LINE__, hc, __VA_ARGS__)) \ + fails++; \ + } while (0) + +/* + * For four-colouring the tiling: these tables give a colouring of + * each kitemap, with colour 3 assigned to the reflected tiles in the + * middle of the H, and 0,1,2 chosen arbitrarily. + */ + +static const int fourcolours_H[] = { + /* 0 */ 0, 2, 1, 3, + /* 1 */ 1, 0, 2, 3, + /* 2 */ 0, 2, 1, 3, + /* 3 */ 1, -1, -1, -1, + /* 4 */ 1, 2, -1, -1, + /* 5 */ 1, 2, -1, -1, + /* 6 */ 2, 1, -1, -1, + /* 7 */ 0, 1, -1, -1, + /* 8 */ 2, 0, -1, -1, + /* 9 */ 2, 0, -1, -1, + /* 10 */ 0, 1, -1, -1, + /* 11 */ 0, 1, -1, -1, + /* 12 */ 2, 0, -1, -1, +}; +static const int fourcolours_T[] = { + /* 0 */ 1, 2, 0, 3, + /* 1 */ 2, 1, -1, -1, + /* 2 */ 0, 1, -1, -1, + /* 3 */ 0, 2, -1, -1, + /* 4 */ 2, 0, -1, -1, + /* 5 */ 0, 1, -1, -1, + /* 6 */ 1, 2, -1, -1, +}; +static const int fourcolours_P[] = { + /* 0 */ 2, 1, 0, 3, + /* 1 */ 1, 2, 0, 3, + /* 2 */ 2, 1, -1, -1, + /* 3 */ 0, 2, -1, -1, + /* 4 */ 0, 1, -1, -1, + /* 5 */ 1, 2, -1, -1, + /* 6 */ 2, 0, -1, -1, + /* 7 */ 0, 1, -1, -1, + /* 8 */ 1, 0, -1, -1, + /* 9 */ 2, 1, -1, -1, + /* 10 */ 0, 2, -1, -1, +}; +static const int fourcolours_F[] = { + /* 0 */ 2, 0, 1, 3, + /* 1 */ 0, 2, 1, 3, + /* 2 */ 1, 2, -1, -1, + /* 3 */ 1, 0, -1, -1, + /* 4 */ 0, 2, -1, -1, + /* 5 */ 2, 1, -1, -1, + /* 6 */ 2, 0, -1, -1, + /* 7 */ 0, 1, -1, -1, + /* 8 */ 0, 1, -1, -1, + /* 9 */ 2, 0, -1, -1, + /* 10 */ 1, 2, -1, -1, +}; +static const int *const fourcolours[] = { + fourcolours_H, fourcolours_T, fourcolours_P, fourcolours_F, +}; + +static bool unit_tests(void) +{ + int fails = 0; + HatContext ctx[1]; + HatCoords *hc_in, *hc_out; + + ctx->rs = NULL; + ctx->prototype = hat_coords_construct(TT_KITE, 0, TT_HAT, 0, TT_H, -1); + + /* Simple steps within a hat */ + + hc_in = hat_coords_construct(TT_KITE, 6, TT_HAT, 2, TT_H, 1, TT_H, -1); + hc_out = hatctx_step(ctx, hc_in, KS_LEFT); + EXPECT(hc_out, TT_KITE, 5, TT_HAT, 2, TT_H, 1, TT_H, -1); + hat_coords_free(hc_in); + hat_coords_free(hc_out); + + hc_in = hat_coords_construct(TT_KITE, 6, TT_HAT, 2, TT_H, 1, TT_H, -1); + hc_out = hatctx_step(ctx, hc_in, KS_RIGHT); + EXPECT(hc_out, TT_KITE, 7, TT_HAT, 2, TT_H, 1, TT_H, -1); + hat_coords_free(hc_in); + hat_coords_free(hc_out); + + hc_in = hat_coords_construct(TT_KITE, 5, TT_HAT, 2, TT_H, 1, TT_H, -1); + hc_out = hatctx_step(ctx, hc_in, KS_F_LEFT); + EXPECT(hc_out, TT_KITE, 2, TT_HAT, 2, TT_H, 1, TT_H, -1); + hat_coords_free(hc_in); + hat_coords_free(hc_out); + + hc_in = hat_coords_construct(TT_KITE, 5, TT_HAT, 2, TT_H, 1, TT_H, -1); + hc_out = hatctx_step(ctx, hc_in, KS_F_RIGHT); + EXPECT(hc_out, TT_KITE, 1, TT_HAT, 2, TT_H, 1, TT_H, -1); + hat_coords_free(hc_in); + hat_coords_free(hc_out); + + /* Step between hats in the same kitemap, which can change the + * metatile type at layer 2 */ + + hc_in = hat_coords_construct(TT_KITE, 6, TT_HAT, 2, TT_H, 1, TT_H, -1); + hc_out = hatctx_step(ctx, hc_in, KS_F_LEFT); + EXPECT(hc_out, TT_KITE, 3, TT_HAT, 0, TT_H, 0, TT_H, -1); + hat_coords_free(hc_in); + hat_coords_free(hc_out); + + hc_in = hat_coords_construct(TT_KITE, 7, TT_HAT, 2, TT_H, 1, TT_H, -1); + hc_out = hatctx_step(ctx, hc_in, KS_F_RIGHT); + EXPECT(hc_out, TT_KITE, 4, TT_HAT, 0, TT_T, 3, TT_H, -1); + hat_coords_free(hc_in); + hat_coords_free(hc_out); + + /* Step off the edge of one kitemap, necessitating a metamap + * rewrite of layers 2,3 to get into a different kitemap where + * that step can be made */ + + hc_in = hat_coords_construct(TT_KITE, 6, TT_HAT, 0, TT_P, 2, TT_P, 3, + TT_P, -1); + hc_out = hatctx_step(ctx, hc_in, KS_F_RIGHT); + /* Working: + * kite 6 . hat 0 . P 2 . P 3 . P ? + * -> kite 6 . hat 0 . P 6 . H 0 . P ? (P metamap says 2.3 = 6.0) + */ + EXPECT(hc_out, TT_KITE, 7, TT_HAT, 1, TT_H, 1, TT_H, 0, TT_P, -1); + hat_coords_free(hc_in); + hat_coords_free(hc_out); + + hatctx_cleanup(ctx); + return fails == 0; +} + +/* + * Structure that describes how the colours in the above maps are + * translated to output colours. This will vary with each kitemap our + * coordinates pass through, in order to maintain consistency. + */ +typedef struct FourColourMap { + unsigned char map[4]; +} FourColourMap; + +/* + * Make an initial FourColourMap by choosing the initial permutation + * of the three 'normal' hat colours randomly. + */ +static inline FourColourMap fourcolourmap_initial(random_state *rs) +{ + FourColourMap f; + unsigned i; + + /* Start with the identity mapping */ + for (i = 0; i < 4; i++) + f.map[i] = i; + + /* Randomly permute colours 0,1,2, leaving 3 as the distinguished + * colour for reflected hats */ + shuffle(f.map, 3, sizeof(f.map[0]), rs); + + return f; +} + +static inline FourColourMap fourcolourmap_update( + FourColourMap prevm, HatCoords *prevc, HatCoords *currc, KiteStep step, + HatContext *ctx) +{ + size_t i, m1, m2; + const int *f1, *f2; + unsigned sum; + int missing; + FourColourMap newm; + HatCoords *prev2c; + + /* + * If prevc and currc are in the same kitemap anyway, that's the + * easy case: the colour map for the new kitemap is the same as + * for the old one, because they're the same kitemap. + */ + hatctx_extend_coords(ctx, prevc, currc->nc); + hatctx_extend_coords(ctx, currc, prevc->nc); + for (i = 3; i < prevc->nc; i++) + if (currc->c[i].index != prevc->c[i].index) + goto mismatch; + return prevm; + mismatch: + + /* + * The hatctx_step algorithm guarantees that the _new_ coordinate + * currc is expected to be in a kitemap containing both this kite + * and the previous one (because it first transformed the previous + * coordinate until it _could_ take a step within the same + * kitemap, and then did). + * + * So if we reverse the last step we took, we should get a second + * HatCoords describing the same kite as prevc but showing its + * position in the _new_ kitemap. This lets us figure out a pair + * of corresponding metatile indices within the old and new + * kitemaps (by looking at which metatile prevc and prev2c claim + * to be in). + * + * That metatile will also always be a P or an F (because all + * metatiles overlapping the next kitemap are of those types), + * which means it will have two hats in it. And those hats will be + * adjacent, so differently coloured. Hence, we have enough + * information to decide how two of the new kitemap's three normal + * colours map to the colours we were using in the old kitemap - + * and then the third is determined by process of elimination. + */ + prev2c = hatctx_step( + ctx, currc, (step == KS_LEFT ? KS_RIGHT : + step == KS_RIGHT ? KS_LEFT : + step == KS_F_LEFT ? KS_F_RIGHT : KS_F_LEFT)); + + /* Metatile indices within the old and new kitemaps */ + m1 = prevc->c[2].index; + m2 = prev2c->c[2].index; + + /* The colourings of those metatiles' hats in our fixed fourcolours[] */ + f1 = fourcolours[prevc->c[3].type] + 4*m1; + f2 = fourcolours[prev2c->c[3].type] + 4*m2; + + /* + * Start making our new output map, filling in all three normal + * colours to 255 = "don't know yet". + */ + newm.map[3] = 3; + newm.map[0] = newm.map[1] = newm.map[2] = 255; + + /* + * Iterate over the tile colourings in fourcolours[] for these + * metatiles, matching up our mappings. + */ + for (i = 0; i < 4; i++) { + /* They should be the same metatile, so have same number of hats! */ + assert((f1[i] == -1) == (f2[i] == -1)); + + if (f1[i] != 255) + newm.map[f2[i]] = prevm.map[f1[i]]; + } + + /* + * We expect to have filled in exactly two of the three normal + * colours. Find the missing index, and fill in its colour by + * arithmetic (using the fact that the three colours add up to 3). + */ + sum = 0; + missing = -1; + for (i = 0; i < 3; i++) { + if (newm.map[i] == 255) { + assert(missing == -1); /* shouldn't have two missing colours */ + missing = i; + } else { + sum += newm.map[i]; + } + } + assert(missing != -1); + assert(0 < sum && sum <= 3); + newm.map[missing] = 3 - sum; + + return newm; +} + +typedef struct pspoint { + float x, y; +} pspoint; + +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; +} + +typedef enum OutFmt { OF_POSTSCRIPT, OF_PYTHON } OutFmt; +typedef enum ColourMode { CM_SEMANTIC, CM_FOURCOLOUR } ColourMode; + +typedef struct drawctx { + OutFmt outfmt; + ColourMode colourmode; + psbbox *bbox; + KiteEnum *kiteenum; + FourColourMap fourcolourmap[KE_NKEEP]; +} drawctx; + +static void bbox_add_hat(void *vctx, Kite kite0, HatCoords *hc, int *coords) +{ + drawctx *ctx = (drawctx *)vctx; + pspoint p; + size_t i; + + for (i = 0; i < 14; i++) { + p.x = coords[2*i] * 1.5; + p.y = coords[2*i+1] * sqrt(0.75); + psbbox_add(ctx->bbox, p); + } +} + +static void header(drawctx *ctx) +{ + switch (ctx->outfmt) { + case OF_POSTSCRIPT: { + float xext = ctx->bbox->tr.x - ctx->bbox->bl.x; + float yext = ctx->bbox->tr.y - ctx->bbox->bl.y; + float ext = (xext > yext ? xext : yext); + float scale = 500 / ext; + float ox = 287 - scale * (ctx->bbox->bl.x + ctx->bbox->tr.x) / 2; + float oy = 421 - scale * (ctx->bbox->bl.y + ctx->bbox->tr.y) / 2; + + printf("%%!PS-Adobe-2.0\n%%%%Creator: hat-test from Simon Tatham's " + "Portable Puzzle Collection\n%%%%Pages: 1\n" + "%%%%BoundingBox: %f %f %f %f\n" + "%%%%EndComments\n%%%%Page: 1 1\n", + ox + scale * ctx->bbox->bl.x - 20, + oy + scale * ctx->bbox->bl.y - 20, + ox + scale * ctx->bbox->tr.x + 20, + oy + scale * ctx->bbox->tr.y + 20); + + printf("%f %f translate %f dup scale\n", ox, oy, scale); + printf("%f setlinewidth\n", scale * 0.03); + printf("0 setgray 1 setlinejoin 1 setlinecap\n"); + break; + } + default: + break; + } +} + +static void draw_hat(void *vctx, Kite kite0, HatCoords *hc, int *coords) +{ + drawctx *ctx = (drawctx *)vctx; + pspoint p; + size_t i; + int orientation; + + /* + * Determine an index for the hat's orientation, based on the axis + * of symmetry of its kite #0. + */ + { + int dx = kite0.outer.x - kite0.centre.x; + int dy = kite0.outer.y - kite0.centre.y; + orientation = 0; + while (dx < 0 || dy < 0) { + int newdx = dx + dy; + int newdy = -dx; + dx = newdx; + dy = newdy; + orientation++; + assert(orientation < 6); + } + } + + switch (ctx->outfmt) { + case OF_POSTSCRIPT: { + const char *colour; + + printf("newpath"); + for (i = 0; i < 14; i++) { + p.x = coords[2*i] * 1.5; + p.y = coords[2*i+1] * sqrt(0.75); + printf(" %f %f %s", p.x, p.y, i ? "lineto" : "moveto"); + } + printf(" closepath gsave"); + + switch (ctx->colourmode) { + case CM_SEMANTIC: + if (hc->c[2].type == TT_H) { + colour = (hc->c[1].index == 3 ? "0 0.5 0.8 setrgbcolor" : + "0.6 0.8 1 setrgbcolor"); + } else if (hc->c[2].type == TT_F) { + colour = "0.7 setgray"; + } else { + colour = "1 setgray"; + } + break; + + default /* case CM_FOURCOLOUR */: { + /* + * Determine the colour of this tile by translating the + * fixed colour from fourcolours[] through our current + * FourColourMap. + */ + FourColourMap f = ctx->fourcolourmap[ctx->kiteenum->curr_index]; + const int *m = fourcolours[hc->c[3].type]; + static const char *const colours[] = { + "1 0.7 0.7 setrgbcolor", + "1 1 0.7 setrgbcolor", + "0.7 1 0.7 setrgbcolor", + "0.6 0.6 1 setrgbcolor", + }; + colour = colours[f.map[m[hc->c[2].index * 4 + hc->c[1].index]]]; + break; + } + } + printf(" %s fill grestore", colour); + printf(" stroke\n"); + break; + } + case OF_PYTHON: { + printf("hat('%c', %d, %d, [", "HTPF"[hc->c[2].type], hc->c[1].index, + orientation); + for (i = 0; i < 14; i++) + printf("%s(%d,%d)", i ? ", " : "", coords[2*i], coords[2*i+1]); + printf("])\n"); + break; + } + } +} + +static void trailer(drawctx *dctx) +{ + switch (dctx->outfmt) { + case OF_POSTSCRIPT: { + printf("showpage\n"); + printf("%%%%Trailer\n"); + printf("%%%%EOF\n"); + break; + } + default: + break; + } +} + +int main(int argc, char **argv) +{ + psbbox bbox[1]; + KiteEnum s[1]; + HatContext ctx[1]; + HatCoords *coords[KE_NKEEP]; + random_state *rs; + const char *random_seed = "12345"; + int w = 10, h = 10; + int argpos = 0; + size_t i; + drawctx dctx[1]; + + dctx->outfmt = OF_POSTSCRIPT; + dctx->colourmode = CM_SEMANTIC; + dctx->kiteenum = s; + + while (--argc > 0) { + const char *arg = *++argv; + if (!strcmp(arg, "--help")) { + printf(" usage: hat-test [options] [] []\n" + "options: --python write a Python function call per hat\n" + " --seed=STR vary the starting random seed\n" + " also: hat-test --test\n"); + return 0; + } else if (!strcmp(arg, "--test")) { + return unit_tests() ? 0 : 1; + } else if (!strcmp(arg, "--python")) { + dctx->outfmt = OF_PYTHON; + } else if (!strcmp(arg, "--fourcolour")) { + dctx->colourmode = CM_FOURCOLOUR; + } else if (!strncmp(arg, "--seed=", 7)) { + random_seed = arg+7; + } else if (arg[0] == '-') { + fprintf(stderr, "unrecognised option '%s'\n", arg); + return 1; + } else { + switch (argpos++) { + case 0: + w = atoi(arg); + break; + case 1: + h = atoi(arg); + break; + default: + fprintf(stderr, "unexpected extra argument '%s'\n", arg); + return 1; + } + } + } + + for (i = 0; i < lenof(coords); i++) + coords[i] = NULL; + + rs = random_new(random_seed, strlen(random_seed)); + hatctx_init_random(ctx, rs); + + bbox->started = false; + dctx->bbox = bbox; + + hat_kiteenum_first(s, w, h); + coords[s->curr_index] = hatctx_initial_coords(ctx); + maybe_report_hat(w, h, *s->curr, coords[s->curr_index], + bbox_add_hat, dctx); + while (hat_kiteenum_next(s)) { + hat_coords_free(coords[s->curr_index]); + coords[s->curr_index] = hatctx_step( + ctx, coords[s->last_index], s->last_step); + maybe_report_hat(w, h, *s->curr, coords[s->curr_index], + bbox_add_hat, dctx); + } + for (i = 0; i < lenof(coords); i++) { + hat_coords_free(coords[i]); + coords[i] = NULL; + } + + header(dctx); + + hat_kiteenum_first(s, w, h); + coords[s->curr_index] = hatctx_initial_coords(ctx); + dctx->fourcolourmap[s->curr_index] = fourcolourmap_initial(rs); + maybe_report_hat(w, h, *s->curr, coords[s->curr_index], + draw_hat, dctx); + while (hat_kiteenum_next(s)) { + hat_coords_free(coords[s->curr_index]); + coords[s->curr_index] = hatctx_step( + ctx, coords[s->last_index], s->last_step); + dctx->fourcolourmap[s->curr_index] = fourcolourmap_update( + dctx->fourcolourmap[s->last_index], coords[s->last_index], + coords[s->curr_index], s->last_step, ctx); + maybe_report_hat(w, h, *s->curr, coords[s->curr_index], + draw_hat, dctx); + } + for (i = 0; i < lenof(coords); i++) { + hat_coords_free(coords[i]); + coords[i] = NULL; + } + + trailer(dctx); + + hatctx_cleanup(ctx); + + return 0; +} diff --git a/hat-internal.h b/hat-internal.h new file mode 100644 index 0000000..2be9632 --- /dev/null +++ b/hat-internal.h @@ -0,0 +1,271 @@ +/* + * Internal definitions for the hat.c tiling generator, shared between + * hat.c itself and hat-test.c. + */ + +#include "puzzles.h" + +/* + * Coordinate system: + * + * The output of 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 pointscale(int scale, Point a) +{ + Point r = { scale * a.x, scale * a.y }; + return r; +} + +static inline Point pointadd(Point a, Point b) +{ + Point r = { a.x + b.x, a.y + b.y }; + return r; +} + +/* + * We identify a single kite by the coordinates of its four vertices. + * This allows us to construct the coordinates of an adjacent kite by + * taking affine transformations of the original kite's vertices. + * + * This is a useful way to do it because it means that if you reflect + * the kite (by swapping its left and right vertices) then these + * transformations also perform in a reflected way. This will be + * useful in the code below that outputs the coordinates of each hat, + * because this way it can work by walking around its 8 kites using a + * fixed set of steps, and if the hat is reflected, then we just + * reflect the starting kite before doing that, and everything still + * works. + */ + +typedef struct Kite { + Point centre, left, right, outer; +} Kite; + +static inline Kite kite_left(Kite k) +{ + Kite r; + r.centre = k.centre; + r.right = k.left; + r.outer = pointadd(pointscale(2, k.left), pointscale(-1, k.outer)); + r.left = pointadd(pointadd(k.centre, k.left), pointscale(-1, k.right)); + return r; +} + +static inline Kite kite_right(Kite k) +{ + Kite r; + r.centre = k.centre; + r.left = k.right; + r.outer = pointadd(pointscale(2, k.right), pointscale(-1, k.outer)); + r.right = pointadd(pointadd(k.centre, k.right), pointscale(-1, k.left)); + return r; +} + +static inline Kite kite_forward_left(Kite k) +{ + Kite r; + r.outer = k.outer; + r.right = k.left; + r.centre = pointadd(pointscale(2, k.left), pointscale(-1, k.centre)); + r.left = pointadd(pointadd(k.right, k.left), pointscale(-1, k.centre)); + return r; +} + +static inline Kite kite_forward_right(Kite k) +{ + Kite r; + r.outer = k.outer; + r.left = k.right; + r.centre = pointadd(pointscale(2, k.right), pointscale(-1, k.centre)); + r.right = pointadd(pointadd(k.left, k.right), pointscale(-1, k.centre)); + return r; +} + +typedef enum KiteStep { KS_LEFT, KS_RIGHT, KS_F_LEFT, KS_F_RIGHT } KiteStep; + +static inline Kite kite_step(Kite 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); + } +} + +/* + * Functiond to enumerate the kites in a rectangular region, in a + * serpentine-raster fashion so that every kite delivered shares an + * edge with a recent previous one. + */ +#define KE_NKEEP 3 +typedef struct KiteEnum { + /* Fields private to the enumerator */ + int state; + int x, y, w, h; + unsigned curr_index; + + /* Fields the client can legitimately read out */ + Kite *curr; + Kite recent[KE_NKEEP]; + unsigned last_index; + KiteStep last_step; /* step that got curr from recent[last_index] */ +} KiteEnum; +void hat_kiteenum_first(KiteEnum *s, int w, int h); +bool hat_kiteenum_next(KiteEnum *s); + +/* + * Assorted useful definitions. + */ +typedef enum TileType { TT_H, TT_T, TT_P, TT_F, TT_KITE, TT_HAT } TileType; +static const char tilechars[] = "HTPF"; + +#define HAT_KITES 8 /* number of kites in a hat */ +#define MT_MAXEXPAND 13 /* largest number of metatiles in any expansion */ + +/* + * Definitions for the autogenerated hat-tables.h header file that + * defines all the lookup tables. + */ +typedef struct KitemapEntry { + int kite, hat, meta; /* all -1 if impossible */ +} KitemapEntry; + +typedef struct MetamapEntry { + int meta, meta2; +} MetamapEntry; + +static inline size_t kitemap_index(KiteStep step, unsigned kite, + unsigned hat, unsigned meta) +{ + return step + 4 * (kite + 8 * (hat + 4 * meta)); +} + +static inline size_t metamap_index(unsigned meta, unsigned meta2) +{ + return meta2 * MT_MAXEXPAND + meta; +} + +/* + * Coordinate system for tracking kites within a randomly selected + * part of the recursively expanded hat tiling. + * + * HatCoords will store an array of HatCoord, in little-endian + * arrangement. So hc->c[0] will always have type TT_KITE and index a + * single kite within a hat; hc->c[1] will have type TT_HAT and index + * a hat within a first-order metatile; hc->c[2] will be the smallest + * metatile containing this hat, and hc->c[3, 4, 5, ...] will be + * higher-order metatiles as needed. + * + * The last coordinate stored, hc->c[hc->nc-1], will have a tile type + * but no index (represented by index==-1). This means "we haven't + * decided yet what this level of metatile needs to be". If we need to + * refer to this level during the hatctx_step algorithm, we make it up + * at random, based on a table of what metatiles each type can + * possibly be part of, at what index. + */ +typedef struct HatCoord { + int index; /* index within that tile, or -1 if not yet known */ + TileType type; /* type of this tile */ +} HatCoord; + +typedef struct HatCoords { + HatCoord *c; + size_t nc, csize; +} HatCoords; + +HatCoords *hat_coords_new(void); +void hat_coords_free(HatCoords *hc); +void hat_coords_make_space(HatCoords *hc, size_t size); +HatCoords *hat_coords_copy(HatCoords *hc_in); + +#ifdef HAT_COORDS_DEBUG +static inline void hat_coords_debug(const char *prefix, HatCoords *hc, + const char *suffix) +{ + const char *sep = ""; + static const char *const types[] = {"H","T","P","F","kite","hat"}; + + fputs(prefix, stderr); + for (size_t i = 0; i < hc->nc; i++) { + fprintf(stderr, "%s %s ", sep, types[hc->c[i].type]); + sep = " ."; + if (hc->c[i].index == -1) + fputs("?", stderr); + else + fprintf(stderr, "%d", hc->c[i].index); + } + fputs(suffix, stderr); +} +#else +#define hat_coords_debug(p,c,s) ((void)0) +#endif + +/* + * HatContext is the shared context of a whole run of the algorithm. + * Its 'prototype' HatCoords object represents the coordinates of the + * starting kite, and is extended as necessary; any other HatCoord + * that needs extending will copy the higher-order values from + * ctx->prototype as needed, so that once each choice has been made, + * it remains consistent. + * + * When we're inventing a random piece of tiling in the first place, + * we append to ctx->prototype by choosing a random (but legal) + * higher-level metatile for the current topmost one to turn out to be + * part of. When we're replaying a generation whose parameters are + * already stored, we don't have a random_state, and we make fixed + * decisions if not enough coordinates were provided. + * + * (Of course another approach would be to reject grid descriptions + * that didn't define enough coordinates! But that would involve a + * whole extra iteration over the whole grid region just for + * validation, and that seems like more timewasting than really + * needed. So we tolerate short descriptions, and do something + * deterministic with them.) + */ + +typedef struct HatContext { + random_state *rs; + HatCoords *prototype; +} HatContext; + +void hatctx_init_random(HatContext *ctx, random_state *rs); +void hatctx_cleanup(HatContext *ctx); +HatCoords *hatctx_initial_coords(HatContext *ctx); +void hatctx_extend_coords(HatContext *ctx, HatCoords *hc, size_t n); +HatCoords *hatctx_step(HatContext *ctx, HatCoords *hc_in, KiteStep step); + +/* + * Subroutine of hat_tiling_generate, called for each kite in the grid + * as we iterate over it, to decide whether to generate an output hat + * and pass it to the client. Exposed in this header file so that + * hat-test can reuse it. + * + * We do this by starting from kite #0 of each hat, and tracing round + * the boundary. If the whole boundary is within the caller's bounding + * region, we return it; if it goes off the edge, we don't. + * + * (Of course, every hat we _do_ want to return will have all its + * kites inside the rectangle, so its kite #0 will certainly be caught + * by this iteration.) + */ + +typedef void (*internal_hat_callback_fn)(void *ctx, Kite kite0, HatCoords *hc, + int *coords); +void maybe_report_hat(int w, int h, Kite kite, HatCoords *hc, + internal_hat_callback_fn cb, void *cbctx); diff --git a/hat.c b/hat.c index 09ab0f6..0a2dd21 100644 --- a/hat.c +++ b/hat.c @@ -20,130 +20,9 @@ #include "puzzles.h" #include "hat.h" +#include "hat-internal.h" -/* - * Coordinate system: - * - * The output of 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 pointscale(int scale, Point a) -{ - Point r = { scale * a.x, scale * a.y }; - return r; -} - -static inline Point pointadd(Point a, Point b) -{ - Point r = { a.x + b.x, a.y + b.y }; - return r; -} - -/* - * We identify a single kite by the coordinates of its four vertices. - * This allows us to construct the coordinates of an adjacent kite by - * taking affine transformations of the original kite's vertices. - * - * This is a useful way to do it because it means that if you reflect - * the kite (by swapping its left and right vertices) then these - * transformations also perform in a reflected way. This will be - * useful in the code below that outputs the coordinates of each hat, - * because this way it can work by walking around its 8 kites using a - * fixed set of steps, and if the hat is reflected, then we just - * reflect the starting kite before doing that, and everything still - * works. - */ - -typedef struct Kite { - Point centre, left, right, outer; -} Kite; - -static inline Kite kite_left(Kite k) -{ - Kite r; - r.centre = k.centre; - r.right = k.left; - r.outer = pointadd(pointscale(2, k.left), pointscale(-1, k.outer)); - r.left = pointadd(pointadd(k.centre, k.left), pointscale(-1, k.right)); - return r; -} - -static inline Kite kite_right(Kite k) -{ - Kite r; - r.centre = k.centre; - r.left = k.right; - r.outer = pointadd(pointscale(2, k.right), pointscale(-1, k.outer)); - r.right = pointadd(pointadd(k.centre, k.right), pointscale(-1, k.left)); - return r; -} - -static inline Kite kite_forward_left(Kite k) -{ - Kite r; - r.outer = k.outer; - r.right = k.left; - r.centre = pointadd(pointscale(2, k.left), pointscale(-1, k.centre)); - r.left = pointadd(pointadd(k.right, k.left), pointscale(-1, k.centre)); - return r; -} - -static inline Kite kite_forward_right(Kite k) -{ - Kite r; - r.outer = k.outer; - r.left = k.right; - r.centre = pointadd(pointscale(2, k.right), pointscale(-1, k.centre)); - r.right = pointadd(pointadd(k.left, k.right), pointscale(-1, k.centre)); - return r; -} - -typedef enum KiteStep { KS_LEFT, KS_RIGHT, KS_F_LEFT, KS_F_RIGHT } KiteStep; - -static inline Kite kite_step(Kite 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); - } -} - -/* - * Function to enumerate the kites in a rectangular region, in a - * serpentine-raster fashion so that every kite delivered shares an - * edge with a recent previous one. - */ -#define KE_NKEEP 3 -typedef struct KiteEnum { - /* Fields private to the enumerator */ - int state; - int x, y, w, h; - unsigned curr_index; - - /* Fields the client can legitimately read out */ - Kite *curr; - Kite recent[KE_NKEEP]; - unsigned last_index; - KiteStep last_step; /* step that got curr from recent[last_index] */ -} KiteEnum; - -static void first_kite(KiteEnum *s, int w, int h) +void hat_kiteenum_first(KiteEnum *s, int w, int h) { Kite start = { {0,0}, {0, 3}, {3, 0}, {2, 2} }; size_t i; @@ -158,7 +37,8 @@ static void first_kite(KiteEnum *s, int w, int h) s->x = 0; s->y = 0; } -static bool next_kite(KiteEnum *s) + +bool hat_kiteenum_next(KiteEnum *s) { unsigned lastbut1 = s->last_index; s->last_index = s->curr_index; @@ -305,38 +185,6 @@ static bool next_kite(KiteEnum *s) return true; } -/* - * Assorted useful definitions. - */ -typedef enum TileType { TT_H, TT_T, TT_P, TT_F, TT_KITE, TT_HAT } TileType; -static const char tilechars[] = "HTPF"; - -#define HAT_KITES 8 /* number of kites in a hat */ -#define MT_MAXEXPAND 13 /* largest number of metatiles in any expansion */ - -/* - * Definitions for the autogenerated hat-tables.h header file that - * defines all the lookup tables. - */ -typedef struct KitemapEntry { - int kite, hat, meta; /* all -1 if impossible */ -} KitemapEntry; - -typedef struct MetamapEntry { - int meta, meta2; -} MetamapEntry; - -static inline size_t kitemap_index(KiteStep step, unsigned kite, - unsigned hat, unsigned meta) -{ - return step + 4 * (kite + 8 * (hat + 4 * meta)); -} - -static inline size_t metamap_index(unsigned meta, unsigned meta2) -{ - return meta2 * MT_MAXEXPAND + meta; -} - /* * The actual tables. */ @@ -524,35 +372,7 @@ static const MetatilePossibleParent starting_hats[] = { #undef PROB_P #undef PROB_F -/* - * Coordinate system for tracking kites within a randomly selected - * part of the recursively expanded hat tiling. - * - * HatCoords will store an array of HatCoord, in little-endian - * arrangement. So hc->c[0] will always have type TT_KITE and index a - * single kite within a hat; hc->c[1] will have type TT_HAT and index - * a hat within a first-order metatile; hc->c[2] will be the smallest - * metatile containing this hat, and hc->c[3, 4, 5, ...] will be - * higher-order metatiles as needed. - * - * The last coordinate stored, hc->c[hc->nc-1], will have a tile type - * but no index (represented by index==-1). This means "we haven't - * decided yet what this level of metatile needs to be". If we need to - * refer to this level during the step_coords algorithm, we make it up - * at random, based on a table of what metatiles each type can - * possibly be part of, at what index. - */ -typedef struct HatCoord { - int index; /* index within that tile, or -1 if not yet known */ - TileType type; /* type of this tile */ -} HatCoord; - -typedef struct HatCoords { - HatCoord *c; - size_t nc, csize; -} HatCoords; - -static HatCoords *hc_new(void) +HatCoords *hat_coords_new(void) { HatCoords *hc = snew(HatCoords); hc->nc = hc->csize = 0; @@ -560,7 +380,7 @@ static HatCoords *hc_new(void) return hc; } -static void hc_free(HatCoords *hc) +void hat_coords_free(HatCoords *hc) { if (hc) { sfree(hc->c); @@ -568,7 +388,7 @@ static void hc_free(HatCoords *hc) } } -static void hc_make_space(HatCoords *hc, size_t size) +void hat_coords_make_space(HatCoords *hc, size_t size) { if (hc->csize < size) { hc->csize = hc->csize * 5 / 4 + 16; @@ -578,10 +398,10 @@ static void hc_make_space(HatCoords *hc, size_t size) } } -static HatCoords *hc_copy(HatCoords *hc_in) +HatCoords *hat_coords_copy(HatCoords *hc_in) { - HatCoords *hc_out = hc_new(); - hc_make_space(hc_out, hc_in->nc); + HatCoords *hc_out = hat_coords_new(); + hat_coords_make_space(hc_out, hc_in->nc); memcpy(hc_out->c, hc_in->c, hc_in->nc * sizeof(*hc_out->c)); hc_out->nc = hc_in->nc; return hc_out; @@ -615,43 +435,14 @@ static const MetatilePossibleParent *choose_mpp( assert(value < parents[i].probability); return &parents[i]; } - -/* - * HatCoordContext is the shared context of a whole run of the - * algorithm. Its 'prototype' HatCoords object represents the - * coordinates of the starting kite, and is extended as necessary; any - * other HatCoord that needs extending will copy the higher-order - * values from ctx->prototype as needed, so that once each choice has - * been made, it remains consistent. - * - * When we're inventing a random piece of tiling in the first place, - * we append to ctx->prototype by choosing a random (but legal) - * higher-level metatile for the current topmost one to turn out to be - * part of. When we're replaying a generation whose parameters are - * already stored, we don't have a random_state, and we make fixed - * decisions if not enough coordinates were provided. - * - * (Of course another approach would be to reject grid descriptions - * that didn't define enough coordinates! But that would involve a - * whole extra iteration over the whole grid region just for - * validation, and that seems like more timewasting than really - * needed. So we tolerate short descriptions, and do something - * deterministic with them.) - */ - -typedef struct HatCoordContext { - random_state *rs; - HatCoords *prototype; -} HatCoordContext; - -static void init_coords_random(HatCoordContext *ctx, random_state *rs) +void hatctx_init_random(HatContext *ctx, random_state *rs) { const MetatilePossibleParent *starting_hat = choose_mpp( rs, starting_hats, lenof(starting_hats)); ctx->rs = rs; - ctx->prototype = hc_new(); - hc_make_space(ctx->prototype, 3); + ctx->prototype = hat_coords_new(); + hat_coords_make_space(ctx->prototype, 3); ctx->prototype->c[2].type = starting_hat->type; ctx->prototype->c[2].index = -1; ctx->prototype->c[1].type = TT_HAT; @@ -669,17 +460,17 @@ static inline int metatile_char_to_enum(char metatile) metatile == 'F' ? TT_F : -1); } -static void init_coords_params(HatCoordContext *ctx, +static void init_coords_params(HatContext *ctx, const struct HatPatchParams *hp) { size_t i; ctx->rs = NULL; - ctx->prototype = hc_new(); + ctx->prototype = hat_coords_new(); assert(hp->ncoords >= 3); - hc_make_space(ctx->prototype, hp->ncoords + 1); + hat_coords_make_space(ctx->prototype, hp->ncoords + 1); ctx->prototype->nc = hp->ncoords + 1; for (i = 0; i < hp->ncoords; i++) @@ -701,9 +492,9 @@ static void init_coords_params(HatCoordContext *ctx, assert(hp->coords[0] < 8); } -static HatCoords *initial_coords(HatCoordContext *ctx) +HatCoords *hatctx_initial_coords(HatContext *ctx) { - return hc_copy(ctx->prototype); + return hat_coords_copy(ctx->prototype); } /* @@ -711,10 +502,10 @@ static HatCoords *initial_coords(HatCoordContext *ctx) * ctx->prototype if needed, and extending ctx->prototype if needed in * order to do that. */ -static void ensure_coords(HatCoordContext *ctx, HatCoords *hc, size_t n) +void hatctx_extend_coords(HatContext *ctx, HatCoords *hc, size_t n) { if (ctx->prototype->nc < n) { - hc_make_space(ctx->prototype, n); + hat_coords_make_space(ctx->prototype, n); while (ctx->prototype->nc < n) { TileType type = ctx->prototype->c[ctx->prototype->nc - 1].type; assert(ctx->prototype->c[ctx->prototype->nc - 1].index == -1); @@ -733,7 +524,7 @@ static void ensure_coords(HatCoordContext *ctx, HatCoords *hc, size_t n) } } - hc_make_space(hc, n); + hat_coords_make_space(hc, n); while (hc->nc < n) { assert(hc->c[hc->nc - 1].index == -1); assert(hc->c[hc->nc - 1].type == ctx->prototype->c[hc->nc - 1].type); @@ -744,33 +535,11 @@ static void ensure_coords(HatCoordContext *ctx, HatCoords *hc, size_t n) } } -static void cleanup_coords(HatCoordContext *ctx) +void hatctx_cleanup(HatContext *ctx) { - hc_free(ctx->prototype); + hat_coords_free(ctx->prototype); } -#ifdef DEBUG_COORDS -static inline void debug_coords(const char *prefix, HatCoords *hc, - const char *suffix) -{ - const char *sep = ""; - static const char *const types[] = {"H","T","P","F","kite","hat"}; - - fputs(prefix, stderr); - for (size_t i = 0; i < hc->nc; i++) { - fprintf(stderr, "%s %s ", sep, types[hc->c[i].type]); - sep = " ."; - if (hc->c[i].index == -1) - fputs("?", stderr); - else - fprintf(stderr, "%d", hc->c[i].index); - } - fputs(suffix, stderr); -} -#else -#define debug_coords(p,c,s) ((void)0) -#endif - /* * The actual system for finding the coordinates of an adjacent kite. */ @@ -781,10 +550,10 @@ static inline void debug_coords(const char *prefix, HatCoords *hc, * around the individual kites. If this fails, return NULL. */ static HatCoords *try_step_coords_kitemap( - HatCoordContext *ctx, HatCoords *hc_in, KiteStep step) + HatContext *ctx, HatCoords *hc_in, KiteStep step) { - ensure_coords(ctx, hc_in, 4); - debug_coords(" try kitemap ", hc_in, "\n"); + hatctx_extend_coords(ctx, hc_in, 4); + hat_coords_debug(" try kitemap ", hc_in, "\n"); unsigned kite = hc_in->c[0].index; unsigned hat = hc_in->c[1].index; unsigned meta = hc_in->c[2].index; @@ -796,7 +565,7 @@ static HatCoords *try_step_coords_kitemap( * Success! We've got coordinates for the next kite in this * direction. */ - HatCoords *hc_out = hc_copy(hc_in); + HatCoords *hc_out = hat_coords_copy(hc_in); hc_out->c[2].index = ke->meta; hc_out->c[2].type = children[meta2type][ke->meta]; @@ -805,7 +574,7 @@ static HatCoords *try_step_coords_kitemap( hc_out->c[0].index = ke->kite; hc_out->c[0].type = TT_KITE; - debug_coords(" success! ", hc_out, "\n"); + hat_coords_debug(" success! ", hc_out, "\n"); return hc_out; } @@ -821,14 +590,14 @@ static HatCoords *try_step_coords_kitemap( * metamap rewrite), return NULL. */ static HatCoords *try_step_coords_metamap( - HatCoordContext *ctx, HatCoords *hc_in, KiteStep step, size_t depth) + HatContext *ctx, HatCoords *hc_in, KiteStep step, size_t depth) { HatCoords *hc_tmp = NULL, *hc_out; - ensure_coords(ctx, hc_in, depth+3); -#ifdef DEBUG_COORDS + hatctx_extend_coords(ctx, hc_in, depth+3); +#ifdef HAT_COORDS_DEBUG fprintf(stderr, " try meta %-4d", (int)depth); - debug_coords("", hc_in, "\n"); + hat_coords_debug("", hc_in, "\n"); #endif unsigned meta_orig = hc_in->c[depth].index; unsigned meta2_orig = hc_in->c[depth+1].index; @@ -845,14 +614,14 @@ static HatCoords *try_step_coords_metamap( else hc_out = try_step_coords_kitemap(ctx, hc_curr, step); if (hc_out) { - hc_free(hc_tmp); + hat_coords_free(hc_tmp); return hc_out; } me = &metamap[meta3type][metamap_index(meta, meta2)]; assert(me->meta != -1); if (me->meta == meta_orig && me->meta2 == meta2_orig) { - hc_free(hc_tmp); + hat_coords_free(hc_tmp); return NULL; } @@ -871,30 +640,29 @@ static HatCoords *try_step_coords_metamap( * just use a separate copy. */ if (!hc_tmp) - hc_tmp = hc_copy(hc_in); + hc_tmp = hat_coords_copy(hc_in); hc_tmp->c[depth+1].index = meta2; hc_tmp->c[depth+1].type = children[meta3type][meta2]; hc_tmp->c[depth].index = meta; hc_tmp->c[depth].type = children[hc_tmp->c[depth+1].type][meta]; - debug_coords(" rewritten -> ", hc_tmp, "\n"); + hat_coords_debug(" rewritten -> ", hc_tmp, "\n"); } } /* * The top-level algorithm for finding the next tile. */ -static HatCoords *step_coords(HatCoordContext *ctx, HatCoords *hc_in, - KiteStep step) +HatCoords *hatctx_step(HatContext *ctx, HatCoords *hc_in, KiteStep step) { HatCoords *hc_out; size_t depth; -#ifdef DEBUG_COORDS +#ifdef HAT_COORDS_DEBUG static const char *const directions[] = { " left\n", " right\n", " forward left\n", " forward right\n" }; - debug_coords("step start ", hc_in, directions[step]); + hat_coords_debug("step start ", hc_in, directions[step]); #endif /* @@ -918,9 +686,10 @@ static HatCoords *step_coords(HatCoordContext *ctx, HatCoords *hc_in, /* * Generate a random set of parameters for a tiling of a given size. - * To do this, we iterate over the whole tiling via first_kite and - * next_kite, and for each kite, calculate its coordinates. But then - * we throw the coordinates away and don't do anything with them! + * To do this, we iterate over the whole tiling via hat_kiteenum_first + * and hat_kiteenum_next, and for each kite, calculate its + * coordinates. But then we throw the coordinates away and don't do + * anything with them! * * But the side effect of _calculating_ all those coordinates is that * we found out how far ctx->prototype needed to be extended, and did @@ -931,21 +700,21 @@ static HatCoords *step_coords(HatCoordContext *ctx, HatCoords *hc_in, void hat_tiling_randomise(struct HatPatchParams *hp, int w, int h, random_state *rs) { - HatCoordContext ctx[1]; + HatContext ctx[1]; HatCoords *coords[KE_NKEEP]; KiteEnum s[1]; size_t i; - init_coords_random(ctx, rs); + hatctx_init_random(ctx, rs); for (i = 0; i < lenof(coords); i++) coords[i] = NULL; - first_kite(s, w, h); - coords[s->curr_index] = initial_coords(ctx); + hat_kiteenum_first(s, w, h); + coords[s->curr_index] = hatctx_initial_coords(ctx); - while (next_kite(s)) { - hc_free(coords[s->curr_index]); - coords[s->curr_index] = step_coords( + while (hat_kiteenum_next(s)) { + hat_coords_free(coords[s->curr_index]); + coords[s->curr_index] = hatctx_step( ctx, coords[s->last_index], s->last_step); } @@ -955,9 +724,9 @@ void hat_tiling_randomise(struct HatPatchParams *hp, int w, int h, hp->coords[i] = ctx->prototype->c[i].index; hp->final_metatile = tilechars[ctx->prototype->c[hp->ncoords].type]; - cleanup_coords(ctx); + hatctx_cleanup(ctx); for (i = 0; i < lenof(coords); i++) - hc_free(coords[i]); + hat_coords_free(coords[i]); } const char *hat_tiling_params_invalid(const struct HatPatchParams *hp) @@ -985,24 +754,8 @@ const char *hat_tiling_params_invalid(const struct HatPatchParams *hp) return NULL; } -/* - * For each kite generated by hat_tiling_generate, potentially - * generate an output hat and give it to our caller. - * - * We do this by starting from kite #0 of each hat, and tracing round - * the boundary. If the whole boundary is within the caller's bounding - * region, we return it; if it goes off the edge, we don't. - * - * (Of course, every hat we _do_ want to return will have all its - * kites inside the rectangle, so its kite #0 will certainly be caught - * by this iteration.) - */ - -typedef void (*internal_hat_callback_fn)(void *ctx, Kite kite0, HatCoords *hc, - int *coords); - -static void maybe_report_hat(int w, int h, Kite kite, HatCoords *hc, - internal_hat_callback_fn cb, void *cbctx) +void maybe_report_hat(int w, int h, Kite kite, HatCoords *hc, + internal_hat_callback_fn cb, void *cbctx) { Kite kite0; Point vertices[14]; @@ -1095,7 +848,7 @@ static void report_hat(void *vctx, Kite kite0, HatCoords *hc, int *coords) void hat_tiling_generate(const struct HatPatchParams *hp, int w, int h, hat_tile_callback_fn cb, void *cbctx) { - HatCoordContext ctx[1]; + HatContext ctx[1]; HatCoords *coords[KE_NKEEP]; KiteEnum s[1]; size_t i; @@ -1108,633 +861,20 @@ void hat_tiling_generate(const struct HatPatchParams *hp, int w, int h, for (i = 0; i < lenof(coords); i++) coords[i] = NULL; - first_kite(s, w, h); - coords[s->curr_index] = initial_coords(ctx); + hat_kiteenum_first(s, w, h); + coords[s->curr_index] = hatctx_initial_coords(ctx); maybe_report_hat(w, h, *s->curr, coords[s->curr_index], report_hat, report_hat_ctx); - while (next_kite(s)) { - hc_free(coords[s->curr_index]); - coords[s->curr_index] = step_coords( + while (hat_kiteenum_next(s)) { + hat_coords_free(coords[s->curr_index]); + coords[s->curr_index] = hatctx_step( ctx, coords[s->last_index], s->last_step); maybe_report_hat(w, h, *s->curr, coords[s->curr_index], report_hat, report_hat_ctx); } - cleanup_coords(ctx); + hatctx_cleanup(ctx); for (i = 0; i < lenof(coords); i++) - hc_free(coords[i]); + hat_coords_free(coords[i]); } - -#ifdef TEST_HAT - -#include - -static HatCoords *hc_construct_v(TileType type, va_list ap) -{ - HatCoords *hc = hc_new(); - while (true) { - int index = va_arg(ap, int); - - hc_make_space(hc, hc->nc + 1); - hc->c[hc->nc].type = type; - hc->c[hc->nc].index = index; - hc->nc++; - - if (index < 0) - return hc; - - type = va_arg(ap, TileType); - } -} - -static HatCoords *hc_construct(TileType type, ...) -{ - HatCoords *hc; - va_list ap; - - va_start(ap, type); - hc = hc_construct_v(type, ap); - va_end(ap); - - return hc; -} - -static bool hc_equal(HatCoords *hc1, HatCoords *hc2) -{ - size_t i; - - if (hc1->nc != hc2->nc) - return false; - - for (i = 0; i < hc1->nc; i++) { - if (hc1->c[i].type != hc2->c[i].type || - hc1->c[i].index != hc2->c[i].index) - return false; - } - - return true; -} - -static bool hc_expect(const char *file, int line, HatCoords *hc, - TileType type, ...) -{ - bool equal; - va_list ap; - HatCoords *hce; - - va_start(ap, type); - hce = hc_construct_v(type, ap); - va_end(ap); - - equal = hc_equal(hc, hce); - - if (!equal) { - fprintf(stderr, "%s:%d: coordinate mismatch\n", file, line); - debug_coords(" expected: ", hce, "\n"); - debug_coords(" actual: ", hc, "\n"); - } - - hc_free(hce); - return equal; -} - -#define EXPECT(hc, ...) do { \ - if (!hc_expect(__FILE__, __LINE__, hc, __VA_ARGS__)) \ - fails++; \ - } while (0) - -/* - * For four-colouring the tiling: these tables give a colouring of - * each kitemap, with colour 3 assigned to the reflected tiles in the - * middle of the H, and 0,1,2 chosen arbitrarily. - */ - -static const int fourcolours_H[] = { - /* 0 */ 0, 2, 1, 3, - /* 1 */ 1, 0, 2, 3, - /* 2 */ 0, 2, 1, 3, - /* 3 */ 1, -1, -1, -1, - /* 4 */ 1, 2, -1, -1, - /* 5 */ 1, 2, -1, -1, - /* 6 */ 2, 1, -1, -1, - /* 7 */ 0, 1, -1, -1, - /* 8 */ 2, 0, -1, -1, - /* 9 */ 2, 0, -1, -1, - /* 10 */ 0, 1, -1, -1, - /* 11 */ 0, 1, -1, -1, - /* 12 */ 2, 0, -1, -1, -}; -static const int fourcolours_T[] = { - /* 0 */ 1, 2, 0, 3, - /* 1 */ 2, 1, -1, -1, - /* 2 */ 0, 1, -1, -1, - /* 3 */ 0, 2, -1, -1, - /* 4 */ 2, 0, -1, -1, - /* 5 */ 0, 1, -1, -1, - /* 6 */ 1, 2, -1, -1, -}; -static const int fourcolours_P[] = { - /* 0 */ 2, 1, 0, 3, - /* 1 */ 1, 2, 0, 3, - /* 2 */ 2, 1, -1, -1, - /* 3 */ 0, 2, -1, -1, - /* 4 */ 0, 1, -1, -1, - /* 5 */ 1, 2, -1, -1, - /* 6 */ 2, 0, -1, -1, - /* 7 */ 0, 1, -1, -1, - /* 8 */ 1, 0, -1, -1, - /* 9 */ 2, 1, -1, -1, - /* 10 */ 0, 2, -1, -1, -}; -static const int fourcolours_F[] = { - /* 0 */ 2, 0, 1, 3, - /* 1 */ 0, 2, 1, 3, - /* 2 */ 1, 2, -1, -1, - /* 3 */ 1, 0, -1, -1, - /* 4 */ 0, 2, -1, -1, - /* 5 */ 2, 1, -1, -1, - /* 6 */ 2, 0, -1, -1, - /* 7 */ 0, 1, -1, -1, - /* 8 */ 0, 1, -1, -1, - /* 9 */ 2, 0, -1, -1, - /* 10 */ 1, 2, -1, -1, -}; -static const int *const fourcolours[] = { - fourcolours_H, fourcolours_T, fourcolours_P, fourcolours_F, -}; - -/* - * Structure that describes how the colours in the above maps are - * translated to output colours. This will vary with each kitemap our - * coordinates pass through, in order to maintain consistency. - */ -typedef struct FourColourMap { - unsigned char map[4]; -} FourColourMap; - -/* - * Make an initial FourColourMap by choosing the initial permutation - * of the three 'normal' hat colours randomly. - */ -static inline FourColourMap fourcolourmap_initial(random_state *rs) -{ - FourColourMap f; - unsigned i; - - /* Start with the identity mapping */ - for (i = 0; i < 4; i++) - f.map[i] = i; - - /* Randomly permute colours 0,1,2, leaving 3 as the distinguished - * colour for reflected hats */ - shuffle(f.map, 3, sizeof(f.map[0]), rs); - - return f; -} - -static inline FourColourMap fourcolourmap_update( - FourColourMap prevm, HatCoords *prevc, HatCoords *currc, KiteStep step, - HatCoordContext *ctx) -{ - size_t i, m1, m2; - const int *f1, *f2; - unsigned sum; - int missing; - FourColourMap newm; - HatCoords *prev2c; - - /* - * If prevc and currc are in the same kitemap anyway, that's the - * easy case: the colour map for the new kitemap is the same as - * for the old one, because they're the same kitemap. - */ - ensure_coords(ctx, prevc, currc->nc); - ensure_coords(ctx, currc, prevc->nc); - for (i = 3; i < prevc->nc; i++) - if (currc->c[i].index != prevc->c[i].index) - goto mismatch; - return prevm; - mismatch: - - /* - * The step_coords algorithm guarantees that the _new_ coordinate - * currc is expected to be in a kitemap containing both this kite - * and the previous one (because it first transformed the previous - * coordinate until it _could_ take a step within the same - * kitemap, and then did). - * - * So if we reverse the last step we took, we should get a second - * HatCoords describing the same kite as prevc but showing its - * position in the _new_ kitemap. This lets us figure out a pair - * of corresponding metatile indices within the old and new - * kitemaps (by looking at which metatile prevc and prev2c claim - * to be in). - * - * That metatile will also always be a P or an F (because all - * metatiles overlapping the next kitemap are of those types), - * which means it will have two hats in it. And those hats will be - * adjacent, so differently coloured. Hence, we have enough - * information to decide how two of the new kitemap's three normal - * colours map to the colours we were using in the old kitemap - - * and then the third is determined by process of elimination. - */ - prev2c = step_coords( - ctx, currc, (step == KS_LEFT ? KS_RIGHT : - step == KS_RIGHT ? KS_LEFT : - step == KS_F_LEFT ? KS_F_RIGHT : KS_F_LEFT)); - - /* Metatile indices within the old and new kitemaps */ - m1 = prevc->c[2].index; - m2 = prev2c->c[2].index; - - /* The colourings of those metatiles' hats in our fixed fourcolours[] */ - f1 = fourcolours[prevc->c[3].type] + 4*m1; - f2 = fourcolours[prev2c->c[3].type] + 4*m2; - - /* - * Start making our new output map, filling in all three normal - * colours to 255 = "don't know yet". - */ - newm.map[3] = 3; - newm.map[0] = newm.map[1] = newm.map[2] = 255; - - /* - * Iterate over the tile colourings in fourcolours[] for these - * metatiles, matching up our mappings. - */ - for (i = 0; i < 4; i++) { - /* They should be the same metatile, so have same number of hats! */ - assert((f1[i] == -1) == (f2[i] == -1)); - - if (f1[i] != 255) - newm.map[f2[i]] = prevm.map[f1[i]]; - } - - /* - * We expect to have filled in exactly two of the three normal - * colours. Find the missing index, and fill in its colour by - * arithmetic (using the fact that the three colours add up to 3). - */ - sum = 0; - missing = -1; - for (i = 0; i < 3; i++) { - if (newm.map[i] == 255) { - assert(missing == -1); /* shouldn't have two missing colours */ - missing = i; - } else { - sum += newm.map[i]; - } - } - assert(missing != -1); - assert(0 < sum && sum <= 3); - newm.map[missing] = 3 - sum; - - return newm; -} - -static bool unit_tests(void) -{ - int fails = 0; - HatCoordContext ctx[1]; - HatCoords *hc_in, *hc_out; - - ctx->rs = NULL; - ctx->prototype = hc_construct(TT_KITE, 0, TT_HAT, 0, TT_H, -1); - - /* Simple steps within a hat */ - - hc_in = hc_construct(TT_KITE, 6, TT_HAT, 2, TT_H, 1, TT_H, -1); - hc_out = step_coords(ctx, hc_in, KS_LEFT); - EXPECT(hc_out, TT_KITE, 5, TT_HAT, 2, TT_H, 1, TT_H, -1); - hc_free(hc_in); - hc_free(hc_out); - - hc_in = hc_construct(TT_KITE, 6, TT_HAT, 2, TT_H, 1, TT_H, -1); - hc_out = step_coords(ctx, hc_in, KS_RIGHT); - EXPECT(hc_out, TT_KITE, 7, TT_HAT, 2, TT_H, 1, TT_H, -1); - hc_free(hc_in); - hc_free(hc_out); - - hc_in = hc_construct(TT_KITE, 5, TT_HAT, 2, TT_H, 1, TT_H, -1); - hc_out = step_coords(ctx, hc_in, KS_F_LEFT); - EXPECT(hc_out, TT_KITE, 2, TT_HAT, 2, TT_H, 1, TT_H, -1); - hc_free(hc_in); - hc_free(hc_out); - - hc_in = hc_construct(TT_KITE, 5, TT_HAT, 2, TT_H, 1, TT_H, -1); - hc_out = step_coords(ctx, hc_in, KS_F_RIGHT); - EXPECT(hc_out, TT_KITE, 1, TT_HAT, 2, TT_H, 1, TT_H, -1); - hc_free(hc_in); - hc_free(hc_out); - - /* Step between hats in the same kitemap, which can change the - * metatile type at layer 2 */ - - hc_in = hc_construct(TT_KITE, 6, TT_HAT, 2, TT_H, 1, TT_H, -1); - hc_out = step_coords(ctx, hc_in, KS_F_LEFT); - EXPECT(hc_out, TT_KITE, 3, TT_HAT, 0, TT_H, 0, TT_H, -1); - hc_free(hc_in); - hc_free(hc_out); - - hc_in = hc_construct(TT_KITE, 7, TT_HAT, 2, TT_H, 1, TT_H, -1); - hc_out = step_coords(ctx, hc_in, KS_F_RIGHT); - EXPECT(hc_out, TT_KITE, 4, TT_HAT, 0, TT_T, 3, TT_H, -1); - hc_free(hc_in); - hc_free(hc_out); - - /* Step off the edge of one kitemap, necessitating a metamap - * rewrite of layers 2,3 to get into a different kitemap where - * that step can be made */ - - hc_in = hc_construct(TT_KITE, 6, TT_HAT, 0, TT_P, 2, TT_P, 3, TT_P, -1); - hc_out = step_coords(ctx, hc_in, KS_F_RIGHT); - /* Working: - * kite 6 . hat 0 . P 2 . P 3 . P ? - * -> kite 6 . hat 0 . P 6 . H 0 . P ? (P metamap says 2.3 = 6.0) - */ - EXPECT(hc_out, TT_KITE, 7, TT_HAT, 1, TT_H, 1, TT_H, 0, TT_P, -1); - hc_free(hc_in); - hc_free(hc_out); - - cleanup_coords(ctx); - return fails == 0; -} - -typedef struct pspoint { - float x, y; -} pspoint; - -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; -} - -typedef enum OutFmt { OF_POSTSCRIPT, OF_PYTHON } OutFmt; -typedef enum ColourMode { CM_SEMANTIC, CM_FOURCOLOUR } ColourMode; - -typedef struct drawctx { - OutFmt outfmt; - ColourMode colourmode; - psbbox *bbox; - KiteEnum *kiteenum; - FourColourMap fourcolourmap[KE_NKEEP]; -} drawctx; - -static void bbox_add_hat(void *vctx, Kite kite0, HatCoords *hc, int *coords) -{ - drawctx *ctx = (drawctx *)vctx; - pspoint p; - size_t i; - - for (i = 0; i < 14; i++) { - p.x = coords[2*i] * 1.5; - p.y = coords[2*i+1] * sqrt(0.75); - psbbox_add(ctx->bbox, p); - } -} - -static void header(drawctx *ctx) -{ - switch (ctx->outfmt) { - case OF_POSTSCRIPT: { - float xext = ctx->bbox->tr.x - ctx->bbox->bl.x; - float yext = ctx->bbox->tr.y - ctx->bbox->bl.y; - float ext = (xext > yext ? xext : yext); - float scale = 500 / ext; - float ox = 287 - scale * (ctx->bbox->bl.x + ctx->bbox->tr.x) / 2; - float oy = 421 - scale * (ctx->bbox->bl.y + ctx->bbox->tr.y) / 2; - - printf("%%!PS-Adobe-2.0\n%%%%Creator: hat-test from Simon Tatham's " - "Portable Puzzle Collection\n%%%%Pages: 1\n" - "%%%%BoundingBox: %f %f %f %f\n" - "%%%%EndComments\n%%%%Page: 1 1\n", - ox + scale * ctx->bbox->bl.x - 20, - oy + scale * ctx->bbox->bl.y - 20, - ox + scale * ctx->bbox->tr.x + 20, - oy + scale * ctx->bbox->tr.y + 20); - - printf("%f %f translate %f dup scale\n", ox, oy, scale); - printf("%f setlinewidth\n", scale * 0.03); - printf("0 setgray 1 setlinejoin 1 setlinecap\n"); - break; - } - default: - break; - } -} - -static void draw_hat(void *vctx, Kite kite0, HatCoords *hc, int *coords) -{ - drawctx *ctx = (drawctx *)vctx; - pspoint p; - size_t i; - int orientation; - - /* - * Determine an index for the hat's orientation, based on the axis - * of symmetry of its kite #0. - */ - { - int dx = kite0.outer.x - kite0.centre.x; - int dy = kite0.outer.y - kite0.centre.y; - orientation = 0; - while (dx < 0 || dy < 0) { - int newdx = dx + dy; - int newdy = -dx; - dx = newdx; - dy = newdy; - orientation++; - assert(orientation < 6); - } - } - - switch (ctx->outfmt) { - case OF_POSTSCRIPT: { - const char *colour; - - printf("newpath"); - for (i = 0; i < 14; i++) { - p.x = coords[2*i] * 1.5; - p.y = coords[2*i+1] * sqrt(0.75); - printf(" %f %f %s", p.x, p.y, i ? "lineto" : "moveto"); - } - printf(" closepath gsave"); - - switch (ctx->colourmode) { - case CM_SEMANTIC: - if (hc->c[2].type == TT_H) { - colour = (hc->c[1].index == 3 ? "0 0.5 0.8 setrgbcolor" : - "0.6 0.8 1 setrgbcolor"); - } else if (hc->c[2].type == TT_F) { - colour = "0.7 setgray"; - } else { - colour = "1 setgray"; - } - break; - - default /* case CM_FOURCOLOUR */: { - /* - * Determine the colour of this tile by translating the - * fixed colour from fourcolours[] through our current - * FourColourMap. - */ - FourColourMap f = ctx->fourcolourmap[ctx->kiteenum->curr_index]; - const int *m = fourcolours[hc->c[3].type]; - static const char *const colours[] = { - "1 0.7 0.7 setrgbcolor", - "1 1 0.7 setrgbcolor", - "0.7 1 0.7 setrgbcolor", - "0.6 0.6 1 setrgbcolor", - }; - colour = colours[f.map[m[hc->c[2].index * 4 + hc->c[1].index]]]; - break; - } - } - printf(" %s fill grestore", colour); - printf(" stroke\n"); - break; - } - case OF_PYTHON: { - printf("hat('%c', %d, %d, [", "HTPF"[hc->c[2].type], hc->c[1].index, - orientation); - for (i = 0; i < 14; i++) - printf("%s(%d,%d)", i ? ", " : "", coords[2*i], coords[2*i+1]); - printf("])\n"); - break; - } - } -} - -static void trailer(drawctx *dctx) -{ - switch (dctx->outfmt) { - case OF_POSTSCRIPT: { - printf("showpage\n"); - printf("%%%%Trailer\n"); - printf("%%%%EOF\n"); - break; - } - default: - break; - } -} - -int main(int argc, char **argv) -{ - psbbox bbox[1]; - KiteEnum s[1]; - HatCoordContext ctx[1]; - HatCoords *coords[KE_NKEEP]; - random_state *rs; - const char *random_seed = "12345"; - int w = 10, h = 10; - int argpos = 0; - size_t i; - drawctx dctx[1]; - - dctx->outfmt = OF_POSTSCRIPT; - dctx->colourmode = CM_SEMANTIC; - dctx->kiteenum = s; - - while (--argc > 0) { - const char *arg = *++argv; - if (!strcmp(arg, "--help")) { - printf(" usage: hat-test [options] [] []\n" - "options: --python write a Python function call per hat\n" - " --seed=STR vary the starting random seed\n" - " also: hat-test --test\n"); - return 0; - } else if (!strcmp(arg, "--test")) { - return unit_tests() ? 0 : 1; - } else if (!strcmp(arg, "--python")) { - dctx->outfmt = OF_PYTHON; - } else if (!strcmp(arg, "--fourcolour")) { - dctx->colourmode = CM_FOURCOLOUR; - } else if (!strncmp(arg, "--seed=", 7)) { - random_seed = arg+7; - } else if (arg[0] == '-') { - fprintf(stderr, "unrecognised option '%s'\n", arg); - return 1; - } else { - switch (argpos++) { - case 0: - w = atoi(arg); - break; - case 1: - h = atoi(arg); - break; - default: - fprintf(stderr, "unexpected extra argument '%s'\n", arg); - return 1; - } - } - } - - for (i = 0; i < lenof(coords); i++) - coords[i] = NULL; - - rs = random_new(random_seed, strlen(random_seed)); - init_coords_random(ctx, rs); - - bbox->started = false; - dctx->bbox = bbox; - - first_kite(s, w, h); - coords[s->curr_index] = initial_coords(ctx); - maybe_report_hat(w, h, *s->curr, coords[s->curr_index], - bbox_add_hat, dctx); - while (next_kite(s)) { - hc_free(coords[s->curr_index]); - coords[s->curr_index] = step_coords( - ctx, coords[s->last_index], s->last_step); - maybe_report_hat(w, h, *s->curr, coords[s->curr_index], - bbox_add_hat, dctx); - } - for (i = 0; i < lenof(coords); i++) { - hc_free(coords[i]); - coords[i] = NULL; - } - - header(dctx); - - first_kite(s, w, h); - coords[s->curr_index] = initial_coords(ctx); - dctx->fourcolourmap[s->curr_index] = fourcolourmap_initial(rs); - maybe_report_hat(w, h, *s->curr, coords[s->curr_index], - draw_hat, dctx); - while (next_kite(s)) { - hc_free(coords[s->curr_index]); - coords[s->curr_index] = step_coords( - ctx, coords[s->last_index], s->last_step); - dctx->fourcolourmap[s->curr_index] = fourcolourmap_update( - dctx->fourcolourmap[s->last_index], coords[s->last_index], - coords[s->curr_index], s->last_step, ctx); - maybe_report_hat(w, h, *s->curr, coords[s->curr_index], - draw_hat, dctx); - } - for (i = 0; i < lenof(coords); i++) { - hc_free(coords[i]); - coords[i] = NULL; - } - - trailer(dctx); - - cleanup_coords(ctx); - - return 0; -} -#endif