shithub: puzzles

ref: 0cfaa8fbd77ea39a6752e5df66dc6559a52585e4
dir: /grid.c/

View raw version
/*
 * (c) Lambros Lambrou 2008
 *
 * Code for working with general grids, which can be any planar graph
 * with faces, edges and vertices (dots).  Includes generators for a few
 * types of grid, including square, hexagonal, triangular and others.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include <float.h>
#include <limits.h>
#ifdef NO_TGMATH_H
#  include <math.h>
#else
#  include <tgmath.h>
#endif

#include "puzzles.h"
#include "tree234.h"
#include "grid.h"
#include "penrose-legacy.h"
#include "penrose.h"
#include "hat.h"
#include "spectre.h"

/* Debugging options */

/*
#define DEBUG_GRID
*/

/* ----------------------------------------------------------------------
 * Deallocate or dereference a grid
 */
void grid_free(grid *g)
{
    assert(g->refcount);

    g->refcount--;
    if (g->refcount == 0) {
        int i;
        for (i = 0; i < g->num_faces; i++) {
            sfree(g->faces[i]->dots);
            sfree(g->faces[i]->edges);
            sfree(g->faces[i]);
        }
        for (i = 0; i < g->num_dots; i++) {
            sfree(g->dots[i]->faces);
            sfree(g->dots[i]->edges);
            sfree(g->dots[i]);
        }
        for (i = 0; i < g->num_edges; i++) {
            sfree(g->edges[i]);
        }
        sfree(g->faces);
        sfree(g->edges);
        sfree(g->dots);
        sfree(g);
    }
}

/* Used by the other grid generators.  Create a brand new grid with nothing
 * initialised (all lists are NULL) */
static grid *grid_empty(void)
{
    grid *g = snew(grid);
    g->faces = NULL;
    g->edges = NULL;
    g->dots = NULL;
    g->num_faces = g->num_edges = g->num_dots = 0;
    g->size_faces = g->size_edges = g->size_dots = 0;
    g->refcount = 1;
    g->lowest_x = g->lowest_y = g->highest_x = g->highest_y = 0;
    return g;
}

/* Helper function to calculate perpendicular distance from
 * a point P to a line AB.  A and B mustn't be equal here.
 *
 * Well-known formula for area A of a triangle:
 *                             /  1   1   1 \
 * 2A = determinant of matrix  | px  ax  bx |
 *                             \ py  ay  by /
 *
 * Also well-known: 2A = base * height
 *                     = perpendicular distance * line-length.
 *
 * Combining gives: distance = determinant / line-length(a,b)
 */
static double point_line_distance(long px, long py,
                                  long ax, long ay,
                                  long bx, long by)
{
    long det = ax*by - bx*ay + bx*py - px*by + px*ay - ax*py;
    double len;
    det = max(det, -det);
    len = sqrt(SQ(ax - bx) + SQ(ay - by));
    return det / len;
}

/* Determine nearest edge to where the user clicked.
 * (x, y) is the clicked location, converted to grid coordinates.
 * Returns the nearest edge, or NULL if no edge is reasonably
 * near the position.
 *
 * Just judging edges by perpendicular distance is not quite right -
 * the edge might be "off to one side". So we insist that the triangle
 * with (x,y) has acute angles at the edge's dots.
 *
 *     edge1
 *  *---------*------
 *            |
 *            |      *(x,y)
 *      edge2 |
 *            |   edge2 is OK, but edge1 is not, even though
 *            |   edge1 is perpendicularly closer to (x,y)
 *            *
 *
 */
grid_edge *grid_nearest_edge(grid *g, int x, int y)
{
    grid_edge *best_edge;
    double best_distance = 0;
    int i;

    best_edge = NULL;

    for (i = 0; i < g->num_edges; i++) {
        grid_edge *e = g->edges[i];
        long e2; /* squared length of edge */
        long a2, b2; /* squared lengths of other sides */
        double dist;

        /* See if edge e is eligible - the triangle must have acute angles
         * at the edge's dots.
         * Pythagoras formula h^2 = a^2 + b^2 detects right-angles,
         * so detect acute angles by testing for h^2 < a^2 + b^2 */
        e2 = SQ((long)e->dot1->x - (long)e->dot2->x) + SQ((long)e->dot1->y - (long)e->dot2->y);
        a2 = SQ((long)e->dot1->x - (long)x) + SQ((long)e->dot1->y - (long)y);
        b2 = SQ((long)e->dot2->x - (long)x) + SQ((long)e->dot2->y - (long)y);
        if (a2 >= e2 + b2) continue;
        if (b2 >= e2 + a2) continue;
         
        /* e is eligible so far.  Now check the edge is reasonably close
         * to where the user clicked.  Don't want to toggle an edge if the
         * click was way off the grid.
         * There is room for experimentation here.  We could check the
         * perpendicular distance is within a certain fraction of the length
         * of the edge.  That amounts to testing a rectangular region around
         * the edge.
         * Alternatively, we could check that the angle at the point is obtuse.
         * That would amount to testing a circular region with the edge as
         * diameter. */
        dist = point_line_distance((long)x, (long)y,
                                   (long)e->dot1->x, (long)e->dot1->y,
                                   (long)e->dot2->x, (long)e->dot2->y);
        /* Is dist more than half edge length ? */
        if (4 * SQ(dist) > e2)
            continue;

        if (best_edge == NULL || dist < best_distance) {
            best_edge = e;
            best_distance = dist;
        }
    }
    return best_edge;
}

/* ----------------------------------------------------------------------
 * Grid generation
 */

#ifdef SVG_GRID

#define SVG_DOTS  1
#define SVG_EDGES 2
#define SVG_FACES 4

#define FACE_COLOUR "red"
#define EDGE_COLOUR "blue"
#define DOT_COLOUR "black"

static void grid_output_svg(FILE *fp, grid *g, int which)
{
    int i, j;

    fprintf(fp,"\
<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n\
<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 20010904//EN\"\n\
\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n\
\n\
<svg xmlns=\"http://www.w3.org/2000/svg\"\n\
xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n\n");

    if (which & SVG_FACES) {
        fprintf(fp, "<g>\n");
        for (i = 0; i < g->num_faces; i++) {
            grid_face *f = g->faces[i];
            fprintf(fp, "<polygon points=\"");
            for (j = 0; j < f->order; j++) {
                grid_dot *d = f->dots[j];
                fprintf(fp, "%s%d,%d", (j == 0) ? "" : " ",
                        d->x, d->y);
            }
            fprintf(fp, "\" style=\"fill: %s; fill-opacity: 0.2; stroke: %s\" />\n",
                    FACE_COLOUR, FACE_COLOUR);
        }
        fprintf(fp, "</g>\n");
    }
    if (which & SVG_EDGES) {
        fprintf(fp, "<g>\n");
        for (i = 0; i < g->num_edges; i++) {
            grid_edge *e = g->edges[i];
            grid_dot *d1 = e->dot1, *d2 = e->dot2;

            fprintf(fp, "<line x1=\"%d\" y1=\"%d\" x2=\"%d\" y2=\"%d\" "
                        "style=\"stroke: %s\" />\n",
                        d1->x, d1->y, d2->x, d2->y, EDGE_COLOUR);
        }
        fprintf(fp, "</g>\n");
    }

    if (which & SVG_DOTS) {
        fprintf(fp, "<g>\n");
        for (i = 0; i < g->num_dots; i++) {
            grid_dot *d = g->dots[i];
            fprintf(fp, "<ellipse cx=\"%d\" cy=\"%d\" rx=\"%d\" ry=\"%d\" fill=\"%s\" />",
                    d->x, d->y, g->tilesize/20, g->tilesize/20, DOT_COLOUR);
        }
        fprintf(fp, "</g>\n");
    }

    fprintf(fp, "</svg>\n");
}
#endif

#ifdef SVG_GRID
#include <errno.h>

static void grid_try_svg(grid *g, int which)
{
    char *svg = getenv("PUZZLES_SVG_GRID");
    if (svg) {
        FILE *svgf = fopen(svg, "w");
        if (svgf) {
            grid_output_svg(svgf, g, which);
            fclose(svgf);
        } else {
            fprintf(stderr, "Unable to open file `%s': %s", svg, strerror(errno));
        }
    }
}
#endif

/* Show the basic grid information, before doing grid_make_consistent */
static void grid_debug_basic(grid *g)
{
    /* TODO: Maybe we should generate an SVG image of the dots and lines
     * of the grid here, before grid_make_consistent.
     * Would help with debugging grid generation. */
#ifdef DEBUG_GRID
    int i;
    printf("--- Basic Grid Data ---\n");
    for (i = 0; i < g->num_dots; i++) {
        grid_dot *d = g->dots[i];
        printf("Dot %d at (%d,%d)\n", i, d->x, d->y);
    }
    for (i = 0; i < g->num_faces; i++) {
        grid_face *f = g->faces[i];
        printf("Face %d: dots[", i);
        int j;
        for (j = 0; j < f->order; j++) {
            grid_dot *d = f->dots[j];
            printf("%s%d", j ? "," : "", (int)(d->index));
        }
        printf("]\n");
    }
#endif
#ifdef SVG_GRID
    grid_try_svg(g, SVG_FACES);
#endif
}

/* Show the derived grid information, computed by grid_make_consistent */
static void grid_debug_derived(grid *g)
{
#ifdef DEBUG_GRID
    /* edges */
    int i;
    printf("--- Derived Grid Data ---\n");
    for (i = 0; i < g->num_edges; i++) {
        grid_edge *e = g->edges[i];
        printf("Edge %d: dots[%d,%d] faces[%d,%d]\n",
            i, (int)(e->dot1->index), (int)(e->dot2->index),
            e->face1 ? (int)(e->face1->index) : -1,
            e->face2 ? (int)(e->face2->index) : -1);
    }
    /* faces */
    for (i = 0; i < g->num_faces; i++) {
        grid_face *f = g->faces[i];
        int j;
        printf("Face %d: faces[", i);
        for (j = 0; j < f->order; j++) {
            grid_edge *e = f->edges[j];
            grid_face *f2 = (e->face1 == f) ? e->face2 : e->face1;
            printf("%s%d", j ? "," : "", f2 ? f2->index : -1);
        }
        printf("]\n");
    }
    /* dots */
    for (i = 0; i < g->num_dots; i++) {
        grid_dot *d = g->dots[i];
        int j;
        printf("Dot %d: dots[", i);
        for (j = 0; j < d->order; j++) {
            grid_edge *e = d->edges[j];
            grid_dot *d2 = (e->dot1 == d) ? e->dot2 : e->dot1;
            printf("%s%d", j ? "," : "", d2->index);
        }
        printf("] faces[");
        for (j = 0; j < d->order; j++) {
            grid_face *f = d->faces[j];
            printf("%s%d", j ? "," : "", f ? f->index : -1);
        }
        printf("]\n");
    }
#endif
#ifdef SVG_GRID
    grid_try_svg(g, SVG_DOTS | SVG_EDGES | SVG_FACES);
#endif
}

/* Helper function for building incomplete-edges list in
 * grid_make_consistent() */
static int grid_edge_bydots_cmpfn(void *v1, void *v2)
{
    grid_edge *a = v1;
    grid_edge *b = v2;
    grid_dot *da, *db;

    /* Edges are not "normalised" - the 2 dots could be stored in any order,
     * so we need to take this into account when comparing edges. */

    /* Compare first dots */
    da = (a->dot1 < a->dot2) ? a->dot1 : a->dot2;
    db = (b->dot1 < b->dot2) ? b->dot1 : b->dot2;
    if (da->index < db->index)
        return -1;
    if (da->index > db->index)
        return +1;
    /* Compare last dots */
    da = (a->dot1 < a->dot2) ? a->dot2 : a->dot1;
    db = (b->dot1 < b->dot2) ? b->dot2 : b->dot1;
    if (da->index < db->index)
        return -1;
    if (da->index > db->index)
        return +1;

    return 0;
}

/*
 * 'Vigorously trim' a grid, by which I mean deleting any isolated or
 * uninteresting faces. By which, in turn, I mean: ensure that the
 * grid is composed solely of faces adjacent to at least one
 * 'landlocked' dot (i.e. one not in contact with the infinite
 * exterior face), and that all those dots are in a single connected
 * component.
 *
 * This function operates on, and returns, a grid satisfying the
 * preconditions to grid_make_consistent() rather than the
 * postconditions. (So call it first.)
 */
static void grid_trim_vigorously(grid *g)
{
    int *dotpairs, *faces, *dots;
    DSF *dsf;
    int i, j, k, size, newfaces, newdots;

    /*
     * First construct a matrix in which each ordered pair of dots is
     * mapped to the index of the face in which those dots occur in
     * that order.
     */
    dotpairs = snewn(g->num_dots * g->num_dots, int);
    for (i = 0; i < g->num_dots; i++)
        for (j = 0; j < g->num_dots; j++)
            dotpairs[i*g->num_dots+j] = -1;
    for (i = 0; i < g->num_faces; i++) {
        grid_face *f = g->faces[i];
        int dot0 = f->dots[f->order-1]->index;
        for (j = 0; j < f->order; j++) {
            int dot1 = f->dots[j]->index;
            dotpairs[dot0 * g->num_dots + dot1] = i;
            dot0 = dot1;
        }
    }

    /*
     * Now we can identify landlocked dots: they're the ones all of
     * whose edges have a mirror-image counterpart in this matrix.
     */
    dots = snewn(g->num_dots, int);
    for (i = 0; i < g->num_dots; i++) {
        dots[i] = 1;
        for (j = 0; j < g->num_dots; j++) {
            if ((dotpairs[i*g->num_dots+j] >= 0) ^
                (dotpairs[j*g->num_dots+i] >= 0))
                dots[i] = 0;    /* non-duplicated edge: coastal dot */
        }
    }

    /*
     * Now identify connected pairs of landlocked dots, and form a dsf
     * unifying them.
     */
    dsf = dsf_new(g->num_dots);
    for (i = 0; i < g->num_dots; i++)
        for (j = 0; j < i; j++)
            if (dots[i] && dots[j] &&
                dotpairs[i*g->num_dots+j] >= 0 &&
                dotpairs[j*g->num_dots+i] >= 0)
                dsf_merge(dsf, i, j);

    /*
     * Now look for the largest component.
     */
    size = 0;
    j = -1;
    for (i = 0; i < g->num_dots; i++) {
        int newsize;
        if (dots[i] && dsf_canonify(dsf, i) == i &&
            (newsize = dsf_size(dsf, i)) > size) {
            j = i;
            size = newsize;
        }
    }

    /*
     * Work out which faces we're going to keep (precisely those with
     * at least one dot in the same connected component as j) and
     * which dots (those required by any face we're keeping).
     *
     * At this point we reuse the 'dots' array to indicate the dots
     * we're keeping, rather than the ones that are landlocked.
     */
    faces = snewn(g->num_faces, int);
    for (i = 0; i < g->num_faces; i++)
        faces[i] = 0;
    for (i = 0; i < g->num_dots; i++)
        dots[i] = 0;
    for (i = 0; i < g->num_faces; i++) {
        grid_face *f = g->faces[i];
        bool keep = false;
        for (k = 0; k < f->order; k++)
            if (dsf_canonify(dsf, f->dots[k]->index) == j)
                keep = true;
        if (keep) {
            faces[i] = 1;
            for (k = 0; k < f->order; k++)
                dots[f->dots[k]->index] = 1;
        }
    }

    /*
     * Compact the faces array, rewriting the faces' indices and
     * freeing the unwanted ones.
     */
    for (i = newfaces = 0; i < g->num_faces; i++) {
        grid_face *f = g->faces[i];
        if (faces[i]) {
            f->index = newfaces++;
            g->faces[f->index] = f;
        } else {
            sfree(f->dots);
            sfree(f);
        }
    }
    g->num_faces = newfaces;

    /*
     * Compact the dots array, similarly.
     */
    for (i = newdots = 0; i < g->num_dots; i++) {
        grid_dot *d = g->dots[i];
        if (dots[i]) {
            d->index = newdots++;
            g->dots[d->index] = d;
        } else {
            sfree(d->edges);
            sfree(d->faces);
            sfree(d);
        }
    }
    g->num_dots = newdots;

    sfree(dotpairs);
    dsf_free(dsf);
    sfree(dots);
    sfree(faces);
}

/* Input: grid has its dots and faces initialised:
 * - dots have (optionally) x and y coordinates, but no edges or faces
 * (pointers are NULL).
 * - edges not initialised at all
 * - faces initialised and know which dots they have (but no edges yet).  The
 * dots around each face are assumed to be clockwise.
 *
 * Output: grid is complete and valid with all relationships defined.
 */
static void grid_make_consistent(grid *g)
{
    int i;
    tree234 *incomplete_edges;

    grid_debug_basic(g);

    /* ====== Stage 1 ======
     * Generate edges
     */

    /* Iterate over faces, and over each face's dots, generating edges as we
     * go.  As we find each new edge, we can immediately fill in the edge's
     * dots, but only one of the edge's faces.  Later on in the iteration, we
     * will find the same edge again (unless it's on the border), but we will
     * know the other face.
     * For efficiency, maintain a list of the incomplete edges, sorted by
     * their dots. */
    incomplete_edges = newtree234(grid_edge_bydots_cmpfn);
    for (i = 0; i < g->num_faces; i++) {
        grid_face *f = g->faces[i];
        int j;
        for (j = 0; j < f->order; j++) {
            grid_edge e; /* fake edge for searching */
            grid_edge *edge_found;
            int j2 = j + 1;
            if (j2 == f->order)
                j2 = 0;
            e.dot1 = f->dots[j];
            e.dot2 = f->dots[j2];
            /* Use del234 instead of find234, because we always want to
             * remove the edge if found */
            edge_found = del234(incomplete_edges, &e);
            if (edge_found) {
                /* This edge already added, so fill out missing face.
                 * Edge is already removed from incomplete_edges. */
                edge_found->face2 = f;
            } else {
                grid_edge *new_edge = snew(grid_edge);
                new_edge->dot1 = e.dot1;
                new_edge->dot2 = e.dot2;
                new_edge->face1 = f;
                new_edge->face2 = NULL; /* potentially infinite face */
                add234(incomplete_edges, new_edge);

                /* And add it to g->edges. */
                if (g->num_edges >= g->size_edges) {
                    int increment = g->num_edges / 4 + 128;
                    g->size_edges = (increment < INT_MAX - g->num_edges ?
                                     g->num_edges + increment : INT_MAX);
                    g->edges = sresize(g->edges, g->size_edges, grid_edge *);
                }
                assert(g->num_edges < INT_MAX);
                new_edge->index = g->num_edges++;
                g->edges[new_edge->index] = new_edge;
            }
        }
    }
    freetree234(incomplete_edges);

    /* ====== Stage 2 ======
     * For each face, build its edge list.
     */

    /* Allocate space for each edge list.  Can do this, because each face's
     * edge-list is the same size as its dot-list. */
    for (i = 0; i < g->num_faces; i++) {
        grid_face *f = g->faces[i];
        int j;
        f->edges = snewn(f->order, grid_edge*);
        /* Preload with NULLs, to help detect potential bugs. */
        for (j = 0; j < f->order; j++)
            f->edges[j] = NULL;
    }
    
    /* Iterate over each edge, and over both its faces.  Add this edge to
     * the face's edge-list, after finding where it should go in the
     * sequence. */
    for (i = 0; i < g->num_edges; i++) {
        grid_edge *e = g->edges[i];
        int j;
        for (j = 0; j < 2; j++) {
            grid_face *f = j ? e->face2 : e->face1;
            int k, k2;
            if (f == NULL) continue;
            /* Find one of the dots around the face */
            for (k = 0; k < f->order; k++) {
                if (f->dots[k] == e->dot1)
                    break; /* found dot1 */
            }
            assert(k != f->order); /* Must find the dot around this face */

            /* Labelling scheme: as we walk clockwise around the face,
             * starting at dot0 (f->dots[0]), we hit:
             * (dot0), edge0, dot1, edge1, dot2,...
             *
             *     0
             *  0-----1
             *        |
             *        |1
             *        |
             *  3-----2
             *     2
             *
             * Therefore, edgeK joins dotK and dot{K+1}
             */
            
            /* Around this face, either the next dot or the previous dot
             * must be e->dot2.  Otherwise the edge is wrong. */
            k2 = k + 1;
            if (k2 == f->order)
                k2 = 0;
            if (f->dots[k2] == e->dot2) {
                /* dot1(k) and dot2(k2) go clockwise around this face, so add
                 * this edge at position k (see diagram). */
                assert(f->edges[k] == NULL);
                f->edges[k] = e;
                continue;
            }
            /* Try previous dot */
            k2 = k - 1;
            if (k2 == -1)
                k2 = f->order - 1;
            if (f->dots[k2] == e->dot2) {
                /* dot1(k) and dot2(k2) go anticlockwise around this face. */
                assert(f->edges[k2] == NULL);
                f->edges[k2] = e;
                continue;
            }
            assert(!"Grid broken: bad edge-face relationship");
        }
    }

    /* ====== Stage 3 ======
     * For each dot, build its edge-list and face-list.
     */

    /* We don't know how many edges/faces go around each dot, so we can't
     * allocate the right space for these lists.  Pre-compute the sizes by
     * iterating over each edge and recording a tally against each dot. */
    for (i = 0; i < g->num_dots; i++) {
        g->dots[i]->order = 0;
    }
    for (i = 0; i < g->num_edges; i++) {
        grid_edge *e = g->edges[i];
        ++(e->dot1->order);
        ++(e->dot2->order);
    }
    /* Now we have the sizes, pre-allocate the edge and face lists. */
    for (i = 0; i < g->num_dots; i++) {
        grid_dot *d = g->dots[i];
        int j;
        assert(d->order >= 2); /* sanity check */
        d->edges = snewn(d->order, grid_edge*);
        d->faces = snewn(d->order, grid_face*);
        for (j = 0; j < d->order; j++) {
            d->edges[j] = NULL;
            d->faces[j] = NULL;
        }
    }
    /* For each dot, need to find a face that touches it, so we can seed
     * the edge-face-edge-face process around each dot. */
    for (i = 0; i < g->num_faces; i++) {
        grid_face *f = g->faces[i];
        int j;
        for (j = 0; j < f->order; j++) {
            grid_dot *d = f->dots[j];
            d->faces[0] = f;
        }
    }
    /* Each dot now has a face in its first slot.  Generate the remaining
     * faces and edges around the dot, by searching both clockwise and
     * anticlockwise from the first face.  Need to do both directions,
     * because of the possibility of hitting the infinite face, which
     * blocks progress.  But there's only one such face, so we will
     * succeed in finding every edge and face this way. */
    for (i = 0; i < g->num_dots; i++) {
        grid_dot *d = g->dots[i];
        int current_face1 = 0; /* ascends clockwise */
        int current_face2 = 0; /* descends anticlockwise */
        
        /* Labelling scheme: as we walk clockwise around the dot, starting
         * at face0 (d->faces[0]), we hit:
         * (face0), edge0, face1, edge1, face2,...
         *
         *       0
         *       |
         *    0  |  1
         *       |
         *  -----d-----1
         *       |
         *       |  2
         *       |
         *       2
         *
         * So, for example, face1 should be joined to edge0 and edge1,
         * and those edges should appear in an anticlockwise sense around
         * that face (see diagram). */
 
        /* clockwise search */
        while (true) {
            grid_face *f = d->faces[current_face1];
            grid_edge *e;
            int j;
            assert(f != NULL);
            /* find dot around this face */
            for (j = 0; j < f->order; j++) {
                if (f->dots[j] == d)
                    break;
            }
            assert(j != f->order); /* must find dot */
            
            /* Around f, required edge is anticlockwise from the dot.  See
             * the other labelling scheme higher up, for why we subtract 1
             * from j. */
            j--;
            if (j == -1)
                j = f->order - 1;
            e = f->edges[j];
            d->edges[current_face1] = e; /* set edge */
            current_face1++;
            if (current_face1 == d->order)
                break;
            else {
                /* set face */
                d->faces[current_face1] =
                    (e->face1 == f) ? e->face2 : e->face1;
                if (d->faces[current_face1] == NULL)
                    break; /* cannot progress beyond infinite face */
            }
        }
        /* If the clockwise search made it all the way round, don't need to
         * bother with the anticlockwise search. */
        if (current_face1 == d->order)
            continue; /* this dot is complete, move on to next dot */
        
        /* anticlockwise search */
        while (true) {
            grid_face *f = d->faces[current_face2];
            grid_edge *e;
            int j;
            assert(f != NULL);
            /* find dot around this face */
            for (j = 0; j < f->order; j++) {
                if (f->dots[j] == d)
                    break;
            }
            assert(j != f->order); /* must find dot */
            
            /* Around f, required edge is clockwise from the dot. */
            e = f->edges[j];
            
            current_face2--;
            if (current_face2 == -1)
                current_face2 = d->order - 1;
            d->edges[current_face2] = e; /* set edge */

            /* set face */
            if (current_face2 == current_face1)
                break;
            d->faces[current_face2] =
                    (e->face1 == f) ? e->face2 : e->face1;
            /* There's only 1 infinite face, so we must get all the way
             * to current_face1 before we hit it. */
            assert(d->faces[current_face2]);
        }
    }

    /* ====== Stage 4 ======
     * Compute other grid settings
     */

    /* Bounding rectangle */
    for (i = 0; i < g->num_dots; i++) {
        grid_dot *d = g->dots[i];
        if (i == 0) {
            g->lowest_x = g->highest_x = d->x;
            g->lowest_y = g->highest_y = d->y;
        } else {
            g->lowest_x = min(g->lowest_x, d->x);
            g->highest_x = max(g->highest_x, d->x);
            g->lowest_y = min(g->lowest_y, d->y);
            g->highest_y = max(g->highest_y, d->y);
        }
    }

    grid_debug_derived(g);
}

/* Helpers for making grid-generation easier.  These functions are only
 * intended for use during grid generation. */

/* Comparison function for the (tree234) sorted dot list */
static int grid_point_cmp_fn(void *v1, void *v2)
{
    grid_dot *p1 = v1;
    grid_dot *p2 = v2;
    if (p1->y != p2->y)
        return p2->y - p1->y;
    else
        return p2->x - p1->x;
}
/* Add a new face to the grid, with its dot list allocated. */
static void grid_face_add_new(grid *g, int face_size)
{
    int i;
    grid_face *new_face = snew(grid_face);
    assert(g->num_faces < INT_MAX);
    if (g->num_faces >= g->size_faces) {
        int increment = g->num_faces / 4 + 128;
        g->size_faces = (increment < INT_MAX - g->num_faces ?
                         g->num_faces + increment : INT_MAX);
        g->faces = sresize(g->faces, g->size_faces, grid_face *);
    }
    new_face->index = g->num_faces++;
    g->faces[new_face->index] = new_face;

    new_face->order = face_size;
    new_face->dots = snewn(face_size, grid_dot*);
    for (i = 0; i < face_size; i++)
        new_face->dots[i] = NULL;
    new_face->edges = NULL;
    new_face->has_incentre = false;
}
static grid_dot *grid_dot_add_new(grid *g, int x, int y)
{
    grid_dot *new_dot = snew(grid_dot);
    if (g->num_dots >= g->size_dots) {
        int increment = g->num_dots / 4 + 128;
        g->size_dots = (increment < INT_MAX - g->num_dots ?
                         g->num_dots + increment : INT_MAX);
        g->dots = sresize(g->dots, g->size_dots, grid_dot *);
    }
    assert(g->num_dots < INT_MAX);
    new_dot->index = g->num_dots++;
    g->dots[new_dot->index] = new_dot;

    new_dot->order = 0;
    new_dot->edges = NULL;
    new_dot->faces = NULL;
    new_dot->x = x;
    new_dot->y = y;

    return new_dot;
}
/* Retrieve a dot with these (x,y) coordinates.  Either return an existing dot
 * in the dot_list, or add a new dot to the grid (and the dot_list) and
 * return that. */
static grid_dot *grid_get_dot(grid *g, tree234 *dot_list, int x, int y)
{
    grid_dot test, *ret;

    test.order = 0;
    test.edges = NULL;
    test.faces = NULL;
    test.x = x;
    test.y = y;
    ret = find234(dot_list, &test, NULL);
    if (ret)
        return ret;

    ret = grid_dot_add_new(g, x, y);
    add234(dot_list, ret);
    return ret;
}

/* Sets the last face of the grid to include this dot, at this position
 * around the face. Assumes num_faces is at least 1 (a new face has
 * previously been added, with the required number of dots allocated) */
static void grid_face_set_dot(grid *g, grid_dot *d, int position)
{
    grid_face *last_face = g->faces[g->num_faces - 1];
    last_face->dots[position] = d;
}

/*
 * Helper routines for grid_find_incentre.
 */
static bool solve_2x2_matrix(double mx[4], double vin[2], double vout[2])
{
    double inv[4];
    double det;
    det = (mx[0]*mx[3] - mx[1]*mx[2]);
    if (det == 0)
        return false;

    inv[0] = mx[3] / det;
    inv[1] = -mx[1] / det;
    inv[2] = -mx[2] / det;
    inv[3] = mx[0] / det;

    vout[0] = inv[0]*vin[0] + inv[1]*vin[1];
    vout[1] = inv[2]*vin[0] + inv[3]*vin[1];

    return true;
}
static bool solve_3x3_matrix(double mx[9], double vin[3], double vout[3])
{
    double inv[9];
    double det;

    det = (mx[0]*mx[4]*mx[8] + mx[1]*mx[5]*mx[6] + mx[2]*mx[3]*mx[7] -
           mx[0]*mx[5]*mx[7] - mx[1]*mx[3]*mx[8] - mx[2]*mx[4]*mx[6]);
    if (det == 0)
        return false;

    inv[0] = (mx[4]*mx[8] - mx[5]*mx[7]) / det;
    inv[1] = (mx[2]*mx[7] - mx[1]*mx[8]) / det;
    inv[2] = (mx[1]*mx[5] - mx[2]*mx[4]) / det;
    inv[3] = (mx[5]*mx[6] - mx[3]*mx[8]) / det;
    inv[4] = (mx[0]*mx[8] - mx[2]*mx[6]) / det;
    inv[5] = (mx[2]*mx[3] - mx[0]*mx[5]) / det;
    inv[6] = (mx[3]*mx[7] - mx[4]*mx[6]) / det;
    inv[7] = (mx[1]*mx[6] - mx[0]*mx[7]) / det;
    inv[8] = (mx[0]*mx[4] - mx[1]*mx[3]) / det;

    vout[0] = inv[0]*vin[0] + inv[1]*vin[1] + inv[2]*vin[2];
    vout[1] = inv[3]*vin[0] + inv[4]*vin[1] + inv[5]*vin[2];
    vout[2] = inv[6]*vin[0] + inv[7]*vin[1] + inv[8]*vin[2];

    return true;
}

void grid_find_incentre(grid_face *f)
{
    double xbest, ybest, bestdist;
    int i, j, k, m;
    grid_dot *edgedot1[3], *edgedot2[3];
    grid_dot *dots[3];
    int nedges, ndots;

    if (f->has_incentre)
        return;

    /*
     * Find the point in the polygon with the maximum distance to any
     * edge or corner.
     *
     * Such a point must exist which is in contact with at least three
     * edges and/or vertices. (Proof: if it's only in contact with two
     * edges and/or vertices, it can't even be at a _local_ maximum -
     * any such circle can always be expanded in some direction.) So
     * we iterate through all 3-subsets of the combined set of edges
     * and vertices; for each subset we generate one or two candidate
     * points that might be the incentre, and then we vet each one to
     * see if it's inside the polygon and what its maximum radius is.
     *
     * (There's one case which this algorithm will get noticeably
     * wrong, and that's when a continuum of equally good answers
     * exists due to parallel edges. Consider a long thin rectangle,
     * for instance, or a parallelogram. This algorithm will pick a
     * point near one end, and choose the end arbitrarily; obviously a
     * nicer point to choose would be in the centre. To fix this I
     * would have to introduce a special-case system which detected
     * parallel edges in advance, set aside all candidate points
     * generated using both edges in a parallel pair, and generated
     * some additional candidate points half way between them. Also,
     * of course, I'd have to cope with rounding error making such a
     * point look worse than one of its endpoints. So I haven't done
     * this for the moment, and will cross it if necessary when I come
     * to it.)
     *
     * We don't actually iterate literally over _edges_, in the sense
     * of grid_edge structures. Instead, we fill in edgedot1[] and
     * edgedot2[] with a pair of dots adjacent in the face's list of
     * vertices. This ensures that we get the edges in consistent
     * orientation, which we could not do from the grid structure
     * alone. (A moment's consideration of an order-3 vertex should
     * make it clear that if a notional arrow was written on each
     * edge, _at least one_ of the three faces bordering that vertex
     * would have to have the two arrows tip-to-tip or tail-to-tail
     * rather than tip-to-tail.)
     */
    nedges = ndots = 0;
    bestdist = 0;
    xbest = ybest = 0;

    for (i = 0; i+2 < 2*f->order; i++) {
        if (i < f->order) {
            edgedot1[nedges] = f->dots[i];
            edgedot2[nedges++] = f->dots[(i+1)%f->order];
        } else
            dots[ndots++] = f->dots[i - f->order];

        for (j = i+1; j+1 < 2*f->order; j++) {
            if (j < f->order) {
                edgedot1[nedges] = f->dots[j];
                edgedot2[nedges++] = f->dots[(j+1)%f->order];
            } else
                dots[ndots++] = f->dots[j - f->order];

            for (k = j+1; k < 2*f->order; k++) {
                double cx[2], cy[2];   /* candidate positions */
                int cn = 0;            /* number of candidates */

                if (k < f->order) {
                    edgedot1[nedges] = f->dots[k];
                    edgedot2[nedges++] = f->dots[(k+1)%f->order];
                } else
                    dots[ndots++] = f->dots[k - f->order];

                /*
                 * Find a point, or pair of points, equidistant from
                 * all the specified edges and/or vertices.
                 */
                if (nedges == 3) {
                    /*
                     * Three edges. This is a linear matrix equation:
                     * each row of the matrix represents the fact that
                     * the point (x,y) we seek is at distance r from
                     * that edge, and we solve three of those
                     * simultaneously to obtain x,y,r. (We ignore r.)
                     */
                    double matrix[9], vector[3], vector2[3];
                    int m;

                    for (m = 0; m < 3; m++) {
                        int x1 = edgedot1[m]->x, x2 = edgedot2[m]->x;
                        int y1 = edgedot1[m]->y, y2 = edgedot2[m]->y;
                        int dx = x2-x1, dy = y2-y1;

                        /*
                         * ((x,y) - (x1,y1)) . (dy,-dx) = r |(dx,dy)|
                         *
                         * => x dy - y dx - r |(dx,dy)| = (x1 dy - y1 dx)
                         */
                        matrix[3*m+0] = dy;
                        matrix[3*m+1] = -dx;
                        matrix[3*m+2] = -sqrt((double)dx*dx+(double)dy*dy);
                        vector[m] = (double)x1*dy - (double)y1*dx;
                    }

                    if (solve_3x3_matrix(matrix, vector, vector2)) {
                        cx[cn] = vector2[0];
                        cy[cn] = vector2[1];
                        cn++;
                    }
                } else if (nedges == 2) {
                    /*
                     * Two edges and a dot. This will end up in a
                     * quadratic equation.
                     *
                     * First, look at the two edges. Having our point
                     * be some distance r from both of them gives rise
                     * to a pair of linear equations in x,y,r of the
                     * form
                     *
                     *   (x-x1) dy - (y-y1) dx = r sqrt(dx^2+dy^2)
                     *
                     * We eliminate r between those equations to give
                     * us a single linear equation in x,y describing
                     * the locus of points equidistant from both lines
                     * - i.e. the angle bisector. 
                     *
                     * We then choose one of x,y to be a parameter t,
                     * and derive linear formulae for x,y,r in terms
                     * of t. This enables us to write down the
                     * circular equation (x-xd)^2+(y-yd)^2=r^2 as a
                     * quadratic in t; solving that and substituting
                     * in for x,y gives us two candidate points.
                     */
                    double eqs[2][4];  /* a,b,c,d : ax+by+cr=d */
                    double eq[3];      /* a,b,c: ax+by=c */
                    double xt[2], yt[2], rt[2]; /* a,b: {x,y,r}=at+b */
                    double q[3];                /* a,b,c: at^2+bt+c=0 */
                    double disc;

                    /* Find equations of the two input lines. */
                    for (m = 0; m < 2; m++) {
                        int x1 = edgedot1[m]->x, x2 = edgedot2[m]->x;
                        int y1 = edgedot1[m]->y, y2 = edgedot2[m]->y;
                        int dx = x2-x1, dy = y2-y1;

                        eqs[m][0] = dy;
                        eqs[m][1] = -dx;
                        eqs[m][2] = -sqrt(dx*dx+dy*dy);
                        eqs[m][3] = x1*dy - y1*dx;
                    }

                    /* Derive the angle bisector by eliminating r. */
                    eq[0] = eqs[0][0]*eqs[1][2] - eqs[1][0]*eqs[0][2];
                    eq[1] = eqs[0][1]*eqs[1][2] - eqs[1][1]*eqs[0][2];
                    eq[2] = eqs[0][3]*eqs[1][2] - eqs[1][3]*eqs[0][2];

                    /* Parametrise x and y in terms of some t. */
                    if (fabs(eq[0]) < fabs(eq[1])) {
                        /* Parameter is x. */
                        xt[0] = 1; xt[1] = 0;
                        yt[0] = -eq[0]/eq[1]; yt[1] = eq[2]/eq[1];
                    } else {
                        /* Parameter is y. */
                        yt[0] = 1; yt[1] = 0;
                        xt[0] = -eq[1]/eq[0]; xt[1] = eq[2]/eq[0];
                    }

                    /* Find a linear representation of r using eqs[0]. */
                    rt[0] = -(eqs[0][0]*xt[0] + eqs[0][1]*yt[0])/eqs[0][2];
                    rt[1] = (eqs[0][3] - eqs[0][0]*xt[1] -
                             eqs[0][1]*yt[1])/eqs[0][2];

                    /* Construct the quadratic equation. */
                    q[0] = -rt[0]*rt[0];
                    q[1] = -2*rt[0]*rt[1];
                    q[2] = -rt[1]*rt[1];
                    q[0] += xt[0]*xt[0];
                    q[1] += 2*xt[0]*(xt[1]-dots[0]->x);
                    q[2] += (xt[1]-dots[0]->x)*(xt[1]-dots[0]->x);
                    q[0] += yt[0]*yt[0];
                    q[1] += 2*yt[0]*(yt[1]-dots[0]->y);
                    q[2] += (yt[1]-dots[0]->y)*(yt[1]-dots[0]->y);

                    /* And solve it. */
                    disc = q[1]*q[1] - 4*q[0]*q[2];
                    if (disc >= 0) {
                        double t;

                        disc = sqrt(disc);

                        t = (-q[1] + disc) / (2*q[0]);
                        cx[cn] = xt[0]*t + xt[1];
                        cy[cn] = yt[0]*t + yt[1];
                        cn++;

                        t = (-q[1] - disc) / (2*q[0]);
                        cx[cn] = xt[0]*t + xt[1];
                        cy[cn] = yt[0]*t + yt[1];
                        cn++;
                    }
                } else if (nedges == 1) {
                    /*
                     * Two dots and an edge. This one's another
                     * quadratic equation.
                     *
                     * The point we want must lie on the perpendicular
                     * bisector of the two dots; that much is obvious.
                     * So we can construct a parametrisation of that
                     * bisecting line, giving linear formulae for x,y
                     * in terms of t. We can also express the distance
                     * from the edge as such a linear formula.
                     *
                     * Then we set that equal to the radius of the
                     * circle passing through the two points, which is
                     * a Pythagoras exercise; that gives rise to a
                     * quadratic in t, which we solve.
                     */
                    double xt[2], yt[2], rt[2]; /* a,b: {x,y,r}=at+b */
                    double q[3];                /* a,b,c: at^2+bt+c=0 */
                    double disc;
                    double halfsep;

                    /* Find parametric formulae for x,y. */
                    {
                        int x1 = dots[0]->x, x2 = dots[1]->x;
                        int y1 = dots[0]->y, y2 = dots[1]->y;
                        int dx = x2-x1, dy = y2-y1;
                        double d = sqrt((double)dx*dx + (double)dy*dy);

                        xt[1] = (x1+x2)/2.0;
                        yt[1] = (y1+y2)/2.0;
                        /* It's convenient if we have t at standard scale. */
                        xt[0] = -dy/d;
                        yt[0] = dx/d;

                        /* Also note down half the separation between
                         * the dots, for use in computing the circle radius. */
                        halfsep = 0.5*d;
                    }

                    /* Find a parametric formula for r. */
                    {
                        int x1 = edgedot1[0]->x, x2 = edgedot2[0]->x;
                        int y1 = edgedot1[0]->y, y2 = edgedot2[0]->y;
                        int dx = x2-x1, dy = y2-y1;
                        double d = sqrt((double)dx*dx + (double)dy*dy);
                        rt[0] = (xt[0]*dy - yt[0]*dx) / d;
                        rt[1] = ((xt[1]-x1)*dy - (yt[1]-y1)*dx) / d;
                    }

                    /* Construct the quadratic equation. */
                    q[0] = rt[0]*rt[0];
                    q[1] = 2*rt[0]*rt[1];
                    q[2] = rt[1]*rt[1];
                    q[0] -= 1;
                    q[2] -= halfsep*halfsep;

                    /* And solve it. */
                    disc = q[1]*q[1] - 4*q[0]*q[2];
                    if (disc >= 0) {
                        double t;

                        disc = sqrt(disc);

                        t = (-q[1] + disc) / (2*q[0]);
                        cx[cn] = xt[0]*t + xt[1];
                        cy[cn] = yt[0]*t + yt[1];
                        cn++;

                        t = (-q[1] - disc) / (2*q[0]);
                        cx[cn] = xt[0]*t + xt[1];
                        cy[cn] = yt[0]*t + yt[1];
                        cn++;
                    }
                } else if (nedges == 0) {
                    /*
                     * Three dots. This is another linear matrix
                     * equation, this time with each row of the matrix
                     * representing the perpendicular bisector between
                     * two of the points. Of course we only need two
                     * such lines to find their intersection, so we
                     * need only solve a 2x2 matrix equation.
                     */

                    double matrix[4], vector[2], vector2[2];
                    int m;

                    for (m = 0; m < 2; m++) {
                        int x1 = dots[m]->x, x2 = dots[m+1]->x;
                        int y1 = dots[m]->y, y2 = dots[m+1]->y;
                        int dx = x2-x1, dy = y2-y1;

                        /*
                         * ((x,y) - (x1,y1)) . (dx,dy) = 1/2 |(dx,dy)|^2
                         *
                         * => 2x dx + 2y dy = dx^2+dy^2 + (2 x1 dx + 2 y1 dy)
                         */
                        matrix[2*m+0] = 2*dx;
                        matrix[2*m+1] = 2*dy;
                        vector[m] = ((double)dx*dx + (double)dy*dy +
                                     2.0*x1*dx + 2.0*y1*dy);
                    }

                    if (solve_2x2_matrix(matrix, vector, vector2)) {
                        cx[cn] = vector2[0];
                        cy[cn] = vector2[1];
                        cn++;
                    }
                }

                /*
                 * Now go through our candidate points and see if any
                 * of them are better than what we've got so far.
                 */
                for (m = 0; m < cn; m++) {
                    double x = cx[m], y = cy[m];

                    /*
                     * First, disqualify the point if it's not inside
                     * the polygon, which we work out by counting the
                     * edges to the right of the point. (For
                     * tiebreaking purposes when edges start or end on
                     * our y-coordinate or go right through it, we
                     * consider our point to be offset by a small
                     * _positive_ epsilon in both the x- and
                     * y-direction.)
                     */
                    int e;
                    bool in = false;
                    for (e = 0; e < f->order; e++) {
                        int xs = f->edges[e]->dot1->x;
                        int xe = f->edges[e]->dot2->x;
                        int ys = f->edges[e]->dot1->y;
                        int ye = f->edges[e]->dot2->y;
                        if ((y >= ys && y < ye) || (y >= ye && y < ys)) {
                            /*
                             * The line goes past our y-position. Now we need
                             * to know if its x-coordinate when it does so is
                             * to our right.
                             *
                             * The x-coordinate in question is mathematically
                             * (y - ys) * (xe - xs) / (ye - ys), and we want
                             * to know whether (x - xs) >= that. Of course we
                             * avoid the division, so we can work in integers;
                             * to do this we must multiply both sides of the
                             * inequality by ye - ys, which means we must
                             * first check that's not negative.
                             */
                            int num = xe - xs, denom = ye - ys;
                            if (denom < 0) {
                                num = -num;
                                denom = -denom;
                            }
                            if ((x - xs) * denom >= (y - ys) * num)
                                in = !in;
                        }
                    }

                    if (in) {
#ifdef HUGE_VAL
                        double mindist = HUGE_VAL;
#else
#ifdef DBL_MAX
                        double mindist = DBL_MAX;
#else
#error No way to get maximum floating-point number.
#endif
#endif
                        int e, d;

                        /*
                         * This point is inside the polygon, so now we check
                         * its minimum distance to every edge and corner.
                         * First the corners ...
                         */
                        for (d = 0; d < f->order; d++) {
                            int xp = f->dots[d]->x;
                            int yp = f->dots[d]->y;
                            double dx = x - xp, dy = y - yp;
                            double dist = dx*dx + dy*dy;
                            if (mindist > dist)
                                mindist = dist;
                        }

                        /*
                         * ... and now also check the perpendicular distance
                         * to every edge, if the perpendicular lies between
                         * the edge's endpoints.
                         */
                        for (e = 0; e < f->order; e++) {
                            int xs = f->edges[e]->dot1->x;
                            int xe = f->edges[e]->dot2->x;
                            int ys = f->edges[e]->dot1->y;
                            int ye = f->edges[e]->dot2->y;

                            /*
                             * If s and e are our endpoints, and p our
                             * candidate circle centre, the foot of a
                             * perpendicular from p to the line se lies
                             * between s and e if and only if (p-s).(e-s) lies
                             * strictly between 0 and (e-s).(e-s).
                             */
                            int edx = xe - xs, edy = ye - ys;
                            double pdx = x - xs, pdy = y - ys;
                            double pde = pdx * edx + pdy * edy;
                            long ede = (long)edx * edx + (long)edy * edy;
                            if (0 < pde && pde < ede) {
                                /*
                                 * Yes, the nearest point on this edge is
                                 * closer than either endpoint, so we must
                                 * take it into account by measuring the
                                 * perpendicular distance to the edge and
                                 * checking its square against mindist.
                                 */

                                double pdre = pdx * edy - pdy * edx;
                                double sqlen = pdre * pdre / ede;

                                if (mindist > sqlen)
                                    mindist = sqlen;
                            }
                        }

                        /*
                         * Right. Now we know the biggest circle around this
                         * point, so we can check it against bestdist.
                         */
                        if (bestdist < mindist) {
                            bestdist = mindist;
                            xbest = x;
                            ybest = y;
                        }
                    }
                }

                if (k < f->order)
                    nedges--;
                else
                    ndots--;
            }
            if (j < f->order)
                nedges--;
            else
                ndots--;
        }
        if (i < f->order)
            nedges--;
        else
            ndots--;
    }

    assert(bestdist > 0);

    f->has_incentre = true;
    f->ix = xbest + 0.5;               /* round to nearest */
    f->iy = ybest + 0.5;
}

/* ------ Generate various types of grid ------ */

/* General method is to generate faces, by calculating their dot coordinates.
 * As new faces are added, we keep track of all the dots so we can tell when
 * a new face reuses an existing dot.  For example, two squares touching at an
 * edge would generate six unique dots: four dots from the first face, then
 * two additional dots for the second face, because we detect the other two
 * dots have already been taken up.  This list is stored in a tree234
 * called "points".  No extra memory-allocation needed here - we store the
 * actual grid_dot* pointers, which all point into the g->dots list.
 * For this reason, we have to calculate coordinates in such a way as to
 * eliminate any rounding errors, so we can detect when a dot on one
 * face precisely lands on a dot of a different face.  No floating-point
 * arithmetic here!
 */

#define SQUARE_TILESIZE 20

static const char *grid_validate_params_square(int width, int height)
{
    if (width > INT_MAX / SQUARE_TILESIZE ||  /* xextent */
        height > INT_MAX / SQUARE_TILESIZE || /* yextent */
        width + 1 > INT_MAX / (height + 1))   /* max_dots */
        return "Grid size must not be unreasonably large";
    return NULL;
}

static void grid_size_square(int width, int height,
                      int *tilesize, int *xextent, int *yextent)
{
    int a = SQUARE_TILESIZE;

    *tilesize = a;
    *xextent = width * a;
    *yextent = height * a;
}

static grid *grid_new_square(int width, int height, const char *desc)
{
    int x, y;
    /* Side length */
    int a = SQUARE_TILESIZE;

    tree234 *points;

    grid *g = grid_empty();
    g->tilesize = a;

    points = newtree234(grid_point_cmp_fn);

    /* generate square faces */
    for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
            grid_dot *d;
            /* face position */
            int px = a * x;
            int py = a * y;

            grid_face_add_new(g, 4);
            d = grid_get_dot(g, points, px, py);
            grid_face_set_dot(g, d, 0);
            d = grid_get_dot(g, points, px + a, py);
            grid_face_set_dot(g, d, 1);
            d = grid_get_dot(g, points, px + a, py + a);
            grid_face_set_dot(g, d, 2);
            d = grid_get_dot(g, points, px, py + a);
            grid_face_set_dot(g, d, 3);
        }
    }

    freetree234(points);

    grid_make_consistent(g);
    return g;
}

#define HONEY_TILESIZE 45
/* Vector for side of hexagon - ratio is close to sqrt(3) */
#define HONEY_A 15
#define HONEY_B 26

static const char *grid_validate_params_honeycomb(int width, int height)
{
    int a = HONEY_A;
    int b = HONEY_B;

    if (width - 1 > (INT_MAX - 4*a) / (3 * a) ||  /* xextent */
        height - 1 > (INT_MAX - 3*b) / (2 * b) || /* yextent */
        width + 1 > INT_MAX / 2 / (height + 1))   /* max_dots */
        return "Grid size must not be unreasonably large";
    return NULL;
}

static void grid_size_honeycomb(int width, int height,
                         int *tilesize, int *xextent, int *yextent)
{
    int a = HONEY_A;
    int b = HONEY_B;

    *tilesize = HONEY_TILESIZE;
    *xextent = (3 * a * (width-1)) + 4*a;
    *yextent = (2 * b * (height-1)) + 3*b;
}

static grid *grid_new_honeycomb(int width, int height, const char *desc)
{
    int x, y;
    int a = HONEY_A;
    int b = HONEY_B;

    tree234 *points;

    grid *g = grid_empty();
    g->tilesize = HONEY_TILESIZE;

    points = newtree234(grid_point_cmp_fn);

    /* generate hexagonal faces */
    for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
            grid_dot *d;
            /* face centre */
            int cx = 3 * a * x;
            int cy = 2 * b * y;
            if (x % 2)
                cy += b;
            grid_face_add_new(g, 6);

            d = grid_get_dot(g, points, cx - a, cy - b);
            grid_face_set_dot(g, d, 0);
            d = grid_get_dot(g, points, cx + a, cy - b);
            grid_face_set_dot(g, d, 1);
            d = grid_get_dot(g, points, cx + 2*a, cy);
            grid_face_set_dot(g, d, 2);
            d = grid_get_dot(g, points, cx + a, cy + b);
            grid_face_set_dot(g, d, 3);
            d = grid_get_dot(g, points, cx - a, cy + b);
            grid_face_set_dot(g, d, 4);
            d = grid_get_dot(g, points, cx - 2*a, cy);
            grid_face_set_dot(g, d, 5);
        }
    }

    freetree234(points);

    grid_make_consistent(g);
    return g;
}

#define TRIANGLE_TILESIZE 18
/* Vector for side of triangle - ratio is close to sqrt(3) */
#define TRIANGLE_VEC_X 15
#define TRIANGLE_VEC_Y 26

static const char *grid_validate_params_triangular(int width, int height)
{
    int vec_x = TRIANGLE_VEC_X;
    int vec_y = TRIANGLE_VEC_Y;

    if (width > INT_MAX / (2 * vec_x) - 1 ||    /* xextent */
        height > INT_MAX / vec_y ||             /* yextent */
        width + 1 > INT_MAX / 4 / (height + 1)) /* max_dots */
        return "Grid size must not be unreasonably large";
    return NULL;
}

static void grid_size_triangular(int width, int height,
                          int *tilesize, int *xextent, int *yextent)
{
    int vec_x = TRIANGLE_VEC_X;
    int vec_y = TRIANGLE_VEC_Y;

    *tilesize = TRIANGLE_TILESIZE;
    *xextent = (width+1) * 2 * vec_x;
    *yextent = height * vec_y;
}

static const char *grid_validate_desc_triangular(grid_type type, int width,
                                                 int height, const char *desc)
{
    /*
     * Triangular grids: an absent description is valid (indicating
     * the old-style approach which had 'ears', i.e. triangles
     * connected to only one other face, at some grid corners), and so
     * is a description reading just "0" (indicating the new-style
     * approach in which those ears are trimmed off). Anything else is
     * illegal.
     */

    if (!desc || !strcmp(desc, "0"))
        return NULL;

    return "Unrecognised grid description.";
}

/* Doesn't use the previous method of generation, it pre-dates it!
 * A triangular grid is just about simple enough to do by "brute force" */
static grid *grid_new_triangular(int width, int height, const char *desc)
{
    int x,y;
    int version = (desc == NULL ? -1 : atoi(desc));
    
    /* Vector for side of triangle - ratio is close to sqrt(3) */
    int vec_x = TRIANGLE_VEC_X;
    int vec_y = TRIANGLE_VEC_Y;
    
    int index;

    /* convenient alias */
    int w = width + 1;

    grid *g = grid_empty();
    g->tilesize = TRIANGLE_TILESIZE;

    if (version == -1) {
        /*
         * Old-style triangular grid generation, preserved as-is for
         * backwards compatibility with old game ids, in which it's
         * just a little asymmetric and there are 'ears' (faces linked
         * to only one other face) at two grid corners.
         *
         * Example old-style game ids, which should still work, and in
         * which you should see the ears in the TL/BR corners on the
         * first one and in the TL/BL corners on the second:
         *
         *   5x5t1:2c2a1a2a201a1a1a1112a1a2b1211f0b21a2a2a0a
         *   5x6t1:a022a212h1a1d1a12c2b11a012b1a20d1a0a12e
         */

        g->num_faces = g->size_faces = width * height * 2;
        g->num_dots = g->size_dots = (width + 1) * (height + 1);
        g->faces = snewn(g->size_faces, grid_face *);
        g->dots = snewn(g->size_dots, grid_dot *);

        /* generate dots */
        index = 0;
        for (y = 0; y <= height; y++) {
            for (x = 0; x <= width; x++) {
                grid_dot *d = snew(grid_dot);
                d->index = index;
                g->dots[d->index] = d;
                /* odd rows are offset to the right */
                d->order = 0;
                d->edges = NULL;
                d->faces = NULL;
                d->x = x * 2 * vec_x + ((y % 2) ? vec_x : 0);
                d->y = y * vec_y;
                index++;
            }
        }
    
        /* generate faces */
        index = 0;
        for (y = 0; y < height; y++) {
            for (x = 0; x < width; x++) {
                /* initialise two faces for this (x,y) */
                grid_face *f1 = snew(grid_face), *f2 = snew(grid_face);
                f1->index = index;
                f2->index = index + 1;
                g->faces[f1->index] = f1;
                g->faces[f2->index] = f2;
                f1->edges = NULL;
                f1->order = 3;
                f1->dots = snewn(f1->order, grid_dot*);
                f1->has_incentre = false;
                f2->edges = NULL;
                f2->order = 3;
                f2->dots = snewn(f2->order, grid_dot*);
                f2->has_incentre = false;

                /* face descriptions depend on whether the row-number is
                 * odd or even */
                if (y % 2) {
                    f1->dots[0] = g->dots[y       * w + x];
                    f1->dots[1] = g->dots[(y + 1) * w + x + 1];
                    f1->dots[2] = g->dots[(y + 1) * w + x];
                    f2->dots[0] = g->dots[y       * w + x];
                    f2->dots[1] = g->dots[y       * w + x + 1];
                    f2->dots[2] = g->dots[(y + 1) * w + x + 1];
                } else {
                    f1->dots[0] = g->dots[y       * w + x];
                    f1->dots[1] = g->dots[y       * w + x + 1];
                    f1->dots[2] = g->dots[(y + 1) * w + x];
                    f2->dots[0] = g->dots[y       * w + x + 1];
                    f2->dots[1] = g->dots[(y + 1) * w + x + 1];
                    f2->dots[2] = g->dots[(y + 1) * w + x];
                }
                index += 2;
            }
        }
    } else {
        /*
         * New-style approach, in which there are never any 'ears',
         * and if height is even then the grid is nicely 4-way
         * symmetric.
         *
         * Example new-style grids:
         *
         *   5x5t1:0_21120b11a1a01a1a00c1a0b211021c1h1a2a1a0a
         *   5x6t1:0_a1212c22c2a02a2f22a0c12a110d0e1c0c0a101121a1
         */
        tree234 *points = newtree234(grid_point_cmp_fn);

        for (y = 0; y < height; y++) {
            /*
             * Each row contains (width+1) triangles one way up, and
             * (width) triangles the other way up. Which way up is
             * which varies with parity of y. Also, the dots around
             * each face will flip direction with parity of y, so we
             * set up n1 and n2 to cope with that easily.
             */
            int y0, y1, n1, n2;
            y0 = y1 = y * vec_y;
            if (y % 2) {
                y1 += vec_y;
                n1 = 2; n2 = 1;
            } else {
                y0 += vec_y;
                n1 = 1; n2 = 2;
            }

            for (x = 0; x <= width; x++) {
                int x0 = 2*x * vec_x, x1 = x0 + vec_x, x2 = x1 + vec_x;

                /*
                 * If the grid has odd height, then we skip the first
                 * and last triangles on this row, otherwise they'll
                 * end up as ears.
                 */
                if (height % 2 == 1 && y == height-1 && (x == 0 || x == width))
                    continue;

                grid_face_add_new(g, 3);
                grid_face_set_dot(g, grid_get_dot(g, points, x0, y0), 0);
                grid_face_set_dot(g, grid_get_dot(g, points, x1, y1), n1);
                grid_face_set_dot(g, grid_get_dot(g, points, x2, y0), n2);
            }

            for (x = 0; x < width; x++) {
                int x0 = (2*x+1) * vec_x, x1 = x0 + vec_x, x2 = x1 + vec_x;

                grid_face_add_new(g, 3);
                grid_face_set_dot(g, grid_get_dot(g, points, x0, y1), 0);
                grid_face_set_dot(g, grid_get_dot(g, points, x1, y0), n2);
                grid_face_set_dot(g, grid_get_dot(g, points, x2, y1), n1);
            }
        }

        freetree234(points);
    }

    grid_make_consistent(g);
    return g;
}

#define SNUBSQUARE_TILESIZE 18
/* Vector for side of triangle - ratio is close to sqrt(3) */
#define SNUBSQUARE_A 15
#define SNUBSQUARE_B 26

static const char *grid_validate_params_snubsquare(int width, int height)
{
    int a = SNUBSQUARE_A;
    int b = SNUBSQUARE_B;

    if (width-1 > (INT_MAX - (a + b)) / (a+b) || /* xextent */
        height > (INT_MAX - (a + b)) / (a+b) ||  /* yextent */
        width > INT_MAX / 3 / height ||          /* max_faces */
        width + 1 > INT_MAX / 2 / (height + 1))  /* max_dots */
        return "Grid size must not be unreasonably large";
    return NULL;
}

static void grid_size_snubsquare(int width, int height,
                          int *tilesize, int *xextent, int *yextent)
{
    int a = SNUBSQUARE_A;
    int b = SNUBSQUARE_B;

    *tilesize = SNUBSQUARE_TILESIZE;
    *xextent = (a+b) * (width-1) + a + b;
    *yextent = (a+b) * (height-1) + a + b;
}

static grid *grid_new_snubsquare(int width, int height, const char *desc)
{
    int x, y;
    int a = SNUBSQUARE_A;
    int b = SNUBSQUARE_B;

    tree234 *points;

    grid *g = grid_empty();
    g->tilesize = SNUBSQUARE_TILESIZE;

    points = newtree234(grid_point_cmp_fn);

    for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
            grid_dot *d;
            /* face position */
            int px = (a + b) * x;
            int py = (a + b) * y;

            /* generate square faces */
            grid_face_add_new(g, 4);
            if ((x + y) % 2) {
                d = grid_get_dot(g, points, px + a, py);
                grid_face_set_dot(g, d, 0);
                d = grid_get_dot(g, points, px + a + b, py + a);
                grid_face_set_dot(g, d, 1);
                d = grid_get_dot(g, points, px + b, py + a + b);
                grid_face_set_dot(g, d, 2);
                d = grid_get_dot(g, points, px, py + b);
                grid_face_set_dot(g, d, 3);
            } else {
                d = grid_get_dot(g, points, px + b, py);
                grid_face_set_dot(g, d, 0);
                d = grid_get_dot(g, points, px + a + b, py + b);
                grid_face_set_dot(g, d, 1);
                d = grid_get_dot(g, points, px + a, py + a + b);
                grid_face_set_dot(g, d, 2);
                d = grid_get_dot(g, points, px, py + a);
                grid_face_set_dot(g, d, 3);
            }

            /* generate up/down triangles */
            if (x > 0) {
                grid_face_add_new(g, 3);
                if ((x + y) % 2) {
                    d = grid_get_dot(g, points, px + a, py);
                    grid_face_set_dot(g, d, 0);
                    d = grid_get_dot(g, points, px, py + b);
                    grid_face_set_dot(g, d, 1);
                    d = grid_get_dot(g, points, px - a, py);
                    grid_face_set_dot(g, d, 2);
                } else {
                    d = grid_get_dot(g, points, px, py + a);
                    grid_face_set_dot(g, d, 0);
                    d = grid_get_dot(g, points, px + a, py + a + b);
                    grid_face_set_dot(g, d, 1);
                    d = grid_get_dot(g, points, px - a, py + a + b);
                    grid_face_set_dot(g, d, 2);
                }
            }

            /* generate left/right triangles */
            if (y > 0) {
                grid_face_add_new(g, 3);
                if ((x + y) % 2) {
                    d = grid_get_dot(g, points, px + a, py);
                    grid_face_set_dot(g, d, 0);
                    d = grid_get_dot(g, points, px + a + b, py - a);
                    grid_face_set_dot(g, d, 1);
                    d = grid_get_dot(g, points, px + a + b, py + a);
                    grid_face_set_dot(g, d, 2);
                } else {
                    d = grid_get_dot(g, points, px, py - a);
                    grid_face_set_dot(g, d, 0);
                    d = grid_get_dot(g, points, px + b, py);
                    grid_face_set_dot(g, d, 1);
                    d = grid_get_dot(g, points, px, py + a);
                    grid_face_set_dot(g, d, 2);
                }
            }
        }
    }

    freetree234(points);

    grid_make_consistent(g);
    return g;
}

#define CAIRO_TILESIZE 40
/* Vector for side of pentagon - ratio is close to (4+sqrt(7))/3 */
#define CAIRO_A 14
#define CAIRO_B 31

static const char *grid_validate_params_cairo(int width, int height)
{
    int b = CAIRO_B; /* a unused in determining grid size. */

    if (width - 1 > (INT_MAX - 2*b) / (2*b) ||  /* xextent */
        height - 1 > (INT_MAX - 2*b) / (2*b) || /* yextent */
        width > INT_MAX / 2 / height ||         /* max_faces */
        width + 1 > INT_MAX / 3 / (height + 1)) /* max_dots */
        return "Grid size must not be unreasonably large";
    return NULL;
}

static void grid_size_cairo(int width, int height,
                          int *tilesize, int *xextent, int *yextent)
{
    int b = CAIRO_B; /* a unused in determining grid size. */

    *tilesize = CAIRO_TILESIZE;
    *xextent = 2*b*(width-1) + 2*b;
    *yextent = 2*b*(height-1) + 2*b;
}

static grid *grid_new_cairo(int width, int height, const char *desc)
{
    int x, y;
    int a = CAIRO_A;
    int b = CAIRO_B;

    tree234 *points;

    grid *g = grid_empty();
    g->tilesize = CAIRO_TILESIZE;

    points = newtree234(grid_point_cmp_fn);

    for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
            grid_dot *d;
            /* cell position */
            int px = 2 * b * x;
            int py = 2 * b * y;

            /* horizontal pentagons */
            if (y > 0) {
                grid_face_add_new(g, 5);
                if ((x + y) % 2) {
                    d = grid_get_dot(g, points, px + a, py - b);
                    grid_face_set_dot(g, d, 0);
                    d = grid_get_dot(g, points, px + 2*b - a, py - b);
                    grid_face_set_dot(g, d, 1);
                    d = grid_get_dot(g, points, px + 2*b, py);
                    grid_face_set_dot(g, d, 2);
                    d = grid_get_dot(g, points, px + b, py + a);
                    grid_face_set_dot(g, d, 3);
                    d = grid_get_dot(g, points, px, py);
                    grid_face_set_dot(g, d, 4);
                } else {
                    d = grid_get_dot(g, points, px, py);
                    grid_face_set_dot(g, d, 0);
                    d = grid_get_dot(g, points, px + b, py - a);
                    grid_face_set_dot(g, d, 1);
                    d = grid_get_dot(g, points, px + 2*b, py);
                    grid_face_set_dot(g, d, 2);
                    d = grid_get_dot(g, points, px + 2*b - a, py + b);
                    grid_face_set_dot(g, d, 3);
                    d = grid_get_dot(g, points, px + a, py + b);
                    grid_face_set_dot(g, d, 4);
                }
            }
            /* vertical pentagons */
            if (x > 0) {
                grid_face_add_new(g, 5);
                if ((x + y) % 2) {
                    d = grid_get_dot(g, points, px, py);
                    grid_face_set_dot(g, d, 0);
                    d = grid_get_dot(g, points, px + b, py + a);
                    grid_face_set_dot(g, d, 1);
                    d = grid_get_dot(g, points, px + b, py + 2*b - a);
                    grid_face_set_dot(g, d, 2);
                    d = grid_get_dot(g, points, px, py + 2*b);
                    grid_face_set_dot(g, d, 3);
                    d = grid_get_dot(g, points, px - a, py + b);
                    grid_face_set_dot(g, d, 4);
                } else {
                    d = grid_get_dot(g, points, px, py);
                    grid_face_set_dot(g, d, 0);
                    d = grid_get_dot(g, points, px + a, py + b);
                    grid_face_set_dot(g, d, 1);
                    d = grid_get_dot(g, points, px, py + 2*b);
                    grid_face_set_dot(g, d, 2);
                    d = grid_get_dot(g, points, px - b, py + 2*b - a);
                    grid_face_set_dot(g, d, 3);
                    d = grid_get_dot(g, points, px - b, py + a);
                    grid_face_set_dot(g, d, 4);
                }
            }
        }
    }

    freetree234(points);

    grid_make_consistent(g);
    return g;
}

#define GREATHEX_TILESIZE 18
/* Vector for side of triangle - ratio is close to sqrt(3) */
#define GREATHEX_A 15
#define GREATHEX_B 26

static const char *grid_validate_params_greathexagonal(int width, int height)
{
    int a = GREATHEX_A;
    int b = GREATHEX_B;

    if (width-1 > (INT_MAX - 4*a) / (3*a + b) ||          /* xextent */
        height-1 > (INT_MAX - (3*b + a)) / (2*a + 2*b) || /* yextent */
        width + 1 > INT_MAX / 6 / (height + 1))           /* max_faces */
        return "Grid size must not be unreasonably large";
    return NULL;
}

static void grid_size_greathexagonal(int width, int height,
                          int *tilesize, int *xextent, int *yextent)
{
    int a = GREATHEX_A;
    int b = GREATHEX_B;

    *tilesize = GREATHEX_TILESIZE;
    *xextent = (3*a + b) * (width-1) + 4*a;
    *yextent = (2*a + 2*b) * (height-1) + 3*b + a;
}

static grid *grid_new_greathexagonal(int width, int height, const char *desc)
{
    int x, y;
    int a = GREATHEX_A;
    int b = GREATHEX_B;

    tree234 *points;

    grid *g = grid_empty();
    g->tilesize = GREATHEX_TILESIZE;

    points = newtree234(grid_point_cmp_fn);

    for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
            grid_dot *d;
            /* centre of hexagon */
            int px = (3*a + b) * x;
            int py = (2*a + 2*b) * y;
            if (x % 2)
                py += a + b;

            /* hexagon */
            grid_face_add_new(g, 6);
            d = grid_get_dot(g, points, px - a, py - b);
            grid_face_set_dot(g, d, 0);
            d = grid_get_dot(g, points, px + a, py - b);
            grid_face_set_dot(g, d, 1);
            d = grid_get_dot(g, points, px + 2*a, py);
            grid_face_set_dot(g, d, 2);
            d = grid_get_dot(g, points, px + a, py + b);
            grid_face_set_dot(g, d, 3);
            d = grid_get_dot(g, points, px - a, py + b);
            grid_face_set_dot(g, d, 4);
            d = grid_get_dot(g, points, px - 2*a, py);
            grid_face_set_dot(g, d, 5);

            /* square below hexagon */
            if (y < height - 1) {
                grid_face_add_new(g, 4);
                d = grid_get_dot(g, points, px - a, py + b);
                grid_face_set_dot(g, d, 0);
                d = grid_get_dot(g, points, px + a, py + b);
                grid_face_set_dot(g, d, 1);
                d = grid_get_dot(g, points, px + a, py + 2*a + b);
                grid_face_set_dot(g, d, 2);
                d = grid_get_dot(g, points, px - a, py + 2*a + b);
                grid_face_set_dot(g, d, 3);
            }

            /* square below right */
            if ((x < width - 1) && (((x % 2) == 0) || (y < height - 1))) {
                grid_face_add_new(g, 4);
                d = grid_get_dot(g, points, px + 2*a, py);
                grid_face_set_dot(g, d, 0);
                d = grid_get_dot(g, points, px + 2*a + b, py + a);
                grid_face_set_dot(g, d, 1);
                d = grid_get_dot(g, points, px + a + b, py + a + b);
                grid_face_set_dot(g, d, 2);
                d = grid_get_dot(g, points, px + a, py + b);
                grid_face_set_dot(g, d, 3);
            }

            /* square below left */
            if ((x > 0) && (((x % 2) == 0) || (y < height - 1))) {
                grid_face_add_new(g, 4);
                d = grid_get_dot(g, points, px - 2*a, py);
                grid_face_set_dot(g, d, 0);
                d = grid_get_dot(g, points, px - a, py + b);
                grid_face_set_dot(g, d, 1);
                d = grid_get_dot(g, points, px - a - b, py + a + b);
                grid_face_set_dot(g, d, 2);
                d = grid_get_dot(g, points, px - 2*a - b, py + a);
                grid_face_set_dot(g, d, 3);
            }
           
            /* Triangle below right */
            if ((x < width - 1) && (y < height - 1)) {
                grid_face_add_new(g, 3);
                d = grid_get_dot(g, points, px + a, py + b);
                grid_face_set_dot(g, d, 0);
                d = grid_get_dot(g, points, px + a + b, py + a + b);
                grid_face_set_dot(g, d, 1);
                d = grid_get_dot(g, points, px + a, py + 2*a + b);
                grid_face_set_dot(g, d, 2);
            }

            /* Triangle below left */
            if ((x > 0) && (y < height - 1)) {
                grid_face_add_new(g, 3);
                d = grid_get_dot(g, points, px - a, py + b);
                grid_face_set_dot(g, d, 0);
                d = grid_get_dot(g, points, px - a, py + 2*a + b);
                grid_face_set_dot(g, d, 1);
                d = grid_get_dot(g, points, px - a - b, py + a + b);
                grid_face_set_dot(g, d, 2);
            }
        }
    }

    freetree234(points);

    grid_make_consistent(g);
    return g;
}

#define KAGOME_TILESIZE 18
/* Vector for side of triangle - ratio is close to sqrt(3) */
#define KAGOME_A 15
#define KAGOME_B 26

static const char *grid_validate_params_kagome(int width, int height)
{
    int a = KAGOME_A;
    int b = KAGOME_B;

    if (width-1 > (INT_MAX - 6*a) / (4*a) ||    /* xextent */
        height-1 > (INT_MAX - 2*b) / (2*b) ||   /* yextent */
        width + 1 > INT_MAX / 6 / (height + 1)) /* max_faces */
        return "Grid size must not be unreasonably large";
    return NULL;
}

static void grid_size_kagome(int width, int height,
                             int *tilesize, int *xextent, int *yextent)
{
    int a = KAGOME_A;
    int b = KAGOME_B;

    *tilesize = KAGOME_TILESIZE;
    *xextent = (4*a) * (width-1) + 6*a;
    *yextent = (2*b) * (height-1) + 2*b;
}

static grid *grid_new_kagome(int width, int height, const char *desc)
{
    int x, y;
    int a = KAGOME_A;
    int b = KAGOME_B;

    tree234 *points;

    grid *g = grid_empty();
    g->tilesize = KAGOME_TILESIZE;

    points = newtree234(grid_point_cmp_fn);

    for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
            grid_dot *d;
            /* centre of hexagon */
            int px = (4*a) * x;
            int py = (2*b) * y;
            if (y % 2)
                px += 2*a;

            /* hexagon */
            grid_face_add_new(g, 6);
            d = grid_get_dot(g, points, px +   a, py -   b); grid_face_set_dot(g, d, 0);
            d = grid_get_dot(g, points, px + 2*a, py      ); grid_face_set_dot(g, d, 1);
            d = grid_get_dot(g, points, px +   a, py +   b); grid_face_set_dot(g, d, 2);
            d = grid_get_dot(g, points, px -   a, py +   b); grid_face_set_dot(g, d, 3);
            d = grid_get_dot(g, points, px - 2*a, py      ); grid_face_set_dot(g, d, 4);
            d = grid_get_dot(g, points, px -   a, py -   b); grid_face_set_dot(g, d, 5);

            /* Triangle above right */
            if ((x < width - 1) || (!(y % 2) && y)) {
                grid_face_add_new(g, 3);
                d = grid_get_dot(g, points, px + 3*a, py - b); grid_face_set_dot(g, d, 0);
                d = grid_get_dot(g, points, px + 2*a, py    ); grid_face_set_dot(g, d, 1);
                d = grid_get_dot(g, points, px +   a, py - b); grid_face_set_dot(g, d, 2);
            }

            /* Triangle below right */
            if ((x < width - 1) || (!(y % 2) && (y < height - 1))) {
                grid_face_add_new(g, 3);
                d = grid_get_dot(g, points, px + 3*a, py + b); grid_face_set_dot(g, d, 0);
                d = grid_get_dot(g, points, px +   a, py + b); grid_face_set_dot(g, d, 1);
                d = grid_get_dot(g, points, px + 2*a, py    ); grid_face_set_dot(g, d, 2);
            }

            /* Left triangles */
            if (!x && (y % 2)) {
                /* Triangle above left */
                grid_face_add_new(g, 3);
                d = grid_get_dot(g, points, px -   a, py - b); grid_face_set_dot(g, d, 0);
                d = grid_get_dot(g, points, px - 2*a, py    ); grid_face_set_dot(g, d, 1);
                d = grid_get_dot(g, points, px - 3*a, py - b); grid_face_set_dot(g, d, 2);

                /* Triangle below left */
                if (y < height - 1) {
                    grid_face_add_new(g, 3);
                    d = grid_get_dot(g, points, px -   a, py + b); grid_face_set_dot(g, d, 0);
                    d = grid_get_dot(g, points, px - 3*a, py + b); grid_face_set_dot(g, d, 1);
                    d = grid_get_dot(g, points, px - 2*a, py    ); grid_face_set_dot(g, d, 2);
                }
            }
        }
    }

    freetree234(points);

    grid_make_consistent(g);
    return g;
}

#define OCTAGONAL_TILESIZE 40
/* b/a approx sqrt(2) */
#define OCTAGONAL_A 29
#define OCTAGONAL_B 41

static const char *grid_validate_params_octagonal(int width, int height)
{
    int a = OCTAGONAL_A;
    int b = OCTAGONAL_B;

    if (width > INT_MAX / (2*a + b) ||          /* xextent */
        height > INT_MAX / (2*a + b) ||         /* yextent */
        height + 1 > INT_MAX / 4 / (width + 1)) /* max_dots */
        return "Grid size must not be unreasonably large";
    return NULL;
}

static void grid_size_octagonal(int width, int height,
                          int *tilesize, int *xextent, int *yextent)
{
    int a = OCTAGONAL_A;
    int b = OCTAGONAL_B;

    *tilesize = OCTAGONAL_TILESIZE;
    *xextent = (2*a + b) * width;
    *yextent = (2*a + b) * height;
}

static grid *grid_new_octagonal(int width, int height, const char *desc)
{
    int x, y;
    int a = OCTAGONAL_A;
    int b = OCTAGONAL_B;

    tree234 *points;

    grid *g = grid_empty();
    g->tilesize = OCTAGONAL_TILESIZE;

    points = newtree234(grid_point_cmp_fn);

    for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
            grid_dot *d;
            /* cell position */
            int px = (2*a + b) * x;
            int py = (2*a + b) * y;
            /* octagon */
            grid_face_add_new(g, 8);
            d = grid_get_dot(g, points, px + a, py);
            grid_face_set_dot(g, d, 0);
            d = grid_get_dot(g, points, px + a + b, py);
            grid_face_set_dot(g, d, 1);
            d = grid_get_dot(g, points, px + 2*a + b, py + a);
            grid_face_set_dot(g, d, 2);
            d = grid_get_dot(g, points, px + 2*a + b, py + a + b);
            grid_face_set_dot(g, d, 3);
            d = grid_get_dot(g, points, px + a + b, py + 2*a + b);
            grid_face_set_dot(g, d, 4);
            d = grid_get_dot(g, points, px + a, py + 2*a + b);
            grid_face_set_dot(g, d, 5);
            d = grid_get_dot(g, points, px, py + a + b);
            grid_face_set_dot(g, d, 6);
            d = grid_get_dot(g, points, px, py + a);
            grid_face_set_dot(g, d, 7);

            /* diamond */
            if ((x > 0) && (y > 0)) {
                grid_face_add_new(g, 4);
                d = grid_get_dot(g, points, px, py - a);
                grid_face_set_dot(g, d, 0);
                d = grid_get_dot(g, points, px + a, py);
                grid_face_set_dot(g, d, 1);
                d = grid_get_dot(g, points, px, py + a);
                grid_face_set_dot(g, d, 2);
                d = grid_get_dot(g, points, px - a, py);
                grid_face_set_dot(g, d, 3);
            }
        }
    }

    freetree234(points);

    grid_make_consistent(g);
    return g;
}

#define KITE_TILESIZE 40
/* b/a approx sqrt(3) */
#define KITE_A 15
#define KITE_B 26

static const char *grid_validate_params_kites(int width, int height)
{
    int a = KITE_A;
    int b = KITE_B;

    if (width > (INT_MAX - 2*b) / (4*b) ||      /* xextent */
        height - 1 > (INT_MAX - 8*a) / (6*a) || /* yextent */
        width + 1 > INT_MAX / 6 / (height + 1)) /* max_dots */
        return "Grid size must not be unreasonably large";
    return NULL;
}

static void grid_size_kites(int width, int height,
                     int *tilesize, int *xextent, int *yextent)
{
    int a = KITE_A;
    int b = KITE_B;

    *tilesize = KITE_TILESIZE;
    *xextent = 4*b * width + 2*b;
    *yextent = 6*a * (height-1) + 8*a;
}

static grid *grid_new_kites(int width, int height, const char *desc)
{
    int x, y;
    int a = KITE_A;
    int b = KITE_B;

    tree234 *points;

    grid *g = grid_empty();
    g->tilesize = KITE_TILESIZE;

    points = newtree234(grid_point_cmp_fn);

    for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
            grid_dot *d;
            /* position of order-6 dot */
            int px = 4*b * x;
            int py = 6*a * y;
            if (y % 2)
                px += 2*b;

            /* kite pointing up-left */
            grid_face_add_new(g, 4);
            d = grid_get_dot(g, points, px, py);
            grid_face_set_dot(g, d, 0);
            d = grid_get_dot(g, points, px + 2*b, py);
            grid_face_set_dot(g, d, 1);
            d = grid_get_dot(g, points, px + 2*b, py + 2*a);
            grid_face_set_dot(g, d, 2);
            d = grid_get_dot(g, points, px + b, py + 3*a);
            grid_face_set_dot(g, d, 3);

            /* kite pointing up */
            grid_face_add_new(g, 4);
            d = grid_get_dot(g, points, px, py);
            grid_face_set_dot(g, d, 0);
            d = grid_get_dot(g, points, px + b, py + 3*a);
            grid_face_set_dot(g, d, 1);
            d = grid_get_dot(g, points, px, py + 4*a);
            grid_face_set_dot(g, d, 2);
            d = grid_get_dot(g, points, px - b, py + 3*a);
            grid_face_set_dot(g, d, 3);

            /* kite pointing up-right */
            grid_face_add_new(g, 4);
            d = grid_get_dot(g, points, px, py);
            grid_face_set_dot(g, d, 0);
            d = grid_get_dot(g, points, px - b, py + 3*a);
            grid_face_set_dot(g, d, 1);
            d = grid_get_dot(g, points, px - 2*b, py + 2*a);
            grid_face_set_dot(g, d, 2);
            d = grid_get_dot(g, points, px - 2*b, py);
            grid_face_set_dot(g, d, 3);

            /* kite pointing down-right */
            grid_face_add_new(g, 4);
            d = grid_get_dot(g, points, px, py);
            grid_face_set_dot(g, d, 0);
            d = grid_get_dot(g, points, px - 2*b, py);
            grid_face_set_dot(g, d, 1);
            d = grid_get_dot(g, points, px - 2*b, py - 2*a);
            grid_face_set_dot(g, d, 2);
            d = grid_get_dot(g, points, px - b, py - 3*a);
            grid_face_set_dot(g, d, 3);

            /* kite pointing down */
            grid_face_add_new(g, 4);
            d = grid_get_dot(g, points, px, py);
            grid_face_set_dot(g, d, 0);
            d = grid_get_dot(g, points, px - b, py - 3*a);
            grid_face_set_dot(g, d, 1);
            d = grid_get_dot(g, points, px, py - 4*a);
            grid_face_set_dot(g, d, 2);
            d = grid_get_dot(g, points, px + b, py - 3*a);
            grid_face_set_dot(g, d, 3);

            /* kite pointing down-left */
            grid_face_add_new(g, 4);
            d = grid_get_dot(g, points, px, py);
            grid_face_set_dot(g, d, 0);
            d = grid_get_dot(g, points, px + b, py - 3*a);
            grid_face_set_dot(g, d, 1);
            d = grid_get_dot(g, points, px + 2*b, py - 2*a);
            grid_face_set_dot(g, d, 2);
            d = grid_get_dot(g, points, px + 2*b, py);
            grid_face_set_dot(g, d, 3);
        }
    }

    freetree234(points);

    grid_make_consistent(g);
    return g;
}

#define FLORET_TILESIZE 150
/* -py/px is close to tan(30 - atan(sqrt(3)/9))
 * using py=26 makes everything lean to the left, rather than right
 */
#define FLORET_PX 75
#define FLORET_PY -26

static const char *grid_validate_params_floret(int width, int height)
{
    int px = FLORET_PX, py = FLORET_PY;         /* |( 75, -26)| = 79.43 */
    int qx = 4*px/5, qy = -py*2;                /* |( 60,  52)| = 79.40 */
    int ry = qy-py;
    /* rx unused in determining grid size. */

    if (width - 1 > (INT_MAX - (4*qx + 2*px)) / ((6*px+3*qx)/2) ||/* xextent */
        height - 1 > (INT_MAX - (4*qy + 2*ry)) / (5*qy-4*py) ||   /* yextent */
        width + 1 > INT_MAX / 9 / (height + 1))                  /* max_dots */
        return "Grid size must not be unreasonably large";
    return NULL;
}

static void grid_size_floret(int width, int height,
                          int *tilesize, int *xextent, int *yextent)
{
    int px = FLORET_PX, py = FLORET_PY;         /* |( 75, -26)| = 79.43 */
    int qx = 4*px/5, qy = -py*2;                /* |( 60,  52)| = 79.40 */
    int ry = qy-py;
    /* rx unused in determining grid size. */

    *tilesize = FLORET_TILESIZE;
    *xextent = (6*px+3*qx)/2 * (width-1) + 4*qx + 2*px;
    *yextent = (5*qy-4*py) * (height-1) + 4*qy + 2*ry;
    if (height == 1)
        *yextent += (5*qy-4*py)/2;
}

static grid *grid_new_floret(int width, int height, const char *desc)
{
    int x, y;
    /* Vectors for sides; weird numbers needed to keep puzzle aligned with window
     * -py/px is close to tan(30 - atan(sqrt(3)/9))
     * using py=26 makes everything lean to the left, rather than right
     */
    int px = FLORET_PX, py = FLORET_PY;         /* |( 75, -26)| = 79.43 */
    int qx = 4*px/5, qy = -py*2;                /* |( 60,  52)| = 79.40 */
    int rx = qx-px, ry = qy-py;                 /* |(-15,  78)| = 79.38 */

    tree234 *points;

    grid *g = grid_empty();
    g->tilesize = FLORET_TILESIZE;

    points = newtree234(grid_point_cmp_fn);

    /* generate pentagonal faces */
    for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
            grid_dot *d;
            /* face centre */
            int cx = (6*px+3*qx)/2 * x;
            int cy = (4*py-5*qy) * y;
            if (x % 2)
                cy -= (4*py-5*qy)/2;
            else if (y && y == height-1)
                continue; /* make better looking grids?  try 3x3 for instance */

            grid_face_add_new(g, 5);
            d = grid_get_dot(g, points, cx        , cy        ); grid_face_set_dot(g, d, 0);
            d = grid_get_dot(g, points, cx+2*rx   , cy+2*ry   ); grid_face_set_dot(g, d, 1);
            d = grid_get_dot(g, points, cx+2*rx+qx, cy+2*ry+qy); grid_face_set_dot(g, d, 2);
            d = grid_get_dot(g, points, cx+2*qx+rx, cy+2*qy+ry); grid_face_set_dot(g, d, 3);
            d = grid_get_dot(g, points, cx+2*qx   , cy+2*qy   ); grid_face_set_dot(g, d, 4);

            grid_face_add_new(g, 5);
            d = grid_get_dot(g, points, cx        , cy        ); grid_face_set_dot(g, d, 0);
            d = grid_get_dot(g, points, cx+2*qx   , cy+2*qy   ); grid_face_set_dot(g, d, 1);
            d = grid_get_dot(g, points, cx+2*qx+px, cy+2*qy+py); grid_face_set_dot(g, d, 2);
            d = grid_get_dot(g, points, cx+2*px+qx, cy+2*py+qy); grid_face_set_dot(g, d, 3);
            d = grid_get_dot(g, points, cx+2*px   , cy+2*py   ); grid_face_set_dot(g, d, 4);

            grid_face_add_new(g, 5);
            d = grid_get_dot(g, points, cx        , cy        ); grid_face_set_dot(g, d, 0);
            d = grid_get_dot(g, points, cx+2*px   , cy+2*py   ); grid_face_set_dot(g, d, 1);
            d = grid_get_dot(g, points, cx+2*px-rx, cy+2*py-ry); grid_face_set_dot(g, d, 2);
            d = grid_get_dot(g, points, cx-2*rx+px, cy-2*ry+py); grid_face_set_dot(g, d, 3);
            d = grid_get_dot(g, points, cx-2*rx   , cy-2*ry   ); grid_face_set_dot(g, d, 4);

            grid_face_add_new(g, 5);
            d = grid_get_dot(g, points, cx        , cy        ); grid_face_set_dot(g, d, 0);
            d = grid_get_dot(g, points, cx-2*rx   , cy-2*ry   ); grid_face_set_dot(g, d, 1);
            d = grid_get_dot(g, points, cx-2*rx-qx, cy-2*ry-qy); grid_face_set_dot(g, d, 2);
            d = grid_get_dot(g, points, cx-2*qx-rx, cy-2*qy-ry); grid_face_set_dot(g, d, 3);
            d = grid_get_dot(g, points, cx-2*qx   , cy-2*qy   ); grid_face_set_dot(g, d, 4);

            grid_face_add_new(g, 5);
            d = grid_get_dot(g, points, cx        , cy        ); grid_face_set_dot(g, d, 0);
            d = grid_get_dot(g, points, cx-2*qx   , cy-2*qy   ); grid_face_set_dot(g, d, 1);
            d = grid_get_dot(g, points, cx-2*qx-px, cy-2*qy-py); grid_face_set_dot(g, d, 2);
            d = grid_get_dot(g, points, cx-2*px-qx, cy-2*py-qy); grid_face_set_dot(g, d, 3);
            d = grid_get_dot(g, points, cx-2*px   , cy-2*py   ); grid_face_set_dot(g, d, 4);

            grid_face_add_new(g, 5);
            d = grid_get_dot(g, points, cx        , cy        ); grid_face_set_dot(g, d, 0);
            d = grid_get_dot(g, points, cx-2*px   , cy-2*py   ); grid_face_set_dot(g, d, 1);
            d = grid_get_dot(g, points, cx-2*px+rx, cy-2*py+ry); grid_face_set_dot(g, d, 2);
            d = grid_get_dot(g, points, cx+2*rx-px, cy+2*ry-py); grid_face_set_dot(g, d, 3);
            d = grid_get_dot(g, points, cx+2*rx   , cy+2*ry   ); grid_face_set_dot(g, d, 4);
        }
    }

    freetree234(points);

    grid_make_consistent(g);
    return g;
}

/* DODEC_* are used for dodecagonal and great-dodecagonal grids. */
#define DODEC_TILESIZE 26
/* Vector for side of triangle - ratio is close to sqrt(3) */
#define DODEC_A 15
#define DODEC_B 26

static const char *grid_validate_params_dodecagonal(int width, int height)
{
    int a = DODEC_A;
    int b = DODEC_B;

    if (width - 1 > (INT_MAX - 3*(2*a + b)) / (4*a + 2*b) ||  /* xextent */
        height - 1 > (INT_MAX - 2*(2*a + b)) / (3*a + 2*b) || /* yextent */
        width > INT_MAX / 14 / height)                        /* max_dots */
        return "Grid size must not be unreasonably large";
    return NULL;
}

static void grid_size_dodecagonal(int width, int height,
                          int *tilesize, int *xextent, int *yextent)
{
    int a = DODEC_A;
    int b = DODEC_B;

    *tilesize = DODEC_TILESIZE;
    *xextent = (4*a + 2*b) * (width-1) + 3*(2*a + b);
    *yextent = (3*a + 2*b) * (height-1) + 2*(2*a + b);
}

static grid *grid_new_dodecagonal(int width, int height, const char *desc)
{
    int x, y;
    int a = DODEC_A;
    int b = DODEC_B;

    tree234 *points;

    grid *g = grid_empty();
    g->tilesize = DODEC_TILESIZE;

    points = newtree234(grid_point_cmp_fn);

    for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
            grid_dot *d;
            /* centre of dodecagon */
            int px = (4*a + 2*b) * x;
            int py = (3*a + 2*b) * y;
            if (y % 2)
                px += 2*a + b;

            /* dodecagon */
            grid_face_add_new(g, 12);
            d = grid_get_dot(g, points, px + (  a    ), py - (2*a + b)); grid_face_set_dot(g, d, 0);
            d = grid_get_dot(g, points, px + (  a + b), py - (  a + b)); grid_face_set_dot(g, d, 1);
            d = grid_get_dot(g, points, px + (2*a + b), py - (  a    )); grid_face_set_dot(g, d, 2);
            d = grid_get_dot(g, points, px + (2*a + b), py + (  a    )); grid_face_set_dot(g, d, 3);
            d = grid_get_dot(g, points, px + (  a + b), py + (  a + b)); grid_face_set_dot(g, d, 4);
            d = grid_get_dot(g, points, px + (  a    ), py + (2*a + b)); grid_face_set_dot(g, d, 5);
            d = grid_get_dot(g, points, px - (  a    ), py + (2*a + b)); grid_face_set_dot(g, d, 6);
            d = grid_get_dot(g, points, px - (  a + b), py + (  a + b)); grid_face_set_dot(g, d, 7);
            d = grid_get_dot(g, points, px - (2*a + b), py + (  a    )); grid_face_set_dot(g, d, 8);
            d = grid_get_dot(g, points, px - (2*a + b), py - (  a    )); grid_face_set_dot(g, d, 9);
            d = grid_get_dot(g, points, px - (  a + b), py - (  a + b)); grid_face_set_dot(g, d, 10);
            d = grid_get_dot(g, points, px - (  a    ), py - (2*a + b)); grid_face_set_dot(g, d, 11);

            /* triangle below dodecagon */
	    if ((y < height - 1 && (x < width - 1 || !(y % 2)) && (x > 0 || (y % 2)))) {
	      	grid_face_add_new(g, 3);
	      	d = grid_get_dot(g, points, px + a, py + (2*a +   b)); grid_face_set_dot(g, d, 0);
	      	d = grid_get_dot(g, points, px    , py + (2*a + 2*b)); grid_face_set_dot(g, d, 1);
	      	d = grid_get_dot(g, points, px - a, py + (2*a +   b)); grid_face_set_dot(g, d, 2);
	    }

            /* triangle above dodecagon */
	    if ((y && (x < width - 1 || !(y % 2)) && (x > 0 || (y % 2)))) {
	      	grid_face_add_new(g, 3);
	      	d = grid_get_dot(g, points, px - a, py - (2*a +   b)); grid_face_set_dot(g, d, 0);
	      	d = grid_get_dot(g, points, px    , py - (2*a + 2*b)); grid_face_set_dot(g, d, 1);
	      	d = grid_get_dot(g, points, px + a, py - (2*a +   b)); grid_face_set_dot(g, d, 2);
	    }
	}
    }

    freetree234(points);

    grid_make_consistent(g);
    return g;
}

static const char *grid_validate_params_greatdodecagonal(int width, int height)
{
    int a = DODEC_A;
    int b = DODEC_B;

    if (width - 1 > (INT_MAX - (2*(2*a + b) + 3*a + b)) / (6*a + 2*b) ||
        height - 1 > (INT_MAX - 2*(2*a + b)) / (3*a + 3*b) || /* yextent */
        width > INT_MAX / 200 / height) /* max_dots */
        return "Grid size must not be unreasonably large";
    return NULL;
}

static void grid_size_greatdodecagonal(int width, int height,
                          int *tilesize, int *xextent, int *yextent)
{
    int a = DODEC_A;
    int b = DODEC_B;

    *tilesize = DODEC_TILESIZE;
    *xextent = (6*a + 2*b) * (width-1) + 2*(2*a + b) + 3*a + b;
    *yextent = (3*a + 3*b) * (height-1) + 2*(2*a + b);
}

static grid *grid_new_greatdodecagonal(int width, int height, const char *desc)
{
    int x, y;
    /* Vector for side of triangle - ratio is close to sqrt(3) */
    int a = DODEC_A;
    int b = DODEC_B;

    tree234 *points;

    grid *g = grid_empty();
    g->tilesize = DODEC_TILESIZE;

    points = newtree234(grid_point_cmp_fn);

    for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
            grid_dot *d;
            /* centre of dodecagon */
            int px = (6*a + 2*b) * x;
            int py = (3*a + 3*b) * y;
            if (y % 2)
                px += 3*a + b;

            /* dodecagon */
            grid_face_add_new(g, 12);
            d = grid_get_dot(g, points, px + (  a    ), py - (2*a + b)); grid_face_set_dot(g, d, 0);
            d = grid_get_dot(g, points, px + (  a + b), py - (  a + b)); grid_face_set_dot(g, d, 1);
            d = grid_get_dot(g, points, px + (2*a + b), py - (  a    )); grid_face_set_dot(g, d, 2);
            d = grid_get_dot(g, points, px + (2*a + b), py + (  a    )); grid_face_set_dot(g, d, 3);
            d = grid_get_dot(g, points, px + (  a + b), py + (  a + b)); grid_face_set_dot(g, d, 4);
            d = grid_get_dot(g, points, px + (  a    ), py + (2*a + b)); grid_face_set_dot(g, d, 5);
            d = grid_get_dot(g, points, px - (  a    ), py + (2*a + b)); grid_face_set_dot(g, d, 6);
            d = grid_get_dot(g, points, px - (  a + b), py + (  a + b)); grid_face_set_dot(g, d, 7);
            d = grid_get_dot(g, points, px - (2*a + b), py + (  a    )); grid_face_set_dot(g, d, 8);
            d = grid_get_dot(g, points, px - (2*a + b), py - (  a    )); grid_face_set_dot(g, d, 9);
            d = grid_get_dot(g, points, px - (  a + b), py - (  a + b)); grid_face_set_dot(g, d, 10);
            d = grid_get_dot(g, points, px - (  a    ), py - (2*a + b)); grid_face_set_dot(g, d, 11);

            /* hexagon below dodecagon */
	    if (y < height - 1 && (x < width - 1 || !(y % 2)) && (x > 0 || (y % 2))) {
	      	grid_face_add_new(g, 6);
	      	d = grid_get_dot(g, points, px +   a, py + (2*a +   b)); grid_face_set_dot(g, d, 0);
	      	d = grid_get_dot(g, points, px + 2*a, py + (2*a + 2*b)); grid_face_set_dot(g, d, 1);
	      	d = grid_get_dot(g, points, px +   a, py + (2*a + 3*b)); grid_face_set_dot(g, d, 2);
	      	d = grid_get_dot(g, points, px -   a, py + (2*a + 3*b)); grid_face_set_dot(g, d, 3);
	      	d = grid_get_dot(g, points, px - 2*a, py + (2*a + 2*b)); grid_face_set_dot(g, d, 4);
	      	d = grid_get_dot(g, points, px -   a, py + (2*a +   b)); grid_face_set_dot(g, d, 5);
	    }

            /* hexagon above dodecagon */
	    if (y && (x < width - 1 || !(y % 2)) && (x > 0 || (y % 2))) {
	      	grid_face_add_new(g, 6);
	      	d = grid_get_dot(g, points, px -   a, py - (2*a +   b)); grid_face_set_dot(g, d, 0);
	      	d = grid_get_dot(g, points, px - 2*a, py - (2*a + 2*b)); grid_face_set_dot(g, d, 1);
	      	d = grid_get_dot(g, points, px -   a, py - (2*a + 3*b)); grid_face_set_dot(g, d, 2);
	      	d = grid_get_dot(g, points, px +   a, py - (2*a + 3*b)); grid_face_set_dot(g, d, 3);
	      	d = grid_get_dot(g, points, px + 2*a, py - (2*a + 2*b)); grid_face_set_dot(g, d, 4);
	      	d = grid_get_dot(g, points, px +   a, py - (2*a +   b)); grid_face_set_dot(g, d, 5);
	    }

            /* square on right of dodecagon */
	    if (x < width - 1) {
	      	grid_face_add_new(g, 4);
	      	d = grid_get_dot(g, points, px + 2*a + b, py - a); grid_face_set_dot(g, d, 0);
	      	d = grid_get_dot(g, points, px + 4*a + b, py - a); grid_face_set_dot(g, d, 1);
	      	d = grid_get_dot(g, points, px + 4*a + b, py + a); grid_face_set_dot(g, d, 2);
	      	d = grid_get_dot(g, points, px + 2*a + b, py + a); grid_face_set_dot(g, d, 3);
	    }

            /* square on top right of dodecagon */
	    if (y && (x < width - 1 || !(y % 2))) {
	      	grid_face_add_new(g, 4);
	      	d = grid_get_dot(g, points, px + (  a    ), py - (2*a +   b)); grid_face_set_dot(g, d, 0);
		d = grid_get_dot(g, points, px + (2*a    ), py - (2*a + 2*b)); grid_face_set_dot(g, d, 1);
		d = grid_get_dot(g, points, px + (2*a + b), py - (  a + 2*b)); grid_face_set_dot(g, d, 2);
		d = grid_get_dot(g, points, px + (  a + b), py - (  a +   b)); grid_face_set_dot(g, d, 3);
	    }

            /* square on top left of dodecagon */
	    if (y && (x || (y % 2))) {
	      	grid_face_add_new(g, 4);
		d = grid_get_dot(g, points, px - (  a + b), py - (  a +   b)); grid_face_set_dot(g, d, 0);
		d = grid_get_dot(g, points, px - (2*a + b), py - (  a + 2*b)); grid_face_set_dot(g, d, 1);
		d = grid_get_dot(g, points, px - (2*a    ), py - (2*a + 2*b)); grid_face_set_dot(g, d, 2);
	      	d = grid_get_dot(g, points, px - (  a    ), py - (2*a +   b)); grid_face_set_dot(g, d, 3);
	    }
	}
    }

    freetree234(points);

    grid_make_consistent(g);
    return g;
}

static const char *grid_validate_params_greatgreatdodecagonal(
    int width, int height)
{
    int a = DODEC_A;
    int b = DODEC_B;

    if (width-1 > (INT_MAX - (2*(2*a + b) + 2*a + 2*b)) / (4*a + 4*b) ||
        height-1 > (INT_MAX - 2*(2*a + b)) / (6*a + 2*b) || /* yextent */
        width > INT_MAX / 300 / height) /* max_dots */
        return "Grid size must not be unreasonably large";
    return NULL;
}

static void grid_size_greatgreatdodecagonal(int width, int height,
                                            int *tilesize, int *xextent, int *yextent)
{
    int a = DODEC_A;
    int b = DODEC_B;

    *tilesize = DODEC_TILESIZE;
    *xextent = (4*a + 4*b) * (width-1) + 2*(2*a + b) + 2*a + 2*b;
    *yextent = (6*a + 2*b) * (height-1) + 2*(2*a + b);
}

static grid *grid_new_greatgreatdodecagonal(int width, int height, const char *desc)
{
    int x, y;
    /* Vector for side of triangle - ratio is close to sqrt(3) */
    int a = DODEC_A;
    int b = DODEC_B;

    tree234 *points;

    grid *g = grid_empty();
    g->tilesize = DODEC_TILESIZE;

    points = newtree234(grid_point_cmp_fn);

    for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
            grid_dot *d;
            /* centre of dodecagon */
            int px = (4*a + 4*b) * x;
            int py = (6*a + 2*b) * y;
            if (y % 2)
                px += 2*a + 2*b;

            /* dodecagon */
            grid_face_add_new(g, 12);
            d = grid_get_dot(g, points, px + (  a    ), py - (2*a + b)); grid_face_set_dot(g, d, 0);
            d = grid_get_dot(g, points, px + (  a + b), py - (  a + b)); grid_face_set_dot(g, d, 1);
            d = grid_get_dot(g, points, px + (2*a + b), py - (  a    )); grid_face_set_dot(g, d, 2);
            d = grid_get_dot(g, points, px + (2*a + b), py + (  a    )); grid_face_set_dot(g, d, 3);
            d = grid_get_dot(g, points, px + (  a + b), py + (  a + b)); grid_face_set_dot(g, d, 4);
            d = grid_get_dot(g, points, px + (  a    ), py + (2*a + b)); grid_face_set_dot(g, d, 5);
            d = grid_get_dot(g, points, px - (  a    ), py + (2*a + b)); grid_face_set_dot(g, d, 6);
            d = grid_get_dot(g, points, px - (  a + b), py + (  a + b)); grid_face_set_dot(g, d, 7);
            d = grid_get_dot(g, points, px - (2*a + b), py + (  a    )); grid_face_set_dot(g, d, 8);
            d = grid_get_dot(g, points, px - (2*a + b), py - (  a    )); grid_face_set_dot(g, d, 9);
            d = grid_get_dot(g, points, px - (  a + b), py - (  a + b)); grid_face_set_dot(g, d, 10);
            d = grid_get_dot(g, points, px - (  a    ), py - (2*a + b)); grid_face_set_dot(g, d, 11);

            /* hexagon on top right of dodecagon */
            if (y && (x < width - 1 || !(y % 2))) {
                grid_face_add_new(g, 6);
                d = grid_get_dot(g, points, px + (a + 2*b), py - (4*a + b)); grid_face_set_dot(g, d, 0);
                d = grid_get_dot(g, points, px + (a + 2*b), py - (2*a + b)); grid_face_set_dot(g, d, 1);
                d = grid_get_dot(g, points, px + (a +   b), py - (  a + b)); grid_face_set_dot(g, d, 2);
                d = grid_get_dot(g, points, px + (a      ), py - (2*a + b)); grid_face_set_dot(g, d, 3);
                d = grid_get_dot(g, points, px + (a      ), py - (4*a + b)); grid_face_set_dot(g, d, 4);
                d = grid_get_dot(g, points, px + (a +   b), py - (5*a + b)); grid_face_set_dot(g, d, 5);
            }

            /* hexagon on right of dodecagon*/
            if (x < width - 1) {
                grid_face_add_new(g, 6);
                d = grid_get_dot(g, points, px + (2*a + 3*b), py -   a); grid_face_set_dot(g, d, 0);
                d = grid_get_dot(g, points, px + (2*a + 3*b), py +   a); grid_face_set_dot(g, d, 1);
                d = grid_get_dot(g, points, px + (2*a + 2*b), py + 2*a); grid_face_set_dot(g, d, 2);
                d = grid_get_dot(g, points, px + (2*a +   b), py +   a); grid_face_set_dot(g, d, 3);
                d = grid_get_dot(g, points, px + (2*a +   b), py -   a); grid_face_set_dot(g, d, 4);
                d = grid_get_dot(g, points, px + (2*a + 2*b), py - 2*a); grid_face_set_dot(g, d, 5);
            }

            /* hexagon on bottom right of dodecagon */
            if ((y < height - 1) && (x < width - 1 || !(y % 2))) {
                grid_face_add_new(g, 6);
                d = grid_get_dot(g, points, px + (a + 2*b), py + (2*a + b)); grid_face_set_dot(g, d, 0);
                d = grid_get_dot(g, points, px + (a + 2*b), py + (4*a + b)); grid_face_set_dot(g, d, 1);
                d = grid_get_dot(g, points, px + (a +   b), py + (5*a + b)); grid_face_set_dot(g, d, 2);
                d = grid_get_dot(g, points, px + (a      ), py + (4*a + b)); grid_face_set_dot(g, d, 3);
                d = grid_get_dot(g, points, px + (a      ), py + (2*a + b)); grid_face_set_dot(g, d, 4);
                d = grid_get_dot(g, points, px + (a +   b), py + (  a + b)); grid_face_set_dot(g, d, 5);
            }

            /* square on top right of dodecagon */
            if (y && (x < width - 1 )) {
                grid_face_add_new(g, 4);
                d = grid_get_dot(g, points, px + (  a + 2*b), py - (2*a +   b)); grid_face_set_dot(g, d, 0);
                d = grid_get_dot(g, points, px + (2*a + 2*b), py - (2*a      )); grid_face_set_dot(g, d, 1);
                d = grid_get_dot(g, points, px + (2*a +   b), py - (  a      )); grid_face_set_dot(g, d, 2);
                d = grid_get_dot(g, points, px + (  a +   b), py - (  a +   b)); grid_face_set_dot(g, d, 3);
            }

            /* square on bottom right of dodecagon */
            if ((y < height - 1) && (x < width - 1 )) {
                grid_face_add_new(g, 4);
                d = grid_get_dot(g, points, px + (2*a + 2*b), py + (2*a      )); grid_face_set_dot(g, d, 0);
                d = grid_get_dot(g, points, px + (  a + 2*b), py + (2*a +   b)); grid_face_set_dot(g, d, 1);
                d = grid_get_dot(g, points, px + (  a +   b), py + (  a +   b)); grid_face_set_dot(g, d, 2);
                d = grid_get_dot(g, points, px + (2*a +   b), py + (  a      )); grid_face_set_dot(g, d, 3);
            }

            /* square below dodecagon */
            if ((y < height - 1) && (x < width - 1 || !(y % 2)) && (x > 0 || (y % 2))) {
                grid_face_add_new(g, 4);
                d = grid_get_dot(g, points, px + a, py + (2*a + b)); grid_face_set_dot(g, d, 0);
                d = grid_get_dot(g, points, px + a, py + (4*a + b)); grid_face_set_dot(g, d, 1);
                d = grid_get_dot(g, points, px - a, py + (4*a + b)); grid_face_set_dot(g, d, 2);
                d = grid_get_dot(g, points, px - a, py + (2*a + b)); grid_face_set_dot(g, d, 3);
            }

            /* square on bottom left of dodecagon */
            if (x && (y < height - 1)) {
                grid_face_add_new(g, 4);
                d = grid_get_dot(g, points, px - (2*a +   b), py + (  a      )); grid_face_set_dot(g, d, 0);
                d = grid_get_dot(g, points, px - (  a +   b), py + (  a +   b)); grid_face_set_dot(g, d, 1);
                d = grid_get_dot(g, points, px - (  a + 2*b), py + (2*a +   b)); grid_face_set_dot(g, d, 2);
                d = grid_get_dot(g, points, px - (2*a + 2*b), py + (2*a      )); grid_face_set_dot(g, d, 3);
            }

            /* square on top left of dodecagon */
            if (x && y) {
                grid_face_add_new(g, 4);
                d = grid_get_dot(g, points, px - (  a +   b), py - (  a +   b)); grid_face_set_dot(g, d, 0);
                d = grid_get_dot(g, points, px - (2*a +   b), py - (  a      )); grid_face_set_dot(g, d, 1);
                d = grid_get_dot(g, points, px - (2*a + 2*b), py - (2*a      )); grid_face_set_dot(g, d, 2);
                d = grid_get_dot(g, points, px - (  a + 2*b), py - (2*a +   b)); grid_face_set_dot(g, d, 3);

            }

            /* square above dodecagon */
            if (y && (x < width - 1 || !(y % 2)) && (x > 0 || (y % 2))) {
                grid_face_add_new(g, 4);
                d = grid_get_dot(g, points, px + a, py - (4*a + b)); grid_face_set_dot(g, d, 0);
                d = grid_get_dot(g, points, px + a, py - (2*a + b)); grid_face_set_dot(g, d, 1);
                d = grid_get_dot(g, points, px - a, py - (2*a + b)); grid_face_set_dot(g, d, 2);
                d = grid_get_dot(g, points, px - a, py - (4*a + b)); grid_face_set_dot(g, d, 3);
            }

            /* upper triangle (v) */
            if (y && (x < width - 1)) {
                grid_face_add_new(g, 3);
                d = grid_get_dot(g, points, px + (3*a + 2*b), py - (2*a +   b)); grid_face_set_dot(g, d, 0);
                d = grid_get_dot(g, points, px + (2*a + 2*b), py - (2*a      )); grid_face_set_dot(g, d, 1);
                d = grid_get_dot(g, points, px + (  a + 2*b), py - (2*a +   b)); grid_face_set_dot(g, d, 2);
            }

            /* lower triangle (^) */
            if ((y < height - 1) && (x < width - 1)) {
                grid_face_add_new(g, 3);
                d = grid_get_dot(g, points, px + (3*a + 2*b), py + (2*a +   b)); grid_face_set_dot(g, d, 0);
                d = grid_get_dot(g, points, px + (  a + 2*b), py + (2*a +   b)); grid_face_set_dot(g, d, 1);
                d = grid_get_dot(g, points, px + (2*a + 2*b), py + (2*a      )); grid_face_set_dot(g, d, 2);
            }
        }
    }

    freetree234(points);

    grid_make_consistent(g);
    return g;
}

static const char *grid_validate_params_compassdodecagonal(
    int width, int height)
{
    int a = DODEC_A;
    int b = DODEC_B;

    if (width > INT_MAX / (4*a + 2*b) ||  /* xextent */
        height > INT_MAX / (4*a + 2*b) || /* yextent */
        width > INT_MAX / 18 / height)    /* max_dots */
        return "Grid must not be unreasonably large";
    return NULL;
}

static void grid_size_compassdodecagonal(int width, int height,
                                         int *tilesize, int *xextent, int *yextent)
{
    int a = DODEC_A;
    int b = DODEC_B;

    *tilesize = DODEC_TILESIZE;
    *xextent = (4*a + 2*b) * width;
    *yextent = (4*a + 2*b) * height;
}

static grid *grid_new_compassdodecagonal(int width, int height, const char *desc)
{
    int x, y;
    /* Vector for side of triangle - ratio is close to sqrt(3) */
    int a = DODEC_A;
    int b = DODEC_B;

    tree234 *points;

    grid *g = grid_empty();
    g->tilesize = DODEC_TILESIZE;

    points = newtree234(grid_point_cmp_fn);

    for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
            grid_dot *d;
            /* centre of dodecagon */
            int px = (4*a + 2*b) * x;
            int py = (4*a + 2*b) * y;

            /* dodecagon */
            grid_face_add_new(g, 12);
            d = grid_get_dot(g, points, px + (  a    ), py - (2*a + b)); grid_face_set_dot(g, d, 0);
            d = grid_get_dot(g, points, px + (  a + b), py - (  a + b)); grid_face_set_dot(g, d, 1);
            d = grid_get_dot(g, points, px + (2*a + b), py - (  a    )); grid_face_set_dot(g, d, 2);
            d = grid_get_dot(g, points, px + (2*a + b), py + (  a    )); grid_face_set_dot(g, d, 3);
            d = grid_get_dot(g, points, px + (  a + b), py + (  a + b)); grid_face_set_dot(g, d, 4);
            d = grid_get_dot(g, points, px + (  a    ), py + (2*a + b)); grid_face_set_dot(g, d, 5);
            d = grid_get_dot(g, points, px - (  a    ), py + (2*a + b)); grid_face_set_dot(g, d, 6);
            d = grid_get_dot(g, points, px - (  a + b), py + (  a + b)); grid_face_set_dot(g, d, 7);
            d = grid_get_dot(g, points, px - (2*a + b), py + (  a    )); grid_face_set_dot(g, d, 8);
            d = grid_get_dot(g, points, px - (2*a + b), py - (  a    )); grid_face_set_dot(g, d, 9);
            d = grid_get_dot(g, points, px - (  a + b), py - (  a + b)); grid_face_set_dot(g, d, 10);
            d = grid_get_dot(g, points, px - (  a    ), py - (2*a + b)); grid_face_set_dot(g, d, 11);

            if (x < width - 1 && y < height - 1) {
                /* north triangle */
                grid_face_add_new(g, 3);
                d = grid_get_dot(g, points, px + (2*a + b), py + (  a    )); grid_face_set_dot(g, d, 0);
                d = grid_get_dot(g, points, px + (3*a + b), py + (  a + b)); grid_face_set_dot(g, d, 1);
                d = grid_get_dot(g, points, px + (  a + b), py + (  a + b)); grid_face_set_dot(g, d, 2);

                /* east triangle */
                grid_face_add_new(g, 3);
                d = grid_get_dot(g, points, px + (3*a + 2*b), py + (2*a + b)); grid_face_set_dot(g, d, 0);
                d = grid_get_dot(g, points, px + (3*a +   b), py + (3*a + b)); grid_face_set_dot(g, d, 1);
                d = grid_get_dot(g, points, px + (3*a +   b), py + (  a + b)); grid_face_set_dot(g, d, 2);

                /* south triangle */
                grid_face_add_new(g, 3);
                d = grid_get_dot(g, points, px + (3*a + b), py + (3*a +   b)); grid_face_set_dot(g, d, 0);
                d = grid_get_dot(g, points, px + (2*a + b), py + (3*a + 2*b)); grid_face_set_dot(g, d, 1);
                d = grid_get_dot(g, points, px + (  a + b), py + (3*a +   b)); grid_face_set_dot(g, d, 2);

                /* west triangle */
                grid_face_add_new(g, 3);
                d = grid_get_dot(g, points, px + (a + b), py + (  a + b)); grid_face_set_dot(g, d, 0);
                d = grid_get_dot(g, points, px + (a + b), py + (3*a + b)); grid_face_set_dot(g, d, 1);
                d = grid_get_dot(g, points, px + (a    ), py + (2*a + b)); grid_face_set_dot(g, d, 2);

                /* square in center */
                grid_face_add_new(g, 4);
                d = grid_get_dot(g, points, px + (3*a + b), py + (  a + b)); grid_face_set_dot(g, d, 0);
                d = grid_get_dot(g, points, px + (3*a + b), py + (3*a + b)); grid_face_set_dot(g, d, 1);
                d = grid_get_dot(g, points, px + (  a + b), py + (3*a + b)); grid_face_set_dot(g, d, 2);
                d = grid_get_dot(g, points, px + (  a + b), py + (  a + b)); grid_face_set_dot(g, d, 3);
            }
        }
    }

    freetree234(points);

    grid_make_consistent(g);
    return g;
}

/*
 * Penrose tilings. For historical reasons, we support two totally
 * different generation algorithms: the legacy one is only supported
 * by grid_new_penrose, for backwards compatibility with game
 * descriptions generated before we switched. New grid generation uses
 * only the new system.
 */

#define PENROSE_TILESIZE 100

static const char *grid_validate_params_penrose(int width, int height)
{
    int l = PENROSE_TILESIZE;

    if (width > INT_MAX / l ||                  /* xextent */
        height > INT_MAX / l ||                 /* yextent */
        width > INT_MAX / (3 * 3 * 4 * height)) /* max_dots */
        return "Grid must not be unreasonably large";
    return NULL;
}

static void grid_size_penrose(int width, int height,
                              int *tilesize, int *xextent, int *yextent)
{
    int l = PENROSE_TILESIZE;

    *tilesize = l;
    *xextent = l * width;
    *yextent = l * height;
}

/*
 * Legacy generation by selecting a patch of tiling from the expansion
 * of a big triangle.
 */

typedef struct penrose_legacy_set_faces_ctx {
    int xmin, xmax, ymin, ymax;

    grid *g;
    tree234 *points;
} penrose_legacy_set_faces_ctx;

static double round_int_nearest_away(double r)
{
    return (r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5);
}

static int penrose_legacy_set_faces(penrose_legacy_state *state, vector *vs,
                                    int n, int depth)
{
    penrose_legacy_set_faces_ctx *sf_ctx =
        (penrose_legacy_set_faces_ctx *)state->ctx;
    int i;
    int xs[4], ys[4];

    if (depth < state->max_depth) return 0;
#ifdef DEBUG_PENROSE
    if (n != 4) return 0; /* triangles are sent as debugging. */
#endif

    for (i = 0; i < n; i++) {
        double tx = penrose_legacy_vx(vs, i), ty = penrose_legacy_vy(vs, i);

        xs[i] = (int)round_int_nearest_away(tx);
        ys[i] = (int)round_int_nearest_away(ty);

        if (xs[i] < sf_ctx->xmin || xs[i] > sf_ctx->xmax) return 0;
        if (ys[i] < sf_ctx->ymin || ys[i] > sf_ctx->ymax) return 0;
    }

    grid_face_add_new(sf_ctx->g, n);
    debug(("penrose: new face l=%f gen=%d...",
           penrose_legacy_side_length(state->start_size, depth), depth));
    for (i = 0; i < n; i++) {
        grid_dot *d = grid_get_dot(sf_ctx->g, sf_ctx->points,
                                   xs[i], ys[i]);
        grid_face_set_dot(sf_ctx->g, d, i);
        debug((" ... dot 0x%x (%d,%d) (was %2.2f,%2.2f)",
               d, d->x, d->y, penrose_legacy_vx(vs, i),
               penrose_legacy_vy(vs, i)));
    }

    return 0;
}

static grid *grid_new_penrose_legacy(int width, int height, int which,
                                     const char *desc);

static const char *grid_validate_desc_penrose_legacy(
    grid_type type, int width, int height, const char *desc)
{
    int tilesize = PENROSE_TILESIZE, startsz, depth, xoff, yoff, aoff, inner_radius;
    double outer_radius;
    int which = (type == GRID_PENROSE_P2 ? PENROSE_P2 : PENROSE_P3);
    grid *g;

    if (!desc)
        return "Missing grid description string.";

    penrose_legacy_calculate_size(which, tilesize, width, height,
                                  &outer_radius, &startsz, &depth);
    inner_radius = (int)(outer_radius - sqrt(width*width + height*height));

    if (sscanf(desc, "G%d,%d,%d", &xoff, &yoff, &aoff) != 3)
        return "Invalid format grid description string.";

    if (sqrt(xoff*xoff + yoff*yoff) > inner_radius)
        return "Patch offset out of bounds.";
    if ((aoff % 36) != 0 || aoff < 0 || aoff >= 360)
        return "Angle offset out of bounds.";

    /*
     * Test-generate to ensure these parameters don't end us up with
     * no grid at all.
     */
    g = grid_new_penrose_legacy(width, height, which, desc);
    if (!g)
        return "Patch coordinates do not identify a usable grid fragment";
    grid_free(g);

    return NULL;
}

static grid *grid_new_penrose_legacy(int width, int height, int which,
                                     const char *desc)
{
    int tilesize = PENROSE_TILESIZE;
    int xsz, ysz, xoff, yoff, aoff;
    double rradius;

    tree234 *points;
    grid *g;

    penrose_legacy_state ps;
    penrose_legacy_set_faces_ctx sf_ctx;

    penrose_legacy_calculate_size(which, tilesize, width, height,
                                  &rradius, &ps.start_size, &ps.max_depth);

    debug(("penrose: w%d h%d, tile size %d, start size %d, depth %d",
           width, height, tilesize, ps.start_size, ps.max_depth));

    ps.new_tile = penrose_legacy_set_faces;
    ps.ctx = &sf_ctx;

    g = grid_empty();
    g->tilesize = tilesize;

    points = newtree234(grid_point_cmp_fn);

    memset(&sf_ctx, 0, sizeof(sf_ctx));
    sf_ctx.g = g;
    sf_ctx.points = points;

    if (desc != NULL) {
        if (sscanf(desc, "G%d,%d,%d", &xoff, &yoff, &aoff) != 3)
            assert(!"Invalid grid description.");
    } else {
        xoff = yoff = aoff = 0;
    }

    xsz = width * tilesize;
    ysz = height * tilesize;

    sf_ctx.xmin = xoff - xsz/2;
    sf_ctx.xmax = xoff + xsz/2;
    sf_ctx.ymin = yoff - ysz/2;
    sf_ctx.ymax = yoff + ysz/2;

    debug(("penrose: centre (%f, %f) xsz %f ysz %f",
           0.0, 0.0, xsz, ysz));
    debug(("penrose: x range (%f --> %f), y range (%f --> %f)",
           sf_ctx.xmin, sf_ctx.xmax, sf_ctx.ymin, sf_ctx.ymax));

    penrose_legacy(&ps, which, aoff);

    freetree234(points);

    debug(("penrose: %d faces total (equivalent to %d wide by %d high)",
           g->num_faces, g->num_faces/height, g->num_faces/width));

    /*
     * Return NULL if we ended up with an empty grid, either because
     * the initial generation was over too small a rectangle to
     * encompass any face or because grid_trim_vigorously ended up
     * removing absolutely everything.
     */
    if (g->num_faces == 0 || g->num_dots == 0) {
        grid_free(g);
        return NULL;
    }
    grid_trim_vigorously(g);
    if (g->num_faces == 0 || g->num_dots == 0) {
        grid_free(g);
        return NULL;
    }

    grid_make_consistent(g);

    /*
     * Centre the grid in its originally promised rectangle.
     */
    g->lowest_x -= ((sf_ctx.xmax - sf_ctx.xmin) -
                    (g->highest_x - g->lowest_x)) / 2;
    g->highest_x = g->lowest_x + (sf_ctx.xmax - sf_ctx.xmin);
    g->lowest_y -= ((sf_ctx.ymax - sf_ctx.ymin) -
                    (g->highest_y - g->lowest_y)) / 2;
    g->highest_y = g->lowest_y + (sf_ctx.ymax - sf_ctx.ymin);

    return g;
}

/*
 * Combinatorial-coordinate generation.
 *
 * We receive coordinates from the generator in the form of x,y pairs
 * each of which is an integer combination of 1 and sqrt(5), but those
 * pairs have different scale units in the x and y directions. The
 * scale units are 1/4 for x and sin(pi/5)/2 for y, which makes their
 * ratio equal to 2 sin(pi/5) ~= 1.1756. We fudge that irrational
 * aspect ratio into a rational approximation, by simply taking a pair
 * of integer scale factors for the x and y dimensions; this distorts
 * the output tiling slightly, but the distortion is consistent, and
 * doesn't accumulate over a large patch of tiling, so it won't make
 * anything end up totally out of place.
 *
 * (However, we compute the subsequent combination of 1 and sqrt(5)
 * exactly, because using an approximation to sqrt(5) _could_ mean
 * that in a sufficiently large patch of tiling two such combinations
 * ended up misordered.)
 *
 * Adding to the confusion, we also flip round the x and y
 * coordinates, because it's slightly nicer to have vertical edges in
 * the tiling rather than horizontal ones. (Both for aesthetics, and
 * also because if two P3 thin rhombs are separated by a horizontal
 * line and both contain numeric clues then the clue numbers look a
 * bit crowded, due to digits being taller than they are wide.)
 *
 * Finally, we have different base unit sizes for the two tiling
 * types, because sensible sizes for the two are actually different.
 * Each of P2 and P3 can be subdivided into the other, via dividing
 * the larger triangle type in two, so that L large and S small become
 * L+S large and L small. In the limit, this means that you expect the
 * number of triangles (hence tiles) to grow by a factor of phi in
 * each of those subdivisions (and hence by a factor of phi^2 in a
 * full subdivision of P2 to a finer P2). So a sensible size ratio
 * between the two tilings is one that makes them fit about the same
 * number of tiles into the same area - and since tile area is
 * proportional to _square_ of length, it follows that the P2 and P3
 * length unit should differ by a factor of sqrt(phi).
 */
#define PENROSE_XUNIT_P2 37
#define PENROSE_YUNIT_P2 44
#define PENROSE_XUNIT_P3 30
#define PENROSE_YUNIT_P3 35

struct size { int w, h; };
static struct size api_size_penrose(int width, int height, int which)
{
    int xunit = (which == PENROSE_P2 ? PENROSE_XUNIT_P2 : PENROSE_XUNIT_P3);
    int yunit = (which == PENROSE_P2 ? PENROSE_YUNIT_P2 : PENROSE_YUNIT_P3);
    struct size size = {
        width * PENROSE_TILESIZE / yunit,
        height * PENROSE_TILESIZE / xunit,
    };
    return size;
}

static grid *grid_new_penrose(int width, int height, int which,
                              const char *desc); /* forward reference */

static char *grid_new_desc_penrose(grid_type type, int width, int height,
                                   random_state *rs)
{
    char *buf;
    struct PenrosePatchParams params;
    int which = (type == GRID_PENROSE_P2 ? PENROSE_P2 : PENROSE_P3);
    struct size size = api_size_penrose(width, height, which);

    penrose_tiling_randomise(&params, which, size.h, size.w, rs);

    buf = snewn(params.ncoords + 3, char);
    buf[0] = '0' + params.orientation;
    buf[1] = '0' + params.start_vertex;
    memcpy(buf + 2, params.coords, params.ncoords);
    buf[2 + params.ncoords] = '\0';

    sfree(params.coords);
    return buf;
}

/* Shared code between validating and reading grid descs.
 * Always allocates params->coords, whether or not it returns an error. */
static const char *grid_desc_to_penrose_params(
    const char *desc, int which, struct PenrosePatchParams *params)
{
    int i;

    if (!*desc)
        return "empty grid description";

    params->ncoords = strlen(desc) - 2;
    params->coords = snewn(params->ncoords, char);

    {
        char c = desc[0];
        if (isdigit((unsigned char)c))
            params->orientation = c - '0';
        else
            return "expected digit at start of grid description";

        c = desc[1];
        if (c >= '0' && c < '3')
            params->start_vertex = c - '0';
        else
            return "expected digit as second char of grid description";
    }

    for (i = 0; i < params->ncoords; i++) {
        char c = desc[i+2];
        if (!penrose_valid_letter(c, which))
            return "expected tile letter in grid description";
        params->coords[i] = c;
    }

    return NULL;
}

static const char *grid_validate_desc_penrose(grid_type type,
                                              int width, int height,
                                              const char *desc)
{
    struct PenrosePatchParams params;
    const char *error = NULL;
    int which = (type == GRID_PENROSE_P2 ? PENROSE_P2 : PENROSE_P3);

    if (!desc)
        return "Missing grid description string.";

    if (*desc == 'G')
        return grid_validate_desc_penrose_legacy(type, width, height, desc);

    error = grid_desc_to_penrose_params(desc, which, &params);
    if (!error)
        error = penrose_tiling_params_invalid(&params, which);

    sfree(params.coords);
    return error;
}

struct penrosecontext {
    grid *g;
    tree234 *points;
    int xunit, yunit;
};

static void grid_penrose_callback(void *vctx, const int *coords)
{
    struct penrosecontext *ctx = (struct penrosecontext *)vctx;
    size_t i;

    grid_face_add_new(ctx->g, 4);
    for (i = 0; i < 4; i++) {
        grid_dot *d = grid_get_dot(
            ctx->g, ctx->points,
            coords[4*i+2] * ctx->yunit + n_times_root_k(
                coords[4*i+3] * ctx->yunit, 5),
            coords[4*i+0] * ctx->xunit + n_times_root_k(
                coords[4*i+1] * ctx->xunit, 5));
        grid_face_set_dot(ctx->g, d, i);
    }
}

static grid *grid_new_penrose(int width, int height, int which,
                              const char *desc)
{
    struct PenrosePatchParams params;
    const char *error = NULL;
    struct penrosecontext ctx[1];
    struct size size;

    if (*desc == 'G')
        return grid_new_penrose_legacy(width, height, which, desc);

    error = grid_desc_to_penrose_params(desc, which, &params);
    assert(error == NULL && "grid_validate_desc_penrose should have failed");

    ctx->g = grid_empty();
    ctx->g->tilesize = PENROSE_TILESIZE;

    ctx->points = newtree234(grid_point_cmp_fn);

    ctx->xunit = (which == PENROSE_P2 ? PENROSE_XUNIT_P2 : PENROSE_XUNIT_P3);
    ctx->yunit = (which == PENROSE_P2 ? PENROSE_YUNIT_P2 : PENROSE_YUNIT_P3);

    size = api_size_penrose(width, height, which);
    penrose_tiling_generate(&params, size.h, size.w,
                            grid_penrose_callback, ctx);

    freetree234(ctx->points);
    sfree(params.coords);

    grid_trim_vigorously(ctx->g);
    grid_make_consistent(ctx->g);

    /*
     * Centre the grid in its originally promised rectangle.
     */
    {
        int w = width * PENROSE_TILESIZE, h = height * PENROSE_TILESIZE;
        ctx->g->lowest_x -= (w - (ctx->g->highest_x - ctx->g->lowest_x))/2;
        ctx->g->lowest_y -= (h - (ctx->g->highest_y - ctx->g->lowest_y))/2;
        ctx->g->highest_x = ctx->g->lowest_x + w;
        ctx->g->highest_y = ctx->g->lowest_y + h;
    }

    return ctx->g;
}

static const char *grid_validate_params_penrose_p2_kite(int width, int height)
{
    return grid_validate_params_penrose(width, height);
}

static const char *grid_validate_params_penrose_p3_thick(int width, int height)
{
    return grid_validate_params_penrose(width, height);
}

static void grid_size_penrose_p2_kite(int width, int height,
                       int *tilesize, int *xextent, int *yextent)
{
    grid_size_penrose(width, height, tilesize, xextent, yextent);
}

static void grid_size_penrose_p3_thick(int width, int height,
                       int *tilesize, int *xextent, int *yextent)
{
    grid_size_penrose(width, height, tilesize, xextent, yextent);
}

static grid *grid_new_penrose_p2_kite(int width, int height, const char *desc)
{
    return grid_new_penrose(width, height, PENROSE_P2, desc);
}

static grid *grid_new_penrose_p3_thick(int width, int height, const char *desc)
{
    return grid_new_penrose(width, height, PENROSE_P3, desc);
}

#define HATS_TILESIZE 32
#define HATS_XSQUARELEN 4
#define HATS_YSQUARELEN 6
#define HATS_XUNIT 14
#define HATS_YUNIT 8

static const char *grid_validate_params_hats(
    int width, int height)
{
    int l = HATS_TILESIZE;

    if (width > INT_MAX / l ||                  /* xextent */
        height > INT_MAX / l ||                 /* yextent */
        width > INT_MAX / (6 * height))         /* max_dots */
        return "Grid must not be unreasonably large";
    return NULL;
}

static void grid_size_hats(int width, int height,
                           int *tilesize, int *xextent, int *yextent)
{
    *tilesize = HATS_TILESIZE;
    *xextent = width * HATS_XUNIT * HATS_XSQUARELEN;
    *yextent = height * HATS_YUNIT * HATS_YSQUARELEN;
}

static char *grid_new_desc_hats(
    grid_type type, int width, int height, random_state *rs)
{
    char *buf, *p;
    size_t bufmax, i;
    struct HatPatchParams hp;

    hat_tiling_randomise(&hp, width, height, rs);

    bufmax = 3 * hp.ncoords + 2;
    buf = snewn(bufmax, char);
    p = buf;
    for (i = 0; i < hp.ncoords; i++) {
        assert(hp.coords[i] < 100);    /* at most 2 digits */
        assert(p - buf <= bufmax-4);   /* room for 2 digits, comma and NUL */
        p += sprintf(p, "%d,", (int)hp.coords[i]);
    }
    assert(p - buf <= bufmax-2);       /* room for final letter and NUL */
    p[0] = hp.final_metatile;
    p[1] = '\0';

    sfree(hp.coords);
    return buf;
}

/* Shared code between validating and reading grid descs.
 * Always allocates hp->coords, whether or not it returns an error. */
static const char *grid_desc_to_hat_params(
    const char *desc, struct HatPatchParams *hp)
{
    size_t maxcoords;
    const char *p = desc;

    maxcoords = (strlen(desc) + 1) / 2;
    hp->coords = snewn(maxcoords, unsigned char);
    hp->ncoords = 0;

    while (isdigit((unsigned char)*p)) {
        const char *p_orig = p;
        int n = atoi(p);
        while (*p && isdigit((unsigned char)*p)) p++;
        if (*p != ',')
            return "expected ',' in grid description";
        if (p - p_orig > 2 || n > 0xFF)
            return "too-large coordinate in grid description";
        p++; /* eat the comma */

        /* This assert should be guaranteed by the way we calculated
         * maxcoords, so a failure of this check is a bug in this
         * function, not an indication of an invalid input string */
        assert(hp->ncoords < maxcoords);
        hp->coords[hp->ncoords++] = n;
    }

    if (*p == 'H' || *p == 'T' || *p == 'P' || *p == 'F')
        hp->final_metatile = *p;
    else
        return "invalid character in grid description";

    return NULL;
}

static const char *grid_validate_desc_hats(
    grid_type type, int width, int height, const char *desc)
{
    struct HatPatchParams hp;
    const char *error = NULL;

    if (!desc)
        return "Missing grid description string.";

    error = grid_desc_to_hat_params(desc, &hp);
    if (!error)
        error = hat_tiling_params_invalid(&hp);

    sfree(hp.coords);
    return error;
}

struct hatcontext {
    grid *g;
    tree234 *points;
};

static void grid_hats_callback(void *vctx, size_t nvertices, int *coords)
{
    struct hatcontext *ctx = (struct hatcontext *)vctx;
    size_t i;

    grid_face_add_new(ctx->g, nvertices);
    for (i = 0; i < nvertices; i++) {
        grid_dot *d = grid_get_dot(
            ctx->g, ctx->points,
            coords[2*i] * HATS_XUNIT,
            coords[2*i+1] * HATS_YUNIT);
        grid_face_set_dot(ctx->g, d, i);
    }
}

static grid *grid_new_hats(int width, int height, const char *desc)
{
    struct HatPatchParams hp;
    const char *error = NULL;

    error = grid_desc_to_hat_params(desc, &hp);
    assert(error == NULL && "grid_validate_desc_hats should have failed");

    struct hatcontext ctx[1];

    ctx->g = grid_empty();
    ctx->g->tilesize = HATS_TILESIZE;

    ctx->points = newtree234(grid_point_cmp_fn);

    hat_tiling_generate(&hp, width, height, grid_hats_callback, ctx);

    freetree234(ctx->points);
    sfree(hp.coords);

    grid_trim_vigorously(ctx->g);
    grid_make_consistent(ctx->g);
    return ctx->g;
}

#define SPECTRE_TILESIZE 32
#define SPECTRE_SQUARELEN 7
#define SPECTRE_UNIT 8

static const char *grid_validate_params_spectres(
    int width, int height)
{
    int l = SPECTRE_UNIT * SPECTRE_SQUARELEN;

    if (width > INT_MAX / l ||                  /* xextent */
        height > INT_MAX / l ||                 /* yextent */
        width > (INT_MAX / SPECTRE_SQUARELEN /
                 SPECTRE_SQUARELEN / height))   /* max_faces */
        return "Grid must not be unreasonably large";
    return NULL;
}

static void grid_size_spectres(int width, int height,
                               int *tilesize, int *xextent, int *yextent)
{
    *tilesize = SPECTRE_TILESIZE;
    *xextent = width * SPECTRE_UNIT * SPECTRE_SQUARELEN;
    *yextent = height * SPECTRE_UNIT * SPECTRE_SQUARELEN;
}

static char *grid_new_desc_spectres(
    grid_type type, int width, int height, random_state *rs)
{
    char *buf;
    size_t i;
    struct SpectrePatchParams sp;

    spectre_tiling_randomise(&sp, width * SPECTRE_SQUARELEN,
                             height * SPECTRE_SQUARELEN, rs);

    buf = snewn(sp.ncoords + 3, char);
    buf[0] = (sp.orientation < 10 ? '0' + sp.orientation :
              'A' + sp.orientation - 10);
    for (i = 0; i < sp.ncoords; i++) {
        assert(sp.coords[i] < 10);    /* all indices are 1 digit */
        buf[i+1] = '0' + sp.coords[i];
    }
    buf[sp.ncoords+1] = sp.final_hex;
    buf[sp.ncoords+2] = '\0';

    sfree(sp.coords);
    return buf;
}

/* Shared code between validating and reading grid descs.
 * Always allocates sp->coords, whether or not it returns an error. */
static const char *grid_desc_to_spectre_params(
    const char *desc, struct SpectrePatchParams *sp)
{
    size_t i;

    if (!*desc)
        return "empty grid description";

    sp->ncoords = strlen(desc) - 2;
    sp->coords = snewn(sp->ncoords, unsigned char);

    {
        char c = desc[0];
        if (isdigit((unsigned char)c))
            sp->orientation = c - '0';
        else if (c == 'A' || c == 'B')
            sp->orientation = 10 + c - 'A';
        else
            return "expected digit or A,B at start of grid description";
    }

    for (i = 0; i < sp->ncoords; i++) {
        char c = desc[i+1];
        if (!isdigit((unsigned char)c))
            return "expected digit in grid description";
        sp->coords[i] = c - '0';
    }

    sp->final_hex = desc[sp->ncoords+1];

    return NULL;
}

static const char *grid_validate_desc_spectres(
    grid_type type, int width, int height, const char *desc)
{
    struct SpectrePatchParams sp;
    const char *error = NULL;

    if (!desc)
        return "Missing grid description string.";

    error = grid_desc_to_spectre_params(desc, &sp);
    if (!error)
        error = spectre_tiling_params_invalid(&sp);

    sfree(sp.coords);
    return error;
}

struct spectrecontext {
    grid *g;
    tree234 *points;
};

static void grid_spectres_callback(void *vctx, const int *coords)
{
    struct spectrecontext *ctx = (struct spectrecontext *)vctx;
    size_t i;

    grid_face_add_new(ctx->g, SPECTRE_NVERTICES);
    for (i = 0; i < SPECTRE_NVERTICES; i++) {
        grid_dot *d = grid_get_dot(
            ctx->g, ctx->points,
            (coords[4*i+0] * SPECTRE_UNIT +
             n_times_root_k(coords[4*i+1] * SPECTRE_UNIT, 3)),
            (coords[4*i+2] * SPECTRE_UNIT +
             n_times_root_k(coords[4*i+3] * SPECTRE_UNIT, 3)));
        grid_face_set_dot(ctx->g, d, i);
    }
}

static grid *grid_new_spectres(int width, int height, const char *desc)
{
    struct SpectrePatchParams sp;
    const char *error = NULL;
    int width2 = width * SPECTRE_SQUARELEN;
    int height2 = height * SPECTRE_SQUARELEN;

    error = grid_desc_to_spectre_params(desc, &sp);
    assert(error == NULL && "grid_validate_desc_spectres should have failed");

    struct spectrecontext ctx[1];

    ctx->g = grid_empty();
    ctx->g->tilesize = SPECTRE_TILESIZE;

    ctx->points = newtree234(grid_point_cmp_fn);

    spectre_tiling_generate(&sp, width2, height2, grid_spectres_callback, ctx);

    freetree234(ctx->points);
    sfree(sp.coords);

    grid_trim_vigorously(ctx->g);
    grid_make_consistent(ctx->g);

    /*
     * As with the Penrose tiling, we're likely to have different
     * sized margins due to the lack of a neat grid that this tiling
     * fits on. So now we know what tiles we're left with, recentre
     * them.
     */
    {
        int w = width2 * SPECTRE_UNIT, h = height2 * SPECTRE_UNIT;
        ctx->g->lowest_x -= (w - (ctx->g->highest_x - ctx->g->lowest_x))/2;
        ctx->g->lowest_y -= (h - (ctx->g->highest_y - ctx->g->lowest_y))/2;
        ctx->g->highest_x = ctx->g->lowest_x + w;
        ctx->g->highest_y = ctx->g->lowest_y + h;
    }

    return ctx->g;
}

/* ----------- End of grid generators ------------- */

#define FNVAL(upper,lower) &grid_validate_params_ ## lower,
#define FNNEW(upper,lower) &grid_new_ ## lower,
#define FNSZ(upper,lower) &grid_size_ ## lower,

static const char *(*(grid_validate_paramses[]))(int, int) =
    { GRIDGEN_LIST(FNVAL) };
static grid *(*(grid_news[]))(int, int, const char*) = { GRIDGEN_LIST(FNNEW) };
static void(*(grid_sizes[]))(int, int, int*, int*, int*) = { GRIDGEN_LIST(FNSZ) };

/* Work out if a grid can be made, and complain if not. */

const char *grid_validate_params(grid_type type, int width, int height)
{
    if (width <= 0 || height <= 0)
        return "Width and height must both be positive";
    return grid_validate_paramses[type](width, height);
}

char *grid_new_desc(grid_type type, int width, int height, random_state *rs)
{
    if (type == GRID_PENROSE_P2 || type == GRID_PENROSE_P3) {
        return grid_new_desc_penrose(type, width, height, rs);
    } else if (type == GRID_HATS) {
        return grid_new_desc_hats(type, width, height, rs);
    } else if (type == GRID_SPECTRES) {
        return grid_new_desc_spectres(type, width, height, rs);
    } else if (type == GRID_TRIANGULAR) {
        return dupstr("0"); /* up-to-date version of triangular grid */
    } else {
        return NULL;
    }
}

const char *grid_validate_desc(grid_type type, int width, int height,
                               const char *desc)
{
    if (type == GRID_PENROSE_P2 || type == GRID_PENROSE_P3) {
        return grid_validate_desc_penrose(type, width, height, desc);
    } else if (type == GRID_HATS) {
        return grid_validate_desc_hats(type, width, height, desc);
    } else if (type == GRID_SPECTRES) {
        return grid_validate_desc_spectres(type, width, height, desc);
    } else if (type == GRID_TRIANGULAR) {
        return grid_validate_desc_triangular(type, width, height, desc);
    } else {
        if (desc != NULL)
            return "Grid description strings not used with this grid type";
        return NULL;
    }
}

grid *grid_new(grid_type type, int width, int height, const char *desc)
{
    const char *err = grid_validate_desc(type, width, height, desc);
    if (err) assert(!"Invalid grid description.");

    return grid_news[type](width, height, desc);
}

void grid_compute_size(grid_type type, int width, int height,
                       int *tilesize, int *xextent, int *yextent)
{
    grid_sizes[type](width, height, tilesize, xextent, yextent);
}

/* ----------- End of grid helpers ------------- */

/* vim: set shiftwidth=4 tabstop=8: */