#include "puzzles.h"
#include "tree234.h"
-#define MATMUL(xr,yr,m,x,y) do { \
- float rx, ry, xx = (x), yy = (y), *mat = (m); \
- rx = mat[0] * xx + mat[2] * yy; \
- ry = mat[1] * xx + mat[3] * yy; \
- (xr) = rx; (yr) = ry; \
-} while (0)
+/*
+ * The standard user interface for Net simply has left- and
+ * right-button mouse clicks in a square rotate it one way or the
+ * other. We also provide, by #ifdef, a separate interface based on
+ * rotational dragging motions. I initially developed this for the
+ * Mac on the basis that it might work better than the click
+ * interface with only one mouse button available, but in fact
+ * found it to be quite strange and unintuitive. Apparently it
+ * works better on stylus-driven platforms such as Palm and
+ * PocketPC, though, so we enable it by default there.
+ */
+#ifdef STYLUS_BASED
+#define USE_DRAGGING
+#endif
/* Direction and other bitfields */
#define R 0x01
#define D 0x08
#define LOCKED 0x10
#define ACTIVE 0x20
+#define RLOOP (R << 6)
+#define ULOOP (U << 6)
+#define LLOOP (L << 6)
+#define DLOOP (D << 6)
+#define LOOP(dir) ((dir) << 6)
/* Rotations: Anticlockwise, Clockwise, Flip, general rotate */
#define A(x) ( (((x) & 0x07) << 1) | (((x) & 0x08) >> 3) )
#define PREFERRED_TILE_SIZE 32
#define TILE_SIZE (ds->tilesize)
-#define TILE_BORDER 1
+#define LINE_THICK ((TILE_SIZE+47)/48)
+#ifdef SMALL_SCREEN
+#define WINDOW_OFFSET 4
+#else
#define WINDOW_OFFSET 16
+#endif
#define ROTATE_TIME 0.13F
#define FLASH_FRAME 0.07F
-/* Transform physical coords to game coords using game_drawstate ds */
-#define GX(x) (((x) + ds->org_x) % ds->width)
-#define GY(y) (((y) + ds->org_y) % ds->height)
-/* ...and game coords to physical coords */
-#define RX(x) (((x) + ds->width - ds->org_x) % ds->width)
-#define RY(y) (((y) + ds->height - ds->org_y) % ds->height)
-
enum {
COL_BACKGROUND,
COL_LOCKED,
COL_ENDPOINT,
COL_POWERED,
COL_BARRIER,
+ COL_LOOP,
NCOLOURS
};
float barrier_probability;
};
+typedef struct game_immutable_state {
+ int refcount;
+ unsigned char *barriers;
+} game_immutable_state;
+
struct game_state {
int width, height, wrapping, completed;
int last_rotate_x, last_rotate_y, last_rotate_dir;
int used_solve;
unsigned char *tiles;
- unsigned char *barriers;
+ struct game_immutable_state *imm;
};
#define OFFSETWH(x2,y2,x1,y1,dir,width,height) \
#define index(state, a, x, y) ( a[(y) * (state)->width + (x)] )
#define tile(state, x, y) index(state, (state)->tiles, x, y)
-#define barrier(state, x, y) index(state, (state)->barriers, x, y)
+#define barrier(state, x, y) index(state, (state)->imm->barriers, x, y)
struct xyd {
int x, y, direction;
{7, 7, FALSE, TRUE, 0.0},
{9, 9, FALSE, TRUE, 0.0},
{11, 11, FALSE, TRUE, 0.0},
+#ifndef SMALL_SCREEN
{13, 11, FALSE, TRUE, 0.0},
+#endif
{5, 5, TRUE, TRUE, 0.0},
{7, 7, TRUE, TRUE, 0.0},
{9, 9, TRUE, TRUE, 0.0},
{11, 11, TRUE, TRUE, 0.0},
+#ifndef SMALL_SCREEN
{13, 11, TRUE, TRUE, 0.0},
+#endif
};
static int game_fetch_preset(int i, char **name, game_params **params)
sfree(params);
}
-static game_params *dup_params(game_params *params)
+static game_params *dup_params(const game_params *params)
{
game_params *ret = snew(game_params);
*ret = *params; /* structure copy */
ret->wrapping = TRUE;
} else if (*p == 'b') {
p++;
- ret->barrier_probability = atof(p);
+ ret->barrier_probability = (float)atof(p);
while (*p && (*p == '.' || isdigit((unsigned char)*p))) p++;
} else if (*p == 'a') {
p++;
}
}
-static char *encode_params(game_params *params, int full)
+static char *encode_params(const game_params *params, int full)
{
char ret[400];
int len;
return dupstr(ret);
}
-static config_item *game_configure(game_params *params)
+static config_item *game_configure(const game_params *params)
{
config_item *ret;
char buf[80];
return ret;
}
-static game_params *custom_params(config_item *cfg)
+static game_params *custom_params(const config_item *cfg)
{
game_params *ret = snew(game_params);
return ret;
}
-static char *validate_params(game_params *params, int full)
+static char *validate_params(const game_params *params, int full)
{
if (params->width <= 0 || params->height <= 0)
return "Width and height must both be greater than zero";
return ret;
}
+/*
+ * Return values: -1 means puzzle was proved inconsistent, 0 means we
+ * failed to narrow down to a unique solution, +1 means we solved it
+ * fully.
+ */
static int net_solver(int w, int h, unsigned char *tiles,
unsigned char *barriers, int wrapping)
{
#endif
}
- assert(j > 0); /* we can't lose _all_ possibilities! */
+ if (j == 0) {
+ /* If we've ruled out all possible orientations for a
+ * tile, then our puzzle has no solution at all. */
+ return -1;
+ }
if (j < i) {
done_something = TRUE;
/*
* Mark all completely determined tiles as locked.
*/
- j = TRUE;
+ j = +1;
for (i = 0; i < w*h; i++) {
if (tilestate[i * 4 + 1] == 255) {
assert(tilestate[i * 4 + 0] != 255);
tiles[i] = tilestate[i * 4] | LOCKED;
} else {
tiles[i] &= ~LOCKED;
- j = FALSE;
+ j = 0;
}
}
}
sfree(perim2);
- if (i == nperim)
+ if (i == nperim) {
+ sfree(perimeter);
return; /* nothing we can do! */
+ }
/*
* Now we've constructed a new link, we need to find the entire
sfree(perimeter);
}
-static char *new_game_desc(game_params *params, random_state *rs,
+static int *compute_loops_inner(int w, int h, int wrapping,
+ const unsigned char *tiles,
+ const unsigned char *barriers);
+
+static char *new_game_desc(const game_params *params, random_state *rs,
char **aux, int interactive)
{
tree234 *possibilities, *barriertree;
/*
* Run the solver to check unique solubility.
*/
- while (!net_solver(w, h, tiles, NULL, params->wrapping)) {
+ while (net_solver(w, h, tiles, NULL, params->wrapping) != 1) {
int n = 0;
/*
/*
* Now shuffle the grid.
+ *
+ * In order to avoid accidentally generating an already-solved
+ * grid, we will reshuffle as necessary to ensure that at least
+ * one edge has a mismatched connection.
+ *
+ * This can always be done, since validate_params() enforces a
+ * grid area of at least 2 and our generator never creates
+ * either type of rotationally invariant tile (cross and
+ * blank). Hence there must be at least one edge separating
+ * distinct tiles, and it must be possible to find orientations
+ * of those tiles such that one tile is trying to connect
+ * through that edge and the other is not.
+ *
+ * (We could be more subtle, and allow the shuffle to generate
+ * a grid in which all tiles match up locally and the only
+ * criterion preventing the grid from being already solved is
+ * connectedness. However, that would take more effort, and
+ * it's easier to simply make sure every grid is _obviously_
+ * not solved.)
+ *
+ * We also require that our shuffle produces no loops in the
+ * initial grid state, because it's a bit rude to light up a 'HEY,
+ * YOU DID SOMETHING WRONG!' indicator when the user hasn't even
+ * had a chance to do _anything_ yet. This also is possible just
+ * by retrying the whole shuffle on failure, because it's clear
+ * that at least one non-solved shuffle with no loops must exist.
+ * (Proof: take the _solved_ state of the puzzle, and rotate one
+ * endpoint.)
*/
- for (y = 0; y < h; y++) {
- for (x = 0; x < w; x++) {
- int orig = index(params, tiles, x, y);
- int rot = random_upto(rs, 4);
- index(params, tiles, x, y) = ROT(orig, rot);
- }
+ while (1) {
+ int mismatches, prev_loopsquares, this_loopsquares, i;
+ int *loops;
+
+ shuffle:
+ for (y = 0; y < h; y++) {
+ for (x = 0; x < w; x++) {
+ int orig = index(params, tiles, x, y);
+ int rot = random_upto(rs, 4);
+ index(params, tiles, x, y) = ROT(orig, rot);
+ }
+ }
+
+ /*
+ * Check for loops, and try to fix them by reshuffling just
+ * the squares involved.
+ */
+ prev_loopsquares = w*h+1;
+ while (1) {
+ loops = compute_loops_inner(w, h, params->wrapping, tiles, NULL);
+ this_loopsquares = 0;
+ for (i = 0; i < w*h; i++) {
+ if (loops[i]) {
+ int orig = tiles[i];
+ int rot = random_upto(rs, 4);
+ tiles[i] = ROT(orig, rot);
+ this_loopsquares++;
+ }
+ }
+ sfree(loops);
+ if (this_loopsquares > prev_loopsquares) {
+ /*
+ * We're increasing rather than reducing the number of
+ * loops. Give up and go back to the full shuffle.
+ */
+ goto shuffle;
+ }
+ if (this_loopsquares == 0)
+ break;
+ prev_loopsquares = this_loopsquares;
+ }
+
+ mismatches = 0;
+ /*
+ * I can't even be bothered to check for mismatches across
+ * a wrapping edge, so I'm just going to enforce that there
+ * must be a mismatch across a non-wrapping edge, which is
+ * still always possible.
+ */
+ for (y = 0; y < h; y++) for (x = 0; x < w; x++) {
+ if (x+1 < w && ((ROT(index(params, tiles, x, y), 2) ^
+ index(params, tiles, x+1, y)) & L))
+ mismatches++;
+ if (y+1 < h && ((ROT(index(params, tiles, x, y), 2) ^
+ index(params, tiles, x, y+1)) & U))
+ mismatches++;
+ }
+
+ if (mismatches == 0)
+ continue;
+
+ /* OK. */
+ break;
}
/*
return desc;
}
-static char *validate_desc(game_params *params, char *desc)
+static char *validate_desc(const game_params *params, const char *desc)
{
int w = params->width, h = params->height;
int i;
* Construct an initial game state, given a description and parameters.
*/
-static game_state *new_game(midend *me, game_params *params, char *desc)
+static game_state *new_game(midend *me, const game_params *params,
+ const char *desc)
{
game_state *state;
int w, h, x, y;
w = state->width = params->width;
h = state->height = params->height;
state->wrapping = params->wrapping;
+ state->imm = snew(game_immutable_state);
+ state->imm->refcount = 1;
state->last_rotate_dir = state->last_rotate_x = state->last_rotate_y = 0;
state->completed = state->used_solve = FALSE;
state->tiles = snewn(state->width * state->height, unsigned char);
memset(state->tiles, 0, state->width * state->height);
- state->barriers = snewn(state->width * state->height, unsigned char);
- memset(state->barriers, 0, state->width * state->height);
+ state->imm->barriers = snewn(state->width * state->height, unsigned char);
+ memset(state->imm->barriers, 0, state->width * state->height);
/*
* Parse the game description into the grid.
if (!(barrier(state, x, 0) & U) ||
!(barrier(state, x, state->height-1) & D))
state->wrapping = TRUE;
- for (y = 0; y < state->width; y++)
+ for (y = 0; y < state->height; y++)
if (!(barrier(state, 0, y) & L) ||
!(barrier(state, state->width-1, y) & R))
state->wrapping = TRUE;
return state;
}
-static game_state *dup_game(game_state *state)
+static game_state *dup_game(const game_state *state)
{
game_state *ret;
ret = snew(game_state);
+ ret->imm = state->imm;
+ ret->imm->refcount++;
ret->width = state->width;
ret->height = state->height;
ret->wrapping = state->wrapping;
ret->last_rotate_y = state->last_rotate_y;
ret->tiles = snewn(state->width * state->height, unsigned char);
memcpy(ret->tiles, state->tiles, state->width * state->height);
- ret->barriers = snewn(state->width * state->height, unsigned char);
- memcpy(ret->barriers, state->barriers, state->width * state->height);
return ret;
}
static void free_game(game_state *state)
{
+ if (--state->imm->refcount == 0) {
+ sfree(state->imm->barriers);
+ sfree(state->imm);
+ }
sfree(state->tiles);
- sfree(state->barriers);
sfree(state);
}
-static char *solve_game(game_state *state, game_state *currstate,
- char *aux, char **error)
+static char *solve_game(const game_state *state, const game_state *currstate,
+ const char *aux, char **error)
{
unsigned char *tiles;
char *ret;
* Run the internal solver on the provided grid. This might
* not yield a complete solution.
*/
+ int solver_result;
+
memcpy(tiles, state->tiles, state->width * state->height);
- net_solver(state->width, state->height, tiles,
- state->barriers, state->wrapping);
+ solver_result = net_solver(state->width, state->height, tiles,
+ state->imm->barriers, state->wrapping);
+
+ if (solver_result < 0) {
+ *error = "No solution exists for this puzzle";
+ sfree(tiles);
+ return NULL;
+ }
} else {
for (i = 0; i < state->width * state->height; i++) {
int c = aux[i];
return ret;
}
-static char *game_text_format(game_state *state)
+static int game_can_format_as_text_now(const game_params *params)
+{
+ return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
{
return NULL;
}
* completed - just call this function and see whether every square
* is marked active.
*/
-static unsigned char *compute_active(game_state *state, int cx, int cy)
+static unsigned char *compute_active(const game_state *state, int cx, int cy)
{
unsigned char *active;
tree234 *todo;
return active;
}
+struct net_neighbour_ctx {
+ int w, h;
+ const unsigned char *tiles, *barriers;
+ int i, n, neighbours[4];
+};
+static int net_neighbour(int vertex, void *vctx)
+{
+ struct net_neighbour_ctx *ctx = (struct net_neighbour_ctx *)vctx;
+
+ if (vertex >= 0) {
+ int x = vertex % ctx->w, y = vertex / ctx->w;
+ int tile, dir, x1, y1, v1;
+
+ ctx->i = ctx->n = 0;
+
+ tile = ctx->tiles[vertex];
+ if (ctx->barriers)
+ tile &= ~ctx->barriers[vertex];
+
+ for (dir = 1; dir < 0x10; dir <<= 1) {
+ if (!(tile & dir))
+ continue;
+ OFFSETWH(x1, y1, x, y, dir, ctx->w, ctx->h);
+ v1 = y1 * ctx->w + x1;
+ if (ctx->tiles[v1] & F(dir))
+ ctx->neighbours[ctx->n++] = v1;
+ }
+ }
+
+ if (ctx->i < ctx->n)
+ return ctx->neighbours[ctx->i++];
+ else
+ return -1;
+}
+
+static int *compute_loops_inner(int w, int h, int wrapping,
+ const unsigned char *tiles,
+ const unsigned char *barriers)
+{
+ struct net_neighbour_ctx ctx;
+ struct findloopstate *fls;
+ int *loops;
+ int x, y;
+
+ fls = findloop_new_state(w*h);
+ ctx.w = w;
+ ctx.h = h;
+ ctx.tiles = tiles;
+ ctx.barriers = barriers;
+ findloop_run(fls, w*h, net_neighbour, &ctx);
+
+ loops = snewn(w*h, int);
+
+ for (y = 0; y < h; y++) {
+ for (x = 0; x < w; x++) {
+ int x1, y1, dir;
+ int flags = 0;
+
+ for (dir = 1; dir < 0x10; dir <<= 1) {
+ if ((tiles[y*w+x] & dir) &&
+ !(barriers && (barriers[y*w+x] & dir))) {
+ OFFSETWH(x1, y1, x, y, dir, w, h);
+ if ((tiles[y1*w+x1] & F(dir)) &&
+ findloop_is_loop_edge(fls, y*w+x, y1*w+x1))
+ flags |= LOOP(dir);
+ }
+ }
+ loops[y*w+x] = flags;
+ }
+ }
+
+ findloop_free_state(fls);
+ return loops;
+}
+
+static int *compute_loops(const game_state *state)
+{
+ return compute_loops_inner(state->width, state->height, state->wrapping,
+ state->tiles, state->imm->barriers);
+}
+
struct game_ui {
int org_x, org_y; /* origin */
int cx, cy; /* source tile (game coordinates) */
int cur_x, cur_y;
int cur_visible;
random_state *rs; /* used for jumbling */
+#ifdef USE_DRAGGING
+ int dragtilex, dragtiley, dragstartx, dragstarty, dragged;
+#endif
};
-static game_ui *new_ui(game_state *state)
+static game_ui *new_ui(const game_state *state)
{
void *seed;
int seedsize;
sfree(ui);
}
-static char *encode_ui(game_ui *ui)
+static char *encode_ui(const game_ui *ui)
{
char buf[120];
/*
return dupstr(buf);
}
-static void decode_ui(game_ui *ui, char *encoding)
+static void decode_ui(game_ui *ui, const char *encoding)
{
sscanf(encoding, "O%d,%d;C%d,%d",
&ui->org_x, &ui->org_y, &ui->cx, &ui->cy);
}
-static void game_changed_state(game_ui *ui, game_state *oldstate,
- game_state *newstate)
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+ const game_state *newstate)
{
}
struct game_drawstate {
int started;
int width, height;
- int org_x, org_y;
int tilesize;
- unsigned char *visible;
+ unsigned long *visible, *to_draw;
};
/* ----------------------------------------------------------------------
* Process a move.
*/
-static char *interpret_move(game_state *state, game_ui *ui,
- game_drawstate *ds, int x, int y, int button)
+static char *interpret_move(const game_state *state, game_ui *ui,
+ const game_drawstate *ds,
+ int x, int y, int button)
{
char *nullret;
int tx = -1, ty = -1, dir = 0;
if (button == LEFT_BUTTON ||
button == MIDDLE_BUTTON ||
+#ifdef USE_DRAGGING
+ button == LEFT_DRAG ||
+ button == LEFT_RELEASE ||
+ button == RIGHT_DRAG ||
+ button == RIGHT_RELEASE ||
+#endif
button == RIGHT_BUTTON) {
if (ui->cur_visible) {
/*
* The button must have been clicked on a valid tile.
*/
- x -= WINDOW_OFFSET + TILE_BORDER;
- y -= WINDOW_OFFSET + TILE_BORDER;
+ x -= WINDOW_OFFSET + LINE_THICK;
+ y -= WINDOW_OFFSET + LINE_THICK;
if (x < 0 || y < 0)
return nullret;
tx = x / TILE_SIZE;
/* Transform from physical to game coords */
tx = (tx + ui->org_x) % state->width;
ty = (ty + ui->org_y) % state->height;
- if (x % TILE_SIZE >= TILE_SIZE - TILE_BORDER ||
- y % TILE_SIZE >= TILE_SIZE - TILE_BORDER)
+ if (x % TILE_SIZE >= TILE_SIZE - LINE_THICK ||
+ y % TILE_SIZE >= TILE_SIZE - LINE_THICK)
return nullret;
- action = button == LEFT_BUTTON ? ROTATE_LEFT :
- button == RIGHT_BUTTON ? ROTATE_RIGHT : TOGGLE_LOCK;
- } else if (button == CURSOR_UP || button == CURSOR_DOWN ||
- button == CURSOR_RIGHT || button == CURSOR_LEFT) {
+#ifdef USE_DRAGGING
+
+ if (button == MIDDLE_BUTTON
+#ifdef STYLUS_BASED
+ || button == RIGHT_BUTTON /* with a stylus, `right-click' locks */
+#endif
+ ) {
+ /*
+ * Middle button never drags: it only toggles the lock.
+ */
+ action = TOGGLE_LOCK;
+ } else if (button == LEFT_BUTTON
+#ifndef STYLUS_BASED
+ || button == RIGHT_BUTTON /* (see above) */
+#endif
+ ) {
+ /*
+ * Otherwise, we note down the start point for a drag.
+ */
+ ui->dragtilex = tx;
+ ui->dragtiley = ty;
+ ui->dragstartx = x % TILE_SIZE;
+ ui->dragstarty = y % TILE_SIZE;
+ ui->dragged = FALSE;
+ return nullret; /* no actual action */
+ } else if (button == LEFT_DRAG
+#ifndef STYLUS_BASED
+ || button == RIGHT_DRAG
+#endif
+ ) {
+ /*
+ * Find the new drag point and see if it necessitates a
+ * rotation.
+ */
+ int x0,y0, xA,yA, xC,yC, xF,yF;
+ int mx, my;
+ int d0, dA, dC, dF, dmin;
+
+ tx = ui->dragtilex;
+ ty = ui->dragtiley;
+
+ mx = x - (ui->dragtilex * TILE_SIZE);
+ my = y - (ui->dragtiley * TILE_SIZE);
+
+ x0 = ui->dragstartx;
+ y0 = ui->dragstarty;
+ xA = ui->dragstarty;
+ yA = TILE_SIZE-1 - ui->dragstartx;
+ xF = TILE_SIZE-1 - ui->dragstartx;
+ yF = TILE_SIZE-1 - ui->dragstarty;
+ xC = TILE_SIZE-1 - ui->dragstarty;
+ yC = ui->dragstartx;
+
+ d0 = (mx-x0)*(mx-x0) + (my-y0)*(my-y0);
+ dA = (mx-xA)*(mx-xA) + (my-yA)*(my-yA);
+ dF = (mx-xF)*(mx-xF) + (my-yF)*(my-yF);
+ dC = (mx-xC)*(mx-xC) + (my-yC)*(my-yC);
+
+ dmin = min(min(d0,dA),min(dF,dC));
+
+ if (d0 == dmin) {
+ return nullret;
+ } else if (dF == dmin) {
+ action = ROTATE_180;
+ ui->dragstartx = xF;
+ ui->dragstarty = yF;
+ ui->dragged = TRUE;
+ } else if (dA == dmin) {
+ action = ROTATE_LEFT;
+ ui->dragstartx = xA;
+ ui->dragstarty = yA;
+ ui->dragged = TRUE;
+ } else /* dC == dmin */ {
+ action = ROTATE_RIGHT;
+ ui->dragstartx = xC;
+ ui->dragstarty = yC;
+ ui->dragged = TRUE;
+ }
+ } else if (button == LEFT_RELEASE
+#ifndef STYLUS_BASED
+ || button == RIGHT_RELEASE
+#endif
+ ) {
+ if (!ui->dragged) {
+ /*
+ * There was a click but no perceptible drag:
+ * revert to single-click behaviour.
+ */
+ tx = ui->dragtilex;
+ ty = ui->dragtiley;
+
+ if (button == LEFT_RELEASE)
+ action = ROTATE_LEFT;
+ else
+ action = ROTATE_RIGHT;
+ } else
+ return nullret; /* no action */
+ }
+
+#else /* USE_DRAGGING */
+
+ action = (button == LEFT_BUTTON ? ROTATE_LEFT :
+ button == RIGHT_BUTTON ? ROTATE_RIGHT : TOGGLE_LOCK);
+
+#endif /* USE_DRAGGING */
+
+ } else if (IS_CURSOR_MOVE(button)) {
switch (button) {
case CURSOR_UP: dir = U; break;
case CURSOR_DOWN: dir = D; break;
} else if (button == 'a' || button == 's' || button == 'd' ||
button == 'A' || button == 'S' || button == 'D' ||
button == 'f' || button == 'F' ||
- button == CURSOR_SELECT) {
+ IS_CURSOR_SELECT(button)) {
tx = ui->cur_x;
ty = ui->cur_y;
if (button == 'a' || button == 'A' || button == CURSOR_SELECT)
action = ROTATE_LEFT;
- else if (button == 's' || button == 'S')
+ else if (button == 's' || button == 'S' || button == CURSOR_SELECT2)
action = TOGGLE_LOCK;
else if (button == 'd' || button == 'D')
action = ROTATE_RIGHT;
}
}
-static game_state *execute_move(game_state *from, char *move)
+static game_state *execute_move(const game_state *from, const char *move)
{
game_state *ret;
- int tx, ty, n, noanim, orig;
+ int tx = -1, ty = -1, n, noanim, orig;
ret = dup_game(from);
}
}
if (!noanim) {
+ if (tx == -1 || ty == -1) { free_game(ret); return NULL; }
ret->last_rotate_x = tx;
ret->last_rotate_y = ty;
}
/*
* Check whether the game has been completed.
*
- * For this purpose it doesn't matter where the source square
- * is, because we can start from anywhere and correctly
- * determine whether the game is completed.
+ * For this purpose it doesn't matter where the source square is,
+ * because we can start from anywhere (or, at least, any square
+ * that's non-empty!), and correctly determine whether the game is
+ * completed.
*/
{
- unsigned char *active = compute_active(ret, 0, 0);
- int x1, y1;
+ unsigned char *active;
+ int pos;
int complete = TRUE;
- for (x1 = 0; x1 < ret->width; x1++)
- for (y1 = 0; y1 < ret->height; y1++)
- if ((tile(ret, x1, y1) & 0xF) && !index(ret, active, x1, y1)) {
+ for (pos = 0; pos < ret->width * ret->height; pos++)
+ if (ret->tiles[pos] & 0xF)
+ break;
+
+ if (pos < ret->width * ret->height) {
+ active = compute_active(ret, pos % ret->width, pos / ret->width);
+
+ for (pos = 0; pos < ret->width * ret->height; pos++)
+ if ((ret->tiles[pos] & 0xF) && !active[pos]) {
complete = FALSE;
- goto break_label; /* break out of two loops at once */
- }
- break_label:
+ break;
+ }
- sfree(active);
+ sfree(active);
+ }
if (complete)
ret->completed = TRUE;
* Routines for drawing the game position on the screen.
*/
-static game_drawstate *game_new_drawstate(drawing *dr, game_state *state)
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
{
game_drawstate *ds = snew(game_drawstate);
+ int i, ncells;
ds->started = FALSE;
ds->width = state->width;
ds->height = state->height;
- ds->org_x = ds->org_y = -1;
- ds->visible = snewn(state->width * state->height, unsigned char);
+ ncells = (state->width+2) * (state->height+2);
+ ds->visible = snewn(ncells, unsigned long);
+ ds->to_draw = snewn(ncells, unsigned long);
ds->tilesize = 0; /* undecided yet */
- memset(ds->visible, 0xFF, state->width * state->height);
+ for (i = 0; i < ncells; i++)
+ ds->visible[i] = -1;
return ds;
}
+#define dsindex(ds, field, x, y) ((ds)->field[((y)+1)*((ds)->width+2)+((x)+1)])
+#define visible(ds, x, y) dsindex(ds, visible, x, y)
+#define todraw(ds, x, y) dsindex(ds, to_draw, x, y)
+
static void game_free_drawstate(drawing *dr, game_drawstate *ds)
{
sfree(ds->visible);
sfree(ds);
}
-static void game_compute_size(game_params *params, int tilesize,
- int *x, int *y)
+static void game_compute_size(const game_params *params, int tilesize,
+ int *x, int *y)
{
- *x = WINDOW_OFFSET * 2 + tilesize * params->width + TILE_BORDER;
- *y = WINDOW_OFFSET * 2 + tilesize * params->height + TILE_BORDER;
+ /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+ struct { int tilesize; } ads, *ds = &ads;
+ ads.tilesize = tilesize;
+
+ *x = WINDOW_OFFSET * 2 + TILE_SIZE * params->width + LINE_THICK;
+ *y = WINDOW_OFFSET * 2 + TILE_SIZE * params->height + LINE_THICK;
}
static void game_set_size(drawing *dr, game_drawstate *ds,
- game_params *params, int tilesize)
+ const game_params *params, int tilesize)
{
ds->tilesize = tilesize;
}
ret[COL_BARRIER * 3 + 1] = 0.0F;
ret[COL_BARRIER * 3 + 2] = 0.0F;
+ /*
+ * Highlighted loops are red as well.
+ */
+ ret[COL_LOOP * 3 + 0] = 1.0F;
+ ret[COL_LOOP * 3 + 1] = 0.0F;
+ ret[COL_LOOP * 3 + 2] = 0.0F;
+
/*
* Unpowered endpoints are blue.
*/
return ret;
}
-static void draw_thick_line(drawing *dr, int x1, int y1, int x2, int y2,
- int colour)
+static void rotated_coords(float *ox, float *oy, const float matrix[4],
+ float cx, float cy, float ix, float iy)
{
- draw_line(dr, x1-1, y1, x2-1, y2, COL_WIRE);
- draw_line(dr, x1+1, y1, x2+1, y2, COL_WIRE);
- draw_line(dr, x1, y1-1, x2, y2-1, COL_WIRE);
- draw_line(dr, x1, y1+1, x2, y2+1, COL_WIRE);
- draw_line(dr, x1, y1, x2, y2, colour);
+ *ox = matrix[0] * ix + matrix[2] * iy + cx;
+ *oy = matrix[1] * ix + matrix[3] * iy + cy;
}
-static void draw_rect_coords(drawing *dr, int x1, int y1, int x2, int y2,
- int colour)
+/* Flags describing the visible features of a tile. */
+#define TILE_BARRIER_SHIFT 0 /* 4 bits: R U L D */
+#define TILE_BARRIER_CORNER_SHIFT 4 /* 4 bits: RU UL LD DR */
+#define TILE_KEYBOARD_CURSOR (1<<8) /* 1 bit if cursor is here */
+#define TILE_WIRE_SHIFT 9 /* 8 bits: RR UU LL DD
+ * Each pair: 0=no wire, 1=unpowered,
+ * 2=powered, 3=loop err highlight */
+#define TILE_ENDPOINT_SHIFT 17 /* 2 bits: 0=no endpoint, 1=unpowered,
+ * 2=powered, 3=power-source square */
+#define TILE_WIRE_ON_EDGE_SHIFT 19 /* 8 bits: RR UU LL DD,
+ * same encoding as TILE_WIRE_SHIFT */
+#define TILE_ROTATING (1UL<<27) /* 1 bit if tile is rotating */
+#define TILE_LOCKED (1UL<<28) /* 1 bit if tile is locked */
+
+static void draw_wires(drawing *dr, int cx, int cy, int radius,
+ unsigned long tile, int bitmap,
+ int colour, int halfwidth, const float matrix[4])
{
- int mx = (x1 < x2 ? x1 : x2);
- int my = (y1 < y2 ? y1 : y2);
- int dx = (x2 + x1 - 2*mx + 1);
- int dy = (y2 + y1 - 2*my + 1);
-
- draw_rect(dr, mx, my, dx, dy, colour);
-}
-
-/*
- * draw_barrier_corner() and draw_barrier() are passed physical coords
- */
-static void draw_barrier_corner(drawing *dr, game_drawstate *ds,
- int x, int y, int dx, int dy, int phase)
-{
- int bx = WINDOW_OFFSET + TILE_SIZE * x;
- int by = WINDOW_OFFSET + TILE_SIZE * y;
- int x1, y1;
-
- x1 = (dx > 0 ? TILE_SIZE+TILE_BORDER-1 : 0);
- y1 = (dy > 0 ? TILE_SIZE+TILE_BORDER-1 : 0);
-
- if (phase == 0) {
- draw_rect_coords(dr, bx+x1+dx, by+y1,
- bx+x1-TILE_BORDER*dx, by+y1-(TILE_BORDER-1)*dy,
- COL_WIRE);
- draw_rect_coords(dr, bx+x1, by+y1+dy,
- bx+x1-(TILE_BORDER-1)*dx, by+y1-TILE_BORDER*dy,
- COL_WIRE);
- } else {
- draw_rect_coords(dr, bx+x1, by+y1,
- bx+x1-(TILE_BORDER-1)*dx, by+y1-(TILE_BORDER-1)*dy,
- COL_BARRIER);
+ float fpoints[12*2];
+ int points[12*2];
+ int npoints, d, dsh, i;
+ int any_wire_this_colour = FALSE;
+ float xf, yf;
+
+ npoints = 0;
+ for (d = 1, dsh = 0; d < 16; d *= 2, dsh++) {
+ int wiretype = (tile >> (TILE_WIRE_SHIFT + 2*dsh)) & 3;
+
+ fpoints[2*npoints+0] = halfwidth * (X(d) + X(C(d)));
+ fpoints[2*npoints+1] = halfwidth * (Y(d) + Y(C(d)));
+ npoints++;
+
+ if (bitmap & (1 << wiretype)) {
+ fpoints[2*npoints+0] = radius * X(d) + halfwidth * X(C(d));
+ fpoints[2*npoints+1] = radius * Y(d) + halfwidth * Y(C(d));
+ npoints++;
+ fpoints[2*npoints+0] = radius * X(d) + halfwidth * X(A(d));
+ fpoints[2*npoints+1] = radius * Y(d) + halfwidth * Y(A(d));
+ npoints++;
+
+ any_wire_this_colour = TRUE;
+ }
}
-}
-
-static void draw_barrier(drawing *dr, game_drawstate *ds,
- int x, int y, int dir, int phase)
-{
- int bx = WINDOW_OFFSET + TILE_SIZE * x;
- int by = WINDOW_OFFSET + TILE_SIZE * y;
- int x1, y1, w, h;
- x1 = (X(dir) > 0 ? TILE_SIZE : X(dir) == 0 ? TILE_BORDER : 0);
- y1 = (Y(dir) > 0 ? TILE_SIZE : Y(dir) == 0 ? TILE_BORDER : 0);
- w = (X(dir) ? TILE_BORDER : TILE_SIZE - TILE_BORDER);
- h = (Y(dir) ? TILE_BORDER : TILE_SIZE - TILE_BORDER);
+ if (!any_wire_this_colour)
+ return;
- if (phase == 0) {
- draw_rect(dr, bx+x1-X(dir), by+y1-Y(dir), w, h, COL_WIRE);
- } else {
- draw_rect(dr, bx+x1, by+y1, w, h, COL_BARRIER);
+ for (i = 0; i < npoints; i++) {
+ rotated_coords(&xf, &yf, matrix, cx, cy, fpoints[2*i], fpoints[2*i+1]);
+ points[2*i] = 0.5 + xf;
+ points[2*i+1] = 0.5 + yf;
}
+
+ draw_polygon(dr, points, npoints, colour, colour);
}
-/*
- * draw_tile() is passed physical coordinates
- */
-static void draw_tile(drawing *dr, game_state *state, game_drawstate *ds,
- int x, int y, int tile, int src, float angle, int cursor)
+static void draw_tile(drawing *dr, game_drawstate *ds, int x, int y,
+ unsigned long tile, float angle)
{
- int bx = WINDOW_OFFSET + TILE_SIZE * x;
- int by = WINDOW_OFFSET + TILE_SIZE * y;
+ int tx, ty;
+ int clipx, clipy, clipX, clipY, clipw, cliph;
+ int border_br = LINE_THICK/2, border_tl = LINE_THICK - border_br;
+ int barrier_outline_thick = (LINE_THICK+1)/2;
+ int bg, d, dsh, pass;
+ int cx, cy, radius;
float matrix[4];
- float cx, cy, ex, ey, tx, ty;
- int dir, col, phase;
+
+ tx = WINDOW_OFFSET + TILE_SIZE * x + border_br;
+ ty = WINDOW_OFFSET + TILE_SIZE * y + border_br;
/*
- * When we draw a single tile, we must draw everything up to
- * and including the borders around the tile. This means that
- * if the neighbouring tiles have connections to those borders,
- * we must draw those connections on the borders themselves.
+ * Clip to the tile boundary, with adjustments if we're drawing
+ * just outside the grid.
*/
-
- clip(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
+ clipx = tx; clipX = tx + TILE_SIZE;
+ clipy = ty; clipY = ty + TILE_SIZE;
+ if (x == -1) {
+ clipx = clipX - border_br - barrier_outline_thick;
+ } else if (x == ds->width) {
+ clipX = clipx + border_tl + barrier_outline_thick;
+ }
+ if (y == -1) {
+ clipy = clipY - border_br - barrier_outline_thick;
+ } else if (y == ds->height) {
+ clipY = clipy + border_tl + barrier_outline_thick;
+ }
+ clipw = clipX - clipx;
+ cliph = clipY - clipy;
+ clip(dr, clipx, clipy, clipw, cliph);
/*
- * So. First blank the tile out completely: draw a big
- * rectangle in border colour, and a smaller rectangle in
- * background colour to fill it in.
+ * Clear the clip region.
*/
- draw_rect(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER,
- COL_BORDER);
- draw_rect(dr, bx+TILE_BORDER, by+TILE_BORDER,
- TILE_SIZE-TILE_BORDER, TILE_SIZE-TILE_BORDER,
- tile & LOCKED ? COL_LOCKED : COL_BACKGROUND);
+ bg = (tile & TILE_LOCKED) ? COL_LOCKED : COL_BACKGROUND;
+ draw_rect(dr, clipx, clipy, clipw, cliph, bg);
/*
- * Draw an inset outline rectangle as a cursor, in whichever of
- * COL_LOCKED and COL_BACKGROUND we aren't currently drawing
- * in.
+ * Draw the grid lines.
*/
- if (cursor) {
- draw_line(dr, bx+TILE_SIZE/8, by+TILE_SIZE/8,
- bx+TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8,
- tile & LOCKED ? COL_BACKGROUND : COL_LOCKED);
- draw_line(dr, bx+TILE_SIZE/8, by+TILE_SIZE/8,
- bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE/8,
- tile & LOCKED ? COL_BACKGROUND : COL_LOCKED);
- draw_line(dr, bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE/8,
- bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8,
- tile & LOCKED ? COL_BACKGROUND : COL_LOCKED);
- draw_line(dr, bx+TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8,
- bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8,
- tile & LOCKED ? COL_BACKGROUND : COL_LOCKED);
+ {
+ int gridl = (x == -1 ? tx+TILE_SIZE-border_br : tx);
+ int gridr = (x == ds->width ? tx+border_tl : tx+TILE_SIZE);
+ int gridu = (y == -1 ? ty+TILE_SIZE-border_br : ty);
+ int gridd = (y == ds->height ? ty+border_tl : ty+TILE_SIZE);
+ if (x >= 0)
+ draw_rect(dr, tx, gridu, border_tl, gridd-gridu, COL_BORDER);
+ if (y >= 0)
+ draw_rect(dr, gridl, ty, gridr-gridl, border_tl, COL_BORDER);
+ if (x < ds->width)
+ draw_rect(dr, tx+TILE_SIZE-border_br, gridu,
+ border_br, gridd-gridu, COL_BORDER);
+ if (y < ds->height)
+ draw_rect(dr, gridl, ty+TILE_SIZE-border_br,
+ gridr-gridl, border_br, COL_BORDER);
}
/*
- * Set up the rotation matrix.
+ * Draw the keyboard cursor.
*/
- matrix[0] = (float)cos(angle * PI / 180.0);
- matrix[1] = (float)-sin(angle * PI / 180.0);
- matrix[2] = (float)sin(angle * PI / 180.0);
- matrix[3] = (float)cos(angle * PI / 180.0);
+ if (tile & TILE_KEYBOARD_CURSOR) {
+ int cursorcol = (tile & TILE_LOCKED) ? COL_BACKGROUND : COL_LOCKED;
+ int inset_outer = TILE_SIZE/8, inset_inner = inset_outer + LINE_THICK;
+ draw_rect(dr, tx + inset_outer, ty + inset_outer,
+ TILE_SIZE - 2*inset_outer, TILE_SIZE - 2*inset_outer,
+ cursorcol);
+ draw_rect(dr, tx + inset_inner, ty + inset_inner,
+ TILE_SIZE - 2*inset_inner, TILE_SIZE - 2*inset_inner,
+ bg);
+ }
+
+ radius = (TILE_SIZE+1)/2;
+ cx = tx + radius;
+ cy = ty + radius;
+ radius++;
/*
- * Draw the wires.
+ * Draw protrusions into this cell's edges of wires in
+ * neighbouring cells, as given by the TILE_WIRE_ON_EDGE_SHIFT
+ * flags. We only draw each of these if there _isn't_ a wire of
+ * our own that's going to overlap it, which means either the
+ * corresponding TILE_WIRE_SHIFT flag is zero, or else the
+ * TILE_ROTATING flag is set (so that our main wire won't be drawn
+ * in quite that place anyway).
*/
- cx = cy = TILE_BORDER + (TILE_SIZE-TILE_BORDER) / 2.0F - 0.5F;
- col = (tile & ACTIVE ? COL_POWERED : COL_WIRE);
- for (dir = 1; dir < 0x10; dir <<= 1) {
- if (tile & dir) {
- ex = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * X(dir);
- ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir);
- MATMUL(tx, ty, matrix, ex, ey);
- draw_thick_line(dr, bx+(int)cx, by+(int)cy,
- bx+(int)(cx+tx), by+(int)(cy+ty),
- COL_WIRE);
- }
- }
- for (dir = 1; dir < 0x10; dir <<= 1) {
- if (tile & dir) {
- ex = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * X(dir);
- ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir);
- MATMUL(tx, ty, matrix, ex, ey);
- draw_line(dr, bx+(int)cx, by+(int)cy,
- bx+(int)(cx+tx), by+(int)(cy+ty), col);
+ for (d = 1, dsh = 0; d < 16; d *= 2, dsh++) {
+ int edgetype = ((tile >> (TILE_WIRE_ON_EDGE_SHIFT + 2*dsh)) & 3);
+ if (edgetype == 0)
+ continue; /* there isn't a wire on the edge */
+ if (!(tile & TILE_ROTATING) &&
+ ((tile >> (TILE_WIRE_SHIFT + 2*dsh)) & 3) != 0)
+ continue; /* wire on edge would be overdrawn anyway */
+
+ for (pass = 0; pass < 2; pass++) {
+ int x, y, w, h;
+ int col = (pass == 0 || edgetype == 1 ? COL_WIRE :
+ edgetype == 2 ? COL_POWERED : COL_LOOP);
+ int halfwidth = pass == 0 ? 2*LINE_THICK-1 : LINE_THICK-1;
+
+ if (X(d) < 0) {
+ x = tx;
+ w = border_tl;
+ } else if (X(d) > 0) {
+ x = tx + TILE_SIZE - border_br;
+ w = border_br;
+ } else {
+ x = cx - halfwidth;
+ w = 2 * halfwidth + 1;
+ }
+
+ if (Y(d) < 0) {
+ y = ty;
+ h = border_tl;
+ } else if (Y(d) > 0) {
+ y = ty + TILE_SIZE - border_br;
+ h = border_br;
+ } else {
+ y = cy - halfwidth;
+ h = 2 * halfwidth + 1;
+ }
+
+ draw_rect(dr, x, y, w, h, col);
}
}
/*
- * Draw the box in the middle. We do this in blue if the tile
- * is an unpowered endpoint, in cyan if the tile is a powered
- * endpoint, in black if the tile is the centrepiece, and
- * otherwise not at all.
+ * Set up the rotation matrix for the main cell contents, i.e.
+ * everything that is centred in the grid square and optionally
+ * rotated by an arbitrary angle about that centre point.
*/
- col = -1;
- if (src)
- col = COL_WIRE;
- else if (COUNT(tile) == 1) {
- col = (tile & ACTIVE ? COL_POWERED : COL_ENDPOINT);
- }
- if (col >= 0) {
- int i, points[8];
-
- points[0] = +1; points[1] = +1;
- points[2] = +1; points[3] = -1;
- points[4] = -1; points[5] = -1;
- points[6] = -1; points[7] = +1;
-
- for (i = 0; i < 8; i += 2) {
- ex = (TILE_SIZE * 0.24F) * points[i];
- ey = (TILE_SIZE * 0.24F) * points[i+1];
- MATMUL(tx, ty, matrix, ex, ey);
- points[i] = bx+(int)(cx+tx);
- points[i+1] = by+(int)(cy+ty);
- }
-
- draw_polygon(dr, points, 4, col, COL_WIRE);
+ if (tile & TILE_ROTATING) {
+ matrix[0] = (float)cos(angle * PI / 180.0);
+ matrix[2] = (float)sin(angle * PI / 180.0);
+ } else {
+ matrix[0] = 1.0F;
+ matrix[2] = 0.0F;
}
+ matrix[3] = matrix[0];
+ matrix[1] = -matrix[2];
/*
- * Draw the points on the border if other tiles are connected
- * to us.
+ * Draw the wires.
*/
- for (dir = 1; dir < 0x10; dir <<= 1) {
- int dx, dy, px, py, lx, ly, vx, vy, ox, oy;
-
- dx = X(dir);
- dy = Y(dir);
-
- ox = x + dx;
- oy = y + dy;
-
- if (ox < 0 || ox >= state->width || oy < 0 || oy >= state->height)
- continue;
-
- if (!(tile(state, GX(ox), GY(oy)) & F(dir)))
- continue;
+ draw_wires(dr, cx, cy, radius, tile,
+ 0xE, COL_WIRE, 2*LINE_THICK-1, matrix);
+ draw_wires(dr, cx, cy, radius, tile,
+ 0x4, COL_POWERED, LINE_THICK-1, matrix);
+ draw_wires(dr, cx, cy, radius, tile,
+ 0x8, COL_LOOP, LINE_THICK-1, matrix);
- px = bx + (int)(dx>0 ? TILE_SIZE + TILE_BORDER - 1 : dx<0 ? 0 : cx);
- py = by + (int)(dy>0 ? TILE_SIZE + TILE_BORDER - 1 : dy<0 ? 0 : cy);
- lx = dx * (TILE_BORDER-1);
- ly = dy * (TILE_BORDER-1);
- vx = (dy ? 1 : 0);
- vy = (dx ? 1 : 0);
+ /*
+ * Draw the central box.
+ */
+ for (pass = 0; pass < 2; pass++) {
+ int endtype = (tile >> TILE_ENDPOINT_SHIFT) & 3;
+ if (endtype) {
+ int i, points[8], col;
+ float boxr = TILE_SIZE * 0.24F + (pass == 0 ? LINE_THICK-1 : 0);
+
+ col = (pass == 0 || endtype == 3 ? COL_WIRE :
+ endtype == 2 ? COL_POWERED : COL_ENDPOINT);
+
+ points[0] = +1; points[1] = +1;
+ points[2] = +1; points[3] = -1;
+ points[4] = -1; points[5] = -1;
+ points[6] = -1; points[7] = +1;
+
+ for (i = 0; i < 8; i += 2) {
+ float x, y;
+ rotated_coords(&x, &y, matrix, cx, cy,
+ boxr * points[i], boxr * points[i+1]);
+ points[i] = x + 0.5;
+ points[i+1] = y + 0.5;
+ }
- if (angle == 0.0 && (tile & dir)) {
- /*
- * If we are fully connected to the other tile, we must
- * draw right across the tile border. (We can use our
- * own ACTIVE state to determine what colour to do this
- * in: if we are fully connected to the other tile then
- * the two ACTIVE states will be the same.)
- */
- draw_rect_coords(dr, px-vx, py-vy, px+lx+vx, py+ly+vy, COL_WIRE);
- draw_rect_coords(dr, px, py, px+lx, py+ly,
- (tile & ACTIVE) ? COL_POWERED : COL_WIRE);
- } else {
- /*
- * The other tile extends into our border, but isn't
- * actually connected to us. Just draw a single black
- * dot.
- */
- draw_rect_coords(dr, px, py, px, py, COL_WIRE);
+ draw_polygon(dr, points, 4, col, COL_WIRE);
}
}
/*
- * Draw barrier corners, and then barriers.
+ * Draw barriers along grid edges.
*/
- for (phase = 0; phase < 2; phase++) {
- for (dir = 1; dir < 0x10; dir <<= 1) {
- int x1, y1, corner = FALSE;
- /*
- * If at least one barrier terminates at the corner
- * between dir and A(dir), draw a barrier corner.
- */
- if (barrier(state, GX(x), GY(y)) & (dir | A(dir))) {
- corner = TRUE;
- } else {
- /*
- * Only count barriers terminating at this corner
- * if they're physically next to the corner. (That
- * is, if they've wrapped round from the far side
- * of the screen, they don't count.)
- */
- x1 = x + X(dir);
- y1 = y + Y(dir);
- if (x1 >= 0 && x1 < state->width &&
- y1 >= 0 && y1 < state->height &&
- (barrier(state, GX(x1), GY(y1)) & A(dir))) {
- corner = TRUE;
- } else {
- x1 = x + X(A(dir));
- y1 = y + Y(A(dir));
- if (x1 >= 0 && x1 < state->width &&
- y1 >= 0 && y1 < state->height &&
- (barrier(state, GX(x1), GY(y1)) & dir))
- corner = TRUE;
- }
- }
-
- if (corner) {
- /*
- * At least one barrier terminates here. Draw a
- * corner.
- */
- draw_barrier_corner(dr, ds, x, y,
- X(dir)+X(A(dir)), Y(dir)+Y(A(dir)),
- phase);
- }
+ for (pass = 0; pass < 2; pass++) {
+ int btl = border_tl, bbr = border_br, col = COL_BARRIER;
+ if (pass == 0) {
+ btl += barrier_outline_thick;
+ bbr += barrier_outline_thick;
+ col = COL_WIRE;
}
- for (dir = 1; dir < 0x10; dir <<= 1)
- if (barrier(state, GX(x), GY(y)) & dir)
- draw_barrier(dr, ds, x, y, dir, phase);
+ if (tile & (L << TILE_BARRIER_SHIFT))
+ draw_rect(dr, tx, ty, btl, TILE_SIZE, col);
+ if (tile & (R << TILE_BARRIER_SHIFT))
+ draw_rect(dr, tx+TILE_SIZE-bbr, ty, bbr, TILE_SIZE, col);
+ if (tile & (U << TILE_BARRIER_SHIFT))
+ draw_rect(dr, tx, ty, TILE_SIZE, btl, col);
+ if (tile & (D << TILE_BARRIER_SHIFT))
+ draw_rect(dr, tx, ty+TILE_SIZE-bbr, TILE_SIZE, bbr, col);
+
+ if (tile & (R << TILE_BARRIER_CORNER_SHIFT))
+ draw_rect(dr, tx+TILE_SIZE-bbr, ty, bbr, btl, col);
+ if (tile & (U << TILE_BARRIER_CORNER_SHIFT))
+ draw_rect(dr, tx, ty, btl, btl, col);
+ if (tile & (L << TILE_BARRIER_CORNER_SHIFT))
+ draw_rect(dr, tx, ty+TILE_SIZE-bbr, btl, bbr, col);
+ if (tile & (D << TILE_BARRIER_CORNER_SHIFT))
+ draw_rect(dr, tx+TILE_SIZE-bbr, ty+TILE_SIZE-bbr, bbr, bbr, col);
}
+ /*
+ * Unclip and draw update, to finish.
+ */
unclip(dr);
-
- draw_update(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
+ draw_update(dr, clipx, clipy, clipw, cliph);
}
-static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate,
- game_state *state, int dir, game_ui *ui, float t, float ft)
+static void game_redraw(drawing *dr, game_drawstate *ds,
+ const game_state *oldstate, const game_state *state,
+ int dir, const game_ui *ui,
+ float t, float ft)
{
- int x, y, tx, ty, frame, last_rotate_dir, moved_origin = FALSE;
+ int tx, ty, dx, dy, d, dsh, last_rotate_dir, frame;
unsigned char *active;
+ int *loops;
float angle = 0.0;
/*
- * Clear the screen, and draw the exterior barrier lines, if
- * this is our first call or if the origin has changed.
+ * Clear the screen on our first call.
*/
- if (!ds->started || ui->org_x != ds->org_x || ui->org_y != ds->org_y) {
- int phase;
+ if (!ds->started) {
+ int w, h;
+ game_params params;
ds->started = TRUE;
- draw_rect(dr, 0, 0,
- WINDOW_OFFSET * 2 + TILE_SIZE * state->width + TILE_BORDER,
- WINDOW_OFFSET * 2 + TILE_SIZE * state->height + TILE_BORDER,
- COL_BACKGROUND);
-
- ds->org_x = ui->org_x;
- ds->org_y = ui->org_y;
- moved_origin = TRUE;
-
- draw_update(dr, 0, 0,
- WINDOW_OFFSET*2 + TILE_SIZE*state->width + TILE_BORDER,
- WINDOW_OFFSET*2 + TILE_SIZE*state->height + TILE_BORDER);
+ params.width = ds->width;
+ params.height = ds->height;
+ game_compute_size(¶ms, TILE_SIZE, &w, &h);
- for (phase = 0; phase < 2; phase++) {
-
- for (x = 0; x < ds->width; x++) {
- if (x+1 < ds->width) {
- if (barrier(state, GX(x), GY(0)) & R)
- draw_barrier_corner(dr, ds, x, -1, +1, +1, phase);
- if (barrier(state, GX(x), GY(ds->height-1)) & R)
- draw_barrier_corner(dr, ds, x, ds->height, +1, -1, phase);
- }
- if (barrier(state, GX(x), GY(0)) & U) {
- draw_barrier_corner(dr, ds, x, -1, -1, +1, phase);
- draw_barrier_corner(dr, ds, x, -1, +1, +1, phase);
- draw_barrier(dr, ds, x, -1, D, phase);
- }
- if (barrier(state, GX(x), GY(ds->height-1)) & D) {
- draw_barrier_corner(dr, ds, x, ds->height, -1, -1, phase);
- draw_barrier_corner(dr, ds, x, ds->height, +1, -1, phase);
- draw_barrier(dr, ds, x, ds->height, U, phase);
- }
- }
-
- for (y = 0; y < ds->height; y++) {
- if (y+1 < ds->height) {
- if (barrier(state, GX(0), GY(y)) & D)
- draw_barrier_corner(dr, ds, -1, y, +1, +1, phase);
- if (barrier(state, GX(ds->width-1), GY(y)) & D)
- draw_barrier_corner(dr, ds, ds->width, y, -1, +1, phase);
- }
- if (barrier(state, GX(0), GY(y)) & L) {
- draw_barrier_corner(dr, ds, -1, y, +1, -1, phase);
- draw_barrier_corner(dr, ds, -1, y, +1, +1, phase);
- draw_barrier(dr, ds, -1, y, R, phase);
- }
- if (barrier(state, GX(ds->width-1), GY(y)) & R) {
- draw_barrier_corner(dr, ds, ds->width, y, -1, -1, phase);
- draw_barrier_corner(dr, ds, ds->width, y, -1, +1, phase);
- draw_barrier(dr, ds, ds->width, y, L, phase);
- }
- }
- }
+ draw_rect(dr, 0, 0, w, h, COL_BACKGROUND);
+ draw_update(dr, 0, 0, w, h);
}
tx = ty = -1;
state = oldstate;
}
- frame = -1;
if (ft > 0) {
/*
* We're animating a completion flash. Find which frame
* we're at.
*/
frame = (int)(ft / FLASH_FRAME);
+ } else {
+ frame = 0;
}
/*
- * Draw any tile which differs from the way it was last drawn.
+ * Build up a map of what we want every tile to look like. We
+ * include tiles one square outside the grid, for the outer edges
+ * of barriers.
*/
active = compute_active(state, ui->cx, ui->cy);
+ loops = compute_loops(state);
+
+ for (dy = -1; dy < ds->height+1; dy++) {
+ for (dx = -1; dx < ds->width+1; dx++) {
+ todraw(ds, dx, dy) = 0;
+ }
+ }
+
+ for (dy = 0; dy < ds->height; dy++) {
+ int gy = (dy + ui->org_y) % ds->height;
+ for (dx = 0; dx < ds->width; dx++) {
+ int gx = (dx + ui->org_x) % ds->width;
+ int t = (tile(state, gx, gy) |
+ index(state, loops, gx, gy) |
+ index(state, active, gx, gy));
+
+ for (d = 1, dsh = 0; d < 16; d *= 2, dsh++) {
+ if (barrier(state, gx, gy) & d) {
+ todraw(ds, dx, dy) |=
+ d << TILE_BARRIER_SHIFT;
+ todraw(ds, dx + X(d), dy + Y(d)) |=
+ F(d) << TILE_BARRIER_SHIFT;
+ todraw(ds, dx + X(A(d)), dy + Y(A(d))) |=
+ C(d) << TILE_BARRIER_CORNER_SHIFT;
+ todraw(ds, dx + X(A(d)) + X(d), dy + Y(A(d)) + Y(d)) |=
+ F(d) << TILE_BARRIER_CORNER_SHIFT;
+ todraw(ds, dx + X(C(d)), dy + Y(C(d))) |=
+ d << TILE_BARRIER_CORNER_SHIFT;
+ todraw(ds, dx + X(C(d)) + X(d), dy + Y(C(d)) + Y(d)) |=
+ A(d) << TILE_BARRIER_CORNER_SHIFT;
+ }
+
+ if (t & d) {
+ int edgeval = (t & LOOP(d) ? 3 : t & ACTIVE ? 2 : 1);
+ todraw(ds, dx, dy) |= edgeval << (TILE_WIRE_SHIFT + dsh*2);
+ if (!(gx == tx && gy == ty)) {
+ todraw(ds, dx + X(d), dy + Y(d)) |=
+ edgeval << (TILE_WIRE_ON_EDGE_SHIFT + (dsh ^ 2)*2);
+ }
+ }
+ }
+
+ if (ui->cur_visible && gx == ui->cur_x && gy == ui->cur_y)
+ todraw(ds, dx, dy) |= TILE_KEYBOARD_CURSOR;
+
+ if (gx == tx && gy == ty)
+ todraw(ds, dx, dy) |= TILE_ROTATING;
+
+ if (gx == ui->cx && gy == ui->cy) {
+ todraw(ds, dx, dy) |= 3 << TILE_ENDPOINT_SHIFT;
+ } else if ((t & 0xF) != R && (t & 0xF) != U &&
+ (t & 0xF) != L && (t & 0xF) != D) {
+ /* this is not an endpoint tile */
+ } else if (t & ACTIVE) {
+ todraw(ds, dx, dy) |= 2 << TILE_ENDPOINT_SHIFT;
+ } else {
+ todraw(ds, dx, dy) |= 1 << TILE_ENDPOINT_SHIFT;
+ }
- for (x = 0; x < ds->width; x++)
- for (y = 0; y < ds->height; y++) {
- unsigned char c = tile(state, GX(x), GY(y)) |
- index(state, active, GX(x), GY(y));
- int is_src = GX(x) == ui->cx && GY(y) == ui->cy;
- int is_anim = GX(x) == tx && GY(y) == ty;
- int is_cursor = ui->cur_visible &&
- GX(x) == ui->cur_x && GY(y) == ui->cur_y;
+ if (t & LOCKED)
+ todraw(ds, dx, dy) |= TILE_LOCKED;
/*
* In a completion flash, we adjust the LOCKED bit
* the frame number.
*/
if (frame >= 0) {
- int rcx = RX(ui->cx), rcy = RY(ui->cy);
+ int rcx = (ui->cx + ds->width - ui->org_x) % ds->width;
+ int rcy = (ui->cy + ds->height - ui->org_y) % ds->height;
int xdist, ydist, dist;
- xdist = (x < rcx ? rcx - x : x - rcx);
- ydist = (y < rcy ? rcy - y : y - rcy);
+ xdist = (dx < rcx ? rcx - dx : dx - rcx);
+ ydist = (dy < rcy ? rcy - dy : dy - rcy);
dist = (xdist > ydist ? xdist : ydist);
- if (frame >= dist && frame < dist+4) {
- int lock = (frame - dist) & 1;
- lock = lock ? LOCKED : 0;
- c = (c &~ LOCKED) | lock;
- }
+ if (frame >= dist && frame < dist+4 &&
+ ((frame - dist) & 1))
+ todraw(ds, dx, dy) ^= TILE_LOCKED;
}
+ }
+ }
- if (moved_origin ||
- index(state, ds->visible, x, y) != c ||
- index(state, ds->visible, x, y) == 0xFF ||
- is_src || is_anim || is_cursor) {
- draw_tile(dr, state, ds, x, y, c,
- is_src, (is_anim ? angle : 0.0F), is_cursor);
- if (is_src || is_anim || is_cursor)
- index(state, ds->visible, x, y) = 0xFF;
- else
- index(state, ds->visible, x, y) = c;
+ /*
+ * Now draw any tile that differs from the way it was last drawn.
+ * An exception is that if either the previous _or_ current state
+ * has the TILE_ROTATING bit set, we must draw it regardless,
+ * because it will have rotated to a different angle.q
+ */
+ for (dy = -1; dy < ds->height+1; dy++) {
+ for (dx = -1; dx < ds->width+1; dx++) {
+ int prev = visible(ds, dx, dy);
+ int curr = todraw(ds, dx, dy);
+ if (prev != curr || ((prev | curr) & TILE_ROTATING) != 0) {
+ draw_tile(dr, ds, dx, dy, curr, angle);
+ visible(ds, dx, dy) = curr;
}
}
+ }
/*
* Update the status bar.
*/
{
- char statusbuf[256];
+ char statusbuf[256], *p;
int i, n, n2, a;
+ int complete = FALSE;
- n = state->width * state->height;
- for (i = a = n2 = 0; i < n; i++) {
- if (active[i])
- a++;
- if (state->tiles[i] & 0xF)
- n2++;
+ p = statusbuf;
+ *p = '\0'; /* ensure even an empty status string is terminated */
+
+ if (state->used_solve) {
+ p += sprintf(p, "Auto-solved. ");
+ complete = TRUE;
+ } else if (state->completed) {
+ p += sprintf(p, "COMPLETED! ");
+ complete = TRUE;
}
- sprintf(statusbuf, "%sActive: %d/%d",
- (state->used_solve ? "Auto-solved. " :
- state->completed ? "COMPLETED! " : ""), a, n2);
+ /*
+ * Omit the 'Active: n/N' counter completely if the source
+ * tile is a completely empty one, because then the active
+ * count can't help but read '1'.
+ */
+ if (tile(state, ui->cx, ui->cy) & 0xF) {
+ n = state->width * state->height;
+ for (i = a = n2 = 0; i < n; i++) {
+ if (active[i])
+ a++;
+ if (state->tiles[i] & 0xF)
+ n2++;
+ }
+
+ /*
+ * Also, if we're displaying a completion indicator and
+ * the game is still in its completed state (i.e. every
+ * tile is active), we might as well omit this too.
+ */
+ if (!complete || a < n2)
+ p += sprintf(p, "Active: %d/%d", a, n2);
+ }
status_bar(dr, statusbuf);
}
sfree(active);
+ sfree(loops);
}
-static float game_anim_length(game_state *oldstate,
- game_state *newstate, int dir, game_ui *ui)
+static float game_anim_length(const game_state *oldstate,
+ const game_state *newstate, int dir, game_ui *ui)
{
int last_rotate_dir;
return 0.0F;
}
-static float game_flash_length(game_state *oldstate,
- game_state *newstate, int dir, game_ui *ui)
+static float game_flash_length(const game_state *oldstate,
+ const game_state *newstate, int dir, game_ui *ui)
{
/*
* If the game has just been completed, we display a completion
return 0.0F;
}
-static int game_timing_state(game_state *state, game_ui *ui)
+static int game_status(const game_state *state)
+{
+ return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
{
return TRUE;
}
-static void game_print_size(game_params *params, float *x, float *y)
+static void game_print_size(const game_params *params, float *x, float *y)
{
int pw, ph;
* I'll use 8mm squares by default.
*/
game_compute_size(params, 800, &pw, &ph);
- *x = pw / 100.0;
- *y = ph / 100.0;
+ *x = pw / 100.0F;
+ *y = ph / 100.0F;
}
static void draw_diagram(drawing *dr, game_drawstate *ds, int x, int y,
}
}
-static void game_print(drawing *dr, game_state *state, int tilesize)
+static void game_print(drawing *dr, const game_state *state, int tilesize)
{
int w = state->width, h = state->height;
int ink = print_mono_colour(dr, 0);
const struct game thegame = {
"Net", "games.net", "net",
default_params,
- game_fetch_preset,
+ game_fetch_preset, NULL,
decode_params,
encode_params,
free_params,
dup_game,
free_game,
TRUE, solve_game,
- FALSE, game_text_format,
+ FALSE, game_can_format_as_text_now, game_text_format,
new_ui,
free_ui,
encode_ui,
game_redraw,
game_anim_length,
game_flash_length,
+ game_status,
TRUE, FALSE, game_print_size, game_print,
TRUE, /* wants_statusbar */
FALSE, game_timing_state,