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