shithub: puzzles

ref: 2e214d740cc9ea2c8061713b0d944f1f29374206
dir: /slant.c/

View raw version
/*
 * 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;
};

static 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;
}

static 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->soln);
    assert(state->clues);
    if (--state->clues->refcount <= 0) {
        sfree(state->clues->clues);
        sfree(state->clues->dsf);
        sfree(state->clues);
    }
    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)
		*error = "This puzzle is not self-consistent";
	    else
		*error = "Unable to find a unique solution for this puzzle";
            return NULL;
	}
	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", (int)(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", (int)(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->todraw);
    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 */
};