X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/puzzles/blobdiff_plain/d50832a37e1f285555051de42569505bab2984a3..48a10826fef7777bb8b061f4a121f481ced98bc0:/twiddle.c diff --git a/twiddle.c b/twiddle.c index 44a1e39..b03ca01 100644 --- a/twiddle.c +++ b/twiddle.c @@ -46,6 +46,8 @@ struct game_state { int orientable; int *grid; int completed; + int just_used_solve; /* used to suppress undo animation */ + int used_solve; /* used to suppress completion flash */ int movecount; int lastx, lasty, lastr; /* coordinates of last rotation */ }; @@ -291,7 +293,8 @@ static int grid_complete(int *grid, int wh, int orientable) return ok; } -static char *new_game_seed(game_params *params, random_state *rs) +static char *new_game_seed(game_params *params, random_state *rs, + game_aux_info **aux) { int *grid; int w = params->w, h = params->h, n = params->n, wh = w*h; @@ -314,7 +317,7 @@ static char *new_game_seed(game_params *params, random_state *rs) * and simply shuffle the grid by making a long sequence of * randomly chosen moves. */ - total_moves = w*h*n*n*2; + total_moves = w*h*n*n*2 + random_upto(rs, 1); for (i = 0; i < total_moves; i++) { int x, y; @@ -334,7 +337,9 @@ static char *new_game_seed(game_params *params, random_state *rs) /* * Now construct the game seed, by describing the grid as a - * simple sequence of comma-separated integers. + * simple sequence of integers. They're comma-separated, unless + * the puzzle is orientable in which case they're separated by + * orientation letters `u', `d', `l' and `r'. */ ret = NULL; retlen = 0; @@ -342,18 +347,25 @@ static char *new_game_seed(game_params *params, random_state *rs) char buf[80]; int k; - k = sprintf(buf, "%d,", grid[i]); + k = sprintf(buf, "%d%c", grid[i] / 4, + params->orientable ? "uldr"[grid[i] & 3] : ','); ret = sresize(ret, retlen + k + 1, char); strcpy(ret + retlen, buf); retlen += k; } - ret[retlen-1] = '\0'; /* delete last comma */ + if (!params->orientable) + ret[retlen-1] = '\0'; /* delete last comma */ sfree(grid); return ret; } +static void game_free_aux_info(game_aux_info *aux) +{ + assert(!"Shouldn't happen"); +} + static char *validate_seed(game_params *params, char *seed) { char *p, *err; @@ -364,15 +376,17 @@ static char *validate_seed(game_params *params, char *seed) err = NULL; for (i = 0; i < wh; i++) { - if (*p < '0' || *p > '9') { + if (*p < '0' || *p > '9') return "Not enough numbers in string"; - } while (*p >= '0' && *p <= '9') p++; - if (i < wh-1 && *p != ',') { - return "Expected comma after number"; - } - else if (i == wh-1 && *p) { + if (!params->orientable && i < wh-1) { + if (*p != ',') + return "Expected comma after number"; + } else if (params->orientable && i < wh) { + if (*p != 'l' && *p != 'r' && *p != 'u' && *p != 'd') + return "Expected orientation letter after number"; + } else if (i == wh-1 && *p) { return "Excess junk at end of string"; } @@ -394,6 +408,7 @@ static game_state *new_game(game_params *params, char *seed) state->n = n; state->orientable = params->orientable; state->completed = 0; + state->used_solve = state->just_used_solve = FALSE; state->movecount = 0; state->lastx = state->lasty = state->lastr = -1; @@ -402,11 +417,19 @@ static game_state *new_game(game_params *params, char *seed) p = seed; for (i = 0; i < wh; i++) { - state->grid[i] = atoi(p); + state->grid[i] = 4 * atoi(p); while (*p >= '0' && *p <= '9') p++; - - if (*p) p++; /* eat comma */ + if (*p) { + if (params->orientable) { + switch (*p) { + case 'l': state->grid[i] |= 1; break; + case 'd': state->grid[i] |= 2; break; + case 'r': state->grid[i] |= 3; break; + } + } + p++; + } } return state; @@ -425,6 +448,8 @@ static game_state *dup_game(game_state *state) ret->lastx = state->lastx; ret->lasty = state->lasty; ret->lastr = state->lastr; + ret->used_solve = state->used_solve; + ret->just_used_solve = state->just_used_solve; ret->grid = snewn(ret->w * ret->h, int); memcpy(ret->grid, state->grid, ret->w * ret->h * sizeof(int)); @@ -438,6 +463,87 @@ static void free_game(game_state *state) sfree(state); } +static int compare_int(const void *av, const void *bv) +{ + const int *a = (const int *)av; + const int *b = (const int *)bv; + if (*a < *b) + return -1; + else if (*a > *b) + return +1; + else + return 0; +} + +static game_state *solve_game(game_state *state, game_aux_info *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. + */ + qsort(ret->grid, ret->w*ret->h, sizeof(int), compare_int); + for (i = 0; i < ret->w*ret->h; i++) + ret->grid[i] &= ~3; + ret->used_solve = ret->just_used_solve = TRUE; + ret->completed = ret->movecount; + + return ret; +} + +static char *game_text_format(game_state *state) +{ + char *ret, *p, buf[80]; + int i, x, y, col, o, maxlen; + + /* + * First work out how many characters we need to display each + * number. We're pretty flexible on grid contents here, so we + * have to scan the entire grid. + */ + col = 0; + for (i = 0; i < state->w * state->h; i++) { + x = sprintf(buf, "%d", state->grid[i] / 4); + if (col < x) col = x; + } + o = (state->orientable ? 1 : 0); + + /* + * 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+o, + * w-1 spaces and a trailing newline. + */ + maxlen = state->h * state->w * (col+o+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->grid[state->w*y+x]; + sprintf(buf, "%*d", col, v/4); + memcpy(p, buf, col); + p += col; + if (o) + *p++ = "^"[v & 3]; + 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; @@ -471,6 +577,7 @@ static game_state *make_move(game_state *from, game_ui *ui, int x, int y, * This is a valid move. Make it. */ ret = dup_game(from); + ret->just_used_solve = FALSE; /* zero this in a hurry */ ret->movecount++; dir = (button == LEFT_BUTTON ? 1 : -1); do_rotate(ret->grid, w, h, n, ret->orientable, x, y, dir); @@ -685,14 +792,14 @@ static void draw_tile(frontend *fe, game_state *state, int x, int y, displ = TILE_SIZE / 2 - HIGHLIGHT_WIDTH - 2; displ2 = TILE_SIZE / 3 - HIGHLIGHT_WIDTH; - coords[0] = cx - displ * xdx - displ2 * ydx; - coords[1] = cy - displ * xdy - displ2 * ydy; + coords[0] = cx - displ * xdx + displ2 * ydx; + coords[1] = cy - displ * xdy + displ2 * ydy; rotate(coords+0, rot); - coords[2] = cx + displ * xdx - displ2 * ydx; - coords[3] = cy + displ * xdy - displ2 * ydy; + coords[2] = cx + displ * xdx + displ2 * ydx; + coords[3] = cy + displ * xdy + displ2 * ydy; rotate(coords+2, rot); - coords[4] = cx + displ * ydx; - coords[5] = cy + displ * ydy; + coords[4] = cx - displ * ydx; + coords[5] = cy - displ * ydy; rotate(coords+4, rot); draw_polygon(fe, coords, 3, TRUE, COL_LOWLIGHT_GENTLE); draw_polygon(fe, coords, 3, FALSE, COL_LOWLIGHT_GENTLE); @@ -755,13 +862,18 @@ static int highlight_colour(float angle) static float game_anim_length(game_state *oldstate, game_state *newstate, int dir) { - return ANIM_PER_RADIUS_UNIT * sqrt(newstate->n-1); + if ((dir > 0 && newstate->just_used_solve) || + (dir < 0 && oldstate->just_used_solve)) + return 0.0F; + else + return ANIM_PER_RADIUS_UNIT * sqrt(newstate->n-1); } static float game_flash_length(game_state *oldstate, game_state *newstate, int dir) { - if (!oldstate->completed && newstate->completed) + if (!oldstate->completed && newstate->completed && + !oldstate->used_solve && !newstate->used_solve) return 2 * FLASH_FRAME; else return 0.0F; @@ -896,9 +1008,13 @@ 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); } @@ -914,21 +1030,23 @@ static int game_wants_statusbar(void) #endif const struct game thegame = { - "Twiddle", "games.twiddle", TRUE, + "Twiddle", "games.twiddle", 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, + game_free_aux_info, validate_seed, new_game, dup_game, free_game, + TRUE, solve_game, + TRUE, game_text_format, new_ui, free_ui, make_move,