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
17 #define PREFERRED_TILE_SIZE 48
18 #define TILE_SIZE (ds->tilesize)
19 #define BORDER (TILE_SIZE / 2)
20 #define HIGHLIGHT_WIDTH (TILE_SIZE / 20)
21 #define COORD(x) ( (x) * TILE_SIZE + BORDER )
22 #define FROMCOORD(x) ( ((x) - BORDER + TILE_SIZE) / TILE_SIZE - 1 )
24 #define ANIM_PER_BLKSIZE_UNIT 0.13F
25 #define FLASH_FRAME 0.13F
34 COL_HIGHCURSOR
, COL_LOWCURSOR
,
50 int used_solve
; /* used to suppress completion flash */
51 int movecount
, movetarget
;
52 int lastx
, lasty
, lastr
; /* coordinates of last rotation */
55 static game_params
*default_params(void)
57 game_params
*ret
= snew(game_params
);
61 ret
->rowsonly
= ret
->orientable
= FALSE
;
68 static void free_params(game_params
*params
)
73 static game_params
*dup_params(game_params
*params
)
75 game_params
*ret
= snew(game_params
);
76 *ret
= *params
; /* structure copy */
80 static int game_fetch_preset(int i
, char **name
, game_params
**params
)
86 { "3x3 rows only", { 3, 3, 2, TRUE
, FALSE
} },
87 { "3x3 normal", { 3, 3, 2, FALSE
, FALSE
} },
88 { "3x3 orientable", { 3, 3, 2, FALSE
, TRUE
} },
89 { "4x4 normal", { 4, 4, 2, FALSE
} },
90 { "4x4 orientable", { 4, 4, 2, FALSE
, TRUE
} },
91 { "4x4, rotating 3x3 blocks", { 4, 4, 3, FALSE
} },
92 { "5x5, rotating 3x3 blocks", { 5, 5, 3, FALSE
} },
93 { "6x6, rotating 4x4 blocks", { 6, 6, 4, FALSE
} },
96 if (i
< 0 || i
>= lenof(presets
))
99 *name
= dupstr(presets
[i
].title
);
100 *params
= dup_params(&presets
[i
].params
);
105 static void decode_params(game_params
*ret
, char const *string
)
107 ret
->w
= ret
->h
= atoi(string
);
109 ret
->rowsonly
= ret
->orientable
= FALSE
;
111 while (*string
&& isdigit((unsigned char)*string
)) string
++;
112 if (*string
== 'x') {
114 ret
->h
= atoi(string
);
115 while (*string
&& isdigit((unsigned char)*string
)) string
++;
117 if (*string
== 'n') {
119 ret
->n
= atoi(string
);
120 while (*string
&& isdigit((unsigned char)*string
)) string
++;
123 if (*string
== 'r') {
124 ret
->rowsonly
= TRUE
;
125 } else if (*string
== 'o') {
126 ret
->orientable
= TRUE
;
127 } else if (*string
== 'm') {
129 ret
->movetarget
= atoi(string
);
130 while (string
[1] && isdigit((unsigned char)string
[1])) string
++;
136 static char *encode_params(game_params
*params
, int full
)
139 sprintf(buf
, "%dx%dn%d%s%s", params
->w
, params
->h
, params
->n
,
140 params
->rowsonly
? "r" : "",
141 params
->orientable
? "o" : "");
142 /* Shuffle limit is part of the limited parameters, because we have to
143 * supply the target move count. */
144 if (params
->movetarget
)
145 sprintf(buf
+ strlen(buf
), "m%d", params
->movetarget
);
149 static config_item
*game_configure(game_params
*params
)
154 ret
= snewn(7, config_item
);
156 ret
[0].name
= "Width";
157 ret
[0].type
= C_STRING
;
158 sprintf(buf
, "%d", params
->w
);
159 ret
[0].sval
= dupstr(buf
);
162 ret
[1].name
= "Height";
163 ret
[1].type
= C_STRING
;
164 sprintf(buf
, "%d", params
->h
);
165 ret
[1].sval
= dupstr(buf
);
168 ret
[2].name
= "Rotating block size";
169 ret
[2].type
= C_STRING
;
170 sprintf(buf
, "%d", params
->n
);
171 ret
[2].sval
= dupstr(buf
);
174 ret
[3].name
= "One number per row";
175 ret
[3].type
= C_BOOLEAN
;
177 ret
[3].ival
= params
->rowsonly
;
179 ret
[4].name
= "Orientation matters";
180 ret
[4].type
= C_BOOLEAN
;
182 ret
[4].ival
= params
->orientable
;
184 ret
[5].name
= "Number of shuffling moves";
185 ret
[5].type
= C_STRING
;
186 sprintf(buf
, "%d", params
->movetarget
);
187 ret
[5].sval
= dupstr(buf
);
198 static game_params
*custom_params(config_item
*cfg
)
200 game_params
*ret
= snew(game_params
);
202 ret
->w
= atoi(cfg
[0].sval
);
203 ret
->h
= atoi(cfg
[1].sval
);
204 ret
->n
= atoi(cfg
[2].sval
);
205 ret
->rowsonly
= cfg
[3].ival
;
206 ret
->orientable
= cfg
[4].ival
;
207 ret
->movetarget
= atoi(cfg
[5].sval
);
212 static char *validate_params(game_params
*params
, int full
)
215 return "Rotating block size must be at least two";
216 if (params
->w
< params
->n
)
217 return "Width must be at least the rotating block size";
218 if (params
->h
< params
->n
)
219 return "Height must be at least the rotating block size";
224 * This function actually performs a rotation on a grid. The `x'
225 * and `y' coordinates passed in are the coordinates of the _top
226 * left corner_ of the rotated region. (Using the centre would have
227 * involved half-integers and been annoyingly fiddly. Clicking in
228 * the centre is good for a user interface, but too inconvenient to
231 static void do_rotate(int *grid
, int w
, int h
, int n
, int orientable
,
232 int x
, int y
, int dir
)
236 assert(x
>= 0 && x
+n
<= w
);
237 assert(y
>= 0 && y
+n
<= h
);
240 return; /* nothing to do */
242 grid
+= y
*w
+x
; /* translate region to top corner */
245 * If we were leaving the result of the rotation in a separate
246 * grid, the simple thing to do would be to loop over each
247 * square within the rotated region and assign it from its
248 * source square. However, to do it in place without taking
249 * O(n^2) memory, we need to be marginally more clever. What
250 * I'm going to do is loop over about one _quarter_ of the
251 * rotated region and permute each element within that quarter
252 * with its rotational coset.
254 * The size of the region I need to loop over is (n+1)/2 by
255 * n/2, which is an obvious exact quarter for even n and is a
256 * rectangle for odd n. (For odd n, this technique leaves out
257 * one element of the square, which is of course the central
258 * one that never moves anyway.)
260 for (i
= 0; i
< (n
+1)/2; i
++) {
261 for (j
= 0; j
< n
/2; j
++) {
268 p
[2] = (n
-j
-1)*w
+(n
-i
-1);
271 for (k
= 0; k
< 4; k
++)
274 for (k
= 0; k
< 4; k
++) {
275 int v
= g
[(k
+dir
) & 3];
277 v
^= ((v
+dir
) ^ v
) & 3; /* alter orientation */
284 * Don't forget the orientation on the centre square, if n is
287 if (orientable
&& (n
& 1)) {
288 int v
= grid
[n
/2*(w
+1)];
289 v
^= ((v
+dir
) ^ v
) & 3; /* alter orientation */
294 static int grid_complete(int *grid
, int wh
, int orientable
)
298 for (i
= 1; i
< wh
; i
++)
299 if (grid
[i
] < grid
[i
-1])
302 for (i
= 0; i
< wh
; i
++)
309 static char *new_game_desc(game_params
*params
, random_state
*rs
,
310 char **aux
, int interactive
)
313 int w
= params
->w
, h
= params
->h
, n
= params
->n
, wh
= w
*h
;
320 * Set up a solved grid.
322 grid
= snewn(wh
, int);
323 for (i
= 0; i
< wh
; i
++)
324 grid
[i
] = ((params
->rowsonly
? i
/w
: i
) + 1) * 4;
327 * Shuffle it. This game is complex enough that I don't feel up
328 * to analysing its full symmetry properties (particularly at
329 * n=4 and above!), so I'm going to do it the pedestrian way
330 * and simply shuffle the grid by making a long sequence of
331 * randomly chosen moves.
333 total_moves
= params
->movetarget
;
335 /* Add a random move to avoid parity issues. */
336 total_moves
= w
*h
*n
*n
*2 + random_upto(rs
, 2);
340 int rw
, rh
; /* w/h of rotation centre space */
344 prevmoves
= snewn(rw
* rh
, int);
345 for (i
= 0; i
< rw
* rh
; i
++)
348 for (i
= 0; i
< total_moves
; i
++) {
349 int x
, y
, r
, oldtotal
, newtotal
, dx
, dy
;
352 x
= random_upto(rs
, w
- n
+ 1);
353 y
= random_upto(rs
, h
- n
+ 1);
354 r
= 2 * random_upto(rs
, 2) - 1;
357 * See if any previous rotations has happened at
358 * this point which nothing has overlapped since.
359 * If so, ensure we haven't either undone a
360 * previous move or repeated one so many times that
361 * it turns into fewer moves in the inverse
362 * direction (i.e. three identical rotations).
364 oldtotal
= prevmoves
[y
*rw
+x
];
365 newtotal
= oldtotal
+ r
;
368 * Special case here for w==h==n, in which case
369 * there is actually no way to _avoid_ all moves
370 * repeating or undoing previous ones.
372 } while ((w
!= n
|| h
!= n
) &&
373 (abs(newtotal
) < abs(oldtotal
) || abs(newtotal
) > 2));
375 do_rotate(grid
, w
, h
, n
, params
->orientable
, x
, y
, r
);
378 * Log the rotation we've just performed at this point,
379 * for inversion detection in the next move.
381 * Also zero a section of the prevmoves array, because
382 * any rotation area which _overlaps_ this one is now
383 * entirely safe to perform further moves in.
385 * Two rotation areas overlap if their top left
386 * coordinates differ by strictly less than n in both
389 prevmoves
[y
*rw
+x
] += r
;
390 for (dy
= -n
+1; dy
<= n
-1; dy
++) {
391 if (y
+ dy
< 0 || y
+ dy
>= rh
)
393 for (dx
= -n
+1; dx
<= n
-1; dx
++) {
394 if (x
+ dx
< 0 || x
+ dx
>= rw
)
396 if (dx
== 0 && dy
== 0)
398 prevmoves
[(y
+dy
)*rw
+(x
+dx
)] = 0;
405 } while (grid_complete(grid
, wh
, params
->orientable
));
408 * Now construct the game description, by describing the grid
409 * as a simple sequence of integers. They're comma-separated,
410 * unless the puzzle is orientable in which case they're
411 * separated by orientation letters `u', `d', `l' and `r'.
415 for (i
= 0; i
< wh
; i
++) {
419 k
= sprintf(buf
, "%d%c", grid
[i
] / 4,
420 (char)(params
->orientable
? "uldr"[grid
[i
] & 3] : ','));
422 ret
= sresize(ret
, retlen
+ k
+ 1, char);
423 strcpy(ret
+ retlen
, buf
);
426 if (!params
->orientable
)
427 ret
[retlen
-1] = '\0'; /* delete last comma */
433 static char *validate_desc(game_params
*params
, char *desc
)
436 int w
= params
->w
, h
= params
->h
, wh
= w
*h
;
441 for (i
= 0; i
< wh
; i
++) {
442 if (*p
< '0' || *p
> '9')
443 return "Not enough numbers in string";
444 while (*p
>= '0' && *p
<= '9')
446 if (!params
->orientable
&& i
< wh
-1) {
448 return "Expected comma after number";
449 } else if (params
->orientable
&& i
< wh
) {
450 if (*p
!= 'l' && *p
!= 'r' && *p
!= 'u' && *p
!= 'd')
451 return "Expected orientation letter after number";
452 } else if (i
== wh
-1 && *p
) {
453 return "Excess junk at end of string";
456 if (*p
) p
++; /* eat comma */
462 static game_state
*new_game(midend
*me
, game_params
*params
, char *desc
)
464 game_state
*state
= snew(game_state
);
465 int w
= params
->w
, h
= params
->h
, n
= params
->n
, wh
= w
*h
;
472 state
->orientable
= params
->orientable
;
473 state
->completed
= 0;
474 state
->used_solve
= FALSE
;
475 state
->movecount
= 0;
476 state
->movetarget
= params
->movetarget
;
477 state
->lastx
= state
->lasty
= state
->lastr
= -1;
479 state
->grid
= snewn(wh
, int);
483 for (i
= 0; i
< wh
; i
++) {
484 state
->grid
[i
] = 4 * atoi(p
);
485 while (*p
>= '0' && *p
<= '9')
488 if (params
->orientable
) {
490 case 'l': state
->grid
[i
] |= 1; break;
491 case 'd': state
->grid
[i
] |= 2; break;
492 case 'r': state
->grid
[i
] |= 3; break;
502 static game_state
*dup_game(game_state
*state
)
504 game_state
*ret
= snew(game_state
);
509 ret
->orientable
= state
->orientable
;
510 ret
->completed
= state
->completed
;
511 ret
->movecount
= state
->movecount
;
512 ret
->movetarget
= state
->movetarget
;
513 ret
->lastx
= state
->lastx
;
514 ret
->lasty
= state
->lasty
;
515 ret
->lastr
= state
->lastr
;
516 ret
->used_solve
= state
->used_solve
;
518 ret
->grid
= snewn(ret
->w
* ret
->h
, int);
519 memcpy(ret
->grid
, state
->grid
, ret
->w
* ret
->h
* sizeof(int));
524 static void free_game(game_state
*state
)
530 static int compare_int(const void *av
, const void *bv
)
532 const int *a
= (const int *)av
;
533 const int *b
= (const int *)bv
;
542 static char *solve_game(game_state
*state
, game_state
*currstate
,
543 char *aux
, char **error
)
548 static int game_can_format_as_text_now(game_params
*params
)
553 static char *game_text_format(game_state
*state
)
555 char *ret
, *p
, buf
[80];
556 int i
, x
, y
, col
, o
, maxlen
;
559 * First work out how many characters we need to display each
560 * number. We're pretty flexible on grid contents here, so we
561 * have to scan the entire grid.
564 for (i
= 0; i
< state
->w
* state
->h
; i
++) {
565 x
= sprintf(buf
, "%d", state
->grid
[i
] / 4);
566 if (col
< x
) col
= x
;
568 o
= (state
->orientable
? 1 : 0);
571 * Now we know the exact total size of the grid we're going to
572 * produce: it's got h rows, each containing w lots of col+o,
573 * w-1 spaces and a trailing newline.
575 maxlen
= state
->h
* state
->w
* (col
+o
+1);
577 ret
= snewn(maxlen
+1, char);
580 for (y
= 0; y
< state
->h
; y
++) {
581 for (x
= 0; x
< state
->w
; x
++) {
582 int v
= state
->grid
[state
->w
*y
+x
];
583 sprintf(buf
, "%*d", col
, v
/4);
587 *p
++ = "^<v>"[v
& 3];
595 assert(p
- ret
== maxlen
);
605 static game_ui
*new_ui(game_state
*state
)
607 game_ui
*ui
= snew(game_ui
);
611 ui
->cur_visible
= FALSE
;
616 static void free_ui(game_ui
*ui
)
621 static char *encode_ui(game_ui
*ui
)
626 static void decode_ui(game_ui
*ui
, char *encoding
)
630 static void game_changed_state(game_ui
*ui
, game_state
*oldstate
,
631 game_state
*newstate
)
635 struct game_drawstate
{
643 static char *interpret_move(game_state
*state
, game_ui
*ui
, game_drawstate
*ds
,
644 int x
, int y
, int button
)
646 int w
= state
->w
, h
= state
->h
, n
= state
->n
/* , wh = w*h */;
650 button
= button
& (~MOD_MASK
| MOD_NUM_KEYPAD
);
652 if (IS_CURSOR_MOVE(button
)) {
653 if (button
== CURSOR_LEFT
&& ui
->cur_x
> 0)
655 if (button
== CURSOR_RIGHT
&& (ui
->cur_x
+n
) < (w
))
657 if (button
== CURSOR_UP
&& ui
->cur_y
> 0)
659 if (button
== CURSOR_DOWN
&& (ui
->cur_y
+n
) < (h
))
665 if (button
== LEFT_BUTTON
|| button
== RIGHT_BUTTON
) {
667 * Determine the coordinates of the click. We offset by n-1
668 * half-blocks so that the user must click at the centre of
669 * a rotation region rather than at the corner.
671 x
-= (n
-1) * TILE_SIZE
/ 2;
672 y
-= (n
-1) * TILE_SIZE
/ 2;
675 dir
= (button
== LEFT_BUTTON
? 1 : -1);
676 if (x
< 0 || x
> w
-n
|| y
< 0 || y
> h
-n
)
679 } else if (IS_CURSOR_SELECT(button
)) {
680 if (ui
->cur_visible
) {
683 dir
= (button
== CURSOR_SELECT2
) ? -1 : +1;
688 } else if (button
== 'a' || button
== 'A' || button
==MOD_NUM_KEYPAD
+'7') {
690 dir
= (button
== 'A' ? -1 : +1);
691 } else if (button
== 'b' || button
== 'B' || button
==MOD_NUM_KEYPAD
+'9') {
694 dir
= (button
== 'B' ? -1 : +1);
695 } else if (button
== 'c' || button
== 'C' || button
==MOD_NUM_KEYPAD
+'1') {
698 dir
= (button
== 'C' ? -1 : +1);
699 } else if (button
== 'd' || button
== 'D' || button
==MOD_NUM_KEYPAD
+'3') {
702 dir
= (button
== 'D' ? -1 : +1);
703 } else if (button
==MOD_NUM_KEYPAD
+'8' && (w
-n
) % 2 == 0) {
707 } else if (button
==MOD_NUM_KEYPAD
+'2' && (w
-n
) % 2 == 0) {
711 } else if (button
==MOD_NUM_KEYPAD
+'4' && (h
-n
) % 2 == 0) {
715 } else if (button
==MOD_NUM_KEYPAD
+'6' && (h
-n
) % 2 == 0) {
719 } else if (button
==MOD_NUM_KEYPAD
+'5' && (w
-n
) % 2 == 0 && (h
-n
) % 2 == 0){
724 return NULL
; /* no move to be made */
728 * If we reach here, we have a valid move.
730 sprintf(buf
, "M%d,%d,%d", x
, y
, dir
);
734 static game_state
*execute_move(game_state
*from
, char *move
)
737 int w
= from
->w
, h
= from
->h
, n
= from
->n
, wh
= w
*h
;
740 if (!strcmp(move
, "S")) {
742 ret
= dup_game(from
);
745 * Simply replace the grid with a solved one. For this game,
746 * this isn't a useful operation for actually telling the user
747 * what they should have done, but it is useful for
748 * conveniently being able to get hold of a clean state from
749 * which to practise manoeuvres.
751 qsort(ret
->grid
, ret
->w
*ret
->h
, sizeof(int), compare_int
);
752 for (i
= 0; i
< ret
->w
*ret
->h
; i
++)
754 ret
->used_solve
= TRUE
;
755 ret
->completed
= ret
->movecount
= 1;
760 if (move
[0] != 'M' ||
761 sscanf(move
+1, "%d,%d,%d", &x
, &y
, &dir
) != 3 ||
762 x
< 0 || y
< 0 || x
> from
->w
- n
|| y
> from
->h
- n
)
763 return NULL
; /* can't parse this move string */
765 ret
= dup_game(from
);
767 do_rotate(ret
->grid
, w
, h
, n
, ret
->orientable
, x
, y
, dir
);
773 * See if the game has been completed. To do this we simply
774 * test that the grid contents are in increasing order.
776 if (!ret
->completed
&& grid_complete(ret
->grid
, wh
, ret
->orientable
))
777 ret
->completed
= ret
->movecount
;
781 /* ----------------------------------------------------------------------
785 static void game_compute_size(game_params
*params
, int tilesize
,
788 /* Ick: fake up `ds->tilesize' for macro expansion purposes */
789 struct { int tilesize
; } ads
, *ds
= &ads
;
790 ads
.tilesize
= tilesize
;
792 *x
= TILE_SIZE
* params
->w
+ 2 * BORDER
;
793 *y
= TILE_SIZE
* params
->h
+ 2 * BORDER
;
796 static void game_set_size(drawing
*dr
, game_drawstate
*ds
,
797 game_params
*params
, int tilesize
)
799 ds
->tilesize
= tilesize
;
802 static float *game_colours(frontend
*fe
, int *ncolours
)
804 float *ret
= snewn(3 * NCOLOURS
, float);
807 game_mkhighlight(fe
, ret
, COL_BACKGROUND
, COL_HIGHLIGHT
, COL_LOWLIGHT
);
809 /* cursor is light-background with a red tinge. */
810 ret
[COL_HIGHCURSOR
* 3 + 0] = ret
[COL_BACKGROUND
* 3 + 0] * 1.0F
;
811 ret
[COL_HIGHCURSOR
* 3 + 1] = ret
[COL_BACKGROUND
* 3 + 1] * 0.5F
;
812 ret
[COL_HIGHCURSOR
* 3 + 2] = ret
[COL_BACKGROUND
* 3 + 2] * 0.5F
;
814 for (i
= 0; i
< 3; i
++) {
815 ret
[COL_HIGHLIGHT_GENTLE
* 3 + i
] = ret
[COL_BACKGROUND
* 3 + i
] * 1.1F
;
816 ret
[COL_LOWLIGHT_GENTLE
* 3 + i
] = ret
[COL_BACKGROUND
* 3 + i
] * 0.9F
;
817 ret
[COL_TEXT
* 3 + i
] = 0.0;
818 ret
[COL_LOWCURSOR
* 3 + i
] = ret
[COL_HIGHCURSOR
* 3 + i
] * 0.6F
;
821 *ncolours
= NCOLOURS
;
825 static game_drawstate
*game_new_drawstate(drawing
*dr
, game_state
*state
)
827 struct game_drawstate
*ds
= snew(struct game_drawstate
);
833 ds
->bgcolour
= COL_BACKGROUND
;
834 ds
->grid
= snewn(ds
->w
*ds
->h
, int);
835 ds
->tilesize
= 0; /* haven't decided yet */
836 for (i
= 0; i
< ds
->w
*ds
->h
; i
++)
838 ds
->cur_x
= ds
->cur_y
= -state
->n
;
843 static void game_free_drawstate(drawing
*dr
, game_drawstate
*ds
)
850 int cx
, cy
, cw
, ch
; /* clip region */
851 int ox
, oy
; /* rotation origin */
852 float c
, s
; /* cos and sin of rotation angle */
853 int lc
, rc
, tc
, bc
; /* colours of tile edges */
856 static void rotate(int *xy
, struct rotation
*rot
)
859 float xf
= (float)xy
[0] - rot
->ox
, yf
= (float)xy
[1] - rot
->oy
;
862 xf2
= rot
->c
* xf
+ rot
->s
* yf
;
863 yf2
= - rot
->s
* xf
+ rot
->c
* yf
;
865 xy
[0] = (int)(xf2
+ rot
->ox
+ 0.5); /* round to nearest */
866 xy
[1] = (int)(yf2
+ rot
->oy
+ 0.5); /* round to nearest */
875 static void draw_tile(drawing
*dr
, game_drawstate
*ds
, game_state
*state
,
876 int x
, int y
, int tile
, int flash_colour
,
877 struct rotation
*rot
, unsigned cedges
)
883 * If we've been passed a rotation region but we're drawing a
884 * tile which is outside it, we must draw it normally. This can
885 * occur if we're cleaning up after a completion flash while a
886 * new move is also being made.
888 if (rot
&& (x
< rot
->cx
|| y
< rot
->cy
||
889 x
>= rot
->cx
+rot
->cw
|| y
>= rot
->cy
+rot
->ch
))
893 clip(dr
, rot
->cx
, rot
->cy
, rot
->cw
, rot
->ch
);
896 * We must draw each side of the tile's highlight separately,
897 * because in some cases (during rotation) they will all need
898 * to be different colours.
901 /* The centre point is common to all sides. */
902 coords
[4] = x
+ TILE_SIZE
/ 2;
903 coords
[5] = y
+ TILE_SIZE
/ 2;
904 rotate(coords
+4, rot
);
907 coords
[0] = x
+ TILE_SIZE
- 1;
908 coords
[1] = y
+ TILE_SIZE
- 1;
909 rotate(coords
+0, rot
);
910 coords
[2] = x
+ TILE_SIZE
- 1;
912 rotate(coords
+2, rot
);
913 draw_polygon(dr
, coords
, 3, rot
? rot
->rc
: COL_LOWLIGHT
,
914 rot
? rot
->rc
: (cedges
& CUR_RIGHT
) ? COL_LOWCURSOR
: COL_LOWLIGHT
);
918 coords
[3] = y
+ TILE_SIZE
- 1;
919 rotate(coords
+2, rot
);
920 draw_polygon(dr
, coords
, 3, rot
? rot
->bc
: COL_LOWLIGHT
,
921 rot
? rot
->bc
: (cedges
& CUR_BOTTOM
) ? COL_LOWCURSOR
: COL_LOWLIGHT
);
926 rotate(coords
+0, rot
);
927 draw_polygon(dr
, coords
, 3, rot
? rot
->lc
: COL_HIGHLIGHT
,
928 rot
? rot
->lc
: (cedges
& CUR_LEFT
) ? COL_HIGHCURSOR
: COL_HIGHLIGHT
);
931 coords
[2] = x
+ TILE_SIZE
- 1;
933 rotate(coords
+2, rot
);
934 draw_polygon(dr
, coords
, 3, rot
? rot
->tc
: COL_HIGHLIGHT
,
935 rot
? rot
->tc
: (cedges
& CUR_TOP
) ? COL_HIGHCURSOR
: COL_HIGHLIGHT
);
938 * Now the main blank area in the centre of the tile.
941 coords
[0] = x
+ HIGHLIGHT_WIDTH
;
942 coords
[1] = y
+ HIGHLIGHT_WIDTH
;
943 rotate(coords
+0, rot
);
944 coords
[2] = x
+ HIGHLIGHT_WIDTH
;
945 coords
[3] = y
+ TILE_SIZE
- 1 - HIGHLIGHT_WIDTH
;
946 rotate(coords
+2, rot
);
947 coords
[4] = x
+ TILE_SIZE
- 1 - HIGHLIGHT_WIDTH
;
948 coords
[5] = y
+ TILE_SIZE
- 1 - HIGHLIGHT_WIDTH
;
949 rotate(coords
+4, rot
);
950 coords
[6] = x
+ TILE_SIZE
- 1 - HIGHLIGHT_WIDTH
;
951 coords
[7] = y
+ HIGHLIGHT_WIDTH
;
952 rotate(coords
+6, rot
);
953 draw_polygon(dr
, coords
, 4, flash_colour
, flash_colour
);
955 draw_rect(dr
, x
+ HIGHLIGHT_WIDTH
, y
+ HIGHLIGHT_WIDTH
,
956 TILE_SIZE
- 2*HIGHLIGHT_WIDTH
, TILE_SIZE
- 2*HIGHLIGHT_WIDTH
,
961 * Next, the triangles for orientation.
963 if (state
->orientable
) {
964 int xdx
, xdy
, ydx
, ydy
;
965 int cx
, cy
, displ
, displ2
;
979 default /* case 3 */:
985 cx
= x
+ TILE_SIZE
/ 2;
986 cy
= y
+ TILE_SIZE
/ 2;
987 displ
= TILE_SIZE
/ 2 - HIGHLIGHT_WIDTH
- 2;
988 displ2
= TILE_SIZE
/ 3 - HIGHLIGHT_WIDTH
;
990 coords
[0] = cx
- displ
* xdx
+ displ2
* ydx
;
991 coords
[1] = cy
- displ
* xdy
+ displ2
* ydy
;
992 rotate(coords
+0, rot
);
993 coords
[2] = cx
+ displ
* xdx
+ displ2
* ydx
;
994 coords
[3] = cy
+ displ
* xdy
+ displ2
* ydy
;
995 rotate(coords
+2, rot
);
996 coords
[4] = cx
- displ
* ydx
;
997 coords
[5] = cy
- displ
* ydy
;
998 rotate(coords
+4, rot
);
999 draw_polygon(dr
, coords
, 3, COL_LOWLIGHT_GENTLE
, COL_LOWLIGHT_GENTLE
);
1002 coords
[0] = x
+ TILE_SIZE
/2;
1003 coords
[1] = y
+ TILE_SIZE
/2;
1004 rotate(coords
+0, rot
);
1005 sprintf(str
, "%d", tile
/ 4);
1006 draw_text(dr
, coords
[0], coords
[1],
1007 FONT_VARIABLE
, TILE_SIZE
/3, ALIGN_VCENTRE
| ALIGN_HCENTRE
,
1013 draw_update(dr
, x
, y
, TILE_SIZE
, TILE_SIZE
);
1016 static int highlight_colour(float angle
)
1020 COL_LOWLIGHT_GENTLE
,
1021 COL_LOWLIGHT_GENTLE
,
1022 COL_LOWLIGHT_GENTLE
,
1023 COL_HIGHLIGHT_GENTLE
,
1024 COL_HIGHLIGHT_GENTLE
,
1025 COL_HIGHLIGHT_GENTLE
,
1036 COL_HIGHLIGHT_GENTLE
,
1037 COL_HIGHLIGHT_GENTLE
,
1038 COL_HIGHLIGHT_GENTLE
,
1039 COL_LOWLIGHT_GENTLE
,
1040 COL_LOWLIGHT_GENTLE
,
1041 COL_LOWLIGHT_GENTLE
,
1053 return colours
[(int)((angle
+ 2*PI
) / (PI
/16)) & 31];
1056 static float game_anim_length(game_state
*oldstate
, game_state
*newstate
,
1057 int dir
, game_ui
*ui
)
1059 return (float)(ANIM_PER_BLKSIZE_UNIT
* sqrt(newstate
->n
-1));
1062 static float game_flash_length(game_state
*oldstate
, game_state
*newstate
,
1063 int dir
, game_ui
*ui
)
1065 if (!oldstate
->completed
&& newstate
->completed
&&
1066 !oldstate
->used_solve
&& !newstate
->used_solve
)
1067 return 2 * FLASH_FRAME
;
1072 static int game_status(game_state
*state
)
1074 return state
->completed
? +1 : 0;
1077 static void game_redraw(drawing
*dr
, game_drawstate
*ds
, game_state
*oldstate
,
1078 game_state
*state
, int dir
, game_ui
*ui
,
1079 float animtime
, float flashtime
)
1082 struct rotation srot
, *rot
;
1083 int lastx
= -1, lasty
= -1, lastr
= -1;
1084 int cx
, cy
, cmoved
= 0, n
= state
->n
;
1086 cx
= ui
->cur_visible
? ui
->cur_x
: -state
->n
;
1087 cy
= ui
->cur_visible
? ui
->cur_y
: -state
->n
;
1088 if (cx
!= ds
->cur_x
|| cy
!= ds
->cur_y
)
1091 if (flashtime
> 0) {
1092 int frame
= (int)(flashtime
/ FLASH_FRAME
);
1093 bgcolour
= (frame
% 2 ? COL_LOWLIGHT
: COL_HIGHLIGHT
);
1095 bgcolour
= COL_BACKGROUND
;
1101 TILE_SIZE
* state
->w
+ 2 * BORDER
,
1102 TILE_SIZE
* state
->h
+ 2 * BORDER
, COL_BACKGROUND
);
1103 draw_update(dr
, 0, 0,
1104 TILE_SIZE
* state
->w
+ 2 * BORDER
,
1105 TILE_SIZE
* state
->h
+ 2 * BORDER
);
1108 * Recessed area containing the whole puzzle.
1110 coords
[0] = COORD(state
->w
) + HIGHLIGHT_WIDTH
- 1;
1111 coords
[1] = COORD(state
->h
) + HIGHLIGHT_WIDTH
- 1;
1112 coords
[2] = COORD(state
->w
) + HIGHLIGHT_WIDTH
- 1;
1113 coords
[3] = COORD(0) - HIGHLIGHT_WIDTH
;
1114 coords
[4] = coords
[2] - TILE_SIZE
;
1115 coords
[5] = coords
[3] + TILE_SIZE
;
1116 coords
[8] = COORD(0) - HIGHLIGHT_WIDTH
;
1117 coords
[9] = COORD(state
->h
) + HIGHLIGHT_WIDTH
- 1;
1118 coords
[6] = coords
[8] + TILE_SIZE
;
1119 coords
[7] = coords
[9] - TILE_SIZE
;
1120 draw_polygon(dr
, coords
, 5, COL_HIGHLIGHT
, COL_HIGHLIGHT
);
1122 coords
[1] = COORD(0) - HIGHLIGHT_WIDTH
;
1123 coords
[0] = COORD(0) - HIGHLIGHT_WIDTH
;
1124 draw_polygon(dr
, coords
, 5, COL_LOWLIGHT
, COL_LOWLIGHT
);
1130 * If we're drawing any rotated tiles, sort out the rotation
1131 * parameters, and also zap the rotation region to the
1132 * background colour before doing anything else.
1136 float anim_max
= game_anim_length(oldstate
, state
, dir
, ui
);
1139 lastx
= state
->lastx
;
1140 lasty
= state
->lasty
;
1141 lastr
= state
->lastr
;
1143 lastx
= oldstate
->lastx
;
1144 lasty
= oldstate
->lasty
;
1145 lastr
= -oldstate
->lastr
;
1149 rot
->cx
= COORD(lastx
);
1150 rot
->cy
= COORD(lasty
);
1151 rot
->cw
= rot
->ch
= TILE_SIZE
* state
->n
;
1152 rot
->ox
= rot
->cx
+ rot
->cw
/2;
1153 rot
->oy
= rot
->cy
+ rot
->ch
/2;
1154 angle
= (float)((-PI
/2 * lastr
) * (1.0 - animtime
/ anim_max
));
1155 rot
->c
= (float)cos(angle
);
1156 rot
->s
= (float)sin(angle
);
1159 * Sort out the colours of the various sides of the tile.
1161 rot
->lc
= highlight_colour((float)PI
+ angle
);
1162 rot
->rc
= highlight_colour(angle
);
1163 rot
->tc
= highlight_colour((float)(PI
/2.0) + angle
);
1164 rot
->bc
= highlight_colour((float)(-PI
/2.0) + angle
);
1166 draw_rect(dr
, rot
->cx
, rot
->cy
, rot
->cw
, rot
->ch
, bgcolour
);
1171 * Now draw each tile.
1173 for (i
= 0; i
< state
->w
* state
->h
; i
++) {
1175 int tx
= i
% state
->w
, ty
= i
/ state
->w
;
1178 * Figure out what should be displayed at this location.
1179 * Usually it will be state->grid[i], unless we're in the
1180 * middle of animating an actual rotation and this cell is
1181 * within the rotation region, in which case we set -1
1184 if (oldstate
&& lastx
>= 0 && lasty
>= 0 &&
1185 tx
>= lastx
&& tx
< lastx
+ state
->n
&&
1186 ty
>= lasty
&& ty
< lasty
+ state
->n
)
1192 /* cursor has moved (or changed visibility)... */
1193 if (tx
== cx
|| tx
== cx
+n
-1 || ty
== cy
|| ty
== cy
+n
-1)
1194 cc
= 1; /* ...we're on new cursor, redraw */
1195 if (tx
== ds
->cur_x
|| tx
== ds
->cur_x
+n
-1 ||
1196 ty
== ds
->cur_y
|| ty
== ds
->cur_y
+n
-1)
1197 cc
= 1; /* ...we were on old cursor, redraw */
1200 if (ds
->bgcolour
!= bgcolour
|| /* always redraw when flashing */
1201 ds
->grid
[i
] != t
|| ds
->grid
[i
] == -1 || t
== -1 || cc
) {
1202 int x
= COORD(tx
), y
= COORD(ty
);
1203 unsigned cedges
= 0;
1205 if (tx
== cx
&& ty
>= cy
&& ty
<= cy
+n
-1) cedges
|= CUR_LEFT
;
1206 if (ty
== cy
&& tx
>= cx
&& tx
<= cx
+n
-1) cedges
|= CUR_TOP
;
1207 if (tx
== cx
+n
-1 && ty
>= cy
&& ty
<= cy
+n
-1) cedges
|= CUR_RIGHT
;
1208 if (ty
== cy
+n
-1 && tx
>= cx
&& tx
<= cx
+n
-1) cedges
|= CUR_BOTTOM
;
1210 draw_tile(dr
, ds
, state
, x
, y
, state
->grid
[i
], bgcolour
, rot
, cedges
);
1214 ds
->bgcolour
= bgcolour
;
1215 ds
->cur_x
= cx
; ds
->cur_y
= cy
;
1218 * Update the status bar.
1221 char statusbuf
[256];
1224 * Don't show the new status until we're also showing the
1225 * new _state_ - after the game animation is complete.
1230 if (state
->used_solve
)
1231 sprintf(statusbuf
, "Moves since auto-solve: %d",
1232 state
->movecount
- state
->completed
);
1234 sprintf(statusbuf
, "%sMoves: %d",
1235 (state
->completed
? "COMPLETED! " : ""),
1236 (state
->completed
? state
->completed
: state
->movecount
));
1237 if (state
->movetarget
)
1238 sprintf(statusbuf
+strlen(statusbuf
), " (target %d)",
1242 status_bar(dr
, statusbuf
);
1246 static int game_timing_state(game_state
*state
, game_ui
*ui
)
1251 static void game_print_size(game_params
*params
, float *x
, float *y
)
1255 static void game_print(drawing
*dr
, game_state
*state
, int tilesize
)
1260 #define thegame twiddle
1263 const struct game thegame
= {
1264 "Twiddle", "games.twiddle", "twiddle",
1271 TRUE
, game_configure
, custom_params
,
1279 TRUE
, game_can_format_as_text_now
, game_text_format
,
1287 PREFERRED_TILE_SIZE
, game_compute_size
, game_set_size
,
1290 game_free_drawstate
,
1295 FALSE
, FALSE
, game_print_size
, game_print
,
1296 TRUE
, /* wants_statusbar */
1297 FALSE
, game_timing_state
,
1301 /* vim: set shiftwidth=4 tabstop=8: */