Sync with website
[sgt/puzzles] / samegame.c
index d8fa43c..4bb535d 100644 (file)
@@ -43,11 +43,13 @@ struct game_params {
 /* 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)
@@ -207,7 +209,7 @@ static game_params *custom_params(config_item *cfg)
     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";
@@ -238,13 +240,11 @@ static char *validate_params(game_params *params)
  */
 
 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));
@@ -282,11 +282,6 @@ static char *new_game_desc(game_params *params, random_state *rs,
     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;
@@ -296,9 +291,9 @@ static char *validate_desc(game_params *params, char *desc)
        char *q = p;
        int n;
 
-       if (!isdigit(*p))
+       if (!isdigit((unsigned char)*p))
            return "Not enough numbers in string";
-       while (isdigit(*p)) p++;
+       while (isdigit((unsigned char)*p)) p++;
 
        if (i < area-1 && *p != ',')
            return "Expected comma after number";
@@ -355,8 +350,8 @@ static void free_game(game_state *state)
     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;
 }
@@ -388,6 +383,7 @@ struct game_ui {
     struct game_params params;
     int *tiles; /* selected-ness only */
     int nselected;
+    int xsel, ysel, displaysel;
 };
 
 static game_ui *new_ui(game_state *state)
@@ -399,6 +395,8 @@ 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;
 }
 
@@ -408,6 +406,15 @@ static void free_ui(game_ui *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;
@@ -415,7 +422,6 @@ static void sel_clear(game_ui *ui, game_state *state)
     for (i = 0; i < state->n; i++)
        ui->tiles[i] &= ~TILE_SELECTED;
     ui->nselected = 0;
-    debug(("sel_clear"));
 }
 
 
@@ -423,23 +429,47 @@ static void game_changed_state(game_ui *ui, game_state *oldstate,
                                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)
@@ -447,7 +477,6 @@ 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;
 
@@ -487,7 +516,6 @@ static void sel_expand(game_ui *ui, game_state *state, int tx, int ty)
            }
        }
        ns += nadded;
-       debug(("sel_expand, new pass, selected %d more tiles", nadded));
     } while (nadded > 0);
 
     if (ns > 1) {
@@ -530,7 +558,6 @@ static void sg_snuggle(game_state *ret)
        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);
@@ -569,72 +596,101 @@ 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 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;
@@ -658,29 +714,29 @@ static float *game_colours(frontend *fe, game_state *state, int *ncolours)
     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;
@@ -730,12 +786,12 @@ static void game_free_drawstate(game_drawstate *ds)
 
 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) {
@@ -759,6 +815,15 @@ static void tile_redraw(frontend *fe, game_drawstate *ds,
        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);
 }
 
@@ -768,9 +833,6 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
 {
     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) {
@@ -796,13 +858,11 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
        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;
     }
@@ -821,6 +881,8 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
            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)
@@ -829,6 +891,9 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                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. */
@@ -836,8 +901,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                (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;
            }
        }
@@ -883,7 +947,7 @@ static int game_wants_statusbar(void)
     return TRUE;
 }
 
-static int game_timing_state(game_state *state)
+static int game_timing_state(game_state *state, game_ui *ui)
 {
     return TRUE;
 }
@@ -893,7 +957,7 @@ static int game_timing_state(game_state *state)
 #endif
 
 const struct game thegame = {
-    "Same Game", NULL,
+    "Same Game", "games.samegame",
     default_params,
     game_fetch_preset,
     decode_params,
@@ -903,7 +967,6 @@ const struct game thegame = {
     TRUE, game_configure, custom_params,
     validate_params,
     new_game_desc,
-    game_free_aux_info,
     validate_desc,
     new_game,
     dup_game,
@@ -912,9 +975,12 @@ const struct game thegame = {
     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,