diff options
| author | Simon Tatham <anakin@pobox.com> | 2008-04-07 15:56:42 +0000 |
|---|---|---|
| committer | Simon Tatham <anakin@pobox.com> | 2008-04-07 15:56:42 +0000 |
| commit | 93103eeca4e41ecaa874069b10f2a3de8c392435 (patch) | |
| tree | c1958dcac18da3b939f313f552c930305a5e89f8 /unfinished | |
| parent | d2369aab621c80449db7c589f0c65c722802b8b4 (diff) | |
| download | puzzles-93103eeca4e41ecaa874069b10f2a3de8c392435.zip puzzles-93103eeca4e41ecaa874069b10f2a3de8c392435.tar.gz puzzles-93103eeca4e41ecaa874069b10f2a3de8c392435.tar.bz2 puzzles-93103eeca4e41ecaa874069b10f2a3de8c392435.tar.xz | |
Substantial reworking of Solo so that it implements both Sudoku-X
(require both main diagonals to have one of every digit in addition
to all the usual constraints) and Jigsaw Sudoku (replace the array
of rectangular sub-blocks with the sub-blocks being random
polyominoes). To implement the latter, I've moved my `divvy.c'
library routine out of the `unfinished' subdirectory.
Jigsaw mode is currently an undocumented feature: you enable it by
setting the rows parameter to 1 (and the columns parameter to your
desired grid size, which unlike normal Sudoku can be anything you
like including a prime number). The reason it's undocumented is
because generation times are not yet reliably short: sometimes
generating a jigsaw-type puzzle can hang for hours and still get
nowhere. (The algorithm should terminate in principle, but not in
any time you're prepared to wait.) I _think_ I know how to solve
this, but have yet to try it. Until then, jigsaw mode will remain a
hidden feature.
Printing of X-type puzzles is also substandard at present, because
the current print-colour API replaces the desired light shading of
the X-cells with heavy diagonal hatching. I plan to adjust the API
imminently to address this.
[originally from svn r7974]
Diffstat (limited to 'unfinished')
| -rw-r--r-- | unfinished/divvy.c | 781 |
1 files changed, 0 insertions, 781 deletions
diff --git a/unfinished/divvy.c b/unfinished/divvy.c deleted file mode 100644 index 517e3dd..0000000 --- a/unfinished/divvy.c +++ /dev/null @@ -1,781 +0,0 @@ -/* - * Library code to divide up a rectangle into a number of equally - * sized ominoes, in a random fashion. - * - * Could use this for generating solved grids of - * http://www.nikoli.co.jp/ja/puzzles/block_puzzle/ - * or for generating the playfield for Jigsaw Sudoku. - */ - -/* - * This code is restricted to simply connected solutions: that is, - * no single polyomino may completely surround another (not even - * with a corner visible to the outside world, in the sense that a - * 7-omino can `surround' a single square). - * - * It's tempting to think that this is a natural consequence of - * all the ominoes being the same size - after all, a division of - * anything into 7-ominoes must necessarily have all of them - * simply connected, because if one was not then the 1-square - * space in the middle could not be part of any 7-omino - but in - * fact, for sufficiently large k, it is perfectly possible for a - * k-omino to completely surround another k-omino. A simple - * example is this one with two 25-ominoes: - * - * +--+--+--+--+--+--+--+ - * | | - * + +--+--+--+--+--+ + - * | | | | - * + + + + - * | | | | - * + + + +--+ - * | | | | - * + + + +--+ - * | | | | - * + + + + - * | | | | - * + +--+--+--+--+--+ + - * | | - * +--+--+--+--+--+--+--+ - * - * I claim the smallest k which can manage this is 23. More - * formally: - * - * If a k-omino P is completely surrounded by another k-omino Q, - * such that every edge of P borders on Q, then k >= 23. - * - * Proof: - * - * It's relatively simple to find the largest _rectangle_ a - * k-omino can enclose. So I'll construct my proof in two parts: - * firstly, show that no 22-omino or smaller can enclose a - * rectangle as large as itself, and secondly, show that no - * polyomino can enclose a larger non-rectangle than a rectangle. - * - * The first of those claims: - * - * To surround an m x n rectangle, a polyomino must have 2m - * squares along the two m-sides of the rectangle, 2n squares - * along the two n-sides, and must fill in at least three of the - * corners in order to be connected. Thus, 2(m+n)+3 <= k. We wish - * to find the largest value of mn subject to that constraint, and - * it's clear that this is achieved when m and n are as close to - * equal as possible. (If they aren't, WLOG suppose m < n; then - * (m+1)(n-1) = mn + n - m - 1 >= mn, with equality only when - * m=n-1.) - * - * So the area of the largest rectangle which can be enclosed by a - * k-omino is given by floor(k'/2) * ceil(k'/2), where k' = - * (k-3)/2. This is a monotonic function in k, so there will be a - * unique point at which it goes from being smaller than k to - * being larger than k. That point is between 22 (maximum area 20) - * and 23 (maximum area 25). - * - * The second claim: - * - * Suppose we have an inner polyomino P surrounded by an outer - * polyomino Q. I seek to show that if P is non-rectangular, then - * P is also non-maximal, in the sense that we can transform P and - * Q into a new pair of polyominoes in which P is larger and Q is - * at most the same size. - * - * Consider walking along the boundary of P in a clockwise - * direction. (We may assume, of course, that there is only _one_ - * boundary of P, i.e. P has no hole in the middle. If it does - * have a hole in the middle, it's _trivially_ non-maximal because - * we can just fill the hole in!) Our walk will take us along many - * edges between squares; sometimes we might turn left, and - * certainly sometimes we will turn right. Always there will be a - * square of P on our right, and a square of Q on our left. - * - * The net angle through which we turn during the entire walk must - * add up to 360 degrees rightwards. So if there are no left - * turns, then we must turn right exactly four times, meaning we - * have described a rectangle. Hence, if P is _not_ rectangular, - * then there must have been a left turn at some point. A left - * turn must mean we walk along two edges of the same square of Q. - * - * Thus, there is some square X in Q which is adjacent to two - * diagonally separated squares in P. Let us call those two - * squares N and E; let us refer to the other two neighbours of X - * as S and W; let us refer to the other mutual neighbour of S and - * W as D; and let us refer to the other mutual neighbour of S and - * E as Y. In other words, we have named seven squares, arranged - * thus: - * - * N - * W X E - * D S Y - * - * where N and E are in P, and X is in Q. - * - * Clearly at least one of W and S must be in Q (because otherwise - * X would not be connected to any other square in Q, and would - * hence have to be the whole of Q; and evidently if Q were a - * 1-omino it could not enclose _anything_). So we divide into - * cases: - * - * If both W and S are in Q, then we take X out of Q and put it in - * P, which does not expose any edge of P. If this disconnects Q, - * then we can reconnect it by adding D to Q. - * - * If only one of W and S is in Q, then wlog let it be W. If S is - * in _P_, then we have a particularly easy case: we can simply - * take X out of Q and add it to P, and this cannot disconnect X - * since X was a leaf square of Q. - * - * Our remaining case is that W is in Q and S is in neither P nor - * Q. Again we take X out of Q and put it in P; we also add S to - * Q. This ensures we do not expose an edge of P, but we must now - * prove that S is adjacent to some other existing square of Q so - * that we haven't disconnected Q by adding it. - * - * To do this, we recall that we walked along the edge XE, and - * then turned left to walk along XN. So just before doing all - * that, we must have reached the corner XSE, and we must have - * done it by walking along one of the three edges meeting at that - * corner which are _not_ XE. It can't have been SY, since S would - * then have been on our left and it isn't in Q; and it can't have - * been XS, since S would then have been on our right and it isn't - * in P. So it must have been YE, in which case Y was on our left, - * and hence is in Q. - * - * So in all cases we have shown that we can take X out of Q and - * add it to P, and add at most one square to Q to restore the - * containment and connectedness properties. Hence, we can keep - * doing this until we run out of left turns and P becomes - * rectangular. [] - * - * ------------ - * - * Anyway, that entire proof was a bit of a sidetrack. The point - * is, although constructions of this type are possible for - * sufficiently large k, divvy_rectangle() will never generate - * them. This could be considered a weakness for some purposes, in - * the sense that we can't generate all possible divisions. - * However, there are many divisions which we are highly unlikely - * to generate anyway, so in practice it probably isn't _too_ bad. - * - * If I wanted to fix this issue, I would have to make the rules - * more complicated for determining when a square can safely be - * _removed_ from a polyomino. Adding one becomes easier (a square - * may be added to a polyomino iff it is 4-adjacent to any square - * currently part of the polyomino, and the current test for loop - * formation may be dispensed with), but to determine which - * squares may be removed we must now resort to analysis of the - * overall structure of the polyomino rather than the simple local - * properties we can currently get away with measuring. - */ - -/* - * Possible improvements which might cut the fail rate: - * - * - instead of picking one omino to extend in an iteration, try - * them all in succession (in a randomised order) - * - * - (for real rigour) instead of bfsing over ominoes, bfs over - * the space of possible _removed squares_. That way we aren't - * limited to randomly choosing a single square to remove from - * an omino and failing if that particular square doesn't - * happen to work. - * - * However, I don't currently think it's necessary to do either of - * these, because the failure rate is already low enough to be - * easily tolerable, under all circumstances I've been able to - * think of. - */ - -#include <assert.h> -#include <stdio.h> -#include <stdlib.h> -#include <stddef.h> - -#include "puzzles.h" - -/* - * Subroutine which implements a function used in computing both - * whether a square can safely be added to an omino, and whether - * it can safely be removed. - * - * We enumerate the eight squares 8-adjacent to this one, in - * cyclic order. We go round that loop and count the number of - * times we find a square owned by the target omino next to one - * not owned by it. We then return success iff that count is 2. - * - * When adding a square to an omino, this is precisely the - * criterion which tells us that adding the square won't leave a - * hole in the middle of the omino. (If it did, then things get - * more complicated; see above.) - * - * When removing a square from an omino, the _same_ criterion - * tells us that removing the square won't disconnect the omino. - * (This only works _because_ we've ensured the omino is simply - * connected.) - */ -static int addremcommon(int w, int h, int x, int y, int *own, int val) -{ - int neighbours[8]; - int dir, count; - - for (dir = 0; dir < 8; dir++) { - int dx = ((dir & 3) == 2 ? 0 : dir > 2 && dir < 6 ? +1 : -1); - int dy = ((dir & 3) == 0 ? 0 : dir < 4 ? -1 : +1); - int sx = x+dx, sy = y+dy; - - if (sx < 0 || sx >= w || sy < 0 || sy >= h) - neighbours[dir] = -1; /* outside the grid */ - else - neighbours[dir] = own[sy*w+sx]; - } - - /* - * To begin with, check 4-adjacency. - */ - if (neighbours[0] != val && neighbours[2] != val && - neighbours[4] != val && neighbours[6] != val) - return FALSE; - - count = 0; - - for (dir = 0; dir < 8; dir++) { - int next = (dir + 1) & 7; - int gotthis = (neighbours[dir] == val); - int gotnext = (neighbours[next] == val); - - if (gotthis != gotnext) - count++; - } - - return (count == 2); -} - -/* - * w and h are the dimensions of the rectangle. - * - * k is the size of the required ominoes. (So k must divide w*h, - * of course.) - * - * The returned result is a w*h-sized dsf. - * - * In both of the above suggested use cases, the user would - * probably want w==h==k, but that isn't a requirement. - */ -static int *divvy_internal(int w, int h, int k, random_state *rs) -{ - int *order, *queue, *tmp, *own, *sizes, *addable, *removable, *retdsf; - int wh = w*h; - int i, j, n, x, y, qhead, qtail; - - n = wh / k; - assert(wh == k*n); - - order = snewn(wh, int); - tmp = snewn(wh, int); - own = snewn(wh, int); - sizes = snewn(n, int); - queue = snewn(n, int); - addable = snewn(wh*4, int); - removable = snewn(wh, int); - - /* - * Permute the grid squares into a random order, which will be - * used for iterating over the grid whenever we need to search - * for something. This prevents directional bias and arranges - * for the answer to be non-deterministic. - */ - for (i = 0; i < wh; i++) - order[i] = i; - shuffle(order, wh, sizeof(*order), rs); - - /* - * Begin by choosing a starting square at random for each - * omino. - */ - for (i = 0; i < wh; i++) { - own[i] = -1; - } - for (i = 0; i < n; i++) { - own[order[i]] = i; - sizes[i] = 1; - } - - /* - * Now repeatedly pick a random omino which isn't already at - * the target size, and find a way to expand it by one. This - * may involve stealing a square from another omino, in which - * case we then re-expand that omino, forming a chain of - * square-stealing which terminates in an as yet unclaimed - * square. Hence every successful iteration around this loop - * causes the number of unclaimed squares to drop by one, and - * so the process is bounded in duration. - */ - while (1) { - -#ifdef DIVVY_DIAGNOSTICS - { - int x, y; - printf("Top of loop. Current grid:\n"); - for (y = 0; y < h; y++) { - for (x = 0; x < w; x++) - printf("%3d", own[y*w+x]); - printf("\n"); - } - } -#endif - - /* - * Go over the grid and figure out which squares can - * safely be added to, or removed from, each omino. We - * don't take account of other ominoes in this process, so - * we will often end up knowing that a square can be - * poached from one omino by another. - * - * For each square, there may be up to four ominoes to - * which it can be added (those to which it is - * 4-adjacent). - */ - for (y = 0; y < h; y++) { - for (x = 0; x < w; x++) { - int yx = y*w+x; - int curr = own[yx]; - int dir; - - if (curr < 0) { - removable[yx] = FALSE; /* can't remove if not owned! */ - } else if (sizes[curr] == 1) { - removable[yx] = TRUE; /* can always remove a singleton */ - } else { - /* - * See if this square can be removed from its - * omino without disconnecting it. - */ - removable[yx] = addremcommon(w, h, x, y, own, curr); - } - - for (dir = 0; dir < 4; dir++) { - int dx = (dir == 0 ? -1 : dir == 1 ? +1 : 0); - int dy = (dir == 2 ? -1 : dir == 3 ? +1 : 0); - int sx = x + dx, sy = y + dy; - int syx = sy*w+sx; - - addable[yx*4+dir] = -1; - - if (sx < 0 || sx >= w || sy < 0 || sy >= h) - continue; /* no omino here! */ - if (own[syx] < 0) - continue; /* also no omino here */ - if (own[syx] == own[yx]) - continue; /* we already got one */ - if (!addremcommon(w, h, x, y, own, own[syx])) - continue; /* would non-simply connect the omino */ - - addable[yx*4+dir] = own[syx]; - } - } - } - - for (i = j = 0; i < n; i++) - if (sizes[i] < k) - tmp[j++] = i; - if (j == 0) - break; /* all ominoes are complete! */ - j = tmp[random_upto(rs, j)]; -#ifdef DIVVY_DIAGNOSTICS - printf("Trying to extend %d\n", j); -#endif - - /* - * So we're trying to expand omino j. We breadth-first - * search out from j across the space of ominoes. - * - * For bfs purposes, we use two elements of tmp per omino: - * tmp[2*i+0] tells us which omino we got to i from, and - * tmp[2*i+1] numbers the grid square that omino stole - * from us. - * - * This requires that wh (the size of tmp) is at least 2n, - * i.e. k is at least 2. There would have been nothing to - * stop a user calling this function with k=1, but if they - * did then we wouldn't have got to _here_ in the code - - * we would have noticed above that all ominoes were - * already at their target sizes, and terminated :-) - */ - assert(wh >= 2*n); - for (i = 0; i < n; i++) - tmp[2*i] = tmp[2*i+1] = -1; - qhead = qtail = 0; - queue[qtail++] = j; - tmp[2*j] = tmp[2*j+1] = -2; /* special value: `starting point' */ - - while (qhead < qtail) { - int tmpsq; - - j = queue[qhead]; - - /* - * We wish to expand omino j. However, we might have - * got here by omino j having a square stolen from it, - * so first of all we must temporarily mark that - * square as not belonging to j, so that our adjacency - * calculations don't assume j _does_ belong to us. - */ - tmpsq = tmp[2*j+1]; - if (tmpsq >= 0) { - assert(own[tmpsq] == j); - own[tmpsq] = -3; - } - - /* - * OK. Now begin by seeing if we can find any - * unclaimed square into which we can expand omino j. - * If we find one, the entire bfs terminates. - */ - for (i = 0; i < wh; i++) { - int dir; - - if (own[order[i]] != -1) - continue; /* this square is claimed */ - - /* - * Special case: if our current omino was size 1 - * and then had a square stolen from it, it's now - * size zero, which means it's valid to `expand' - * it into _any_ unclaimed square. - */ - if (sizes[j] == 1 && tmpsq >= 0) - break; /* got one */ - - /* - * Failing that, we must do the full test for - * addability. - */ - for (dir = 0; dir < 4; dir++) - if (addable[order[i]*4+dir] == j) { - /* - * We know this square is addable to this - * omino with the grid in the state it had - * at the top of the loop. However, we - * must now check that it's _still_ - * addable to this omino when the omino is - * missing a square. To do this it's only - * necessary to re-check addremcommon. - */ - if (!addremcommon(w, h, order[i]%w, order[i]/w, - own, j)) - continue; - break; - } - if (dir == 4) - continue; /* we can't add this square to j */ - - break; /* got one! */ - } - if (i < wh) { - i = order[i]; - - /* - * Restore the temporarily removed square _before_ - * we start shifting ownerships about. - */ - if (tmpsq >= 0) - own[tmpsq] = j; - - /* - * We are done. We can add square i to omino j, - * and then backtrack along the trail in tmp - * moving squares between ominoes, ending up - * expanding our starting omino by one. - */ -#ifdef DIVVY_DIAGNOSTICS - printf("(%d,%d)", i%w, i/w); -#endif - while (1) { - own[i] = j; -#ifdef DIVVY_DIAGNOSTICS - printf(" -> %d", j); -#endif - if (tmp[2*j] == -2) - break; - i = tmp[2*j+1]; - j = tmp[2*j]; -#ifdef DIVVY_DIAGNOSTICS - printf("; (%d,%d)", i%w, i/w); -#endif - } -#ifdef DIVVY_DIAGNOSTICS - printf("\n"); -#endif - - /* - * Increment the size of the starting omino. - */ - sizes[j]++; - - /* - * Terminate the bfs loop. - */ - break; - } - - /* - * If we get here, we haven't been able to expand - * omino j into an unclaimed square. So now we begin - * to investigate expanding it into squares which are - * claimed by ominoes the bfs has not yet visited. - */ - for (i = 0; i < wh; i++) { - int dir, nj; - - nj = own[order[i]]; - if (nj < 0 || tmp[2*nj] != -1) - continue; /* unclaimed, or owned by wrong omino */ - if (!removable[order[i]]) - continue; /* its omino won't let it go */ - - for (dir = 0; dir < 4; dir++) - if (addable[order[i]*4+dir] == j) { - /* - * As above, re-check addremcommon. - */ - if (!addremcommon(w, h, order[i]%w, order[i]/w, - own, j)) - continue; - - /* - * We have found a square we can use to - * expand omino j, at the expense of the - * as-yet unvisited omino nj. So add this - * to the bfs queue. - */ - assert(qtail < n); - queue[qtail++] = nj; - tmp[2*nj] = j; - tmp[2*nj+1] = order[i]; - - /* - * Now terminate the loop over dir, to - * ensure we don't accidentally add the - * same omino twice to the queue. - */ - break; - } - } - - /* - * Restore the temporarily removed square. - */ - if (tmpsq >= 0) - own[tmpsq] = j; - - /* - * Advance the queue head. - */ - qhead++; - } - - if (qhead == qtail) { - /* - * We have finished the bfs and not found any way to - * expand omino j. Panic, and return failure. - * - * FIXME: or should we loop over all ominoes before we - * give up? - */ -#ifdef DIVVY_DIAGNOSTICS - printf("FAIL!\n"); -#endif - retdsf = NULL; - goto cleanup; - } - } - -#ifdef DIVVY_DIAGNOSTICS - { - int x, y; - printf("SUCCESS! Final grid:\n"); - for (y = 0; y < h; y++) { - for (x = 0; x < w; x++) - printf("%3d", own[y*w+x]); - printf("\n"); - } - } -#endif - - /* - * Construct the output dsf. - */ - for (i = 0; i < wh; i++) { - assert(own[i] >= 0 && own[i] < n); - tmp[own[i]] = i; - } - retdsf = snew_dsf(wh); - for (i = 0; i < wh; i++) { - dsf_merge(retdsf, i, tmp[own[i]]); - } - - /* - * Construct the output dsf a different way, to verify that - * the ominoes really are k-ominoes and we haven't - * accidentally split one into two disconnected pieces. - */ - dsf_init(tmp, wh); - for (y = 0; y < h; y++) - for (x = 0; x+1 < w; x++) - if (own[y*w+x] == own[y*w+(x+1)]) - dsf_merge(tmp, y*w+x, y*w+(x+1)); - for (x = 0; x < w; x++) - for (y = 0; y+1 < h; y++) - if (own[y*w+x] == own[(y+1)*w+x]) - dsf_merge(tmp, y*w+x, (y+1)*w+x); - for (i = 0; i < wh; i++) { - j = dsf_canonify(retdsf, i); - assert(dsf_canonify(tmp, j) == dsf_canonify(tmp, i)); - } - - cleanup: - - /* - * Free our temporary working space. - */ - sfree(order); - sfree(tmp); - sfree(own); - sfree(sizes); - sfree(queue); - sfree(addable); - sfree(removable); - - /* - * And we're done. - */ - return retdsf; -} - -#ifdef TESTMODE -static int fail_counter = 0; -#endif - -int *divvy_rectangle(int w, int h, int k, random_state *rs) -{ - int *ret; - - do { - ret = divvy_internal(w, h, k, rs); - -#ifdef TESTMODE - if (!ret) - fail_counter++; -#endif - - } while (!ret); - - return ret; -} - -#ifdef TESTMODE - -/* - * gcc -g -O0 -DTESTMODE -I.. -o divvy divvy.c ../random.c ../malloc.c ../dsf.c ../misc.c ../nullfe.c - * - * or to debug - * - * gcc -g -O0 -DDIVVY_DIAGNOSTICS -DTESTMODE -I.. -o divvy divvy.c ../random.c ../malloc.c ../dsf.c ../misc.c ../nullfe.c - */ - -int main(int argc, char **argv) -{ - int *dsf; - int i; - int w = 9, h = 4, k = 6, tries = 100; - random_state *rs; - - rs = random_new("123456", 6); - - if (argc > 1) - w = atoi(argv[1]); - if (argc > 2) - h = atoi(argv[2]); - if (argc > 3) - k = atoi(argv[3]); - if (argc > 4) - tries = atoi(argv[4]); - - for (i = 0; i < tries; i++) { - int x, y; - - dsf = divvy_rectangle(w, h, k, rs); - assert(dsf); - - for (y = 0; y <= 2*h; y++) { - for (x = 0; x <= 2*w; x++) { - int miny = y/2 - 1, maxy = y/2; - int minx = x/2 - 1, maxx = x/2; - int classes[4], tx, ty; - for (ty = 0; ty < 2; ty++) - for (tx = 0; tx < 2; tx++) { - int cx = minx+tx, cy = miny+ty; - if (cx < 0 || cx >= w || cy < 0 || cy >= h) - classes[ty*2+tx] = -1; - else - classes[ty*2+tx] = dsf_canonify(dsf, cy*w+cx); - } - switch (y%2 * 2 + x%2) { - case 0: /* corner */ - /* - * Cases for the corner: - * - * - if all four surrounding squares belong - * to the same omino, we print a space. - * - * - if the top two are the same and the - * bottom two are the same, we print a - * horizontal line. - * - * - if the left two are the same and the - * right two are the same, we print a - * vertical line. - * - * - otherwise, we print a cross. - */ - if (classes[0] == classes[1] && - classes[1] == classes[2] && - classes[2] == classes[3]) - printf(" "); - else if (classes[0] == classes[1] && - classes[2] == classes[3]) - printf("-"); - else if (classes[0] == classes[2] && - classes[1] == classes[3]) - printf("|"); - else - printf("+"); - break; - case 1: /* horiz edge */ - if (classes[1] == classes[3]) - printf(" "); - else - printf("--"); - break; - case 2: /* vert edge */ - if (classes[2] == classes[3]) - printf(" "); - else - printf("|"); - break; - case 3: /* square centre */ - printf(" "); - break; - } - } - printf("\n"); - } - printf("\n"); - sfree(dsf); - } - - printf("%d retries needed for %d successes\n", fail_counter, tries); - - return 0; -} - -#endif |