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;
}
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);
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! */
* 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;
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;
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)
} 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
new_ui,
free_ui,
game_changed_state,
- make_move,
+ interpret_move,
+ execute_move,
game_size,
game_colours,
game_new_drawstate,
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)
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
} 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.
new_ui,
free_ui,
game_changed_state,
- make_move,
+ interpret_move,
+ execute_move,
game_size,
game_colours,
game_new_drawstate,
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
}
/*
- * 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);
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 */
}
/* ----------------------------------------------------------------------
new_ui,
free_ui,
game_changed_state,
- make_move,
+ interpret_move,
+ execute_move,
game_size,
game_colours,
game_new_drawstate,
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)
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);
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",
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 */
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++;
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.
*/
new_ui,
free_ui,
game_changed_state,
- make_move,
+ interpret_move,
+ execute_move,
game_size,
game_colours,
game_new_drawstate,
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) {
/*
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";
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.
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)
*/
};
-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 */
ui->hx = cx;
ui->hy = cy;
ui->hradius = (from->grid[cy*from->w+cx] >= 0 ? 1 : 0);
- return from;
+ return "";
}
if (button == RIGHT_BUTTON) {
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) {
/*
* 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
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);
}
/*
}
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 &&
(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;
}
/* ----------------------------------------------------------------------
new_ui,
free_ui,
game_changed_state,
- make_move,
+ interpret_move,
+ execute_move,
game_size,
game_colours,
game_new_drawstate,
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;
}
/* ----------------------------------------------------------------------
* 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;
if (ui->cur_visible) {
ui->cur_visible = FALSE;
- nullret = state;
+ nullret = "";
}
/*
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) {
* 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) {
/*
* 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;
return ret;
}
+
/* ----------------------------------------------------------------------
* Routines for drawing the game position on the screen.
*/
new_ui,
free_ui,
game_changed_state,
- make_move,
+ interpret_move,
+ execute_move,
game_size,
game_colours,
game_new_drawstate,
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";
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;
}
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;
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.
new_ui,
free_ui,
game_changed_state,
- make_move,
+ interpret_move,
+ execute_move,
game_size,
game_colours,
game_new_drawstate,
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;
}
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;
}
new_ui,
free_ui,
game_changed_state,
- make_move,
+ interpret_move,
+ execute_move,
game_size,
game_colours,
game_new_drawstate,
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);
}
} 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;
}
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)) {
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) {
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) {
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.
*/
new_ui,
free_ui,
game_changed_state,
- make_move,
+ interpret_move,
+ execute_move,
game_size,
game_colours,
game_new_drawstate,
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);
}
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;
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;
+ }
}
}
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 */
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;
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.
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;
}
}
}
-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.
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;
}
/*
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,
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;
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;
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.
*/
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;
new_ui,
free_ui,
game_changed_state,
- make_move,
+ interpret_move,
+ execute_move,
game_size,
game_colours,
game_new_drawstate,
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;
}
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)
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;
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') {
} 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.
*/
new_ui,
free_ui,
game_changed_state,
- make_move,
+ interpret_move,
+ execute_move,
game_size,
game_colours,
game_new_drawstate,
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)
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)
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.
new_ui,
free_ui,
game_changed_state,
- make_move,
+ interpret_move,
+ execute_move,
game_size,
game_colours,
game_new_drawstate,
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
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;
}
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;
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;
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 {
} else {
ui->hx = ui->hy = -1;
}
- return from; /* UI activity occurred */
+ return ""; /* UI activity occurred */
}
}
* 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;
/*
* 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
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 */
}
/* ----------------------------------------------------------------------
new_ui,
free_ui,
game_changed_state,
- make_move,
+ interpret_move,
+ execute_move,
game_size,
game_colours,
game_new_drawstate,
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)
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);
}
/*
- * 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++;
new_ui,
free_ui,
game_changed_state,
- make_move,
+ interpret_move,
+ execute_move,
game_size,
game_colours,
game_new_drawstate,