Copy-to-clipboard facility for Fifteen, Sixteen and Twiddle.
[sgt/puzzles] / twiddle.c
index 44a1e39..162f68b 100644 (file)
--- a/twiddle.c
+++ b/twiddle.c
@@ -334,7 +334,9 @@ static char *new_game_seed(game_params *params, random_state *rs)
 
     /*
      * Now construct the game seed, by describing the grid as a
-     * simple sequence of comma-separated integers.
+     * simple sequence of integers. They're comma-separated, unless
+     * the puzzle is orientable in which case they're separated by
+     * orientation letters `u', `d', `l' and `r'.
      */
     ret = NULL;
     retlen = 0;
@@ -342,13 +344,15 @@ static char *new_game_seed(game_params *params, random_state *rs)
         char buf[80];
         int k;
 
-        k = sprintf(buf, "%d,", grid[i]);
+        k = sprintf(buf, "%d%c", grid[i] / 4,
+                   params->orientable ? "uldr"[grid[i] & 3] : ',');
 
         ret = sresize(ret, retlen + k + 1, char);
         strcpy(ret + retlen, buf);
         retlen += k;
     }
-    ret[retlen-1] = '\0';              /* delete last comma */
+    if (!params->orientable)
+       ret[retlen-1] = '\0';          /* delete last comma */
 
     sfree(grid);
     return ret;
@@ -364,15 +368,17 @@ static char *validate_seed(game_params *params, char *seed)
     err = NULL;
 
     for (i = 0; i < wh; i++) {
-       if (*p < '0' || *p > '9') {
+       if (*p < '0' || *p > '9')
            return "Not enough numbers in string";
-       }
        while (*p >= '0' && *p <= '9')
            p++;
-       if (i < wh-1 && *p != ',') {
-           return "Expected comma after number";
-       }
-       else if (i == wh-1 && *p) {
+       if (!params->orientable && i < wh-1) {
+           if (*p != ',')
+               return "Expected comma after number";
+       } else if (params->orientable && i < wh) {
+           if (*p != 'l' && *p != 'r' && *p != 'u' && *p != 'd')
+               return "Expected orientation letter after number";
+       } else if (i == wh-1 && *p) {
            return "Excess junk at end of string";
        }
 
@@ -402,11 +408,19 @@ static game_state *new_game(game_params *params, char *seed)
     p = seed;
 
     for (i = 0; i < wh; i++) {
-       state->grid[i] = atoi(p);
+       state->grid[i] = 4 * atoi(p);
        while (*p >= '0' && *p <= '9')
            p++;
-
-       if (*p) p++;                   /* eat comma */
+       if (*p) {
+           if (params->orientable) {
+               switch (*p) {
+                 case 'l': state->grid[i] |= 1; break;
+                 case 'd': state->grid[i] |= 2; break;
+                 case 'r': state->grid[i] |= 3; break;
+               }
+           }
+           p++;
+       }
     }
 
     return state;
@@ -438,6 +452,53 @@ static void free_game(game_state *state)
     sfree(state);
 }
 
+static char *game_text_format(game_state *state)
+{
+    char *ret, *p, buf[80];
+    int i, x, y, col, o, maxlen;
+
+    /*
+     * First work out how many characters we need to display each
+     * number. We're pretty flexible on grid contents here, so we
+     * have to scan the entire grid.
+     */
+    col = 0;
+    for (i = 0; i < state->w * state->h; i++) {
+       x = sprintf(buf, "%d", state->grid[i] / 4);
+       if (col < x) col = x;
+    }
+    o = (state->orientable ? 1 : 0);
+
+    /*
+     * 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+o,
+     * w-1 spaces and a trailing newline.
+     */
+    maxlen = state->h * state->w * (col+o+1);
+
+    ret = snewn(maxlen, char);
+    p = ret;
+
+    for (y = 0; y < state->h; y++) {
+       for (x = 0; x < state->w; x++) {
+           int v = state->grid[state->w*y+x];
+           sprintf(buf, "%*d", col, v/4);
+           memcpy(p, buf, col);
+           p += col;
+           if (o)
+               *p++ = "^<v>"[v & 3];
+           if (x+1 == state->w)
+               *p++ = '\n';
+           else
+               *p++ = ' ';
+       }
+    }
+
+    assert(p - ret == maxlen);
+    *p = '\0';
+    return ret;
+}
+
 static game_ui *new_ui(game_state *state)
 {
     return NULL;
@@ -685,14 +746,14 @@ static void draw_tile(frontend *fe, game_state *state, int x, int y,
        displ = TILE_SIZE / 2 - HIGHLIGHT_WIDTH - 2;
        displ2 = TILE_SIZE / 3 - HIGHLIGHT_WIDTH;
 
-       coords[0] = cx - displ * xdx - displ2 * ydx;
-       coords[1] = cy - displ * xdy - displ2 * ydy;
+       coords[0] = cx - displ * xdx + displ2 * ydx;
+       coords[1] = cy - displ * xdy + displ2 * ydy;
        rotate(coords+0, rot);
-       coords[2] = cx + displ * xdx - displ2 * ydx;
-       coords[3] = cy + displ * xdy - displ2 * ydy;
+       coords[2] = cx + displ * xdx + displ2 * ydx;
+       coords[3] = cy + displ * xdy + displ2 * ydy;
        rotate(coords+2, rot);
-       coords[4] = cx + displ * ydx;
-       coords[5] = cy + displ * ydy;
+       coords[4] = cx - displ * ydx;
+       coords[5] = cy - displ * ydy;
        rotate(coords+4, rot);
        draw_polygon(fe, coords, 3, TRUE, COL_LOWLIGHT_GENTLE);
        draw_polygon(fe, coords, 3, FALSE, COL_LOWLIGHT_GENTLE);
@@ -914,21 +975,21 @@ static int game_wants_statusbar(void)
 #endif
 
 const struct game thegame = {
-    "Twiddle", "games.twiddle", TRUE,
+    "Twiddle", "games.twiddle",
     default_params,
     game_fetch_preset,
     decode_params,
     encode_params,
     free_params,
     dup_params,
-    game_configure,
-    custom_params,
+    TRUE, game_configure, custom_params,
     validate_params,
     new_game_seed,
     validate_seed,
     new_game,
     dup_game,
     free_game,
+    TRUE, game_text_format,
     new_ui,
     free_ui,
     make_move,