+ draw_line(fe, x1-1, y1, x2-1, y2, COL_WIRE);
+ draw_line(fe, x1+1, y1, x2+1, y2, COL_WIRE);
+ draw_line(fe, x1, y1-1, x2, y2-1, COL_WIRE);
+ draw_line(fe, x1, y1+1, x2, y2+1, COL_WIRE);
+ draw_line(fe, x1, y1, x2, y2, colour);
+}
+
+static void draw_rect_coords(frontend *fe, int x1, int y1, int x2, int y2,
+ int colour)
+{
+ 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(fe, mx, my, dx, dy, colour);
+}
+
+static void draw_barrier_corner(frontend *fe, 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, dx, dy, dir2;
+
+ dir >>= 4;
+
+ dir2 = A(dir);
+ dx = X(dir) + X(dir2);
+ dy = Y(dir) + Y(dir2);
+ x1 = (dx > 0 ? TILE_SIZE+TILE_BORDER-1 : 0);
+ y1 = (dy > 0 ? TILE_SIZE+TILE_BORDER-1 : 0);
+
+ if (phase == 0) {
+ draw_rect_coords(fe, bx+x1, by+y1,
+ bx+x1-TILE_BORDER*dx, by+y1-(TILE_BORDER-1)*dy,
+ COL_WIRE);
+ draw_rect_coords(fe, bx+x1, by+y1,
+ bx+x1-(TILE_BORDER-1)*dx, by+y1-TILE_BORDER*dy,
+ COL_WIRE);
+ } else {
+ draw_rect_coords(fe, bx+x1, by+y1,
+ bx+x1-(TILE_BORDER-1)*dx, by+y1-(TILE_BORDER-1)*dy,
+ COL_BARRIER);
+ }
+}
+
+static void draw_barrier(frontend *fe, 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 (phase == 0) {
+ draw_rect(fe, bx+x1-X(dir), by+y1-Y(dir), w, h, COL_WIRE);
+ } else {
+ draw_rect(fe, bx+x1, by+y1, w, h, COL_BARRIER);
+ }
+}
+
+static void draw_tile(frontend *fe, game_state *state, int x, int y, int tile,
+ float angle, int cursor)
+{
+ int bx = WINDOW_OFFSET + TILE_SIZE * x;
+ int by = WINDOW_OFFSET + TILE_SIZE * y;
+ float matrix[4];
+ float cx, cy, ex, ey, tx, ty;
+ int dir, col, phase;
+
+ /*
+ * 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.
+ *
+ * This would be terribly fiddly if we ever had to draw a tile
+ * while its neighbour was in mid-rotate, because we'd have to
+ * arrange to _know_ that the neighbour was being rotated and
+ * hence had an anomalous effect on the redraw of this tile.
+ * Fortunately, the drawing algorithm avoids ever calling us in
+ * this circumstance: we're either drawing lots of straight
+ * tiles at game start or after a move is complete, or we're
+ * repeatedly drawing only the rotating tile. So no problem.
+ */
+
+ /*
+ * 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.
+ */
+ draw_rect(fe, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER,
+ COL_BORDER);
+ draw_rect(fe, bx+TILE_BORDER, by+TILE_BORDER,
+ TILE_SIZE-TILE_BORDER, TILE_SIZE-TILE_BORDER,
+ tile & LOCKED ? COL_LOCKED : COL_BACKGROUND);
+
+ /*
+ * Draw an inset outline rectangle as a cursor, in whichever of
+ * COL_LOCKED and COL_BACKGROUND we aren't currently drawing
+ * in.
+ */
+ if (cursor) {
+ draw_line(fe, 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(fe, 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(fe, 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(fe, 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);
+ }
+
+ /*
+ * Set up the rotation matrix.
+ */
+ 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);
+
+ /*
+ * Draw the wires.
+ */
+ 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(fe, 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(fe, bx+(int)cx, by+(int)cy,
+ bx+(int)(cx+tx), by+(int)(cy+ty), 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.
+ */
+ col = -1;
+ if (x == state->cx && y == state->cy)
+ 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(fe, points, 4, TRUE, col);
+ draw_polygon(fe, points, 4, FALSE, COL_WIRE);
+ }
+
+ /*
+ * Draw the points on the border if other tiles are connected
+ * to us.
+ */
+ 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, ox, oy) & F(dir)))
+ continue;
+
+ 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);
+
+ 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(fe, px-vx, py-vy, px+lx+vx, py+ly+vy, COL_WIRE);
+ draw_rect_coords(fe, 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(fe, px, py, px, py, COL_WIRE);
+ }
+ }
+
+ /*
+ * Draw barrier corners, and then barriers.
+ */
+ for (phase = 0; phase < 2; phase++) {
+ for (dir = 1; dir < 0x10; dir <<= 1)
+ if (barrier(state, x, y) & (dir << 4))
+ draw_barrier_corner(fe, x, y, dir << 4, phase);
+ for (dir = 1; dir < 0x10; dir <<= 1)
+ if (barrier(state, x, y) & dir)
+ draw_barrier(fe, x, y, dir, phase);
+ }
+
+ draw_update(fe, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
+}
+
+static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
+ game_state *state, int dir, game_ui *ui, float t, float ft)
+{
+ int x, y, tx, ty, frame, last_rotate_dir;