chiark / gitweb /
Add a `full' parameter to validate_params(), analogous to the one in
[sgt-puzzles.git] / pegs.c
1 /*
2  * pegs.c: the classic Peg Solitaire game.
3  */
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <assert.h>
9 #include <ctype.h>
10 #include <math.h>
11
12 #include "puzzles.h"
13 #include "tree234.h"
14
15 #define GRID_HOLE 0
16 #define GRID_PEG  1
17 #define GRID_OBST 2
18
19 enum {
20     COL_BACKGROUND,
21     COL_HIGHLIGHT,
22     COL_LOWLIGHT,
23     COL_PEG,
24     NCOLOURS
25 };
26
27 /*
28  * Grid shapes. I do some macro ickery here to ensure that my enum
29  * and the various forms of my name list always match up.
30  */
31 #define TYPELIST(A) \
32     A(CROSS,Cross,cross) \
33     A(OCTAGON,Octagon,octagon) \
34     A(RANDOM,Random,random)
35 #define ENUM(upper,title,lower) TYPE_ ## upper,
36 #define TITLE(upper,title,lower) #title,
37 #define LOWER(upper,title,lower) #lower,
38 #define CONFIG(upper,title,lower) ":" #title
39
40 enum { TYPELIST(ENUM) TYPECOUNT };
41 static char const *const pegs_titletypes[] = { TYPELIST(TITLE) };
42 static char const *const pegs_lowertypes[] = { TYPELIST(LOWER) };
43 #define TYPECONFIG TYPELIST(CONFIG)
44
45 #define FLASH_FRAME 0.13F
46
47 struct game_params {
48     int w, h;
49     int type;
50 };
51
52 struct game_state {
53     int w, h;
54     int completed;
55     unsigned char *grid;
56 };
57
58 static game_params *default_params(void)
59 {
60     game_params *ret = snew(game_params);
61
62     ret->w = ret->h = 7;
63     ret->type = TYPE_CROSS;
64
65     return ret;
66 }
67
68 static const struct game_params pegs_presets[] = {
69     {7, 7, TYPE_CROSS},
70     {7, 7, TYPE_OCTAGON},
71     {5, 5, TYPE_RANDOM},
72     {7, 7, TYPE_RANDOM},
73     {9, 9, TYPE_RANDOM},
74 };
75
76 static int game_fetch_preset(int i, char **name, game_params **params)
77 {
78     game_params *ret;
79     char str[80];
80
81     if (i < 0 || i >= lenof(pegs_presets))
82         return FALSE;
83
84     ret = snew(game_params);
85     *ret = pegs_presets[i];
86
87     strcpy(str, pegs_titletypes[ret->type]);
88     if (ret->type == TYPE_RANDOM)
89         sprintf(str + strlen(str), " %dx%d", ret->w, ret->h);
90
91     *name = dupstr(str);
92     *params = ret;
93     return TRUE;
94 }
95
96 static void free_params(game_params *params)
97 {
98     sfree(params);
99 }
100
101 static game_params *dup_params(game_params *params)
102 {
103     game_params *ret = snew(game_params);
104     *ret = *params;                    /* structure copy */
105     return ret;
106 }
107
108 static void decode_params(game_params *params, char const *string)
109 {
110     char const *p = string;
111     int i;
112
113     params->w = atoi(p);
114     while (*p && isdigit((unsigned char)*p)) p++;
115     if (*p == 'x') {
116         p++;
117         params->h = atoi(p);
118         while (*p && isdigit((unsigned char)*p)) p++;
119     } else {
120         params->h = params->w;
121     }
122
123     /*
124      * Assume a random generation scheme unless told otherwise, for the
125      * sake of internal consistency.
126      */
127     params->type = TYPE_RANDOM;
128     for (i = 0; i < lenof(pegs_lowertypes); i++)
129         if (!strcmp(p, pegs_lowertypes[i]))
130             params->type = i;
131 }
132
133 static char *encode_params(game_params *params, int full)
134 {
135     char str[80];
136
137     sprintf(str, "%dx%d", params->w, params->h);
138     if (full) {
139         assert(params->type >= 0 && params->type < lenof(pegs_lowertypes));
140         strcat(str, pegs_lowertypes[params->type]);
141     }
142     return dupstr(str);
143 }
144
145 static config_item *game_configure(game_params *params)
146 {
147     config_item *ret = snewn(4, config_item);
148     char buf[80];
149
150     ret[0].name = "Width";
151     ret[0].type = C_STRING;
152     sprintf(buf, "%d", params->w);
153     ret[0].sval = dupstr(buf);
154     ret[0].ival = 0;
155
156     ret[1].name = "Height";
157     ret[1].type = C_STRING;
158     sprintf(buf, "%d", params->h);
159     ret[1].sval = dupstr(buf);
160     ret[1].ival = 0;
161
162     ret[2].name = "Board type";
163     ret[2].type = C_CHOICES;
164     ret[2].sval = TYPECONFIG;
165     ret[2].ival = params->type;
166
167     ret[3].name = NULL;
168     ret[3].type = C_END;
169     ret[3].sval = NULL;
170     ret[3].ival = 0;
171
172     return ret;
173 }
174
175 static game_params *custom_params(config_item *cfg)
176 {
177     game_params *ret = snew(game_params);
178
179     ret->w = atoi(cfg[0].sval);
180     ret->h = atoi(cfg[1].sval);
181     ret->type = cfg[2].ival;
182
183     return ret;
184 }
185
186 static char *validate_params(game_params *params, int full)
187 {
188     if (full && (params->w <= 3 || params->h <= 3))
189         return "Width and height must both be greater than three";
190
191     /*
192      * It might be possible to implement generalisations of Cross
193      * and Octagon, but only if I can find a proof that they're all
194      * soluble. For the moment, therefore, I'm going to disallow
195      * them at any size other than the standard one.
196      */
197     if (full && (params->type == TYPE_CROSS || params->type == TYPE_OCTAGON)) {
198         if (params->w != 7 || params->h != 7)
199             return "This board type is only supported at 7x7";
200     }
201     return NULL;
202 }
203
204 /* ----------------------------------------------------------------------
205  * Beginning of code to generate random Peg Solitaire boards.
206  * 
207  * This procedure is done with no aesthetic judgment, no effort at
208  * symmetry, no difficulty grading and generally no finesse
209  * whatsoever. We simply begin with an empty board containing a
210  * single peg, and repeatedly make random reverse moves until it's
211  * plausibly full. This typically yields a scrappy haphazard mess
212  * with several holes, an uneven shape, and no redeeming features
213  * except guaranteed solubility.
214  *
215  * My only concessions to sophistication are (a) to repeat the
216  * generation process until I at least get a grid that touches
217  * every edge of the specified board size, and (b) to try when
218  * selecting moves to reuse existing space rather than expanding
219  * into new space (so that non-rectangular board shape becomes a
220  * factor during play).
221  */
222
223 struct move {
224     /*
225      * x,y are the start point of the move during generation (hence
226      * its endpoint during normal play).
227      * 
228      * dx,dy are the direction of the move during generation.
229      * Absolute value 1. Hence, for example, x=3,y=5,dx=1,dy=0
230      * means that the move during generation starts at (3,5) and
231      * ends at (5,5), and vice versa during normal play.
232      */
233     int x, y, dx, dy;
234     /*
235      * cost is 0, 1 or 2, depending on how many GRID_OBSTs we must
236      * turn into GRID_HOLEs to play this move.
237      */
238     int cost;
239 };
240
241 static int movecmp(void *av, void *bv)
242 {
243     struct move *a = (struct move *)av;
244     struct move *b = (struct move *)bv;
245
246     if (a->y < b->y)
247         return -1;
248     else if (a->y > b->y)
249         return +1;
250
251     if (a->x < b->x)
252         return -1;
253     else if (a->x > b->x)
254         return +1;
255
256     if (a->dy < b->dy)
257         return -1;
258     else if (a->dy > b->dy)
259         return +1;
260
261     if (a->dx < b->dx)
262         return -1;
263     else if (a->dx > b->dx)
264         return +1;
265
266     return 0;
267 }
268
269 static int movecmpcost(void *av, void *bv)
270 {
271     struct move *a = (struct move *)av;
272     struct move *b = (struct move *)bv;
273
274     if (a->cost < b->cost)
275         return -1;
276     else if (a->cost > b->cost)
277         return +1;
278
279     return movecmp(av, bv);
280 }
281
282 struct movetrees {
283     tree234 *bymove, *bycost;
284 };
285
286 static void update_moves(unsigned char *grid, int w, int h, int x, int y,
287                          struct movetrees *trees)
288 {
289     struct move move;
290     int dir, pos;
291
292     /*
293      * There are twelve moves that can include (x,y): three in each
294      * of four directions. Check each one to see if it's possible.
295      */
296     for (dir = 0; dir < 4; dir++) {
297         int dx, dy;
298
299         if (dir & 1)
300             dx = 0, dy = dir - 2;
301         else
302             dy = 0, dx = dir - 1;
303
304         assert(abs(dx) + abs(dy) == 1);
305
306         for (pos = 0; pos < 3; pos++) {
307             int v1, v2, v3;
308
309             move.dx = dx;
310             move.dy = dy;
311             move.x = x - pos*dx;
312             move.y = y - pos*dy;
313
314             if (move.x < 0 || move.x >= w || move.y < 0 || move.y >= h)
315                 continue;              /* completely invalid move */
316             if (move.x+2*move.dx < 0 || move.x+2*move.dx >= w ||
317                 move.y+2*move.dy < 0 || move.y+2*move.dy >= h)
318                 continue;              /* completely invalid move */
319
320             v1 = grid[move.y * w + move.x];
321             v2 = grid[(move.y+move.dy) * w + (move.x+move.dx)];
322             v3 = grid[(move.y+2*move.dy)*w + (move.x+2*move.dx)];
323             if (v1 == GRID_PEG && v2 != GRID_PEG && v3 != GRID_PEG) {
324                 struct move *m;
325
326                 move.cost = (v2 == GRID_OBST) + (v3 == GRID_OBST);
327
328                 /*
329                  * This move is possible. See if it's already in
330                  * the tree.
331                  */
332                 m = find234(trees->bymove, &move, NULL);
333                 if (m && m->cost != move.cost) {
334                     /*
335                      * It's in the tree but listed with the wrong
336                      * cost. Remove the old version.
337                      */
338 #ifdef GENERATION_DIAGNOSTICS
339                     printf("correcting %d%+d,%d%+d at cost %d\n",
340                            m->x, m->dx, m->y, m->dy, m->cost);
341 #endif
342                     del234(trees->bymove, m);
343                     del234(trees->bycost, m);
344                     sfree(m);
345                     m = NULL;
346                 }
347                 if (!m) {
348                     struct move *m, *m2;
349                     m = snew(struct move);
350                     *m = move;
351                     m2 = add234(trees->bymove, m);
352                     m2 = add234(trees->bycost, m);
353                     assert(m2 == m);
354 #ifdef GENERATION_DIAGNOSTICS
355                     printf("adding %d%+d,%d%+d at cost %d\n",
356                            move.x, move.dx, move.y, move.dy, move.cost);
357 #endif
358                 } else {
359 #ifdef GENERATION_DIAGNOSTICS
360                     printf("not adding %d%+d,%d%+d at cost %d\n",
361                            move.x, move.dx, move.y, move.dy, move.cost);
362 #endif
363                 }
364             } else {
365                 /*
366                  * This move is impossible. If it is already in the
367                  * tree, delete it.
368                  * 
369                  * (We make use here of the fact that del234
370                  * doesn't have to be passed a pointer to the
371                  * _actual_ element it's deleting: it merely needs
372                  * one that compares equal to it, and it will
373                  * return the one it deletes.)
374                  */
375                 struct move *m = del234(trees->bymove, &move);
376 #ifdef GENERATION_DIAGNOSTICS
377                 printf("%sdeleting %d%+d,%d%+d\n", m ? "" : "not ",
378                        move.x, move.dx, move.y, move.dy);
379 #endif
380                 if (m) {
381                     del234(trees->bycost, m);
382                     sfree(m);
383                 }
384             }
385         }
386     }
387 }
388
389 static void pegs_genmoves(unsigned char *grid, int w, int h, random_state *rs)
390 {
391     struct movetrees atrees, *trees = &atrees;
392     struct move *m;
393     int x, y, i, nmoves;
394
395     trees->bymove = newtree234(movecmp);
396     trees->bycost = newtree234(movecmpcost);
397
398     for (y = 0; y < h; y++)
399         for (x = 0; x < w; x++)
400             if (grid[y*w+x] == GRID_PEG)
401                 update_moves(grid, w, h, x, y, trees);
402
403     nmoves = 0;
404
405     while (1) {
406         int limit, maxcost, index;
407         struct move mtmp, move, *m;
408
409         /*
410          * See how many moves we can make at zero cost. Make one,
411          * if possible. Failing that, make a one-cost move, and
412          * then a two-cost one.
413          * 
414          * After filling at least half the input grid, we no longer
415          * accept cost-2 moves: if that's our only option, we give
416          * up and finish.
417          */
418         mtmp.y = h+1;
419         maxcost = (nmoves < w*h/2 ? 2 : 1);
420         m = NULL;                      /* placate optimiser */
421         for (mtmp.cost = 0; mtmp.cost <= maxcost; mtmp.cost++) {
422             limit = -1;
423             m = findrelpos234(trees->bycost, &mtmp, NULL, REL234_LT, &limit);
424 #ifdef GENERATION_DIAGNOSTICS
425             printf("%d moves available with cost %d\n", limit+1, mtmp.cost);
426 #endif
427             if (m)
428                 break;
429         }
430         if (!m)
431             break;
432
433         index = random_upto(rs, limit+1);
434         move = *(struct move *)index234(trees->bycost, index);
435
436 #ifdef GENERATION_DIAGNOSTICS
437         printf("selecting move %d%+d,%d%+d at cost %d\n",
438                move.x, move.dx, move.y, move.dy, move.cost);
439 #endif
440
441         grid[move.y * w + move.x] = GRID_HOLE;
442         grid[(move.y+move.dy) * w + (move.x+move.dx)] = GRID_PEG;
443         grid[(move.y+2*move.dy)*w + (move.x+2*move.dx)] = GRID_PEG;
444
445         for (i = 0; i <= 2; i++) {
446             int tx = move.x + i*move.dx;
447             int ty = move.y + i*move.dy;
448             update_moves(grid, w, h, tx, ty, trees);
449         }
450
451         nmoves++;
452     }
453
454     while ((m = delpos234(trees->bymove, 0)) != NULL) {
455         del234(trees->bycost, m);
456         sfree(m);
457     }
458     freetree234(trees->bymove);
459     freetree234(trees->bycost);
460 }
461
462 static void pegs_generate(unsigned char *grid, int w, int h, random_state *rs)
463 {
464     while (1) {
465         int x, y, extremes;
466
467         memset(grid, GRID_OBST, w*h);
468         grid[(h/2) * w + (w/2)] = GRID_PEG;
469 #ifdef GENERATION_DIAGNOSTICS
470         printf("beginning move selection\n");
471 #endif
472         pegs_genmoves(grid, w, h, rs);
473 #ifdef GENERATION_DIAGNOSTICS
474         printf("finished move selection\n");
475 #endif
476
477         extremes = 0;
478         for (y = 0; y < h; y++) {
479             if (grid[y*w+0] != GRID_OBST)
480                 extremes |= 1;
481             if (grid[y*w+w-1] != GRID_OBST)
482                 extremes |= 2;
483         }
484         for (x = 0; x < w; x++) {
485             if (grid[0*w+x] != GRID_OBST)
486                 extremes |= 4;
487             if (grid[(h-1)*w+x] != GRID_OBST)
488                 extremes |= 8;
489         }
490
491         if (extremes == 15)
492             break;
493 #ifdef GENERATION_DIAGNOSTICS
494         printf("insufficient extent; trying again\n");
495 #endif
496     }
497 #ifdef GENERATION_DIAGNOSTICS
498     fflush(stdout);
499 #endif
500 }
501
502 /* ----------------------------------------------------------------------
503  * End of board generation code. Now for the client code which uses
504  * it as part of the puzzle.
505  */
506
507 static char *new_game_desc(game_params *params, random_state *rs,
508                            char **aux, int interactive)
509 {
510     int w = params->w, h = params->h;
511     unsigned char *grid;
512     char *ret;
513     int i;
514
515     grid = snewn(w*h, unsigned char);
516     if (params->type == TYPE_RANDOM) {
517         pegs_generate(grid, w, h, rs);
518     } else {
519         int x, y, cx, cy, v;
520
521         for (y = 0; y < h; y++)
522             for (x = 0; x < w; x++) {
523                 v = GRID_OBST;         /* placate optimiser */
524                 switch (params->type) {
525                   case TYPE_CROSS:
526                     cx = abs(x - w/2);
527                     cy = abs(y - h/2);
528                     if (cx == 0 && cy == 0)
529                         v = GRID_HOLE;
530                     else if (cx > 1 && cy > 1)
531                         v = GRID_OBST;
532                     else
533                         v = GRID_PEG;
534                     break;
535                   case TYPE_OCTAGON:
536                     cx = abs(x - w/2);
537                     cy = abs(y - h/2);
538                     if (cx == 0 && cy == 0)
539                         v = GRID_HOLE;
540                     else if (cx + cy > 1 + max(w,h)/2)
541                         v = GRID_OBST;
542                     else
543                         v = GRID_PEG;
544                     break;
545                 }
546                 grid[y*w+x] = v;
547             }
548     }
549
550     /*
551      * Encode a game description which is simply a long list of P
552      * for peg, H for hole or O for obstacle.
553      */
554     ret = snewn(w*h+1, char);
555     for (i = 0; i < w*h; i++)
556         ret[i] = (grid[i] == GRID_PEG ? 'P' :
557                   grid[i] == GRID_HOLE ? 'H' : 'O');
558     ret[w*h] = '\0';
559
560     sfree(grid);
561
562     return ret;
563 }
564
565 static char *validate_desc(game_params *params, char *desc)
566 {
567     int len = params->w * params->h;
568
569     if (len != strlen(desc))
570         return "Game description is wrong length";
571     if (len != strspn(desc, "PHO"))
572         return "Invalid character in game description";
573
574     return NULL;
575 }
576
577 static game_state *new_game(midend_data *me, game_params *params, char *desc)
578 {
579     int w = params->w, h = params->h;
580     game_state *state = snew(game_state);
581     int i;
582
583     state->w = w;
584     state->h = h;
585     state->completed = 0;
586     state->grid = snewn(w*h, unsigned char);
587     for (i = 0; i < w*h; i++)
588         state->grid[i] = (desc[i] == 'P' ? GRID_PEG :
589                           desc[i] == 'H' ? GRID_HOLE : GRID_OBST);
590
591     return state;
592 }
593
594 static game_state *dup_game(game_state *state)
595 {
596     int w = state->w, h = state->h;
597     game_state *ret = snew(game_state);
598
599     ret->w = state->w;
600     ret->h = state->h;
601     ret->completed = state->completed;
602     ret->grid = snewn(w*h, unsigned char);
603     memcpy(ret->grid, state->grid, w*h);
604
605     return ret;
606 }
607
608 static void free_game(game_state *state)
609 {
610     sfree(state->grid);
611     sfree(state);
612 }
613
614 static char *solve_game(game_state *state, game_state *currstate,
615                         char *aux, char **error)
616 {
617     return NULL;
618 }
619
620 static char *game_text_format(game_state *state)
621 {
622     int w = state->w, h = state->h;
623     int x, y;
624     char *ret;
625
626     ret = snewn((w+1)*h + 1, char);
627
628     for (y = 0; y < h; y++) {
629         for (x = 0; x < w; x++)
630             ret[y*(w+1)+x] = (state->grid[y*w+x] == GRID_HOLE ? '-' :
631                               state->grid[y*w+x] == GRID_PEG ? '*' : ' ');
632         ret[y*(w+1)+w] = '\n';
633     }
634     ret[h*(w+1)] = '\0';
635
636     return ret;
637 }
638
639 struct game_ui {
640     int dragging;                      /* boolean: is a drag in progress? */
641     int sx, sy;                        /* grid coords of drag start cell */
642     int dx, dy;                        /* pixel coords of current drag posn */
643 };
644
645 static game_ui *new_ui(game_state *state)
646 {
647     game_ui *ui = snew(game_ui);
648
649     ui->sx = ui->sy = ui->dx = ui->dy = 0;
650     ui->dragging = FALSE;
651
652     return ui;
653 }
654
655 static void free_ui(game_ui *ui)
656 {
657     sfree(ui);
658 }
659
660 static char *encode_ui(game_ui *ui)
661 {
662     return NULL;
663 }
664
665 static void decode_ui(game_ui *ui, char *encoding)
666 {
667 }
668
669 static void game_changed_state(game_ui *ui, game_state *oldstate,
670                                game_state *newstate)
671 {
672     /*
673      * Cancel a drag, in case the source square has become
674      * unoccupied.
675      */
676     ui->dragging = FALSE;
677 }
678
679 #define PREFERRED_TILE_SIZE 33
680 #define TILESIZE (ds->tilesize)
681 #define BORDER (TILESIZE / 2)
682
683 #define HIGHLIGHT_WIDTH (TILESIZE / 16)
684
685 #define COORD(x)     ( BORDER + (x) * TILESIZE )
686 #define FROMCOORD(x) ( ((x) + TILESIZE - BORDER) / TILESIZE - 1 )
687
688 struct game_drawstate {
689     int tilesize;
690     blitter *drag_background;
691     int dragging, dragx, dragy;
692     int w, h;
693     unsigned char *grid;
694     int started;
695     int bgcolour;
696 };
697
698 static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
699                             int x, int y, int button)
700 {
701     int w = state->w, h = state->h;
702
703     if (button == LEFT_BUTTON) {
704         int tx, ty;
705
706         /*
707          * Left button down: we attempt to start a drag.
708          */
709         
710         /*
711          * There certainly shouldn't be a current drag in progress,
712          * unless the midend failed to send us button events in
713          * order; it has a responsibility to always get that right,
714          * so we can legitimately punish it by failing an
715          * assertion.
716          */
717         assert(!ui->dragging);
718
719         tx = FROMCOORD(x);
720         ty = FROMCOORD(y);
721         if (tx >= 0 && tx < w && ty >= 0 && ty < h &&
722             state->grid[ty*w+tx] == GRID_PEG) {
723             ui->dragging = TRUE;
724             ui->sx = tx;
725             ui->sy = ty;
726             ui->dx = x;
727             ui->dy = y;
728             return "";                 /* ui modified */
729         }
730     } else if (button == LEFT_DRAG && ui->dragging) {
731         /*
732          * Mouse moved; just move the peg being dragged.
733          */
734         ui->dx = x;
735         ui->dy = y;
736         return "";                     /* ui modified */
737     } else if (button == LEFT_RELEASE && ui->dragging) {
738         char buf[80];
739         int tx, ty, dx, dy;
740
741         /*
742          * Button released. Identify the target square of the drag,
743          * see if it represents a valid move, and if so make it.
744          */
745         ui->dragging = FALSE;          /* cancel the drag no matter what */
746         tx = FROMCOORD(x);
747         ty = FROMCOORD(y);
748         if (tx < 0 || tx >= w || ty < 0 || ty >= h)
749             return "";                 /* target out of range */
750         dx = tx - ui->sx;
751         dy = ty - ui->sy;
752         if (max(abs(dx),abs(dy)) != 2 || min(abs(dx),abs(dy)) != 0)
753             return "";                 /* move length was wrong */
754         dx /= 2;
755         dy /= 2;
756
757         if (state->grid[ty*w+tx] != GRID_HOLE ||
758             state->grid[(ty-dy)*w+(tx-dx)] != GRID_PEG ||
759             state->grid[ui->sy*w+ui->sx] != GRID_PEG)
760             return "";                 /* grid contents were invalid */
761
762         /*
763          * We have a valid move. Encode it simply as source and
764          * destination coordinate pairs.
765          */
766         sprintf(buf, "%d,%d-%d,%d", ui->sx, ui->sy, tx, ty);
767         return dupstr(buf);
768     }
769     return NULL;
770 }
771
772 static game_state *execute_move(game_state *state, char *move)
773 {
774     int w = state->w, h = state->h;
775     int sx, sy, tx, ty;
776     game_state *ret;
777
778     if (sscanf(move, "%d,%d-%d,%d", &sx, &sy, &tx, &ty)) {
779         int mx, my, dx, dy;
780
781         if (sx < 0 || sx >= w || sy < 0 || sy >= h)
782             return NULL;               /* source out of range */
783         if (tx < 0 || tx >= w || ty < 0 || ty >= h)
784             return NULL;               /* target out of range */
785
786         dx = tx - sx;
787         dy = ty - sy;
788         if (max(abs(dx),abs(dy)) != 2 || min(abs(dx),abs(dy)) != 0)
789             return NULL;               /* move length was wrong */
790         mx = sx + dx/2;
791         my = sy + dy/2;
792
793         if (state->grid[sy*w+sx] != GRID_PEG ||
794             state->grid[my*w+mx] != GRID_PEG ||
795             state->grid[ty*w+tx] != GRID_HOLE)
796             return NULL;               /* grid contents were invalid */
797
798         ret = dup_game(state);
799         ret->grid[sy*w+sx] = GRID_HOLE;
800         ret->grid[my*w+mx] = GRID_HOLE;
801         ret->grid[ty*w+tx] = GRID_PEG;
802
803         /*
804          * Opinion varies on whether getting to a single peg counts as
805          * completing the game, or whether that peg has to be at a
806          * specific location (central in the classic cross game, for
807          * instance). For now we take the former, rather lax position.
808          */
809         if (!ret->completed) {
810             int count = 0, i;
811             for (i = 0; i < w*h; i++)
812                 if (ret->grid[i] == GRID_PEG)
813                     count++;
814             if (count == 1)
815                 ret->completed = 1;
816         }
817
818         return ret;
819     }
820     return NULL;
821 }
822
823 /* ----------------------------------------------------------------------
824  * Drawing routines.
825  */
826
827 static void game_compute_size(game_params *params, int tilesize,
828                               int *x, int *y)
829 {
830     /* Ick: fake up `ds->tilesize' for macro expansion purposes */
831     struct { int tilesize; } ads, *ds = &ads;
832     ads.tilesize = tilesize;
833
834     *x = TILESIZE * params->w + 2 * BORDER;
835     *y = TILESIZE * params->h + 2 * BORDER;
836 }
837
838 static void game_set_size(game_drawstate *ds, game_params *params,
839                           int tilesize)
840 {
841     ds->tilesize = tilesize;
842
843     assert(TILESIZE > 0);
844
845     if (ds->drag_background)
846         blitter_free(ds->drag_background);
847     ds->drag_background = blitter_new(TILESIZE, TILESIZE);
848 }
849
850 static float *game_colours(frontend *fe, game_state *state, int *ncolours)
851 {
852     float *ret = snewn(3 * NCOLOURS, float);
853     int i;
854     float max;
855
856     frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
857
858     /*
859      * Drop the background colour so that the highlight is
860      * noticeably brighter than it while still being under 1.
861      */
862     max = ret[COL_BACKGROUND*3];
863     for (i = 1; i < 3; i++)
864         if (ret[COL_BACKGROUND*3+i] > max)
865             max = ret[COL_BACKGROUND*3+i];
866     if (max * 1.2F > 1.0F) {
867         for (i = 0; i < 3; i++)
868             ret[COL_BACKGROUND*3+i] /= (max * 1.2F);
869     }
870
871     for (i = 0; i < 3; i++) {
872         ret[COL_HIGHLIGHT * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 1.2F;
873         ret[COL_LOWLIGHT * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 0.8F;
874     }
875
876     ret[COL_PEG * 3 + 0] = 0.0F;
877     ret[COL_PEG * 3 + 1] = 0.0F;
878     ret[COL_PEG * 3 + 2] = 1.0F;
879
880     *ncolours = NCOLOURS;
881     return ret;
882 }
883
884 static game_drawstate *game_new_drawstate(game_state *state)
885 {
886     int w = state->w, h = state->h;
887     struct game_drawstate *ds = snew(struct game_drawstate);
888
889     ds->tilesize = 0;                  /* not decided yet */
890
891     /* We can't allocate the blitter rectangle for the drag background
892      * until we know what size to make it. */
893     ds->drag_background = NULL;
894     ds->dragging = FALSE;
895
896     ds->w = w;
897     ds->h = h;
898     ds->grid = snewn(w*h, unsigned char);
899     memset(ds->grid, 255, w*h);
900
901     ds->started = FALSE;
902     ds->bgcolour = -1;
903
904     return ds;
905 }
906
907 static void game_free_drawstate(game_drawstate *ds)
908 {
909     if (ds->drag_background)
910         blitter_free(ds->drag_background);
911     sfree(ds->grid);
912     sfree(ds);
913 }
914
915 static void draw_tile(frontend *fe, game_drawstate *ds,
916                       int x, int y, int v, int bgcolour)
917 {
918     if (bgcolour >= 0) {
919         draw_rect(fe, x, y, TILESIZE, TILESIZE, bgcolour);
920     }
921
922     if (v == GRID_HOLE) {
923         draw_circle(fe, x+TILESIZE/2, y+TILESIZE/2, TILESIZE/4,
924                     COL_LOWLIGHT, COL_LOWLIGHT);
925     } else if (v == GRID_PEG) {
926         draw_circle(fe, x+TILESIZE/2, y+TILESIZE/2, TILESIZE/3,
927                     COL_PEG, COL_PEG);
928     }
929
930     draw_update(fe, x, y, TILESIZE, TILESIZE);
931 }
932
933 static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
934                         game_state *state, int dir, game_ui *ui,
935                         float animtime, float flashtime)
936 {
937     int w = state->w, h = state->h;
938     int x, y;
939     int bgcolour;
940
941     if (flashtime > 0) {
942         int frame = (int)(flashtime / FLASH_FRAME);
943         bgcolour = (frame % 2 ? COL_LOWLIGHT : COL_HIGHLIGHT);
944     } else
945         bgcolour = COL_BACKGROUND;
946
947     /*
948      * Erase the sprite currently being dragged, if any.
949      */
950     if (ds->dragging) {
951         assert(ds->drag_background);
952         blitter_load(fe, ds->drag_background, ds->dragx, ds->dragy);
953         draw_update(fe, ds->dragx, ds->dragy, TILESIZE, TILESIZE);
954         ds->dragging = FALSE;
955     }
956
957     if (!ds->started) {
958         draw_rect(fe, 0, 0,
959                   TILESIZE * state->w + 2 * BORDER,
960                   TILESIZE * state->h + 2 * BORDER, COL_BACKGROUND);
961
962         /*
963          * Draw relief marks around all the squares that aren't
964          * GRID_OBST.
965          */
966         for (y = 0; y < h; y++)
967             for (x = 0; x < w; x++)
968                 if (state->grid[y*w+x] != GRID_OBST) {
969                     /*
970                      * First pass: draw the full relief square.
971                      */
972                     int coords[6];
973                     coords[0] = COORD(x+1) + HIGHLIGHT_WIDTH - 1;
974                     coords[1] = COORD(y) - HIGHLIGHT_WIDTH;
975                     coords[2] = COORD(x) - HIGHLIGHT_WIDTH;
976                     coords[3] = COORD(y+1) + HIGHLIGHT_WIDTH - 1;
977                     coords[4] = COORD(x) - HIGHLIGHT_WIDTH;
978                     coords[5] = COORD(y) - HIGHLIGHT_WIDTH;
979                     draw_polygon(fe, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT);
980                     coords[4] = COORD(x+1) + HIGHLIGHT_WIDTH - 1;
981                     coords[5] = COORD(y+1) + HIGHLIGHT_WIDTH - 1;
982                     draw_polygon(fe, coords, 3, COL_LOWLIGHT, COL_LOWLIGHT);
983                 }
984         for (y = 0; y < h; y++)
985             for (x = 0; x < w; x++)
986                 if (state->grid[y*w+x] != GRID_OBST) {
987                     /*
988                      * Second pass: draw everything but the two
989                      * diagonal corners.
990                      */
991                     draw_rect(fe, COORD(x) - HIGHLIGHT_WIDTH,
992                               COORD(y) - HIGHLIGHT_WIDTH,
993                               TILESIZE + HIGHLIGHT_WIDTH,
994                               TILESIZE + HIGHLIGHT_WIDTH, COL_HIGHLIGHT);
995                     draw_rect(fe, COORD(x),
996                               COORD(y),
997                               TILESIZE + HIGHLIGHT_WIDTH,
998                               TILESIZE + HIGHLIGHT_WIDTH, COL_LOWLIGHT);
999                 }
1000         for (y = 0; y < h; y++)
1001             for (x = 0; x < w; x++)
1002                 if (state->grid[y*w+x] != GRID_OBST) {
1003                     /*
1004                      * Third pass: draw a trapezium on each edge.
1005                      */
1006                     int coords[8];
1007                     int dx, dy, s, sn, c;
1008
1009                     for (dx = 0; dx < 2; dx++) {
1010                         dy = 1 - dx;
1011                         for (s = 0; s < 2; s++) {
1012                             sn = 2*s - 1;
1013                             c = s ? COL_LOWLIGHT : COL_HIGHLIGHT;
1014
1015                             coords[0] = COORD(x) + (s*dx)*(TILESIZE-1);
1016                             coords[1] = COORD(y) + (s*dy)*(TILESIZE-1);
1017                             coords[2] = COORD(x) + (s*dx+dy)*(TILESIZE-1);
1018                             coords[3] = COORD(y) + (s*dy+dx)*(TILESIZE-1);
1019                             coords[4] = coords[2] - HIGHLIGHT_WIDTH * (dy-sn*dx);
1020                             coords[5] = coords[3] - HIGHLIGHT_WIDTH * (dx-sn*dy);
1021                             coords[6] = coords[0] + HIGHLIGHT_WIDTH * (dy+sn*dx);
1022                             coords[7] = coords[1] + HIGHLIGHT_WIDTH * (dx+sn*dy);
1023                             draw_polygon(fe, coords, 4, c, c);
1024                         }
1025                     }
1026                 }
1027         for (y = 0; y < h; y++)
1028             for (x = 0; x < w; x++)
1029                 if (state->grid[y*w+x] != GRID_OBST) {
1030                     /*
1031                      * Second pass: draw everything but the two
1032                      * diagonal corners.
1033                      */
1034                     draw_rect(fe, COORD(x),
1035                               COORD(y),
1036                               TILESIZE,
1037                               TILESIZE, COL_BACKGROUND);
1038                 }
1039
1040         ds->started = TRUE;
1041
1042         draw_update(fe, 0, 0,
1043                     TILESIZE * state->w + 2 * BORDER,
1044                     TILESIZE * state->h + 2 * BORDER);
1045     }
1046
1047     /*
1048      * Loop over the grid redrawing anything that looks as if it
1049      * needs it.
1050      */
1051     for (y = 0; y < h; y++)
1052         for (x = 0; x < w; x++) {
1053             int v;
1054
1055             v = state->grid[y*w+x];
1056             /*
1057              * Blank the source of a drag so it looks as if the
1058              * user picked the peg up physically.
1059              */
1060             if (ui->dragging && ui->sx == x && ui->sy == y && v == GRID_PEG)
1061                 v = GRID_HOLE;
1062             if (v != GRID_OBST &&
1063                 (bgcolour != ds->bgcolour || /* always redraw when flashing */
1064                  v != ds->grid[y*w+x])) {
1065                 draw_tile(fe, ds, COORD(x), COORD(y), v, bgcolour);
1066             }
1067         }
1068
1069     /*
1070      * Draw the dragging sprite if any.
1071      */
1072     if (ui->dragging) {
1073         ds->dragging = TRUE;
1074         ds->dragx = ui->dx - TILESIZE/2;
1075         ds->dragy = ui->dy - TILESIZE/2;
1076         blitter_save(fe, ds->drag_background, ds->dragx, ds->dragy);
1077         draw_tile(fe, ds, ds->dragx, ds->dragy, GRID_PEG, -1);
1078     }
1079
1080     ds->bgcolour = bgcolour;
1081 }
1082
1083 static float game_anim_length(game_state *oldstate, game_state *newstate,
1084                               int dir, game_ui *ui)
1085 {
1086     return 0.0F;
1087 }
1088
1089 static float game_flash_length(game_state *oldstate, game_state *newstate,
1090                                int dir, game_ui *ui)
1091 {
1092     if (!oldstate->completed && newstate->completed)
1093         return 2 * FLASH_FRAME;
1094     else
1095         return 0.0F;
1096 }
1097
1098 static int game_wants_statusbar(void)
1099 {
1100     return FALSE;
1101 }
1102
1103 static int game_timing_state(game_state *state)
1104 {
1105     return TRUE;
1106 }
1107
1108 #ifdef COMBINED
1109 #define thegame pegs
1110 #endif
1111
1112 const struct game thegame = {
1113     "Pegs", "games.pegs",
1114     default_params,
1115     game_fetch_preset,
1116     decode_params,
1117     encode_params,
1118     free_params,
1119     dup_params,
1120     TRUE, game_configure, custom_params,
1121     validate_params,
1122     new_game_desc,
1123     validate_desc,
1124     new_game,
1125     dup_game,
1126     free_game,
1127     FALSE, solve_game,
1128     TRUE, game_text_format,
1129     new_ui,
1130     free_ui,
1131     encode_ui,
1132     decode_ui,
1133     game_changed_state,
1134     interpret_move,
1135     execute_move,
1136     PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
1137     game_colours,
1138     game_new_drawstate,
1139     game_free_drawstate,
1140     game_redraw,
1141     game_anim_length,
1142     game_flash_length,
1143     game_wants_statusbar,
1144     FALSE, game_timing_state,
1145     0,                                 /* mouse_priorities */
1146 };