diff options
| author | Simon Tatham <anakin@pobox.com> | 2023-04-02 10:20:37 +0100 |
|---|---|---|
| committer | Simon Tatham <anakin@pobox.com> | 2023-04-02 14:35:12 +0100 |
| commit | 71e1776094aa9240e9772b7fbc99dd5e2f4e1acb (patch) | |
| tree | 8d8b1772914ba2aa5d523753cf779362e7fca64e /hat.c | |
| parent | 0bd1a8057841386754f9f4a8a268616c7ce80e80 (diff) | |
| download | puzzles-71e1776094aa9240e9772b7fbc99dd5e2f4e1acb.zip puzzles-71e1776094aa9240e9772b7fbc99dd5e2f4e1acb.tar.gz puzzles-71e1776094aa9240e9772b7fbc99dd5e2f4e1acb.tar.bz2 puzzles-71e1776094aa9240e9772b7fbc99dd5e2f4e1acb.tar.xz | |
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.
Diffstat (limited to 'hat.c')
| -rw-r--r-- | hat.c | 984 |
1 files changed, 62 insertions, 922 deletions
@@ -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; @@ -306,38 +186,6 @@ static bool next_kite(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; -} - -/* * The actual tables. */ #include "hat-tables.h" @@ -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,32 +535,10 @@ static void ensure_coords(HatCoordContext *ctx, HatCoords *hc, size_t n) } } -static void cleanup_coords(HatCoordContext *ctx) -{ - hc_free(ctx->prototype); -} - -#ifdef DEBUG_COORDS -static inline void debug_coords(const char *prefix, HatCoords *hc, - const char *suffix) +void hatctx_cleanup(HatContext *ctx) { - 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); + hat_coords_free(ctx->prototype); } -#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]); -} - -#ifdef TEST_HAT - -#include <stdarg.h> - -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; + hat_coords_free(coords[i]); } - -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] [<width>] [<height>]\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 |