chiark / gitweb /
Re-architecting of the game backend interface. make_move() has been
authorSimon Tatham <anakin@pobox.com>
Mon, 27 Jun 2005 19:34:54 +0000 (19:34 +0000)
committerSimon Tatham <anakin@pobox.com>
Mon, 27 Jun 2005 19:34:54 +0000 (19:34 +0000)
split into two functions. The first, interpret_move(), takes all the
arguments that make_move() used to get and may have the usual side
effects of modifying the game_ui, but instead of returning a
modified game_state it instead returns a string description of the
move to be made. This string description is then passed to a second
function, execute_move(), together with an input game_state, which
is responsible for actually producing the new state. (solve_game()
also returns a string to be passed to execute_move().)

The point of this is to work towards being able to serialise the
whole of a game midend into a byte stream such as a disk file, which
will eventually support save and load functions in the desktop
puzzles, as well as restoring half-finished games after a quit and
restart in James Harvey's Palm port. Making each game supply a
convert-to-string function for its game_state format would have been
an unreliable way to do this, since those functions would not have
been used in normal play, so they'd only have been tested when you
actually tried to save and load - a recipe for latent bugs if ever I
heard one. This way, you won't even be able to _make_ a move if
execute_move() doesn't work properly, which means that if you can
play a game at all I can have pretty high confidence that
serialising it will work first time.

This is only the groundwork; there will be more checkins to come on
this theme. But the major upheaval should now be done, and as far as
I can tell everything's still working normally.

[originally from svn r6024]

16 files changed:
cube.c
fifteen.c
flip.c
guess.c
midend.c
mines.c
net.c
netslide.c
nullgame.c
pattern.c
puzzles.h
rect.c
samegame.c
sixteen.c
solo.c
twiddle.c

diff --git a/cube.c b/cube.c
index 0658970e40a247185af13349c3320b3b4a576285..4f26df7f1a24f72f151b82f868b19e8532c0582a 100644 (file)
--- a/cube.c
+++ b/cube.c
@@ -984,8 +984,8 @@ static void free_game(game_state *state)
     sfree(state);
 }
 
-static game_state *solve_game(game_state *state, game_state *currstate,
-                             game_aux_info *aux, char **error)
+static char *solve_game(game_state *state, game_state *currstate,
+                       game_aux_info *aux, char **error)
 {
     return NULL;
 }
@@ -1014,16 +1014,66 @@ struct game_drawstate {
     int ox, oy;                        /* pixel position of float origin */
 };
 
-static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
-                            int x, int y, int button)
+/*
+ * Code shared between interpret_move() and execute_move().
+ */
+static int find_move_dest(game_state *from, int direction,
+                         int *skey, int *dkey)
 {
-    int direction;
-    int pkey[2], skey[2], dkey[2];
+    int mask, dest, i, j;
     float points[4];
-    game_state *ret;
-    float angle;
-    int i, j, dest, mask;
-    struct solid *poly;
+
+    /*
+     * Find the two points in the current grid square which
+     * correspond to this move.
+     */
+    mask = from->squares[from->current].directions[direction];
+    if (mask == 0)
+        return -1;
+    for (i = j = 0; i < from->squares[from->current].npoints; i++)
+        if (mask & (1 << i)) {
+            points[j*2] = from->squares[from->current].points[i*2];
+            points[j*2+1] = from->squares[from->current].points[i*2+1];
+            skey[j] = i;
+            j++;
+        }
+    assert(j == 2);
+
+    /*
+     * Now find the other grid square which shares those points.
+     * This is our move destination.
+     */
+    dest = -1;
+    for (i = 0; i < from->nsquares; i++)
+        if (i != from->current) {
+            int match = 0;
+            float dist;
+
+            for (j = 0; j < from->squares[i].npoints; j++) {
+                dist = (SQ(from->squares[i].points[j*2] - points[0]) +
+                        SQ(from->squares[i].points[j*2+1] - points[1]));
+                if (dist < 0.1)
+                    dkey[match++] = j;
+                dist = (SQ(from->squares[i].points[j*2] - points[2]) +
+                        SQ(from->squares[i].points[j*2+1] - points[3]));
+                if (dist < 0.1)
+                    dkey[match++] = j;
+            }
+
+            if (match == 2) {
+                dest = i;
+                break;
+            }
+        }
+
+    return dest;
+}
+
+static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
+                           int x, int y, int button)
+{
+    int direction, mask, i;
+    int skey[2], dkey[2];
 
     button = button & (~MOD_MASK | MOD_NUM_KEYPAD);
 
@@ -1056,8 +1106,8 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
         int cx, cy;
         double angle;
 
-        cx = from->squares[from->current].x * GRID_SCALE + ds->ox;
-        cy = from->squares[from->current].y * GRID_SCALE + ds->oy;
+        cx = state->squares[state->current].x * GRID_SCALE + ds->ox;
+        cy = state->squares[state->current].y * GRID_SCALE + ds->oy;
 
         if (x == cx && y == cy)
             return NULL;               /* clicked in exact centre!  */
@@ -1082,7 +1132,7 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
          * x-axis, not anticlockwise as most mathematicians would
          * instinctively assume.
          */
-        if (from->squares[from->current].npoints == 4) {
+        if (state->squares[state->current].npoints == 4) {
             /* Square. */
             if (fabs(angle) > 3*PI/4)
                 direction = LEFT;
@@ -1092,7 +1142,7 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
                 direction = DOWN;
             else
                 direction = UP;
-        } else if (from->squares[from->current].directions[UP] == 0) {
+        } else if (state->squares[state->current].directions[UP] == 0) {
             /* Up-pointing triangle. */
             if (angle < -PI/2 || angle > 5*PI/6)
                 direction = LEFT;
@@ -1102,7 +1152,7 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
                 direction = RIGHT;
         } else {
             /* Down-pointing triangle. */
-            assert(from->squares[from->current].directions[DOWN] == 0);
+            assert(state->squares[state->current].directions[DOWN] == 0);
             if (angle > PI/2 || angle < -5*PI/6)
                 direction = LEFT;
             else if (angle < -PI/6)
@@ -1113,54 +1163,57 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
     } else
         return NULL;
 
-    /*
-     * Find the two points in the current grid square which
-     * correspond to this move.
-     */
-    mask = from->squares[from->current].directions[direction];
+    mask = state->squares[state->current].directions[direction];
     if (mask == 0)
         return NULL;
-    for (i = j = 0; i < from->squares[from->current].npoints; i++)
-        if (mask & (1 << i)) {
-            points[j*2] = from->squares[from->current].points[i*2];
-            points[j*2+1] = from->squares[from->current].points[i*2+1];
-            skey[j] = i;
-            j++;
-        }
-    assert(j == 2);
 
     /*
-     * Now find the other grid square which shares those points.
-     * This is our move destination.
+     * Translate diagonal directions into orthogonal ones.
      */
-    dest = -1;
-    for (i = 0; i < from->nsquares; i++)
-        if (i != from->current) {
-            int match = 0;
-            float dist;
+    if (direction > DOWN) {
+       for (i = LEFT; i <= DOWN; i++)
+           if (state->squares[state->current].directions[i] == mask) {
+               direction = i;
+               break;
+           }
+       assert(direction <= DOWN);
+    }
 
-            for (j = 0; j < from->squares[i].npoints; j++) {
-                dist = (SQ(from->squares[i].points[j*2] - points[0]) +
-                        SQ(from->squares[i].points[j*2+1] - points[1]));
-                if (dist < 0.1)
-                    dkey[match++] = j;
-                dist = (SQ(from->squares[i].points[j*2] - points[2]) +
-                        SQ(from->squares[i].points[j*2+1] - points[3]));
-                if (dist < 0.1)
-                    dkey[match++] = j;
-            }
+    if (find_move_dest(state, direction, skey, dkey) < 0)
+       return NULL;
 
-            if (match == 2) {
-                dest = i;
-                break;
-            }
-        }
+    if (direction == LEFT)  return dupstr("L");
+    if (direction == RIGHT) return dupstr("R");
+    if (direction == UP)    return dupstr("U");
+    if (direction == DOWN)  return dupstr("D");
+
+    return NULL;                      /* should never happen */
+}
+
+static game_state *execute_move(game_state *from, char *move)
+{
+    game_state *ret;
+    float angle;
+    struct solid *poly;
+    int pkey[2];
+    int skey[2], dkey[2];
+    int i, j, dest;
+    int direction;
+
+    switch (*move) {
+      case 'L': direction = LEFT; break;
+      case 'R': direction = RIGHT; break;
+      case 'U': direction = UP; break;
+      case 'D': direction = DOWN; break;
+      default: return NULL;
+    }
 
+    dest = find_move_dest(from, direction, skey, dkey);
     if (dest < 0)
         return NULL;
 
     ret = dup_game(from);
-    ret->current = i;
+    ret->current = dest;
 
     /*
      * So we know what grid square we're aiming for, and we also
@@ -1662,7 +1715,8 @@ const struct game thegame = {
     new_ui,
     free_ui,
     game_changed_state,
-    make_move,
+    interpret_move,
+    execute_move,
     game_size,
     game_colours,
     game_new_drawstate,
index d8ba3b0eb7ada4445f3cd6badc7780c79d1deb8e..9d763a6cf8fd7b75a6e78075266d3a5b92d55159 100644 (file)
--- a/fifteen.c
+++ b/fifteen.c
@@ -379,26 +379,10 @@ static void free_game(game_state *state)
     sfree(state);
 }
 
-static game_state *solve_game(game_state *state, game_state *currstate,
-                             game_aux_info *aux, char **error)
+static char *solve_game(game_state *state, game_state *currstate,
+                       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.
-     */
-    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;
+    return dupstr("S");
 }
 
 static char *game_text_format(game_state *state)
@@ -464,28 +448,29 @@ struct game_drawstate {
     int tilesize;
 };
 
-static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
-                             int x, int y, int button) {
-    int gx, gy, dx, dy, ux, uy, up, p;
-    game_state *ret;
+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(from, from->gap_pos);
-    gy = Y(from, from->gap_pos);
+    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
@@ -496,6 +481,45 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
     } 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.
@@ -859,7 +883,8 @@ const struct game thegame = {
     new_ui,
     free_ui,
     game_changed_state,
-    make_move,
+    interpret_move,
+    execute_move,
     game_size,
     game_colours,
     game_new_drawstate,
diff --git a/flip.c b/flip.c
index e6c4077b680a88a36f05e98dff946e97cc57dea5..a8636dd25d7f5256fd3c37e78774e411dce112b4 100644 (file)
--- a/flip.c
+++ b/flip.c
@@ -675,15 +675,15 @@ static void rowxor(unsigned char *row1, unsigned char *row2, int len)
        row1[i] ^= row2[i];
 }
 
-static game_state *solve_game(game_state *state, game_state *currstate,
-                             game_aux_info *aux, char **error)
+static char *solve_game(game_state *state, game_state *currstate,
+                       game_aux_info *aux, char **error)
 {
     int w = state->w, h = state->h, wh = w * h;
     unsigned char *equations, *solution, *shortest;
     int *und, nund;
     int rowsdone, colsdone;
     int i, j, k, len, bestlen;
-    game_state *ret;
+    char *ret;
 
     /*
      * Set up a list of simultaneous equations. Each one is of
@@ -840,17 +840,14 @@ static game_state *solve_game(game_state *state, game_state *currstate,
     }
 
     /*
-     * We have a solution. Produce a game state with the solution
-     * marked in annotations.
+     * We have a solution. Produce a move string encoding the
+     * solution.
      */
-    ret = dup_game(currstate);
-    ret->hints_active = TRUE;
-    ret->cheated = TRUE;
-    for (i = 0; i < wh; i++) {
-       ret->grid[i] &= ~2;
-       if (shortest[i])
-           ret->grid[i] |= 2;
-    }
+    ret = snewn(wh + 2, char);
+    ret[0] = 'S';
+    for (i = 0; i < wh; i++)
+       ret[i+1] = shortest[i] ? '1' : '0';
+    ret[wh+1] = '\0';
 
     sfree(shortest);
     sfree(solution);
@@ -885,41 +882,68 @@ struct game_drawstate {
     int tilesize;
 };
 
-static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
-                             int x, int y, int button)
+static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
+                           int x, int y, int button)
 {
-    int w = from->w, h = from->h, wh = w * h;
-    game_state *ret;
+    int w = state->w, h = state->h /*, wh = w * h */;
+    char buf[80];
 
     if (button == LEFT_BUTTON) {
         int tx = FROMCOORD(x), ty = FROMCOORD(y);
         if (tx >= 0 && tx < w && ty >= 0 && ty < h) {
-            int i, j, done;
+           sprintf(buf, "M%d,%d", tx, ty);
+           return dupstr(buf);
+        }
+    }
+
+    return NULL;
+}
 
-            ret = dup_game(from);
+static game_state *execute_move(game_state *from, char *move)
+{
+    int w = from->w, h = from->h, wh = w * h;
+    game_state *ret;
+    int x, y;
+
+    if (move[0] == 'S' && strlen(move) == wh+1) {
+       int i;
+
+       ret = dup_game(from);
+       ret->hints_active = TRUE;
+       ret->cheated = TRUE;
+       for (i = 0; i < wh; i++) {
+           ret->grid[i] &= ~2;
+           if (move[i+1] != '0')
+               ret->grid[i] |= 2;
+       }
+       return ret;
+    } else if (move[0] == 'M' &&
+              sscanf(move+1, "%d,%d", &x, &y) == 2 &&
+       x >= 0 && x < w && y >= 0 && y < h) {
+       int i, j, done;
 
-           if (!ret->completed)
-               ret->moves++;
+       ret = dup_game(from);
 
-            i = ty * w + tx;
+       if (!ret->completed)
+           ret->moves++;
 
-           done = TRUE;
-            for (j = 0; j < wh; j++) {
-                ret->grid[j] ^= ret->matrix->matrix[i*wh+j];
-               if (ret->grid[j] & 1)
-                   done = FALSE;
-           }
-           ret->grid[i] ^= 2;         /* toggle hint */
-           if (done) {
-               ret->completed = TRUE;
-               ret->hints_active = FALSE;
-           }
+       i = y * w + x;
 
-            return ret;
-        }
-    }
+       done = TRUE;
+       for (j = 0; j < wh; j++) {
+           ret->grid[j] ^= ret->matrix->matrix[i*wh+j];
+           if (ret->grid[j] & 1)
+               done = FALSE;
+       }
+       ret->grid[i] ^= 2;             /* toggle hint */
+       if (done) {
+           ret->completed = TRUE;
+           ret->hints_active = FALSE;
+       }
 
-    return NULL;
+       return ret;
+    } else
+       return NULL;                   /* can't parse move string */
 }
 
 /* ----------------------------------------------------------------------
@@ -1206,7 +1230,8 @@ const struct game thegame = {
     new_ui,
     free_ui,
     game_changed_state,
-    make_move,
+    interpret_move,
+    execute_move,
     game_size,
     game_colours,
     game_new_drawstate,
diff --git a/guess.c b/guess.c
index 702a1ad9523616d7fb8fea4211ad29a3b19a2338..f3738c0eb44f75102d802fe145463cd0901a2d9f 100644 (file)
--- a/guess.c
+++ b/guess.c
@@ -364,12 +364,10 @@ static void free_game(game_state *state)
     sfree(state);
 }
 
-static game_state *solve_game(game_state *state, game_state *currstate,
-                              game_aux_info *aux, char **error)
+static char *solve_game(game_state *state, game_state *currstate,
+                       game_aux_info *aux, char **error)
 {
-    game_state *ret = dup_game(currstate);
-    ret->solved = 1;
-    return ret;
+    return dupstr("S");
 }
 
 static char *game_text_format(game_state *state)
@@ -548,47 +546,52 @@ static int mark_pegs(pegrow guess, pegrow solution, int ncols)
     return nc_place;
 }
 
-static game_state *mark_move(game_state *from, game_ui *ui)
+static char *encode_move(game_state *from, game_ui *ui)
 {
-    int i, ncleared = 0, nc_place;
-    game_state *to = dup_game(from);
-
-    for (i = 0; i < to->solution->npegs; i++) {
-        to->guesses[from->next_go]->pegs[i] = ui->curr_pegs->pegs[i];
-    }
-    nc_place = mark_pegs(to->guesses[from->next_go], to->solution, to->params.ncolours);
-
-    if (nc_place == to->solution->npegs) {
-        to->solved = 1; /* win! */
-    } else {
-        to->next_go = from->next_go + 1;
-        if (to->next_go >= to->params.nguesses)
-            to->solved = 1; /* 'lose' so we show the pegs. */
+    char *buf, *p, *sep;
+    int len, i, solved;
+    pegrow tmppegs;
+
+    len = ui->curr_pegs->npegs * 20 + 2;
+    buf = snewn(len, char);
+    p = buf;
+    *p++ = 'G';
+    sep = "";
+    for (i = 0; i < ui->curr_pegs->npegs; i++) {
+       p += sprintf(p, "%s%d", sep, ui->curr_pegs->pegs[i]);
+       sep = ",";
     }
-
-    for (i = 0; i < to->solution->npegs; i++) {
-        if (!ui->holds[i] || to->solved) {
-            ui->curr_pegs->pegs[i] = 0;
-            ncleared++;
-        }
-        if (to->solved) ui->holds[i] = 0;
+    *p++ = '\0';
+    assert(p - buf <= len);
+    buf = sresize(buf, len, char);
+
+    tmppegs = dup_pegrow(ui->curr_pegs);
+    solved = mark_pegs(tmppegs, from->solution, from->params.ncolours);
+    solved = (solved == from->params.ncolours);
+    free_pegrow(tmppegs);
+
+    for (i = 0; i < from->solution->npegs; i++) {
+       if (!ui->holds[i] || solved) {
+           ui->curr_pegs->pegs[i] = 0;
+       }
+       if (solved) ui->holds[i] = 0;
     }
     ui->markable = is_markable(&from->params, ui->curr_pegs);
-    if (!ui->markable && ui->peg_cur == to->solution->npegs)
-        ui->peg_cur--;
+    if (!ui->markable && ui->peg_cur == from->solution->npegs)
+       ui->peg_cur--;
 
-    return to;
+    return buf;
 }
 
-static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
-                             int x, int y, int button)
+static char *interpret_move(game_state *from, game_ui *ui, game_drawstate *ds,
+                           int x, int y, int button)
 {
     int over_col = 0;           /* one-indexed */
     int over_guess = -1;        /* zero-indexed */
     int over_past_guess_y = -1; /* zero-indexed */
     int over_past_guess_x = -1; /* zero-indexed */
     int over_hint = 0;          /* zero or one */
-    game_state *ret = NULL;
+    char *ret = NULL;
 
     int guess_ox = GUESS_X(from->next_go, 0);
     int guess_oy = GUESS_Y(from->next_go, 0);
@@ -643,13 +646,13 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
             ui->drag_y = y;
             debug(("Start dragging, col = %d, (%d,%d)",
                    ui->drag_col, ui->drag_x, ui->drag_y));
-            ret = from;
+            ret = "";
         }
     } else if (button == LEFT_DRAG && ui->drag_col) {
         ui->drag_x = x;
         ui->drag_y = y;
         debug(("Keep dragging, (%d,%d)", ui->drag_x, ui->drag_y));
-        ret = from;
+        ret = "";
     } else if (button == LEFT_RELEASE && ui->drag_col) {
         if (over_guess > -1) {
             debug(("Dropping colour %d onto guess peg %d",
@@ -666,18 +669,18 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
         ui->drag_opeg = -1;
         ui->display_cur = 0;
         debug(("Stop dragging."));
-        ret = from;
+        ret = "";
     } else if (button == RIGHT_BUTTON) {
         if (over_guess > -1) {
             /* we use ths feedback in the game_ui to signify
              * 'carry this peg to the next guess as well'. */
             ui->holds[over_guess] = 1 - ui->holds[over_guess];
-            ret = from;
+            ret = "";
         }
     } else if (button == LEFT_RELEASE && over_hint && ui->markable) {
         /* NB this won't trigger if on the end of a drag; that's on
          * purpose, in case you drop by mistake... */
-        ret = mark_move(from, ui);
+        ret = encode_move(from, ui);
     }
 
     /* keyboard input */
@@ -687,7 +690,7 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
             ui->colour_cur++;
         if (button == CURSOR_UP && ui->colour_cur > 0)
             ui->colour_cur--;
-        ret = from;
+        ret = "";
     } else if (button == CURSOR_LEFT || button == CURSOR_RIGHT) {
         int maxcur = from->params.npegs;
         if (ui->markable) maxcur++;
@@ -697,24 +700,65 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
             ui->peg_cur++;
         if (button == CURSOR_LEFT && ui->peg_cur > 0)
             ui->peg_cur--;
-        ret = from;
+        ret = "";
     } else if (button == CURSOR_SELECT || button == ' ' || button == '\r' ||
                button == '\n') {
         ui->display_cur = 1;
         if (ui->peg_cur == from->params.npegs) {
-            ret = mark_move(from, ui);
+            ret = encode_move(from, ui);
         } else {
             set_peg(&from->params, ui, ui->peg_cur, ui->colour_cur+1);
-            ret = from;
+            ret = "";
         }
     } else if (button == 'H' || button == 'h') {
         ui->display_cur = 1;
         ui->holds[ui->peg_cur] = 1 - ui->holds[ui->peg_cur];
-        ret = from;
+        ret = "";
     }
     return ret;
 }
 
+static game_state *execute_move(game_state *from, char *move)
+{
+    int i, nc_place;
+    game_state *ret;
+    char *p;
+
+    if (!strcmp(move, "S")) {
+       ret = dup_game(from);
+       ret->solved = 1;
+       return ret;
+    } else if (move[0] == 'G') {
+       p = move+1;
+
+       ret = dup_game(from);
+
+       for (i = 0; i < from->solution->npegs; i++) {
+           int val = atoi(p);
+           if (val <= 0 || val > from->params.ncolours) {
+               free_game(ret);
+               return NULL;
+           }
+           ret->guesses[from->next_go]->pegs[i] = atoi(p);
+           while (*p && isdigit((unsigned char)*p)) p++;
+           if (*p == ',') p++;
+       }
+
+       nc_place = mark_pegs(ret->guesses[from->next_go], ret->solution, ret->params.ncolours);
+
+       if (nc_place == ret->solution->npegs) {
+           ret->solved = 1; /* win! */
+       } else {
+           ret->next_go = from->next_go + 1;
+           if (ret->next_go >= ret->params.nguesses)
+               ret->solved = 1; /* 'lose' so we show the pegs. */
+       }
+
+       return ret;
+    } else
+       return NULL;
+}
+
 /* ----------------------------------------------------------------------
  * Drawing routines.
  */
@@ -1217,7 +1261,8 @@ const struct game thegame = {
     new_ui,
     free_ui,
     game_changed_state,
-    make_move,
+    interpret_move,
+    execute_move,
     game_size,
     game_colours,
     game_new_drawstate,
index dec0cc955955ac7be676f40a26ed4201978a2c74..5f4e795da7b029063d6c0b89d0ad440c734233c6 100644 (file)
--- a/midend.c
+++ b/midend.c
@@ -367,9 +367,22 @@ static int midend_really_process_key(midend_data *me, int x, int y, int button)
        ret = 0;
        goto done;
     } else {
-        game_state *s =
-            me->ourgame->make_move(me->states[me->statepos-1].state,
-                                   me->ui, me->drawstate, x, y, button);
+        game_state *s;
+       char *movestr;
+       
+       movestr =
+            me->ourgame->interpret_move(me->states[me->statepos-1].state,
+                                       me->ui, me->drawstate, x, y, button);
+       if (!movestr)
+           s = NULL;
+       else if (!*movestr)
+           s = me->states[me->statepos-1].state;
+       else {
+           s = me->ourgame->execute_move(me->states[me->statepos-1].state,
+                                         movestr);
+           assert(s != NULL);
+           sfree(movestr);
+       }
 
         if (s == me->states[me->statepos-1].state) {
             /*
@@ -938,7 +951,7 @@ char *midend_text_format(midend_data *me)
 char *midend_solve(midend_data *me)
 {
     game_state *s;
-    char *msg;
+    char *msg, *movestr;
 
     if (!me->ourgame->can_solve)
        return "This game does not support the Solve operation";
@@ -947,11 +960,14 @@ char *midend_solve(midend_data *me)
        return "No game set up to solve";   /* _shouldn't_ happen! */
 
     msg = "Solve operation failed";    /* game _should_ overwrite on error */
-    s = me->ourgame->solve(me->states[0].state,
-                          me->states[me->statepos-1].state,
-                          me->aux_info, &msg);
-    if (!s)
+    movestr = me->ourgame->solve(me->states[0].state,
+                                me->states[me->statepos-1].state,
+                                me->aux_info, &msg);
+    if (!movestr)
        return msg;
+    s = me->ourgame->execute_move(me->states[me->statepos-1].state, movestr);
+    assert(s);
+    sfree(movestr);
 
     /*
      * Now enter the solved state as the next move.
diff --git a/mines.c b/mines.c
index 6895b2e997e318fee6704a0e01a4409712170ce5..4fd0e218c2c7f5930dd16b55fe39b06c33807cf6 100644 (file)
--- a/mines.c
+++ b/mines.c
@@ -2279,46 +2279,15 @@ static void free_game(game_state *state)
     sfree(state);
 }
 
-static game_state *solve_game(game_state *state, game_state *currstate,
-                             game_aux_info *aux, char **error)
+static char *solve_game(game_state *state, game_state *currstate,
+                       game_aux_info *aux, char **error)
 {
-    /*
-     * Simply expose the entire grid as if it were a completed
-     * solution.
-     */
-    game_state *ret;
-    int yy, xx;
-
     if (!state->layout->mines) {
-        *error = "Game has not been started yet";
-        return NULL;
+       *error = "Game has not been started yet";
+       return NULL;
     }
 
-    ret = dup_game(state);
-    for (yy = 0; yy < ret->h; yy++)
-        for (xx = 0; xx < ret->w; xx++) {
-
-            if (ret->layout->mines[yy*ret->w+xx]) {
-                ret->grid[yy*ret->w+xx] = -1;
-            } else {
-                int dx, dy, v;
-
-                v = 0;
-
-                for (dx = -1; dx <= +1; dx++)
-                    for (dy = -1; dy <= +1; dy++)
-                        if (xx+dx >= 0 && xx+dx < ret->w &&
-                            yy+dy >= 0 && yy+dy < ret->h &&
-                            ret->layout->mines[(yy+dy)*ret->w+(xx+dx)])
-                            v++;
-
-                ret->grid[yy*ret->w+xx] = v;
-            }
-        }
-    ret->used_solve = ret->just_used_solve = TRUE;
-    ret->won = TRUE;
-
-    return ret;
+    return dupstr("S");
 }
 
 static char *game_text_format(game_state *state)
@@ -2390,11 +2359,11 @@ struct game_drawstate {
      */
 };
 
-static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
-                             int x, int y, int button)
+static char *interpret_move(game_state *from, game_ui *ui, game_drawstate *ds,
+                           int x, int y, int button)
 {
-    game_state *ret;
     int cx, cy;
+    char buf[256];
 
     if (from->dead || from->won)
        return NULL;                   /* no further moves permitted */
@@ -2418,7 +2387,7 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
        ui->hx = cx;
        ui->hy = cy;
        ui->hradius = (from->grid[cy*from->w+cx] >= 0 ? 1 : 0);
-       return from;
+       return "";
     }
 
     if (button == RIGHT_BUTTON) {
@@ -2436,11 +2405,8 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
            from->grid[cy * from->w + cx] != -1)
            return NULL;
 
-       ret = dup_game(from);
-        ret->just_used_solve = FALSE;
-       ret->grid[cy * from->w + cx] ^= (-2 ^ -1);
-
-       return ret;
+       sprintf(buf, "F%d,%d", cx, cy);
+       return dupstr(buf);
     }
 
     if (button == LEFT_RELEASE || button == MIDDLE_RELEASE) {
@@ -2449,10 +2415,10 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
 
        /*
         * At this stage we must never return NULL: we have adjusted
-        * the ui, so at worst we return `from'.
+        * the ui, so at worst we return "".
         */
        if (cx < 0 || cx >= from->w || cy < 0 || cy >= from->h)
-           return from;
+           return "";
 
        /*
         * Left-clicking on a covered square opens a tile. Not
@@ -2462,12 +2428,12 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
        if (button == LEFT_RELEASE &&
            (from->grid[cy * from->w + cx] == -2 ||
             from->grid[cy * from->w + cx] == -3)) {
-           ret = dup_game(from);
-            ret->just_used_solve = FALSE;
-           open_square(ret, cx, cy);
-            if (ret->dead)
-                ui->deaths++;
-           return ret;
+           /* Check if you've killed yourself. */
+           if (from->layout->mines && from->layout->mines[cy * from->w + cx])
+               ui->deaths++;
+
+           sprintf(buf, "O%d,%d", cx, cy);
+           return dupstr(buf);
        }
 
        /*
@@ -2490,8 +2456,101 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
                    }
 
            if (n == from->grid[cy * from->w + cx]) {
-               ret = dup_game(from);
-                ret->just_used_solve = FALSE;
+
+               /*
+                * Now see if any of the squares we're clearing
+                * contains a mine (which will happen iff you've
+                * incorrectly marked the mines around the clicked
+                * square). If so, we open _just_ those squares, to
+                * reveal as little additional information as we
+                * can.
+                */
+               char *p = buf;
+               char *sep = "";
+
+               for (dy = -1; dy <= +1; dy++)
+                   for (dx = -1; dx <= +1; dx++)
+                       if (cx+dx >= 0 && cx+dx < from->w &&
+                           cy+dy >= 0 && cy+dy < from->h) {
+                           if (from->grid[(cy+dy)*from->w+(cx+dx)] != -1 &&
+                               from->layout->mines &&
+                               from->layout->mines[(cy+dy)*from->w+(cx+dx)]) {
+                               p += sprintf(p, "%sO%d,%d", sep, cx+dx, cy+dy);
+                               sep = ";";
+                           }
+                       }
+
+               if (p > buf) {
+                   ui->deaths++;
+               } else {
+                   sprintf(buf, "C%d,%d", cx, cy);
+               }
+
+               return dupstr(buf);
+           }
+       }
+
+       return "";
+    }
+
+    return NULL;
+}
+
+static game_state *execute_move(game_state *from, char *move)
+{
+    int cy, cx;
+    game_state *ret;
+
+    if (!strcmp(move, "S")) {
+       /*
+        * Simply expose the entire grid as if it were a completed
+        * solution.
+        */
+       int yy, xx;
+
+       ret = dup_game(from);
+       for (yy = 0; yy < ret->h; yy++)
+           for (xx = 0; xx < ret->w; xx++) {
+
+               if (ret->layout->mines[yy*ret->w+xx]) {
+                   ret->grid[yy*ret->w+xx] = -1;
+               } else {
+                   int dx, dy, v;
+
+                   v = 0;
+
+                   for (dx = -1; dx <= +1; dx++)
+                       for (dy = -1; dy <= +1; dy++)
+                           if (xx+dx >= 0 && xx+dx < ret->w &&
+                               yy+dy >= 0 && yy+dy < ret->h &&
+                               ret->layout->mines[(yy+dy)*ret->w+(xx+dx)])
+                               v++;
+
+                   ret->grid[yy*ret->w+xx] = v;
+               }
+           }
+       ret->used_solve = ret->just_used_solve = TRUE;
+       ret->won = TRUE;
+
+       return ret;
+    } else {
+       ret = dup_game(from);
+       ret->just_used_solve = FALSE;
+
+       while (*move) {
+           if (move[0] == 'F' &&
+               sscanf(move+1, "%d,%d", &cx, &cy) == 2 &&
+               cx >= 0 && cx < from->w && cy >= 0 && cy < from->h) {
+               ret->grid[cy * from->w + cx] ^= (-2 ^ -1);
+           } else if (move[0] == 'O' &&
+                      sscanf(move+1, "%d,%d", &cx, &cy) == 2 &&
+                      cx >= 0 && cx < from->w && cy >= 0 && cy < from->h) {
+               open_square(ret, cx, cy);
+           } else if (move[0] == 'C' &&
+                      sscanf(move+1, "%d,%d", &cx, &cy) == 2 &&
+                      cx >= 0 && cx < from->w && cy >= 0 && cy < from->h) {
+               int dx, dy;
+
                for (dy = -1; dy <= +1; dy++)
                    for (dx = -1; dx <= +1; dx++)
                        if (cx+dx >= 0 && cx+dx < ret->w &&
@@ -2499,16 +2558,17 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
                            (ret->grid[(cy+dy)*ret->w+(cx+dx)] == -2 ||
                             ret->grid[(cy+dy)*ret->w+(cx+dx)] == -3))
                            open_square(ret, cx+dx, cy+dy);
-                if (ret->dead)
-                    ui->deaths++;
-               return ret;
+           } else {
+               free_game(ret);
+               return NULL;
            }
+
+           while (*move && *move != ';') move++;
+           if (*move) move++;
        }
 
-       return from;
+       return ret;
     }
-
-    return NULL;
 }
 
 /* ----------------------------------------------------------------------
@@ -2962,7 +3022,8 @@ const struct game thegame = {
     new_ui,
     free_ui,
     game_changed_state,
-    make_move,
+    interpret_move,
+    execute_move,
     game_size,
     game_colours,
     game_new_drawstate,
diff --git a/net.c b/net.c
index 8bfd899a29a0a803925e990f63eca2ae74bc404c..62535686a99aba8bb58cc5f4acc7822820a01fee 100644 (file)
--- a/net.c
+++ b/net.c
@@ -1666,28 +1666,87 @@ static void free_game(game_state *state)
     sfree(state);
 }
 
-static game_state *solve_game(game_state *state, game_state *currstate,
-                             game_aux_info *aux, char **error)
+static char *solve_game(game_state *state, game_state *currstate,
+                       game_aux_info *aux, char **error)
 {
-    game_state *ret;
+    unsigned char *tiles;
+    char *ret;
+    int retlen, retsize;
+    int i;
+    int tiles_need_freeing;
 
     if (!aux) {
        /*
         * Run the internal solver on the provided grid. This might
         * not yield a complete solution.
         */
-       ret = dup_game(state);
-       net_solver(ret->width, ret->height, ret->tiles,
-                  ret->barriers, ret->wrapping);
+       tiles = snewn(state->width * state->height, unsigned char);
+       memcpy(tiles, state->tiles, state->width * state->height);
+       net_solver(state->width, state->height, tiles,
+                  state->barriers, state->wrapping);
+       tiles_need_freeing = TRUE;
     } else {
-       assert(aux->width == state->width);
-       assert(aux->height == state->height);
-       ret = dup_game(state);
-       memcpy(ret->tiles, aux->tiles, ret->width * ret->height);
-       ret->used_solve = ret->just_used_solve = TRUE;
-       ret->completed = TRUE;
+       tiles = aux->tiles;
+       tiles_need_freeing = FALSE;
+    }
+
+    /*
+     * Now construct a string which can be passed to execute_move()
+     * to transform the current grid into the solved one.
+     */
+    retsize = 256;
+    ret = snewn(retsize, char);
+    retlen = 0;
+    ret[retlen++] = 'S';
+
+    for (i = 0; i < state->width * state->height; i++) {
+       int from = currstate->tiles[i], to = tiles[i];
+       int ft = from & (R|L|U|D), tt = to & (R|L|U|D);
+       int x = i % state->width, y = i / state->width;
+       int chr = '\0';
+       char buf[80], *p = buf;
+
+       if (from == to)
+           continue;                  /* nothing needs doing at all */
+
+       /*
+        * To transform this tile into the desired tile: first
+        * unlock the tile if it's locked, then rotate it if
+        * necessary, then lock it if necessary.
+        */
+       if (from & LOCKED)
+           p += sprintf(p, ";L%d,%d", x, y);
+
+       if (tt == A(ft))
+           chr = 'A';
+       else if (tt == C(ft))
+           chr = 'C';
+       else if (tt == F(ft))
+           chr = 'F';
+       else {
+           assert(tt == ft);
+           chr = '\0';
+       }
+       if (chr)
+           p += sprintf(p, ";%c%d,%d", chr, x, y);
+
+       if (to & LOCKED)
+           p += sprintf(p, ";L%d,%d", x, y);
+
+       if (p > buf) {
+           if (retlen + (p - buf) >= retsize) {
+               retsize = retlen + (p - buf) + 512;
+               ret = sresize(ret, retsize, char);
+           }
+           memcpy(ret+retlen, buf, p - buf);
+           retlen += p - buf;
+       }
     }
 
+    assert(retlen < retsize);
+    ret[retlen] = '\0';
+    ret = sresize(ret, retlen+1, char);
+
     return ret;
 }
 
@@ -1803,10 +1862,11 @@ struct game_drawstate {
 /* ----------------------------------------------------------------------
  * Process a move.
  */
-static game_state *make_move(game_state *state, game_ui *ui,
-                             game_drawstate *ds, int x, int y, int button) {
-    game_state *ret, *nullret;
-    int tx, ty, orig;
+static char *interpret_move(game_state *state, game_ui *ui,
+                           game_drawstate *ds, int x, int y, int button)
+{
+    char *nullret;
+    int tx, ty;
     int shift = button & MOD_SHFT, ctrl = button & MOD_CTRL;
 
     button &= ~MOD_MASK;
@@ -1818,7 +1878,7 @@ static game_state *make_move(game_state *state, game_ui *ui,
 
        if (ui->cur_visible) {
            ui->cur_visible = FALSE;
-           nullret = state;
+           nullret = "";
        }
 
        /*
@@ -1869,7 +1929,7 @@ static game_state *make_move(game_state *state, game_ui *ui,
             OFFSET(ui->cur_x, ui->cur_y, ui->cur_x, ui->cur_y, dir, state);
             ui->cur_visible = TRUE;
         }
-        return state;                 /* UI activity has occurred */
+        return "";                    /* UI activity has occurred */
     } else if (button == 'a' || button == 's' || button == 'd' ||
               button == 'A' || button == 'S' || button == 'D' ||
               button == CURSOR_SELECT) {
@@ -1900,13 +1960,9 @@ static game_state *make_move(game_state *state, game_ui *ui,
      * unlocks it.)
      */
     if (button == MIDDLE_BUTTON) {
-
-       ret = dup_game(state);
-       ret->just_used_solve = FALSE;
-       tile(ret, tx, ty) ^= LOCKED;
-       ret->last_rotate_dir = ret->last_rotate_x = ret->last_rotate_y = 0;
-       return ret;
-
+       char buf[80];
+       sprintf(buf, "L%d,%d", tx, ty);
+       return dupstr(buf);
     } else if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
 
         /*
@@ -1920,49 +1976,115 @@ static game_state *make_move(game_state *state, game_ui *ui,
          * Otherwise, turn the tile one way or the other. Left button
          * turns anticlockwise; right button turns clockwise.
          */
-        ret = dup_game(state);
-       ret->just_used_solve = FALSE;
-        orig = tile(ret, tx, ty);
-        if (button == LEFT_BUTTON) {
-            tile(ret, tx, ty) = A(orig);
-            ret->last_rotate_dir = +1;
-        } else {
-            tile(ret, tx, ty) = C(orig);
-            ret->last_rotate_dir = -1;
-        }
-        ret->last_rotate_x = tx;
-        ret->last_rotate_y = ty;
-
+       char buf[80];
+       sprintf(buf, "%c%d,%d", (button == LEFT_BUTTON ? 'A' : 'C'), tx, ty);
+       return dupstr(buf);
     } else if (button == 'J') {
-
         /*
          * Jumble all unlocked tiles to random orientations.
          */
-        int jx, jy;
-        ret = dup_game(state);
-       ret->just_used_solve = FALSE;
-        for (jy = 0; jy < ret->height; jy++) {
-            for (jx = 0; jx < ret->width; jx++) {
-                if (!(tile(ret, jx, jy) & LOCKED)) {
+
+        int jx, jy, maxlen;
+       char *ret, *p;
+
+       /*
+        * Maximum string length assumes no int can be converted to
+        * decimal and take more than 11 digits!
+        */
+       maxlen = state->width * state->height * 25 + 3;
+
+       ret = snewn(maxlen, char);
+       p = ret;
+       *p++ = 'J';
+
+        for (jy = 0; jy < state->height; jy++) {
+            for (jx = 0; jx < state->width; jx++) {
+                if (!(tile(state, jx, jy) & LOCKED)) {
                     int rot = random_upto(ui->rs, 4);
-                    orig = tile(ret, jx, jy);
-                    tile(ret, jx, jy) = ROT(orig, rot);
+                   if (rot) {
+                       p += sprintf(p, ";%c%d,%d", "AFC"[rot-1], jx, jy);
+                   }
                 }
             }
         }
-        ret->last_rotate_dir = 0; /* suppress animation */
-        ret->last_rotate_x = ret->last_rotate_y = 0;
+       *p++ = '\0';
+       assert(p - ret < maxlen);
+       ret = sresize(ret, p - ret, char);
 
+       return ret;
     } else {
-       ret = NULL;  /* placate optimisers which don't understand assert(0) */
-       assert(0);
+       return NULL;
+    }
+}
+
+static game_state *execute_move(game_state *from, char *move)
+{
+    game_state *ret;
+    int tx, ty, n, noanim, orig;
+
+    ret = dup_game(from);
+    ret->just_used_solve = FALSE;
+
+    if (move[0] == 'J' || move[0] == 'S') {
+       if (move[0] == 'S')
+           ret->just_used_solve = ret->used_solve = TRUE;
+
+       move++;
+       if (*move == ';')
+           move++;
+       noanim = TRUE;
+    } else
+       noanim = FALSE;
+
+    ret->last_rotate_dir = 0;         /* suppress animation */
+    ret->last_rotate_x = ret->last_rotate_y = 0;
+
+    while (*move) {
+       if ((move[0] == 'A' || move[0] == 'C' ||
+            move[0] == 'F' || move[0] == 'L') &&
+           sscanf(move+1, "%d,%d%n", &tx, &ty, &n) >= 2 &&
+           tx >= 0 && tx < from->width && ty >= 0 && ty < from->height) {
+           orig = tile(ret, tx, ty);
+           if (move[0] == 'A') {
+               tile(ret, tx, ty) = A(orig);
+               if (!noanim)
+                   ret->last_rotate_dir = +1;
+           } else if (move[0] == 'F') {
+               tile(ret, tx, ty) = F(orig);
+               if (!noanim) {
+                   free_game(ret);
+                   return NULL;
+               }
+           } else if (move[0] == 'C') {
+               tile(ret, tx, ty) = C(orig);
+               if (!noanim)
+                   ret->last_rotate_dir = -1;
+           } else {
+               assert(move[0] == 'L');
+               tile(ret, tx, ty) ^= LOCKED;
+           }
+
+           move += 1 + n;
+           if (*move == ';') move++;
+       } else {
+           free_game(ret);
+           return NULL;
+       }
+    }
+    if (!noanim) {
+       ret->last_rotate_x = tx;
+       ret->last_rotate_y = ty;
     }
 
     /*
      * Check whether the game has been completed.
+     * 
+     * For this purpose it doesn't matter where the source square
+     * is, because we can start from anywhere and correctly
+     * determine whether the game is completed.
      */
     {
-       unsigned char *active = compute_active(ret, ui->cx, ui->cy);
+       unsigned char *active = compute_active(ret, 0, 0);
        int x1, y1;
        int complete = TRUE;
 
@@ -1983,6 +2105,7 @@ static game_state *make_move(game_state *state, game_ui *ui,
     return ret;
 }
 
+
 /* ----------------------------------------------------------------------
  * Routines for drawing the game position on the screen.
  */
@@ -2617,7 +2740,8 @@ const struct game thegame = {
     new_ui,
     free_ui,
     game_changed_state,
-    make_move,
+    interpret_move,
+    execute_move,
     game_size,
     game_colours,
     game_new_drawstate,
index 5aa58b62a5f7d26f10b4bf8fb94bfd29140cdfec..b329d8d56a62b5b990e7315c6e752bbd21f41686 100644 (file)
@@ -893,10 +893,11 @@ static void free_game(game_state *state)
     sfree(state);
 }
 
-static game_state *solve_game(game_state *state, game_state *currstate,
-                             game_aux_info *aux, char **error)
+static char *solve_game(game_state *state, game_state *currstate,
+                       game_aux_info *aux, char **error)
 {
-    game_state *ret;
+    char *ret;
+    int i;
 
     if (!aux) {
        *error = "Solution not known for this puzzle";
@@ -905,11 +906,11 @@ static game_state *solve_game(game_state *state, game_state *currstate,
 
     assert(aux->width == state->width);
     assert(aux->height == state->height);
-    ret = dup_game(state);
-    memcpy(ret->tiles, aux->tiles, ret->width * ret->height);
-    ret->used_solve = ret->just_used_solve = TRUE;
-    ret->completed = ret->move_count = 1;
-
+    ret = snewn(aux->width * aux->height + 2, char);
+    ret[0] = 'S';
+    for (i = 0; i < aux->width * aux->height; i++)
+       ret[i+1] = "0123456789abcdef"[aux->tiles[i] & 0xF];
+    ret[i+1] = '\0';
     return ret;
 }
 
@@ -1058,12 +1059,12 @@ struct game_drawstate {
     unsigned char *visible;
 };
 
-static game_state *make_move(game_state *state, game_ui *ui,
-                             game_drawstate *ds, int x, int y, int button)
+static char *interpret_move(game_state *state, game_ui *ui,
+                           game_drawstate *ds, int x, int y, int button)
 {
     int cx, cy;
     int n, dx, dy;
-    game_state *ret;
+    char buf[80];
 
     button &= ~MOD_MASK;
 
@@ -1099,16 +1100,59 @@ static game_state *make_move(game_state *state, game_ui *ui,
         dy = -dy;
     }
 
-    ret = dup_game(state);
+    if (dx == 0)
+       sprintf(buf, "C%d,%d", cx, dy);
+    else
+       sprintf(buf, "R%d,%d", cy, dx);
+    return dupstr(buf);
+}
+
+static game_state *execute_move(game_state *from, char *move)
+{
+    game_state *ret;
+    int c, d, col;
+
+    if ((move[0] == 'C' || move[0] == 'R') &&
+       sscanf(move+1, "%d,%d", &c, &d) == 2 &&
+       c >= 0 && c < (move[0] == 'C' ? from->width : from->height)) {
+       col = (move[0] == 'C');
+    } else if (move[0] == 'S' &&
+              strlen(move) == from->width * from->height + 1) {
+       int i;
+       ret = dup_game(from);
+       ret->used_solve = ret->just_used_solve = TRUE;
+       ret->completed = ret->move_count = 1;
+
+       for (i = 0; i < from->width * from->height; i++) {
+           c = move[i+1];
+           if (c >= '0' && c <= '9')
+               c -= '0';
+           else if (c >= 'A' && c <= 'F')
+               c -= 'A' - 10;
+           else if (c >= 'a' && c <= 'f')
+               c -= 'a' - 10;
+           else {
+               free_game(ret);
+               return NULL;
+           }
+           ret->tiles[i] = c;
+       }
+       return ret;
+    } else
+       return NULL;                   /* can't parse move string */
+
+    ret = dup_game(from);
     ret->just_used_solve = FALSE;
 
-    if (dx == 0) slide_col(ret, dy, cx);
-    else slide_row(ret, dx, cy);
+    if (col)
+       slide_col(ret, d, c);
+    else
+       slide_row(ret, d, c);
 
     ret->move_count++;
-    ret->last_move_row = dx ? cy : -1;
-    ret->last_move_col = dx ? -1 : cx;
-    ret->last_move_dir = dx + dy;
+    ret->last_move_row = col ? -1 : c;
+    ret->last_move_col = col ? c : -1;
+    ret->last_move_dir = d;
 
     /*
      * See if the game has been completed.
@@ -1779,7 +1823,8 @@ const struct game thegame = {
     new_ui,
     free_ui,
     game_changed_state,
-    make_move,
+    interpret_move,
+    execute_move,
     game_size,
     game_colours,
     game_new_drawstate,
index e2c4a74bd87bbb3289850374163a8677035ff549..bc3f64a06b7174127c26634b76e7b94df2601265 100644 (file)
@@ -122,8 +122,8 @@ static void free_game(game_state *state)
     sfree(state);
 }
 
-static game_state *solve_game(game_state *state, game_state *currstate,
-                             game_aux_info *aux, char **error)
+static char *solve_game(game_state *state, game_state *currstate,
+                       game_aux_info *aux, char **error)
 {
     return NULL;
 }
@@ -151,8 +151,13 @@ struct game_drawstate {
     int FIXME;
 };
 
-static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
-                             int x, int y, int button)
+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;
 }
@@ -251,7 +256,8 @@ const struct game thegame = {
     new_ui,
     free_ui,
     game_changed_state,
-    make_move,
+    interpret_move,
+    execute_move,
     game_size,
     game_colours,
     game_new_drawstate,
index 6f0b8fd2ce0eff1c2b0c40e484e8e89ebef4c57f..c6e2cda494882beaecf805cb1b9c129c47a57527 100644 (file)
--- a/pattern.c
+++ b/pattern.c
@@ -664,27 +664,30 @@ static void free_game(game_state *state)
     sfree(state);
 }
 
-static game_state *solve_game(game_state *state, game_state *currstate,
-                             game_aux_info *ai, char **error)
+static char *solve_game(game_state *state, game_state *currstate,
+                       game_aux_info *ai, char **error)
 {
-    game_state *ret;
-
-    ret = dup_game(state);
-    ret->completed = ret->cheated = TRUE;
+    unsigned char *matrix;
+    int matrix_needs_freeing;
+    int full, empty;
+    int w = state->w, h = state->h;
+    int i;
+    char *ret;
 
     /*
      * If we already have the solved state in an aux_info, copy it
      * out.
      */
     if (ai) {
+       assert(ai->w == w && ai->h == h);
 
-       assert(ret->w == ai->w);
-       assert(ret->h == ai->h);
-       memcpy(ret->grid, ai->grid, ai->w * ai->h);
-
+       matrix = ai->grid;
+       matrix_needs_freeing = FALSE;
+       full = GRID_FULL;
+       empty = GRID_EMPTY;
     } else {
-       int w = state->w, h = state->h, i, j, done_any, max;
-       unsigned char *matrix, *workspace;
+       int done_any, max;
+       unsigned char *workspace;
        int *rowdata;
 
        matrix = snewn(w*h, unsigned char);
@@ -711,22 +714,32 @@ static game_state *solve_game(game_state *state, game_state *currstate,
             }
         } while (done_any);
 
-       for (i = 0; i < h; i++) {
-           for (j = 0; j < w; j++) {
-               int c = (matrix[i*w+j] == BLOCK ? GRID_FULL :
-                        matrix[i*w+j] == DOT ? GRID_EMPTY : GRID_UNKNOWN);
-               ret->grid[i*w+j] = c;
-               if (c == GRID_UNKNOWN)
-                   ret->completed = FALSE;
+       sfree(workspace);
+       sfree(rowdata);
+
+       for (i = 0; i < w*h; i++) {
+           if (matrix[i] != BLOCK && matrix[i] != DOT) {
+               sfree(matrix);
+               *error = "Solving algorithm cannot complete this puzzle";
+               return NULL;
            }
        }
 
-       if (!ret->completed) {
-           free_game(ret);
-           *error = "Solving algorithm cannot complete this puzzle";
-           return NULL;
-       }
+       matrix_needs_freeing = TRUE;
+       full = BLOCK;
+       empty = DOT;
+    }
+
+    ret = snewn(w*h+2, char);
+    ret[0] = 'S';
+    for (i = 0; i < w*h; i++) {
+       assert(matrix[i] == full || matrix[i] == empty);
+       ret[i+1] = (matrix[i] == full ? '1' : '0');
     }
+    ret[w*h+1] = '\0';
+
+    if (matrix_needs_freeing)
+       sfree(matrix);
 
     return ret;
 }
@@ -772,16 +785,15 @@ struct game_drawstate {
     unsigned char *visible;
 };
 
-static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
-                             int x, int y, int button) {
-    game_state *ret;
-
+static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
+                           int x, int y, int button)
+{
     button &= ~MOD_MASK;
 
-    x = FROMCOORD(from->w, x);
-    y = FROMCOORD(from->h, y);
+    x = FROMCOORD(state->w, x);
+    y = FROMCOORD(state->h, y);
 
-    if (x >= 0 && x < from->w && y >= 0 && y < from->h &&
+    if (x >= 0 && x < state->w && y >= 0 && y < state->h &&
         (button == LEFT_BUTTON || button == RIGHT_BUTTON ||
          button == MIDDLE_BUTTON)) {
 
@@ -804,7 +816,7 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
         ui->drag_start_x = ui->drag_end_x = x;
         ui->drag_start_y = ui->drag_end_y = y;
 
-        return from;                   /* UI activity occurred */
+        return "";                    /* UI activity occurred */
     }
 
     if (ui->dragging && button == ui->drag) {
@@ -827,13 +839,13 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
 
         if (x < 0) x = 0;
         if (y < 0) y = 0;
-        if (x >= from->w) x = from->w - 1;
-        if (y >= from->h) y = from->h - 1;
+        if (x >= state->w) x = state->w - 1;
+        if (y >= state->h) y = state->h - 1;
 
         ui->drag_end_x = x;
         ui->drag_end_y = y;
 
-        return from;                   /* UI activity occurred */
+        return "";                    /* UI activity occurred */
     }
 
     if (ui->dragging && button == ui->release) {
@@ -847,59 +859,96 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
 
         for (yy = y1; yy <= y2; yy++)
             for (xx = x1; xx <= x2; xx++)
-                if (from->grid[yy * from->w + xx] != ui->state)
+                if (state->grid[yy * state->w + xx] != ui->state)
                     move_needed = TRUE;
 
         ui->dragging = FALSE;
 
         if (move_needed) {
-            ret = dup_game(from);
-            for (yy = y1; yy <= y2; yy++)
-                for (xx = x1; xx <= x2; xx++)
-                    ret->grid[yy * ret->w + xx] = ui->state;
-
-            /*
-             * An actual change, so check to see if we've completed
-             * the game.
-             */
-            if (!ret->completed) {
-                int *rowdata = snewn(ret->rowsize, int);
-                int i, len;
-
-                ret->completed = TRUE;
-
-                for (i=0; i<ret->w; i++) {
-                    len = compute_rowdata(rowdata,
-                                          ret->grid+i, ret->h, ret->w);
-                    if (len != ret->rowlen[i] ||
-                        memcmp(ret->rowdata+i*ret->rowsize, rowdata,
-                               len * sizeof(int))) {
-                        ret->completed = FALSE;
-                        break;
-                    }
-                }
-                for (i=0; i<ret->h; i++) {
-                    len = compute_rowdata(rowdata,
-                                          ret->grid+i*ret->w, ret->w, 1);
-                    if (len != ret->rowlen[i+ret->w] ||
-                        memcmp(ret->rowdata+(i+ret->w)*ret->rowsize, rowdata,
-                               len * sizeof(int))) {
-                        ret->completed = FALSE;
-                        break;
-                    }
-                }
-
-                sfree(rowdata);
-            }
-
-            return ret;
+           char buf[80];
+           sprintf(buf, "%c%d,%d,%d,%d",
+                   (ui->state == GRID_FULL ? 'F' :
+                    ui->state == GRID_EMPTY ? 'E' : 'U'),
+                   x1, y1, x2-x1+1, y2-y1+1);
+           return dupstr(buf);
         } else
-            return from;               /* UI activity occurred */
+            return "";                /* UI activity occurred */
     }
 
     return NULL;
 }
 
+static game_state *execute_move(game_state *from, char *move)
+{
+    game_state *ret;
+    int x1, x2, y1, y2, xx, yy;
+    int val;
+
+    if (move[0] == 'S' && strlen(move) == from->w * from->h + 1) {
+       int i;
+
+       ret = dup_game(from);
+
+       for (i = 0; i < ret->w * ret->h; i++)
+           ret->grid[i] = (move[i+1] == '1' ? GRID_FULL : GRID_EMPTY);
+
+       ret->completed = ret->cheated = TRUE;
+
+       return ret;
+    } else if ((move[0] == 'F' || move[0] == 'E' || move[0] == 'U') &&
+       sscanf(move+1, "%d,%d,%d,%d", &x1, &y1, &x2, &y2) == 4 &&
+       x1 >= 0 && x2 >= 0 && x1+x2 <= from->w &&
+       y1 >= 0 && y2 >= 0 && y1+y2 <= from->h) {
+
+       x2 += x1;
+       y2 += y1;
+       val = (move[0] == 'F' ? GRID_FULL :
+                move[0] == 'E' ? GRID_EMPTY : GRID_UNKNOWN);
+
+       ret = dup_game(from);
+       for (yy = y1; yy < y2; yy++)
+           for (xx = x1; xx < x2; xx++)
+               ret->grid[yy * ret->w + xx] = val;
+
+       /*
+        * An actual change, so check to see if we've completed the
+        * game.
+        */
+       if (!ret->completed) {
+           int *rowdata = snewn(ret->rowsize, int);
+           int i, len;
+
+           ret->completed = TRUE;
+
+           for (i=0; i<ret->w; i++) {
+               len = compute_rowdata(rowdata,
+                                     ret->grid+i, ret->h, ret->w);
+               if (len != ret->rowlen[i] ||
+                   memcmp(ret->rowdata+i*ret->rowsize, rowdata,
+                          len * sizeof(int))) {
+                   ret->completed = FALSE;
+                   break;
+               }
+           }
+           for (i=0; i<ret->h; i++) {
+               len = compute_rowdata(rowdata,
+                                     ret->grid+i*ret->w, ret->w, 1);
+               if (len != ret->rowlen[i+ret->w] ||
+                   memcmp(ret->rowdata+(i+ret->w)*ret->rowsize, rowdata,
+                          len * sizeof(int))) {
+                   ret->completed = FALSE;
+                   break;
+               }
+           }
+
+           sfree(rowdata);
+       }
+
+       return ret;
+    } else
+       return NULL;
+}
+
 /* ----------------------------------------------------------------------
  * Drawing routines.
  */
@@ -1146,7 +1195,8 @@ const struct game thegame = {
     new_ui,
     free_ui,
     game_changed_state,
-    make_move,
+    interpret_move,
+    execute_move,
     game_size,
     game_colours,
     game_new_drawstate,
index 09848d7b8f1156c4a632cdac2f7c34b9ff0ada40..40c391695d356f379a3dd85aa7651baee6c84ea8 100644 (file)
--- a/puzzles.h
+++ b/puzzles.h
@@ -269,16 +269,17 @@ struct game {
     game_state *(*dup_game)(game_state *state);
     void (*free_game)(game_state *state);
     int can_solve;
-    game_state *(*solve)(game_state *orig, game_state *curr,
-                        game_aux_info *aux, char **error);
+    char *(*solve)(game_state *orig, game_state *curr,
+                  game_aux_info *aux, char **error);
     int can_format_as_text;
     char *(*text_format)(game_state *state);
     game_ui *(*new_ui)(game_state *state);
     void (*free_ui)(game_ui *ui);
     void (*changed_state)(game_ui *ui, game_state *oldstate,
                           game_state *newstate);
-    game_state *(*make_move)(game_state *from, game_ui *ui, game_drawstate *ds,
-                             int x, int y, int button);
+    char *(*interpret_move)(game_state *state, game_ui *ui, game_drawstate *ds,
+                           int x, int y, int button);
+    game_state *(*execute_move)(game_state *state, char *move);
     void (*size)(game_params *params, game_drawstate *ds, int *x, int *y,
                  int expand);
     float *(*colours)(frontend *fe, game_state *state, int *ncolours);
diff --git a/rect.c b/rect.c
index 229ce1831b780deae6409e99cc6bc7ba51a0f22c..679ba930e57466f8abca5525ce97e024b5b99f4f 100644 (file)
--- a/rect.c
+++ b/rect.c
@@ -323,7 +323,8 @@ static void remove_number_placement(int w, int h, struct numberdata *number,
 }
 
 static int rect_solver(int w, int h, int nrects, struct numberdata *numbers,
-                       game_state *result, random_state *rs)
+                       unsigned char *hedge, unsigned char *vedge,
+                      random_state *rs)
 {
     struct rectlist *rectpositions;
     int *overlaps, *rectbyplace, *workspace;
@@ -848,25 +849,25 @@ static int rect_solver(int w, int h, int nrects, struct numberdata *numbers,
         assert(rectpositions[i].n > 0);
         if (rectpositions[i].n > 1) {
             ret = FALSE;
-       } else if (result) {
-           /*
-            * Place the rectangle in its only possible position.
-            */
-           int x, y;
-           struct rect *r = &rectpositions[i].rects[0];
-
-           for (y = 0; y < r->h; y++) {
-               if (r->x > 0)
-                   vedge(result, r->x, r->y+y) = 1;
-               if (r->x+r->w < result->w)
-                   vedge(result, r->x+r->w, r->y+y) = 1;
-           }
-           for (x = 0; x < r->w; x++) {
-               if (r->y > 0)
-                   hedge(result, r->x+x, r->y) = 1;
-               if (r->y+r->h < result->h)
-                   hedge(result, r->x+x, r->y+r->h) = 1;
-           }
+        } else if (hedge && vedge) {
+            /*
+             * Place the rectangle in its only possible position.
+             */
+            int x, y;
+            struct rect *r = &rectpositions[i].rects[0];
+
+            for (y = 0; y < r->h; y++) {
+                if (r->x > 0)
+                   vedge[(r->y+y) * w + r->x] = 1;
+                if (r->x+r->w < w)
+                   vedge[(r->y+y) * w + r->x+r->w] = 1;
+            }
+            for (x = 0; x < r->w; x++) {
+                if (r->y > 0)
+                    hedge[r->y * w + r->x+x] = 1;
+                if (r->y+r->h < h)
+                    hedge[(r->y+r->h) * w + r->x+x] = 1;
+            }
        }
     }
 
@@ -1633,7 +1634,7 @@ static char *new_game_desc(game_params *params, random_state *rs,
 
            if (params->unique)
                ret = rect_solver(params->w, params->h, nnumbers, nd,
-                                 NULL, rs);
+                                 NULL, NULL, rs);
            else
                ret = TRUE;            /* allow any number placement at all */
 
@@ -1852,10 +1853,13 @@ static void free_game(game_state *state)
     sfree(state);
 }
 
-static game_state *solve_game(game_state *state, game_state *currstate,
-                             game_aux_info *ai, char **error)
+static char *solve_game(game_state *state, game_state *currstate,
+                       game_aux_info *ai, char **error)
 {
-    game_state *ret;
+    unsigned char *vedge, *hedge;
+    int edges_need_freeing;
+    int x, y, len;
+    char *ret, *p;
 
     if (!ai) {
        int i, j, n;
@@ -1884,10 +1888,13 @@ static game_state *solve_game(game_state *state, game_state *currstate,
 
        assert(j == n);
 
-       ret = dup_game(state);
-       ret->cheated = TRUE;
+       vedge = snewn(state->w * state->h, unsigned char);
+       hedge = snewn(state->w * state->h, unsigned char);
+       memset(vedge, 0, state->w * state->h);
+       memset(hedge, 0, state->w * state->h);
+       edges_need_freeing = TRUE;
 
-       rect_solver(state->w, state->h, n, nd, ret, NULL);
+       rect_solver(state->w, state->h, n, nd, hedge, vedge, NULL);
 
        /*
         * Clean up.
@@ -1895,17 +1902,32 @@ static game_state *solve_game(game_state *state, game_state *currstate,
        for (i = 0; i < n; i++)
            sfree(nd[i].points);
        sfree(nd);
-
-       return ret;
+    } else {
+       assert(state->w == ai->w);
+       assert(state->h == ai->h);
+       vedge = ai->vedge;
+       hedge = ai->hedge;
+       edges_need_freeing = FALSE;
     }
 
-    assert(state->w == ai->w);
-    assert(state->h == ai->h);
+    len = 2 + (state->w-1)*state->h + (state->h-1)*state->w;
+    ret = snewn(len, char);
+
+    p = ret;
+    *p++ = 'S';
+    for (y = 0; y < state->h; y++)
+       for (x = 1; x < state->w; x++)
+           *p++ = vedge[y*state->w+x] ? '1' : '0';
+    for (y = 1; y < state->h; y++)
+       for (x = 0; x < state->w; x++)
+           *p++ = hedge[y*state->w+x] ? '1' : '0';
+    *p++ = '\0';
+    assert(p - ret == len);
 
-    ret = dup_game(state);
-    memcpy(ret->vedge, ai->vedge, ai->w * ai->h * sizeof(unsigned char));
-    memcpy(ret->hedge, ai->hedge, ai->w * ai->h * sizeof(unsigned char));
-    ret->cheated = TRUE;
+    if (edges_need_freeing) {
+       sfree(vedge);
+       sfree(hedge);
+    }
 
     return ret;
 }
@@ -2232,14 +2254,16 @@ static void coord_round(float x, float y, int *xr, int *yr)
     }
 }
 
-static void ui_draw_rect(game_state *state, game_ui *ui,
-                         unsigned char *hedge, unsigned char *vedge, int c)
+/*
+ * Returns TRUE if it has made any change to the grid.
+ */
+static int grid_draw_rect(game_state *state,
+                         unsigned char *hedge, unsigned char *vedge,
+                         int c, int really,
+                         int x1, int y1, int x2, int y2)
 {
     int x, y;
-    int x1 = ui->x1;
-    int y1 = ui->y1;
-    int x2 = ui->x2;
-    int y2 = ui->y2;
+    int changed = FALSE;
 
     /*
      * Draw horizontal edges of rectangles.
@@ -2252,7 +2276,9 @@ static void ui_draw_rect(game_state *state, game_ui *ui,
                     val = c;
                 else if (c == 1)
                     val = 0;
-                index(state,hedge,x,y) = val;
+               changed = changed || (index(state,hedge,x,y) != val);
+               if (really)
+                   index(state,hedge,x,y) = val;
             }
 
     /*
@@ -2266,8 +2292,20 @@ static void ui_draw_rect(game_state *state, game_ui *ui,
                     val = c;
                 else if (c == 1)
                     val = 0;
-                index(state,vedge,x,y) = val;
+               changed = changed || (index(state,vedge,x,y) != val);
+                if (really)
+                   index(state,vedge,x,y) = val;
             }
+
+    return changed;
+}
+
+static int ui_draw_rect(game_state *state, game_ui *ui,
+                       unsigned char *hedge, unsigned char *vedge, int c,
+                       int really)
+{
+    return grid_draw_rect(state, hedge, vedge, c, really,
+                         ui->x1, ui->y1, ui->x2, ui->y2);
 }
 
 static void game_changed_state(game_ui *ui, game_state *oldstate,
@@ -2281,11 +2319,12 @@ struct game_drawstate {
     unsigned long *visible;
 };
 
-static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
-                             int x, int y, int button) {
+static char *interpret_move(game_state *from, game_ui *ui, game_drawstate *ds,
+                           int x, int y, int button)
+{
     int xc, yc;
     int startdrag = FALSE, enddrag = FALSE, active = FALSE;
-    game_state *ret;
+    char buf[80], *ret;
 
     button &= ~MOD_MASK;
 
@@ -2343,44 +2382,24 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
     if (enddrag) {
        if (xc >= 0 && xc <= 2*from->w &&
            yc >= 0 && yc <= 2*from->h) {
-           ret = dup_game(from);
 
            if (ui->dragged) {
-               ui_draw_rect(ret, ui, ret->hedge, ret->vedge, 1);
+               if (ui_draw_rect(from, ui, from->hedge,
+                                from->vedge, 1, FALSE)) {
+                   sprintf(buf, "R%d,%d,%d,%d",
+                           ui->x1, ui->y1, ui->x2 - ui->x1, ui->y2 - ui->y1);
+                   ret = dupstr(buf);
+               }
            } else {
                if ((xc & 1) && !(yc & 1) && HRANGE(from,xc/2,yc/2)) {
-                   hedge(ret,xc/2,yc/2) = !hedge(ret,xc/2,yc/2);
+                   sprintf(buf, "H%d,%d", xc/2, yc/2);
+                   ret = dupstr(buf);
                }
                if ((yc & 1) && !(xc & 1) && VRANGE(from,xc/2,yc/2)) {
-                   vedge(ret,xc/2,yc/2) = !vedge(ret,xc/2,yc/2);
+                   sprintf(buf, "V%d,%d", xc/2, yc/2);
+                   ret = dupstr(buf);
                }
            }
-
-           if (!memcmp(ret->hedge, from->hedge, from->w*from->h) &&
-               !memcmp(ret->vedge, from->vedge, from->w*from->h)) {
-               free_game(ret);
-               ret = NULL;
-           }
-
-            /*
-             * We've made a real change to the grid. Check to see
-             * if the game has been completed.
-             */
-            if (ret && !ret->completed) {
-                int x, y, ok;
-                unsigned char *correct = get_correct(ret);
-
-                ok = TRUE;
-                for (x = 0; x < ret->w; x++)
-                    for (y = 0; y < ret->h; y++)
-                        if (!index(ret, correct, x, y))
-                            ok = FALSE;
-
-                sfree(correct);
-
-                if (ok)
-                    ret->completed = TRUE;
-            }
        }
 
        ui->drag_start_x = -1;
@@ -2398,11 +2417,84 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
     if (ret)
        return ret;                    /* a move has been made */
     else if (active)
-        return from;                   /* UI activity has occurred */
+        return "";                    /* UI activity has occurred */
     else
        return NULL;
 }
 
+static game_state *execute_move(game_state *from, char *move)
+{
+    game_state *ret;
+    int x1, y1, x2, y2, mode;
+
+    if (move[0] == 'S') {
+       char *p = move+1;
+       int x, y;
+
+       ret = dup_game(from);
+       ret->cheated = TRUE;
+
+       for (y = 0; y < ret->h; y++)
+           for (x = 1; x < ret->w; x++) {
+               vedge(ret, x, y) = (*p == '1');
+               if (*p) p++;
+           }
+       for (y = 1; y < ret->h; y++)
+           for (x = 0; x < ret->w; x++) {
+               hedge(ret, x, y) = (*p == '1');
+               if (*p) p++;
+           }
+
+       return ret;
+
+    } else if (move[0] == 'R' &&
+       sscanf(move+1, "%d,%d,%d,%d", &x1, &y1, &x2, &y2) == 4 &&
+       x1 >= 0 && x2 >= 0 && x1+x2 <= from->w &&
+       y1 >= 0 && y2 >= 0 && y1+y2 <= from->h) {
+       x2 += x1;
+       y2 += y1;
+       mode = move[0];
+    } else if ((move[0] == 'H' || move[0] == 'V') &&
+              sscanf(move+1, "%d,%d", &x1, &y1) == 2 &&
+              (move[0] == 'H' ? HRANGE(from, x1, y1) :
+               VRANGE(from, x1, y1))) {
+       mode = move[0];
+    } else
+       return NULL;                   /* can't parse move string */
+
+    ret = dup_game(from);
+
+    if (mode == 'R') {
+       grid_draw_rect(ret, ret->hedge, ret->vedge, 1, TRUE, x1, y1, x2, y2);
+    } else if (mode == 'H') {
+       hedge(ret,x1,y1) = !hedge(ret,x1,y1);
+    } else if (mode == 'V') {
+       vedge(ret,x1,y1) = !vedge(ret,x1,y1);
+    }
+
+    /*
+     * We've made a real change to the grid. Check to see
+     * if the game has been completed.
+     */
+    if (!ret->completed) {
+       int x, y, ok;
+       unsigned char *correct = get_correct(ret);
+
+       ok = TRUE;
+       for (x = 0; x < ret->w; x++)
+           for (y = 0; y < ret->h; y++)
+               if (!index(ret, correct, x, y))
+                   ok = FALSE;
+
+       sfree(correct);
+
+       if (ok)
+           ret->completed = TRUE;
+    }
+
+    return ret;
+}
+
 /* ----------------------------------------------------------------------
  * Drawing routines.
  */
@@ -2559,7 +2651,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
         vedge = snewn(state->w*state->h, unsigned char);
         memcpy(hedge, state->hedge, state->w*state->h);
         memcpy(vedge, state->vedge, state->w*state->h);
-        ui_draw_rect(state, ui, hedge, vedge, 2);
+        ui_draw_rect(state, ui, hedge, vedge, 2, TRUE);
     } else {
         hedge = state->hedge;
         vedge = state->vedge;
@@ -2708,7 +2800,8 @@ const struct game thegame = {
     new_ui,
     free_ui,
     game_changed_state,
-    make_move,
+    interpret_move,
+    execute_move,
     game_size,
     game_colours,
     game_new_drawstate,
index c066258a017c48cf6e8b1ce0c524bbffe7d8f650..95ced1a6a7064be192269589df3b7ea1df8776d6 100644 (file)
@@ -355,8 +355,8 @@ static void free_game(game_state *state)
     sfree(state);
 }
 
-static game_state *solve_game(game_state *state, game_state *currstate,
-                             game_aux_info *aux, char **error)
+static char *solve_game(game_state *state, game_state *currstate,
+                       game_aux_info *aux, char **error)
 {
     return NULL;
 }
@@ -427,20 +427,37 @@ static void game_changed_state(game_ui *ui, game_state *oldstate,
     sel_clear(ui, newstate);
 }
 
-static void sel_remove(game_ui *ui, game_state *state)
+static char *sel_movedesc(game_ui *ui, game_state *state)
 {
-    int i, nremoved = 0;
+    int i;
+    char *ret, *sep, buf[80];
+    int retlen, retsize;
 
-    state->score += npoints(&state->params, ui->nselected);
+    retsize = 256;
+    ret = snewn(retsize, char);
+    retlen = 0;
+    ret[retlen++] = 'M';
+    sep = "";
 
     for (i = 0; i < state->n; i++) {
        if (ui->tiles[i] & TILE_SELECTED) {
-           nremoved++;
-           state->tiles[i] = 0;
+           sprintf(buf, "%s%d", sep, i);
+           sep = ",";
+           if (retlen + strlen(buf) >= retsize) {
+               retsize = retlen + strlen(buf) + 256;
+               ret = sresize(ret, retsize, char);
+           }
+           strcpy(ret + retlen, buf);
+           retlen += strlen(buf);
+
            ui->tiles[i] &= ~TILE_SELECTED;
        }
     }
     ui->nselected = 0;
+
+    assert(retlen < retsize);
+    ret[retlen++] = '\0';
+    return sresize(ret, retlen, char);
 }
 
 static void sel_expand(game_ui *ui, game_state *state, int tx, int ty)
@@ -567,11 +584,13 @@ struct game_drawstate {
     int *tiles; /* contains colour and SELECTED. */
 };
 
-static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
-                             int x, int y, int button)
+static game_state *execute_move(game_state *from, char *move);
+
+static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
+                           int x, int y, int button)
 {
     int tx, ty;
-    game_state *ret = from;
+    char *ret = "";
 
     ui->displaysel = 0;
 
@@ -583,8 +602,8 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
        ui->displaysel = 1;
        dx = (button == CURSOR_LEFT) ? -1 : ((button == CURSOR_RIGHT) ? +1 : 0);
        dy = (button == CURSOR_DOWN) ? +1 : ((button == CURSOR_UP)    ? -1 : 0);
-       ui->xsel = (ui->xsel + from->params.w + dx) % from->params.w;
-       ui->ysel = (ui->ysel + from->params.h + dy) % from->params.h;
+       ui->xsel = (ui->xsel + state->params.w + dx) % state->params.w;
+       ui->ysel = (ui->ysel + state->params.h + dy) % state->params.h;
        return ret;
     } else if (button == CURSOR_SELECT || button == ' ' || button == '\r' ||
               button == '\n') {
@@ -594,30 +613,71 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
     } else
        return NULL;
 
-    if (tx < 0 || tx >= from->params.w || ty < 0 || ty >= from->params.h)
+    if (tx < 0 || tx >= state->params.w || ty < 0 || ty >= state->params.h)
        return NULL;
-    if (COL(from, tx, ty) == 0) return NULL;
+    if (COL(state, tx, ty) == 0) return NULL;
 
     if (ISSEL(ui,tx,ty)) {
        if (button == RIGHT_BUTTON)
-           sel_clear(ui, from);
+           sel_clear(ui, state);
        else {
-           /* this is the actual move. */
-           ret = dup_game(from);
-           sel_remove(ui, ret);
-           sg_snuggle(ret); /* shifts blanks down and to the left */
-           sg_check(ret);   /* checks for completeness or impossibility */
+           game_state *tmp;
+
+           ret = sel_movedesc(ui, state);
+
+           /*
+            * Unfortunately, we must check for completeness or
+            * impossibility now, in order to update the game_ui;
+            * and we can't do that without constructing the new
+            * grid. Sigh.
+            */
+           tmp = execute_move(state, ret);
+           if (tmp->complete || tmp->impossible)
+               ui->displaysel = 0;
+           free_game(tmp);
        }
     } else {
-       sel_clear(ui, from); /* might be no-op */
-       sel_expand(ui, from, tx, ty);
+       sel_clear(ui, state); /* might be no-op */
+       sel_expand(ui, state, tx, ty);
     }
-    if (ret->complete || ret->impossible)
-       ui->displaysel = 0;
 
     return ret;
 }
 
+static game_state *execute_move(game_state *from, char *move)
+{
+    int i, n;
+    game_state *ret;
+
+    if (move[0] == 'M') {
+       ret = dup_game(from);
+
+       n = 0;
+       move++;
+
+       while (*move) {
+           i = atoi(move);
+           if (i < 0 || i >= ret->n) {
+               free_game(ret);
+               return NULL;
+           }
+           n++;
+           ret->tiles[i] = 0;
+
+           while (*move && isdigit((unsigned char)*move)) move++;
+           if (*move == ',') move++;
+       }
+
+       ret->score += npoints(&ret->params, n);
+
+       sg_snuggle(ret); /* shifts blanks down and to the left */
+       sg_check(ret);   /* checks for completeness or impossibility */
+
+       return ret;
+    } else
+       return NULL;                   /* couldn't parse move string */
+}
+
 /* ----------------------------------------------------------------------
  * Drawing routines.
  */
@@ -940,7 +1000,8 @@ const struct game thegame = {
     new_ui,
     free_ui,
     game_changed_state,
-    make_move,
+    interpret_move,
+    execute_move,
     game_size,
     game_colours,
     game_new_drawstate,
index 9e93b2fa9ff2a9690050b71533b129478e762844..6c158d5f9c0e6ee12cc4a02707289b6b3c4fdc8f 100644 (file)
--- a/sixteen.c
+++ b/sixteen.c
@@ -510,25 +510,10 @@ static void free_game(game_state *state)
     sfree(state);
 }
 
-static game_state *solve_game(game_state *state, game_state *currstate,
-                             game_aux_info *aux, char **error)
+static char *solve_game(game_state *state, game_state *currstate,
+                       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.
-     */
-    for (i = 0; i < ret->n; i++)
-       ret->tiles[i] = i+1;
-    ret->used_solve = ret->just_used_solve = TRUE;
-    ret->completed = ret->movecount = 1;
-
-    return ret;
+    return dupstr("S");
 }
 
 static char *game_text_format(game_state *state)
@@ -591,11 +576,11 @@ struct game_drawstate {
     int tilesize;
 };
 
-static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
-                             int x, int y, int button) {
-    int cx, cy;
-    int dx, dy, tx, ty, n;
-    game_state *ret;
+static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
+                           int x, int y, int button)
+{
+    int cx, cy, dx, dy;
+    char buf[80];
 
     button &= ~MOD_MASK;
     if (button != LEFT_BUTTON && button != RIGHT_BUTTON)
@@ -603,38 +588,81 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
 
     cx = FROMCOORD(x);
     cy = FROMCOORD(y);
-    if (cx == -1 && cy >= 0 && cy < from->h)
-        n = from->w, dx = +1, dy = 0;
-    else if (cx == from->w && cy >= 0 && cy < from->h)
-        n = from->w, dx = -1, dy = 0;
-    else if (cy == -1 && cx >= 0 && cx < from->w)
-        n = from->h, dy = +1, dx = 0;
-    else if (cy == from->h && cx >= 0 && cx < from->w)
-        n = from->h, dy = -1, dx = 0;
+    if (cx == -1 && cy >= 0 && cy < state->h)
+        dx = -1, dy = 0;
+    else if (cx == state->w && cy >= 0 && cy < state->h)
+        dx = +1, dy = 0;
+    else if (cy == -1 && cx >= 0 && cx < state->w)
+        dy = -1, dx = 0;
+    else if (cy == state->h && cx >= 0 && cx < state->w)
+        dy = +1, dx = 0;
     else
         return NULL;                   /* invalid click location */
 
     /* reverse direction if right hand button is pressed */
-    if (button == RIGHT_BUTTON)
-    {
-        dx = -dx; if (dx) cx = from->w - 1 - cx;
-        dy = -dy; if (dy) cy = from->h - 1 - cy;
+    if (button == RIGHT_BUTTON) {
+        dx = -dx;
+        dy = -dy;
     }
 
+    if (dx)
+       sprintf(buf, "R%d,%d", cy, dx);
+    else
+       sprintf(buf, "C%d,%d", cx, dy);
+    return dupstr(buf);
+}
+
+static game_state *execute_move(game_state *from, char *move)
+{
+    int cx, cy, dx, dy;
+    int tx, ty, n;
+    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->used_solve = ret->just_used_solve = TRUE;
+       ret->completed = ret->movecount = 1;
+
+       return ret;
+    }
+
+    if (move[0] == 'R' && sscanf(move+1, "%d,%d", &cy, &dx) == 2 &&
+       cy >= 0 && cy < from->h) {
+       cx = dy = 0;
+       n = from->w;
+    } else if (move[0] == 'C' && sscanf(move+1, "%d,%d", &cx, &dy) == 2 &&
+              cx >= 0 && cx < from->w) {
+       cy = dx = 0;
+       n = from->h;
+    } else
+       return NULL;
+
     ret = dup_game(from);
     ret->just_used_solve = FALSE;      /* zero this in a hurry */
 
     do {
-        cx += dx;
-        cy += dy;
-        tx = (cx + dx + from->w) % from->w;
-        ty = (cy + dy + from->h) % from->h;
+        tx = (cx - dx + from->w) % from->w;
+        ty = (cy - dy + from->h) % from->h;
         ret->tiles[C(ret, cx, cy)] = from->tiles[C(from, tx, ty)];
+        cx = tx;
+        cy = ty;
     } while (--n > 0);
 
     ret->movecount++;
 
-    ret->last_movement_sense = -(dx+dy);
+    ret->last_movement_sense = dx+dy;
 
     /*
      * See if the game has been completed.
@@ -1033,7 +1061,8 @@ const struct game thegame = {
     new_ui,
     free_ui,
     game_changed_state,
-    make_move,
+    interpret_move,
+    execute_move,
     game_size,
     game_colours,
     game_new_drawstate,
diff --git a/solo.c b/solo.c
index 4255c02a4d5efe38f4d57b000ecf66b237696545..74ca8720b626009f837d154d0049d102c372fa5f 100644 (file)
--- a/solo.c
+++ b/solo.c
@@ -1759,15 +1759,14 @@ static void free_game(game_state *state)
     sfree(state);
 }
 
-static game_state *solve_game(game_state *state, game_state *currstate,
-                             game_aux_info *ai, char **error)
+static char *solve_game(game_state *state, game_state *currstate,
+                       game_aux_info *ai, char **error)
 {
-    game_state *ret;
     int c = state->c, r = state->r, cr = c*r;
-    int rsolve_ret;
-
-    ret = dup_game(state);
-    ret->completed = ret->cheated = TRUE;
+    int i, len;
+    char *ret, *p, *sep;
+    digit *grid;
+    int grid_needs_freeing;
 
     /*
      * If we already have the solution in the aux_info, save
@@ -1777,20 +1776,65 @@ static game_state *solve_game(game_state *state, game_state *currstate,
 
        assert(c == ai->c);
        assert(r == ai->r);
-       memcpy(ret->grid, ai->grid, cr * cr * sizeof(digit));
+       grid = ai->grid;
+       grid_needs_freeing = FALSE;
 
     } else {
-       rsolve_ret = rsolve(c, r, ret->grid, NULL, 2);
+       int rsolve_ret;
+
+       grid = snewn(cr*cr, digit);
+       memcpy(grid, state->grid, cr*cr);
+       rsolve_ret = rsolve(c, r, grid, NULL, 2);
 
        if (rsolve_ret != 1) {
-           free_game(ret);
+           sfree(grid);
            if (rsolve_ret == 0)
                *error = "No solution exists for this puzzle";
            else
                *error = "Multiple solutions exist for this puzzle";
            return NULL;
        }
+
+       grid_needs_freeing = TRUE;
+    }
+
+    /*
+     * It's surprisingly easy to work out _exactly_ how long this
+     * string needs to be. To decimal-encode all the numbers from 1
+     * to n:
+     * 
+     *  - every number has a units digit; total is n.
+     *  - all numbers above 9 have a tens digit; total is max(n-9,0).
+     *  - all numbers above 99 have a hundreds digit; total is max(n-99,0).
+     *  - and so on.
+     */
+    len = 0;
+    for (i = 1; i <= cr; i *= 10)
+       len += max(cr - i + 1, 0);
+    len += cr;                /* don't forget the commas */
+    len *= cr;                /* there are cr rows of these */
+
+    /*
+     * Now len is one bigger than the total size of the
+     * comma-separated numbers (because we counted an
+     * additional leading comma). We need to have a leading S
+     * and a trailing NUL, so we're off by one in total.
+     */
+    len++;
+
+    ret = snewn(len, char);
+    p = ret;
+    *p++ = 'S';
+    sep = "";
+    for (i = 0; i < cr*cr; i++) {
+       p += sprintf(p, "%s%d", sep, grid[i]);
+       sep = ",";
     }
+    *p++ = '\0';
+    assert(p - ret == len);
+
+    if (grid_needs_freeing)
+       sfree(grid);
 
     return ret;
 }
@@ -1914,12 +1958,12 @@ struct game_drawstate {
     int *entered_items;
 };
 
-static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
-                             int x, int y, int button)
+static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
+                           int x, int y, int button)
 {
-    int c = from->c, r = from->r, cr = c*r;
+    int c = state->c, r = state->r, cr = c*r;
     int tx, ty;
-    game_state *ret;
+    char buf[80];
 
     button &= ~MOD_MASK;
 
@@ -1928,7 +1972,7 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
 
     if (tx >= 0 && tx < cr && ty >= 0 && ty < cr) {
         if (button == LEFT_BUTTON) {
-            if (from->immutable[ty*cr+tx]) {
+            if (state->immutable[ty*cr+tx]) {
                 ui->hx = ui->hy = -1;
             } else if (tx == ui->hx && ty == ui->hy && ui->hpencil == 0) {
                 ui->hx = ui->hy = -1;
@@ -1937,13 +1981,13 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
                 ui->hy = ty;
                 ui->hpencil = 0;
             }
-            return from;                      /* UI activity occurred */
+            return "";                /* UI activity occurred */
         }
         if (button == RIGHT_BUTTON) {
             /*
              * Pencil-mode highlighting for non filled squares.
              */
-            if (from->grid[ty*cr+tx] == 0) {
+            if (state->grid[ty*cr+tx] == 0) {
                 if (tx == ui->hx && ty == ui->hy && ui->hpencil) {
                     ui->hx = ui->hy = -1;
                 } else {
@@ -1954,7 +1998,7 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
             } else {
                 ui->hx = ui->hy = -1;
             }
-            return from;                      /* UI activity occurred */
+            return "";                /* UI activity occurred */
         }
     }
 
@@ -1977,7 +2021,7 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
          * able to highlight the square, but it never hurts to be
          * careful.
          */
-       if (from->immutable[ui->hy*cr+ui->hx])
+       if (state->immutable[ui->hy*cr+ui->hx])
            return NULL;
 
         /*
@@ -1986,16 +2030,57 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
          * have even been able to pencil-highlight the square, but
          * it never hurts to be careful.
          */
-        if (ui->hpencil && from->grid[ui->hy*cr+ui->hx])
+        if (ui->hpencil && state->grid[ui->hy*cr+ui->hx])
             return NULL;
 
+       sprintf(buf, "%c%d,%d,%d",
+               ui->hpencil && n > 0 ? 'P' : 'R', ui->hx, ui->hy, n);
+
+       ui->hx = ui->hy = -1;
+
+       return dupstr(buf);
+    }
+
+    return NULL;
+}
+
+static game_state *execute_move(game_state *from, char *move)
+{
+    int c = from->c, r = from->r, cr = c*r;
+    game_state *ret;
+    int x, y, n;
+
+    if (move[0] == 'S') {
+       char *p;
+
        ret = dup_game(from);
-        if (ui->hpencil && n > 0) {
-            int index = (ui->hy*cr+ui->hx) * cr + (n-1);
+       ret->completed = ret->cheated = TRUE;
+
+       p = move+1;
+       for (n = 0; n < cr*cr; n++) {
+           ret->grid[n] = atoi(p);
+
+           if (!*p || ret->grid[n] < 1 || ret->grid[n] > cr) {
+               free_game(ret);
+               return NULL;
+           }
+
+           while (*p && isdigit((unsigned char)*p)) p++;
+           if (*p == ',') p++;
+       }
+
+       return ret;
+    } else if ((move[0] == 'P' || move[0] == 'R') &&
+       sscanf(move+1, "%d,%d,%d", &x, &y, &n) == 3 &&
+       x >= 0 && x < cr && y >= 0 && y < cr && n >= 0 && n <= cr) {
+
+       ret = dup_game(from);
+        if (move[0] == 'P' && n > 0) {
+            int index = (y*cr+x) * cr + (n-1);
             ret->pencil[index] = !ret->pencil[index];
         } else {
-            ret->grid[ui->hy*cr+ui->hx] = n;
-            memset(ret->pencil + (ui->hy*cr+ui->hx)*cr, 0, cr);
+            ret->grid[y*cr+x] = n;
+            memset(ret->pencil + (y*cr+x)*cr, 0, cr);
 
             /*
              * We've made a real change to the grid. Check to see
@@ -2005,12 +2090,9 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
                 ret->completed = TRUE;
             }
         }
-       ui->hx = ui->hy = -1;
-
-       return ret;                    /* made a valid move */
-    }
-
-    return NULL;
+       return ret;
+    } else
+       return NULL;                   /* couldn't parse move string */
 }
 
 /* ----------------------------------------------------------------------
@@ -2339,7 +2421,8 @@ const struct game thegame = {
     new_ui,
     free_ui,
     game_changed_state,
-    make_move,
+    interpret_move,
+    execute_move,
     game_size,
     game_colours,
     game_new_drawstate,
index 8000780f13989923dd6a41b7c1d61cdb351382fb..66ce4d120e756207eec94d8bf06825437b8c9ea7 100644 (file)
--- a/twiddle.c
+++ b/twiddle.c
@@ -546,26 +546,10 @@ static int compare_int(const void *av, const void *bv)
        return 0;
 }
 
-static game_state *solve_game(game_state *state, game_state *currstate,
-                             game_aux_info *aux, char **error)
+static char *solve_game(game_state *state, game_state *currstate,
+                       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 = 1;
-
-    return ret;
+    return dupstr("S");
 }
 
 static char *game_text_format(game_state *state)
@@ -636,11 +620,11 @@ struct game_drawstate {
     int tilesize;
 };
 
-static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
-                             int x, int y, int button)
+static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
+                           int x, int y, int button)
 {
-    int w = from->w, h = from->h, n = from->n, wh = w*h;
-    game_state *ret;
+    int w = state->w, h = state->h, n = state->n /* , wh = w*h */;
+    char buf[80];
     int dir;
 
     button = button & (~MOD_MASK | MOD_NUM_KEYPAD);
@@ -698,8 +682,43 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
     }
 
     /*
-     * This is a valid move. Make it.
+     * If we reach here, we have a valid move.
      */
+    sprintf(buf, "M%d,%d,%d", x, y, dir);
+    return dupstr(buf);
+}
+
+static game_state *execute_move(game_state *from, char *move)
+{
+    game_state *ret;
+    int w = from->w, h = from->h, n = from->n, wh = w*h;
+    int x, y, dir;
+
+    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.
+        */
+       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 = 1;
+
+       return ret;
+    }
+
+    if (move[0] != 'M' ||
+       sscanf(move+1, "%d,%d,%d", &x, &y, &dir) != 3 ||
+       x < 0 || y < 0 || x > from->w - n || y > from->h - n)
+       return NULL;                   /* can't parse this move string */
+
     ret = dup_game(from);
     ret->just_used_solve = FALSE;  /* zero this in a hurry */
     ret->movecount++;
@@ -1206,7 +1225,8 @@ const struct game thegame = {
     new_ui,
     free_ui,
     game_changed_state,
-    make_move,
+    interpret_move,
+    execute_move,
     game_size,
     game_colours,
     game_new_drawstate,