c9afe410830d17aea14b07068ca24ff893c98df7
[sgt/puzzles] / unfinished / slide.c
1 /*
2 * slide.c: Implementation of the block-sliding puzzle `Klotski'.
3 */
4
5 /*
6 * TODO:
7 *
8 * - Solve function:
9 * * try to generate a solution when Solve is pressed
10 * + from the start, or from here? From here, I fear.
11 * + hence, not much point saving the solution in an aux
12 * string
13 * * Inertia-like method for telling the user the solution
14 * * standalone solver which draws diagrams
15 *
16 * - The dragging semantics are still subtly wrong in complex
17 * cases.
18 *
19 * - Improve the generator.
20 *
21 * - All the colours are a bit wishy-washy. _Some_ dark colours
22 * would surely not be excessive? Probably darken the tiles,
23 * the walls and the main block, and leave the target marker
24 * pale.
25 */
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <assert.h>
31 #include <ctype.h>
32 #include <math.h>
33
34 #include "puzzles.h"
35 #include "tree234.h"
36
37 /*
38 * The implementation of this game revolves around the insight
39 * which makes an exhaustive-search solver feasible: although
40 * there are many blocks which can be rearranged in many ways, any
41 * two blocks of the same shape are _indistinguishable_ and hence
42 * the number of _distinct_ board layouts is generally much
43 * smaller. So we adopt a representation for board layouts which
44 * is inherently canonical, i.e. there are no two distinct
45 * representations which encode indistinguishable layouts.
46 *
47 * The way we do this is to encode each square of the board, in
48 * the normal left-to-right top-to-bottom order, as being one of
49 * the following things:
50 * - the first square (in the given order) of a block (`anchor')
51 * - special case of the above: the anchor for the _main_ block
52 * (i.e. the one which the aim of the game is to get to the
53 * target position)
54 * - a subsequent square of a block whose previous square was N
55 * squares ago
56 * - an impassable wall
57 *
58 * (We also separately store data about which board positions are
59 * forcefields only passable by the main block. We can't encode
60 * that in the main board data, because then the main block would
61 * destroy forcefields as it went over them.)
62 *
63 * Hence, for example, a 2x2 square block would be encoded as
64 * ANCHOR, followed by DIST(1), and w-2 squares later on there
65 * would be DIST(w-1) followed by DIST(1). So if you start at the
66 * last of those squares, the DIST numbers give you a linked list
67 * pointing back through all the other squares in the same block.
68 *
69 * So the solver simply does a bfs over all reachable positions,
70 * encoding them in this format and storing them in a tree234 to
71 * ensure it doesn't ever revisit an already-analysed position.
72 */
73
74 enum {
75 /*
76 * The colours are arranged here so that every base colour is
77 * directly followed by its highlight colour and then its
78 * lowlight colour. Do not break this, or draw_tile() will get
79 * confused.
80 */
81 COL_BACKGROUND,
82 COL_HIGHLIGHT,
83 COL_LOWLIGHT,
84 COL_DRAGGING,
85 COL_DRAGGING_HIGHLIGHT,
86 COL_DRAGGING_LOWLIGHT,
87 COL_MAIN,
88 COL_MAIN_HIGHLIGHT,
89 COL_MAIN_LOWLIGHT,
90 COL_MAIN_DRAGGING,
91 COL_MAIN_DRAGGING_HIGHLIGHT,
92 COL_MAIN_DRAGGING_LOWLIGHT,
93 COL_TARGET,
94 COL_TARGET_HIGHLIGHT,
95 COL_TARGET_LOWLIGHT,
96 NCOLOURS
97 };
98
99 /*
100 * Board layout is a simple array of bytes. Each byte holds:
101 */
102 #define ANCHOR 255 /* top-left-most square of some piece */
103 #define MAINANCHOR 254 /* anchor of _main_ piece */
104 #define EMPTY 253 /* empty square */
105 #define WALL 252 /* immovable wall */
106 #define MAXDIST 251
107 /* all other values indicate distance back to previous square of same block */
108 #define ISDIST(x) ( (unsigned char)((x)-1) <= MAXDIST-1 )
109 #define DIST(x) (x)
110 #define ISANCHOR(x) ( (x)==ANCHOR || (x)==MAINANCHOR )
111 #define ISBLOCK(x) ( ISANCHOR(x) || ISDIST(x) )
112
113 /*
114 * MAXDIST is the largest DIST value we can encode. This must
115 * therefore also be the maximum puzzle width in theory (although
116 * solver running time will dictate a much smaller limit in
117 * practice).
118 */
119 #define MAXWID MAXDIST
120
121 struct game_params {
122 int w, h;
123 };
124
125 struct game_immutable_state {
126 int refcount;
127 unsigned char *forcefield;
128 };
129
130 struct game_state {
131 int w, h;
132 unsigned char *board;
133 int tx, ty; /* target coords for MAINANCHOR */
134 int minmoves; /* for display only */
135 int lastmoved, lastmoved_pos; /* for move counting */
136 int movecount;
137 int completed;
138 struct game_immutable_state *imm;
139 };
140
141 static game_params *default_params(void)
142 {
143 game_params *ret = snew(game_params);
144
145 ret->w = 8;
146 ret->h = 6;
147
148 return ret;
149 }
150
151 static const struct game_params slide_presets[] = {
152 {6, 5},
153 {7, 5},
154 {7, 6},
155 {8, 6},
156 };
157
158 static int game_fetch_preset(int i, char **name, game_params **params)
159 {
160 game_params *ret;
161 char str[80];
162
163 if (i < 0 || i >= lenof(slide_presets))
164 return FALSE;
165
166 ret = snew(game_params);
167 *ret = slide_presets[i];
168
169 sprintf(str, "%dx%d", ret->w, ret->h);
170
171 *name = dupstr(str);
172 *params = ret;
173 return TRUE;
174 }
175
176 static void free_params(game_params *params)
177 {
178 sfree(params);
179 }
180
181 static game_params *dup_params(game_params *params)
182 {
183 game_params *ret = snew(game_params);
184 *ret = *params; /* structure copy */
185 return ret;
186 }
187
188 static void decode_params(game_params *params, char const *string)
189 {
190 params->w = params->h = atoi(string);
191 while (*string && isdigit((unsigned char)*string)) string++;
192 if (*string == 'x') {
193 string++;
194 params->h = atoi(string);
195 }
196 }
197
198 static char *encode_params(game_params *params, int full)
199 {
200 char data[256];
201
202 sprintf(data, "%dx%d", params->w, params->h);
203
204 return dupstr(data);
205 }
206
207 static config_item *game_configure(game_params *params)
208 {
209 config_item *ret;
210 char buf[80];
211
212 ret = snewn(3, config_item);
213
214 ret[0].name = "Width";
215 ret[0].type = C_STRING;
216 sprintf(buf, "%d", params->w);
217 ret[0].sval = dupstr(buf);
218 ret[0].ival = 0;
219
220 ret[1].name = "Height";
221 ret[1].type = C_STRING;
222 sprintf(buf, "%d", params->h);
223 ret[1].sval = dupstr(buf);
224 ret[1].ival = 0;
225
226 ret[2].name = NULL;
227 ret[2].type = C_END;
228 ret[2].sval = NULL;
229 ret[2].ival = 0;
230
231 return ret;
232 }
233
234 static game_params *custom_params(config_item *cfg)
235 {
236 game_params *ret = snew(game_params);
237
238 ret->w = atoi(cfg[0].sval);
239 ret->h = atoi(cfg[1].sval);
240
241 return ret;
242 }
243
244 static char *validate_params(game_params *params, int full)
245 {
246 if (params->w > MAXWID)
247 return "Width must be at most " STR(MAXWID);
248
249 if (params->w < 5)
250 return "Width must be at least 5";
251 if (params->h < 4)
252 return "Height must be at least 4";
253
254 return NULL;
255 }
256
257 static char *board_text_format(int w, int h, unsigned char *data,
258 unsigned char *forcefield)
259 {
260 int wh = w*h;
261 int *dsf = snew_dsf(wh);
262 int i, x, y;
263 int retpos, retlen = (w*2+2)*(h*2+1)+1;
264 char *ret = snewn(retlen, char);
265
266 for (i = 0; i < wh; i++)
267 if (ISDIST(data[i]))
268 dsf_merge(dsf, i - data[i], i);
269 retpos = 0;
270 for (y = 0; y < 2*h+1; y++) {
271 for (x = 0; x < 2*w+1; x++) {
272 int v;
273 int i = (y/2)*w+(x/2);
274
275 #define dtype(i) (ISBLOCK(data[i]) ? \
276 dsf_canonify(dsf, i) : data[i])
277 #define dchar(t) ((t)==EMPTY ? ' ' : (t)==WALL ? '#' : \
278 data[t] == MAINANCHOR ? '*' : '%')
279
280 if (y % 2 && x % 2) {
281 int j = dtype(i);
282 v = dchar(j);
283 } else if (y % 2 && !(x % 2)) {
284 int j1 = (x > 0 ? dtype(i-1) : -1);
285 int j2 = (x < 2*w ? dtype(i) : -1);
286 if (j1 != j2)
287 v = '|';
288 else
289 v = dchar(j1);
290 } else if (!(y % 2) && (x % 2)) {
291 int j1 = (y > 0 ? dtype(i-w) : -1);
292 int j2 = (y < 2*h ? dtype(i) : -1);
293 if (j1 != j2)
294 v = '-';
295 else
296 v = dchar(j1);
297 } else {
298 int j1 = (x > 0 && y > 0 ? dtype(i-w-1) : -1);
299 int j2 = (x > 0 && y < 2*h ? dtype(i-1) : -1);
300 int j3 = (x < 2*w && y > 0 ? dtype(i-w) : -1);
301 int j4 = (x < 2*w && y < 2*h ? dtype(i) : -1);
302 if (j1 == j2 && j2 == j3 && j3 == j4)
303 v = dchar(j1);
304 else if (j1 == j2 && j3 == j4)
305 v = '|';
306 else if (j1 == j3 && j2 == j4)
307 v = '-';
308 else
309 v = '+';
310 }
311
312 assert(retpos < retlen);
313 ret[retpos++] = v;
314 }
315 assert(retpos < retlen);
316 ret[retpos++] = '\n';
317 }
318 assert(retpos < retlen);
319 ret[retpos++] = '\0';
320 assert(retpos == retlen);
321
322 return ret;
323 }
324
325 /* ----------------------------------------------------------------------
326 * Solver.
327 */
328
329 /*
330 * During solver execution, the set of visited board positions is
331 * stored as a tree234 of the following structures. `w', `h' and
332 * `data' are obvious in meaning; `dist' represents the minimum
333 * distance to reach this position from the starting point.
334 *
335 * `prev' links each board to the board position from which it was
336 * most efficiently derived.
337 */
338 struct board {
339 int w, h;
340 int dist;
341 struct board *prev;
342 unsigned char *data;
343 };
344
345 static int boardcmp(void *av, void *bv)
346 {
347 struct board *a = (struct board *)av;
348 struct board *b = (struct board *)bv;
349 return memcmp(a->data, b->data, a->w * a->h);
350 }
351
352 static struct board *newboard(int w, int h, unsigned char *data)
353 {
354 struct board *b = malloc(sizeof(struct board) + w*h);
355 b->data = (unsigned char *)b + sizeof(struct board);
356 memcpy(b->data, data, w*h);
357 b->w = w;
358 b->h = h;
359 b->dist = -1;
360 b->prev = NULL;
361 return b;
362 }
363
364 /*
365 * The actual solver. Given a board, attempt to find the minimum
366 * length of move sequence which moves MAINANCHOR to (tx,ty), or
367 * -1 if no solution exists. Returns that minimum length, and
368 * (FIXME) optionally also writes out the actual moves into an
369 * as-yet-unprovided parameter.
370 */
371 static int solve_board(int w, int h, unsigned char *board,
372 unsigned char *forcefield, int tx, int ty)
373 {
374 int wh = w*h;
375 struct board *b, *b2, *b3;
376 int *next, *anchors, *which;
377 int *movereached, *movequeue, mqhead, mqtail;
378 tree234 *sorted, *queue;
379 int i, j, dir;
380 int qlen, lastdist;
381 int ret;
382
383 #ifdef SOLVER_DIAGNOSTICS
384 {
385 char *t = board_text_format(w, h, board);
386 for (i = 0; i < h; i++) {
387 for (j = 0; j < w; j++) {
388 int c = board[i*w+j];
389 if (ISDIST(c))
390 printf("D%-3d", c);
391 else if (c == MAINANCHOR)
392 printf("M ");
393 else if (c == ANCHOR)
394 printf("A ");
395 else if (c == WALL)
396 printf("W ");
397 else if (c == EMPTY)
398 printf("E ");
399 }
400 printf("\n");
401 }
402
403 printf("Starting solver for:\n%s\n", t);
404 sfree(t);
405 }
406 #endif
407
408 sorted = newtree234(boardcmp);
409 queue = newtree234(NULL);
410
411 b = newboard(w, h, board);
412 b->dist = 0;
413 add234(sorted, b);
414 addpos234(queue, b, 0);
415 qlen = 1;
416
417 next = snewn(wh, int);
418 anchors = snewn(wh, int);
419 which = snewn(wh, int);
420 movereached = snewn(wh, int);
421 movequeue = snewn(wh, int);
422 lastdist = -1;
423
424 while ((b = delpos234(queue, 0)) != NULL) {
425 qlen--;
426 if (b->dist != lastdist) {
427 #ifdef SOLVER_DIAGNOSTICS
428 printf("dist %d (%d)\n", b->dist, count234(sorted));
429 #endif
430 lastdist = b->dist;
431 }
432 /*
433 * Find all the anchors and form a linked list of the
434 * squares within each block.
435 */
436 for (i = 0; i < wh; i++) {
437 next[i] = -1;
438 anchors[i] = FALSE;
439 which[i] = -1;
440 if (ISANCHOR(b->data[i])) {
441 anchors[i] = TRUE;
442 which[i] = i;
443 } else if (ISDIST(b->data[i])) {
444 j = i - b->data[i];
445 next[j] = i;
446 which[i] = which[j];
447 }
448 }
449
450 /*
451 * For each anchor, do an array-based BFS to find all the
452 * places we can slide it to.
453 */
454 for (i = 0; i < wh; i++) {
455 if (!anchors[i])
456 continue;
457
458 mqhead = mqtail = 0;
459 for (j = 0; j < wh; j++)
460 movereached[j] = FALSE;
461 movequeue[mqtail++] = i;
462 while (mqhead < mqtail) {
463 int pos = movequeue[mqhead++];
464
465 /*
466 * Try to move in each direction from here.
467 */
468 for (dir = 0; dir < 4; dir++) {
469 int dx = (dir == 0 ? -1 : dir == 1 ? +1 : 0);
470 int dy = (dir == 2 ? -1 : dir == 3 ? +1 : 0);
471 int offset = dy*w + dx;
472 int newpos = pos + offset;
473 int d = newpos - i;
474
475 /*
476 * For each square involved in this block,
477 * check to see if the square d spaces away
478 * from it is either empty or part of the same
479 * block.
480 */
481 for (j = i; j >= 0; j = next[j]) {
482 int jy = (pos+j-i) / w + dy, jx = (pos+j-i) % w + dx;
483 if (jy >= 0 && jy < h && jx >= 0 && jx < w &&
484 ((b->data[j+d] == EMPTY || which[j+d] == i) &&
485 (b->data[i] == MAINANCHOR || !forcefield[j+d])))
486 /* ok */;
487 else
488 break;
489 }
490 if (j >= 0)
491 continue; /* this direction wasn't feasible */
492
493 /*
494 * If we've already tried moving this piece
495 * here, leave it.
496 */
497 if (movereached[newpos])
498 continue;
499 movereached[newpos] = TRUE;
500 movequeue[mqtail++] = newpos;
501
502 /*
503 * We have a viable move. Make it.
504 */
505 b2 = newboard(w, h, b->data);
506 for (j = i; j >= 0; j = next[j])
507 b2->data[j] = EMPTY;
508 for (j = i; j >= 0; j = next[j])
509 b2->data[j+d] = b->data[j];
510
511 b3 = add234(sorted, b2);
512 if (b3 != b2) {
513 sfree(b2); /* we already got one */
514 } else {
515 b2->dist = b->dist + 1;
516 b2->prev = b;
517 addpos234(queue, b2, qlen++);
518 if (b2->data[ty*w+tx] == MAINANCHOR)
519 goto done; /* search completed! */
520 }
521 }
522 }
523 }
524 }
525 b2 = NULL;
526
527 done:
528
529 if (b2)
530 ret = b2->dist;
531 else
532 ret = -1; /* no solution */
533
534 freetree234(queue);
535
536 while ((b = delpos234(sorted, 0)) != NULL)
537 sfree(b);
538 freetree234(sorted);
539
540 sfree(next);
541 sfree(anchors);
542 sfree(movereached);
543 sfree(movequeue);
544 sfree(which);
545
546 return ret;
547 }
548
549 /* ----------------------------------------------------------------------
550 * Random board generation.
551 */
552
553 static void generate_board(int w, int h, int *rtx, int *rty, int *minmoves,
554 random_state *rs, unsigned char **rboard,
555 unsigned char **rforcefield)
556 {
557 int wh = w*h;
558 unsigned char *board, *board2, *forcefield;
559 int *list, nlist, pos;
560 int tx, ty;
561 int i, j;
562 int moves;
563
564 /*
565 * Set up a board and fill it with singletons, except for a
566 * border of walls.
567 */
568 board = snewn(wh, unsigned char);
569 forcefield = snewn(wh, unsigned char);
570 board2 = snewn(wh, unsigned char);
571 memset(board, ANCHOR, wh);
572 memset(forcefield, FALSE, wh);
573 for (i = 0; i < w; i++)
574 board[i] = board[i+w*(h-1)] = WALL;
575 for (i = 0; i < h; i++)
576 board[i*w] = board[i*w+(w-1)] = WALL;
577
578 /*
579 * Invent a main piece at one extreme. (FIXME: vary the
580 * extreme, and the piece.)
581 */
582 board[w+1] = MAINANCHOR;
583 board[w+2] = DIST(1);
584 board[w*2+1] = DIST(w-1);
585 board[w*2+2] = DIST(1);
586
587 /*
588 * Invent a target position. (FIXME: vary this too.)
589 */
590 tx = w-2;
591 ty = h-3;
592 forcefield[ty*w+tx+1] = forcefield[(ty+1)*w+tx+1] = TRUE;
593 board[ty*w+tx+1] = board[(ty+1)*w+tx+1] = EMPTY;
594
595 /*
596 * Gradually remove singletons until the game becomes soluble.
597 */
598 for (j = w; j-- > 0 ;)
599 for (i = h; i-- > 0 ;)
600 if (board[i*w+j] == ANCHOR) {
601 /*
602 * See if the board is already soluble.
603 */
604 if ((moves = solve_board(w, h, board, forcefield,
605 tx, ty)) >= 0)
606 goto soluble;
607
608 /*
609 * Otherwise, remove this piece.
610 */
611 board[i*w+j] = EMPTY;
612 }
613 assert(!"We shouldn't get here");
614 soluble:
615
616 /*
617 * Make a list of all the inter-block edges on the board.
618 */
619 list = snewn(wh*2, int);
620 nlist = 0;
621 for (i = 0; i+1 < w; i++)
622 for (j = 0; j < h; j++)
623 list[nlist++] = (j*w+i) * 2 + 0; /* edge to the right of j*w+i */
624 for (j = 0; j+1 < h; j++)
625 for (i = 0; i < w; i++)
626 list[nlist++] = (j*w+i) * 2 + 1; /* edge below j*w+i */
627
628 /*
629 * Now go through that list in random order, trying to merge
630 * the blocks on each side of each edge.
631 *
632 * FIXME: this seems to produce unpleasantly unbalanced
633 * results. Perhaps we'd do better if we always tried to
634 * combine the _smallest_ block with something?
635 *
636 * FIXME: also one reason it's slow might be because we aren't
637 * tracking which blocks we've already tried to merge, when
638 * another edge ends up linking the same ones.
639 */
640 shuffle(list, nlist, sizeof(*list), rs);
641 while (nlist > 0) {
642 int x1, y1, p1;
643 int x2, y2, p2;
644
645 pos = list[--nlist];
646 y1 = y2 = pos / (w*2);
647 x1 = x2 = (pos / 2) % w;
648 if (pos % 2)
649 y2++;
650 else
651 x2++;
652 p1 = y1*w+x1;
653 p2 = y2*w+x2;
654
655 /*
656 * In order to be mergeable, these two squares must each
657 * either be, or belong to, a non-main anchor, and their
658 * anchors must also be distinct.
659 */
660 if (!ISBLOCK(board[p1]) || !ISBLOCK(board[p2]))
661 continue;
662 while (ISDIST(board[p1]))
663 p1 -= board[p1];
664 while (ISDIST(board[p2]))
665 p2 -= board[p2];
666 if (board[p1] == MAINANCHOR || board[p2] == MAINANCHOR || p1 == p2)
667 continue;
668
669 /*
670 * We can merge these blocks. Try it, and see if the
671 * puzzle remains soluble.
672 */
673 memcpy(board2, board, wh);
674 j = -1;
675 while (p1 < wh || p2 < wh) {
676 /*
677 * p1 and p2 are the squares at the head of each block
678 * list. Pick the smaller one and put it on the output
679 * block list.
680 */
681 i = min(p1, p2);
682 if (j < 0) {
683 board[i] = ANCHOR;
684 } else {
685 assert(i - j <= MAXDIST);
686 board[i] = DIST(i - j);
687 }
688 j = i;
689
690 /*
691 * Now advance whichever list that came from.
692 */
693 if (i == p1) {
694 do {
695 p1++;
696 } while (p1 < wh && board[p1] != DIST(p1-i));
697 } else {
698 do {
699 p2++;
700 } while (p2 < wh && board[p2] != DIST(p2-i));
701 }
702 }
703 j = solve_board(w, h, board, forcefield, tx, ty);
704 if (j < 0) {
705 /*
706 * Didn't work. Revert the merge.
707 */
708 memcpy(board, board2, wh);
709 } else {
710 moves = j;
711 }
712 }
713
714 sfree(board2);
715
716 *rtx = tx;
717 *rty = ty;
718 *rboard = board;
719 *rforcefield = forcefield;
720 *minmoves = moves;
721 }
722
723 /* ----------------------------------------------------------------------
724 * End of solver/generator code.
725 */
726
727 static char *new_game_desc(game_params *params, random_state *rs,
728 char **aux, int interactive)
729 {
730 int w = params->w, h = params->h, wh = w*h;
731 int tx, ty, minmoves;
732 unsigned char *board, *forcefield;
733 char *ret, *p;
734 int i;
735
736 generate_board(params->w, params->h, &tx, &ty, &minmoves, rs,
737 &board, &forcefield);
738 #ifdef GENERATOR_DIAGNOSTICS
739 {
740 char *t = board_text_format(params->w, params->h, board);
741 printf("%s\n", t);
742 sfree(t);
743 }
744 #endif
745
746 /*
747 * Encode as a game ID.
748 */
749 ret = snewn(wh * 6 + 40, char);
750 p = ret;
751 i = 0;
752 while (i < wh) {
753 if (ISDIST(board[i])) {
754 p += sprintf(p, "d%d", board[i]);
755 i++;
756 } else {
757 int count = 1;
758 int b = board[i], f = forcefield[i];
759 int c = (b == ANCHOR ? 'a' :
760 b == MAINANCHOR ? 'm' :
761 b == EMPTY ? 'e' :
762 /* b == WALL ? */ 'w');
763 if (f) *p++ = 'f';
764 *p++ = c;
765 i++;
766 while (i < wh && board[i] == b && forcefield[i] == f)
767 i++, count++;
768 if (count > 1)
769 p += sprintf(p, "%d", count);
770 }
771 }
772 p += sprintf(p, ",%d,%d,%d", tx, ty, minmoves);
773 ret = sresize(ret, p+1 - ret, char);
774
775 /*
776 * FIXME: generate an aux string
777 */
778
779 sfree(board);
780 sfree(forcefield);
781
782 return ret;
783 }
784
785 static char *validate_desc(game_params *params, char *desc)
786 {
787 int w = params->w, h = params->h, wh = w*h;
788 int *active, *link;
789 int mains = 0, mpos = -1;
790 int i, j, tx, ty, minmoves;
791 char *ret;
792
793 active = snewn(wh, int);
794 link = snewn(wh, int);
795 i = 0;
796
797 while (*desc && *desc != ',') {
798 if (i >= wh) {
799 ret = "Too much data in game description";
800 goto done;
801 }
802 link[i] = -1;
803 active[i] = FALSE;
804 if (*desc == 'f' || *desc == 'F') {
805 desc++;
806 if (!*desc) {
807 ret = "Expected another character after 'f' in game "
808 "description";
809 goto done;
810 }
811 }
812
813 if (*desc == 'd' || *desc == 'D') {
814 int dist;
815
816 desc++;
817 if (!isdigit((unsigned char)*desc)) {
818 ret = "Expected a number after 'd' in game description";
819 goto done;
820 }
821 dist = atoi(desc);
822 while (*desc && isdigit((unsigned char)*desc)) desc++;
823
824 if (dist <= 0 || dist > i) {
825 ret = "Out-of-range number after 'd' in game description";
826 goto done;
827 }
828
829 if (!active[i - dist]) {
830 ret = "Invalid back-reference in game description";
831 goto done;
832 }
833
834 link[i] = i - dist;
835 for (j = i; j > 0; j = link[j])
836 if (j == i-1 || j == i-w)
837 break;
838 if (j < 0) {
839 ret = "Disconnected piece in game description";
840 goto done;
841 }
842
843 active[i] = TRUE;
844 active[link[i]] = FALSE;
845 i++;
846 } else {
847 int c = *desc++;
848 int count = 1;
849
850 if (!strchr("aAmMeEwW", c)) {
851 ret = "Invalid character in game description";
852 goto done;
853 }
854 if (isdigit((unsigned char)*desc)) {
855 count = atoi(desc);
856 while (*desc && isdigit((unsigned char)*desc)) desc++;
857 }
858 if (i + count > wh) {
859 ret = "Too much data in game description";
860 goto done;
861 }
862 while (count-- > 0) {
863 active[i] = (strchr("aAmM", c) != NULL);
864 link[i] = -1;
865 if (strchr("mM", c) != NULL) {
866 mains++;
867 mpos = i;
868 }
869 i++;
870 }
871 }
872 }
873 if (mains != 1) {
874 ret = (mains == 0 ? "No main piece specified in game description" :
875 "More than one main piece specified in game description");
876 goto done;
877 }
878 if (i < wh) {
879 ret = "Not enough data in game description";
880 goto done;
881 }
882
883 /*
884 * Now read the target coordinates.
885 */
886 i = sscanf(desc, ",%d,%d,%d", &tx, &ty, &minmoves);
887 if (i < 2) {
888 ret = "No target coordinates specified";
889 goto done;
890 /*
891 * (but minmoves is optional)
892 */
893 }
894
895 ret = NULL;
896
897 done:
898 sfree(active);
899 sfree(link);
900 return ret;
901 }
902
903 static game_state *new_game(midend *me, game_params *params, char *desc)
904 {
905 int w = params->w, h = params->h, wh = w*h;
906 game_state *state;
907 int i;
908
909 state = snew(game_state);
910 state->w = w;
911 state->h = h;
912 state->board = snewn(wh, unsigned char);
913 state->lastmoved = state->lastmoved_pos = -1;
914 state->movecount = 0;
915 state->imm = snew(struct game_immutable_state);
916 state->imm->refcount = 1;
917 state->imm->forcefield = snewn(wh, unsigned char);
918
919 i = 0;
920
921 while (*desc && *desc != ',') {
922 int f = FALSE;
923
924 assert(i < wh);
925
926 if (*desc == 'f') {
927 f = TRUE;
928 desc++;
929 assert(*desc);
930 }
931
932 if (*desc == 'd' || *desc == 'D') {
933 int dist;
934
935 desc++;
936 dist = atoi(desc);
937 while (*desc && isdigit((unsigned char)*desc)) desc++;
938
939 state->board[i] = DIST(dist);
940 state->imm->forcefield[i] = f;
941
942 i++;
943 } else {
944 int c = *desc++;
945 int count = 1;
946
947 if (isdigit((unsigned char)*desc)) {
948 count = atoi(desc);
949 while (*desc && isdigit((unsigned char)*desc)) desc++;
950 }
951 assert(i + count <= wh);
952
953 c = (c == 'a' || c == 'A' ? ANCHOR :
954 c == 'm' || c == 'M' ? MAINANCHOR :
955 c == 'e' || c == 'E' ? EMPTY :
956 /* c == 'w' || c == 'W' ? */ WALL);
957
958 while (count-- > 0) {
959 state->board[i] = c;
960 state->imm->forcefield[i] = f;
961 i++;
962 }
963 }
964 }
965
966 /*
967 * Now read the target coordinates.
968 */
969 state->tx = state->ty = 0;
970 state->minmoves = -1;
971 i = sscanf(desc, ",%d,%d,%d", &state->tx, &state->ty, &state->minmoves);
972
973 if (state->board[state->ty*w+state->tx] == MAINANCHOR)
974 state->completed = 0; /* already complete! */
975 else
976 state->completed = -1;
977
978 return state;
979 }
980
981 static game_state *dup_game(game_state *state)
982 {
983 int w = state->w, h = state->h, wh = w*h;
984 game_state *ret = snew(game_state);
985
986 ret->w = state->w;
987 ret->h = state->h;
988 ret->board = snewn(wh, unsigned char);
989 memcpy(ret->board, state->board, wh);
990 ret->tx = state->tx;
991 ret->ty = state->ty;
992 ret->minmoves = state->minmoves;
993 ret->lastmoved = state->lastmoved;
994 ret->lastmoved_pos = state->lastmoved_pos;
995 ret->movecount = state->movecount;
996 ret->completed = state->completed;
997 ret->imm = state->imm;
998 ret->imm->refcount++;
999
1000 return ret;
1001 }
1002
1003 static void free_game(game_state *state)
1004 {
1005 if (--state->imm->refcount <= 0) {
1006 sfree(state->imm->forcefield);
1007 sfree(state->imm);
1008 }
1009 sfree(state->board);
1010 sfree(state);
1011 }
1012
1013 static char *solve_game(game_state *state, game_state *currstate,
1014 char *aux, char **error)
1015 {
1016 /*
1017 * FIXME: we have a solver, so use it
1018 *
1019 * FIXME: we should have generated an aux string, so use that
1020 */
1021 return NULL;
1022 }
1023
1024 static char *game_text_format(game_state *state)
1025 {
1026 return board_text_format(state->w, state->h, state->board,
1027 state->imm->forcefield);
1028 }
1029
1030 struct game_ui {
1031 int dragging;
1032 int drag_anchor;
1033 int drag_offset_x, drag_offset_y;
1034 int drag_currpos;
1035 unsigned char *reachable;
1036 int *bfs_queue; /* used as scratch in interpret_move */
1037 };
1038
1039 static game_ui *new_ui(game_state *state)
1040 {
1041 int w = state->w, h = state->h, wh = w*h;
1042 game_ui *ui = snew(game_ui);
1043
1044 ui->dragging = FALSE;
1045 ui->drag_anchor = ui->drag_currpos = -1;
1046 ui->drag_offset_x = ui->drag_offset_y = -1;
1047 ui->reachable = snewn(wh, unsigned char);
1048 memset(ui->reachable, 0, wh);
1049 ui->bfs_queue = snewn(wh, int);
1050
1051 return ui;
1052 }
1053
1054 static void free_ui(game_ui *ui)
1055 {
1056 sfree(ui->bfs_queue);
1057 sfree(ui->reachable);
1058 sfree(ui);
1059 }
1060
1061 static char *encode_ui(game_ui *ui)
1062 {
1063 return NULL;
1064 }
1065
1066 static void decode_ui(game_ui *ui, char *encoding)
1067 {
1068 }
1069
1070 static void game_changed_state(game_ui *ui, game_state *oldstate,
1071 game_state *newstate)
1072 {
1073 }
1074
1075 #define PREFERRED_TILESIZE 32
1076 #define TILESIZE (ds->tilesize)
1077 #define BORDER (TILESIZE/2)
1078 #define COORD(x) ( (x) * TILESIZE + BORDER )
1079 #define FROMCOORD(x) ( ((x) - BORDER + TILESIZE) / TILESIZE - 1 )
1080 #define BORDER_WIDTH (1 + TILESIZE/20)
1081 #define HIGHLIGHT_WIDTH (1 + TILESIZE/16)
1082
1083 #define FLASH_INTERVAL 0.10F
1084 #define FLASH_TIME 3*FLASH_INTERVAL
1085
1086 struct game_drawstate {
1087 int tilesize;
1088 int w, h;
1089 unsigned long *grid; /* what's currently displayed */
1090 int started;
1091 };
1092
1093 static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
1094 int x, int y, int button)
1095 {
1096 int w = state->w, h = state->h, wh = w*h;
1097 int tx, ty, i, j;
1098 int qhead, qtail;
1099
1100 if (button == LEFT_BUTTON) {
1101 tx = FROMCOORD(x);
1102 ty = FROMCOORD(y);
1103
1104 if (tx < 0 || tx >= w || ty < 0 || ty >= h ||
1105 !ISBLOCK(state->board[ty*w+tx]))
1106 return NULL; /* this click has no effect */
1107
1108 /*
1109 * User has clicked on a block. Find the block's anchor
1110 * and register that we've started dragging it.
1111 */
1112 i = ty*w+tx;
1113 while (ISDIST(state->board[i]))
1114 i -= state->board[i];
1115 assert(i >= 0 && i < wh);
1116
1117 ui->dragging = TRUE;
1118 ui->drag_anchor = i;
1119 ui->drag_offset_x = tx - (i % w);
1120 ui->drag_offset_y = ty - (i / w);
1121 ui->drag_currpos = i;
1122
1123 /*
1124 * Now we immediately bfs out from the current location of
1125 * the anchor, to find all the places to which this block
1126 * can be dragged.
1127 */
1128 memset(ui->reachable, FALSE, wh);
1129 qhead = qtail = 0;
1130 ui->reachable[i] = TRUE;
1131 ui->bfs_queue[qtail++] = i;
1132 for (j = i; j < wh; j++)
1133 if (state->board[j] == DIST(j - i))
1134 i = j;
1135 while (qhead < qtail) {
1136 int pos = ui->bfs_queue[qhead++];
1137 int x = pos % w, y = pos / w;
1138 int dir;
1139
1140 for (dir = 0; dir < 4; dir++) {
1141 int dx = (dir == 0 ? -1 : dir == 1 ? +1 : 0);
1142 int dy = (dir == 2 ? -1 : dir == 3 ? +1 : 0);
1143 int newpos;
1144
1145 if (x + dx < 0 || x + dx >= w ||
1146 y + dy < 0 || y + dy >= h)
1147 continue;
1148
1149 newpos = pos + dy*w + dx;
1150 if (ui->reachable[newpos])
1151 continue; /* already done this one */
1152
1153 /*
1154 * Now search the grid to see if the block we're
1155 * dragging could fit into this space.
1156 */
1157 for (j = i; j >= 0; j = (ISDIST(state->board[j]) ?
1158 j - state->board[j] : -1)) {
1159 int jx = (j+pos-ui->drag_anchor) % w;
1160 int jy = (j+pos-ui->drag_anchor) / w;
1161 int j2;
1162
1163 if (jx + dx < 0 || jx + dx >= w ||
1164 jy + dy < 0 || jy + dy >= h)
1165 break; /* this position isn't valid at all */
1166
1167 j2 = (j+pos-ui->drag_anchor) + dy*w + dx;
1168
1169 if (state->board[j2] == EMPTY &&
1170 (!state->imm->forcefield[j2] ||
1171 state->board[ui->drag_anchor] == MAINANCHOR))
1172 continue;
1173 while (ISDIST(state->board[j2]))
1174 j2 -= state->board[j2];
1175 assert(j2 >= 0 && j2 < wh);
1176 if (j2 == ui->drag_anchor)
1177 continue;
1178 else
1179 break;
1180 }
1181
1182 if (j < 0) {
1183 /*
1184 * If we got to the end of that loop without
1185 * disqualifying this position, mark it as
1186 * reachable for this drag.
1187 */
1188 ui->reachable[newpos] = TRUE;
1189 ui->bfs_queue[qtail++] = newpos;
1190 }
1191 }
1192 }
1193
1194 /*
1195 * And that's it. Update the display to reflect the start
1196 * of a drag.
1197 */
1198 return "";
1199 } else if (button == LEFT_DRAG && ui->dragging) {
1200 tx = FROMCOORD(x);
1201 ty = FROMCOORD(y);
1202
1203 tx -= ui->drag_offset_x;
1204 ty -= ui->drag_offset_y;
1205 if (tx < 0 || tx >= w || ty < 0 || ty >= h ||
1206 !ui->reachable[ty*w+tx])
1207 return NULL; /* this drag has no effect */
1208
1209 ui->drag_currpos = ty*w+tx;
1210 return "";
1211 } else if (button == LEFT_RELEASE && ui->dragging) {
1212 char data[256], *str;
1213
1214 /*
1215 * Terminate the drag, and if the piece has actually moved
1216 * then return a move string quoting the old and new
1217 * locations of the piece's anchor.
1218 */
1219 if (ui->drag_anchor != ui->drag_currpos) {
1220 sprintf(data, "M%d-%d", ui->drag_anchor, ui->drag_currpos);
1221 str = dupstr(data);
1222 } else
1223 str = ""; /* null move; just update the UI */
1224
1225 ui->dragging = FALSE;
1226 ui->drag_anchor = ui->drag_currpos = -1;
1227 ui->drag_offset_x = ui->drag_offset_y = -1;
1228 memset(ui->reachable, 0, wh);
1229
1230 return str;
1231 }
1232
1233 return NULL;
1234 }
1235
1236 static int move_piece(int w, int h, const unsigned char *src,
1237 unsigned char *dst, unsigned char *ff, int from, int to)
1238 {
1239 int wh = w*h;
1240 int i, j;
1241
1242 if (!ISANCHOR(dst[from]))
1243 return FALSE;
1244
1245 /*
1246 * Scan to the far end of the piece's linked list.
1247 */
1248 for (i = j = from; j < wh; j++)
1249 if (src[j] == DIST(j - i))
1250 i = j;
1251
1252 /*
1253 * Remove the piece from its old location in the new
1254 * game state.
1255 */
1256 for (j = i; j >= 0; j = (ISDIST(src[j]) ? j - src[j] : -1))
1257 dst[j] = EMPTY;
1258
1259 /*
1260 * And put it back in at the new location.
1261 */
1262 for (j = i; j >= 0; j = (ISDIST(src[j]) ? j - src[j] : -1)) {
1263 int jn = j + to - from;
1264 if (jn < 0 || jn >= wh)
1265 return FALSE;
1266 if (dst[jn] == EMPTY && (!ff[jn] || src[from] == MAINANCHOR)) {
1267 dst[jn] = src[j];
1268 } else {
1269 return FALSE;
1270 }
1271 }
1272
1273 return TRUE;
1274 }
1275
1276 static game_state *execute_move(game_state *state, char *move)
1277 {
1278 int w = state->w, h = state->h /* , wh = w*h */;
1279 char c;
1280 int a1, a2, n;
1281 game_state *ret = dup_game(state);
1282
1283 while (*move) {
1284 c = *move;
1285 if (c == 'M') {
1286 move++;
1287 if (sscanf(move, "%d-%d%n", &a1, &a2, &n) != 2 ||
1288 !move_piece(w, h, state->board, ret->board,
1289 state->imm->forcefield, a1, a2)) {
1290 free_game(ret);
1291 return NULL;
1292 }
1293 if (a1 == ret->lastmoved) {
1294 /*
1295 * If the player has moved the same piece as they
1296 * moved last time, don't increment the move
1297 * count. In fact, if they've put the piece back
1298 * where it started from, _decrement_ the move
1299 * count.
1300 */
1301 if (a2 == ret->lastmoved_pos) {
1302 ret->movecount--; /* reverted last move */
1303 ret->lastmoved = ret->lastmoved_pos = -1;
1304 } else {
1305 ret->lastmoved = a2;
1306 /* don't change lastmoved_pos */
1307 }
1308 } else {
1309 ret->lastmoved = a2;
1310 ret->lastmoved_pos = a1;
1311 ret->movecount++;
1312 }
1313 if (ret->board[a2] == MAINANCHOR &&
1314 a2 == ret->ty * w + ret->tx && ret->completed < 0)
1315 ret->completed = ret->movecount;
1316 move += n;
1317 } else {
1318 free_game(ret);
1319 return NULL;
1320 }
1321 if (*move == ';')
1322 move++;
1323 else if (*move) {
1324 free_game(ret);
1325 return NULL;
1326 }
1327 }
1328
1329 return ret;
1330 }
1331
1332 /* ----------------------------------------------------------------------
1333 * Drawing routines.
1334 */
1335
1336 static void game_compute_size(game_params *params, int tilesize,
1337 int *x, int *y)
1338 {
1339 /* fool the macros */
1340 struct dummy { int tilesize; } dummy = { tilesize }, *ds = &dummy;
1341
1342 *x = params->w * TILESIZE + 2*BORDER;
1343 *y = params->h * TILESIZE + 2*BORDER;
1344 }
1345
1346 static void game_set_size(drawing *dr, game_drawstate *ds,
1347 game_params *params, int tilesize)
1348 {
1349 ds->tilesize = tilesize;
1350 }
1351
1352 static void raise_colour(float *target, float *src, float *limit)
1353 {
1354 int i;
1355 for (i = 0; i < 3; i++)
1356 target[i] = (2*src[i] + limit[i]) / 3;
1357 }
1358
1359 static float *game_colours(frontend *fe, int *ncolours)
1360 {
1361 float *ret = snewn(3 * NCOLOURS, float);
1362
1363 game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT);
1364
1365 /*
1366 * When dragging a tile, we light it up a bit.
1367 */
1368 raise_colour(ret+3*COL_DRAGGING,
1369 ret+3*COL_BACKGROUND, ret+3*COL_HIGHLIGHT);
1370 raise_colour(ret+3*COL_DRAGGING_HIGHLIGHT,
1371 ret+3*COL_HIGHLIGHT, ret+3*COL_HIGHLIGHT);
1372 raise_colour(ret+3*COL_DRAGGING_LOWLIGHT,
1373 ret+3*COL_LOWLIGHT, ret+3*COL_HIGHLIGHT);
1374
1375 /*
1376 * The main tile is tinted blue.
1377 */
1378 ret[COL_MAIN * 3 + 0] = ret[COL_BACKGROUND * 3 + 0];
1379 ret[COL_MAIN * 3 + 1] = ret[COL_BACKGROUND * 3 + 1];
1380 ret[COL_MAIN * 3 + 2] = ret[COL_HIGHLIGHT * 3 + 2];
1381 game_mkhighlight_specific(fe, ret, COL_MAIN,
1382 COL_MAIN_HIGHLIGHT, COL_MAIN_LOWLIGHT);
1383
1384 /*
1385 * And we light that up a bit too when dragging.
1386 */
1387 raise_colour(ret+3*COL_MAIN_DRAGGING,
1388 ret+3*COL_MAIN, ret+3*COL_MAIN_HIGHLIGHT);
1389 raise_colour(ret+3*COL_MAIN_DRAGGING_HIGHLIGHT,
1390 ret+3*COL_MAIN_HIGHLIGHT, ret+3*COL_MAIN_HIGHLIGHT);
1391 raise_colour(ret+3*COL_MAIN_DRAGGING_LOWLIGHT,
1392 ret+3*COL_MAIN_LOWLIGHT, ret+3*COL_MAIN_HIGHLIGHT);
1393
1394 /*
1395 * The target area on the floor is tinted green.
1396 */
1397 ret[COL_TARGET * 3 + 0] = ret[COL_BACKGROUND * 3 + 0];
1398 ret[COL_TARGET * 3 + 1] = ret[COL_HIGHLIGHT * 3 + 1];
1399 ret[COL_TARGET * 3 + 2] = ret[COL_BACKGROUND * 3 + 2];
1400 game_mkhighlight_specific(fe, ret, COL_TARGET,
1401 COL_TARGET_HIGHLIGHT, COL_TARGET_LOWLIGHT);
1402
1403 *ncolours = NCOLOURS;
1404 return ret;
1405 }
1406
1407 static game_drawstate *game_new_drawstate(drawing *dr, game_state *state)
1408 {
1409 int w = state->w, h = state->h, wh = w*h;
1410 struct game_drawstate *ds = snew(struct game_drawstate);
1411 int i;
1412
1413 ds->tilesize = 0;
1414 ds->w = w;
1415 ds->h = h;
1416 ds->started = FALSE;
1417 ds->grid = snewn(wh, unsigned long);
1418 for (i = 0; i < wh; i++)
1419 ds->grid[i] = ~(unsigned long)0;
1420
1421 return ds;
1422 }
1423
1424 static void game_free_drawstate(drawing *dr, game_drawstate *ds)
1425 {
1426 sfree(ds->grid);
1427 sfree(ds);
1428 }
1429
1430 #define BG_NORMAL 0x00000001UL
1431 #define BG_TARGET 0x00000002UL
1432 #define BG_FORCEFIELD 0x00000004UL
1433 #define FLASH_LOW 0x00000008UL
1434 #define FLASH_HIGH 0x00000010UL
1435 #define FG_WALL 0x00000020UL
1436 #define FG_MAIN 0x00000040UL
1437 #define FG_NORMAL 0x00000080UL
1438 #define FG_DRAGGING 0x00000100UL
1439 #define FG_LBORDER 0x00000200UL
1440 #define FG_TBORDER 0x00000400UL
1441 #define FG_RBORDER 0x00000800UL
1442 #define FG_BBORDER 0x00001000UL
1443 #define FG_TLCORNER 0x00002000UL
1444 #define FG_TRCORNER 0x00004000UL
1445 #define FG_BLCORNER 0x00008000UL
1446 #define FG_BRCORNER 0x00010000UL
1447
1448 /*
1449 * Utility function.
1450 */
1451 #define TYPE_MASK 0xF000
1452 #define COL_MASK 0x0FFF
1453 #define TYPE_RECT 0x0000
1454 #define TYPE_TLCIRC 0x4000
1455 #define TYPE_TRCIRC 0x5000
1456 #define TYPE_BLCIRC 0x6000
1457 #define TYPE_BRCIRC 0x7000
1458 static void maybe_rect(drawing *dr, int x, int y, int w, int h, int coltype)
1459 {
1460 int colour = coltype & COL_MASK, type = coltype & TYPE_MASK;
1461
1462 if (colour > NCOLOURS)
1463 return;
1464 if (type == TYPE_RECT) {
1465 draw_rect(dr, x, y, w, h, colour);
1466 } else {
1467 int cx, cy, r;
1468
1469 clip(dr, x, y, w, h);
1470
1471 cx = x;
1472 cy = y;
1473 assert(w == h);
1474 r = w-1;
1475 if (type & 0x1000)
1476 cx += r;
1477 if (type & 0x2000)
1478 cy += r;
1479 draw_circle(dr, cx, cy, r, colour, colour);
1480
1481 unclip(dr);
1482 }
1483 }
1484
1485 static void draw_tile(drawing *dr, game_drawstate *ds,
1486 int x, int y, unsigned long val)
1487 {
1488 int tx = COORD(x), ty = COORD(y);
1489 int cc, ch, cl;
1490
1491 /*
1492 * Draw the tile background.
1493 */
1494 if (val & BG_TARGET)
1495 cc = COL_TARGET;
1496 else
1497 cc = COL_BACKGROUND;
1498 ch = cc+1;
1499 cl = cc+2;
1500 if (val & FLASH_LOW)
1501 cc = cl;
1502 else if (val & FLASH_HIGH)
1503 cc = ch;
1504
1505 draw_rect(dr, tx, ty, TILESIZE, TILESIZE, cc);
1506 if (val & BG_FORCEFIELD) {
1507 /*
1508 * Cattle-grid effect to indicate that nothing but the
1509 * main block can slide over this square.
1510 */
1511 int n = 3 * (TILESIZE / (3*HIGHLIGHT_WIDTH));
1512 int i;
1513
1514 for (i = 1; i < n; i += 3) {
1515 draw_rect(dr, tx,ty+(TILESIZE*i/n), TILESIZE,HIGHLIGHT_WIDTH, cl);
1516 draw_rect(dr, tx+(TILESIZE*i/n),ty, HIGHLIGHT_WIDTH,TILESIZE, cl);
1517 }
1518 }
1519
1520 /*
1521 * Draw the tile foreground, i.e. some section of a block or
1522 * wall.
1523 */
1524 if (val & FG_WALL) {
1525 cc = COL_BACKGROUND;
1526 ch = cc+1;
1527 cl = cc+2;
1528 if (val & FLASH_LOW)
1529 cc = cl;
1530 else if (val & FLASH_HIGH)
1531 cc = ch;
1532
1533 draw_rect(dr, tx, ty, TILESIZE, TILESIZE, cc);
1534 if (val & FG_LBORDER)
1535 draw_rect(dr, tx, ty, HIGHLIGHT_WIDTH, TILESIZE,
1536 ch);
1537 if (val & FG_RBORDER)
1538 draw_rect(dr, tx+TILESIZE-HIGHLIGHT_WIDTH, ty,
1539 HIGHLIGHT_WIDTH, TILESIZE, cl);
1540 if (val & FG_TBORDER)
1541 draw_rect(dr, tx, ty, TILESIZE, HIGHLIGHT_WIDTH, ch);
1542 if (val & FG_BBORDER)
1543 draw_rect(dr, tx, ty+TILESIZE-HIGHLIGHT_WIDTH,
1544 TILESIZE, HIGHLIGHT_WIDTH, cl);
1545 if (!((FG_BBORDER | FG_LBORDER) &~ val))
1546 draw_rect(dr, tx, ty+TILESIZE-HIGHLIGHT_WIDTH,
1547 HIGHLIGHT_WIDTH, HIGHLIGHT_WIDTH, cc);
1548 if (!((FG_TBORDER | FG_RBORDER) &~ val))
1549 draw_rect(dr, tx+TILESIZE-HIGHLIGHT_WIDTH, ty,
1550 HIGHLIGHT_WIDTH, HIGHLIGHT_WIDTH, cc);
1551 if (val & FG_TLCORNER)
1552 draw_rect(dr, tx, ty, HIGHLIGHT_WIDTH, HIGHLIGHT_WIDTH, ch);
1553 if (val & FG_BRCORNER)
1554 draw_rect(dr, tx+TILESIZE-HIGHLIGHT_WIDTH,
1555 ty+TILESIZE-HIGHLIGHT_WIDTH,
1556 HIGHLIGHT_WIDTH, HIGHLIGHT_WIDTH, cl);
1557 } else if (val & (FG_MAIN | FG_NORMAL)) {
1558 int x[6], y[6];
1559
1560 if (val & FG_DRAGGING)
1561 cc = (val & FG_MAIN ? COL_MAIN_DRAGGING : COL_DRAGGING);
1562 else
1563 cc = (val & FG_MAIN ? COL_MAIN : COL_BACKGROUND);
1564 ch = cc+1;
1565 cl = cc+2;
1566
1567 if (val & FLASH_LOW)
1568 cc = cl;
1569 else if (val & FLASH_HIGH)
1570 cc = ch;
1571
1572 /*
1573 * Drawing the blocks is hellishly fiddly. The blocks
1574 * don't stretch to the full size of the tile; there's a
1575 * border around them of size BORDER_WIDTH. Then they have
1576 * bevelled borders of size HIGHLIGHT_WIDTH, and also
1577 * rounded corners.
1578 *
1579 * I tried for some time to find a clean and clever way to
1580 * figure out what needed drawing from the corner and
1581 * border flags, but in the end the cleanest way I could
1582 * find was the following. We divide the grid square into
1583 * 25 parts by ruling four horizontal and four vertical
1584 * lines across it; those lines are at BORDER_WIDTH and
1585 * BORDER_WIDTH+HIGHLIGHT_WIDTH from the top, from the
1586 * bottom, from the left and from the right. Then we
1587 * carefully consider each of the resulting 25 sections of
1588 * square, and decide separately what needs to go in it
1589 * based on the flags. In complicated cases there can be
1590 * up to five possibilities affecting any given section
1591 * (no corner or border flags, just the corner flag, one
1592 * border flag, the other border flag, both border flags).
1593 * So there's a lot of very fiddly logic here and all I
1594 * could really think to do was give it my best shot and
1595 * then test it and correct all the typos. Not fun to
1596 * write, and I'm sure it isn't fun to read either, but it
1597 * seems to work.
1598 */
1599
1600 x[0] = tx;
1601 x[1] = x[0] + BORDER_WIDTH;
1602 x[2] = x[1] + HIGHLIGHT_WIDTH;
1603 x[5] = tx + TILESIZE;
1604 x[4] = x[5] - BORDER_WIDTH;
1605 x[3] = x[4] - HIGHLIGHT_WIDTH;
1606
1607 y[0] = ty;
1608 y[1] = y[0] + BORDER_WIDTH;
1609 y[2] = y[1] + HIGHLIGHT_WIDTH;
1610 y[5] = ty + TILESIZE;
1611 y[4] = y[5] - BORDER_WIDTH;
1612 y[3] = y[4] - HIGHLIGHT_WIDTH;
1613
1614 #define RECT(p,q) x[p], y[q], x[(p)+1]-x[p], y[(q)+1]-y[q]
1615
1616 maybe_rect(dr, RECT(0,0),
1617 (val & (FG_TLCORNER | FG_TBORDER | FG_LBORDER)) ? -1 : cc);
1618 maybe_rect(dr, RECT(1,0),
1619 (val & FG_TLCORNER) ? ch : (val & FG_TBORDER) ? -1 :
1620 (val & FG_LBORDER) ? ch : cc);
1621 maybe_rect(dr, RECT(2,0),
1622 (val & FG_TBORDER) ? -1 : cc);
1623 maybe_rect(dr, RECT(3,0),
1624 (val & FG_TRCORNER) ? cl : (val & FG_TBORDER) ? -1 :
1625 (val & FG_RBORDER) ? cl : cc);
1626 maybe_rect(dr, RECT(4,0),
1627 (val & (FG_TRCORNER | FG_TBORDER | FG_RBORDER)) ? -1 : cc);
1628 maybe_rect(dr, RECT(0,1),
1629 (val & FG_TLCORNER) ? ch : (val & FG_LBORDER) ? -1 :
1630 (val & FG_TBORDER) ? ch : cc);
1631 maybe_rect(dr, RECT(1,1),
1632 (val & FG_TLCORNER) ? cc : -1);
1633 maybe_rect(dr, RECT(1,1),
1634 (val & FG_TLCORNER) ? ch | TYPE_TLCIRC :
1635 !((FG_TBORDER | FG_LBORDER) &~ val) ? ch | TYPE_BRCIRC :
1636 (val & (FG_TBORDER | FG_LBORDER)) ? ch : cc);
1637 maybe_rect(dr, RECT(2,1),
1638 (val & FG_TBORDER) ? ch : cc);
1639 maybe_rect(dr, RECT(3,1),
1640 (val & (FG_TBORDER | FG_RBORDER)) == FG_TBORDER ? ch :
1641 (val & (FG_TBORDER | FG_RBORDER)) == FG_RBORDER ? cl :
1642 !((FG_TBORDER|FG_RBORDER) &~ val) ? cc | TYPE_BLCIRC : cc);
1643 maybe_rect(dr, RECT(4,1),
1644 (val & FG_TRCORNER) ? ch : (val & FG_RBORDER) ? -1 :
1645 (val & FG_TBORDER) ? ch : cc);
1646 maybe_rect(dr, RECT(0,2),
1647 (val & FG_LBORDER) ? -1 : cc);
1648 maybe_rect(dr, RECT(1,2),
1649 (val & FG_LBORDER) ? ch : cc);
1650 maybe_rect(dr, RECT(2,2),
1651 cc);
1652 maybe_rect(dr, RECT(3,2),
1653 (val & FG_RBORDER) ? cl : cc);
1654 maybe_rect(dr, RECT(4,2),
1655 (val & FG_RBORDER) ? -1 : cc);
1656 maybe_rect(dr, RECT(0,3),
1657 (val & FG_BLCORNER) ? cl : (val & FG_LBORDER) ? -1 :
1658 (val & FG_BBORDER) ? cl : cc);
1659 maybe_rect(dr, RECT(1,3),
1660 (val & (FG_BBORDER | FG_LBORDER)) == FG_BBORDER ? cl :
1661 (val & (FG_BBORDER | FG_LBORDER)) == FG_LBORDER ? ch :
1662 !((FG_BBORDER|FG_LBORDER) &~ val) ? cc | TYPE_TRCIRC : cc);
1663 maybe_rect(dr, RECT(2,3),
1664 (val & FG_BBORDER) ? cl : cc);
1665 maybe_rect(dr, RECT(3,3),
1666 (val & FG_BRCORNER) ? cc : -1);
1667 maybe_rect(dr, RECT(3,3),
1668 (val & FG_BRCORNER) ? cl | TYPE_BRCIRC :
1669 !((FG_BBORDER | FG_RBORDER) &~ val) ? cl | TYPE_TLCIRC :
1670 (val & (FG_BBORDER | FG_RBORDER)) ? cl : cc);
1671 maybe_rect(dr, RECT(4,3),
1672 (val & FG_BRCORNER) ? cl : (val & FG_RBORDER) ? -1 :
1673 (val & FG_BBORDER) ? cl : cc);
1674 maybe_rect(dr, RECT(0,4),
1675 (val & (FG_BLCORNER | FG_BBORDER | FG_LBORDER)) ? -1 : cc);
1676 maybe_rect(dr, RECT(1,4),
1677 (val & FG_BLCORNER) ? ch : (val & FG_BBORDER) ? -1 :
1678 (val & FG_LBORDER) ? ch : cc);
1679 maybe_rect(dr, RECT(2,4),
1680 (val & FG_BBORDER) ? -1 : cc);
1681 maybe_rect(dr, RECT(3,4),
1682 (val & FG_BRCORNER) ? cl : (val & FG_BBORDER) ? -1 :
1683 (val & FG_RBORDER) ? cl : cc);
1684 maybe_rect(dr, RECT(4,4),
1685 (val & (FG_BRCORNER | FG_BBORDER | FG_RBORDER)) ? -1 : cc);
1686
1687 #undef RECT
1688
1689 }
1690
1691 draw_update(dr, tx, ty, TILESIZE, TILESIZE);
1692 }
1693
1694 static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate,
1695 game_state *state, int dir, game_ui *ui,
1696 float animtime, float flashtime)
1697 {
1698 int w = state->w, h = state->h, wh = w*h;
1699 unsigned char *board;
1700 int *dsf;
1701 int x, y, mainanchor, mainpos, dragpos;
1702
1703 if (!ds->started) {
1704 /*
1705 * The initial contents of the window are not guaranteed
1706 * and can vary with front ends. To be on the safe side,
1707 * all games should start by drawing a big
1708 * background-colour rectangle covering the whole window.
1709 */
1710 draw_rect(dr, 0, 0, 10*ds->tilesize, 10*ds->tilesize, COL_BACKGROUND);
1711 ds->started = TRUE;
1712 }
1713
1714 /*
1715 * Construct the board we'll be displaying (which may be
1716 * different from the one in state if ui describes a drag in
1717 * progress).
1718 */
1719 board = snewn(wh, unsigned char);
1720 memcpy(board, state->board, wh);
1721 if (ui->dragging) {
1722 int mpret = move_piece(w, h, state->board, board,
1723 state->imm->forcefield,
1724 ui->drag_anchor, ui->drag_currpos);
1725 assert(mpret);
1726 }
1727
1728 /*
1729 * Build a dsf out of that board, so we can conveniently tell
1730 * which edges are connected and which aren't.
1731 */
1732 dsf = snew_dsf(wh);
1733 mainanchor = -1;
1734 for (y = 0; y < h; y++)
1735 for (x = 0; x < w; x++) {
1736 int i = y*w+x;
1737
1738 if (ISDIST(board[i]))
1739 dsf_merge(dsf, i, i - board[i]);
1740 if (board[i] == MAINANCHOR)
1741 mainanchor = i;
1742 if (board[i] == WALL) {
1743 if (x > 0 && board[i-1] == WALL)
1744 dsf_merge(dsf, i, i-1);
1745 if (y > 0 && board[i-w] == WALL)
1746 dsf_merge(dsf, i, i-w);
1747 }
1748 }
1749 assert(mainanchor >= 0);
1750 mainpos = dsf_canonify(dsf, mainanchor);
1751 dragpos = ui->drag_currpos > 0 ? dsf_canonify(dsf, ui->drag_currpos) : -1;
1752
1753 /*
1754 * Now we can construct the data about what we want to draw.
1755 */
1756 for (y = 0; y < h; y++)
1757 for (x = 0; x < w; x++) {
1758 int i = y*w+x;
1759 int j;
1760 unsigned long val;
1761 int canon;
1762
1763 /*
1764 * See if this square is part of the target area.
1765 */
1766 j = i + mainanchor - (state->ty * w + state->tx);
1767 while (j >= 0 && j < wh && ISDIST(board[j]))
1768 j -= board[j];
1769 if (j == mainanchor)
1770 val = BG_TARGET;
1771 else
1772 val = BG_NORMAL;
1773
1774 if (state->imm->forcefield[i])
1775 val |= BG_FORCEFIELD;
1776
1777 if (flashtime > 0) {
1778 int flashtype = (int)(flashtime / FLASH_INTERVAL) & 1;
1779 val |= (flashtype ? FLASH_LOW : FLASH_HIGH);
1780 }
1781
1782 if (board[i] != EMPTY) {
1783 canon = dsf_canonify(dsf, i);
1784
1785 if (board[i] == WALL)
1786 val |= FG_WALL;
1787 else if (canon == mainpos)
1788 val |= FG_MAIN;
1789 else
1790 val |= FG_NORMAL;
1791 if (canon == dragpos)
1792 val |= FG_DRAGGING;
1793
1794 /*
1795 * Now look around to see if other squares
1796 * belonging to the same block are adjacent to us.
1797 */
1798 if (x == 0 || canon != dsf_canonify(dsf, i-1))
1799 val |= FG_LBORDER;
1800 if (y== 0 || canon != dsf_canonify(dsf, i-w))
1801 val |= FG_TBORDER;
1802 if (x == w-1 || canon != dsf_canonify(dsf, i+1))
1803 val |= FG_RBORDER;
1804 if (y == h-1 || canon != dsf_canonify(dsf, i+w))
1805 val |= FG_BBORDER;
1806 if (!(val & (FG_TBORDER | FG_LBORDER)) &&
1807 canon != dsf_canonify(dsf, i-1-w))
1808 val |= FG_TLCORNER;
1809 if (!(val & (FG_TBORDER | FG_RBORDER)) &&
1810 canon != dsf_canonify(dsf, i+1-w))
1811 val |= FG_TRCORNER;
1812 if (!(val & (FG_BBORDER | FG_LBORDER)) &&
1813 canon != dsf_canonify(dsf, i-1+w))
1814 val |= FG_BLCORNER;
1815 if (!(val & (FG_BBORDER | FG_RBORDER)) &&
1816 canon != dsf_canonify(dsf, i+1+w))
1817 val |= FG_BRCORNER;
1818 }
1819
1820 if (val != ds->grid[i]) {
1821 draw_tile(dr, ds, x, y, val);
1822 ds->grid[i] = val;
1823 }
1824 }
1825
1826 /*
1827 * Update the status bar.
1828 */
1829 {
1830 char statusbuf[256];
1831
1832 /*
1833 * FIXME: do something about auto-solve?
1834 */
1835 sprintf(statusbuf, "%sMoves: %d",
1836 (state->completed >= 0 ? "COMPLETED! " : ""),
1837 (state->completed >= 0 ? state->completed : state->movecount));
1838 if (state->minmoves)
1839 sprintf(statusbuf+strlen(statusbuf), " (min %d)",
1840 state->minmoves);
1841
1842 status_bar(dr, statusbuf);
1843 }
1844
1845 sfree(dsf);
1846 sfree(board);
1847 }
1848
1849 static float game_anim_length(game_state *oldstate, game_state *newstate,
1850 int dir, game_ui *ui)
1851 {
1852 return 0.0F;
1853 }
1854
1855 static float game_flash_length(game_state *oldstate, game_state *newstate,
1856 int dir, game_ui *ui)
1857 {
1858 if (oldstate->completed < 0 && newstate->completed >= 0)
1859 return FLASH_TIME;
1860
1861 return 0.0F;
1862 }
1863
1864 static int game_timing_state(game_state *state, game_ui *ui)
1865 {
1866 return TRUE;
1867 }
1868
1869 static void game_print_size(game_params *params, float *x, float *y)
1870 {
1871 }
1872
1873 static void game_print(drawing *dr, game_state *state, int tilesize)
1874 {
1875 }
1876
1877 #ifdef COMBINED
1878 #define thegame nullgame
1879 #endif
1880
1881 const struct game thegame = {
1882 "Slide", NULL, NULL,
1883 default_params,
1884 game_fetch_preset,
1885 decode_params,
1886 encode_params,
1887 free_params,
1888 dup_params,
1889 TRUE, game_configure, custom_params,
1890 validate_params,
1891 new_game_desc,
1892 validate_desc,
1893 new_game,
1894 dup_game,
1895 free_game,
1896 FALSE, solve_game, /* FIXME */
1897 TRUE, game_text_format,
1898 new_ui,
1899 free_ui,
1900 encode_ui,
1901 decode_ui,
1902 game_changed_state,
1903 interpret_move,
1904 execute_move,
1905 PREFERRED_TILESIZE, game_compute_size, game_set_size,
1906 game_colours,
1907 game_new_drawstate,
1908 game_free_drawstate,
1909 game_redraw,
1910 game_anim_length,
1911 game_flash_length,
1912 FALSE, FALSE, game_print_size, game_print,
1913 TRUE, /* wants_statusbar */
1914 FALSE, game_timing_state,
1915 0, /* flags */
1916 };