* 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>
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;
ret->w = ret->h = 3;
ret->n = 2;
- ret->rowsonly = FALSE;
+ ret->rowsonly = ret->orientable = FALSE;
return ret;
}
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 } },
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++;
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;
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);
}
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;
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;
}
ret->h = atoi(cfg[1].sval);
ret->n = atoi(cfg[2].sval);
ret->rowsonly = cfg[3].ival;
+ ret->orientable = cfg[4].ival;
return ret;
}
* 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;
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;
}
*/
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
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;
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;
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";
}
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;
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;
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;
sfree(state);
}
+static char *game_text_format(game_state *state)
+{
+ return NULL;
+}
+
static game_ui *new_ui(game_state *state)
{
return NULL;
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;
* 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;
}
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;
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);
#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,