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
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.)
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 )
32 #define PI 3.141592653589793238462643383279502884197169399
34 #define ANIM_PER_RADIUS_UNIT 0.13F
35 #define FLASH_FRAME 0.13F
61 int lastx
, lasty
, lastr
; /* coordinates of last rotation */
64 static game_params
*default_params(void)
66 game_params
*ret
= snew(game_params
);
70 ret
->rowsonly
= ret
->orientable
= FALSE
;
76 static void free_params(game_params
*params
)
81 static game_params
*dup_params(game_params
*params
)
83 game_params
*ret
= snew(game_params
);
84 *ret
= *params
; /* structure copy */
88 static int game_fetch_preset(int i
, char **name
, game_params
**params
)
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
} },
104 if (i
< 0 || i
>= lenof(presets
))
107 *name
= dupstr(presets
[i
].title
);
108 *params
= dup_params(&presets
[i
].params
);
113 static game_params
*decode_params(char const *string
)
115 game_params
*ret
= snew(game_params
);
117 ret
->w
= ret
->h
= atoi(string
);
119 ret
->rowsonly
= ret
->orientable
= FALSE
;
120 while (*string
&& isdigit(*string
)) string
++;
121 if (*string
== 'x') {
123 ret
->h
= atoi(string
);
124 while (*string
&& isdigit(*string
)) string
++;
126 if (*string
== 'n') {
128 ret
->n
= atoi(string
);
129 while (*string
&& isdigit(*string
)) string
++;
132 if (*string
== 'r') {
133 ret
->rowsonly
= TRUE
;
134 } else if (*string
== 'o') {
135 ret
->orientable
= TRUE
;
143 static char *encode_params(game_params
*params
)
146 sprintf(buf
, "%dx%dn%d%s%s", params
->w
, params
->h
, params
->n
,
147 params
->rowsonly ?
"r" : "",
148 params
->orientable ?
"o" : "");
152 static config_item
*game_configure(game_params
*params
)
157 ret
= snewn(6, config_item
);
159 ret
[0].name
= "Width";
160 ret
[0].type
= C_STRING
;
161 sprintf(buf
, "%d", params
->w
);
162 ret
[0].sval
= dupstr(buf
);
165 ret
[1].name
= "Height";
166 ret
[1].type
= C_STRING
;
167 sprintf(buf
, "%d", params
->h
);
168 ret
[1].sval
= dupstr(buf
);
171 ret
[2].name
= "Rotation radius";
172 ret
[2].type
= C_STRING
;
173 sprintf(buf
, "%d", params
->n
);
174 ret
[2].sval
= dupstr(buf
);
177 ret
[3].name
= "One number per row";
178 ret
[3].type
= C_BOOLEAN
;
180 ret
[3].ival
= params
->rowsonly
;
182 ret
[4].name
= "Orientation matters";
183 ret
[4].type
= C_BOOLEAN
;
185 ret
[4].ival
= params
->orientable
;
195 static game_params
*custom_params(config_item
*cfg
)
197 game_params
*ret
= snew(game_params
);
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
;
208 static char *validate_params(game_params
*params
)
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";
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
227 static void do_rotate(int *grid
, int w
, int h
, int n
, int orientable
,
228 int x
, int y
, int dir
)
232 assert(x
>= 0 && x
+n
<= w
);
233 assert(y
>= 0 && y
+n
<= h
);
236 return; /* nothing to do */
238 grid
+= y
*w
+x
; /* translate region to top corner */
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.
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.)
256 for (i
= 0; i
< (n
+1)/2; i
++) {
257 for (j
= 0; j
< n
/2; j
++) {
267 for (k
= 0; k
< 4; k
++)
270 for (k
= 0; k
< 4; k
++) {
271 int v
= g
[(k
+dir
) & 3];
273 v
^= ((v
+dir
) ^ v
) & 3; /* alter orientation */
280 * Don't forget the orientation on the centre square, if n is
283 if (orientable
&& (n
& 1)) {
284 int v
= grid
[n
/2*(w
+1)];
285 v
^= ((v
+dir
) ^ v
) & 3; /* alter orientation */
290 static int grid_complete(int *grid
, int wh
, int orientable
)
294 for (i
= 1; i
< wh
; i
++)
295 if (grid
[i
] < grid
[i
-1])
298 for (i
= 0; i
< wh
; i
++)
305 static char *new_game_seed(game_params
*params
, random_state
*rs
)
308 int w
= params
->w
, h
= params
->h
, n
= params
->n
, wh
= w
*h
;
315 * Set up a solved grid.
317 grid
= snewn(wh
, int);
318 for (i
= 0; i
< wh
; i
++)
319 grid
[i
] = ((params
->rowsonly ? i
/w
: i
) + 1) * 4;
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.
328 total_moves
= w
*h
*n
*n
*2;
329 for (i
= 0; i
< total_moves
; i
++) {
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));
338 * Optionally one more move in case the entire grid has
339 * happened to come out solved.
341 if (i
== total_moves
- 1 && grid_complete(grid
, wh
,
347 * Now construct the game seed, by describing the grid as a
348 * simple sequence of comma-separated integers.
352 for (i
= 0; i
< wh
; i
++) {
356 k
= sprintf(buf
, "%d,", grid
[i
]);
358 ret
= sresize(ret
, retlen
+ k
+ 1, char);
359 strcpy(ret
+ retlen
, buf
);
362 ret
[retlen
-1] = '\0'; /* delete last comma */
368 static char *validate_seed(game_params
*params
, char *seed
)
371 int w
= params
->w
, h
= params
->h
, wh
= w
*h
;
377 for (i
= 0; i
< wh
; i
++) {
378 if (*p
< '0' || *p
> '9') {
379 return "Not enough numbers in string";
381 while (*p
>= '0' && *p
<= '9')
383 if (i
< wh
-1 && *p
!= ',') {
384 return "Expected comma after number";
386 else if (i
== wh
-1 && *p
) {
387 return "Excess junk at end of string";
390 if (*p
) p
++; /* eat comma */
396 static game_state
*new_game(game_params
*params
, char *seed
)
398 game_state
*state
= snew(game_state
);
399 int w
= params
->w
, h
= params
->h
, n
= params
->n
, wh
= w
*h
;
406 state
->orientable
= params
->orientable
;
407 state
->completed
= 0;
408 state
->movecount
= 0;
409 state
->lastx
= state
->lasty
= state
->lastr
= -1;
411 state
->grid
= snewn(wh
, int);
415 for (i
= 0; i
< wh
; i
++) {
416 state
->grid
[i
] = atoi(p
);
417 while (*p
>= '0' && *p
<= '9')
420 if (*p
) p
++; /* eat comma */
426 static game_state
*dup_game(game_state
*state
)
428 game_state
*ret
= snew(game_state
);
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
;
440 ret
->grid
= snewn(ret
->w
* ret
->h
, int);
441 memcpy(ret
->grid
, state
->grid
, ret
->w
* ret
->h
* sizeof(int));
446 static void free_game(game_state
*state
)
452 static game_ui
*new_ui(game_state
*state
)
457 static void free_ui(game_ui
*ui
)
461 static game_state
*make_move(game_state
*from
, game_ui
*ui
, int x
, int y
,
464 int w
= from
->w
, h
= from
->h
, n
= from
->n
, wh
= w
*h
;
468 if (button
== LEFT_BUTTON
|| button
== RIGHT_BUTTON
) {
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.
474 x
-= (n
-1) * TILE_SIZE
/ 2;
475 y
-= (n
-1) * TILE_SIZE
/ 2;
478 if (x
< 0 || x
> w
-n
|| y
< 0 || y
> w
-n
)
482 * This is a valid move. Make it.
484 ret
= dup_game(from
);
486 dir
= (button
== LEFT_BUTTON ?
1 : -1);
487 do_rotate(ret
->grid
, w
, h
, n
, ret
->orientable
, x
, y
, dir
);
493 * See if the game has been completed. To do this we simply
494 * test that the grid contents are in increasing order.
496 if (!ret
->completed
&& grid_complete(ret
->grid
, wh
, ret
->orientable
))
497 ret
->completed
= ret
->movecount
;
503 /* ----------------------------------------------------------------------
507 struct game_drawstate
{
513 static void game_size(game_params
*params
, int *x
, int *y
)
515 *x
= TILE_SIZE
* params
->w
+ 2 * BORDER
;
516 *y
= TILE_SIZE
* params
->h
+ 2 * BORDER
;
519 static float *game_colours(frontend
*fe
, game_state
*state
, int *ncolours
)
521 float *ret
= snewn(3 * NCOLOURS
, float);
525 frontend_default_colour(fe
, &ret
[COL_BACKGROUND
* 3]);
528 * Drop the background colour so that the highlight is
529 * noticeably brighter than it while still being under 1.
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
);
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;
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
;
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
;
556 *ncolours
= NCOLOURS
;
560 static game_drawstate
*game_new_drawstate(game_state
*state
)
562 struct game_drawstate
*ds
= snew(struct game_drawstate
);
568 ds
->bgcolour
= COL_BACKGROUND
;
569 ds
->grid
= snewn(ds
->w
*ds
->h
, int);
570 for (i
= 0; i
< ds
->w
*ds
->h
; i
++)
576 static void game_free_drawstate(game_drawstate
*ds
)
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 */
588 static void rotate(int *xy
, struct rotation
*rot
)
591 float xf
= xy
[0] - rot
->ox
, yf
= xy
[1] - rot
->oy
;
594 xf2
= rot
->c
* xf
+ rot
->s
* yf
;
595 yf2
= - rot
->s
* xf
+ rot
->c
* yf
;
597 xy
[0] = xf2
+ rot
->ox
+ 0.5; /* round to nearest */
598 xy
[1] = yf2
+ rot
->oy
+ 0.5; /* round to nearest */
602 static void draw_tile(frontend
*fe
, game_state
*state
, int x
, int y
,
603 int tile
, int flash_colour
, struct rotation
*rot
)
609 clip(fe
, rot
->cx
, rot
->cy
, rot
->cw
, rot
->ch
);
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.
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
);
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;
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
);
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
);
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
);
647 coords
[2] = x
+ TILE_SIZE
- 1;
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
);
654 * Now the main blank area in the centre of the tile.
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
);
672 draw_rect(fe
, x
+ HIGHLIGHT_WIDTH
, y
+ HIGHLIGHT_WIDTH
,
673 TILE_SIZE
- 2*HIGHLIGHT_WIDTH
, TILE_SIZE
- 2*HIGHLIGHT_WIDTH
,
678 * Next, the colour bars for orientation.
680 if (state
->orientable
) {
684 xw
= TILE_SIZE
- 3 - 2*HIGHLIGHT_WIDTH
;
685 yw
= HIGHLIGHT_WIDTH
;
689 xw
= HIGHLIGHT_WIDTH
;
690 yw
= TILE_SIZE
- 3 - 2*HIGHLIGHT_WIDTH
;
694 xw
= TILE_SIZE
- 3 - 2*HIGHLIGHT_WIDTH
;
695 yw
= HIGHLIGHT_WIDTH
;
698 default /* case 3 */:
699 xw
= HIGHLIGHT_WIDTH
;
700 yw
= TILE_SIZE
- 3 - 2*HIGHLIGHT_WIDTH
;
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
);
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
);
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
,
747 draw_update(fe
, x
, y
, TILE_SIZE
, TILE_SIZE
);
750 static int highlight_colour(float angle
)
757 COL_HIGHLIGHT_GENTLE
,
758 COL_HIGHLIGHT_GENTLE
,
759 COL_HIGHLIGHT_GENTLE
,
770 COL_HIGHLIGHT_GENTLE
,
771 COL_HIGHLIGHT_GENTLE
,
772 COL_HIGHLIGHT_GENTLE
,
787 return colours
[(int)((angle
+ 2*PI
) / (PI
/16)) & 31];
790 static float game_anim_length(game_state
*oldstate
, game_state
*newstate
,
793 return ANIM_PER_RADIUS_UNIT
* sqrt(newstate
->n
-1);
796 static float game_flash_length(game_state
*oldstate
, game_state
*newstate
,
799 if (!oldstate
->completed
&& newstate
->completed
)
800 return 2 * FLASH_FRAME
;
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
)
810 struct rotation srot
, *rot
;
811 int lastx
= -1, lasty
= -1, lastr
= -1;
814 int frame
= (int)(flashtime
/ FLASH_FRAME
);
815 bgcolour
= (frame
% 2 ? COL_LOWLIGHT
: COL_HIGHLIGHT
);
817 bgcolour
= COL_BACKGROUND
;
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
);
830 * In an orientable puzzle, draw some colour bars at the
831 * sides as a gentle reminder of which colours need to be
834 if (state
->orientable
) {
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
);
857 * Recessed area containing the whole puzzle.
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
);
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
);
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.
883 float anim_max
= game_anim_length(oldstate
, state
, dir
);
886 lastx
= state
->lastx
;
887 lasty
= state
->lasty
;
888 lastr
= state
->lastr
;
890 lastx
= oldstate
->lastx
;
891 lasty
= oldstate
->lasty
;
892 lastr
= -oldstate
->lastr
;
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
);
906 * Sort out the colours of the various sides of the tile.
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
);
913 draw_rect(fe
, rot
->cx
, rot
->cy
, rot
->cw
, rot
->ch
, bgcolour
);
918 * Now draw each tile.
920 for (i
= 0; i
< state
->w
* state
->h
; i
++) {
922 int tx
= i
% state
->w
, ty
= i
/ state
->w
;
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
931 if (oldstate
&& lastx
>= 0 && lasty
>= 0 &&
932 tx
>= lastx
&& tx
< lastx
+ state
->n
&&
933 ty
>= lasty
&& ty
< lasty
+ state
->n
)
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
);
942 draw_tile(fe
, state
, x
, y
, state
->grid
[i
], bgcolour
, rot
);
946 ds
->bgcolour
= bgcolour
;
949 * Update the status bar.
955 * Don't show the new status until we're also showing the
956 * new _state_ - after the game animation is complete.
961 sprintf(statusbuf
, "%sMoves: %d",
962 (state
->completed ?
"COMPLETED! " : ""),
963 (state
->completed ? state
->completed
: state
->movecount
));
965 status_bar(fe
, statusbuf
);
969 static int game_wants_statusbar(void)
975 #define thegame twiddle
978 const struct game thegame
= {
979 "Twiddle", "games.twiddle", TRUE
,
1000 game_free_drawstate
,
1004 game_wants_statusbar
,