mirror of
git://git.tartarus.org/simon/puzzles.git
synced 2025-04-20 07:31:30 -07:00
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.
This commit is contained in:
@ -271,7 +271,6 @@ cliprogram(latincheck latin.c COMPILE_DEFINITIONS STANDALONE_LATIN_TEST)
|
|||||||
cliprogram(matching matching.c COMPILE_DEFINITIONS STANDALONE_MATCHING_TEST)
|
cliprogram(matching matching.c COMPILE_DEFINITIONS STANDALONE_MATCHING_TEST)
|
||||||
cliprogram(combi combi.c COMPILE_DEFINITIONS STANDALONE_COMBI_TEST)
|
cliprogram(combi combi.c COMPILE_DEFINITIONS STANDALONE_COMBI_TEST)
|
||||||
cliprogram(divvy divvy.c COMPILE_DEFINITIONS TESTMODE)
|
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-test penrose.c COMPILE_DEFINITIONS TEST_PENROSE)
|
||||||
cliprogram(penrose-vector-test penrose.c COMPILE_DEFINITIONS TEST_VECTORS)
|
cliprogram(penrose-vector-test penrose.c COMPILE_DEFINITIONS TEST_VECTORS)
|
||||||
cliprogram(sort-test sort.c COMPILE_DEFINITIONS SORT_TEST)
|
cliprogram(sort-test sort.c COMPILE_DEFINITIONS SORT_TEST)
|
||||||
|
@ -1 +1,2 @@
|
|||||||
cliprogram(hatgen hatgen.c COMPILE_DEFINITIONS TEST_HAT)
|
cliprogram(hatgen hatgen.c COMPILE_DEFINITIONS TEST_HAT)
|
||||||
|
cliprogram(hat-test hat-test.c)
|
||||||
|
622
auxiliary/hat-test.c
Normal file
622
auxiliary/hat-test.c
Normal file
@ -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 <assert.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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] [<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));
|
||||||
|
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;
|
||||||
|
}
|
271
hat-internal.h
Normal file
271
hat-internal.h
Normal file
@ -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);
|
Reference in New Issue
Block a user