Files
puzzles/auxiliary/spectre-test.c
Simon Tatham da014d23da spectre-test: support raster-mode tiling generation.
This is the most efficient way to apply the combinatorial coordinate
system. As described in my original article (and mentioned again in
the followup one), you can walk along a horizontal or vertical line of
the tiling, identifying which edge of each tile the line will leave it
by, and computing the location and coordinates of the next tile beyond
that edge, so that you visit every tile intersected by the edge.

By doing one iteration, say vertically up the left of your target
area, and using the tiles you find as starting points for a series of
perpendicular sub-iterations spaced closely enough not to miss any
tiles, you can arrange to visit every tile intersecting your target
region, without having ever had to store a large BFS queue of tiles
left to visit. You only have to keep a small bounded number of
coordinate variables for the whole run, so you can generate a very
large patch of tiling with minimal memory and CPU time.

You can even arrange not to emit any duplicates, without having to
actually store the tiles you've already visited, by checking whether
the y-coordinate of the previous horizontal iteration will have
visited the same tile already.

For Spectres, an extra wrinkle (almost literally) is that they're not
convex, so a horizontal line can visit the same one twice, with
another tile in between. So another part of de-duplication is noticing
_that_: is the edge through which we've just entered this tile the
first one we would have seen while traversing our line? If not, then
trust that it's been emitted already.

As a proof of concept (since I claimed it would work in my writeup
article), and in case anyone wants larger tilings than actual Loopy
will conveniently give you, I've implemented that algorithm in
spectre-test.

However, the actual grid generation for Loopy still uses the more
memory-intensive breadth-first search: because that's what I
implemented first (it's more likely to detect its own errors); because
if I changed it now it would invalidate game descriptions (from all of
two days' worth of live play, but even so); and because the linear
space requirement isn't an important cost for Loopy, which is actually
going to _store_ the whole grid after it's generated, so it needed
linear space _anyway_.
2023-06-18 14:33:58 +01:00

790 lines
23 KiB
C

/*
* 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, bool centre,
int *xmin, int *xmax, int *ymin, int *ymax)
{
if (centre) {
*xmax = ceil(width/(2*scale));
*xmin = -*xmax;
*ymax = ceil(height/(2*scale));
*ymin = -*ymax;
} else {
*xmin = *ymin = 0;
*xmax = ceil(width/scale);
*ymax = ceil(height/scale);
}
/* 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 spectrectx_init_random_with_four_colouring(
SpectreContext *ctx, random_state *rs)
{
spectrectx_init_random(ctx, rs);
ctx->prototype->hex_colour = random_upto(rs, 3);
ctx->prototype->prev_hex_colour = (ctx->prototype->hex_colour + 1 +
random_upto(rs, 2)) % 3;
ctx->prototype->incoming_hex_edge = random_upto(rs, 2);
}
static void generate_bfs(struct genctx *gctx)
{
SpectreContext ctx[1];
spectrectx_init_random_with_four_colouring(ctx, gctx->rs);
spectrectx_generate(ctx, callback, gctx);
spectrectx_cleanup(ctx);
}
static inline Point reflected(Point p)
{
/*
* This reflection operation is used as a conjugation by
* periodic_cheat(). For that purpose, it doesn't matter _what_
* reflection it is, only that it reverses sense.
*
* generate_raster() also uses it to conjugate between the 'find
* edges intersecting a horizontal line' and 'ditto vertical'
* operations, so for that purpose, it wants to be the specific
* reflection about the 45-degree line that swaps the positive x-
* and y-axes.
*/
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 Spectre *spectre_copy(const Spectre *orig)
{
Spectre *copy = snew(Spectre);
memcpy(copy->vertices, orig->vertices, sizeof(copy->vertices));
copy->sc = spectre_coords_copy(orig->sc);
copy->next = NULL; /* not used in this tool */
return copy;
}
static size_t find_crossings(struct genctx *gctx, const Spectre *spec, Coord y,
size_t direction, unsigned *edges_out)
{
/*
* Find edges of this Spectre which cross the horizontal line
* specified by the coordinate y.
*
* For tie-breaking purposes, we're treating the line as actually
* being at y + epsilon, so that a line with one endpoint _on_
* that coordinate is counted as crossing it if it goes upwards,
* and not downwards. Put another way, we seek edges one of whose
* vertices is < y and the other >= y.
*
* Also, we're only interested in crossings in a particular
* direction, specified by 'direction' being 0 or 1.
*/
size_t i, j;
struct Edge {
unsigned edge;
/* Location of the crossing point, as the ratio of two Coord */
Coord n, d;
} edges[14];
size_t nedges = 0;
for (i = 0; i < 14; i++) {
Coord yc[2], d[2];
yc[0] = point_y(spec->vertices[i]);
yc[1] = point_y(spec->vertices[(i+1) % 14]);
for (j = 0; j < 2; j++)
d[j] = coord_sub(yc[j], y);
if (coord_sign(d[1-direction]) >= 0 && coord_sign(d[direction]) < 0) {
Coord a0 = coord_abs(d[0]), a1 = coord_abs(d[1]);
Coord x0 = point_x(spec->vertices[i]);
Coord x1 = point_x(spec->vertices[(i+1) % 14]);
edges[nedges].d = coord_add(a0, a1);
edges[nedges].n = coord_add(coord_mul(a1, x0), coord_mul(a0, x1));
edges[nedges].edge = i;
nedges++;
/*
* Insertion sort: swap this edge backwards in the array
* until it's in the right order.
*/
{
size_t j = nedges - 1;
while (j > 0 && coord_cmp(
coord_mul(edges[j-1].n, edges[j].d),
coord_mul(edges[j].n, edges[j-1].d)) > 0) {
struct Edge tmp = edges[j-1];
edges[j-1] = edges[j];
edges[j] = tmp;
j--;
}
}
}
}
for (i = 0; i < nedges; i++)
edges_out[i] = edges[i].edge;
return nedges;
}
static void raster_emit(struct genctx *gctx, const Spectre *spec,
Coord y, unsigned edge)
{
unsigned edges[14];
size_t nedges;
Coord yprev = coord_sub(y, coord_construct(2, 4));
if (find_crossings(gctx, spec, yprev, true, edges))
return; /* we've seen this on a previous raster_x pass */
if (edge != (unsigned)-1) {
nedges = find_crossings(gctx, spec, y, false, edges);
assert(nedges > 0);
if (edge != edges[0])
return; /* we've seen this before within the same raster_x pass */
}
callback(gctx, spec);
}
static void raster_x(struct genctx *gctx, SpectreContext *ctx,
const Spectre *start, Coord *yptr, Coord xlimit)
{
Spectre *curr, *new;
Coord y;
size_t i;
unsigned incoming_edge;
/*
* Find out if this Spectre intersects our current
* y-coordinate.
*/
for (i = 0; i < 14; i++)
if (coord_cmp(point_y(start->vertices[i]), *yptr) > 0)
break;
if (i == 14) {
/*
* No, this Spectre is still below the start line.
*/
return;
}
/*
* It does! Start an x iteration here, and increment y by 2 + 4
* sqrt(3), which is the smallest possible y-extent of any
* rotation of our starting Spectre.
*/
y = *yptr;
*yptr = coord_add(*yptr, coord_construct(2, 4));
curr = spectre_copy(start);
incoming_edge = -1;
while (true) {
unsigned edges[14];
size_t nedges;
raster_emit(gctx, curr, y, incoming_edge);
nedges = find_crossings(gctx, curr, y, true, edges);
assert(nedges > 0);
for (i = 0; i+1 < nedges; i++) {
new = spectre_adjacent(ctx, curr, edges[i], &incoming_edge);
raster_emit(gctx, new, y, incoming_edge);
spectre_free(new);
}
new = spectre_adjacent(ctx, curr, edges[nedges-1], &incoming_edge);
spectre_free(curr);
curr = new;
/*
* Find out whether this Spectre is entirely beyond the
* x-limit.
*/
for (i = 0; i < 14; i++)
if (coord_cmp(point_x(curr->vertices[i]), xlimit) < 0)
break;
if (i == 14) /* no vertex broke that loop */
break;
}
spectre_free(curr);
}
static void raster_y(struct genctx *gctx, SpectreContext *ctx,
const Spectre *start, Coord x, Coord ylimit,
Coord *yptr, Coord xlimit)
{
Spectre *curr, *new;
curr = spectre_copy(start);
while (true) {
unsigned edges[14];
size_t i, nedges;
raster_x(gctx, ctx, curr, yptr, xlimit);
reflect_spectre(curr);
nedges = find_crossings(gctx, curr, x, false, edges);
reflect_spectre(curr);
assert(nedges > 0);
for (i = 0; i+1 < nedges; i++) {
new = spectre_adjacent(ctx, curr, edges[i], NULL);
raster_x(gctx, ctx, new, yptr, xlimit);
spectre_free(new);
}
new = spectre_adjacent(ctx, curr, edges[nedges-1], NULL);
spectre_free(curr);
curr = new;
/*
* Find out whether this Spectre is entirely beyond the
* y-limit.
*/
for (i = 0; i < 14; i++)
if (coord_cmp(point_y(curr->vertices[i]), ylimit) < 0)
break;
if (i == 14) /* no vertex broke that loop */
break;
}
spectre_free(curr);
}
static void generate_raster(struct genctx *gctx)
{
SpectreContext ctx[1];
Spectre *start;
Coord y = coord_integer(-10);
spectrectx_init_random_with_four_colouring(ctx, gctx->rs);
start = spectre_initial(ctx);
/*
* Move the starting Spectre down and left a bit, so that edge
* effects causing a few Spectres to be missed on the initial
* passes won't affect the overall result.
*/
{
Point offset = {{ -5, 0, 0, -5 }};
size_t i;
for (i = 0; i < 14; i++)
start->vertices[i] = point_add(start->vertices[i], offset);
}
raster_y(gctx, ctx, start, coord_integer(-10), gctx->ymax, &y, gctx->xmax);
spectre_free(start);
spectrectx_cleanup(ctx);
}
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_BFS, TILING_RASTER, CHEAT, HEXES
} mode = TILING_RASTER;
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, "--bfs")) {
mode = TILING_BFS;
} 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_BFS:
case TILING_RASTER:
case CHEAT: {
struct genctx gctx[1];
bool close_output = false;
int xmin, xmax, ymin, ymax;
gctx_set_size(gctx, width, height, scale, (mode != TILING_RASTER),
&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_RASTER:
generate_raster(gctx);
break;
case TILING_BFS:
generate_bfs(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, true,
&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;
}
}
}