2 * pegs.c: the classic Peg Solitaire game.
19 #define GRID_CURSOR 10
20 #define GRID_JUMPING 20
32 * Grid shapes. I do some macro ickery here to ensure that my enum
33 * and the various forms of my name list always match up.
36 A(CROSS,Cross,cross) \
37 A(OCTAGON,Octagon,octagon) \
38 A(RANDOM,Random,random)
39 #define ENUM(upper,title,lower) TYPE_ ## upper,
40 #define TITLE(upper,title,lower) #title,
41 #define LOWER(upper,title,lower) #lower,
42 #define CONFIG(upper,title,lower) ":" #title
44 enum { TYPELIST(ENUM
) TYPECOUNT
};
45 static char const *const pegs_titletypes
[] = { TYPELIST(TITLE
) };
46 static char const *const pegs_lowertypes
[] = { TYPELIST(LOWER
) };
47 #define TYPECONFIG TYPELIST(CONFIG)
49 #define FLASH_FRAME 0.13F
62 static game_params
*default_params(void)
64 game_params
*ret
= snew(game_params
);
67 ret
->type
= TYPE_CROSS
;
72 static const struct game_params pegs_presets
[] = {
80 static int game_fetch_preset(int i
, char **name
, game_params
**params
)
85 if (i
< 0 || i
>= lenof(pegs_presets
))
88 ret
= snew(game_params
);
89 *ret
= pegs_presets
[i
];
91 strcpy(str
, pegs_titletypes
[ret
->type
]);
92 if (ret
->type
== TYPE_RANDOM
)
93 sprintf(str
+ strlen(str
), " %dx%d", ret
->w
, ret
->h
);
100 static void free_params(game_params
*params
)
105 static game_params
*dup_params(const game_params
*params
)
107 game_params
*ret
= snew(game_params
);
108 *ret
= *params
; /* structure copy */
112 static void decode_params(game_params
*params
, char const *string
)
114 char const *p
= string
;
118 while (*p
&& isdigit((unsigned char)*p
)) p
++;
122 while (*p
&& isdigit((unsigned char)*p
)) p
++;
124 params
->h
= params
->w
;
127 for (i
= 0; i
< lenof(pegs_lowertypes
); i
++)
128 if (!strcmp(p
, pegs_lowertypes
[i
]))
132 static char *encode_params(const game_params
*params
, int full
)
136 sprintf(str
, "%dx%d", params
->w
, params
->h
);
138 assert(params
->type
>= 0 && params
->type
< lenof(pegs_lowertypes
));
139 strcat(str
, pegs_lowertypes
[params
->type
]);
144 static config_item
*game_configure(const game_params
*params
)
146 config_item
*ret
= snewn(4, config_item
);
149 ret
[0].name
= "Width";
150 ret
[0].type
= C_STRING
;
151 sprintf(buf
, "%d", params
->w
);
152 ret
[0].sval
= dupstr(buf
);
155 ret
[1].name
= "Height";
156 ret
[1].type
= C_STRING
;
157 sprintf(buf
, "%d", params
->h
);
158 ret
[1].sval
= dupstr(buf
);
161 ret
[2].name
= "Board type";
162 ret
[2].type
= C_CHOICES
;
163 ret
[2].sval
= TYPECONFIG
;
164 ret
[2].ival
= params
->type
;
174 static game_params
*custom_params(const config_item
*cfg
)
176 game_params
*ret
= snew(game_params
);
178 ret
->w
= atoi(cfg
[0].sval
);
179 ret
->h
= atoi(cfg
[1].sval
);
180 ret
->type
= cfg
[2].ival
;
185 static char *validate_params(const game_params
*params
, int full
)
187 if (full
&& (params
->w
<= 3 || params
->h
<= 3))
188 return "Width and height must both be greater than three";
191 * It might be possible to implement generalisations of Cross
192 * and Octagon, but only if I can find a proof that they're all
193 * soluble. For the moment, therefore, I'm going to disallow
194 * them at any size other than the standard one.
196 if (full
&& (params
->type
== TYPE_CROSS
|| params
->type
== TYPE_OCTAGON
)) {
197 if (params
->w
!= 7 || params
->h
!= 7)
198 return "This board type is only supported at 7x7";
203 /* ----------------------------------------------------------------------
204 * Beginning of code to generate random Peg Solitaire boards.
206 * This procedure is done with no aesthetic judgment, no effort at
207 * symmetry, no difficulty grading and generally no finesse
208 * whatsoever. We simply begin with an empty board containing a
209 * single peg, and repeatedly make random reverse moves until it's
210 * plausibly full. This typically yields a scrappy haphazard mess
211 * with several holes, an uneven shape, and no redeeming features
212 * except guaranteed solubility.
214 * My only concessions to sophistication are (a) to repeat the
215 * generation process until I at least get a grid that touches
216 * every edge of the specified board size, and (b) to try when
217 * selecting moves to reuse existing space rather than expanding
218 * into new space (so that non-rectangular board shape becomes a
219 * factor during play).
224 * x,y are the start point of the move during generation (hence
225 * its endpoint during normal play).
227 * dx,dy are the direction of the move during generation.
228 * Absolute value 1. Hence, for example, x=3,y=5,dx=1,dy=0
229 * means that the move during generation starts at (3,5) and
230 * ends at (5,5), and vice versa during normal play.
234 * cost is 0, 1 or 2, depending on how many GRID_OBSTs we must
235 * turn into GRID_HOLEs to play this move.
240 static int movecmp(void *av
, void *bv
)
242 struct move
*a
= (struct move
*)av
;
243 struct move
*b
= (struct move
*)bv
;
247 else if (a
->y
> b
->y
)
252 else if (a
->x
> b
->x
)
257 else if (a
->dy
> b
->dy
)
262 else if (a
->dx
> b
->dx
)
268 static int movecmpcost(void *av
, void *bv
)
270 struct move
*a
= (struct move
*)av
;
271 struct move
*b
= (struct move
*)bv
;
273 if (a
->cost
< b
->cost
)
275 else if (a
->cost
> b
->cost
)
278 return movecmp(av
, bv
);
282 tree234
*bymove
, *bycost
;
285 static void update_moves(unsigned char *grid
, int w
, int h
, int x
, int y
,
286 struct movetrees
*trees
)
292 * There are twelve moves that can include (x,y): three in each
293 * of four directions. Check each one to see if it's possible.
295 for (dir
= 0; dir
< 4; dir
++) {
299 dx
= 0, dy
= dir
- 2;
301 dy
= 0, dx
= dir
- 1;
303 assert(abs(dx
) + abs(dy
) == 1);
305 for (pos
= 0; pos
< 3; pos
++) {
313 if (move
.x
< 0 || move
.x
>= w
|| move
.y
< 0 || move
.y
>= h
)
314 continue; /* completely invalid move */
315 if (move
.x
+2*move
.dx
< 0 || move
.x
+2*move
.dx
>= w
||
316 move
.y
+2*move
.dy
< 0 || move
.y
+2*move
.dy
>= h
)
317 continue; /* completely invalid move */
319 v1
= grid
[move
.y
* w
+ move
.x
];
320 v2
= grid
[(move
.y
+move
.dy
) * w
+ (move
.x
+move
.dx
)];
321 v3
= grid
[(move
.y
+2*move
.dy
)*w
+ (move
.x
+2*move
.dx
)];
322 if (v1
== GRID_PEG
&& v2
!= GRID_PEG
&& v3
!= GRID_PEG
) {
325 move
.cost
= (v2
== GRID_OBST
) + (v3
== GRID_OBST
);
328 * This move is possible. See if it's already in
331 m
= find234(trees
->bymove
, &move
, NULL
);
332 if (m
&& m
->cost
!= move
.cost
) {
334 * It's in the tree but listed with the wrong
335 * cost. Remove the old version.
337 #ifdef GENERATION_DIAGNOSTICS
338 printf("correcting %d%+d,%d%+d at cost %d\n",
339 m
->x
, m
->dx
, m
->y
, m
->dy
, m
->cost
);
341 del234(trees
->bymove
, m
);
342 del234(trees
->bycost
, m
);
348 m
= snew(struct move
);
350 m2
= add234(trees
->bymove
, m
);
351 m2
= add234(trees
->bycost
, m
);
353 #ifdef GENERATION_DIAGNOSTICS
354 printf("adding %d%+d,%d%+d at cost %d\n",
355 move
.x
, move
.dx
, move
.y
, move
.dy
, move
.cost
);
358 #ifdef GENERATION_DIAGNOSTICS
359 printf("not adding %d%+d,%d%+d at cost %d\n",
360 move
.x
, move
.dx
, move
.y
, move
.dy
, move
.cost
);
365 * This move is impossible. If it is already in the
368 * (We make use here of the fact that del234
369 * doesn't have to be passed a pointer to the
370 * _actual_ element it's deleting: it merely needs
371 * one that compares equal to it, and it will
372 * return the one it deletes.)
374 struct move
*m
= del234(trees
->bymove
, &move
);
375 #ifdef GENERATION_DIAGNOSTICS
376 printf("%sdeleting %d%+d,%d%+d\n", m
? "" : "not ",
377 move
.x
, move
.dx
, move
.y
, move
.dy
);
380 del234(trees
->bycost
, m
);
388 static void pegs_genmoves(unsigned char *grid
, int w
, int h
, random_state
*rs
)
390 struct movetrees atrees
, *trees
= &atrees
;
394 trees
->bymove
= newtree234(movecmp
);
395 trees
->bycost
= newtree234(movecmpcost
);
397 for (y
= 0; y
< h
; y
++)
398 for (x
= 0; x
< w
; x
++)
399 if (grid
[y
*w
+x
] == GRID_PEG
)
400 update_moves(grid
, w
, h
, x
, y
, trees
);
405 int limit
, maxcost
, index
;
406 struct move mtmp
, move
, *m
;
409 * See how many moves we can make at zero cost. Make one,
410 * if possible. Failing that, make a one-cost move, and
411 * then a two-cost one.
413 * After filling at least half the input grid, we no longer
414 * accept cost-2 moves: if that's our only option, we give
418 maxcost
= (nmoves
< w
*h
/2 ? 2 : 1);
419 m
= NULL
; /* placate optimiser */
420 for (mtmp
.cost
= 0; mtmp
.cost
<= maxcost
; mtmp
.cost
++) {
422 m
= findrelpos234(trees
->bycost
, &mtmp
, NULL
, REL234_LT
, &limit
);
423 #ifdef GENERATION_DIAGNOSTICS
424 printf("%d moves available with cost %d\n", limit
+1, mtmp
.cost
);
432 index
= random_upto(rs
, limit
+1);
433 move
= *(struct move
*)index234(trees
->bycost
, index
);
435 #ifdef GENERATION_DIAGNOSTICS
436 printf("selecting move %d%+d,%d%+d at cost %d\n",
437 move
.x
, move
.dx
, move
.y
, move
.dy
, move
.cost
);
440 grid
[move
.y
* w
+ move
.x
] = GRID_HOLE
;
441 grid
[(move
.y
+move
.dy
) * w
+ (move
.x
+move
.dx
)] = GRID_PEG
;
442 grid
[(move
.y
+2*move
.dy
)*w
+ (move
.x
+2*move
.dx
)] = GRID_PEG
;
444 for (i
= 0; i
<= 2; i
++) {
445 int tx
= move
.x
+ i
*move
.dx
;
446 int ty
= move
.y
+ i
*move
.dy
;
447 update_moves(grid
, w
, h
, tx
, ty
, trees
);
453 while ((m
= delpos234(trees
->bymove
, 0)) != NULL
) {
454 del234(trees
->bycost
, m
);
457 freetree234(trees
->bymove
);
458 freetree234(trees
->bycost
);
461 static void pegs_generate(unsigned char *grid
, int w
, int h
, random_state
*rs
)
466 memset(grid
, GRID_OBST
, w
*h
);
467 grid
[(h
/2) * w
+ (w
/2)] = GRID_PEG
;
468 #ifdef GENERATION_DIAGNOSTICS
469 printf("beginning move selection\n");
471 pegs_genmoves(grid
, w
, h
, rs
);
472 #ifdef GENERATION_DIAGNOSTICS
473 printf("finished move selection\n");
477 for (y
= 0; y
< h
; y
++) {
478 if (grid
[y
*w
+0] != GRID_OBST
)
480 if (grid
[y
*w
+w
-1] != GRID_OBST
)
483 for (x
= 0; x
< w
; x
++) {
484 if (grid
[0*w
+x
] != GRID_OBST
)
486 if (grid
[(h
-1)*w
+x
] != GRID_OBST
)
492 #ifdef GENERATION_DIAGNOSTICS
493 printf("insufficient extent; trying again\n");
496 #ifdef GENERATION_DIAGNOSTICS
501 /* ----------------------------------------------------------------------
502 * End of board generation code. Now for the client code which uses
503 * it as part of the puzzle.
506 static char *new_game_desc(const game_params
*params
, random_state
*rs
,
507 char **aux
, int interactive
)
509 int w
= params
->w
, h
= params
->h
;
514 grid
= snewn(w
*h
, unsigned char);
515 if (params
->type
== TYPE_RANDOM
) {
516 pegs_generate(grid
, w
, h
, rs
);
520 for (y
= 0; y
< h
; y
++)
521 for (x
= 0; x
< w
; x
++) {
522 v
= GRID_OBST
; /* placate optimiser */
523 switch (params
->type
) {
527 if (cx
== 0 && cy
== 0)
529 else if (cx
> 1 && cy
> 1)
537 if (cx
+ cy
> 1 + max(w
,h
)/2)
546 if (params
->type
== TYPE_OCTAGON
) {
548 * The octagonal (European) solitaire layout is
549 * actually _insoluble_ with the starting hole at the
550 * centre. Here's a proof:
552 * Colour the squares of the board diagonally in
553 * stripes of three different colours, which I'll call
554 * A, B and C. So the board looks like this:
564 * Suppose we keep running track of the number of pegs
565 * occuping each colour of square. This colouring has
566 * the property that any valid move whatsoever changes
567 * all three of those counts by one (two of them go
568 * down and one goes up), which means that the _parity_
569 * of every count flips on every move.
571 * If the centre square starts off unoccupied, then
572 * there are twelve pegs on each colour and all three
573 * counts start off even; therefore, after 35 moves all
574 * three counts would have to be odd, which isn't
575 * possible if there's only one peg left. []
577 * This proof works just as well if the starting hole
578 * is _any_ of the thirteen positions labelled B. Also,
579 * we can stripe the board in the opposite direction
580 * and rule out any square labelled B in that colouring
581 * as well. This leaves:
591 * where the ns are squares we've proved insoluble, and
592 * the Ys are the ones remaining.
594 * That doesn't prove all those starting positions to
595 * be soluble, of course; they're merely the ones we
596 * _haven't_ proved to be impossible. Nevertheless, it
597 * turns out that they are all soluble, so when the
598 * user requests an Octagon board the simplest thing is
599 * to pick one of these at random.
601 * Rather than picking equiprobably from those twelve
602 * positions, we'll pick equiprobably from the three
603 * equivalence classes
605 switch (random_upto(rs
, 3)) {
607 /* Remove a random corner piece. */
611 dx
= random_upto(rs
, 2) * 2 - 1; /* +1 or -1 */
612 dy
= random_upto(rs
, 2) * 2 - 1; /* +1 or -1 */
613 if (random_upto(rs
, 2))
617 grid
[(3+dy
)*w
+(3+dx
)] = GRID_HOLE
;
621 /* Remove a random piece two from the centre. */
624 dx
= 2 * (random_upto(rs
, 2) * 2 - 1);
625 if (random_upto(rs
, 2))
629 grid
[(3+dy
)*w
+(3+dx
)] = GRID_HOLE
;
632 default /* case 2 */:
633 /* Remove a random piece one from the centre. */
636 dx
= random_upto(rs
, 2) * 2 - 1;
637 if (random_upto(rs
, 2))
641 grid
[(3+dy
)*w
+(3+dx
)] = GRID_HOLE
;
649 * Encode a game description which is simply a long list of P
650 * for peg, H for hole or O for obstacle.
652 ret
= snewn(w
*h
+1, char);
653 for (i
= 0; i
< w
*h
; i
++)
654 ret
[i
] = (grid
[i
] == GRID_PEG
? 'P' :
655 grid
[i
] == GRID_HOLE
? 'H' : 'O');
663 static char *validate_desc(const game_params
*params
, const char *desc
)
665 int len
= params
->w
* params
->h
;
667 if (len
!= strlen(desc
))
668 return "Game description is wrong length";
669 if (len
!= strspn(desc
, "PHO"))
670 return "Invalid character in game description";
675 static game_state
*new_game(midend
*me
, const game_params
*params
,
678 int w
= params
->w
, h
= params
->h
;
679 game_state
*state
= snew(game_state
);
684 state
->completed
= 0;
685 state
->grid
= snewn(w
*h
, unsigned char);
686 for (i
= 0; i
< w
*h
; i
++)
687 state
->grid
[i
] = (desc
[i
] == 'P' ? GRID_PEG
:
688 desc
[i
] == 'H' ? GRID_HOLE
: GRID_OBST
);
693 static game_state
*dup_game(const game_state
*state
)
695 int w
= state
->w
, h
= state
->h
;
696 game_state
*ret
= snew(game_state
);
700 ret
->completed
= state
->completed
;
701 ret
->grid
= snewn(w
*h
, unsigned char);
702 memcpy(ret
->grid
, state
->grid
, w
*h
);
707 static void free_game(game_state
*state
)
713 static char *solve_game(const game_state
*state
, const game_state
*currstate
,
714 const char *aux
, char **error
)
719 static int game_can_format_as_text_now(const game_params
*params
)
724 static char *game_text_format(const game_state
*state
)
726 int w
= state
->w
, h
= state
->h
;
730 ret
= snewn((w
+1)*h
+ 1, char);
732 for (y
= 0; y
< h
; y
++) {
733 for (x
= 0; x
< w
; x
++)
734 ret
[y
*(w
+1)+x
] = (state
->grid
[y
*w
+x
] == GRID_HOLE
? '-' :
735 state
->grid
[y
*w
+x
] == GRID_PEG
? '*' : ' ');
736 ret
[y
*(w
+1)+w
] = '\n';
744 int dragging
; /* boolean: is a drag in progress? */
745 int sx
, sy
; /* grid coords of drag start cell */
746 int dx
, dy
; /* pixel coords of current drag posn */
747 int cur_x
, cur_y
, cur_visible
, cur_jumping
;
750 static game_ui
*new_ui(const game_state
*state
)
752 game_ui
*ui
= snew(game_ui
);
755 ui
->sx
= ui
->sy
= ui
->dx
= ui
->dy
= 0;
756 ui
->dragging
= FALSE
;
757 ui
->cur_visible
= ui
->cur_jumping
= 0;
759 /* make sure we start the cursor somewhere on the grid. */
760 for (x
= 0; x
< state
->w
; x
++) {
761 for (y
= 0; y
< state
->h
; y
++) {
762 v
= state
->grid
[y
*state
->w
+x
];
763 if (v
== GRID_PEG
|| v
== GRID_HOLE
) {
764 ui
->cur_x
= x
; ui
->cur_y
= y
;
769 assert(!"new_ui found nowhere for cursor");
775 static void free_ui(game_ui
*ui
)
780 static char *encode_ui(const game_ui
*ui
)
785 static void decode_ui(game_ui
*ui
, const char *encoding
)
789 static void game_changed_state(game_ui
*ui
, const game_state
*oldstate
,
790 const game_state
*newstate
)
793 * Cancel a drag, in case the source square has become
796 ui
->dragging
= FALSE
;
799 #define PREFERRED_TILE_SIZE 33
800 #define TILESIZE (ds->tilesize)
801 #define BORDER (TILESIZE / 2)
803 #define HIGHLIGHT_WIDTH (TILESIZE / 16)
805 #define COORD(x) ( BORDER + (x) * TILESIZE )
806 #define FROMCOORD(x) ( ((x) + TILESIZE - BORDER) / TILESIZE - 1 )
808 struct game_drawstate
{
810 blitter
*drag_background
;
811 int dragging
, dragx
, dragy
;
818 static char *interpret_move(const game_state
*state
, game_ui
*ui
,
819 const game_drawstate
*ds
,
820 int x
, int y
, int button
)
822 int w
= state
->w
, h
= state
->h
;
825 if (button
== LEFT_BUTTON
) {
829 * Left button down: we attempt to start a drag.
833 * There certainly shouldn't be a current drag in progress,
834 * unless the midend failed to send us button events in
835 * order; it has a responsibility to always get that right,
836 * so we can legitimately punish it by failing an
839 assert(!ui
->dragging
);
843 if (tx
>= 0 && tx
< w
&& ty
>= 0 && ty
< h
&&
844 state
->grid
[ty
*w
+tx
] == GRID_PEG
) {
850 ui
->cur_visible
= ui
->cur_jumping
= 0;
851 return ""; /* ui modified */
853 } else if (button
== LEFT_DRAG
&& ui
->dragging
) {
855 * Mouse moved; just move the peg being dragged.
859 return ""; /* ui modified */
860 } else if (button
== LEFT_RELEASE
&& ui
->dragging
) {
864 * Button released. Identify the target square of the drag,
865 * see if it represents a valid move, and if so make it.
867 ui
->dragging
= FALSE
; /* cancel the drag no matter what */
870 if (tx
< 0 || tx
>= w
|| ty
< 0 || ty
>= h
)
871 return ""; /* target out of range */
874 if (max(abs(dx
),abs(dy
)) != 2 || min(abs(dx
),abs(dy
)) != 0)
875 return ""; /* move length was wrong */
879 if (state
->grid
[ty
*w
+tx
] != GRID_HOLE
||
880 state
->grid
[(ty
-dy
)*w
+(tx
-dx
)] != GRID_PEG
||
881 state
->grid
[ui
->sy
*w
+ui
->sx
] != GRID_PEG
)
882 return ""; /* grid contents were invalid */
885 * We have a valid move. Encode it simply as source and
886 * destination coordinate pairs.
888 sprintf(buf
, "%d,%d-%d,%d", ui
->sx
, ui
->sy
, tx
, ty
);
890 } else if (IS_CURSOR_MOVE(button
)) {
891 if (!ui
->cur_jumping
) {
892 /* Not jumping; move cursor as usual, making sure we don't
893 * leave the gameboard (which may be an irregular shape) */
894 int cx
= ui
->cur_x
, cy
= ui
->cur_y
;
895 move_cursor(button
, &cx
, &cy
, w
, h
, 0);
897 if (state
->grid
[cy
*w
+cx
] == GRID_HOLE
||
898 state
->grid
[cy
*w
+cx
] == GRID_PEG
) {
904 int dx
, dy
, mx
, my
, jx
, jy
;
906 /* We're jumping; if the requested direction has a hole, and
907 * there's a peg in the way, */
908 assert(state
->grid
[ui
->cur_y
*w
+ui
->cur_x
] == GRID_PEG
);
909 dx
= (button
== CURSOR_RIGHT
) ? 1 : (button
== CURSOR_LEFT
) ? -1 : 0;
910 dy
= (button
== CURSOR_DOWN
) ? 1 : (button
== CURSOR_UP
) ? -1 : 0;
912 mx
= ui
->cur_x
+dx
; my
= ui
->cur_y
+dy
;
913 jx
= mx
+dx
; jy
= my
+dy
;
915 ui
->cur_jumping
= 0; /* reset, whatever. */
916 if (jx
>= 0 && jy
>= 0 && jx
< w
&& jy
< h
&&
917 state
->grid
[my
*w
+mx
] == GRID_PEG
&&
918 state
->grid
[jy
*w
+jx
] == GRID_HOLE
) {
919 /* Move cursor to the jumped-to location (this felt more
920 * natural while playtesting) */
921 sprintf(buf
, "%d,%d-%d,%d", ui
->cur_x
, ui
->cur_y
, jx
, jy
);
922 ui
->cur_x
= jx
; ui
->cur_y
= jy
;
927 } else if (IS_CURSOR_SELECT(button
)) {
928 if (!ui
->cur_visible
) {
932 if (ui
->cur_jumping
) {
936 if (state
->grid
[ui
->cur_y
*w
+ui
->cur_x
] == GRID_PEG
) {
937 /* cursor is on peg: next arrow-move wil jump. */
947 static game_state
*execute_move(const game_state
*state
, const char *move
)
949 int w
= state
->w
, h
= state
->h
;
953 if (sscanf(move
, "%d,%d-%d,%d", &sx
, &sy
, &tx
, &ty
) == 4) {
956 if (sx
< 0 || sx
>= w
|| sy
< 0 || sy
>= h
)
957 return NULL
; /* source out of range */
958 if (tx
< 0 || tx
>= w
|| ty
< 0 || ty
>= h
)
959 return NULL
; /* target out of range */
963 if (max(abs(dx
),abs(dy
)) != 2 || min(abs(dx
),abs(dy
)) != 0)
964 return NULL
; /* move length was wrong */
968 if (state
->grid
[sy
*w
+sx
] != GRID_PEG
||
969 state
->grid
[my
*w
+mx
] != GRID_PEG
||
970 state
->grid
[ty
*w
+tx
] != GRID_HOLE
)
971 return NULL
; /* grid contents were invalid */
973 ret
= dup_game(state
);
974 ret
->grid
[sy
*w
+sx
] = GRID_HOLE
;
975 ret
->grid
[my
*w
+mx
] = GRID_HOLE
;
976 ret
->grid
[ty
*w
+tx
] = GRID_PEG
;
979 * Opinion varies on whether getting to a single peg counts as
980 * completing the game, or whether that peg has to be at a
981 * specific location (central in the classic cross game, for
982 * instance). For now we take the former, rather lax position.
984 if (!ret
->completed
) {
986 for (i
= 0; i
< w
*h
; i
++)
987 if (ret
->grid
[i
] == GRID_PEG
)
998 /* ----------------------------------------------------------------------
1002 static void game_compute_size(const game_params
*params
, int tilesize
,
1005 /* Ick: fake up `ds->tilesize' for macro expansion purposes */
1006 struct { int tilesize
; } ads
, *ds
= &ads
;
1007 ads
.tilesize
= tilesize
;
1009 *x
= TILESIZE
* params
->w
+ 2 * BORDER
;
1010 *y
= TILESIZE
* params
->h
+ 2 * BORDER
;
1013 static void game_set_size(drawing
*dr
, game_drawstate
*ds
,
1014 const game_params
*params
, int tilesize
)
1016 ds
->tilesize
= tilesize
;
1018 assert(TILESIZE
> 0);
1020 assert(!ds
->drag_background
); /* set_size is never called twice */
1021 ds
->drag_background
= blitter_new(dr
, TILESIZE
, TILESIZE
);
1024 static float *game_colours(frontend
*fe
, int *ncolours
)
1026 float *ret
= snewn(3 * NCOLOURS
, float);
1028 game_mkhighlight(fe
, ret
, COL_BACKGROUND
, COL_HIGHLIGHT
, COL_LOWLIGHT
);
1030 ret
[COL_PEG
* 3 + 0] = 0.0F
;
1031 ret
[COL_PEG
* 3 + 1] = 0.0F
;
1032 ret
[COL_PEG
* 3 + 2] = 1.0F
;
1034 ret
[COL_CURSOR
* 3 + 0] = 0.5F
;
1035 ret
[COL_CURSOR
* 3 + 1] = 0.5F
;
1036 ret
[COL_CURSOR
* 3 + 2] = 1.0F
;
1038 *ncolours
= NCOLOURS
;
1042 static game_drawstate
*game_new_drawstate(drawing
*dr
, const game_state
*state
)
1044 int w
= state
->w
, h
= state
->h
;
1045 struct game_drawstate
*ds
= snew(struct game_drawstate
);
1047 ds
->tilesize
= 0; /* not decided yet */
1049 /* We can't allocate the blitter rectangle for the drag background
1050 * until we know what size to make it. */
1051 ds
->drag_background
= NULL
;
1052 ds
->dragging
= FALSE
;
1056 ds
->grid
= snewn(w
*h
, unsigned char);
1057 memset(ds
->grid
, 255, w
*h
);
1059 ds
->started
= FALSE
;
1065 static void game_free_drawstate(drawing
*dr
, game_drawstate
*ds
)
1067 if (ds
->drag_background
)
1068 blitter_free(dr
, ds
->drag_background
);
1073 static void draw_tile(drawing
*dr
, game_drawstate
*ds
,
1074 int x
, int y
, int v
, int bgcolour
)
1076 int cursor
= 0, jumping
= 0, bg
;
1078 if (bgcolour
>= 0) {
1079 draw_rect(dr
, x
, y
, TILESIZE
, TILESIZE
, bgcolour
);
1081 if (v
>= GRID_JUMPING
) {
1082 jumping
= 1; v
-= GRID_JUMPING
;
1084 if (v
>= GRID_CURSOR
) {
1085 cursor
= 1; v
-= GRID_CURSOR
;
1088 if (v
== GRID_HOLE
) {
1089 bg
= cursor
? COL_HIGHLIGHT
: COL_LOWLIGHT
;
1090 assert(!jumping
); /* can't jump from a hole! */
1091 draw_circle(dr
, x
+TILESIZE
/2, y
+TILESIZE
/2, TILESIZE
/4,
1093 } else if (v
== GRID_PEG
) {
1094 bg
= (cursor
|| jumping
) ? COL_CURSOR
: COL_PEG
;
1095 draw_circle(dr
, x
+TILESIZE
/2, y
+TILESIZE
/2, TILESIZE
/3,
1097 bg
= (!cursor
|| jumping
) ? COL_PEG
: COL_CURSOR
;
1098 draw_circle(dr
, x
+TILESIZE
/2, y
+TILESIZE
/2, TILESIZE
/4,
1102 draw_update(dr
, x
, y
, TILESIZE
, TILESIZE
);
1105 static void game_redraw(drawing
*dr
, game_drawstate
*ds
,
1106 const game_state
*oldstate
, const game_state
*state
,
1107 int dir
, const game_ui
*ui
,
1108 float animtime
, float flashtime
)
1110 int w
= state
->w
, h
= state
->h
;
1114 if (flashtime
> 0) {
1115 int frame
= (int)(flashtime
/ FLASH_FRAME
);
1116 bgcolour
= (frame
% 2 ? COL_LOWLIGHT
: COL_HIGHLIGHT
);
1118 bgcolour
= COL_BACKGROUND
;
1121 * Erase the sprite currently being dragged, if any.
1124 assert(ds
->drag_background
);
1125 blitter_load(dr
, ds
->drag_background
, ds
->dragx
, ds
->dragy
);
1126 draw_update(dr
, ds
->dragx
, ds
->dragy
, TILESIZE
, TILESIZE
);
1127 ds
->dragging
= FALSE
;
1132 TILESIZE
* state
->w
+ 2 * BORDER
,
1133 TILESIZE
* state
->h
+ 2 * BORDER
, COL_BACKGROUND
);
1136 * Draw relief marks around all the squares that aren't
1139 for (y
= 0; y
< h
; y
++)
1140 for (x
= 0; x
< w
; x
++)
1141 if (state
->grid
[y
*w
+x
] != GRID_OBST
) {
1143 * First pass: draw the full relief square.
1146 coords
[0] = COORD(x
+1) + HIGHLIGHT_WIDTH
- 1;
1147 coords
[1] = COORD(y
) - HIGHLIGHT_WIDTH
;
1148 coords
[2] = COORD(x
) - HIGHLIGHT_WIDTH
;
1149 coords
[3] = COORD(y
+1) + HIGHLIGHT_WIDTH
- 1;
1150 coords
[4] = COORD(x
) - HIGHLIGHT_WIDTH
;
1151 coords
[5] = COORD(y
) - HIGHLIGHT_WIDTH
;
1152 draw_polygon(dr
, coords
, 3, COL_HIGHLIGHT
, COL_HIGHLIGHT
);
1153 coords
[4] = COORD(x
+1) + HIGHLIGHT_WIDTH
- 1;
1154 coords
[5] = COORD(y
+1) + HIGHLIGHT_WIDTH
- 1;
1155 draw_polygon(dr
, coords
, 3, COL_LOWLIGHT
, COL_LOWLIGHT
);
1157 for (y
= 0; y
< h
; y
++)
1158 for (x
= 0; x
< w
; x
++)
1159 if (state
->grid
[y
*w
+x
] != GRID_OBST
) {
1161 * Second pass: draw everything but the two
1164 draw_rect(dr
, COORD(x
) - HIGHLIGHT_WIDTH
,
1165 COORD(y
) - HIGHLIGHT_WIDTH
,
1166 TILESIZE
+ HIGHLIGHT_WIDTH
,
1167 TILESIZE
+ HIGHLIGHT_WIDTH
, COL_HIGHLIGHT
);
1168 draw_rect(dr
, COORD(x
),
1170 TILESIZE
+ HIGHLIGHT_WIDTH
,
1171 TILESIZE
+ HIGHLIGHT_WIDTH
, COL_LOWLIGHT
);
1173 for (y
= 0; y
< h
; y
++)
1174 for (x
= 0; x
< w
; x
++)
1175 if (state
->grid
[y
*w
+x
] != GRID_OBST
) {
1177 * Third pass: draw a trapezium on each edge.
1180 int dx
, dy
, s
, sn
, c
;
1182 for (dx
= 0; dx
< 2; dx
++) {
1184 for (s
= 0; s
< 2; s
++) {
1186 c
= s
? COL_LOWLIGHT
: COL_HIGHLIGHT
;
1188 coords
[0] = COORD(x
) + (s
*dx
)*(TILESIZE
-1);
1189 coords
[1] = COORD(y
) + (s
*dy
)*(TILESIZE
-1);
1190 coords
[2] = COORD(x
) + (s
*dx
+dy
)*(TILESIZE
-1);
1191 coords
[3] = COORD(y
) + (s
*dy
+dx
)*(TILESIZE
-1);
1192 coords
[4] = coords
[2] - HIGHLIGHT_WIDTH
* (dy
-sn
*dx
);
1193 coords
[5] = coords
[3] - HIGHLIGHT_WIDTH
* (dx
-sn
*dy
);
1194 coords
[6] = coords
[0] + HIGHLIGHT_WIDTH
* (dy
+sn
*dx
);
1195 coords
[7] = coords
[1] + HIGHLIGHT_WIDTH
* (dx
+sn
*dy
);
1196 draw_polygon(dr
, coords
, 4, c
, c
);
1200 for (y
= 0; y
< h
; y
++)
1201 for (x
= 0; x
< w
; x
++)
1202 if (state
->grid
[y
*w
+x
] != GRID_OBST
) {
1204 * Second pass: draw everything but the two
1207 draw_rect(dr
, COORD(x
),
1210 TILESIZE
, COL_BACKGROUND
);
1215 draw_update(dr
, 0, 0,
1216 TILESIZE
* state
->w
+ 2 * BORDER
,
1217 TILESIZE
* state
->h
+ 2 * BORDER
);
1221 * Loop over the grid redrawing anything that looks as if it
1224 for (y
= 0; y
< h
; y
++)
1225 for (x
= 0; x
< w
; x
++) {
1228 v
= state
->grid
[y
*w
+x
];
1230 * Blank the source of a drag so it looks as if the
1231 * user picked the peg up physically.
1233 if (ui
->dragging
&& ui
->sx
== x
&& ui
->sy
== y
&& v
== GRID_PEG
)
1236 if (ui
->cur_visible
&& ui
->cur_x
== x
&& ui
->cur_y
== y
)
1237 v
+= ui
->cur_jumping
? GRID_JUMPING
: GRID_CURSOR
;
1239 if (v
!= GRID_OBST
&&
1240 (bgcolour
!= ds
->bgcolour
|| /* always redraw when flashing */
1241 v
!= ds
->grid
[y
*w
+x
])) {
1242 draw_tile(dr
, ds
, COORD(x
), COORD(y
), v
, bgcolour
);
1243 ds
->grid
[y
*w
+x
] = v
;
1248 * Draw the dragging sprite if any.
1251 ds
->dragging
= TRUE
;
1252 ds
->dragx
= ui
->dx
- TILESIZE
/2;
1253 ds
->dragy
= ui
->dy
- TILESIZE
/2;
1254 blitter_save(dr
, ds
->drag_background
, ds
->dragx
, ds
->dragy
);
1255 draw_tile(dr
, ds
, ds
->dragx
, ds
->dragy
, GRID_PEG
, -1);
1258 ds
->bgcolour
= bgcolour
;
1261 static float game_anim_length(const game_state
*oldstate
,
1262 const game_state
*newstate
, int dir
, game_ui
*ui
)
1267 static float game_flash_length(const game_state
*oldstate
,
1268 const game_state
*newstate
, int dir
, game_ui
*ui
)
1270 if (!oldstate
->completed
&& newstate
->completed
)
1271 return 2 * FLASH_FRAME
;
1276 static int game_status(const game_state
*state
)
1279 * Dead-end situations are assumed to be rescuable by Undo, so we
1280 * don't bother to identify them and return -1.
1282 return state
->completed
? +1 : 0;
1285 static int game_timing_state(const game_state
*state
, game_ui
*ui
)
1290 static void game_print_size(const game_params
*params
, float *x
, float *y
)
1294 static void game_print(drawing
*dr
, const game_state
*state
, int tilesize
)
1299 #define thegame pegs
1302 const struct game thegame
= {
1303 "Pegs", "games.pegs", "pegs",
1305 game_fetch_preset
, NULL
,
1310 TRUE
, game_configure
, custom_params
,
1318 TRUE
, game_can_format_as_text_now
, game_text_format
,
1326 PREFERRED_TILE_SIZE
, game_compute_size
, game_set_size
,
1329 game_free_drawstate
,
1334 FALSE
, FALSE
, game_print_size
, game_print
,
1335 FALSE
, /* wants_statusbar */
1336 FALSE
, game_timing_state
,
1340 /* vim: set shiftwidth=4 tabstop=8: */