/* * 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 #include #include #include #include #include #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) { #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 int done_something = FALSE; /* * 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<= 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<= 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 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_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_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, FALSE, FALSE, game_print_size, game_print, FALSE, /* wants_statusbar */ FALSE, game_timing_state, 0, /* flags */ };