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
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 )
23 #define PI 3.141592653589793238462643383279502884197169399
25 #define ANIM_PER_RADIUS_UNIT 0.13F
26 #define FLASH_FRAME 0.13F
50 int just_used_solve
; /* used to suppress undo animation */
51 int used_solve
; /* used to suppress completion flash */
52 int movecount
, movetarget
;
53 int lastx
, lasty
, lastr
; /* coordinates of last rotation */
56 static game_params
*default_params(void)
58 game_params
*ret
= snew(game_params
);
62 ret
->rowsonly
= ret
->orientable
= FALSE
;
69 static void free_params(game_params
*params
)
74 static game_params
*dup_params(game_params
*params
)
76 game_params
*ret
= snew(game_params
);
77 *ret
= *params
; /* structure copy */
81 static int game_fetch_preset(int i
, char **name
, game_params
**params
)
87 { "3x3 rows only", { 3, 3, 2, TRUE
, FALSE
} },
88 { "3x3 normal", { 3, 3, 2, FALSE
, FALSE
} },
89 { "3x3 orientable", { 3, 3, 2, FALSE
, TRUE
} },
90 { "4x4 normal", { 4, 4, 2, FALSE
} },
91 { "4x4 orientable", { 4, 4, 2, FALSE
, TRUE
} },
92 { "4x4 radius 3", { 4, 4, 3, FALSE
} },
93 { "5x5 radius 3", { 5, 5, 3, FALSE
} },
94 { "6x6 radius 4", { 6, 6, 4, FALSE
} },
97 if (i
< 0 || i
>= lenof(presets
))
100 *name
= dupstr(presets
[i
].title
);
101 *params
= dup_params(&presets
[i
].params
);
106 static void decode_params(game_params
*ret
, char const *string
)
108 ret
->w
= ret
->h
= atoi(string
);
110 ret
->rowsonly
= ret
->orientable
= FALSE
;
112 while (*string
&& isdigit(*string
)) string
++;
113 if (*string
== 'x') {
115 ret
->h
= atoi(string
);
116 while (*string
&& isdigit(*string
)) string
++;
118 if (*string
== 'n') {
120 ret
->n
= atoi(string
);
121 while (*string
&& isdigit(*string
)) string
++;
124 if (*string
== 'r') {
125 ret
->rowsonly
= TRUE
;
126 } else if (*string
== 'o') {
127 ret
->orientable
= TRUE
;
128 } else if (*string
== 'm') {
130 ret
->movetarget
= atoi(string
);
131 while (string
[1] && isdigit(string
[1])) string
++;
137 static char *encode_params(game_params
*params
, int full
)
140 sprintf(buf
, "%dx%dn%d%s%s", params
->w
, params
->h
, params
->n
,
141 params
->rowsonly ?
"r" : "",
142 params
->orientable ?
"o" : "");
143 /* Shuffle limit is part of the limited parameters, because we have to
144 * supply the target move count. */
145 if (params
->movetarget
)
146 sprintf(buf
+ strlen(buf
), "m%d", params
->movetarget
);
150 static config_item
*game_configure(game_params
*params
)
155 ret
= snewn(7, config_item
);
157 ret
[0].name
= "Width";
158 ret
[0].type
= C_STRING
;
159 sprintf(buf
, "%d", params
->w
);
160 ret
[0].sval
= dupstr(buf
);
163 ret
[1].name
= "Height";
164 ret
[1].type
= C_STRING
;
165 sprintf(buf
, "%d", params
->h
);
166 ret
[1].sval
= dupstr(buf
);
169 ret
[2].name
= "Rotation radius";
170 ret
[2].type
= C_STRING
;
171 sprintf(buf
, "%d", params
->n
);
172 ret
[2].sval
= dupstr(buf
);
175 ret
[3].name
= "One number per row";
176 ret
[3].type
= C_BOOLEAN
;
178 ret
[3].ival
= params
->rowsonly
;
180 ret
[4].name
= "Orientation matters";
181 ret
[4].type
= C_BOOLEAN
;
183 ret
[4].ival
= params
->orientable
;
185 ret
[5].name
= "Number of shuffling moves";
186 ret
[5].type
= C_STRING
;
187 sprintf(buf
, "%d", params
->movetarget
);
188 ret
[5].sval
= dupstr(buf
);
199 static game_params
*custom_params(config_item
*cfg
)
201 game_params
*ret
= snew(game_params
);
203 ret
->w
= atoi(cfg
[0].sval
);
204 ret
->h
= atoi(cfg
[1].sval
);
205 ret
->n
= atoi(cfg
[2].sval
);
206 ret
->rowsonly
= cfg
[3].ival
;
207 ret
->orientable
= cfg
[4].ival
;
208 ret
->movetarget
= atoi(cfg
[5].sval
);
213 static char *validate_params(game_params
*params
)
216 return "Rotation radius must be at least two";
217 if (params
->w
< params
->n
)
218 return "Width must be at least the rotation radius";
219 if (params
->h
< params
->n
)
220 return "Height must be at least the rotation radius";
225 * This function actually performs a rotation on a grid. The `x'
226 * and `y' coordinates passed in are the coordinates of the _top
227 * left corner_ of the rotated region. (Using the centre would have
228 * involved half-integers and been annoyingly fiddly. Clicking in
229 * the centre is good for a user interface, but too inconvenient to
232 static void do_rotate(int *grid
, int w
, int h
, int n
, int orientable
,
233 int x
, int y
, int dir
)
237 assert(x
>= 0 && x
+n
<= w
);
238 assert(y
>= 0 && y
+n
<= h
);
241 return; /* nothing to do */
243 grid
+= y
*w
+x
; /* translate region to top corner */
246 * If we were leaving the result of the rotation in a separate
247 * grid, the simple thing to do would be to loop over each
248 * square within the rotated region and assign it from its
249 * source square. However, to do it in place without taking
250 * O(n^2) memory, we need to be marginally more clever. What
251 * I'm going to do is loop over about one _quarter_ of the
252 * rotated region and permute each element within that quarter
253 * with its rotational coset.
255 * The size of the region I need to loop over is (n+1)/2 by
256 * n/2, which is an obvious exact quarter for even n and is a
257 * rectangle for odd n. (For odd n, this technique leaves out
258 * one element of the square, which is of course the central
259 * one that never moves anyway.)
261 for (i
= 0; i
< (n
+1)/2; i
++) {
262 for (j
= 0; j
< n
/2; j
++) {
272 for (k
= 0; k
< 4; k
++)
275 for (k
= 0; k
< 4; k
++) {
276 int v
= g
[(k
+dir
) & 3];
278 v
^= ((v
+dir
) ^ v
) & 3; /* alter orientation */
285 * Don't forget the orientation on the centre square, if n is
288 if (orientable
&& (n
& 1)) {
289 int v
= grid
[n
/2*(w
+1)];
290 v
^= ((v
+dir
) ^ v
) & 3; /* alter orientation */
295 static int grid_complete(int *grid
, int wh
, int orientable
)
299 for (i
= 1; i
< wh
; i
++)
300 if (grid
[i
] < grid
[i
-1])
303 for (i
= 0; i
< wh
; i
++)
310 static char *new_game_desc(game_params
*params
, random_state
*rs
,
314 int w
= params
->w
, h
= params
->h
, n
= params
->n
, wh
= w
*h
;
321 * Set up a solved grid.
323 grid
= snewn(wh
, int);
324 for (i
= 0; i
< wh
; i
++)
325 grid
[i
] = ((params
->rowsonly ? i
/w
: i
) + 1) * 4;
328 * Shuffle it. This game is complex enough that I don't feel up
329 * to analysing its full symmetry properties (particularly at
330 * n=4 and above!), so I'm going to do it the pedestrian way
331 * and simply shuffle the grid by making a long sequence of
332 * randomly chosen moves.
334 total_moves
= params
->movetarget
;
336 total_moves
= w
*h
*n
*n
*2 + random_upto(rs
, 2);
339 int oldx
= -1, oldy
= -1, oldr
= -1;
341 for (i
= 0; i
< total_moves
; i
++) {
345 x
= random_upto(rs
, w
- n
+ 1);
346 y
= random_upto(rs
, h
- n
+ 1);
347 r
= 1 + 2 * random_upto(rs
, 2);
348 } while (x
== oldx
&& y
== oldy
&& (oldr
== 0 || r
== oldr
));
350 do_rotate(grid
, w
, h
, n
, params
->orientable
,
354 * Prevent immediate reversal of a previous move, or
355 * execution of three consecutive identical moves
356 * adding up to a single inverse move. One exception is
357 * when we only _have_ one x,y setting.
359 if (w
!= n
|| h
!= n
) {
360 if (oldx
== x
&& oldy
== y
)
361 oldr
= 0; /* now avoid _any_ move in this x,y */
363 oldr
= -r
& 3; /* only prohibit the exact inverse */
368 } while (grid_complete(grid
, wh
, params
->orientable
));
371 * Now construct the game description, by describing the grid
372 * as a simple sequence of integers. They're comma-separated,
373 * unless the puzzle is orientable in which case they're
374 * separated by orientation letters `u', `d', `l' and `r'.
378 for (i
= 0; i
< wh
; i
++) {
382 k
= sprintf(buf
, "%d%c", grid
[i
] / 4,
383 params
->orientable ?
"uldr"[grid
[i
] & 3] : ',');
385 ret
= sresize(ret
, retlen
+ k
+ 1, char);
386 strcpy(ret
+ retlen
, buf
);
389 if (!params
->orientable
)
390 ret
[retlen
-1] = '\0'; /* delete last comma */
396 static void game_free_aux_info(game_aux_info
*aux
)
398 assert(!"Shouldn't happen");
401 static char *validate_desc(game_params
*params
, char *desc
)
404 int w
= params
->w
, h
= params
->h
, wh
= w
*h
;
410 for (i
= 0; i
< wh
; i
++) {
411 if (*p
< '0' || *p
> '9')
412 return "Not enough numbers in string";
413 while (*p
>= '0' && *p
<= '9')
415 if (!params
->orientable
&& i
< wh
-1) {
417 return "Expected comma after number";
418 } else if (params
->orientable
&& i
< wh
) {
419 if (*p
!= 'l' && *p
!= 'r' && *p
!= 'u' && *p
!= 'd')
420 return "Expected orientation letter after number";
421 } else if (i
== wh
-1 && *p
) {
422 return "Excess junk at end of string";
425 if (*p
) p
++; /* eat comma */
431 static game_state
*new_game(midend_data
*me
, game_params
*params
, char *desc
)
433 game_state
*state
= snew(game_state
);
434 int w
= params
->w
, h
= params
->h
, n
= params
->n
, wh
= w
*h
;
441 state
->orientable
= params
->orientable
;
442 state
->completed
= 0;
443 state
->used_solve
= state
->just_used_solve
= FALSE
;
444 state
->movecount
= 0;
445 state
->movetarget
= params
->movetarget
;
446 state
->lastx
= state
->lasty
= state
->lastr
= -1;
448 state
->grid
= snewn(wh
, int);
452 for (i
= 0; i
< wh
; i
++) {
453 state
->grid
[i
] = 4 * atoi(p
);
454 while (*p
>= '0' && *p
<= '9')
457 if (params
->orientable
) {
459 case 'l': state
->grid
[i
] |= 1; break;
460 case 'd': state
->grid
[i
] |= 2; break;
461 case 'r': state
->grid
[i
] |= 3; break;
471 static game_state
*dup_game(game_state
*state
)
473 game_state
*ret
= snew(game_state
);
478 ret
->orientable
= state
->orientable
;
479 ret
->completed
= state
->completed
;
480 ret
->movecount
= state
->movecount
;
481 ret
->movetarget
= state
->movetarget
;
482 ret
->lastx
= state
->lastx
;
483 ret
->lasty
= state
->lasty
;
484 ret
->lastr
= state
->lastr
;
485 ret
->used_solve
= state
->used_solve
;
486 ret
->just_used_solve
= state
->just_used_solve
;
488 ret
->grid
= snewn(ret
->w
* ret
->h
, int);
489 memcpy(ret
->grid
, state
->grid
, ret
->w
* ret
->h
* sizeof(int));
494 static void free_game(game_state
*state
)
500 static int compare_int(const void *av
, const void *bv
)
502 const int *a
= (const int *)av
;
503 const int *b
= (const int *)bv
;
512 static game_state
*solve_game(game_state
*state
, game_aux_info
*aux
,
515 game_state
*ret
= dup_game(state
);
519 * Simply replace the grid with a solved one. For this game,
520 * this isn't a useful operation for actually telling the user
521 * what they should have done, but it is useful for
522 * conveniently being able to get hold of a clean state from
523 * which to practise manoeuvres.
525 qsort(ret
->grid
, ret
->w
*ret
->h
, sizeof(int), compare_int
);
526 for (i
= 0; i
< ret
->w
*ret
->h
; i
++)
528 ret
->used_solve
= ret
->just_used_solve
= TRUE
;
529 ret
->completed
= ret
->movecount
= 1;
534 static char *game_text_format(game_state
*state
)
536 char *ret
, *p
, buf
[80];
537 int i
, x
, y
, col
, o
, maxlen
;
540 * First work out how many characters we need to display each
541 * number. We're pretty flexible on grid contents here, so we
542 * have to scan the entire grid.
545 for (i
= 0; i
< state
->w
* state
->h
; i
++) {
546 x
= sprintf(buf
, "%d", state
->grid
[i
] / 4);
547 if (col
< x
) col
= x
;
549 o
= (state
->orientable ?
1 : 0);
552 * Now we know the exact total size of the grid we're going to
553 * produce: it's got h rows, each containing w lots of col+o,
554 * w-1 spaces and a trailing newline.
556 maxlen
= state
->h
* state
->w
* (col
+o
+1);
558 ret
= snewn(maxlen
+1, char);
561 for (y
= 0; y
< state
->h
; y
++) {
562 for (x
= 0; x
< state
->w
; x
++) {
563 int v
= state
->grid
[state
->w
*y
+x
];
564 sprintf(buf
, "%*d", col
, v
/4);
568 *p
++ = "^<v>"[v
& 3];
576 assert(p
- ret
== maxlen
);
581 static game_ui
*new_ui(game_state
*state
)
586 static void free_ui(game_ui
*ui
)
590 static game_state
*make_move(game_state
*from
, game_ui
*ui
, int x
, int y
,
593 int w
= from
->w
, h
= from
->h
, n
= from
->n
, wh
= w
*h
;
597 button
= button
& (~MOD_MASK
| MOD_NUM_KEYPAD
);
599 if (button
== LEFT_BUTTON
|| button
== RIGHT_BUTTON
) {
601 * Determine the coordinates of the click. We offset by n-1
602 * half-blocks so that the user must click at the centre of
603 * a rotation region rather than at the corner.
605 x
-= (n
-1) * TILE_SIZE
/ 2;
606 y
-= (n
-1) * TILE_SIZE
/ 2;
609 dir
= (button
== LEFT_BUTTON ?
1 : -1);
610 if (x
< 0 || x
> w
-n
|| y
< 0 || y
> h
-n
)
612 } else if (button
== 'a' || button
== 'A' || button
==MOD_NUM_KEYPAD
+'7') {
614 dir
= (button
== 'A' ?
-1 : +1);
615 } else if (button
== 'b' || button
== 'B' || button
==MOD_NUM_KEYPAD
+'9') {
618 dir
= (button
== 'B' ?
-1 : +1);
619 } else if (button
== 'c' || button
== 'C' || button
==MOD_NUM_KEYPAD
+'1') {
622 dir
= (button
== 'C' ?
-1 : +1);
623 } else if (button
== 'd' || button
== 'D' || button
==MOD_NUM_KEYPAD
+'3') {
626 dir
= (button
== 'D' ?
-1 : +1);
627 } else if (button
==MOD_NUM_KEYPAD
+'8' && (w
-n
) % 2 == 0) {
631 } else if (button
==MOD_NUM_KEYPAD
+'2' && (w
-n
) % 2 == 0) {
635 } else if (button
==MOD_NUM_KEYPAD
+'4' && (h
-n
) % 2 == 0) {
639 } else if (button
==MOD_NUM_KEYPAD
+'6' && (h
-n
) % 2 == 0) {
643 } else if (button
==MOD_NUM_KEYPAD
+'5' && (w
-n
) % 2 == 0 && (h
-n
) % 2 == 0){
648 return NULL
; /* no move to be made */
652 * This is a valid move. Make it.
654 ret
= dup_game(from
);
655 ret
->just_used_solve
= FALSE
; /* zero this in a hurry */
657 do_rotate(ret
->grid
, w
, h
, n
, ret
->orientable
, x
, y
, dir
);
663 * See if the game has been completed. To do this we simply
664 * test that the grid contents are in increasing order.
666 if (!ret
->completed
&& grid_complete(ret
->grid
, wh
, ret
->orientable
))
667 ret
->completed
= ret
->movecount
;
671 /* ----------------------------------------------------------------------
675 struct game_drawstate
{
681 static void game_size(game_params
*params
, int *x
, int *y
)
683 *x
= TILE_SIZE
* params
->w
+ 2 * BORDER
;
684 *y
= TILE_SIZE
* params
->h
+ 2 * BORDER
;
687 static float *game_colours(frontend
*fe
, game_state
*state
, int *ncolours
)
689 float *ret
= snewn(3 * NCOLOURS
, float);
693 frontend_default_colour(fe
, &ret
[COL_BACKGROUND
* 3]);
696 * Drop the background colour so that the highlight is
697 * noticeably brighter than it while still being under 1.
699 max
= ret
[COL_BACKGROUND
*3];
700 for (i
= 1; i
< 3; i
++)
701 if (ret
[COL_BACKGROUND
*3+i
] > max
)
702 max
= ret
[COL_BACKGROUND
*3+i
];
703 if (max
* 1.2F
> 1.0F
) {
704 for (i
= 0; i
< 3; i
++)
705 ret
[COL_BACKGROUND
*3+i
] /= (max
* 1.2F
);
708 for (i
= 0; i
< 3; i
++) {
709 ret
[COL_HIGHLIGHT
* 3 + i
] = ret
[COL_BACKGROUND
* 3 + i
] * 1.2F
;
710 ret
[COL_HIGHLIGHT_GENTLE
* 3 + i
] = ret
[COL_BACKGROUND
* 3 + i
] * 1.1F
;
711 ret
[COL_LOWLIGHT
* 3 + i
] = ret
[COL_BACKGROUND
* 3 + i
] * 0.8F
;
712 ret
[COL_LOWLIGHT_GENTLE
* 3 + i
] = ret
[COL_BACKGROUND
* 3 + i
] * 0.9F
;
713 ret
[COL_TEXT
* 3 + i
] = 0.0;
716 *ncolours
= NCOLOURS
;
720 static game_drawstate
*game_new_drawstate(game_state
*state
)
722 struct game_drawstate
*ds
= snew(struct game_drawstate
);
728 ds
->bgcolour
= COL_BACKGROUND
;
729 ds
->grid
= snewn(ds
->w
*ds
->h
, int);
730 for (i
= 0; i
< ds
->w
*ds
->h
; i
++)
736 static void game_free_drawstate(game_drawstate
*ds
)
742 int cx
, cy
, cw
, ch
; /* clip region */
743 int ox
, oy
; /* rotation origin */
744 float c
, s
; /* cos and sin of rotation angle */
745 int lc
, rc
, tc
, bc
; /* colours of tile edges */
748 static void rotate(int *xy
, struct rotation
*rot
)
751 float xf
= xy
[0] - rot
->ox
, yf
= xy
[1] - rot
->oy
;
754 xf2
= rot
->c
* xf
+ rot
->s
* yf
;
755 yf2
= - rot
->s
* xf
+ rot
->c
* yf
;
757 xy
[0] = xf2
+ rot
->ox
+ 0.5; /* round to nearest */
758 xy
[1] = yf2
+ rot
->oy
+ 0.5; /* round to nearest */
762 static void draw_tile(frontend
*fe
, game_state
*state
, int x
, int y
,
763 int tile
, int flash_colour
, struct rotation
*rot
)
769 * If we've been passed a rotation region but we're drawing a
770 * tile which is outside it, we must draw it normally. This can
771 * occur if we're cleaning up after a completion flash while a
772 * new move is also being made.
774 if (rot
&& (x
< rot
->cx
|| y
< rot
->cy
||
775 x
>= rot
->cx
+rot
->cw
|| y
>= rot
->cy
+rot
->ch
))
779 clip(fe
, rot
->cx
, rot
->cy
, rot
->cw
, rot
->ch
);
782 * We must draw each side of the tile's highlight separately,
783 * because in some cases (during rotation) they will all need
784 * to be different colours.
787 /* The centre point is common to all sides. */
788 coords
[4] = x
+ TILE_SIZE
/ 2;
789 coords
[5] = y
+ TILE_SIZE
/ 2;
790 rotate(coords
+4, rot
);
793 coords
[0] = x
+ TILE_SIZE
- 1;
794 coords
[1] = y
+ TILE_SIZE
- 1;
795 rotate(coords
+0, rot
);
796 coords
[2] = x
+ TILE_SIZE
- 1;
798 rotate(coords
+2, rot
);
799 draw_polygon(fe
, coords
, 3, TRUE
, rot ? rot
->rc
: COL_LOWLIGHT
);
800 draw_polygon(fe
, coords
, 3, FALSE
, rot ? rot
->rc
: COL_LOWLIGHT
);
804 coords
[3] = y
+ TILE_SIZE
- 1;
805 rotate(coords
+2, rot
);
806 draw_polygon(fe
, coords
, 3, TRUE
, rot ? rot
->bc
: COL_LOWLIGHT
);
807 draw_polygon(fe
, coords
, 3, FALSE
, rot ? rot
->bc
: COL_LOWLIGHT
);
812 rotate(coords
+0, rot
);
813 draw_polygon(fe
, coords
, 3, TRUE
, rot ? rot
->lc
: COL_HIGHLIGHT
);
814 draw_polygon(fe
, coords
, 3, FALSE
, rot ? rot
->lc
: COL_HIGHLIGHT
);
817 coords
[2] = x
+ TILE_SIZE
- 1;
819 rotate(coords
+2, rot
);
820 draw_polygon(fe
, coords
, 3, TRUE
, rot ? rot
->tc
: COL_HIGHLIGHT
);
821 draw_polygon(fe
, coords
, 3, FALSE
, rot ? rot
->tc
: COL_HIGHLIGHT
);
824 * Now the main blank area in the centre of the tile.
827 coords
[0] = x
+ HIGHLIGHT_WIDTH
;
828 coords
[1] = y
+ HIGHLIGHT_WIDTH
;
829 rotate(coords
+0, rot
);
830 coords
[2] = x
+ HIGHLIGHT_WIDTH
;
831 coords
[3] = y
+ TILE_SIZE
- 1 - HIGHLIGHT_WIDTH
;
832 rotate(coords
+2, rot
);
833 coords
[4] = x
+ TILE_SIZE
- 1 - HIGHLIGHT_WIDTH
;
834 coords
[5] = y
+ TILE_SIZE
- 1 - HIGHLIGHT_WIDTH
;
835 rotate(coords
+4, rot
);
836 coords
[6] = x
+ TILE_SIZE
- 1 - HIGHLIGHT_WIDTH
;
837 coords
[7] = y
+ HIGHLIGHT_WIDTH
;
838 rotate(coords
+6, rot
);
839 draw_polygon(fe
, coords
, 4, TRUE
, flash_colour
);
840 draw_polygon(fe
, coords
, 4, FALSE
, flash_colour
);
842 draw_rect(fe
, x
+ HIGHLIGHT_WIDTH
, y
+ HIGHLIGHT_WIDTH
,
843 TILE_SIZE
- 2*HIGHLIGHT_WIDTH
, TILE_SIZE
- 2*HIGHLIGHT_WIDTH
,
848 * Next, the triangles for orientation.
850 if (state
->orientable
) {
851 int xdx
, xdy
, ydx
, ydy
;
852 int cx
, cy
, displ
, displ2
;
866 default /* case 3 */:
872 cx
= x
+ TILE_SIZE
/ 2;
873 cy
= y
+ TILE_SIZE
/ 2;
874 displ
= TILE_SIZE
/ 2 - HIGHLIGHT_WIDTH
- 2;
875 displ2
= TILE_SIZE
/ 3 - HIGHLIGHT_WIDTH
;
877 coords
[0] = cx
- displ
* xdx
+ displ2
* ydx
;
878 coords
[1] = cy
- displ
* xdy
+ displ2
* ydy
;
879 rotate(coords
+0, rot
);
880 coords
[2] = cx
+ displ
* xdx
+ displ2
* ydx
;
881 coords
[3] = cy
+ displ
* xdy
+ displ2
* ydy
;
882 rotate(coords
+2, rot
);
883 coords
[4] = cx
- displ
* ydx
;
884 coords
[5] = cy
- displ
* ydy
;
885 rotate(coords
+4, rot
);
886 draw_polygon(fe
, coords
, 3, TRUE
, COL_LOWLIGHT_GENTLE
);
887 draw_polygon(fe
, coords
, 3, FALSE
, COL_LOWLIGHT_GENTLE
);
890 coords
[0] = x
+ TILE_SIZE
/2;
891 coords
[1] = y
+ TILE_SIZE
/2;
892 rotate(coords
+0, rot
);
893 sprintf(str
, "%d", tile
/ 4);
894 draw_text(fe
, coords
[0], coords
[1],
895 FONT_VARIABLE
, TILE_SIZE
/3, ALIGN_VCENTRE
| ALIGN_HCENTRE
,
901 draw_update(fe
, x
, y
, TILE_SIZE
, TILE_SIZE
);
904 static int highlight_colour(float angle
)
911 COL_HIGHLIGHT_GENTLE
,
912 COL_HIGHLIGHT_GENTLE
,
913 COL_HIGHLIGHT_GENTLE
,
924 COL_HIGHLIGHT_GENTLE
,
925 COL_HIGHLIGHT_GENTLE
,
926 COL_HIGHLIGHT_GENTLE
,
941 return colours
[(int)((angle
+ 2*PI
) / (PI
/16)) & 31];
944 static float game_anim_length(game_state
*oldstate
, game_state
*newstate
,
945 int dir
, game_ui
*ui
)
947 if ((dir
> 0 && newstate
->just_used_solve
) ||
948 (dir
< 0 && oldstate
->just_used_solve
))
951 return ANIM_PER_RADIUS_UNIT
* sqrt(newstate
->n
-1);
954 static float game_flash_length(game_state
*oldstate
, game_state
*newstate
,
955 int dir
, game_ui
*ui
)
957 if (!oldstate
->completed
&& newstate
->completed
&&
958 !oldstate
->used_solve
&& !newstate
->used_solve
)
959 return 2 * FLASH_FRAME
;
964 static void game_redraw(frontend
*fe
, game_drawstate
*ds
, game_state
*oldstate
,
965 game_state
*state
, int dir
, game_ui
*ui
,
966 float animtime
, float flashtime
)
969 struct rotation srot
, *rot
;
970 int lastx
= -1, lasty
= -1, lastr
= -1;
973 int frame
= (int)(flashtime
/ FLASH_FRAME
);
974 bgcolour
= (frame
% 2 ? COL_LOWLIGHT
: COL_HIGHLIGHT
);
976 bgcolour
= COL_BACKGROUND
;
982 TILE_SIZE
* state
->w
+ 2 * BORDER
,
983 TILE_SIZE
* state
->h
+ 2 * BORDER
, COL_BACKGROUND
);
984 draw_update(fe
, 0, 0,
985 TILE_SIZE
* state
->w
+ 2 * BORDER
,
986 TILE_SIZE
* state
->h
+ 2 * BORDER
);
989 * Recessed area containing the whole puzzle.
991 coords
[0] = COORD(state
->w
) + HIGHLIGHT_WIDTH
- 1;
992 coords
[1] = COORD(state
->h
) + HIGHLIGHT_WIDTH
- 1;
993 coords
[2] = COORD(state
->w
) + HIGHLIGHT_WIDTH
- 1;
994 coords
[3] = COORD(0) - HIGHLIGHT_WIDTH
;
995 coords
[4] = COORD(0) - HIGHLIGHT_WIDTH
;
996 coords
[5] = COORD(state
->h
) + HIGHLIGHT_WIDTH
- 1;
997 draw_polygon(fe
, coords
, 3, TRUE
, COL_HIGHLIGHT
);
998 draw_polygon(fe
, coords
, 3, FALSE
, COL_HIGHLIGHT
);
1000 coords
[1] = COORD(0) - HIGHLIGHT_WIDTH
;
1001 coords
[0] = COORD(0) - HIGHLIGHT_WIDTH
;
1002 draw_polygon(fe
, coords
, 3, TRUE
, COL_LOWLIGHT
);
1003 draw_polygon(fe
, coords
, 3, FALSE
, COL_LOWLIGHT
);
1009 * If we're drawing any rotated tiles, sort out the rotation
1010 * parameters, and also zap the rotation region to the
1011 * background colour before doing anything else.
1015 float anim_max
= game_anim_length(oldstate
, state
, dir
, ui
);
1018 lastx
= state
->lastx
;
1019 lasty
= state
->lasty
;
1020 lastr
= state
->lastr
;
1022 lastx
= oldstate
->lastx
;
1023 lasty
= oldstate
->lasty
;
1024 lastr
= -oldstate
->lastr
;
1028 rot
->cx
= COORD(lastx
);
1029 rot
->cy
= COORD(lasty
);
1030 rot
->cw
= rot
->ch
= TILE_SIZE
* state
->n
;
1031 rot
->ox
= rot
->cx
+ rot
->cw
/2;
1032 rot
->oy
= rot
->cy
+ rot
->ch
/2;
1033 angle
= (-PI
/2 * lastr
) * (1.0 - animtime
/ anim_max
);
1034 rot
->c
= cos(angle
);
1035 rot
->s
= sin(angle
);
1038 * Sort out the colours of the various sides of the tile.
1040 rot
->lc
= highlight_colour(PI
+ angle
);
1041 rot
->rc
= highlight_colour(angle
);
1042 rot
->tc
= highlight_colour(PI
/2 + angle
);
1043 rot
->bc
= highlight_colour(-PI
/2 + angle
);
1045 draw_rect(fe
, rot
->cx
, rot
->cy
, rot
->cw
, rot
->ch
, bgcolour
);
1050 * Now draw each tile.
1052 for (i
= 0; i
< state
->w
* state
->h
; i
++) {
1054 int tx
= i
% state
->w
, ty
= i
/ state
->w
;
1057 * Figure out what should be displayed at this location.
1058 * Usually it will be state->grid[i], unless we're in the
1059 * middle of animating an actual rotation and this cell is
1060 * within the rotation region, in which case we set -1
1063 if (oldstate
&& lastx
>= 0 && lasty
>= 0 &&
1064 tx
>= lastx
&& tx
< lastx
+ state
->n
&&
1065 ty
>= lasty
&& ty
< lasty
+ state
->n
)
1070 if (ds
->bgcolour
!= bgcolour
|| /* always redraw when flashing */
1071 ds
->grid
[i
] != t
|| ds
->grid
[i
] == -1 || t
== -1) {
1072 int x
= COORD(tx
), y
= COORD(ty
);
1074 draw_tile(fe
, state
, x
, y
, state
->grid
[i
], bgcolour
, rot
);
1078 ds
->bgcolour
= bgcolour
;
1081 * Update the status bar.
1084 char statusbuf
[256];
1087 * Don't show the new status until we're also showing the
1088 * new _state_ - after the game animation is complete.
1093 if (state
->used_solve
)
1094 sprintf(statusbuf
, "Moves since auto-solve: %d",
1095 state
->movecount
- state
->completed
);
1097 sprintf(statusbuf
, "%sMoves: %d",
1098 (state
->completed ?
"COMPLETED! " : ""),
1099 (state
->completed ? state
->completed
: state
->movecount
));
1100 if (state
->movetarget
)
1101 sprintf(statusbuf
+strlen(statusbuf
), " (target %d)",
1105 status_bar(fe
, statusbuf
);
1109 static int game_wants_statusbar(void)
1114 static int game_timing_state(game_state
*state
)
1120 #define thegame twiddle
1123 const struct game thegame
= {
1124 "Twiddle", "games.twiddle",
1131 TRUE
, game_configure
, custom_params
,
1140 TRUE
, game_text_format
,
1147 game_free_drawstate
,
1151 game_wants_statusbar
,
1152 FALSE
, game_timing_state
,