chiark / gitweb /
Substantial infrastructure upheaval. I've separated the drawing API
[sgt-puzzles.git] / net.c
diff --git a/net.c b/net.c
index 7c14f6f68ef396c4cb469ce773cf83c2ea617a48..e205341a11dc6d5e6b7c1e428dedd59191197540 100644 (file)
--- a/net.c
+++ b/net.c
@@ -293,7 +293,7 @@ static game_params *custom_params(config_item *cfg)
     return ret;
 }
 
-static char *validate_params(game_params *params)
+static char *validate_params(game_params *params, int full)
 {
     if (params->width <= 0 || params->height <= 0)
        return "Width and height must both be greater than zero";
@@ -347,7 +347,7 @@ static char *validate_params(game_params *params)
      * is at least 2^(number of such rows), and in particular is at
      * least 2 since there must be at least one such row. []
      */
-    if (params->unique && params->wrapping &&
+    if (full && params->unique && params->wrapping &&
         (params->width == 2 || params->height == 2))
         return "No wrapping puzzle with a width or height of 2 can have"
         " a unique solution";
@@ -382,29 +382,6 @@ static char *validate_params(game_params *params)
  * avoidance is required.
  */
 
-static int dsf_canonify(int *dsf, int val)
-{
-    int v2 = val;
-
-    while (dsf[val] != val)
-       val = dsf[val];
-
-    while (v2 != val) {
-       int tmp = dsf[v2];
-       dsf[v2] = val;
-       v2 = tmp;
-    }
-
-    return val;
-}
-
-static void dsf_merge(int *dsf, int v1, int v2)
-{
-    v1 = dsf_canonify(dsf, v1);
-    v2 = dsf_canonify(dsf, v2);
-    dsf[v2] = v1;
-}
-
 struct todo {
     unsigned char *marked;
     int *buffer;
@@ -1540,7 +1517,7 @@ static char *validate_desc(game_params *params, char *desc)
  * Construct an initial game state, given a description and parameters.
  */
 
-static game_state *new_game(midend_data *me, game_params *params, char *desc)
+static game_state *new_game(midend *me, game_params *params, char *desc)
 {
     game_state *state;
     int w, h, x, y;
@@ -1683,6 +1660,8 @@ static char *solve_game(game_state *state, game_state *currstate,
                 tiles[i] = c - 'a' + 10;
             else if (c >= 'A' && c <= 'F')
                 tiles[i] = c - 'A' + 10;
+
+           tiles[i] |= LOCKED;
         }
     }
 
@@ -1844,7 +1823,7 @@ static void free_ui(game_ui *ui)
     sfree(ui);
 }
 
-char *encode_ui(game_ui *ui)
+static char *encode_ui(game_ui *ui)
 {
     char buf[120];
     /*
@@ -1855,7 +1834,7 @@ char *encode_ui(game_ui *ui)
     return dupstr(buf);
 }
 
-void decode_ui(game_ui *ui, char *encoding)
+static void decode_ui(game_ui *ui, char *encoding)
 {
     sscanf(encoding, "O%d,%d;C%d,%d",
           &ui->org_x, &ui->org_y, &ui->cx, &ui->cy);
@@ -1881,11 +1860,16 @@ static char *interpret_move(game_state *state, game_ui *ui,
                            game_drawstate *ds, int x, int y, int button)
 {
     char *nullret;
-    int tx, ty;
+    int tx = -1, ty = -1, dir = 0;
     int shift = button & MOD_SHFT, ctrl = button & MOD_CTRL;
+    enum {
+        NONE, ROTATE_LEFT, ROTATE_180, ROTATE_RIGHT, TOGGLE_LOCK, JUMBLE,
+        MOVE_ORIGIN, MOVE_SOURCE, MOVE_ORIGIN_AND_SOURCE, MOVE_CURSOR
+    } action;
 
     button &= ~MOD_MASK;
     nullret = NULL;
+    action = NONE;
 
     if (button == LEFT_BUTTON ||
        button == MIDDLE_BUTTON ||
@@ -1913,9 +1897,11 @@ static char *interpret_move(game_state *state, game_ui *ui,
        if (x % TILE_SIZE >= TILE_SIZE - TILE_BORDER ||
            y % TILE_SIZE >= TILE_SIZE - TILE_BORDER)
            return nullret;
+
+        action = button == LEFT_BUTTON ? ROTATE_LEFT :
+                 button == RIGHT_BUTTON ? ROTATE_RIGHT : TOGGLE_LOCK;
     } else if (button == CURSOR_UP || button == CURSOR_DOWN ||
               button == CURSOR_RIGHT || button == CURSOR_LEFT) {
-        int dir;
         switch (button) {
           case CURSOR_UP:       dir = U; break;
           case CURSOR_DOWN:     dir = D; break;
@@ -1923,44 +1909,28 @@ static char *interpret_move(game_state *state, game_ui *ui,
           case CURSOR_RIGHT:    dir = R; break;
           default:              return nullret;
         }
-        if (shift) {
-            /*
-             * Move origin.
-             */
-            if (state->wrapping) {
-                OFFSET(ui->org_x, ui->org_y, ui->org_x, ui->org_y, dir, state);
-            } else return nullret; /* disallowed for non-wrapping grids */
-        }
-        if (ctrl) {
-            /*
-             * Change source tile.
-             */
-            OFFSET(ui->cx, ui->cy, ui->cx, ui->cy, dir, state);
-        }
-        if (!shift && !ctrl) {
-            /*
-             * Move keyboard cursor.
-             */
-            OFFSET(ui->cur_x, ui->cur_y, ui->cur_x, ui->cur_y, dir, state);
-            ui->cur_visible = TRUE;
-        }
-        return "";                    /* UI activity has occurred */
+        if (shift && ctrl) action = MOVE_ORIGIN_AND_SOURCE;
+        else if (shift)    action = MOVE_ORIGIN;
+        else if (ctrl)     action = MOVE_SOURCE;
+        else               action = MOVE_CURSOR;
     } else if (button == 'a' || button == 's' || button == 'd' ||
               button == 'A' || button == 'S' || button == 'D' ||
+               button == 'f' || button == 'F' ||
               button == CURSOR_SELECT) {
        tx = ui->cur_x;
        ty = ui->cur_y;
        if (button == 'a' || button == 'A' || button == CURSOR_SELECT)
-           button = LEFT_BUTTON;
+           action = ROTATE_LEFT;
        else if (button == 's' || button == 'S')
-           button = MIDDLE_BUTTON;
+           action = TOGGLE_LOCK;
        else if (button == 'd' || button == 'D')
-           button = RIGHT_BUTTON;
+           action = ROTATE_RIGHT;
+        else if (button == 'f' || button == 'F')
+            action = ROTATE_180;
         ui->cur_visible = TRUE;
     } else if (button == 'j' || button == 'J') {
        /* XXX should we have some mouse control for this? */
-       button = 'J';   /* canonify */
-       tx = ty = -1;   /* shut gcc up :( */
+       action = JUMBLE;
     } else
        return nullret;
 
@@ -1974,11 +1944,12 @@ static char *interpret_move(game_state *state, game_ui *ui,
      * accident. If they change their mind, another middle click
      * unlocks it.)
      */
-    if (button == MIDDLE_BUTTON) {
+    if (action == TOGGLE_LOCK) {
        char buf[80];
        sprintf(buf, "L%d,%d", tx, ty);
        return dupstr(buf);
-    } else if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
+    } else if (action == ROTATE_LEFT || action == ROTATE_RIGHT ||
+               action == ROTATE_180) {
        char buf[80];
 
         /*
@@ -1992,9 +1963,10 @@ static char *interpret_move(game_state *state, game_ui *ui,
          * Otherwise, turn the tile one way or the other. Left button
          * turns anticlockwise; right button turns clockwise.
          */
-       sprintf(buf, "%c%d,%d", (button == LEFT_BUTTON ? 'A' : 'C'), tx, ty);
+       sprintf(buf, "%c%d,%d", (int)(action == ROTATE_LEFT ? 'A' :
+                                      action == ROTATE_RIGHT ? 'C' : 'F'), tx, ty);
        return dupstr(buf);
-    } else if (button == 'J') {
+    } else if (action == JUMBLE) {
         /*
          * Jumble all unlocked tiles to random orientations.
          */
@@ -2027,6 +1999,22 @@ static char *interpret_move(game_state *state, game_ui *ui,
        ret = sresize(ret, p - ret, char);
 
        return ret;
+    } else if (action == MOVE_ORIGIN || action == MOVE_SOURCE ||
+               action == MOVE_ORIGIN_AND_SOURCE || action == MOVE_CURSOR) {
+        assert(dir != 0);
+        if (action == MOVE_ORIGIN || action == MOVE_ORIGIN_AND_SOURCE) {
+            if (state->wrapping) {
+                 OFFSET(ui->org_x, ui->org_y, ui->org_x, ui->org_y, dir, state);
+            } else return nullret; /* disallowed for non-wrapping grids */
+        }
+        if (action == MOVE_SOURCE || action == MOVE_ORIGIN_AND_SOURCE) {
+            OFFSET(ui->cx, ui->cy, ui->cx, ui->cy, dir, state);
+        }
+        if (action == MOVE_CURSOR) {
+            OFFSET(ui->cur_x, ui->cur_y, ui->cur_x, ui->cur_y, dir, state);
+            ui->cur_visible = TRUE;
+        }
+        return "";
     } else {
        return NULL;
     }
@@ -2066,10 +2054,8 @@ static game_state *execute_move(game_state *from, char *move)
                    ret->last_rotate_dir = +1;
            } else if (move[0] == 'F') {
                tile(ret, tx, ty) = F(orig);
-               if (!noanim) {
-                   free_game(ret);
-                   return NULL;
-               }
+               if (!noanim)
+                    ret->last_rotate_dir = +2; /* + for sake of argument */
            } else if (move[0] == 'C') {
                tile(ret, tx, ty) = C(orig);
                if (!noanim)
@@ -2125,7 +2111,7 @@ static game_state *execute_move(game_state *from, char *move)
  * Routines for drawing the game position on the screen.
  */
 
-static game_drawstate *game_new_drawstate(game_state *state)
+static game_drawstate *game_new_drawstate(drawing *dr, game_state *state)
 {
     game_drawstate *ds = snew(game_drawstate);
 
@@ -2140,31 +2126,23 @@ static game_drawstate *game_new_drawstate(game_state *state)
     return ds;
 }
 
-static void game_free_drawstate(game_drawstate *ds)
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
 {
     sfree(ds->visible);
     sfree(ds);
 }
 
-static void game_size(game_params *params, game_drawstate *ds, int *x, int *y,
-                      int expand)
+static void game_compute_size(game_params *params, int tilesize,
+                             int *x, int *y)
 {
-    int tsx, tsy, ts;
-    /*
-     * Each window dimension equals the tile size times the grid
-     * dimension, plus TILE_BORDER, plus twice WINDOW_OFFSET.
-     */
-    tsx = (*x - 2*WINDOW_OFFSET - TILE_BORDER) / params->width;
-    tsy = (*y - 2*WINDOW_OFFSET - TILE_BORDER) / params->height;
-    ts = min(tsx, tsy);
-
-    if (expand)
-        ds->tilesize = ts;
-    else
-        ds->tilesize = min(ts, PREFERRED_TILE_SIZE);
+    *x = WINDOW_OFFSET * 2 + tilesize * params->width + TILE_BORDER;
+    *y = WINDOW_OFFSET * 2 + tilesize * params->height + TILE_BORDER;
+}
 
-    *x = WINDOW_OFFSET * 2 + TILE_SIZE * params->width + TILE_BORDER;
-    *y = WINDOW_OFFSET * 2 + TILE_SIZE * params->height + TILE_BORDER;
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                         game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
 }
 
 static float *game_colours(frontend *fe, game_state *state, int *ncolours)
@@ -2225,17 +2203,17 @@ static float *game_colours(frontend *fe, game_state *state, int *ncolours)
     return ret;
 }
 
-static void draw_thick_line(frontend *fe, int x1, int y1, int x2, int y2,
+static void draw_thick_line(drawing *dr, int x1, int y1, int x2, int y2,
                             int colour)
 {
-    draw_line(fe, x1-1, y1, x2-1, y2, COL_WIRE);
-    draw_line(fe, x1+1, y1, x2+1, y2, COL_WIRE);
-    draw_line(fe, x1, y1-1, x2, y2-1, COL_WIRE);
-    draw_line(fe, x1, y1+1, x2, y2+1, COL_WIRE);
-    draw_line(fe, x1, y1, x2, y2, colour);
+    draw_line(dr, x1-1, y1, x2-1, y2, COL_WIRE);
+    draw_line(dr, x1+1, y1, x2+1, y2, COL_WIRE);
+    draw_line(dr, x1, y1-1, x2, y2-1, COL_WIRE);
+    draw_line(dr, x1, y1+1, x2, y2+1, COL_WIRE);
+    draw_line(dr, x1, y1, x2, y2, colour);
 }
 
-static void draw_rect_coords(frontend *fe, int x1, int y1, int x2, int y2,
+static void draw_rect_coords(drawing *dr, int x1, int y1, int x2, int y2,
                              int colour)
 {
     int mx = (x1 < x2 ? x1 : x2);
@@ -2243,13 +2221,13 @@ static void draw_rect_coords(frontend *fe, int x1, int y1, int x2, int y2,
     int dx = (x2 + x1 - 2*mx + 1);
     int dy = (y2 + y1 - 2*my + 1);
 
-    draw_rect(fe, mx, my, dx, dy, colour);
+    draw_rect(dr, mx, my, dx, dy, colour);
 }
 
 /*
  * draw_barrier_corner() and draw_barrier() are passed physical coords
  */
-static void draw_barrier_corner(frontend *fe, game_drawstate *ds,
+static void draw_barrier_corner(drawing *dr, game_drawstate *ds,
                                 int x, int y, int dx, int dy, int phase)
 {
     int bx = WINDOW_OFFSET + TILE_SIZE * x;
@@ -2260,20 +2238,20 @@ static void draw_barrier_corner(frontend *fe, game_drawstate *ds,
     y1 = (dy > 0 ? TILE_SIZE+TILE_BORDER-1 : 0);
 
     if (phase == 0) {
-        draw_rect_coords(fe, bx+x1+dx, by+y1,
+        draw_rect_coords(dr, bx+x1+dx, by+y1,
                          bx+x1-TILE_BORDER*dx, by+y1-(TILE_BORDER-1)*dy,
                          COL_WIRE);
-        draw_rect_coords(fe, bx+x1, by+y1+dy,
+        draw_rect_coords(dr, bx+x1, by+y1+dy,
                          bx+x1-(TILE_BORDER-1)*dx, by+y1-TILE_BORDER*dy,
                          COL_WIRE);
     } else {
-        draw_rect_coords(fe, bx+x1, by+y1,
+        draw_rect_coords(dr, bx+x1, by+y1,
                          bx+x1-(TILE_BORDER-1)*dx, by+y1-(TILE_BORDER-1)*dy,
                          COL_BARRIER);
     }
 }
 
-static void draw_barrier(frontend *fe, game_drawstate *ds,
+static void draw_barrier(drawing *dr, game_drawstate *ds,
                          int x, int y, int dir, int phase)
 {
     int bx = WINDOW_OFFSET + TILE_SIZE * x;
@@ -2286,16 +2264,16 @@ static void draw_barrier(frontend *fe, game_drawstate *ds,
     h = (Y(dir) ? TILE_BORDER : TILE_SIZE - TILE_BORDER);
 
     if (phase == 0) {
-        draw_rect(fe, bx+x1-X(dir), by+y1-Y(dir), w, h, COL_WIRE);
+        draw_rect(dr, bx+x1-X(dir), by+y1-Y(dir), w, h, COL_WIRE);
     } else {
-        draw_rect(fe, bx+x1, by+y1, w, h, COL_BARRIER);
+        draw_rect(dr, bx+x1, by+y1, w, h, COL_BARRIER);
     }
 }
 
 /*
  * draw_tile() is passed physical coordinates
  */
-static void draw_tile(frontend *fe, game_state *state, game_drawstate *ds,
+static void draw_tile(drawing *dr, game_state *state, game_drawstate *ds,
                       int x, int y, int tile, int src, float angle, int cursor)
 {
     int bx = WINDOW_OFFSET + TILE_SIZE * x;
@@ -2311,16 +2289,16 @@ static void draw_tile(frontend *fe, game_state *state, game_drawstate *ds,
      * we must draw those connections on the borders themselves.
      */
 
-    clip(fe, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
+    clip(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
 
     /*
      * So. First blank the tile out completely: draw a big
      * rectangle in border colour, and a smaller rectangle in
      * background colour to fill it in.
      */
-    draw_rect(fe, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER,
+    draw_rect(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER,
               COL_BORDER);
-    draw_rect(fe, bx+TILE_BORDER, by+TILE_BORDER,
+    draw_rect(dr, bx+TILE_BORDER, by+TILE_BORDER,
               TILE_SIZE-TILE_BORDER, TILE_SIZE-TILE_BORDER,
               tile & LOCKED ? COL_LOCKED : COL_BACKGROUND);
 
@@ -2330,16 +2308,16 @@ static void draw_tile(frontend *fe, game_state *state, game_drawstate *ds,
      * in.
      */
     if (cursor) {
-       draw_line(fe, bx+TILE_SIZE/8, by+TILE_SIZE/8,
+       draw_line(dr, bx+TILE_SIZE/8, by+TILE_SIZE/8,
                  bx+TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8,
                  tile & LOCKED ? COL_BACKGROUND : COL_LOCKED);
-       draw_line(fe, bx+TILE_SIZE/8, by+TILE_SIZE/8,
+       draw_line(dr, bx+TILE_SIZE/8, by+TILE_SIZE/8,
                  bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE/8,
                  tile & LOCKED ? COL_BACKGROUND : COL_LOCKED);
-       draw_line(fe, bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE/8,
+       draw_line(dr, bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE/8,
                  bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8,
                  tile & LOCKED ? COL_BACKGROUND : COL_LOCKED);
-       draw_line(fe, bx+TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8,
+       draw_line(dr, bx+TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8,
                  bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8,
                  tile & LOCKED ? COL_BACKGROUND : COL_LOCKED);
     }
@@ -2362,7 +2340,7 @@ static void draw_tile(frontend *fe, game_state *state, game_drawstate *ds,
             ex = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * X(dir);
             ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir);
             MATMUL(tx, ty, matrix, ex, ey);
-            draw_thick_line(fe, bx+(int)cx, by+(int)cy,
+            draw_thick_line(dr, bx+(int)cx, by+(int)cy,
                            bx+(int)(cx+tx), by+(int)(cy+ty),
                             COL_WIRE);
         }
@@ -2372,7 +2350,7 @@ static void draw_tile(frontend *fe, game_state *state, game_drawstate *ds,
             ex = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * X(dir);
             ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir);
             MATMUL(tx, ty, matrix, ex, ey);
-            draw_line(fe, bx+(int)cx, by+(int)cy,
+            draw_line(dr, bx+(int)cx, by+(int)cy,
                      bx+(int)(cx+tx), by+(int)(cy+ty), col);
         }
     }
@@ -2405,8 +2383,7 @@ static void draw_tile(frontend *fe, game_state *state, game_drawstate *ds,
             points[i+1] = by+(int)(cy+ty);
         }
 
-        draw_polygon(fe, points, 4, TRUE, col);
-        draw_polygon(fe, points, 4, FALSE, COL_WIRE);
+        draw_polygon(dr, points, 4, col, COL_WIRE);
     }
 
     /*
@@ -2443,8 +2420,8 @@ static void draw_tile(frontend *fe, game_state *state, game_drawstate *ds,
              * in: if we are fully connected to the other tile then
              * the two ACTIVE states will be the same.)
              */
-            draw_rect_coords(fe, px-vx, py-vy, px+lx+vx, py+ly+vy, COL_WIRE);
-            draw_rect_coords(fe, px, py, px+lx, py+ly,
+            draw_rect_coords(dr, px-vx, py-vy, px+lx+vx, py+ly+vy, COL_WIRE);
+            draw_rect_coords(dr, px, py, px+lx, py+ly,
                              (tile & ACTIVE) ? COL_POWERED : COL_WIRE);
         } else {
             /*
@@ -2452,7 +2429,7 @@ static void draw_tile(frontend *fe, game_state *state, game_drawstate *ds,
              * actually connected to us. Just draw a single black
              * dot.
              */
-            draw_rect_coords(fe, px, py, px, py, COL_WIRE);
+            draw_rect_coords(dr, px, py, px, py, COL_WIRE);
         }
     }
 
@@ -2496,7 +2473,7 @@ static void draw_tile(frontend *fe, game_state *state, game_drawstate *ds,
                  * At least one barrier terminates here. Draw a
                  * corner.
                  */
-                draw_barrier_corner(fe, ds, x, y,
+                draw_barrier_corner(dr, ds, x, y,
                                     X(dir)+X(A(dir)), Y(dir)+Y(A(dir)),
                                     phase);
             }
@@ -2504,15 +2481,15 @@ static void draw_tile(frontend *fe, game_state *state, game_drawstate *ds,
 
         for (dir = 1; dir < 0x10; dir <<= 1)
             if (barrier(state, GX(x), GY(y)) & dir)
-                draw_barrier(fe, ds, x, y, dir, phase);
+                draw_barrier(dr, ds, x, y, dir, phase);
     }
 
-    unclip(fe);
+    unclip(dr);
 
-    draw_update(fe, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
+    draw_update(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
 }
 
-static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
+static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate,
                  game_state *state, int dir, game_ui *ui, float t, float ft)
 {
     int x, y, tx, ty, frame, last_rotate_dir, moved_origin = FALSE;
@@ -2528,7 +2505,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
 
         ds->started = TRUE;
 
-        draw_rect(fe, 0, 0, 
+        draw_rect(dr, 0, 0, 
                   WINDOW_OFFSET * 2 + TILE_SIZE * state->width + TILE_BORDER,
                   WINDOW_OFFSET * 2 + TILE_SIZE * state->height + TILE_BORDER,
                   COL_BACKGROUND);
@@ -2537,7 +2514,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
         ds->org_y = ui->org_y;
         moved_origin = TRUE;
 
-        draw_update(fe, 0, 0, 
+        draw_update(dr, 0, 0, 
                     WINDOW_OFFSET*2 + TILE_SIZE*state->width + TILE_BORDER,
                     WINDOW_OFFSET*2 + TILE_SIZE*state->height + TILE_BORDER);
 
@@ -2546,38 +2523,38 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
             for (x = 0; x < ds->width; x++) {
                 if (x+1 < ds->width) {
                     if (barrier(state, GX(x), GY(0)) & R)
-                        draw_barrier_corner(fe, ds, x, -1, +1, +1, phase);
+                        draw_barrier_corner(dr, ds, x, -1, +1, +1, phase);
                     if (barrier(state, GX(x), GY(ds->height-1)) & R)
-                        draw_barrier_corner(fe, ds, x, ds->height, +1, -1, phase);
+                        draw_barrier_corner(dr, ds, x, ds->height, +1, -1, phase);
                 }
                 if (barrier(state, GX(x), GY(0)) & U) {
-                    draw_barrier_corner(fe, ds, x, -1, -1, +1, phase);
-                    draw_barrier_corner(fe, ds, x, -1, +1, +1, phase);
-                    draw_barrier(fe, ds, x, -1, D, phase);
+                    draw_barrier_corner(dr, ds, x, -1, -1, +1, phase);
+                    draw_barrier_corner(dr, ds, x, -1, +1, +1, phase);
+                    draw_barrier(dr, ds, x, -1, D, phase);
                 }
                 if (barrier(state, GX(x), GY(ds->height-1)) & D) {
-                    draw_barrier_corner(fe, ds, x, ds->height, -1, -1, phase);
-                    draw_barrier_corner(fe, ds, x, ds->height, +1, -1, phase);
-                    draw_barrier(fe, ds, x, ds->height, U, phase);
+                    draw_barrier_corner(dr, ds, x, ds->height, -1, -1, phase);
+                    draw_barrier_corner(dr, ds, x, ds->height, +1, -1, phase);
+                    draw_barrier(dr, ds, x, ds->height, U, phase);
                 }
             }
 
             for (y = 0; y < ds->height; y++) {
                 if (y+1 < ds->height) {
                     if (barrier(state, GX(0), GY(y)) & D)
-                        draw_barrier_corner(fe, ds, -1, y, +1, +1, phase);
+                        draw_barrier_corner(dr, ds, -1, y, +1, +1, phase);
                     if (barrier(state, GX(ds->width-1), GY(y)) & D)
-                        draw_barrier_corner(fe, ds, ds->width, y, -1, +1, phase);
+                        draw_barrier_corner(dr, ds, ds->width, y, -1, +1, phase);
                 }
                 if (barrier(state, GX(0), GY(y)) & L) {
-                    draw_barrier_corner(fe, ds, -1, y, +1, -1, phase);
-                    draw_barrier_corner(fe, ds, -1, y, +1, +1, phase);
-                    draw_barrier(fe, ds, -1, y, R, phase);
+                    draw_barrier_corner(dr, ds, -1, y, +1, -1, phase);
+                    draw_barrier_corner(dr, ds, -1, y, +1, +1, phase);
+                    draw_barrier(dr, ds, -1, y, R, phase);
                 }
                 if (barrier(state, GX(ds->width-1), GY(y)) & R) {
-                    draw_barrier_corner(fe, ds, ds->width, y, -1, -1, phase);
-                    draw_barrier_corner(fe, ds, ds->width, y, -1, +1, phase);
-                    draw_barrier(fe, ds, ds->width, y, L, phase);
+                    draw_barrier_corner(dr, ds, ds->width, y, -1, -1, phase);
+                    draw_barrier_corner(dr, ds, ds->width, y, -1, +1, phase);
+                    draw_barrier(dr, ds, ds->width, y, L, phase);
                 }
             }
         }
@@ -2643,7 +2620,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                 index(state, ds->visible, x, y) != c ||
                 index(state, ds->visible, x, y) == 0xFF ||
                 is_src || is_anim || is_cursor) {
-                draw_tile(fe, state, ds, x, y, c,
+                draw_tile(dr, state, ds, x, y, c,
                           is_src, (is_anim ? angle : 0.0F), is_cursor);
                 if (is_src || is_anim || is_cursor)
                     index(state, ds->visible, x, y) = 0xFF;
@@ -2671,7 +2648,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                (state->used_solve ? "Auto-solved. " :
                 state->completed ? "COMPLETED! " : ""), a, n2);
 
-       status_bar(fe, statusbuf);
+       status_bar(dr, statusbuf);
     }
 
     sfree(active);
@@ -2725,11 +2702,148 @@ static int game_wants_statusbar(void)
     return TRUE;
 }
 
-static int game_timing_state(game_state *state)
+static int game_timing_state(game_state *state, game_ui *ui)
 {
     return TRUE;
 }
 
+static void game_print_size(game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    /*
+     * I'll use 8mm squares by default.
+     */
+    game_compute_size(params, 800, &pw, &ph);
+    *x = pw / 100.0;
+    *y = ph / 100.0;
+}
+
+static void draw_diagram(drawing *dr, game_drawstate *ds, int x, int y,
+                        int topleft, int v, int drawlines, int ink)
+{
+    int tx, ty, cx, cy, r, br, k, thick;
+
+    tx = WINDOW_OFFSET + TILE_SIZE * x;
+    ty = WINDOW_OFFSET + TILE_SIZE * y;
+
+    /*
+     * Find our centre point.
+     */
+    if (topleft) {
+       cx = tx + (v & L ? TILE_SIZE / 4 : TILE_SIZE / 6);
+       cy = ty + (v & U ? TILE_SIZE / 4 : TILE_SIZE / 6);
+       r = TILE_SIZE / 8;
+       br = TILE_SIZE / 32;
+    } else {
+       cx = tx + TILE_SIZE / 2;
+       cy = ty + TILE_SIZE / 2;
+       r = TILE_SIZE / 2;
+       br = TILE_SIZE / 8;
+    }
+    thick = r / 20;
+
+    /*
+     * Draw the square block if we have an endpoint.
+     */
+    if (v == 1 || v == 2 || v == 4 || v == 8)
+       draw_rect(dr, cx - br, cy - br, br*2, br*2, ink);
+
+    /*
+     * Draw each radial line.
+     */
+    if (drawlines) {
+       print_line_width(dr, thick * 2);
+       for (k = 1; k < 16; k *= 2)
+           if (v & k) {
+               int x1 = min(cx, cx + (r-thick) * X(k));
+               int x2 = max(cx, cx + (r-thick) * X(k));
+               int y1 = min(cy, cy + (r-thick) * Y(k));
+               int y2 = max(cy, cy + (r-thick) * Y(k));
+               draw_rect(dr, x1 - thick, y1 - thick,
+                         (x2 - x1) + 2*thick, (y2 - y1) + 2*thick, ink);
+           }
+    }
+}
+
+static void game_print(drawing *dr, game_state *state, int tilesize)
+{
+    int w = state->width, h = state->height;
+    int ink = print_mono_colour(dr, 0);
+    int x, y;
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    game_drawstate ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    /*
+     * Border.
+     */
+    print_line_width(dr, TILE_SIZE / (state->wrapping ? 128 : 12));
+    draw_rect_outline(dr, WINDOW_OFFSET, WINDOW_OFFSET,
+                     TILE_SIZE * w, TILE_SIZE * h, ink);
+
+    /*
+     * Grid.
+     */
+    print_line_width(dr, TILE_SIZE / 128);
+    for (x = 1; x < w; x++)
+       draw_line(dr, WINDOW_OFFSET + TILE_SIZE * x, WINDOW_OFFSET,
+                 WINDOW_OFFSET + TILE_SIZE * x, WINDOW_OFFSET + TILE_SIZE * h,
+                 ink);
+    for (y = 1; y < h; y++)
+       draw_line(dr, WINDOW_OFFSET, WINDOW_OFFSET + TILE_SIZE * y,
+                 WINDOW_OFFSET + TILE_SIZE * w, WINDOW_OFFSET + TILE_SIZE * y,
+                 ink);
+
+    /*
+     * Barriers.
+     */
+    for (y = 0; y <= h; y++)
+       for (x = 0; x <= w; x++) {
+           int b = barrier(state, x % w, y % h);
+           fprintf(stderr, "%d,%d: %d\n", x, y, b);
+           if (x < w && (b & U))
+               draw_rect(dr, WINDOW_OFFSET + TILE_SIZE * x - TILE_SIZE/24,
+                         WINDOW_OFFSET + TILE_SIZE * y - TILE_SIZE/24,
+                         TILE_SIZE + TILE_SIZE/24 * 2, TILE_SIZE/24 * 2, ink);
+           if (y < h && (b & L))
+               draw_rect(dr, WINDOW_OFFSET + TILE_SIZE * x - TILE_SIZE/24,
+                         WINDOW_OFFSET + TILE_SIZE * y - TILE_SIZE/24,
+                         TILE_SIZE/24 * 2, TILE_SIZE + TILE_SIZE/24 * 2, ink);
+       }
+
+    /*
+     * Grid contents.
+     */
+    for (y = 0; y < h; y++)
+       for (x = 0; x < w; x++) {
+           int vx, v = tile(state, x, y);
+           int locked = v & LOCKED;
+
+           v &= 0xF;
+
+           /*
+            * Rotate into a standard orientation for the top left
+            * corner diagram.
+            */
+           vx = v;
+           while (vx != 0 && vx != 15 && vx != 1 && vx != 9 && vx != 13 &&
+                  vx != 5)
+               vx = A(vx);
+
+           /*
+            * Draw the top left corner diagram.
+            */
+           draw_diagram(dr, ds, x, y, TRUE, vx, TRUE, ink);
+
+           /*
+            * Draw the real solution diagram, if we're doing so.
+            */
+           draw_diagram(dr, ds, x, y, FALSE, v, locked, ink);
+       }
+}
+
 #ifdef COMBINED
 #define thegame net
 #endif
@@ -2758,13 +2872,14 @@ const struct game thegame = {
     game_changed_state,
     interpret_move,
     execute_move,
-    game_size,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
     game_colours,
     game_new_drawstate,
     game_free_drawstate,
     game_redraw,
     game_anim_length,
     game_flash_length,
+    TRUE, FALSE, game_print_size, game_print,
     game_wants_statusbar,
     FALSE, game_timing_state,
     0,                                /* mouse_priorities */