Re-architecting of the game backend interface. make_move() has been

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]
This commit is contained in:
Simon Tatham
2005-06-27 19:34:54 +00:00
parent 7cb29412c1
commit 76d50e6905
16 changed files with 1377 additions and 639 deletions

254
cube.c
View File

@ -984,7 +984,7 @@ static void free_game(game_state *state)
sfree(state); sfree(state);
} }
static game_state *solve_game(game_state *state, game_state *currstate, static char *solve_game(game_state *state, game_state *currstate,
game_aux_info *aux, char **error) game_aux_info *aux, char **error)
{ {
return NULL; return NULL;
@ -1014,104 +1014,14 @@ struct game_drawstate {
int ox, oy; /* pixel position of float origin */ 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 mask, dest, i, j;
int pkey[2], skey[2], dkey[2];
float points[4]; float points[4];
game_state *ret;
float angle;
int i, j, dest, mask;
struct solid *poly;
button = button & (~MOD_MASK | MOD_NUM_KEYPAD);
/*
* Moves can be made with the cursor keys or numeric keypad, or
* alternatively you can left-click and the polyhedron will
* move in the general direction of the mouse pointer.
*/
if (button == CURSOR_UP || button == (MOD_NUM_KEYPAD | '8'))
direction = UP;
else if (button == CURSOR_DOWN || button == (MOD_NUM_KEYPAD | '2'))
direction = DOWN;
else if (button == CURSOR_LEFT || button == (MOD_NUM_KEYPAD | '4'))
direction = LEFT;
else if (button == CURSOR_RIGHT || button == (MOD_NUM_KEYPAD | '6'))
direction = RIGHT;
else if (button == (MOD_NUM_KEYPAD | '7'))
direction = UP_LEFT;
else if (button == (MOD_NUM_KEYPAD | '1'))
direction = DOWN_LEFT;
else if (button == (MOD_NUM_KEYPAD | '9'))
direction = UP_RIGHT;
else if (button == (MOD_NUM_KEYPAD | '3'))
direction = DOWN_RIGHT;
else if (button == LEFT_BUTTON) {
/*
* Find the bearing of the click point from the current
* square's centre.
*/
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;
if (x == cx && y == cy)
return NULL; /* clicked in exact centre! */
angle = atan2(y - cy, x - cx);
/*
* There are three possibilities.
*
* - This square is a square, so we choose between UP,
* DOWN, LEFT and RIGHT by dividing the available angle
* at the 45-degree points.
*
* - This square is an up-pointing triangle, so we choose
* between DOWN, LEFT and RIGHT by dividing into
* 120-degree arcs.
*
* - This square is a down-pointing triangle, so we choose
* between UP, LEFT and RIGHT in the inverse manner.
*
* Don't forget that since our y-coordinates increase
* downwards, `angle' is measured _clockwise_ from the
* x-axis, not anticlockwise as most mathematicians would
* instinctively assume.
*/
if (from->squares[from->current].npoints == 4) {
/* Square. */
if (fabs(angle) > 3*PI/4)
direction = LEFT;
else if (fabs(angle) < PI/4)
direction = RIGHT;
else if (angle > 0)
direction = DOWN;
else
direction = UP;
} else if (from->squares[from->current].directions[UP] == 0) {
/* Up-pointing triangle. */
if (angle < -PI/2 || angle > 5*PI/6)
direction = LEFT;
else if (angle > PI/6)
direction = DOWN;
else
direction = RIGHT;
} else {
/* Down-pointing triangle. */
assert(from->squares[from->current].directions[DOWN] == 0);
if (angle > PI/2 || angle < -5*PI/6)
direction = LEFT;
else if (angle < -PI/6)
direction = UP;
else
direction = RIGHT;
}
} else
return NULL;
/* /*
* Find the two points in the current grid square which * Find the two points in the current grid square which
@ -1119,7 +1029,7 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
*/ */
mask = from->squares[from->current].directions[direction]; mask = from->squares[from->current].directions[direction];
if (mask == 0) if (mask == 0)
return NULL; return -1;
for (i = j = 0; i < from->squares[from->current].npoints; i++) for (i = j = 0; i < from->squares[from->current].npoints; i++)
if (mask & (1 << i)) { if (mask & (1 << i)) {
points[j*2] = from->squares[from->current].points[i*2]; points[j*2] = from->squares[from->current].points[i*2];
@ -1156,11 +1066,154 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
} }
} }
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);
/*
* Moves can be made with the cursor keys or numeric keypad, or
* alternatively you can left-click and the polyhedron will
* move in the general direction of the mouse pointer.
*/
if (button == CURSOR_UP || button == (MOD_NUM_KEYPAD | '8'))
direction = UP;
else if (button == CURSOR_DOWN || button == (MOD_NUM_KEYPAD | '2'))
direction = DOWN;
else if (button == CURSOR_LEFT || button == (MOD_NUM_KEYPAD | '4'))
direction = LEFT;
else if (button == CURSOR_RIGHT || button == (MOD_NUM_KEYPAD | '6'))
direction = RIGHT;
else if (button == (MOD_NUM_KEYPAD | '7'))
direction = UP_LEFT;
else if (button == (MOD_NUM_KEYPAD | '1'))
direction = DOWN_LEFT;
else if (button == (MOD_NUM_KEYPAD | '9'))
direction = UP_RIGHT;
else if (button == (MOD_NUM_KEYPAD | '3'))
direction = DOWN_RIGHT;
else if (button == LEFT_BUTTON) {
/*
* Find the bearing of the click point from the current
* square's centre.
*/
int cx, cy;
double angle;
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! */
angle = atan2(y - cy, x - cx);
/*
* There are three possibilities.
*
* - This square is a square, so we choose between UP,
* DOWN, LEFT and RIGHT by dividing the available angle
* at the 45-degree points.
*
* - This square is an up-pointing triangle, so we choose
* between DOWN, LEFT and RIGHT by dividing into
* 120-degree arcs.
*
* - This square is a down-pointing triangle, so we choose
* between UP, LEFT and RIGHT in the inverse manner.
*
* Don't forget that since our y-coordinates increase
* downwards, `angle' is measured _clockwise_ from the
* x-axis, not anticlockwise as most mathematicians would
* instinctively assume.
*/
if (state->squares[state->current].npoints == 4) {
/* Square. */
if (fabs(angle) > 3*PI/4)
direction = LEFT;
else if (fabs(angle) < PI/4)
direction = RIGHT;
else if (angle > 0)
direction = DOWN;
else
direction = UP;
} else if (state->squares[state->current].directions[UP] == 0) {
/* Up-pointing triangle. */
if (angle < -PI/2 || angle > 5*PI/6)
direction = LEFT;
else if (angle > PI/6)
direction = DOWN;
else
direction = RIGHT;
} else {
/* Down-pointing triangle. */
assert(state->squares[state->current].directions[DOWN] == 0);
if (angle > PI/2 || angle < -5*PI/6)
direction = LEFT;
else if (angle < -PI/6)
direction = UP;
else
direction = RIGHT;
}
} else
return NULL;
mask = state->squares[state->current].directions[direction];
if (mask == 0)
return NULL;
/*
* Translate diagonal directions into orthogonal ones.
*/
if (direction > DOWN) {
for (i = LEFT; i <= DOWN; i++)
if (state->squares[state->current].directions[i] == mask) {
direction = i;
break;
}
assert(direction <= DOWN);
}
if (find_move_dest(state, direction, skey, dkey) < 0)
return NULL;
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) if (dest < 0)
return NULL; return NULL;
ret = dup_game(from); ret = dup_game(from);
ret->current = i; ret->current = dest;
/* /*
* So we know what grid square we're aiming for, and we also * So we know what grid square we're aiming for, and we also
@ -1662,7 +1715,8 @@ const struct game thegame = {
new_ui, new_ui,
free_ui, free_ui,
game_changed_state, game_changed_state,
make_move, interpret_move,
execute_move,
game_size, game_size,
game_colours, game_colours,
game_new_drawstate, game_new_drawstate,

View File

@ -379,26 +379,10 @@ static void free_game(game_state *state)
sfree(state); sfree(state);
} }
static game_state *solve_game(game_state *state, game_state *currstate, static char *solve_game(game_state *state, game_state *currstate,
game_aux_info *aux, char **error) game_aux_info *aux, char **error)
{ {
game_state *ret = dup_game(state); return dupstr("S");
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;
} }
static char *game_text_format(game_state *state) static char *game_text_format(game_state *state)
@ -464,28 +448,29 @@ struct game_drawstate {
int tilesize; int tilesize;
}; };
static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds, static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
int x, int y, int button) { int x, int y, int button)
int gx, gy, dx, dy, ux, uy, up, p; {
game_state *ret; int gx, gy, dx, dy;
char buf[80];
button &= ~MOD_MASK; button &= ~MOD_MASK;
gx = X(from, from->gap_pos); gx = X(state, state->gap_pos);
gy = Y(from, from->gap_pos); gy = Y(state, state->gap_pos);
if (button == CURSOR_RIGHT && gx > 0) if (button == CURSOR_RIGHT && gx > 0)
dx = gx - 1, dy = gy; 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; dx = gx + 1, dy = gy;
else if (button == CURSOR_DOWN && gy > 0) else if (button == CURSOR_DOWN && gy > 0)
dy = gy - 1, dx = gx; 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; dy = gy + 1, dx = gx;
else if (button == LEFT_BUTTON) { else if (button == LEFT_BUTTON) {
dx = FROMCOORD(x); dx = FROMCOORD(x);
dy = FROMCOORD(y); 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 */ return NULL; /* out of bounds */
/* /*
* Any click location should be equal to the gap location * 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 } else
return NULL; /* no move */ 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 * Find the unit displacement from the original gap
* position towards this one. * position towards this one.
@ -859,7 +883,8 @@ const struct game thegame = {
new_ui, new_ui,
free_ui, free_ui,
game_changed_state, game_changed_state,
make_move, interpret_move,
execute_move,
game_size, game_size,
game_colours, game_colours,
game_new_drawstate, game_new_drawstate,

67
flip.c
View File

@ -675,7 +675,7 @@ static void rowxor(unsigned char *row1, unsigned char *row2, int len)
row1[i] ^= row2[i]; row1[i] ^= row2[i];
} }
static game_state *solve_game(game_state *state, game_state *currstate, static char *solve_game(game_state *state, game_state *currstate,
game_aux_info *aux, char **error) game_aux_info *aux, char **error)
{ {
int w = state->w, h = state->h, wh = w * h; int w = state->w, h = state->h, wh = w * h;
@ -683,7 +683,7 @@ static game_state *solve_game(game_state *state, game_state *currstate,
int *und, nund; int *und, nund;
int rowsdone, colsdone; int rowsdone, colsdone;
int i, j, k, len, bestlen; int i, j, k, len, bestlen;
game_state *ret; char *ret;
/* /*
* Set up a list of simultaneous equations. Each one is of * 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 * We have a solution. Produce a move string encoding the
* marked in annotations. * solution.
*/ */
ret = dup_game(currstate); ret = snewn(wh + 2, char);
ret->hints_active = TRUE; ret[0] = 'S';
ret->cheated = TRUE; for (i = 0; i < wh; i++)
for (i = 0; i < wh; i++) { ret[i+1] = shortest[i] ? '1' : '0';
ret->grid[i] &= ~2; ret[wh+1] = '\0';
if (shortest[i])
ret->grid[i] |= 2;
}
sfree(shortest); sfree(shortest);
sfree(solution); sfree(solution);
@ -885,15 +882,44 @@ struct game_drawstate {
int tilesize; int tilesize;
}; };
static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds, static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
int x, int y, int button) int x, int y, int button)
{ {
int w = from->w, h = from->h, wh = w * h; int w = state->w, h = state->h /*, wh = w * h */;
game_state *ret; char buf[80];
if (button == LEFT_BUTTON) { if (button == LEFT_BUTTON) {
int tx = FROMCOORD(x), ty = FROMCOORD(y); int tx = FROMCOORD(x), ty = FROMCOORD(y);
if (tx >= 0 && tx < w && ty >= 0 && ty < h) { if (tx >= 0 && tx < w && ty >= 0 && ty < h) {
sprintf(buf, "M%d,%d", tx, ty);
return dupstr(buf);
}
}
return NULL;
}
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; int i, j, done;
ret = dup_game(from); ret = dup_game(from);
@ -901,7 +927,7 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
if (!ret->completed) if (!ret->completed)
ret->moves++; ret->moves++;
i = ty * w + tx; i = y * w + x;
done = TRUE; done = TRUE;
for (j = 0; j < wh; j++) { for (j = 0; j < wh; j++) {
@ -916,10 +942,8 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
} }
return ret; return ret;
} } else
} return NULL; /* can't parse move string */
return NULL;
} }
/* ---------------------------------------------------------------------- /* ----------------------------------------------------------------------
@ -1206,7 +1230,8 @@ const struct game thegame = {
new_ui, new_ui,
free_ui, free_ui,
game_changed_state, game_changed_state,
make_move, interpret_move,
execute_move,
game_size, game_size,
game_colours, game_colours,
game_new_drawstate, game_new_drawstate,

117
guess.c
View File

@ -364,12 +364,10 @@ static void free_game(game_state *state)
sfree(state); sfree(state);
} }
static game_state *solve_game(game_state *state, game_state *currstate, static char *solve_game(game_state *state, game_state *currstate,
game_aux_info *aux, char **error) game_aux_info *aux, char **error)
{ {
game_state *ret = dup_game(currstate); return dupstr("S");
ret->solved = 1;
return ret;
} }
static char *game_text_format(game_state *state) static char *game_text_format(game_state *state)
@ -548,39 +546,44 @@ static int mark_pegs(pegrow guess, pegrow solution, int ncols)
return nc_place; 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; char *buf, *p, *sep;
game_state *to = dup_game(from); int len, i, solved;
pegrow tmppegs;
for (i = 0; i < to->solution->npegs; i++) { len = ui->curr_pegs->npegs * 20 + 2;
to->guesses[from->next_go]->pegs[i] = ui->curr_pegs->pegs[i]; 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 = ",";
} }
nc_place = mark_pegs(to->guesses[from->next_go], to->solution, to->params.ncolours); *p++ = '\0';
assert(p - buf <= len);
buf = sresize(buf, len, char);
if (nc_place == to->solution->npegs) { tmppegs = dup_pegrow(ui->curr_pegs);
to->solved = 1; /* win! */ solved = mark_pegs(tmppegs, from->solution, from->params.ncolours);
} else { solved = (solved == from->params.ncolours);
to->next_go = from->next_go + 1; free_pegrow(tmppegs);
if (to->next_go >= to->params.nguesses)
to->solved = 1; /* 'lose' so we show the pegs. */
}
for (i = 0; i < to->solution->npegs; i++) { for (i = 0; i < from->solution->npegs; i++) {
if (!ui->holds[i] || to->solved) { if (!ui->holds[i] || solved) {
ui->curr_pegs->pegs[i] = 0; ui->curr_pegs->pegs[i] = 0;
ncleared++;
} }
if (to->solved) ui->holds[i] = 0; if (solved) ui->holds[i] = 0;
} }
ui->markable = is_markable(&from->params, ui->curr_pegs); ui->markable = is_markable(&from->params, ui->curr_pegs);
if (!ui->markable && ui->peg_cur == to->solution->npegs) if (!ui->markable && ui->peg_cur == from->solution->npegs)
ui->peg_cur--; ui->peg_cur--;
return to; return buf;
} }
static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds, static char *interpret_move(game_state *from, game_ui *ui, game_drawstate *ds,
int x, int y, int button) int x, int y, int button)
{ {
int over_col = 0; /* one-indexed */ int over_col = 0; /* one-indexed */
@ -588,7 +591,7 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
int over_past_guess_y = -1; /* zero-indexed */ int over_past_guess_y = -1; /* zero-indexed */
int over_past_guess_x = -1; /* zero-indexed */ int over_past_guess_x = -1; /* zero-indexed */
int over_hint = 0; /* zero or one */ 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_ox = GUESS_X(from->next_go, 0);
int guess_oy = GUESS_Y(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; ui->drag_y = y;
debug(("Start dragging, col = %d, (%d,%d)", debug(("Start dragging, col = %d, (%d,%d)",
ui->drag_col, ui->drag_x, ui->drag_y)); ui->drag_col, ui->drag_x, ui->drag_y));
ret = from; ret = "";
} }
} else if (button == LEFT_DRAG && ui->drag_col) { } else if (button == LEFT_DRAG && ui->drag_col) {
ui->drag_x = x; ui->drag_x = x;
ui->drag_y = y; ui->drag_y = y;
debug(("Keep dragging, (%d,%d)", ui->drag_x, ui->drag_y)); debug(("Keep dragging, (%d,%d)", ui->drag_x, ui->drag_y));
ret = from; ret = "";
} else if (button == LEFT_RELEASE && ui->drag_col) { } else if (button == LEFT_RELEASE && ui->drag_col) {
if (over_guess > -1) { if (over_guess > -1) {
debug(("Dropping colour %d onto guess peg %d", 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->drag_opeg = -1;
ui->display_cur = 0; ui->display_cur = 0;
debug(("Stop dragging.")); debug(("Stop dragging."));
ret = from; ret = "";
} else if (button == RIGHT_BUTTON) { } else if (button == RIGHT_BUTTON) {
if (over_guess > -1) { if (over_guess > -1) {
/* we use ths feedback in the game_ui to signify /* we use ths feedback in the game_ui to signify
* 'carry this peg to the next guess as well'. */ * 'carry this peg to the next guess as well'. */
ui->holds[over_guess] = 1 - ui->holds[over_guess]; ui->holds[over_guess] = 1 - ui->holds[over_guess];
ret = from; ret = "";
} }
} else if (button == LEFT_RELEASE && over_hint && ui->markable) { } else if (button == LEFT_RELEASE && over_hint && ui->markable) {
/* NB this won't trigger if on the end of a drag; that's on /* NB this won't trigger if on the end of a drag; that's on
* purpose, in case you drop by mistake... */ * purpose, in case you drop by mistake... */
ret = mark_move(from, ui); ret = encode_move(from, ui);
} }
/* keyboard input */ /* keyboard input */
@ -687,7 +690,7 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
ui->colour_cur++; ui->colour_cur++;
if (button == CURSOR_UP && ui->colour_cur > 0) if (button == CURSOR_UP && ui->colour_cur > 0)
ui->colour_cur--; ui->colour_cur--;
ret = from; ret = "";
} else if (button == CURSOR_LEFT || button == CURSOR_RIGHT) { } else if (button == CURSOR_LEFT || button == CURSOR_RIGHT) {
int maxcur = from->params.npegs; int maxcur = from->params.npegs;
if (ui->markable) maxcur++; 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++; ui->peg_cur++;
if (button == CURSOR_LEFT && ui->peg_cur > 0) if (button == CURSOR_LEFT && ui->peg_cur > 0)
ui->peg_cur--; ui->peg_cur--;
ret = from; ret = "";
} else if (button == CURSOR_SELECT || button == ' ' || button == '\r' || } else if (button == CURSOR_SELECT || button == ' ' || button == '\r' ||
button == '\n') { button == '\n') {
ui->display_cur = 1; ui->display_cur = 1;
if (ui->peg_cur == from->params.npegs) { if (ui->peg_cur == from->params.npegs) {
ret = mark_move(from, ui); ret = encode_move(from, ui);
} else { } else {
set_peg(&from->params, ui, ui->peg_cur, ui->colour_cur+1); set_peg(&from->params, ui, ui->peg_cur, ui->colour_cur+1);
ret = from; ret = "";
} }
} else if (button == 'H' || button == 'h') { } else if (button == 'H' || button == 'h') {
ui->display_cur = 1; ui->display_cur = 1;
ui->holds[ui->peg_cur] = 1 - ui->holds[ui->peg_cur]; ui->holds[ui->peg_cur] = 1 - ui->holds[ui->peg_cur];
ret = from; ret = "";
} }
return 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. * Drawing routines.
*/ */
@ -1217,7 +1261,8 @@ const struct game thegame = {
new_ui, new_ui,
free_ui, free_ui,
game_changed_state, game_changed_state,
make_move, interpret_move,
execute_move,
game_size, game_size,
game_colours, game_colours,
game_new_drawstate, game_new_drawstate,

View File

@ -367,9 +367,22 @@ static int midend_really_process_key(midend_data *me, int x, int y, int button)
ret = 0; ret = 0;
goto done; goto done;
} else { } else {
game_state *s = game_state *s;
me->ourgame->make_move(me->states[me->statepos-1].state, char *movestr;
movestr =
me->ourgame->interpret_move(me->states[me->statepos-1].state,
me->ui, me->drawstate, x, y, button); 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) { 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) char *midend_solve(midend_data *me)
{ {
game_state *s; game_state *s;
char *msg; char *msg, *movestr;
if (!me->ourgame->can_solve) if (!me->ourgame->can_solve)
return "This game does not support the Solve operation"; 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! */ return "No game set up to solve"; /* _shouldn't_ happen! */
msg = "Solve operation failed"; /* game _should_ overwrite on error */ msg = "Solve operation failed"; /* game _should_ overwrite on error */
s = me->ourgame->solve(me->states[0].state, movestr = me->ourgame->solve(me->states[0].state,
me->states[me->statepos-1].state, me->states[me->statepos-1].state,
me->aux_info, &msg); me->aux_info, &msg);
if (!s) if (!movestr)
return msg; 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. * Now enter the solved state as the next move.

175
mines.c
View File

@ -2279,46 +2279,15 @@ static void free_game(game_state *state)
sfree(state); sfree(state);
} }
static game_state *solve_game(game_state *state, game_state *currstate, static char *solve_game(game_state *state, game_state *currstate,
game_aux_info *aux, char **error) 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) { if (!state->layout->mines) {
*error = "Game has not been started yet"; *error = "Game has not been started yet";
return NULL; return NULL;
} }
ret = dup_game(state); return dupstr("S");
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;
} }
static char *game_text_format(game_state *state) 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, static char *interpret_move(game_state *from, game_ui *ui, game_drawstate *ds,
int x, int y, int button) int x, int y, int button)
{ {
game_state *ret;
int cx, cy; int cx, cy;
char buf[256];
if (from->dead || from->won) if (from->dead || from->won)
return NULL; /* no further moves permitted */ 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->hx = cx;
ui->hy = cy; ui->hy = cy;
ui->hradius = (from->grid[cy*from->w+cx] >= 0 ? 1 : 0); ui->hradius = (from->grid[cy*from->w+cx] >= 0 ? 1 : 0);
return from; return "";
} }
if (button == RIGHT_BUTTON) { 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) from->grid[cy * from->w + cx] != -1)
return NULL; return NULL;
ret = dup_game(from); sprintf(buf, "F%d,%d", cx, cy);
ret->just_used_solve = FALSE; return dupstr(buf);
ret->grid[cy * from->w + cx] ^= (-2 ^ -1);
return ret;
} }
if (button == LEFT_RELEASE || button == MIDDLE_RELEASE) { 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 * 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) if (cx < 0 || cx >= from->w || cy < 0 || cy >= from->h)
return from; return "";
/* /*
* Left-clicking on a covered square opens a tile. Not * 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 && if (button == LEFT_RELEASE &&
(from->grid[cy * from->w + cx] == -2 || (from->grid[cy * from->w + cx] == -2 ||
from->grid[cy * from->w + cx] == -3)) { from->grid[cy * from->w + cx] == -3)) {
ret = dup_game(from); /* Check if you've killed yourself. */
ret->just_used_solve = FALSE; if (from->layout->mines && from->layout->mines[cy * from->w + cx])
open_square(ret, cx, cy);
if (ret->dead)
ui->deaths++; ui->deaths++;
return ret;
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]) { if (n == from->grid[cy * from->w + cx]) {
/*
* 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 = dup_game(from);
ret->just_used_solve = FALSE; 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 (dy = -1; dy <= +1; dy++)
for (dx = -1; dx <= +1; dx++) for (dx = -1; dx <= +1; dx++)
if (cx+dx >= 0 && cx+dx < ret->w && 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)] == -2 ||
ret->grid[(cy+dy)*ret->w+(cx+dx)] == -3)) ret->grid[(cy+dy)*ret->w+(cx+dx)] == -3))
open_square(ret, cx+dx, cy+dy); open_square(ret, cx+dx, cy+dy);
if (ret->dead) } else {
ui->deaths++; free_game(ret);
return NULL;
}
while (*move && *move != ';') move++;
if (*move) move++;
}
return ret; return ret;
} }
}
return from;
}
return NULL;
} }
/* ---------------------------------------------------------------------- /* ----------------------------------------------------------------------
@ -2962,7 +3022,8 @@ const struct game thegame = {
new_ui, new_ui,
free_ui, free_ui,
game_changed_state, game_changed_state,
make_move, interpret_move,
execute_move,
game_size, game_size,
game_colours, game_colours,
game_new_drawstate, game_new_drawstate,

224
net.c
View File

@ -1666,28 +1666,87 @@ static void free_game(game_state *state)
sfree(state); sfree(state);
} }
static game_state *solve_game(game_state *state, game_state *currstate, static char *solve_game(game_state *state, game_state *currstate,
game_aux_info *aux, char **error) 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) { if (!aux) {
/* /*
* Run the internal solver on the provided grid. This might * Run the internal solver on the provided grid. This might
* not yield a complete solution. * not yield a complete solution.
*/ */
ret = dup_game(state); tiles = snewn(state->width * state->height, unsigned char);
net_solver(ret->width, ret->height, ret->tiles, memcpy(tiles, state->tiles, state->width * state->height);
ret->barriers, ret->wrapping); net_solver(state->width, state->height, tiles,
state->barriers, state->wrapping);
tiles_need_freeing = TRUE;
} else { } else {
assert(aux->width == state->width); tiles = aux->tiles;
assert(aux->height == state->height); tiles_need_freeing = FALSE;
ret = dup_game(state);
memcpy(ret->tiles, aux->tiles, ret->width * ret->height);
ret->used_solve = ret->just_used_solve = TRUE;
ret->completed = TRUE;
} }
/*
* 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; return ret;
} }
@ -1803,10 +1862,11 @@ struct game_drawstate {
/* ---------------------------------------------------------------------- /* ----------------------------------------------------------------------
* Process a move. * Process a move.
*/ */
static game_state *make_move(game_state *state, game_ui *ui, static char *interpret_move(game_state *state, game_ui *ui,
game_drawstate *ds, int x, int y, int button) { game_drawstate *ds, int x, int y, int button)
game_state *ret, *nullret; {
int tx, ty, orig; char *nullret;
int tx, ty;
int shift = button & MOD_SHFT, ctrl = button & MOD_CTRL; int shift = button & MOD_SHFT, ctrl = button & MOD_CTRL;
button &= ~MOD_MASK; button &= ~MOD_MASK;
@ -1818,7 +1878,7 @@ static game_state *make_move(game_state *state, game_ui *ui,
if (ui->cur_visible) { if (ui->cur_visible) {
ui->cur_visible = FALSE; 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); OFFSET(ui->cur_x, ui->cur_y, ui->cur_x, ui->cur_y, dir, state);
ui->cur_visible = TRUE; ui->cur_visible = TRUE;
} }
return state; /* UI activity has occurred */ return ""; /* UI activity has occurred */
} else if (button == 'a' || button == 's' || button == 'd' || } else if (button == 'a' || button == 's' || button == 'd' ||
button == 'A' || button == 'S' || button == 'D' || button == 'A' || button == 'S' || button == 'D' ||
button == CURSOR_SELECT) { button == CURSOR_SELECT) {
@ -1900,13 +1960,9 @@ static game_state *make_move(game_state *state, game_ui *ui,
* unlocks it.) * unlocks it.)
*/ */
if (button == MIDDLE_BUTTON) { if (button == MIDDLE_BUTTON) {
char buf[80];
ret = dup_game(state); sprintf(buf, "L%d,%d", tx, ty);
ret->just_used_solve = FALSE; return dupstr(buf);
tile(ret, tx, ty) ^= LOCKED;
ret->last_rotate_dir = ret->last_rotate_x = ret->last_rotate_y = 0;
return ret;
} else if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { } 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 * Otherwise, turn the tile one way or the other. Left button
* turns anticlockwise; right button turns clockwise. * turns anticlockwise; right button turns clockwise.
*/ */
ret = dup_game(state); char buf[80];
ret->just_used_solve = FALSE; sprintf(buf, "%c%d,%d", (button == LEFT_BUTTON ? 'A' : 'C'), tx, ty);
orig = tile(ret, tx, ty); return dupstr(buf);
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;
} else if (button == 'J') { } else if (button == 'J') {
/* /*
* Jumble all unlocked tiles to random orientations. * Jumble all unlocked tiles to random orientations.
*/ */
int jx, jy;
ret = dup_game(state); int jx, jy, maxlen;
ret->just_used_solve = FALSE; char *ret, *p;
for (jy = 0; jy < ret->height; jy++) {
for (jx = 0; jx < ret->width; jx++) { /*
if (!(tile(ret, jx, jy) & LOCKED)) { * 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); int rot = random_upto(ui->rs, 4);
orig = tile(ret, jx, jy); if (rot) {
tile(ret, jx, jy) = ROT(orig, rot); p += sprintf(p, ";%c%d,%d", "AFC"[rot-1], jx, jy);
} }
} }
} }
}
*p++ = '\0';
assert(p - ret < maxlen);
ret = sresize(ret, p - ret, char);
return ret;
} else {
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_dir = 0; /* suppress animation */
ret->last_rotate_x = ret->last_rotate_y = 0; 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 { } else {
ret = NULL; /* placate optimisers which don't understand assert(0) */ assert(move[0] == 'L');
assert(0); 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. * 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 x1, y1;
int complete = TRUE; int complete = TRUE;
@ -1983,6 +2105,7 @@ static game_state *make_move(game_state *state, game_ui *ui,
return ret; return ret;
} }
/* ---------------------------------------------------------------------- /* ----------------------------------------------------------------------
* Routines for drawing the game position on the screen. * Routines for drawing the game position on the screen.
*/ */
@ -2617,7 +2740,8 @@ const struct game thegame = {
new_ui, new_ui,
free_ui, free_ui,
game_changed_state, game_changed_state,
make_move, interpret_move,
execute_move,
game_size, game_size,
game_colours, game_colours,
game_new_drawstate, game_new_drawstate,

View File

@ -893,10 +893,11 @@ static void free_game(game_state *state)
sfree(state); sfree(state);
} }
static game_state *solve_game(game_state *state, game_state *currstate, static char *solve_game(game_state *state, game_state *currstate,
game_aux_info *aux, char **error) game_aux_info *aux, char **error)
{ {
game_state *ret; char *ret;
int i;
if (!aux) { if (!aux) {
*error = "Solution not known for this puzzle"; *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->width == state->width);
assert(aux->height == state->height); assert(aux->height == state->height);
ret = dup_game(state); ret = snewn(aux->width * aux->height + 2, char);
memcpy(ret->tiles, aux->tiles, ret->width * ret->height); ret[0] = 'S';
ret->used_solve = ret->just_used_solve = TRUE; for (i = 0; i < aux->width * aux->height; i++)
ret->completed = ret->move_count = 1; ret[i+1] = "0123456789abcdef"[aux->tiles[i] & 0xF];
ret[i+1] = '\0';
return ret; return ret;
} }
@ -1058,12 +1059,12 @@ struct game_drawstate {
unsigned char *visible; unsigned char *visible;
}; };
static game_state *make_move(game_state *state, game_ui *ui, static char *interpret_move(game_state *state, game_ui *ui,
game_drawstate *ds, int x, int y, int button) game_drawstate *ds, int x, int y, int button)
{ {
int cx, cy; int cx, cy;
int n, dx, dy; int n, dx, dy;
game_state *ret; char buf[80];
button &= ~MOD_MASK; button &= ~MOD_MASK;
@ -1099,16 +1100,59 @@ static game_state *make_move(game_state *state, game_ui *ui,
dy = -dy; 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; ret->just_used_solve = FALSE;
if (dx == 0) slide_col(ret, dy, cx); if (col)
else slide_row(ret, dx, cy); slide_col(ret, d, c);
else
slide_row(ret, d, c);
ret->move_count++; ret->move_count++;
ret->last_move_row = dx ? cy : -1; ret->last_move_row = col ? -1 : c;
ret->last_move_col = dx ? -1 : cx; ret->last_move_col = col ? c : -1;
ret->last_move_dir = dx + dy; ret->last_move_dir = d;
/* /*
* See if the game has been completed. * See if the game has been completed.
@ -1779,7 +1823,8 @@ const struct game thegame = {
new_ui, new_ui,
free_ui, free_ui,
game_changed_state, game_changed_state,
make_move, interpret_move,
execute_move,
game_size, game_size,
game_colours, game_colours,
game_new_drawstate, game_new_drawstate,

View File

@ -122,7 +122,7 @@ static void free_game(game_state *state)
sfree(state); sfree(state);
} }
static game_state *solve_game(game_state *state, game_state *currstate, static char *solve_game(game_state *state, game_state *currstate,
game_aux_info *aux, char **error) game_aux_info *aux, char **error)
{ {
return NULL; return NULL;
@ -151,12 +151,17 @@ struct game_drawstate {
int FIXME; int FIXME;
}; };
static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds, static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
int x, int y, int button) int x, int y, int button)
{ {
return NULL; return NULL;
} }
static game_state *execute_move(game_state *state, char *move)
{
return NULL;
}
/* ---------------------------------------------------------------------- /* ----------------------------------------------------------------------
* Drawing routines. * Drawing routines.
*/ */
@ -251,7 +256,8 @@ const struct game thegame = {
new_ui, new_ui,
free_ui, free_ui,
game_changed_state, game_changed_state,
make_move, interpret_move,
execute_move,
game_size, game_size,
game_colours, game_colours,
game_new_drawstate, game_new_drawstate,

136
pattern.c
View File

@ -664,27 +664,30 @@ static void free_game(game_state *state)
sfree(state); sfree(state);
} }
static game_state *solve_game(game_state *state, game_state *currstate, static char *solve_game(game_state *state, game_state *currstate,
game_aux_info *ai, char **error) game_aux_info *ai, char **error)
{ {
game_state *ret; unsigned char *matrix;
int matrix_needs_freeing;
ret = dup_game(state); int full, empty;
ret->completed = ret->cheated = TRUE; int w = state->w, h = state->h;
int i;
char *ret;
/* /*
* If we already have the solved state in an aux_info, copy it * If we already have the solved state in an aux_info, copy it
* out. * out.
*/ */
if (ai) { if (ai) {
assert(ai->w == w && ai->h == h);
assert(ret->w == ai->w); matrix = ai->grid;
assert(ret->h == ai->h); matrix_needs_freeing = FALSE;
memcpy(ret->grid, ai->grid, ai->w * ai->h); full = GRID_FULL;
empty = GRID_EMPTY;
} else { } else {
int w = state->w, h = state->h, i, j, done_any, max; int done_any, max;
unsigned char *matrix, *workspace; unsigned char *workspace;
int *rowdata; int *rowdata;
matrix = snewn(w*h, unsigned char); matrix = snewn(w*h, unsigned char);
@ -711,23 +714,33 @@ static game_state *solve_game(game_state *state, game_state *currstate,
} }
} while (done_any); } while (done_any);
for (i = 0; i < h; i++) { sfree(workspace);
for (j = 0; j < w; j++) { sfree(rowdata);
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;
}
}
if (!ret->completed) { for (i = 0; i < w*h; i++) {
free_game(ret); if (matrix[i] != BLOCK && matrix[i] != DOT) {
sfree(matrix);
*error = "Solving algorithm cannot complete this puzzle"; *error = "Solving algorithm cannot complete this puzzle";
return NULL; 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; return ret;
} }
@ -772,16 +785,15 @@ struct game_drawstate {
unsigned char *visible; unsigned char *visible;
}; };
static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds, static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
int x, int y, int button) { int x, int y, int button)
game_state *ret; {
button &= ~MOD_MASK; button &= ~MOD_MASK;
x = FROMCOORD(from->w, x); x = FROMCOORD(state->w, x);
y = FROMCOORD(from->h, y); 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 == LEFT_BUTTON || button == RIGHT_BUTTON ||
button == MIDDLE_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_x = ui->drag_end_x = x;
ui->drag_start_y = ui->drag_end_y = y; ui->drag_start_y = ui->drag_end_y = y;
return from; /* UI activity occurred */ return ""; /* UI activity occurred */
} }
if (ui->dragging && button == ui->drag) { 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 (x < 0) x = 0;
if (y < 0) y = 0; if (y < 0) y = 0;
if (x >= from->w) x = from->w - 1; if (x >= state->w) x = state->w - 1;
if (y >= from->h) y = from->h - 1; if (y >= state->h) y = state->h - 1;
ui->drag_end_x = x; ui->drag_end_x = x;
ui->drag_end_y = y; ui->drag_end_y = y;
return from; /* UI activity occurred */ return ""; /* UI activity occurred */
} }
if (ui->dragging && button == ui->release) { if (ui->dragging && button == ui->release) {
@ -847,20 +859,60 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
for (yy = y1; yy <= y2; yy++) for (yy = y1; yy <= y2; yy++)
for (xx = x1; xx <= x2; xx++) 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; move_needed = TRUE;
ui->dragging = FALSE; ui->dragging = FALSE;
if (move_needed) { if (move_needed) {
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 ""; /* 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); ret = dup_game(from);
for (yy = y1; yy <= y2; yy++)
for (xx = x1; xx <= x2; xx++) for (i = 0; i < ret->w * ret->h; i++)
ret->grid[yy * ret->w + xx] = ui->state; 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 * An actual change, so check to see if we've completed the
* the game. * game.
*/ */
if (!ret->completed) { if (!ret->completed) {
int *rowdata = snewn(ret->rowsize, int); int *rowdata = snewn(ret->rowsize, int);
@ -894,9 +946,6 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
return ret; return ret;
} else } else
return from; /* UI activity occurred */
}
return NULL; return NULL;
} }
@ -1146,7 +1195,8 @@ const struct game thegame = {
new_ui, new_ui,
free_ui, free_ui,
game_changed_state, game_changed_state,
make_move, interpret_move,
execute_move,
game_size, game_size,
game_colours, game_colours,
game_new_drawstate, game_new_drawstate,

View File

@ -269,7 +269,7 @@ struct game {
game_state *(*dup_game)(game_state *state); game_state *(*dup_game)(game_state *state);
void (*free_game)(game_state *state); void (*free_game)(game_state *state);
int can_solve; int can_solve;
game_state *(*solve)(game_state *orig, game_state *curr, char *(*solve)(game_state *orig, game_state *curr,
game_aux_info *aux, char **error); game_aux_info *aux, char **error);
int can_format_as_text; int can_format_as_text;
char *(*text_format)(game_state *state); char *(*text_format)(game_state *state);
@ -277,8 +277,9 @@ struct game {
void (*free_ui)(game_ui *ui); void (*free_ui)(game_ui *ui);
void (*changed_state)(game_ui *ui, game_state *oldstate, void (*changed_state)(game_ui *ui, game_state *oldstate,
game_state *newstate); game_state *newstate);
game_state *(*make_move)(game_state *from, game_ui *ui, game_drawstate *ds, char *(*interpret_move)(game_state *state, game_ui *ui, game_drawstate *ds,
int x, int y, int button); 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, void (*size)(game_params *params, game_drawstate *ds, int *x, int *y,
int expand); int expand);
float *(*colours)(frontend *fe, game_state *state, int *ncolours); float *(*colours)(frontend *fe, game_state *state, int *ncolours);

221
rect.c
View File

@ -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, 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; struct rectlist *rectpositions;
int *overlaps, *rectbyplace, *workspace; int *overlaps, *rectbyplace, *workspace;
@ -848,7 +849,7 @@ static int rect_solver(int w, int h, int nrects, struct numberdata *numbers,
assert(rectpositions[i].n > 0); assert(rectpositions[i].n > 0);
if (rectpositions[i].n > 1) { if (rectpositions[i].n > 1) {
ret = FALSE; ret = FALSE;
} else if (result) { } else if (hedge && vedge) {
/* /*
* Place the rectangle in its only possible position. * Place the rectangle in its only possible position.
*/ */
@ -857,15 +858,15 @@ static int rect_solver(int w, int h, int nrects, struct numberdata *numbers,
for (y = 0; y < r->h; y++) { for (y = 0; y < r->h; y++) {
if (r->x > 0) if (r->x > 0)
vedge(result, r->x, r->y+y) = 1; vedge[(r->y+y) * w + r->x] = 1;
if (r->x+r->w < result->w) if (r->x+r->w < w)
vedge(result, r->x+r->w, r->y+y) = 1; vedge[(r->y+y) * w + r->x+r->w] = 1;
} }
for (x = 0; x < r->w; x++) { for (x = 0; x < r->w; x++) {
if (r->y > 0) if (r->y > 0)
hedge(result, r->x+x, r->y) = 1; hedge[r->y * w + r->x+x] = 1;
if (r->y+r->h < result->h) if (r->y+r->h < h)
hedge(result, r->x+x, r->y+r->h) = 1; 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) if (params->unique)
ret = rect_solver(params->w, params->h, nnumbers, nd, ret = rect_solver(params->w, params->h, nnumbers, nd,
NULL, rs); NULL, NULL, rs);
else else
ret = TRUE; /* allow any number placement at all */ ret = TRUE; /* allow any number placement at all */
@ -1852,10 +1853,13 @@ static void free_game(game_state *state)
sfree(state); sfree(state);
} }
static game_state *solve_game(game_state *state, game_state *currstate, static char *solve_game(game_state *state, game_state *currstate,
game_aux_info *ai, char **error) 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) { if (!ai) {
int i, j, n; int i, j, n;
@ -1884,10 +1888,13 @@ static game_state *solve_game(game_state *state, game_state *currstate,
assert(j == n); assert(j == n);
ret = dup_game(state); vedge = snewn(state->w * state->h, unsigned char);
ret->cheated = TRUE; 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. * Clean up.
@ -1895,17 +1902,32 @@ static game_state *solve_game(game_state *state, game_state *currstate,
for (i = 0; i < n; i++) for (i = 0; i < n; i++)
sfree(nd[i].points); sfree(nd[i].points);
sfree(nd); sfree(nd);
} else {
return ret;
}
assert(state->w == ai->w); assert(state->w == ai->w);
assert(state->h == ai->h); assert(state->h == ai->h);
vedge = ai->vedge;
hedge = ai->hedge;
edges_need_freeing = FALSE;
}
ret = dup_game(state); len = 2 + (state->w-1)*state->h + (state->h-1)*state->w;
memcpy(ret->vedge, ai->vedge, ai->w * ai->h * sizeof(unsigned char)); ret = snewn(len, char);
memcpy(ret->hedge, ai->hedge, ai->w * ai->h * sizeof(unsigned char));
ret->cheated = TRUE; 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);
if (edges_need_freeing) {
sfree(vedge);
sfree(hedge);
}
return ret; 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 x, y;
int x1 = ui->x1; int changed = FALSE;
int y1 = ui->y1;
int x2 = ui->x2;
int y2 = ui->y2;
/* /*
* Draw horizontal edges of rectangles. * Draw horizontal edges of rectangles.
@ -2252,6 +2276,8 @@ static void ui_draw_rect(game_state *state, game_ui *ui,
val = c; val = c;
else if (c == 1) else if (c == 1)
val = 0; val = 0;
changed = changed || (index(state,hedge,x,y) != val);
if (really)
index(state,hedge,x,y) = val; index(state,hedge,x,y) = val;
} }
@ -2266,8 +2292,20 @@ static void ui_draw_rect(game_state *state, game_ui *ui,
val = c; val = c;
else if (c == 1) else if (c == 1)
val = 0; val = 0;
changed = changed || (index(state,vedge,x,y) != val);
if (really)
index(state,vedge,x,y) = val; 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, static void game_changed_state(game_ui *ui, game_state *oldstate,
@ -2281,11 +2319,12 @@ struct game_drawstate {
unsigned long *visible; unsigned long *visible;
}; };
static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds, static char *interpret_move(game_state *from, game_ui *ui, game_drawstate *ds,
int x, int y, int button) { int x, int y, int button)
{
int xc, yc; int xc, yc;
int startdrag = FALSE, enddrag = FALSE, active = FALSE; int startdrag = FALSE, enddrag = FALSE, active = FALSE;
game_state *ret; char buf[80], *ret;
button &= ~MOD_MASK; button &= ~MOD_MASK;
@ -2343,44 +2382,24 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
if (enddrag) { if (enddrag) {
if (xc >= 0 && xc <= 2*from->w && if (xc >= 0 && xc <= 2*from->w &&
yc >= 0 && yc <= 2*from->h) { yc >= 0 && yc <= 2*from->h) {
ret = dup_game(from);
if (ui->dragged) { 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 { } else {
if ((xc & 1) && !(yc & 1) && HRANGE(from,xc/2,yc/2)) { 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)) { 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; 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) if (ret)
return ret; /* a move has been made */ return ret; /* a move has been made */
else if (active) else if (active)
return from; /* UI activity has occurred */ return ""; /* UI activity has occurred */
else else
return NULL; 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. * 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); vedge = snewn(state->w*state->h, unsigned char);
memcpy(hedge, state->hedge, state->w*state->h); memcpy(hedge, state->hedge, state->w*state->h);
memcpy(vedge, state->vedge, 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 { } else {
hedge = state->hedge; hedge = state->hedge;
vedge = state->vedge; vedge = state->vedge;
@ -2708,7 +2800,8 @@ const struct game thegame = {
new_ui, new_ui,
free_ui, free_ui,
game_changed_state, game_changed_state,
make_move, interpret_move,
execute_move,
game_size, game_size,
game_colours, game_colours,
game_new_drawstate, game_new_drawstate,

View File

@ -355,7 +355,7 @@ static void free_game(game_state *state)
sfree(state); sfree(state);
} }
static game_state *solve_game(game_state *state, game_state *currstate, static char *solve_game(game_state *state, game_state *currstate,
game_aux_info *aux, char **error) game_aux_info *aux, char **error)
{ {
return NULL; return NULL;
@ -427,20 +427,37 @@ static void game_changed_state(game_ui *ui, game_state *oldstate,
sel_clear(ui, newstate); 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++) { for (i = 0; i < state->n; i++) {
if (ui->tiles[i] & TILE_SELECTED) { if (ui->tiles[i] & TILE_SELECTED) {
nremoved++; sprintf(buf, "%s%d", sep, i);
state->tiles[i] = 0; 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->tiles[i] &= ~TILE_SELECTED;
} }
} }
ui->nselected = 0; 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) 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. */ int *tiles; /* contains colour and SELECTED. */
}; };
static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds, 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 x, int y, int button)
{ {
int tx, ty; int tx, ty;
game_state *ret = from; char *ret = "";
ui->displaysel = 0; ui->displaysel = 0;
@ -583,8 +602,8 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
ui->displaysel = 1; ui->displaysel = 1;
dx = (button == CURSOR_LEFT) ? -1 : ((button == CURSOR_RIGHT) ? +1 : 0); dx = (button == CURSOR_LEFT) ? -1 : ((button == CURSOR_RIGHT) ? +1 : 0);
dy = (button == CURSOR_DOWN) ? +1 : ((button == CURSOR_UP) ? -1 : 0); dy = (button == CURSOR_DOWN) ? +1 : ((button == CURSOR_UP) ? -1 : 0);
ui->xsel = (ui->xsel + from->params.w + dx) % from->params.w; ui->xsel = (ui->xsel + state->params.w + dx) % state->params.w;
ui->ysel = (ui->ysel + from->params.h + dy) % from->params.h; ui->ysel = (ui->ysel + state->params.h + dy) % state->params.h;
return ret; return ret;
} else if (button == CURSOR_SELECT || button == ' ' || button == '\r' || } else if (button == CURSOR_SELECT || button == ' ' || button == '\r' ||
button == '\n') { button == '\n') {
@ -594,30 +613,71 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
} else } else
return NULL; 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; return NULL;
if (COL(from, tx, ty) == 0) return NULL; if (COL(state, tx, ty) == 0) return NULL;
if (ISSEL(ui,tx,ty)) { if (ISSEL(ui,tx,ty)) {
if (button == RIGHT_BUTTON) if (button == RIGHT_BUTTON)
sel_clear(ui, from); sel_clear(ui, state);
else { else {
/* this is the actual move. */ game_state *tmp;
ret = dup_game(from);
sel_remove(ui, ret); ret = sel_movedesc(ui, state);
sg_snuggle(ret); /* shifts blanks down and to the left */
sg_check(ret); /* checks for completeness or impossibility */ /*
* 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 { } else {
sel_clear(ui, from); /* might be no-op */ sel_clear(ui, state); /* might be no-op */
sel_expand(ui, from, tx, ty); sel_expand(ui, state, tx, ty);
} }
if (ret->complete || ret->impossible)
ui->displaysel = 0;
return ret; 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. * Drawing routines.
*/ */
@ -940,7 +1000,8 @@ const struct game thegame = {
new_ui, new_ui,
free_ui, free_ui,
game_changed_state, game_changed_state,
make_move, interpret_move,
execute_move,
game_size, game_size,
game_colours, game_colours,
game_new_drawstate, game_new_drawstate,

109
sixteen.c
View File

@ -510,25 +510,10 @@ static void free_game(game_state *state)
sfree(state); sfree(state);
} }
static game_state *solve_game(game_state *state, game_state *currstate, static char *solve_game(game_state *state, game_state *currstate,
game_aux_info *aux, char **error) game_aux_info *aux, char **error)
{ {
game_state *ret = dup_game(state); return dupstr("S");
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;
} }
static char *game_text_format(game_state *state) static char *game_text_format(game_state *state)
@ -591,11 +576,11 @@ struct game_drawstate {
int tilesize; int tilesize;
}; };
static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds, static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
int x, int y, int button) { int x, int y, int button)
int cx, cy; {
int dx, dy, tx, ty, n; int cx, cy, dx, dy;
game_state *ret; char buf[80];
button &= ~MOD_MASK; button &= ~MOD_MASK;
if (button != LEFT_BUTTON && button != RIGHT_BUTTON) 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); cx = FROMCOORD(x);
cy = FROMCOORD(y); cy = FROMCOORD(y);
if (cx == -1 && cy >= 0 && cy < from->h) if (cx == -1 && cy >= 0 && cy < state->h)
n = from->w, dx = +1, dy = 0; dx = -1, dy = 0;
else if (cx == from->w && cy >= 0 && cy < from->h) else if (cx == state->w && cy >= 0 && cy < state->h)
n = from->w, dx = -1, dy = 0; dx = +1, dy = 0;
else if (cy == -1 && cx >= 0 && cx < from->w) else if (cy == -1 && cx >= 0 && cx < state->w)
n = from->h, dy = +1, dx = 0; dy = -1, dx = 0;
else if (cy == from->h && cx >= 0 && cx < from->w) else if (cy == state->h && cx >= 0 && cx < state->w)
n = from->h, dy = -1, dx = 0; dy = +1, dx = 0;
else else
return NULL; /* invalid click location */ return NULL; /* invalid click location */
/* reverse direction if right hand button is pressed */ /* reverse direction if right hand button is pressed */
if (button == RIGHT_BUTTON) if (button == RIGHT_BUTTON) {
{ dx = -dx;
dx = -dx; if (dx) cx = from->w - 1 - cx; dy = -dy;
dy = -dy; if (dy) cy = from->h - 1 - cy;
} }
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 = dup_game(from);
ret->just_used_solve = FALSE; /* zero this in a hurry */ ret->just_used_solve = FALSE; /* zero this in a hurry */
do { do {
cx += dx; tx = (cx - dx + from->w) % from->w;
cy += dy; 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)]; ret->tiles[C(ret, cx, cy)] = from->tiles[C(from, tx, ty)];
cx = tx;
cy = ty;
} while (--n > 0); } while (--n > 0);
ret->movecount++; ret->movecount++;
ret->last_movement_sense = -(dx+dy); ret->last_movement_sense = dx+dy;
/* /*
* See if the game has been completed. * See if the game has been completed.
@ -1033,7 +1061,8 @@ const struct game thegame = {
new_ui, new_ui,
free_ui, free_ui,
game_changed_state, game_changed_state,
make_move, interpret_move,
execute_move,
game_size, game_size,
game_colours, game_colours,
game_new_drawstate, game_new_drawstate,

141
solo.c
View File

@ -1759,15 +1759,14 @@ static void free_game(game_state *state)
sfree(state); sfree(state);
} }
static game_state *solve_game(game_state *state, game_state *currstate, static char *solve_game(game_state *state, game_state *currstate,
game_aux_info *ai, char **error) game_aux_info *ai, char **error)
{ {
game_state *ret;
int c = state->c, r = state->r, cr = c*r; int c = state->c, r = state->r, cr = c*r;
int rsolve_ret; int i, len;
char *ret, *p, *sep;
ret = dup_game(state); digit *grid;
ret->completed = ret->cheated = TRUE; int grid_needs_freeing;
/* /*
* If we already have the solution in the aux_info, save * If we already have the solution in the aux_info, save
@ -1777,21 +1776,66 @@ static game_state *solve_game(game_state *state, game_state *currstate,
assert(c == ai->c); assert(c == ai->c);
assert(r == ai->r); assert(r == ai->r);
memcpy(ret->grid, ai->grid, cr * cr * sizeof(digit)); grid = ai->grid;
grid_needs_freeing = FALSE;
} else { } 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) { if (rsolve_ret != 1) {
free_game(ret); sfree(grid);
if (rsolve_ret == 0) if (rsolve_ret == 0)
*error = "No solution exists for this puzzle"; *error = "No solution exists for this puzzle";
else else
*error = "Multiple solutions exist for this puzzle"; *error = "Multiple solutions exist for this puzzle";
return NULL; 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; return ret;
} }
@ -1914,12 +1958,12 @@ struct game_drawstate {
int *entered_items; int *entered_items;
}; };
static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds, static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
int x, int y, int button) 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; int tx, ty;
game_state *ret; char buf[80];
button &= ~MOD_MASK; 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 (tx >= 0 && tx < cr && ty >= 0 && ty < cr) {
if (button == LEFT_BUTTON) { if (button == LEFT_BUTTON) {
if (from->immutable[ty*cr+tx]) { if (state->immutable[ty*cr+tx]) {
ui->hx = ui->hy = -1; ui->hx = ui->hy = -1;
} else if (tx == ui->hx && ty == ui->hy && ui->hpencil == 0) { } else if (tx == ui->hx && ty == ui->hy && ui->hpencil == 0) {
ui->hx = ui->hy = -1; 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->hy = ty;
ui->hpencil = 0; ui->hpencil = 0;
} }
return from; /* UI activity occurred */ return ""; /* UI activity occurred */
} }
if (button == RIGHT_BUTTON) { if (button == RIGHT_BUTTON) {
/* /*
* Pencil-mode highlighting for non filled squares. * 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) { if (tx == ui->hx && ty == ui->hy && ui->hpencil) {
ui->hx = ui->hy = -1; ui->hx = ui->hy = -1;
} else { } else {
@ -1954,7 +1998,7 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
} else { } else {
ui->hx = ui->hy = -1; 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 * able to highlight the square, but it never hurts to be
* careful. * careful.
*/ */
if (from->immutable[ui->hy*cr+ui->hx]) if (state->immutable[ui->hy*cr+ui->hx])
return NULL; 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 * have even been able to pencil-highlight the square, but
* it never hurts to be careful. * 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; 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); ret = dup_game(from);
if (ui->hpencil && n > 0) { ret->completed = ret->cheated = TRUE;
int index = (ui->hy*cr+ui->hx) * cr + (n-1);
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]; ret->pencil[index] = !ret->pencil[index];
} else { } else {
ret->grid[ui->hy*cr+ui->hx] = n; ret->grid[y*cr+x] = n;
memset(ret->pencil + (ui->hy*cr+ui->hx)*cr, 0, cr); memset(ret->pencil + (y*cr+x)*cr, 0, cr);
/* /*
* We've made a real change to the grid. Check to see * 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; ret->completed = TRUE;
} }
} }
ui->hx = ui->hy = -1; return ret;
} else
return ret; /* made a valid move */ return NULL; /* couldn't parse move string */
}
return NULL;
} }
/* ---------------------------------------------------------------------- /* ----------------------------------------------------------------------
@ -2339,7 +2421,8 @@ const struct game thegame = {
new_ui, new_ui,
free_ui, free_ui,
game_changed_state, game_changed_state,
make_move, interpret_move,
execute_move,
game_size, game_size,
game_colours, game_colours,
game_new_drawstate, game_new_drawstate,

View File

@ -546,26 +546,10 @@ static int compare_int(const void *av, const void *bv)
return 0; return 0;
} }
static game_state *solve_game(game_state *state, game_state *currstate, static char *solve_game(game_state *state, game_state *currstate,
game_aux_info *aux, char **error) game_aux_info *aux, char **error)
{ {
game_state *ret = dup_game(state); return dupstr("S");
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;
} }
static char *game_text_format(game_state *state) static char *game_text_format(game_state *state)
@ -636,11 +620,11 @@ struct game_drawstate {
int tilesize; int tilesize;
}; };
static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds, static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
int x, int y, int button) int x, int y, int button)
{ {
int w = from->w, h = from->h, n = from->n, wh = w*h; int w = state->w, h = state->h, n = state->n /* , wh = w*h */;
game_state *ret; char buf[80];
int dir; int dir;
button = button & (~MOD_MASK | MOD_NUM_KEYPAD); 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 = dup_game(from);
ret->just_used_solve = FALSE; /* zero this in a hurry */ ret->just_used_solve = FALSE; /* zero this in a hurry */
ret->movecount++; ret->movecount++;
@ -1206,7 +1225,8 @@ const struct game thegame = {
new_ui, new_ui,
free_ui, free_ui,
game_changed_state, game_changed_state,
make_move, interpret_move,
execute_move,
game_size, game_size,
game_colours, game_colours,
game_new_drawstate, game_new_drawstate,