X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/puzzles/blobdiff_plain/be8d5aa175777c6b6b5a90342afbd8f77c615911..871bf2946c5cbfdc7621c4b3d6850af0699297a0:/fifteen.c diff --git a/fifteen.c b/fifteen.c index 2263bc1..bd791ea 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 ) @@ -41,6 +42,8 @@ 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; }; @@ -70,21 +73,17 @@ static game_params *dup_params(game_params *params) return ret; } -static game_params *decode_params(char const *string) +static void decode_params(game_params *ret, char const *string) { - game_params *ret = default_params(); - ret->w = ret->h = atoi(string); while (*string && isdigit(*string)) string++; if (*string == 'x') { string++; ret->h = atoi(string); } - - return ret; } -static char *encode_params(game_params *params) +static char *encode_params(game_params *params, int full) { char data[256]; @@ -132,7 +131,7 @@ static game_params *custom_params(config_item *cfg) static char *validate_params(game_params *params) { - 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; @@ -152,7 +151,8 @@ static int perm_parity(int *perm, int n) return ret; } -static char *new_game_seed(game_params *params, random_state *rs) +static char *new_game_desc(game_params *params, random_state *rs, + char **aux, int interactive) { int gap, n, i, x; int x1, x2, p1, p2, parity; @@ -244,8 +244,8 @@ static char *new_game_seed(game_params *params, random_state *rs) } /* - * Now construct the game seed, by describing the tile array as - * a simple sequence of comma-separated integers. + * Now construct the game description, by describing the tile + * array as a simple sequence of comma-separated integers. */ ret = NULL; retlen = 0; @@ -267,14 +267,14 @@ static char *new_game_seed(game_params *params, random_state *rs) return ret; } -static char *validate_seed(game_params *params, char *seed) +static char *validate_desc(game_params *params, char *desc) { char *p, *err; int i, area; int *used; area = params->w * params->h; - p = seed; + p = desc; err = NULL; used = snewn(area, int); @@ -318,7 +318,7 @@ static char *validate_seed(game_params *params, char *seed) return err; } -static game_state *new_game(game_params *params, char *seed) +static game_state *new_game(midend_data *me, game_params *params, char *desc) { game_state *state = snew(game_state); int i; @@ -330,7 +330,7 @@ static game_state *new_game(game_params *params, char *seed) state->tiles = snewn(state->n, int); state->gap_pos = 0; - p = seed; + p = desc; i = 0; for (i = 0; i < state->n; i++) { assert(*p); @@ -345,6 +345,7 @@ static game_state *new_game(game_params *params, char *seed) assert(state->tiles[state->gap_pos] == 0); state->completed = state->movecount = 0; + state->used_solve = state->just_used_solve = FALSE; return state; } @@ -361,15 +362,66 @@ static game_state *dup_game(game_state *state) ret->gap_pos = state->gap_pos; 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 char *solve_game(game_state *state, game_state *currstate, + char *aux, char **error) +{ + return dupstr("S"); +} + +static char *game_text_format(game_state *state) +{ + char *ret, *p, buf[80]; + int x, y, col, maxlen; + + /* + * First work out how many characters we need to display each + * number. + */ + col = sprintf(buf, "%d", state->n-1); + + /* + * Now we know the exact total size of the grid we're going to + * produce: it's got h rows, each containing w lots of col, w-1 + * spaces and a trailing newline. + */ + maxlen = state->h * state->w * (col+1); + + ret = snewn(maxlen+1, char); + p = ret; + + for (y = 0; y < state->h; y++) { + for (x = 0; x < state->w; x++) { + int v = state->tiles[state->w*y+x]; + if (v == 0) + sprintf(buf, "%*s", col, ""); + else + sprintf(buf, "%*d", col, v); + memcpy(p, buf, col); + p += col; + if (x+1 == state->w) + *p++ = '\n'; + else + *p++ = ' '; + } + } + + assert(p - ret == maxlen); + *p = '\0'; + return ret; +} + static game_ui *new_ui(game_state *state) { return NULL; @@ -379,27 +431,50 @@ 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(game_ui *ui) { - int gx, gy, dx, dy, ux, uy, up, p; - game_state *ret; + return NULL; +} - gx = X(from, from->gap_pos); - gy = Y(from, from->gap_pos); +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 started; + int w, h, bgcolour; + int *tiles; + int tilesize; +}; + +static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds, + int x, int y, int button) +{ + int gx, gy, dx, dy; + char buf[80]; + + button &= ~MOD_MASK; + + gx = X(state, state->gap_pos); + gy = Y(state, state->gap_pos); if (button == CURSOR_RIGHT && gx > 0) dx = gx - 1, dy = gy; - else if (button == CURSOR_LEFT && gx < from->w-1) + else if (button == CURSOR_LEFT && gx < state->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) + else if (button == CURSOR_UP && gy < state->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) + if (dx < 0 || dx >= state->w || dy < 0 || dy >= state->h) return NULL; /* out of bounds */ /* * Any click location should be equal to the gap location @@ -410,6 +485,45 @@ static game_state *make_move(game_state *from, game_ui *ui, } else return NULL; /* no move */ + sprintf(buf, "M%d,%d", dx, dy); + return dupstr(buf); +} + +static game_state *execute_move(game_state *from, 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 = ret->just_used_solve = TRUE; + ret->completed = ret->movecount = 1; + + return ret; + } + + gx = X(from, from->gap_pos); + gy = Y(from, from->gap_pos); + + 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 * position towards this one. @@ -419,6 +533,7 @@ 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); @@ -448,14 +563,23 @@ 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_size(game_params *params, game_drawstate *ds, + int *x, int *y, int expand) { + double tsx, tsy, ts; + /* + * Each window dimension equals the tile size times one more + * than the grid dimension (the border is half the width of the + * tiles). + */ + tsx = (double)*x / ((double)params->w + 1.0); + tsy = (double)*y / ((double)params->h + 1.0); + ts = min(tsx, tsy); + if (expand) + ds->tilesize = (int)(ts + 0.5); + else + ds->tilesize = min((int)ts, PREFERRED_TILE_SIZE); + *x = TILE_SIZE * params->w + 2 * BORDER; *y = TILE_SIZE * params->h + 2 * BORDER; } @@ -501,6 +625,7 @@ 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; @@ -513,8 +638,8 @@ static void game_free_drawstate(game_drawstate *ds) sfree(ds); } -static void draw_tile(frontend *fe, game_state *state, int x, int y, - int tile, int flash_colour) +static void draw_tile(frontend *fe, game_drawstate *ds, game_state *state, + int x, int y, int tile, int flash_colour) { if (tile == 0) { draw_rect(fe, x, y, TILE_SIZE, TILE_SIZE, @@ -562,7 +687,7 @@ 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, TILE_SIZE * state->w + 2 * BORDER, @@ -578,15 +703,19 @@ 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(fe, coords, 5, TRUE, COL_HIGHLIGHT); + draw_polygon(fe, coords, 5, FALSE, 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(fe, coords, 5, TRUE, COL_LOWLIGHT); + draw_polygon(fe, coords, 5, FALSE, COL_LOWLIGHT); ds->started = TRUE; } @@ -671,7 +800,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(fe, ds, state, x, y, t, bgcolour); } ds->tiles[i] = t0; } @@ -691,24 +820,33 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate, if (oldstate) state = oldstate; - sprintf(statusbuf, "%sMoves: %d", - (state->completed ? "COMPLETED! " : ""), - (state->completed ? state->completed : state->movecount)); + if (state->used_solve) + sprintf(statusbuf, "Moves since auto-solve: %d", + state->movecount - state->completed); + else + sprintf(statusbuf, "%sMoves: %d", + (state->completed ? "COMPLETED! " : ""), + (state->completed ? state->completed : state->movecount)); status_bar(fe, statusbuf); } } static float game_anim_length(game_state *oldstate, - game_state *newstate, int dir) + game_state *newstate, int dir, game_ui *ui) { - return ANIM_TIME; + if ((dir > 0 && newstate->just_used_solve) || + (dir < 0 && oldstate->just_used_solve)) + return 0.0F; + else + return ANIM_TIME; } static float game_flash_length(game_state *oldstate, - game_state *newstate, int dir) + game_state *newstate, int dir, game_ui *ui) { - if (!oldstate->completed && newstate->completed) + if (!oldstate->completed && newstate->completed && + !oldstate->used_solve && !newstate->used_solve) return 2 * FLASH_FRAME; else return 0.0F; @@ -719,29 +857,39 @@ static int game_wants_statusbar(void) return TRUE; } +static int game_timing_state(game_state *state) +{ + return TRUE; +} + #ifdef COMBINED #define thegame fifteen #endif const struct game thegame = { - "Fifteen", "games.fifteen", TRUE, + "Fifteen", "games.fifteen", default_params, game_fetch_preset, decode_params, encode_params, free_params, dup_params, - game_configure, - custom_params, + TRUE, game_configure, custom_params, validate_params, - new_game_seed, - validate_seed, + new_game_desc, + validate_desc, new_game, dup_game, free_game, + TRUE, solve_game, + TRUE, game_text_format, new_ui, free_ui, - make_move, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, game_size, game_colours, game_new_drawstate, @@ -750,4 +898,6 @@ const struct game thegame = { game_anim_length, game_flash_length, game_wants_statusbar, + FALSE, game_timing_state, + 0, /* mouse_priorities */ };