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