chiark / gitweb /
Add a menu bar, in both Windows and GTK. In particular, game modules
[sgt-puzzles.git] / net.c
1 /*
2  * net.c: Net game.
3  */
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <assert.h>
9 #include <math.h>
10
11 #include "puzzles.h"
12 #include "tree234.h"
13
14 #define PI 3.141592653589793238462643383279502884197169399
15
16 #define MATMUL(xr,yr,m,x,y) do { \
17     float rx, ry, xx = (x), yy = (y), *mat = (m); \
18     rx = mat[0] * xx + mat[2] * yy; \
19     ry = mat[1] * xx + mat[3] * yy; \
20     (xr) = rx; (yr) = ry; \
21 } while (0)
22
23 /* Direction and other bitfields */
24 #define R 0x01
25 #define U 0x02
26 #define L 0x04
27 #define D 0x08
28 #define LOCKED 0x10
29 #define ACTIVE 0x20
30 /* Corner flags go in the barriers array */
31 #define RU 0x10
32 #define UL 0x20
33 #define LD 0x40
34 #define DR 0x80
35
36 /* Rotations: Anticlockwise, Clockwise, Flip, general rotate */
37 #define A(x) ( (((x) & 0x07) << 1) | (((x) & 0x08) >> 3) )
38 #define C(x) ( (((x) & 0x0E) >> 1) | (((x) & 0x01) << 3) )
39 #define F(x) ( (((x) & 0x0C) >> 2) | (((x) & 0x03) << 2) )
40 #define ROT(x, n) ( ((n)&3) == 0 ? (x) : \
41                     ((n)&3) == 1 ? A(x) : \
42                     ((n)&3) == 2 ? F(x) : C(x) )
43
44 /* X and Y displacements */
45 #define X(x) ( (x) == R ? +1 : (x) == L ? -1 : 0 )
46 #define Y(x) ( (x) == D ? +1 : (x) == U ? -1 : 0 )
47
48 /* Bit count */
49 #define COUNT(x) ( (((x) & 0x08) >> 3) + (((x) & 0x04) >> 2) + \
50                    (((x) & 0x02) >> 1) + ((x) & 0x01) )
51
52 #define TILE_SIZE 32
53 #define TILE_BORDER 1
54 #define WINDOW_OFFSET 16
55
56 #define ROTATE_TIME 0.1
57 #define FLASH_FRAME 0.05
58
59 enum {
60     COL_BACKGROUND,
61     COL_LOCKED,
62     COL_BORDER,
63     COL_WIRE,
64     COL_ENDPOINT,
65     COL_POWERED,
66     COL_BARRIER,
67     NCOLOURS
68 };
69
70 struct game_params {
71     int width;
72     int height;
73     int wrapping;
74     float barrier_probability;
75 };
76
77 struct game_state {
78     int width, height, cx, cy, wrapping, completed, last_rotate_dir;
79     unsigned char *tiles;
80     unsigned char *barriers;
81 };
82
83 #define OFFSET(x2,y2,x1,y1,dir,state) \
84     ( (x2) = ((x1) + (state)->width + X((dir))) % (state)->width, \
85       (y2) = ((y1) + (state)->height + Y((dir))) % (state)->height)
86
87 #define index(state, a, x, y) ( a[(y) * (state)->width + (x)] )
88 #define tile(state, x, y)     index(state, (state)->tiles, x, y)
89 #define barrier(state, x, y)  index(state, (state)->barriers, x, y)
90
91 struct xyd {
92     int x, y, direction;
93 };
94
95 static int xyd_cmp(void *av, void *bv) {
96     struct xyd *a = (struct xyd *)av;
97     struct xyd *b = (struct xyd *)bv;
98     if (a->x < b->x)
99         return -1;
100     if (a->x > b->x)
101         return +1;
102     if (a->y < b->y)
103         return -1;
104     if (a->y > b->y)
105         return +1;
106     if (a->direction < b->direction)
107         return -1;
108     if (a->direction > b->direction)
109         return +1;
110     return 0;
111 };
112
113 static struct xyd *new_xyd(int x, int y, int direction)
114 {
115     struct xyd *xyd = snew(struct xyd);
116     xyd->x = x;
117     xyd->y = y;
118     xyd->direction = direction;
119     return xyd;
120 }
121
122 /* ----------------------------------------------------------------------
123  * Manage game parameters.
124  */
125 game_params *default_params(void)
126 {
127     game_params *ret = snew(game_params);
128
129     ret->width = 5;
130     ret->height = 5;
131     ret->wrapping = FALSE;
132     ret->barrier_probability = 0.0;
133
134     return ret;
135 }
136
137 int game_fetch_preset(int i, char **name, game_params **params)
138 {
139     game_params *ret;
140     char str[80];
141     static const struct { int x, y, wrap; } values[] = {
142         {5, 5, FALSE},
143         {7, 7, FALSE},
144         {9, 9, FALSE},
145         {11, 11, FALSE},
146         {13, 11, FALSE},
147         {5, 5, TRUE},
148         {7, 7, TRUE},
149         {9, 9, TRUE},
150         {11, 11, TRUE},
151         {13, 11, TRUE},
152     };
153
154     if (i < 0 || i >= lenof(values))
155         return FALSE;
156
157     ret = snew(game_params);
158     ret->width = values[i].x;
159     ret->height = values[i].y;
160     ret->wrapping = values[i].wrap;
161     ret->barrier_probability = 0.0;
162
163     sprintf(str, "%dx%d%s", ret->width, ret->height,
164             ret->wrapping ? " wrapping" : "");
165
166     *name = dupstr(str);
167     *params = ret;
168     return TRUE;
169 }
170
171 void free_params(game_params *params)
172 {
173     sfree(params);
174 }
175
176 game_params *dup_params(game_params *params)
177 {
178     game_params *ret = snew(game_params);
179     *ret = *params;                    /* structure copy */
180     return ret;
181 }
182
183 /* ----------------------------------------------------------------------
184  * Randomly select a new game seed.
185  */
186
187 char *new_game_seed(game_params *params)
188 {
189     /*
190      * The full description of a Net game is far too large to
191      * encode directly in the seed, so by default we'll have to go
192      * for the simple approach of providing a random-number seed.
193      * 
194      * (This does not restrict me from _later on_ inventing a seed
195      * string syntax which can never be generated by this code -
196      * for example, strings beginning with a letter - allowing me
197      * to type in a precise game, and have new_game detect it and
198      * understand it and do something completely different.)
199      */
200     char buf[40];
201     sprintf(buf, "%d", rand());
202     return dupstr(buf);
203 }
204
205 /* ----------------------------------------------------------------------
206  * Construct an initial game state, given a seed and parameters.
207  */
208
209 game_state *new_game(game_params *params, char *seed)
210 {
211     random_state *rs;
212     game_state *state;
213     tree234 *possibilities, *barriers;
214     int w, h, x, y, nbarriers;
215
216     assert(params->width > 2);
217     assert(params->height > 2);
218
219     /*
220      * Create a blank game state.
221      */
222     state = snew(game_state);
223     w = state->width = params->width;
224     h = state->height = params->height;
225     state->cx = state->width / 2;
226     state->cy = state->height / 2;
227     state->wrapping = params->wrapping;
228     state->last_rotate_dir = +1;       /* *shrug* */
229     state->completed = FALSE;
230     state->tiles = snewn(state->width * state->height, unsigned char);
231     memset(state->tiles, 0, state->width * state->height);
232     state->barriers = snewn(state->width * state->height, unsigned char);
233     memset(state->barriers, 0, state->width * state->height);
234
235     /*
236      * Set up border barriers if this is a non-wrapping game.
237      */
238     if (!state->wrapping) {
239         for (x = 0; x < state->width; x++) {
240             barrier(state, x, 0) |= U;
241             barrier(state, x, state->height-1) |= D;
242         }
243         for (y = 0; y < state->height; y++) {
244             barrier(state, 0, y) |= L;
245             barrier(state, state->width-1, y) |= R;
246         }
247     }
248
249     /*
250      * Seed the internal random number generator.
251      */
252     rs = random_init(seed, strlen(seed));
253
254     /*
255      * Construct the unshuffled grid.
256      * 
257      * To do this, we simply start at the centre point, repeatedly
258      * choose a random possibility out of the available ways to
259      * extend a used square into an unused one, and do it. After
260      * extending the third line out of a square, we remove the
261      * fourth from the possibilities list to avoid any full-cross
262      * squares (which would make the game too easy because they
263      * only have one orientation).
264      * 
265      * The slightly worrying thing is the avoidance of full-cross
266      * squares. Can this cause our unsophisticated construction
267      * algorithm to paint itself into a corner, by getting into a
268      * situation where there are some unreached squares and the
269      * only way to reach any of them is to extend a T-piece into a
270      * full cross?
271      * 
272      * Answer: no it can't, and here's a proof.
273      * 
274      * Any contiguous group of such unreachable squares must be
275      * surrounded on _all_ sides by T-pieces pointing away from the
276      * group. (If not, then there is a square which can be extended
277      * into one of the `unreachable' ones, and so it wasn't
278      * unreachable after all.) In particular, this implies that
279      * each contiguous group of unreachable squares must be
280      * rectangular in shape (any deviation from that yields a
281      * non-T-piece next to an `unreachable' square).
282      * 
283      * So we have a rectangle of unreachable squares, with T-pieces
284      * forming a solid border around the rectangle. The corners of
285      * that border must be connected (since every tile connects all
286      * the lines arriving in it), and therefore the border must
287      * form a closed loop around the rectangle.
288      * 
289      * But this can't have happened in the first place, since we
290      * _know_ we've avoided creating closed loops! Hence, no such
291      * situation can ever arise, and the naive grid construction
292      * algorithm will guaranteeably result in a complete grid
293      * containing no unreached squares, no full crosses _and_ no
294      * closed loops. []
295      */
296     possibilities = newtree234(xyd_cmp);
297     
298     add234(possibilities, new_xyd(state->cx, state->cy, R));
299     add234(possibilities, new_xyd(state->cx, state->cy, U));
300     add234(possibilities, new_xyd(state->cx, state->cy, L));
301     add234(possibilities, new_xyd(state->cx, state->cy, D));
302
303     while (count234(possibilities) > 0) {
304         int i;
305         struct xyd *xyd;
306         int x1, y1, d1, x2, y2, d2, d;
307
308         /*
309          * Extract a randomly chosen possibility from the list.
310          */
311         i = random_upto(rs, count234(possibilities));
312         xyd = delpos234(possibilities, i);
313         x1 = xyd->x;
314         y1 = xyd->y;
315         d1 = xyd->direction;
316         sfree(xyd);
317
318         OFFSET(x2, y2, x1, y1, d1, state);
319         d2 = F(d1);
320 #ifdef DEBUG
321         printf("picked (%d,%d,%c) <-> (%d,%d,%c)\n",
322                x1, y1, "0RU3L567D9abcdef"[d1], x2, y2, "0RU3L567D9abcdef"[d2]);
323 #endif
324
325         /*
326          * Make the connection. (We should be moving to an as yet
327          * unused tile.)
328          */
329         tile(state, x1, y1) |= d1;
330         assert(tile(state, x2, y2) == 0);
331         tile(state, x2, y2) |= d2;
332
333         /*
334          * If we have created a T-piece, remove its last
335          * possibility.
336          */
337         if (COUNT(tile(state, x1, y1)) == 3) {
338             struct xyd xyd1, *xydp;
339
340             xyd1.x = x1;
341             xyd1.y = y1;
342             xyd1.direction = 0x0F ^ tile(state, x1, y1);
343
344             xydp = find234(possibilities, &xyd1, NULL);
345
346             if (xydp) {
347 #ifdef DEBUG
348                 printf("T-piece; removing (%d,%d,%c)\n",
349                        xydp->x, xydp->y, "0RU3L567D9abcdef"[xydp->direction]);
350 #endif
351                 del234(possibilities, xydp);
352                 sfree(xydp);
353             }
354         }
355
356         /*
357          * Remove all other possibilities that were pointing at the
358          * tile we've just moved into.
359          */
360         for (d = 1; d < 0x10; d <<= 1) {
361             int x3, y3, d3;
362             struct xyd xyd1, *xydp;
363
364             OFFSET(x3, y3, x2, y2, d, state);
365             d3 = F(d);
366
367             xyd1.x = x3;
368             xyd1.y = y3;
369             xyd1.direction = d3;
370
371             xydp = find234(possibilities, &xyd1, NULL);
372
373             if (xydp) {
374 #ifdef DEBUG
375                 printf("Loop avoidance; removing (%d,%d,%c)\n",
376                        xydp->x, xydp->y, "0RU3L567D9abcdef"[xydp->direction]);
377 #endif
378                 del234(possibilities, xydp);
379                 sfree(xydp);
380             }
381         }
382
383         /*
384          * Add new possibilities to the list for moving _out_ of
385          * the tile we have just moved into.
386          */
387         for (d = 1; d < 0x10; d <<= 1) {
388             int x3, y3;
389
390             if (d == d2)
391                 continue;              /* we've got this one already */
392
393             if (!state->wrapping) {
394                 if (d == U && y2 == 0)
395                     continue;
396                 if (d == D && y2 == state->height-1)
397                     continue;
398                 if (d == L && x2 == 0)
399                     continue;
400                 if (d == R && x2 == state->width-1)
401                     continue;
402             }
403
404             OFFSET(x3, y3, x2, y2, d, state);
405
406             if (tile(state, x3, y3))
407                 continue;              /* this would create a loop */
408
409 #ifdef DEBUG
410             printf("New frontier; adding (%d,%d,%c)\n",
411                    x2, y2, "0RU3L567D9abcdef"[d]);
412 #endif
413             add234(possibilities, new_xyd(x2, y2, d));
414         }
415     }
416     /* Having done that, we should have no possibilities remaining. */
417     assert(count234(possibilities) == 0);
418     freetree234(possibilities);
419
420     /*
421      * Now compute a list of the possible barrier locations.
422      */
423     barriers = newtree234(xyd_cmp);
424     for (y = 0; y < state->height; y++) {
425         for (x = 0; x < state->width; x++) {
426
427             if (!(tile(state, x, y) & R) &&
428                 (state->wrapping || x < state->width-1))
429                 add234(barriers, new_xyd(x, y, R));
430             if (!(tile(state, x, y) & D) &&
431                 (state->wrapping || y < state->height-1))
432                 add234(barriers, new_xyd(x, y, D));
433         }
434     }
435
436     /*
437      * Now shuffle the grid.
438      */
439     for (y = 0; y < state->height; y++) {
440         for (x = 0; x < state->width; x++) {
441             int orig = tile(state, x, y);
442             int rot = random_upto(rs, 4);
443             tile(state, x, y) = ROT(orig, rot);
444         }
445     }
446
447     /*
448      * And now choose barrier locations. (We carefully do this
449      * _after_ shuffling, so that changing the barrier rate in the
450      * params while keeping the game seed the same will give the
451      * same shuffled grid and _only_ change the barrier locations.
452      * Also the way we choose barrier locations, by repeatedly
453      * choosing one possibility from the list until we have enough,
454      * is designed to ensure that raising the barrier rate while
455      * keeping the seed the same will provide a superset of the
456      * previous barrier set - i.e. if you ask for 10 barriers, and
457      * then decide that's still too hard and ask for 20, you'll get
458      * the original 10 plus 10 more, rather than getting 20 new
459      * ones and the chance of remembering your first 10.)
460      */
461     nbarriers = params->barrier_probability * count234(barriers);
462     assert(nbarriers >= 0 && nbarriers <= count234(barriers));
463
464     while (nbarriers > 0) {
465         int i;
466         struct xyd *xyd;
467         int x1, y1, d1, x2, y2, d2;
468
469         /*
470          * Extract a randomly chosen barrier from the list.
471          */
472         i = random_upto(rs, count234(barriers));
473         xyd = delpos234(barriers, i);
474
475         assert(xyd != NULL);
476
477         x1 = xyd->x;
478         y1 = xyd->y;
479         d1 = xyd->direction;
480         sfree(xyd);
481
482         OFFSET(x2, y2, x1, y1, d1, state);
483         d2 = F(d1);
484
485         barrier(state, x1, y1) |= d1;
486         barrier(state, x2, y2) |= d2;
487
488         nbarriers--;
489     }
490
491     /*
492      * Clean up the rest of the barrier list.
493      */
494     {
495         struct xyd *xyd;
496
497         while ( (xyd = delpos234(barriers, 0)) != NULL)
498             sfree(xyd);
499
500         freetree234(barriers);
501     }
502
503     /*
504      * Set up the barrier corner flags, for drawing barriers
505      * prettily when they meet.
506      */
507     for (y = 0; y < state->height; y++) {
508         for (x = 0; x < state->width; x++) {
509             int dir;
510
511             for (dir = 1; dir < 0x10; dir <<= 1) {
512                 int dir2 = A(dir);
513                 int x1, y1, x2, y2, x3, y3;
514                 int corner = FALSE;
515
516                 if (!(barrier(state, x, y) & dir))
517                     continue;
518
519                 if (barrier(state, x, y) & dir2)
520                     corner = TRUE;
521
522                 x1 = x + X(dir), y1 = y + Y(dir);
523                 if (x1 >= 0 && x1 < state->width &&
524                     y1 >= 0 && y1 < state->height &&
525                     (barrier(state, x1, y1) & dir2))
526                     corner = TRUE;
527
528                 x2 = x + X(dir2), y2 = y + Y(dir2);
529                 if (x2 >= 0 && x2 < state->width &&
530                     y2 >= 0 && y2 < state->height &&
531                     (barrier(state, x2, y2) & dir))
532                     corner = TRUE;
533
534                 if (corner) {
535                     barrier(state, x, y) |= (dir << 4);
536                     if (x1 >= 0 && x1 < state->width &&
537                         y1 >= 0 && y1 < state->height)
538                         barrier(state, x1, y1) |= (A(dir) << 4);
539                     if (x2 >= 0 && x2 < state->width &&
540                         y2 >= 0 && y2 < state->height)
541                         barrier(state, x2, y2) |= (C(dir) << 4);
542                     x3 = x + X(dir) + X(dir2), y3 = y + Y(dir) + Y(dir2);
543                     if (x3 >= 0 && x3 < state->width &&
544                         y3 >= 0 && y3 < state->height)
545                         barrier(state, x3, y3) |= (F(dir) << 4);
546                 }
547             }
548         }
549     }
550
551     random_free(rs);
552
553     return state;
554 }
555
556 game_state *dup_game(game_state *state)
557 {
558     game_state *ret;
559
560     ret = snew(game_state);
561     ret->width = state->width;
562     ret->height = state->height;
563     ret->cx = state->cx;
564     ret->cy = state->cy;
565     ret->wrapping = state->wrapping;
566     ret->completed = state->completed;
567     ret->last_rotate_dir = state->last_rotate_dir;
568     ret->tiles = snewn(state->width * state->height, unsigned char);
569     memcpy(ret->tiles, state->tiles, state->width * state->height);
570     ret->barriers = snewn(state->width * state->height, unsigned char);
571     memcpy(ret->barriers, state->barriers, state->width * state->height);
572
573     return ret;
574 }
575
576 void free_game(game_state *state)
577 {
578     sfree(state->tiles);
579     sfree(state->barriers);
580     sfree(state);
581 }
582
583 /* ----------------------------------------------------------------------
584  * Utility routine.
585  */
586
587 /*
588  * Compute which squares are reachable from the centre square, as a
589  * quick visual aid to determining how close the game is to
590  * completion. This is also a simple way to tell if the game _is_
591  * completed - just call this function and see whether every square
592  * is marked active.
593  */
594 static unsigned char *compute_active(game_state *state)
595 {
596     unsigned char *active;
597     tree234 *todo;
598     struct xyd *xyd;
599
600     active = snewn(state->width * state->height, unsigned char);
601     memset(active, 0, state->width * state->height);
602
603     /*
604      * We only store (x,y) pairs in todo, but it's easier to reuse
605      * xyd_cmp and just store direction 0 every time.
606      */
607     todo = newtree234(xyd_cmp);
608     index(state, active, state->cx, state->cy) = ACTIVE;
609     add234(todo, new_xyd(state->cx, state->cy, 0));
610
611     while ( (xyd = delpos234(todo, 0)) != NULL) {
612         int x1, y1, d1, x2, y2, d2;
613
614         x1 = xyd->x;
615         y1 = xyd->y;
616         sfree(xyd);
617
618         for (d1 = 1; d1 < 0x10; d1 <<= 1) {
619             OFFSET(x2, y2, x1, y1, d1, state);
620             d2 = F(d1);
621
622             /*
623              * If the next tile in this direction is connected to
624              * us, and there isn't a barrier in the way, and it
625              * isn't already marked active, then mark it active and
626              * add it to the to-examine list.
627              */
628             if ((tile(state, x1, y1) & d1) &&
629                 (tile(state, x2, y2) & d2) &&
630                 !(barrier(state, x1, y1) & d1) &&
631                 !index(state, active, x2, y2)) {
632                 index(state, active, x2, y2) = ACTIVE;
633                 add234(todo, new_xyd(x2, y2, 0));
634             }
635         }
636     }
637     /* Now we expect the todo list to have shrunk to zero size. */
638     assert(count234(todo) == 0);
639     freetree234(todo);
640
641     return active;
642 }
643
644 /* ----------------------------------------------------------------------
645  * Process a move.
646  */
647 game_state *make_move(game_state *state, int x, int y, int button)
648 {
649     game_state *ret;
650     int tx, ty, orig;
651
652     /*
653      * All moves in Net are made with the mouse.
654      */
655     if (button != LEFT_BUTTON &&
656         button != MIDDLE_BUTTON &&
657         button != RIGHT_BUTTON)
658         return NULL;
659
660     /*
661      * The button must have been clicked on a valid tile.
662      */
663     x -= WINDOW_OFFSET + TILE_BORDER;
664     y -= WINDOW_OFFSET + TILE_BORDER;
665     if (x < 0 || y < 0)
666         return NULL;
667     tx = x / TILE_SIZE;
668     ty = y / TILE_SIZE;
669     if (tx >= state->width || ty >= state->height)
670         return NULL;
671     if (tx % TILE_SIZE >= TILE_SIZE - TILE_BORDER ||
672         ty % TILE_SIZE >= TILE_SIZE - TILE_BORDER)
673         return NULL;
674
675     /*
676      * The middle button locks or unlocks a tile. (A locked tile
677      * cannot be turned, and is visually marked as being locked.
678      * This is a convenience for the player, so that once they are
679      * sure which way round a tile goes, they can lock it and thus
680      * avoid forgetting later on that they'd already done that one;
681      * and the locking also prevents them turning the tile by
682      * accident. If they change their mind, another middle click
683      * unlocks it.)
684      */
685     if (button == MIDDLE_BUTTON) {
686         ret = dup_game(state);
687         tile(ret, tx, ty) ^= LOCKED;
688         return ret;
689     }
690
691     /*
692      * The left and right buttons have no effect if clicked on a
693      * locked tile.
694      */
695     if (tile(state, tx, ty) & LOCKED)
696         return NULL;
697
698     /*
699      * Otherwise, turn the tile one way or the other. Left button
700      * turns anticlockwise; right button turns clockwise.
701      */
702     ret = dup_game(state);
703     orig = tile(ret, tx, ty);
704     if (button == LEFT_BUTTON) {
705         tile(ret, tx, ty) = A(orig);
706         ret->last_rotate_dir = +1;
707     } else {
708         tile(ret, tx, ty) = C(orig);
709         ret->last_rotate_dir = -1;
710     }
711
712     /*
713      * Check whether the game has been completed.
714      */
715     {
716         unsigned char *active = compute_active(ret);
717         int x1, y1;
718         int complete = TRUE;
719
720         for (x1 = 0; x1 < ret->width; x1++)
721             for (y1 = 0; y1 < ret->height; y1++)
722                 if (!index(ret, active, x1, y1)) {
723                     complete = FALSE;
724                     goto break_label;  /* break out of two loops at once */
725                 }
726         break_label:
727
728         sfree(active);
729
730         if (complete)
731             ret->completed = TRUE;
732     }
733
734     return ret;
735 }
736
737 /* ----------------------------------------------------------------------
738  * Routines for drawing the game position on the screen.
739  */
740
741 struct game_drawstate {
742     int started;
743     int width, height;
744     unsigned char *visible;
745 };
746
747 game_drawstate *game_new_drawstate(game_state *state)
748 {
749     game_drawstate *ds = snew(game_drawstate);
750
751     ds->started = FALSE;
752     ds->width = state->width;
753     ds->height = state->height;
754     ds->visible = snewn(state->width * state->height, unsigned char);
755     memset(ds->visible, 0xFF, state->width * state->height);
756
757     return ds;
758 }
759
760 void game_free_drawstate(game_drawstate *ds)
761 {
762     sfree(ds->visible);
763     sfree(ds);
764 }
765
766 void game_size(game_params *params, int *x, int *y)
767 {
768     *x = WINDOW_OFFSET * 2 + TILE_SIZE * params->width + TILE_BORDER;
769     *y = WINDOW_OFFSET * 2 + TILE_SIZE * params->height + TILE_BORDER;
770 }
771
772 float *game_colours(frontend *fe, game_state *state, int *ncolours)
773 {
774     float *ret;
775
776     ret = snewn(NCOLOURS * 3, float);
777     *ncolours = NCOLOURS;
778
779     /*
780      * Basic background colour is whatever the front end thinks is
781      * a sensible default.
782      */
783     frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
784
785     /*
786      * Wires are black.
787      */
788     ret[COL_WIRE * 3 + 0] = 0.0;
789     ret[COL_WIRE * 3 + 1] = 0.0;
790     ret[COL_WIRE * 3 + 2] = 0.0;
791
792     /*
793      * Powered wires and powered endpoints are cyan.
794      */
795     ret[COL_POWERED * 3 + 0] = 0.0;
796     ret[COL_POWERED * 3 + 1] = 1.0;
797     ret[COL_POWERED * 3 + 2] = 1.0;
798
799     /*
800      * Barriers are red.
801      */
802     ret[COL_BARRIER * 3 + 0] = 1.0;
803     ret[COL_BARRIER * 3 + 1] = 0.0;
804     ret[COL_BARRIER * 3 + 2] = 0.0;
805
806     /*
807      * Unpowered endpoints are blue.
808      */
809     ret[COL_ENDPOINT * 3 + 0] = 0.0;
810     ret[COL_ENDPOINT * 3 + 1] = 0.0;
811     ret[COL_ENDPOINT * 3 + 2] = 1.0;
812
813     /*
814      * Tile borders are a darker grey than the background.
815      */
816     ret[COL_BORDER * 3 + 0] = 0.5 * ret[COL_BACKGROUND * 3 + 0];
817     ret[COL_BORDER * 3 + 1] = 0.5 * ret[COL_BACKGROUND * 3 + 1];
818     ret[COL_BORDER * 3 + 2] = 0.5 * ret[COL_BACKGROUND * 3 + 2];
819
820     /*
821      * Locked tiles are a grey in between those two.
822      */
823     ret[COL_LOCKED * 3 + 0] = 0.75 * ret[COL_BACKGROUND * 3 + 0];
824     ret[COL_LOCKED * 3 + 1] = 0.75 * ret[COL_BACKGROUND * 3 + 1];
825     ret[COL_LOCKED * 3 + 2] = 0.75 * ret[COL_BACKGROUND * 3 + 2];
826
827     return ret;
828 }
829
830 static void draw_thick_line(frontend *fe, int x1, int y1, int x2, int y2,
831                             int colour)
832 {
833     draw_line(fe, x1-1, y1, x2-1, y2, COL_WIRE);
834     draw_line(fe, x1+1, y1, x2+1, y2, COL_WIRE);
835     draw_line(fe, x1, y1-1, x2, y2-1, COL_WIRE);
836     draw_line(fe, x1, y1+1, x2, y2+1, COL_WIRE);
837     draw_line(fe, x1, y1, x2, y2, colour);
838 }
839
840 static void draw_rect_coords(frontend *fe, int x1, int y1, int x2, int y2,
841                              int colour)
842 {
843     int mx = (x1 < x2 ? x1 : x2);
844     int my = (y1 < y2 ? y1 : y2);
845     int dx = (x2 + x1 - 2*mx + 1);
846     int dy = (y2 + y1 - 2*my + 1);
847
848     draw_rect(fe, mx, my, dx, dy, colour);
849 }
850
851 static void draw_barrier_corner(frontend *fe, int x, int y, int dir, int phase)
852 {
853     int bx = WINDOW_OFFSET + TILE_SIZE * x;
854     int by = WINDOW_OFFSET + TILE_SIZE * y;
855     int x1, y1, dx, dy, dir2;
856
857     dir >>= 4;
858
859     dir2 = A(dir);
860     dx = X(dir) + X(dir2);
861     dy = Y(dir) + Y(dir2);
862     x1 = (dx > 0 ? TILE_SIZE+TILE_BORDER-1 : 0);
863     y1 = (dy > 0 ? TILE_SIZE+TILE_BORDER-1 : 0);
864
865     if (phase == 0) {
866         draw_rect_coords(fe, bx+x1, by+y1,
867                          bx+x1-TILE_BORDER*dx, by+y1-(TILE_BORDER-1)*dy,
868                          COL_WIRE);
869         draw_rect_coords(fe, bx+x1, by+y1,
870                          bx+x1-(TILE_BORDER-1)*dx, by+y1-TILE_BORDER*dy,
871                          COL_WIRE);
872     } else {
873         draw_rect_coords(fe, bx+x1, by+y1,
874                          bx+x1-(TILE_BORDER-1)*dx, by+y1-(TILE_BORDER-1)*dy,
875                          COL_BARRIER);
876     }
877 }
878
879 static void draw_barrier(frontend *fe, int x, int y, int dir, int phase)
880 {
881     int bx = WINDOW_OFFSET + TILE_SIZE * x;
882     int by = WINDOW_OFFSET + TILE_SIZE * y;
883     int x1, y1, w, h;
884
885     x1 = (X(dir) > 0 ? TILE_SIZE : X(dir) == 0 ? TILE_BORDER : 0);
886     y1 = (Y(dir) > 0 ? TILE_SIZE : Y(dir) == 0 ? TILE_BORDER : 0);
887     w = (X(dir) ? TILE_BORDER : TILE_SIZE - TILE_BORDER);
888     h = (Y(dir) ? TILE_BORDER : TILE_SIZE - TILE_BORDER);
889
890     if (phase == 0) {
891         draw_rect(fe, bx+x1-X(dir), by+y1-Y(dir), w, h, COL_WIRE);
892     } else {
893         draw_rect(fe, bx+x1, by+y1, w, h, COL_BARRIER);
894     }
895 }
896
897 static void draw_tile(frontend *fe, game_state *state, int x, int y, int tile,
898                       float angle)
899 {
900     int bx = WINDOW_OFFSET + TILE_SIZE * x;
901     int by = WINDOW_OFFSET + TILE_SIZE * y;
902     float matrix[4];
903     float cx, cy, ex, ey, tx, ty;
904     int dir, col, phase;
905
906     /*
907      * When we draw a single tile, we must draw everything up to
908      * and including the borders around the tile. This means that
909      * if the neighbouring tiles have connections to those borders,
910      * we must draw those connections on the borders themselves.
911      *
912      * This would be terribly fiddly if we ever had to draw a tile
913      * while its neighbour was in mid-rotate, because we'd have to
914      * arrange to _know_ that the neighbour was being rotated and
915      * hence had an anomalous effect on the redraw of this tile.
916      * Fortunately, the drawing algorithm avoids ever calling us in
917      * this circumstance: we're either drawing lots of straight
918      * tiles at game start or after a move is complete, or we're
919      * repeatedly drawing only the rotating tile. So no problem.
920      */
921
922     /*
923      * So. First blank the tile out completely: draw a big
924      * rectangle in border colour, and a smaller rectangle in
925      * background colour to fill it in.
926      */
927     draw_rect(fe, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER,
928               COL_BORDER);
929     draw_rect(fe, bx+TILE_BORDER, by+TILE_BORDER,
930               TILE_SIZE-TILE_BORDER, TILE_SIZE-TILE_BORDER,
931               tile & LOCKED ? COL_LOCKED : COL_BACKGROUND);
932
933     /*
934      * Set up the rotation matrix.
935      */
936     matrix[0] = cos(angle * PI / 180.0);
937     matrix[1] = -sin(angle * PI / 180.0);
938     matrix[2] = sin(angle * PI / 180.0);
939     matrix[3] = cos(angle * PI / 180.0);
940
941     /*
942      * Draw the wires.
943      */
944     cx = cy = TILE_BORDER + (TILE_SIZE-TILE_BORDER) / 2.0 - 0.5;
945     col = (tile & ACTIVE ? COL_POWERED : COL_WIRE);
946     for (dir = 1; dir < 0x10; dir <<= 1) {
947         if (tile & dir) {
948             ex = (TILE_SIZE - TILE_BORDER - 1.0) / 2.0 * X(dir);
949             ey = (TILE_SIZE - TILE_BORDER - 1.0) / 2.0 * Y(dir);
950             MATMUL(tx, ty, matrix, ex, ey);
951             draw_thick_line(fe, bx+cx, by+cy, bx+(cx+tx), by+(cy+ty),
952                             COL_WIRE);
953         }
954     }
955     for (dir = 1; dir < 0x10; dir <<= 1) {
956         if (tile & dir) {
957             ex = (TILE_SIZE - TILE_BORDER - 1.0) / 2.0 * X(dir);
958             ey = (TILE_SIZE - TILE_BORDER - 1.0) / 2.0 * Y(dir);
959             MATMUL(tx, ty, matrix, ex, ey);
960             draw_line(fe, bx+cx, by+cy, bx+(cx+tx), by+(cy+ty), col);
961         }
962     }
963
964     /*
965      * Draw the box in the middle. We do this in blue if the tile
966      * is an unpowered endpoint, in cyan if the tile is a powered
967      * endpoint, in black if the tile is the centrepiece, and
968      * otherwise not at all.
969      */
970     col = -1;
971     if (x == state->cx && y == state->cy)
972         col = COL_WIRE;
973     else if (COUNT(tile) == 1) {
974         col = (tile & ACTIVE ? COL_POWERED : COL_ENDPOINT);
975     }
976     if (col >= 0) {
977         int i, points[8];
978
979         points[0] = +1; points[1] = +1;
980         points[2] = +1; points[3] = -1;
981         points[4] = -1; points[5] = -1;
982         points[6] = -1; points[7] = +1;
983
984         for (i = 0; i < 8; i += 2) {
985             ex = (TILE_SIZE * 0.24) * points[i];
986             ey = (TILE_SIZE * 0.24) * points[i+1];
987             MATMUL(tx, ty, matrix, ex, ey);
988             points[i] = bx+cx+tx;
989             points[i+1] = by+cy+ty;
990         }
991
992         draw_polygon(fe, points, 4, TRUE, col);
993         draw_polygon(fe, points, 4, FALSE, COL_WIRE);
994     }
995
996     /*
997      * Draw the points on the border if other tiles are connected
998      * to us.
999      */
1000     for (dir = 1; dir < 0x10; dir <<= 1) {
1001         int dx, dy, px, py, lx, ly, vx, vy, ox, oy;
1002
1003         dx = X(dir);
1004         dy = Y(dir);
1005
1006         ox = x + dx;
1007         oy = y + dy;
1008
1009         if (ox < 0 || ox >= state->width || oy < 0 || oy >= state->height)
1010             continue;
1011
1012         if (!(tile(state, ox, oy) & F(dir)))
1013             continue;
1014
1015         px = bx + (dx>0 ? TILE_SIZE + TILE_BORDER - 1 : dx<0 ? 0 : cx);
1016         py = by + (dy>0 ? TILE_SIZE + TILE_BORDER - 1 : dy<0 ? 0 : cy);
1017         lx = dx * (TILE_BORDER-1);
1018         ly = dy * (TILE_BORDER-1);
1019         vx = (dy ? 1 : 0);
1020         vy = (dx ? 1 : 0);
1021
1022         if (angle == 0.0 && (tile & dir)) {
1023             /*
1024              * If we are fully connected to the other tile, we must
1025              * draw right across the tile border. (We can use our
1026              * own ACTIVE state to determine what colour to do this
1027              * in: if we are fully connected to the other tile then
1028              * the two ACTIVE states will be the same.)
1029              */
1030             draw_rect_coords(fe, px-vx, py-vy, px+lx+vx, py+ly+vy, COL_WIRE);
1031             draw_rect_coords(fe, px, py, px+lx, py+ly,
1032                              (tile & ACTIVE) ? COL_POWERED : COL_WIRE);
1033         } else {
1034             /*
1035              * The other tile extends into our border, but isn't
1036              * actually connected to us. Just draw a single black
1037              * dot.
1038              */
1039             draw_rect_coords(fe, px, py, px, py, COL_WIRE);
1040         }
1041     }
1042
1043     /*
1044      * Draw barrier corners, and then barriers.
1045      */
1046     for (phase = 0; phase < 2; phase++) {
1047         for (dir = 1; dir < 0x10; dir <<= 1)
1048             if (barrier(state, x, y) & (dir << 4))
1049                 draw_barrier_corner(fe, x, y, dir << 4, phase);
1050         for (dir = 1; dir < 0x10; dir <<= 1)
1051             if (barrier(state, x, y) & dir)
1052                 draw_barrier(fe, x, y, dir, phase);
1053     }
1054
1055     draw_update(fe, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
1056 }
1057
1058 void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
1059                  game_state *state, float t)
1060 {
1061     int x, y, tx, ty, frame;
1062     unsigned char *active;
1063     float angle = 0.0;
1064
1065     /*
1066      * Clear the screen and draw the exterior barrier lines if this
1067      * is our first call.
1068      */
1069     if (!ds->started) {
1070         int phase;
1071
1072         ds->started = TRUE;
1073
1074         draw_rect(fe, 0, 0, 
1075                   WINDOW_OFFSET * 2 + TILE_SIZE * state->width + TILE_BORDER,
1076                   WINDOW_OFFSET * 2 + TILE_SIZE * state->height + TILE_BORDER,
1077                   COL_BACKGROUND);
1078         draw_update(fe, 0, 0, 
1079                     WINDOW_OFFSET*2 + TILE_SIZE*state->width + TILE_BORDER,
1080                     WINDOW_OFFSET*2 + TILE_SIZE*state->height + TILE_BORDER);
1081
1082         for (phase = 0; phase < 2; phase++) {
1083
1084             for (x = 0; x < ds->width; x++) {
1085                 if (barrier(state, x, 0) & UL)
1086                     draw_barrier_corner(fe, x, -1, LD, phase);
1087                 if (barrier(state, x, 0) & RU)
1088                     draw_barrier_corner(fe, x, -1, DR, phase);
1089                 if (barrier(state, x, 0) & U)
1090                     draw_barrier(fe, x, -1, D, phase);
1091                 if (barrier(state, x, ds->height-1) & DR)
1092                     draw_barrier_corner(fe, x, ds->height, RU, phase);
1093                 if (barrier(state, x, ds->height-1) & LD)
1094                     draw_barrier_corner(fe, x, ds->height, UL, phase);
1095                 if (barrier(state, x, ds->height-1) & D)
1096                     draw_barrier(fe, x, ds->height, U, phase);
1097             }
1098
1099             for (y = 0; y < ds->height; y++) {
1100                 if (barrier(state, 0, y) & UL)
1101                     draw_barrier_corner(fe, -1, y, RU, phase);
1102                 if (barrier(state, 0, y) & LD)
1103                     draw_barrier_corner(fe, -1, y, DR, phase);
1104                 if (barrier(state, 0, y) & L)
1105                     draw_barrier(fe, -1, y, R, phase);
1106                 if (barrier(state, ds->width-1, y) & RU)
1107                     draw_barrier_corner(fe, ds->width, y, UL, phase);
1108                 if (barrier(state, ds->width-1, y) & DR)
1109                     draw_barrier_corner(fe, ds->width, y, LD, phase);
1110                 if (barrier(state, ds->width-1, y) & R)
1111                     draw_barrier(fe, ds->width, y, L, phase);
1112             }
1113         }
1114     }
1115
1116     tx = ty = -1;
1117     frame = -1;
1118     if (oldstate && (t < ROTATE_TIME)) {
1119         /*
1120          * We're animating a tile rotation. Find the turning tile,
1121          * if any.
1122          */
1123         for (x = 0; x < oldstate->width; x++)
1124             for (y = 0; y < oldstate->height; y++)
1125                 if ((tile(oldstate, x, y) ^ tile(state, x, y)) & 0xF) {
1126                     tx = x, ty = y;
1127                     goto break_label;  /* leave both loops at once */
1128                 }
1129         break_label:
1130
1131         if (tx >= 0) {
1132             if (tile(state, tx, ty) == ROT(tile(oldstate, tx, ty),
1133                                            state->last_rotate_dir))
1134                 angle = state->last_rotate_dir * 90.0 * (t / ROTATE_TIME);
1135             else
1136                 angle = state->last_rotate_dir * -90.0 * (t / ROTATE_TIME);
1137             state = oldstate;
1138         }
1139     } else if (t > ROTATE_TIME) {
1140         /*
1141          * We're animating a completion flash. Find which frame
1142          * we're at.
1143          */
1144         frame = (t - ROTATE_TIME) / FLASH_FRAME;
1145     }
1146
1147     /*
1148      * Draw any tile which differs from the way it was last drawn.
1149      */
1150     active = compute_active(state);
1151
1152     for (x = 0; x < ds->width; x++)
1153         for (y = 0; y < ds->height; y++) {
1154             unsigned char c = tile(state, x, y) | index(state, active, x, y);
1155
1156             /*
1157              * In a completion flash, we adjust the LOCKED bit
1158              * depending on our distance from the centre point and
1159              * the frame number.
1160              */
1161             if (frame >= 0) {
1162                 int xdist, ydist, dist;
1163                 xdist = (x < state->cx ? state->cx - x : x - state->cx);
1164                 ydist = (y < state->cy ? state->cy - y : y - state->cy);
1165                 dist = (xdist > ydist ? xdist : ydist);
1166
1167                 if (frame >= dist && frame < dist+4) {
1168                     int lock = (frame - dist) & 1;
1169                     lock = lock ? LOCKED : 0;
1170                     c = (c &~ LOCKED) | lock;
1171                 }
1172             }
1173
1174             if (index(state, ds->visible, x, y) != c ||
1175                 index(state, ds->visible, x, y) == 0xFF ||
1176                 (x == tx && y == ty)) {
1177                 draw_tile(fe, state, x, y, c,
1178                           (x == tx && y == ty ? angle : 0.0));
1179                 if (x == tx && y == ty)
1180                     index(state, ds->visible, x, y) = 0xFF;
1181                 else
1182                     index(state, ds->visible, x, y) = c;
1183             }
1184         }
1185
1186     sfree(active);
1187 }
1188
1189 float game_anim_length(game_state *oldstate, game_state *newstate)
1190 {
1191     float ret = 0.0;
1192     int x, y;
1193
1194     /*
1195      * If there's a tile which has been rotated, allow time to
1196      * animate its rotation.
1197      */
1198     for (x = 0; x < oldstate->width; x++)
1199         for (y = 0; y < oldstate->height; y++)
1200             if ((tile(oldstate, x, y) ^ tile(newstate, x, y)) & 0xF) {
1201                 ret = ROTATE_TIME;
1202                 goto break_label;      /* leave both loops at once */
1203             }
1204     break_label:
1205
1206     /*
1207      * Also, if the game has just been completed, allow time for a
1208      * completion flash.
1209      */
1210     if (!oldstate->completed && newstate->completed) {
1211         int size;
1212         size = 0;
1213         if (size < newstate->cx+1)
1214             size = newstate->cx+1;
1215         if (size < newstate->cy+1)
1216             size = newstate->cy+1;
1217         if (size < newstate->width - newstate->cx)
1218             size = newstate->width - newstate->cx;
1219         if (size < newstate->height - newstate->cy)
1220             size = newstate->height - newstate->cy;
1221         ret += FLASH_FRAME * (size+4);
1222     }
1223
1224     return ret;
1225 }