2 * pegs.c: the classic Peg Solitaire game.
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.
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
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)
45 #define FLASH_FRAME 0.13F
58 static game_params
*default_params(void)
60 game_params
*ret
= snew(game_params
);
63 ret
->type
= TYPE_CROSS
;
68 static const struct game_params pegs_presets
[] = {
76 static int game_fetch_preset(int i
, char **name
, game_params
**params
)
81 if (i
< 0 || i
>= lenof(pegs_presets
))
84 ret
= snew(game_params
);
85 *ret
= pegs_presets
[i
];
87 strcpy(str
, pegs_titletypes
[ret
->type
]);
88 if (ret
->type
== TYPE_RANDOM
)
89 sprintf(str
+ strlen(str
), " %dx%d", ret
->w
, ret
->h
);
96 static void free_params(game_params
*params
)
101 static game_params
*dup_params(game_params
*params
)
103 game_params
*ret
= snew(game_params
);
104 *ret
= *params
; /* structure copy */
108 static void decode_params(game_params
*params
, char const *string
)
110 char const *p
= string
;
114 while (*p
&& isdigit((unsigned char)*p
)) p
++;
118 while (*p
&& isdigit((unsigned char)*p
)) p
++;
120 params
->h
= params
->w
;
124 * Assume a random generation scheme unless told otherwise, for the
125 * sake of internal consistency.
127 params
->type
= TYPE_RANDOM
;
128 for (i
= 0; i
< lenof(pegs_lowertypes
); i
++)
129 if (!strcmp(p
, pegs_lowertypes
[i
]))
133 static char *encode_params(game_params
*params
, int full
)
137 sprintf(str
, "%dx%d", params
->w
, params
->h
);
139 assert(params
->type
>= 0 && params
->type
< lenof(pegs_lowertypes
));
140 strcat(str
, pegs_lowertypes
[params
->type
]);
145 static config_item
*game_configure(game_params
*params
)
147 config_item
*ret
= snewn(4, config_item
);
150 ret
[0].name
= "Width";
151 ret
[0].type
= C_STRING
;
152 sprintf(buf
, "%d", params
->w
);
153 ret
[0].sval
= dupstr(buf
);
156 ret
[1].name
= "Height";
157 ret
[1].type
= C_STRING
;
158 sprintf(buf
, "%d", params
->h
);
159 ret
[1].sval
= dupstr(buf
);
162 ret
[2].name
= "Board type";
163 ret
[2].type
= C_CHOICES
;
164 ret
[2].sval
= TYPECONFIG
;
165 ret
[2].ival
= params
->type
;
175 static game_params
*custom_params(config_item
*cfg
)
177 game_params
*ret
= snew(game_params
);
179 ret
->w
= atoi(cfg
[0].sval
);
180 ret
->h
= atoi(cfg
[1].sval
);
181 ret
->type
= cfg
[2].ival
;
186 static char *validate_params(game_params
*params
, int full
)
188 if (full
&& (params
->w
<= 3 || params
->h
<= 3))
189 return "Width and height must both be greater than three";
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.
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";
204 /* ----------------------------------------------------------------------
205 * Beginning of code to generate random Peg Solitaire boards.
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.
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).
225 * x,y are the start point of the move during generation (hence
226 * its endpoint during normal play).
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.
235 * cost is 0, 1 or 2, depending on how many GRID_OBSTs we must
236 * turn into GRID_HOLEs to play this move.
241 static int movecmp(void *av
, void *bv
)
243 struct move
*a
= (struct move
*)av
;
244 struct move
*b
= (struct move
*)bv
;
248 else if (a
->y
> b
->y
)
253 else if (a
->x
> b
->x
)
258 else if (a
->dy
> b
->dy
)
263 else if (a
->dx
> b
->dx
)
269 static int movecmpcost(void *av
, void *bv
)
271 struct move
*a
= (struct move
*)av
;
272 struct move
*b
= (struct move
*)bv
;
274 if (a
->cost
< b
->cost
)
276 else if (a
->cost
> b
->cost
)
279 return movecmp(av
, bv
);
283 tree234
*bymove
, *bycost
;
286 static void update_moves(unsigned char *grid
, int w
, int h
, int x
, int y
,
287 struct movetrees
*trees
)
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.
296 for (dir
= 0; dir
< 4; dir
++) {
300 dx
= 0, dy
= dir
- 2;
302 dy
= 0, dx
= dir
- 1;
304 assert(abs(dx
) + abs(dy
) == 1);
306 for (pos
= 0; pos
< 3; pos
++) {
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 */
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
) {
326 move
.cost
= (v2
== GRID_OBST
) + (v3
== GRID_OBST
);
329 * This move is possible. See if it's already in
332 m
= find234(trees
->bymove
, &move
, NULL
);
333 if (m
&& m
->cost
!= move
.cost
) {
335 * It's in the tree but listed with the wrong
336 * cost. Remove the old version.
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
);
342 del234(trees
->bymove
, m
);
343 del234(trees
->bycost
, m
);
349 m
= snew(struct move
);
351 m2
= add234(trees
->bymove
, m
);
352 m2
= add234(trees
->bycost
, 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
);
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
);
366 * This move is impossible. If it is already in the
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.)
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
);
381 del234(trees
->bycost
, m
);
389 static void pegs_genmoves(unsigned char *grid
, int w
, int h
, random_state
*rs
)
391 struct movetrees atrees
, *trees
= &atrees
;
395 trees
->bymove
= newtree234(movecmp
);
396 trees
->bycost
= newtree234(movecmpcost
);
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
);
406 int limit
, maxcost
, index
;
407 struct move mtmp
, move
, *m
;
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.
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
419 maxcost
= (nmoves
< w
*h
/2 ?
2 : 1);
420 m
= NULL
; /* placate optimiser */
421 for (mtmp
.cost
= 0; mtmp
.cost
<= maxcost
; mtmp
.cost
++) {
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
);
433 index
= random_upto(rs
, limit
+1);
434 move
= *(struct move
*)index234(trees
->bycost
, index
);
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
);
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
;
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
);
454 while ((m
= delpos234(trees
->bymove
, 0)) != NULL
) {
455 del234(trees
->bycost
, m
);
458 freetree234(trees
->bymove
);
459 freetree234(trees
->bycost
);
462 static void pegs_generate(unsigned char *grid
, int w
, int h
, random_state
*rs
)
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");
472 pegs_genmoves(grid
, w
, h
, rs
);
473 #ifdef GENERATION_DIAGNOSTICS
474 printf("finished move selection\n");
478 for (y
= 0; y
< h
; y
++) {
479 if (grid
[y
*w
+0] != GRID_OBST
)
481 if (grid
[y
*w
+w
-1] != GRID_OBST
)
484 for (x
= 0; x
< w
; x
++) {
485 if (grid
[0*w
+x
] != GRID_OBST
)
487 if (grid
[(h
-1)*w
+x
] != GRID_OBST
)
493 #ifdef GENERATION_DIAGNOSTICS
494 printf("insufficient extent; trying again\n");
497 #ifdef GENERATION_DIAGNOSTICS
502 /* ----------------------------------------------------------------------
503 * End of board generation code. Now for the client code which uses
504 * it as part of the puzzle.
507 static char *new_game_desc(game_params
*params
, random_state
*rs
,
508 char **aux
, int interactive
)
510 int w
= params
->w
, h
= params
->h
;
515 grid
= snewn(w
*h
, unsigned char);
516 if (params
->type
== TYPE_RANDOM
) {
517 pegs_generate(grid
, w
, h
, rs
);
521 for (y
= 0; y
< h
; y
++)
522 for (x
= 0; x
< w
; x
++) {
523 v
= GRID_OBST
; /* placate optimiser */
524 switch (params
->type
) {
528 if (cx
== 0 && cy
== 0)
530 else if (cx
> 1 && cy
> 1)
538 if (cx
== 0 && cy
== 0)
540 else if (cx
+ cy
> 1 + max(w
,h
)/2)
551 * Encode a game description which is simply a long list of P
552 * for peg, H for hole or O for obstacle.
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');
565 static char *validate_desc(game_params
*params
, char *desc
)
567 int len
= params
->w
* params
->h
;
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";
577 static game_state
*new_game(midend_data
*me
, game_params
*params
, char *desc
)
579 int w
= params
->w
, h
= params
->h
;
580 game_state
*state
= snew(game_state
);
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
);
594 static game_state
*dup_game(game_state
*state
)
596 int w
= state
->w
, h
= state
->h
;
597 game_state
*ret
= snew(game_state
);
601 ret
->completed
= state
->completed
;
602 ret
->grid
= snewn(w
*h
, unsigned char);
603 memcpy(ret
->grid
, state
->grid
, w
*h
);
608 static void free_game(game_state
*state
)
614 static char *solve_game(game_state
*state
, game_state
*currstate
,
615 char *aux
, char **error
)
620 static char *game_text_format(game_state
*state
)
622 int w
= state
->w
, h
= state
->h
;
626 ret
= snewn((w
+1)*h
+ 1, char);
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';
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 */
645 static game_ui
*new_ui(game_state
*state
)
647 game_ui
*ui
= snew(game_ui
);
649 ui
->sx
= ui
->sy
= ui
->dx
= ui
->dy
= 0;
650 ui
->dragging
= FALSE
;
655 static void free_ui(game_ui
*ui
)
660 static char *encode_ui(game_ui
*ui
)
665 static void decode_ui(game_ui
*ui
, char *encoding
)
669 static void game_changed_state(game_ui
*ui
, game_state
*oldstate
,
670 game_state
*newstate
)
673 * Cancel a drag, in case the source square has become
676 ui
->dragging
= FALSE
;
679 #define PREFERRED_TILE_SIZE 33
680 #define TILESIZE (ds->tilesize)
681 #define BORDER (TILESIZE / 2)
683 #define HIGHLIGHT_WIDTH (TILESIZE / 16)
685 #define COORD(x) ( BORDER + (x) * TILESIZE )
686 #define FROMCOORD(x) ( ((x) + TILESIZE - BORDER) / TILESIZE - 1 )
688 struct game_drawstate
{
690 blitter
*drag_background
;
691 int dragging
, dragx
, dragy
;
698 static char *interpret_move(game_state
*state
, game_ui
*ui
, game_drawstate
*ds
,
699 int x
, int y
, int button
)
701 int w
= state
->w
, h
= state
->h
;
703 if (button
== LEFT_BUTTON
) {
707 * Left button down: we attempt to start a drag.
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
717 assert(!ui
->dragging
);
721 if (tx
>= 0 && tx
< w
&& ty
>= 0 && ty
< h
&&
722 state
->grid
[ty
*w
+tx
] == GRID_PEG
) {
728 return ""; /* ui modified */
730 } else if (button
== LEFT_DRAG
&& ui
->dragging
) {
732 * Mouse moved; just move the peg being dragged.
736 return ""; /* ui modified */
737 } else if (button
== LEFT_RELEASE
&& ui
->dragging
) {
742 * Button released. Identify the target square of the drag,
743 * see if it represents a valid move, and if so make it.
745 ui
->dragging
= FALSE
; /* cancel the drag no matter what */
748 if (tx
< 0 || tx
>= w
|| ty
< 0 || ty
>= h
)
749 return ""; /* target out of range */
752 if (max(abs(dx
),abs(dy
)) != 2 || min(abs(dx
),abs(dy
)) != 0)
753 return ""; /* move length was wrong */
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 */
763 * We have a valid move. Encode it simply as source and
764 * destination coordinate pairs.
766 sprintf(buf
, "%d,%d-%d,%d", ui
->sx
, ui
->sy
, tx
, ty
);
772 static game_state
*execute_move(game_state
*state
, char *move
)
774 int w
= state
->w
, h
= state
->h
;
778 if (sscanf(move
, "%d,%d-%d,%d", &sx
, &sy
, &tx
, &ty
)) {
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 */
788 if (max(abs(dx
),abs(dy
)) != 2 || min(abs(dx
),abs(dy
)) != 0)
789 return NULL
; /* move length was wrong */
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 */
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
;
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.
809 if (!ret
->completed
) {
811 for (i
= 0; i
< w
*h
; i
++)
812 if (ret
->grid
[i
] == GRID_PEG
)
823 /* ----------------------------------------------------------------------
827 static void game_compute_size(game_params
*params
, int tilesize
,
830 /* Ick: fake up `ds->tilesize' for macro expansion purposes */
831 struct { int tilesize
; } ads
, *ds
= &ads
;
832 ads
.tilesize
= tilesize
;
834 *x
= TILESIZE
* params
->w
+ 2 * BORDER
;
835 *y
= TILESIZE
* params
->h
+ 2 * BORDER
;
838 static void game_set_size(game_drawstate
*ds
, game_params
*params
,
841 ds
->tilesize
= tilesize
;
843 assert(TILESIZE
> 0);
845 if (ds
->drag_background
)
846 blitter_free(ds
->drag_background
);
847 ds
->drag_background
= blitter_new(TILESIZE
, TILESIZE
);
850 static float *game_colours(frontend
*fe
, game_state
*state
, int *ncolours
)
852 float *ret
= snewn(3 * NCOLOURS
, float);
856 frontend_default_colour(fe
, &ret
[COL_BACKGROUND
* 3]);
859 * Drop the background colour so that the highlight is
860 * noticeably brighter than it while still being under 1.
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
);
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
;
876 ret
[COL_PEG
* 3 + 0] = 0.0F
;
877 ret
[COL_PEG
* 3 + 1] = 0.0F
;
878 ret
[COL_PEG
* 3 + 2] = 1.0F
;
880 *ncolours
= NCOLOURS
;
884 static game_drawstate
*game_new_drawstate(game_state
*state
)
886 int w
= state
->w
, h
= state
->h
;
887 struct game_drawstate
*ds
= snew(struct game_drawstate
);
889 ds
->tilesize
= 0; /* not decided yet */
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
;
898 ds
->grid
= snewn(w
*h
, unsigned char);
899 memset(ds
->grid
, 255, w
*h
);
907 static void game_free_drawstate(game_drawstate
*ds
)
909 if (ds
->drag_background
)
910 blitter_free(ds
->drag_background
);
915 static void draw_tile(frontend
*fe
, game_drawstate
*ds
,
916 int x
, int y
, int v
, int bgcolour
)
919 draw_rect(fe
, x
, y
, TILESIZE
, TILESIZE
, bgcolour
);
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,
930 draw_update(fe
, x
, y
, TILESIZE
, TILESIZE
);
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
)
937 int w
= state
->w
, h
= state
->h
;
942 int frame
= (int)(flashtime
/ FLASH_FRAME
);
943 bgcolour
= (frame
% 2 ? COL_LOWLIGHT
: COL_HIGHLIGHT
);
945 bgcolour
= COL_BACKGROUND
;
948 * Erase the sprite currently being dragged, if any.
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
;
959 TILESIZE
* state
->w
+ 2 * BORDER
,
960 TILESIZE
* state
->h
+ 2 * BORDER
, COL_BACKGROUND
);
963 * Draw relief marks around all the squares that aren't
966 for (y
= 0; y
< h
; y
++)
967 for (x
= 0; x
< w
; x
++)
968 if (state
->grid
[y
*w
+x
] != GRID_OBST
) {
970 * First pass: draw the full relief square.
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
);
984 for (y
= 0; y
< h
; y
++)
985 for (x
= 0; x
< w
; x
++)
986 if (state
->grid
[y
*w
+x
] != GRID_OBST
) {
988 * Second pass: draw everything but the two
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
),
997 TILESIZE
+ HIGHLIGHT_WIDTH
,
998 TILESIZE
+ HIGHLIGHT_WIDTH
, COL_LOWLIGHT
);
1000 for (y
= 0; y
< h
; y
++)
1001 for (x
= 0; x
< w
; x
++)
1002 if (state
->grid
[y
*w
+x
] != GRID_OBST
) {
1004 * Third pass: draw a trapezium on each edge.
1007 int dx
, dy
, s
, sn
, c
;
1009 for (dx
= 0; dx
< 2; dx
++) {
1011 for (s
= 0; s
< 2; s
++) {
1013 c
= s ? COL_LOWLIGHT
: COL_HIGHLIGHT
;
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
);
1027 for (y
= 0; y
< h
; y
++)
1028 for (x
= 0; x
< w
; x
++)
1029 if (state
->grid
[y
*w
+x
] != GRID_OBST
) {
1031 * Second pass: draw everything but the two
1034 draw_rect(fe
, COORD(x
),
1037 TILESIZE
, COL_BACKGROUND
);
1042 draw_update(fe
, 0, 0,
1043 TILESIZE
* state
->w
+ 2 * BORDER
,
1044 TILESIZE
* state
->h
+ 2 * BORDER
);
1048 * Loop over the grid redrawing anything that looks as if it
1051 for (y
= 0; y
< h
; y
++)
1052 for (x
= 0; x
< w
; x
++) {
1055 v
= state
->grid
[y
*w
+x
];
1057 * Blank the source of a drag so it looks as if the
1058 * user picked the peg up physically.
1060 if (ui
->dragging
&& ui
->sx
== x
&& ui
->sy
== y
&& v
== GRID_PEG
)
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
);
1070 * Draw the dragging sprite if any.
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);
1080 ds
->bgcolour
= bgcolour
;
1083 static float game_anim_length(game_state
*oldstate
, game_state
*newstate
,
1084 int dir
, game_ui
*ui
)
1089 static float game_flash_length(game_state
*oldstate
, game_state
*newstate
,
1090 int dir
, game_ui
*ui
)
1092 if (!oldstate
->completed
&& newstate
->completed
)
1093 return 2 * FLASH_FRAME
;
1098 static int game_wants_statusbar(void)
1103 static int game_timing_state(game_state
*state
)
1109 #define thegame pegs
1112 const struct game thegame
= {
1113 "Pegs", "games.pegs",
1120 TRUE
, game_configure
, custom_params
,
1128 TRUE
, game_text_format
,
1136 PREFERRED_TILE_SIZE
, game_compute_size
, game_set_size
,
1139 game_free_drawstate
,
1143 game_wants_statusbar
,
1144 FALSE
, game_timing_state
,
1145 0, /* mouse_priorities */