Allow a 1-omino to be completely destroyed and recreated in an
[sgt/puzzles] / unfinished / divvy.c
CommitLineData
11d273f7 1/*
2 * Library code to divide up a rectangle into a number of equally
3 * sized ominoes, in a random fashion.
4 *
5 * Could use this for generating solved grids of
6 * http://www.nikoli.co.jp/ja/puzzles/block_puzzle/
7 * or for generating the playfield for Jigsaw Sudoku.
8 */
9
9d36cbd7 10/*
11 * Possible improvements which might cut the fail rate:
12 *
13 * - instead of picking one omino to extend in an iteration, try
14 * them all in succession (in a randomised order)
15 *
16 * - (for real rigour) instead of bfsing over ominoes, bfs over
17 * the space of possible _removed squares_. That way we aren't
18 * limited to randomly choosing a single square to remove from
19 * an omino and failing if that particular square doesn't
20 * happen to work.
21 *
22 * However, I don't currently think it's neecss~|~
23 */
24
11d273f7 25#include <assert.h>
26#include <stdio.h>
27#include <stdlib.h>
28#include <stddef.h>
29
30#include "puzzles.h"
31
32/*
33 * Subroutine which implements a function used in computing both
34 * whether a square can safely be added to an omino, and whether
35 * it can safely be removed.
36 *
37 * We enumerate the eight squares 8-adjacent to this one, in
38 * cyclic order. We go round that loop and count the number of
39 * times we find a square owned by the target omino next to one
40 * not owned by it. We then return success iff that count is 2.
41 *
42 * When adding a square to an omino, this is precisely the
43 * criterion which tells us that adding the square won't leave a
44 * hole in the middle of the omino. (There's no explicit
45 * requirement in the statement of our problem that the ominoes be
46 * simply connected, but we do know they must be all of equal size
47 * and so it's clear that we must avoid leaving holes, since a
48 * hole would necessarily be smaller than the maximum omino size.)
49 *
50 * When removing a square from an omino, the _same_ criterion
51 * tells us that removing the square won't disconnect the omino.
52 */
53static int addremcommon(int w, int h, int x, int y, int *own, int val)
54{
55 int neighbours[8];
56 int dir, count;
57
58 for (dir = 0; dir < 8; dir++) {
59 int dx = ((dir & 3) == 2 ? 0 : dir > 2 && dir < 6 ? +1 : -1);
60 int dy = ((dir & 3) == 0 ? 0 : dir < 4 ? -1 : +1);
61 int sx = x+dx, sy = y+dy;
62
63 if (sx < 0 || sx >= w || sy < 0 || sy >= h)
64 neighbours[dir] = -1; /* outside the grid */
65 else
66 neighbours[dir] = own[sy*w+sx];
67 }
68
69 /*
70 * To begin with, check 4-adjacency.
71 */
72 if (neighbours[0] != val && neighbours[2] != val &&
73 neighbours[4] != val && neighbours[6] != val)
74 return FALSE;
75
76 count = 0;
77
78 for (dir = 0; dir < 8; dir++) {
79 int next = (dir + 1) & 7;
80 int gotthis = (neighbours[dir] == val);
81 int gotnext = (neighbours[next] == val);
82
83 if (gotthis != gotnext)
84 count++;
85 }
86
87 return (count == 2);
88}
89
90/*
91 * w and h are the dimensions of the rectangle.
92 *
93 * k is the size of the required ominoes. (So k must divide w*h,
94 * of course.)
95 *
96 * The returned result is a w*h-sized dsf.
97 *
98 * In both of the above suggested use cases, the user would
99 * probably want w==h==k, but that isn't a requirement.
100 */
9d36cbd7 101static int *divvy_internal(int w, int h, int k, random_state *rs)
11d273f7 102{
103 int *order, *queue, *tmp, *own, *sizes, *addable, *removable, *retdsf;
104 int wh = w*h;
105 int i, j, n, x, y, qhead, qtail;
106
107 n = wh / k;
108 assert(wh == k*n);
109
110 order = snewn(wh, int);
111 tmp = snewn(wh, int);
112 own = snewn(wh, int);
113 sizes = snewn(n, int);
114 queue = snewn(n, int);
115 addable = snewn(wh*4, int);
116 removable = snewn(wh, int);
117
118 /*
119 * Permute the grid squares into a random order, which will be
120 * used for iterating over the grid whenever we need to search
121 * for something. This prevents directional bias and arranges
122 * for the answer to be non-deterministic.
123 */
124 for (i = 0; i < wh; i++)
125 order[i] = i;
126 shuffle(order, wh, sizeof(*order), rs);
127
128 /*
129 * Begin by choosing a starting square at random for each
130 * omino.
131 */
132 for (i = 0; i < wh; i++) {
133 own[i] = -1;
134 }
135 for (i = 0; i < n; i++) {
136 own[order[i]] = i;
137 sizes[i] = 1;
138 }
139
140 /*
141 * Now repeatedly pick a random omino which isn't already at
142 * the target size, and find a way to expand it by one. This
143 * may involve stealing a square from another omino, in which
144 * case we then re-expand that omino, forming a chain of
145 * square-stealing which terminates in an as yet unclaimed
146 * square. Hence every successful iteration around this loop
147 * causes the number of unclaimed squares to drop by one, and
148 * so the process is bounded in duration.
149 */
150 while (1) {
151
152#ifdef DIVVY_DIAGNOSTICS
153 {
154 int x, y;
155 printf("Top of loop. Current grid:\n");
156 for (y = 0; y < h; y++) {
157 for (x = 0; x < w; x++)
158 printf("%3d", own[y*w+x]);
159 printf("\n");
160 }
161 }
162#endif
163
164 /*
165 * Go over the grid and figure out which squares can
166 * safely be added to, or removed from, each omino. We
167 * don't take account of other ominoes in this process, so
168 * we will often end up knowing that a square can be
169 * poached from one omino by another.
170 *
171 * For each square, there may be up to four ominoes to
172 * which it can be added (those to which it is
173 * 4-adjacent).
174 */
175 for (y = 0; y < h; y++) {
176 for (x = 0; x < w; x++) {
177 int yx = y*w+x;
178 int curr = own[yx];
179 int dir;
180
181 if (curr < 0) {
9d36cbd7 182 removable[yx] = FALSE; /* can't remove if not owned! */
183 } else if (sizes[curr] == 1) {
184 removable[yx] = TRUE; /* can always remove a singleton */
11d273f7 185 } else {
186 /*
187 * See if this square can be removed from its
188 * omino without disconnecting it.
189 */
190 removable[yx] = addremcommon(w, h, x, y, own, curr);
191 }
192
193 for (dir = 0; dir < 4; dir++) {
194 int dx = (dir == 0 ? -1 : dir == 1 ? +1 : 0);
195 int dy = (dir == 2 ? -1 : dir == 3 ? +1 : 0);
196 int sx = x + dx, sy = y + dy;
197 int syx = sy*w+sx;
198
199 addable[yx*4+dir] = -1;
200
201 if (sx < 0 || sx >= w || sy < 0 || sy >= h)
202 continue; /* no omino here! */
203 if (own[syx] < 0)
204 continue; /* also no omino here */
205 if (own[syx] == own[yx])
206 continue; /* we already got one */
207 if (!addremcommon(w, h, x, y, own, own[syx]))
208 continue; /* would non-simply connect the omino */
209
210 addable[yx*4+dir] = own[syx];
211 }
212 }
213 }
214
215 for (i = j = 0; i < n; i++)
216 if (sizes[i] < k)
217 tmp[j++] = i;
218 if (j == 0)
219 break; /* all ominoes are complete! */
220 j = tmp[random_upto(rs, j)];
f40d37bd 221#ifdef DIVVY_DIAGNOSTICS
222 printf("Trying to extend %d\n", j);
223#endif
11d273f7 224
225 /*
226 * So we're trying to expand omino j. We breadth-first
227 * search out from j across the space of ominoes.
228 *
229 * For bfs purposes, we use two elements of tmp per omino:
230 * tmp[2*i+0] tells us which omino we got to i from, and
231 * tmp[2*i+1] numbers the grid square that omino stole
232 * from us.
233 *
234 * This requires that wh (the size of tmp) is at least 2n,
235 * i.e. k is at least 2. There would have been nothing to
236 * stop a user calling this function with k=1, but if they
237 * did then we wouldn't have got to _here_ in the code -
238 * we would have noticed above that all ominoes were
239 * already at their target sizes, and terminated :-)
240 */
241 assert(wh >= 2*n);
242 for (i = 0; i < n; i++)
243 tmp[2*i] = tmp[2*i+1] = -1;
244 qhead = qtail = 0;
245 queue[qtail++] = j;
246 tmp[2*j] = tmp[2*j+1] = -2; /* special value: `starting point' */
247
248 while (qhead < qtail) {
249 int tmpsq;
250
251 j = queue[qhead];
252
253 /*
254 * We wish to expand omino j. However, we might have
255 * got here by omino j having a square stolen from it,
256 * so first of all we must temporarily mark that
257 * square as not belonging to j, so that our adjacency
258 * calculations don't assume j _does_ belong to us.
259 */
260 tmpsq = tmp[2*j+1];
261 if (tmpsq >= 0) {
262 assert(own[tmpsq] == j);
9d36cbd7 263 own[tmpsq] = -3;
11d273f7 264 }
265
266 /*
267 * OK. Now begin by seeing if we can find any
268 * unclaimed square into which we can expand omino j.
269 * If we find one, the entire bfs terminates.
270 */
271 for (i = 0; i < wh; i++) {
272 int dir;
273
9d36cbd7 274 if (own[order[i]] != -1)
11d273f7 275 continue; /* this square is claimed */
9d36cbd7 276
277 /*
278 * Special case: if our current omino was size 1
279 * and then had a square stolen from it, it's now
280 * size zero, which means it's valid to `expand'
281 * it into _any_ unclaimed square.
282 */
283 if (sizes[j] == 1 && tmpsq >= 0)
284 break; /* got one */
285
286 /*
287 * Failing that, we must do the full test for
288 * addability.
289 */
11d273f7 290 for (dir = 0; dir < 4; dir++)
291 if (addable[order[i]*4+dir] == j) {
292 /*
293 * We know this square is addable to this
294 * omino with the grid in the state it had
295 * at the top of the loop. However, we
296 * must now check that it's _still_
297 * addable to this omino when the omino is
298 * missing a square. To do this it's only
299 * necessary to re-check addremcommon.
f40d37bd 300 */
11d273f7 301 if (!addremcommon(w, h, order[i]%w, order[i]/w,
302 own, j))
303 continue;
304 break;
305 }
306 if (dir == 4)
307 continue; /* we can't add this square to j */
9d36cbd7 308
11d273f7 309 break; /* got one! */
310 }
311 if (i < wh) {
312 i = order[i];
313
314 /*
9d36cbd7 315 * Restore the temporarily removed square _before_
316 * we start shifting ownerships about.
317 */
318 if (tmpsq >= 0)
319 own[tmpsq] = j;
320
321 /*
11d273f7 322 * We are done. We can add square i to omino j,
323 * and then backtrack along the trail in tmp
324 * moving squares between ominoes, ending up
325 * expanding our starting omino by one.
326 */
f40d37bd 327#ifdef DIVVY_DIAGNOSTICS
328 printf("(%d,%d)", i%w, i/w);
329#endif
11d273f7 330 while (1) {
331 own[i] = j;
f40d37bd 332#ifdef DIVVY_DIAGNOSTICS
333 printf(" -> %d", j);
334#endif
11d273f7 335 if (tmp[2*j] == -2)
336 break;
337 i = tmp[2*j+1];
338 j = tmp[2*j];
f40d37bd 339#ifdef DIVVY_DIAGNOSTICS
340 printf("; (%d,%d)", i%w, i/w);
341#endif
11d273f7 342 }
f40d37bd 343#ifdef DIVVY_DIAGNOSTICS
344 printf("\n");
345#endif
11d273f7 346
347 /*
348 * Increment the size of the starting omino.
349 */
350 sizes[j]++;
351
352 /*
353 * Terminate the bfs loop.
354 */
355 break;
356 }
357
358 /*
359 * If we get here, we haven't been able to expand
360 * omino j into an unclaimed square. So now we begin
361 * to investigate expanding it into squares which are
362 * claimed by ominoes the bfs has not yet visited.
363 */
364 for (i = 0; i < wh; i++) {
365 int dir, nj;
366
367 nj = own[order[i]];
368 if (nj < 0 || tmp[2*nj] != -1)
369 continue; /* unclaimed, or owned by wrong omino */
370 if (!removable[order[i]])
371 continue; /* its omino won't let it go */
372
373 for (dir = 0; dir < 4; dir++)
374 if (addable[order[i]*4+dir] == j) {
375 /*
376 * As above, re-check addremcommon.
377 */
378 if (!addremcommon(w, h, order[i]%w, order[i]/w,
379 own, j))
380 continue;
381
382 /*
383 * We have found a square we can use to
384 * expand omino j, at the expense of the
385 * as-yet unvisited omino nj. So add this
386 * to the bfs queue.
387 */
388 assert(qtail < n);
389 queue[qtail++] = nj;
390 tmp[2*nj] = j;
391 tmp[2*nj+1] = order[i];
392
393 /*
394 * Now terminate the loop over dir, to
395 * ensure we don't accidentally add the
396 * same omino twice to the queue.
397 */
398 break;
399 }
400 }
401
402 /*
403 * Restore the temporarily removed square.
404 */
405 if (tmpsq >= 0)
406 own[tmpsq] = j;
407
408 /*
409 * Advance the queue head.
410 */
411 qhead++;
412 }
413
414 if (qhead == qtail) {
415 /*
416 * We have finished the bfs and not found any way to
417 * expand omino j. Panic, and return failure.
418 *
419 * FIXME: or should we loop over all ominoes before we
420 * give up?
421 */
f40d37bd 422#ifdef DIVVY_DIAGNOSTICS
423 printf("FAIL!\n");
424#endif
11d273f7 425 retdsf = NULL;
426 goto cleanup;
427 }
428 }
429
f40d37bd 430#ifdef DIVVY_DIAGNOSTICS
431 {
432 int x, y;
433 printf("SUCCESS! Final grid:\n");
434 for (y = 0; y < h; y++) {
435 for (x = 0; x < w; x++)
436 printf("%3d", own[y*w+x]);
437 printf("\n");
438 }
439 }
440#endif
441
11d273f7 442 /*
443 * Construct the output dsf.
444 */
445 for (i = 0; i < wh; i++) {
446 assert(own[i] >= 0 && own[i] < n);
447 tmp[own[i]] = i;
448 }
449 retdsf = snew_dsf(wh);
450 for (i = 0; i < wh; i++) {
451 dsf_merge(retdsf, i, tmp[own[i]]);
452 }
453
454 /*
455 * Construct the output dsf a different way, to verify that
456 * the ominoes really are k-ominoes and we haven't
457 * accidentally split one into two disconnected pieces.
458 */
459 dsf_init(tmp, wh);
460 for (y = 0; y < h; y++)
461 for (x = 0; x+1 < w; x++)
462 if (own[y*w+x] == own[y*w+(x+1)])
463 dsf_merge(tmp, y*w+x, y*w+(x+1));
464 for (x = 0; x < w; x++)
465 for (y = 0; y+1 < h; y++)
466 if (own[y*w+x] == own[(y+1)*w+x])
467 dsf_merge(tmp, y*w+x, (y+1)*w+x);
468 for (i = 0; i < wh; i++) {
469 j = dsf_canonify(retdsf, i);
470 assert(dsf_canonify(tmp, j) == dsf_canonify(tmp, i));
471 }
472
473 cleanup:
474
475 /*
476 * Free our temporary working space.
477 */
478 sfree(order);
479 sfree(tmp);
480 sfree(own);
481 sfree(sizes);
482 sfree(queue);
483 sfree(addable);
484 sfree(removable);
485
486 /*
487 * And we're done.
488 */
489 return retdsf;
490}
491
492#ifdef TESTMODE
9d36cbd7 493static int fail_counter = 0;
494#endif
495
496int *divvy_rectangle(int w, int h, int k, random_state *rs)
497{
498 int *ret;
499
500 do {
501 ret = divvy_internal(w, h, k, rs);
502
503#ifdef TESTMODE
504 if (!ret)
505 fail_counter++;
506#endif
507
508 } while (!ret);
509
510 return ret;
511}
512
513#ifdef TESTMODE
11d273f7 514
515/*
516 * gcc -g -O0 -DTESTMODE -I.. -o divvy divvy.c ../random.c ../malloc.c ../dsf.c ../misc.c ../nullfe.c
517 *
518 * or to debug
519 *
520 * gcc -g -O0 -DDIVVY_DIAGNOSTICS -DTESTMODE -I.. -o divvy divvy.c ../random.c ../malloc.c ../dsf.c ../misc.c ../nullfe.c
521 */
522
523int main(int argc, char **argv)
524{
525 int *dsf;
9d36cbd7 526 int i;
11d273f7 527 int w = 9, h = 4, k = 6, tries = 100;
528 random_state *rs;
529
530 rs = random_new("123456", 6);
531
532 if (argc > 1)
533 w = atoi(argv[1]);
534 if (argc > 2)
535 h = atoi(argv[2]);
536 if (argc > 3)
537 k = atoi(argv[3]);
538 if (argc > 4)
539 tries = atoi(argv[4]);
540
11d273f7 541 for (i = 0; i < tries; i++) {
9d36cbd7 542 int x, y;
11d273f7 543
9d36cbd7 544 dsf = divvy_rectangle(w, h, k, rs);
545 assert(dsf);
546
547 for (y = 0; y <= 2*h; y++) {
548 for (x = 0; x <= 2*w; x++) {
549 int miny = y/2 - 1, maxy = y/2;
550 int minx = x/2 - 1, maxx = x/2;
551 int classes[4], tx, ty;
552 for (ty = 0; ty < 2; ty++)
553 for (tx = 0; tx < 2; tx++) {
554 int cx = minx+tx, cy = miny+ty;
555 if (cx < 0 || cx >= w || cy < 0 || cy >= h)
556 classes[ty*2+tx] = -1;
11d273f7 557 else
9d36cbd7 558 classes[ty*2+tx] = dsf_canonify(dsf, cy*w+cx);
11d273f7 559 }
9d36cbd7 560 switch (y%2 * 2 + x%2) {
561 case 0: /* corner */
562 /*
563 * Cases for the corner:
564 *
565 * - if all four surrounding squares belong
566 * to the same omino, we print a space.
567 *
568 * - if the top two are the same and the
569 * bottom two are the same, we print a
570 * horizontal line.
571 *
572 * - if the left two are the same and the
573 * right two are the same, we print a
574 * vertical line.
575 *
576 * - otherwise, we print a cross.
577 */
578 if (classes[0] == classes[1] &&
579 classes[1] == classes[2] &&
580 classes[2] == classes[3])
581 printf(" ");
582 else if (classes[0] == classes[1] &&
583 classes[2] == classes[3])
584 printf("-");
585 else if (classes[0] == classes[2] &&
586 classes[1] == classes[3])
587 printf("|");
588 else
589 printf("+");
590 break;
591 case 1: /* horiz edge */
592 if (classes[1] == classes[3])
593 printf(" ");
594 else
595 printf("--");
596 break;
597 case 2: /* vert edge */
598 if (classes[2] == classes[3])
599 printf(" ");
600 else
601 printf("|");
602 break;
603 case 3: /* square centre */
604 printf(" ");
605 break;
11d273f7 606 }
11d273f7 607 }
608 printf("\n");
11d273f7 609 }
9d36cbd7 610 printf("\n");
611 sfree(dsf);
11d273f7 612 }
613
9d36cbd7 614 printf("%d retries needed for %d successes\n", fail_counter, tries);
11d273f7 615
616 return 0;
617}
618
619#endif