mirror of
git://git.tartarus.org/simon/puzzles.git
synced 2025-04-20 23:51:29 -07:00
Loopy / grid.c: support the new Spectre monotiling.
This uses a tile shape very similar to the hat, but the tiling _structure_ is totally changed so that there aren't any reflected copies of the tile. I'm not sure how much difference this makes to gameplay: the two tilings are very similar for Loopy purposes. But the code was fun to write, and I think the Spectre shape is noticeably prettier, so I'm adding this to the collection anyway. The test programs also generate a pile of SVG images used in the companion article on my website.
This commit is contained in:
@ -7,4 +7,6 @@ cliprogram(matching matching.c)
|
||||
cliprogram(obfusc obfusc.c)
|
||||
cliprogram(penrose-test penrose-test.c)
|
||||
cliprogram(sort-test sort-test.c)
|
||||
cliprogram(spectre-gen spectre-gen.c spectre-help.c CORE_LIB)
|
||||
cliprogram(spectre-test spectre-test.c spectre-help.c)
|
||||
cliprogram(tree234-test tree234-test.c)
|
||||
|
709
auxiliary/spectre-gen.c
Normal file
709
auxiliary/spectre-gen.c
Normal file
@ -0,0 +1,709 @@
|
||||
/*
|
||||
* Generate the lookup tables used by the Spectre tiling.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "puzzles.h"
|
||||
#include "tree234.h"
|
||||
#include "spectre-internal.h"
|
||||
#include "spectre-tables-manual.h"
|
||||
#include "spectre-tables-extra.h"
|
||||
#include "spectre-help.h"
|
||||
|
||||
struct HexData {
|
||||
const Hex *subhexes;
|
||||
const unsigned *orientations;
|
||||
const int *edges;
|
||||
Point hex_outline_start, hex_outline_direction;
|
||||
unsigned spectre_outline_start_spec, spectre_outline_start_vertex;
|
||||
};
|
||||
|
||||
static const struct HexData hexdata[] = {
|
||||
#define HEXDATA_ENTRY(x) { subhexes_##x, orientations_##x, edges_##x, \
|
||||
HEX_OUTLINE_START_##x, SPEC_OUTLINE_START_##x },
|
||||
HEX_LETTERS(HEXDATA_ENTRY)
|
||||
#undef HEXDATA_ENTRY
|
||||
};
|
||||
|
||||
/*
|
||||
* Store information about an edge of the hexagonal tiling.
|
||||
*/
|
||||
typedef struct EdgeData {
|
||||
/* Edges are regarded as directed, so that we can store
|
||||
* information separately about what's on each side of one. The
|
||||
* names 'start' and 'finish' indicate a direction of travel,
|
||||
* which is taken to be anticlockwise around a hexagon, i.e. if
|
||||
* you walk from 'start' to 'finish' then the hexagon in question
|
||||
* is the one on your left. */
|
||||
Point start, finish;
|
||||
|
||||
/* Whether this edge is internal (i.e. owned by a hexagon). */
|
||||
bool internal;
|
||||
|
||||
/*
|
||||
* High- and low-order parts of the edge identity.
|
||||
*
|
||||
* If the edge is internal, then 'hi' indexes the hexagon it's an
|
||||
* edge of, and 'lo' identifies one of its edges.
|
||||
*
|
||||
* If it's external, then 'hi' is the index of the edge segment
|
||||
* corresponding to a particular edge of the superhex, and 'lo'
|
||||
* the sub-index within that segment.
|
||||
*/
|
||||
unsigned hi, lo;
|
||||
} EdgeData;
|
||||
|
||||
static int edge_cmp(void *av, void *bv)
|
||||
{
|
||||
const EdgeData *a = (const EdgeData *)av;
|
||||
const EdgeData *b = (const EdgeData *)bv;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
if (a->start.coeffs[i] < b->start.coeffs[i])
|
||||
return -1;
|
||||
if (a->start.coeffs[i] > b->start.coeffs[i])
|
||||
return +1;
|
||||
}
|
||||
for (i = 0; i < 4; i++) {
|
||||
if (a->finish.coeffs[i] < b->finish.coeffs[i])
|
||||
return -1;
|
||||
if (a->finish.coeffs[i] > b->finish.coeffs[i])
|
||||
return +1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void lay_out_hexagons(Hex h, Graphics *gr, FILE *hdr)
|
||||
{
|
||||
size_t i, j;
|
||||
tree234 *edge_map = newtree234(edge_cmp);
|
||||
EdgeData *edge;
|
||||
EdgeData *intmap[48], *extmap[22];
|
||||
unsigned edgestarts[7];
|
||||
const struct HexData *hd = h == NO_HEX ? NULL : &hexdata[h];
|
||||
|
||||
/*
|
||||
* Iterate over all hexagons and enter their edges into the edge
|
||||
* map.
|
||||
*/
|
||||
for (i = 0; i < (h == NO_HEX ? 8 : num_subhexes(h)); i++) {
|
||||
Point centre = hex_centres[i];
|
||||
Point vrel = {{ -2, 0, 4, 0 }};
|
||||
Point vertices[6];
|
||||
|
||||
if (hd)
|
||||
vrel = point_mul(vrel, point_rot(2*hd->orientations[i]));
|
||||
for (j = 0; j < 6; j++) {
|
||||
Point vrelnext = point_mul(vrel, point_rot(2));
|
||||
|
||||
edge = snew(EdgeData);
|
||||
edge->start = point_add(centre, vrel);
|
||||
edge->finish = point_add(centre, vrelnext);
|
||||
edge->internal = true;
|
||||
edge->hi = i;
|
||||
edge->lo = j;
|
||||
add234(edge_map, edge);
|
||||
intmap[6*i + j] = edge;
|
||||
|
||||
vertices[j] = edge->start;
|
||||
|
||||
vrel = vrelnext;
|
||||
}
|
||||
|
||||
gr_draw_hex(gr, gr->jigsaw_mode ? -1 : i,
|
||||
hd ? hd->subhexes[i] : NO_HEX, vertices);
|
||||
}
|
||||
|
||||
/*
|
||||
* Trace round the exterior outline of the hex expansion,
|
||||
* following the list of edge types.
|
||||
*/
|
||||
if (hd) {
|
||||
Point pos, dir;
|
||||
size_t mappos = 0;
|
||||
|
||||
pos = hd->hex_outline_start;
|
||||
dir = hd->hex_outline_direction;
|
||||
|
||||
for (i = 0; i < 6; i++) {
|
||||
int edge_type = hd->edges[i];
|
||||
int sign = edge_type < 0 ? -1 : +1;
|
||||
const int *edge_shape = hex_edge_shapes[abs(edge_type)];
|
||||
size_t len = hex_edge_lengths[abs(edge_type)];
|
||||
size_t index = sign < 0 ? len-2 : 0;
|
||||
|
||||
if (gr->vertex_blobs)
|
||||
gr_draw_blob(gr, (i == 0 ? "startpoint" : "edgesep"),
|
||||
gr_logcoords(pos), (i == 0 ? 0.6 : 0.3));
|
||||
|
||||
edgestarts[i] = mappos;
|
||||
|
||||
for (j = 0; j < len; j++) {
|
||||
Point posnext = point_add(pos, dir);
|
||||
if (j < len-1) {
|
||||
dir = point_mul(dir, point_rot(sign * edge_shape[index]));
|
||||
index += sign;
|
||||
}
|
||||
|
||||
edge = snew(EdgeData);
|
||||
edge->start = pos;
|
||||
edge->finish = posnext;
|
||||
edge->internal = false;
|
||||
edge->hi = i;
|
||||
edge->lo = j;
|
||||
add234(edge_map, edge);
|
||||
|
||||
assert(mappos < lenof(extmap));
|
||||
extmap[mappos++] = edge;
|
||||
|
||||
pos = posnext;
|
||||
}
|
||||
|
||||
/*
|
||||
* In the hex expansion, every pair of edges meet at a
|
||||
* 60-degree left turn.
|
||||
*/
|
||||
dir = point_mul(dir, point_rot(-2));
|
||||
}
|
||||
|
||||
edgestarts[i] = mappos; /* record end position */
|
||||
|
||||
for (i = 0; i < 4; i++)
|
||||
assert(pos.coeffs[i] == hd->hex_outline_start.coeffs[i]);
|
||||
}
|
||||
|
||||
/*
|
||||
* Draw the labels on the edges.
|
||||
*/
|
||||
if (gr->number_edges) {
|
||||
for (i = 0; (edge = index234(edge_map, i)) != NULL; i++) {
|
||||
char buf[64];
|
||||
double textheight = 0.8, offset = textheight * 0.2;
|
||||
GrCoords start = gr_logcoords(edge->start);
|
||||
GrCoords finish = gr_logcoords(edge->finish);
|
||||
GrCoords len = { finish.x - start.x, finish.y - start.y };
|
||||
GrCoords perp = { -len.y, +len.x };
|
||||
GrCoords mid = { (start.x+finish.x)/2, (start.y+finish.y)/2 };
|
||||
|
||||
if (edge->internal) {
|
||||
sprintf(buf, "%u", edge->lo);
|
||||
} else {
|
||||
sprintf(buf, "%u.%u", edge->lo, edge->hi);
|
||||
offset = textheight * 0.3;
|
||||
}
|
||||
|
||||
{
|
||||
GrCoords pos = {
|
||||
mid.x + offset * perp.x,
|
||||
mid.y + offset * perp.y,
|
||||
};
|
||||
gr_draw_text(gr, pos, textheight, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Write out C array declarations for the machine-readable version
|
||||
* of the maps we just generated.
|
||||
*/
|
||||
if (hdr) {
|
||||
fprintf(hdr, "static const struct MapEntry hexmap_%s[] = {\n",
|
||||
hex_names[h]);
|
||||
for (i = 0; i < 6 * num_subhexes(h); i++) {
|
||||
EdgeData *our_edge = intmap[i];
|
||||
EdgeData key, *rev_edge;
|
||||
key.finish = our_edge->start;
|
||||
key.start = our_edge->finish;
|
||||
rev_edge = find234(edge_map, &key, NULL);
|
||||
assert(rev_edge);
|
||||
fprintf(hdr, " { %-6s %u, %u }, /* edge %u of hex %u (%s) */\n",
|
||||
rev_edge->internal ? "true," : "false,",
|
||||
rev_edge->hi, rev_edge->lo,
|
||||
our_edge->lo, our_edge->hi,
|
||||
hex_names[hd->subhexes[our_edge->hi]]);
|
||||
}
|
||||
fprintf(hdr, "};\n");
|
||||
|
||||
fprintf(hdr, "static const struct MapEdge hexedges_%s[] = {\n",
|
||||
hex_names[h]);
|
||||
for (i = 0; i < 6; i++)
|
||||
fprintf(hdr, " { %2u, %u },\n", edgestarts[i],
|
||||
edgestarts[i+1] - edgestarts[i]);
|
||||
fprintf(hdr, "};\n");
|
||||
|
||||
fprintf(hdr, "static const struct MapEntry hexin_%s[] = {\n",
|
||||
hex_names[h]);
|
||||
for (i = 0; i < edgestarts[6]; i++) {
|
||||
EdgeData *our_edge = extmap[i];
|
||||
EdgeData key, *rev_edge;
|
||||
key.finish = our_edge->start;
|
||||
key.start = our_edge->finish;
|
||||
rev_edge = find234(edge_map, &key, NULL);
|
||||
assert(rev_edge);
|
||||
fprintf(hdr, " { %-6s %u, %u }, /* subedge %u of edge %u */\n",
|
||||
rev_edge->internal ? "true," : "false,",
|
||||
rev_edge->hi, rev_edge->lo,
|
||||
our_edge->lo, our_edge->hi);
|
||||
}
|
||||
fprintf(hdr, "};\n");
|
||||
}
|
||||
|
||||
while ((edge = delpos234(edge_map, 0)) != NULL)
|
||||
sfree(edge);
|
||||
freetree234(edge_map);
|
||||
}
|
||||
|
||||
static void lay_out_spectres(Hex h, Graphics *gr, FILE *hdr)
|
||||
{
|
||||
size_t i, j;
|
||||
tree234 *edge_map = newtree234(edge_cmp);
|
||||
EdgeData *edge;
|
||||
EdgeData *intmap[28], *extmap[24];
|
||||
Point vertices[28];
|
||||
unsigned edgestarts[7];
|
||||
const struct HexData *hd = (h == NO_HEX ? NULL : &hexdata[h]);
|
||||
|
||||
/*
|
||||
* Iterate over the Spectres in a hex (usually only one), and enter
|
||||
* their edges into the edge map.
|
||||
*/
|
||||
for (i = 0; i < (h == NO_HEX ? 2 : num_spectres(h)); i++) {
|
||||
Point start = {{ 0, 0, 0, 0 }};
|
||||
Point pos = start;
|
||||
Point diag = {{ 2, 0, 0, 2 }};
|
||||
Point dir = point_mul(diag, point_rot(5));
|
||||
|
||||
/*
|
||||
* Usually the single Spectre in each map is oriented in the
|
||||
* same place. For spectre #1 in the G map, however, we orient
|
||||
* it manually in a different location. (There's no point
|
||||
* making an organised lookup table for just this one
|
||||
* exceptional case.)
|
||||
*/
|
||||
if (i == 1) {
|
||||
Point unusual_start = {{ 2, 6, 2, 0 }};
|
||||
pos = unusual_start;
|
||||
dir = point_mul(dir, point_rot(+1));
|
||||
}
|
||||
|
||||
for (j = 0; j < 14; j++) {
|
||||
edge = snew(EdgeData);
|
||||
edge->start = pos;
|
||||
edge->finish = point_add(pos, dir);
|
||||
edge->internal = true;
|
||||
edge->hi = i;
|
||||
edge->lo = j;
|
||||
add234(edge_map, edge);
|
||||
intmap[14*i + j] = edge;
|
||||
|
||||
vertices[14*i + j] = edge->start;
|
||||
|
||||
pos = edge->finish;
|
||||
dir = point_mul(dir, point_rot(spectre_angles[(j+1) % 14]));
|
||||
}
|
||||
|
||||
gr_draw_spectre(gr, h, i, vertices + 14*i);
|
||||
}
|
||||
|
||||
/*
|
||||
* Trace round the exterior outline of the hex expansion,
|
||||
* following the list of edge types. Due to the confusing
|
||||
* reflection of all the expansions, we end up doing this in the
|
||||
* reverse order to the hexes code above.
|
||||
*/
|
||||
if (hd) {
|
||||
Point start, pos, dir;
|
||||
size_t mappos = lenof(extmap);
|
||||
|
||||
start = pos = vertices[14 * hd->spectre_outline_start_spec +
|
||||
hd->spectre_outline_start_vertex];
|
||||
|
||||
edgestarts[6] = mappos;
|
||||
|
||||
for (i = 0; i < 6; i++) {
|
||||
int edge_type = hd->edges[5-i];
|
||||
int sign = edge_type < 0 ? -1 : +1;
|
||||
const int *edge_shape = spec_edge_shapes[abs(edge_type)];
|
||||
size_t len = spec_edge_lengths[abs(edge_type)];
|
||||
size_t index = sign < 0 ? len-2 : 0;
|
||||
|
||||
if (gr->vertex_blobs)
|
||||
gr_draw_blob(gr, (i == 0 ? "startpoint" : "edgesep"),
|
||||
gr_logcoords(pos), (i == 0 ? 0.6 : 0.3));
|
||||
|
||||
if (h == HEX_S && i >= 4) {
|
||||
/*
|
||||
* Two special cases
|
||||
*/
|
||||
if (i == 4)
|
||||
/* leave dir from last time */;
|
||||
else
|
||||
dir = point_mul(dir, point_rot(6)); /* reverse */
|
||||
} else {
|
||||
/*
|
||||
* Determine the direction of the first sub-edge of
|
||||
* this edge expansion, by iterating over all the
|
||||
* edges in edge_map starting at this point and
|
||||
* finding one whose reverse isn't in the map (hence,
|
||||
* it's an exterior edge).
|
||||
*/
|
||||
EdgeData dummy, *iter, *found = NULL;
|
||||
dummy.start = pos;
|
||||
for (j = 0; j < 4; j++)
|
||||
dummy.finish.coeffs[j] = INT_MIN;
|
||||
for (iter = findrel234(edge_map, &dummy, NULL, REL234_GE);
|
||||
iter != NULL && point_equal(iter->start, pos);
|
||||
iter = findrel234(edge_map, iter, NULL, REL234_GT)) {
|
||||
EdgeData *rev;
|
||||
|
||||
dummy.finish = iter->start;
|
||||
dummy.start = iter->finish;
|
||||
rev = find234(edge_map, &dummy, NULL);
|
||||
if (!rev) {
|
||||
found = iter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert(found);
|
||||
dir = point_sub(found->finish, found->start);
|
||||
}
|
||||
|
||||
for (j = 0; j < len; j++) {
|
||||
Point posnext = point_add(pos, dir);
|
||||
if (j < len-1) {
|
||||
dir = point_mul(dir, point_rot(sign * edge_shape[index]));
|
||||
index += sign;
|
||||
}
|
||||
|
||||
edge = snew(EdgeData);
|
||||
edge->start = posnext;
|
||||
edge->finish = pos;
|
||||
edge->internal = false;
|
||||
edge->hi = 5-i;
|
||||
edge->lo = len-1-j;
|
||||
add234(edge_map, edge);
|
||||
|
||||
assert(mappos > 0);
|
||||
extmap[--mappos] = edge;
|
||||
|
||||
pos = posnext;
|
||||
}
|
||||
|
||||
edgestarts[5-i] = mappos;
|
||||
}
|
||||
|
||||
assert(point_equal(pos, start));
|
||||
}
|
||||
|
||||
/*
|
||||
* Draw the labels on the edges.
|
||||
*/
|
||||
if (gr->number_edges) {
|
||||
for (i = 0; (edge = index234(edge_map, i)) != NULL; i++) {
|
||||
char buf[64];
|
||||
double textheight = 0.8, offset = textheight * 0.2;
|
||||
GrCoords start = gr_logcoords(edge->start);
|
||||
GrCoords finish = gr_logcoords(edge->finish);
|
||||
GrCoords len = { finish.x - start.x, finish.y - start.y };
|
||||
GrCoords perp = { +len.y, -len.x };
|
||||
GrCoords mid = { (start.x+finish.x)/2, (start.y+finish.y)/2 };
|
||||
|
||||
if (edge->internal) {
|
||||
sprintf(buf, "%u", edge->lo);
|
||||
} else {
|
||||
sprintf(buf, "%u.%u", edge->lo, edge->hi);
|
||||
textheight = 0.6;
|
||||
}
|
||||
if (strlen(buf) > 1)
|
||||
offset = textheight * 0.35;
|
||||
|
||||
{
|
||||
GrCoords pos = {
|
||||
mid.x + offset * perp.x,
|
||||
mid.y + offset * perp.y,
|
||||
};
|
||||
gr_draw_text(gr, pos, textheight, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Write out C array declarations for the machine-readable version
|
||||
* of the maps we just generated.
|
||||
*
|
||||
* Also, because it's easier than having a whole extra iteration,
|
||||
* draw lines for the extraordinary edges outside the S diagram.
|
||||
*/
|
||||
if (hdr) {
|
||||
fprintf(hdr, "static const struct MapEntry specmap_%s[] = {\n",
|
||||
hex_names[h]);
|
||||
for (i = 0; i < 14 * num_spectres(h); i++) {
|
||||
EdgeData *our_edge = intmap[i];
|
||||
EdgeData key, *rev_edge;
|
||||
key.finish = our_edge->start;
|
||||
key.start = our_edge->finish;
|
||||
rev_edge = find234(edge_map, &key, NULL);
|
||||
assert(rev_edge);
|
||||
fprintf(hdr, " { %-6s %u, %2u }, /* edge %2u of Spectre %u */\n",
|
||||
rev_edge->internal ? "true," : "false,",
|
||||
rev_edge->hi, rev_edge->lo,
|
||||
our_edge->lo, our_edge->hi);
|
||||
}
|
||||
fprintf(hdr, "};\n");
|
||||
|
||||
fprintf(hdr, "static const struct MapEdge specedges_%s[] = {\n",
|
||||
hex_names[h]);
|
||||
for (i = 0; i < 6; i++)
|
||||
fprintf(hdr, " { %2u, %u },\n", edgestarts[i] - edgestarts[0],
|
||||
edgestarts[i+1] - edgestarts[i]);
|
||||
fprintf(hdr, "};\n");
|
||||
|
||||
fprintf(hdr, "static const struct MapEntry specin_%s[] = {\n",
|
||||
hex_names[h]);
|
||||
for (i = edgestarts[0]; i < edgestarts[6]; i++) {
|
||||
EdgeData *our_edge = extmap[i];
|
||||
EdgeData key, *rev_edge;
|
||||
key.finish = our_edge->start;
|
||||
key.start = our_edge->finish;
|
||||
rev_edge = find234(edge_map, &key, NULL);
|
||||
assert(rev_edge);
|
||||
fprintf(hdr, " { %-6s %u, %2u }, /* subedge %u of edge %u */\n",
|
||||
rev_edge->internal ? "true," : "false,",
|
||||
rev_edge->hi, rev_edge->lo,
|
||||
our_edge->lo, our_edge->hi);
|
||||
|
||||
if (!our_edge->internal && !rev_edge->internal)
|
||||
gr_draw_extra_edge(gr, key.start, key.finish);
|
||||
}
|
||||
fprintf(hdr, "};\n");
|
||||
}
|
||||
|
||||
while ((edge = delpos234(edge_map, 0)) != NULL)
|
||||
sfree(edge);
|
||||
freetree234(edge_map);
|
||||
}
|
||||
|
||||
static void draw_base_hex(Hex h, Graphics *gr)
|
||||
{
|
||||
size_t i;
|
||||
Point vertices[6];
|
||||
|
||||
/*
|
||||
* Plot the points of the hex.
|
||||
*/
|
||||
for (i = 0; i < 6; i++) {
|
||||
Point startvertex = {{ -2, 0, 4, 0 }};
|
||||
vertices[i] = point_mul(startvertex, point_rot(2*i));
|
||||
}
|
||||
|
||||
/*
|
||||
* Draw the hex itself.
|
||||
*/
|
||||
gr_draw_hex(gr, -1, h, vertices);
|
||||
|
||||
if (gr->vertex_blobs) {
|
||||
/*
|
||||
* Draw edge-division blobs on all vertices, to match the ones on
|
||||
* the expansion diagrams.
|
||||
*/
|
||||
for (i = 0; i < 6; i++) {
|
||||
gr_draw_blob(gr, (i == 0 ? "startpoint" : "edgesep"),
|
||||
gr_logcoords(vertices[i]), (i == 0 ? 0.6 : 0.3));
|
||||
}
|
||||
}
|
||||
|
||||
if (gr->number_edges) {
|
||||
/*
|
||||
* Draw the labels on its edges.
|
||||
*/
|
||||
for (i = 0; i < 6; i++) {
|
||||
char buf[64];
|
||||
double textheight = 0.8, offset = textheight * 0.2;
|
||||
GrCoords start = gr_logcoords(vertices[i]);
|
||||
GrCoords finish = gr_logcoords(vertices[(i+1) % 6]);
|
||||
GrCoords len = { finish.x - start.x, finish.y - start.y };
|
||||
GrCoords perp = { -len.y, +len.x };
|
||||
GrCoords mid = { (start.x+finish.x)/2, (start.y+finish.y)/2 };
|
||||
|
||||
sprintf(buf, "%zu", i);
|
||||
|
||||
{
|
||||
GrCoords pos = {
|
||||
mid.x + offset * perp.x,
|
||||
mid.y + offset * perp.y,
|
||||
};
|
||||
gr_draw_text(gr, pos, textheight, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_one_spectre(Graphics *gr)
|
||||
{
|
||||
size_t i, j;
|
||||
Point vertices[14];
|
||||
|
||||
{
|
||||
Point start = {{ 0, 0, 0, 0 }};
|
||||
Point pos = start;
|
||||
Point diag = {{ 2, 0, 0, 2 }};
|
||||
Point dir = point_mul(diag, point_rot(9));
|
||||
|
||||
for (j = 0; j < 14; j++) {
|
||||
vertices[j] = pos;
|
||||
pos = point_add(pos, dir);
|
||||
dir = point_mul(dir, point_rot(spectre_angles[(j+1) % 14]));
|
||||
}
|
||||
|
||||
gr_draw_spectre(gr, NO_HEX, -1, vertices);
|
||||
}
|
||||
|
||||
/*
|
||||
* Draw the labels on the edges.
|
||||
*/
|
||||
if (gr->number_edges) {
|
||||
for (i = 0; i < 14; i++) {
|
||||
char buf[64];
|
||||
double textheight = 0.8, offset = textheight * 0.2;
|
||||
GrCoords start = gr_logcoords(vertices[i]);
|
||||
GrCoords finish = gr_logcoords(vertices[(i+1) % 14]);
|
||||
GrCoords len = { finish.x - start.x, finish.y - start.y };
|
||||
GrCoords perp = { +len.y, -len.x };
|
||||
GrCoords mid = { (start.x+finish.x)/2, (start.y+finish.y)/2 };
|
||||
|
||||
sprintf(buf, "%zu", i);
|
||||
if (strlen(buf) > 1)
|
||||
offset = textheight * 0.35;
|
||||
|
||||
{
|
||||
GrCoords pos = {
|
||||
mid.x + offset * perp.x,
|
||||
mid.y + offset * perp.y,
|
||||
};
|
||||
gr_draw_text(gr, pos, textheight, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void make_parent_tables(FILE *fp)
|
||||
{
|
||||
size_t i, j, k;
|
||||
|
||||
for (i = 0; i < 9; i++) {
|
||||
fprintf(fp, "static const struct Possibility poss_%s[] = {\n",
|
||||
hex_names[i]);
|
||||
for (j = 0; j < 9; j++) {
|
||||
for (k = 0; k < num_subhexes(j); k++) {
|
||||
if (hexdata[j].subhexes[k] == i) {
|
||||
fprintf(fp, " { HEX_%s, %zu, PROB_%s },\n",
|
||||
hex_names[j], k, hex_names[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
fprintf(fp, "};\n");
|
||||
}
|
||||
|
||||
fprintf(fp, "static const struct Possibility poss_spectre[] = {\n");
|
||||
for (j = 0; j < 9; j++) {
|
||||
for (k = 0; k < num_spectres(j); k++) {
|
||||
fprintf(fp, " { HEX_%s, %zu, PROB_%s },\n",
|
||||
hex_names[j], k, hex_names[j]);
|
||||
}
|
||||
}
|
||||
fprintf(fp, "};\n");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
size_t i;
|
||||
FILE *fp = fopen("spectre-tables-auto.h", "w");
|
||||
fprintf(fp,
|
||||
"/*\n"
|
||||
" * Autogenerated transition tables for the Spectre tiling.\n"
|
||||
" * Generated by auxiliary/spectre-gen.c.\n"
|
||||
" */\n\n");
|
||||
|
||||
for (i = 0; i < 9; i++) {
|
||||
char buf[64];
|
||||
sprintf(buf, "hexmap_%s.svg", hex_names[i]);
|
||||
Graphics *gr = gr_new(buf, -11, +11, -20, +4.5, 13);
|
||||
lay_out_hexagons(i, gr, fp);
|
||||
gr_free(gr);
|
||||
}
|
||||
for (i = 0; i < 9; i++) {
|
||||
char buf[64];
|
||||
sprintf(buf, "specmap_%s.svg", hex_names[i]);
|
||||
Graphics *gr = gr_new(buf, (i == HEX_S ? -14 : -11.5),
|
||||
(i == HEX_G ? +10 : 0.5),
|
||||
-2, +12, 15);
|
||||
lay_out_spectres(i, gr, fp);
|
||||
gr_free(gr);
|
||||
}
|
||||
for (i = 0; i < 9; i++) {
|
||||
char buf[64];
|
||||
sprintf(buf, "basehex_%s.svg", hex_names[i]);
|
||||
Graphics *gr = gr_new(buf, -4, +4, -4.2, +4.5, 15);
|
||||
draw_base_hex(i, gr);
|
||||
gr_free(gr);
|
||||
}
|
||||
for (i = 0; i < 9; i++) {
|
||||
char buf[64];
|
||||
sprintf(buf, "jigsawhex_%s.svg", hex_names[i]);
|
||||
Graphics *gr = gr_new(buf, -4, +4, -4.2, +4.5, 20);
|
||||
gr->jigsaw_mode = true;
|
||||
gr->vertex_blobs = false;
|
||||
gr->number_edges = false;
|
||||
draw_base_hex(i, gr);
|
||||
gr_free(gr);
|
||||
}
|
||||
{
|
||||
Graphics *gr = gr_new("basehex_null.svg", -4, +4, -4.2, +4.5, 20);
|
||||
gr->vertex_blobs = false;
|
||||
draw_base_hex(NO_HEX, gr);
|
||||
gr_free(gr);
|
||||
}
|
||||
{
|
||||
Graphics *gr = gr_new("basespec_null.svg", -7, +6, -14, +1, 15);
|
||||
gr->vertex_blobs = false;
|
||||
draw_one_spectre(gr);
|
||||
gr_free(gr);
|
||||
}
|
||||
{
|
||||
Graphics *gr = gr_new("hexmap_null.svg", -11, +11, -20, +4.5, 10);
|
||||
gr->vertex_blobs = false;
|
||||
gr->number_edges = false;
|
||||
gr->hex_arrows = false;
|
||||
lay_out_hexagons(NO_HEX, gr, NULL);
|
||||
gr_free(gr);
|
||||
}
|
||||
{
|
||||
Graphics *gr = gr_new("specmap_null.svg", -11.5, +10, -2, +12, 15);
|
||||
gr->vertex_blobs = false;
|
||||
gr->number_edges = false;
|
||||
gr->hex_arrows = false;
|
||||
lay_out_spectres(NO_HEX, gr, NULL);
|
||||
gr_free(gr);
|
||||
}
|
||||
for (i = 0; i < 2; i++) {
|
||||
char buf[64];
|
||||
sprintf(buf, "jigsawexpand_%s.svg", hex_names[i]);
|
||||
Graphics *gr = gr_new(buf, -11, +11, -20, +4.5, 10);
|
||||
gr->jigsaw_mode = true;
|
||||
gr->vertex_blobs = false;
|
||||
gr->number_edges = false;
|
||||
lay_out_hexagons(i, gr, fp);
|
||||
gr_free(gr);
|
||||
}
|
||||
make_parent_tables(fp);
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
417
auxiliary/spectre-help.c
Normal file
417
auxiliary/spectre-help.c
Normal file
@ -0,0 +1,417 @@
|
||||
/*
|
||||
* Common code between spectre-test and spectre-gen, since both of
|
||||
* them want to output SVG graphics.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "puzzles.h"
|
||||
#include "tree234.h"
|
||||
#include "spectre-internal.h"
|
||||
#include "spectre-tables-extra.h"
|
||||
#include "spectre-help.h"
|
||||
|
||||
struct HexData {
|
||||
const int *edges;
|
||||
};
|
||||
|
||||
static const struct HexData hexdata[] = {
|
||||
#define HEXDATA_ENTRY(x) { edges_##x },
|
||||
HEX_LETTERS(HEXDATA_ENTRY)
|
||||
#undef HEXDATA_ENTRY
|
||||
};
|
||||
|
||||
const char *hex_names[10] = {
|
||||
"G", "D", "J", "L", "X", "P", "S", "F", "Y",
|
||||
"" /* NO_HEX */
|
||||
};
|
||||
|
||||
Graphics *gr_new(const char *filename, double xmin, double xmax,
|
||||
double ymin, double ymax, double scale)
|
||||
{
|
||||
Graphics *gr = snew(Graphics);
|
||||
if (!strcmp(filename, "-")) {
|
||||
gr->fp = stdout;
|
||||
gr->close_file = false;
|
||||
} else {
|
||||
gr->fp = fopen(filename, "w");
|
||||
if (!gr->fp) {
|
||||
fprintf(stderr, "%s: open: %s\n", filename, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
gr->close_file = true;
|
||||
}
|
||||
|
||||
fprintf(gr->fp, "<?xml version=\"1.0\" encoding=\"UTF-8\" "
|
||||
"standalone=\"no\"?>\n");
|
||||
fprintf(gr->fp, "<svg xmlns=\"http://www.w3.org/2000/svg\" "
|
||||
"version=\"1.1\" width=\"%f\" height=\"%f\">\n",
|
||||
(xmax - xmin) * scale, (ymax - ymin) * scale);
|
||||
|
||||
gr->absscale = fabs(scale);
|
||||
gr->xoff = -xmin * scale;
|
||||
gr->xscale = scale;
|
||||
/* invert y axis for SVG top-down coordinate system */
|
||||
gr->yoff = ymax * scale;
|
||||
gr->yscale = -scale;
|
||||
|
||||
/* Defaults, which can be overridden by the caller immediately
|
||||
* after this constructor returns */
|
||||
gr->jigsaw_mode = false;
|
||||
gr->vertex_blobs = true;
|
||||
gr->number_cells = true;
|
||||
gr->four_colour = false;
|
||||
gr->arcs = false;
|
||||
gr->linewidth = 1.5;
|
||||
|
||||
gr->started = false;
|
||||
|
||||
return gr;
|
||||
}
|
||||
|
||||
void gr_free(Graphics *gr)
|
||||
{
|
||||
if (!gr)
|
||||
return;
|
||||
fprintf(gr->fp, "</svg>\n");
|
||||
if (gr->close_file)
|
||||
fclose(gr->fp);
|
||||
sfree(gr);
|
||||
}
|
||||
|
||||
static void gr_ensure_started(Graphics *gr)
|
||||
{
|
||||
if (gr->started)
|
||||
return;
|
||||
|
||||
fprintf(gr->fp, "<style type=\"text/css\">\n");
|
||||
fprintf(gr->fp, "path { fill: none; stroke: black; stroke-width: %f; "
|
||||
"stroke-linejoin: round; stroke-linecap: round; }\n",
|
||||
gr->linewidth);
|
||||
fprintf(gr->fp, "text { fill: black; font-family: Sans; "
|
||||
"text-anchor: middle; text-align: center; }\n");
|
||||
if (gr->four_colour) {
|
||||
fprintf(gr->fp, ".c0 { fill: rgb(255, 178, 178); }\n");
|
||||
fprintf(gr->fp, ".c1 { fill: rgb(255, 255, 178); }\n");
|
||||
fprintf(gr->fp, ".c2 { fill: rgb(178, 255, 178); }\n");
|
||||
fprintf(gr->fp, ".c3 { fill: rgb(153, 153, 255); }\n");
|
||||
} else {
|
||||
fprintf(gr->fp, ".G { fill: rgb(255, 128, 128); }\n");
|
||||
fprintf(gr->fp, ".G1 { fill: rgb(255, 64, 64); }\n");
|
||||
fprintf(gr->fp, ".F { fill: rgb(255, 192, 128); }\n");
|
||||
fprintf(gr->fp, ".Y { fill: rgb(255, 255, 128); }\n");
|
||||
fprintf(gr->fp, ".S { fill: rgb(128, 255, 128); }\n");
|
||||
fprintf(gr->fp, ".D { fill: rgb(128, 255, 255); }\n");
|
||||
fprintf(gr->fp, ".P { fill: rgb(128, 128, 255); }\n");
|
||||
fprintf(gr->fp, ".X { fill: rgb(192, 128, 255); }\n");
|
||||
fprintf(gr->fp, ".J { fill: rgb(255, 128, 255); }\n");
|
||||
fprintf(gr->fp, ".L { fill: rgb(128, 128, 128); }\n");
|
||||
fprintf(gr->fp, ".optional { stroke-dasharray: 5; }\n");
|
||||
fprintf(gr->fp, ".arrow { fill: rgba(0, 0, 0, 0.2); "
|
||||
"stroke: none; }\n");
|
||||
}
|
||||
fprintf(gr->fp, "</style>\n");
|
||||
|
||||
gr->started = true;
|
||||
}
|
||||
|
||||
/* Logical coordinates in our mathematical space */
|
||||
GrCoords gr_logcoords(Point p)
|
||||
{
|
||||
double rt3o2 = sqrt(3) / 2;
|
||||
GrCoords r = {
|
||||
p.coeffs[0] + rt3o2 * p.coeffs[1] + 0.5 * p.coeffs[2],
|
||||
p.coeffs[3] + rt3o2 * p.coeffs[2] + 0.5 * p.coeffs[1],
|
||||
};
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Physical coordinates in the output image */
|
||||
GrCoords gr_log2phys(Graphics *gr, GrCoords c)
|
||||
{
|
||||
c.x = gr->xoff + gr->xscale * c.x;
|
||||
c.y = gr->yoff + gr->yscale * c.y;
|
||||
return c;
|
||||
}
|
||||
GrCoords gr_physcoords(Graphics *gr, Point p)
|
||||
{
|
||||
return gr_log2phys(gr, gr_logcoords(p));
|
||||
}
|
||||
|
||||
void gr_draw_text(Graphics *gr, GrCoords logpos, double logheight,
|
||||
const char *text)
|
||||
{
|
||||
GrCoords pos;
|
||||
double height;
|
||||
|
||||
if (!gr)
|
||||
return;
|
||||
gr_ensure_started(gr);
|
||||
|
||||
pos = gr_log2phys(gr, logpos);
|
||||
height = gr->absscale * logheight;
|
||||
fprintf(gr->fp, "<text style=\"font-size: %fpx\" x=\"%f\" y=\"%f\">"
|
||||
"%s</text>\n", height, pos.x, pos.y + 0.35 * height, text);
|
||||
}
|
||||
|
||||
void gr_draw_path(Graphics *gr, const char *classes, const GrCoords *phys,
|
||||
size_t n, bool closed)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (!gr)
|
||||
return;
|
||||
gr_ensure_started(gr);
|
||||
|
||||
fprintf(gr->fp, "<path class=\"%s\" d=\"", classes);
|
||||
for (i = 0; i < n; i++) {
|
||||
GrCoords c = phys[i];
|
||||
if (i == 0)
|
||||
fprintf(gr->fp, "M %f %f", c.x, c.y);
|
||||
else if (gr->arcs)
|
||||
fprintf(gr->fp, "A %f %f 10 0 %zu %f %f",
|
||||
gr->absscale, gr->absscale, i&1, c.x, c.y);
|
||||
else
|
||||
fprintf(gr->fp, "L %f %f", c.x, c.y);
|
||||
}
|
||||
if (gr->arcs) {
|
||||
/* Explicitly return to the starting point so as to curve the
|
||||
* final edge */
|
||||
fprintf(gr->fp, "A %f %f 10 0 0 %f %f",
|
||||
gr->absscale, gr->absscale, phys[0].x, phys[0].y);
|
||||
}
|
||||
if (closed)
|
||||
fprintf(gr->fp, " z");
|
||||
fprintf(gr->fp, "\"/>\n");
|
||||
}
|
||||
|
||||
void gr_draw_blob(Graphics *gr, const char *classes, GrCoords log,
|
||||
double logradius)
|
||||
{
|
||||
GrCoords centre;
|
||||
|
||||
if (!gr)
|
||||
return;
|
||||
gr_ensure_started(gr);
|
||||
|
||||
centre = gr_log2phys(gr, log);
|
||||
fprintf(gr->fp, "<circle class=\"%s\" cx=\"%f\" cy=\"%f\" r=\"%f\"/>\n",
|
||||
classes, centre.x, centre.y, gr->absscale * logradius);
|
||||
}
|
||||
|
||||
void gr_draw_hex(Graphics *gr, unsigned index, Hex htype,
|
||||
const Point *vertices)
|
||||
{
|
||||
size_t i;
|
||||
Point centre;
|
||||
|
||||
if (!gr)
|
||||
return;
|
||||
gr_ensure_started(gr);
|
||||
|
||||
/* Draw the actual hexagon, in its own colour */
|
||||
if (!gr->jigsaw_mode) {
|
||||
GrCoords phys[6];
|
||||
for (i = 0; i < 6; i++)
|
||||
phys[i] = gr_physcoords(gr, vertices[i]);
|
||||
gr_draw_path(gr, (index == 7 && htype == NO_HEX ?
|
||||
"optional" : hex_names[htype]), phys, 6, true);
|
||||
} else {
|
||||
GrCoords phys[66];
|
||||
size_t pos = 0;
|
||||
const struct HexData *hd = &hexdata[htype];
|
||||
|
||||
for (i = 0; i < 6; i++) {
|
||||
int edge_type = hd->edges[i];
|
||||
int sign = edge_type < 0 ? -1 : +1;
|
||||
int edge_abs = abs(edge_type);
|
||||
int left_sign = (edge_abs & 4) ? sign : edge_type == 0 ? +1 : 0;
|
||||
int mid_sign = (edge_abs & 2) ? sign : 0;
|
||||
int right_sign = (edge_abs & 1) ? sign : edge_type == 0 ? -1 : 0;
|
||||
|
||||
GrCoords start = gr_physcoords(gr, vertices[i]);
|
||||
GrCoords end = gr_physcoords(gr, vertices[(i+1) % 6]);
|
||||
GrCoords x = { (end.x - start.x) / 7, (end.y - start.y) / 7 };
|
||||
GrCoords y = { -x.y, +x.x };
|
||||
|
||||
#define addpoint(X, Y) do { \
|
||||
GrCoords p = { \
|
||||
start.x + (X) * x.x + (Y) * y.x, \
|
||||
start.y + (X) * x.y + (Y) * y.y, \
|
||||
}; \
|
||||
phys[pos++] = p; \
|
||||
} while (0)
|
||||
|
||||
if (sign < 0) {
|
||||
int tmp = right_sign;
|
||||
right_sign = left_sign;
|
||||
left_sign = tmp;
|
||||
}
|
||||
|
||||
addpoint(0, 0);
|
||||
if (left_sign) {
|
||||
addpoint(1, 0);
|
||||
addpoint(2, left_sign);
|
||||
addpoint(2, 0);
|
||||
}
|
||||
if (mid_sign) {
|
||||
addpoint(3, 0);
|
||||
addpoint(3, mid_sign);
|
||||
addpoint(4, mid_sign);
|
||||
addpoint(4, 0);
|
||||
}
|
||||
if (right_sign) {
|
||||
addpoint(5, 0);
|
||||
addpoint(5, right_sign);
|
||||
addpoint(6, 0);
|
||||
}
|
||||
|
||||
#undef addpoint
|
||||
|
||||
}
|
||||
gr_draw_path(gr, hex_names[htype], phys, pos, true);
|
||||
}
|
||||
|
||||
/* Find the centre of the hex */
|
||||
for (i = 0; i < 4; i++)
|
||||
centre.coeffs[i] = 0;
|
||||
for (i = 0; i < 6; i++)
|
||||
centre = point_add(centre, vertices[i]);
|
||||
for (i = 0; i < 4; i++)
|
||||
centre.coeffs[i] /= 6;
|
||||
|
||||
/* Draw an arrow towards vertex 0 of the hex */
|
||||
if (gr->hex_arrows) {
|
||||
double ext = 0.6;
|
||||
double headlen = 0.3, thick = 0.08, headwid = 0.25;
|
||||
GrCoords top = gr_physcoords(gr, vertices[0]);
|
||||
GrCoords bot = gr_physcoords(gr, vertices[3]);
|
||||
GrCoords mid = gr_physcoords(gr, centre);
|
||||
GrCoords base = { mid.x + ext * (bot.x - mid.x),
|
||||
mid.y + ext * (bot.y - mid.y) };
|
||||
GrCoords tip = { mid.x + ext * (top.x - mid.x),
|
||||
mid.y + ext * (top.y - mid.y) };
|
||||
GrCoords len = { tip.x - base.x, tip.y - base.y };
|
||||
GrCoords perp = { -len.y, +len.x };
|
||||
GrCoords basep = { base.x+perp.x*thick, base.y+perp.y*thick };
|
||||
GrCoords basen = { base.x-perp.x*thick, base.y-perp.y*thick };
|
||||
GrCoords hbase = { tip.x-len.x*headlen, tip.y-len.y*headlen };
|
||||
GrCoords headp = { hbase.x+perp.x*thick, hbase.y+perp.y*thick };
|
||||
GrCoords headn = { hbase.x-perp.x*thick, hbase.y-perp.y*thick };
|
||||
GrCoords headP = { hbase.x+perp.x*headwid, hbase.y+perp.y*headwid };
|
||||
GrCoords headN = { hbase.x-perp.x*headwid, hbase.y-perp.y*headwid };
|
||||
|
||||
GrCoords phys[] = {
|
||||
basep, headp, headP, tip, headN, headn, basen
|
||||
};
|
||||
|
||||
gr_draw_path(gr, "arrow", phys, lenof(phys), true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Label the hex with its index and type.
|
||||
*/
|
||||
if (gr->number_cells) {
|
||||
char buf[64];
|
||||
if (index == (unsigned)-1) {
|
||||
if (htype == NO_HEX)
|
||||
buf[0] = '\0';
|
||||
else
|
||||
strcpy(buf, hex_names[htype]);
|
||||
} else {
|
||||
if (htype == NO_HEX)
|
||||
sprintf(buf, "%u", index);
|
||||
else
|
||||
sprintf(buf, "%u (%s)", index, hex_names[htype]);
|
||||
}
|
||||
if (buf[0])
|
||||
gr_draw_text(gr, gr_logcoords(centre), 1.2, buf);
|
||||
}
|
||||
}
|
||||
|
||||
void gr_draw_spectre(Graphics *gr, Hex container, unsigned index,
|
||||
const Point *vertices)
|
||||
{
|
||||
size_t i;
|
||||
GrCoords log[14];
|
||||
GrCoords centre;
|
||||
|
||||
if (!gr)
|
||||
return;
|
||||
gr_ensure_started(gr);
|
||||
|
||||
for (i = 0; i < 14; i++)
|
||||
log[i] = gr_logcoords(vertices[i]);
|
||||
|
||||
/* Draw the actual Spectre */
|
||||
{
|
||||
GrCoords phys[14];
|
||||
char class[16];
|
||||
for (i = 0; i < 14; i++)
|
||||
phys[i] = gr_log2phys(gr, log[i]);
|
||||
if (gr->four_colour) {
|
||||
sprintf(class, "c%u", index);
|
||||
} else if (index == 1 && container == NO_HEX) {
|
||||
sprintf(class, "optional");
|
||||
} else {
|
||||
sprintf(class, "%s%.0u", hex_names[container], index);
|
||||
}
|
||||
gr_draw_path(gr, class, phys, 14, true);
|
||||
}
|
||||
|
||||
/* Pick a point to use as the centre of the Spectre for labelling */
|
||||
centre.x = (log[5].x + log[6].x + log[11].x + log[12].x) / 4;
|
||||
centre.y = (log[5].y + log[6].y + log[11].y + log[12].y) / 4;
|
||||
|
||||
/*
|
||||
* Label the hex with its index and type.
|
||||
*/
|
||||
if (gr->number_cells && index != (unsigned)-1) {
|
||||
char buf[64];
|
||||
sprintf(buf, "%u", index);
|
||||
gr_draw_text(gr, centre, 1.2, buf);
|
||||
}
|
||||
}
|
||||
|
||||
void gr_draw_spectre_from_coords(Graphics *gr, SpectreCoords *sc,
|
||||
const Point *vertices)
|
||||
{
|
||||
Hex h;
|
||||
unsigned index;
|
||||
|
||||
if (!gr)
|
||||
return;
|
||||
gr_ensure_started(gr);
|
||||
|
||||
if (gr->four_colour) {
|
||||
h = NO_HEX;
|
||||
if (sc->index == 1)
|
||||
index = 3; /* special colour for odd G1 Spectres */
|
||||
else
|
||||
index = sc->hex_colour;
|
||||
} else if (sc) {
|
||||
h = sc->c[0].type;
|
||||
index = sc->index;
|
||||
} else {
|
||||
h = NO_HEX;
|
||||
index = -1;
|
||||
}
|
||||
gr_draw_spectre(gr, h, index, vertices);
|
||||
}
|
||||
|
||||
void gr_draw_extra_edge(Graphics *gr, Point a, Point b)
|
||||
{
|
||||
GrCoords phys[2];
|
||||
|
||||
if (!gr)
|
||||
return;
|
||||
gr_ensure_started(gr);
|
||||
|
||||
phys[0] = gr_physcoords(gr, a);
|
||||
phys[1] = gr_physcoords(gr, b);
|
||||
gr_draw_path(gr, "extraedge", phys, 2, false);
|
||||
}
|
51
auxiliary/spectre-help.h
Normal file
51
auxiliary/spectre-help.h
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Header for spectre-help.c
|
||||
*/
|
||||
|
||||
/* Dummy value indicating no specific hexagon, used in some diagrams
|
||||
* for the accompanying article. */
|
||||
#define NO_HEX (Hex)9
|
||||
|
||||
/*
|
||||
* String constants for the hex names, including an extra entry
|
||||
* mapping NO_HEX to the empty string.
|
||||
*/
|
||||
extern const char *hex_names[10];
|
||||
|
||||
typedef struct Graphics {
|
||||
FILE *fp;
|
||||
bool close_file; /* if it's not stdout */
|
||||
bool started; /* have we written the header yet? */
|
||||
double xoff, xscale, yoff, yscale, absscale, linewidth;
|
||||
bool jigsaw_mode; /* draw protrusions on hex edges */
|
||||
bool vertex_blobs; /* draw blobs marking hex vertices */
|
||||
bool hex_arrows; /* draw arrows orienting each hex */
|
||||
bool number_edges; /* number the edges of everything */
|
||||
bool number_cells; /* number the things themselves */
|
||||
bool four_colour; /* four-colour Spectres instead of semantically */
|
||||
bool arcs; /* draw Spectre edges as arcs */
|
||||
} Graphics;
|
||||
|
||||
typedef struct GrCoords {
|
||||
double x, y;
|
||||
} GrCoords;
|
||||
|
||||
Graphics *gr_new(const char *filename, double xmin, double xmax,
|
||||
double ymin, double ymax, double scale);
|
||||
void gr_free(Graphics *gr);
|
||||
GrCoords gr_logcoords(Point p);
|
||||
GrCoords gr_log2phys(Graphics *gr, GrCoords c);
|
||||
GrCoords gr_physcoords(Graphics *gr, Point p);
|
||||
void gr_draw_text(Graphics *gr, GrCoords logpos, double logheight,
|
||||
const char *text);
|
||||
void gr_draw_path(Graphics *gr, const char *classes, const GrCoords *phys,
|
||||
size_t n, bool closed);
|
||||
void gr_draw_blob(Graphics *gr, const char *classes, GrCoords log,
|
||||
double logradius);
|
||||
void gr_draw_hex(Graphics *gr, unsigned index, Hex htype,
|
||||
const Point *vertices);
|
||||
void gr_draw_spectre(Graphics *gr, Hex container, unsigned index,
|
||||
const Point *vertices);
|
||||
void gr_draw_spectre_from_coords(Graphics *gr, SpectreCoords *sc,
|
||||
const Point *vertices);
|
||||
void gr_draw_extra_edge(Graphics *gr, Point a, Point b);
|
334
auxiliary/spectre-tables-extra.h
Normal file
334
auxiliary/spectre-tables-extra.h
Normal file
@ -0,0 +1,334 @@
|
||||
/*
|
||||
* Further data tables used to generate the final transition maps.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Locations in the plane of the centres of the 8 hexagons in the
|
||||
* expansion of each hex.
|
||||
*
|
||||
* We take the centre-to-centre distance to be 6 units, so that other
|
||||
* locations in the hex tiling (e.g. edge midpoints and vertices) will
|
||||
* still have integer coefficients.
|
||||
*
|
||||
* These locations are represented using the same Point type used for
|
||||
* the whole tiling, but all our angles are 60 degrees, so we don't
|
||||
* ever need the coefficients of d or d^3, only of 1 and d^2.
|
||||
*/
|
||||
static const Point hex_centres[] = {
|
||||
{{0, 0, 0, 0}}, {{6, 0, 0, 0}}, /* 0 1 */
|
||||
{{0, 0, -6, 0}}, {{6, 0, -6, 0}}, /* 2 3 */
|
||||
{{0, 0, -12, 0}}, {{6, 0, -12, 0}}, {{12, 0, -12, 0}}, /* 4 5 6 */
|
||||
{{12, 0, -18, 0}}, /* 7 */
|
||||
};
|
||||
|
||||
/*
|
||||
* Orientations of all the sub-hexes in the expansion of each hex.
|
||||
* Measured anticlockwise (that is, as a power of s) from 0, where 0
|
||||
* means the hex is upright, with its own vertex #0 at the top.
|
||||
*/
|
||||
|
||||
static const unsigned orientations_G[] = {
|
||||
2, /* HEX_F */
|
||||
1, /* HEX_X */
|
||||
0, /* HEX_G */
|
||||
1, /* HEX_S */
|
||||
4, /* HEX_P */
|
||||
5, /* HEX_D */
|
||||
0, /* HEX_J */
|
||||
/* hex #7 is not present for this tile */
|
||||
};
|
||||
static const unsigned orientations_D[] = {
|
||||
2, /* HEX_F */
|
||||
1, /* HEX_P */
|
||||
0, /* HEX_G */
|
||||
1, /* HEX_S */
|
||||
4, /* HEX_X */
|
||||
5, /* HEX_D */
|
||||
0, /* HEX_F */
|
||||
5, /* HEX_X */
|
||||
};
|
||||
static const unsigned orientations_J[] = {
|
||||
2, /* HEX_F */
|
||||
1, /* HEX_P */
|
||||
0, /* HEX_G */
|
||||
1, /* HEX_S */
|
||||
4, /* HEX_Y */
|
||||
5, /* HEX_D */
|
||||
0, /* HEX_F */
|
||||
5, /* HEX_P */
|
||||
};
|
||||
static const unsigned orientations_L[] = {
|
||||
2, /* HEX_F */
|
||||
1, /* HEX_P */
|
||||
0, /* HEX_G */
|
||||
1, /* HEX_S */
|
||||
4, /* HEX_Y */
|
||||
5, /* HEX_D */
|
||||
0, /* HEX_F */
|
||||
5, /* HEX_X */
|
||||
};
|
||||
static const unsigned orientations_X[] = {
|
||||
2, /* HEX_F */
|
||||
1, /* HEX_Y */
|
||||
0, /* HEX_G */
|
||||
1, /* HEX_S */
|
||||
4, /* HEX_Y */
|
||||
5, /* HEX_D */
|
||||
0, /* HEX_F */
|
||||
5, /* HEX_P */
|
||||
};
|
||||
static const unsigned orientations_P[] = {
|
||||
2, /* HEX_F */
|
||||
1, /* HEX_Y */
|
||||
0, /* HEX_G */
|
||||
1, /* HEX_S */
|
||||
4, /* HEX_Y */
|
||||
5, /* HEX_D */
|
||||
0, /* HEX_F */
|
||||
5, /* HEX_X */
|
||||
};
|
||||
static const unsigned orientations_S[] = {
|
||||
2, /* HEX_L */
|
||||
1, /* HEX_P */
|
||||
0, /* HEX_G */
|
||||
1, /* HEX_S */
|
||||
4, /* HEX_X */
|
||||
5, /* HEX_D */
|
||||
0, /* HEX_F */
|
||||
5, /* HEX_X */
|
||||
};
|
||||
static const unsigned orientations_F[] = {
|
||||
2, /* HEX_F */
|
||||
1, /* HEX_P */
|
||||
0, /* HEX_G */
|
||||
1, /* HEX_S */
|
||||
4, /* HEX_Y */
|
||||
5, /* HEX_D */
|
||||
0, /* HEX_F */
|
||||
5, /* HEX_Y */
|
||||
};
|
||||
static const unsigned orientations_Y[] = {
|
||||
2, /* HEX_F */
|
||||
1, /* HEX_Y */
|
||||
0, /* HEX_G */
|
||||
1, /* HEX_S */
|
||||
4, /* HEX_Y */
|
||||
5, /* HEX_D */
|
||||
0, /* HEX_F */
|
||||
5, /* HEX_Y */
|
||||
};
|
||||
|
||||
/*
|
||||
* For each hex type, indicate the point on the boundary of the
|
||||
* expansion that corresponds to vertex 0 of the superhex. Also,
|
||||
* indicate the initial direction we head in to go round the edge.
|
||||
*/
|
||||
#define HEX_OUTLINE_START_COMMON {{ -4, 0, -10, 0 }}, {{ +2, 0, +2, 0 }}
|
||||
#define HEX_OUTLINE_START_RARE {{ -2, 0, -14, 0 }}, {{ -2, 0, +4, 0 }}
|
||||
#define HEX_OUTLINE_START_G HEX_OUTLINE_START_COMMON
|
||||
#define HEX_OUTLINE_START_D HEX_OUTLINE_START_RARE
|
||||
#define HEX_OUTLINE_START_J HEX_OUTLINE_START_COMMON
|
||||
#define HEX_OUTLINE_START_L HEX_OUTLINE_START_COMMON
|
||||
#define HEX_OUTLINE_START_X HEX_OUTLINE_START_COMMON
|
||||
#define HEX_OUTLINE_START_P HEX_OUTLINE_START_COMMON
|
||||
#define HEX_OUTLINE_START_S HEX_OUTLINE_START_RARE
|
||||
#define HEX_OUTLINE_START_F HEX_OUTLINE_START_COMMON
|
||||
#define HEX_OUTLINE_START_Y HEX_OUTLINE_START_COMMON
|
||||
|
||||
/*
|
||||
* Similarly, for each hex type, indicate the point on the boundary of
|
||||
* its Spectre expansion that corresponds to hex vertex 0.
|
||||
*
|
||||
* This time, it's easiest just to indicate which vertex of which
|
||||
* sub-Spectre we take in each case, because the Spectre outlines
|
||||
* don't take predictable turns between the edge expansions, so the
|
||||
* routine consuming this data will have to look things up in its
|
||||
* edgemap anyway.
|
||||
*/
|
||||
#define SPEC_OUTLINE_START_COMMON 0, 9
|
||||
#define SPEC_OUTLINE_START_RARE 0, 8
|
||||
#define SPEC_OUTLINE_START_G SPEC_OUTLINE_START_COMMON
|
||||
#define SPEC_OUTLINE_START_D SPEC_OUTLINE_START_RARE
|
||||
#define SPEC_OUTLINE_START_J SPEC_OUTLINE_START_COMMON
|
||||
#define SPEC_OUTLINE_START_L SPEC_OUTLINE_START_COMMON
|
||||
#define SPEC_OUTLINE_START_X SPEC_OUTLINE_START_COMMON
|
||||
#define SPEC_OUTLINE_START_P SPEC_OUTLINE_START_COMMON
|
||||
#define SPEC_OUTLINE_START_S SPEC_OUTLINE_START_RARE
|
||||
#define SPEC_OUTLINE_START_F SPEC_OUTLINE_START_COMMON
|
||||
#define SPEC_OUTLINE_START_Y SPEC_OUTLINE_START_COMMON
|
||||
|
||||
/*
|
||||
* The paper also defines a set of 8 different classes of edges for
|
||||
* the hexagons. (You can imagine these as different shapes of
|
||||
* jigsaw-piece tab, constraining how the hexes can fit together). So
|
||||
* for each hex, we need a list of its edge types.
|
||||
*
|
||||
* Most edge types come in two matching pairs, which the paper labels
|
||||
* with the same lowercase Greek letter and a + or - superscript, e.g.
|
||||
* alpha^+ and alpha^-. The usual rule is that when two edges meet,
|
||||
* they have to be the + and - versions of the same letter. The
|
||||
* exception to this rule is the 'eta' edge, which has no sign: it's
|
||||
* symmetric, so any two eta edges can validly meet.
|
||||
*
|
||||
* We express this here by defining an enumeration in which eta = 0
|
||||
* and all other edge types have positive values, so that integer
|
||||
* negation can be used to indicate the other edge that fits with this
|
||||
* one (and for eta, it doesn't change the value).
|
||||
*/
|
||||
enum Edge {
|
||||
edge_eta = 0,
|
||||
edge_alpha,
|
||||
edge_beta,
|
||||
edge_gamma,
|
||||
edge_delta,
|
||||
edge_epsilon,
|
||||
edge_zeta,
|
||||
edge_theta,
|
||||
};
|
||||
|
||||
/*
|
||||
* Edge types for each hex are specified anticlockwise, starting from
|
||||
* the top vertex, so that edge #0 is the top-left diagonal edge, edge
|
||||
* #1 the left-hand vertical edge, etc.
|
||||
*/
|
||||
static const int edges_G[6] = {
|
||||
-edge_beta, -edge_alpha, +edge_alpha,
|
||||
-edge_gamma, -edge_delta, +edge_beta,
|
||||
};
|
||||
static const int edges_D[6] = {
|
||||
-edge_zeta, +edge_gamma, +edge_beta,
|
||||
-edge_epsilon, +edge_alpha, -edge_gamma,
|
||||
};
|
||||
static const int edges_J[6] = {
|
||||
-edge_beta, +edge_gamma, +edge_beta,
|
||||
+edge_theta, +edge_beta, edge_eta,
|
||||
};
|
||||
static const int edges_L[6] = {
|
||||
-edge_beta, +edge_gamma, +edge_beta,
|
||||
-edge_epsilon, +edge_alpha, -edge_theta,
|
||||
};
|
||||
static const int edges_X[6] = {
|
||||
-edge_beta, -edge_alpha, +edge_epsilon,
|
||||
+edge_theta, +edge_beta, edge_eta,
|
||||
};
|
||||
static const int edges_P[6] = {
|
||||
-edge_beta, -edge_alpha, +edge_epsilon,
|
||||
-edge_epsilon, +edge_alpha, -edge_theta,
|
||||
};
|
||||
static const int edges_S[6] = {
|
||||
+edge_delta, +edge_zeta, +edge_beta,
|
||||
-edge_epsilon, +edge_alpha, -edge_gamma,
|
||||
};
|
||||
static const int edges_F[6] = {
|
||||
-edge_beta, +edge_gamma, +edge_beta,
|
||||
-edge_epsilon, +edge_epsilon, edge_eta,
|
||||
};
|
||||
static const int edges_Y[6] = {
|
||||
-edge_beta, -edge_alpha, +edge_epsilon,
|
||||
-edge_epsilon, +edge_epsilon, edge_eta,
|
||||
};
|
||||
|
||||
/*
|
||||
* Now specify the actual shape of each edge type, in terms of the
|
||||
* angles of turns as you traverse the edge.
|
||||
*
|
||||
* Edges around the outline of a hex expansion are traversed
|
||||
* _clockwise_, because each expansion step flips the handedness of
|
||||
* the whole system.
|
||||
*
|
||||
* Each array has one fewer element than the number of sub-edges in
|
||||
* the edge shape (for the usual reason - n edges in a path have only
|
||||
* n-1 vertices separating them).
|
||||
*
|
||||
* These arrays show the positive version of each edge type. The
|
||||
* negative version is obtained by reversing the order of the turns
|
||||
* and also the sign of each turn.
|
||||
*/
|
||||
static const int hex_edge_shape_eta[] = { +2, +2, -2, -2 };
|
||||
static const int hex_edge_shape_alpha[] = { +2, -2 };
|
||||
static const int hex_edge_shape_beta[] = { -2 };
|
||||
static const int hex_edge_shape_gamma[] = { +2, -2, -2, +2 };
|
||||
static const int hex_edge_shape_delta[] = { -2, +2, -2, +2 };
|
||||
static const int hex_edge_shape_epsilon[] = { +2, -2, -2 };
|
||||
static const int hex_edge_shape_zeta[] = { -2, +2 };
|
||||
static const int hex_edge_shape_theta[] = { +2, +2, -2, -2, +2 };
|
||||
|
||||
static const int *const hex_edge_shapes[] = {
|
||||
hex_edge_shape_eta,
|
||||
hex_edge_shape_alpha,
|
||||
hex_edge_shape_beta,
|
||||
hex_edge_shape_gamma,
|
||||
hex_edge_shape_delta,
|
||||
hex_edge_shape_epsilon,
|
||||
hex_edge_shape_zeta,
|
||||
hex_edge_shape_theta,
|
||||
};
|
||||
static const size_t hex_edge_lengths[] = {
|
||||
lenof(hex_edge_shape_eta) + 1,
|
||||
lenof(hex_edge_shape_alpha) + 1,
|
||||
lenof(hex_edge_shape_beta) + 1,
|
||||
lenof(hex_edge_shape_gamma) + 1,
|
||||
lenof(hex_edge_shape_delta) + 1,
|
||||
lenof(hex_edge_shape_epsilon) + 1,
|
||||
lenof(hex_edge_shape_zeta) + 1,
|
||||
lenof(hex_edge_shape_theta) + 1,
|
||||
};
|
||||
|
||||
static const int spec_edge_shape_eta[] = { 0 };
|
||||
static const int spec_edge_shape_alpha[] = { -2, +3 };
|
||||
static const int spec_edge_shape_beta[] = { +3, -2 };
|
||||
static const int spec_edge_shape_gamma[] = { +2 };
|
||||
static const int spec_edge_shape_delta[] = { +2, +3, +2, -3, +2 };
|
||||
static const int spec_edge_shape_epsilon[] = { +3 };
|
||||
static const int spec_edge_shape_zeta[] = { -2 };
|
||||
/* In expansion to Spectres, a theta edge corresponds to just one
|
||||
* Spectre edge, so its turns array would be completely empty! */
|
||||
|
||||
static const int *const spec_edge_shapes[] = {
|
||||
spec_edge_shape_eta,
|
||||
spec_edge_shape_alpha,
|
||||
spec_edge_shape_beta,
|
||||
spec_edge_shape_gamma,
|
||||
spec_edge_shape_delta,
|
||||
spec_edge_shape_epsilon,
|
||||
spec_edge_shape_zeta,
|
||||
NULL, /* theta has no turns */
|
||||
};
|
||||
static const size_t spec_edge_lengths[] = {
|
||||
lenof(spec_edge_shape_eta) + 1,
|
||||
lenof(spec_edge_shape_alpha) + 1,
|
||||
lenof(spec_edge_shape_beta) + 1,
|
||||
lenof(spec_edge_shape_gamma) + 1,
|
||||
lenof(spec_edge_shape_delta) + 1,
|
||||
lenof(spec_edge_shape_epsilon) + 1,
|
||||
lenof(spec_edge_shape_zeta) + 1,
|
||||
1, /* theta is only one edge long */
|
||||
};
|
||||
|
||||
/*
|
||||
* Each edge type corresponds to a fixed number of edges of the
|
||||
* hexagon layout in the expansion of each hex, and also to a fixed
|
||||
* number of edges of the Spectre(s) that each hex expands to in the
|
||||
* final step.
|
||||
*/
|
||||
static const int edgelen_hex[] = {
|
||||
5, /* edge_eta */
|
||||
3, /* edge_alpha */
|
||||
2, /* edge_beta */
|
||||
5, /* edge_gamma */
|
||||
5, /* edge_delta */
|
||||
4, /* edge_epsilon */
|
||||
3, /* edge_zeta */
|
||||
6, /* edge_theta */
|
||||
};
|
||||
|
||||
static const int edgelen_spectre[] = {
|
||||
2, /* edge_eta */
|
||||
3, /* edge_alpha */
|
||||
3, /* edge_beta */
|
||||
2, /* edge_gamma */
|
||||
6, /* edge_delta */
|
||||
2, /* edge_epsilon */
|
||||
2, /* edge_zeta */
|
||||
1, /* edge_theta */
|
||||
};
|
534
auxiliary/spectre-test.c
Normal file
534
auxiliary/spectre-test.c
Normal file
@ -0,0 +1,534 @@
|
||||
/*
|
||||
* Standalone test program for spectre.c.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#ifdef NO_TGMATH_H
|
||||
# include <math.h>
|
||||
#else
|
||||
# include <tgmath.h>
|
||||
#endif
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "puzzles.h"
|
||||
#include "spectre-internal.h"
|
||||
#include "spectre-tables-manual.h"
|
||||
#include "spectre-tables-auto.h"
|
||||
#include "spectre-help.h"
|
||||
|
||||
static void step_tests(void)
|
||||
{
|
||||
SpectreContext ctx[1];
|
||||
random_state *rs;
|
||||
SpectreCoords *sc;
|
||||
unsigned outedge;
|
||||
|
||||
rs = random_new("12345", 5);
|
||||
spectrectx_init_random(ctx, rs);
|
||||
|
||||
/* Simplest possible transition: between the two Spectres making
|
||||
* up a G hex. */
|
||||
sc = spectre_coords_new();
|
||||
spectre_coords_make_space(sc, 1);
|
||||
sc->index = 0;
|
||||
sc->nc = 1;
|
||||
sc->c[0].type = HEX_G;
|
||||
sc->c[0].index = -1;
|
||||
spectrectx_step(ctx, sc, 12, &outedge);
|
||||
assert(outedge == 5);
|
||||
assert(sc->index == 1);
|
||||
assert(sc->nc == 1);
|
||||
assert(sc->c[0].type == HEX_G);
|
||||
assert(sc->c[0].index == -1);
|
||||
spectre_coords_free(sc);
|
||||
|
||||
/* Test the double Spectre transition. Here, within a F superhex,
|
||||
* we attempt to step from the G subhex to the S one, in such a
|
||||
* way that the place where we enter the Spectre corresponding to
|
||||
* the S hex is on its spur of detached edge, causing us to
|
||||
* immediately transition back out of the other side of that spur
|
||||
* and end up in the D subhex instead. */
|
||||
sc = spectre_coords_new();
|
||||
spectre_coords_make_space(sc, 2);
|
||||
sc->index = 1;
|
||||
sc->nc = 2;
|
||||
sc->c[0].type = HEX_G;
|
||||
sc->c[0].index = 2;
|
||||
sc->c[1].type = HEX_F;
|
||||
sc->c[1].index = -1;
|
||||
spectrectx_step(ctx, sc, 1, &outedge);
|
||||
assert(outedge == 6);
|
||||
assert(sc->index == 0);
|
||||
assert(sc->nc == 2);
|
||||
assert(sc->c[0].type == HEX_D);
|
||||
assert(sc->c[0].index == 5);
|
||||
assert(sc->c[1].type == HEX_F);
|
||||
assert(sc->c[1].index == -1);
|
||||
spectre_coords_free(sc);
|
||||
|
||||
/* However, _this_ transition leaves the same G subhex by the same
|
||||
* edge of the hexagon, but further along it, so that we land in
|
||||
* the S Spectre and stay there, without needing a double
|
||||
* transition. */
|
||||
sc = spectre_coords_new();
|
||||
spectre_coords_make_space(sc, 2);
|
||||
sc->index = 1;
|
||||
sc->nc = 2;
|
||||
sc->c[0].type = HEX_G;
|
||||
sc->c[0].index = 2;
|
||||
sc->c[1].type = HEX_F;
|
||||
sc->c[1].index = -1;
|
||||
spectrectx_step(ctx, sc, 13, &outedge);
|
||||
assert(outedge == 4);
|
||||
assert(sc->index == 0);
|
||||
assert(sc->nc == 2);
|
||||
assert(sc->c[0].type == HEX_S);
|
||||
assert(sc->c[0].index == 3);
|
||||
assert(sc->c[1].type == HEX_F);
|
||||
assert(sc->c[1].index == -1);
|
||||
spectre_coords_free(sc);
|
||||
|
||||
/* A couple of randomly generated transition tests that go a long
|
||||
* way up the stack. */
|
||||
sc = spectre_coords_new();
|
||||
spectre_coords_make_space(sc, 7);
|
||||
sc->index = 0;
|
||||
sc->nc = 7;
|
||||
sc->c[0].type = HEX_S;
|
||||
sc->c[0].index = 3;
|
||||
sc->c[1].type = HEX_Y;
|
||||
sc->c[1].index = 7;
|
||||
sc->c[2].type = HEX_Y;
|
||||
sc->c[2].index = 4;
|
||||
sc->c[3].type = HEX_Y;
|
||||
sc->c[3].index = 4;
|
||||
sc->c[4].type = HEX_F;
|
||||
sc->c[4].index = 0;
|
||||
sc->c[5].type = HEX_X;
|
||||
sc->c[5].index = 1;
|
||||
sc->c[6].type = HEX_G;
|
||||
sc->c[6].index = -1;
|
||||
spectrectx_step(ctx, sc, 13, &outedge);
|
||||
assert(outedge == 12);
|
||||
assert(sc->index == 0);
|
||||
assert(sc->nc == 7);
|
||||
assert(sc->c[0].type == HEX_Y);
|
||||
assert(sc->c[0].index == 1);
|
||||
assert(sc->c[1].type == HEX_P);
|
||||
assert(sc->c[1].index == 1);
|
||||
assert(sc->c[2].type == HEX_D);
|
||||
assert(sc->c[2].index == 5);
|
||||
assert(sc->c[3].type == HEX_Y);
|
||||
assert(sc->c[3].index == 4);
|
||||
assert(sc->c[4].type == HEX_X);
|
||||
assert(sc->c[4].index == 7);
|
||||
assert(sc->c[5].type == HEX_S);
|
||||
assert(sc->c[5].index == 3);
|
||||
assert(sc->c[6].type == HEX_G);
|
||||
assert(sc->c[6].index == -1);
|
||||
spectre_coords_free(sc);
|
||||
|
||||
sc = spectre_coords_new();
|
||||
spectre_coords_make_space(sc, 7);
|
||||
sc->index = 0;
|
||||
sc->nc = 7;
|
||||
sc->c[0].type = HEX_Y;
|
||||
sc->c[0].index = 7;
|
||||
sc->c[1].type = HEX_F;
|
||||
sc->c[1].index = 6;
|
||||
sc->c[2].type = HEX_Y;
|
||||
sc->c[2].index = 4;
|
||||
sc->c[3].type = HEX_X;
|
||||
sc->c[3].index = 7;
|
||||
sc->c[4].type = HEX_L;
|
||||
sc->c[4].index = 0;
|
||||
sc->c[5].type = HEX_S;
|
||||
sc->c[5].index = 3;
|
||||
sc->c[6].type = HEX_F;
|
||||
sc->c[6].index = -1;
|
||||
spectrectx_step(ctx, sc, 0, &outedge);
|
||||
assert(outedge == 1);
|
||||
assert(sc->index == 0);
|
||||
assert(sc->nc == 7);
|
||||
assert(sc->c[0].type == HEX_P);
|
||||
assert(sc->c[0].index == 1);
|
||||
assert(sc->c[1].type == HEX_F);
|
||||
assert(sc->c[1].index == 0);
|
||||
assert(sc->c[2].type == HEX_Y);
|
||||
assert(sc->c[2].index == 7);
|
||||
assert(sc->c[3].type == HEX_F);
|
||||
assert(sc->c[3].index == 0);
|
||||
assert(sc->c[4].type == HEX_G);
|
||||
assert(sc->c[4].index == 2);
|
||||
assert(sc->c[5].type == HEX_D);
|
||||
assert(sc->c[5].index == 5);
|
||||
assert(sc->c[6].type == HEX_F);
|
||||
assert(sc->c[6].index == -1);
|
||||
spectre_coords_free(sc);
|
||||
|
||||
spectrectx_cleanup(ctx);
|
||||
random_free(rs);
|
||||
}
|
||||
|
||||
struct genctx {
|
||||
Graphics *gr;
|
||||
FILE *fp; /* for non-graphical output modes */
|
||||
random_state *rs;
|
||||
Coord xmin, xmax, ymin, ymax;
|
||||
};
|
||||
|
||||
static void gctx_set_size(
|
||||
struct genctx *gctx, int width, int height, double scale,
|
||||
int *xmin, int *xmax, int *ymin, int *ymax)
|
||||
{
|
||||
*xmax = ceil(width/(2*scale));
|
||||
*xmin = -*xmax;
|
||||
*ymax = ceil(height/(2*scale));
|
||||
*ymin = -*ymax;
|
||||
|
||||
/* point_x() and point_y() double their output to avoid having
|
||||
* to use fractions, so double the bounds we'll compare their
|
||||
* results against */
|
||||
gctx->xmin.c1 = *xmin * 2; gctx->xmin.cr3 = 0;
|
||||
gctx->xmax.c1 = *xmax * 2; gctx->xmax.cr3 = 0;
|
||||
gctx->ymin.c1 = *ymin * 2; gctx->ymin.cr3 = 0;
|
||||
gctx->ymax.c1 = *ymax * 2; gctx->ymax.cr3 = 0;
|
||||
}
|
||||
|
||||
static bool callback(void *vctx, const Spectre *spec)
|
||||
{
|
||||
struct genctx *gctx = (struct genctx *)vctx;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < 14; i++) {
|
||||
Point p = spec->vertices[i];
|
||||
Coord x = point_x(p), y = point_y(p);
|
||||
if (coord_cmp(x, gctx->xmin) >= 0 && coord_cmp(x, gctx->xmax) <= 0 &&
|
||||
coord_cmp(y, gctx->ymin) >= 0 && coord_cmp(y, gctx->ymax) <= 0)
|
||||
goto ok;
|
||||
}
|
||||
return false;
|
||||
|
||||
ok:
|
||||
gr_draw_spectre_from_coords(gctx->gr, spec->sc, spec->vertices);
|
||||
if (gctx->fp) {
|
||||
/*
|
||||
* Emit calls to a made-up Python 'spectre()' function which
|
||||
* takes the following parameters:
|
||||
*
|
||||
* - lowest-level hexagon type (one-character string)
|
||||
* - index of Spectre within hexagon (0 or rarely 1)
|
||||
* - array of 14 point coordinates. Each is a 2-tuple
|
||||
* containing x and y. Each of those in turn is a 2-tuple
|
||||
* containing coordinates of 1 and sqrt(3).
|
||||
*/
|
||||
fprintf(gctx->fp, "spectre('%s', %d, [",
|
||||
hex_names[spec->sc->c[0].type], spec->sc->index);
|
||||
for (i = 0; i < 14; i++) {
|
||||
Point p = spec->vertices[i];
|
||||
Coord x = point_x(p), y = point_y(p);
|
||||
fprintf(gctx->fp, "%s((%d,%d),(%d,%d))", i ? ", " : "",
|
||||
x.c1, x.cr3, y.c1, y.cr3);
|
||||
}
|
||||
fprintf(gctx->fp, "])\n");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void generate(struct genctx *gctx)
|
||||
{
|
||||
SpectreContext ctx[1];
|
||||
|
||||
spectrectx_init_random(ctx, gctx->rs);
|
||||
ctx->prototype->hex_colour = random_upto(gctx->rs, 3);
|
||||
ctx->prototype->prev_hex_colour = (ctx->prototype->hex_colour + 1 +
|
||||
random_upto(gctx->rs, 2)) % 3;
|
||||
ctx->prototype->incoming_hex_edge = random_upto(gctx->rs, 2);
|
||||
|
||||
spectrectx_generate(ctx, callback, gctx);
|
||||
|
||||
spectrectx_cleanup(ctx);
|
||||
}
|
||||
|
||||
static inline Point reflected(Point p)
|
||||
{
|
||||
/*
|
||||
* This reflection operation is used as a conjugation, so it
|
||||
* doesn't matter _what_ reflection it is, only that it reverses
|
||||
* sense.
|
||||
*/
|
||||
Point r;
|
||||
size_t i;
|
||||
for (i = 0; i < 4; i++)
|
||||
r.coeffs[i] = p.coeffs[3-i];
|
||||
return r;
|
||||
}
|
||||
static void reflect_spectre(Spectre *spec)
|
||||
{
|
||||
size_t i;
|
||||
for (i = 0; i < 14; i++)
|
||||
spec->vertices[i] = reflected(spec->vertices[i]);
|
||||
}
|
||||
|
||||
static void periodic_cheat(struct genctx *gctx)
|
||||
{
|
||||
Spectre start, sh, sv;
|
||||
size_t i;
|
||||
|
||||
start.sc = NULL;
|
||||
{
|
||||
Point u = {{ 0, 0, 0, 0 }};
|
||||
Point v = {{ 1, 0, 0, 1 }};
|
||||
v = point_mul(v, point_rot(1));
|
||||
spectre_place(&start, u, v, 0);
|
||||
}
|
||||
|
||||
sh = start;
|
||||
while (callback(gctx, &sh)) {
|
||||
sv = sh;
|
||||
i = 0;
|
||||
do {
|
||||
if (i) {
|
||||
spectre_place(&sv, sv.vertices[6], sv.vertices[7], 0);
|
||||
} else {
|
||||
spectre_place(&sv, reflected(sv.vertices[6]),
|
||||
reflected(sv.vertices[7]), 0);
|
||||
reflect_spectre(&sv);
|
||||
}
|
||||
i ^= 1;
|
||||
} while (callback(gctx, &sv));
|
||||
|
||||
sv = sh;
|
||||
i = 0;
|
||||
do {
|
||||
if (i) {
|
||||
spectre_place(&sv, sv.vertices[0], sv.vertices[1], 6);
|
||||
} else {
|
||||
spectre_place(&sv, reflected(sv.vertices[0]),
|
||||
reflected(sv.vertices[1]), 6);
|
||||
reflect_spectre(&sv);
|
||||
}
|
||||
i ^= 1;
|
||||
} while (callback(gctx, &sv));
|
||||
|
||||
spectre_place(&sh, sh.vertices[12], sh.vertices[11], 4);
|
||||
}
|
||||
|
||||
sh = start;
|
||||
do {
|
||||
spectre_place(&sh, sh.vertices[5], sh.vertices[4], 11);
|
||||
|
||||
sv = sh;
|
||||
i = 0;
|
||||
do {
|
||||
if (i) {
|
||||
spectre_place(&sv, sv.vertices[6], sv.vertices[7], 0);
|
||||
} else {
|
||||
spectre_place(&sv, reflected(sv.vertices[6]),
|
||||
reflected(sv.vertices[7]), 0);
|
||||
reflect_spectre(&sv);
|
||||
}
|
||||
i ^= 1;
|
||||
} while (callback(gctx, &sv));
|
||||
|
||||
sv = sh;
|
||||
i = 0;
|
||||
do {
|
||||
if (i) {
|
||||
spectre_place(&sv, sv.vertices[0], sv.vertices[1], 6);
|
||||
} else {
|
||||
spectre_place(&sv, reflected(sv.vertices[0]),
|
||||
reflected(sv.vertices[1]), 6);
|
||||
reflect_spectre(&sv);
|
||||
}
|
||||
i ^= 1;
|
||||
} while (callback(gctx, &sv));
|
||||
} while (callback(gctx, &sh));
|
||||
}
|
||||
|
||||
static void generate_hexes(struct genctx *gctx)
|
||||
{
|
||||
SpectreContext ctx[1];
|
||||
spectrectx_init_random(ctx, gctx->rs);
|
||||
SpectreCoords *sc;
|
||||
unsigned orient, outedge, inedge;
|
||||
bool printed_any = false;
|
||||
size_t r = 1, ri = 0, rj = 0;
|
||||
|
||||
Point centre = {{ 0, 0, 0, 0 }};
|
||||
const Point six = {{ 6, 0, 0, 0 }};
|
||||
|
||||
sc = spectre_coords_copy(ctx->prototype);
|
||||
orient = random_upto(gctx->rs, 6);
|
||||
|
||||
while (true) {
|
||||
Point top = {{ -2, 0, 4, 0 }};
|
||||
Point vertices[6];
|
||||
bool print_this = false;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < 6; i++) {
|
||||
vertices[i] = point_add(centre, point_mul(
|
||||
top, point_rot(2 * (orient + i))));
|
||||
Coord x = point_x(vertices[i]), y = point_y(vertices[i]);
|
||||
if (coord_cmp(x, gctx->xmin) >= 0 &&
|
||||
coord_cmp(x, gctx->xmax) <= 0 &&
|
||||
coord_cmp(y, gctx->ymin) >= 0 &&
|
||||
coord_cmp(y, gctx->ymax) <= 0)
|
||||
print_this = true;
|
||||
}
|
||||
|
||||
if (print_this) {
|
||||
printed_any = true;
|
||||
gr_draw_hex(gctx->gr, -1, sc->c[0].type, vertices);
|
||||
}
|
||||
|
||||
/*
|
||||
* Decide which way to step next. We spiral outwards from a
|
||||
* central hexagon.
|
||||
*/
|
||||
outedge = (ri == 0 && rj == 0) ? 5 : ri;
|
||||
if (++rj >= r) {
|
||||
rj = 0;
|
||||
if (++ri >= 6) {
|
||||
ri = 0;
|
||||
if (!printed_any)
|
||||
break;
|
||||
printed_any = false;
|
||||
++r;
|
||||
}
|
||||
}
|
||||
|
||||
spectrectx_step_hex(ctx, sc, 0, (outedge + 6 - orient) % 6, &inedge);
|
||||
orient = (outedge + 9 - inedge) % 6;
|
||||
|
||||
centre = point_add(centre, point_mul(six, point_rot(4 + 2 * outedge)));
|
||||
}
|
||||
|
||||
spectre_coords_free(sc);
|
||||
spectrectx_cleanup(ctx);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *random_seed = "12345";
|
||||
const char *outfile = "-";
|
||||
bool four_colour = false;
|
||||
enum { TESTS, TILING, CHEAT, HEXES } mode = TILING;
|
||||
enum { SVG, PYTHON } outmode = SVG;
|
||||
double scale = 10, linewidth = 1.5;
|
||||
int width = 1024, height = 768;
|
||||
bool arcs = false;
|
||||
|
||||
while (--argc > 0) {
|
||||
const char *arg = *++argv;
|
||||
if (!strcmp(arg, "--help")) {
|
||||
printf(" usage: spectre-test [FIXME]\n"
|
||||
" also: spectre-test --test\n");
|
||||
return 0;
|
||||
} else if (!strcmp(arg, "--test")) {
|
||||
mode = TESTS;
|
||||
} else if (!strcmp(arg, "--hex")) {
|
||||
mode = HEXES;
|
||||
} else if (!strcmp(arg, "--cheat")) {
|
||||
mode = CHEAT;
|
||||
} else if (!strcmp(arg, "--python")) {
|
||||
outmode = PYTHON;
|
||||
} else if (!strcmp(arg, "--arcs")) {
|
||||
arcs = true;
|
||||
} else if (!strncmp(arg, "--seed=", 7)) {
|
||||
random_seed = arg+7;
|
||||
} else if (!strcmp(arg, "--fourcolour")) {
|
||||
four_colour = true;
|
||||
} else if (!strncmp(arg, "--scale=", 8)) {
|
||||
scale = atof(arg+8);
|
||||
} else if (!strncmp(arg, "--width=", 8)) {
|
||||
width = atof(arg+8);
|
||||
} else if (!strncmp(arg, "--height=", 9)) {
|
||||
height = atof(arg+9);
|
||||
} else if (!strncmp(arg, "--linewidth=", 12)) {
|
||||
linewidth = atof(arg+12);
|
||||
} else if (!strcmp(arg, "-o")) {
|
||||
if (--argc <= 0) {
|
||||
fprintf(stderr, "expected argument to '%s'\n", arg);
|
||||
return 1;
|
||||
}
|
||||
outfile = *++argv;
|
||||
} else {
|
||||
fprintf(stderr, "unexpected extra argument '%s'\n", arg);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case TESTS: {
|
||||
step_tests();
|
||||
break;
|
||||
}
|
||||
|
||||
case TILING:
|
||||
case CHEAT: {
|
||||
struct genctx gctx[1];
|
||||
bool close_output = false;
|
||||
int xmin, xmax, ymin, ymax;
|
||||
|
||||
gctx_set_size(gctx, width, height, scale, &xmin, &xmax, &ymin, &ymax);
|
||||
|
||||
switch (outmode) {
|
||||
case SVG:
|
||||
gctx->gr = gr_new(outfile, xmin, xmax, ymin, ymax, scale);
|
||||
gctx->gr->number_cells = false;
|
||||
gctx->gr->four_colour = four_colour;
|
||||
gctx->gr->linewidth = linewidth;
|
||||
gctx->gr->arcs = arcs;
|
||||
gctx->fp = NULL;
|
||||
break;
|
||||
case PYTHON:
|
||||
gctx->gr = NULL;
|
||||
if (!strcmp(outfile, "-")) {
|
||||
gctx->fp = stdout;
|
||||
} else {
|
||||
gctx->fp = fopen(outfile, "w");
|
||||
close_output = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
gctx->rs = random_new(random_seed, strlen(random_seed));
|
||||
switch (mode) {
|
||||
case TILING:
|
||||
generate(gctx);
|
||||
break;
|
||||
case CHEAT:
|
||||
periodic_cheat(gctx);
|
||||
break;
|
||||
default: /* shouldn't happen */
|
||||
break;
|
||||
}
|
||||
random_free(gctx->rs);
|
||||
gr_free(gctx->gr);
|
||||
if (close_output)
|
||||
fclose(gctx->fp);
|
||||
break;
|
||||
}
|
||||
|
||||
case HEXES: {
|
||||
struct genctx gctx[1];
|
||||
int xmin, xmax, ymin, ymax;
|
||||
|
||||
gctx_set_size(gctx, width, height, scale, &xmin, &xmax, &ymin, &ymax);
|
||||
|
||||
gctx->gr = gr_new(outfile, xmin, xmax, ymin, ymax, scale);
|
||||
gctx->gr->jigsaw_mode = true;
|
||||
gctx->gr->number_edges = false;
|
||||
gctx->gr->linewidth = linewidth;
|
||||
gctx->rs = random_new(random_seed, strlen(random_seed));
|
||||
generate_hexes(gctx); /* FIXME: bounds */
|
||||
random_free(gctx->rs);
|
||||
gr_free(gctx->gr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user