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