/* * Not-guaranteed-soluble grid generator; kept as a legacy, and in * case someone finds the slightly odd quality of the guaranteed- * soluble grids to be aesthetically displeasing or finds its CPU * utilisation to be excessive. */ static void gen_grid_random(int w, int h, int nc, int *grid, random_state *rs) { int i, j, c; int n = w * h; for (i = 0; i < n; i++) grid[i] = 0; /* * Our sole concession to not gratuitously generating insoluble * grids is to ensure we have at least two of every colour. */ for (c = 1; c <= nc; c++) { for (j = 0; j < 2; j++) { do { i = (int)random_upto(rs, n); } while (grid[i] != 0); grid[i] = c; } } /* * Fill in the rest of the grid at random. */ for (i = 0; i < n; i++) { if (grid[i] == 0) grid[i] = (int)random_upto(rs, nc)+1; } }
static char *new_game_desc(game_params *params, random_state *rs, char **aux, int interactive) { int nballs = params->minballs, i; char *grid, *ret; unsigned char *bmp; if (params->maxballs > params->minballs) nballs += random_upto(rs, params->maxballs - params->minballs + 1); grid = snewn(params->w*params->h, char); memset(grid, 0, params->w * params->h * sizeof(char)); bmp = snewn(nballs*2 + 2, unsigned char); memset(bmp, 0, (nballs*2 + 2) * sizeof(unsigned char)); bmp[0] = params->w; bmp[1] = params->h; for (i = 0; i < nballs; i++) { int x, y; do { x = random_upto(rs, params->w); y = random_upto(rs, params->h); } while (grid[y*params->w + x]); grid[y*params->w + x] = 1; bmp[(i+1)*2 + 0] = x; bmp[(i+1)*2 + 1] = y; #ifdef ANDROID if (android_cancelled()) { sfree(grid); sfree(bmp); return NULL; } #endif } sfree(grid); obfuscate_bitmap(bmp, (nballs*2 + 2) * 8, FALSE); ret = bin2hex(bmp, nballs*2 + 2); sfree(bmp); return ret; }
static char *new_game_desc(const game_params *params, random_state *rs, char **aux, int interactive) { struct grid_data data; int i, j, k, m, area, facesperclass; int *flags; char *desc, *p; /* * Enumerate the grid squares, dividing them into equivalence * classes as appropriate. (For the tetrahedron, there is one * equivalence class for each face; for the octahedron there * are two classes; for the other two solids there's only one.) */ area = grid_area(params->d1, params->d2, solids[params->solid]->order); if (params->solid == TETRAHEDRON) data.nclasses = 4; else if (params->solid == OCTAHEDRON) data.nclasses = 2; else data.nclasses = 1; data.gridptrs[0] = snewn(data.nclasses * area, int); for (i = 0; i < data.nclasses; i++) { data.gridptrs[i] = data.gridptrs[0] + i * area; data.nsquares[i] = 0; } data.squareindex = 0; enum_grid_squares(params, classify_grid_square_callback, &data); facesperclass = solids[params->solid]->nfaces / data.nclasses; for (i = 0; i < data.nclasses; i++) assert(data.nsquares[i] >= facesperclass); assert(data.squareindex == area); /* * So now we know how many faces to allocate in each class. Get * on with it. */ flags = snewn(area, int); for (i = 0; i < area; i++) flags[i] = FALSE; for (i = 0; i < data.nclasses; i++) { for (j = 0; j < facesperclass; j++) { int n = random_upto(rs, data.nsquares[i]); assert(!flags[data.gridptrs[i][n]]); flags[data.gridptrs[i][n]] = TRUE; /* * Move everything else up the array. I ought to use a * better data structure for this, but for such small * numbers it hardly seems worth the effort. */ while (n < data.nsquares[i]-1) { data.gridptrs[i][n] = data.gridptrs[i][n+1]; n++; } data.nsquares[i]--; } } /* * Now we know precisely which squares are blue. Encode this * information in hex. While we're looping over this, collect * the non-blue squares into a list in the now-unused gridptrs * array. */ desc = snewn(area / 4 + 40, char); p = desc; j = 0; k = 8; m = 0; for (i = 0; i < area; i++) { if (flags[i]) { j |= k; } else { data.gridptrs[0][m++] = i; } k >>= 1; if (!k) { *p++ = "0123456789ABCDEF"[j]; k = 8; j = 0; } } if (k != 8) *p++ = "0123456789ABCDEF"[j]; /* * Choose a non-blue square for the polyhedron. */ sprintf(p, ",%d", data.gridptrs[0][random_upto(rs, m)]); sfree(data.gridptrs[0]); sfree(flags); return desc; }
/* * Guaranteed-soluble grid generator. */ static void gen_grid(int w, int h, int nc, int *grid, random_state *rs) { int wh = w*h, tc = nc+1; int i, j, k, c, x, y, pos, n; int *list, *grid2; int ok, failures = 0; /* * We'll use `list' to track the possible places to put our * next insertion. There are up to h places to insert in each * column: in a column of height n there are n+1 places because * we can insert at the very bottom or the very top, but a * column of height h can't have anything at all inserted in it * so we have up to h in each column. Likewise, with n columns * present there are n+1 places to fit a new one in between but * we can't insert a column if there are already w; so there * are a maximum of w new columns too. Total is wh + w. */ list = snewn(wh + w, int); grid2 = snewn(wh, int); do { /* * Start with two or three squares - depending on parity of w*h * - of a random colour. */ for (i = 0; i < wh; i++) grid[i] = 0; j = 2 + (wh % 2); c = 1 + random_upto(rs, nc); if (j <= w) { for (i = 0; i < j; i++) grid[(h-1)*w+i] = c; } else { assert(j <= h); for (i = 0; i < j; i++) grid[(h-1-i)*w] = c; } /* * Now repeatedly insert a two-square blob in the grid, of * whatever colour will go at the position we chose. */ while (1) { n = 0; /* * Build up a list of insertion points. Each point is * encoded as y*w+x; insertion points between columns are * encoded as h*w+x. */ if (grid[wh - 1] == 0) { /* * The final column is empty, so we can insert new * columns. */ for (i = 0; i < w; i++) { list[n++] = wh + i; if (grid[(h-1)*w + i] == 0) break; } } /* * Now look for places to insert within columns. */ for (i = 0; i < w; i++) { if (grid[(h-1)*w+i] == 0) break; /* no more columns */ if (grid[i] != 0) continue; /* this column is full */ for (j = h; j-- > 0 ;) { list[n++] = j*w+i; if (grid[j*w+i] == 0) break; /* this column is exhausted */ } } if (n == 0) break; /* we're done */ #ifdef GENERATION_DIAGNOSTICS printf("initial grid:\n"); { int x,y; for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { if (grid[y*w+x] == 0) printf("-"); else printf("%d", grid[y*w+x]); } printf("\n"); } } #endif /* * Now go through the list one element at a time in * random order, and actually attempt to insert * something there. */ while (n-- > 0) { int dirs[4], ndirs, dir; i = random_upto(rs, n+1); pos = list[i]; list[i] = list[n]; x = pos % w; y = pos / w; memcpy(grid2, grid, wh * sizeof(int)); if (y == h) { /* * Insert a column at position x. */ for (i = w-1; i > x; i--) for (j = 0; j < h; j++) grid2[j*w+i] = grid2[j*w+(i-1)]; /* * Clear the new column. */ for (j = 0; j < h; j++) grid2[j*w+x] = 0; /* * Decrement y so that our first square is actually * inserted _in_ the grid rather than just below it. */ y--; } /* * Insert a square within column x at position y. */ for (i = 0; i+1 <= y; i++) grid2[i*w+x] = grid2[(i+1)*w+x]; #ifdef GENERATION_DIAGNOSTICS printf("trying at n=%d (%d,%d)\n", n, x, y); grid2[y*w+x] = tc; { int x,y; for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { if (grid2[y*w+x] == 0) printf("-"); else if (grid2[y*w+x] <= nc) printf("%d", grid2[y*w+x]); else printf("*"); } printf("\n"); } } #endif /* * Pick our square colour so that it doesn't match any * of its neighbours. */ { int wrongcol[4], nwrong = 0; /* * List the neighbouring colours. */ if (x > 0) wrongcol[nwrong++] = grid2[y*w+(x-1)]; if (x+1 < w) wrongcol[nwrong++] = grid2[y*w+(x+1)]; if (y > 0) wrongcol[nwrong++] = grid2[(y-1)*w+x]; if (y+1 < h) wrongcol[nwrong++] = grid2[(y+1)*w+x]; /* * Eliminate duplicates. We can afford a shoddy * algorithm here because the problem size is * bounded. */ for (i = j = 0 ;; i++) { int pos = -1, min = 0; if (j > 0) min = wrongcol[j-1]; for (k = i; k < nwrong; k++) if (wrongcol[k] > min && (pos == -1 || wrongcol[k] < wrongcol[pos])) pos = k; if (pos >= 0) { int v = wrongcol[pos]; wrongcol[pos] = wrongcol[j]; wrongcol[j++] = v; } else break; } nwrong = j; /* * If no colour will go here, stop trying. */ if (nwrong == nc) continue; /* * Otherwise, pick a colour from the remaining * ones. */ c = 1 + random_upto(rs, nc - nwrong); for (i = 0; i < nwrong; i++) { if (c >= wrongcol[i]) c++; else break; } } /* * Place the new square. * * Although I've _chosen_ the new region's colour * (so that we can check adjacency), I'm going to * actually place it as an invalid colour (tc) * until I'm sure it's viable. This is so that I * can conveniently check that I really have made a * _valid_ inverse move later on. */ #ifdef GENERATION_DIAGNOSTICS printf("picked colour %d\n", c); #endif grid2[y*w+x] = tc; /* * Now attempt to extend it in one of three ways: left, * right or up. */ ndirs = 0; if (x > 0 && grid2[y*w+(x-1)] != c && grid2[x-1] == 0 && (y+1 >= h || grid2[(y+1)*w+(x-1)] != c) && (y+1 >= h || grid2[(y+1)*w+(x-1)] != 0) && (x <= 1 || grid2[y*w+(x-2)] != c)) dirs[ndirs++] = -1; /* left */ if (x+1 < w && grid2[y*w+(x+1)] != c && grid2[x+1] == 0 && (y+1 >= h || grid2[(y+1)*w+(x+1)] != c) && (y+1 >= h || grid2[(y+1)*w+(x+1)] != 0) && (x+2 >= w || grid2[y*w+(x+2)] != c)) dirs[ndirs++] = +1; /* right */ if (y > 0 && grid2[x] == 0 && (x <= 0 || grid2[(y-1)*w+(x-1)] != c) && (x+1 >= w || grid2[(y-1)*w+(x+1)] != c)) { /* * We add this possibility _twice_, so that the * probability of placing a vertical domino is * about the same as that of a horizontal. This * should yield less bias in the generated * grids. */ dirs[ndirs++] = 0; /* up */ dirs[ndirs++] = 0; /* up */ } if (ndirs == 0) continue; dir = dirs[random_upto(rs, ndirs)]; #ifdef GENERATION_DIAGNOSTICS printf("picked dir %d\n", dir); #endif /* * Insert a square within column (x+dir) at position y. */ for (i = 0; i+1 <= y; i++) grid2[i*w+x+dir] = grid2[(i+1)*w+x+dir]; grid2[y*w+x+dir] = tc; /* * See if we've divided the remaining grid squares * into sub-areas. If so, we need every sub-area to * have an even area or we won't be able to * complete generation. * * If the height is odd and not all columns are * present, we can increase the area of a subarea * by adding a new column in it, so in that * situation we don't mind having as many odd * subareas as there are spare columns. * * If the height is even, we can't fix it at all. */ { int nerrs = 0, nfix = 0; k = 0; /* current subarea size */ for (i = 0; i < w; i++) { if (grid2[(h-1)*w+i] == 0) { if (h % 2) nfix++; continue; } for (j = 0; j < h && grid2[j*w+i] == 0; j++); assert(j < h); if (j == 0) { /* * End of previous subarea. */ if (k % 2) nerrs++; k = 0; } else { k += j; } } if (k % 2) nerrs++; if (nerrs > nfix) continue; /* try a different placement */ } /* * We've made a move. Verify that it is a valid * move and that if made it would indeed yield the * previous grid state. The criteria are: * * (a) removing all the squares of colour tc (and * shuffling the columns up etc) from grid2 * would yield grid * (b) no square of colour tc is adjacent to one * of colour c * (c) all the squares of colour tc form a single * connected component * * We verify the latter property at the same time * as checking that removing all the tc squares * would yield the previous grid. Then we colour * the tc squares in colour c by breadth-first * search, which conveniently permits us to test * that they're all connected. */ { int x1, x2, y1, y2; int ok = TRUE; int fillstart = -1, ntc = 0; #ifdef GENERATION_DIAGNOSTICS { int x,y; printf("testing move (new, old):\n"); for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { if (grid2[y*w+x] == 0) printf("-"); else if (grid2[y*w+x] <= nc) printf("%d", grid2[y*w+x]); else printf("*"); } printf(" "); for (x = 0; x < w; x++) { if (grid[y*w+x] == 0) printf("-"); else printf("%d", grid[y*w+x]); } printf("\n"); } } #endif for (x1 = x2 = 0; x2 < w; x2++) { int usedcol = FALSE; for (y1 = y2 = h-1; y2 >= 0; y2--) { if (grid2[y2*w+x2] == tc) { ntc++; if (fillstart == -1) fillstart = y2*w+x2; if ((y2+1 < h && grid2[(y2+1)*w+x2] == c) || (y2-1 >= 0 && grid2[(y2-1)*w+x2] == c) || (x2+1 < w && grid2[y2*w+x2+1] == c) || (x2-1 >= 0 && grid2[y2*w+x2-1] == c)) { #ifdef GENERATION_DIAGNOSTICS printf("adjacency failure at %d,%d\n", x2, y2); #endif ok = FALSE; } continue; } if (grid2[y2*w+x2] == 0) break; usedcol = TRUE; if (grid2[y2*w+x2] != grid[y1*w+x1]) { #ifdef GENERATION_DIAGNOSTICS printf("matching failure at %d,%d vs %d,%d\n", x2, y2, x1, y1); #endif ok = FALSE; } y1--; } /* * If we've reached the top of the column * in grid2, verify that we've also reached * the top of the column in `grid'. */ if (usedcol) { while (y1 >= 0) { if (grid[y1*w+x1] != 0) { #ifdef GENERATION_DIAGNOSTICS printf("junk at column top (%d,%d)\n", x1, y1); #endif ok = FALSE; } y1--; } } if (!ok) break; if (usedcol) x1++; } if (!ok) { assert(!"This should never happen"); /* * If this game is compiled NDEBUG so that * the assertion doesn't bring it to a * crashing halt, the only thing we can do * is to give up, loop round again, and * hope to randomly avoid making whatever * type of move just caused this failure. */ continue; } /* * Now use bfs to fill in the tc section as * colour c. We use `list' to store the set of * squares we have to process. */ i = j = 0; assert(fillstart >= 0); list[i++] = fillstart; #ifdef OUTPUT_SOLUTION printf("M"); #endif while (j < i) { k = list[j]; x = k % w; y = k / w; #ifdef OUTPUT_SOLUTION printf("%s%d", j ? "," : "", k); #endif j++; assert(grid2[k] == tc); grid2[k] = c; if (x > 0 && grid2[k-1] == tc) list[i++] = k-1; if (x+1 < w && grid2[k+1] == tc) list[i++] = k+1; if (y > 0 && grid2[k-w] == tc) list[i++] = k-w; if (y+1 < h && grid2[k+w] == tc) list[i++] = k+w; } #ifdef OUTPUT_SOLUTION printf("\n"); #endif /* * Check that we've filled the same number of * tc squares as we originally found. */ assert(j == ntc); } memcpy(grid, grid2, wh * sizeof(int)); break; /* done it! */ } #ifdef GENERATION_DIAGNOSTICS { int x,y; printf("n=%d\n", n); for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { if (grid[y*w+x] == 0) printf("-"); else printf("%d", grid[y*w+x]); } printf("\n"); } } #endif if (n < 0) break; } ok = TRUE; for (i = 0; i < wh; i++) if (grid[i] == 0) { ok = FALSE; failures++; #if defined GENERATION_DIAGNOSTICS || defined SHOW_INCOMPLETE { int x,y; printf("incomplete grid:\n"); for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { if (grid[y*w+x] == 0) printf("-"); else printf("%d", grid[y*w+x]); } printf("\n"); } } #endif break; } } while (!ok); #if defined GENERATION_DIAGNOSTICS || defined COUNT_FAILURES printf("%d failures\n", failures); #endif #ifdef GENERATION_DIAGNOSTICS { int x,y; printf("final grid:\n"); for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { printf("%d", grid[y*w+x]); } printf("\n"); } } #endif sfree(grid2); sfree(list); }
static char *new_game_desc(game_params *params, random_state *rs, char **aux, int interactive) { int stop, n, i, x; int x1, x2, p1, p2; int *tiles, *used; char *ret; int retlen; n = params->w * params->h; tiles = snewn(n, int); if (params->movetarget) { int prevoffset = -1; int max = (params->w > params->h ? params->w : params->h); int *prevmoves = snewn(max, int); /* * Shuffle the old-fashioned way, by making a series of * single moves on the grid. */ for (i = 0; i < n; i++) tiles[i] = i; for (i = 0; i < params->movetarget; i++) { int start, offset, len, direction, index; int j, tmp; /* * Choose a move to make. We can choose from any row * or any column. */ while (1) { j = random_upto(rs, params->w + params->h); if (j < params->w) { /* Column. */ index = j; start = j; offset = params->w; len = params->h; } else { /* Row. */ index = j - params->w; start = index * params->w; offset = 1; len = params->w; } direction = -1 + 2 * random_upto(rs, 2); /* * To at least _try_ to avoid boring cases, check * that this move doesn't directly undo a previous * one, or repeat it so many times as to turn it * into fewer moves in the opposite direction. (For * example, in a row of length 4, we're allowed to * move it the same way twice, but not three * times.) * * We track this for each individual row/column, * and clear all the counters as soon as a * perpendicular move is made. This isn't perfect * (it _can't_ guaranteeably be perfect - there * will always come a move count beyond which a * shorter solution will be possible than the one * which constructed the position) but it should * sort out all the obvious cases. */ if (offset == prevoffset) { tmp = prevmoves[index] + direction; if (abs(2*tmp) > len || abs(tmp) < abs(prevmoves[index])) continue; } /* If we didn't `continue', we've found an OK move to make. */ if (offset != prevoffset) { int i; for (i = 0; i < max; i++) prevmoves[i] = 0; prevoffset = offset; } prevmoves[index] += direction; break; } /* * Make the move. */ if (direction < 0) { start += (len-1) * offset; offset = -offset; } tmp = tiles[start]; for (j = 0; j+1 < len; j++) tiles[start + j*offset] = tiles[start + (j+1)*offset]; tiles[start + (len-1) * offset] = tmp; } sfree(prevmoves); } else {
static char *new_game_desc(game_params *params, random_state *rs, char **aux, int interactive) { int gap, n, i, x; int x1, x2, p1, p2, parity; int *tiles, *used; char *ret; int retlen; n = params->w * params->h; tiles = snewn(n, int); used = snewn(n, int); for (i = 0; i < n; i++) { tiles[i] = -1; used[i] = FALSE; } gap = random_upto(rs, n); tiles[gap] = 0; used[0] = TRUE; /* * Place everything else except the last two tiles. */ for (x = 0, i = n-1; i > 2; i--) { int k = random_upto(rs, i); int j; for (j = 0; j < n; j++) if (!used[j] && (k-- == 0)) break; assert(j < n && !used[j]); used[j] = TRUE; while (tiles[x] >= 0) x++; assert(x < n); tiles[x] = j; } /* * Find the last two locations, and the last two pieces. */ while (tiles[x] >= 0) x++; assert(x < n); x1 = x; x++; while (tiles[x] >= 0) x++; assert(x < n); x2 = x; for (i = 0; i < n; i++) if (!used[i]) break; p1 = i; for (i = p1+1; i < n; i++) if (!used[i]) break; p2 = i; /* * Determine the required parity of the overall permutation. * This is the XOR of: * * - The chessboard parity ((x^y)&1) of the gap square. The * bottom right counts as even. * * - The parity of n. (The target permutation is 1,...,n-1,0 * rather than 0,...,n-1; this is a cyclic permutation of * the starting point and hence is odd iff n is even.) */ parity = ((X(params, gap) - (params->w-1)) ^ (Y(params, gap) - (params->h-1)) ^ (n+1)) & 1; /* * Try the last two tiles one way round. If that fails, swap * them. */ tiles[x1] = p1; tiles[x2] = p2; if (perm_parity(tiles, n) != parity) { tiles[x1] = p2; tiles[x2] = p1; assert(perm_parity(tiles, n) == parity); } /* * Now construct the game description, by describing the tile * array as a simple sequence of comma-separated integers. */ ret = NULL; retlen = 0; for (i = 0; i < n; i++) { char buf[80]; int k; k = sprintf(buf, "%d,", tiles[i]); ret = sresize(ret, retlen + k + 1, char); strcpy(ret + retlen, buf); retlen += k; } ret[retlen-1] = '\0'; /* delete last comma */ sfree(tiles); sfree(used); return ret; }
static char *new_game_desc(game_params *params, random_state *rs, char **aux, int interactive) { int *grid; int w = params->w, h = params->h, n = params->n, wh = w*h; int i; char *ret; int retlen; int total_moves; /* * Set up a solved grid. */ grid = snewn(wh, int); for (i = 0; i < wh; i++) grid[i] = ((params->rowsonly ? i/w : i) + 1) * 4; /* * Shuffle it. This game is complex enough that I don't feel up * to analysing its full symmetry properties (particularly at * n=4 and above!), so I'm going to do it the pedestrian way * and simply shuffle the grid by making a long sequence of * randomly chosen moves. */ total_moves = params->movetarget; if (!total_moves) /* Add a random move to avoid parity issues. */ total_moves = w*h*n*n*2 + random_upto(rs, 2); do { int *prevmoves; int rw, rh; /* w/h of rotation centre space */ rw = w - n + 1; rh = h - n + 1; prevmoves = snewn(rw * rh, int); for (i = 0; i < rw * rh; i++) prevmoves[i] = 0; for (i = 0; i < total_moves; i++) { int x, y, r, oldtotal, newtotal, dx, dy; do { x = random_upto(rs, w - n + 1); y = random_upto(rs, h - n + 1); r = 2 * random_upto(rs, 2) - 1; /* * See if any previous rotations has happened at * this point which nothing has overlapped since. * If so, ensure we haven't either undone a * previous move or repeated one so many times that * it turns into fewer moves in the inverse * direction (i.e. three identical rotations). */ oldtotal = prevmoves[y*rw+x]; newtotal = oldtotal + r; /* * Special case here for w==h==n, in which case * there is actually no way to _avoid_ all moves * repeating or undoing previous ones. */ } while ((w != n || h != n) && (abs(newtotal) < abs(oldtotal) || abs(newtotal) > 2)); do_rotate(grid, w, h, n, params->orientable, x, y, r); /* * Log the rotation we've just performed at this point, * for inversion detection in the next move. * * Also zero a section of the prevmoves array, because * any rotation area which _overlaps_ this one is now * entirely safe to perform further moves in. * * Two rotation areas overlap if their top left * coordinates differ by strictly less than n in both * directions */ prevmoves[y*rw+x] += r; for (dy = -n+1; dy <= n-1; dy++) { if (y + dy < 0 || y + dy >= rh) continue; for (dx = -n+1; dx <= n-1; dx++) { if (x + dx < 0 || x + dx >= rw) continue; if (dx == 0 && dy == 0) continue; prevmoves[(y+dy)*rw+(x+dx)] = 0; } } } sfree(prevmoves); } while (grid_complete(grid, wh, params->orientable)); /* * Now construct the game description, by describing the grid * as a simple sequence of integers. They're comma-separated, * unless the puzzle is orientable in which case they're * separated by orientation letters `u', `d', `l' and `r'. */ ret = NULL; retlen = 0; for (i = 0; i < wh; i++) { char buf[80]; int k; k = sprintf(buf, "%d%c", grid[i] / 4, (char)(params->orientable ? "uldr"[grid[i] & 3] : ',')); ret = sresize(ret, retlen + k + 1, char); strcpy(ret + retlen, buf); retlen += k; } if (!params->orientable) ret[retlen-1] = '\0'; /* delete last comma */ sfree(grid); return ret; }
/* Checks that the guessed balls in the state match up with the real balls * for all possible lasers (i.e. not just the ones that the player might * have already guessed). This is required because any layout with >4 balls * might have multiple valid solutions. Returns non-zero for a 'correct' * (i.e. consistent) layout. */ static int check_guesses(game_state *state, int cagey) { game_state *solution, *guesses; int i, x, y, n, unused, tmp; int ret = 0; if (cagey) { /* * First, check that each laser the player has already * fired is consistent with the layout. If not, show them * one error they've made and reveal no further * information. * * Failing that, check to see whether the player would have * been able to fire any laser which distinguished the real * solution from their guess. If so, show them one such * laser and reveal no further information. */ guesses = dup_game(state); /* clear out BALL_CORRECT on guess, make BALL_GUESS BALL_CORRECT. */ for (x = 1; x <= state->w; x++) { for (y = 1; y <= state->h; y++) { GRID(guesses, x, y) &= ~BALL_CORRECT; if (GRID(guesses, x, y) & BALL_GUESS) GRID(guesses, x, y) |= BALL_CORRECT; } } n = 0; for (i = 0; i < guesses->nlasers; i++) { if (guesses->exits[i] != LASER_EMPTY && guesses->exits[i] != laser_exit(guesses, i)) n++; } if (n) { /* * At least one of the player's existing lasers * contradicts their ball placement. Pick a random one, * highlight it, and return. * * A temporary random state is created from the current * grid, so that repeating the same marking will give * the same answer instead of a different one. */ random_state *rs = random_new((char *)guesses->grid, (state->w+2)*(state->h+2) * sizeof(unsigned int)); n = random_upto(rs, n); random_free(rs); for (i = 0; i < guesses->nlasers; i++) { if (guesses->exits[i] != LASER_EMPTY && guesses->exits[i] != laser_exit(guesses, i) && n-- == 0) { state->exits[i] |= LASER_WRONG; tmp = laser_exit(state, i); if (RANGECHECK(state, tmp)) state->exits[tmp] |= LASER_WRONG; state->justwrong = TRUE; free_game(guesses); return 0; } } } n = 0; for (i = 0; i < guesses->nlasers; i++) { if (guesses->exits[i] == LASER_EMPTY && laser_exit(state, i) != laser_exit(guesses, i)) n++; } if (n) { /* * At least one of the player's unfired lasers would * demonstrate their ball placement to be wrong. Pick a * random one, highlight it, and return. * * A temporary random state is created from the current * grid, so that repeating the same marking will give * the same answer instead of a different one. */ random_state *rs = random_new((char *)guesses->grid, (state->w+2)*(state->h+2) * sizeof(unsigned int)); n = random_upto(rs, n); random_free(rs); for (i = 0; i < guesses->nlasers; i++) { if (guesses->exits[i] == LASER_EMPTY && laser_exit(state, i) != laser_exit(guesses, i) && n-- == 0) { fire_laser(state, i); state->exits[i] |= LASER_OMITTED; tmp = laser_exit(state, i); if (RANGECHECK(state, tmp)) state->exits[tmp] |= LASER_OMITTED; state->justwrong = TRUE; free_game(guesses); return 0; } } } free_game(guesses); } /* duplicate the state (to solution) */ solution = dup_game(state); /* clear out the lasers of solution */ for (i = 0; i < solution->nlasers; i++) { tmp = range2grid(solution, i, &x, &y, &unused); assert(tmp); GRID(solution, x, y) = 0; solution->exits[i] = LASER_EMPTY; } /* duplicate solution to guess. */ guesses = dup_game(solution); /* clear out BALL_CORRECT on guess, make BALL_GUESS BALL_CORRECT. */ for (x = 1; x <= state->w; x++) { for (y = 1; y <= state->h; y++) { GRID(guesses, x, y) &= ~BALL_CORRECT; if (GRID(guesses, x, y) & BALL_GUESS) GRID(guesses, x, y) |= BALL_CORRECT; } } /* for each laser (on both game_states), fire it if it hasn't been fired. * If one has been fired (or received a hit) and another hasn't, we know * the ball layouts didn't match and can short-circuit return. */ for (i = 0; i < solution->nlasers; i++) { if (solution->exits[i] == LASER_EMPTY) fire_laser(solution, i); if (guesses->exits[i] == LASER_EMPTY) fire_laser(guesses, i); } /* check each game_state's laser against the other; if any differ, return 0 */ ret = 1; for (i = 0; i < solution->nlasers; i++) { tmp = range2grid(solution, i, &x, &y, &unused); assert(tmp); if (solution->exits[i] != guesses->exits[i]) { /* If the original state didn't have this shot fired, * and it would be wrong between the guess and the solution, * add it. */ if (state->exits[i] == LASER_EMPTY) { state->exits[i] = solution->exits[i]; if (state->exits[i] == LASER_REFLECT || state->exits[i] == LASER_HIT) GRID(state, x, y) = state->exits[i]; else { /* add a new shot, incrementing state's laser count. */ int ex, ey, newno = state->laserno++; tmp = range2grid(state, state->exits[i], &ex, &ey, &unused); assert(tmp); GRID(state, x, y) = newno; GRID(state, ex, ey) = newno; } state->exits[i] |= LASER_OMITTED; } else { state->exits[i] |= LASER_WRONG; } ret = 0; } } if (ret == 0 || state->nguesses < state->minballs || state->nguesses > state->maxballs) goto done; /* fix up original state so the 'correct' balls end up matching the guesses, * as we've just proved that they were equivalent. */ for (x = 1; x <= state->w; x++) { for (y = 1; y <= state->h; y++) { if (GRID(state, x, y) & BALL_GUESS) GRID(state, x, y) |= BALL_CORRECT; else GRID(state, x, y) &= ~BALL_CORRECT; } } done: /* fill in nright and nwrong. */ state->nright = state->nwrong = state->nmissed = 0; for (x = 1; x <= state->w; x++) { for (y = 1; y <= state->h; y++) { int bs = GRID(state, x, y) & (BALL_GUESS | BALL_CORRECT); if (bs == (BALL_GUESS | BALL_CORRECT)) state->nright++; else if (bs == BALL_GUESS) state->nwrong++; else if (bs == BALL_CORRECT) state->nmissed++; } } free_game(solution); free_game(guesses); state->reveal = 1; return ret; }
/* * Generate a new complete random closed loop for the given grid. * * The method is to generate a WHITE/BLACK colouring of all the faces, * such that the WHITE faces will define the inside of the path, and the * BLACK faces define the outside. * To do this, we initially colour all faces GREY. The infinite space outside * the grid is coloured BLACK, and we choose a random face to colour WHITE. * Then we gradually grow the BLACK and the WHITE regions, eliminating GREY * faces, until the grid is filled with BLACK/WHITE. As we grow the regions, * we avoid creating loops of a single colour, to preserve the topological * shape of the WHITE and BLACK regions. * We also try to make the boundary as loopy and twisty as possible, to avoid * generating paths that are uninteresting. * The algorithm works by choosing a BLACK/WHITE colour, then choosing a GREY * face that can be coloured with that colour (without violating the * topological shape of that region). It's not obvious, but I think this * algorithm is guaranteed to terminate without leaving any GREY faces behind. * Indeed, if there are any GREY faces at all, both the WHITE and BLACK * regions can be grown. * This is checked using assert()ions, and I haven't seen any failures yet. * * Hand-wavy proof: imagine what can go wrong... * * Could the white faces get completely cut off by the black faces, and still * leave some grey faces remaining? * No, because then the black faces would form a loop around both the white * faces and the grey faces, which is disallowed because we continually * maintain the correct topological shape of the black region. * Similarly, the black faces can never get cut off by the white faces. That * means both the WHITE and BLACK regions always have some room to grow into * the GREY regions. * Could it be that we can't colour some GREY face, because there are too many * WHITE/BLACK transitions as we walk round the face? (see the * can_colour_face() function for details) * No. Imagine otherwise, and we see WHITE/BLACK/WHITE/BLACK as we walk * around the face. The two WHITE faces would be connected by a WHITE path, * and the BLACK faces would be connected by a BLACK path. These paths would * have to cross, which is impossible. * Another thing that could go wrong: perhaps we can't find any GREY face to * colour WHITE, because it would create a loop-violation or a corner-violation * with the other WHITE faces? * This is a little bit tricky to prove impossible. Imagine you have such a * GREY face (that is, if you coloured it WHITE, you would create a WHITE loop * or corner violation). * That would cut all the non-white area into two blobs. One of those blobs * must be free of BLACK faces (because the BLACK stuff is a connected blob). * So we have a connected GREY area, completely surrounded by WHITE * (including the GREY face we've tentatively coloured WHITE). * A well-known result in graph theory says that you can always find a GREY * face whose removal leaves the remaining GREY area connected. And it says * there are at least two such faces, so we can always choose the one that * isn't the "tentative" GREY face. Colouring that face WHITE leaves * everything nice and connected, including that "tentative" GREY face which * acts as a gateway to the rest of the non-WHITE grid. */ void generate_loop(grid *g, char *board, random_state *rs, loopgen_bias_fn_t bias, void *biasctx) { int i, j; int num_faces = g->num_faces; struct face_score *face_scores; /* Array of face_score objects */ struct face_score *fs; /* Points somewhere in the above list */ struct grid_face *cur_face; tree234 *lightable_faces_sorted; tree234 *darkable_faces_sorted; int *face_list; int do_random_pass; /* Make a board */ memset(board, FACE_GREY, num_faces); /* Create and initialise the list of face_scores */ face_scores = snewn(num_faces, struct face_score); for (i = 0; i < num_faces; i++) { face_scores[i].random = random_bits(rs, 31); face_scores[i].black_score = face_scores[i].white_score = 0; } /* Colour a random, finite face white. The infinite face is implicitly * coloured black. Together, they will seed the random growth process * for the black and white areas. */ i = random_upto(rs, num_faces); board[i] = FACE_WHITE; /* We need a way of favouring faces that will increase our loopiness. * We do this by maintaining a list of all candidate faces sorted by * their score and choose randomly from that with appropriate skew. * In order to avoid consistently biasing towards particular faces, we * need the sort order _within_ each group of scores to be completely * random. But it would be abusing the hospitality of the tree234 data * structure if our comparison function were nondeterministic :-). So with * each face we associate a random number that does not change during a * particular run of the generator, and use that as a secondary sort key. * Yes, this means we will be biased towards particular random faces in * any one run but that doesn't actually matter. */ lightable_faces_sorted = newtree234(white_sort_cmpfn); darkable_faces_sorted = newtree234(black_sort_cmpfn); /* Initialise the lists of lightable and darkable faces. This is * slightly different from the code inside the while-loop, because we need * to check every face of the board (the grid structure does not keep a * list of the infinite face's neighbours). */ for (i = 0; i < num_faces; i++) { grid_face *f = g->faces + i; struct face_score *fs = face_scores + i; if (board[i] != FACE_GREY) continue; /* We need the full colourability check here, it's not enough simply * to check neighbourhood. On some grids, a neighbour of the infinite * face is not necessarily darkable. */ if (can_colour_face(g, board, i, FACE_BLACK)) { fs->black_score = face_score(g, board, f, FACE_BLACK); add234(darkable_faces_sorted, fs); } if (can_colour_face(g, board, i, FACE_WHITE)) { fs->white_score = face_score(g, board, f, FACE_WHITE); add234(lightable_faces_sorted, fs); } } /* Colour faces one at a time until no more faces are colourable. */ while (TRUE) { enum face_colour colour; tree234 *faces_to_pick; int c_lightable = count234(lightable_faces_sorted); int c_darkable = count234(darkable_faces_sorted); if (c_lightable == 0 && c_darkable == 0) { /* No more faces we can use at all. */ break; } assert(c_lightable != 0 && c_darkable != 0); /* Choose a colour, and colour the best available face * with that colour. */ colour = random_upto(rs, 2) ? FACE_WHITE : FACE_BLACK; if (colour == FACE_WHITE) faces_to_pick = lightable_faces_sorted; else faces_to_pick = darkable_faces_sorted; if (bias) { /* * Go through all the candidate faces and pick the one the * bias function likes best, breaking ties using the * ordering in our tree234 (which is why we replace only * if score > bestscore, not >=). */ int j, k; struct face_score *best = NULL; int score, bestscore = 0; for (j = 0; (fs = (struct face_score *)index234(faces_to_pick, j))!=NULL; j++) { assert(fs); k = fs - face_scores; assert(board[k] == FACE_GREY); board[k] = colour; score = bias(biasctx, board, k); board[k] = FACE_GREY; bias(biasctx, board, k); /* let bias know we put it back */ if (!best || score > bestscore) { bestscore = score; best = fs; } } fs = best; } else { fs = (struct face_score *)index234(faces_to_pick, 0); } assert(fs); i = fs - face_scores; assert(board[i] == FACE_GREY); board[i] = colour; if (bias) bias(biasctx, board, i); /* notify bias function of the change */ /* Remove this newly-coloured face from the lists. These lists should * only contain grey faces. */ del234(lightable_faces_sorted, fs); del234(darkable_faces_sorted, fs); /* Remember which face we've just coloured */ cur_face = g->faces + i; /* The face we've just coloured potentially affects the colourability * and the scores of any neighbouring faces (touching at a corner or * edge). So the search needs to be conducted around all faces * touching the one we've just lit. Iterate over its corners, then * over each corner's faces. For each such face, we remove it from * the lists, recalculate any scores, then add it back to the lists * (depending on whether it is lightable, darkable or both). */ for (i = 0; i < cur_face->order; i++) { grid_dot *d = cur_face->dots[i]; for (j = 0; j < d->order; j++) { grid_face *f = d->faces[j]; int fi; /* face index of f */ if (f == NULL) continue; if (f == cur_face) continue; /* If the face is already coloured, it won't be on our * lightable/darkable lists anyway, so we can skip it without * bothering with the removal step. */ if (FACE_COLOUR(f) != FACE_GREY) continue; /* Find the face index and face_score* corresponding to f */ fi = f - g->faces; fs = face_scores + fi; /* Remove from lightable list if it's in there. We do this, * even if it is still lightable, because the score might * be different, and we need to remove-then-add to maintain * correct sort order. */ del234(lightable_faces_sorted, fs); if (can_colour_face(g, board, fi, FACE_WHITE)) { fs->white_score = face_score(g, board, f, FACE_WHITE); add234(lightable_faces_sorted, fs); } /* Do the same for darkable list. */ del234(darkable_faces_sorted, fs); if (can_colour_face(g, board, fi, FACE_BLACK)) { fs->black_score = face_score(g, board, f, FACE_BLACK); add234(darkable_faces_sorted, fs); } } } } /* Clean up */ freetree234(lightable_faces_sorted); freetree234(darkable_faces_sorted); sfree(face_scores); /* The next step requires a shuffled list of all faces */ face_list = snewn(num_faces, int); for (i = 0; i < num_faces; ++i) { face_list[i] = i; } shuffle(face_list, num_faces, sizeof(int), rs); /* The above loop-generation algorithm can often leave large clumps * of faces of one colour. In extreme cases, the resulting path can be * degenerate and not very satisfying to solve. * This next step alleviates this problem: * Go through the shuffled list, and flip the colour of any face we can * legally flip, and which is adjacent to only one face of the opposite * colour - this tends to grow 'tendrils' into any clumps. * Repeat until we can find no more faces to flip. This will * eventually terminate, because each flip increases the loop's * perimeter, which cannot increase for ever. * The resulting path will have maximal loopiness (in the sense that it * cannot be improved "locally". Unfortunately, this allows a player to * make some illicit deductions. To combat this (and make the path more * interesting), we do one final pass making random flips. */ /* Set to TRUE for final pass */ do_random_pass = FALSE; while (TRUE) { /* Remember whether a flip occurred during this pass */ int flipped = FALSE; for (i = 0; i < num_faces; ++i) { int j = face_list[i]; enum face_colour opp = (board[j] == FACE_WHITE) ? FACE_BLACK : FACE_WHITE; if (can_colour_face(g, board, j, opp)) { grid_face *face = g->faces +j; if (do_random_pass) { /* final random pass */ if (!random_upto(rs, 10)) board[j] = opp; } else { /* normal pass - flip when neighbour count is 1 */ if (face_num_neighbours(g, board, face, opp) == 1) { board[j] = opp; flipped = TRUE; } } } } if (do_random_pass) break; if (!flipped) do_random_pass = TRUE; } sfree(face_list); }
static char *new_game_desc(game_params *params, random_state *rs, char **aux, int interactive) { int n = params->n, w = n+2, h = n+1, wh = w*h; int *grid, *grid2, *list; int i, j, k, len; char *ret; /* * Allocate space in which to lay the grid out. */ grid = snewn(wh, int); grid2 = snewn(wh, int); list = snewn(2*wh, int); /* * I haven't been able to think of any particularly clever * techniques for generating instances of Dominosa with a * unique solution. Many of the deductions used in this puzzle * are based on information involving half the grid at a time * (`of all the 6s, exactly one is next to a 3'), so a strategy * of partially solving the grid and then perturbing the place * where the solver got stuck seems particularly likely to * accidentally destroy the information which the solver had * used in getting that far. (Contrast with, say, Mines, in * which most deductions are local so this is an excellent * strategy.) * * Therefore I resort to the basest of brute force methods: * generate a random grid, see if it's solvable, throw it away * and try again if not. My only concession to sophistication * and cleverness is to at least _try_ not to generate obvious * 2x2 ambiguous sections (see comment below in the domino- * flipping section). * * During tests performed on 2005-07-15, I found that the brute * force approach without that tweak had to throw away about 87 * grids on average (at the default n=6) before finding a * unique one, or a staggering 379 at n=9; good job the * generator and solver are fast! When I added the * ambiguous-section avoidance, those numbers came down to 19 * and 26 respectively, which is a lot more sensible. */ do { domino_layout_prealloc(w, h, rs, grid, grid2, list); /* * Now we have a complete layout covering the whole * rectangle with dominoes. So shuffle the actual domino * values and fill the rectangle with numbers. */ k = 0; for (i = 0; i <= params->n; i++) for (j = 0; j <= i; j++) { list[k++] = i; list[k++] = j; } shuffle(list, k/2, 2*sizeof(*list), rs); j = 0; for (i = 0; i < wh; i++) if (grid[i] > i) { /* Optionally flip the domino round. */ int flip = -1; if (params->unique) { int t1, t2; /* * If we're after a unique solution, we can do * something here to improve the chances. If * we're placing a domino so that it forms a * 2x2 rectangle with one we've already placed, * and if that domino and this one share a * number, we can try not to put them so that * the identical numbers are diagonally * separated, because that automatically causes * non-uniqueness: * * +---+ +-+-+ * |2 3| |2|3| * +---+ -> | | | * |4 2| |4|2| * +---+ +-+-+ */ t1 = i; t2 = grid[i]; if (t2 == t1 + w) { /* this domino is vertical */ if (t1 % w > 0 &&/* and not on the left hand edge */ grid[t1-1] == t2-1 &&/* alongside one to left */ (grid2[t1-1] == list[j] || /* and has a number */ grid2[t1-1] == list[j+1] || /* in common */ grid2[t2-1] == list[j] || grid2[t2-1] == list[j+1])) { if (grid2[t1-1] == list[j] || grid2[t2-1] == list[j+1]) flip = 0; else flip = 1; } } else { /* this domino is horizontal */ if (t1 / w > 0 &&/* and not on the top edge */ grid[t1-w] == t2-w &&/* alongside one above */ (grid2[t1-w] == list[j] || /* and has a number */ grid2[t1-w] == list[j+1] || /* in common */ grid2[t2-w] == list[j] || grid2[t2-w] == list[j+1])) { if (grid2[t1-w] == list[j] || grid2[t2-w] == list[j+1]) flip = 0; else flip = 1; } } } if (flip < 0) flip = random_upto(rs, 2); grid2[i] = list[j + flip]; grid2[grid[i]] = list[j + 1 - flip]; j += 2; } assert(j == k); } while (params->unique && solver(w, h, n, grid2, NULL) > 1); #ifdef GENERATION_DIAGNOSTICS for (j = 0; j < h; j++) { for (i = 0; i < w; i++) { putchar('0' + grid2[j*w+i]); } putchar('\n'); } putchar('\n'); #endif /* * Encode the resulting game state. * * Our encoding is a string of digits. Any number greater than * 9 is represented by a decimal integer within square * brackets. We know there are n+2 of every number (it's paired * with each number from 0 to n inclusive, and one of those is * itself so that adds another occurrence), so we can work out * the string length in advance. */ /* * To work out the total length of the decimal encodings of all * the numbers from 0 to n inclusive: * - every number has a units digit; total is n+1. * - all numbers above 9 have a tens digit; total is max(n+1-10,0). * - all numbers above 99 have a hundreds digit; total is max(n+1-100,0). * - and so on. */ len = n+1; for (i = 10; i <= n; i *= 10) len += max(n + 1 - i, 0); /* Now add two square brackets for each number above 9. */ len += 2 * max(n + 1 - 10, 0); /* And multiply by n+2 for the repeated occurrences of each number. */ len *= n+2; /* * Now actually encode the string. */ ret = snewn(len+1, char); j = 0; for (i = 0; i < wh; i++) { k = grid2[i]; if (k < 10) ret[j++] = '0' + k; else j += sprintf(ret+j, "[%d]", k); assert(j <= len); } assert(j == len); ret[j] = '\0'; /* * Encode the solved state as an aux_info. */ { char *auxinfo = snewn(wh+1, char); for (i = 0; i < wh; i++) { int v = grid[i]; auxinfo[i] = (v == i+1 ? 'L' : v == i-1 ? 'R' : v == i+w ? 'T' : v == i-w ? 'B' : '.'); } auxinfo[wh] = '\0'; *aux = auxinfo; } sfree(list); sfree(grid2); sfree(grid); return ret; }
static void generate(random_state *rs, int w, int h, unsigned char *retgrid) { float *fgrid; float *fgrid2; int step, i, j; float threshold; fgrid = snewn(w*h, float); for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { fgrid[i*w+j] = random_upto(rs, 100000000UL) / 100000000.F; } } /* * The above gives a completely random splattering of black and * white cells. We want to gently bias this in favour of _some_ * reasonably thick areas of white and black, while retaining * some randomness and fine detail. * * So we evolve the starting grid using a cellular automaton. * Currently, I'm doing something very simple indeed, which is * to set each square to the average of the surrounding nine * cells (or the average of fewer, if we're on a corner). */ for (step = 0; step < 1; step++) { fgrid2 = snewn(w*h, float); for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { float sx, xbar; int n, p, q; /* * Compute the average of the surrounding cells. */ n = 0; sx = 0.F; for (p = -1; p <= +1; p++) { for (q = -1; q <= +1; q++) { if (i+p < 0 || i+p >= h || j+q < 0 || j+q >= w) continue; /* * An additional special case not mentioned * above: if a grid dimension is 2xn then * we do not average across that dimension * at all. Otherwise a 2x2 grid would * contain four identical squares. */ if ((h==2 && p!=0) || (w==2 && q!=0)) continue; n++; sx += fgrid[(i+p)*w+(j+q)]; } } xbar = sx / n; fgrid2[i*w+j] = xbar; } } sfree(fgrid); fgrid = fgrid2; } fgrid2 = snewn(w*h, float); memcpy(fgrid2, fgrid, w*h*sizeof(float)); qsort(fgrid2, w*h, sizeof(float), float_compare); threshold = fgrid2[w*h/2]; sfree(fgrid2); for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { retgrid[i*w+j] = (fgrid[i*w+j] >= threshold ? GRID_FULL : GRID_EMPTY); } } sfree(fgrid); }