/* These flags must be unique across all uses; in the game_state,
* the game_ui, and the drawstate (as they all get combined in the
* drawstate). */
-#define TILE_COLMASK 0x0ff
-#define TILE_SELECTED 0x100 /* used in ui and drawstate */
-#define TILE_JOINRIGHT 0x200 /* used in drawstate */
-#define TILE_JOINDOWN 0x400 /* used in drawstate */
-#define TILE_JOINDIAG 0x800 /* used in drawstate */
+#define TILE_COLMASK 0x00ff
+#define TILE_SELECTED 0x0100 /* used in ui and drawstate */
+#define TILE_JOINRIGHT 0x0200 /* used in drawstate */
+#define TILE_JOINDOWN 0x0400 /* used in drawstate */
+#define TILE_JOINDIAG 0x0800 /* used in drawstate */
+#define TILE_HASSEL 0x1000 /* used in drawstate */
+#define TILE_IMPOSSIBLE 0x2000 /* used in drawstate */
#define TILE(gs,x,y) ((gs)->tiles[(gs)->params.w*(y)+(x)])
#define COL(gs,x,y) (TILE(gs,x,y) & TILE_COLMASK)
return ret;
}
-static char *validate_params(game_params *params)
+static char *validate_params(game_params *params, int full)
{
if (params->w < 1 || params->h < 1)
return "Width and height must both be positive";
*/
static char *new_game_desc(game_params *params, random_state *rs,
- game_aux_info **aux, int interactive)
+ char **aux, int interactive)
{
char *ret;
int n, i, j, c, retlen, *tiles;
- debug(("new_game_desc: %dx%d, %d colours",
- params->w, params->h, params->ncols));
n = params->w * params->h;
tiles = snewn(n, int);
memset(tiles, 0, n*sizeof(int));
return ret;
}
-static void game_free_aux_info(game_aux_info *aux)
-{
- assert(!"Shouldn't happen");
-}
-
static char *validate_desc(game_params *params, char *desc)
{
int area = params->w * params->h, i;
sfree(state);
}
-static game_state *solve_game(game_state *state, game_aux_info *aux,
- char **error)
+static char *solve_game(game_state *state, game_state *currstate,
+ char *aux, char **error)
{
return NULL;
}
struct game_params params;
int *tiles; /* selected-ness only */
int nselected;
+ int xsel, ysel, displaysel;
};
static game_ui *new_ui(game_state *state)
memset(ui->tiles, 0, state->n*sizeof(int));
ui->nselected = 0;
+ ui->xsel = ui->ysel = ui->displaysel = 0;
+
return ui;
}
sfree(ui);
}
+static char *encode_ui(game_ui *ui)
+{
+ return NULL;
+}
+
+static void decode_ui(game_ui *ui, char *encoding)
+{
+}
+
static void sel_clear(game_ui *ui, game_state *state)
{
int i;
for (i = 0; i < state->n; i++)
ui->tiles[i] &= ~TILE_SELECTED;
ui->nselected = 0;
- debug(("sel_clear"));
}
game_state *newstate)
{
sel_clear(ui, newstate);
+
+ /*
+ * If the game state has just changed into an unplayable one
+ * (either completed or impossible), we vanish the keyboard-
+ * control cursor.
+ */
+ if (newstate->complete || newstate->impossible)
+ ui->displaysel = 0;
}
-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;
- debug(("sel_remove: removed %d selected tiles", nremoved));
+
+ assert(retlen < retsize);
+ ret[retlen++] = '\0';
+ return sresize(ret, retlen, char);
}
static void sel_expand(game_ui *ui, game_state *state, int tx, int ty)
int ns = 1, nadded, x, y, c;
TILE(ui,tx,ty) |= TILE_SELECTED;
- debug(("sel_expand, selected initial tile"));
do {
nadded = 0;
}
}
ns += nadded;
- debug(("sel_expand, new pass, selected %d more tiles", nadded));
} while (nadded > 0);
if (ns > 1) {
ndone = 0;
for (x = 0; x < ret->params.w-1; x++) {
if (sg_emptycol(ret,x) && !sg_emptycol(ret,x+1)) {
- debug(("column %d is empty, shuffling from %d", x, x+1));
ndone++;
for (y = 0; y < ret->params.h; y++) {
SWAPTILE(ret,x,y,x+1,y);
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 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;
-
- if (button != RIGHT_BUTTON && button != LEFT_BUTTON)
+ char *ret = "";
+
+ ui->displaysel = 0;
+
+ if (button == RIGHT_BUTTON || button == LEFT_BUTTON) {
+ tx = FROMCOORD(x); ty= FROMCOORD(y);
+ } else if (button == CURSOR_UP || button == CURSOR_DOWN ||
+ button == CURSOR_LEFT || button == CURSOR_RIGHT) {
+ int dx = 0, dy = 0;
+ ui->displaysel = 1;
+ dx = (button == CURSOR_LEFT) ? -1 : ((button == CURSOR_RIGHT) ? +1 : 0);
+ dy = (button == CURSOR_DOWN) ? +1 : ((button == CURSOR_UP) ? -1 : 0);
+ ui->xsel = (ui->xsel + 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') {
+ ui->displaysel = 1;
+ tx = ui->xsel;
+ ty = ui->ysel;
+ } else
return NULL;
- tx = FROMCOORD(x); ty= FROMCOORD(y);
- 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);
- 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 */
- }
+ sel_clear(ui, state);
+ else
+ ret = sel_movedesc(ui, state);
} 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);
}
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.
*/
-static void game_size(game_params *params, game_drawstate *ds, int *x, int *y,
- int expand)
+static void game_set_size(game_drawstate *ds, game_params *params,
+ int tilesize)
{
- int tsx, tsy, ts;
-
- /*
- * We could choose the tile gap dynamically as well if we
- * wanted to; for example, at low tile sizes it might be
- * sensible to leave it out completely. However, for the moment
- * and for the sake of simplicity I'm just going to fix it at
- * 2.
- */
ds->tilegap = 2;
+ ds->tileinner = tilesize - ds->tilegap;
+}
- /*
- * Each window dimension equals the tile size (inner plus gap)
- * times the grid dimension, plus another tile size (border is
- * half the width of a tile), minus one tile gap.
- *
- * We must cast to unsigned before adding to *x and *y, since
- * they might be INT_MAX!
- */
- tsx = (unsigned)(*x + ds->tilegap) / (params->w + 1);
- tsy = (unsigned)(*y + ds->tilegap) / (params->h + 1);
-
- ts = min(tsx, tsy);
- if (expand)
- ds->tileinner = ts - ds->tilegap;
- else
- ds->tileinner = min(ts, PREFERRED_TILE_SIZE) - ds->tilegap;
+static void game_compute_size(game_params *params, int tilesize,
+ int *x, int *y)
+{
+ /* Ick: fake up tile size variables for macro expansion purposes */
+ game_drawstate ads, *ds = &ads;
+ game_set_size(ds, params, tilesize);
*x = TILE_SIZE * params->w + 2 * BORDER - TILE_GAP;
*y = TILE_SIZE * params->h + 2 * BORDER - TILE_GAP;
ret[COL_3 * 3 + 1] = 0.0F;
ret[COL_3 * 3 + 2] = 0.0F;
- ret[COL_4 * 3 + 0] = 0.5F;
- ret[COL_4 * 3 + 1] = 0.5F;
- ret[COL_4 * 3 + 2] = 1.0F;
+ ret[COL_4 * 3 + 0] = 1.0F;
+ ret[COL_4 * 3 + 1] = 1.0F;
+ ret[COL_4 * 3 + 2] = 0.0F;
- ret[COL_5 * 3 + 0] = 0.5F;
- ret[COL_5 * 3 + 1] = 1.0F;
- ret[COL_5 * 3 + 2] = 0.5F;
+ ret[COL_5 * 3 + 0] = 1.0F;
+ ret[COL_5 * 3 + 1] = 0.0F;
+ ret[COL_5 * 3 + 2] = 1.0F;
- ret[COL_6 * 3 + 0] = 1.0F;
- ret[COL_6 * 3 + 1] = 0.5F;
- ret[COL_6 * 3 + 2] = 0.5F;
+ ret[COL_6 * 3 + 0] = 0.0F;
+ ret[COL_6 * 3 + 1] = 1.0F;
+ ret[COL_6 * 3 + 2] = 1.0F;
- ret[COL_7 * 3 + 0] = 1.0F;
- ret[COL_7 * 3 + 1] = 1.0F;
- ret[COL_7 * 3 + 2] = 0.0F;
+ ret[COL_7 * 3 + 0] = 0.5F;
+ ret[COL_7 * 3 + 1] = 0.5F;
+ ret[COL_7 * 3 + 2] = 1.0F;
- ret[COL_8 * 3 + 0] = 1.0F;
- ret[COL_8 * 3 + 1] = 0.0F;
- ret[COL_8 * 3 + 2] = 1.0F;
+ ret[COL_8 * 3 + 0] = 0.5F;
+ ret[COL_8 * 3 + 1] = 1.0F;
+ ret[COL_8 * 3 + 2] = 0.5F;
- ret[COL_9 * 3 + 0] = 0.0F;
- ret[COL_9 * 3 + 1] = 1.0F;
- ret[COL_9 * 3 + 2] = 1.0F;
+ ret[COL_9 * 3 + 0] = 1.0F;
+ ret[COL_9 * 3 + 1] = 0.5F;
+ ret[COL_9 * 3 + 2] = 0.5F;
ret[COL_IMPOSSIBLE * 3 + 0] = 0.0F;
ret[COL_IMPOSSIBLE * 3 + 1] = 0.0F;
static void tile_redraw(frontend *fe, game_drawstate *ds,
int x, int y, int dright, int dbelow,
- int tile, game_state *state, int bgcolour)
+ int tile, int bgcolour)
{
int outer = bgcolour, inner = outer, col = tile & TILE_COLMASK;
if (col) {
- if (state->impossible) {
+ if (tile & TILE_IMPOSSIBLE) {
outer = col;
inner = COL_IMPOSSIBLE;
} else if (tile & TILE_SELECTED) {
draw_rect(fe, COORD(x)+TILE_INNER, COORD(y)+TILE_INNER, TILE_GAP, TILE_GAP,
(tile & TILE_JOINDIAG) ? outer : bgcolour);
+ if (tile & TILE_HASSEL) {
+ int sx = COORD(x)+2, sy = COORD(y)+2, ssz = TILE_INNER-5;
+ int scol = (outer == COL_SEL) ? COL_LOWLIGHT : COL_HIGHLIGHT;
+ draw_line(fe, sx, sy, sx+ssz, sy, scol);
+ draw_line(fe, sx+ssz, sy, sx+ssz, sy+ssz, scol);
+ draw_line(fe, sx+ssz, sy+ssz, sx, sy+ssz, scol);
+ draw_line(fe, sx, sy+ssz, sx, sy, scol);
+ }
+
draw_update(fe, COORD(x), COORD(y), TILE_SIZE, TILE_SIZE);
}
{
int bgcolour, x, y;
- debug(("samegame redraw: dir %d, oldstate 0x%lx, animtime %f, flashtime %f",
- dir, oldstate, animtime, flashtime));
-
/* This was entirely cloned from fifteen.c; it should probably be
* moved into some generic 'draw-recessed-rectangle' utility fn. */
if (!ds->started) {
coords[9] = COORD(state->params.h) + HIGHLIGHT_WIDTH - 1 - TILE_GAP;
coords[6] = coords[8] + TILE_SIZE;
coords[7] = coords[9] - TILE_SIZE;
- draw_polygon(fe, coords, 5, TRUE, COL_HIGHLIGHT);
- draw_polygon(fe, coords, 5, FALSE, COL_HIGHLIGHT);
+ draw_polygon(fe, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT);
coords[1] = COORD(0) - HIGHLIGHT_WIDTH;
coords[0] = COORD(0) - HIGHLIGHT_WIDTH;
- draw_polygon(fe, coords, 5, TRUE, COL_LOWLIGHT);
- draw_polygon(fe, coords, 5, FALSE, COL_LOWLIGHT);
+ draw_polygon(fe, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT);
ds->started = 1;
}
int dbelow = (y+1 < state->params.h);
tile |= ISSEL(ui,x,y);
+ if (state->impossible)
+ tile |= TILE_IMPOSSIBLE;
if (dright && COL(state,x+1,y) == col)
tile |= TILE_JOINRIGHT;
if (dbelow && COL(state,x,y+1) == col)
COL(state,x+1,y+1) == col)
tile |= TILE_JOINDIAG;
+ if (ui->displaysel && ui->xsel == x && ui->ysel == y)
+ tile |= TILE_HASSEL;
+
/* For now we're never expecting oldstate at all (because we have
* no animation); when we do we might well want to be looking
* at the tile colours from oldstate, not state. */
(flashtime > 0.0) ||
(ds->bgcolour != bgcolour) ||
(tile != ds->tiles[i])) {
- tile_redraw(fe, ds, x, y, dright, dbelow,
- tile, state, bgcolour);
+ tile_redraw(fe, ds, x, y, dright, dbelow, tile, bgcolour);
ds->tiles[i] = tile;
}
}
return TRUE;
}
-static int game_timing_state(game_state *state)
+static int game_timing_state(game_state *state, game_ui *ui)
{
return TRUE;
}
#endif
const struct game thegame = {
- "Same Game", NULL,
+ "Same Game", "games.samegame",
default_params,
game_fetch_preset,
decode_params,
TRUE, game_configure, custom_params,
validate_params,
new_game_desc,
- game_free_aux_info,
validate_desc,
new_game,
dup_game,
TRUE, game_text_format,
new_ui,
free_ui,
+ encode_ui,
+ decode_ui,
game_changed_state,
- make_move,
- game_size,
+ interpret_move,
+ execute_move,
+ PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
game_colours,
game_new_drawstate,
game_free_drawstate,