+ sfree(ds->visible);
+ sfree(ds);
+}
+
+static void game_compute_size(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;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+ game_params *params, int tilesize)
+{
+ 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;
+
+ /*
+ * 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 draw_thick_line(drawing *dr, int x1, int y1, int x2, int y2,
+ int colour)
+{
+ 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);
+}
+
+static void draw_rect_coords(drawing *dr, 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(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);
+ }
+}
+
+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 (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);
+ }
+}
+
+/*
+ * 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)
+{
+ 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.
+ */
+
+ clip(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
+
+ /*
+ * 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(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);
+
+ /*
+ * 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(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);
+ }
+
+ /*
+ * 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(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);
+ }
+ }
+
+ /*
+ * 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 (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);
+ }
+
+ /*
+ * 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, GX(ox), GY(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(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 barrier corners, and then barriers.
+ */
+ 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 (dir = 1; dir < 0x10; dir <<= 1)
+ if (barrier(state, GX(x), GY(y)) & dir)
+ draw_barrier(dr, ds, x, y, dir, phase);
+ }
+
+ unclip(dr);
+
+ draw_update(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
+}
+
+static void game_redraw(drawing *dr, 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, moved_origin = FALSE;