X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?a=blobdiff_plain;f=fifteen.c;h=648271489da6635ad39c3333c6c535a1026f49d9;hb=3ce69e84cad15844282d691fa03e711c5353c05e;hp=9d138b15a6c09d340bf343e2b5f22296b7467391;hpb=2534ec5d695a363775f73d62f153f946da01fa69;p=sgt-puzzles.git diff --git a/fifteen.c b/fifteen.c index 9d138b1..6482714 100644 --- a/fifteen.c +++ b/fifteen.c @@ -11,7 +11,8 @@ #include "puzzles.h" -#define TILE_SIZE 48 +#define PREFERRED_TILE_SIZE 48 +#define TILE_SIZE (ds->tilesize) #define BORDER (TILE_SIZE / 2) #define HIGHLIGHT_WIDTH (TILE_SIZE / 20) #define COORD(x) ( (x) * TILE_SIZE + BORDER ) @@ -24,6 +25,11 @@ #define Y(state, i) ( (i) / (state)->w ) #define C(state, x, y) ( (y) * (state)->w + (x) ) +#define PARITY_P(params, gap) (((X((params), (gap)) - ((params)->w - 1)) ^ \ + (Y((params), (gap)) - ((params)->h - 1)) ^ \ + (((params)->w * (params)->h) + 1)) & 1) +#define PARITY_S(state) PARITY_P((state), ((state)->gap_pos)) + enum { COL_BACKGROUND, COL_TEXT, @@ -41,7 +47,6 @@ struct game_state { int *tiles; int gap_pos; int completed; - int just_used_solve; /* used to suppress undo animation */ int used_solve; /* used to suppress completion flash */ int movecount; }; @@ -57,6 +62,11 @@ static game_params *default_params(void) static int game_fetch_preset(int i, char **name, game_params **params) { + if (i == 0) { + *params = default_params(); + *name = dupstr("4x4"); + return TRUE; + } return FALSE; } @@ -65,7 +75,7 @@ static void free_params(game_params *params) sfree(params); } -static game_params *dup_params(game_params *params) +static game_params *dup_params(const game_params *params) { game_params *ret = snew(game_params); *ret = *params; /* structure copy */ @@ -75,14 +85,14 @@ static game_params *dup_params(game_params *params) static void decode_params(game_params *ret, char const *string) { ret->w = ret->h = atoi(string); - while (*string && isdigit(*string)) string++; + while (*string && isdigit((unsigned char)*string)) string++; if (*string == 'x') { string++; ret->h = atoi(string); } } -static char *encode_params(game_params *params, int full) +static char *encode_params(const game_params *params, int full) { char data[256]; @@ -91,7 +101,7 @@ static char *encode_params(game_params *params, int full) return dupstr(data); } -static config_item *game_configure(game_params *params) +static config_item *game_configure(const game_params *params) { config_item *ret; char buf[80]; @@ -118,7 +128,7 @@ static config_item *game_configure(game_params *params) return ret; } -static game_params *custom_params(config_item *cfg) +static game_params *custom_params(const config_item *cfg) { game_params *ret = snew(game_params); @@ -128,9 +138,9 @@ static game_params *custom_params(config_item *cfg) return ret; } -static char *validate_params(game_params *params) +static char *validate_params(const game_params *params, int full) { - if (params->w < 2 && params->h < 2) + if (params->w < 2 || params->h < 2) return "Width and height must both be at least two"; return NULL; @@ -150,8 +160,8 @@ static int perm_parity(int *perm, int n) return ret; } -static char *new_game_desc(game_params *params, random_state *rs, - game_aux_info **aux) +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) { int gap, n, i, x; int x1, x2, p1, p2, parity; @@ -226,9 +236,7 @@ static char *new_game_desc(game_params *params, random_state *rs, * 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; + parity = PARITY_P(params, gap); /* * Try the last two tiles one way round. If that fails, swap @@ -266,14 +274,10 @@ static char *new_game_desc(game_params *params, random_state *rs, return ret; } -static void game_free_aux_info(game_aux_info *aux) -{ - assert(!"Shouldn't happen"); -} - -static char *validate_desc(game_params *params, char *desc) +static char *validate_desc(const game_params *params, const char *desc) { - char *p, *err; + const char *p; + char *err; int i, area; int *used; @@ -286,7 +290,7 @@ static char *validate_desc(game_params *params, char *desc) used[i] = FALSE; for (i = 0; i < area; i++) { - char *q = p; + const char *q = p; int n; if (*p < '0' || *p > '9') { @@ -322,11 +326,12 @@ static char *validate_desc(game_params *params, char *desc) return err; } -static game_state *new_game(game_params *params, char *desc) +static game_state *new_game(midend *me, const game_params *params, + const char *desc) { game_state *state = snew(game_state); int i; - char *p; + const char *p; state->w = params->w; state->h = params->h; @@ -349,12 +354,12 @@ static game_state *new_game(game_params *params, char *desc) assert(state->tiles[state->gap_pos] == 0); state->completed = state->movecount = 0; - state->used_solve = state->just_used_solve = FALSE; + state->used_solve = FALSE; return state; } -static game_state *dup_game(game_state *state) +static game_state *dup_game(const game_state *state) { game_state *ret = snew(game_state); @@ -367,39 +372,28 @@ static game_state *dup_game(game_state *state) ret->completed = state->completed; ret->movecount = state->movecount; ret->used_solve = state->used_solve; - ret->just_used_solve = state->just_used_solve; return ret; } static void free_game(game_state *state) { + sfree(state->tiles); sfree(state); } -static game_state *solve_game(game_state *state, game_aux_info *aux, - char **error) +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) { - game_state *ret = dup_game(state); - int i; - - /* - * Simply replace the grid with a solved one. For this game, - * this isn't a useful operation for actually telling the user - * what they should have done, but it is useful for - * conveniently being able to get hold of a clean state from - * which to practise manoeuvres. - */ - for (i = 0; i < ret->n; i++) - ret->tiles[i] = (i+1) % ret->n; - ret->gap_pos = ret->n-1; - ret->used_solve = ret->just_used_solve = TRUE; - ret->completed = ret->movecount = 1; + return dupstr("S"); +} - return ret; +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; } -static char *game_text_format(game_state *state) +static char *game_text_format(const game_state *state) { char *ret, *p, buf[80]; int x, y, col, maxlen; @@ -441,7 +435,7 @@ static char *game_text_format(game_state *state) return ret; } -static game_ui *new_ui(game_state *state) +static game_ui *new_ui(const game_state *state) { return NULL; } @@ -450,36 +444,326 @@ static void free_ui(game_ui *ui) { } -static game_state *make_move(game_state *from, game_ui *ui, - int x, int y, int button) +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ +} + +struct game_drawstate { + int started; + int w, h, bgcolour; + int *tiles; + int tilesize; +}; + +static int flip_cursor(int button) +{ + switch (button) { + case CURSOR_UP: return CURSOR_DOWN; + case CURSOR_DOWN: return CURSOR_UP; + case CURSOR_LEFT: return CURSOR_RIGHT; + case CURSOR_RIGHT: return CURSOR_LEFT; + } + return 0; +} + +static void next_move_3x2(int ax, int ay, int bx, int by, + int gx, int gy, int *dx, int *dy) +{ + /* When w = 3 and h = 2 and the tile going in the top left corner + * is at (ax, ay) and the tile going in the bottom left corner is + * at (bx, by) and the blank tile is at (gx, gy), how do you move? */ + + /* Hard-coded shortest solutions. Sorry. */ + static const unsigned char move[120] = { + 1,2,0,1,2,2, + 2,0,0,2,0,0, + 0,0,2,0,2,0, + 0,0,0,2,0,2, + 2,0,0,0,2,0, + + 0,3,0,1,1,1, + 3,0,3,2,1,2, + 2,1,1,0,1,0, + 2,1,2,1,0,1, + 1,2,0,2,1,2, + + 0,1,3,1,3,0, + 1,3,1,3,0,3, + 0,0,3,3,0,0, + 0,0,0,1,2,1, + 3,0,0,1,1,1, + + 3,1,1,1,3,0, + 1,1,1,1,1,1, + 1,3,1,1,3,0, + 1,1,3,3,1,3, + 1,3,0,0,0,0 + }; + static const struct { int dx, dy; } d[4] = {{+1,0},{-1,0},{0,+1},{0,-1}}; + + int ea = 3*ay + ax, eb = 3*by + bx, eg = 3*gy + gx, v; + if (eb > ea) --eb; + if (eg > ea) --eg; + if (eg > eb) --eg; + v = move[ea + eb*6 + eg*5*6]; + *dx = d[v].dx; + *dy = d[v].dy; +} + +static void next_move(int nx, int ny, int ox, int oy, int gx, int gy, + int tx, int ty, int w, int *dx, int *dy) +{ + const int to_tile_x = (gx < nx ? +1 : -1); + const int to_goal_x = (gx < tx ? +1 : -1); + const int gap_x_on_goal_side = ((nx-tx) * (nx-gx) > 0); + + assert (nx != tx || ny != ty); /* not already in place */ + assert (nx != gx || ny != gy); /* not placing the gap */ + assert (ty <= ny); /* because we're greedy (and flipping) */ + assert (ty <= gy); /* because we're greedy (and flipping) */ + + /* TODO: define a termination function. Idea: 0 if solved, or + * the number of moves to solve the next piece plus the number of + * further unsolved pieces times an upper bound on the number of + * moves required to solve any piece. If such a function can be + * found, we have (termination && (termination => correctness)). + * The catch is our temporary disturbance of 2x3 corners. */ + + /* handles end-of-row, when 3 and 4 are in the top right 2x3 box */ + if (tx == w - 2 && + ny <= ty + 2 && (nx == tx || nx == tx + 1) && + oy <= ty + 2 && (ox == tx || ox == tx + 1) && + gy <= ty + 2 && (gx == tx || gx == tx + 1)) + { + next_move_3x2(oy - ty, tx + 1 - ox, + ny - ty, tx + 1 - nx, + gy - ty, tx + 1 - gx, dy, dx); + *dx *= -1; + return; + } + + if (tx == w - 1) { + if (ny <= ty + 2 && (nx == tx || nx == tx - 1) && + gy <= ty + 2 && (gx == tx || gx == tx - 1)) { + next_move_3x2(ny - ty, tx - nx, 0, 1, gy - ty, tx - gx, dy, dx); + *dx *= -1; + } else if (gy == ty) + *dy = +1; + else if (nx != tx || ny != ty + 1) { + next_move((w - 1) - nx, ny, -1, -1, (w - 1) - gx, gy, + 0, ty + 1, -1, dx, dy); + *dx *= -1; + } else if (gx == nx) + *dy = -1; + else + *dx = +1; + return; + } + + /* note that *dy = -1 is unsafe when gy = ty + 1 and gx < tx */ + if (gy < ny) + if (nx == gx || (gy == ty && gx == tx)) + *dy = +1; + else if (!gap_x_on_goal_side) + *dx = to_tile_x; + else if (ny - ty > abs(nx - tx)) + *dx = to_tile_x; + else *dy = +1; + + else if (gy == ny) + if (nx == tx) /* then we know ny > ty */ + if (gx > nx || ny > ty + 1) + *dy = -1; /* ... so this is safe */ + else + *dy = +1; + else if (gap_x_on_goal_side) + *dx = to_tile_x; + else if (gy == ty || (gy == ty + 1 && gx < tx)) + *dy = +1; + else + *dy = -1; + + else if (nx == tx) /* gy > ny */ + if (gx > nx) + *dy = -1; + else + *dx = +1; + else if (gx == nx) + *dx = to_goal_x; + else if (gap_x_on_goal_side) + if (gy == ty + 1 && gx < tx) + *dx = to_tile_x; + else + *dy = -1; + + else if (ny - ty > abs(nx - tx)) + *dy = -1; + else + *dx = to_tile_x; +} + +static int compute_hint(const game_state *state, int *out_x, int *out_y) +{ + /* The overall solving process is this: + * 1. Find the next piece to be put in its place + * 2. Move it diagonally towards its place + * 3. Move it horizontally or vertically towards its place + * (Modulo the last two tiles at the end of each row/column) + */ + + int gx = X(state, state->gap_pos); + int gy = Y(state, state->gap_pos); + + int tx, ty, nx, ny, ox, oy, /* {target,next,next2}_{x,y} */ i; + int dx = 0, dy = 0; + + /* 1. Find the next piece + * if (there are no more unfinished columns than rows) { + * fill the top-most row, left to right + * } else { fill the left-most column, top to bottom } + */ + const int w = state->w, h = state->h, n = w*h; + int next_piece = 0, next_piece_2 = 0, solr = 0, solc = 0; + int unsolved_rows = h, unsolved_cols = w; + + assert(out_x); + assert(out_y); + + while (solr < h && solc < w) { + int start, step, stop; + if (unsolved_cols <= unsolved_rows) + start = solr*w + solc, step = 1, stop = unsolved_cols; + else + start = solr*w + solc, step = w, stop = unsolved_rows; + for (i = 0; i < stop; ++i) { + const int j = start + i*step; + if (state->tiles[j] != j + 1) { + next_piece = j + 1; + next_piece_2 = next_piece + step; + break; + } + } + if (i < stop) break; + + (unsolved_cols <= unsolved_rows) + ? (++solr, --unsolved_rows) + : (++solc, --unsolved_cols); + } + + if (next_piece == n) + return FALSE; + + /* 2, 3. Move the next piece towards its place */ + + /* gx, gy already set */ + tx = X(state, next_piece - 1); /* where we're going */ + ty = Y(state, next_piece - 1); + for (i = 0; i < n && state->tiles[i] != next_piece; ++i); + nx = X(state, i); /* where we're at */ + ny = Y(state, i); + for (i = 0; i < n && state->tiles[i] != next_piece_2; ++i); + ox = X(state, i); + oy = Y(state, i); + + if (unsolved_cols <= unsolved_rows) + next_move(nx, ny, ox, oy, gx, gy, tx, ty, w, &dx, &dy); + else + next_move(ny, nx, oy, ox, gy, gx, ty, tx, h, &dy, &dx); + + assert (dx || dy); + + *out_x = gx + dx; + *out_y = gy + dy; + return TRUE; +} + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + int cx = X(state, state->gap_pos), nx = cx; + int cy = Y(state, state->gap_pos), ny = cy; + char buf[80]; + + button &= ~MOD_MASK; + + if (button == LEFT_BUTTON) { + nx = FROMCOORD(x); + ny = FROMCOORD(y); + if (nx < 0 || nx >= state->w || ny < 0 || ny >= state->h) + return NULL; /* out of bounds */ + } else if (IS_CURSOR_MOVE(button)) { + static int invert_cursor = -1; + if (invert_cursor == -1) { + char *env = getenv("FIFTEEN_INVERT_CURSOR"); + invert_cursor = (env && (env[0] == 'y' || env[0] == 'Y')); + } + button = flip_cursor(button); /* the default */ + if (invert_cursor) + button = flip_cursor(button); /* undoes the first flip */ + move_cursor(button, &nx, &ny, state->w, state->h, FALSE); + } else if ((button == 'h' || button == 'H') && !state->completed) { + if (!compute_hint(state, &nx, &ny)) + return NULL; /* shouldn't happen, since ^^we^^checked^^ */ + } else + return NULL; /* no move */ + + /* + * Any click location should be equal to the gap location + * in _precisely_ one coordinate. + */ + if ((cx == nx) ^ (cy == ny)) { + sprintf(buf, "M%d,%d", nx, ny); + return dupstr(buf); + } + + return NULL; +} + +static game_state *execute_move(const game_state *from, const char *move) { int gx, gy, dx, dy, ux, uy, up, p; game_state *ret; + if (!strcmp(move, "S")) { + int i; + + ret = dup_game(from); + + /* + * Simply replace the grid with a solved one. For this game, + * this isn't a useful operation for actually telling the user + * what they should have done, but it is useful for + * conveniently being able to get hold of a clean state from + * which to practise manoeuvres. + */ + for (i = 0; i < ret->n; i++) + ret->tiles[i] = (i+1) % ret->n; + ret->gap_pos = ret->n-1; + ret->used_solve = TRUE; + ret->completed = ret->movecount = 1; + + return ret; + } + gx = X(from, from->gap_pos); gy = Y(from, from->gap_pos); - if (button == CURSOR_RIGHT && gx > 0) - dx = gx - 1, dy = gy; - else if (button == CURSOR_LEFT && gx < from->w-1) - dx = gx + 1, dy = gy; - else if (button == CURSOR_DOWN && gy > 0) - dy = gy - 1, dx = gx; - else if (button == CURSOR_UP && gy < from->h-1) - dy = gy + 1, dx = gx; - else if (button == LEFT_BUTTON) { - dx = FROMCOORD(x); - dy = FROMCOORD(y); - if (dx < 0 || dx >= from->w || dy < 0 || dy >= from->h) - return NULL; /* out of bounds */ - /* - * Any click location should be equal to the gap location - * in _precisely_ one coordinate. - */ - if ((dx == gx && dy == gy) || (dx != gx && dy != gy)) - return NULL; - } else - return NULL; /* no move */ + if (move[0] != 'M' || + sscanf(move+1, "%d,%d", &dx, &dy) != 2 || + (dx == gx && dy == gy) || (dx != gx && dy != gy) || + dx < 0 || dx >= from->w || dy < 0 || dy >= from->h) + return NULL; /* * Find the unit displacement from the original gap @@ -490,7 +774,6 @@ static game_state *make_move(game_state *from, game_ui *ui, up = C(from, ux, uy); ret = dup_game(from); - ret->just_used_solve = FALSE; /* zero this in a hurry */ ret->gap_pos = C(from, dx, dy); assert(ret->gap_pos >= 0 && ret->gap_pos < ret->n); @@ -520,50 +803,38 @@ static game_state *make_move(game_state *from, game_ui *ui, * Drawing routines. */ -struct game_drawstate { - int started; - int w, h, bgcolour; - int *tiles; -}; - -static void game_size(game_params *params, int *x, int *y) +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) { + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + struct { int tilesize; } ads, *ds = &ads; + ads.tilesize = tilesize; + *x = TILE_SIZE * params->w + 2 * BORDER; *y = TILE_SIZE * params->h + 2 * BORDER; } -static float *game_colours(frontend *fe, game_state *state, int *ncolours) +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +static float *game_colours(frontend *fe, int *ncolours) { float *ret = snewn(3 * NCOLOURS, float); int i; - float max; - - frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); - /* - * Drop the background colour so that the highlight is - * noticeably brighter than it while still being under 1. - */ - max = ret[COL_BACKGROUND*3]; - for (i = 1; i < 3; i++) - if (ret[COL_BACKGROUND*3+i] > max) - max = ret[COL_BACKGROUND*3+i]; - if (max * 1.2F > 1.0F) { - for (i = 0; i < 3; i++) - ret[COL_BACKGROUND*3+i] /= (max * 1.2F); - } + game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT); - for (i = 0; i < 3; i++) { - ret[COL_HIGHLIGHT * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 1.2F; - ret[COL_LOWLIGHT * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 0.8F; + for (i = 0; i < 3; i++) ret[COL_TEXT * 3 + i] = 0.0; - } *ncolours = NCOLOURS; return ret; } -static game_drawstate *game_new_drawstate(game_state *state) +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) { struct game_drawstate *ds = snew(struct game_drawstate); int i; @@ -573,23 +844,24 @@ static game_drawstate *game_new_drawstate(game_state *state) ds->h = state->h; ds->bgcolour = COL_BACKGROUND; ds->tiles = snewn(ds->w*ds->h, int); + ds->tilesize = 0; /* haven't decided yet */ for (i = 0; i < ds->w*ds->h; i++) ds->tiles[i] = -1; return ds; } -static void game_free_drawstate(game_drawstate *ds) +static void game_free_drawstate(drawing *dr, game_drawstate *ds) { sfree(ds->tiles); sfree(ds); } -static void draw_tile(frontend *fe, game_state *state, int x, int y, - int tile, int flash_colour) +static void draw_tile(drawing *dr, game_drawstate *ds, const game_state *state, + int x, int y, int tile, int flash_colour) { if (tile == 0) { - draw_rect(fe, x, y, TILE_SIZE, TILE_SIZE, + draw_rect(dr, x, y, TILE_SIZE, TILE_SIZE, flash_colour); } else { int coords[6]; @@ -601,29 +873,28 @@ static void draw_tile(frontend *fe, game_state *state, int x, int y, coords[3] = y; coords[4] = x; coords[5] = y + TILE_SIZE - 1; - draw_polygon(fe, coords, 3, TRUE, COL_LOWLIGHT); - draw_polygon(fe, coords, 3, FALSE, COL_LOWLIGHT); + draw_polygon(dr, coords, 3, COL_LOWLIGHT, COL_LOWLIGHT); coords[0] = x; coords[1] = y; - draw_polygon(fe, coords, 3, TRUE, COL_HIGHLIGHT); - draw_polygon(fe, coords, 3, FALSE, COL_HIGHLIGHT); + draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT); - draw_rect(fe, x + HIGHLIGHT_WIDTH, y + HIGHLIGHT_WIDTH, + draw_rect(dr, x + HIGHLIGHT_WIDTH, y + HIGHLIGHT_WIDTH, TILE_SIZE - 2*HIGHLIGHT_WIDTH, TILE_SIZE - 2*HIGHLIGHT_WIDTH, flash_colour); sprintf(str, "%d", tile); - draw_text(fe, x + TILE_SIZE/2, y + TILE_SIZE/2, + draw_text(dr, x + TILE_SIZE/2, y + TILE_SIZE/2, FONT_VARIABLE, TILE_SIZE/3, ALIGN_VCENTRE | ALIGN_HCENTRE, COL_TEXT, str); } - draw_update(fe, x, y, TILE_SIZE, TILE_SIZE); + draw_update(dr, x, y, TILE_SIZE, TILE_SIZE); } -static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate, - game_state *state, int dir, game_ui *ui, - float animtime, float flashtime) +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) { int i, pass, bgcolour; @@ -634,12 +905,12 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate, bgcolour = COL_BACKGROUND; if (!ds->started) { - int coords[6]; + int coords[10]; - draw_rect(fe, 0, 0, + draw_rect(dr, 0, 0, TILE_SIZE * state->w + 2 * BORDER, TILE_SIZE * state->h + 2 * BORDER, COL_BACKGROUND); - draw_update(fe, 0, 0, + draw_update(dr, 0, 0, TILE_SIZE * state->w + 2 * BORDER, TILE_SIZE * state->h + 2 * BORDER); @@ -650,15 +921,17 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate, coords[1] = COORD(state->h) + HIGHLIGHT_WIDTH - 1; coords[2] = COORD(state->w) + HIGHLIGHT_WIDTH - 1; coords[3] = COORD(0) - HIGHLIGHT_WIDTH; - coords[4] = COORD(0) - HIGHLIGHT_WIDTH; - coords[5] = COORD(state->h) + HIGHLIGHT_WIDTH - 1; - draw_polygon(fe, coords, 3, TRUE, COL_HIGHLIGHT); - draw_polygon(fe, coords, 3, FALSE, COL_HIGHLIGHT); + coords[4] = coords[2] - TILE_SIZE; + coords[5] = coords[3] + TILE_SIZE; + coords[8] = COORD(0) - HIGHLIGHT_WIDTH; + coords[9] = COORD(state->h) + HIGHLIGHT_WIDTH - 1; + coords[6] = coords[8] + TILE_SIZE; + coords[7] = coords[9] - TILE_SIZE; + draw_polygon(dr, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT); coords[1] = COORD(0) - HIGHLIGHT_WIDTH; coords[0] = COORD(0) - HIGHLIGHT_WIDTH; - draw_polygon(fe, coords, 3, TRUE, COL_LOWLIGHT); - draw_polygon(fe, coords, 3, FALSE, COL_LOWLIGHT); + draw_polygon(dr, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT); ds->started = TRUE; } @@ -743,7 +1016,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate, y = COORD(Y(state, i)); } - draw_tile(fe, state, x, y, t, bgcolour); + draw_tile(dr, ds, state, x, y, t, bgcolour); } ds->tiles[i] = t0; } @@ -771,22 +1044,18 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate, (state->completed ? "COMPLETED! " : ""), (state->completed ? state->completed : state->movecount)); - status_bar(fe, statusbuf); + status_bar(dr, statusbuf); } } -static float game_anim_length(game_state *oldstate, - game_state *newstate, int dir) +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) { - if ((dir > 0 && newstate->just_used_solve) || - (dir < 0 && oldstate->just_used_solve)) - return 0.0F; - else - return ANIM_TIME; + return ANIM_TIME; } -static float game_flash_length(game_state *oldstate, - game_state *newstate, int dir) +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) { if (!oldstate->completed && newstate->completed && !oldstate->used_solve && !newstate->used_solve) @@ -795,17 +1064,30 @@ static float game_flash_length(game_state *oldstate, return 0.0F; } -static int game_wants_statusbar(void) +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) { return TRUE; } +static void game_print_size(const game_params *params, float *x, float *y) +{ +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ +} + #ifdef COMBINED #define thegame fifteen #endif const struct game thegame = { - "Fifteen", "games.fifteen", + "Fifteen", "games.fifteen", "fifteen", default_params, game_fetch_preset, decode_params, @@ -815,22 +1097,119 @@ const struct game thegame = { TRUE, game_configure, custom_params, validate_params, new_game_desc, - game_free_aux_info, validate_desc, new_game, dup_game, free_game, TRUE, solve_game, - TRUE, game_text_format, + TRUE, game_can_format_as_text_now, game_text_format, new_ui, free_ui, - make_move, - game_size, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILE_SIZE, game_compute_size, game_set_size, game_colours, game_new_drawstate, game_free_drawstate, game_redraw, game_anim_length, game_flash_length, - game_wants_statusbar, + game_status, + FALSE, FALSE, game_print_size, game_print, + TRUE, /* wants_statusbar */ + FALSE, game_timing_state, + 0, /* flags */ }; + +#ifdef STANDALONE_SOLVER + +int main(int argc, char **argv) +{ + game_params *params; + game_state *state; + char *id = NULL, *desc, *err; + int grade = FALSE; + char *progname = argv[0]; + + char buf[80]; + int limit, x, y, solvable; + + while (--argc > 0) { + char *p = *++argv; + if (!strcmp(p, "-v")) { + /* solver_show_working = TRUE; */ + } else if (!strcmp(p, "-g")) { + grade = TRUE; + } else if (*p == '-') { + fprintf(stderr, "%s: unrecognised option `%s'\n", progname, p); + return 1; + } else { + id = p; + } + } + + if (!id) { + fprintf(stderr, "usage: %s [-g | -v] \n", argv[0]); + return 1; + } + + desc = strchr(id, ':'); + if (!desc) { + fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]); + return 1; + } + *desc++ = '\0'; + + params = default_params(); + decode_params(params, id); + err = validate_desc(params, desc); + if (err) { + free_params(params); + fprintf(stderr, "%s: %s\n", argv[0], err); + return 1; + } + + state = new_game(NULL, params, desc); + free_params(params); + + solvable = (PARITY_S(state) == perm_parity(state->tiles, state->n)); + if (grade || !solvable) { + free_game(state); + fputs(solvable ? "Game is solvable" : "Game is unsolvable", + grade ? stdout : stderr); + return !grade; + } + + for (limit = 5 * state->n * state->n * state->n; limit; --limit) { + game_state *next_state; + if (!compute_hint(state, &x, &y)) { + fprintf(stderr, "couldn't compute next move while solving %s:%s", + id, desc); + return 1; + } + printf("Move the space to (%d, %d), moving %d into the space\n", + x + 1, y + 1, state->tiles[C(state, x, y)]); + sprintf(buf, "M%d,%d", x, y); + next_state = execute_move(state, buf); + + free_game(state); + if (!next_state) { + fprintf(stderr, "invalid move when solving %s:%s\n", id, desc); + return 1; + } + state = next_state; + if (next_state->completed) { + free_game(state); + return 0; + } + } + + free_game(state); + fprintf(stderr, "ran out of moves for %s:%s\n", id, desc); + return 1; +} + +#endif