Twiddle now has an additional mode in which tile orientation
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Sat, 30 Apr 2005 14:14:14 +0000 (14:14 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Sat, 30 Apr 2005 14:14:14 +0000 (14:14 +0000)
matters. This mode is hard enough to scare the wossnames out of me,
but that's no reason not to put it in anyway!

git-svn-id: svn://svn.tartarus.org/sgt/puzzles@5713 cda61777-01e9-0310-a592-d414129be87e

puzzles.but
twiddle.c

index b3317c3..657c255 100644 (file)
@@ -392,9 +392,9 @@ containing a number, and your aim is to arrange the numbers into
 ascending order.
 
 In basic Twiddle, your move is to rotate a square group of four
-tiles about their common centre. (Orientation is not significant:
-tiles never end up upside down!) On more advanced settings, you can
-rotate a larger square group of tiles.
+tiles about their common centre. (Orientation is not significant in
+the basic puzzle, although you can select it.) On more advanced
+settings, you can rotate a larger square group of tiles.
 
 I first saw this type of puzzle in the GameCube game \q{Metroid
 Prime 2}. In the Main Gyro Chamber in that game, there is a puzzle
@@ -432,6 +432,13 @@ are groups of identical numbers. In the simplified puzzle your aim
 is just to arrange all the 1s into the first row, all the 2s into
 the second row, and so on.
 
+\b You can configure whether the orientation of tiles matters. If
+you ask for an orientable puzzle, each tile will have a yellow bar
+along the side that should be at the top, and a green bar along the
+side that should be at the bottom. To remind you of which way round
+things go, there will be coloured bars by the sides of the grid.
+Line up matching colours horizontally to complete the puzzle.
+
 
 \C{rectangles} \i{Rectangles}
 
index e7560f6..5af27d5 100644 (file)
--- a/twiddle.c
+++ b/twiddle.c
@@ -41,16 +41,20 @@ enum {
     COL_HIGHLIGHT_GENTLE,
     COL_LOWLIGHT,
     COL_LOWLIGHT_GENTLE,
+    COL_TOP,
+    COL_BOTTOM,
     NCOLOURS
 };
 
 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 +67,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 +91,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 +116,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 +128,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 +143,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 +154,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 +179,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 +200,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 +224,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 +267,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 +316,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,13 +331,15 @@ 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--;
     }
 
@@ -364,6 +403,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;
@@ -390,6 +430,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;
@@ -443,7 +484,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 +493,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;
     }
@@ -504,6 +545,14 @@ static float *game_colours(frontend *fe, game_state *state, int *ncolours)
         ret[COL_TEXT * 3 + i] = 0.0;
     }
 
+    ret[COL_TOP * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 1.3F;
+    ret[COL_TOP * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 1.3F;
+    ret[COL_TOP * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 0.6F;
+
+    ret[COL_BOTTOM * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 0.6F;
+    ret[COL_BOTTOM * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 1.3F;
+    ret[COL_BOTTOM * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 0.6F;
+
     *ncolours = NCOLOURS;
     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,69 @@ 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 xw, yw, swap;
+       switch (tile & 3) {
+         case 0:
+           xw = TILE_SIZE - 3 - 2*HIGHLIGHT_WIDTH;
+           yw = HIGHLIGHT_WIDTH;
+           swap = FALSE;
+           break;
+         case 1:
+           xw = HIGHLIGHT_WIDTH;
+           yw = TILE_SIZE - 3 - 2*HIGHLIGHT_WIDTH;
+           swap = FALSE;
+           break;
+         case 2:
+           xw = TILE_SIZE - 3 - 2*HIGHLIGHT_WIDTH;
+           yw = HIGHLIGHT_WIDTH;
+           swap = TRUE;
+           break;
+         default /* case 3 */:
+           xw = HIGHLIGHT_WIDTH;
+           yw = TILE_SIZE - 3 - 2*HIGHLIGHT_WIDTH;
+           swap = TRUE;
+           break;
+       }
+
+       coords[0] = x + HIGHLIGHT_WIDTH + 1;
+       coords[1] = y + HIGHLIGHT_WIDTH + 1;
+       rotate(coords+0, rot);
+       coords[2] = x + HIGHLIGHT_WIDTH + 1 + xw;
+       coords[3] = y + HIGHLIGHT_WIDTH + 1;
+       rotate(coords+2, rot);
+       coords[4] = x + HIGHLIGHT_WIDTH + 1 + xw;
+       coords[5] = y + HIGHLIGHT_WIDTH + 1 + yw;
+       rotate(coords+4, rot);
+       coords[6] = x + HIGHLIGHT_WIDTH + 1;
+       coords[7] = y + HIGHLIGHT_WIDTH + 1 + yw;
+       rotate(coords+6, rot);
+       draw_polygon(fe, coords, 4, TRUE, swap ? COL_BOTTOM : COL_TOP);
+       draw_polygon(fe, coords, 4, FALSE, swap ? COL_BOTTOM : COL_TOP);
+
+       coords[0] = x + TILE_SIZE - 2 - HIGHLIGHT_WIDTH;
+       coords[1] = y + TILE_SIZE - 2 - HIGHLIGHT_WIDTH;
+       rotate(coords+0, rot);
+       coords[2] = x + TILE_SIZE - 2 - HIGHLIGHT_WIDTH - xw;
+       coords[3] = y + TILE_SIZE - 2 - HIGHLIGHT_WIDTH;
+       rotate(coords+2, rot);
+       coords[4] = x + TILE_SIZE - 2 - HIGHLIGHT_WIDTH - xw;
+       coords[5] = y + TILE_SIZE - 2 - HIGHLIGHT_WIDTH - yw;
+       rotate(coords+4, rot);
+       coords[6] = x + TILE_SIZE - 2 - HIGHLIGHT_WIDTH;
+       coords[7] = y + TILE_SIZE - 2 - HIGHLIGHT_WIDTH - yw;
+       rotate(coords+6, rot);
+       draw_polygon(fe, coords, 4, TRUE, swap ? COL_TOP : COL_BOTTOM);
+       draw_polygon(fe, coords, 4, FALSE, swap ? COL_TOP : COL_BOTTOM);
+    }
+
     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);
@@ -715,6 +826,33 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                    TILE_SIZE * state->w + 2 * BORDER,
                    TILE_SIZE * state->h + 2 * BORDER);
 
+       /*
+        * In an orientable puzzle, draw some colour bars at the
+        * sides as a gentle reminder of which colours need to be
+        * aligned where.
+        */
+       if (state->orientable) {
+           int y;
+           for (y = 0; y < state->h; y++) {
+               draw_rect(fe, COORD(0) - BORDER / 2,
+                         COORD(y) + HIGHLIGHT_WIDTH + 1,
+                         BORDER / 2 - 2 * HIGHLIGHT_WIDTH,
+                         HIGHLIGHT_WIDTH + 1, COL_TOP);
+               draw_rect(fe, COORD(state->w) + 2 * HIGHLIGHT_WIDTH,
+                         COORD(y) + HIGHLIGHT_WIDTH + 1,
+                         BORDER / 2 - 2 * HIGHLIGHT_WIDTH,
+                         HIGHLIGHT_WIDTH + 1, COL_TOP);
+               draw_rect(fe, COORD(0) - BORDER / 2,
+                         COORD(y) + TILE_SIZE - 2 - 2 * HIGHLIGHT_WIDTH,
+                         BORDER / 2 - 2 * HIGHLIGHT_WIDTH,
+                         HIGHLIGHT_WIDTH + 1, COL_BOTTOM);
+               draw_rect(fe, COORD(state->w) + 2 * HIGHLIGHT_WIDTH,
+                         COORD(y) + TILE_SIZE - 2 - 2 * HIGHLIGHT_WIDTH,
+                         BORDER / 2 - 2 * HIGHLIGHT_WIDTH,
+                         HIGHLIGHT_WIDTH + 1, COL_BOTTOM);
+           }
+       }
+
         /*
          * Recessed area containing the whole puzzle.
          */