A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 1396 lines 38 kB view raw
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 <limits.h> 11#ifdef NO_TGMATH_H 12# include <math.h> 13#else 14# include <tgmath.h> 15#endif 16 17#include "puzzles.h" 18#include "tree234.h" 19 20#define GRID_HOLE 0 21#define GRID_PEG 1 22#define GRID_OBST 2 23 24#define GRID_CURSOR 10 25#define GRID_JUMPING 20 26 27enum { 28 COL_BACKGROUND, 29 COL_HIGHLIGHT, 30 COL_LOWLIGHT, 31 COL_PEG, 32 COL_CURSOR, 33 NCOLOURS 34}; 35 36/* 37 * Grid shapes. I do some macro ickery here to ensure that my enum 38 * and the various forms of my name list always match up. 39 */ 40#define TYPELIST(A) \ 41 A(CROSS,Cross,cross) \ 42 A(OCTAGON,Octagon,octagon) \ 43 A(RANDOM,Random,random) 44#define ENUM(upper,title,lower) TYPE_ ## upper, 45#define TITLE(upper,title,lower) #title, 46#define LOWER(upper,title,lower) #lower, 47#define CONFIG(upper,title,lower) ":" #title 48 49enum { TYPELIST(ENUM) TYPECOUNT }; 50static char const *const pegs_titletypes[] = { TYPELIST(TITLE) }; 51static char const *const pegs_lowertypes[] = { TYPELIST(LOWER) }; 52#define TYPECONFIG TYPELIST(CONFIG) 53 54#define FLASH_FRAME 0.13F 55 56struct game_params { 57 int w, h; 58 int type; 59}; 60 61struct game_state { 62 int w, h; 63 bool completed; 64 unsigned char *grid; 65}; 66 67static game_params *default_params(void) 68{ 69 game_params *ret = snew(game_params); 70 71 ret->w = ret->h = 7; 72 ret->type = TYPE_CROSS; 73 74 return ret; 75} 76 77static const struct game_params pegs_presets[] = { 78 {5, 7, TYPE_CROSS}, 79 {7, 7, TYPE_CROSS}, 80 {5, 9, TYPE_CROSS}, 81 {7, 9, TYPE_CROSS}, 82 {9, 9, TYPE_CROSS}, 83 {7, 7, TYPE_OCTAGON}, 84 {5, 5, TYPE_RANDOM}, 85 {7, 7, TYPE_RANDOM}, 86 {9, 9, TYPE_RANDOM}, 87}; 88 89static bool game_fetch_preset(int i, char **name, game_params **params) 90{ 91 game_params *ret; 92 char str[80]; 93 94 if (i < 0 || i >= lenof(pegs_presets)) 95 return false; 96 97 ret = snew(game_params); 98 *ret = pegs_presets[i]; 99 100 strcpy(str, pegs_titletypes[ret->type]); 101 if (ret->type == TYPE_CROSS || ret->type == TYPE_RANDOM) 102 sprintf(str + strlen(str), " %dx%d", ret->w, ret->h); 103 104 *name = dupstr(str); 105 *params = ret; 106 return true; 107} 108 109static void free_params(game_params *params) 110{ 111 sfree(params); 112} 113 114static game_params *dup_params(const game_params *params) 115{ 116 game_params *ret = snew(game_params); 117 *ret = *params; /* structure copy */ 118 return ret; 119} 120 121static void decode_params(game_params *params, char const *string) 122{ 123 char const *p = string; 124 int i; 125 126 params->w = atoi(p); 127 while (*p && isdigit((unsigned char)*p)) p++; 128 if (*p == 'x') { 129 p++; 130 params->h = atoi(p); 131 while (*p && isdigit((unsigned char)*p)) p++; 132 } else { 133 params->h = params->w; 134 } 135 136 for (i = 0; i < lenof(pegs_lowertypes); i++) 137 if (!strcmp(p, pegs_lowertypes[i])) 138 params->type = i; 139} 140 141static char *encode_params(const game_params *params, bool full) 142{ 143 char str[80]; 144 145 sprintf(str, "%dx%d", params->w, params->h); 146 if (full) { 147 assert(params->type >= 0 && params->type < lenof(pegs_lowertypes)); 148 strcat(str, pegs_lowertypes[params->type]); 149 } 150 return dupstr(str); 151} 152 153static config_item *game_configure(const game_params *params) 154{ 155 config_item *ret = snewn(4, config_item); 156 char buf[80]; 157 158 ret[0].name = "Width"; 159 ret[0].type = C_STRING; 160 sprintf(buf, "%d", params->w); 161 ret[0].u.string.sval = dupstr(buf); 162 163 ret[1].name = "Height"; 164 ret[1].type = C_STRING; 165 sprintf(buf, "%d", params->h); 166 ret[1].u.string.sval = dupstr(buf); 167 168 ret[2].name = "Board type"; 169 ret[2].type = C_CHOICES; 170 ret[2].u.choices.choicenames = TYPECONFIG; 171 ret[2].u.choices.selected = params->type; 172 173 ret[3].name = NULL; 174 ret[3].type = C_END; 175 176 return ret; 177} 178 179static game_params *custom_params(const config_item *cfg) 180{ 181 game_params *ret = snew(game_params); 182 183 ret->w = atoi(cfg[0].u.string.sval); 184 ret->h = atoi(cfg[1].u.string.sval); 185 ret->type = cfg[2].u.choices.selected; 186 187 return ret; 188} 189 190static const char *validate_params(const game_params *params, bool full) 191{ 192 if (full && (params->w <= 3 || params->h <= 3)) 193 return "Width and height must both be greater than three"; 194 if (params->w < 1 || params->h < 1) 195 return "Width and height must both be at least one"; 196 if (params->w > INT_MAX / params->h) 197 return "Width times height must not be unreasonably large"; 198 199 /* 200 * At http://www.gibell.net/pegsolitaire/GenCross/GenCrossBoards0.html 201 * George I. Bell asserts that various generalised cross-shaped 202 * boards are soluble starting (and finishing) with the centre 203 * hole. We permit the symmetric ones. Bell's notation for each 204 * soluble board is listed. 205 */ 206 if (full && params->type == TYPE_CROSS) { 207 if (!((params->w == 9 && params->h == 5) || /* (3,1,3,1) */ 208 (params->w == 5 && params->h == 9) || /* (1,3,1,3) */ 209 (params->w == 9 && params->h == 9) || /* (3,3,3,3) */ 210 (params->w == 7 && params->h == 5) || /* (2,1,2,1) */ 211 (params->w == 5 && params->h == 7) || /* (1,2,1,2) */ 212 (params->w == 9 && params->h == 7) || /* (3,2,3,2) */ 213 (params->w == 7 && params->h == 9) || /* (2,3,2,3) */ 214 (params->w == 7 && params->h == 7))) /* (2,2,2,2) */ 215 return "This board type is only supported at " 216 "5x7, 5x9, 7x7, 7x9, and 9x9"; 217 } 218 219 /* 220 * It might be possible to implement generalisations of 221 * Octagon, but only if I can find a proof that they're all 222 * soluble. For the moment, therefore, I'm going to disallow 223 * it at any size other than the standard one. 224 */ 225 if (full && params->type == TYPE_OCTAGON) { 226 if (params->w != 7 || params->h != 7) 227 return "This board type is only supported at 7x7"; 228 } 229 return NULL; 230} 231 232/* ---------------------------------------------------------------------- 233 * Beginning of code to generate random Peg Solitaire boards. 234 * 235 * This procedure is done with no aesthetic judgment, no effort at 236 * symmetry, no difficulty grading and generally no finesse 237 * whatsoever. We simply begin with an empty board containing a 238 * single peg, and repeatedly make random reverse moves until it's 239 * plausibly full. This typically yields a scrappy haphazard mess 240 * with several holes, an uneven shape, and no redeeming features 241 * except guaranteed solubility. 242 * 243 * My only concessions to sophistication are (a) to repeat the 244 * generation process until I at least get a grid that touches 245 * every edge of the specified board size, and (b) to try when 246 * selecting moves to reuse existing space rather than expanding 247 * into new space (so that non-rectangular board shape becomes a 248 * factor during play). 249 */ 250 251struct move { 252 /* 253 * x,y are the start point of the move during generation (hence 254 * its endpoint during normal play). 255 * 256 * dx,dy are the direction of the move during generation. 257 * Absolute value 1. Hence, for example, x=3,y=5,dx=1,dy=0 258 * means that the move during generation starts at (3,5) and 259 * ends at (5,5), and vice versa during normal play. 260 */ 261 int x, y, dx, dy; 262 /* 263 * cost is 0, 1 or 2, depending on how many GRID_OBSTs we must 264 * turn into GRID_HOLEs to play this move. 265 */ 266 int cost; 267}; 268 269static int movecmp(void *av, void *bv) 270{ 271 struct move *a = (struct move *)av; 272 struct move *b = (struct move *)bv; 273 274 if (a->y < b->y) 275 return -1; 276 else if (a->y > b->y) 277 return +1; 278 279 if (a->x < b->x) 280 return -1; 281 else if (a->x > b->x) 282 return +1; 283 284 if (a->dy < b->dy) 285 return -1; 286 else if (a->dy > b->dy) 287 return +1; 288 289 if (a->dx < b->dx) 290 return -1; 291 else if (a->dx > b->dx) 292 return +1; 293 294 return 0; 295} 296 297static int movecmpcost(void *av, void *bv) 298{ 299 struct move *a = (struct move *)av; 300 struct move *b = (struct move *)bv; 301 302 if (a->cost < b->cost) 303 return -1; 304 else if (a->cost > b->cost) 305 return +1; 306 307 return movecmp(av, bv); 308} 309 310struct movetrees { 311 tree234 *bymove, *bycost; 312}; 313 314static void update_moves(unsigned char *grid, int w, int h, int x, int y, 315 struct movetrees *trees) 316{ 317 struct move move; 318 int dir, pos; 319 320 /* 321 * There are twelve moves that can include (x,y): three in each 322 * of four directions. Check each one to see if it's possible. 323 */ 324 for (dir = 0; dir < 4; dir++) { 325 int dx, dy; 326 327 if (dir & 1) 328 dx = 0, dy = dir - 2; 329 else 330 dy = 0, dx = dir - 1; 331 332 assert(abs(dx) + abs(dy) == 1); 333 334 for (pos = 0; pos < 3; pos++) { 335 int v1, v2, v3; 336 337 move.dx = dx; 338 move.dy = dy; 339 move.x = x - pos*dx; 340 move.y = y - pos*dy; 341 342 if (move.x < 0 || move.x >= w || move.y < 0 || move.y >= h) 343 continue; /* completely invalid move */ 344 if (move.x+2*move.dx < 0 || move.x+2*move.dx >= w || 345 move.y+2*move.dy < 0 || move.y+2*move.dy >= h) 346 continue; /* completely invalid move */ 347 348 v1 = grid[move.y * w + move.x]; 349 v2 = grid[(move.y+move.dy) * w + (move.x+move.dx)]; 350 v3 = grid[(move.y+2*move.dy)*w + (move.x+2*move.dx)]; 351 if (v1 == GRID_PEG && v2 != GRID_PEG && v3 != GRID_PEG) { 352 struct move *m; 353 354 move.cost = (v2 == GRID_OBST) + (v3 == GRID_OBST); 355 356 /* 357 * This move is possible. See if it's already in 358 * the tree. 359 */ 360 m = find234(trees->bymove, &move, NULL); 361 if (m && m->cost != move.cost) { 362 /* 363 * It's in the tree but listed with the wrong 364 * cost. Remove the old version. 365 */ 366#ifdef GENERATION_DIAGNOSTICS 367 printf("correcting %d%+d,%d%+d at cost %d\n", 368 m->x, m->dx, m->y, m->dy, m->cost); 369#endif 370 del234(trees->bymove, m); 371 del234(trees->bycost, m); 372 sfree(m); 373 m = NULL; 374 } 375 if (!m) { 376 struct move *m, *m2; 377 m = snew(struct move); 378 *m = move; 379 m2 = add234(trees->bymove, m); 380 m2 = add234(trees->bycost, m); 381 assert(m2 == m); 382#ifdef GENERATION_DIAGNOSTICS 383 printf("adding %d%+d,%d%+d at cost %d\n", 384 move.x, move.dx, move.y, move.dy, move.cost); 385#endif 386 } else { 387#ifdef GENERATION_DIAGNOSTICS 388 printf("not adding %d%+d,%d%+d at cost %d\n", 389 move.x, move.dx, move.y, move.dy, move.cost); 390#endif 391 } 392 } else { 393 /* 394 * This move is impossible. If it is already in the 395 * tree, delete it. 396 * 397 * (We make use here of the fact that del234 398 * doesn't have to be passed a pointer to the 399 * _actual_ element it's deleting: it merely needs 400 * one that compares equal to it, and it will 401 * return the one it deletes.) 402 */ 403 struct move *m = del234(trees->bymove, &move); 404#ifdef GENERATION_DIAGNOSTICS 405 printf("%sdeleting %d%+d,%d%+d\n", m ? "" : "not ", 406 move.x, move.dx, move.y, move.dy); 407#endif 408 if (m) { 409 del234(trees->bycost, m); 410 sfree(m); 411 } 412 } 413 } 414 } 415} 416 417static void pegs_genmoves(unsigned char *grid, int w, int h, random_state *rs) 418{ 419 struct movetrees atrees, *trees = &atrees; 420 struct move *m; 421 int x, y, i, nmoves; 422 423 trees->bymove = newtree234(movecmp); 424 trees->bycost = newtree234(movecmpcost); 425 426 for (y = 0; y < h; y++) 427 for (x = 0; x < w; x++) 428 if (grid[y*w+x] == GRID_PEG) 429 update_moves(grid, w, h, x, y, trees); 430 431 nmoves = 0; 432 433 while (1) { 434 int limit, maxcost, index; 435 struct move mtmp, move, *m; 436 437 /* 438 * See how many moves we can make at zero cost. Make one, 439 * if possible. Failing that, make a one-cost move, and 440 * then a two-cost one. 441 * 442 * After filling at least half the input grid, we no longer 443 * accept cost-2 moves: if that's our only option, we give 444 * up and finish. 445 */ 446 mtmp.y = h+1; 447 maxcost = (nmoves < w*h/2 ? 2 : 1); 448 m = NULL; /* placate optimiser */ 449 for (mtmp.cost = 0; mtmp.cost <= maxcost; mtmp.cost++) { 450 limit = -1; 451 m = findrelpos234(trees->bycost, &mtmp, NULL, REL234_LT, &limit); 452#ifdef GENERATION_DIAGNOSTICS 453 printf("%d moves available with cost %d\n", limit+1, mtmp.cost); 454#endif 455 if (m) 456 break; 457 } 458 if (!m) 459 break; 460 461 index = random_upto(rs, limit+1); 462 move = *(struct move *)index234(trees->bycost, index); 463 464#ifdef GENERATION_DIAGNOSTICS 465 printf("selecting move %d%+d,%d%+d at cost %d\n", 466 move.x, move.dx, move.y, move.dy, move.cost); 467#endif 468 469 grid[move.y * w + move.x] = GRID_HOLE; 470 grid[(move.y+move.dy) * w + (move.x+move.dx)] = GRID_PEG; 471 grid[(move.y+2*move.dy)*w + (move.x+2*move.dx)] = GRID_PEG; 472 473 for (i = 0; i <= 2; i++) { 474 int tx = move.x + i*move.dx; 475 int ty = move.y + i*move.dy; 476 update_moves(grid, w, h, tx, ty, trees); 477 } 478 479 nmoves++; 480 } 481 482 while ((m = delpos234(trees->bymove, 0)) != NULL) { 483 del234(trees->bycost, m); 484 sfree(m); 485 } 486 freetree234(trees->bymove); 487 freetree234(trees->bycost); 488} 489 490static void pegs_generate(unsigned char *grid, int w, int h, random_state *rs) 491{ 492 while (1) { 493 int x, y, extremes; 494 495 memset(grid, GRID_OBST, w*h); 496 grid[(h/2) * w + (w/2)] = GRID_PEG; 497#ifdef GENERATION_DIAGNOSTICS 498 printf("beginning move selection\n"); 499#endif 500 pegs_genmoves(grid, w, h, rs); 501#ifdef GENERATION_DIAGNOSTICS 502 printf("finished move selection\n"); 503#endif 504 505 extremes = 0; 506 for (y = 0; y < h; y++) { 507 if (grid[y*w+0] != GRID_OBST) 508 extremes |= 1; 509 if (grid[y*w+w-1] != GRID_OBST) 510 extremes |= 2; 511 } 512 for (x = 0; x < w; x++) { 513 if (grid[0*w+x] != GRID_OBST) 514 extremes |= 4; 515 if (grid[(h-1)*w+x] != GRID_OBST) 516 extremes |= 8; 517 } 518 519 if (extremes == 15) 520 break; 521#ifdef GENERATION_DIAGNOSTICS 522 printf("insufficient extent; trying again\n"); 523#endif 524 } 525#ifdef GENERATION_DIAGNOSTICS 526 fflush(stdout); 527#endif 528} 529 530/* ---------------------------------------------------------------------- 531 * End of board generation code. Now for the client code which uses 532 * it as part of the puzzle. 533 */ 534 535static char *new_game_desc(const game_params *params, random_state *rs, 536 char **aux, bool interactive) 537{ 538 int w = params->w, h = params->h; 539 unsigned char *grid; 540 char *ret; 541 int i; 542 543 grid = snewn(w*h, unsigned char); 544 if (params->type == TYPE_RANDOM) { 545 pegs_generate(grid, w, h, rs); 546 } else { 547 int x, y, cx, cy, v; 548 549 for (y = 0; y < h; y++) 550 for (x = 0; x < w; x++) { 551 v = GRID_OBST; /* placate optimiser */ 552 switch (params->type) { 553 case TYPE_CROSS: 554 cx = abs(x - w/2); 555 cy = abs(y - h/2); 556 if (cx == 0 && cy == 0) 557 v = GRID_HOLE; 558 else if (cx > 1 && cy > 1) 559 v = GRID_OBST; 560 else 561 v = GRID_PEG; 562 break; 563 case TYPE_OCTAGON: 564 cx = abs(x - w/2); 565 cy = abs(y - h/2); 566 if (cx + cy > 1 + max(w,h)/2) 567 v = GRID_OBST; 568 else 569 v = GRID_PEG; 570 break; 571 } 572 grid[y*w+x] = v; 573 } 574 575 if (params->type == TYPE_OCTAGON) { 576 /* 577 * The octagonal (European) solitaire layout is 578 * actually _insoluble_ with the starting hole at the 579 * centre. Here's a proof: 580 * 581 * Colour the squares of the board diagonally in 582 * stripes of three different colours, which I'll call 583 * A, B and C. So the board looks like this: 584 * 585 * A B C 586 * A B C A B 587 * A B C A B C A 588 * B C A B C A B 589 * C A B C A B C 590 * B C A B C 591 * A B C 592 * 593 * Suppose we keep running track of the number of pegs 594 * occuping each colour of square. This colouring has 595 * the property that any valid move whatsoever changes 596 * all three of those counts by one (two of them go 597 * down and one goes up), which means that the _parity_ 598 * of every count flips on every move. 599 * 600 * If the centre square starts off unoccupied, then 601 * there are twelve pegs on each colour and all three 602 * counts start off even; therefore, after 35 moves all 603 * three counts would have to be odd, which isn't 604 * possible if there's only one peg left. [] 605 * 606 * This proof works just as well if the starting hole 607 * is _any_ of the thirteen positions labelled B. Also, 608 * we can stripe the board in the opposite direction 609 * and rule out any square labelled B in that colouring 610 * as well. This leaves: 611 * 612 * Y n Y 613 * n n Y n n 614 * Y n n Y n n Y 615 * n Y Y n Y Y n 616 * Y n n Y n n Y 617 * n n Y n n 618 * Y n Y 619 * 620 * where the ns are squares we've proved insoluble, and 621 * the Ys are the ones remaining. 622 * 623 * That doesn't prove all those starting positions to 624 * be soluble, of course; they're merely the ones we 625 * _haven't_ proved to be impossible. Nevertheless, it 626 * turns out that they are all soluble, so when the 627 * user requests an Octagon board the simplest thing is 628 * to pick one of these at random. 629 * 630 * Rather than picking equiprobably from those twelve 631 * positions, we'll pick equiprobably from the three 632 * equivalence classes 633 */ 634 switch (random_upto(rs, 3)) { 635 case 0: 636 /* Remove a random corner piece. */ 637 { 638 int dx, dy; 639 640 dx = random_upto(rs, 2) * 2 - 1; /* +1 or -1 */ 641 dy = random_upto(rs, 2) * 2 - 1; /* +1 or -1 */ 642 if (random_upto(rs, 2)) 643 dy *= 3; 644 else 645 dx *= 3; 646 grid[(3+dy)*w+(3+dx)] = GRID_HOLE; 647 } 648 break; 649 case 1: 650 /* Remove a random piece two from the centre. */ 651 { 652 int dx, dy; 653 dx = 2 * (random_upto(rs, 2) * 2 - 1); 654 if (random_upto(rs, 2)) 655 dy = 0; 656 else 657 dy = dx, dx = 0; 658 grid[(3+dy)*w+(3+dx)] = GRID_HOLE; 659 } 660 break; 661 default /* case 2 */: 662 /* Remove a random piece one from the centre. */ 663 { 664 int dx, dy; 665 dx = random_upto(rs, 2) * 2 - 1; 666 if (random_upto(rs, 2)) 667 dy = 0; 668 else 669 dy = dx, dx = 0; 670 grid[(3+dy)*w+(3+dx)] = GRID_HOLE; 671 } 672 break; 673 } 674 } 675 } 676 677 /* 678 * Encode a game description which is simply a long list of P 679 * for peg, H for hole or O for obstacle. 680 */ 681 ret = snewn(w*h+1, char); 682 for (i = 0; i < w*h; i++) 683 ret[i] = (grid[i] == GRID_PEG ? 'P' : 684 grid[i] == GRID_HOLE ? 'H' : 'O'); 685 ret[w*h] = '\0'; 686 687 sfree(grid); 688 689 return ret; 690} 691 692static const char *validate_desc(const game_params *params, const char *desc) 693{ 694 int len, i, npeg = 0, nhole = 0; 695 696 len = params->w * params->h; 697 698 if (len != strlen(desc)) 699 return "Game description is wrong length"; 700 if (len != strspn(desc, "PHO")) 701 return "Invalid character in game description"; 702 for (i = 0; i < len; i++) { 703 npeg += desc[i] == 'P'; 704 nhole += desc[i] == 'H'; 705 } 706 /* The minimal soluble game has two pegs and a hole: "3x1:PPH". */ 707 if (npeg < 2) 708 return "Too few pegs in game description"; 709 if (nhole < 1) 710 return "Too few holes in game description"; 711 712 return NULL; 713} 714 715static game_state *new_game(midend *me, const game_params *params, 716 const char *desc) 717{ 718 int w = params->w, h = params->h; 719 game_state *state = snew(game_state); 720 int i; 721 722 state->w = w; 723 state->h = h; 724 state->completed = false; 725 state->grid = snewn(w*h, unsigned char); 726 for (i = 0; i < w*h; i++) 727 state->grid[i] = (desc[i] == 'P' ? GRID_PEG : 728 desc[i] == 'H' ? GRID_HOLE : GRID_OBST); 729 730 return state; 731} 732 733static game_state *dup_game(const game_state *state) 734{ 735 int w = state->w, h = state->h; 736 game_state *ret = snew(game_state); 737 738 ret->w = state->w; 739 ret->h = state->h; 740 ret->completed = state->completed; 741 ret->grid = snewn(w*h, unsigned char); 742 memcpy(ret->grid, state->grid, w*h); 743 744 return ret; 745} 746 747static void free_game(game_state *state) 748{ 749 sfree(state->grid); 750 sfree(state); 751} 752 753static bool game_can_format_as_text_now(const game_params *params) 754{ 755 return true; 756} 757 758static char *game_text_format(const game_state *state) 759{ 760 int w = state->w, h = state->h; 761 int x, y; 762 char *ret; 763 764 ret = snewn((w+1)*h + 1, char); 765 766 for (y = 0; y < h; y++) { 767 for (x = 0; x < w; x++) 768 ret[y*(w+1)+x] = (state->grid[y*w+x] == GRID_HOLE ? '-' : 769 state->grid[y*w+x] == GRID_PEG ? '*' : ' '); 770 ret[y*(w+1)+w] = '\n'; 771 } 772 ret[h*(w+1)] = '\0'; 773 774 return ret; 775} 776 777struct game_ui { 778 bool dragging; /* is a drag in progress? */ 779 int sx, sy; /* grid coords of drag start cell */ 780 int dx, dy; /* pixel coords of current drag posn */ 781 int cur_x, cur_y; 782 bool cur_visible, cur_jumping; 783}; 784 785static game_ui *new_ui(const game_state *state) 786{ 787 game_ui *ui = snew(game_ui); 788 int x, y, v; 789 790 ui->sx = ui->sy = ui->dx = ui->dy = 0; 791 ui->dragging = false; 792 ui->cur_visible = getenv_bool("PUZZLES_SHOW_CURSOR", false); 793 ui->cur_jumping = false; 794 795 /* make sure we start the cursor somewhere on the grid. */ 796 for (x = 0; x < state->w; x++) { 797 for (y = 0; y < state->h; y++) { 798 v = state->grid[y*state->w+x]; 799 if (v == GRID_PEG || v == GRID_HOLE) { 800 ui->cur_x = x; ui->cur_y = y; 801 goto found; 802 } 803 } 804 } 805 assert(!"new_ui found nowhere for cursor"); 806found: 807 808 return ui; 809} 810 811static void free_ui(game_ui *ui) 812{ 813 sfree(ui); 814} 815 816static void game_changed_state(game_ui *ui, const game_state *oldstate, 817 const game_state *newstate) 818{ 819 /* 820 * Cancel a drag, in case the source square has become 821 * unoccupied. 822 */ 823 ui->dragging = false; 824 825 /* 826 * Also, cancel a keyboard-driven jump if one is half way to being 827 * input. 828 */ 829 ui->cur_jumping = false; 830} 831 832static const char *current_key_label(const game_ui *ui, 833 const game_state *state, int button) 834{ 835 int w = state->w; 836 837 if (IS_CURSOR_SELECT(button)) { 838 if (!ui->cur_visible) return ""; 839 if (ui->cur_jumping) return "Cancel"; 840 if (state->grid[ui->cur_y*w+ui->cur_x] == GRID_PEG) return "Select"; 841 } 842 return ""; 843} 844 845#define PREFERRED_TILE_SIZE 33 846#define TILESIZE (ds->tilesize) 847#define BORDER (TILESIZE / 2) 848 849#define HIGHLIGHT_WIDTH (TILESIZE / 16) 850 851#define COORD(x) ( BORDER + (x) * TILESIZE ) 852#define FROMCOORD(x) ( ((x) + TILESIZE - BORDER) / TILESIZE - 1 ) 853 854struct game_drawstate { 855 int tilesize; 856 blitter *drag_background; 857 bool dragging; 858 int dragx, dragy; 859 int w, h; 860 unsigned char *grid; 861 bool started; 862 int bgcolour; 863}; 864 865static char *interpret_move(const game_state *state, game_ui *ui, 866 const game_drawstate *ds, 867 int x, int y, int button) 868{ 869 int w = state->w, h = state->h; 870 char buf[80]; 871 872 if (button == LEFT_BUTTON) { 873 int tx, ty; 874 875 /* 876 * Left button down: we attempt to start a drag. 877 */ 878 879 /* 880 * There certainly shouldn't be a current drag in progress, 881 * unless the midend failed to send us button events in 882 * order; it has a responsibility to always get that right, 883 * so we can legitimately punish it by failing an 884 * assertion. 885 */ 886 assert(!ui->dragging); 887 888 tx = FROMCOORD(x); 889 ty = FROMCOORD(y); 890 if (tx >= 0 && tx < w && ty >= 0 && ty < h) { 891 switch (state->grid[ty*w+tx]) { 892 case GRID_PEG: 893 ui->dragging = true; 894 ui->sx = tx; 895 ui->sy = ty; 896 ui->dx = x; 897 ui->dy = y; 898 ui->cur_visible = false; 899 ui->cur_jumping = false; 900 return MOVE_UI_UPDATE; 901 case GRID_HOLE: 902 return MOVE_NO_EFFECT; 903 case GRID_OBST: 904 default: 905 return MOVE_UNUSED; 906 } 907 } 908 } else if (button == LEFT_DRAG && ui->dragging) { 909 /* 910 * Mouse moved; just move the peg being dragged. 911 */ 912 ui->dx = x; 913 ui->dy = y; 914 return MOVE_UI_UPDATE; 915 } else if (button == LEFT_RELEASE && ui->dragging) { 916 int tx, ty, dx, dy; 917 918 /* 919 * Button released. Identify the target square of the drag, 920 * see if it represents a valid move, and if so make it. 921 */ 922 ui->dragging = false; /* cancel the drag no matter what */ 923 tx = FROMCOORD(x); 924 ty = FROMCOORD(y); 925 if (tx < 0 || tx >= w || ty < 0 || ty >= h) 926 return MOVE_UI_UPDATE; /* target out of range */ 927 dx = tx - ui->sx; 928 dy = ty - ui->sy; 929 if (max(abs(dx),abs(dy)) != 2 || min(abs(dx),abs(dy)) != 0) 930 return MOVE_UI_UPDATE; /* move length was wrong */ 931 dx /= 2; 932 dy /= 2; 933 934 if (state->grid[ty*w+tx] != GRID_HOLE || 935 state->grid[(ty-dy)*w+(tx-dx)] != GRID_PEG || 936 state->grid[ui->sy*w+ui->sx] != GRID_PEG) 937 return MOVE_UI_UPDATE; /* grid contents were invalid */ 938 939 /* 940 * We have a valid move. Encode it simply as source and 941 * destination coordinate pairs. 942 */ 943 sprintf(buf, "%d,%d-%d,%d", ui->sx, ui->sy, tx, ty); 944 return dupstr(buf); 945 } else if (IS_CURSOR_MOVE(button)) { 946 if (!ui->cur_jumping) { 947 /* Not jumping; move cursor as usual, making sure we don't 948 * leave the gameboard (which may be an irregular shape) */ 949 int cx = ui->cur_x, cy = ui->cur_y; 950 move_cursor(button, &cx, &cy, w, h, false, NULL); 951 ui->cur_visible = true; 952 if (state->grid[cy*w+cx] == GRID_HOLE || 953 state->grid[cy*w+cx] == GRID_PEG) { 954 ui->cur_x = cx; 955 ui->cur_y = cy; 956 } 957 return MOVE_UI_UPDATE; 958 } else { 959 int dx, dy, mx, my, jx, jy; 960 961 /* We're jumping; if the requested direction has a hole, and 962 * there's a peg in the way, */ 963 assert(state->grid[ui->cur_y*w+ui->cur_x] == GRID_PEG); 964 dx = (button == CURSOR_RIGHT) ? 1 : (button == CURSOR_LEFT) ? -1 : 0; 965 dy = (button == CURSOR_DOWN) ? 1 : (button == CURSOR_UP) ? -1 : 0; 966 967 mx = ui->cur_x+dx; my = ui->cur_y+dy; 968 jx = mx+dx; jy = my+dy; 969 970 ui->cur_jumping = false; /* reset, whatever. */ 971 if (jx >= 0 && jy >= 0 && jx < w && jy < h && 972 state->grid[my*w+mx] == GRID_PEG && 973 state->grid[jy*w+jx] == GRID_HOLE) { 974 /* Move cursor to the jumped-to location (this felt more 975 * natural while playtesting) */ 976 sprintf(buf, "%d,%d-%d,%d", ui->cur_x, ui->cur_y, jx, jy); 977 ui->cur_x = jx; ui->cur_y = jy; 978 return dupstr(buf); 979 } 980 return MOVE_UI_UPDATE; 981 } 982 } else if (IS_CURSOR_SELECT(button)) { 983 if (!ui->cur_visible) { 984 ui->cur_visible = true; 985 return MOVE_UI_UPDATE; 986 } 987 if (ui->cur_jumping) { 988 ui->cur_jumping = false; 989 return MOVE_UI_UPDATE; 990 } 991 if (state->grid[ui->cur_y*w+ui->cur_x] == GRID_PEG) { 992 /* cursor is on peg: next arrow-move will jump. */ 993 ui->cur_jumping = true; 994 return MOVE_UI_UPDATE; 995 } 996 return MOVE_NO_EFFECT; 997 } 998 999 return MOVE_UNUSED; 1000} 1001 1002static game_state *execute_move(const game_state *state, const char *move) 1003{ 1004 int w = state->w, h = state->h; 1005 int sx, sy, tx, ty; 1006 game_state *ret; 1007 1008 if (sscanf(move, "%d,%d-%d,%d", &sx, &sy, &tx, &ty) == 4) { 1009 int mx, my, dx, dy; 1010 1011 if (sx < 0 || sx >= w || sy < 0 || sy >= h) 1012 return NULL; /* source out of range */ 1013 if (tx < 0 || tx >= w || ty < 0 || ty >= h) 1014 return NULL; /* target out of range */ 1015 1016 dx = tx - sx; 1017 dy = ty - sy; 1018 if (max(abs(dx),abs(dy)) != 2 || min(abs(dx),abs(dy)) != 0) 1019 return NULL; /* move length was wrong */ 1020 mx = sx + dx/2; 1021 my = sy + dy/2; 1022 1023 if (state->grid[sy*w+sx] != GRID_PEG || 1024 state->grid[my*w+mx] != GRID_PEG || 1025 state->grid[ty*w+tx] != GRID_HOLE) 1026 return NULL; /* grid contents were invalid */ 1027 1028 ret = dup_game(state); 1029 ret->grid[sy*w+sx] = GRID_HOLE; 1030 ret->grid[my*w+mx] = GRID_HOLE; 1031 ret->grid[ty*w+tx] = GRID_PEG; 1032 1033 /* 1034 * Opinion varies on whether getting to a single peg counts as 1035 * completing the game, or whether that peg has to be at a 1036 * specific location (central in the classic cross game, for 1037 * instance). For now we take the former, rather lax position. 1038 */ 1039 if (!ret->completed) { 1040 int count = 0, i; 1041 for (i = 0; i < w*h; i++) 1042 if (ret->grid[i] == GRID_PEG) 1043 count++; 1044 if (count == 1) 1045 ret->completed = true; 1046 } 1047 1048 return ret; 1049 } 1050 return NULL; 1051} 1052 1053/* ---------------------------------------------------------------------- 1054 * Drawing routines. 1055 */ 1056 1057static void game_compute_size(const game_params *params, int tilesize, 1058 const game_ui *ui, int *x, int *y) 1059{ 1060 /* Ick: fake up `ds->tilesize' for macro expansion purposes */ 1061 struct { int tilesize; } ads, *ds = &ads; 1062 ads.tilesize = tilesize; 1063 1064 *x = TILESIZE * params->w + 2 * BORDER; 1065 *y = TILESIZE * params->h + 2 * BORDER; 1066} 1067 1068static void game_set_size(drawing *dr, game_drawstate *ds, 1069 const game_params *params, int tilesize) 1070{ 1071 ds->tilesize = tilesize; 1072 1073 assert(TILESIZE > 0); 1074 1075 assert(!ds->drag_background); /* set_size is never called twice */ 1076 ds->drag_background = blitter_new(dr, TILESIZE, TILESIZE); 1077} 1078 1079static float *game_colours(frontend *fe, int *ncolours) 1080{ 1081 float *ret = snewn(3 * NCOLOURS, float); 1082 1083 game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT); 1084 1085 ret[COL_PEG * 3 + 0] = 0.0F; 1086 ret[COL_PEG * 3 + 1] = 0.0F; 1087 ret[COL_PEG * 3 + 2] = 1.0F; 1088 1089 ret[COL_CURSOR * 3 + 0] = 0.5F; 1090 ret[COL_CURSOR * 3 + 1] = 0.5F; 1091 ret[COL_CURSOR * 3 + 2] = 1.0F; 1092 1093 *ncolours = NCOLOURS; 1094 return ret; 1095} 1096 1097static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) 1098{ 1099 int w = state->w, h = state->h; 1100 struct game_drawstate *ds = snew(struct game_drawstate); 1101 1102 ds->tilesize = 0; /* not decided yet */ 1103 1104 /* We can't allocate the blitter rectangle for the drag background 1105 * until we know what size to make it. */ 1106 ds->drag_background = NULL; 1107 ds->dragging = false; 1108 1109 ds->w = w; 1110 ds->h = h; 1111 ds->grid = snewn(w*h, unsigned char); 1112 memset(ds->grid, 255, w*h); 1113 1114 ds->started = false; 1115 ds->bgcolour = -1; 1116 1117 return ds; 1118} 1119 1120static void game_free_drawstate(drawing *dr, game_drawstate *ds) 1121{ 1122 if (ds->drag_background) 1123 blitter_free(dr, ds->drag_background); 1124 sfree(ds->grid); 1125 sfree(ds); 1126} 1127 1128static void draw_tile(drawing *dr, game_drawstate *ds, 1129 int x, int y, int v, int bgcolour) 1130{ 1131 bool cursor = false, jumping = false; 1132 int bg; 1133 1134 if (bgcolour >= 0) { 1135 draw_rect(dr, x, y, TILESIZE, TILESIZE, bgcolour); 1136 } 1137 if (v >= GRID_JUMPING) { 1138 jumping = true; v -= GRID_JUMPING; 1139 } 1140 if (v >= GRID_CURSOR) { 1141 cursor = true; v -= GRID_CURSOR; 1142 } 1143 1144 if (v == GRID_HOLE) { 1145 bg = cursor ? COL_HIGHLIGHT : COL_LOWLIGHT; 1146 assert(!jumping); /* can't jump from a hole! */ 1147 draw_circle(dr, x+TILESIZE/2, y+TILESIZE/2, TILESIZE/4, 1148 bg, bg); 1149 } else if (v == GRID_PEG) { 1150 bg = (cursor || jumping) ? COL_CURSOR : COL_PEG; 1151 draw_circle(dr, x+TILESIZE/2, y+TILESIZE/2, TILESIZE/3, 1152 bg, bg); 1153 bg = (!cursor || jumping) ? COL_PEG : COL_CURSOR; 1154 draw_circle(dr, x+TILESIZE/2, y+TILESIZE/2, TILESIZE/4, 1155 bg, bg); 1156 } 1157 1158 draw_update(dr, x, y, TILESIZE, TILESIZE); 1159} 1160 1161static void game_redraw(drawing *dr, game_drawstate *ds, 1162 const game_state *oldstate, const game_state *state, 1163 int dir, const game_ui *ui, 1164 float animtime, float flashtime) 1165{ 1166 int w = state->w, h = state->h; 1167 int x, y; 1168 int bgcolour; 1169 1170 if (flashtime > 0) { 1171 int frame = (int)(flashtime / FLASH_FRAME); 1172 bgcolour = (frame % 2 ? COL_LOWLIGHT : COL_HIGHLIGHT); 1173 } else 1174 bgcolour = COL_BACKGROUND; 1175 1176 /* 1177 * Erase the sprite currently being dragged, if any. 1178 */ 1179 if (ds->dragging) { 1180 assert(ds->drag_background); 1181 blitter_load(dr, ds->drag_background, ds->dragx, ds->dragy); 1182 draw_update(dr, ds->dragx, ds->dragy, TILESIZE, TILESIZE); 1183 ds->dragging = false; 1184 } 1185 1186 if (!ds->started) { 1187 /* 1188 * Draw relief marks around all the squares that aren't 1189 * GRID_OBST. 1190 */ 1191 for (y = 0; y < h; y++) 1192 for (x = 0; x < w; x++) 1193 if (state->grid[y*w+x] != GRID_OBST) { 1194 /* 1195 * First pass: draw the full relief square. 1196 */ 1197 int coords[6]; 1198 coords[0] = COORD(x+1) + HIGHLIGHT_WIDTH - 1; 1199 coords[1] = COORD(y) - HIGHLIGHT_WIDTH; 1200 coords[2] = COORD(x) - HIGHLIGHT_WIDTH; 1201 coords[3] = COORD(y+1) + HIGHLIGHT_WIDTH - 1; 1202 coords[4] = COORD(x) - HIGHLIGHT_WIDTH; 1203 coords[5] = COORD(y) - HIGHLIGHT_WIDTH; 1204 draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT); 1205 coords[4] = COORD(x+1) + HIGHLIGHT_WIDTH - 1; 1206 coords[5] = COORD(y+1) + HIGHLIGHT_WIDTH - 1; 1207 draw_polygon(dr, coords, 3, COL_LOWLIGHT, COL_LOWLIGHT); 1208 } 1209 for (y = 0; y < h; y++) 1210 for (x = 0; x < w; x++) 1211 if (state->grid[y*w+x] != GRID_OBST) { 1212 /* 1213 * Second pass: draw everything but the two 1214 * diagonal corners. 1215 */ 1216 draw_rect(dr, COORD(x) - HIGHLIGHT_WIDTH, 1217 COORD(y) - HIGHLIGHT_WIDTH, 1218 TILESIZE + HIGHLIGHT_WIDTH, 1219 TILESIZE + HIGHLIGHT_WIDTH, COL_HIGHLIGHT); 1220 draw_rect(dr, COORD(x), 1221 COORD(y), 1222 TILESIZE + HIGHLIGHT_WIDTH, 1223 TILESIZE + HIGHLIGHT_WIDTH, COL_LOWLIGHT); 1224 } 1225 for (y = 0; y < h; y++) 1226 for (x = 0; x < w; x++) 1227 if (state->grid[y*w+x] != GRID_OBST) { 1228 /* 1229 * Third pass: draw a trapezium on each edge. 1230 */ 1231 int coords[8]; 1232 int dx, dy, s, sn, c; 1233 1234 for (dx = 0; dx < 2; dx++) { 1235 dy = 1 - dx; 1236 for (s = 0; s < 2; s++) { 1237 sn = 2*s - 1; 1238 c = s ? COL_LOWLIGHT : COL_HIGHLIGHT; 1239 1240 coords[0] = COORD(x) + (s*dx)*(TILESIZE-1); 1241 coords[1] = COORD(y) + (s*dy)*(TILESIZE-1); 1242 coords[2] = COORD(x) + (s*dx+dy)*(TILESIZE-1); 1243 coords[3] = COORD(y) + (s*dy+dx)*(TILESIZE-1); 1244 coords[4] = coords[2] - HIGHLIGHT_WIDTH * (dy-sn*dx); 1245 coords[5] = coords[3] - HIGHLIGHT_WIDTH * (dx-sn*dy); 1246 coords[6] = coords[0] + HIGHLIGHT_WIDTH * (dy+sn*dx); 1247 coords[7] = coords[1] + HIGHLIGHT_WIDTH * (dx+sn*dy); 1248 draw_polygon(dr, coords, 4, c, c); 1249 } 1250 } 1251 } 1252 for (y = 0; y < h; y++) 1253 for (x = 0; x < w; x++) 1254 if (state->grid[y*w+x] != GRID_OBST) { 1255 /* 1256 * Second pass: draw everything but the two 1257 * diagonal corners. 1258 */ 1259 draw_rect(dr, COORD(x), 1260 COORD(y), 1261 TILESIZE, 1262 TILESIZE, COL_BACKGROUND); 1263 } 1264 1265 ds->started = true; 1266 1267 draw_update(dr, 0, 0, 1268 TILESIZE * state->w + 2 * BORDER, 1269 TILESIZE * state->h + 2 * BORDER); 1270 } 1271 1272 /* 1273 * Loop over the grid redrawing anything that looks as if it 1274 * needs it. 1275 */ 1276 for (y = 0; y < h; y++) 1277 for (x = 0; x < w; x++) { 1278 int v; 1279 1280 v = state->grid[y*w+x]; 1281 /* 1282 * Blank the source of a drag so it looks as if the 1283 * user picked the peg up physically. 1284 */ 1285 if (ui->dragging && ui->sx == x && ui->sy == y && v == GRID_PEG) 1286 v = GRID_HOLE; 1287 1288 if (ui->cur_visible && ui->cur_x == x && ui->cur_y == y) 1289 v += ui->cur_jumping ? GRID_JUMPING : GRID_CURSOR; 1290 1291 if (v != GRID_OBST && 1292 (bgcolour != ds->bgcolour || /* always redraw when flashing */ 1293 v != ds->grid[y*w+x])) { 1294 draw_tile(dr, ds, COORD(x), COORD(y), v, bgcolour); 1295 ds->grid[y*w+x] = v; 1296 } 1297 } 1298 1299 /* 1300 * Draw the dragging sprite if any. 1301 */ 1302 if (ui->dragging) { 1303 ds->dragging = true; 1304 ds->dragx = ui->dx - TILESIZE/2; 1305 ds->dragy = ui->dy - TILESIZE/2; 1306 blitter_save(dr, ds->drag_background, ds->dragx, ds->dragy); 1307 draw_tile(dr, ds, ds->dragx, ds->dragy, GRID_PEG, -1); 1308 } 1309 1310 ds->bgcolour = bgcolour; 1311} 1312 1313static float game_anim_length(const game_state *oldstate, 1314 const game_state *newstate, int dir, game_ui *ui) 1315{ 1316 return 0.0F; 1317} 1318 1319static float game_flash_length(const game_state *oldstate, 1320 const game_state *newstate, int dir, game_ui *ui) 1321{ 1322 if (!oldstate->completed && newstate->completed) 1323 return 2 * FLASH_FRAME; 1324 else 1325 return 0.0F; 1326} 1327 1328static void game_get_cursor_location(const game_ui *ui, 1329 const game_drawstate *ds, 1330 const game_state *state, 1331 const game_params *params, 1332 int *x, int *y, int *w, int *h) 1333{ 1334 if(ui->cur_visible) { 1335 *x = COORD(ui->cur_x); 1336 *y = COORD(ui->cur_y); 1337 *w = *h = TILESIZE; 1338 } 1339} 1340 1341static int game_status(const game_state *state) 1342{ 1343 /* 1344 * Dead-end situations are assumed to be rescuable by Undo, so we 1345 * don't bother to identify them and return -1. 1346 */ 1347 return state->completed ? +1 : 0; 1348} 1349 1350#ifdef COMBINED 1351#define thegame pegs 1352#endif 1353 1354const struct game thegame = { 1355 "Pegs", "games.pegs", "pegs", 1356 default_params, 1357 game_fetch_preset, NULL, 1358 decode_params, 1359 encode_params, 1360 free_params, 1361 dup_params, 1362 true, game_configure, custom_params, 1363 validate_params, 1364 new_game_desc, 1365 validate_desc, 1366 new_game, 1367 dup_game, 1368 free_game, 1369 false, NULL, /* solve */ 1370 true, game_can_format_as_text_now, game_text_format, 1371 NULL, NULL, /* get_prefs, set_prefs */ 1372 new_ui, 1373 free_ui, 1374 NULL, /* encode_ui */ 1375 NULL, /* decode_ui */ 1376 NULL, /* game_request_keys */ 1377 game_changed_state, 1378 current_key_label, 1379 interpret_move, 1380 execute_move, 1381 PREFERRED_TILE_SIZE, game_compute_size, game_set_size, 1382 game_colours, 1383 game_new_drawstate, 1384 game_free_drawstate, 1385 game_redraw, 1386 game_anim_length, 1387 game_flash_length, 1388 game_get_cursor_location, 1389 game_status, 1390 false, false, NULL, NULL, /* print_size, print */ 1391 false, /* wants_statusbar */ 1392 false, NULL, /* timing_state */ 1393 0, /* flags */ 1394}; 1395 1396/* vim: set shiftwidth=4 tabstop=8: */