chiark / gitweb /
New puzzle: `Slant', picked from the Japanese-language section of
authorSimon Tatham <anakin@pobox.com>
Tue, 2 Aug 2005 23:16:46 +0000 (23:16 +0000)
committerSimon Tatham <anakin@pobox.com>
Tue, 2 Aug 2005 23:16:46 +0000 (23:16 +0000)
nikoli.co.jp (which has quite a few puzzles that they don't seem to
have bothered to translate into English).

Minor structural change: the disjoint set forest code used in the
Net solver has come in handy again, so I've moved it out into its
own module dsf.c.

[originally from svn r6155]

Recipe
dsf.c [new file with mode: 0644]
list.c
net.c
print.py
puzzles.but
puzzles.h
slant.c [new file with mode: 0644]

diff --git a/Recipe b/Recipe
index 92c09a42c493ddaa6de350edf5a12c740d723ed3..55b0213c3e1b21780169c5e3bb79a27c56887e40 100644 (file)
--- a/Recipe
+++ b/Recipe
 
 WINDOWS  = windows user32.lib gdi32.lib comctl32.lib comdlg32.lib
 COMMON   = midend misc malloc random version
-NET      = net tree234
+NET      = net tree234 dsf
 NETSLIDE = netslide tree234
 MINES    = mines tree234
 FLIP     = flip tree234
 PEGS     = pegs tree234
 UNTANGLE = untangle tree234
+SLANT    = slant dsf
 
 ALL      = list NET NETSLIDE cube fifteen sixteen rect pattern solo twiddle
-         + MINES samegame FLIP guess PEGS dominosa UNTANGLE blackbox
+         + MINES samegame FLIP guess PEGS dominosa UNTANGLE blackbox SLANT
 
 net      : [X] gtk COMMON NET
 netslide : [X] gtk COMMON NETSLIDE
@@ -42,6 +43,7 @@ pegs     : [X] gtk COMMON PEGS
 dominosa : [X] gtk COMMON dominosa
 untangle : [X] gtk COMMON UNTANGLE
 blackbox : [X] gtk COMMON blackbox
+slant    : [X] gtk COMMON SLANT
 
 # Auxiliary command-line programs.
 solosolver :    [U] solo[STANDALONE_SOLVER] malloc
@@ -71,6 +73,7 @@ pegs     : [G] WINDOWS COMMON PEGS
 dominosa : [G] WINDOWS COMMON dominosa
 untangle : [G] WINDOWS COMMON UNTANGLE
 blackbox : [G] WINDOWS COMMON blackbox
+slant    : [G] WINDOWS COMMON SLANT
 
 # Mac OS X unified application containing all the puzzles.
 Puzzles  : [MX] osx osx.icns osx-info.plist COMMON ALL
@@ -162,7 +165,7 @@ FORCE:
 install:
        for i in cube net netslide fifteen sixteen twiddle \
                 pattern rect solo mines samegame flip guess \
-                pegs dominosa untangle blackbox; do \
+                pegs dominosa untangle blackbox slant; do \
                $(INSTALL_PROGRAM) -m 755 $$i $(DESTDIR)$(gamesdir)/$$i; \
        done
 !end
diff --git a/dsf.c b/dsf.c
new file mode 100644 (file)
index 0000000..353bf1a
--- /dev/null
+++ b/dsf.c
@@ -0,0 +1,28 @@
+/*
+ * dsf.c: two small functions to handle a disjoint set forest,
+ * which is a data structure useful in any solver which has to
+ * worry about avoiding closed loops.
+ */
+
+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;
+}
+
+void dsf_merge(int *dsf, int v1, int v2)
+{
+    v1 = dsf_canonify(dsf, v1);
+    v2 = dsf_canonify(dsf, v2);
+    dsf[v2] = v1;
+}
diff --git a/list.c b/list.c
index 93de9fc060d7b2a2f785f9faa151fd9fa40db85a..3cb049577eec20357fdc9aca254c651b112b8534 100644 (file)
--- a/list.c
+++ b/list.c
@@ -31,6 +31,7 @@ extern const game pegs;
 extern const game rect;
 extern const game samegame;
 extern const game sixteen;
+extern const game slant;
 extern const game solo;
 extern const game twiddle;
 extern const game untangle;
@@ -50,6 +51,7 @@ const game *gamelist[] = {
     &rect,
     &samegame,
     &sixteen,
+    &slant,
     &solo,
     &twiddle,
     &untangle,
diff --git a/net.c b/net.c
index a788d4692cd65fa8745390c656e3094665278afb..71c2b9d56363127ad3e4e61ff9706e6daa4f13a2 100644 (file)
--- a/net.c
+++ b/net.c
@@ -382,29 +382,6 @@ static char *validate_params(game_params *params, int full)
  * 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;
index d6b27f5b5d756b80c838201425b604b8897cd295..75a1f9183ec04c47b4883a5847f4d2bb4112c5e1 100755 (executable)
--- a/print.py
+++ b/print.py
@@ -365,13 +365,67 @@ def dominosa_format(s):
                 ((x+0.5)*gridpitch, (h-y-0.5)*gridpitch, grid[y*w+x]))
     return ret.coords, ret.s
 
+def slant_format(s):
+    # Parse the game ID.
+    ret = Holder()
+    ret.s = ""
+    params, seed = string.split(s, ":")
+    w, h = map(string.atoi, string.split(params, "x"))
+    W = w+1
+    H = h+1
+    grid = []
+    while len(seed) > 0:
+       if seed[0] in string.lowercase:
+           grid.extend([-1] * (ord(seed[0]) - ord('a') + 1))
+           seed = seed[1:]
+       elif seed[0] in "01234":
+           grid.append(string.atoi(seed[0]))
+           seed = seed[1:]
+    assert W * H == len(grid)
+    # I'm going to arbitrarily choose to use 7pt text for the
+    # numbers, and a 14pt grid pitch.
+    textht = 7
+    gridpitch = 14
+    radius = textht * 2.0 / 3.0
+    # Set up coordinate system.
+    pw = gridpitch * w
+    ph = gridpitch * h
+    ret.coords = (pw/2, pw/2, ph/2, ph/2)
+    psprint(ret, "%g %g translate" % (-ret.coords[0], -ret.coords[2]))
+    # Draw round the grid exterior, thickly.
+    psprint(ret, "newpath 1 setlinewidth")
+    psprint(ret, "0 0 moveto 0 %g rlineto %g 0 rlineto 0 %g rlineto" % \
+    (h * gridpitch, w * gridpitch, -h * gridpitch))
+    psprint(ret, "closepath stroke")
+    # Draw the internal grid lines, _very_ thin (the player will
+    # need to draw over them visibly).
+    psprint(ret, "newpath 0.01 setlinewidth")
+    for x in xrange(1,w):
+       psprint(ret, "%g 0 moveto 0 %g rlineto" % (x * gridpitch, h * gridpitch))
+    for y in xrange(1,h):
+       psprint(ret, "0 %g moveto %g 0 rlineto" % (y * gridpitch, w * gridpitch))
+    psprint(ret, "stroke")
+    # And draw the numbers.
+    psprint(ret, "/Helvetica findfont %g scalefont setfont" % textht)
+    for y in xrange(H):
+       for x in xrange(W):
+           n = grid[y*W+x]
+           if n >= 0:
+               psprint(ret, "newpath %g %g %g 0 360 arc" % \
+               ((x)*gridpitch, (h-y)*gridpitch, radius),
+               "gsave 1 setgray fill grestore stroke")
+               psprint(ret, "%g %g (%d) ctshow" % \
+               ((x)*gridpitch, (h-y)*gridpitch, n))
+    return ret.coords, ret.s
+
 formatters = {
 "net": net_format,
 "rect": rect_format,
 "rectangles": rect_format,
 "pattern": pattern_format,
 "solo": solo_format,
-"dominosa": dominosa_format
+"dominosa": dominosa_format,
+"slant": slant_format
 }
 
 if len(sys.argv) < 3:
index 13056d73d6ef41b8b2d83b75525776e175845956..2c0fe87a0bae97e561e6481c084f99a026ca9758 100644 (file)
@@ -20,6 +20,8 @@
 
 \define{by} \u00D7{x}
 
+\define{dash} \u2013{-}
+
 This is a collection of small one-player puzzle games.
 
 \copyright This manual is copyright 2004-5 Simon Tatham. All rights
@@ -43,9 +45,9 @@ both, and have more recently done a port to Mac OS X as well. When I
 find (or perhaps invent) further puzzle games that I like, they'll
 be added to this collection and will immediately be available on
 both platforms. And if anyone feels like writing any other front
-ends - PocketPC, Mac OS pre-10, or whatever it might be - then all
-the games in this framework will immediately become available on
-another platform as well.
+ends \dash PocketPC, Mac OS pre-10, or whatever it might be \dash
+then all the games in this framework will immediately become
+available on another platform as well.
 
 The actual games in this collection were mostly not my invention; they
 are re-implementations of existing game concepts within my portable
@@ -1208,12 +1210,12 @@ time (but always one that is known to have a solution).
 
 \cfg{winhelp-topic}{games.dominosa}
 
-A normal set of dominoes - that is, one instance of every (unordered)
-pair of numbers from 0 to 6 - has been arranged irregularly into a
-rectangle; then the number in each square has been written down and
-the dominoes themselves removed. Your task is to reconstruct the
-pattern by arranging the set of dominoes to match the provided array
-of numbers.
+A normal set of dominoes \dash that is, one instance of every
+(unordered) pair of numbers from 0 to 6 \dash has been arranged
+irregularly into a rectangle; then the number in each square has
+been written down and the dominoes themselves removed. Your task is
+to reconstruct the pattern by arranging the set of dominoes to match
+the provided array of numbers.
 
 This puzzle is widely credited to O. S. Adler, and takes part of its
 name from those initials.
@@ -1430,6 +1432,60 @@ using a different number to the original solution is still acceptable,
 if all the laser inputs and outputs match.
 
 
+\C{slant} \i{Slant}
+
+\cfg{winhelp-topic}{games.slant}
+
+You have a grid of squares. Your aim is to draw a diagonal line
+through each square, and choose which way each line slants so that
+the following conditions are met:
+
+\b The diagonal lines never form a loop.
+
+\b Any point with a circled number has precisely that many lines
+meeting at it. (Thus, a 4 is the centre of a cross shape, whereas a
+zero is the centre of a diamond shape \dash or rather, a partial
+diamond shape, because a zero can never appear in the middle of the
+grid because that would immediately cause a loop.)
+
+Credit for this puzzle goes to \i{Nikoli} \k{nikoli-slant}.
+
+\B{nikoli-slant}
+\W{http://www.nikoli.co.jp/puzzles/39/index.htm}\cw{http://www.nikoli.co.jp/puzzles/39/index.htm}
+(in Japanese)
+
+
+\H{slant-controls} \i{Slant controls}
+
+\IM{Slant controls} controls, for Slant
+\IM{Slant controls} keys, for Slant
+\IM{Slant controls} shortcuts (keyboard), for Slant
+
+Left-clicking in a blank square will place a \cw{\\} in it (a line
+leaning to the left, i.e. running from the top left of the square to
+the bottom right). Right-clicking in a blank square will place a
+\cw{/} in it (leaning to the right, running from top right to bottom
+left).
+
+Continuing to click either button will cycle between the three
+possible square contents. Thus, if you left-click repeatedly in a
+blank square it will change from blank to \cw{\\} to \cw{/} back to
+blank, and if you right-click repeatedly the square will change from
+blank to \cw{/} to \cw{\\} back to blank. (Therefore, you can play
+the game entirely with one button if you need to.)
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{slant-parameters} \I{parameters, for slant}Slant parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+
 \A{licence} \I{MIT licence}\ii{Licence}
 
 This software is \i{copyright} 2004-2005 Simon Tatham.
index cf182557234bf792bad0d1cdf528610834ce4b04..4762c7cbf42580dd43c247cdf24788a281fceec8 100644 (file)
--- a/puzzles.h
+++ b/puzzles.h
@@ -234,6 +234,12 @@ void shuffle(void *array, int nelts, int eltsize, random_state *rs);
 void draw_rect_outline(frontend *fe, int x, int y, int w, int h,
                        int colour);
 
+/*
+ * dsf.c
+ */
+int dsf_canonify(int *dsf, int val);
+void dsf_merge(int *dsf, int v1, int v2);
+
 /*
  * version.c
  */
diff --git a/slant.c b/slant.c
new file mode 100644 (file)
index 0000000..c562b48
--- /dev/null
+++ b/slant.c
@@ -0,0 +1,1251 @@
+/*
+ * slant.c: Puzzle from nikoli.co.jp involving drawing a diagonal
+ * line through each square of a grid.
+ */
+
+/*
+ * In this puzzle you have a grid of squares, each of which must
+ * contain a diagonal line; you also have clue numbers placed at
+ * _points_ of that grid, which means there's a (w+1) x (h+1) array
+ * of possible clue positions.
+ * 
+ * I'm therefore going to adopt a rigid convention throughout this
+ * source file of using w and h for the dimensions of the grid of
+ * squares, and W and H for the dimensions of the grid of points.
+ * Thus, W == w+1 and H == h+1 always.
+ * 
+ * Clue arrays will be W*H `signed char's, and the clue at each
+ * point will be a number from 0 to 4, or -1 if there's no clue.
+ * 
+ * Solution arrays will be W*H `signed char's, and the number at
+ * each point will be +1 for a forward slash (/), -1 for a
+ * backslash (\), and 0 for unknown.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+enum {
+    COL_BACKGROUND,
+    COL_GRID,
+    COL_INK,
+    NCOLOURS
+};
+
+struct game_params {
+    int w, h;
+};
+
+typedef struct game_clues {
+    int w, h;
+    signed char *clues;
+    int *dsf;                         /* scratch space for completion check */
+    int refcount;
+} game_clues;
+
+struct game_state {
+    struct game_params p;
+    game_clues *clues;
+    signed char *soln;
+    int completed;
+    int used_solve;                   /* used to suppress completion flash */
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = ret->h = 8;
+
+    return ret;
+}
+
+static const struct game_params slant_presets[] = {
+  {5, 5},
+  {8, 8},
+  {12, 10},
+};
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    char str[80];
+
+    if (i < 0 || i >= lenof(slant_presets))
+        return FALSE;
+
+    ret = snew(game_params);
+    *ret = slant_presets[i];
+
+    sprintf(str, "%dx%d", ret->w, ret->h);
+
+    *name = dupstr(str);
+    *params = ret;
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *ret, char const *string)
+{
+    ret->w = ret->h = atoi(string);
+    while (*string && isdigit((unsigned char)*string)) string++;
+    if (*string == 'x') {
+        string++;
+        ret->h = atoi(string);
+    }
+}
+
+static char *encode_params(game_params *params, int full)
+{
+    char data[256];
+
+    sprintf(data, "%dx%d", params->w, params->h);
+
+    return dupstr(data);
+}
+
+static config_item *game_configure(game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(3, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = NULL;
+    ret[2].type = C_END;
+    ret[2].sval = NULL;
+    ret[2].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = atoi(cfg[0].sval);
+    ret->h = atoi(cfg[1].sval);
+
+    return ret;
+}
+
+static char *validate_params(game_params *params, int full)
+{
+    /*
+     * (At least at the time of writing this comment) The grid
+     * generator is actually capable of handling even zero grid
+     * dimensions without crashing. Puzzles with a zero-area grid
+     * are a bit boring, though, because they're already solved :-)
+     */
+
+    if (params->w < 1 || params->h < 1)
+       return "Width and height must both be at least one";
+
+    return NULL;
+}
+
+/*
+ * Utility function used by both the solver and the filled-grid
+ * generator.
+ */
+
+static void fill_square(int w, int h, int y, int x, int v,
+                       signed char *soln, int *dsf)
+{
+    int W = w+1 /*, H = h+1 */;
+
+    soln[y*w+x] = v;
+
+    if (v < 0)
+       dsf_merge(dsf, y*W+x, (y+1)*W+(x+1));
+    else
+       dsf_merge(dsf, y*W+(x+1), (y+1)*W+x);
+}
+
+/*
+ * Scratch space for solver.
+ */
+struct solver_scratch {
+    int *dsf;
+};
+
+struct solver_scratch *new_scratch(int w, int h)
+{
+    int W = w+1, H = h+1;
+    struct solver_scratch *ret = snew(struct solver_scratch);
+    ret->dsf = snewn(W*H, int);
+    return ret;
+}
+
+void free_scratch(struct solver_scratch *sc)
+{
+    sfree(sc->dsf);
+    sfree(sc);
+}
+
+/*
+ * Solver. Returns 0 for impossibility, 1 for success, 2 for
+ * ambiguity or failure to converge.
+ */
+static int slant_solve(int w, int h, const signed char *clues,
+                      signed char *soln, struct solver_scratch *sc)
+{
+    int W = w+1, H = h+1;
+    int x, y, i;
+    int done_something;
+
+    /*
+     * Clear the output.
+     */
+    memset(soln, 0, w*h);
+
+    /*
+     * Establish a disjoint set forest for tracking connectedness
+     * between grid points.
+     */
+    for (i = 0; i < W*H; i++)
+       sc->dsf[i] = i;                /* initially all distinct */
+
+    /*
+     * Repeatedly try to deduce something until we can't.
+     */
+    do {
+       done_something = FALSE;
+
+       /*
+        * Any clue point with the number of remaining lines equal
+        * to zero or to the number of remaining undecided
+        * neighbouring squares can be filled in completely.
+        */
+       for (y = 0; y < H; y++)
+           for (x = 0; x < W; x++) {
+               int nu, nl, v, c;
+
+               if ((c = clues[y*W+x]) < 0)
+                   continue;
+
+               /*
+                * We have a clue point. Count up the number of
+                * undecided neighbours, and also the number of
+                * lines already present.
+                */
+               nu = 0;
+               nl = c;
+               if (x > 0 && y > 0 && (v = soln[(y-1)*w+(x-1)]) != +1)
+                   v == 0 ? nu++ : nl--;
+               if (x > 0 && y < h && (v = soln[y*w+(x-1)]) != -1)
+                   v == 0 ? nu++ : nl--;
+               if (x < w && y > 0 && (v = soln[(y-1)*w+x]) != -1)
+                   v == 0 ? nu++ : nl--;
+               if (x < w && y < h && (v = soln[y*w+x]) != +1)
+                   v == 0 ? nu++ : nl--;
+
+               /*
+                * Check the counts.
+                */
+               if (nl < 0 || nl > nu) {
+                   /*
+                    * No consistent value for this at all!
+                    */
+                   return 0;          /* impossible */
+               }
+
+               if (nu > 0 && (nl == 0 || nl == nu)) {
+#ifdef SOLVER_DIAGNOSTICS
+                   printf("%s around clue point at %d,%d\n",
+                          nl ? "filling" : "emptying", x, y);
+#endif
+                   if (x > 0 && y > 0 && soln[(y-1)*w+(x-1)] == 0)
+                       fill_square(w, h, y-1, x-1, (nl ? -1 : +1), soln,
+                                   sc->dsf);
+                   if (x > 0 && y < h && soln[y*w+(x-1)] == 0)
+                       fill_square(w, h, y, x-1, (nl ? +1 : -1), soln,
+                                   sc->dsf);
+                   if (x < w && y > 0 && soln[(y-1)*w+x] == 0)
+                       fill_square(w, h, y-1, x, (nl ? +1 : -1), soln,
+                                   sc->dsf);
+                   if (x < w && y < h && soln[y*w+x] == 0)
+                       fill_square(w, h, y, x, (nl ? -1 : +1), soln,
+                                   sc->dsf);
+
+                   done_something = TRUE;
+               }
+           }
+
+       if (done_something)
+           continue;
+
+       /*
+        * Failing that, we now apply the second condition, which
+        * is that no square may be filled in such a way as to form
+        * a loop.
+        */
+       for (y = 0; y < h; y++)
+           for (x = 0; x < w; x++) {
+               int fs, bs;
+
+               if (soln[y*w+x])
+                   continue;          /* got this one already */
+
+               fs = (dsf_canonify(sc->dsf, y*W+x) ==
+                     dsf_canonify(sc->dsf, (y+1)*W+(x+1)));
+               bs = (dsf_canonify(sc->dsf, (y+1)*W+x) ==
+                     dsf_canonify(sc->dsf, y*W+(x+1)));
+
+               if (fs && bs) {
+                   /*
+                    * Loop avoidance leaves no consistent value
+                    * for this at all!
+                    */
+                   return 0;          /* impossible */
+               }
+
+               if (fs) {
+                   /*
+                    * Top left and bottom right corners of this
+                    * square are already connected, which means we
+                    * aren't allowed to put a backslash in here.
+                    */
+#ifdef SOLVER_DIAGNOSTICS
+                   printf("placing / in %d,%d by loop avoidance\n", x, y);
+#endif
+                   fill_square(w, h, y, x, +1, soln, sc->dsf);
+                   done_something = TRUE;
+               } else if (bs) {
+                   /*
+                    * Top right and bottom left corners of this
+                    * square are already connected, which means we
+                    * aren't allowed to put a forward slash in
+                    * here.
+                    */
+#ifdef SOLVER_DIAGNOSTICS
+                   printf("placing \\ in %d,%d by loop avoidance\n", x, y);
+#endif
+                   fill_square(w, h, y, x, -1, soln, sc->dsf);
+                   done_something = TRUE;
+               }
+           }
+
+    } while (done_something);
+
+    /*
+     * Solver can make no more progress. See if the grid is full.
+     */
+    for (i = 0; i < w*h; i++)
+       if (!soln[i])
+           return 2;                  /* failed to converge */
+    return 1;                         /* success */
+}
+
+/*
+ * Filled-grid generator.
+ */
+static void slant_generate(int w, int h, signed char *soln, random_state *rs)
+{
+    int W = w+1, H = h+1;
+    int x, y, i;
+    int *dsf, *indices;
+
+    /*
+     * Clear the output.
+     */
+    memset(soln, 0, w*h);
+
+    /*
+     * Establish a disjoint set forest for tracking connectedness
+     * between grid points.
+     */
+    dsf = snewn(W*H, int);
+    for (i = 0; i < W*H; i++)
+       dsf[i] = i;                    /* initially all distinct */
+
+    /*
+     * Prepare a list of the squares in the grid, and fill them in
+     * in a random order.
+     */
+    indices = snewn(w*h, int);
+    for (i = 0; i < w*h; i++)
+       indices[i] = i;
+    shuffle(indices, w*h, sizeof(*indices), rs);
+
+    /*
+     * Fill in each one in turn.
+     */
+    for (i = 0; i < w*h; i++) {
+       int fs, bs, v;
+
+       y = indices[i] / w;
+       x = indices[i] % w;
+
+       fs = (dsf_canonify(dsf, y*W+x) ==
+             dsf_canonify(dsf, (y+1)*W+(x+1)));
+       bs = (dsf_canonify(dsf, (y+1)*W+x) ==
+             dsf_canonify(dsf, y*W+(x+1)));
+
+       /*
+        * It isn't possible to get into a situation where we
+        * aren't allowed to place _either_ type of slash in a
+        * square.
+        * 
+        * Proof (thanks to Gareth Taylor):
+        * 
+        * If it were possible, it would have to be because there
+        * was an existing path (not using this square) between the
+        * top-left and bottom-right corners of this square, and
+        * another between the other two. These two paths would
+        * have to cross at some point.
+        * 
+        * Obviously they can't cross in the middle of a square, so
+        * they must cross by sharing a point in common. But this
+        * isn't possible either: if you chessboard-colour all the
+        * points on the grid, you find that any continuous
+        * diagonal path is entirely composed of points of the same
+        * colour. And one of our two hypothetical paths is between
+        * two black points, and the other is between two white
+        * points - therefore they can have no point in common. []
+        */
+       assert(!(fs && bs));
+
+       v = fs ? +1 : bs ? -1 : 2 * random_upto(rs, 2) - 1;
+       fill_square(w, h, y, x, v, soln, dsf);
+    }
+
+    sfree(indices);
+    sfree(dsf);
+}
+
+static char *new_game_desc(game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    int w = params->w, h = params->h, W = w+1, H = h+1;
+    signed char *soln, *tmpsoln, *clues;
+    int *clueindices;
+    struct solver_scratch *sc;
+    int x, y, v, i;
+    char *desc;
+
+    soln = snewn(w*h, signed char);
+    tmpsoln = snewn(w*h, signed char);
+    clues = snewn(W*H, signed char);
+    clueindices = snewn(W*H, int);
+    sc = new_scratch(w, h);
+
+    do {
+       /*
+        * Create the filled grid.
+        */
+       slant_generate(w, h, soln, rs);
+
+       /*
+        * Fill in the complete set of clues.
+        */
+       for (y = 0; y < H; y++)
+           for (x = 0; x < W; x++) {
+               v = 0;
+
+               if (x > 0 && y > 0 && soln[(y-1)*w+(x-1)] == -1) v++;
+               if (x > 0 && y < h && soln[y*w+(x-1)] == +1) v++;
+               if (x < w && y > 0 && soln[(y-1)*w+x] == +1) v++;
+               if (x < w && y < h && soln[y*w+x] == -1) v++;
+
+               clues[y*W+x] = v;
+           }
+    } while (slant_solve(w, h, clues, tmpsoln, sc) != 1);
+
+    /*
+     * Remove as many clues as possible while retaining solubility.
+     */
+    for (i = 0; i < W*H; i++)
+       clueindices[i] = i;
+    shuffle(clueindices, W*H, sizeof(*clueindices), rs);
+    for (i = 0; i < W*H; i++) {
+       y = clueindices[i] / W;
+       x = clueindices[i] % W;
+       v = clues[y*W+x];
+       clues[y*W+x] = -1;
+       if (slant_solve(w, h, clues, tmpsoln, sc) != 1)
+           clues[y*W+x] = v;          /* put it back */
+    }
+
+    /*
+     * Now we have the clue set as it will be presented to the
+     * user. Encode it in a game desc.
+     */
+    {
+       char *p;
+       int run, i;
+
+       desc = snewn(W*H+1, char);
+       p = desc;
+       run = 0;
+       for (i = 0; i <= W*H; i++) {
+           int n = (i < W*H ? clues[i] : -2);
+
+           if (n == -1)
+               run++;
+           else {
+               if (run) {
+                   while (run > 0) {
+                       int c = 'a' - 1 + run;
+                       if (run > 26)
+                           c = 'z';
+                       *p++ = c;
+                       run -= c - ('a' - 1);
+                   }
+               }
+               if (n >= 0)
+                   *p++ = '0' + n;
+               run = 0;
+           }
+       }
+       assert(p - desc <= W*H);
+       *p++ = '\0';
+       desc = sresize(desc, p - desc, char);
+    }
+
+    /*
+     * Encode the solution as an aux_info.
+     */
+    {
+       char *auxbuf;
+       *aux = auxbuf = snewn(w*h+1, char);
+       for (i = 0; i < w*h; i++)
+           auxbuf[i] = soln[i] < 0 ? '\\' : '/';
+       auxbuf[w*h] = '\0';
+    }
+
+    free_scratch(sc);
+    sfree(clueindices);
+    sfree(clues);
+    sfree(tmpsoln);
+    sfree(soln);
+
+    return desc;
+}
+
+static char *validate_desc(game_params *params, char *desc)
+{
+    int w = params->w, h = params->h, W = w+1, H = h+1;
+    int area = W*H;
+    int squares = 0;
+
+    while (*desc) {
+        int n = *desc++;
+        if (n >= 'a' && n <= 'z') {
+            squares += n - 'a' + 1;
+        } else if (n >= '0' && n <= '4') {
+            squares++;
+        } else
+            return "Invalid character in game description";
+    }
+
+    if (squares < area)
+        return "Not enough data to fill grid";
+
+    if (squares > area)
+        return "Too much data to fit in grid";
+
+    return NULL;
+}
+
+static game_state *new_game(midend_data *me, game_params *params, char *desc)
+{
+    int w = params->w, h = params->h, W = w+1, H = h+1;
+    game_state *state = snew(game_state);
+    int area = W*H;
+    int squares = 0;
+
+    state->p = *params;
+    state->soln = snewn(w*h, signed char);
+    memset(state->soln, 0, w*h);
+    state->completed = state->used_solve = FALSE;
+
+    state->clues = snew(game_clues);
+    state->clues->w = w;
+    state->clues->h = h;
+    state->clues->clues = snewn(W*H, signed char);
+    state->clues->refcount = 1;
+    state->clues->dsf = snewn(W*H, int);
+    memset(state->clues->clues, -1, W*H);
+    while (*desc) {
+        int n = *desc++;
+        if (n >= 'a' && n <= 'z') {
+            squares += n - 'a' + 1;
+        } else if (n >= '0' && n <= '4') {
+            state->clues->clues[squares++] = n - '0';
+        } else
+           assert(!"can't get here");
+    }
+    assert(squares == area);
+
+    return state;
+}
+
+static game_state *dup_game(game_state *state)
+{
+    int w = state->p.w, h = state->p.h;
+    game_state *ret = snew(game_state);
+
+    ret->p = state->p;
+    ret->clues = state->clues;
+    ret->clues->refcount++;
+    ret->completed = state->completed;
+    ret->used_solve = state->used_solve;
+
+    ret->soln = snewn(w*h, signed char);
+    memcpy(ret->soln, state->soln, w*h);
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    sfree(state);
+}
+
+static int check_completion(game_state *state)
+{
+    int w = state->p.w, h = state->p.h, W = w+1, H = h+1;
+    int i, x, y;
+
+    /*
+     * Establish a disjoint set forest for tracking connectedness
+     * between grid points. Use the dsf scratch space in the shared
+     * clues structure, to avoid mallocing too often.
+     */
+    for (i = 0; i < W*H; i++)
+       state->clues->dsf[i] = i;      /* initially all distinct */
+
+    /*
+     * Now go through the grid checking connectedness. While we're
+     * here, also check that everything is filled in.
+     */
+    for (y = 0; y < h; y++)
+       for (x = 0; x < w; x++) {
+           int i1, i2;
+
+           if (state->soln[y*w+x] == 0)
+               return FALSE;
+           if (state->soln[y*w+x] < 0) {
+               i1 = y*W+x;
+               i2 = (y+1)*W+(x+1);
+           } else {
+               i1 = (y+1)*W+x;
+               i2 = y*W+(x+1);
+           }
+
+           /*
+            * Our edge connects i1 with i2. If they're already
+            * connected, return failure. Otherwise, link them.
+            */
+           if (dsf_canonify(state->clues->dsf, i1) ==
+               dsf_canonify(state->clues->dsf, i2))
+               return FALSE;
+           else
+               dsf_merge(state->clues->dsf, i1, i2);
+       }
+
+    /*
+     * The grid is _a_ valid grid; let's see if it matches the
+     * clues.
+     */
+    for (y = 0; y < H; y++)
+       for (x = 0; x < W; x++) {
+           int v, c;
+
+           if ((c = state->clues->clues[y*W+x]) < 0)
+               continue;
+
+           v = 0;
+
+           if (x > 0 && y > 0 && state->soln[(y-1)*w+(x-1)] == -1) v++;
+           if (x > 0 && y < h && state->soln[y*w+(x-1)] == +1) v++;
+           if (x < w && y > 0 && state->soln[(y-1)*w+x] == +1) v++;
+           if (x < w && y < h && state->soln[y*w+x] == -1) v++;
+
+           if (c != v)
+               return FALSE;
+       }
+
+    return TRUE;
+}
+
+static char *solve_game(game_state *state, game_state *currstate,
+                       char *aux, char **error)
+{
+    int w = state->p.w, h = state->p.h;
+    signed char *soln;
+    int bs, ret;
+    int free_soln = FALSE;
+    char *move, buf[80];
+    int movelen, movesize;
+    int x, y;
+
+    if (aux) {
+       /*
+        * If we already have the solution, save ourselves some
+        * time.
+        */
+       soln = (signed char *)aux;
+       bs = (signed char)'\\';
+       free_soln = FALSE;
+    } else {
+       struct solver_scratch *sc = new_scratch(w, h);
+       soln = snewn(w*h, signed char);
+       bs = -1;
+       ret = slant_solve(w, h, state->clues->clues, soln, sc);
+       free_scratch(sc);
+       if (ret != 1) {
+           sfree(soln);
+           if (ret == 0)
+               return "This puzzle is not self-consistent";
+           else
+               return "Unable to find a unique solution for this puzzle";
+       }
+       free_soln = TRUE;
+    }
+
+    /*
+     * Construct a move string which turns the current state into
+     * the solved state.
+     */
+    movesize = 256;
+    move = snewn(movesize, char);
+    movelen = 0;
+    move[movelen++] = 'S';
+    move[movelen] = '\0';
+    for (y = 0; y < h; y++)
+       for (x = 0; x < w; x++) {
+           int v = (soln[y*w+x] == bs ? -1 : +1);
+           if (state->soln[y*w+x] != v) {
+               int len = sprintf(buf, ";%c%d,%d", v < 0 ? '\\' : '/', x, y);
+               if (movelen + len >= movesize) {
+                   movesize = movelen + len + 256;
+                   move = sresize(move, movesize, char);
+               }
+               strcpy(move + movelen, buf);
+               movelen += len;
+           }
+       }
+
+    if (free_soln)
+       sfree(soln);
+
+    return move;
+}
+
+static char *game_text_format(game_state *state)
+{
+    int w = state->p.w, h = state->p.h, W = w+1, H = h+1;
+    int x, y, len;
+    char *ret, *p;
+
+    /*
+     * There are h+H rows of w+W columns.
+     */
+    len = (h+H) * (w+W+1) + 1;
+    ret = snewn(len, char);
+    p = ret;
+
+    for (y = 0; y < H; y++) {
+       for (x = 0; x < W; x++) {
+           if (state->clues->clues[y*W+x] >= 0)
+               *p++ = state->clues->clues[y*W+x] + '0';
+           else
+               *p++ = '+';
+           if (x < w)
+               *p++ = '-';
+       }
+       *p++ = '\n';
+       if (y < h) {
+           for (x = 0; x < W; x++) {
+               *p++ = '|';
+               if (x < w) {
+                   if (state->soln[y*w+x] != 0)
+                       *p++ = (state->soln[y*w+x] < 0 ? '\\' : '/');
+                   else
+                       *p++ = ' ';
+               }
+           }
+           *p++ = '\n';
+       }
+    }
+    *p++ = '\0';
+
+    assert(p - ret == len);
+    return ret;
+}
+
+static game_ui *new_ui(game_state *state)
+{
+    return NULL;
+}
+
+static void free_ui(game_ui *ui)
+{
+}
+
+static char *encode_ui(game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, game_state *oldstate,
+                               game_state *newstate)
+{
+}
+
+#define PREFERRED_TILESIZE 32
+#define TILESIZE (ds->tilesize)
+#define BORDER TILESIZE
+#define CLUE_RADIUS (TILESIZE / 3)
+#define CLUE_TEXTSIZE (TILESIZE / 2)
+#define COORD(x)  ( (x) * TILESIZE + BORDER )
+#define FROMCOORD(x)  ( ((x) - BORDER + TILESIZE) / TILESIZE - 1 )
+
+#define FLASH_TIME 0.30F
+
+/*
+ * Bit fields in the `grid' and `todraw' elements of the drawstate.
+ */
+#define BACKSLASH 0x0001
+#define FORWSLASH 0x0002
+#define L_T       0x0004
+#define L_B       0x0008
+#define T_L       0x0010
+#define T_R       0x0020
+#define R_T       0x0040
+#define R_B       0x0080
+#define B_L       0x0100
+#define B_R       0x0200
+#define C_TL      0x0400
+#define C_TR      0x0800
+#define C_BL      0x1000
+#define C_BR      0x2000
+#define FLASH     0x4000
+
+struct game_drawstate {
+    int tilesize;
+    int started;
+    int *grid;
+    int *todraw;
+};
+
+static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
+                           int x, int y, int button)
+{
+    int w = state->p.w, h = state->p.h;
+
+    if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
+        int v;
+        char buf[80];
+
+        x = FROMCOORD(x);
+        y = FROMCOORD(y);
+        if (x < 0 || y < 0 || x >= w || y >= h)
+            return NULL;
+
+        if (button == LEFT_BUTTON) {
+            /*
+             * Left-clicking cycles blank -> \ -> / -> blank.
+             */
+            v = state->soln[y*w+x] - 1;
+            if (v == -2)
+                v = +1;
+        } else {
+            /*
+             * Right-clicking cycles blank -> / -> \ -> blank.
+             */
+            v = state->soln[y*w+x] + 1;
+            if (v == +2)
+                v = -1;
+        }
+
+        sprintf(buf, "%c%d,%d", v==-1 ? '\\' : v==+1 ? '/' : 'C', x, y);
+        return dupstr(buf);
+    }
+
+    return NULL;
+}
+
+static game_state *execute_move(game_state *state, char *move)
+{
+    int w = state->p.w, h = state->p.h;
+    char c;
+    int x, y, n;
+    game_state *ret = dup_game(state);
+
+    while (*move) {
+        c = *move;
+       if (c == 'S') {
+           ret->used_solve = TRUE;
+           move++;
+       } else if (c == '\\' || c == '/' || c == 'C') {
+            move++;
+            if (sscanf(move, "%d,%d%n", &x, &y, &n) != 2 ||
+                x < 0 || y < 0 || x >= w || y >= h) {
+                free_game(ret);
+                return NULL;
+            }
+            ret->soln[y*w+x] = (c == '\\' ? -1 : c == '/' ? +1 : 0);
+            move += n;
+        } else {
+            free_game(ret);
+            return NULL;
+        }
+        if (*move == ';')
+            move++;
+        else if (*move) {
+            free_game(ret);
+            return NULL;
+        }
+    }
+
+    if (!ret->completed)
+       ret->completed = check_completion(ret);
+
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+static void game_compute_size(game_params *params, int tilesize,
+                             int *x, int *y)
+{
+    /* fool the macros */
+    struct dummy { int tilesize; } dummy = { tilesize }, *ds = &dummy;
+
+    *x = 2 * BORDER + params->w * TILESIZE + 1;
+    *y = 2 * BORDER + params->h * TILESIZE + 1;
+}
+
+static void game_set_size(game_drawstate *ds, game_params *params,
+                         int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, game_state *state, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+
+    frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
+
+    ret[COL_GRID * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 0.7F;
+    ret[COL_GRID * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 0.7F;
+    ret[COL_GRID * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 0.7F;
+
+    ret[COL_INK * 3 + 0] = 0.0F;
+    ret[COL_INK * 3 + 1] = 0.0F;
+    ret[COL_INK * 3 + 2] = 0.0F;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(game_state *state)
+{
+    int w = state->p.w, h = state->p.h;
+    int i;
+    struct game_drawstate *ds = snew(struct game_drawstate);
+
+    ds->tilesize = 0;
+    ds->started = FALSE;
+    ds->grid = snewn(w*h, int);
+    ds->todraw = snewn(w*h, int);
+    for (i = 0; i < w*h; i++)
+       ds->grid[i] = ds->todraw[i] = -1;
+
+    return ds;
+}
+
+static void game_free_drawstate(game_drawstate *ds)
+{
+    sfree(ds->grid);
+    sfree(ds);
+}
+
+static void draw_clue(frontend *fe, game_drawstate *ds,
+                     int x, int y, int v)
+{
+    char p[2];
+
+    if (v < 0)
+       return;
+
+    p[0] = v + '0';
+    p[1] = '\0';
+    draw_circle(fe, COORD(x), COORD(y), CLUE_RADIUS,
+               COL_BACKGROUND, COL_INK);
+    draw_text(fe, COORD(x), COORD(y), FONT_VARIABLE,
+             CLUE_TEXTSIZE, ALIGN_VCENTRE|ALIGN_HCENTRE,
+             COL_INK, p);
+}
+
+static void draw_tile(frontend *fe, game_drawstate *ds, game_clues *clues,
+                     int x, int y, int v)
+{
+    int w = clues->w /*, h = clues->h*/, W = w+1 /*, H = h+1 */;
+    int xx, yy;
+
+    clip(fe, COORD(x), COORD(y), TILESIZE+1, TILESIZE+1);
+
+    draw_rect(fe, COORD(x), COORD(y), TILESIZE, TILESIZE,
+             (v & FLASH) ? COL_GRID : COL_BACKGROUND);
+
+    /*
+     * Draw the grid lines.
+     */
+    draw_line(fe, COORD(x), COORD(y), COORD(x+1), COORD(y), COL_GRID);
+    draw_line(fe, COORD(x), COORD(y+1), COORD(x+1), COORD(y+1), COL_GRID);
+    draw_line(fe, COORD(x), COORD(y), COORD(x), COORD(y+1), COL_GRID);
+    draw_line(fe, COORD(x+1), COORD(y), COORD(x+1), COORD(y+1), COL_GRID);
+
+    /*
+     * Draw the slash.
+     */
+    if (v & BACKSLASH) {
+       draw_line(fe, COORD(x), COORD(y), COORD(x+1), COORD(y+1), COL_INK);
+       draw_line(fe, COORD(x)+1, COORD(y), COORD(x+1), COORD(y+1)-1,
+                 COL_INK);
+       draw_line(fe, COORD(x), COORD(y)+1, COORD(x+1)-1, COORD(y+1),
+                 COL_INK);
+    } else if (v & FORWSLASH) {
+       draw_line(fe, COORD(x+1), COORD(y), COORD(x), COORD(y+1), COL_INK);
+       draw_line(fe, COORD(x+1)-1, COORD(y), COORD(x), COORD(y+1)-1,
+                 COL_INK);
+       draw_line(fe, COORD(x+1), COORD(y)+1, COORD(x)+1, COORD(y+1),
+                 COL_INK);
+    }
+
+    /*
+     * Draw dots on the grid corners that appear if a slash is in a
+     * neighbouring cell.
+     */
+    if (v & L_T)
+       draw_rect(fe, COORD(x), COORD(y)+1, 1, 1, COL_INK);
+    if (v & L_B)
+       draw_rect(fe, COORD(x), COORD(y+1)-1, 1, 1, COL_INK);
+    if (v & R_T)
+       draw_rect(fe, COORD(x+1), COORD(y)+1, 1, 1, COL_INK);
+    if (v & R_B)
+       draw_rect(fe, COORD(x+1), COORD(y+1)-1, 1, 1, COL_INK);
+    if (v & T_L)
+       draw_rect(fe, COORD(x)+1, COORD(y), 1, 1, COL_INK);
+    if (v & T_R)
+       draw_rect(fe, COORD(x+1)-1, COORD(y), 1, 1, COL_INK);
+    if (v & B_L)
+       draw_rect(fe, COORD(x)+1, COORD(y+1), 1, 1, COL_INK);
+    if (v & B_R)
+       draw_rect(fe, COORD(x+1)-1, COORD(y+1), 1, 1, COL_INK);
+    if (v & C_TL)
+       draw_rect(fe, COORD(x), COORD(y), 1, 1, COL_INK);
+    if (v & C_TR)
+       draw_rect(fe, COORD(x+1), COORD(y), 1, 1, COL_INK);
+    if (v & C_BL)
+       draw_rect(fe, COORD(x), COORD(y+1), 1, 1, COL_INK);
+    if (v & C_BR)
+       draw_rect(fe, COORD(x+1), COORD(y+1), 1, 1, COL_INK);
+
+    /*
+     * And finally the clues at the corners.
+     */
+    for (xx = x; xx <= x+1; xx++)
+       for (yy = y; yy <= y+1; yy++)
+           draw_clue(fe, ds, xx, yy, clues->clues[yy*W+xx]);
+
+    unclip(fe);
+    draw_update(fe, COORD(x), COORD(y), TILESIZE+1, TILESIZE+1);
+}
+
+static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
+                       game_state *state, int dir, game_ui *ui,
+                       float animtime, float flashtime)
+{
+    int w = state->p.w, h = state->p.h, W = w+1 /*, H = h+1 */;
+    int x, y;
+    int flashing;
+
+    if (flashtime > 0)
+       flashing = (int)(flashtime * 3 / FLASH_TIME) != 1;
+    else
+       flashing = FALSE;
+
+    if (!ds->started) {
+       int ww, wh;
+       game_compute_size(&state->p, TILESIZE, &ww, &wh);
+       draw_rect(fe, 0, 0, ww, wh, COL_BACKGROUND);
+       draw_update(fe, 0, 0, ww, wh);
+
+       /*
+        * Draw any clues on the very edges (since normal tile
+        * redraw won't draw the bits outside the grid boundary).
+        */
+       for (y = 0; y < h; y++) {
+           draw_clue(fe, ds, 0, y, state->clues->clues[y*W+0]);
+           draw_clue(fe, ds, w, y, state->clues->clues[y*W+w]);
+       }
+       for (x = 0; x < w; x++) {
+           draw_clue(fe, ds, x, 0, state->clues->clues[0*W+x]);
+           draw_clue(fe, ds, x, h, state->clues->clues[h*W+x]);
+       }
+
+       ds->started = TRUE;
+    }
+
+    /*
+     * Loop over the grid and work out where all the slashes are.
+     * We need to do this because a slash in one square affects the
+     * drawing of the next one along.
+     */
+    for (y = 0; y < h; y++)
+       for (x = 0; x < w; x++)
+           ds->todraw[y*w+x] = flashing ? FLASH : 0;
+
+    for (y = 0; y < h; y++) {
+       for (x = 0; x < w; x++) {
+           if (state->soln[y*w+x] < 0) {
+               ds->todraw[y*w+x] |= BACKSLASH;
+               if (x > 0)
+                   ds->todraw[y*w+(x-1)] |= R_T | C_TR;
+               if (x+1 < w)
+                   ds->todraw[y*w+(x+1)] |= L_B | C_BL;
+               if (y > 0)
+                   ds->todraw[(y-1)*w+x] |= B_L | C_BL;
+               if (y+1 < h)
+                   ds->todraw[(y+1)*w+x] |= T_R | C_TR;
+               if (x > 0 && y > 0)
+                   ds->todraw[(y-1)*w+(x-1)] |= C_BR;
+               if (x+1 < w && y+1 < h)
+                   ds->todraw[(y+1)*w+(x+1)] |= C_TL;
+           } else if (state->soln[y*w+x] > 0) {
+               ds->todraw[y*w+x] |= FORWSLASH;
+               if (x > 0)
+                   ds->todraw[y*w+(x-1)] |= R_B | C_BR;
+               if (x+1 < w)
+                   ds->todraw[y*w+(x+1)] |= L_T | C_TL;
+               if (y > 0)
+                   ds->todraw[(y-1)*w+x] |= B_R | C_BR;
+               if (y+1 < h)
+                   ds->todraw[(y+1)*w+x] |= T_L | C_TL;
+               if (x > 0 && y+1 < h)
+                   ds->todraw[(y+1)*w+(x-1)] |= C_TR;
+               if (x+1 < w && y > 0)
+                   ds->todraw[(y-1)*w+(x+1)] |= C_BL;
+           }
+       }
+    }
+
+    /*
+     * Now go through and draw the grid squares.
+     */
+    for (y = 0; y < h; y++) {
+       for (x = 0; x < w; x++) {
+           if (ds->todraw[y*w+x] != ds->grid[y*w+x]) {
+               draw_tile(fe, ds, state->clues, x, y, ds->todraw[y*w+x]);
+               ds->grid[y*w+x] = ds->todraw[y*w+x];
+           }
+       }
+    }
+}
+
+static float game_anim_length(game_state *oldstate, game_state *newstate,
+                             int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(game_state *oldstate, game_state *newstate,
+                              int dir, game_ui *ui)
+{
+    if (!oldstate->completed && newstate->completed &&
+       !oldstate->used_solve && !newstate->used_solve)
+        return FLASH_TIME;
+
+    return 0.0F;
+}
+
+static int game_wants_statusbar(void)
+{
+    return FALSE;
+}
+
+static int game_timing_state(game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+#ifdef COMBINED
+#define thegame slant
+#endif
+
+const struct game thegame = {
+    "Slant", "games.slant",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILESIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_wants_statusbar,
+    FALSE, game_timing_state,
+    0,                                /* mouse_priorities */
+};