12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220 |
- /*
- * sixteen.c: `16-puzzle', a sliding-tiles jigsaw which differs
- * from the 15-puzzle in that you toroidally rotate a row or column
- * at a time.
- */
- #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
- #define HIGHLIGHT_WIDTH (TILE_SIZE / 20)
- #define COORD(x) ( (x) * TILE_SIZE + BORDER )
- #define FROMCOORD(x) ( ((x) - BORDER + 2*TILE_SIZE) / TILE_SIZE - 2 )
- #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 TILE_CURSOR(i, state, x, y) ((i) == C((state), (x), (y)) && \
- 0 <= (x) && (x) < (state)->w && \
- 0 <= (y) && (y) < (state)->h)
- enum {
- COL_BACKGROUND,
- COL_TEXT,
- COL_HIGHLIGHT,
- COL_LOWLIGHT,
- NCOLOURS
- };
- struct game_params {
- int w, h;
- int movetarget;
- };
- struct game_state {
- int w, h, n;
- int *tiles;
- int completed;
- bool used_solve; /* used to suppress completion flash */
- int movecount, movetarget;
- int last_movement_sense;
- };
- static game_params *default_params(void)
- {
- game_params *ret = snew(game_params);
- ret->w = ret->h = 4;
- ret->movetarget = 0;
- return ret;
- }
- static bool game_fetch_preset(int i, char **name, game_params **params)
- {
- game_params *ret;
- int w, h;
- char buf[80];
- switch (i) {
- case 0: w = 3, h = 3; break;
- case 1: w = 4, h = 3; break;
- case 2: w = 4, h = 4; break;
- case 3: w = 5, h = 4; break;
- case 4: w = 5, h = 5; break;
- default: return false;
- }
- sprintf(buf, "%dx%d", w, h);
- *name = dupstr(buf);
- *params = ret = snew(game_params);
- ret->w = w;
- ret->h = h;
- ret->movetarget = 0;
- return true;
- }
- 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);
- ret->movetarget = 0;
- while (*string && isdigit((unsigned char)*string)) string++;
- if (*string == 'x') {
- string++;
- ret->h = atoi(string);
- while (*string && isdigit((unsigned char)*string))
- string++;
- }
- if (*string == 'm') {
- string++;
- ret->movetarget = atoi(string);
- while (*string && isdigit((unsigned char)*string))
- string++;
- }
- }
- static char *encode_params(const game_params *params, bool full)
- {
- char data[256];
- sprintf(data, "%dx%d", params->w, params->h);
- /* Shuffle limit is part of the limited parameters, because we have to
- * supply the target move count. */
- if (params->movetarget)
- sprintf(data + strlen(data), "m%d", params->movetarget);
- return dupstr(data);
- }
- static config_item *game_configure(const game_params *params)
- {
- config_item *ret;
- char buf[80];
- ret = snewn(4, 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 = "Number of shuffling moves";
- ret[2].type = C_STRING;
- sprintf(buf, "%d", params->movetarget);
- ret[2].u.string.sval = dupstr(buf);
- ret[3].name = NULL;
- ret[3].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);
- ret->movetarget = atoi(cfg[2].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 char *new_game_desc(const game_params *params, random_state *rs,
- char **aux, bool interactive)
- {
- int stop, n, i, x;
- int x1, x2, p1, p2;
- int *tiles;
- bool *used;
- char *ret;
- int retlen;
- n = params->w * params->h;
- tiles = snewn(n, int);
- if (params->movetarget) {
- int prevoffset = -1;
- int max = (params->w > params->h ? params->w : params->h);
- int *prevmoves = snewn(max, int);
- /*
- * Shuffle the old-fashioned way, by making a series of
- * single moves on the grid.
- */
- for (i = 0; i < n; i++)
- tiles[i] = i;
- for (i = 0; i < params->movetarget; i++) {
- int start, offset, len, direction, index;
- int j, tmp;
- /*
- * Choose a move to make. We can choose from any row
- * or any column.
- */
- while (1) {
- j = random_upto(rs, params->w + params->h);
- if (j < params->w) {
- /* Column. */
- index = j;
- start = j;
- offset = params->w;
- len = params->h;
- } else {
- /* Row. */
- index = j - params->w;
- start = index * params->w;
- offset = 1;
- len = params->w;
- }
- direction = -1 + 2 * random_upto(rs, 2);
- /*
- * To at least _try_ to avoid boring cases, check
- * that this move doesn't directly undo a previous
- * one, or repeat it so many times as to turn it
- * into fewer moves in the opposite direction. (For
- * example, in a row of length 4, we're allowed to
- * move it the same way twice, but not three
- * times.)
- *
- * We track this for each individual row/column,
- * and clear all the counters as soon as a
- * perpendicular move is made. This isn't perfect
- * (it _can't_ guaranteeably be perfect - there
- * will always come a move count beyond which a
- * shorter solution will be possible than the one
- * which constructed the position) but it should
- * sort out all the obvious cases.
- */
- if (offset == prevoffset) {
- tmp = prevmoves[index] + direction;
- if (abs(2*tmp) > len || abs(tmp) < abs(prevmoves[index]))
- continue;
- }
- /* If we didn't `continue', we've found an OK move to make. */
- if (offset != prevoffset) {
- int i;
- for (i = 0; i < max; i++)
- prevmoves[i] = 0;
- prevoffset = offset;
- }
- prevmoves[index] += direction;
- break;
- }
- /*
- * Make the move.
- */
- if (direction < 0) {
- start += (len-1) * offset;
- offset = -offset;
- }
- tmp = tiles[start];
- for (j = 0; j+1 < len; j++)
- tiles[start + j*offset] = tiles[start + (j+1)*offset];
- tiles[start + (len-1) * offset] = tmp;
- }
- sfree(prevmoves);
- } else {
- used = snewn(n, bool);
- for (i = 0; i < n; i++) {
- tiles[i] = -1;
- used[i] = false;
- }
- /*
- * If both dimensions are odd, there is a parity
- * constraint.
- */
- if (params->w & params->h & 1)
- stop = 2;
- else
- stop = 0;
- /*
- * Place everything except (possibly) the last two tiles.
- */
- for (x = 0, i = n; i > stop; i--) {
- int k = i > 1 ? random_upto(rs, i) : 0;
- 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;
- }
- if (stop) {
- /*
- * 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;
- /*
- * Try the last two tiles one way round. If that fails,
- * swap them.
- */
- tiles[x1] = p1;
- tiles[x2] = p2;
- if (perm_parity(tiles, n) != 0) {
- tiles[x1] = p2;
- tiles[x2] = p1;
- assert(perm_parity(tiles, n) == 0);
- }
- }
- sfree(used);
- }
- /*
- * 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]+1);
- ret = sresize(ret, retlen + k + 1, char);
- strcpy(ret + retlen, buf);
- retlen += k;
- }
- ret[retlen-1] = '\0'; /* delete last comma */
- sfree(tiles);
- return ret;
- }
- static const char *validate_desc(const game_params *params, const char *desc)
- {
- const char *p, *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 < 1 || n > area) {
- err = "Number out of range";
- goto leave;
- }
- if (used[n-1]) {
- err = "Number used twice";
- goto leave;
- }
- used[n-1] = 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);
- p = desc;
- i = 0;
- for (i = 0; i < state->n; i++) {
- assert(*p);
- state->tiles[i] = atoi(p);
- while (*p && *p != ',')
- p++;
- if (*p) p++; /* eat comma */
- }
- assert(!*p);
- state->completed = state->movecount = 0;
- state->movetarget = params->movetarget;
- state->used_solve = false;
- state->last_movement_sense = 0;
- 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->completed = state->completed;
- ret->movecount = state->movecount;
- ret->movetarget = state->movetarget;
- ret->used_solve = state->used_solve;
- ret->last_movement_sense = state->last_movement_sense;
- 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);
- /*
- * 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];
- 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;
- }
- enum cursor_mode { unlocked, lock_tile, lock_position };
- struct game_ui {
- int cur_x, cur_y;
- bool cur_visible;
- enum cursor_mode cur_mode;
- };
- static game_ui *new_ui(const game_state *state)
- {
- game_ui *ui = snew(game_ui);
- ui->cur_x = 0;
- ui->cur_y = 0;
- ui->cur_visible = getenv_bool("PUZZLES_SHOW_CURSOR", false);
- ui->cur_mode = unlocked;
- return ui;
- }
- 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)
- {
- }
- static const char *current_key_label(const game_ui *ui,
- const game_state *state, int button)
- {
- if (IS_CURSOR_SELECT(button) && ui->cur_visible) {
- if (ui->cur_x == -1 || ui->cur_x == state->w ||
- ui->cur_y == -1 || ui->cur_y == state->h)
- return button == CURSOR_SELECT2 ? "Back" : "Slide";
- if (button == CURSOR_SELECT)
- return ui->cur_mode == lock_tile ? "Unlock" : "Lock tile";
- if (button == CURSOR_SELECT2)
- return ui->cur_mode == lock_position ? "Unlock" : "Lock pos";
- }
- return "";
- }
- struct game_drawstate {
- bool started;
- int w, h, bgcolour;
- int *tiles;
- int tilesize;
- int cur_x, cur_y;
- };
- static char *interpret_move(const game_state *state, game_ui *ui,
- const game_drawstate *ds,
- int x, int y, int button)
- {
- int cx = -1, cy = -1, dx, dy;
- char buf[80];
- bool shift = button & MOD_SHFT, control = button & MOD_CTRL;
- int pad = button & MOD_NUM_KEYPAD;
- button &= ~MOD_MASK;
- if (IS_CURSOR_MOVE(button) || pad) {
- if (!ui->cur_visible) {
- ui->cur_visible = true;
- return MOVE_UI_UPDATE;
- }
- if (control || shift || ui->cur_mode) {
- int x = ui->cur_x, y = ui->cur_y, xwrap = x, ywrap = y;
- if (x < 0 || x >= state->w || y < 0 || y >= state->h)
- return NULL;
- move_cursor(button | pad, &x, &y,
- state->w, state->h, false);
- move_cursor(button | pad, &xwrap, &ywrap,
- state->w, state->h, true);
- if (x != xwrap) {
- sprintf(buf, "R%d,%c1", y, x ? '+' : '-');
- } else if (y != ywrap) {
- sprintf(buf, "C%d,%c1", x, y ? '+' : '-');
- } else if (x == ui->cur_x)
- sprintf(buf, "C%d,%d", x, y - ui->cur_y);
- else
- sprintf(buf, "R%d,%d", y, x - ui->cur_x);
- if (control || (!shift && ui->cur_mode == lock_tile)) {
- ui->cur_x = xwrap;
- ui->cur_y = ywrap;
- }
- return dupstr(buf);
- } else {
- int x = ui->cur_x + 1, y = ui->cur_y + 1;
- move_cursor(button | pad, &x, &y,
- state->w + 2, state->h + 2, false);
- if (x == 0 && y == 0) {
- int t = ui->cur_x;
- ui->cur_x = ui->cur_y;
- ui->cur_y = t;
- } else if (x == 0 && y == state->h + 1) {
- int t = ui->cur_x;
- ui->cur_x = (state->h - 1) - ui->cur_y;
- ui->cur_y = (state->h - 1) - t;
- } else if (x == state->w + 1 && y == 0) {
- int t = ui->cur_x;
- ui->cur_x = (state->w - 1) - ui->cur_y;
- ui->cur_y = (state->w - 1) - t;
- } else if (x == state->w + 1 && y == state->h + 1) {
- int t = ui->cur_x;
- ui->cur_x = state->w - state->h + ui->cur_y;
- ui->cur_y = state->h - state->w + t;
- } else {
- ui->cur_x = x - 1;
- ui->cur_y = y - 1;
- }
- ui->cur_visible = true;
- return MOVE_UI_UPDATE;
- }
- }
- if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
- cx = FROMCOORD(x);
- cy = FROMCOORD(y);
- ui->cur_visible = false;
- } else if (IS_CURSOR_SELECT(button)) {
- if (ui->cur_visible) {
- if (ui->cur_x == -1 || ui->cur_x == state->w ||
- ui->cur_y == -1 || ui->cur_y == state->h) {
- cx = ui->cur_x;
- cy = ui->cur_y;
- } else {
- const enum cursor_mode m = (button == CURSOR_SELECT2 ?
- lock_position : lock_tile);
- ui->cur_mode = (ui->cur_mode == m ? unlocked : m);
- return MOVE_UI_UPDATE;
- }
- } else {
- ui->cur_visible = true;
- return MOVE_UI_UPDATE;
- }
- } else {
- return NULL;
- }
- 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 MOVE_UI_UPDATE; /* invalid click location */
- /* reverse direction if right hand button is pressed */
- if (button == RIGHT_BUTTON || button == CURSOR_SELECT2) {
- 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(const game_state *from, const 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 = 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 && -from->h <= dx && dx <= from->w ) {
- cx = dy = 0;
- n = from->w;
- } else if (move[0] == 'C' && sscanf(move+1, "%d,%d", &cx, &dy) == 2 &&
- cx >= 0 && cx < from->w && -from->h <= dy && dy <= from->h) {
- cy = dx = 0;
- n = from->h;
- } else
- return NULL;
- ret = dup_game(from);
- do {
- 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;
- /*
- * See if the game has been completed.
- */
- if (!ret->completed) {
- ret->completed = ret->movecount;
- for (n = 0; n < ret->n; n++)
- if (ret->tiles[n] != n+1)
- ret->completed = 0;
- }
- 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;
- ds->cur_x = ds->cur_y = -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 draw_arrow(drawing *dr, game_drawstate *ds,
- int x, int y, int xdx, int xdy, bool cur)
- {
- int coords[14];
- int ydy = -xdx, ydx = xdy;
- #define POINT(n, xx, yy) ( \
- coords[2*(n)+0] = x + (xx)*xdx + (yy)*ydx, \
- coords[2*(n)+1] = y + (xx)*xdy + (yy)*ydy)
- POINT(0, TILE_SIZE / 2, 3 * TILE_SIZE / 4); /* top of arrow */
- POINT(1, 3 * TILE_SIZE / 4, TILE_SIZE / 2); /* right corner */
- POINT(2, 5 * TILE_SIZE / 8, TILE_SIZE / 2); /* right concave */
- POINT(3, 5 * TILE_SIZE / 8, TILE_SIZE / 4); /* bottom right */
- POINT(4, 3 * TILE_SIZE / 8, TILE_SIZE / 4); /* bottom left */
- POINT(5, 3 * TILE_SIZE / 8, TILE_SIZE / 2); /* left concave */
- POINT(6, TILE_SIZE / 4, TILE_SIZE / 2); /* left corner */
- draw_polygon(dr, coords, 7, cur ? COL_HIGHLIGHT : COL_LOWLIGHT, COL_TEXT);
- }
- static void draw_arrow_for_cursor(drawing *dr, game_drawstate *ds,
- int cur_x, int cur_y, bool cur)
- {
- if (cur_x == -1 && cur_y == -1)
- return; /* 'no cursur here */
- else if (cur_x == -1) /* LH column. */
- draw_arrow(dr, ds, COORD(0), COORD(cur_y+1), 0, -1, cur);
- else if (cur_x == ds->w) /* RH column */
- draw_arrow(dr, ds, COORD(ds->w), COORD(cur_y), 0, +1, cur);
- else if (cur_y == -1) /* Top row */
- draw_arrow(dr, ds, COORD(cur_x), COORD(0), +1, 0, cur);
- else if (cur_y == ds->h) /* Bottom row */
- draw_arrow(dr, ds, COORD(cur_x+1), COORD(ds->h), -1, 0, cur);
- else
- return;
- draw_update(dr, COORD(cur_x), COORD(cur_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, bgcolour;
- int cur_x = -1, cur_y = -1;
- 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);
- /*
- * Arrows for making moves.
- */
- for (i = 0; i < state->w; i++) {
- draw_arrow(dr, ds, COORD(i), COORD(0), +1, 0, false);
- draw_arrow(dr, ds, COORD(i+1), COORD(state->h), -1, 0, false);
- }
- for (i = 0; i < state->h; i++) {
- draw_arrow(dr, ds, COORD(state->w), COORD(i), 0, +1, false);
- draw_arrow(dr, ds, COORD(0), COORD(i+1), 0, -1, false);
- }
- ds->started = true;
- }
- /*
- * Cursor (highlighted arrow around edge)
- */
- if (ui->cur_visible) {
- cur_x = ui->cur_x; cur_y = ui->cur_y;
- }
- if (cur_x != ds->cur_x || cur_y != ds->cur_y) {
- /* Cursor has changed; redraw two (prev and curr) arrows. */
- draw_arrow_for_cursor(dr, ds, cur_x, cur_y, true);
- draw_arrow_for_cursor(dr, ds, ds->cur_x, ds->cur_y, false);
- }
- /*
- * Now draw each tile.
- */
- clip(dr, COORD(0), COORD(0), TILE_SIZE*state->w, TILE_SIZE*state->h);
- 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 ||
- ((ds->cur_x != cur_x || ds->cur_y != cur_y) && /* cursor moved */
- (TILE_CURSOR(i, state, ds->cur_x, ds->cur_y) ||
- TILE_CURSOR(i, state, cur_x, cur_y)))) {
- int x, y, x2, y2;
- /*
- * Figure out what to _actually_ draw, and where to
- * draw it.
- */
- if (t == -1) {
- int x0, y0, x1, y1, dx, dy;
- int j;
- float c;
- int sense;
- if (dir < 0) {
- assert(oldstate);
- sense = -oldstate->last_movement_sense;
- } else {
- sense = state->last_movement_sense;
- }
- t = state->tiles[i];
- /*
- * FIXME: must be prepared to draw a double
- * tile in some situations.
- */
- /*
- * 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));
- dx = (x1 - x0);
- if (dx != 0 &&
- dx != TILE_SIZE * sense) {
- dx = (dx < 0 ? dx + TILE_SIZE * state->w :
- dx - TILE_SIZE * state->w);
- assert(abs(dx) == TILE_SIZE);
- }
- dy = (y1 - y0);
- if (dy != 0 &&
- dy != TILE_SIZE * sense) {
- dy = (dy < 0 ? dy + TILE_SIZE * state->h :
- dy - TILE_SIZE * state->h);
- assert(abs(dy) == TILE_SIZE);
- }
- c = (animtime / ANIM_TIME);
- if (c < 0.0F) c = 0.0F;
- if (c > 1.0F) c = 1.0F;
- x = x0 + (int)(c * dx);
- y = y0 + (int)(c * dy);
- x2 = x1 - dx + (int)(c * dx);
- y2 = y1 - dy + (int)(c * dy);
- } else {
- x = COORD(X(state, i));
- y = COORD(Y(state, i));
- x2 = y2 = -1;
- }
- draw_tile(dr, ds, state, x, y, t,
- (x2 == -1 && TILE_CURSOR(i, state, cur_x, cur_y)) ?
- COL_LOWLIGHT : bgcolour);
- if (x2 != -1 || y2 != -1)
- draw_tile(dr, ds, state, x2, y2, t, bgcolour);
- }
- ds->tiles[i] = t0;
- }
- ds->cur_x = cur_x;
- ds->cur_y = cur_y;
- unclip(dr);
- 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));
- if (state->movetarget)
- sprintf(statusbuf+strlen(statusbuf), " (target %d)",
- state->movetarget);
- }
- 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)
- {
- if(ui->cur_visible) {
- *x = COORD(ui->cur_x);
- *y = COORD(ui->cur_y);
- *w = *h = TILE_SIZE;
- }
- }
- static int game_status(const game_state *state)
- {
- return state->completed ? +1 : 0;
- }
- #ifdef COMBINED
- #define thegame sixteen
- #endif
- const struct game thegame = {
- "Sixteen", "games.sixteen", "sixteen",
- 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,
- NULL, NULL, /* get_prefs, set_prefs */
- new_ui,
- free_ui,
- NULL, /* encode_ui */
- NULL, /* decode_ui */
- NULL, /* game_request_keys */
- game_changed_state,
- 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 */
- };
- /* vim: set shiftwidth=4 tabstop=8: */
|