Files
puzzles/hat-internal.h
Simon Tatham 71e1776094 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.
2023-04-02 14:35:12 +01:00

272 lines
8.9 KiB
C

/*
* 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);