chiark / gitweb /
Bah, and of course there's a TODO comment I forgot to remove.
[sgt-puzzles.git] / twiddle.c
1 /*
2  * twiddle.c: Puzzle involving rearranging a grid of squares by
3  * rotating subsquares. Adapted and generalised from a
4  * door-unlocking puzzle in Metroid Prime 2 (the one in the Main
5  * Gyro Chamber).
6  */
7
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <assert.h>
12 #include <ctype.h>
13 #include <math.h>
14
15 #include "puzzles.h"
16
17 #define TILE_SIZE 48
18 #define BORDER    (TILE_SIZE / 2)
19 #define HIGHLIGHT_WIDTH (TILE_SIZE / 20)
20 #define COORD(x)  ( (x) * TILE_SIZE + BORDER )
21 #define FROMCOORD(x)  ( ((x) - BORDER + TILE_SIZE) / TILE_SIZE - 1 )
22
23 #define PI 3.141592653589793238462643383279502884197169399
24
25 #define ANIM_PER_RADIUS_UNIT 0.13F
26 #define FLASH_FRAME 0.13F
27
28 enum {
29     COL_BACKGROUND,
30     COL_TEXT,
31     COL_HIGHLIGHT,
32     COL_HIGHLIGHT_GENTLE,
33     COL_LOWLIGHT,
34     COL_LOWLIGHT_GENTLE,
35     COL_TOP,
36     COL_BOTTOM,
37     NCOLOURS
38 };
39
40 struct game_params {
41     int w, h, n;
42     int rowsonly;
43     int orientable;
44 };
45
46 struct game_state {
47     int w, h, n;
48     int orientable;
49     int *grid;
50     int completed;
51     int movecount;
52     int lastx, lasty, lastr;           /* coordinates of last rotation */
53 };
54
55 static game_params *default_params(void)
56 {
57     game_params *ret = snew(game_params);
58
59     ret->w = ret->h = 3;
60     ret->n = 2;
61     ret->rowsonly = ret->orientable = FALSE;
62
63     return ret;
64 }
65
66
67 static void free_params(game_params *params)
68 {
69     sfree(params);
70 }
71
72 static game_params *dup_params(game_params *params)
73 {
74     game_params *ret = snew(game_params);
75     *ret = *params;                    /* structure copy */
76     return ret;
77 }
78
79 static int game_fetch_preset(int i, char **name, game_params **params)
80 {
81     static struct {
82         char *title;
83         game_params params;
84     } presets[] = {
85         { "3x3 rows only", { 3, 3, 2, TRUE, FALSE } },
86         { "3x3 normal", { 3, 3, 2, FALSE, FALSE } },
87         { "3x3 orientable", { 3, 3, 2, FALSE, TRUE } },
88         { "4x4 normal", { 4, 4, 2, FALSE } },
89         { "4x4 orientable", { 4, 4, 2, FALSE, TRUE } },
90         { "4x4 radius 3", { 4, 4, 3, FALSE } },
91         { "5x5 radius 3", { 5, 5, 3, FALSE } },
92         { "6x6 radius 4", { 6, 6, 4, FALSE } },
93     };
94
95     if (i < 0 || i >= lenof(presets))
96         return FALSE;
97
98     *name = dupstr(presets[i].title);
99     *params = dup_params(&presets[i].params);
100
101     return TRUE;
102 }
103
104 static game_params *decode_params(char const *string)
105 {
106     game_params *ret = snew(game_params);
107
108     ret->w = ret->h = atoi(string);
109     ret->n = 2;
110     ret->rowsonly = ret->orientable = FALSE;
111     while (*string && isdigit(*string)) string++;
112     if (*string == 'x') {
113         string++;
114         ret->h = atoi(string);
115         while (*string && isdigit(*string)) string++;
116     }
117     if (*string == 'n') {
118         string++;
119         ret->n = atoi(string);
120         while (*string && isdigit(*string)) string++;
121     }
122     while (*string) {
123         if (*string == 'r') {
124             ret->rowsonly = TRUE;
125         } else if (*string == 'o') {
126             ret->orientable = TRUE;
127         }
128         string++;
129     }
130
131     return ret;
132 }
133
134 static char *encode_params(game_params *params)
135 {
136     char buf[256];
137     sprintf(buf, "%dx%dn%d%s%s", params->w, params->h, params->n,
138             params->rowsonly ? "r" : "",
139             params->orientable ? "o" : "");
140     return dupstr(buf);
141 }
142
143 static config_item *game_configure(game_params *params)
144 {
145     config_item *ret;
146     char buf[80];
147
148     ret = snewn(6, config_item);
149
150     ret[0].name = "Width";
151     ret[0].type = C_STRING;
152     sprintf(buf, "%d", params->w);
153     ret[0].sval = dupstr(buf);
154     ret[0].ival = 0;
155
156     ret[1].name = "Height";
157     ret[1].type = C_STRING;
158     sprintf(buf, "%d", params->h);
159     ret[1].sval = dupstr(buf);
160     ret[1].ival = 0;
161
162     ret[2].name = "Rotation radius";
163     ret[2].type = C_STRING;
164     sprintf(buf, "%d", params->n);
165     ret[2].sval = dupstr(buf);
166     ret[2].ival = 0;
167
168     ret[3].name = "One number per row";
169     ret[3].type = C_BOOLEAN;
170     ret[3].sval = NULL;
171     ret[3].ival = params->rowsonly;
172
173     ret[4].name = "Orientation matters";
174     ret[4].type = C_BOOLEAN;
175     ret[4].sval = NULL;
176     ret[4].ival = params->orientable;
177
178     ret[5].name = NULL;
179     ret[5].type = C_END;
180     ret[5].sval = NULL;
181     ret[5].ival = 0;
182
183     return ret;
184 }
185
186 static game_params *custom_params(config_item *cfg)
187 {
188     game_params *ret = snew(game_params);
189
190     ret->w = atoi(cfg[0].sval);
191     ret->h = atoi(cfg[1].sval);
192     ret->n = atoi(cfg[2].sval);
193     ret->rowsonly = cfg[3].ival;
194     ret->orientable = cfg[4].ival;
195
196     return ret;
197 }
198
199 static char *validate_params(game_params *params)
200 {
201     if (params->n < 2)
202         return "Rotation radius must be at least two";
203     if (params->w < params->n)
204         return "Width must be at least the rotation radius";
205     if (params->h < params->n)
206         return "Height must be at least the rotation radius";
207     return NULL;
208 }
209
210 /*
211  * This function actually performs a rotation on a grid. The `x'
212  * and `y' coordinates passed in are the coordinates of the _top
213  * left corner_ of the rotated region. (Using the centre would have
214  * involved half-integers and been annoyingly fiddly. Clicking in
215  * the centre is good for a user interface, but too inconvenient to
216  * use internally.)
217  */
218 static void do_rotate(int *grid, int w, int h, int n, int orientable,
219                       int x, int y, int dir)
220 {
221     int i, j;
222
223     assert(x >= 0 && x+n <= w);
224     assert(y >= 0 && y+n <= h);
225     dir &= 3;
226     if (dir == 0)
227         return;                        /* nothing to do */
228
229     grid += y*w+x;                     /* translate region to top corner */
230
231     /*
232      * If we were leaving the result of the rotation in a separate
233      * grid, the simple thing to do would be to loop over each
234      * square within the rotated region and assign it from its
235      * source square. However, to do it in place without taking
236      * O(n^2) memory, we need to be marginally more clever. What
237      * I'm going to do is loop over about one _quarter_ of the
238      * rotated region and permute each element within that quarter
239      * with its rotational coset.
240      * 
241      * The size of the region I need to loop over is (n+1)/2 by
242      * n/2, which is an obvious exact quarter for even n and is a
243      * rectangle for odd n. (For odd n, this technique leaves out
244      * one element of the square, which is of course the central
245      * one that never moves anyway.)
246      */
247     for (i = 0; i < (n+1)/2; i++) {
248         for (j = 0; j < n/2; j++) {
249             int k;
250             int g[4];
251             int p[4] = {
252                 j*w+i,
253                 i*w+(n-j-1),
254                 (n-j-1)*w+(n-i-1),
255                 (n-i-1)*w+j
256             };
257
258             for (k = 0; k < 4; k++)
259                 g[k] = grid[p[k]];
260
261             for (k = 0; k < 4; k++) {
262                 int v = g[(k+dir) & 3];
263                 if (orientable)
264                     v ^= ((v+dir) ^ v) & 3;  /* alter orientation */
265                 grid[p[k]] = v;
266             }
267         }
268     }
269
270     /*
271      * Don't forget the orientation on the centre square, if n is
272      * odd.
273      */
274     if (orientable && (n & 1)) {
275         int v = grid[n/2*(w+1)];
276         v ^= ((v+dir) ^ v) & 3;  /* alter orientation */
277         grid[n/2*(w+1)] = v;
278     }
279 }
280
281 static int grid_complete(int *grid, int wh, int orientable)
282 {
283     int ok = TRUE;
284     int i;
285     for (i = 1; i < wh; i++)
286         if (grid[i] < grid[i-1])
287             ok = FALSE;
288     if (orientable) {
289         for (i = 0; i < wh; i++)
290             if (grid[i] & 3)
291                 ok = FALSE;
292     }
293     return ok;
294 }
295
296 static char *new_game_seed(game_params *params, random_state *rs)
297 {
298     int *grid;
299     int w = params->w, h = params->h, n = params->n, wh = w*h;
300     int i;
301     char *ret;
302     int retlen;
303     int total_moves;
304
305     /*
306      * Set up a solved grid.
307      */
308     grid = snewn(wh, int);
309     for (i = 0; i < wh; i++)
310         grid[i] = ((params->rowsonly ? i/w : i) + 1) * 4;
311
312     /*
313      * Shuffle it. This game is complex enough that I don't feel up
314      * to analysing its full symmetry properties (particularly at
315      * n=4 and above!), so I'm going to do it the pedestrian way
316      * and simply shuffle the grid by making a long sequence of
317      * randomly chosen moves.
318      */
319     total_moves = w*h*n*n*2;
320     for (i = 0; i < total_moves; i++) {
321         int x, y;
322
323         x = random_upto(rs, w - n + 1);
324         y = random_upto(rs, h - n + 1);
325         do_rotate(grid, w, h, n, params->orientable,
326                   x, y, 1 + random_upto(rs, 3));
327
328         /*
329          * Optionally one more move in case the entire grid has
330          * happened to come out solved.
331          */
332         if (i == total_moves - 1 && grid_complete(grid, wh,
333                                                   params->orientable))
334             i--;
335     }
336
337     /*
338      * Now construct the game seed, by describing the grid as a
339      * simple sequence of comma-separated integers.
340      */
341     ret = NULL;
342     retlen = 0;
343     for (i = 0; i < wh; i++) {
344         char buf[80];
345         int k;
346
347         k = sprintf(buf, "%d,", grid[i]);
348
349         ret = sresize(ret, retlen + k + 1, char);
350         strcpy(ret + retlen, buf);
351         retlen += k;
352     }
353     ret[retlen-1] = '\0';              /* delete last comma */
354
355     sfree(grid);
356     return ret;
357 }
358
359 static char *validate_seed(game_params *params, char *seed)
360 {
361     char *p, *err;
362     int w = params->w, h = params->h, wh = w*h;
363     int i;
364
365     p = seed;
366     err = NULL;
367
368     for (i = 0; i < wh; i++) {
369         if (*p < '0' || *p > '9') {
370             return "Not enough numbers in string";
371         }
372         while (*p >= '0' && *p <= '9')
373             p++;
374         if (i < wh-1 && *p != ',') {
375             return "Expected comma after number";
376         }
377         else if (i == wh-1 && *p) {
378             return "Excess junk at end of string";
379         }
380
381         if (*p) p++;                   /* eat comma */
382     }
383
384     return NULL;
385 }
386
387 static game_state *new_game(game_params *params, char *seed)
388 {
389     game_state *state = snew(game_state);
390     int w = params->w, h = params->h, n = params->n, wh = w*h;
391     int i;
392     char *p;
393
394     state->w = w;
395     state->h = h;
396     state->n = n;
397     state->orientable = params->orientable;
398     state->completed = 0;
399     state->movecount = 0;
400     state->lastx = state->lasty = state->lastr = -1;
401
402     state->grid = snewn(wh, int);
403
404     p = seed;
405
406     for (i = 0; i < wh; i++) {
407         state->grid[i] = atoi(p);
408         while (*p >= '0' && *p <= '9')
409             p++;
410
411         if (*p) p++;                   /* eat comma */
412     }
413
414     return state;
415 }
416
417 static game_state *dup_game(game_state *state)
418 {
419     game_state *ret = snew(game_state);
420
421     ret->w = state->w;
422     ret->h = state->h;
423     ret->n = state->n;
424     ret->orientable = state->orientable;
425     ret->completed = state->completed;
426     ret->movecount = state->movecount;
427     ret->lastx = state->lastx;
428     ret->lasty = state->lasty;
429     ret->lastr = state->lastr;
430
431     ret->grid = snewn(ret->w * ret->h, int);
432     memcpy(ret->grid, state->grid, ret->w * ret->h * sizeof(int));
433
434     return ret;
435 }
436
437 static void free_game(game_state *state)
438 {
439     sfree(state->grid);
440     sfree(state);
441 }
442
443 static game_ui *new_ui(game_state *state)
444 {
445     return NULL;
446 }
447
448 static void free_ui(game_ui *ui)
449 {
450 }
451
452 static game_state *make_move(game_state *from, game_ui *ui, int x, int y,
453                              int button)
454 {
455     int w = from->w, h = from->h, n = from->n, wh = w*h;
456     game_state *ret;
457     int dir;
458
459     if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
460         /*
461          * Determine the coordinates of the click. We offset by n-1
462          * half-blocks so that the user must click at the centre of
463          * a rotation region rather than at the corner.
464          */
465         x -= (n-1) * TILE_SIZE / 2;
466         y -= (n-1) * TILE_SIZE / 2;
467         x = FROMCOORD(x);
468         y = FROMCOORD(y);
469         if (x < 0 || x > w-n || y < 0 || y > w-n)
470             return NULL;
471
472         /*
473          * This is a valid move. Make it.
474          */
475         ret = dup_game(from);
476         ret->movecount++;
477         dir = (button == LEFT_BUTTON ? 1 : -1);
478         do_rotate(ret->grid, w, h, n, ret->orientable, x, y, dir);
479         ret->lastx = x;
480         ret->lasty = y;
481         ret->lastr = dir;
482
483         /*
484          * See if the game has been completed. To do this we simply
485          * test that the grid contents are in increasing order.
486          */
487         if (!ret->completed && grid_complete(ret->grid, wh, ret->orientable))
488             ret->completed = ret->movecount;
489         return ret;
490     }
491     return NULL;
492 }
493
494 /* ----------------------------------------------------------------------
495  * Drawing routines.
496  */
497
498 struct game_drawstate {
499     int started;
500     int w, h, bgcolour;
501     int *grid;
502 };
503
504 static void game_size(game_params *params, int *x, int *y)
505 {
506     *x = TILE_SIZE * params->w + 2 * BORDER;
507     *y = TILE_SIZE * params->h + 2 * BORDER;
508 }
509
510 static float *game_colours(frontend *fe, game_state *state, int *ncolours)
511 {
512     float *ret = snewn(3 * NCOLOURS, float);
513     int i;
514     float max;
515
516     frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
517
518     /*
519      * Drop the background colour so that the highlight is
520      * noticeably brighter than it while still being under 1.
521      */
522     max = ret[COL_BACKGROUND*3];
523     for (i = 1; i < 3; i++)
524         if (ret[COL_BACKGROUND*3+i] > max)
525             max = ret[COL_BACKGROUND*3+i];
526     if (max * 1.2F > 1.0F) {
527         for (i = 0; i < 3; i++)
528             ret[COL_BACKGROUND*3+i] /= (max * 1.2F);
529     }
530
531     for (i = 0; i < 3; i++) {
532         ret[COL_HIGHLIGHT * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 1.2F;
533         ret[COL_HIGHLIGHT_GENTLE * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 1.1F;
534         ret[COL_LOWLIGHT * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 0.8F;
535         ret[COL_LOWLIGHT_GENTLE * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 0.9F;
536         ret[COL_TEXT * 3 + i] = 0.0;
537     }
538
539     ret[COL_TOP * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 1.3F;
540     ret[COL_TOP * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 1.3F;
541     ret[COL_TOP * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 0.6F;
542
543     ret[COL_BOTTOM * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 0.6F;
544     ret[COL_BOTTOM * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 1.3F;
545     ret[COL_BOTTOM * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 0.6F;
546
547     *ncolours = NCOLOURS;
548     return ret;
549 }
550
551 static game_drawstate *game_new_drawstate(game_state *state)
552 {
553     struct game_drawstate *ds = snew(struct game_drawstate);
554     int i;
555
556     ds->started = FALSE;
557     ds->w = state->w;
558     ds->h = state->h;
559     ds->bgcolour = COL_BACKGROUND;
560     ds->grid = snewn(ds->w*ds->h, int);
561     for (i = 0; i < ds->w*ds->h; i++)
562         ds->grid[i] = -1;
563
564     return ds;
565 }
566
567 static void game_free_drawstate(game_drawstate *ds)
568 {
569     sfree(ds);
570 }
571
572 struct rotation {
573     int cx, cy, cw, ch;                /* clip region */
574     int ox, oy;                        /* rotation origin */
575     float c, s;                        /* cos and sin of rotation angle */
576     int lc, rc, tc, bc;                /* colours of tile edges */
577 };
578
579 static void rotate(int *xy, struct rotation *rot)
580 {
581     if (rot) {
582         float xf = xy[0] - rot->ox, yf = xy[1] - rot->oy;
583         float xf2, yf2;
584
585         xf2 = rot->c * xf + rot->s * yf;
586         yf2 = - rot->s * xf + rot->c * yf;
587
588         xy[0] = xf2 + rot->ox + 0.5;   /* round to nearest */
589         xy[1] = yf2 + rot->oy + 0.5;   /* round to nearest */
590     }
591 }
592
593 static void draw_tile(frontend *fe, game_state *state, int x, int y,
594                       int tile, int flash_colour, struct rotation *rot)
595 {
596     int coords[8];
597     char str[40];
598
599     if (rot)
600         clip(fe, rot->cx, rot->cy, rot->cw, rot->ch);
601
602     /*
603      * We must draw each side of the tile's highlight separately,
604      * because in some cases (during rotation) they will all need
605      * to be different colours.
606      */
607
608     /* The centre point is common to all sides. */
609     coords[4] = x + TILE_SIZE / 2;
610     coords[5] = y + TILE_SIZE / 2;
611     rotate(coords+4, rot);
612
613     /* Right side. */
614     coords[0] = x + TILE_SIZE - 1;
615     coords[1] = y + TILE_SIZE - 1;
616     rotate(coords+0, rot);
617     coords[2] = x + TILE_SIZE - 1;
618     coords[3] = y;
619     rotate(coords+2, rot);
620     draw_polygon(fe, coords, 3, TRUE, rot ? rot->rc : COL_LOWLIGHT);
621     draw_polygon(fe, coords, 3, FALSE, rot ? rot->rc : COL_LOWLIGHT);
622
623     /* Bottom side. */
624     coords[2] = x;
625     coords[3] = y + TILE_SIZE - 1;
626     rotate(coords+2, rot);
627     draw_polygon(fe, coords, 3, TRUE, rot ? rot->bc : COL_LOWLIGHT);
628     draw_polygon(fe, coords, 3, FALSE, rot ? rot->bc : COL_LOWLIGHT);
629
630     /* Left side. */
631     coords[0] = x;
632     coords[1] = y;
633     rotate(coords+0, rot);
634     draw_polygon(fe, coords, 3, TRUE, rot ? rot->lc : COL_HIGHLIGHT);
635     draw_polygon(fe, coords, 3, FALSE, rot ? rot->lc : COL_HIGHLIGHT);
636
637     /* Top side. */
638     coords[2] = x + TILE_SIZE - 1;
639     coords[3] = y;
640     rotate(coords+2, rot);
641     draw_polygon(fe, coords, 3, TRUE, rot ? rot->tc : COL_HIGHLIGHT);
642     draw_polygon(fe, coords, 3, FALSE, rot ? rot->tc : COL_HIGHLIGHT);
643
644     /*
645      * Now the main blank area in the centre of the tile.
646      */
647     if (rot) {
648         coords[0] = x + HIGHLIGHT_WIDTH;
649         coords[1] = y + HIGHLIGHT_WIDTH;
650         rotate(coords+0, rot);
651         coords[2] = x + HIGHLIGHT_WIDTH;
652         coords[3] = y + TILE_SIZE - 1 - HIGHLIGHT_WIDTH;
653         rotate(coords+2, rot);
654         coords[4] = x + TILE_SIZE - 1 - HIGHLIGHT_WIDTH;
655         coords[5] = y + TILE_SIZE - 1 - HIGHLIGHT_WIDTH;
656         rotate(coords+4, rot);
657         coords[6] = x + TILE_SIZE - 1 - HIGHLIGHT_WIDTH;
658         coords[7] = y + HIGHLIGHT_WIDTH;
659         rotate(coords+6, rot);
660         draw_polygon(fe, coords, 4, TRUE, flash_colour);
661         draw_polygon(fe, coords, 4, FALSE, flash_colour);
662     } else {
663         draw_rect(fe, x + HIGHLIGHT_WIDTH, y + HIGHLIGHT_WIDTH,
664                   TILE_SIZE - 2*HIGHLIGHT_WIDTH, TILE_SIZE - 2*HIGHLIGHT_WIDTH,
665                   flash_colour);
666     }
667
668     /*
669      * Next, the colour bars for orientation.
670      */
671     if (state->orientable) {
672         int xw, yw, swap;
673         switch (tile & 3) {
674           case 0:
675             xw = TILE_SIZE - 3 - 2*HIGHLIGHT_WIDTH;
676             yw = HIGHLIGHT_WIDTH;
677             swap = FALSE;
678             break;
679           case 1:
680             xw = HIGHLIGHT_WIDTH;
681             yw = TILE_SIZE - 3 - 2*HIGHLIGHT_WIDTH;
682             swap = FALSE;
683             break;
684           case 2:
685             xw = TILE_SIZE - 3 - 2*HIGHLIGHT_WIDTH;
686             yw = HIGHLIGHT_WIDTH;
687             swap = TRUE;
688             break;
689           default /* case 3 */:
690             xw = HIGHLIGHT_WIDTH;
691             yw = TILE_SIZE - 3 - 2*HIGHLIGHT_WIDTH;
692             swap = TRUE;
693             break;
694         }
695
696         coords[0] = x + HIGHLIGHT_WIDTH + 1;
697         coords[1] = y + HIGHLIGHT_WIDTH + 1;
698         rotate(coords+0, rot);
699         coords[2] = x + HIGHLIGHT_WIDTH + 1 + xw;
700         coords[3] = y + HIGHLIGHT_WIDTH + 1;
701         rotate(coords+2, rot);
702         coords[4] = x + HIGHLIGHT_WIDTH + 1 + xw;
703         coords[5] = y + HIGHLIGHT_WIDTH + 1 + yw;
704         rotate(coords+4, rot);
705         coords[6] = x + HIGHLIGHT_WIDTH + 1;
706         coords[7] = y + HIGHLIGHT_WIDTH + 1 + yw;
707         rotate(coords+6, rot);
708         draw_polygon(fe, coords, 4, TRUE, swap ? COL_BOTTOM : COL_TOP);
709         draw_polygon(fe, coords, 4, FALSE, swap ? COL_BOTTOM : COL_TOP);
710
711         coords[0] = x + TILE_SIZE - 2 - HIGHLIGHT_WIDTH;
712         coords[1] = y + TILE_SIZE - 2 - HIGHLIGHT_WIDTH;
713         rotate(coords+0, rot);
714         coords[2] = x + TILE_SIZE - 2 - HIGHLIGHT_WIDTH - xw;
715         coords[3] = y + TILE_SIZE - 2 - HIGHLIGHT_WIDTH;
716         rotate(coords+2, rot);
717         coords[4] = x + TILE_SIZE - 2 - HIGHLIGHT_WIDTH - xw;
718         coords[5] = y + TILE_SIZE - 2 - HIGHLIGHT_WIDTH - yw;
719         rotate(coords+4, rot);
720         coords[6] = x + TILE_SIZE - 2 - HIGHLIGHT_WIDTH;
721         coords[7] = y + TILE_SIZE - 2 - HIGHLIGHT_WIDTH - yw;
722         rotate(coords+6, rot);
723         draw_polygon(fe, coords, 4, TRUE, swap ? COL_TOP : COL_BOTTOM);
724         draw_polygon(fe, coords, 4, FALSE, swap ? COL_TOP : COL_BOTTOM);
725     }
726
727     coords[0] = x + TILE_SIZE/2;
728     coords[1] = y + TILE_SIZE/2;
729     rotate(coords+0, rot);
730     sprintf(str, "%d", tile / 4);
731     draw_text(fe, coords[0], coords[1],
732               FONT_VARIABLE, TILE_SIZE/3, ALIGN_VCENTRE | ALIGN_HCENTRE,
733               COL_TEXT, str);
734
735     if (rot)
736         unclip(fe);
737
738     draw_update(fe, x, y, TILE_SIZE, TILE_SIZE);
739 }
740
741 static int highlight_colour(float angle)
742 {
743     int colours[32] = {
744         COL_LOWLIGHT,
745         COL_LOWLIGHT_GENTLE,
746         COL_LOWLIGHT_GENTLE,
747         COL_LOWLIGHT_GENTLE,
748         COL_HIGHLIGHT_GENTLE,
749         COL_HIGHLIGHT_GENTLE,
750         COL_HIGHLIGHT_GENTLE,
751         COL_HIGHLIGHT,
752         COL_HIGHLIGHT,
753         COL_HIGHLIGHT,
754         COL_HIGHLIGHT,
755         COL_HIGHLIGHT,
756         COL_HIGHLIGHT,
757         COL_HIGHLIGHT,
758         COL_HIGHLIGHT,
759         COL_HIGHLIGHT,
760         COL_HIGHLIGHT,
761         COL_HIGHLIGHT_GENTLE,
762         COL_HIGHLIGHT_GENTLE,
763         COL_HIGHLIGHT_GENTLE,
764         COL_LOWLIGHT_GENTLE,
765         COL_LOWLIGHT_GENTLE,
766         COL_LOWLIGHT_GENTLE,
767         COL_LOWLIGHT,
768         COL_LOWLIGHT,
769         COL_LOWLIGHT,
770         COL_LOWLIGHT,
771         COL_LOWLIGHT,
772         COL_LOWLIGHT,
773         COL_LOWLIGHT,
774         COL_LOWLIGHT,
775         COL_LOWLIGHT,
776     };
777
778     return colours[(int)((angle + 2*PI) / (PI/16)) & 31];
779 }
780
781 static float game_anim_length(game_state *oldstate, game_state *newstate,
782                               int dir)
783 {
784     return ANIM_PER_RADIUS_UNIT * sqrt(newstate->n-1);
785 }
786
787 static float game_flash_length(game_state *oldstate, game_state *newstate,
788                                int dir)
789 {
790     if (!oldstate->completed && newstate->completed)
791         return 2 * FLASH_FRAME;
792     else
793         return 0.0F;
794 }
795
796 static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
797                         game_state *state, int dir, game_ui *ui,
798                         float animtime, float flashtime)
799 {
800     int i, bgcolour;
801     struct rotation srot, *rot;
802     int lastx = -1, lasty = -1, lastr = -1;
803
804     if (flashtime > 0) {
805         int frame = (int)(flashtime / FLASH_FRAME);
806         bgcolour = (frame % 2 ? COL_LOWLIGHT : COL_HIGHLIGHT);
807     } else
808         bgcolour = COL_BACKGROUND;
809
810     if (!ds->started) {
811         int coords[6];
812
813         draw_rect(fe, 0, 0,
814                   TILE_SIZE * state->w + 2 * BORDER,
815                   TILE_SIZE * state->h + 2 * BORDER, COL_BACKGROUND);
816         draw_update(fe, 0, 0,
817                     TILE_SIZE * state->w + 2 * BORDER,
818                     TILE_SIZE * state->h + 2 * BORDER);
819
820         /*
821          * In an orientable puzzle, draw some colour bars at the
822          * sides as a gentle reminder of which colours need to be
823          * aligned where.
824          */
825         if (state->orientable) {
826             int y;
827             for (y = 0; y < state->h; y++) {
828                 draw_rect(fe, COORD(0) - BORDER / 2,
829                           COORD(y) + HIGHLIGHT_WIDTH + 1,
830                           BORDER / 2 - 2 * HIGHLIGHT_WIDTH,
831                           HIGHLIGHT_WIDTH + 1, COL_TOP);
832                 draw_rect(fe, COORD(state->w) + 2 * HIGHLIGHT_WIDTH,
833                           COORD(y) + HIGHLIGHT_WIDTH + 1,
834                           BORDER / 2 - 2 * HIGHLIGHT_WIDTH,
835                           HIGHLIGHT_WIDTH + 1, COL_TOP);
836                 draw_rect(fe, COORD(0) - BORDER / 2,
837                           COORD(y) + TILE_SIZE - 2 - 2 * HIGHLIGHT_WIDTH,
838                           BORDER / 2 - 2 * HIGHLIGHT_WIDTH,
839                           HIGHLIGHT_WIDTH + 1, COL_BOTTOM);
840                 draw_rect(fe, COORD(state->w) + 2 * HIGHLIGHT_WIDTH,
841                           COORD(y) + TILE_SIZE - 2 - 2 * HIGHLIGHT_WIDTH,
842                           BORDER / 2 - 2 * HIGHLIGHT_WIDTH,
843                           HIGHLIGHT_WIDTH + 1, COL_BOTTOM);
844             }
845         }
846
847         /*
848          * Recessed area containing the whole puzzle.
849          */
850         coords[0] = COORD(state->w) + HIGHLIGHT_WIDTH - 1;
851         coords[1] = COORD(state->h) + HIGHLIGHT_WIDTH - 1;
852         coords[2] = COORD(state->w) + HIGHLIGHT_WIDTH - 1;
853         coords[3] = COORD(0) - HIGHLIGHT_WIDTH;
854         coords[4] = COORD(0) - HIGHLIGHT_WIDTH;
855         coords[5] = COORD(state->h) + HIGHLIGHT_WIDTH - 1;
856         draw_polygon(fe, coords, 3, TRUE, COL_HIGHLIGHT);
857         draw_polygon(fe, coords, 3, FALSE, COL_HIGHLIGHT);
858
859         coords[1] = COORD(0) - HIGHLIGHT_WIDTH;
860         coords[0] = COORD(0) - HIGHLIGHT_WIDTH;
861         draw_polygon(fe, coords, 3, TRUE, COL_LOWLIGHT);
862         draw_polygon(fe, coords, 3, FALSE, COL_LOWLIGHT);
863
864         ds->started = TRUE;
865     }
866
867     /*
868      * If we're drawing any rotated tiles, sort out the rotation
869      * parameters, and also zap the rotation region to the
870      * background colour before doing anything else.
871      */
872     if (oldstate) {
873         float angle;
874         float anim_max = game_anim_length(oldstate, state, dir);
875
876         if (dir > 0) {
877             lastx = state->lastx;
878             lasty = state->lasty;
879             lastr = state->lastr;
880         } else {
881             lastx = oldstate->lastx;
882             lasty = oldstate->lasty;
883             lastr = -oldstate->lastr;
884         }
885
886         rot = &srot;
887         rot->cx = COORD(lastx);
888         rot->cy = COORD(lasty);
889         rot->cw = rot->ch = TILE_SIZE * state->n;
890         rot->ox = rot->cx + rot->cw/2;
891         rot->oy = rot->cy + rot->ch/2;
892         angle = (-PI/2 * lastr) * (1.0 - animtime / anim_max);
893         rot->c = cos(angle);
894         rot->s = sin(angle);
895
896         /*
897          * Sort out the colours of the various sides of the tile.
898          */
899         rot->lc = highlight_colour(PI + angle);
900         rot->rc = highlight_colour(angle);
901         rot->tc = highlight_colour(PI/2 + angle);
902         rot->bc = highlight_colour(-PI/2 + angle);
903
904         draw_rect(fe, rot->cx, rot->cy, rot->cw, rot->ch, bgcolour);
905     } else
906         rot = NULL;
907
908     /*
909      * Now draw each tile.
910      */
911     for (i = 0; i < state->w * state->h; i++) {
912         int t;
913         int tx = i % state->w, ty = i / state->w;
914
915         /*
916          * Figure out what should be displayed at this location.
917          * Usually it will be state->grid[i], unless we're in the
918          * middle of animating an actual rotation and this cell is
919          * within the rotation region, in which case we set -1
920          * (always display).
921          */
922         if (oldstate && lastx >= 0 && lasty >= 0 &&
923             tx >= lastx && tx < lastx + state->n &&
924             ty >= lasty && ty < lasty + state->n)
925             t = -1;
926         else
927             t = state->grid[i];
928
929         if (ds->bgcolour != bgcolour ||   /* always redraw when flashing */
930             ds->grid[i] != t || ds->grid[i] == -1 || t == -1) {
931             int x = COORD(tx), y = COORD(ty);
932
933             draw_tile(fe, state, x, y, state->grid[i], bgcolour, rot);
934             ds->grid[i] = t;
935         }
936     }
937     ds->bgcolour = bgcolour;
938
939     /*
940      * Update the status bar.
941      */
942     {
943         char statusbuf[256];
944
945         /*
946          * Don't show the new status until we're also showing the
947          * new _state_ - after the game animation is complete.
948          */
949         if (oldstate)
950             state = oldstate;
951
952         sprintf(statusbuf, "%sMoves: %d",
953                 (state->completed ? "COMPLETED! " : ""),
954                 (state->completed ? state->completed : state->movecount));
955
956         status_bar(fe, statusbuf);
957     }
958 }
959
960 static int game_wants_statusbar(void)
961 {
962     return TRUE;
963 }
964
965 #ifdef COMBINED
966 #define thegame twiddle
967 #endif
968
969 const struct game thegame = {
970     "Twiddle", "games.twiddle", TRUE,
971     default_params,
972     game_fetch_preset,
973     decode_params,
974     encode_params,
975     free_params,
976     dup_params,
977     game_configure,
978     custom_params,
979     validate_params,
980     new_game_seed,
981     validate_seed,
982     new_game,
983     dup_game,
984     free_game,
985     new_ui,
986     free_ui,
987     make_move,
988     game_size,
989     game_colours,
990     game_new_drawstate,
991     game_free_drawstate,
992     game_redraw,
993     game_anim_length,
994     game_flash_length,
995     game_wants_statusbar,
996 };