+ ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+ float *ret;
+
+ ret = snewn(NCOLOURS * 3, float);
+ *ncolours = NCOLOURS;
+
+ /*
+ * Basic background colour is whatever the front end thinks is
+ * a sensible default.
+ */
+ frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
+
+ /*
+ * Wires are black.
+ */
+ ret[COL_WIRE * 3 + 0] = 0.0F;
+ ret[COL_WIRE * 3 + 1] = 0.0F;
+ ret[COL_WIRE * 3 + 2] = 0.0F;
+
+ /*
+ * Powered wires and powered endpoints are cyan.
+ */
+ ret[COL_POWERED * 3 + 0] = 0.0F;
+ ret[COL_POWERED * 3 + 1] = 1.0F;
+ ret[COL_POWERED * 3 + 2] = 1.0F;
+
+ /*
+ * Barriers are red.
+ */
+ ret[COL_BARRIER * 3 + 0] = 1.0F;
+ 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.
+ */
+ ret[COL_ENDPOINT * 3 + 0] = 0.0F;
+ ret[COL_ENDPOINT * 3 + 1] = 0.0F;
+ ret[COL_ENDPOINT * 3 + 2] = 1.0F;
+
+ /*
+ * Tile borders are a darker grey than the background.
+ */
+ ret[COL_BORDER * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0];
+ ret[COL_BORDER * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1];
+ ret[COL_BORDER * 3 + 2] = 0.5F * ret[COL_BACKGROUND * 3 + 2];
+
+ /*
+ * Locked tiles are a grey in between those two.
+ */
+ ret[COL_LOCKED * 3 + 0] = 0.75F * ret[COL_BACKGROUND * 3 + 0];
+ ret[COL_LOCKED * 3 + 1] = 0.75F * ret[COL_BACKGROUND * 3 + 1];
+ ret[COL_LOCKED * 3 + 2] = 0.75F * ret[COL_BACKGROUND * 3 + 2];
+
+ return ret;
+}
+
+static void rotated_coords(float *ox, float *oy, const float matrix[4],
+ float cx, float cy, float ix, float iy)
+{
+ *ox = matrix[0] * ix + matrix[2] * iy + cx;
+ *oy = matrix[1] * ix + matrix[3] * iy + cy;
+}
+
+/* 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])
+{
+ 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;
+ }
+ }
+
+ if (!any_wire_this_colour)
+ return;
+
+ 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);
+}
+
+static void draw_tile(drawing *dr, game_drawstate *ds, int x, int y,
+ unsigned long tile, float angle)
+{
+ 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];
+
+ tx = WINDOW_OFFSET + TILE_SIZE * x + border_br;
+ ty = WINDOW_OFFSET + TILE_SIZE * y + border_br;
+
+ /*
+ * Clip to the tile boundary, with adjustments if we're drawing
+ * just outside the grid.
+ */
+ 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);
+
+ /*
+ * Clear the clip region.
+ */
+ bg = (tile & TILE_LOCKED) ? COL_LOCKED : COL_BACKGROUND;
+ draw_rect(dr, clipx, clipy, clipw, cliph, bg);
+
+ /*
+ * Draw the grid lines.
+ */
+ {
+ 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);
+ }
+
+ /*
+ * Draw the keyboard cursor.
+ */
+ 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 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).
+ */
+ 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);
+ }
+ }
+
+ /*
+ * 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.
+ */
+ 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 wires.
+ */
+ 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);
+
+ /*
+ * 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;
+ }
+
+ draw_polygon(dr, points, 4, col, COL_WIRE);
+ }
+ }
+
+ /*
+ * Draw barriers along grid edges.
+ */
+ 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;
+ }
+
+ 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, clipx, clipy, clipw, cliph);
+}
+
+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 tx, ty, dx, dy, d, dsh, last_rotate_dir, frame;