ref: c4e486c2a1aea38df9c573d168979536ced9ad15
dir: /unfinished/pearl.c/
/* * pearl.c: Nikoli's `Masyu' puzzle. Currently this is a blank * puzzle file with nothing but a test solver-generator. */ /* * TODO: * * - The generation method appears to be fundamentally flawed. I * think generating a random loop and then choosing a clue set * is simply not a viable approach, because on a test run of * 10,000 attempts, it generated _six_ viable puzzles. All the * rest of the randomly generated loops failed to be soluble * even given a maximal clue set. Also, the vast majority of the * clues were white circles (straight clues); black circles * (corners) seem very uncommon. * + So what can we do? One possible approach would be to * adjust the random loop generation so that it created loops * which were in some heuristic sense more likely to be * viable Masyu puzzles. Certainly a good start on that would * be to arrange that black clues actually _came up_ slightly * more often, but I have no idea whether that would be * sufficient. * + A second option would be to throw the entire mechanism out * and instead write a different generator from scratch which * evolves the solution along with the puzzle: place a few * clues, nail down a bit of the loop, place another clue, * nail down some more, etc. It's unclear whether this can * sensibly be done, though. * * - Puzzle playing UI and everything else apart from the * generator... */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include <ctype.h> #include <math.h> #include "puzzles.h" #define NOCLUE 0 #define CORNER 1 #define STRAIGHT 2 #define R 1 #define U 2 #define L 4 #define D 8 #define DX(d) ( ((d)==R) - ((d)==L) ) #define DY(d) ( ((d)==D) - ((d)==U) ) #define F(d) (((d << 2) | (d >> 2)) & 0xF) #define C(d) (((d << 3) | (d >> 1)) & 0xF) #define A(d) (((d << 1) | (d >> 3)) & 0xF) #define LR (L | R) #define RL (R | L) #define UD (U | D) #define DU (D | U) #define LU (L | U) #define UL (U | L) #define LD (L | D) #define DL (D | L) #define RU (R | U) #define UR (U | R) #define RD (R | D) #define DR (D | R) #define BLANK 0 #define UNKNOWN 15 #define bLR (1 << LR) #define bRL (1 << RL) #define bUD (1 << UD) #define bDU (1 << DU) #define bLU (1 << LU) #define bUL (1 << UL) #define bLD (1 << LD) #define bDL (1 << DL) #define bRU (1 << RU) #define bUR (1 << UR) #define bRD (1 << RD) #define bDR (1 << DR) #define bBLANK (1 << BLANK) enum { COL_BACKGROUND, NCOLOURS }; struct game_params { int FIXME; }; struct game_state { int FIXME; }; static game_params *default_params(void) { game_params *ret = snew(game_params); ret->FIXME = 0; return ret; } static int game_fetch_preset(int i, char **name, game_params **params) { return FALSE; } static void free_params(game_params *params) { sfree(params); } static game_params *dup_params(game_params *params) { game_params *ret = snew(game_params); *ret = *params; /* structure copy */ return ret; } static void decode_params(game_params *params, char const *string) { } static char *encode_params(game_params *params, int full) { return dupstr("FIXME"); } static config_item *game_configure(game_params *params) { return NULL; } static game_params *custom_params(config_item *cfg) { return NULL; } static char *validate_params(game_params *params, int full) { return NULL; } /* ---------------------------------------------------------------------- * Solver. */ int pearl_solve(int w, int h, char *clues, char *result) { int W = 2*w+1, H = 2*h+1; short *workspace; int *dsf, *dsfsize; int x, y, b, d; int ret = -1; /* * workspace[(2*y+1)*W+(2*x+1)] indicates the possible nature * of the square (x,y), as a logical OR of bitfields. * * workspace[(2*y)*W+(2*x+1)], for x odd and y even, indicates * whether the horizontal edge between (x,y) and (x+1,y) is * connected (1), disconnected (2) or unknown (3). * * workspace[(2*y+1)*W+(2*x)], indicates the same about the * vertical edge between (x,y) and (x,y+1). * * Initially, every square is considered capable of being in * any of the seven possible states (two straights, four * corners and empty), except those corresponding to clue * squares which are more restricted. * * Initially, all edges are unknown, except the ones around the * grid border which are known to be disconnected. */ workspace = snewn(W*H, short); for (x = 0; x < W*H; x++) workspace[x] = 0; /* Square states */ for (y = 0; y < h; y++) for (x = 0; x < w; x++) switch (clues[y*w+x]) { case CORNER: workspace[(2*y+1)*W+(2*x+1)] = bLU|bLD|bRU|bRD; break; case STRAIGHT: workspace[(2*y+1)*W+(2*x+1)] = bLR|bUD; break; default: workspace[(2*y+1)*W+(2*x+1)] = bLR|bUD|bLU|bLD|bRU|bRD|bBLANK; break; } /* Horizontal edges */ for (y = 0; y <= h; y++) for (x = 0; x < w; x++) workspace[(2*y)*W+(2*x+1)] = (y==0 || y==h ? 2 : 3); /* Vertical edges */ for (y = 0; y < h; y++) for (x = 0; x <= w; x++) workspace[(2*y+1)*W+(2*x)] = (x==0 || x==w ? 2 : 3); /* * We maintain a dsf of connected squares, together with a * count of the size of each equivalence class. */ dsf = snewn(w*h, int); dsfsize = snewn(w*h, int); /* * Now repeatedly try to find something we can do. */ while (1) { int done_something = FALSE; #ifdef SOLVER_DIAGNOSTICS for (y = 0; y < H; y++) { for (x = 0; x < W; x++) printf("%*x", (x&1) ? 5 : 2, workspace[y*W+x]); printf("\n"); } #endif /* * Go through the square state words, and discard any * square state which is inconsistent with known facts * about the edges around the square. */ for (y = 0; y < h; y++) for (x = 0; x < w; x++) { for (b = 0; b < 0xD; b++) if (workspace[(2*y+1)*W+(2*x+1)] & (1<<b)) { /* * If any edge of this square is known to * be connected when state b would require * it disconnected, or vice versa, discard * the state. */ for (d = 1; d <= 8; d += d) { int ex = 2*x+1 + DX(d), ey = 2*y+1 + DY(d); if (workspace[ey*W+ex] == ((b & d) ? 2 : 1)) { workspace[(2*y+1)*W+(2*x+1)] &= ~(1<<b); #ifdef SOLVER_DIAGNOSTICS printf("edge (%d,%d)-(%d,%d) rules out state" " %d for square (%d,%d)\n", ex/2, ey/2, (ex+1)/2, (ey+1)/2, b, x, y); #endif done_something = TRUE; break; } } } /* * Consistency check: each square must have at * least one state left! */ if (!workspace[(2*y+1)*W+(2*x+1)]) { #ifdef SOLVER_DIAGNOSTICS printf("edge check at (%d,%d): inconsistency\n", x, y); #endif ret = 0; goto cleanup; } } /* * Now go through the states array again, and nail down any * unknown edge if one of its neighbouring squares makes it * known. */ for (y = 0; y < h; y++) for (x = 0; x < w; x++) { int edgeor = 0, edgeand = 15; for (b = 0; b < 0xD; b++) if (workspace[(2*y+1)*W+(2*x+1)] & (1<<b)) { edgeor |= b; edgeand &= b; } /* * Now any bit clear in edgeor marks a disconnected * edge, and any bit set in edgeand marks a * connected edge. */ /* First check consistency: neither bit is both! */ if (edgeand & ~edgeor) { #ifdef SOLVER_DIAGNOSTICS printf("square check at (%d,%d): inconsistency\n", x, y); #endif ret = 0; goto cleanup; } for (d = 1; d <= 8; d += d) { int ex = 2*x+1 + DX(d), ey = 2*y+1 + DY(d); if (!(edgeor & d) && workspace[ey*W+ex] == 3) { workspace[ey*W+ex] = 2; done_something = TRUE; #ifdef SOLVER_DIAGNOSTICS printf("possible states of square (%d,%d) force edge" " (%d,%d)-(%d,%d) to be disconnected\n", x, y, ex/2, ey/2, (ex+1)/2, (ey+1)/2); #endif } else if ((edgeand & d) && workspace[ey*W+ex] == 3) { workspace[ey*W+ex] = 1; done_something = TRUE; #ifdef SOLVER_DIAGNOSTICS printf("possible states of square (%d,%d) force edge" " (%d,%d)-(%d,%d) to be connected\n", x, y, ex/2, ey/2, (ex+1)/2, (ey+1)/2); #endif } } } if (done_something) continue; /* * Now for longer-range clue-based deductions (using the * rules that a corner clue must connect to two straight * squares, and a straight clue must connect to at least * one corner square). */ for (y = 0; y < h; y++) for (x = 0; x < w; x++) switch (clues[y*w+x]) { case CORNER: for (d = 1; d <= 8; d += d) { int ex = 2*x+1 + DX(d), ey = 2*y+1 + DY(d); int fx = ex + DX(d), fy = ey + DY(d); int type = d | F(d); if (workspace[ey*W+ex] == 1) { /* * If a corner clue is connected on any * edge, then we can immediately nail * down the square beyond that edge as * being a straight in the appropriate * direction. */ if (workspace[fy*W+fx] != (1<<type)) { workspace[fy*W+fx] = (1<<type); done_something = TRUE; #ifdef SOLVER_DIAGNOSTICS printf("corner clue at (%d,%d) forces square " "(%d,%d) into state %d\n", x, y, fx/2, fy/2, type); #endif } } else if (workspace[ey*W+ex] == 3) { /* * Conversely, if a corner clue is * separated by an unknown edge from a * square which _cannot_ be a straight * in the appropriate direction, we can * mark that edge as disconnected. */ if (!(workspace[fy*W+fx] & (1<<type))) { workspace[ey*W+ex] = 2; done_something = TRUE; #ifdef SOLVER_DIAGNOSTICS printf("corner clue at (%d,%d), plus square " "(%d,%d) not being state %d, " "disconnects edge (%d,%d)-(%d,%d)\n", x, y, fx/2, fy/2, type, ex/2, ey/2, (ex+1)/2, (ey+1)/2); #endif } } } break; case STRAIGHT: /* * If a straight clue is between two squares * neither of which is capable of being a * corner connected to it, then the straight * clue cannot point in that direction. */ for (d = 1; d <= 2; d += d) { int fx = 2*x+1 + 2*DX(d), fy = 2*y+1 + 2*DY(d); int gx = 2*x+1 - 2*DX(d), gy = 2*y+1 - 2*DY(d); int type = d | F(d); if (!(workspace[(2*y+1)*W+(2*x+1)] & (1<<type))) continue; if (!(workspace[fy*W+fx] & ((1<<(F(d)|A(d))) | (1<<(F(d)|C(d))))) && !(workspace[gy*W+gx] & ((1<<( d |A(d))) | (1<<( d |C(d)))))) { workspace[(2*y+1)*W+(2*x+1)] &= ~(1<<type); done_something = TRUE; #ifdef SOLVER_DIAGNOSTICS printf("straight clue at (%d,%d) cannot corner at " "(%d,%d) or (%d,%d) so is not state %d\n", x, y, fx/2, fy/2, gx/2, gy/2, type); #endif } } /* * If a straight clue with known direction is * connected on one side to a known straight, * then on the other side it must be a corner. */ for (d = 1; d <= 8; d += d) { int fx = 2*x+1 + 2*DX(d), fy = 2*y+1 + 2*DY(d); int gx = 2*x+1 - 2*DX(d), gy = 2*y+1 - 2*DY(d); int type = d | F(d); if (workspace[(2*y+1)*W+(2*x+1)] != (1<<type)) continue; if (!(workspace[fy*W+fx] &~ (bLR|bUD)) && (workspace[gy*W+gx] &~ (bLU|bLD|bRU|bRD))) { workspace[gy*W+gx] &= (bLU|bLD|bRU|bRD); done_something = TRUE; #ifdef SOLVER_DIAGNOSTICS printf("straight clue at (%d,%d) connecting to " "straight at (%d,%d) makes (%d,%d) a " "corner\n", x, y, fx/2, fy/2, gx/2, gy/2); #endif } } break; } if (done_something) continue; /* * Now detect shortcut loops. */ { int nonblanks, loopclass; dsf_init(dsf, w*h); for (x = 0; x < w*h; x++) dsfsize[x] = 1; /* * First go through the edge entries and update the dsf * of which squares are connected to which others. We * also track the number of squares in each equivalence * class, and count the overall number of * known-non-blank squares. * * In the process of doing this, we must notice if a * loop has already been formed. If it has, we blank * out any square which isn't part of that loop * (failing a consistency check if any such square does * not have BLANK as one of its remaining options) and * exit the deduction loop with success. */ nonblanks = 0; loopclass = -1; for (y = 1; y < H-1; y++) for (x = 1; x < W-1; x++) if ((y ^ x) & 1) { /* * (x,y) are the workspace coordinates of * an edge field. Compute the normal-space * coordinates of the squares it connects. */ int ax = (x-1)/2, ay = (y-1)/2, ac = ay*w+ax; int bx = x/2, by = y/2, bc = by*w+bx; /* * If the edge is connected, do the dsf * thing. */ if (workspace[y*W+x] == 1) { int ae, be; ae = dsf_canonify(dsf, ac); be = dsf_canonify(dsf, bc); if (ae == be) { /* * We have a loop! */ if (loopclass != -1) { /* * In fact, we have two * separate loops, which is * doom. */ #ifdef SOLVER_DIAGNOSTICS printf("two loops found in grid!\n"); #endif ret = 0; goto cleanup; } loopclass = ae; } else { /* * Merge the two equivalence * classes. */ int size = dsfsize[ae] + dsfsize[be]; dsf_merge(dsf, ac, bc); ae = dsf_canonify(dsf, ac); dsfsize[ae] = size; } } } else if ((y & x) & 1) { /* * (x,y) are the workspace coordinates of a * square field. If the square is * definitely not blank, count it. */ if (!(workspace[y*W+x] & bBLANK)) nonblanks++; } /* * If we discovered an existing loop above, we must now * blank every square not part of it, and exit the main * deduction loop. */ if (loopclass != -1) { #ifdef SOLVER_DIAGNOSTICS printf("loop found in grid!\n"); #endif for (y = 0; y < h; y++) for (x = 0; x < w; x++) if (dsf_canonify(dsf, y*w+x) != loopclass) { if (workspace[(y*2+1)*W+(x*2+1)] & bBLANK) { workspace[(y*2+1)*W+(x*2+1)] = bBLANK; } else { /* * This square is not part of the * loop, but is known non-blank. We * have goofed. */ #ifdef SOLVER_DIAGNOSTICS printf("non-blank square (%d,%d) found outside" " loop!\n", x, y); #endif ret = 0; goto cleanup; } } /* * And we're done. */ ret = 1; break; } /* * Now go through the workspace again and mark any edge * which would cause a shortcut loop (i.e. would * connect together two squares in the same equivalence * class, and that equivalence class does not contain * _all_ the known-non-blank squares currently in the * grid) as disconnected. Also, mark any _square state_ * which would cause a shortcut loop as disconnected. */ for (y = 1; y < H-1; y++) for (x = 1; x < W-1; x++) if ((y ^ x) & 1) { /* * (x,y) are the workspace coordinates of * an edge field. Compute the normal-space * coordinates of the squares it connects. */ int ax = (x-1)/2, ay = (y-1)/2, ac = ay*w+ax; int bx = x/2, by = y/2, bc = by*w+bx; /* * If the edge is currently unknown, and * sits between two squares in the same * equivalence class, and the size of that * class is less than nonblanks, then * connecting this edge would be a shortcut * loop and so we must not do so. */ if (workspace[y*W+x] == 3) { int ae, be; ae = dsf_canonify(dsf, ac); be = dsf_canonify(dsf, bc); if (ae == be) { /* * We have a loop. Is it a shortcut? */ if (dsfsize[ae] < nonblanks) { /* * Yes! Mark this edge disconnected. */ workspace[y*W+x] = 2; done_something = TRUE; #ifdef SOLVER_DIAGNOSTICS printf("edge (%d,%d)-(%d,%d) would create" " a shortcut loop, hence must be" " disconnected\n", x/2, y/2, (x+1)/2, (y+1)/2); #endif } } } } else if ((y & x) & 1) { /* * (x,y) are the workspace coordinates of a * square field. Go through its possible * (non-blank) states and see if any gives * rise to a shortcut loop. * * This is slightly fiddly, because we have * to check whether this square is already * part of the same equivalence class as * the things it's joining. */ int ae = dsf_canonify(dsf, (y/2)*w+(x/2)); for (b = 2; b < 0xD; b++) if (workspace[y*W+x] & (1<<b)) { /* * Find the equivalence classes of * the two squares this one would * connect if it were in this * state. */ int e = -1; for (d = 1; d <= 8; d += d) if (b & d) { int xx = x/2 + DX(d), yy = y/2 + DY(d); int ee = dsf_canonify(dsf, yy*w+xx); if (e == -1) ee = e; else if (e != ee) e = -2; } if (e >= 0) { /* * This square state would form * a loop on equivalence class * e. Measure the size of that * loop, and see if it's a * shortcut. */ int loopsize = dsfsize[e]; if (e != ae) loopsize++;/* add the square itself */ if (loopsize < nonblanks) { /* * It is! Mark this square * state invalid. */ workspace[y*W+x] &= ~(1<<b); done_something = TRUE; #ifdef SOLVER_DIAGNOSTICS printf("square (%d,%d) would create a " "shortcut loop in state %d, " "hence cannot be\n", x/2, y/2, b); #endif } } } } } if (done_something) continue; /* * If we reach here, there is nothing left we can do. * Return 2 for ambiguous puzzle. */ ret = 2; goto cleanup; } /* * If we reach _here_, it's by `break' out of the main loop, * which means we've successfully achieved a solution. This * means that we expect every square to be nailed down to * exactly one possibility. Transcribe those possibilities into * the result array. */ for (y = 0; y < h; y++) for (x = 0; x < w; x++) { for (b = 0; b < 0xD; b++) if (workspace[(2*y+1)*W+(2*x+1)] == (1<<b)) { result[y*w+x] = b; break; } assert(b < 0xD); /* we should have had a break by now */ } cleanup: sfree(dsfsize); sfree(dsf); sfree(workspace); assert(ret >= 0); return ret; } /* ---------------------------------------------------------------------- * Loop generator. */ void pearl_loopgen(int w, int h, char *grid, random_state *rs) { int *options, *mindist, *maxdist, *list; int x, y, d, total, n, area, limit; /* * We're eventually going to have to return a w-by-h array * containing line segment data. However, it's more convenient * while actually generating the loop to consider the problem * as a (w-1) by (h-1) array in which some squares are `inside' * and some `outside'. * * I'm going to use the top left corner of my return array in * the latter manner until the end of the function. */ /* * To begin with, all squares are outside (0), except for one * randomly selected one which is inside (1). */ memset(grid, 0, w*h); x = random_upto(rs, w-1); y = random_upto(rs, h-1); grid[y*w+x] = 1; /* * I'm also going to need an array to store the possible * options for the next extension of the grid. */ options = snewn(w*h, int); for (x = 0; x < w*h; x++) options[x] = 0; /* * And some arrays and a list for breadth-first searching. */ mindist = snewn(w*h, int); maxdist = snewn(w*h, int); list = snewn(w*h, int); /* * Now we repeatedly scan the grid for feasible squares into * which we can extend our loop, pick one, and do it. */ area = 1; while (1) { #ifdef LOOPGEN_DIAGNOSTICS for (y = 0; y < h; y++) { for (x = 0; x < w; x++) printf("%d", grid[y*w+x]); printf("\n"); } printf("\n"); #endif /* * Our primary aim in growing this loop is to make it * reasonably _dense_ in the target rectangle. That is, we * want the maximum over all squares of the minimum * distance from that square to the loop to be small. * * Therefore, we start with a breadth-first search of the * grid to find those minimum distances. */ { int head = 0, tail = 0; int i; for (i = 0; i < w*h; i++) { mindist[i] = -1; if (grid[i]) { mindist[i] = 0; list[tail++] = i; } } while (head < tail) { i = list[head++]; y = i / w; x = i % w; for (d = 1; d <= 8; d += d) { int xx = x + DX(d), yy = y + DY(d); if (xx >= 0 && xx < w && yy >= 0 && yy < h && mindist[yy*w+xx] < 0) { mindist[yy*w+xx] = mindist[i] + 1; list[tail++] = yy*w+xx; } } } /* * Having done the BFS, we now backtrack along its path * to determine the most distant square that each * square is on the shortest path to. This tells us * which of the loop extension candidates (all of which * are squares marked 1) is most desirable to extend * into in terms of minimising the maximum distance * from any empty square to the nearest loop square. */ for (head = tail; head-- > 0 ;) { int max; i = list[head]; y = i / w; x = i % w; max = mindist[i]; for (d = 1; d <= 8; d += d) { int xx = x + DX(d), yy = y + DY(d); if (xx >= 0 && xx < w && yy >= 0 && yy < h && mindist[yy*w+xx] > mindist[i] && maxdist[yy*w+xx] > max) { max = maxdist[yy*w+xx]; } } maxdist[i] = max; } } /* * A square is a viable candidate for extension of our loop * if and only if the following conditions are all met: * - It is currently labelled 0. * - At least one of its four orthogonal neighbours is * labelled 1. * - If you consider its eight orthogonal and diagonal * neighbours to form a ring, that ring contains at most * one contiguous run of 1s. (It must also contain at * _least_ one, of course, but that's already guaranteed * by the previous condition so there's no need to test * it separately.) */ total = 0; for (y = 0; y < h-1; y++) for (x = 0; x < w-1; x++) { int ring[8]; int rx, neighbours, runs, dist; dist = maxdist[y*w+x]; options[y*w+x] = 0; if (grid[y*w+x]) continue; /* it isn't labelled 0 */ neighbours = 0; for (rx = 0, d = 1; d <= 8; rx += 2, d += d) { int x2 = x + DX(d), y2 = y + DY(d); int x3 = x2 + DX(A(d)), y3 = y2 + DY(A(d)); int g2 = (x2 >= 0 && x2 < w && y2 >= 0 && y2 < h ? grid[y2*w+x2] : 0); int g3 = (x3 >= 0 && x3 < w && y3 >= 0 && y3 < h ? grid[y3*w+x3] : 0); ring[rx] = g2; ring[rx+1] = g3; if (g2) neighbours++; } if (!neighbours) continue; /* it doesn't have a 1 neighbour */ runs = 0; for (rx = 0; rx < 8; rx++) if (ring[rx] && !ring[(rx+1) & 7]) runs++; if (runs > 1) continue; /* too many runs of 1s */ /* * Now we know this square is a viable extension * candidate. Mark it. * * FIXME: probabilistic prioritisation based on * perimeter perturbation? (Wow, must keep that * phrase.) */ options[y*w+x] = dist * (4-neighbours) * (4-neighbours); total += options[y*w+x]; } if (!total) break; /* nowhere to go! */ /* * Now pick a random one of the viable extension squares, * and extend into it. */ n = random_upto(rs, total); for (y = 0; y < h-1; y++) for (x = 0; x < w-1; x++) { assert(n >= 0); if (options[y*w+x] > n) goto found; /* two-level break */ n -= options[y*w+x]; } assert(!"We shouldn't ever get here"); found: grid[y*w+x] = 1; area++; /* * We terminate the loop when around 7/12 of the grid area * is full, but we also require that the loop has reached * all four edges. */ limit = random_upto(rs, (w-1)*(h-1)) + 13*(w-1)*(h-1); if (24 * area > limit) { int l = FALSE, r = FALSE, u = FALSE, d = FALSE; for (x = 0; x < w; x++) { if (grid[0*w+x]) u = TRUE; if (grid[(h-2)*w+x]) d = TRUE; } for (y = 0; y < h; y++) { if (grid[y*w+0]) l = TRUE; if (grid[y*w+(w-2)]) r = TRUE; } if (l && r && u && d) break; } } sfree(list); sfree(maxdist); sfree(mindist); sfree(options); #ifdef LOOPGEN_DIAGNOSTICS printf("final loop:\n"); for (y = 0; y < h; y++) { for (x = 0; x < w; x++) printf("%d", grid[y*w+x]); printf("\n"); } printf("\n"); #endif /* * Now convert this array of 0s and 1s into an array of path * components. */ for (y = h; y-- > 0 ;) { for (x = w; x-- > 0 ;) { /* * Examine the four grid squares of which (x,y) are in * the bottom right, to determine the output for this * square. */ int ul = (x > 0 && y > 0 ? grid[(y-1)*w+(x-1)] : 0); int ur = (y > 0 ? grid[(y-1)*w+x] : 0); int dl = (x > 0 ? grid[y*w+(x-1)] : 0); int dr = grid[y*w+x]; int type = 0; if (ul != ur) type |= U; if (dl != dr) type |= D; if (ul != dl) type |= L; if (ur != dr) type |= R; assert((bLR|bUD|bLU|bLD|bRU|bRD|bBLANK) & (1 << type)); grid[y*w+x] = type; } } #if defined LOOPGEN_DIAGNOSTICS && !defined GENERATION_DIAGNOSTICS printf("as returned:\n"); for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { int type = grid[y*w+x]; char s[5], *p = s; if (type & L) *p++ = 'L'; if (type & R) *p++ = 'R'; if (type & U) *p++ = 'U'; if (type & D) *p++ = 'D'; *p = '\0'; printf("%3s", s); } printf("\n"); } printf("\n"); #endif } static char *new_game_desc(game_params *params, random_state *rs, char **aux, int interactive) { char *grid, *clues; int *clueorder; int w = 10, h = 10; int x, y, d, ret, i; #if 0 clues = snewn(7*7, char); memcpy(clues, "\0\1\0\0\2\0\0" "\0\0\0\2\0\0\0" "\0\0\0\2\0\0\1" "\2\0\0\2\0\0\0" "\2\0\0\0\0\0\1" "\0\0\1\0\0\2\0" "\0\0\2\0\0\0\0", 7*7); grid = snewn(7*7, char); printf("%d\n", pearl_solve(7, 7, clues, grid)); #elif 0 clues = snewn(10*10, char); memcpy(clues, "\0\0\2\0\2\0\0\0\0\0" "\0\0\0\0\2\0\0\0\1\0" "\0\0\1\0\1\0\2\0\0\0" "\0\0\0\2\0\0\2\0\0\0" "\1\0\0\0\0\2\0\0\0\2" "\0\0\2\0\0\0\0\2\0\0" "\0\0\1\0\0\0\2\0\0\0" "\2\0\0\0\1\0\0\0\0\2" "\0\0\0\0\0\0\2\2\0\0" "\0\0\1\0\0\0\0\0\0\1", 10*10); grid = snewn(10*10, char); printf("%d\n", pearl_solve(10, 10, clues, grid)); #elif 0 clues = snewn(10*10, char); memcpy(clues, "\0\0\0\0\0\0\1\0\0\0" "\0\1\0\1\2\0\0\0\0\2" "\0\0\0\0\0\0\0\0\0\1" "\2\0\0\1\2\2\1\0\0\0" "\1\0\0\0\0\0\0\1\0\0" "\0\0\2\0\0\0\0\0\0\2" "\0\0\0\2\1\2\1\0\0\2" "\2\0\0\0\0\0\0\0\0\0" "\2\0\0\0\0\1\1\0\2\0" "\0\0\0\2\0\0\0\0\0\0", 10*10); grid = snewn(10*10, char); printf("%d\n", pearl_solve(10, 10, clues, grid)); #endif grid = snewn(w*h, char); clues = snewn(w*h, char); clueorder = snewn(w*h, int); while (1) { pearl_loopgen(w, h, grid, rs); #ifdef GENERATION_DIAGNOSTICS printf("grid array:\n"); for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { int type = grid[y*w+x]; char s[5], *p = s; if (type & L) *p++ = 'L'; if (type & R) *p++ = 'R'; if (type & U) *p++ = 'U'; if (type & D) *p++ = 'D'; *p = '\0'; printf("%2s ", s); } printf("\n"); } printf("\n"); #endif /* * Set up the maximal clue array. */ for (y = 0; y < h; y++) for (x = 0; x < w; x++) { int type = grid[y*w+x]; clues[y*w+x] = NOCLUE; if ((bLR|bUD) & (1 << type)) { /* * This is a straight; see if it's a viable * candidate for a straight clue. It qualifies if * at least one of the squares it connects to is a * corner. */ for (d = 1; d <= 8; d += d) if (type & d) { int xx = x + DX(d), yy = y + DY(d); assert(xx >= 0 && xx < w && yy >= 0 && yy < h); if ((bLU|bLD|bRU|bRD) & (1 << grid[yy*w+xx])) break; } if (d <= 8) /* we found one */ clues[y*w+x] = STRAIGHT; } else if ((bLU|bLD|bRU|bRD) & (1 << type)) { /* * This is a corner; see if it's a viable candidate * for a corner clue. It qualifies if all the * squares it connects to are straights. */ for (d = 1; d <= 8; d += d) if (type & d) { int xx = x + DX(d), yy = y + DY(d); assert(xx >= 0 && xx < w && yy >= 0 && yy < h); if (!((bLR|bUD) & (1 << grid[yy*w+xx]))) break; } if (d > 8) /* we didn't find a counterexample */ clues[y*w+x] = CORNER; } } #ifdef GENERATION_DIAGNOSTICS printf("clue array:\n"); for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { printf("%c", " *O"[(unsigned char)clues[y*w+x]]); } printf("\n"); } printf("\n"); #endif /* * See if we can solve the puzzle just like this. */ ret = pearl_solve(w, h, clues, grid); assert(ret > 0); /* shouldn't be inconsistent! */ if (ret != 1) continue; /* go round and try again */ /* * Now shuffle the grid points and gradually remove the * clues to find a minimal set which still leaves the * puzzle soluble. */ for (i = 0; i < w*h; i++) clueorder[i] = i; shuffle(clueorder, w*h, sizeof(*clueorder), rs); for (i = 0; i < w*h; i++) { int clue; y = clueorder[i] / w; x = clueorder[i] % w; if (clues[y*w+x] == 0) continue; clue = clues[y*w+x]; clues[y*w+x] = 0; /* try removing this clue */ ret = pearl_solve(w, h, clues, grid); assert(ret > 0); if (ret != 1) clues[y*w+x] = clue; /* oops, put it back again */ } #ifdef FINISHED_PUZZLE printf("clue array:\n"); for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { printf("%c", " *O"[(unsigned char)clues[y*w+x]]); } printf("\n"); } printf("\n"); #endif break; /* got it */ } sfree(grid); sfree(clues); sfree(clueorder); return dupstr("FIXME"); } static char *validate_desc(game_params *params, char *desc) { return NULL; } static game_state *new_game(midend *me, game_params *params, char *desc) { game_state *state = snew(game_state); state->FIXME = 0; return state; } static game_state *dup_game(game_state *state) { game_state *ret = snew(game_state); ret->FIXME = state->FIXME; return ret; } static void free_game(game_state *state) { sfree(state); } static char *solve_game(game_state *state, game_state *currstate, char *aux, char **error) { return NULL; } static int game_can_format_as_text_now(game_params *params) { return TRUE; } static char *game_text_format(game_state *state) { return NULL; } static game_ui *new_ui(game_state *state) { return NULL; } static void free_ui(game_ui *ui) { } static char *encode_ui(game_ui *ui) { return NULL; } static void decode_ui(game_ui *ui, char *encoding) { } static void game_changed_state(game_ui *ui, game_state *oldstate, game_state *newstate) { } struct game_drawstate { int tilesize; int FIXME; }; static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds, int x, int y, int button) { return NULL; } static game_state *execute_move(game_state *state, char *move) { return NULL; } /* ---------------------------------------------------------------------- * Drawing routines. */ static void game_compute_size(game_params *params, int tilesize, int *x, int *y) { *x = *y = 10 * tilesize; /* FIXME */ } static void game_set_size(drawing *dr, game_drawstate *ds, game_params *params, int tilesize) { ds->tilesize = tilesize; } static float *game_colours(frontend *fe, int *ncolours) { float *ret = snewn(3 * NCOLOURS, float); frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); *ncolours = NCOLOURS; return ret; } static game_drawstate *game_new_drawstate(drawing *dr, game_state *state) { struct game_drawstate *ds = snew(struct game_drawstate); ds->tilesize = 0; ds->FIXME = 0; return ds; } static void game_free_drawstate(drawing *dr, game_drawstate *ds) { sfree(ds); } static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate, game_state *state, int dir, game_ui *ui, float animtime, float flashtime) { /* * The initial contents of the window are not guaranteed and * can vary with front ends. To be on the safe side, all games * should start by drawing a big background-colour rectangle * covering the whole window. */ draw_rect(dr, 0, 0, 10*ds->tilesize, 10*ds->tilesize, COL_BACKGROUND); } static float game_anim_length(game_state *oldstate, game_state *newstate, int dir, game_ui *ui) { return 0.0F; } static float game_flash_length(game_state *oldstate, game_state *newstate, int dir, game_ui *ui) { return 0.0F; } static int game_status(game_state *state) { return 0; } static int game_timing_state(game_state *state, game_ui *ui) { return TRUE; } static void game_print_size(game_params *params, float *x, float *y) { } static void game_print(drawing *dr, game_state *state, int tilesize) { } #ifdef COMBINED #define thegame pearl #endif const struct game thegame = { "Pearl", NULL, NULL, default_params, game_fetch_preset, decode_params, encode_params, free_params, dup_params, FALSE, game_configure, custom_params, validate_params, new_game_desc, validate_desc, new_game, dup_game, free_game, FALSE, solve_game, FALSE, game_can_format_as_text_now, game_text_format, new_ui, free_ui, encode_ui, decode_ui, game_changed_state, interpret_move, execute_move, 20 /* FIXME */, game_compute_size, game_set_size, game_colours, game_new_drawstate, game_free_drawstate, game_redraw, game_anim_length, game_flash_length, game_status, FALSE, FALSE, game_print_size, game_print, FALSE, /* wants_statusbar */ FALSE, game_timing_state, 0, /* flags */ };