Fix outdated comment
[sgt/puzzles] / twiddle.c
index 1992c01..8fd0b21 100644 (file)
--- 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;
 
@@ -358,6 +361,11 @@ static char *new_game_seed(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_seed(game_params *params, char *seed)
 {
     char *p, *err;
@@ -400,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;
 
@@ -439,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));
@@ -452,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>"[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;
@@ -485,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);
@@ -670,7 +763,7 @@ static void draw_tile(frontend *fe, game_state *state, int x, int y,
     }
 
     /*
-     * Next, the colour bars for orientation.
+     * Next, the triangles for orientation.
      */
     if (state->orientable) {
        int xdx, xdy, ydx, ydy;
@@ -769,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;
@@ -910,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);
     }
@@ -928,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,