chiark / gitweb /
Introduced a new function in every game which formats a game_state
[sgt-puzzles.git] / twiddle.c
index e7560f605e1a3f70f1bb42107959a52228f34bcd..4bfd30cd78549d2955646cd76b016611252a45d1 100644 (file)
--- a/twiddle.c
+++ b/twiddle.c
@@ -5,15 +5,6 @@
  * Gyro Chamber).
  */
 
-/*
- * Possibly TODO:
- * 
- *  - it's horribly tempting to give the pieces significant
- *    _orientations_, perhaps by drawing some sort of oriented
- *    polygonal figure beneath the number. (An arrow pointing
- *    upwards springs readily to mind.)
- */
-
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -47,10 +38,12 @@ enum {
 struct game_params {
     int w, h, n;
     int rowsonly;
+    int orientable;
 };
 
 struct game_state {
     int w, h, n;
+    int orientable;
     int *grid;
     int completed;
     int movecount;
@@ -63,7 +56,7 @@ static game_params *default_params(void)
 
     ret->w = ret->h = 3;
     ret->n = 2;
-    ret->rowsonly = FALSE;
+    ret->rowsonly = ret->orientable = FALSE;
 
     return ret;
 }
@@ -87,9 +80,11 @@ static int game_fetch_preset(int i, char **name, game_params **params)
         char *title;
         game_params params;
     } presets[] = {
-        { "3x3 rows only", { 3, 3, 2, TRUE } },
-        { "3x3 normal", { 3, 3, 2, FALSE } },
+        { "3x3 rows only", { 3, 3, 2, TRUE, FALSE } },
+        { "3x3 normal", { 3, 3, 2, FALSE, FALSE } },
+        { "3x3 orientable", { 3, 3, 2, FALSE, TRUE } },
         { "4x4 normal", { 4, 4, 2, FALSE } },
+        { "4x4 orientable", { 4, 4, 2, FALSE, TRUE } },
         { "4x4 radius 3", { 4, 4, 3, FALSE } },
         { "5x5 radius 3", { 5, 5, 3, FALSE } },
         { "6x6 radius 4", { 6, 6, 4, FALSE } },
@@ -110,7 +105,7 @@ static game_params *decode_params(char const *string)
 
     ret->w = ret->h = atoi(string);
     ret->n = 2;
-    ret->rowsonly = FALSE;
+    ret->rowsonly = ret->orientable = FALSE;
     while (*string && isdigit(*string)) string++;
     if (*string == 'x') {
         string++;
@@ -122,9 +117,13 @@ static game_params *decode_params(char const *string)
         ret->n = atoi(string);
        while (*string && isdigit(*string)) string++;
     }
-    if (*string == 'r') {
+    while (*string) {
+       if (*string == 'r') {
+           ret->rowsonly = TRUE;
+       } else if (*string == 'o') {
+           ret->orientable = TRUE;
+       }
        string++;
-       ret->rowsonly = TRUE;
     }
 
     return ret;
@@ -133,8 +132,9 @@ static game_params *decode_params(char const *string)
 static char *encode_params(game_params *params)
 {
     char buf[256];
-    sprintf(buf, "%dx%dn%d%s", params->w, params->h, params->n,
-           params->rowsonly ? "r" : "");
+    sprintf(buf, "%dx%dn%d%s%s", params->w, params->h, params->n,
+           params->rowsonly ? "r" : "",
+           params->orientable ? "o" : "");
     return dupstr(buf);
 }
 
@@ -143,7 +143,7 @@ static config_item *game_configure(game_params *params)
     config_item *ret;
     char buf[80];
 
-    ret = snewn(4, config_item);
+    ret = snewn(6, config_item);
 
     ret[0].name = "Width";
     ret[0].type = C_STRING;
@@ -168,10 +168,15 @@ static config_item *game_configure(game_params *params)
     ret[3].sval = NULL;
     ret[3].ival = params->rowsonly;
 
-    ret[4].name = NULL;
-    ret[4].type = C_END;
+    ret[4].name = "Orientation matters";
+    ret[4].type = C_BOOLEAN;
     ret[4].sval = NULL;
-    ret[4].ival = 0;
+    ret[4].ival = params->orientable;
+
+    ret[5].name = NULL;
+    ret[5].type = C_END;
+    ret[5].sval = NULL;
+    ret[5].ival = 0;
 
     return ret;
 }
@@ -184,6 +189,7 @@ static game_params *custom_params(config_item *cfg)
     ret->h = atoi(cfg[1].sval);
     ret->n = atoi(cfg[2].sval);
     ret->rowsonly = cfg[3].ival;
+    ret->orientable = cfg[4].ival;
 
     return ret;
 }
@@ -207,7 +213,8 @@ static char *validate_params(game_params *params)
  * the centre is good for a user interface, but too inconvenient to
  * use internally.)
  */
-static void do_rotate(int *grid, int w, int h, int n, int x, int y, int dir)
+static void do_rotate(int *grid, int w, int h, int n, int orientable,
+                     int x, int y, int dir)
 {
     int i, j;
 
@@ -249,19 +256,38 @@ static void do_rotate(int *grid, int w, int h, int n, int x, int y, int dir)
            for (k = 0; k < 4; k++)
                g[k] = grid[p[k]];
 
-           for (k = 0; k < 4; k++)
-               grid[p[k]] = g[(k+dir) & 3];
+           for (k = 0; k < 4; k++) {
+               int v = g[(k+dir) & 3];
+               if (orientable)
+                   v ^= ((v+dir) ^ v) & 3;  /* alter orientation */
+               grid[p[k]] = v;
+           }
        }
     }
+
+    /*
+     * Don't forget the orientation on the centre square, if n is
+     * odd.
+     */
+    if (orientable && (n & 1)) {
+       int v = grid[n/2*(w+1)];
+       v ^= ((v+dir) ^ v) & 3;  /* alter orientation */
+       grid[n/2*(w+1)] = v;
+    }
 }
 
-static int grid_complete(int *grid, int wh)
+static int grid_complete(int *grid, int wh, int orientable)
 {
     int ok = TRUE;
     int i;
     for (i = 1; i < wh; i++)
        if (grid[i] < grid[i-1])
            ok = FALSE;
+    if (orientable) {
+       for (i = 0; i < wh; i++)
+           if (grid[i] & 3)
+               ok = FALSE;
+    }
     return ok;
 }
 
@@ -279,7 +305,7 @@ static char *new_game_seed(game_params *params, random_state *rs)
      */
     grid = snewn(wh, int);
     for (i = 0; i < wh; i++)
-       grid[i] = (params->rowsonly ? i/w : i) + 1;
+       grid[i] = ((params->rowsonly ? i/w : i) + 1) * 4;
 
     /*
      * Shuffle it. This game is complex enough that I don't feel up
@@ -294,19 +320,23 @@ static char *new_game_seed(game_params *params, random_state *rs)
 
        x = random_upto(rs, w - n + 1);
        y = random_upto(rs, h - n + 1);
-       do_rotate(grid, w, h, n, x, y, 1 + random_upto(rs, 3));
+       do_rotate(grid, w, h, n, params->orientable,
+                 x, y, 1 + random_upto(rs, 3));
 
        /*
         * Optionally one more move in case the entire grid has
         * happened to come out solved.
         */
-       if (i == total_moves - 1 && grid_complete(grid, wh))
+       if (i == total_moves - 1 && grid_complete(grid, wh,
+                                                 params->orientable))
            i--;
     }
 
     /*
      * 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;
@@ -314,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;
@@ -336,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";
        }
 
@@ -364,6 +398,7 @@ static game_state *new_game(game_params *params, char *seed)
     state->w = w;
     state->h = h;
     state->n = n;
+    state->orientable = params->orientable;
     state->completed = 0;
     state->movecount = 0;
     state->lastx = state->lasty = state->lastr = -1;
@@ -373,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;
@@ -390,6 +433,7 @@ static game_state *dup_game(game_state *state)
     ret->w = state->w;
     ret->h = state->h;
     ret->n = state->n;
+    ret->orientable = state->orientable;
     ret->completed = state->completed;
     ret->movecount = state->movecount;
     ret->lastx = state->lastx;
@@ -408,6 +452,11 @@ static void free_game(game_state *state)
     sfree(state);
 }
 
+static char *game_text_format(game_state *state)
+{
+    return NULL;
+}
+
 static game_ui *new_ui(game_state *state)
 {
     return NULL;
@@ -443,7 +492,7 @@ static game_state *make_move(game_state *from, game_ui *ui, int x, int y,
        ret = dup_game(from);
        ret->movecount++;
        dir = (button == LEFT_BUTTON ? 1 : -1);
-       do_rotate(ret->grid, w, h, n, x, y, dir);
+       do_rotate(ret->grid, w, h, n, ret->orientable, x, y, dir);
        ret->lastx = x;
        ret->lasty = y;
        ret->lastr = dir;
@@ -452,7 +501,7 @@ static game_state *make_move(game_state *from, game_ui *ui, int x, int y,
         * See if the game has been completed. To do this we simply
         * test that the grid contents are in increasing order.
         */
-       if (!ret->completed && grid_complete(ret->grid, wh))
+       if (!ret->completed && grid_complete(ret->grid, wh, ret->orientable))
            ret->completed = ret->movecount;
        return ret;
     }
@@ -601,6 +650,9 @@ static void draw_tile(frontend *fe, game_state *state, int x, int y,
     draw_polygon(fe, coords, 3, TRUE, rot ? rot->tc : COL_HIGHLIGHT);
     draw_polygon(fe, coords, 3, FALSE, rot ? rot->tc : COL_HIGHLIGHT);
 
+    /*
+     * Now the main blank area in the centre of the tile.
+     */
     if (rot) {
        coords[0] = x + HIGHLIGHT_WIDTH;
        coords[1] = y + HIGHLIGHT_WIDTH;
@@ -622,10 +674,53 @@ static void draw_tile(frontend *fe, game_state *state, int x, int y,
                  flash_colour);
     }
 
+    /*
+     * Next, the colour bars for orientation.
+     */
+    if (state->orientable) {
+       int xdx, xdy, ydx, ydy;
+       int cx, cy, displ, displ2;
+       switch (tile & 3) {
+         case 0:
+           xdx = 1, xdy = 0;
+           ydx = 0, ydy = 1;
+           break;
+         case 1:
+           xdx = 0, xdy = -1;
+           ydx = 1, ydy = 0;
+           break;
+         case 2:
+           xdx = -1, xdy = 0;
+           ydx = 0, ydy = -1;
+           break;
+         default /* case 3 */:
+           xdx = 0, xdy = 1;
+           ydx = -1, ydy = 0;
+           break;
+       }
+
+       cx = x + TILE_SIZE / 2;
+       cy = y + TILE_SIZE / 2;
+       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;
+       rotate(coords+0, rot);
+       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;
+       rotate(coords+4, rot);
+       draw_polygon(fe, coords, 3, TRUE, COL_LOWLIGHT_GENTLE);
+       draw_polygon(fe, coords, 3, FALSE, COL_LOWLIGHT_GENTLE);
+    }
+
     coords[0] = x + TILE_SIZE/2;
     coords[1] = y + TILE_SIZE/2;
     rotate(coords+0, rot);
-    sprintf(str, "%d", tile);
+    sprintf(str, "%d", tile / 4);
     draw_text(fe, coords[0], coords[1],
              FONT_VARIABLE, TILE_SIZE/3, ALIGN_VCENTRE | ALIGN_HCENTRE,
              COL_TEXT, str);
@@ -838,21 +933,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,
+    FALSE, game_text_format,
     new_ui,
     free_ui,
     make_move,