12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265 |
- /*
- * fifteen.c: standard 15-puzzle.
- */
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <assert.h>
- #include <ctype.h>
- #include <limits.h>
- #ifdef NO_TGMATH_H
- # include <math.h>
- #else
- # include <tgmath.h>
- #endif
- #include "puzzles.h"
- #define PREFERRED_TILE_SIZE 48
- #define TILE_SIZE (ds->tilesize)
- #define BORDER (TILE_SIZE / 2)
- #define HIGHLIGHT_WIDTH (TILE_SIZE / 20)
- #define COORD(x) ( (x) * TILE_SIZE + BORDER )
- #define FROMCOORD(x) ( ((x) - BORDER + TILE_SIZE) / TILE_SIZE - 1 )
- #define ANIM_TIME 0.13F
- #define FLASH_FRAME 0.13F
- #define X(state, i) ( (i) % (state)->w )
- #define Y(state, i) ( (i) / (state)->w )
- #define C(state, x, y) ( (y) * (state)->w + (x) )
- #define PARITY_P(params, gap) (((X((params), (gap)) - ((params)->w - 1)) ^ \
- (Y((params), (gap)) - ((params)->h - 1)) ^ \
- (((params)->w * (params)->h) + 1)) & 1)
- #define PARITY_S(state) PARITY_P((state), ((state)->gap_pos))
- enum {
- COL_BACKGROUND,
- COL_TEXT,
- COL_HIGHLIGHT,
- COL_LOWLIGHT,
- NCOLOURS
- };
- struct game_params {
- int w, h;
- };
- struct game_state {
- int w, h, n;
- int *tiles;
- int gap_pos;
- int completed; /* move count at time of completion */
- bool used_solve; /* used to suppress completion flash */
- int movecount;
- };
- static game_params *default_params(void)
- {
- game_params *ret = snew(game_params);
- ret->w = ret->h = 4;
- return ret;
- }
- static bool game_fetch_preset(int i, char **name, game_params **params)
- {
- if (i == 0) {
- *params = default_params();
- *name = dupstr("4x4");
- return true;
- }
- return false;
- }
- static void free_params(game_params *params)
- {
- sfree(params);
- }
- static game_params *dup_params(const game_params *params)
- {
- game_params *ret = snew(game_params);
- *ret = *params; /* structure copy */
- return ret;
- }
- static void decode_params(game_params *ret, char const *string)
- {
- ret->w = ret->h = atoi(string);
- while (*string && isdigit((unsigned char)*string)) string++;
- if (*string == 'x') {
- string++;
- ret->h = atoi(string);
- }
- }
- static char *encode_params(const game_params *params, bool full)
- {
- char data[256];
- sprintf(data, "%dx%d", params->w, params->h);
- return dupstr(data);
- }
- static config_item *game_configure(const game_params *params)
- {
- config_item *ret;
- char buf[80];
- ret = snewn(3, config_item);
- ret[0].name = "Width";
- ret[0].type = C_STRING;
- sprintf(buf, "%d", params->w);
- ret[0].u.string.sval = dupstr(buf);
- ret[1].name = "Height";
- ret[1].type = C_STRING;
- sprintf(buf, "%d", params->h);
- ret[1].u.string.sval = dupstr(buf);
- ret[2].name = NULL;
- ret[2].type = C_END;
- return ret;
- }
- static game_params *custom_params(const config_item *cfg)
- {
- game_params *ret = snew(game_params);
- ret->w = atoi(cfg[0].u.string.sval);
- ret->h = atoi(cfg[1].u.string.sval);
- return ret;
- }
- static const char *validate_params(const game_params *params, bool full)
- {
- if (params->w < 2 || params->h < 2)
- return "Width and height must both be at least two";
- if (params->w > INT_MAX / params->h)
- return "Width times height must not be unreasonably large";
- return NULL;
- }
- static int perm_parity(int *perm, int n)
- {
- int i, j, ret;
- ret = 0;
- for (i = 0; i < n-1; i++)
- for (j = i+1; j < n; j++)
- if (perm[i] > perm[j])
- ret = !ret;
- return ret;
- }
- static int is_completed(int *tiles, int n) {
- int p;
- for (p = 0; p < n; p++)
- if (tiles[p] != (p < n-1 ? p+1 : 0))
- return 0;
- return 1;
- }
- static char *new_game_desc(const game_params *params, random_state *rs,
- char **aux, bool interactive)
- {
- int gap, n, i, x;
- int x1, x2, p1, p2, parity;
- int *tiles;
- bool *used;
- char *ret;
- int retlen;
- n = params->w * params->h;
- tiles = snewn(n, int);
- used = snewn(n, bool);
- do {
- for (i = 0; i < n; i++) {
- tiles[i] = -1;
- used[i] = false;
- }
- gap = random_upto(rs, n);
- tiles[gap] = 0;
- used[0] = true;
- /*
- * Place everything else except the last two tiles.
- */
- for (x = 0, i = n - 1; i > 2; i--) {
- int k = random_upto(rs, i);
- int j;
- for (j = 0; j < n; j++)
- if (!used[j] && (k-- == 0))
- break;
- assert(j < n && !used[j]);
- used[j] = true;
- while (tiles[x] >= 0)
- x++;
- assert(x < n);
- tiles[x] = j;
- }
- /*
- * Find the last two locations, and the last two pieces.
- */
- while (tiles[x] >= 0)
- x++;
- assert(x < n);
- x1 = x;
- x++;
- while (tiles[x] >= 0)
- x++;
- assert(x < n);
- x2 = x;
- for (i = 0; i < n; i++)
- if (!used[i])
- break;
- p1 = i;
- for (i = p1 + 1; i < n; i++)
- if (!used[i])
- break;
- p2 = i;
- /*
- * Determine the required parity of the overall permutation.
- * This is the XOR of:
- *
- * - The chessboard parity ((x^y)&1) of the gap square. The
- * bottom right counts as even.
- *
- * - The parity of n. (The target permutation is 1,...,n-1,0
- * rather than 0,...,n-1; this is a cyclic permutation of
- * the starting point and hence is odd iff n is even.)
- */
- parity = PARITY_P(params, gap);
- /*
- * Try the last two tiles one way round. If that fails, swap
- * them.
- */
- tiles[x1] = p1;
- tiles[x2] = p2;
- if (perm_parity(tiles, n) != parity) {
- tiles[x1] = p2;
- tiles[x2] = p1;
- assert(perm_parity(tiles, n) == parity);
- }
- } while (is_completed(tiles, n));
- /*
- * Now construct the game description, by describing the tile
- * array as a simple sequence of comma-separated integers.
- */
- ret = NULL;
- retlen = 0;
- for (i = 0; i < n; i++) {
- char buf[80];
- int k;
- k = sprintf(buf, "%d,", tiles[i]);
- ret = sresize(ret, retlen + k + 1, char);
- strcpy(ret + retlen, buf);
- retlen += k;
- }
- ret[retlen-1] = '\0'; /* delete last comma */
- sfree(tiles);
- sfree(used);
- return ret;
- }
- static const char *validate_desc(const game_params *params, const char *desc)
- {
- const char *p;
- const char *err;
- int i, area;
- bool *used;
- area = params->w * params->h;
- p = desc;
- err = NULL;
- used = snewn(area, bool);
- for (i = 0; i < area; i++)
- used[i] = false;
- for (i = 0; i < area; i++) {
- const char *q = p;
- int n;
- if (*p < '0' || *p > '9') {
- err = "Not enough numbers in string";
- goto leave;
- }
- while (*p >= '0' && *p <= '9')
- p++;
- if (i < area-1 && *p != ',') {
- err = "Expected comma after number";
- goto leave;
- }
- else if (i == area-1 && *p) {
- err = "Excess junk at end of string";
- goto leave;
- }
- n = atoi(q);
- if (n < 0 || n >= area) {
- err = "Number out of range";
- goto leave;
- }
- if (used[n]) {
- err = "Number used twice";
- goto leave;
- }
- used[n] = true;
- if (*p) p++; /* eat comma */
- }
- leave:
- sfree(used);
- return err;
- }
- static game_state *new_game(midend *me, const game_params *params,
- const char *desc)
- {
- game_state *state = snew(game_state);
- int i;
- const char *p;
- state->w = params->w;
- state->h = params->h;
- state->n = params->w * params->h;
- state->tiles = snewn(state->n, int);
- state->gap_pos = 0;
- p = desc;
- i = 0;
- for (i = 0; i < state->n; i++) {
- assert(*p);
- state->tiles[i] = atoi(p);
- if (state->tiles[i] == 0)
- state->gap_pos = i;
- while (*p && *p != ',')
- p++;
- if (*p) p++; /* eat comma */
- }
- assert(!*p);
- assert(state->tiles[state->gap_pos] == 0);
- state->completed = state->movecount = 0;
- state->used_solve = false;
- return state;
- }
- static game_state *dup_game(const game_state *state)
- {
- game_state *ret = snew(game_state);
- ret->w = state->w;
- ret->h = state->h;
- ret->n = state->n;
- ret->tiles = snewn(state->w * state->h, int);
- memcpy(ret->tiles, state->tiles, state->w * state->h * sizeof(int));
- ret->gap_pos = state->gap_pos;
- ret->completed = state->completed;
- ret->movecount = state->movecount;
- ret->used_solve = state->used_solve;
- return ret;
- }
- static void free_game(game_state *state)
- {
- sfree(state->tiles);
- sfree(state);
- }
- static char *solve_game(const game_state *state, const game_state *currstate,
- const char *aux, const char **error)
- {
- return dupstr("S");
- }
- static bool game_can_format_as_text_now(const game_params *params)
- {
- return true;
- }
- static char *game_text_format(const game_state *state)
- {
- char *ret, *p, buf[80];
- int x, y, col, maxlen;
- /*
- * First work out how many characters we need to display each
- * number.
- */
- col = sprintf(buf, "%d", state->n-1);
- /*
- * Now we know the exact total size of the grid we're going to
- * produce: it's got h rows, each containing w lots of col, w-1
- * spaces and a trailing newline.
- */
- maxlen = state->h * state->w * (col+1);
- ret = snewn(maxlen+1, char);
- p = ret;
- for (y = 0; y < state->h; y++) {
- for (x = 0; x < state->w; x++) {
- int v = state->tiles[state->w*y+x];
- if (v == 0)
- sprintf(buf, "%*s", col, "");
- else
- sprintf(buf, "%*d", col, v);
- memcpy(p, buf, col);
- p += col;
- if (x+1 == state->w)
- *p++ = '\n';
- else
- *p++ = ' ';
- }
- }
- assert(p - ret == maxlen);
- *p = '\0';
- return ret;
- }
- struct game_ui {
- /*
- * User-preference option: invert the direction of arrow-key
- * control, so that the arrow on the key you press indicates in
- * which direction you want the _space_ to move, rather than in
- * which direction you want a tile to move to fill the space.
- */
- bool invert_cursor;
- };
- static void legacy_prefs_override(struct game_ui *ui_out)
- {
- static bool initialised = false;
- static int invert_cursor = -1;
- if (!initialised) {
- initialised = true;
- invert_cursor = getenv_bool("FIFTEEN_INVERT_CURSOR", -1);
- }
- if (invert_cursor != -1)
- ui_out->invert_cursor = invert_cursor;
- }
- static game_ui *new_ui(const game_state *state)
- {
- struct game_ui *ui = snew(struct game_ui);
- ui->invert_cursor = false;
- legacy_prefs_override(ui);
- return ui;
- }
- static config_item *get_prefs(game_ui *ui)
- {
- config_item *ret;
- ret = snewn(2, config_item);
- ret[0].name = "Sense of arrow keys";
- ret[0].kw = "arrow-semantics";
- ret[0].type = C_CHOICES;
- ret[0].u.choices.choicenames = ":Move the tile:Move the gap";
- ret[0].u.choices.choicekws = ":tile:gap";
- ret[0].u.choices.selected = ui->invert_cursor;
- ret[1].name = NULL;
- ret[1].type = C_END;
- return ret;
- }
- static void set_prefs(game_ui *ui, const config_item *cfg)
- {
- ui->invert_cursor = cfg[0].u.choices.selected;
- }
- static void free_ui(game_ui *ui)
- {
- sfree(ui);
- }
- static void game_changed_state(game_ui *ui, const game_state *oldstate,
- const game_state *newstate)
- {
- }
- struct game_drawstate {
- bool started;
- int w, h, bgcolour;
- int *tiles;
- int tilesize;
- };
- static int flip_cursor(int button)
- {
- switch (button) {
- case CURSOR_UP: return CURSOR_DOWN;
- case CURSOR_DOWN: return CURSOR_UP;
- case CURSOR_LEFT: return CURSOR_RIGHT;
- case CURSOR_RIGHT: return CURSOR_LEFT;
- }
- return 0;
- }
- static void next_move_3x2(int ax, int ay, int bx, int by,
- int gx, int gy, int *dx, int *dy)
- {
- /* When w = 3 and h = 2 and the tile going in the top left corner
- * is at (ax, ay) and the tile going in the bottom left corner is
- * at (bx, by) and the blank tile is at (gx, gy), how do you move? */
- /* Hard-coded shortest solutions. Sorry. */
- static const unsigned char move[120] = {
- 1,2,0,1,2,2,
- 2,0,0,2,0,0,
- 0,0,2,0,2,0,
- 0,0,0,2,0,2,
- 2,0,0,0,2,0,
- 0,3,0,1,1,1,
- 3,0,3,2,1,2,
- 2,1,1,0,1,0,
- 2,1,2,1,0,1,
- 1,2,0,2,1,2,
- 0,1,3,1,3,0,
- 1,3,1,3,0,3,
- 0,0,3,3,0,0,
- 0,0,0,1,2,1,
- 3,0,0,1,1,1,
- 3,1,1,1,3,0,
- 1,1,1,1,1,1,
- 1,3,1,1,3,0,
- 1,1,3,3,1,3,
- 1,3,0,0,0,0
- };
- static const struct { int dx, dy; } d[4] = {{+1,0},{-1,0},{0,+1},{0,-1}};
- int ea = 3*ay + ax, eb = 3*by + bx, eg = 3*gy + gx, v;
- if (eb > ea) --eb;
- if (eg > ea) --eg;
- if (eg > eb) --eg;
- v = move[ea + eb*6 + eg*5*6];
- *dx = d[v].dx;
- *dy = d[v].dy;
- }
- static void next_move(int nx, int ny, int ox, int oy, int gx, int gy,
- int tx, int ty, int w, int *dx, int *dy)
- {
- const int to_tile_x = (gx < nx ? +1 : -1);
- const int to_goal_x = (gx < tx ? +1 : -1);
- const bool gap_x_on_goal_side = ((nx-tx) * (nx-gx) > 0);
- assert (nx != tx || ny != ty); /* not already in place */
- assert (nx != gx || ny != gy); /* not placing the gap */
- assert (ty <= ny); /* because we're greedy (and flipping) */
- assert (ty <= gy); /* because we're greedy (and flipping) */
- /* TODO: define a termination function. Idea: 0 if solved, or
- * the number of moves to solve the next piece plus the number of
- * further unsolved pieces times an upper bound on the number of
- * moves required to solve any piece. If such a function can be
- * found, we have (termination && (termination => correctness)).
- * The catch is our temporary disturbance of 2x3 corners. */
- /* handles end-of-row, when 3 and 4 are in the top right 2x3 box */
- if (tx == w - 2 &&
- ny <= ty + 2 && (nx == tx || nx == tx + 1) &&
- oy <= ty + 2 && (ox == tx || ox == tx + 1) &&
- gy <= ty + 2 && (gx == tx || gx == tx + 1))
- {
- next_move_3x2(oy - ty, tx + 1 - ox,
- ny - ty, tx + 1 - nx,
- gy - ty, tx + 1 - gx, dy, dx);
- *dx *= -1;
- return;
- }
- if (tx == w - 1) {
- if (ny <= ty + 2 && (nx == tx || nx == tx - 1) &&
- gy <= ty + 2 && (gx == tx || gx == tx - 1)) {
- next_move_3x2(ny - ty, tx - nx, 0, 1, gy - ty, tx - gx, dy, dx);
- *dx *= -1;
- } else if (gy == ty)
- *dy = +1;
- else if (nx != tx || ny != ty + 1) {
- next_move((w - 1) - nx, ny, -1, -1, (w - 1) - gx, gy,
- 0, ty + 1, -1, dx, dy);
- *dx *= -1;
- } else if (gx == nx)
- *dy = -1;
- else
- *dx = +1;
- return;
- }
- /* note that *dy = -1 is unsafe when gy = ty + 1 and gx < tx */
- if (gy < ny)
- if (nx == gx || (gy == ty && gx == tx))
- *dy = +1;
- else if (!gap_x_on_goal_side)
- *dx = to_tile_x;
- else if (ny - ty > abs(nx - tx))
- *dx = to_tile_x;
- else *dy = +1;
- else if (gy == ny)
- if (nx == tx) /* then we know ny > ty */
- if (gx > nx || ny > ty + 1)
- *dy = -1; /* ... so this is safe */
- else
- *dy = +1;
- else if (gap_x_on_goal_side)
- *dx = to_tile_x;
- else if (gy == ty || (gy == ty + 1 && gx < tx))
- *dy = +1;
- else
- *dy = -1;
- else if (nx == tx) /* gy > ny */
- if (gx > nx)
- *dy = -1;
- else
- *dx = +1;
- else if (gx == nx)
- *dx = to_goal_x;
- else if (gap_x_on_goal_side)
- if (gy == ty + 1 && gx < tx)
- *dx = to_tile_x;
- else
- *dy = -1;
- else if (ny - ty > abs(nx - tx))
- *dy = -1;
- else
- *dx = to_tile_x;
- }
- static bool compute_hint(const game_state *state, int *out_x, int *out_y)
- {
- /* The overall solving process is this:
- * 1. Find the next piece to be put in its place
- * 2. Move it diagonally towards its place
- * 3. Move it horizontally or vertically towards its place
- * (Modulo the last two tiles at the end of each row/column)
- */
- int gx = X(state, state->gap_pos);
- int gy = Y(state, state->gap_pos);
- int tx, ty, nx, ny, ox, oy, /* {target,next,next2}_{x,y} */ i;
- int dx = 0, dy = 0;
- /* 1. Find the next piece
- * if (there are no more unfinished columns than rows) {
- * fill the top-most row, left to right
- * } else { fill the left-most column, top to bottom }
- */
- const int w = state->w, h = state->h, n = w*h;
- int next_piece = 0, next_piece_2 = 0, solr = 0, solc = 0;
- int unsolved_rows = h, unsolved_cols = w;
- assert(out_x);
- assert(out_y);
- while (solr < h && solc < w) {
- int start, step, stop;
- if (unsolved_cols <= unsolved_rows)
- start = solr*w + solc, step = 1, stop = unsolved_cols;
- else
- start = solr*w + solc, step = w, stop = unsolved_rows;
- for (i = 0; i < stop; ++i) {
- const int j = start + i*step;
- if (state->tiles[j] != j + 1) {
- next_piece = j + 1;
- next_piece_2 = next_piece + step;
- break;
- }
- }
- if (i < stop) break;
- (unsolved_cols <= unsolved_rows)
- ? (++solr, --unsolved_rows)
- : (++solc, --unsolved_cols);
- }
- if (next_piece == n)
- return false;
- /* 2, 3. Move the next piece towards its place */
- /* gx, gy already set */
- tx = X(state, next_piece - 1); /* where we're going */
- ty = Y(state, next_piece - 1);
- for (i = 0; i < n && state->tiles[i] != next_piece; ++i);
- nx = X(state, i); /* where we're at */
- ny = Y(state, i);
- for (i = 0; i < n && state->tiles[i] != next_piece_2; ++i);
- ox = X(state, i);
- oy = Y(state, i);
- if (unsolved_cols <= unsolved_rows)
- next_move(nx, ny, ox, oy, gx, gy, tx, ty, w, &dx, &dy);
- else
- next_move(ny, nx, oy, ox, gy, gx, ty, tx, h, &dy, &dx);
- assert (dx || dy);
- *out_x = gx + dx;
- *out_y = gy + dy;
- return true;
- }
- static char *interpret_move(const game_state *state, game_ui *ui,
- const game_drawstate *ds,
- int x, int y, int button)
- {
- int cx = X(state, state->gap_pos), nx = cx;
- int cy = Y(state, state->gap_pos), ny = cy;
- char buf[80];
- button &= ~MOD_MASK;
- if (button == LEFT_BUTTON) {
- nx = FROMCOORD(x);
- ny = FROMCOORD(y);
- if (nx < 0 || nx >= state->w || ny < 0 || ny >= state->h)
- return MOVE_UNUSED; /* out of bounds */
- } else if (IS_CURSOR_MOVE(button)) {
- button = flip_cursor(button); /* the default */
- if (ui->invert_cursor)
- button = flip_cursor(button); /* undoes the first flip */
- move_cursor(button, &nx, &ny, state->w, state->h, false);
- } else if ((button == 'h' || button == 'H') && !state->completed) {
- if (!compute_hint(state, &nx, &ny))
- return MOVE_NO_EFFECT;/* shouldn't happen, since ^^we^^checked^^ */
- } else
- return MOVE_UNUSED; /* no move */
- /*
- * Any click location should be equal to the gap location
- * in _precisely_ one coordinate.
- */
- if ((cx == nx) ^ (cy == ny)) {
- sprintf(buf, "M%d,%d", nx, ny);
- return dupstr(buf);
- }
- return MOVE_NO_EFFECT;
- }
- static game_state *execute_move(const game_state *from, const 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 = 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.
- */
- ux = (dx < gx ? -1 : dx > gx ? +1 : 0);
- uy = (dy < gy ? -1 : dy > gy ? +1 : 0);
- up = C(from, ux, uy);
- ret = dup_game(from);
- ret->gap_pos = C(from, dx, dy);
- assert(ret->gap_pos >= 0 && ret->gap_pos < ret->n);
- ret->tiles[ret->gap_pos] = 0;
- for (p = from->gap_pos; p != ret->gap_pos; p += up) {
- assert(p >= 0 && p < from->n);
- ret->tiles[p] = from->tiles[p + up];
- ret->movecount++;
- }
- /*
- * See if the game has been completed.
- */
- if (!ret->completed && is_completed(ret->tiles, ret->n)) {
- ret->completed = ret->movecount;
- }
- return ret;
- }
- /* ----------------------------------------------------------------------
- * Drawing routines.
- */
- static void game_compute_size(const game_params *params, int tilesize,
- const game_ui *ui, int *x, int *y)
- {
- /* Ick: fake up `ds->tilesize' for macro expansion purposes */
- struct { int tilesize; } ads, *ds = &ads;
- ads.tilesize = tilesize;
- *x = TILE_SIZE * params->w + 2 * BORDER;
- *y = TILE_SIZE * params->h + 2 * BORDER;
- }
- static void game_set_size(drawing *dr, game_drawstate *ds,
- const game_params *params, int tilesize)
- {
- ds->tilesize = tilesize;
- }
- static float *game_colours(frontend *fe, int *ncolours)
- {
- float *ret = snewn(3 * NCOLOURS, float);
- int i;
- game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT);
- for (i = 0; i < 3; i++)
- ret[COL_TEXT * 3 + i] = 0.0;
- *ncolours = NCOLOURS;
- return ret;
- }
- static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
- {
- struct game_drawstate *ds = snew(struct game_drawstate);
- int i;
- ds->started = false;
- ds->w = state->w;
- ds->h = state->h;
- ds->bgcolour = COL_BACKGROUND;
- ds->tiles = snewn(ds->w*ds->h, int);
- ds->tilesize = 0; /* haven't decided yet */
- for (i = 0; i < ds->w*ds->h; i++)
- ds->tiles[i] = -1;
- return ds;
- }
- static void game_free_drawstate(drawing *dr, game_drawstate *ds)
- {
- sfree(ds->tiles);
- sfree(ds);
- }
- static void draw_tile(drawing *dr, game_drawstate *ds, const game_state *state,
- int x, int y, int tile, int flash_colour)
- {
- if (tile == 0) {
- draw_rect(dr, x, y, TILE_SIZE, TILE_SIZE,
- flash_colour);
- } else {
- int coords[6];
- char str[40];
- coords[0] = x + TILE_SIZE - 1;
- coords[1] = y + TILE_SIZE - 1;
- coords[2] = x + TILE_SIZE - 1;
- coords[3] = y;
- coords[4] = x;
- coords[5] = y + TILE_SIZE - 1;
- draw_polygon(dr, coords, 3, COL_LOWLIGHT, COL_LOWLIGHT);
- coords[0] = x;
- coords[1] = y;
- draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT);
- draw_rect(dr, x + HIGHLIGHT_WIDTH, y + HIGHLIGHT_WIDTH,
- TILE_SIZE - 2*HIGHLIGHT_WIDTH, TILE_SIZE - 2*HIGHLIGHT_WIDTH,
- flash_colour);
- sprintf(str, "%d", tile);
- draw_text(dr, x + TILE_SIZE/2, y + TILE_SIZE/2,
- FONT_VARIABLE, TILE_SIZE/3, ALIGN_VCENTRE | ALIGN_HCENTRE,
- COL_TEXT, str);
- }
- draw_update(dr, x, y, TILE_SIZE, TILE_SIZE);
- }
- static void game_redraw(drawing *dr, game_drawstate *ds,
- const game_state *oldstate, const game_state *state,
- int dir, const game_ui *ui,
- float animtime, float flashtime)
- {
- int i, pass, bgcolour;
- if (flashtime > 0) {
- int frame = (int)(flashtime / FLASH_FRAME);
- bgcolour = (frame % 2 ? COL_LOWLIGHT : COL_HIGHLIGHT);
- } else
- bgcolour = COL_BACKGROUND;
- if (!ds->started) {
- int coords[10];
- /*
- * Recessed area containing the whole puzzle.
- */
- coords[0] = COORD(state->w) + HIGHLIGHT_WIDTH - 1;
- coords[1] = COORD(state->h) + HIGHLIGHT_WIDTH - 1;
- coords[2] = COORD(state->w) + HIGHLIGHT_WIDTH - 1;
- coords[3] = COORD(0) - HIGHLIGHT_WIDTH;
- coords[4] = coords[2] - TILE_SIZE;
- coords[5] = coords[3] + TILE_SIZE;
- coords[8] = COORD(0) - HIGHLIGHT_WIDTH;
- coords[9] = COORD(state->h) + HIGHLIGHT_WIDTH - 1;
- coords[6] = coords[8] + TILE_SIZE;
- coords[7] = coords[9] - TILE_SIZE;
- draw_polygon(dr, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT);
- coords[1] = COORD(0) - HIGHLIGHT_WIDTH;
- coords[0] = COORD(0) - HIGHLIGHT_WIDTH;
- draw_polygon(dr, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT);
- ds->started = true;
- }
- /*
- * Now draw each tile. We do this in two passes to make
- * animation easy.
- */
- for (pass = 0; pass < 2; pass++) {
- for (i = 0; i < state->n; i++) {
- int t, t0;
- /*
- * Figure out what should be displayed at this
- * location. It's either a simple tile, or it's a
- * transition between two tiles (in which case we say
- * -1 because it must always be drawn).
- */
- if (oldstate && oldstate->tiles[i] != state->tiles[i])
- t = -1;
- else
- t = state->tiles[i];
- t0 = t;
- if (ds->bgcolour != bgcolour || /* always redraw when flashing */
- ds->tiles[i] != t || ds->tiles[i] == -1 || t == -1) {
- int x, y;
- /*
- * Figure out what to _actually_ draw, and where to
- * draw it.
- */
- if (t == -1) {
- int x0, y0, x1, y1;
- int j;
- /*
- * On the first pass, just blank the tile.
- */
- if (pass == 0) {
- x = COORD(X(state, i));
- y = COORD(Y(state, i));
- t = 0;
- } else {
- float c;
- t = state->tiles[i];
- /*
- * Don't bother moving the gap; just don't
- * draw it.
- */
- if (t == 0)
- continue;
- /*
- * Find the coordinates of this tile in the old and
- * new states.
- */
- x1 = COORD(X(state, i));
- y1 = COORD(Y(state, i));
- for (j = 0; j < oldstate->n; j++)
- if (oldstate->tiles[j] == state->tiles[i])
- break;
- assert(j < oldstate->n);
- x0 = COORD(X(state, j));
- y0 = COORD(Y(state, j));
- c = (animtime / ANIM_TIME);
- if (c < 0.0F) c = 0.0F;
- if (c > 1.0F) c = 1.0F;
- x = x0 + (int)(c * (x1 - x0));
- y = y0 + (int)(c * (y1 - y0));
- }
- } else {
- if (pass == 0)
- continue;
- x = COORD(X(state, i));
- y = COORD(Y(state, i));
- }
- draw_tile(dr, ds, state, x, y, t, bgcolour);
- }
- ds->tiles[i] = t0;
- }
- }
- ds->bgcolour = bgcolour;
- /*
- * Update the status bar.
- */
- {
- char statusbuf[256];
- /*
- * Don't show the new status until we're also showing the
- * new _state_ - after the game animation is complete.
- */
- if (oldstate)
- state = oldstate;
- if (state->used_solve)
- sprintf(statusbuf, "Moves since auto-solve: %d",
- state->movecount - state->completed);
- else
- sprintf(statusbuf, "%sMoves: %d",
- (state->completed ? "COMPLETED! " : ""),
- (state->completed ? state->completed : state->movecount));
- status_bar(dr, statusbuf);
- }
- }
- static float game_anim_length(const game_state *oldstate,
- const game_state *newstate, int dir, game_ui *ui)
- {
- return ANIM_TIME;
- }
- static float game_flash_length(const game_state *oldstate,
- const game_state *newstate, int dir, game_ui *ui)
- {
- if (!oldstate->completed && newstate->completed &&
- !oldstate->used_solve && !newstate->used_solve)
- return 2 * FLASH_FRAME;
- else
- return 0.0F;
- }
- static void game_get_cursor_location(const game_ui *ui,
- const game_drawstate *ds,
- const game_state *state,
- const game_params *params,
- int *x, int *y, int *w, int *h)
- {
- *x = COORD(X(state, state->gap_pos));
- *y = COORD(Y(state, state->gap_pos));
- *w = *h = TILE_SIZE;
- }
- static int game_status(const game_state *state)
- {
- return state->completed ? +1 : 0;
- }
- #ifdef COMBINED
- #define thegame fifteen
- #endif
- const struct game thegame = {
- "Fifteen", "games.fifteen", "fifteen",
- default_params,
- game_fetch_preset, NULL,
- decode_params,
- encode_params,
- free_params,
- dup_params,
- true, game_configure, custom_params,
- validate_params,
- new_game_desc,
- validate_desc,
- new_game,
- dup_game,
- free_game,
- true, solve_game,
- true, game_can_format_as_text_now, game_text_format,
- get_prefs, set_prefs,
- new_ui,
- free_ui,
- NULL, /* encode_ui */
- NULL, /* decode_ui */
- NULL, /* game_request_keys */
- game_changed_state,
- NULL, /* current_key_label */
- interpret_move,
- execute_move,
- PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
- game_colours,
- game_new_drawstate,
- game_free_drawstate,
- game_redraw,
- game_anim_length,
- game_flash_length,
- game_get_cursor_location,
- game_status,
- false, false, NULL, NULL, /* print_size, print */
- true, /* wants_statusbar */
- false, NULL, /* timing_state */
- 0, /* flags */
- };
- #ifdef STANDALONE_SOLVER
- int main(int argc, char **argv)
- {
- game_params *params;
- game_state *state;
- char *id = NULL, *desc;
- const char *err;
- bool grade = false;
- char *progname = argv[0];
- char buf[80];
- int limit, x, y;
- bool solvable;
- while (--argc > 0) {
- char *p = *++argv;
- if (!strcmp(p, "-v")) {
- /* solver_show_working = true; */
- } else if (!strcmp(p, "-g")) {
- grade = true;
- } else if (*p == '-') {
- fprintf(stderr, "%s: unrecognised option `%s'\n", progname, p);
- return 1;
- } else {
- id = p;
- }
- }
- if (!id) {
- fprintf(stderr, "usage: %s [-g | -v] <game_id>\n", argv[0]);
- return 1;
- }
- desc = strchr(id, ':');
- if (!desc) {
- fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]);
- return 1;
- }
- *desc++ = '\0';
- params = default_params();
- decode_params(params, id);
- err = validate_desc(params, desc);
- if (err) {
- free_params(params);
- fprintf(stderr, "%s: %s\n", argv[0], err);
- return 1;
- }
- state = new_game(NULL, params, desc);
- free_params(params);
- solvable = (PARITY_S(state) == perm_parity(state->tiles, state->n));
- if (grade || !solvable) {
- free_game(state);
- fputs(solvable ? "Game is solvable" : "Game is unsolvable",
- grade ? stdout : stderr);
- return !grade;
- }
- for (limit = 5 * state->n * state->n * state->n; limit; --limit) {
- game_state *next_state;
- if (!compute_hint(state, &x, &y)) {
- fprintf(stderr, "couldn't compute next move while solving %s:%s",
- id, desc);
- return 1;
- }
- printf("Move the space to (%d, %d), moving %d into the space\n",
- x + 1, y + 1, state->tiles[C(state, x, y)]);
- sprintf(buf, "M%d,%d", x, y);
- next_state = execute_move(state, buf);
- free_game(state);
- if (!next_state) {
- fprintf(stderr, "invalid move when solving %s:%s\n", id, desc);
- return 1;
- }
- state = next_state;
- if (next_state->completed) {
- free_game(state);
- return 0;
- }
- }
- free_game(state);
- fprintf(stderr, "ran out of moves for %s:%s\n", id, desc);
- return 1;
- }
- #endif
|