shithub: puzzles

ref: 48f6bfa474f091c661a281e36b390a52f998f94d
dir: /unfinished/pearl.c/

View raw version
/*
 * pearl.c: Nikoli's `Masyu' puzzle. Currently this is a blank
 * puzzle file with nothing but a test solver-generator.
 */

/*
 * TODO:
 * 
 *  - The generation method appears to be fundamentally flawed. I
 *    think generating a random loop and then choosing a clue set
 *    is simply not a viable approach, because on a test run of
 *    10,000 attempts, it generated _six_ viable puzzles. All the
 *    rest of the randomly generated loops failed to be soluble
 *    even given a maximal clue set. Also, the vast majority of the
 *    clues were white circles (straight clues); black circles
 *    (corners) seem very uncommon.
 *     + So what can we do? One possible approach would be to
 * 	 adjust the random loop generation so that it created loops
 * 	 which were in some heuristic sense more likely to be
 * 	 viable Masyu puzzles. Certainly a good start on that would
 * 	 be to arrange that black clues actually _came up_ slightly
 * 	 more often, but I have no idea whether that would be
 * 	 sufficient.
 *     + A second option would be to throw the entire mechanism out
 * 	 and instead write a different generator from scratch which
 * 	 evolves the solution along with the puzzle: place a few
 * 	 clues, nail down a bit of the loop, place another clue,
 * 	 nail down some more, etc. It's unclear whether this can
 * 	 sensibly be done, though.
 * 
 *  - Puzzle playing UI and everything else apart from the
 *    generator...
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include <math.h>

#include "puzzles.h"

#define NOCLUE 0
#define CORNER 1
#define STRAIGHT 2

#define R 1
#define U 2
#define L 4
#define D 8

#define DX(d) ( ((d)==R) - ((d)==L) )
#define DY(d) ( ((d)==D) - ((d)==U) )

#define F(d) (((d << 2) | (d >> 2)) & 0xF)
#define C(d) (((d << 3) | (d >> 1)) & 0xF)
#define A(d) (((d << 1) | (d >> 3)) & 0xF)

#define LR (L | R)
#define RL (R | L)
#define UD (U | D)
#define DU (D | U)
#define LU (L | U)
#define UL (U | L)
#define LD (L | D)
#define DL (D | L)
#define RU (R | U)
#define UR (U | R)
#define RD (R | D)
#define DR (D | R)
#define BLANK 0
#define UNKNOWN 15

#define bLR (1 << LR)
#define bRL (1 << RL)
#define bUD (1 << UD)
#define bDU (1 << DU)
#define bLU (1 << LU)
#define bUL (1 << UL)
#define bLD (1 << LD)
#define bDL (1 << DL)
#define bRU (1 << RU)
#define bUR (1 << UR)
#define bRD (1 << RD)
#define bDR (1 << DR)
#define bBLANK (1 << BLANK)

enum {
    COL_BACKGROUND,
    NCOLOURS
};

struct game_params {
    int FIXME;
};

struct game_state {
    int FIXME;
};

static game_params *default_params(void)
{
    game_params *ret = snew(game_params);

    ret->FIXME = 0;

    return ret;
}

static int game_fetch_preset(int i, char **name, game_params **params)
{
    return FALSE;
}

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 *params, char const *string)
{
}

static char *encode_params(game_params *params, int full)
{
    return dupstr("FIXME");
}

static config_item *game_configure(game_params *params)
{
    return NULL;
}

static game_params *custom_params(config_item *cfg)
{
    return NULL;
}

static char *validate_params(game_params *params, int full)
{
    return NULL;
}

/* ----------------------------------------------------------------------
 * Solver.
 */

int pearl_solve(int w, int h, char *clues, char *result)
{
    int W = 2*w+1, H = 2*h+1;
    short *workspace;
    int *dsf, *dsfsize;
    int x, y, b, d;
    int ret = -1;

    /*
     * workspace[(2*y+1)*W+(2*x+1)] indicates the possible nature
     * of the square (x,y), as a logical OR of bitfields.
     * 
     * workspace[(2*y)*W+(2*x+1)], for x odd and y even, indicates
     * whether the horizontal edge between (x,y) and (x+1,y) is
     * connected (1), disconnected (2) or unknown (3).
     * 
     * workspace[(2*y+1)*W+(2*x)], indicates the same about the
     * vertical edge between (x,y) and (x,y+1).
     * 
     * Initially, every square is considered capable of being in
     * any of the seven possible states (two straights, four
     * corners and empty), except those corresponding to clue
     * squares which are more restricted.
     * 
     * Initially, all edges are unknown, except the ones around the
     * grid border which are known to be disconnected.
     */
    workspace = snewn(W*H, short);
    for (x = 0; x < W*H; x++)
	workspace[x] = 0;
    /* Square states */
    for (y = 0; y < h; y++)
	for (x = 0; x < w; x++)
	    switch (clues[y*w+x]) {
	      case CORNER:
		workspace[(2*y+1)*W+(2*x+1)] = bLU|bLD|bRU|bRD;
		break;
	      case STRAIGHT:
		workspace[(2*y+1)*W+(2*x+1)] = bLR|bUD;
		break;
	      default:
		workspace[(2*y+1)*W+(2*x+1)] = bLR|bUD|bLU|bLD|bRU|bRD|bBLANK;
		break;
	    }
    /* Horizontal edges */
    for (y = 0; y <= h; y++)
	for (x = 0; x < w; x++)
	    workspace[(2*y)*W+(2*x+1)] = (y==0 || y==h ? 2 : 3);
    /* Vertical edges */
    for (y = 0; y < h; y++)
	for (x = 0; x <= w; x++)
	    workspace[(2*y+1)*W+(2*x)] = (x==0 || x==w ? 2 : 3);

    /*
     * We maintain a dsf of connected squares, together with a
     * count of the size of each equivalence class.
     */
    dsf = snewn(w*h, int);
    dsfsize = snewn(w*h, int);

    /*
     * Now repeatedly try to find something we can do.
     */
    while (1) {
	int done_something = FALSE;

#ifdef SOLVER_DIAGNOSTICS
	for (y = 0; y < H; y++) {
	    for (x = 0; x < W; x++)
		printf("%*x", (x&1) ? 5 : 2, workspace[y*W+x]);
	    printf("\n");
	}
#endif

	/*
	 * Go through the square state words, and discard any
	 * square state which is inconsistent with known facts
	 * about the edges around the square.
	 */
	for (y = 0; y < h; y++)
	    for (x = 0; x < w; x++) {
		for (b = 0; b < 0xD; b++)
		    if (workspace[(2*y+1)*W+(2*x+1)] & (1<<b)) {
			/*
			 * If any edge of this square is known to
			 * be connected when state b would require
			 * it disconnected, or vice versa, discard
			 * the state.
			 */
			for (d = 1; d <= 8; d += d) {
			    int ex = 2*x+1 + DX(d), ey = 2*y+1 + DY(d);
			    if (workspace[ey*W+ex] ==
				((b & d) ? 2 : 1)) {
				workspace[(2*y+1)*W+(2*x+1)] &= ~(1<<b);
#ifdef SOLVER_DIAGNOSTICS
				printf("edge (%d,%d)-(%d,%d) rules out state"
				       " %d for square (%d,%d)\n",
				       ex/2, ey/2, (ex+1)/2, (ey+1)/2,
				       b, x, y);
#endif
				done_something = TRUE;
				break;
			    }
			}
		    }

		/*
		 * Consistency check: each square must have at
		 * least one state left!
		 */
		if (!workspace[(2*y+1)*W+(2*x+1)]) {
#ifdef SOLVER_DIAGNOSTICS
		    printf("edge check at (%d,%d): inconsistency\n", x, y);
#endif
		    ret = 0;
		    goto cleanup;
		}
	    }

	/*
	 * Now go through the states array again, and nail down any
	 * unknown edge if one of its neighbouring squares makes it
	 * known.
	 */
	for (y = 0; y < h; y++)
	    for (x = 0; x < w; x++) {
		int edgeor = 0, edgeand = 15;

		for (b = 0; b < 0xD; b++)
		    if (workspace[(2*y+1)*W+(2*x+1)] & (1<<b)) {
			edgeor |= b;
			edgeand &= b;
		    }

		/*
		 * Now any bit clear in edgeor marks a disconnected
		 * edge, and any bit set in edgeand marks a
		 * connected edge.
		 */

		/* First check consistency: neither bit is both! */
		if (edgeand & ~edgeor) {
#ifdef SOLVER_DIAGNOSTICS
		    printf("square check at (%d,%d): inconsistency\n", x, y);
#endif
		    ret = 0;
		    goto cleanup;
		}

		for (d = 1; d <= 8; d += d) {
		    int ex = 2*x+1 + DX(d), ey = 2*y+1 + DY(d);

		    if (!(edgeor & d) && workspace[ey*W+ex] == 3) {
			workspace[ey*W+ex] = 2;
			done_something = TRUE;
#ifdef SOLVER_DIAGNOSTICS
			printf("possible states of square (%d,%d) force edge"
			       " (%d,%d)-(%d,%d) to be disconnected\n",
			       x, y, ex/2, ey/2, (ex+1)/2, (ey+1)/2);
#endif
		    } else if ((edgeand & d) && workspace[ey*W+ex] == 3) {
			workspace[ey*W+ex] = 1;
			done_something = TRUE;
#ifdef SOLVER_DIAGNOSTICS
			printf("possible states of square (%d,%d) force edge"
			       " (%d,%d)-(%d,%d) to be connected\n",
			       x, y, ex/2, ey/2, (ex+1)/2, (ey+1)/2);
#endif
		    }
		}
	    }

	if (done_something)
	    continue;

	/*
	 * Now for longer-range clue-based deductions (using the
	 * rules that a corner clue must connect to two straight
	 * squares, and a straight clue must connect to at least
	 * one corner square).
	 */
	for (y = 0; y < h; y++)
	    for (x = 0; x < w; x++)
		switch (clues[y*w+x]) {
		  case CORNER:
		    for (d = 1; d <= 8; d += d) {
			int ex = 2*x+1 + DX(d), ey = 2*y+1 + DY(d);
			int fx = ex + DX(d), fy = ey + DY(d);
			int type = d | F(d);

			if (workspace[ey*W+ex] == 1) {
			    /*
			     * If a corner clue is connected on any
			     * edge, then we can immediately nail
			     * down the square beyond that edge as
			     * being a straight in the appropriate
			     * direction.
			     */
			    if (workspace[fy*W+fx] != (1<<type)) {
				workspace[fy*W+fx] = (1<<type);
				done_something = TRUE;
#ifdef SOLVER_DIAGNOSTICS
				printf("corner clue at (%d,%d) forces square "
				       "(%d,%d) into state %d\n", x, y,
				       fx/2, fy/2, type);
#endif
				
			    }
			} else if (workspace[ey*W+ex] == 3) {
			    /*
			     * Conversely, if a corner clue is
			     * separated by an unknown edge from a
			     * square which _cannot_ be a straight
			     * in the appropriate direction, we can
			     * mark that edge as disconnected.
			     */
			    if (!(workspace[fy*W+fx] & (1<<type))) {
				workspace[ey*W+ex] = 2;
				done_something = TRUE;
#ifdef SOLVER_DIAGNOSTICS
				printf("corner clue at (%d,%d), plus square "
				       "(%d,%d) not being state %d, "
				       "disconnects edge (%d,%d)-(%d,%d)\n",
				       x, y, fx/2, fy/2, type,
				       ex/2, ey/2, (ex+1)/2, (ey+1)/2);
#endif

			    }
			}
		    }

		    break;
		  case STRAIGHT:
		    /*
		     * If a straight clue is between two squares
		     * neither of which is capable of being a
		     * corner connected to it, then the straight
		     * clue cannot point in that direction.
		     */
		    for (d = 1; d <= 2; d += d) {
			int fx = 2*x+1 + 2*DX(d), fy = 2*y+1 + 2*DY(d);
			int gx = 2*x+1 - 2*DX(d), gy = 2*y+1 - 2*DY(d);
			int type = d | F(d);

			if (!(workspace[(2*y+1)*W+(2*x+1)] & (1<<type)))
			    continue;

			if (!(workspace[fy*W+fx] & ((1<<(F(d)|A(d))) |
						    (1<<(F(d)|C(d))))) &&
			    !(workspace[gy*W+gx] & ((1<<(  d |A(d))) |
						    (1<<(  d |C(d)))))) {
			    workspace[(2*y+1)*W+(2*x+1)] &= ~(1<<type);
			    done_something = TRUE;
#ifdef SOLVER_DIAGNOSTICS
			    printf("straight clue at (%d,%d) cannot corner at "
				   "(%d,%d) or (%d,%d) so is not state %d\n",
				   x, y, fx/2, fy/2, gx/2, gy/2, type);
#endif
			}
						    
		    }

		    /*
		     * If a straight clue with known direction is
		     * connected on one side to a known straight,
		     * then on the other side it must be a corner.
		     */
		    for (d = 1; d <= 8; d += d) {
			int fx = 2*x+1 + 2*DX(d), fy = 2*y+1 + 2*DY(d);
			int gx = 2*x+1 - 2*DX(d), gy = 2*y+1 - 2*DY(d);
			int type = d | F(d);

			if (workspace[(2*y+1)*W+(2*x+1)] != (1<<type))
			    continue;

			if (!(workspace[fy*W+fx] &~ (bLR|bUD)) &&
			    (workspace[gy*W+gx] &~ (bLU|bLD|bRU|bRD))) {
			    workspace[gy*W+gx] &= (bLU|bLD|bRU|bRD);
			    done_something = TRUE;
#ifdef SOLVER_DIAGNOSTICS
			    printf("straight clue at (%d,%d) connecting to "
				   "straight at (%d,%d) makes (%d,%d) a "
				   "corner\n", x, y, fx/2, fy/2, gx/2, gy/2);
#endif
			}
						    
		    }
		    break;
		}

	if (done_something)
	    continue;

	/*
	 * Now detect shortcut loops.
	 */

	{
	    int nonblanks, loopclass;

	    dsf_init(dsf, w*h);
	    for (x = 0; x < w*h; x++)
		dsfsize[x] = 1;

	    /*
	     * First go through the edge entries and update the dsf
	     * of which squares are connected to which others. We
	     * also track the number of squares in each equivalence
	     * class, and count the overall number of
	     * known-non-blank squares.
	     *
	     * In the process of doing this, we must notice if a
	     * loop has already been formed. If it has, we blank
	     * out any square which isn't part of that loop
	     * (failing a consistency check if any such square does
	     * not have BLANK as one of its remaining options) and
	     * exit the deduction loop with success.
	     */
	    nonblanks = 0;
	    loopclass = -1;
	    for (y = 1; y < H-1; y++)
		for (x = 1; x < W-1; x++)
		    if ((y ^ x) & 1) {
			/*
			 * (x,y) are the workspace coordinates of
			 * an edge field. Compute the normal-space
			 * coordinates of the squares it connects.
			 */
			int ax = (x-1)/2, ay = (y-1)/2, ac = ay*w+ax;
			int bx = x/2, by = y/2, bc = by*w+bx;

			/*
			 * If the edge is connected, do the dsf
			 * thing.
			 */
			if (workspace[y*W+x] == 1) {
			    int ae, be;

			    ae = dsf_canonify(dsf, ac);
			    be = dsf_canonify(dsf, bc);

			    if (ae == be) {
				/*
				 * We have a loop!
				 */
				if (loopclass != -1) {
				    /*
				     * In fact, we have two
				     * separate loops, which is
				     * doom.
				     */
#ifdef SOLVER_DIAGNOSTICS
				    printf("two loops found in grid!\n");
#endif
				    ret = 0;
				    goto cleanup;
				}
				loopclass = ae;
			    } else {
				/*
				 * Merge the two equivalence
				 * classes.
				 */
				int size = dsfsize[ae] + dsfsize[be];
				dsf_merge(dsf, ac, bc);
				ae = dsf_canonify(dsf, ac);
				dsfsize[ae] = size;
			    }
			}
		    } else if ((y & x) & 1) {
			/*
			 * (x,y) are the workspace coordinates of a
			 * square field. If the square is
			 * definitely not blank, count it.
			 */
			if (!(workspace[y*W+x] & bBLANK))
			    nonblanks++;
		    }

	    /*
	     * If we discovered an existing loop above, we must now
	     * blank every square not part of it, and exit the main
	     * deduction loop.
	     */
	    if (loopclass != -1) {
#ifdef SOLVER_DIAGNOSTICS
		printf("loop found in grid!\n");
#endif
		for (y = 0; y < h; y++)
		    for (x = 0; x < w; x++)
			if (dsf_canonify(dsf, y*w+x) != loopclass) {
			    if (workspace[(y*2+1)*W+(x*2+1)] & bBLANK) {
				workspace[(y*2+1)*W+(x*2+1)] = bBLANK;
			    } else {
				/*
				 * This square is not part of the
				 * loop, but is known non-blank. We
				 * have goofed.
				 */
#ifdef SOLVER_DIAGNOSTICS
				printf("non-blank square (%d,%d) found outside"
				       " loop!\n", x, y);
#endif
				ret = 0;
				goto cleanup;
			    }
			}
		/*
		 * And we're done.
		 */
		ret = 1;
		break;
	    }

	    /*
	     * Now go through the workspace again and mark any edge
	     * which would cause a shortcut loop (i.e. would
	     * connect together two squares in the same equivalence
	     * class, and that equivalence class does not contain
	     * _all_ the known-non-blank squares currently in the
	     * grid) as disconnected. Also, mark any _square state_
	     * which would cause a shortcut loop as disconnected.
	     */
	    for (y = 1; y < H-1; y++)
		for (x = 1; x < W-1; x++)
		    if ((y ^ x) & 1) {
			/*
			 * (x,y) are the workspace coordinates of
			 * an edge field. Compute the normal-space
			 * coordinates of the squares it connects.
			 */
			int ax = (x-1)/2, ay = (y-1)/2, ac = ay*w+ax;
			int bx = x/2, by = y/2, bc = by*w+bx;

			/*
			 * If the edge is currently unknown, and
			 * sits between two squares in the same
			 * equivalence class, and the size of that
			 * class is less than nonblanks, then
			 * connecting this edge would be a shortcut
			 * loop and so we must not do so.
			 */
			if (workspace[y*W+x] == 3) {
			    int ae, be;

			    ae = dsf_canonify(dsf, ac);
			    be = dsf_canonify(dsf, bc);

			    if (ae == be) {
				/*
				 * We have a loop. Is it a shortcut?
				 */
				if (dsfsize[ae] < nonblanks) {
				    /*
				     * Yes! Mark this edge disconnected.
				     */
				    workspace[y*W+x] = 2;
				    done_something = TRUE;
#ifdef SOLVER_DIAGNOSTICS
				    printf("edge (%d,%d)-(%d,%d) would create"
					   " a shortcut loop, hence must be"
					   " disconnected\n", x/2, y/2,
					   (x+1)/2, (y+1)/2);
#endif
				}
			    }
			}
		    } else if ((y & x) & 1) {
			/*
			 * (x,y) are the workspace coordinates of a
			 * square field. Go through its possible
			 * (non-blank) states and see if any gives
			 * rise to a shortcut loop.
			 * 
			 * This is slightly fiddly, because we have
			 * to check whether this square is already
			 * part of the same equivalence class as
			 * the things it's joining.
			 */
			int ae = dsf_canonify(dsf, (y/2)*w+(x/2));

			for (b = 2; b < 0xD; b++)
			    if (workspace[y*W+x] & (1<<b)) {
				/*
				 * Find the equivalence classes of
				 * the two squares this one would
				 * connect if it were in this
				 * state.
				 */
				int e = -1;

				for (d = 1; d <= 8; d += d) if (b & d) {
				    int xx = x/2 + DX(d), yy = y/2 + DY(d);
				    int ee = dsf_canonify(dsf, yy*w+xx);

				    if (e == -1)
					ee = e;
				    else if (e != ee)
					e = -2;
				}

				if (e >= 0) {
				    /*
				     * This square state would form
				     * a loop on equivalence class
				     * e. Measure the size of that
				     * loop, and see if it's a
				     * shortcut.
				     */
				    int loopsize = dsfsize[e];
				    if (e != ae)
					loopsize++;/* add the square itself */
				    if (loopsize < nonblanks) {
					/*
					 * It is! Mark this square
					 * state invalid.
					 */
					workspace[y*W+x] &= ~(1<<b);
					done_something = TRUE;
#ifdef SOLVER_DIAGNOSTICS
					printf("square (%d,%d) would create a "
					       "shortcut loop in state %d, "
					       "hence cannot be\n",
					       x/2, y/2, b);
#endif
				    }
				}
			    }
		    }
	}

	if (done_something)
	    continue;

	/*
	 * If we reach here, there is nothing left we can do.
	 * Return 2 for ambiguous puzzle.
	 */
	ret = 2;
	goto cleanup;
    }

    /*
     * If we reach _here_, it's by `break' out of the main loop,
     * which means we've successfully achieved a solution. This
     * means that we expect every square to be nailed down to
     * exactly one possibility. Transcribe those possibilities into
     * the result array.
     */
    for (y = 0; y < h; y++)
	for (x = 0; x < w; x++) {
	    for (b = 0; b < 0xD; b++)
		if (workspace[(2*y+1)*W+(2*x+1)] == (1<<b)) {
		    result[y*w+x] = b;
		    break;
		}
	    assert(b < 0xD);	       /* we should have had a break by now */
	}

    cleanup:
    sfree(dsfsize);
    sfree(dsf);
    sfree(workspace);
    assert(ret >= 0);
    return ret;
}

/* ----------------------------------------------------------------------
 * Loop generator.
 */

void pearl_loopgen(int w, int h, char *grid, random_state *rs)
{
    int *options, *mindist, *maxdist, *list;
    int x, y, d, total, n, area, limit;

    /*
     * We're eventually going to have to return a w-by-h array
     * containing line segment data. However, it's more convenient
     * while actually generating the loop to consider the problem
     * as a (w-1) by (h-1) array in which some squares are `inside'
     * and some `outside'.
     * 
     * I'm going to use the top left corner of my return array in
     * the latter manner until the end of the function.
     */

    /*
     * To begin with, all squares are outside (0), except for one
     * randomly selected one which is inside (1).
     */
    memset(grid, 0, w*h);
    x = random_upto(rs, w-1);
    y = random_upto(rs, h-1);
    grid[y*w+x] = 1;

    /*
     * I'm also going to need an array to store the possible
     * options for the next extension of the grid.
     */
    options = snewn(w*h, int);
    for (x = 0; x < w*h; x++)
	options[x] = 0;

    /*
     * And some arrays and a list for breadth-first searching.
     */
    mindist = snewn(w*h, int);
    maxdist = snewn(w*h, int);
    list = snewn(w*h, int);

    /*
     * Now we repeatedly scan the grid for feasible squares into
     * which we can extend our loop, pick one, and do it.
     */
    area = 1;

    while (1) {
#ifdef LOOPGEN_DIAGNOSTICS
	for (y = 0; y < h; y++) {
	    for (x = 0; x < w; x++)
		printf("%d", grid[y*w+x]);
	    printf("\n");
	}
	printf("\n");
#endif

	/*
	 * Our primary aim in growing this loop is to make it
	 * reasonably _dense_ in the target rectangle. That is, we
	 * want the maximum over all squares of the minimum
	 * distance from that square to the loop to be small.
	 * 
	 * Therefore, we start with a breadth-first search of the
	 * grid to find those minimum distances.
	 */
	{
	    int head = 0, tail = 0;
	    int i;

	    for (i = 0; i < w*h; i++) {
		mindist[i] = -1;
		if (grid[i]) {
		    mindist[i] = 0;
		    list[tail++] = i;
		}
	    }

	    while (head < tail) {
		i = list[head++];
		y = i / w;
		x = i % w;
		for (d = 1; d <= 8; d += d) {
		    int xx = x + DX(d), yy = y + DY(d);
		    if (xx >= 0 && xx < w && yy >= 0 && yy < h &&
			mindist[yy*w+xx] < 0) {
			mindist[yy*w+xx] = mindist[i] + 1;
			list[tail++] = yy*w+xx;
		    }
		}
	    }

	    /*
	     * Having done the BFS, we now backtrack along its path
	     * to determine the most distant square that each
	     * square is on the shortest path to. This tells us
	     * which of the loop extension candidates (all of which
	     * are squares marked 1) is most desirable to extend
	     * into in terms of minimising the maximum distance
	     * from any empty square to the nearest loop square.
	     */
	    for (head = tail; head-- > 0 ;) {
		int max;

		i = list[head];
		y = i / w;
		x = i % w;

		max = mindist[i];

		for (d = 1; d <= 8; d += d) {
		    int xx = x + DX(d), yy = y + DY(d);
		    if (xx >= 0 && xx < w && yy >= 0 && yy < h &&
			mindist[yy*w+xx] > mindist[i] &&
			maxdist[yy*w+xx] > max) {
			max = maxdist[yy*w+xx];
		    }
		}

		maxdist[i] = max;
	    }
	}

	/*
	 * A square is a viable candidate for extension of our loop
	 * if and only if the following conditions are all met:
	 *  - It is currently labelled 0.
	 *  - At least one of its four orthogonal neighbours is
	 *    labelled 1.
	 *  - If you consider its eight orthogonal and diagonal
	 *    neighbours to form a ring, that ring contains at most
	 *    one contiguous run of 1s. (It must also contain at
	 *    _least_ one, of course, but that's already guaranteed
	 *    by the previous condition so there's no need to test
	 *    it separately.)
	 */
	total = 0;
	for (y = 0; y < h-1; y++)
	    for (x = 0; x < w-1; x++) {
		int ring[8];
		int rx, neighbours, runs, dist;

		dist = maxdist[y*w+x];
		options[y*w+x] = 0;

		if (grid[y*w+x])
		    continue;	       /* it isn't labelled 0 */

		neighbours = 0;
		for (rx = 0, d = 1; d <= 8; rx += 2, d += d) {
		    int x2 = x + DX(d), y2 = y + DY(d);
		    int x3 = x2 + DX(A(d)), y3 = y2 + DY(A(d));
		    int g2 = (x2 >= 0 && x2 < w && y2 >= 0 && y2 < h ?
			      grid[y2*w+x2] : 0);
		    int g3 = (x3 >= 0 && x3 < w && y3 >= 0 && y3 < h ?
			      grid[y3*w+x3] : 0);
		    ring[rx] = g2;
		    ring[rx+1] = g3;
		    if (g2)
			neighbours++;
		}

		if (!neighbours)
		    continue;	       /* it doesn't have a 1 neighbour */

		runs = 0;
		for (rx = 0; rx < 8; rx++)
		    if (ring[rx] && !ring[(rx+1) & 7])
			runs++;

		if (runs > 1)
		    continue;	       /* too many runs of 1s */

		/*
		 * Now we know this square is a viable extension
		 * candidate. Mark it.
		 * 
		 * FIXME: probabilistic prioritisation based on
		 * perimeter perturbation? (Wow, must keep that
		 * phrase.)
		 */
		options[y*w+x] = dist * (4-neighbours) * (4-neighbours);
		total += options[y*w+x];
	    }

	if (!total)
	    break;		       /* nowhere to go! */

	/*
	 * Now pick a random one of the viable extension squares,
	 * and extend into it.
	 */
	n = random_upto(rs, total);
	for (y = 0; y < h-1; y++)
	    for (x = 0; x < w-1; x++) {
		assert(n >= 0);
		if (options[y*w+x] > n)
		    goto found;	       /* two-level break */
		n -= options[y*w+x];
	    }
	assert(!"We shouldn't ever get here");
	found:
	grid[y*w+x] = 1;
	area++;

	/*
	 * We terminate the loop when around 7/12 of the grid area
	 * is full, but we also require that the loop has reached
	 * all four edges.
	 */
	limit = random_upto(rs, (w-1)*(h-1)) + 13*(w-1)*(h-1);
	if (24 * area > limit) {
	    int l = FALSE, r = FALSE, u = FALSE, d = FALSE;
	    for (x = 0; x < w; x++) {
		if (grid[0*w+x])
		    u = TRUE;
		if (grid[(h-2)*w+x])
		    d = TRUE;
	    }
	    for (y = 0; y < h; y++) {
		if (grid[y*w+0])
		    l = TRUE;
		if (grid[y*w+(w-2)])
		    r = TRUE;
	    }
	    if (l && r && u && d)
		break;
	}
    }

    sfree(list);
    sfree(maxdist);
    sfree(mindist);
    sfree(options);

#ifdef LOOPGEN_DIAGNOSTICS
    printf("final loop:\n");
    for (y = 0; y < h; y++) {
	for (x = 0; x < w; x++)
	    printf("%d", grid[y*w+x]);
	printf("\n");
    }
    printf("\n");
#endif

    /*
     * Now convert this array of 0s and 1s into an array of path
     * components.
     */
    for (y = h; y-- > 0 ;) {
	for (x = w; x-- > 0 ;) {
	    /*
	     * Examine the four grid squares of which (x,y) are in
	     * the bottom right, to determine the output for this
	     * square.
	     */
	    int ul = (x > 0 && y > 0 ? grid[(y-1)*w+(x-1)] : 0);
	    int ur = (y > 0 ? grid[(y-1)*w+x] : 0);
	    int dl = (x > 0 ? grid[y*w+(x-1)] : 0);
	    int dr = grid[y*w+x];
	    int type = 0;

	    if (ul != ur) type |= U;
	    if (dl != dr) type |= D;
	    if (ul != dl) type |= L;
	    if (ur != dr) type |= R;

	    assert((bLR|bUD|bLU|bLD|bRU|bRD|bBLANK) & (1 << type));

	    grid[y*w+x] = type;

	}
    }

#if defined LOOPGEN_DIAGNOSTICS && !defined GENERATION_DIAGNOSTICS
    printf("as returned:\n");
    for (y = 0; y < h; y++) {
	for (x = 0; x < w; x++) {
	    int type = grid[y*w+x];
	    char s[5], *p = s;
	    if (type & L) *p++ = 'L';
	    if (type & R) *p++ = 'R';
	    if (type & U) *p++ = 'U';
	    if (type & D) *p++ = 'D';
	    *p = '\0';
	    printf("%3s", s);
	}
	printf("\n");
    }
    printf("\n");
#endif
}

static char *new_game_desc(game_params *params, random_state *rs,
			   char **aux, int interactive)
{
    char *grid, *clues;
    int *clueorder;
    int w = 10, h = 10;
    int x, y, d, ret, i;

#if 0
    clues = snewn(7*7, char);
    memcpy(clues,
	   "\0\1\0\0\2\0\0"
	   "\0\0\0\2\0\0\0"
	   "\0\0\0\2\0\0\1"
	   "\2\0\0\2\0\0\0"
	   "\2\0\0\0\0\0\1"
	   "\0\0\1\0\0\2\0"
	   "\0\0\2\0\0\0\0", 7*7);
    grid = snewn(7*7, char);
    printf("%d\n", pearl_solve(7, 7, clues, grid));
#elif 0
    clues = snewn(10*10, char);
    memcpy(clues,
	   "\0\0\2\0\2\0\0\0\0\0"
	   "\0\0\0\0\2\0\0\0\1\0"
	   "\0\0\1\0\1\0\2\0\0\0"
	   "\0\0\0\2\0\0\2\0\0\0"
	   "\1\0\0\0\0\2\0\0\0\2"
	   "\0\0\2\0\0\0\0\2\0\0"
	   "\0\0\1\0\0\0\2\0\0\0"
	   "\2\0\0\0\1\0\0\0\0\2"
	   "\0\0\0\0\0\0\2\2\0\0"
	   "\0\0\1\0\0\0\0\0\0\1", 10*10);
    grid = snewn(10*10, char);
    printf("%d\n", pearl_solve(10, 10, clues, grid));
#elif 0
    clues = snewn(10*10, char);
    memcpy(clues,
	   "\0\0\0\0\0\0\1\0\0\0"
	   "\0\1\0\1\2\0\0\0\0\2"
	   "\0\0\0\0\0\0\0\0\0\1"
	   "\2\0\0\1\2\2\1\0\0\0"
	   "\1\0\0\0\0\0\0\1\0\0"
	   "\0\0\2\0\0\0\0\0\0\2"
	   "\0\0\0\2\1\2\1\0\0\2"
	   "\2\0\0\0\0\0\0\0\0\0"
	   "\2\0\0\0\0\1\1\0\2\0"
	   "\0\0\0\2\0\0\0\0\0\0", 10*10);
    grid = snewn(10*10, char);
    printf("%d\n", pearl_solve(10, 10, clues, grid));
#endif

    grid = snewn(w*h, char);
    clues = snewn(w*h, char);
    clueorder = snewn(w*h, int);

    while (1) {
	pearl_loopgen(w, h, grid, rs);

#ifdef GENERATION_DIAGNOSTICS
	printf("grid array:\n");
	for (y = 0; y < h; y++) {
	    for (x = 0; x < w; x++) {
		int type = grid[y*w+x];
		char s[5], *p = s;
		if (type & L) *p++ = 'L';
		if (type & R) *p++ = 'R';
		if (type & U) *p++ = 'U';
		if (type & D) *p++ = 'D';
		*p = '\0';
		printf("%2s ", s);
	    }
	    printf("\n");
	}
	printf("\n");
#endif

	/*
	 * Set up the maximal clue array.
	 */
	for (y = 0; y < h; y++)
	    for (x = 0; x < w; x++) {
		int type = grid[y*w+x];

		clues[y*w+x] = NOCLUE;

		if ((bLR|bUD) & (1 << type)) {
		    /*
		     * This is a straight; see if it's a viable
		     * candidate for a straight clue. It qualifies if
		     * at least one of the squares it connects to is a
		     * corner.
		     */
		    for (d = 1; d <= 8; d += d) if (type & d) {
			int xx = x + DX(d), yy = y + DY(d);
			assert(xx >= 0 && xx < w && yy >= 0 && yy < h);
			if ((bLU|bLD|bRU|bRD) & (1 << grid[yy*w+xx]))
			    break;
		    }
		    if (d <= 8)	       /* we found one */
			clues[y*w+x] = STRAIGHT;
		} else if ((bLU|bLD|bRU|bRD) & (1 << type)) {
		    /*
		     * This is a corner; see if it's a viable candidate
		     * for a corner clue. It qualifies if all the
		     * squares it connects to are straights.
		     */
		    for (d = 1; d <= 8; d += d) if (type & d) {
			int xx = x + DX(d), yy = y + DY(d);
			assert(xx >= 0 && xx < w && yy >= 0 && yy < h);
			if (!((bLR|bUD) & (1 << grid[yy*w+xx])))
			    break;
		    }
		    if (d > 8)	       /* we didn't find a counterexample */
			clues[y*w+x] = CORNER;
		}
	    }

#ifdef GENERATION_DIAGNOSTICS
	printf("clue array:\n");
	for (y = 0; y < h; y++) {
	    for (x = 0; x < w; x++) {
		printf("%c", " *O"[(unsigned char)clues[y*w+x]]);
	    }
	    printf("\n");
	}
	printf("\n");
#endif

	/*
	 * See if we can solve the puzzle just like this.
	 */
	ret = pearl_solve(w, h, clues, grid);
	assert(ret > 0);	       /* shouldn't be inconsistent! */
	if (ret != 1)
	    continue;		       /* go round and try again */

	/*
	 * Now shuffle the grid points and gradually remove the
	 * clues to find a minimal set which still leaves the
	 * puzzle soluble.
	 */
	for (i = 0; i < w*h; i++)
	    clueorder[i] = i;
	shuffle(clueorder, w*h, sizeof(*clueorder), rs);
	for (i = 0; i < w*h; i++) {
	    int clue;

	    y = clueorder[i] / w;
	    x = clueorder[i] % w;

	    if (clues[y*w+x] == 0)
		continue;

	    clue = clues[y*w+x];
	    clues[y*w+x] = 0;	       /* try removing this clue */

	    ret = pearl_solve(w, h, clues, grid);
	    assert(ret > 0);
	    if (ret != 1)
		clues[y*w+x] = clue;   /* oops, put it back again */
	}

#ifdef FINISHED_PUZZLE
	printf("clue array:\n");
	for (y = 0; y < h; y++) {
	    for (x = 0; x < w; x++) {
		printf("%c", " *O"[(unsigned char)clues[y*w+x]]);
	    }
	    printf("\n");
	}
	printf("\n");
#endif

	break;			       /* got it */
    }

    sfree(grid);
    sfree(clues);
    sfree(clueorder);

    return dupstr("FIXME");
}

static char *validate_desc(game_params *params, char *desc)
{
    return NULL;
}

static game_state *new_game(midend *me, game_params *params, char *desc)
{
    game_state *state = snew(game_state);

    state->FIXME = 0;

    return state;
}

static game_state *dup_game(game_state *state)
{
    game_state *ret = snew(game_state);

    ret->FIXME = state->FIXME;

    return ret;
}

static void free_game(game_state *state)
{
    sfree(state);
}

static char *solve_game(game_state *state, game_state *currstate,
			char *aux, char **error)
{
    return NULL;
}

static int game_can_format_as_text_now(game_params *params)
{
    return TRUE;
}

static char *game_text_format(game_state *state)
{
    return NULL;
}

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)
{
}

struct game_drawstate {
    int tilesize;
    int FIXME;
};

static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
			    int x, int y, int button)
{
    return NULL;
}

static game_state *execute_move(game_state *state, char *move)
{
    return NULL;
}

/* ----------------------------------------------------------------------
 * Drawing routines.
 */

static void game_compute_size(game_params *params, int tilesize,
			      int *x, int *y)
{
    *x = *y = 10 * tilesize;	       /* FIXME */
}

static void game_set_size(drawing *dr, game_drawstate *ds,
			  game_params *params, int tilesize)
{
    ds->tilesize = tilesize;
}

static float *game_colours(frontend *fe, int *ncolours)
{
    float *ret = snewn(3 * NCOLOURS, float);

    frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);

    *ncolours = NCOLOURS;
    return ret;
}

static game_drawstate *game_new_drawstate(drawing *dr, game_state *state)
{
    struct game_drawstate *ds = snew(struct game_drawstate);

    ds->tilesize = 0;
    ds->FIXME = 0;

    return ds;
}

static void game_free_drawstate(drawing *dr, game_drawstate *ds)
{
    sfree(ds);
}

static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate,
			game_state *state, int dir, game_ui *ui,
			float animtime, float flashtime)
{
    /*
     * The initial contents of the window are not guaranteed and
     * can vary with front ends. To be on the safe side, all games
     * should start by drawing a big background-colour rectangle
     * covering the whole window.
     */
    draw_rect(dr, 0, 0, 10*ds->tilesize, 10*ds->tilesize, COL_BACKGROUND);
}

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)
{
    return 0.0F;
}

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)
{
}

static void game_print(drawing *dr, game_state *state, int tilesize)
{
}

#ifdef COMBINED
#define thegame pearl
#endif

const struct game thegame = {
    "Pearl", NULL, NULL,
    default_params,
    game_fetch_preset,
    decode_params,
    encode_params,
    free_params,
    dup_params,
    FALSE, game_configure, custom_params,
    validate_params,
    new_game_desc,
    validate_desc,
    new_game,
    dup_game,
    free_game,
    FALSE, solve_game,
    FALSE, game_can_format_as_text_now, game_text_format,
    new_ui,
    free_ui,
    encode_ui,
    decode_ui,
    game_changed_state,
    interpret_move,
    execute_move,
    20 /* FIXME */, game_compute_size, game_set_size,
    game_colours,
    game_new_drawstate,
    game_free_drawstate,
    game_redraw,
    game_anim_length,
    game_flash_length,
    FALSE, FALSE, game_print_size, game_print,
    FALSE,			       /* wants_statusbar */
    FALSE, game_timing_state,
    0,				       /* flags */
};