+void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
+ game_state *state, float t)
+{
+ int x, y, tx, ty, frame;
+ unsigned char *active;
+ float angle = 0.0;
+
+ /*
+ * Clear the screen and draw the exterior barrier lines if this
+ * is our first call.
+ */
+ if (!ds->started) {
+ int phase;
+
+ ds->started = TRUE;
+
+ draw_rect(fe, 0, 0,
+ WINDOW_OFFSET * 2 + TILE_SIZE * state->width + TILE_BORDER,
+ WINDOW_OFFSET * 2 + TILE_SIZE * state->height + TILE_BORDER,
+ COL_BACKGROUND);
+ draw_update(fe, 0, 0,
+ WINDOW_OFFSET*2 + TILE_SIZE*state->width + TILE_BORDER,
+ WINDOW_OFFSET*2 + TILE_SIZE*state->height + TILE_BORDER);
+
+ for (phase = 0; phase < 2; phase++) {
+
+ for (x = 0; x < ds->width; x++) {
+ if (barrier(state, x, 0) & UL)
+ draw_barrier_corner(fe, x, -1, LD, phase);
+ if (barrier(state, x, 0) & RU)
+ draw_barrier_corner(fe, x, -1, DR, phase);
+ if (barrier(state, x, 0) & U)
+ draw_barrier(fe, x, -1, D, phase);
+ if (barrier(state, x, ds->height-1) & DR)
+ draw_barrier_corner(fe, x, ds->height, RU, phase);
+ if (barrier(state, x, ds->height-1) & LD)
+ draw_barrier_corner(fe, x, ds->height, UL, phase);
+ if (barrier(state, x, ds->height-1) & D)
+ draw_barrier(fe, x, ds->height, U, phase);
+ }
+
+ for (y = 0; y < ds->height; y++) {
+ if (barrier(state, 0, y) & UL)
+ draw_barrier_corner(fe, -1, y, RU, phase);
+ if (barrier(state, 0, y) & LD)
+ draw_barrier_corner(fe, -1, y, DR, phase);
+ if (barrier(state, 0, y) & L)
+ draw_barrier(fe, -1, y, R, phase);
+ if (barrier(state, ds->width-1, y) & RU)
+ draw_barrier_corner(fe, ds->width, y, UL, phase);
+ if (barrier(state, ds->width-1, y) & DR)
+ draw_barrier_corner(fe, ds->width, y, LD, phase);
+ if (barrier(state, ds->width-1, y) & R)
+ draw_barrier(fe, ds->width, y, L, phase);
+ }
+ }
+ }
+
+ tx = ty = -1;
+ frame = -1;
+ if (oldstate && (t < ROTATE_TIME)) {
+ /*
+ * We're animating a tile rotation. Find the turning tile,
+ * if any.
+ */
+ for (x = 0; x < oldstate->width; x++)
+ for (y = 0; y < oldstate->height; y++)
+ if ((tile(oldstate, x, y) ^ tile(state, x, y)) & 0xF) {
+ tx = x, ty = y;
+ goto break_label; /* leave both loops at once */
+ }
+ break_label:
+
+ if (tx >= 0) {
+ if (tile(state, tx, ty) == ROT(tile(oldstate, tx, ty),
+ state->last_rotate_dir))
+ angle = state->last_rotate_dir * 90.0 * (t / ROTATE_TIME);
+ else
+ angle = state->last_rotate_dir * -90.0 * (t / ROTATE_TIME);
+ state = oldstate;
+ }
+ } else if (t > ROTATE_TIME) {
+ /*
+ * We're animating a completion flash. Find which frame
+ * we're at.
+ */
+ frame = (t - ROTATE_TIME) / FLASH_FRAME;
+ }
+
+ /*
+ * Draw any tile which differs from the way it was last drawn.
+ */
+ active = compute_active(state);
+
+ for (x = 0; x < ds->width; x++)
+ for (y = 0; y < ds->height; y++) {
+ unsigned char c = tile(state, x, y) | index(state, active, x, y);
+
+ /*
+ * In a completion flash, we adjust the LOCKED bit
+ * depending on our distance from the centre point and
+ * the frame number.
+ */
+ if (frame >= 0) {
+ int xdist, ydist, dist;
+ xdist = (x < state->cx ? state->cx - x : x - state->cx);
+ ydist = (y < state->cy ? state->cy - y : y - state->cy);
+ 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 (index(state, ds->visible, x, y) != c ||
+ index(state, ds->visible, x, y) == 0xFF ||
+ (x == tx && y == ty)) {
+ draw_tile(fe, state, x, y, c,
+ (x == tx && y == ty ? angle : 0.0));
+ if (x == tx && y == ty)
+ index(state, ds->visible, x, y) = 0xFF;
+ else
+ index(state, ds->visible, x, y) = c;
+ }
+ }
+
+ sfree(active);
+}
+
+float game_anim_length(game_state *oldstate, game_state *newstate)
+{
+ float ret = 0.0;
+ int x, y;
+
+ /*
+ * If there's a tile which has been rotated, allow time to
+ * animate its rotation.
+ */
+ for (x = 0; x < oldstate->width; x++)
+ for (y = 0; y < oldstate->height; y++)
+ if ((tile(oldstate, x, y) ^ tile(newstate, x, y)) & 0xF) {
+ ret = ROTATE_TIME;
+ goto break_label; /* leave both loops at once */
+ }
+ break_label:
+
+ /*
+ * Also, if the game has just been completed, allow time for a
+ * completion flash.
+ */
+ if (!oldstate->completed && newstate->completed) {
+ int size;
+ size = 0;
+ if (size < newstate->cx+1)
+ size = newstate->cx+1;
+ if (size < newstate->cy+1)
+ size = newstate->cy+1;
+ if (size < newstate->width - newstate->cx)
+ size = newstate->width - newstate->cx;
+ if (size < newstate->height - newstate->cy)
+ size = newstate->height - newstate->cy;
+ ret += FLASH_FRAME * (size+4);
+ }
+
+ return ret;
+}