X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/puzzles/blobdiff_plain/370cbbac43638ae4e2148c4d637ba08d83c966b6..b760b8bdf1ff5b603e3076c8e21cf246d08937f7:/unfinished/pearl.c diff --git a/unfinished/pearl.c b/unfinished/pearl.c deleted file mode 100644 index 51b2ed2..0000000 --- a/unfinished/pearl.c +++ /dev/null @@ -1,1410 +0,0 @@ -/* - * 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) { - 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<= 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 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 */ -};