shithub: puzzles

Download patch

ref: 739609cec2262017aaabb7e34ce75af784fd683c
parent: c11f9ff173763e92155838d79a5ea130c5693540
author: Simon Tatham <anakin@pobox.com>
date: Tue May 31 14:09:28 EDT 2005

Aha! It turns out, after a bit of failure-mode profiling, that when
the Mines unique grid generator fails at high mine densities it is
_almost always_ for the same reason, and it also turns out that this
reason is one which can be addressed. So here's an enhancement to
mineperturb() which enables Mines to generate a grid at (as far as I
can tell) any mine density you like, up to and including w*h-9
mines. At densities of 1 in 2 or thereabouts the grids start to look
rather strange, but it can at least generate them without hanging.

[originally from svn r5885]

--- a/mines.c
+++ b/mines.c
@@ -10,14 +10,8 @@
  *       That hook can talk to the game_ui and set the cheated flag,
  *       and then make_move can avoid setting the `won' flag after that.
  *
- *  - question marks (arrgh, preferences?)
- * 
- *  - sensible parameter constraints
- *     + 30x16: 191 mines just about works if rather slowly, 192 is
- * 	 just about doom (the latter corresponding to a density of
- * 	 exactly 1 in 2.5)
- *     + 9x9: 45 mines works - over 1 in 2! 50 seems a bit slow.
- *     + it might not be feasible to work out the exact limit
+ *  - think about configurably supporting question marks. Once,
+ *    that is, we've thought about configurability in general!
  */
 
 #include <stdio.h>
@@ -1166,13 +1160,18 @@
 	     * 
 	     * If we have no sets at all, we must give up.
 	     */
-	    if (count234(ss->sets) == 0)
-		break;
-	    s = index234(ss->sets, random_upto(rs, count234(ss->sets)));
+	    if (count234(ss->sets) == 0) {
 #ifdef SOLVER_DIAGNOSTICS
-	    printf("perturbing on set %d,%d %03x\n", s->x, s->y, s->mask);
+		printf("perturbing on entire unknown set\n");
 #endif
-	    ret = perturb(ctx, grid, s->x, s->y, s->mask);
+		ret = perturb(ctx, grid, 0, 0, 0);
+	    } else {
+		s = index234(ss->sets, random_upto(rs, count234(ss->sets)));
+#ifdef SOLVER_DIAGNOSTICS
+		printf("perturbing on set %d,%d %03x\n", s->x, s->y, s->mask);
+#endif
+		ret = perturb(ctx, grid, s->x, s->y, s->mask);
+	    }
 
 	    if (ret) {
 		assert(ret->n > 0);    /* otherwise should have been NULL */
@@ -1182,6 +1181,8 @@
 		 * the returned structure tells us which. Adjust
 		 * the mine count in any set which overlaps one of
 		 * those squares, and put them back on the to-do
+		 * list. Also, if the square itself is marked as a
+		 * known non-mine, put it back on the squares-to-do
 		 * list.
 		 */
 		for (i = 0; i < ret->n; i++) {
@@ -1191,6 +1192,11 @@
 			   ret->changes[i].x, ret->changes[i].y);
 #endif
 
+		    if (ret->changes[i].delta < 0 &&
+			grid[ret->changes[i].y*w+ret->changes[i].x] != -2) {
+			std_add(std, ret->changes[i].y*w+ret->changes[i].x);
+		    }
+
 		    list = ss_overlap(ss,
 				      ret->changes[i].x, ret->changes[i].y, 1);
 
@@ -1212,7 +1218,7 @@
 		/*
 		 * Dump the current known state of the grid.
 		 */
-		printf("state after perturbation:\n", nperturbs);
+		printf("state after perturbation:\n");
 		for (y = 0; y < h; y++) {
 		    for (x = 0; x < w; x++) {
 			int v = grid[y*w+x];
@@ -1284,6 +1290,7 @@
     signed char *grid;
     int w, h;
     int sx, sy;
+    int allow_big_perturbs;
     random_state *rs;
 };
 
@@ -1340,6 +1347,22 @@
     return 0;
 }
 
+/*
+ * Normally this function is passed an (x,y,mask) set description.
+ * On occasions, though, there is no _localised_ set being used,
+ * and the set being perturbed is supposed to be the entirety of
+ * the unreachable area. This is signified by the special case
+ * mask==0: in this case, anything labelled -2 in the grid is part
+ * of the set.
+ * 
+ * Allowing perturbation in this special case appears to make it
+ * guaranteeably possible to generate a workable grid for any mine
+ * density, but they tend to be a bit boring, with mines packed
+ * densely into far corners of the grid and the remainder being
+ * less dense than one might like. Therefore, to improve overall
+ * grid quality I disable this feature for the first few attempts,
+ * and fall back to it after no useful grid has been generated.
+ */
 static struct perturbations *mineperturb(void *vctx, signed char *grid,
 					 int setx, int sety, int mask)
 {
@@ -1346,10 +1369,14 @@
     struct minectx *ctx = (struct minectx *)vctx;
     struct square *sqlist;
     int x, y, dx, dy, i, n, nfull, nempty;
-    struct square *tofill[9], *toempty[9], **todo;
+    struct square **tofill, **toempty, **todo;
     int ntofill, ntoempty, ntodo, dtodo, dset;
     struct perturbations *ret;
+    int *setlist;
 
+    if (!mask && !ctx->allow_big_perturbs)
+	return NULL;
+
     /*
      * Make a list of all the squares in the grid which we can
      * possibly use. This list should be in preference order, which
@@ -1379,9 +1406,10 @@
 	     * If this square is in the input set, also don't put
 	     * it on the list!
 	     */
-	    if (x >= setx && x < setx + 3 &&
-		y >= sety && y < sety + 3 &&
-		mask & (1 << ((y-sety)*3+(x-setx))))
+	    if ((mask == 0 && grid[y*ctx->w+x] == -2) ||
+		(x >= setx && x < setx + 3 &&
+		 y >= sety && y < sety + 3 &&
+		 mask & (1 << ((y-sety)*3+(x-setx)))))
 		continue;
 
 	    sqlist[n].x = x;
@@ -1424,16 +1452,27 @@
      * we've been provided.
      */
     nfull = nempty = 0;
-    for (dy = 0; dy < 3; dy++)
-	for (dx = 0; dx < 3; dx++)
-	    if (mask & (1 << (dy*3+dx))) {
-		assert(setx+dx <= ctx->w);
-		assert(sety+dy <= ctx->h);
-		if (ctx->grid[(sety+dy)*ctx->w+(setx+dx)])
-		    nfull++;
-		else
-		    nempty++;
-	    }
+    if (mask) {
+	for (dy = 0; dy < 3; dy++)
+	    for (dx = 0; dx < 3; dx++)
+		if (mask & (1 << (dy*3+dx))) {
+		    assert(setx+dx <= ctx->w);
+		    assert(sety+dy <= ctx->h);
+		    if (ctx->grid[(sety+dy)*ctx->w+(setx+dx)])
+			nfull++;
+		    else
+			nempty++;
+		}
+    } else {
+	for (y = 0; y < ctx->h; y++)
+	    for (x = 0; x < ctx->w; x++)
+		if (grid[y*ctx->w+x] == -2) {
+		    if (ctx->grid[y*ctx->w+x])
+			nfull++;
+		    else
+			nempty++;
+		}
+    }
 
     /*
      * Now go through our sorted list until we find either `nfull'
@@ -1443,6 +1482,13 @@
      * overall.
      */
     ntofill = ntoempty = 0;
+    if (mask) {
+	tofill = snewn(9, struct square *);
+	toempty = snewn(9, struct square *);
+    } else {
+	tofill = snewn(ctx->w * ctx->h, struct square *);
+	toempty = snewn(ctx->w * ctx->h, struct square *);
+    }
     for (i = 0; i < n; i++) {
 	struct square *sq = &sqlist[i];
 	if (ctx->grid[sq->y * ctx->w + sq->x])
@@ -1454,13 +1500,56 @@
     }
 
     /*
-     * If this didn't work at all, I think we just give up.
+     * If we haven't found enough empty squares outside the set to
+     * empty it into _or_ enough full squares outside it to fill it
+     * up with, we'll have to settle for doing only a partial job.
+     * In this case we choose to always _fill_ the set (because
+     * this case will tend to crop up when we're working with very
+     * high mine densities and the only way to get a solvable grid
+     * is going to be to pack most of the mines solidly around the
+     * edges). So now our job is to make a list of the empty
+     * squares in the set, and shuffle that list so that we fill a
+     * random selection of them.
      */
     if (ntofill != nfull && ntoempty != nempty) {
-	sfree(sqlist);
-	return NULL;
-    }
+	int k;
 
+	assert(ntoempty != 0);
+
+	setlist = snewn(ctx->w * ctx->h, int);
+	i = 0;
+	if (mask) {
+	    for (dy = 0; dy < 3; dy++)
+		for (dx = 0; dx < 3; dx++)
+		    if (mask & (1 << (dy*3+dx))) {
+			assert(setx+dx <= ctx->w);
+			assert(sety+dy <= ctx->h);
+			if (!ctx->grid[(sety+dy)*ctx->w+(setx+dx)])
+			    setlist[i++] = (sety+dy)*ctx->w+(setx+dx);
+		    }
+	} else {
+	    for (y = 0; y < ctx->h; y++)
+		for (x = 0; x < ctx->w; x++)
+		    if (grid[y*ctx->w+x] == -2) {
+			if (!ctx->grid[y*ctx->w+x])
+			    setlist[i++] = y*ctx->w+x;
+		    }
+	}
+	assert(i > ntoempty);
+	/*
+	 * Now pick `ntoempty' items at random from the list.
+	 */
+	for (k = 0; k < ntoempty; k++) {
+	    int index = k + random_upto(ctx->rs, i - k);
+	    int tmp;
+
+	    tmp = setlist[k];
+	    setlist[k] = setlist[index];
+	    setlist[index] = tmp;
+	}
+    } else
+	setlist = NULL;
+
     /*
      * Now we're pretty much there. We need to either
      * 	(a) put a mine in each of the empty squares in the set, and
@@ -1479,11 +1568,17 @@
 	ntodo = ntofill;
 	dtodo = +1;
 	dset = -1;
+	sfree(toempty);
     } else {
+	/*
+	 * (We also fall into this case if we've constructed a
+	 * setlist.)
+	 */
 	todo = toempty;
 	ntodo = ntoempty;
 	dtodo = -1;
 	dset = +1;
+	sfree(tofill);
     }
     ret->n = 2 * ntodo;
     ret->changes = snewn(ret->n, struct perturbation);
@@ -1493,20 +1588,45 @@
 	ret->changes[i].delta = dtodo;
     }
     /* now i == ntodo */
-    for (dy = 0; dy < 3; dy++)
-	for (dx = 0; dx < 3; dx++)
-	    if (mask & (1 << (dy*3+dx))) {
-		int currval = (ctx->grid[(sety+dy)*ctx->w+(setx+dx)] ? +1 : -1);
-		if (dset == -currval) {
-		    ret->changes[i].x = setx + dx;
-		    ret->changes[i].y = sety + dy;
-		    ret->changes[i].delta = dset;
-		    i++;
+    if (setlist) {
+	int j;
+	assert(todo == toempty);
+	for (j = 0; j < ntoempty; j++) {
+	    ret->changes[i].x = setlist[j] % ctx->w;
+	    ret->changes[i].y = setlist[j] / ctx->w;
+	    ret->changes[i].delta = dset;
+	    i++;
+	}
+	sfree(setlist);
+    } else if (mask) {
+	for (dy = 0; dy < 3; dy++)
+	    for (dx = 0; dx < 3; dx++)
+		if (mask & (1 << (dy*3+dx))) {
+		    int currval = (ctx->grid[(sety+dy)*ctx->w+(setx+dx)] ? +1 : -1);
+		    if (dset == -currval) {
+			ret->changes[i].x = setx + dx;
+			ret->changes[i].y = sety + dy;
+			ret->changes[i].delta = dset;
+			i++;
+		    }
 		}
-	    }
+    } else {
+	for (y = 0; y < ctx->h; y++)
+	    for (x = 0; x < ctx->w; x++)
+		if (grid[y*ctx->w+x] == -2) {
+		    int currval = (ctx->grid[y*ctx->w+x] ? +1 : -1);
+		    if (dset == -currval) {
+			ret->changes[i].x = x;
+			ret->changes[i].y = y;
+			ret->changes[i].delta = dset;
+			i++;
+		    }
+		}
+    }
     assert(i == ret->n);
 
     sfree(sqlist);
+    sfree(todo);
 
     /*
      * Having set up the precise list of changes we're going to
@@ -1593,9 +1713,11 @@
 {
     char *ret = snewn(w*h, char);
     int success;
+    int ntries = 0;
 
     do {
 	success = FALSE;
+	ntries++;
 
 	memset(ret, 0, w*h);
 
@@ -1670,6 +1792,7 @@
 	    ctx->sx = x;
 	    ctx->sy = y;
 	    ctx->rs = rs;
+	    ctx->allow_big_perturbs = (ntries > 100);
 
 	    while (1) {
 		memset(solvegrid, -2, w*h);