shithub: puzzles

Download patch

ref: 62c20496bf106b1034f4be95bdb7723c3ec78d00
parent: f390d0d7ff4972e4cb8aa778c2eb44452067d6d0
author: Simon Tatham <anakin@pobox.com>
date: Sun Apr 24 05:10:52 EDT 2011

From James Harvey (via a period of collaborative polishing), a patch
to add two kinds of Penrose tiling to the grid types supported by
Loopy.

This has involved a certain amount of infrastructure work, because of
course the whole point of Penrose tilings is that they don't have to
be the same every time: so now grid.c has grown the capacity to
describe its grids as strings, and reconstitute them from those string
descriptions. Hence a Penrose Loopy game description consists of a
string identifying a particular piece of Penrose tiling, followed by
the normal Loopy clue encoding.

All the existing grid types decline to provide a grid description
string, so their Loopy game descriptions have not changed encoding.

[originally from svn r9159]

--- a/grid.c
+++ b/grid.c
@@ -12,10 +12,12 @@
 #include <assert.h>
 #include <ctype.h>
 #include <math.h>
+#include <errno.h>
 
 #include "puzzles.h"
 #include "tree234.h"
 #include "grid.h"
+#include "penrose.h"
 
 /* Debugging options */
 
@@ -50,7 +52,7 @@
 
 /* Used by the other grid generators.  Create a brand new grid with nothing
  * initialised (all lists are NULL) */
-static grid *grid_new(void)
+static grid *grid_empty()
 {
     grid *g = snew(grid);
     g->faces = NULL;
@@ -158,13 +160,93 @@
  * Grid generation
  */
 
-#ifdef DEBUG_GRID
+#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
+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_print_basic(grid *g)
+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_faces; i++) {
@@ -177,10 +259,16 @@
         }
         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_print_derived(grid *g)
+static void grid_debug_derived(grid *g)
 {
+#ifdef DEBUG_GRID
     /* edges */
     int i;
     printf("--- Derived Grid Data ---\n");
@@ -220,8 +308,11 @@
         }
         printf("]\n");
     }
+#endif
+#ifdef SVG_GRID
+    grid_try_svg(g, SVG_DOTS | SVG_EDGES | SVG_FACES);
+#endif
 }
-#endif /* DEBUG_GRID */
 
 /* Helper function for building incomplete-edges list in
  * grid_make_consistent() */
@@ -250,6 +341,147 @@
     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;
+    int *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] - g->dots;
+        for (j = 0; j < f->order; j++) {
+            int dot1 = f->dots[j] - g->dots;
+            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] = TRUE;
+        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] = FALSE;    /* non-duplicated edge: coastal dot */
+        }
+    }
+
+    /*
+     * Now identify connected pairs of landlocked dots, and form a dsf
+     * unifying them.
+     */
+    dsf = snew_dsf(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;
+        int keep = FALSE;
+        for (k = 0; k < f->order; k++)
+            if (dsf_canonify(dsf, f->dots[k] - g->dots) == j)
+                keep = TRUE;
+        if (keep) {
+            faces[i] = TRUE;
+            for (k = 0; k < f->order; k++)
+                dots[f->dots[k]-g->dots] = TRUE;
+        }
+    }
+
+    /*
+     * Work out the new indices of those faces and dots, when we
+     * compact the arrays containing them.
+     */
+    for (i = newfaces = 0; i < g->num_faces; i++)
+        faces[i] = (faces[i] ? newfaces++ : -1);
+    for (i = newdots = 0; i < g->num_dots; i++)
+        dots[i] = (dots[i] ? newdots++ : -1);
+
+    /*
+     * Go through and compact the arrays.
+     */
+    for (i = 0; i < g->num_dots; i++)
+        if (dots[i] >= 0) {
+            grid_dot *dnew = g->dots + dots[i], *dold = g->dots + i;
+            *dnew = *dold;             /* structure copy */
+        }
+    for (i = 0; i < g->num_faces; i++)
+        if (faces[i] >= 0) {
+            grid_face *fnew = g->faces + faces[i], *fold = g->faces + i;
+            *fnew = *fold;             /* structure copy */
+            for (j = 0; j < fnew->order; j++) {
+                /*
+                 * Reindex the dots in this face.
+                 */
+                k = fnew->dots[j] - g->dots;
+                fnew->dots[j] = g->dots + dots[k];
+            }
+        }
+    g->num_faces = newfaces;
+    g->num_dots = newdots;
+
+    sfree(dotpairs);
+    sfree(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).
@@ -265,9 +497,7 @@
     tree234 *incomplete_edges;
     grid_edge *next_new_edge; /* Where new edge will go into g->edges */
 
-#ifdef DEBUG_GRID
-    grid_print_basic(g);
-#endif
+    grid_debug_basic(g);
 
     /* ====== Stage 1 ======
      * Generate edges
@@ -545,10 +775,8 @@
             g->highest_y = max(g->highest_y, d->y);
         }
     }
-    
-#ifdef DEBUG_GRID
-    grid_print_derived(g);
-#endif
+
+    grid_debug_derived(g);
 }
 
 /* Helpers for making grid-generation easier.  These functions are only
@@ -1139,11 +1367,23 @@
  * arithmetic here!
  */
 
-grid *grid_new_square(int width, int height)
+#define SQUARE_TILESIZE 20
+
+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;
+}
+
+grid *grid_new_square(int width, int height, char *desc)
+{
     int x, y;
     /* Side length */
-    int a = 20;
+    int a = SQUARE_TILESIZE;
 
     /* Upper bounds - don't have to be exact */
     int max_faces = width * height;
@@ -1151,7 +1391,7 @@
 
     tree234 *points;
 
-    grid *g = grid_new();
+    grid *g = grid_empty();
     g->tilesize = a;
     g->faces = snewn(max_faces, grid_face);
     g->dots = snewn(max_dots, grid_dot);
@@ -1186,21 +1426,36 @@
     return g;
 }
 
-grid *grid_new_honeycomb(int width, int height)
+#define HONEY_TILESIZE 45
+/* Vector for side of hexagon - ratio is close to sqrt(3) */
+#define HONEY_A 15
+#define HONEY_B 26
+
+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;
+}
+
+grid *grid_new_honeycomb(int width, int height, char *desc)
+{
     int x, y;
-    /* Vector for side of hexagon - ratio is close to sqrt(3) */
-    int a = 15;
-    int b = 26;
+    int a = HONEY_A;
+    int b = HONEY_B;
 
     /* Upper bounds - don't have to be exact */
     int max_faces = width * height;
     int max_dots = 2 * (width + 1) * (height + 1);
-    
+
     tree234 *points;
 
-    grid *g = grid_new();
-    g->tilesize = 3 * a;
+    grid *g = grid_empty();
+    g->tilesize = HONEY_TILESIZE;
     g->faces = snewn(max_faces, grid_face);
     g->dots = snewn(max_dots, grid_dot);
 
@@ -1240,15 +1495,31 @@
     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
+
+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 * 2 * vec_x + vec_x;
+    *yextent = height * vec_y;
+}
+
 /* 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" */
-grid *grid_new_triangular(int width, int height)
+grid *grid_new_triangular(int width, int height, char *desc)
 {
     int x,y;
     
     /* Vector for side of triangle - ratio is close to sqrt(3) */
-    int vec_x = 15;
-    int vec_y = 26;
+    int vec_x = TRIANGLE_VEC_X;
+    int vec_y = TRIANGLE_VEC_Y;
     
     int index;
 
@@ -1255,8 +1526,8 @@
     /* convenient alias */
     int w = width + 1;
 
-    grid *g = grid_new();
-    g->tilesize = 18; /* adjust to your taste */
+    grid *g = grid_empty();
+    g->tilesize = TRIANGLE_TILESIZE;
 
     g->num_faces = width * height * 2;
     g->num_dots = (width + 1) * (height + 1);
@@ -1317,21 +1588,36 @@
     return g;
 }
 
-grid *grid_new_snubsquare(int width, int height)
+#define SNUBSQUARE_TILESIZE 18
+/* Vector for side of triangle - ratio is close to sqrt(3) */
+#define SNUBSQUARE_A 15
+#define SNUBSQUARE_B 26
+
+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;
+}
+
+grid *grid_new_snubsquare(int width, int height, char *desc)
+{
     int x, y;
-    /* Vector for side of triangle - ratio is close to sqrt(3) */
-    int a = 15;
-    int b = 26;
+    int a = SNUBSQUARE_A;
+    int b = SNUBSQUARE_B;
 
     /* Upper bounds - don't have to be exact */
     int max_faces = 3 * width * height;
     int max_dots = 2 * (width + 1) * (height + 1);
-    
+
     tree234 *points;
 
-    grid *g = grid_new();
-    g->tilesize = 18;
+    grid *g = grid_empty();
+    g->tilesize = SNUBSQUARE_TILESIZE;
     g->faces = snewn(max_faces, grid_face);
     g->dots = snewn(max_dots, grid_dot);
 
@@ -1416,21 +1702,35 @@
     return g;
 }
 
-grid *grid_new_cairo(int width, int height)
+#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
+
+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;
+}
+
+grid *grid_new_cairo(int width, int height, char *desc)
+{
     int x, y;
-    /* Vector for side of pentagon - ratio is close to (4+sqrt(7))/3 */
-    int a = 14;
-    int b = 31;
+    int a = CAIRO_A;
+    int b = CAIRO_B;
 
     /* Upper bounds - don't have to be exact */
     int max_faces = 2 * width * height;
     int max_dots = 3 * (width + 1) * (height + 1);
-    
+
     tree234 *points;
 
-    grid *g = grid_new();
-    g->tilesize = 40;
+    grid *g = grid_empty();
+    g->tilesize = CAIRO_TILESIZE;
     g->faces = snewn(max_faces, grid_face);
     g->dots = snewn(max_dots, grid_dot);
 
@@ -1508,12 +1808,27 @@
     return g;
 }
 
-grid *grid_new_greathexagonal(int width, int height)
+#define GREATHEX_TILESIZE 18
+/* Vector for side of triangle - ratio is close to sqrt(3) */
+#define GREATHEX_A 15
+#define GREATHEX_B 26
+
+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;
+}
+
+grid *grid_new_greathexagonal(int width, int height, char *desc)
+{
     int x, y;
-    /* Vector for side of triangle - ratio is close to sqrt(3) */
-    int a = 15;
-    int b = 26;
+    int a = GREATHEX_A;
+    int b = GREATHEX_B;
 
     /* Upper bounds - don't have to be exact */
     int max_faces = 6 * (width + 1) * (height + 1);
@@ -1521,8 +1836,8 @@
 
     tree234 *points;
 
-    grid *g = grid_new();
-    g->tilesize = 18;
+    grid *g = grid_empty();
+    g->tilesize = GREATHEX_TILESIZE;
     g->faces = snewn(max_faces, grid_face);
     g->dots = snewn(max_dots, grid_dot);
 
@@ -1623,12 +1938,27 @@
     return g;
 }
 
-grid *grid_new_octagonal(int width, int height)
+#define OCTAGONAL_TILESIZE 40
+/* b/a approx sqrt(2) */
+#define OCTAGONAL_A 29
+#define OCTAGONAL_B 41
+
+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;
+}
+
+grid *grid_new_octagonal(int width, int height, char *desc)
+{
     int x, y;
-    /* b/a approx sqrt(2) */
-    int a = 29;
-    int b = 41;
+    int a = OCTAGONAL_A;
+    int b = OCTAGONAL_B;
 
     /* Upper bounds - don't have to be exact */
     int max_faces = 2 * width * height;
@@ -1636,8 +1966,8 @@
 
     tree234 *points;
 
-    grid *g = grid_new();
-    g->tilesize = 40;
+    grid *g = grid_empty();
+    g->tilesize = OCTAGONAL_TILESIZE;
     g->faces = snewn(max_faces, grid_face);
     g->dots = snewn(max_dots, grid_dot);
 
@@ -1691,12 +2021,27 @@
     return g;
 }
 
-grid *grid_new_kites(int width, int height)
+#define KITE_TILESIZE 40
+/* b/a approx sqrt(3) */
+#define KITE_A 15
+#define KITE_B 26
+
+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;
+}
+
+grid *grid_new_kites(int width, int height, char *desc)
+{
     int x, y;
-    /* b/a approx sqrt(3) */
-    int a = 15;
-    int b = 26;
+    int a = KITE_A;
+    int b = KITE_B;
 
     /* Upper bounds - don't have to be exact */
     int max_faces = 6 * width * height;
@@ -1704,8 +2049,8 @@
 
     tree234 *points;
 
-    grid *g = grid_new();
-    g->tilesize = 40;
+    grid *g = grid_empty();
+    g->tilesize = KITE_TILESIZE;
     g->faces = snewn(max_faces, grid_face);
     g->dots = snewn(max_dots, grid_dot);
 
@@ -1796,25 +2141,45 @@
     return g;
 }
 
-grid *grid_new_floret(int width, int height)
+#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
+
+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;
+}
+
+grid *grid_new_floret(int width, int height, 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 = 75, py = -26;       /* |( 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 */
+    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 */
 
     /* Upper bounds - don't have to be exact */
     int max_faces = 6 * width * height;
     int max_dots = 9 * (width + 1) * (height + 1);
-    
+
     tree234 *points;
 
-    grid *g = grid_new();
-    g->tilesize = 2 * px;
+    grid *g = grid_empty();
+    g->tilesize = FLORET_TILESIZE;
     g->faces = snewn(max_faces, grid_face);
     g->dots = snewn(max_dots, grid_dot);
 
@@ -1884,12 +2249,28 @@
     return g;
 }
 
-grid *grid_new_dodecagonal(int width, int height)
+/* 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
+
+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);
+}
+
+grid *grid_new_dodecagonal(int width, int height, char *desc)
+{
     int x, y;
-    /* Vector for side of triangle - ratio is close to sqrt(3) */
-    int a = 15;
-    int b = 26;
+    int a = DODEC_A;
+    int b = DODEC_B;
 
     /* Upper bounds - don't have to be exact */
     int max_faces = 3 * width * height;
@@ -1897,8 +2278,8 @@
 
     tree234 *points;
 
-    grid *g = grid_new();
-    g->tilesize = b;
+    grid *g = grid_empty();
+    g->tilesize = DODEC_TILESIZE;
     g->faces = snewn(max_faces, grid_face);
     g->dots = snewn(max_dots, grid_dot);
 
@@ -1954,12 +2335,23 @@
     return g;
 }
 
-grid *grid_new_greatdodecagonal(int width, int height)
+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);
+}
+
+grid *grid_new_greatdodecagonal(int width, int height, char *desc)
+{
     int x, y;
     /* Vector for side of triangle - ratio is close to sqrt(3) */
-    int a = 15;
-    int b = 26;
+    int a = DODEC_A;
+    int b = DODEC_B;
 
     /* Upper bounds - don't have to be exact */
     int max_faces = 30 * width * height;
@@ -1967,8 +2359,8 @@
 
     tree234 *points;
 
-    grid *g = grid_new();
-    g->tilesize = b;
+    grid *g = grid_empty();
+    g->tilesize = DODEC_TILESIZE;
     g->faces = snewn(max_faces, grid_face);
     g->dots = snewn(max_dots, grid_dot);
 
@@ -2057,4 +2449,279 @@
     return g;
 }
 
+typedef struct setface_ctx
+{
+    int xmin, xmax, ymin, ymax;
+    int aoff;
+
+    grid *g;
+    tree234 *points;
+} setface_ctx;
+
+double round(double r)
+{
+    return (r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5);
+}
+
+int set_faces(penrose_state *state, vector *vs, int n, int depth)
+{
+    setface_ctx *sf_ctx = (setface_ctx *)state->ctx;
+    int i;
+    int xs[4], ys[4];
+    double cosa = cos(sf_ctx->aoff * PI / 180.0);
+    double sina = sin(sf_ctx->aoff * PI / 180.0);
+
+    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 = v_x(vs, i), ty = v_y(vs, i);
+
+        xs[i] = (int)round( tx*cosa + ty*sina);
+        ys[i] = (int)round(-tx*sina + ty*cosa);
+
+        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_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, v_x(vs, i), v_y(vs, i)));
+    }
+
+    return 0;
+}
+
+#define PENROSE_TILESIZE 100
+
+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;
+}
+
+static char *grid_new_desc_penrose(grid_type type, int width, int height, random_state *rs)
+{
+    int tilesize = PENROSE_TILESIZE, startsz, depth, xoff, yoff, aoff;
+    double outer_radius;
+    int inner_radius;
+    char gd[255];
+    int which = (type == GRID_PENROSE_P2 ? PENROSE_P2 : PENROSE_P3);
+
+    /* We want to produce a random bit of penrose tiling, so we calculate
+     * a random offset (within the patch that penrose.c calculates for us)
+     * and an angle (multiple of 36) to rotate the patch. */
+
+    penrose_calculate_size(which, tilesize, width, height,
+                           &outer_radius, &startsz, &depth);
+
+    /* Calculate radius of (circumcircle of) patch, subtract from
+     * radius calculated. */
+    inner_radius = (int)(outer_radius - sqrt(width*width + height*height));
+
+    /* Pick a random offset (the easy way: choose within outer square,
+     * discarding while it's outside the circle) */
+    do {
+        xoff = random_upto(rs, 2*inner_radius) - inner_radius;
+        yoff = random_upto(rs, 2*inner_radius) - inner_radius;
+    } while (sqrt(xoff*xoff+yoff*yoff) > inner_radius);
+
+    aoff = random_upto(rs, 360/36) * 36;
+
+    debug(("grid_desc: ts %d, %dx%d patch, orad %2.2f irad %d",
+           tilesize, width, height, outer_radius, inner_radius));
+    debug(("    -> xoff %d yoff %d aoff %d", xoff, yoff, aoff));
+
+    sprintf(gd, "G%d,%d,%d", xoff, yoff, aoff);
+
+    return dupstr(gd);
+}
+
+static char *grid_validate_desc_penrose(grid_type type, int width, int height, 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);
+
+    if (!desc)
+        return "Missing grid description string.";
+
+    penrose_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.";
+
+    return NULL;
+}
+
+/*
+ * We're asked for a grid of a particular size, and we generate enough
+ * of the tiling so we can be sure to have enough random grid from which
+ * to pick.
+ */
+
+static grid *grid_new_penrose(int width, int height, int which, char *desc)
+{
+    int max_faces, max_dots, tilesize = PENROSE_TILESIZE;
+    int xsz, ysz, xoff, yoff;
+    double rradius;
+
+    tree234 *points;
+    grid *g;
+
+    penrose_state ps;
+    setface_ctx sf_ctx;
+
+    penrose_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 = set_faces;
+    ps.ctx = &sf_ctx;
+
+    max_faces = (width*3) * (height*3); /* somewhat paranoid... */
+    max_dots = max_faces * 4; /* ditto... */
+
+    g = grid_empty();
+    g->tilesize = tilesize;
+    g->faces = snewn(max_faces, grid_face);
+    g->dots = snewn(max_dots, grid_dot);
+
+    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, &sf_ctx.aoff) != 3)
+            assert(!"Invalid grid description.");
+    } else {
+        xoff = yoff = 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(&ps, which);
+
+    freetree234(points);
+    assert(g->num_faces <= max_faces);
+    assert(g->num_dots <= max_dots);
+
+    debug(("penrose: %d faces total (equivalent to %d wide by %d high)",
+           g->num_faces, g->num_faces/height, g->num_faces/width));
+
+    grid_trim_vigorously(g);
+    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;
+}
+
+void grid_size_penrose_p2_kite(int width, int height,
+                       int *tilesize, int *xextent, int *yextent)
+{
+    grid_size_penrose(width, height, tilesize, xextent, yextent);
+}
+
+void grid_size_penrose_p3_thick(int width, int height,
+                       int *tilesize, int *xextent, int *yextent)
+{
+    grid_size_penrose(width, height, tilesize, xextent, yextent);
+}
+
+grid *grid_new_penrose_p2_kite(int width, int height, char *desc)
+{
+    return grid_new_penrose(width, height, PENROSE_P2, desc);
+}
+
+grid *grid_new_penrose_p3_thick(int width, int height, char *desc)
+{
+    return grid_new_penrose(width, height, PENROSE_P3, desc);
+}
+
 /* ----------- End of grid generators ------------- */
+
+#define FNNEW(upper,lower) &grid_new_ ## lower,
+#define FNSZ(upper,lower) &grid_size_ ## lower,
+
+static grid *(*(grid_news[]))(int, int, char*) = { GRIDGEN_LIST(FNNEW) };
+static void(*(grid_sizes[]))(int, int, int*, int*, int*) = { GRIDGEN_LIST(FNSZ) };
+
+char *grid_new_desc(grid_type type, int width, int height, random_state *rs)
+{
+    if (type != GRID_PENROSE_P2 && type != GRID_PENROSE_P3)
+        return NULL;
+
+    return grid_new_desc_penrose(type, width, height, rs);
+}
+
+char *grid_validate_desc(grid_type type, int width, int height, char *desc)
+{
+    if (type != GRID_PENROSE_P2 && type != GRID_PENROSE_P3) {
+        if (desc != NULL)
+            return "Grid description strings not used with this grid type";
+        return NULL;
+    }
+
+    return grid_validate_desc_penrose(type, width, height, desc);
+}
+
+grid *grid_new(grid_type type, int width, int height, char *desc)
+{
+    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: */
--- a/grid.h
+++ b/grid.h
@@ -9,6 +9,8 @@
 #ifndef PUZZLES_GRID_H
 #define PUZZLES_GRID_H
 
+#include "puzzles.h" /* for random_state */
+
 /* Useful macros */
 #define SQ(x) ( (x) * (x) )
 
@@ -89,21 +91,40 @@
   int refcount;
 } grid;
 
-grid *grid_new_square(int width, int height);
-grid *grid_new_honeycomb(int width, int height);
-grid *grid_new_triangular(int width, int height);
-grid *grid_new_snubsquare(int width, int height);
-grid *grid_new_cairo(int width, int height);
-grid *grid_new_greathexagonal(int width, int height);
-grid *grid_new_octagonal(int width, int height);
-grid *grid_new_kites(int width, int height);
-grid *grid_new_floret(int width, int height);
-grid *grid_new_dodecagonal(int width, int height);
-grid *grid_new_greatdodecagonal(int width, int height);
+/* Grids are specified by type: GRID_SQUARE, GRID_KITE, etc. */
 
+#define GRIDGEN_LIST(A) \
+  A(SQUARE,square) \
+  A(HONEYCOMB,honeycomb) \
+  A(TRIANGULAR,triangular) \
+  A(SNUBSQUARE,snubsquare) \
+  A(CAIRO,cairo) \
+  A(GREATHEXAGONAL,greathexagonal) \
+  A(OCTAGONAL,octagonal) \
+  A(KITE,kites) \
+  A(FLORET,floret) \
+  A(DODECAGONAL,dodecagonal) \
+  A(GREATDODECAGONAL,greatdodecagonal) \
+  A(PENROSE_P2,penrose_p2_kite) \
+  A(PENROSE_P3,penrose_p3_thick)
+
+#define ENUM(upper,lower) GRID_ ## upper,
+typedef enum grid_type { GRIDGEN_LIST(ENUM) GRID_TYPE_MAX } grid_type;
+#undef ENUM
+
+/* Free directly after use if non-NULL. Will never contain an underscore
+ * (so clients can safely use that as a separator). */
+char *grid_new_desc(grid_type type, int width, int height, random_state *rs);
+char *grid_validate_desc(grid_type type, int width, int height, char *desc);
+
+grid *grid_new(grid_type type, int width, int height, char *desc);
+
 void grid_free(grid *g);
 
 grid_edge *grid_nearest_edge(grid *g, int x, int y);
+
+void grid_compute_size(grid_type type, int width, int height,
+                       int *tilesize, int *xextent, int *yextent);
 
 void grid_find_incentre(grid_face *f);
 
--- a/loopy.R
+++ b/loopy.R
@@ -1,6 +1,6 @@
 # -*- makefile -*-
 
-LOOPY_EXTRA = tree234 dsf grid
+LOOPY_EXTRA = tree234 dsf grid penrose
 
 loopy     : [X] GTK COMMON loopy LOOPY_EXTRA loopy-icon|no-icon
 
@@ -8,6 +8,13 @@
 
 loopysolver :   [U] loopy[STANDALONE_SOLVER] LOOPY_EXTRA STANDALONE m.lib
 loopysolver :   [C] loopy[STANDALONE_SOLVER] LOOPY_EXTRA STANDALONE
+
+#penrose :    [U] penrose[TEST_PENROSE] STANDALONE m.lib
+#penrose :    [C] penrose[TEST_PENROSE] STANDALONE
+
+#test-basis : [U] penrose[TEST_VECTORS] tree234 STANDALONE m.lib
+#test-basis : [C] penrose[TEST_VECTORS] tree234 STANDALONE
+
 
 ALL += loopy[COMBINED] LOOPY_EXTRA
 
--- a/loopy.c
+++ b/loopy.c
@@ -107,7 +107,7 @@
 };
 
 struct game_state {
-    grid *game_grid;
+    grid *game_grid; /* ref-counted (internally) */
 
     /* Put -1 in a face that doesn't get a clue */
     signed char *clues;
@@ -207,10 +207,6 @@
     int w, h;
     int diff;
     int type;
-
-    /* Grid generation is expensive, so keep a (ref-counted) reference to the
-     * grid for these parameters, and only generate when required. */
-    grid *game_grid;
 };
 
 /* line_drawstate is the same as line_state, but with the extra ERROR
@@ -247,29 +243,31 @@
 
 /* ------- List of grid generators ------- */
 #define GRIDLIST(A) \
-    A(Squares,grid_new_square,3,3) \
-    A(Triangular,grid_new_triangular,3,3) \
-    A(Honeycomb,grid_new_honeycomb,3,3) \
-    A(Snub-Square,grid_new_snubsquare,3,3) \
-    A(Cairo,grid_new_cairo,3,4) \
-    A(Great-Hexagonal,grid_new_greathexagonal,3,3) \
-    A(Octagonal,grid_new_octagonal,3,3) \
-    A(Kites,grid_new_kites,3,3) \
-    A(Floret,grid_new_floret,1,2) \
-    A(Dodecagonal,grid_new_dodecagonal,2,2) \
-    A(Great-Dodecagonal,grid_new_greatdodecagonal,2,2)
+    A(Squares,GRID_SQUARE,3,3) \
+    A(Triangular,GRID_TRIANGULAR,3,3) \
+    A(Honeycomb,GRID_HONEYCOMB,3,3) \
+    A(Snub-Square,GRID_SNUBSQUARE,3,3) \
+    A(Cairo,GRID_CAIRO,3,4) \
+    A(Great-Hexagonal,GRID_GREATHEXAGONAL,3,3) \
+    A(Octagonal,GRID_OCTAGONAL,3,3) \
+    A(Kites,GRID_KITE,3,3) \
+    A(Floret,GRID_FLORET,1,2) \
+    A(Dodecagonal,GRID_DODECAGONAL,2,2) \
+    A(Great-Dodecagonal,GRID_GREATDODECAGONAL,2,2) \
+    A(Penrose (kite/dart),GRID_PENROSE_P2,3,3) \
+    A(Penrose (rhombs),GRID_PENROSE_P3,3,3)
 
-#define GRID_NAME(title,fn,amin,omin) #title,
-#define GRID_CONFIG(title,fn,amin,omin) ":" #title
-#define GRID_FN(title,fn,amin,omin) &fn,
-#define GRID_SIZES(title,fn,amin,omin) \
+#define GRID_NAME(title,type,amin,omin) #title,
+#define GRID_CONFIG(title,type,amin,omin) ":" #title
+#define GRID_TYPE(title,type,amin,omin) type,
+#define GRID_SIZES(title,type,amin,omin) \
     {amin, omin, \
      "Width and height for this grid type must both be at least " #amin, \
      "At least one of width and height for this grid type must be at least " #omin,},
 static char const *const gridnames[] = { GRIDLIST(GRID_NAME) };
 #define GRID_CONFIGS GRIDLIST(GRID_CONFIG)
-static grid * (*(grid_fns[]))(int w, int h) = { GRIDLIST(GRID_FN) };
-#define NUM_GRID_TYPES (sizeof(grid_fns) / sizeof(grid_fns[0]))
+static grid_type grid_types[] = { GRIDLIST(GRID_TYPE) };
+#define NUM_GRID_TYPES (sizeof(grid_types) / sizeof(grid_types[0]))
 static const struct {
     int amin, omin;
     char *aerr, *oerr;
@@ -277,13 +275,10 @@
 
 /* Generates a (dynamically allocated) new grid, according to the
  * type and size requested in params.  Does nothing if the grid is already
- * generated.  The allocated grid is owned by the params object, and will be
- * freed in free_params(). */
-static void params_generate_grid(game_params *params)
+ * generated. */
+static grid *loopy_generate_grid(game_params *params, char *grid_desc)
 {
-    if (!params->game_grid) {
-        params->game_grid = grid_fns[params->type](params->w, params->h);
-    }
+    return grid_new(grid_types[params->type], params->w, params->h, grid_desc);
 }
 
 /* ----------------------------------------------------------------------
@@ -480,8 +475,6 @@
     ret->diff = DIFF_EASY;
     ret->type = 0;
 
-    ret->game_grid = NULL;
-
     return ret;
 }
 
@@ -490,44 +483,45 @@
     game_params *ret = snew(game_params);
 
     *ret = *params;                       /* structure copy */
-    if (ret->game_grid) {
-        ret->game_grid->refcount++;
-    }
     return ret;
 }
 
 static const game_params presets[] = {
 #ifdef SMALL_SCREEN
-    {  7,  7, DIFF_EASY, 0, NULL },
-    {  7,  7, DIFF_NORMAL, 0, NULL },
-    {  7,  7, DIFF_HARD, 0, NULL },
-    {  7,  7, DIFF_HARD, 1, NULL },
-    {  7,  7, DIFF_HARD, 2, NULL },
-    {  5,  5, DIFF_HARD, 3, NULL },
-    {  7,  7, DIFF_HARD, 4, NULL },
-    {  5,  4, DIFF_HARD, 5, NULL },
-    {  5,  5, DIFF_HARD, 6, NULL },
-    {  5,  5, DIFF_HARD, 7, NULL },
-    {  3,  3, DIFF_HARD, 8, NULL },
-    {  3,  3, DIFF_HARD, 9, NULL },
-    {  3,  3, DIFF_HARD, 10, NULL },
+    {  7,  7, DIFF_EASY, 0 },
+    {  7,  7, DIFF_NORMAL, 0 },
+    {  7,  7, DIFF_HARD, 0 },
+    {  7,  7, DIFF_HARD, 1 },
+    {  7,  7, DIFF_HARD, 2 },
+    {  5,  5, DIFF_HARD, 3 },
+    {  7,  7, DIFF_HARD, 4 },
+    {  5,  4, DIFF_HARD, 5 },
+    {  5,  5, DIFF_HARD, 6 },
+    {  5,  5, DIFF_HARD, 7 },
+    {  3,  3, DIFF_HARD, 8 },
+    {  3,  3, DIFF_HARD, 9 },
+    {  3,  3, DIFF_HARD, 10 },
+    {  6,  6, DIFF_HARD, 11 },
+    {  6,  6, DIFF_HARD, 12 },
 #else
-    {  7,  7, DIFF_EASY, 0, NULL },
-    {  10,  10, DIFF_EASY, 0, NULL },
-    {  7,  7, DIFF_NORMAL, 0, NULL },
-    {  10,  10, DIFF_NORMAL, 0, NULL },
-    {  7,  7, DIFF_HARD, 0, NULL },
-    {  10,  10, DIFF_HARD, 0, NULL },
-    {  10,  10, DIFF_HARD, 1, NULL },
-    {  12,  10, DIFF_HARD, 2, NULL },
-    {  7,  7, DIFF_HARD, 3, NULL },
-    {  9,  9, DIFF_HARD, 4, NULL },
-    {  5,  4, DIFF_HARD, 5, NULL },
-    {  7,  7, DIFF_HARD, 6, NULL },
-    {  5,  5, DIFF_HARD, 7, NULL },
-    {  5,  5, DIFF_HARD, 8, NULL },
-    {  5,  4, DIFF_HARD, 9, NULL },
-    {  5,  4, DIFF_HARD, 10, NULL },
+    {  7,  7, DIFF_EASY, 0 },
+    {  10,  10, DIFF_EASY, 0 },
+    {  7,  7, DIFF_NORMAL, 0 },
+    {  10,  10, DIFF_NORMAL, 0 },
+    {  7,  7, DIFF_HARD, 0 },
+    {  10,  10, DIFF_HARD, 0 },
+    {  10,  10, DIFF_HARD, 1 },
+    {  12,  10, DIFF_HARD, 2 },
+    {  7,  7, DIFF_HARD, 3 },
+    {  9,  9, DIFF_HARD, 4 },
+    {  5,  4, DIFF_HARD, 5 },
+    {  7,  7, DIFF_HARD, 6 },
+    {  5,  5, DIFF_HARD, 7 },
+    {  5,  5, DIFF_HARD, 8 },
+    {  5,  4, DIFF_HARD, 9 },
+    {  5,  4, DIFF_HARD, 10 },
+    {  10, 10, DIFF_HARD, 11 },
+    {  10, 10, DIFF_HARD, 12 }
 #endif
 };
 
@@ -551,18 +545,11 @@
 
 static void free_params(game_params *params)
 {
-    if (params->game_grid) {
-        grid_free(params->game_grid);
-    }
     sfree(params);
 }
 
 static void decode_params(game_params *params, char const *string)
 {
-    if (params->game_grid) {
-        grid_free(params->game_grid);
-        params->game_grid = NULL;
-    }
     params->h = params->w = atoi(string);
     params->diff = DIFF_EASY;
     while (*string && isdigit((unsigned char)*string)) string++;
@@ -641,7 +628,6 @@
     ret->type = cfg[2].ival;
     ret->diff = cfg[3].ival;
 
-    ret->game_grid = NULL;
     return ret;
 }
 
@@ -702,6 +688,28 @@
     return retval;
 }
 
+#define GRID_DESC_SEP '_'
+
+/* Splits up a (optional) grid_desc from the game desc. Returns the
+ * grid_desc (which needs freeing) and updates the desc pointer to
+ * start of real desc, or returns NULL if no desc. */
+static char *extract_grid_desc(char **desc)
+{
+    char *sep = strchr(*desc, GRID_DESC_SEP), *gd;
+    int gd_len;
+
+    if (!sep) return NULL;
+
+    gd_len = sep - (*desc);
+    gd = snewn(gd_len+1, char);
+    memcpy(gd, *desc, gd_len);
+    gd[gd_len] = '\0';
+
+    *desc = sep+1;
+
+    return gd;
+}
+
 /* We require that the params pass the test in validate_params and that the
  * description fills the entire game area */
 static char *validate_desc(game_params *params, char *desc)
@@ -708,9 +716,17 @@
 {
     int count = 0;
     grid *g;
-    params_generate_grid(params);
-    g = params->game_grid;
+    char *grid_desc, *ret;
 
+    /* It's pretty inefficient to do this just for validation. All we need to
+     * know is the precise number of faces. */
+    grid_desc = extract_grid_desc(&desc);
+    ret = grid_validate_desc(grid_types[params->type], params->w, params->h, grid_desc);
+    if (ret) return ret;
+
+    g = loopy_generate_grid(params, grid_desc);
+    if (grid_desc) sfree(grid_desc);
+
     for (; *desc; ++desc) {
         if ((*desc >= '0' && *desc <= '9') || (*desc >= 'A' && *desc <= 'Z')) {
             count++;
@@ -728,6 +744,8 @@
     if (count > g->num_faces)
         return "Description too long for board size";
 
+    grid_free(g);
+
     return NULL;
 }
 
@@ -809,16 +827,15 @@
 static void game_compute_size(game_params *params, int tilesize,
                               int *x, int *y)
 {
-    grid *g;
     int grid_width, grid_height, rendered_width, rendered_height;
+    int g_tilesize;
 
-    params_generate_grid(params);
-    g = params->game_grid;
-    grid_width = g->highest_x - g->lowest_x;
-    grid_height = g->highest_y - g->lowest_y;
+    grid_compute_size(grid_types[params->type], params->w, params->h,
+                      &g_tilesize, &grid_width, &grid_height);
+
     /* multiply first to minimise rounding error on integer division */
-    rendered_width = grid_width * tilesize / g->tilesize;
-    rendered_height = grid_height * tilesize / g->tilesize;
+    rendered_width = grid_width * tilesize / g_tilesize;
+    rendered_height = grid_height * tilesize / g_tilesize;
     *x = rendered_width + 2 * BORDER(tilesize) + 1;
     *y = rendered_height + 2 * BORDER(tilesize) + 1;
 }
@@ -1836,13 +1853,14 @@
                            char **aux, int interactive)
 {
     /* solution and description both use run-length encoding in obvious ways */
-    char *retval;
+    char *retval, *game_desc, *grid_desc;
     grid *g;
     game_state *state = snew(game_state);
     game_state *state_new;
-    params_generate_grid(params);
-    state->game_grid = g = params->game_grid;
-    g->refcount++;
+
+    grid_desc = grid_new_desc(grid_types[params->type], params->w, params->h, rs);
+    state->game_grid = g = loopy_generate_grid(params, grid_desc);
+
     state->clues = snewn(g->num_faces, signed char);
     state->lines = snewn(g->num_edges, char);
     state->line_errors = snewn(g->num_edges, unsigned char);
@@ -1875,10 +1893,19 @@
         goto newboard_please;
     }
 
-    retval = state_to_text(state);
+    game_desc = state_to_text(state);
 
     free_game(state);
 
+    if (grid_desc) {
+        retval = snewn(strlen(grid_desc) + 1 + strlen(game_desc) + 1, char);
+        sprintf(retval, "%s%c%s", grid_desc, GRID_DESC_SEP, game_desc);
+        sfree(grid_desc);
+        sfree(game_desc);
+    } else {
+        retval = game_desc;
+    }
+
     assert(!validate_desc(params, retval));
 
     return retval;
@@ -1890,13 +1917,17 @@
     game_state *state = snew(game_state);
     int empties_to_make = 0;
     int n,n2;
-    const char *dp = desc;
+    const char *dp;
+    char *grid_desc;
     grid *g;
     int num_faces, num_edges;
 
-    params_generate_grid(params);
-    state->game_grid = g = params->game_grid;
-    g->refcount++;
+    grid_desc = extract_grid_desc(&desc);
+    state->game_grid = g = loopy_generate_grid(params, grid_desc);
+    if (grid_desc) sfree(grid_desc);
+
+    dp = desc;
+
     num_faces = g->num_faces;
     num_edges = g->num_edges;
 
@@ -4017,3 +4048,5 @@
 }
 
 #endif
+
+/* vim: set shiftwidth=4 tabstop=8: */
--- /dev/null
+++ b/penrose.c
@@ -1,0 +1,626 @@
+/* penrose.c
+ *
+ * Penrose tile generator.
+ *
+ * Uses half-tile technique outlined on:
+ *
+ * http://tartarus.org/simon/20110412-penrose/penrose.xhtml
+ */
+
+#include <assert.h>
+#include <string.h>
+#include <math.h>
+#include <stdio.h>
+
+#include "puzzles.h" /* for malloc routines, and PI */
+#include "penrose.h"
+
+/* -------------------------------------------------------
+ * 36-degree basis vector arithmetic routines.
+ */
+
+/* Imagine drawing a
+ * ten-point 'clock face' like this:
+ *
+ *                     -E
+ *                 -D   |   A
+ *                   \  |  /
+ *              -C.   \ | /   ,B
+ *                 `-._\|/_,-'
+ *                 ,-' /|\ `-.
+ *              -B'   / | \   `C
+ *                   /  |  \
+ *                 -A   |   D
+ *                      E
+ *
+ * In case the ASCII art isn't clear, those are supposed to be ten
+ * vectors of length 1, all sticking out from the origin at equal
+ * angular spacing (hence 36 degrees). Our basis vectors are A,B,C,D (I
+ * choose them to be symmetric about the x-axis so that the final
+ * translation into 2d coordinates will also be symmetric, which I
+ * think will avoid minor rounding uglinesses), so our vector
+ * representation sets
+ *
+ *   A = (1,0,0,0)
+ *   B = (0,1,0,0)
+ *   C = (0,0,1,0)
+ *   D = (0,0,0,1)
+ *
+ * The fifth vector E looks at first glance as if it needs to be
+ * another basis vector, but in fact it doesn't, because it can be
+ * represented in terms of the other four. Imagine starting from the
+ * origin and following the path -A, +B, -C, +D: you'll find you've
+ * traced four sides of a pentagram, and ended up one E-vector away
+ * from the origin. So we have
+ *
+ *   E = (-1,1,-1,1)
+ *
+ * This tells us that we can rotate any vector in this system by 36
+ * degrees: if we start with a*A + b*B + c*C + d*D, we want to end up
+ * with a*B + b*C + c*D + d*E, and we substitute our identity for E to
+ * turn that into a*B + b*C + c*D + d*(-A+B-C+D). In other words,
+ *
+ *   rotate_one_notch_clockwise(a,b,c,d) = (-d, d+a, -d+b, d+c)
+ *
+ * and you can verify for yourself that applying that operation
+ * repeatedly starting with (1,0,0,0) cycles round ten vectors and
+ * comes back to where it started.
+ *
+ * The other operation that may be required is to construct vectors
+ * with lengths that are multiples of phi. That can be done by
+ * observing that the vector C-B is parallel to E and has length 1/phi,
+ * and the vector D-A is parallel to E and has length phi. So this
+ * tells us that given any vector, we can construct one which points in
+ * the same direction and is 1/phi or phi times its length, like this:
+ *
+ *   divide_by_phi(vector) = rotate(vector, 2) - rotate(vector, 3)
+ *   multiply_by_phi(vector) = rotate(vector, 1) - rotate(vector, 4)
+ *
+ * where rotate(vector, n) means applying the above
+ * rotate_one_notch_clockwise primitive n times. Expanding out the
+ * applications of rotate gives the following direct representation in
+ * terms of the vector coordinates:
+ *
+ *   divide_by_phi(a,b,c,d) = (b-d, c+d-b, a+b-c, c-a)
+ *   multiply_by_phi(a,b,c,d) = (a+b-d, c+d, a+b, c+d-a)
+ *
+ * and you can verify for yourself that those two operations are
+ * inverses of each other (as you'd hope!).
+ *
+ * Having done all of this, testing for equality between two vectors is
+ * a trivial matter of comparing the four integer coordinates. (Which
+ * it _wouldn't_ have been if we'd kept E as a fifth basis vector,
+ * because then (-1,1,-1,1,0) and (0,0,0,0,1) would have had to be
+ * considered identical. So leaving E out is vital.)
+ */
+
+struct vector { int a, b, c, d; };
+
+static vector v_origin()
+{
+    vector v;
+    v.a = v.b = v.c = v.d = 0;
+    return v;
+}
+
+/* We start with a unit vector of B: this means we can easily
+ * draw an isoceles triangle centred on the X axis. */
+#ifdef TEST_VECTORS
+
+static vector v_unit()
+{
+    vector v;
+
+    v.b = 1;
+    v.a = v.c = v.d = 0;
+    return v;
+}
+
+#endif
+
+#define COS54 0.5877852
+#define SIN54 0.8090169
+#define COS18 0.9510565
+#define SIN18 0.3090169
+
+/* These two are a bit rough-and-ready for now. Note that B/C are
+ * 18 degrees from the x-axis, and A/D are 54 degrees. */
+double v_x(vector *vs, int i)
+{
+    return (vs[i].a + vs[i].d) * COS54 +
+           (vs[i].b + vs[i].c) * COS18;
+}
+
+double v_y(vector *vs, int i)
+{
+    return (vs[i].a - vs[i].d) * SIN54 +
+           (vs[i].b - vs[i].c) * SIN18;
+
+}
+
+static vector v_trans(vector v, vector trans)
+{
+    v.a += trans.a;
+    v.b += trans.b;
+    v.c += trans.c;
+    v.d += trans.d;
+    return v;
+}
+
+static vector v_rotate_36(vector v)
+{
+    vector vv;
+    vv.a = -v.d;
+    vv.b =  v.d + v.a;
+    vv.c = -v.d + v.b;
+    vv.d =  v.d + v.c;
+    return vv;
+}
+
+static vector v_rotate(vector v, int ang)
+{
+    int i;
+
+    assert((ang % 36) == 0);
+    while (ang < 0) ang += 360;
+    ang = 360-ang;
+    for (i = 0; i < (ang/36); i++)
+        v = v_rotate_36(v);
+    return v;
+}
+
+#ifdef TEST_VECTORS
+
+static vector v_scale(vector v, int sc)
+{
+    v.a *= sc;
+    v.b *= sc;
+    v.c *= sc;
+    v.d *= sc;
+    return v;
+}
+
+#endif
+
+static vector v_growphi(vector v)
+{
+    vector vv;
+    vv.a = v.a + v.b - v.d;
+    vv.b = v.c + v.d;
+    vv.c = v.a + v.b;
+    vv.d = v.c + v.d - v.a;
+    return vv;
+}
+
+static vector v_shrinkphi(vector v)
+{
+    vector vv;
+    vv.a = v.b - v.d;
+    vv.b = v.c + v.d - v.b;
+    vv.c = v.a + v.b - v.c;
+    vv.d = v.c - v.a;
+    return vv;
+}
+
+#ifdef TEST_VECTORS
+
+static const char *v_debug(vector v)
+{
+    static char buf[255];
+    sprintf(buf,
+             "(%d,%d,%d,%d)[%2.2f,%2.2f]",
+             v.a, v.b, v.c, v.d, v_x(&v,0), v_y(&v,0));
+    return buf;
+}
+
+#endif
+
+/* -------------------------------------------------------
+ * Tiling routines.
+ */
+
+vector xform_coord(vector vo, int shrink, vector vtrans, int ang)
+{
+    if (shrink < 0)
+        vo = v_shrinkphi(vo);
+    else if (shrink > 0)
+        vo = v_growphi(vo);
+
+    vo = v_rotate(vo, ang);
+    vo = v_trans(vo, vtrans);
+
+    return vo;
+}
+
+
+#define XFORM(n,o,s,a) vs[(n)] = xform_coord(v_edge, (s), vs[(o)], (a))
+
+static int penrose_p2_small(penrose_state *state, int depth, int flip,
+                            vector v_orig, vector v_edge);
+
+static int penrose_p2_large(penrose_state *state, int depth, int flip,
+                            vector v_orig, vector v_edge)
+{
+    vector vv_orig, vv_edge;
+
+#ifdef DEBUG_PENROSE
+    {
+        vector vs[3];
+        vs[0] = v_orig;
+        XFORM(1, 0, 0, 0);
+        XFORM(2, 0, 0, -36*flip);
+
+        state->new_tile(state, vs, 3, depth);
+    }
+#endif
+
+    if (flip > 0) {
+        vector vs[4];
+
+        vs[0] = v_orig;
+        XFORM(1, 0, 0, -36);
+        XFORM(2, 0, 0, 0);
+        XFORM(3, 0, 0, 36);
+
+        state->new_tile(state, vs, 4, depth);
+    }
+    if (depth >= state->max_depth) return 0;
+
+    vv_orig = v_trans(v_orig, v_rotate(v_edge, -36*flip));
+    vv_edge = v_rotate(v_edge, 108*flip);
+
+    penrose_p2_small(state, depth+1, flip,
+                     v_orig, v_shrinkphi(v_edge));
+
+    penrose_p2_large(state, depth+1, flip,
+                     vv_orig, v_shrinkphi(vv_edge));
+    penrose_p2_large(state, depth+1, -flip,
+                     vv_orig, v_shrinkphi(vv_edge));
+
+    return 0;
+}
+
+static int penrose_p2_small(penrose_state *state, int depth, int flip,
+                            vector v_orig, vector v_edge)
+{
+    vector vv_orig;
+
+#ifdef DEBUG_PENROSE
+    {
+        vector vs[3];
+        vs[0] = v_orig;
+        XFORM(1, 0, 0, 0);
+        XFORM(2, 0, -1, -36*flip);
+
+        state->new_tile(state, vs, 3, depth);
+    }
+#endif
+
+    if (flip > 0) {
+        vector vs[4];
+
+        vs[0] = v_orig;
+        XFORM(1, 0, 0, -72);
+        XFORM(2, 0, -1, -36);
+        XFORM(3, 0, 0, 0);
+
+        state->new_tile(state, vs, 4, depth);
+    }
+
+    if (depth >= state->max_depth) return 0;
+
+    vv_orig = v_trans(v_orig, v_edge);
+
+    penrose_p2_large(state, depth+1, -flip,
+                     v_orig, v_shrinkphi(v_rotate(v_edge, -36*flip)));
+
+    penrose_p2_small(state, depth+1, flip,
+                     vv_orig, v_shrinkphi(v_rotate(v_edge, -144*flip)));
+
+    return 0;
+}
+
+static int penrose_p3_small(penrose_state *state, int depth, int flip,
+                            vector v_orig, vector v_edge);
+
+static int penrose_p3_large(penrose_state *state, int depth, int flip,
+                            vector v_orig, vector v_edge)
+{
+    vector vv_orig;
+
+#ifdef DEBUG_PENROSE
+    {
+        vector vs[3];
+        vs[0] = v_orig;
+        XFORM(1, 0, 1, 0);
+        XFORM(2, 0, 0, -36*flip);
+
+        state->new_tile(state, vs, 3, depth);
+    }
+#endif
+
+    if (flip > 0) {
+        vector vs[4];
+
+        vs[0] = v_orig;
+        XFORM(1, 0, 0, -36);
+        XFORM(2, 0, 1, 0);
+        XFORM(3, 0, 0, 36);
+
+        state->new_tile(state, vs, 4, depth);
+    }
+    if (depth >= state->max_depth) return 0;
+
+    vv_orig = v_trans(v_orig, v_edge);
+
+    penrose_p3_large(state, depth+1, -flip,
+                     vv_orig, v_shrinkphi(v_rotate(v_edge, 180)));
+
+    penrose_p3_small(state, depth+1, flip,
+                     vv_orig, v_shrinkphi(v_rotate(v_edge, -108*flip)));
+
+    vv_orig = v_trans(v_orig, v_growphi(v_edge));
+
+    penrose_p3_large(state, depth+1, flip,
+                     vv_orig, v_shrinkphi(v_rotate(v_edge, -144*flip)));
+
+
+    return 0;
+}
+
+static int penrose_p3_small(penrose_state *state, int depth, int flip,
+                            vector v_orig, vector v_edge)
+{
+    vector vv_orig;
+
+#ifdef DEBUG_PENROSE
+    {
+        vector vs[3];
+        vs[0] = v_orig;
+        XFORM(1, 0, 0, 0);
+        XFORM(2, 0, 0, -36*flip);
+
+        state->new_tile(state, vs, 3, depth);
+    }
+#endif
+
+    if (flip > 0) {
+        vector vs[4];
+
+        vs[0] = v_orig;
+        XFORM(1, 0, 0, -36);
+        XFORM(3, 0, 0, 0);
+        XFORM(2, 3, 0, -36);
+
+        state->new_tile(state, vs, 4, depth);
+    }
+    if (depth >= state->max_depth) return 0;
+
+    /* NB these two are identical to the first two of p3_large. */
+    vv_orig = v_trans(v_orig, v_edge);
+
+    penrose_p3_large(state, depth+1, -flip,
+                     vv_orig, v_shrinkphi(v_rotate(v_edge, 180)));
+
+    penrose_p3_small(state, depth+1, flip,
+                     vv_orig, v_shrinkphi(v_rotate(v_edge, -108*flip)));
+
+    return 0;
+}
+
+/* -------------------------------------------------------
+ * Utility routines.
+ */
+
+double penrose_side_length(double start_size, int depth)
+{
+  return start_size / pow(PHI, depth);
+}
+
+void penrose_count_tiles(int depth, int *nlarge, int *nsmall)
+{
+  /* Steal sgt's fibonacci thingummy. */
+}
+
+/*
+ * It turns out that an acute isosceles triangle with sides in ratio 1:phi:phi
+ * has an incentre which is conveniently 2*phi^-2 of the way from the apex to
+ * the base. Why's that convenient? Because: if we situate the incentre of the
+ * triangle at the origin, then we can place the apex at phi^-2 * (B+C), and
+ * the other two vertices at apex-B and apex-C respectively. So that's an acute
+ * triangle with its long sides of unit length, covering a circle about the
+ * origin of radius 1-(2*phi^-2), which is conveniently enough phi^-3.
+ *
+ * (later mail: this is an overestimate by about 5%)
+ */
+
+int penrose(penrose_state *state, int which)
+{
+    vector vo = v_origin();
+    vector vb = v_origin();
+
+    vo.b = vo.c = -state->start_size;
+    vo = v_shrinkphi(v_shrinkphi(vo));
+
+    vb.b = state->start_size;
+
+    if (which == PENROSE_P2)
+        return penrose_p2_large(state, 0, 1, vo, vb);
+    else
+        return penrose_p3_small(state, 0, 1, vo, vb);
+}
+
+/*
+ * We're asked for a MxN grid, which just means a tiling fitting into roughly
+ * an MxN space in some kind of reasonable unit - say, the side length of the
+ * two-arrow edges of the tiles. By some reasoning in a previous email, that
+ * means we want to pick some subarea of a circle of radius 3.11*sqrt(M^2+N^2).
+ * To cover that circle, we need to subdivide a triangle large enough that it
+ * contains a circle of that radius.
+ *
+ * Hence: start with those three vectors marking triangle vertices, scale them
+ * all up by phi repeatedly until the radius of the inscribed circle gets
+ * bigger than the target, and then recurse into that triangle with the same
+ * recursion depth as the number of times you scaled up. That will give you
+ * tiles of unit side length, covering a circle big enough that if you randomly
+ * choose an orientation and coordinates within the circle, you'll be able to
+ * get any valid piece of Penrose tiling of size MxN.
+ */
+#define INCIRCLE_RADIUS 0.22426 /* phi^-3 less 5%: see above */
+
+void penrose_calculate_size(int which, int tilesize, int w, int h,
+                            double *required_radius, int *start_size, int *depth)
+{
+    double rradius, size;
+    int n = 0;
+
+    /*
+     * Fudge factor to scale P2 and P3 tilings differently. This
+     * doesn't seem to have much relevance to questions like the
+     * average number of tiles per unit area; it's just aesthetic.
+     */
+    if (which == PENROSE_P2)
+        tilesize = tilesize * 3 / 2;
+    else
+        tilesize = tilesize * 5 / 4;
+
+    rradius = tilesize * 3.11 * sqrt((double)(w*w + h*h));
+    size = tilesize;
+
+    while ((size * INCIRCLE_RADIUS) < rradius) {
+        n++;
+        size = size * PHI;
+    }
+
+    *start_size = (int)size;
+    *depth = n;
+    *required_radius = rradius;
+}
+
+/* -------------------------------------------------------
+ * Test code.
+ */
+
+#ifdef TEST_PENROSE
+
+#include <stdio.h>
+#include <string.h>
+
+int show_recursion = 0;
+int ntiles, nfinal;
+
+int test_cb(penrose_state *state, vector *vs, int n, int depth)
+{
+    int i, xoff = 0, yoff = 0;
+    double l = penrose_side_length(state->start_size, depth);
+    double rball = l / 10.0;
+    const char *col;
+
+    ntiles++;
+    if (state->max_depth == depth) {
+        col = n == 4 ? "black" : "green";
+        nfinal++;
+    } else {
+        if (!show_recursion)
+            return 0;
+        col = n == 4 ? "red" : "blue";
+    }
+    if (n != 4) yoff = state->start_size;
+
+    printf("<polygon points=\"");
+    for (i = 0; i < n; i++) {
+        printf("%s%f,%f", (i == 0) ? "" : " ",
+               v_x(vs, i) + xoff, v_y(vs, i) + yoff);
+    }
+    printf("\" style=\"fill: %s; fill-opacity: 0.2; stroke: %s\" />\n", col, col);
+    printf("<ellipse cx=\"%f\" cy=\"%f\" rx=\"%f\" ry=\"%f\" fill=\"%s\" />",
+           v_x(vs, 0) + xoff, v_y(vs, 0) + yoff, rball, rball, col);
+
+    return 0;
+}
+
+void usage_exit()
+{
+    fprintf(stderr, "Usage: penrose-test [--recursion] P2|P3 SIZE DEPTH\n");
+    exit(1);
+}
+
+int main(int argc, char *argv[])
+{
+    penrose_state ps;
+    int which = 0;
+
+    while (--argc > 0) {
+        char *p = *++argv;
+        if (!strcmp(p, "-h") || !strcmp(p, "--help")) {
+            usage_exit();
+        } else if (!strcmp(p, "--recursion")) {
+            show_recursion = 1;
+        } else if (*p == '-') {
+            fprintf(stderr, "Unrecognised option '%s'\n", p);
+            exit(1);
+        } else {
+            break;
+        }
+    }
+
+    if (argc < 3) usage_exit();
+
+    if (strcmp(argv[0], "P2") == 0) which = PENROSE_P2;
+    else if (strcmp(argv[0], "P3") == 0) which = PENROSE_P3;
+    else usage_exit();
+
+    ps.start_size = atoi(argv[1]);
+    ps.max_depth = atoi(argv[2]);
+    ps.new_tile = test_cb;
+
+    ntiles = nfinal = 0;
+
+    printf("\
+<?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");
+
+    printf("<g>\n");
+    penrose(&ps, which);
+    printf("</g>\n");
+
+    printf("<!-- %d tiles and %d leaf tiles total -->\n",
+           ntiles, nfinal);
+
+    printf("</svg>");
+
+    return 0;
+}
+
+#endif
+
+#ifdef TEST_VECTORS
+
+static void dbgv(const char *msg, vector v)
+{
+    printf("%s: %s\n", msg, v_debug(v));
+}
+
+int main(int argc, const char *argv[])
+{
+   vector v = v_unit();
+
+   dbgv("unit vector", v);
+   v = v_rotate(v, 36);
+   dbgv("rotated 36", v);
+   v = v_scale(v, 2);
+   dbgv("scaled x2", v);
+   v = v_shrinkphi(v);
+   dbgv("shrunk phi", v);
+   v = v_rotate(v, -36);
+   dbgv("rotated -36", v);
+
+   return 0;
+}
+
+#endif
+/* vim: set shiftwidth=4 tabstop=8: */
--- /dev/null
+++ b/penrose.h
@@ -1,0 +1,59 @@
+/* penrose.h
+ *
+ * Penrose tiling functions.
+ *
+ * Provides an interface with which to generate Penrose tilings
+ * by recursive subdivision of an initial tile of choice (one of the
+ * four sets of two pairs kite/dart, or thin/thick rhombus).
+ *
+ * You supply a callback function and a context pointer, which is
+ * called with each tile in turn: you choose how many times to recurse.
+ */
+
+#ifndef _PENROSE_H
+#define _PENROSE_H
+
+#ifndef PHI
+#define PHI 1.6180339887
+#endif
+
+typedef struct vector vector;
+
+double v_x(vector *vs, int i);
+double v_y(vector *vs, int i);
+
+typedef struct penrose_state penrose_state;
+
+/* Return non-zero to clip the tree here (i.e. not recurse
+ * below this tile).
+ *
+ * Parameters are state, vector array, npoints, depth.
+ * ctx is inside state.
+ */
+typedef int (*tile_callback)(penrose_state *, vector *, int, int);
+
+struct penrose_state {
+    int start_size;  /* initial side length */
+    int max_depth;      /* Recursion depth */
+
+    tile_callback new_tile;
+    void *ctx;          /* for callback */
+};
+
+enum { PENROSE_P2, PENROSE_P3 };
+
+extern int penrose(penrose_state *state, int which);
+
+/* Returns the side-length of a penrose tile at recursion level
+ * gen, given a starting side length. */
+extern double penrose_side_length(double start_size, int depth);
+
+/* Returns the count of each type of tile at a given recursion depth. */
+extern void penrose_count_tiles(int gen, int *nlarge, int *nsmall);
+
+/* Calculate start size and recursion depth required to produce a
+ * width-by-height sized patch of penrose tiles with the given tilesize */
+extern void penrose_calculate_size(int which, int tilesize, int w, int h,
+                                   double *required_radius, int *start_size, int *depth);
+
+#endif