ref: d35f64096dae0d6a7d6f9e94a01445acd247bc22
parent: f5ae256b77098d1505aa63dd78a044a732f9f717
author: Simon Tatham <anakin@pobox.com>
date: Sat Apr 30 10:14:14 EDT 2005
Twiddle now has an additional mode in which tile orientation matters. This mode is hard enough to scare the wossnames out of me, but that's no reason not to put it in anyway! [originally from svn r5713]
--- a/puzzles.but
+++ b/puzzles.but
@@ -392,9 +392,9 @@
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
@@ -431,6 +431,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}
--- a/twiddle.c
+++ b/twiddle.c
@@ -41,6 +41,8 @@
COL_HIGHLIGHT_GENTLE,
COL_LOWLIGHT,
COL_LOWLIGHT_GENTLE,
+ COL_TOP,
+ COL_BOTTOM,
NCOLOURS
};
@@ -47,10 +49,12 @@
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 @@
ret->w = ret->h = 3;
ret->n = 2;
- ret->rowsonly = FALSE;
+ ret->rowsonly = ret->orientable = FALSE;
return ret;
}
@@ -87,9 +91,11 @@
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 @@
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 @@
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 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 @@
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,11 +179,16 @@
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 @@
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 @@
* 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,13 +267,27 @@
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;
@@ -262,6 +294,11 @@
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 @@
*/
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 @@
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 @@
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 @@
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 @@
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 @@
* 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 @@
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 @@
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 @@
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);
@@ -714,6 +825,33 @@
draw_update(fe, 0, 0,
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.