A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/*
2 * netslide.c: cross between Net and Sixteen, courtesy of Richard
3 * Boulton.
4 */
5
6#include <stdio.h>
7#include <stdlib.h>
8#include <string.h>
9#include <assert.h>
10#include <ctype.h>
11#include <limits.h>
12#ifdef NO_TGMATH_H
13# include <math.h>
14#else
15# include <tgmath.h>
16#endif
17
18#include "puzzles.h"
19#include "tree234.h"
20
21#define MATMUL(xr,yr,m,x,y) do { \
22 float rx, ry, xx = (x), yy = (y), *mat = (m); \
23 rx = mat[0] * xx + mat[2] * yy; \
24 ry = mat[1] * xx + mat[3] * yy; \
25 (xr) = rx; (yr) = ry; \
26} while (0)
27
28/* Direction and other bitfields */
29#define R 0x01
30#define U 0x02
31#define L 0x04
32#define D 0x08
33#define FLASHING 0x10
34#define ACTIVE 0x20
35/* Corner flags go in the barriers array */
36#define RU 0x10
37#define UL 0x20
38#define LD 0x40
39#define DR 0x80
40
41/* Get tile at given coordinate */
42#define T(state, x, y) ( (y) * (state)->width + (x) )
43
44/* Rotations: Anticlockwise, Clockwise, Flip, general rotate */
45#define A(x) ( (((x) & 0x07) << 1) | (((x) & 0x08) >> 3) )
46#define C(x) ( (((x) & 0x0E) >> 1) | (((x) & 0x01) << 3) )
47#define F(x) ( (((x) & 0x0C) >> 2) | (((x) & 0x03) << 2) )
48#define ROT(x, n) ( ((n)&3) == 0 ? (x) : \
49 ((n)&3) == 1 ? A(x) : \
50 ((n)&3) == 2 ? F(x) : C(x) )
51
52/* X and Y displacements */
53#define X(x) ( (x) == R ? +1 : (x) == L ? -1 : 0 )
54#define Y(x) ( (x) == D ? +1 : (x) == U ? -1 : 0 )
55
56/* Bit count */
57#define COUNT(x) ( (((x) & 0x08) >> 3) + (((x) & 0x04) >> 2) + \
58 (((x) & 0x02) >> 1) + ((x) & 0x01) )
59
60#define PREFERRED_TILE_SIZE 48
61#define TILE_SIZE (ds->tilesize)
62#define BORDER TILE_SIZE
63#define TILE_BORDER 1
64#define WINDOW_OFFSET 0
65
66#define ANIM_TIME 0.13F
67#define FLASH_FRAME 0.07F
68
69enum {
70 COL_BACKGROUND,
71 COL_FLASHING,
72 COL_BORDER,
73 COL_WIRE,
74 COL_ENDPOINT,
75 COL_POWERED,
76 COL_BARRIER,
77 COL_LOWLIGHT,
78 COL_TEXT,
79 NCOLOURS
80};
81
82struct game_params {
83 int width;
84 int height;
85 bool wrapping;
86 float barrier_probability;
87 int movetarget;
88};
89
90struct game_state {
91 int width, height, cx, cy, completed;
92 bool wrapping, used_solve;
93 int move_count, movetarget;
94
95 /* position (row or col number, starting at 0) of last move. */
96 int last_move_row, last_move_col;
97
98 /* direction of last move: +1 or -1 */
99 int last_move_dir;
100
101 unsigned char *tiles;
102 unsigned char *barriers;
103};
104
105#define OFFSET(x2,y2,x1,y1,dir,state) \
106 ( (x2) = ((x1) + (state)->width + X((dir))) % (state)->width, \
107 (y2) = ((y1) + (state)->height + Y((dir))) % (state)->height)
108
109#define index(state, a, x, y) ( a[(y) * (state)->width + (x)] )
110#define tile(state, x, y) index(state, (state)->tiles, x, y)
111#define barrier(state, x, y) index(state, (state)->barriers, x, y)
112
113struct xyd {
114 int x, y, direction;
115};
116
117static int xyd_cmp(void *av, void *bv) {
118 struct xyd *a = (struct xyd *)av;
119 struct xyd *b = (struct xyd *)bv;
120 if (a->x < b->x)
121 return -1;
122 if (a->x > b->x)
123 return +1;
124 if (a->y < b->y)
125 return -1;
126 if (a->y > b->y)
127 return +1;
128 if (a->direction < b->direction)
129 return -1;
130 if (a->direction > b->direction)
131 return +1;
132 return 0;
133}
134
135static struct xyd *new_xyd(int x, int y, int direction)
136{
137 struct xyd *xyd = snew(struct xyd);
138 xyd->x = x;
139 xyd->y = y;
140 xyd->direction = direction;
141 return xyd;
142}
143
144static void slide_col(game_state *state, int dir, int col);
145static void slide_col_int(int w, int h, unsigned char *tiles, int dir, int col);
146static void slide_row(game_state *state, int dir, int row);
147static void slide_row_int(int w, int h, unsigned char *tiles, int dir, int row);
148
149/* ----------------------------------------------------------------------
150 * Manage game parameters.
151 */
152static game_params *default_params(void)
153{
154 game_params *ret = snew(game_params);
155
156 ret->width = 3;
157 ret->height = 3;
158 ret->wrapping = false;
159 ret->barrier_probability = 1.0;
160 ret->movetarget = 0;
161
162 return ret;
163}
164
165static const struct { int x, y, wrap, bprob; const char* desc; }
166netslide_presets[] = {
167 {3, 3, false, 1, " easy"},
168 {3, 3, false, 0, " medium"},
169 {3, 3, true, 0, " hard"},
170 {4, 4, false, 1, " easy"},
171 {4, 4, false, 0, " medium"},
172 {4, 4, true, 0, " hard"},
173 {5, 5, false, 1, " easy"},
174 {5, 5, false, 0, " medium"},
175 {5, 5, true, 0, " hard"},
176};
177
178static bool game_fetch_preset(int i, char **name, game_params **params)
179{
180 game_params *ret;
181 char str[80];
182
183 if (i < 0 || i >= lenof(netslide_presets))
184 return false;
185
186 ret = snew(game_params);
187 ret->width = netslide_presets[i].x;
188 ret->height = netslide_presets[i].y;
189 ret->wrapping = netslide_presets[i].wrap;
190 ret->barrier_probability = (float)netslide_presets[i].bprob;
191 ret->movetarget = 0;
192
193 sprintf(str, "%dx%d%s", ret->width, ret->height, netslide_presets[i].desc);
194
195 *name = dupstr(str);
196 *params = ret;
197 return true;
198}
199
200static void free_params(game_params *params)
201{
202 sfree(params);
203}
204
205static game_params *dup_params(const game_params *params)
206{
207 game_params *ret = snew(game_params);
208 *ret = *params; /* structure copy */
209 return ret;
210}
211
212static void decode_params(game_params *ret, char const *string)
213{
214 char const *p = string;
215
216 ret->wrapping = false;
217 ret->barrier_probability = 0.0;
218 ret->movetarget = 0;
219
220 ret->width = atoi(p);
221 while (*p && isdigit((unsigned char)*p)) p++;
222 if (*p == 'x') {
223 p++;
224 ret->height = atoi(p);
225 while (*p && isdigit((unsigned char)*p)) p++;
226 ret->wrapping = (*p == 'w');
227 if (ret->wrapping)
228 p++;
229 if (*p == 'b') {
230 ret->barrier_probability = (float)atof(++p);
231 while (*p && (isdigit((unsigned char)*p) || *p == '.')) p++;
232 }
233 if (*p == 'm') {
234 ret->movetarget = atoi(++p);
235 }
236 } else {
237 ret->height = ret->width;
238 }
239}
240
241static char *encode_params(const game_params *params, bool full)
242{
243 char ret[400];
244 int len;
245
246 len = sprintf(ret, "%dx%d", params->width, params->height);
247 if (params->wrapping)
248 ret[len++] = 'w';
249 if (full && params->barrier_probability)
250 len += sprintf(ret+len, "b%g", params->barrier_probability);
251 /* Shuffle limit is part of the limited parameters, because we have to
252 * provide the target move count. */
253 if (params->movetarget)
254 len += sprintf(ret+len, "m%d", params->movetarget);
255 assert(len < lenof(ret));
256 ret[len] = '\0';
257
258 return dupstr(ret);
259}
260
261static config_item *game_configure(const game_params *params)
262{
263 config_item *ret;
264 char buf[80];
265
266 ret = snewn(6, config_item);
267
268 ret[0].name = "Width";
269 ret[0].type = C_STRING;
270 sprintf(buf, "%d", params->width);
271 ret[0].u.string.sval = dupstr(buf);
272
273 ret[1].name = "Height";
274 ret[1].type = C_STRING;
275 sprintf(buf, "%d", params->height);
276 ret[1].u.string.sval = dupstr(buf);
277
278 ret[2].name = "Walls wrap around";
279 ret[2].type = C_BOOLEAN;
280 ret[2].u.boolean.bval = params->wrapping;
281
282 ret[3].name = "Barrier probability";
283 ret[3].type = C_STRING;
284 sprintf(buf, "%g", params->barrier_probability);
285 ret[3].u.string.sval = dupstr(buf);
286
287 ret[4].name = "Number of shuffling moves";
288 ret[4].type = C_STRING;
289 sprintf(buf, "%d", params->movetarget);
290 ret[4].u.string.sval = dupstr(buf);
291
292 ret[5].name = NULL;
293 ret[5].type = C_END;
294
295 return ret;
296}
297
298static game_params *custom_params(const config_item *cfg)
299{
300 game_params *ret = snew(game_params);
301
302 ret->width = atoi(cfg[0].u.string.sval);
303 ret->height = atoi(cfg[1].u.string.sval);
304 ret->wrapping = cfg[2].u.boolean.bval;
305 ret->barrier_probability = (float)atof(cfg[3].u.string.sval);
306 ret->movetarget = atoi(cfg[4].u.string.sval);
307
308 return ret;
309}
310
311static const char *validate_params(const game_params *params, bool full)
312{
313 if (params->width <= 1 || params->height <= 1)
314 return "Width and height must both be greater than one";
315 if (params->width > INT_MAX / params->height)
316 return "Width times height must not be unreasonably large";
317 if (params->barrier_probability < 0)
318 return "Barrier probability may not be negative";
319 if (params->barrier_probability > 1)
320 return "Barrier probability may not be greater than 1";
321 if (params->movetarget < 0)
322 return "Number of shuffling moves may not be negative";
323 return NULL;
324}
325
326/* ----------------------------------------------------------------------
327 * Randomly select a new game description.
328 */
329
330static char *new_game_desc(const game_params *params, random_state *rs,
331 char **aux, bool interactive)
332{
333 tree234 *possibilities, *barriertree;
334 int w, h, x, y, cx, cy, nbarriers;
335 unsigned char *tiles, *barriers;
336 char *desc, *p;
337
338 w = params->width;
339 h = params->height;
340
341 tiles = snewn(w * h, unsigned char);
342 memset(tiles, 0, w * h);
343 barriers = snewn(w * h, unsigned char);
344 memset(barriers, 0, w * h);
345
346 cx = w / 2;
347 cy = h / 2;
348
349 /*
350 * Construct the unshuffled grid.
351 *
352 * To do this, we simply start at the centre point, repeatedly
353 * choose a random possibility out of the available ways to
354 * extend a used square into an unused one, and do it. After
355 * extending the third line out of a square, we remove the
356 * fourth from the possibilities list to avoid any full-cross
357 * squares (which would make the game too easy because they
358 * only have one orientation).
359 *
360 * The slightly worrying thing is the avoidance of full-cross
361 * squares. Can this cause our unsophisticated construction
362 * algorithm to paint itself into a corner, by getting into a
363 * situation where there are some unreached squares and the
364 * only way to reach any of them is to extend a T-piece into a
365 * full cross?
366 *
367 * Answer: no it can't, and here's a proof.
368 *
369 * Any contiguous group of such unreachable squares must be
370 * surrounded on _all_ sides by T-pieces pointing away from the
371 * group. (If not, then there is a square which can be extended
372 * into one of the `unreachable' ones, and so it wasn't
373 * unreachable after all.) In particular, this implies that
374 * each contiguous group of unreachable squares must be
375 * rectangular in shape (any deviation from that yields a
376 * non-T-piece next to an `unreachable' square).
377 *
378 * So we have a rectangle of unreachable squares, with T-pieces
379 * forming a solid border around the rectangle. The corners of
380 * that border must be connected (since every tile connects all
381 * the lines arriving in it), and therefore the border must
382 * form a closed loop around the rectangle.
383 *
384 * But this can't have happened in the first place, since we
385 * _know_ we've avoided creating closed loops! Hence, no such
386 * situation can ever arise, and the naive grid construction
387 * algorithm will guaranteeably result in a complete grid
388 * containing no unreached squares, no full crosses _and_ no
389 * closed loops. []
390 */
391 possibilities = newtree234(xyd_cmp);
392
393 if (cx+1 < w)
394 add234(possibilities, new_xyd(cx, cy, R));
395 if (cy-1 >= 0)
396 add234(possibilities, new_xyd(cx, cy, U));
397 if (cx-1 >= 0)
398 add234(possibilities, new_xyd(cx, cy, L));
399 if (cy+1 < h)
400 add234(possibilities, new_xyd(cx, cy, D));
401
402 while (count234(possibilities) > 0) {
403 int i;
404 struct xyd *xyd;
405 int x1, y1, d1, x2, y2, d2, d;
406
407 /*
408 * Extract a randomly chosen possibility from the list.
409 */
410 i = random_upto(rs, count234(possibilities));
411 xyd = delpos234(possibilities, i);
412 x1 = xyd->x;
413 y1 = xyd->y;
414 d1 = xyd->direction;
415 sfree(xyd);
416
417 OFFSET(x2, y2, x1, y1, d1, params);
418 d2 = F(d1);
419#ifdef GENERATION_DIAGNOSTICS
420 printf("picked (%d,%d,%c) <-> (%d,%d,%c)\n",
421 x1, y1, "0RU3L567D9abcdef"[d1], x2, y2, "0RU3L567D9abcdef"[d2]);
422#endif
423
424 /*
425 * Make the connection. (We should be moving to an as yet
426 * unused tile.)
427 */
428 index(params, tiles, x1, y1) |= d1;
429 assert(index(params, tiles, x2, y2) == 0);
430 index(params, tiles, x2, y2) |= d2;
431
432 /*
433 * If we have created a T-piece, remove its last
434 * possibility.
435 */
436 if (COUNT(index(params, tiles, x1, y1)) == 3) {
437 struct xyd xyd1, *xydp;
438
439 xyd1.x = x1;
440 xyd1.y = y1;
441 xyd1.direction = 0x0F ^ index(params, tiles, x1, y1);
442
443 xydp = find234(possibilities, &xyd1, NULL);
444
445 if (xydp) {
446#ifdef GENERATION_DIAGNOSTICS
447 printf("T-piece; removing (%d,%d,%c)\n",
448 xydp->x, xydp->y, "0RU3L567D9abcdef"[xydp->direction]);
449#endif
450 del234(possibilities, xydp);
451 sfree(xydp);
452 }
453 }
454
455 /*
456 * Remove all other possibilities that were pointing at the
457 * tile we've just moved into.
458 */
459 for (d = 1; d < 0x10; d <<= 1) {
460 int x3, y3, d3;
461 struct xyd xyd1, *xydp;
462
463 OFFSET(x3, y3, x2, y2, d, params);
464 d3 = F(d);
465
466 xyd1.x = x3;
467 xyd1.y = y3;
468 xyd1.direction = d3;
469
470 xydp = find234(possibilities, &xyd1, NULL);
471
472 if (xydp) {
473#ifdef GENERATION_DIAGNOSTICS
474 printf("Loop avoidance; removing (%d,%d,%c)\n",
475 xydp->x, xydp->y, "0RU3L567D9abcdef"[xydp->direction]);
476#endif
477 del234(possibilities, xydp);
478 sfree(xydp);
479 }
480 }
481
482 /*
483 * Add new possibilities to the list for moving _out_ of
484 * the tile we have just moved into.
485 */
486 for (d = 1; d < 0x10; d <<= 1) {
487 int x3, y3;
488
489 if (d == d2)
490 continue; /* we've got this one already */
491
492 if (!params->wrapping) {
493 if (d == U && y2 == 0)
494 continue;
495 if (d == D && y2 == h-1)
496 continue;
497 if (d == L && x2 == 0)
498 continue;
499 if (d == R && x2 == w-1)
500 continue;
501 }
502
503 OFFSET(x3, y3, x2, y2, d, params);
504
505 if (index(params, tiles, x3, y3))
506 continue; /* this would create a loop */
507
508#ifdef GENERATION_DIAGNOSTICS
509 printf("New frontier; adding (%d,%d,%c)\n",
510 x2, y2, "0RU3L567D9abcdef"[d]);
511#endif
512 add234(possibilities, new_xyd(x2, y2, d));
513 }
514 }
515 /* Having done that, we should have no possibilities remaining. */
516 assert(count234(possibilities) == 0);
517 freetree234(possibilities);
518
519 /*
520 * Now compute a list of the possible barrier locations.
521 */
522 barriertree = newtree234(xyd_cmp);
523 for (y = 0; y < h; y++) {
524 for (x = 0; x < w; x++) {
525
526 if (!(index(params, tiles, x, y) & R) &&
527 (params->wrapping || x < w-1))
528 add234(barriertree, new_xyd(x, y, R));
529 if (!(index(params, tiles, x, y) & D) &&
530 (params->wrapping || y < h-1))
531 add234(barriertree, new_xyd(x, y, D));
532 }
533 }
534
535 /*
536 * Save the unshuffled grid in aux.
537 */
538 {
539 char *solution;
540 int i;
541
542 /*
543 * String format is exactly the same as a solve move, so we
544 * can just dupstr this in solve_game().
545 */
546
547 solution = snewn(w * h + 2, char);
548 solution[0] = 'S';
549 for (i = 0; i < w * h; i++)
550 solution[i+1] = "0123456789abcdef"[tiles[i] & 0xF];
551 solution[w*h+1] = '\0';
552
553 *aux = solution;
554 }
555
556 /*
557 * Now shuffle the grid.
558 * FIXME - this simply does a set of random moves to shuffle the pieces,
559 * although we make a token effort to avoid boring cases by avoiding moves
560 * that directly undo the previous one, or that repeat so often as to
561 * turn into fewer moves.
562 *
563 * A better way would be to number all the pieces, generate a placement
564 * for all the numbers as for "sixteen", observing parity constraints if
565 * neccessary, and then place the pieces according to their numbering.
566 * BUT - I'm not sure if this will work, since we disallow movement of
567 * the middle row and column.
568 */
569 {
570 int i;
571 int cols = w - 1;
572 int rows = h - 1;
573 int moves = params->movetarget;
574 int prevdir = -1, prevrowcol = -1, nrepeats = 0;
575 if (!moves) moves = cols * rows * 2;
576 for (i = 0; i < moves; /* incremented conditionally */) {
577 /* Choose a direction: 0,1,2,3 = up, right, down, left. */
578 int dir = random_upto(rs, 4);
579 int rowcol;
580 if (dir % 2 == 0) {
581 int col = random_upto(rs, cols);
582 if (col >= cx) col += 1; /* avoid centre */
583 if (col == prevrowcol) {
584 if (dir == 2-prevdir)
585 continue; /* undoes last move */
586 else if (dir == prevdir && (nrepeats+1)*2 > h)
587 continue; /* makes fewer moves */
588 }
589 slide_col_int(w, h, tiles, 1 - dir, col);
590 rowcol = col;
591 } else {
592 int row = random_upto(rs, rows);
593 if (row >= cy) row += 1; /* avoid centre */
594 if (row == prevrowcol) {
595 if (dir == 4-prevdir)
596 continue; /* undoes last move */
597 else if (dir == prevdir && (nrepeats+1)*2 > w)
598 continue; /* makes fewer moves */
599 }
600 slide_row_int(w, h, tiles, 2 - dir, row);
601 rowcol = row;
602 }
603 if (dir == prevdir && rowcol == prevrowcol)
604 nrepeats++;
605 else
606 nrepeats = 1;
607 prevdir = dir;
608 prevrowcol = rowcol;
609 i++; /* if we got here, the move was accepted */
610 }
611 }
612
613 /*
614 * And now choose barrier locations. (We carefully do this
615 * _after_ shuffling, so that changing the barrier rate in the
616 * params while keeping the random seed the same will give the
617 * same shuffled grid and _only_ change the barrier locations.
618 * Also the way we choose barrier locations, by repeatedly
619 * choosing one possibility from the list until we have enough,
620 * is designed to ensure that raising the barrier rate while
621 * keeping the seed the same will provide a superset of the
622 * previous barrier set - i.e. if you ask for 10 barriers, and
623 * then decide that's still too hard and ask for 20, you'll get
624 * the original 10 plus 10 more, rather than getting 20 new
625 * ones and the chance of remembering your first 10.)
626 */
627 nbarriers = (int)(params->barrier_probability * count234(barriertree));
628 assert(nbarriers >= 0 && nbarriers <= count234(barriertree));
629
630 while (nbarriers > 0) {
631 int i;
632 struct xyd *xyd;
633 int x1, y1, d1, x2, y2, d2;
634
635 /*
636 * Extract a randomly chosen barrier from the list.
637 */
638 i = random_upto(rs, count234(barriertree));
639 xyd = delpos234(barriertree, i);
640
641 assert(xyd != NULL);
642
643 x1 = xyd->x;
644 y1 = xyd->y;
645 d1 = xyd->direction;
646 sfree(xyd);
647
648 OFFSET(x2, y2, x1, y1, d1, params);
649 d2 = F(d1);
650
651 index(params, barriers, x1, y1) |= d1;
652 index(params, barriers, x2, y2) |= d2;
653
654 nbarriers--;
655 }
656
657 /*
658 * Clean up the rest of the barrier list.
659 */
660 {
661 struct xyd *xyd;
662
663 while ( (xyd = delpos234(barriertree, 0)) != NULL)
664 sfree(xyd);
665
666 freetree234(barriertree);
667 }
668
669 /*
670 * Finally, encode the grid into a string game description.
671 *
672 * My syntax is extremely simple: each square is encoded as a
673 * hex digit in which bit 0 means a connection on the right,
674 * bit 1 means up, bit 2 left and bit 3 down. (i.e. the same
675 * encoding as used internally). Each digit is followed by
676 * optional barrier indicators: `v' means a vertical barrier to
677 * the right of it, and `h' means a horizontal barrier below
678 * it.
679 */
680 desc = snewn(w * h * 3 + 1, char);
681 p = desc;
682 for (y = 0; y < h; y++) {
683 for (x = 0; x < w; x++) {
684 *p++ = "0123456789abcdef"[index(params, tiles, x, y)];
685 if ((params->wrapping || x < w-1) &&
686 (index(params, barriers, x, y) & R))
687 *p++ = 'v';
688 if ((params->wrapping || y < h-1) &&
689 (index(params, barriers, x, y) & D))
690 *p++ = 'h';
691 }
692 }
693 assert(p - desc <= w*h*3);
694 *p = '\0';
695
696 sfree(tiles);
697 sfree(barriers);
698
699 return desc;
700}
701
702static const char *validate_desc(const game_params *params, const char *desc)
703{
704 int w = params->width, h = params->height;
705 int i;
706
707 for (i = 0; i < w*h; i++) {
708 if (*desc >= '0' && *desc <= '9')
709 /* OK */;
710 else if (*desc >= 'a' && *desc <= 'f')
711 /* OK */;
712 else if (*desc >= 'A' && *desc <= 'F')
713 /* OK */;
714 else if (!*desc)
715 return "Game description shorter than expected";
716 else
717 return "Game description contained unexpected character";
718 desc++;
719 while (*desc == 'h' || *desc == 'v')
720 desc++;
721 }
722 if (*desc)
723 return "Game description longer than expected";
724
725 return NULL;
726}
727
728/* ----------------------------------------------------------------------
729 * Construct an initial game state, given a description and parameters.
730 */
731
732static game_state *new_game(midend *me, const game_params *params,
733 const char *desc)
734{
735 game_state *state;
736 int w, h, x, y;
737
738 assert(params->width > 0 && params->height > 0);
739 assert(params->width > 1 || params->height > 1);
740
741 /*
742 * Create a blank game state.
743 */
744 state = snew(game_state);
745 w = state->width = params->width;
746 h = state->height = params->height;
747 state->cx = state->width / 2;
748 state->cy = state->height / 2;
749 state->wrapping = params->wrapping;
750 state->movetarget = params->movetarget;
751 state->completed = 0;
752 state->used_solve = false;
753 state->move_count = 0;
754 state->last_move_row = -1;
755 state->last_move_col = -1;
756 state->last_move_dir = 0;
757 state->tiles = snewn(state->width * state->height, unsigned char);
758 memset(state->tiles, 0, state->width * state->height);
759 state->barriers = snewn(state->width * state->height, unsigned char);
760 memset(state->barriers, 0, state->width * state->height);
761
762
763 /*
764 * Parse the game description into the grid.
765 */
766 for (y = 0; y < h; y++) {
767 for (x = 0; x < w; x++) {
768 if (*desc >= '0' && *desc <= '9')
769 tile(state, x, y) = *desc - '0';
770 else if (*desc >= 'a' && *desc <= 'f')
771 tile(state, x, y) = *desc - 'a' + 10;
772 else if (*desc >= 'A' && *desc <= 'F')
773 tile(state, x, y) = *desc - 'A' + 10;
774 if (*desc)
775 desc++;
776 while (*desc == 'h' || *desc == 'v') {
777 int x2, y2, d1, d2;
778 if (*desc == 'v')
779 d1 = R;
780 else
781 d1 = D;
782
783 OFFSET(x2, y2, x, y, d1, state);
784 d2 = F(d1);
785
786 barrier(state, x, y) |= d1;
787 barrier(state, x2, y2) |= d2;
788
789 desc++;
790 }
791 }
792 }
793
794 /*
795 * Set up border barriers if this is a non-wrapping game.
796 */
797 if (!state->wrapping) {
798 for (x = 0; x < state->width; x++) {
799 barrier(state, x, 0) |= U;
800 barrier(state, x, state->height-1) |= D;
801 }
802 for (y = 0; y < state->height; y++) {
803 barrier(state, 0, y) |= L;
804 barrier(state, state->width-1, y) |= R;
805 }
806 }
807
808 /*
809 * Set up the barrier corner flags, for drawing barriers
810 * prettily when they meet.
811 */
812 for (y = 0; y < state->height; y++) {
813 for (x = 0; x < state->width; x++) {
814 int dir;
815
816 for (dir = 1; dir < 0x10; dir <<= 1) {
817 int dir2 = A(dir);
818 int x1, y1, x2, y2, x3, y3;
819 bool corner = false;
820
821 if (!(barrier(state, x, y) & dir))
822 continue;
823
824 if (barrier(state, x, y) & dir2)
825 corner = true;
826
827 x1 = x + X(dir), y1 = y + Y(dir);
828 if (x1 >= 0 && x1 < state->width &&
829 y1 >= 0 && y1 < state->height &&
830 (barrier(state, x1, y1) & dir2))
831 corner = true;
832
833 x2 = x + X(dir2), y2 = y + Y(dir2);
834 if (x2 >= 0 && x2 < state->width &&
835 y2 >= 0 && y2 < state->height &&
836 (barrier(state, x2, y2) & dir))
837 corner = true;
838
839 if (corner) {
840 barrier(state, x, y) |= (dir << 4);
841 if (x1 >= 0 && x1 < state->width &&
842 y1 >= 0 && y1 < state->height)
843 barrier(state, x1, y1) |= (A(dir) << 4);
844 if (x2 >= 0 && x2 < state->width &&
845 y2 >= 0 && y2 < state->height)
846 barrier(state, x2, y2) |= (C(dir) << 4);
847 x3 = x + X(dir) + X(dir2), y3 = y + Y(dir) + Y(dir2);
848 if (x3 >= 0 && x3 < state->width &&
849 y3 >= 0 && y3 < state->height)
850 barrier(state, x3, y3) |= (F(dir) << 4);
851 }
852 }
853 }
854 }
855
856 return state;
857}
858
859static game_state *dup_game(const game_state *state)
860{
861 game_state *ret;
862
863 ret = snew(game_state);
864 ret->width = state->width;
865 ret->height = state->height;
866 ret->cx = state->cx;
867 ret->cy = state->cy;
868 ret->wrapping = state->wrapping;
869 ret->movetarget = state->movetarget;
870 ret->completed = state->completed;
871 ret->used_solve = state->used_solve;
872 ret->move_count = state->move_count;
873 ret->last_move_row = state->last_move_row;
874 ret->last_move_col = state->last_move_col;
875 ret->last_move_dir = state->last_move_dir;
876 ret->tiles = snewn(state->width * state->height, unsigned char);
877 memcpy(ret->tiles, state->tiles, state->width * state->height);
878 ret->barriers = snewn(state->width * state->height, unsigned char);
879 memcpy(ret->barriers, state->barriers, state->width * state->height);
880
881 return ret;
882}
883
884static void free_game(game_state *state)
885{
886 sfree(state->tiles);
887 sfree(state->barriers);
888 sfree(state);
889}
890
891static char *solve_game(const game_state *state, const game_state *currstate,
892 const char *aux, const char **error)
893{
894 if (!aux) {
895 *error = "Solution not known for this puzzle";
896 return NULL;
897 }
898
899 return dupstr(aux);
900}
901
902/* ----------------------------------------------------------------------
903 * Utility routine.
904 */
905
906/*
907 * Compute which squares are reachable from the centre square, as a
908 * quick visual aid to determining how close the game is to
909 * completion. This is also a simple way to tell if the game _is_
910 * completed - just call this function and see whether every square
911 * is marked active.
912 *
913 * squares in the moving_row and moving_col are always inactive - this
914 * is so that "current" doesn't appear to jump across moving lines.
915 */
916static unsigned char *compute_active(const game_state *state,
917 int moving_row, int moving_col)
918{
919 unsigned char *active;
920 tree234 *todo;
921 struct xyd *xyd;
922
923 active = snewn(state->width * state->height, unsigned char);
924 memset(active, 0, state->width * state->height);
925
926 /*
927 * We only store (x,y) pairs in todo, but it's easier to reuse
928 * xyd_cmp and just store direction 0 every time.
929 */
930 todo = newtree234(xyd_cmp);
931 index(state, active, state->cx, state->cy) = ACTIVE;
932 add234(todo, new_xyd(state->cx, state->cy, 0));
933
934 while ( (xyd = delpos234(todo, 0)) != NULL) {
935 int x1, y1, d1, x2, y2, d2;
936
937 x1 = xyd->x;
938 y1 = xyd->y;
939 sfree(xyd);
940
941 for (d1 = 1; d1 < 0x10; d1 <<= 1) {
942 OFFSET(x2, y2, x1, y1, d1, state);
943 d2 = F(d1);
944
945 /*
946 * If the next tile in this direction is connected to
947 * us, and there isn't a barrier in the way, and it
948 * isn't already marked active, then mark it active and
949 * add it to the to-examine list.
950 */
951 if ((x2 != moving_col && y2 != moving_row) &&
952 (tile(state, x1, y1) & d1) &&
953 (tile(state, x2, y2) & d2) &&
954 !(barrier(state, x1, y1) & d1) &&
955 !index(state, active, x2, y2)) {
956 index(state, active, x2, y2) = ACTIVE;
957 add234(todo, new_xyd(x2, y2, 0));
958 }
959 }
960 }
961 /* Now we expect the todo list to have shrunk to zero size. */
962 assert(count234(todo) == 0);
963 freetree234(todo);
964
965 return active;
966}
967
968struct game_ui {
969 int cur_x, cur_y;
970 bool cur_visible;
971};
972
973static game_ui *new_ui(const game_state *state)
974{
975 game_ui *ui = snew(game_ui);
976 ui->cur_x = 0;
977 ui->cur_y = -1;
978 ui->cur_visible = getenv_bool("PUZZLES_SHOW_CURSOR", false);
979
980 return ui;
981}
982
983static void free_ui(game_ui *ui)
984{
985 sfree(ui);
986}
987
988/* ----------------------------------------------------------------------
989 * Process a move.
990 */
991
992static void slide_row_int(int w, int h, unsigned char *tiles, int dir, int row)
993{
994 int x = dir > 0 ? -1 : w;
995 int tx = x + dir;
996 int n = w - 1;
997 unsigned char endtile;
998 assert(0 <= tx && tx < w);
999 endtile = tiles[row * w + tx];
1000 do {
1001 x = tx;
1002 tx = (x + dir + w) % w;
1003 tiles[row * w + x] = tiles[row * w + tx];
1004 } while (--n > 0);
1005 tiles[row * w + tx] = endtile;
1006}
1007
1008static void slide_col_int(int w, int h, unsigned char *tiles, int dir, int col)
1009{
1010 int y = dir > 0 ? -1 : h;
1011 int ty = y + dir;
1012 int n = h - 1;
1013 unsigned char endtile;
1014 assert(0 <= ty && ty < h);
1015 endtile = tiles[ty * w + col];
1016 do {
1017 y = ty;
1018 ty = (y + dir + h) % h;
1019 tiles[y * w + col] = tiles[ty * w + col];
1020 } while (--n > 0);
1021 tiles[ty * w + col] = endtile;
1022}
1023
1024static void slide_row(game_state *state, int dir, int row)
1025{
1026 slide_row_int(state->width, state->height, state->tiles, dir, row);
1027}
1028
1029static void slide_col(game_state *state, int dir, int col)
1030{
1031 slide_col_int(state->width, state->height, state->tiles, dir, col);
1032}
1033
1034static void game_changed_state(game_ui *ui, const game_state *oldstate,
1035 const game_state *newstate)
1036{
1037}
1038
1039struct game_drawstate {
1040 bool started;
1041 int width, height;
1042 int tilesize;
1043 unsigned char *visible;
1044 int cur_x, cur_y;
1045};
1046
1047static const char *current_key_label(const game_ui *ui,
1048 const game_state *state, int button)
1049{
1050 if (IS_CURSOR_SELECT(button) && ui->cur_visible)
1051 return "Slide";
1052 return "";
1053}
1054
1055static char *interpret_move(const game_state *state, game_ui *ui,
1056 const game_drawstate *ds,
1057 int x, int y, int button)
1058{
1059 int cx, cy;
1060 int dx, dy;
1061 char buf[80];
1062
1063 button = STRIP_BUTTON_MODIFIERS(button);
1064
1065 if (IS_CURSOR_MOVE(button)) {
1066 int cpos, diff = 0;
1067 cpos = c2pos(state->width, state->height, ui->cur_x, ui->cur_y);
1068 diff = c2diff(state->width, state->height, ui->cur_x, ui->cur_y, button);
1069
1070 if (diff != 0) {
1071 do { /* we might have to do this more than once to skip missing arrows */
1072 cpos += diff;
1073 pos2c(state->width, state->height, cpos, &ui->cur_x, &ui->cur_y);
1074 } while (ui->cur_x == state->cx || ui->cur_y == state->cy);
1075 }
1076
1077 ui->cur_visible = true;
1078 return MOVE_UI_UPDATE;
1079 }
1080
1081 if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
1082 cx = (x - (BORDER + WINDOW_OFFSET + TILE_BORDER) + 2*TILE_SIZE) / TILE_SIZE - 2;
1083 cy = (y - (BORDER + WINDOW_OFFSET + TILE_BORDER) + 2*TILE_SIZE) / TILE_SIZE - 2;
1084 ui->cur_visible = false;
1085 } else if (IS_CURSOR_SELECT(button)) {
1086 if (ui->cur_visible) {
1087 cx = ui->cur_x;
1088 cy = ui->cur_y;
1089 } else {
1090 /* 'click' when cursor is invisible just makes cursor visible. */
1091 ui->cur_visible = true;
1092 return MOVE_UI_UPDATE;
1093 }
1094 } else
1095 return NULL;
1096
1097 if (cy >= 0 && cy < state->height && cy != state->cy)
1098 {
1099 if (cx == -1) dx = +1;
1100 else if (cx == state->width) dx = -1;
1101 else return NULL;
1102 dy = 0;
1103 }
1104 else if (cx >= 0 && cx < state->width && cx != state->cx)
1105 {
1106 if (cy == -1) dy = +1;
1107 else if (cy == state->height) dy = -1;
1108 else return NULL;
1109 dx = 0;
1110 }
1111 else
1112 return NULL;
1113
1114 /* reverse direction if right hand button is pressed */
1115 if (button == RIGHT_BUTTON)
1116 {
1117 dx = -dx;
1118 dy = -dy;
1119 }
1120
1121 if (dx == 0)
1122 sprintf(buf, "C%d,%d", cx, dy);
1123 else
1124 sprintf(buf, "R%d,%d", cy, dx);
1125 return dupstr(buf);
1126}
1127
1128static game_state *execute_move(const game_state *from, const char *move)
1129{
1130 game_state *ret;
1131 int c, d;
1132 bool col;
1133
1134 if ((move[0] == 'C' || move[0] == 'R') &&
1135 sscanf(move+1, "%d,%d", &c, &d) == 2 &&
1136 c >= 0 && c < (move[0] == 'C' ? from->width : from->height) &&
1137 d <= (move[0] == 'C' ? from->height : from->width) &&
1138 d >= -(move[0] == 'C' ? from->height : from->width) && d != 0) {
1139 col = (move[0] == 'C');
1140 } else if (move[0] == 'S' &&
1141 strlen(move) == from->width * from->height + 1) {
1142 int i;
1143 ret = dup_game(from);
1144 ret->used_solve = true;
1145 ret->completed = ret->move_count = 1;
1146
1147 for (i = 0; i < from->width * from->height; i++) {
1148 c = move[i+1];
1149 if (c >= '0' && c <= '9')
1150 c -= '0';
1151 else if (c >= 'A' && c <= 'F')
1152 c -= 'A' - 10;
1153 else if (c >= 'a' && c <= 'f')
1154 c -= 'a' - 10;
1155 else {
1156 free_game(ret);
1157 return NULL;
1158 }
1159 ret->tiles[i] = c;
1160 }
1161 return ret;
1162 } else
1163 return NULL; /* can't parse move string */
1164
1165 ret = dup_game(from);
1166
1167 if (col)
1168 slide_col(ret, d, c);
1169 else
1170 slide_row(ret, d, c);
1171
1172 ret->move_count++;
1173 ret->last_move_row = col ? -1 : c;
1174 ret->last_move_col = col ? c : -1;
1175 ret->last_move_dir = d;
1176
1177 /*
1178 * See if the game has been completed.
1179 */
1180 if (!ret->completed) {
1181 unsigned char *active = compute_active(ret, -1, -1);
1182 int x1, y1;
1183 bool complete = true;
1184
1185 for (x1 = 0; x1 < ret->width; x1++)
1186 for (y1 = 0; y1 < ret->height; y1++)
1187 if (!index(ret, active, x1, y1)) {
1188 complete = false;
1189 goto break_label; /* break out of two loops at once */
1190 }
1191 break_label:
1192
1193 sfree(active);
1194
1195 if (complete)
1196 ret->completed = ret->move_count;
1197 }
1198
1199 return ret;
1200}
1201
1202/* ----------------------------------------------------------------------
1203 * Routines for drawing the game position on the screen.
1204 */
1205
1206static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
1207{
1208 game_drawstate *ds = snew(game_drawstate);
1209
1210 ds->started = false;
1211 ds->width = state->width;
1212 ds->height = state->height;
1213 ds->visible = snewn(state->width * state->height, unsigned char);
1214 ds->tilesize = 0; /* not decided yet */
1215 memset(ds->visible, 0xFF, state->width * state->height);
1216 ds->cur_x = ds->cur_y = -1;
1217
1218 return ds;
1219}
1220
1221static void game_free_drawstate(drawing *dr, game_drawstate *ds)
1222{
1223 sfree(ds->visible);
1224 sfree(ds);
1225}
1226
1227static void game_compute_size(const game_params *params, int tilesize,
1228 const game_ui *ui, int *x, int *y)
1229{
1230 /* Ick: fake up `ds->tilesize' for macro expansion purposes */
1231 struct { int tilesize; } ads, *ds = &ads;
1232 ads.tilesize = tilesize;
1233
1234 *x = BORDER * 2 + WINDOW_OFFSET * 2 + TILE_SIZE * params->width + TILE_BORDER;
1235 *y = BORDER * 2 + WINDOW_OFFSET * 2 + TILE_SIZE * params->height + TILE_BORDER;
1236}
1237
1238static void game_set_size(drawing *dr, game_drawstate *ds,
1239 const game_params *params, int tilesize)
1240{
1241 ds->tilesize = tilesize;
1242}
1243
1244static float *game_colours(frontend *fe, int *ncolours)
1245{
1246 float *ret;
1247
1248 ret = snewn(NCOLOURS * 3, float);
1249 *ncolours = NCOLOURS;
1250
1251 /*
1252 * Basic background colour is whatever the front end thinks is
1253 * a sensible default.
1254 */
1255 frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
1256
1257 /*
1258 * Wires are black.
1259 */
1260 ret[COL_WIRE * 3 + 0] = 0.0F;
1261 ret[COL_WIRE * 3 + 1] = 0.0F;
1262 ret[COL_WIRE * 3 + 2] = 0.0F;
1263
1264 /*
1265 * Powered wires and powered endpoints are cyan.
1266 */
1267 ret[COL_POWERED * 3 + 0] = 0.0F;
1268 ret[COL_POWERED * 3 + 1] = 1.0F;
1269 ret[COL_POWERED * 3 + 2] = 1.0F;
1270
1271 /*
1272 * Barriers are red.
1273 */
1274 ret[COL_BARRIER * 3 + 0] = 1.0F;
1275 ret[COL_BARRIER * 3 + 1] = 0.0F;
1276 ret[COL_BARRIER * 3 + 2] = 0.0F;
1277
1278 /*
1279 * Unpowered endpoints are blue.
1280 */
1281 ret[COL_ENDPOINT * 3 + 0] = 0.0F;
1282 ret[COL_ENDPOINT * 3 + 1] = 0.0F;
1283 ret[COL_ENDPOINT * 3 + 2] = 1.0F;
1284
1285 /*
1286 * Tile borders are a darker grey than the background.
1287 */
1288 ret[COL_BORDER * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0];
1289 ret[COL_BORDER * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1];
1290 ret[COL_BORDER * 3 + 2] = 0.5F * ret[COL_BACKGROUND * 3 + 2];
1291
1292 /*
1293 * Flashing tiles are a grey in between those two.
1294 */
1295 ret[COL_FLASHING * 3 + 0] = 0.75F * ret[COL_BACKGROUND * 3 + 0];
1296 ret[COL_FLASHING * 3 + 1] = 0.75F * ret[COL_BACKGROUND * 3 + 1];
1297 ret[COL_FLASHING * 3 + 2] = 0.75F * ret[COL_BACKGROUND * 3 + 2];
1298
1299 ret[COL_LOWLIGHT * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 0.8F;
1300 ret[COL_LOWLIGHT * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 0.8F;
1301 ret[COL_LOWLIGHT * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 0.8F;
1302 ret[COL_TEXT * 3 + 0] = 0.0;
1303 ret[COL_TEXT * 3 + 1] = 0.0;
1304 ret[COL_TEXT * 3 + 2] = 0.0;
1305
1306 return ret;
1307}
1308
1309static void draw_filled_line(drawing *dr, int x1, int y1, int x2, int y2,
1310 int colour)
1311{
1312 draw_line(dr, x1-1, y1, x2-1, y2, COL_WIRE);
1313 draw_line(dr, x1+1, y1, x2+1, y2, COL_WIRE);
1314 draw_line(dr, x1, y1-1, x2, y2-1, COL_WIRE);
1315 draw_line(dr, x1, y1+1, x2, y2+1, COL_WIRE);
1316 draw_line(dr, x1, y1, x2, y2, colour);
1317}
1318
1319static void draw_rect_coords(drawing *dr, int x1, int y1, int x2, int y2,
1320 int colour)
1321{
1322 int mx = (x1 < x2 ? x1 : x2);
1323 int my = (y1 < y2 ? y1 : y2);
1324 int dx = (x2 + x1 - 2*mx + 1);
1325 int dy = (y2 + y1 - 2*my + 1);
1326
1327 draw_rect(dr, mx, my, dx, dy, colour);
1328}
1329
1330static void draw_barrier_corner(drawing *dr, game_drawstate *ds,
1331 int x, int y, int dir, int phase)
1332{
1333 int bx = BORDER + WINDOW_OFFSET + TILE_SIZE * x;
1334 int by = BORDER + WINDOW_OFFSET + TILE_SIZE * y;
1335 int x1, y1, dx, dy, dir2;
1336
1337 dir >>= 4;
1338
1339 dir2 = A(dir);
1340 dx = X(dir) + X(dir2);
1341 dy = Y(dir) + Y(dir2);
1342 x1 = (dx > 0 ? TILE_SIZE+TILE_BORDER-1 : 0);
1343 y1 = (dy > 0 ? TILE_SIZE+TILE_BORDER-1 : 0);
1344
1345 if (phase == 0) {
1346 draw_rect_coords(dr, bx+x1, by+y1,
1347 bx+x1-TILE_BORDER*dx, by+y1-(TILE_BORDER-1)*dy,
1348 COL_WIRE);
1349 draw_rect_coords(dr, bx+x1, by+y1,
1350 bx+x1-(TILE_BORDER-1)*dx, by+y1-TILE_BORDER*dy,
1351 COL_WIRE);
1352 } else {
1353 draw_rect_coords(dr, bx+x1, by+y1,
1354 bx+x1-(TILE_BORDER-1)*dx, by+y1-(TILE_BORDER-1)*dy,
1355 COL_BARRIER);
1356 }
1357}
1358
1359static void draw_barrier(drawing *dr, game_drawstate *ds,
1360 int x, int y, int dir, int phase)
1361{
1362 int bx = BORDER + WINDOW_OFFSET + TILE_SIZE * x;
1363 int by = BORDER + WINDOW_OFFSET + TILE_SIZE * y;
1364 int x1, y1, w, h;
1365
1366 x1 = (X(dir) > 0 ? TILE_SIZE : X(dir) == 0 ? TILE_BORDER : 0);
1367 y1 = (Y(dir) > 0 ? TILE_SIZE : Y(dir) == 0 ? TILE_BORDER : 0);
1368 w = (X(dir) ? TILE_BORDER : TILE_SIZE - TILE_BORDER);
1369 h = (Y(dir) ? TILE_BORDER : TILE_SIZE - TILE_BORDER);
1370
1371 if (phase == 0) {
1372 draw_rect(dr, bx+x1-X(dir), by+y1-Y(dir), w, h, COL_WIRE);
1373 } else {
1374 draw_rect(dr, bx+x1, by+y1, w, h, COL_BARRIER);
1375 }
1376}
1377
1378static void draw_tile(drawing *dr, game_drawstate *ds, const game_state *state,
1379 int x, int y, int tile, float xshift, float yshift)
1380{
1381 int bx = BORDER + WINDOW_OFFSET + TILE_SIZE * x + (int)(xshift * TILE_SIZE);
1382 int by = BORDER + WINDOW_OFFSET + TILE_SIZE * y + (int)(yshift * TILE_SIZE);
1383 float cx, cy, ex, ey;
1384 int dir, col;
1385
1386 /*
1387 * When we draw a single tile, we must draw everything up to
1388 * and including the borders around the tile. This means that
1389 * if the neighbouring tiles have connections to those borders,
1390 * we must draw those connections on the borders themselves.
1391 *
1392 * This would be terribly fiddly if we ever had to draw a tile
1393 * while its neighbour was in mid-rotate, because we'd have to
1394 * arrange to _know_ that the neighbour was being rotated and
1395 * hence had an anomalous effect on the redraw of this tile.
1396 * Fortunately, the drawing algorithm avoids ever calling us in
1397 * this circumstance: we're either drawing lots of straight
1398 * tiles at game start or after a move is complete, or we're
1399 * repeatedly drawing only the rotating tile. So no problem.
1400 */
1401
1402 /*
1403 * So. First blank the tile out completely: draw a big
1404 * rectangle in border colour, and a smaller rectangle in
1405 * background colour to fill it in.
1406 */
1407 draw_rect(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER,
1408 COL_BORDER);
1409 draw_rect(dr, bx+TILE_BORDER, by+TILE_BORDER,
1410 TILE_SIZE-TILE_BORDER, TILE_SIZE-TILE_BORDER,
1411 tile & FLASHING ? COL_FLASHING : COL_BACKGROUND);
1412
1413 /*
1414 * Draw the wires.
1415 */
1416 cx = cy = TILE_BORDER + (TILE_SIZE-TILE_BORDER) / 2.0F - 0.5F;
1417 col = (tile & ACTIVE ? COL_POWERED : COL_WIRE);
1418 for (dir = 1; dir < 0x10; dir <<= 1) {
1419 if (tile & dir) {
1420 ex = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * X(dir);
1421 ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir);
1422 draw_filled_line(dr, bx+(int)cx, by+(int)cy,
1423 bx+(int)(cx+ex), by+(int)(cy+ey),
1424 COL_WIRE);
1425 }
1426 }
1427 for (dir = 1; dir < 0x10; dir <<= 1) {
1428 if (tile & dir) {
1429 ex = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * X(dir);
1430 ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir);
1431 draw_line(dr, bx+(int)cx, by+(int)cy,
1432 bx+(int)(cx+ex), by+(int)(cy+ey), col);
1433 }
1434 }
1435
1436 /*
1437 * Draw the box in the middle. We do this in blue if the tile
1438 * is an unpowered endpoint, in cyan if the tile is a powered
1439 * endpoint, in black if the tile is the centrepiece, and
1440 * otherwise not at all.
1441 */
1442 col = -1;
1443 if (x == state->cx && y == state->cy)
1444 col = COL_WIRE;
1445 else if (COUNT(tile) == 1) {
1446 col = (tile & ACTIVE ? COL_POWERED : COL_ENDPOINT);
1447 }
1448 if (col >= 0) {
1449 int i, points[8];
1450
1451 points[0] = +1; points[1] = +1;
1452 points[2] = +1; points[3] = -1;
1453 points[4] = -1; points[5] = -1;
1454 points[6] = -1; points[7] = +1;
1455
1456 for (i = 0; i < 8; i += 2) {
1457 ex = (TILE_SIZE * 0.24F) * points[i];
1458 ey = (TILE_SIZE * 0.24F) * points[i+1];
1459 points[i] = bx+(int)(cx+ex);
1460 points[i+1] = by+(int)(cy+ey);
1461 }
1462
1463 draw_polygon(dr, points, 4, col, COL_WIRE);
1464 }
1465
1466 /*
1467 * Draw the points on the border if other tiles are connected
1468 * to us.
1469 */
1470 for (dir = 1; dir < 0x10; dir <<= 1) {
1471 int dx, dy, px, py, lx, ly, vx, vy, ox, oy;
1472
1473 dx = X(dir);
1474 dy = Y(dir);
1475
1476 ox = x + dx;
1477 oy = y + dy;
1478
1479 if (ox < 0 || ox >= state->width || oy < 0 || oy >= state->height)
1480 continue;
1481
1482 if (!(tile(state, ox, oy) & F(dir)))
1483 continue;
1484
1485 px = bx + (int)(dx>0 ? TILE_SIZE + TILE_BORDER - 1 : dx<0 ? 0 : cx);
1486 py = by + (int)(dy>0 ? TILE_SIZE + TILE_BORDER - 1 : dy<0 ? 0 : cy);
1487 lx = dx * (TILE_BORDER-1);
1488 ly = dy * (TILE_BORDER-1);
1489 vx = (dy ? 1 : 0);
1490 vy = (dx ? 1 : 0);
1491
1492 if (xshift == 0.0F && yshift == 0.0F && (tile & dir)) {
1493 /*
1494 * If we are fully connected to the other tile, we must
1495 * draw right across the tile border. (We can use our
1496 * own ACTIVE state to determine what colour to do this
1497 * in: if we are fully connected to the other tile then
1498 * the two ACTIVE states will be the same.)
1499 */
1500 draw_rect_coords(dr, px-vx, py-vy, px+lx+vx, py+ly+vy, COL_WIRE);
1501 draw_rect_coords(dr, px, py, px+lx, py+ly,
1502 (tile & ACTIVE) ? COL_POWERED : COL_WIRE);
1503 } else {
1504 /*
1505 * The other tile extends into our border, but isn't
1506 * actually connected to us. Just draw a single black
1507 * dot.
1508 */
1509 draw_rect_coords(dr, px, py, px, py, COL_WIRE);
1510 }
1511 }
1512
1513 draw_update(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
1514}
1515
1516static void draw_tile_barriers(drawing *dr, game_drawstate *ds,
1517 const game_state *state, int x, int y)
1518{
1519 int phase;
1520 int dir;
1521 int bx = BORDER + WINDOW_OFFSET + TILE_SIZE * x;
1522 int by = BORDER + WINDOW_OFFSET + TILE_SIZE * y;
1523 /*
1524 * Draw barrier corners, and then barriers.
1525 */
1526 for (phase = 0; phase < 2; phase++) {
1527 for (dir = 1; dir < 0x10; dir <<= 1)
1528 if (barrier(state, x, y) & (dir << 4))
1529 draw_barrier_corner(dr, ds, x, y, dir << 4, phase);
1530 for (dir = 1; dir < 0x10; dir <<= 1)
1531 if (barrier(state, x, y) & dir)
1532 draw_barrier(dr, ds, x, y, dir, phase);
1533 }
1534
1535 draw_update(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
1536}
1537
1538static void draw_arrow(drawing *dr, game_drawstate *ds,
1539 int x, int y, int xdx, int xdy, bool cur)
1540{
1541 int coords[14];
1542 int ydy = -xdx, ydx = xdy;
1543
1544 x = x * TILE_SIZE + BORDER + WINDOW_OFFSET;
1545 y = y * TILE_SIZE + BORDER + WINDOW_OFFSET;
1546
1547#define POINT(n, xx, yy) ( \
1548 coords[2*(n)+0] = x + (xx)*xdx + (yy)*ydx, \
1549 coords[2*(n)+1] = y + (xx)*xdy + (yy)*ydy)
1550
1551 POINT(0, TILE_SIZE / 2, 3 * TILE_SIZE / 4); /* top of arrow */
1552 POINT(1, 3 * TILE_SIZE / 4, TILE_SIZE / 2); /* right corner */
1553 POINT(2, 5 * TILE_SIZE / 8, TILE_SIZE / 2); /* right concave */
1554 POINT(3, 5 * TILE_SIZE / 8, TILE_SIZE / 4); /* bottom right */
1555 POINT(4, 3 * TILE_SIZE / 8, TILE_SIZE / 4); /* bottom left */
1556 POINT(5, 3 * TILE_SIZE / 8, TILE_SIZE / 2); /* left concave */
1557 POINT(6, TILE_SIZE / 4, TILE_SIZE / 2); /* left corner */
1558
1559 draw_polygon(dr, coords, 7, cur ? COL_POWERED : COL_LOWLIGHT, COL_TEXT);
1560}
1561
1562static void draw_arrow_for_cursor(drawing *dr, game_drawstate *ds,
1563 int cur_x, int cur_y, bool cur)
1564{
1565 if (cur_x == -1 && cur_y == -1)
1566 return; /* 'no cursur here */
1567 else if (cur_x == -1) /* LH column. */
1568 draw_arrow(dr, ds, 0, cur_y+1, 0, -1, cur);
1569 else if (cur_x == ds->width) /* RH column */
1570 draw_arrow(dr, ds, ds->width, cur_y, 0, +1, cur);
1571 else if (cur_y == -1) /* Top row */
1572 draw_arrow(dr, ds, cur_x, 0, +1, 0, cur);
1573 else if (cur_y == ds->height) /* Bottom row */
1574 draw_arrow(dr, ds, cur_x+1, ds->height, -1, 0, cur);
1575 else
1576 assert(!"Invalid cursor position");
1577
1578 draw_update(dr,
1579 cur_x * TILE_SIZE + BORDER + WINDOW_OFFSET,
1580 cur_y * TILE_SIZE + BORDER + WINDOW_OFFSET,
1581 TILE_SIZE, TILE_SIZE);
1582}
1583
1584static void game_redraw(drawing *dr, game_drawstate *ds,
1585 const game_state *oldstate, const game_state *state,
1586 int dir, const game_ui *ui,
1587 float t, float ft)
1588{
1589 int x, y, frame;
1590 unsigned char *active;
1591 float xshift = 0.0;
1592 float yshift = 0.0;
1593 int cur_x = -1, cur_y = -1;
1594
1595 /*
1596 * Draw the exterior barrier lines if this is our first call.
1597 */
1598 if (!ds->started) {
1599 int phase;
1600
1601 ds->started = true;
1602
1603 for (phase = 0; phase < 2; phase++) {
1604
1605 for (x = 0; x < ds->width; x++) {
1606 if (barrier(state, x, 0) & UL)
1607 draw_barrier_corner(dr, ds, x, -1, LD, phase);
1608 if (barrier(state, x, 0) & RU)
1609 draw_barrier_corner(dr, ds, x, -1, DR, phase);
1610 if (barrier(state, x, 0) & U)
1611 draw_barrier(dr, ds, x, -1, D, phase);
1612 if (barrier(state, x, ds->height-1) & DR)
1613 draw_barrier_corner(dr, ds, x, ds->height, RU, phase);
1614 if (barrier(state, x, ds->height-1) & LD)
1615 draw_barrier_corner(dr, ds, x, ds->height, UL, phase);
1616 if (barrier(state, x, ds->height-1) & D)
1617 draw_barrier(dr, ds, x, ds->height, U, phase);
1618 }
1619
1620 for (y = 0; y < ds->height; y++) {
1621 if (barrier(state, 0, y) & UL)
1622 draw_barrier_corner(dr, ds, -1, y, RU, phase);
1623 if (barrier(state, 0, y) & LD)
1624 draw_barrier_corner(dr, ds, -1, y, DR, phase);
1625 if (barrier(state, 0, y) & L)
1626 draw_barrier(dr, ds, -1, y, R, phase);
1627 if (barrier(state, ds->width-1, y) & RU)
1628 draw_barrier_corner(dr, ds, ds->width, y, UL, phase);
1629 if (barrier(state, ds->width-1, y) & DR)
1630 draw_barrier_corner(dr, ds, ds->width, y, LD, phase);
1631 if (barrier(state, ds->width-1, y) & R)
1632 draw_barrier(dr, ds, ds->width, y, L, phase);
1633 }
1634 }
1635
1636 /*
1637 * Arrows for making moves.
1638 */
1639 for (x = 0; x < ds->width; x++) {
1640 if (x == state->cx) continue;
1641 draw_arrow(dr, ds, x, 0, +1, 0, false);
1642 draw_arrow(dr, ds, x+1, ds->height, -1, 0, false);
1643 }
1644 for (y = 0; y < ds->height; y++) {
1645 if (y == state->cy) continue;
1646 draw_arrow(dr, ds, ds->width, y, 0, +1, false);
1647 draw_arrow(dr, ds, 0, y+1, 0, -1, false);
1648 }
1649 }
1650 if (ui->cur_visible) {
1651 cur_x = ui->cur_x; cur_y = ui->cur_y;
1652 }
1653 if (cur_x != ds->cur_x || cur_y != ds->cur_y) {
1654 /* Cursor has changed; redraw two (prev and curr) arrows. */
1655 assert(cur_x != state->cx && cur_y != state->cy);
1656
1657 draw_arrow_for_cursor(dr, ds, cur_x, cur_y, true);
1658 draw_arrow_for_cursor(dr, ds, ds->cur_x, ds->cur_y, false);
1659 ds->cur_x = cur_x; ds->cur_y = cur_y;
1660 }
1661
1662 /* Check if this is an undo. If so, we will need to run any animation
1663 * backwards.
1664 */
1665 if (oldstate && oldstate->move_count > state->move_count) {
1666 const game_state * tmpstate = state;
1667 state = oldstate;
1668 oldstate = tmpstate;
1669 t = ANIM_TIME - t;
1670 }
1671
1672 if (oldstate && (t < ANIM_TIME)) {
1673 /*
1674 * We're animating a slide, of row/column number
1675 * state->last_move_pos, in direction
1676 * state->last_move_dir
1677 */
1678 xshift = state->last_move_row == -1 ? 0.0F :
1679 (1 - t / ANIM_TIME) * state->last_move_dir;
1680 yshift = state->last_move_col == -1 ? 0.0F :
1681 (1 - t / ANIM_TIME) * state->last_move_dir;
1682 }
1683
1684 frame = -1;
1685 if (ft > 0) {
1686 /*
1687 * We're animating a completion flash. Find which frame
1688 * we're at.
1689 */
1690 frame = (int)(ft / FLASH_FRAME);
1691 }
1692
1693 /*
1694 * Draw any tile which differs from the way it was last drawn.
1695 */
1696 if (xshift != 0.0F || yshift != 0.0F) {
1697 active = compute_active(state,
1698 state->last_move_row, state->last_move_col);
1699 } else {
1700 active = compute_active(state, -1, -1);
1701 }
1702
1703 clip(dr,
1704 BORDER + WINDOW_OFFSET, BORDER + WINDOW_OFFSET,
1705 TILE_SIZE * state->width + TILE_BORDER,
1706 TILE_SIZE * state->height + TILE_BORDER);
1707
1708 for (x = 0; x < ds->width; x++)
1709 for (y = 0; y < ds->height; y++) {
1710 unsigned char c = tile(state, x, y) | index(state, active, x, y);
1711
1712 /*
1713 * In a completion flash, we adjust the FLASHING bit
1714 * depending on our distance from the centre point and
1715 * the frame number.
1716 */
1717 if (frame >= 0) {
1718 int xdist, ydist, dist;
1719 xdist = (x < state->cx ? state->cx - x : x - state->cx);
1720 ydist = (y < state->cy ? state->cy - y : y - state->cy);
1721 dist = (xdist > ydist ? xdist : ydist);
1722
1723 if (frame >= dist && frame < dist+4) {
1724 int flash = (frame - dist) & 1;
1725 flash = flash ? FLASHING : 0;
1726 c = (c &~ FLASHING) | flash;
1727 }
1728 }
1729
1730 if (index(state, ds->visible, x, y) != c ||
1731 index(state, ds->visible, x, y) == 0xFF ||
1732 (x == state->last_move_col || y == state->last_move_row))
1733 {
1734 float xs = (y == state->last_move_row ? xshift : (float)0.0);
1735 float ys = (x == state->last_move_col ? yshift : (float)0.0);
1736
1737 draw_tile(dr, ds, state, x, y, c, xs, ys);
1738 if (xs < 0 && x == 0)
1739 draw_tile(dr, ds, state, state->width, y, c, xs, ys);
1740 else if (xs > 0 && x == state->width - 1)
1741 draw_tile(dr, ds, state, -1, y, c, xs, ys);
1742 else if (ys < 0 && y == 0)
1743 draw_tile(dr, ds, state, x, state->height, c, xs, ys);
1744 else if (ys > 0 && y == state->height - 1)
1745 draw_tile(dr, ds, state, x, -1, c, xs, ys);
1746
1747 if (x == state->last_move_col || y == state->last_move_row)
1748 index(state, ds->visible, x, y) = 0xFF;
1749 else
1750 index(state, ds->visible, x, y) = c;
1751 }
1752 }
1753
1754 for (x = 0; x < ds->width; x++)
1755 for (y = 0; y < ds->height; y++)
1756 draw_tile_barriers(dr, ds, state, x, y);
1757
1758 unclip(dr);
1759
1760 /*
1761 * Update the status bar.
1762 */
1763 {
1764 char statusbuf[256];
1765 int i, n, a;
1766
1767 n = state->width * state->height;
1768 for (i = a = 0; i < n; i++)
1769 if (active[i])
1770 a++;
1771
1772 if (state->used_solve)
1773 sprintf(statusbuf, "Moves since auto-solve: %d",
1774 state->move_count - state->completed);
1775 else
1776 sprintf(statusbuf, "%sMoves: %d",
1777 (state->completed ? "COMPLETED! " : ""),
1778 (state->completed ? state->completed : state->move_count));
1779
1780 if (state->movetarget)
1781 sprintf(statusbuf + strlen(statusbuf), " (target %d)",
1782 state->movetarget);
1783
1784 sprintf(statusbuf + strlen(statusbuf), " Active: %d/%d", a, n);
1785
1786 status_bar(dr, statusbuf);
1787 }
1788
1789 sfree(active);
1790}
1791
1792static float game_anim_length(const game_state *oldstate,
1793 const game_state *newstate, int dir, game_ui *ui)
1794{
1795 return ANIM_TIME;
1796}
1797
1798static float game_flash_length(const game_state *oldstate,
1799 const game_state *newstate, int dir, game_ui *ui)
1800{
1801 /*
1802 * If the game has just been completed, we display a completion
1803 * flash.
1804 */
1805 if (!oldstate->completed && newstate->completed &&
1806 !oldstate->used_solve && !newstate->used_solve) {
1807 int size;
1808 size = 0;
1809 if (size < newstate->cx+1)
1810 size = newstate->cx+1;
1811 if (size < newstate->cy+1)
1812 size = newstate->cy+1;
1813 if (size < newstate->width - newstate->cx)
1814 size = newstate->width - newstate->cx;
1815 if (size < newstate->height - newstate->cy)
1816 size = newstate->height - newstate->cy;
1817 return FLASH_FRAME * (size+4);
1818 }
1819
1820 return 0.0F;
1821}
1822
1823static void game_get_cursor_location(const game_ui *ui,
1824 const game_drawstate *ds,
1825 const game_state *state,
1826 const game_params *params,
1827 int *x, int *y, int *w, int *h)
1828{
1829 if(ui->cur_visible) {
1830 *x = BORDER + WINDOW_OFFSET + TILE_SIZE * ui->cur_x;
1831 *y = BORDER + WINDOW_OFFSET + TILE_SIZE * ui->cur_y;
1832
1833 *w = *h = TILE_SIZE;
1834 }
1835}
1836
1837static int game_status(const game_state *state)
1838{
1839 return state->completed ? +1 : 0;
1840}
1841
1842#ifdef COMBINED
1843#define thegame netslide
1844#endif
1845
1846const struct game thegame = {
1847 "Netslide", "games.netslide", "netslide",
1848 default_params,
1849 game_fetch_preset, NULL,
1850 decode_params,
1851 encode_params,
1852 free_params,
1853 dup_params,
1854 true, game_configure, custom_params,
1855 validate_params,
1856 new_game_desc,
1857 validate_desc,
1858 new_game,
1859 dup_game,
1860 free_game,
1861 true, solve_game,
1862 false, NULL, NULL, /* can_format_as_text_now, text_format */
1863 NULL, NULL, /* get_prefs, set_prefs */
1864 new_ui,
1865 free_ui,
1866 NULL, /* encode_ui */
1867 NULL, /* decode_ui */
1868 NULL, /* game_request_keys */
1869 game_changed_state,
1870 current_key_label,
1871 interpret_move,
1872 execute_move,
1873 PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
1874 game_colours,
1875 game_new_drawstate,
1876 game_free_drawstate,
1877 game_redraw,
1878 game_anim_length,
1879 game_flash_length,
1880 game_get_cursor_location,
1881 game_status,
1882 false, false, NULL, NULL, /* print_size, print */
1883 true, /* wants_statusbar */
1884 false, NULL, /* timing_state */
1885 0, /* flags */
1886};
1887
1888/* vim: set shiftwidth=4 tabstop=8: */