A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 1685 lines 50 kB view raw
1/* 2 * 'same game' -- try to remove all the coloured squares by 3 * selecting regions of contiguous colours. 4 */ 5 6/* 7 * TODO on grid generation: 8 * 9 * - Generation speed could still be improved. 10 * * 15x10c3 is the only really difficult one of the existing 11 * presets. The others are all either small enough, or have 12 * the great flexibility given by four colours, that they 13 * don't take long at all. 14 * * I still suspect many problems arise from separate 15 * subareas. I wonder if we can also somehow prioritise left- 16 * or rightmost insertions so as to avoid area splitting at 17 * all where feasible? It's not easy, though, because the 18 * current shuffle-then-try-all-options approach to move 19 * choice doesn't leave room for `soft' probabilistic 20 * prioritisation: we either try all class A moves before any 21 * class B ones, or we don't. 22 * 23 * - The current generation algorithm inserts exactly two squares 24 * at a time, with a single exception at the beginning of 25 * generation for grids of odd overall size. An obvious 26 * extension would be to permit larger inverse moves during 27 * generation. 28 * * this might reduce the number of failed generations by 29 * making the insertion algorithm more flexible 30 * * on the other hand, it would be significantly more complex 31 * * if I do this I'll need to take out the odd-subarea 32 * avoidance 33 * * a nice feature of the current algorithm is that the 34 * computer's `intended' solution always receives the minimum 35 * possible score, so that pretty much the player's entire 36 * score represents how much better they did than the 37 * computer. 38 * 39 * - Is it possible we can _temporarily_ tolerate neighbouring 40 * squares of the same colour, until we've finished setting up 41 * our inverse move? 42 * * or perhaps even not choose the colour of our inserted 43 * region until we have finished placing it, and _then_ look 44 * at what colours border on it? 45 * * I don't think this is currently meaningful unless we're 46 * placing more than a domino at a time. 47 * 48 * - possibly write out a full solution so that Solve can somehow 49 * show it step by step? 50 * * aux_info would have to encode the click points 51 * * solve_game() would have to encode not only those click 52 * points but also give a move string which reconstructed the 53 * initial state 54 * * the game_state would include a pointer to a solution move 55 * list, plus an index into that list 56 * * game_changed_state would auto-select the next move if 57 * handed a new state which had a solution move list active 58 * * execute_move, if passed such a state as input, would check 59 * to see whether the move being made was the same as the one 60 * stated by the solution, and if so would advance the move 61 * index. Failing that it would return a game_state without a 62 * solution move list active at all. 63 */ 64 65#include <stdio.h> 66#include <stdlib.h> 67#include <string.h> 68#include <assert.h> 69#include <ctype.h> 70#include <limits.h> 71#ifdef NO_TGMATH_H 72# include <math.h> 73#else 74# include <tgmath.h> 75#endif 76 77#include "puzzles.h" 78 79#define TILE_INNER (ds->tileinner) 80#define TILE_GAP (ds->tilegap) 81#define TILE_SIZE (TILE_INNER + TILE_GAP) 82#define PREFERRED_TILE_SIZE 32 83#define BORDER (TILE_SIZE / 2) 84#define HIGHLIGHT_WIDTH 2 85 86#define FLASH_FRAME 0.13F 87 88#define COORD(x) ( (x) * TILE_SIZE + BORDER ) 89#define FROMCOORD(x) ( ((x) - BORDER + TILE_SIZE) / TILE_SIZE - 1 ) 90 91#define X(state, i) ( (i) % (state)->params.w ) 92#define Y(state, i) ( (i) / (state)->params.w ) 93#define C(state, x, y) ( (y) * (state)->w + (x) ) 94 95enum { 96 COL_BACKGROUND, 97 COL_1, COL_2, COL_3, COL_4, COL_5, COL_6, COL_7, COL_8, COL_9, 98 COL_IMPOSSIBLE, COL_SEL, COL_HIGHLIGHT, COL_LOWLIGHT, 99 NCOLOURS 100}; 101 102/* scoresub is 1 or 2 (for (n-1)^2 or (n-2)^2) */ 103struct game_params { 104 int w, h, ncols, scoresub; 105 bool soluble; /* choose generation algorithm */ 106}; 107 108/* These flags must be unique across all uses; in the game_state, 109 * the game_ui, and the drawstate (as they all get combined in the 110 * drawstate). */ 111#define TILE_COLMASK 0x00ff 112#define TILE_SELECTED 0x0100 /* used in ui and drawstate */ 113#define TILE_JOINRIGHT 0x0200 /* used in drawstate */ 114#define TILE_JOINDOWN 0x0400 /* used in drawstate */ 115#define TILE_JOINDIAG 0x0800 /* used in drawstate */ 116#define TILE_HASSEL 0x1000 /* used in drawstate */ 117#define TILE_IMPOSSIBLE 0x2000 /* used in drawstate */ 118 119#define TILE(gs,x,y) ((gs)->tiles[(gs)->params.w*(y)+(x)]) 120#define COL(gs,x,y) (TILE(gs,x,y) & TILE_COLMASK) 121#define ISSEL(gs,x,y) (TILE(gs,x,y) & TILE_SELECTED) 122 123#define SWAPTILE(gs,x1,y1,x2,y2) do { \ 124 int t = TILE(gs,x1,y1); \ 125 TILE(gs,x1,y1) = TILE(gs,x2,y2); \ 126 TILE(gs,x2,y2) = t; \ 127} while (0) 128 129static int npoints(const game_params *params, int nsel) 130{ 131 int sdiff = nsel - params->scoresub; 132 return (sdiff > 0) ? sdiff * sdiff : 0; 133} 134 135struct game_state { 136 struct game_params params; 137 int n; 138 int *tiles; /* colour only */ 139 int score; 140 bool complete, impossible; 141}; 142 143static game_params *default_params(void) 144{ 145 game_params *ret = snew(game_params); 146 ret->w = 5; 147 ret->h = 5; 148 ret->ncols = 3; 149 ret->scoresub = 2; 150 ret->soluble = true; 151 return ret; 152} 153 154static const struct game_params samegame_presets[] = { 155 { 5, 5, 3, 2, true }, 156 { 10, 5, 3, 2, true }, 157#ifdef SLOW_SYSTEM 158 { 10, 10, 3, 2, true }, 159#else 160 { 15, 10, 3, 2, true }, 161#endif 162 { 15, 10, 4, 2, true }, 163 { 20, 15, 4, 2, true } 164}; 165 166static bool game_fetch_preset(int i, char **name, game_params **params) 167{ 168 game_params *ret; 169 char str[80]; 170 171 if (i < 0 || i >= lenof(samegame_presets)) 172 return false; 173 174 ret = snew(game_params); 175 *ret = samegame_presets[i]; 176 177 sprintf(str, "%dx%d, %d colours", ret->w, ret->h, ret->ncols); 178 179 *name = dupstr(str); 180 *params = ret; 181 return true; 182} 183 184static void free_params(game_params *params) 185{ 186 sfree(params); 187} 188 189static game_params *dup_params(const game_params *params) 190{ 191 game_params *ret = snew(game_params); 192 *ret = *params; /* structure copy */ 193 return ret; 194} 195 196static void decode_params(game_params *params, char const *string) 197{ 198 char const *p = string; 199 200 params->w = atoi(p); 201 while (*p && isdigit((unsigned char)*p)) p++; 202 if (*p == 'x') { 203 p++; 204 params->h = atoi(p); 205 while (*p && isdigit((unsigned char)*p)) p++; 206 } else { 207 params->h = params->w; 208 } 209 if (*p == 'c') { 210 p++; 211 params->ncols = atoi(p); 212 while (*p && isdigit((unsigned char)*p)) p++; 213 } else { 214 params->ncols = 3; 215 } 216 if (*p == 's') { 217 p++; 218 params->scoresub = atoi(p); 219 while (*p && isdigit((unsigned char)*p)) p++; 220 } else { 221 params->scoresub = 2; 222 } 223 if (*p == 'r') { 224 p++; 225 params->soluble = false; 226 } 227} 228 229static char *encode_params(const game_params *params, bool full) 230{ 231 char ret[80]; 232 233 sprintf(ret, "%dx%dc%ds%d%s", 234 params->w, params->h, params->ncols, params->scoresub, 235 full && !params->soluble ? "r" : ""); 236 return dupstr(ret); 237} 238 239static config_item *game_configure(const game_params *params) 240{ 241 config_item *ret; 242 char buf[80]; 243 244 ret = snewn(6, config_item); 245 246 ret[0].name = "Width"; 247 ret[0].type = C_STRING; 248 sprintf(buf, "%d", params->w); 249 ret[0].u.string.sval = dupstr(buf); 250 251 ret[1].name = "Height"; 252 ret[1].type = C_STRING; 253 sprintf(buf, "%d", params->h); 254 ret[1].u.string.sval = dupstr(buf); 255 256 ret[2].name = "No. of colours"; 257 ret[2].type = C_STRING; 258 sprintf(buf, "%d", params->ncols); 259 ret[2].u.string.sval = dupstr(buf); 260 261 ret[3].name = "Scoring system"; 262 ret[3].type = C_CHOICES; 263 ret[3].u.choices.choicenames = ":(n-1)^2:(n-2)^2"; 264 ret[3].u.choices.selected = params->scoresub-1; 265 266 ret[4].name = "Ensure solubility"; 267 ret[4].type = C_BOOLEAN; 268 ret[4].u.boolean.bval = params->soluble; 269 270 ret[5].name = NULL; 271 ret[5].type = C_END; 272 273 return ret; 274} 275 276static game_params *custom_params(const config_item *cfg) 277{ 278 game_params *ret = snew(game_params); 279 280 ret->w = atoi(cfg[0].u.string.sval); 281 ret->h = atoi(cfg[1].u.string.sval); 282 ret->ncols = atoi(cfg[2].u.string.sval); 283 ret->scoresub = cfg[3].u.choices.selected + 1; 284 ret->soluble = cfg[4].u.boolean.bval; 285 286 return ret; 287} 288 289static const char *validate_params(const game_params *params, bool full) 290{ 291 if (params->w < 1 || params->h < 1) 292 return "Width and height must both be positive"; 293 if (params->w > INT_MAX / params->h) 294 return "Width times height must not be unreasonably large"; 295 296 if (params->ncols > 9) 297 return "Maximum of 9 colours"; 298 299 if (params->soluble) { 300 if (params->ncols < 3) 301 return "Number of colours must be at least three"; 302 if (params->w * params->h <= 1) 303 return "Grid area must be greater than 1"; 304 } else { 305 if (params->ncols < 2) 306 return "Number of colours must be at least three"; 307 /* ...and we must make sure we can generate at least 2 squares 308 * of each colour so it's theoretically soluble. */ 309 if ((params->w * params->h) < (params->ncols * 2)) 310 return "Too many colours makes given grid size impossible"; 311 } 312 313 if ((params->scoresub < 1) || (params->scoresub > 2)) 314 return "Scoring system not recognised"; 315 316 return NULL; 317} 318 319/* 320 * Guaranteed-soluble grid generator. 321 */ 322static void gen_grid(int w, int h, int nc, int *grid, random_state *rs) 323{ 324 int wh = w*h, tc = nc+1; 325 int i, j, k, c, x, y, pos, n; 326 int *list, *grid2; 327 bool ok; 328 int failures = 0; 329 330 /* 331 * We'll use `list' to track the possible places to put our 332 * next insertion. There are up to h places to insert in each 333 * column: in a column of height n there are n+1 places because 334 * we can insert at the very bottom or the very top, but a 335 * column of height h can't have anything at all inserted in it 336 * so we have up to h in each column. Likewise, with n columns 337 * present there are n+1 places to fit a new one in between but 338 * we can't insert a column if there are already w; so there 339 * are a maximum of w new columns too. Total is wh + w. 340 */ 341 list = snewn(wh + w, int); 342 grid2 = snewn(wh, int); 343 344 do { 345 /* 346 * Start with two or three squares - depending on parity of w*h 347 * - of a random colour. 348 */ 349 for (i = 0; i < wh; i++) 350 grid[i] = 0; 351 j = 2 + (wh % 2); 352 c = 1 + random_upto(rs, nc); 353 if (j <= w) { 354 for (i = 0; i < j; i++) 355 grid[(h-1)*w+i] = c; 356 } else { 357 assert(j <= h); 358 for (i = 0; i < j; i++) 359 grid[(h-1-i)*w] = c; 360 } 361 362 /* 363 * Now repeatedly insert a two-square blob in the grid, of 364 * whatever colour will go at the position we chose. 365 */ 366 while (1) { 367 n = 0; 368 369 /* 370 * Build up a list of insertion points. Each point is 371 * encoded as y*w+x; insertion points between columns are 372 * encoded as h*w+x. 373 */ 374 375 if (grid[wh - 1] == 0) { 376 /* 377 * The final column is empty, so we can insert new 378 * columns. 379 */ 380 for (i = 0; i < w; i++) { 381 list[n++] = wh + i; 382 if (grid[(h-1)*w + i] == 0) 383 break; 384 } 385 } 386 387 /* 388 * Now look for places to insert within columns. 389 */ 390 for (i = 0; i < w; i++) { 391 if (grid[(h-1)*w+i] == 0) 392 break; /* no more columns */ 393 394 if (grid[i] != 0) 395 continue; /* this column is full */ 396 397 for (j = h; j-- > 0 ;) { 398 list[n++] = j*w+i; 399 if (grid[j*w+i] == 0) 400 break; /* this column is exhausted */ 401 } 402 } 403 404 if (n == 0) 405 break; /* we're done */ 406 407#ifdef GENERATION_DIAGNOSTICS 408 printf("initial grid:\n"); 409 { 410 int x,y; 411 for (y = 0; y < h; y++) { 412 for (x = 0; x < w; x++) { 413 if (grid[y*w+x] == 0) 414 printf("-"); 415 else 416 printf("%d", grid[y*w+x]); 417 } 418 printf("\n"); 419 } 420 } 421#endif 422 423 /* 424 * Now go through the list one element at a time in 425 * random order, and actually attempt to insert 426 * something there. 427 */ 428 while (n-- > 0) { 429 int dirs[4], ndirs, dir; 430 431 i = random_upto(rs, n+1); 432 pos = list[i]; 433 list[i] = list[n]; 434 435 x = pos % w; 436 y = pos / w; 437 438 memcpy(grid2, grid, wh * sizeof(int)); 439 440 if (y == h) { 441 /* 442 * Insert a column at position x. 443 */ 444 for (i = w-1; i > x; i--) 445 for (j = 0; j < h; j++) 446 grid2[j*w+i] = grid2[j*w+(i-1)]; 447 /* 448 * Clear the new column. 449 */ 450 for (j = 0; j < h; j++) 451 grid2[j*w+x] = 0; 452 /* 453 * Decrement y so that our first square is actually 454 * inserted _in_ the grid rather than just below it. 455 */ 456 y--; 457 } 458 459 /* 460 * Insert a square within column x at position y. 461 */ 462 for (i = 0; i+1 <= y; i++) 463 grid2[i*w+x] = grid2[(i+1)*w+x]; 464 465#ifdef GENERATION_DIAGNOSTICS 466 printf("trying at n=%d (%d,%d)\n", n, x, y); 467 grid2[y*w+x] = tc; 468 { 469 int x,y; 470 for (y = 0; y < h; y++) { 471 for (x = 0; x < w; x++) { 472 if (grid2[y*w+x] == 0) 473 printf("-"); 474 else if (grid2[y*w+x] <= nc) 475 printf("%d", grid2[y*w+x]); 476 else 477 printf("*"); 478 } 479 printf("\n"); 480 } 481 } 482#endif 483 484 /* 485 * Pick our square colour so that it doesn't match any 486 * of its neighbours. 487 */ 488 { 489 int wrongcol[4], nwrong = 0; 490 491 /* 492 * List the neighbouring colours. 493 */ 494 if (x > 0) 495 wrongcol[nwrong++] = grid2[y*w+(x-1)]; 496 if (x+1 < w) 497 wrongcol[nwrong++] = grid2[y*w+(x+1)]; 498 if (y > 0) 499 wrongcol[nwrong++] = grid2[(y-1)*w+x]; 500 if (y+1 < h) 501 wrongcol[nwrong++] = grid2[(y+1)*w+x]; 502 503 /* 504 * Eliminate duplicates. We can afford a shoddy 505 * algorithm here because the problem size is 506 * bounded. 507 */ 508 for (i = j = 0 ;; i++) { 509 int pos = -1, min = 0; 510 if (j > 0) 511 min = wrongcol[j-1]; 512 for (k = i; k < nwrong; k++) 513 if (wrongcol[k] > min && 514 (pos == -1 || wrongcol[k] < wrongcol[pos])) 515 pos = k; 516 if (pos >= 0) { 517 int v = wrongcol[pos]; 518 wrongcol[pos] = wrongcol[j]; 519 wrongcol[j++] = v; 520 } else 521 break; 522 } 523 nwrong = j; 524 525 /* 526 * If no colour will go here, stop trying. 527 */ 528 if (nwrong == nc) 529 continue; 530 531 /* 532 * Otherwise, pick a colour from the remaining 533 * ones. 534 */ 535 c = 1 + random_upto(rs, nc - nwrong); 536 for (i = 0; i < nwrong; i++) { 537 if (c >= wrongcol[i]) 538 c++; 539 else 540 break; 541 } 542 } 543 544 /* 545 * Place the new square. 546 * 547 * Although I've _chosen_ the new region's colour 548 * (so that we can check adjacency), I'm going to 549 * actually place it as an invalid colour (tc) 550 * until I'm sure it's viable. This is so that I 551 * can conveniently check that I really have made a 552 * _valid_ inverse move later on. 553 */ 554#ifdef GENERATION_DIAGNOSTICS 555 printf("picked colour %d\n", c); 556#endif 557 grid2[y*w+x] = tc; 558 559 /* 560 * Now attempt to extend it in one of three ways: left, 561 * right or up. 562 */ 563 ndirs = 0; 564 if (x > 0 && 565 grid2[y*w+(x-1)] != c && 566 grid2[x-1] == 0 && 567 (y+1 >= h || grid2[(y+1)*w+(x-1)] != c) && 568 (y+1 >= h || grid2[(y+1)*w+(x-1)] != 0) && 569 (x <= 1 || grid2[y*w+(x-2)] != c)) 570 dirs[ndirs++] = -1; /* left */ 571 if (x+1 < w && 572 grid2[y*w+(x+1)] != c && 573 grid2[x+1] == 0 && 574 (y+1 >= h || grid2[(y+1)*w+(x+1)] != c) && 575 (y+1 >= h || grid2[(y+1)*w+(x+1)] != 0) && 576 (x+2 >= w || grid2[y*w+(x+2)] != c)) 577 dirs[ndirs++] = +1; /* right */ 578 if (y > 0 && 579 grid2[x] == 0 && 580 (x <= 0 || grid2[(y-1)*w+(x-1)] != c) && 581 (x+1 >= w || grid2[(y-1)*w+(x+1)] != c)) { 582 /* 583 * We add this possibility _twice_, so that the 584 * probability of placing a vertical domino is 585 * about the same as that of a horizontal. This 586 * should yield less bias in the generated 587 * grids. 588 */ 589 dirs[ndirs++] = 0; /* up */ 590 dirs[ndirs++] = 0; /* up */ 591 } 592 593 if (ndirs == 0) 594 continue; 595 596 dir = dirs[random_upto(rs, ndirs)]; 597 598#ifdef GENERATION_DIAGNOSTICS 599 printf("picked dir %d\n", dir); 600#endif 601 602 /* 603 * Insert a square within column (x+dir) at position y. 604 */ 605 for (i = 0; i+1 <= y; i++) 606 grid2[i*w+x+dir] = grid2[(i+1)*w+x+dir]; 607 grid2[y*w+x+dir] = tc; 608 609 /* 610 * See if we've divided the remaining grid squares 611 * into sub-areas. If so, we need every sub-area to 612 * have an even area or we won't be able to 613 * complete generation. 614 * 615 * If the height is odd and not all columns are 616 * present, we can increase the area of a subarea 617 * by adding a new column in it, so in that 618 * situation we don't mind having as many odd 619 * subareas as there are spare columns. 620 * 621 * If the height is even, we can't fix it at all. 622 */ 623 { 624 int nerrs = 0, nfix = 0; 625 k = 0; /* current subarea size */ 626 for (i = 0; i < w; i++) { 627 if (grid2[(h-1)*w+i] == 0) { 628 if (h % 2) 629 nfix++; 630 continue; 631 } 632 for (j = 0; j < h && grid2[j*w+i] == 0; j++); 633 assert(j < h); 634 if (j == 0) { 635 /* 636 * End of previous subarea. 637 */ 638 if (k % 2) 639 nerrs++; 640 k = 0; 641 } else { 642 k += j; 643 } 644 } 645 if (k % 2) 646 nerrs++; 647 if (nerrs > nfix) 648 continue; /* try a different placement */ 649 } 650 651 /* 652 * We've made a move. Verify that it is a valid 653 * move and that if made it would indeed yield the 654 * previous grid state. The criteria are: 655 * 656 * (a) removing all the squares of colour tc (and 657 * shuffling the columns up etc) from grid2 658 * would yield grid 659 * (b) no square of colour tc is adjacent to one 660 * of colour c 661 * (c) all the squares of colour tc form a single 662 * connected component 663 * 664 * We verify the latter property at the same time 665 * as checking that removing all the tc squares 666 * would yield the previous grid. Then we colour 667 * the tc squares in colour c by breadth-first 668 * search, which conveniently permits us to test 669 * that they're all connected. 670 */ 671 { 672 int x1, x2, y1, y2; 673 bool ok = true; 674 int fillstart = -1, ntc = 0; 675 676#ifdef GENERATION_DIAGNOSTICS 677 { 678 int x,y; 679 printf("testing move (new, old):\n"); 680 for (y = 0; y < h; y++) { 681 for (x = 0; x < w; x++) { 682 if (grid2[y*w+x] == 0) 683 printf("-"); 684 else if (grid2[y*w+x] <= nc) 685 printf("%d", grid2[y*w+x]); 686 else 687 printf("*"); 688 } 689 printf(" "); 690 for (x = 0; x < w; x++) { 691 if (grid[y*w+x] == 0) 692 printf("-"); 693 else 694 printf("%d", grid[y*w+x]); 695 } 696 printf("\n"); 697 } 698 } 699#endif 700 701 for (x1 = x2 = 0; x2 < w; x2++) { 702 bool usedcol = false; 703 704 for (y1 = y2 = h-1; y2 >= 0; y2--) { 705 if (grid2[y2*w+x2] == tc) { 706 ntc++; 707 if (fillstart == -1) 708 fillstart = y2*w+x2; 709 if ((y2+1 < h && grid2[(y2+1)*w+x2] == c) || 710 (y2-1 >= 0 && grid2[(y2-1)*w+x2] == c) || 711 (x2+1 < w && grid2[y2*w+x2+1] == c) || 712 (x2-1 >= 0 && grid2[y2*w+x2-1] == c)) { 713#ifdef GENERATION_DIAGNOSTICS 714 printf("adjacency failure at %d,%d\n", 715 x2, y2); 716#endif 717 ok = false; 718 } 719 continue; 720 } 721 if (grid2[y2*w+x2] == 0) 722 break; 723 usedcol = true; 724 if (grid2[y2*w+x2] != grid[y1*w+x1]) { 725#ifdef GENERATION_DIAGNOSTICS 726 printf("matching failure at %d,%d vs %d,%d\n", 727 x2, y2, x1, y1); 728#endif 729 ok = false; 730 } 731 y1--; 732 } 733 734 /* 735 * If we've reached the top of the column 736 * in grid2, verify that we've also reached 737 * the top of the column in `grid'. 738 */ 739 if (usedcol) { 740 while (y1 >= 0) { 741 if (grid[y1*w+x1] != 0) { 742#ifdef GENERATION_DIAGNOSTICS 743 printf("junk at column top (%d,%d)\n", 744 x1, y1); 745#endif 746 ok = false; 747 } 748 y1--; 749 } 750 } 751 752 if (!ok) 753 break; 754 755 if (usedcol) 756 x1++; 757 } 758 759 if (!ok) { 760 assert(!"This should never happen"); 761 762 /* 763 * If this game is compiled NDEBUG so that 764 * the assertion doesn't bring it to a 765 * crashing halt, the only thing we can do 766 * is to give up, loop round again, and 767 * hope to randomly avoid making whatever 768 * type of move just caused this failure. 769 */ 770 continue; 771 } 772 773 /* 774 * Now use bfs to fill in the tc section as 775 * colour c. We use `list' to store the set of 776 * squares we have to process. 777 */ 778 i = j = 0; 779 assert(fillstart >= 0); 780 list[i++] = fillstart; 781#ifdef OUTPUT_SOLUTION 782 printf("M"); 783#endif 784 while (j < i) { 785 k = list[j]; 786 x = k % w; 787 y = k / w; 788#ifdef OUTPUT_SOLUTION 789 printf("%s%d", j ? "," : "", k); 790#endif 791 j++; 792 793 assert(grid2[k] == tc); 794 grid2[k] = c; 795 796 if (x > 0 && grid2[k-1] == tc) 797 list[i++] = k-1; 798 if (x+1 < w && grid2[k+1] == tc) 799 list[i++] = k+1; 800 if (y > 0 && grid2[k-w] == tc) 801 list[i++] = k-w; 802 if (y+1 < h && grid2[k+w] == tc) 803 list[i++] = k+w; 804 } 805#ifdef OUTPUT_SOLUTION 806 printf("\n"); 807#endif 808 809 /* 810 * Check that we've filled the same number of 811 * tc squares as we originally found. 812 */ 813 assert(j == ntc); 814 } 815 816 memcpy(grid, grid2, wh * sizeof(int)); 817 818 break; /* done it! */ 819 } 820 821#ifdef GENERATION_DIAGNOSTICS 822 { 823 int x,y; 824 printf("n=%d\n", n); 825 for (y = 0; y < h; y++) { 826 for (x = 0; x < w; x++) { 827 if (grid[y*w+x] == 0) 828 printf("-"); 829 else 830 printf("%d", grid[y*w+x]); 831 } 832 printf("\n"); 833 } 834 } 835#endif 836 837 if (n < 0) 838 break; 839 } 840 841 ok = true; 842 for (i = 0; i < wh; i++) 843 if (grid[i] == 0) { 844 ok = false; 845 failures++; 846#if defined GENERATION_DIAGNOSTICS || defined SHOW_INCOMPLETE 847 { 848 int x,y; 849 printf("incomplete grid:\n"); 850 for (y = 0; y < h; y++) { 851 for (x = 0; x < w; x++) { 852 if (grid[y*w+x] == 0) 853 printf("-"); 854 else 855 printf("%d", grid[y*w+x]); 856 } 857 printf("\n"); 858 } 859 } 860#endif 861 break; 862 } 863 864 } while (!ok); 865 866#if defined GENERATION_DIAGNOSTICS || defined COUNT_FAILURES 867 printf("%d failures\n", failures); 868#else 869 (void)failures; 870#endif 871#ifdef GENERATION_DIAGNOSTICS 872 { 873 int x,y; 874 printf("final grid:\n"); 875 for (y = 0; y < h; y++) { 876 for (x = 0; x < w; x++) { 877 printf("%d", grid[y*w+x]); 878 } 879 printf("\n"); 880 } 881 } 882#endif 883 884 sfree(grid2); 885 sfree(list); 886} 887 888/* 889 * Not-guaranteed-soluble grid generator; kept as a legacy, and in 890 * case someone finds the slightly odd quality of the guaranteed- 891 * soluble grids to be aesthetically displeasing or finds its CPU 892 * utilisation to be excessive. 893 */ 894static void gen_grid_random(int w, int h, int nc, int *grid, random_state *rs) 895{ 896 int i, j, c; 897 int n = w * h; 898 899 for (i = 0; i < n; i++) 900 grid[i] = 0; 901 902 /* 903 * Our sole concession to not gratuitously generating insoluble 904 * grids is to ensure we have at least two of every colour. 905 */ 906 for (c = 1; c <= nc; c++) { 907 for (j = 0; j < 2; j++) { 908 do { 909 i = (int)random_upto(rs, n); 910 } while (grid[i] != 0); 911 grid[i] = c; 912 } 913 } 914 915 /* 916 * Fill in the rest of the grid at random. 917 */ 918 for (i = 0; i < n; i++) { 919 if (grid[i] == 0) 920 grid[i] = (int)random_upto(rs, nc)+1; 921 } 922} 923 924static char *new_game_desc(const game_params *params, random_state *rs, 925 char **aux, bool interactive) 926{ 927 char *ret; 928 int n, i, retlen, *tiles; 929 930 n = params->w * params->h; 931 tiles = snewn(n, int); 932 933 if (params->soluble) 934 gen_grid(params->w, params->h, params->ncols, tiles, rs); 935 else 936 gen_grid_random(params->w, params->h, params->ncols, tiles, rs); 937 938 ret = NULL; 939 retlen = 0; 940 for (i = 0; i < n; i++) { 941 char buf[80]; 942 int k; 943 944 k = sprintf(buf, "%d,", tiles[i]); 945 ret = sresize(ret, retlen + k + 1, char); 946 strcpy(ret + retlen, buf); 947 retlen += k; 948 } 949 ret[retlen-1] = '\0'; /* delete last comma */ 950 951 sfree(tiles); 952 return ret; 953} 954 955static const char *validate_desc(const game_params *params, const char *desc) 956{ 957 int area = params->w * params->h, i; 958 const char *p = desc; 959 960 for (i = 0; i < area; i++) { 961 const char *q = p; 962 int n; 963 964 if (!isdigit((unsigned char)*p)) 965 return "Not enough numbers in string"; 966 while (isdigit((unsigned char)*p)) p++; 967 968 if (i < area-1 && *p != ',') 969 return "Expected comma after number"; 970 else if (i == area-1 && *p) 971 return "Excess junk at end of string"; 972 973 n = atoi(q); 974 if (n < 0 || n > params->ncols) 975 return "Colour out of range"; 976 977 if (*p) p++; /* eat comma */ 978 } 979 return NULL; 980} 981 982static game_state *new_game(midend *me, const game_params *params, 983 const char *desc) 984{ 985 game_state *state = snew(game_state); 986 const char *p = desc; 987 int i; 988 989 state->params = *params; /* struct copy */ 990 state->n = state->params.w * state->params.h; 991 state->tiles = snewn(state->n, int); 992 993 for (i = 0; i < state->n; i++) { 994 assert(*p); 995 state->tiles[i] = atoi(p); 996 while (*p && *p != ',') 997 p++; 998 if (*p) p++; /* eat comma */ 999 } 1000 state->complete = false; 1001 state->impossible = false; 1002 state->score = 0; 1003 1004 return state; 1005} 1006 1007static game_state *dup_game(const game_state *state) 1008{ 1009 game_state *ret = snew(game_state); 1010 1011 *ret = *state; /* structure copy, except... */ 1012 1013 ret->tiles = snewn(state->n, int); 1014 memcpy(ret->tiles, state->tiles, state->n * sizeof(int)); 1015 1016 return ret; 1017} 1018 1019static void free_game(game_state *state) 1020{ 1021 sfree(state->tiles); 1022 sfree(state); 1023} 1024 1025static bool game_can_format_as_text_now(const game_params *params) 1026{ 1027 return true; 1028} 1029 1030static char *game_text_format(const game_state *state) 1031{ 1032 char *ret, *p; 1033 int x, y, maxlen; 1034 1035 maxlen = state->params.h * (state->params.w + 1); 1036 ret = snewn(maxlen+1, char); 1037 p = ret; 1038 1039 for (y = 0; y < state->params.h; y++) { 1040 for (x = 0; x < state->params.w; x++) { 1041 int t = TILE(state,x,y); 1042 if (t <= 0) *p++ = ' '; 1043 else if (t < 10) *p++ = '0'+t; 1044 else *p++ = 'a'+(t-10); 1045 } 1046 *p++ = '\n'; 1047 } 1048 assert(p - ret == maxlen); 1049 *p = '\0'; 1050 return ret; 1051} 1052 1053struct game_ui { 1054 struct game_params params; 1055 int *tiles; /* selected-ness only */ 1056 int nselected; 1057 int xsel, ysel; 1058 bool displaysel; 1059}; 1060 1061static game_ui *new_ui(const game_state *state) 1062{ 1063 game_ui *ui = snew(game_ui); 1064 1065 ui->params = state->params; /* structure copy */ 1066 ui->tiles = snewn(state->n, int); 1067 memset(ui->tiles, 0, state->n*sizeof(int)); 1068 ui->nselected = 0; 1069 1070 ui->xsel = ui->ysel = 0; 1071 ui->displaysel = getenv_bool("PUZZLES_SHOW_CURSOR", false); 1072 1073 return ui; 1074} 1075 1076static void free_ui(game_ui *ui) 1077{ 1078 sfree(ui->tiles); 1079 sfree(ui); 1080} 1081 1082static void sel_clear(game_ui *ui, const game_state *state) 1083{ 1084 int i; 1085 1086 for (i = 0; i < state->n; i++) 1087 ui->tiles[i] &= ~TILE_SELECTED; 1088 ui->nselected = 0; 1089} 1090 1091 1092static void game_changed_state(game_ui *ui, const game_state *oldstate, 1093 const game_state *newstate) 1094{ 1095 sel_clear(ui, newstate); 1096} 1097 1098static const char *current_key_label(const game_ui *ui, 1099 const game_state *state, int button) 1100{ 1101 if (IS_CURSOR_SELECT(button)) { 1102 int x = ui->xsel, y = ui->ysel, c = COL(state,x,y); 1103 if (c == 0) return ""; 1104 if (ISSEL(ui, x, y)) 1105 return button == CURSOR_SELECT2 ? "Unselect" : "Remove"; 1106 if ((x > 0 && COL(state,x-1,y) == c) || 1107 (x+1 < state->params.w && COL(state,x+1,y) == c) || 1108 (y > 0 && COL(state,x,y-1) == c) || 1109 (y+1 < state->params.h && COL(state,x,y+1) == c)) 1110 return "Select"; 1111 /* Cursor is over a lone square, so we can't select it. */ 1112 if (ui->nselected) return "Unselect"; 1113 } 1114 return ""; 1115} 1116 1117static char *sel_movedesc(game_ui *ui, const game_state *state) 1118{ 1119 int i; 1120 char *ret, buf[80]; 1121 const char *sep; 1122 int retlen, retsize; 1123 1124 retsize = 256; 1125 ret = snewn(retsize, char); 1126 retlen = 0; 1127 ret[retlen++] = 'M'; 1128 sep = ""; 1129 1130 for (i = 0; i < state->n; i++) { 1131 if (ui->tiles[i] & TILE_SELECTED) { 1132 sprintf(buf, "%s%d", sep, i); 1133 sep = ","; 1134 if (retlen + (int)strlen(buf) >= retsize) { 1135 retsize = retlen + strlen(buf) + 256; 1136 ret = sresize(ret, retsize, char); 1137 } 1138 strcpy(ret + retlen, buf); 1139 retlen += strlen(buf); 1140 1141 ui->tiles[i] &= ~TILE_SELECTED; 1142 } 1143 } 1144 ui->nselected = 0; 1145 1146 assert(retlen < retsize); 1147 ret[retlen++] = '\0'; 1148 return sresize(ret, retlen, char); 1149} 1150 1151static void sel_expand(game_ui *ui, const game_state *state, int tx, int ty) 1152{ 1153 int ns = 1, nadded, x, y, c; 1154 1155 TILE(ui,tx,ty) |= TILE_SELECTED; 1156 do { 1157 nadded = 0; 1158 1159 for (x = 0; x < state->params.w; x++) { 1160 for (y = 0; y < state->params.h; y++) { 1161 if (x == tx && y == ty) continue; 1162 if (ISSEL(ui,x,y)) continue; 1163 1164 c = COL(state,x,y); 1165 if ((x > 0) && 1166 ISSEL(ui,x-1,y) && COL(state,x-1,y) == c) { 1167 TILE(ui,x,y) |= TILE_SELECTED; 1168 nadded++; 1169 continue; 1170 } 1171 1172 if ((x+1 < state->params.w) && 1173 ISSEL(ui,x+1,y) && COL(state,x+1,y) == c) { 1174 TILE(ui,x,y) |= TILE_SELECTED; 1175 nadded++; 1176 continue; 1177 } 1178 1179 if ((y > 0) && 1180 ISSEL(ui,x,y-1) && COL(state,x,y-1) == c) { 1181 TILE(ui,x,y) |= TILE_SELECTED; 1182 nadded++; 1183 continue; 1184 } 1185 1186 if ((y+1 < state->params.h) && 1187 ISSEL(ui,x,y+1) && COL(state,x,y+1) == c) { 1188 TILE(ui,x,y) |= TILE_SELECTED; 1189 nadded++; 1190 continue; 1191 } 1192 } 1193 } 1194 ns += nadded; 1195 } while (nadded > 0); 1196 1197 if (ns > 1) { 1198 ui->nselected = ns; 1199 } else { 1200 sel_clear(ui, state); 1201 } 1202} 1203 1204static bool sg_emptycol(game_state *ret, int x) 1205{ 1206 int y; 1207 for (y = 0; y < ret->params.h; y++) { 1208 if (COL(ret,x,y)) return false; 1209 } 1210 return true; 1211} 1212 1213 1214static void sg_snuggle(game_state *ret) 1215{ 1216 int x,y, ndone; 1217 1218 /* make all unsupported tiles fall down. */ 1219 do { 1220 ndone = 0; 1221 for (x = 0; x < ret->params.w; x++) { 1222 for (y = ret->params.h-1; y > 0; y--) { 1223 if (COL(ret,x,y) != 0) continue; 1224 if (COL(ret,x,y-1) != 0) { 1225 SWAPTILE(ret,x,y,x,y-1); 1226 ndone++; 1227 } 1228 } 1229 } 1230 } while (ndone); 1231 1232 /* shuffle all columns as far left as they can go. */ 1233 do { 1234 ndone = 0; 1235 for (x = 0; x < ret->params.w-1; x++) { 1236 if (sg_emptycol(ret,x) && !sg_emptycol(ret,x+1)) { 1237 ndone++; 1238 for (y = 0; y < ret->params.h; y++) { 1239 SWAPTILE(ret,x,y,x+1,y); 1240 } 1241 } 1242 } 1243 } while (ndone); 1244} 1245 1246static void sg_check(game_state *ret) 1247{ 1248 int x,y; 1249 bool complete = true, impossible = true; 1250 1251 for (x = 0; x < ret->params.w; x++) { 1252 for (y = 0; y < ret->params.h; y++) { 1253 if (COL(ret,x,y) == 0) 1254 continue; 1255 complete = false; 1256 if (x+1 < ret->params.w) { 1257 if (COL(ret,x,y) == COL(ret,x+1,y)) 1258 impossible = false; 1259 } 1260 if (y+1 < ret->params.h) { 1261 if (COL(ret,x,y) == COL(ret,x,y+1)) 1262 impossible = false; 1263 } 1264 } 1265 } 1266 ret->complete = complete; 1267 ret->impossible = impossible; 1268} 1269 1270struct game_drawstate { 1271 bool started; 1272 int bgcolour; 1273 int tileinner, tilegap; 1274 int *tiles; /* contains colour and SELECTED. */ 1275}; 1276 1277static char *interpret_move(const game_state *state, game_ui *ui, 1278 const game_drawstate *ds, 1279 int x, int y, int button) 1280{ 1281 int tx, ty; 1282 char *ret = MOVE_UI_UPDATE; 1283 1284 if (button == RIGHT_BUTTON || button == LEFT_BUTTON) { 1285 ui->displaysel = false; 1286 tx = FROMCOORD(x); ty= FROMCOORD(y); 1287 } else if (IS_CURSOR_MOVE(button)) { 1288 return move_cursor(button, &ui->xsel, &ui->ysel, 1289 state->params.w, state->params.h, 1290 true, &ui->displaysel); 1291 } else if (IS_CURSOR_SELECT(button)) { 1292 ui->displaysel = true; 1293 tx = ui->xsel; 1294 ty = ui->ysel; 1295 } else 1296 return MOVE_UNUSED; 1297 1298 if (tx < 0 || tx >= state->params.w || ty < 0 || ty >= state->params.h) 1299 return MOVE_UNUSED; 1300 if (COL(state, tx, ty) == 0) return MOVE_NO_EFFECT; 1301 1302 if (ISSEL(ui,tx,ty)) { 1303 if (button == RIGHT_BUTTON || button == CURSOR_SELECT2) 1304 sel_clear(ui, state); 1305 else 1306 ret = sel_movedesc(ui, state); 1307 } else { 1308 sel_clear(ui, state); /* might be no-op */ 1309 sel_expand(ui, state, tx, ty); 1310 } 1311 1312 return ret; 1313} 1314 1315static game_state *execute_move(const game_state *from, const char *move) 1316{ 1317 int i, n; 1318 game_state *ret; 1319 1320 if (move[0] == 'M') { 1321 ret = dup_game(from); 1322 1323 n = 0; 1324 move++; 1325 1326 while (*move) { 1327 if (!isdigit((unsigned char)*move)) { 1328 free_game(ret); 1329 return NULL; 1330 } 1331 i = atoi(move); 1332 if (i < 0 || i >= ret->n) { 1333 free_game(ret); 1334 return NULL; 1335 } 1336 n++; 1337 ret->tiles[i] = 0; 1338 1339 while (*move && isdigit((unsigned char)*move)) move++; 1340 if (*move == ',') move++; 1341 } 1342 1343 ret->score += npoints(&ret->params, n); 1344 1345 sg_snuggle(ret); /* shifts blanks down and to the left */ 1346 sg_check(ret); /* checks for completeness or impossibility */ 1347 1348 return ret; 1349 } else 1350 return NULL; /* couldn't parse move string */ 1351} 1352 1353/* ---------------------------------------------------------------------- 1354 * Drawing routines. 1355 */ 1356 1357static void game_set_size(drawing *dr, game_drawstate *ds, 1358 const game_params *params, int tilesize) 1359{ 1360 ds->tilegap = (tilesize + 8) / 16; 1361 ds->tileinner = tilesize - ds->tilegap; 1362} 1363 1364static void game_compute_size(const game_params *params, int tilesize, 1365 const game_ui *ui, int *x, int *y) 1366{ 1367 /* Ick: fake up tile size variables for macro expansion purposes */ 1368 game_drawstate ads, *ds = &ads; 1369 game_set_size(NULL, ds, params, tilesize); 1370 1371 *x = TILE_SIZE * params->w + 2 * BORDER - TILE_GAP; 1372 *y = TILE_SIZE * params->h + 2 * BORDER - TILE_GAP; 1373} 1374 1375static float *game_colours(frontend *fe, int *ncolours) 1376{ 1377 float *ret = snewn(3 * NCOLOURS, float); 1378 1379 game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT); 1380 1381 ret[COL_1 * 3 + 0] = 0.0F; 1382 ret[COL_1 * 3 + 1] = 0.0F; 1383 ret[COL_1 * 3 + 2] = 1.0F; 1384 1385 ret[COL_2 * 3 + 0] = 0.0F; 1386 ret[COL_2 * 3 + 1] = 0.5F; 1387 ret[COL_2 * 3 + 2] = 0.0F; 1388 1389 ret[COL_3 * 3 + 0] = 1.0F; 1390 ret[COL_3 * 3 + 1] = 0.0F; 1391 ret[COL_3 * 3 + 2] = 0.0F; 1392 1393 ret[COL_4 * 3 + 0] = 0.7F; 1394 ret[COL_4 * 3 + 1] = 0.7F; 1395 ret[COL_4 * 3 + 2] = 0.0F; 1396 1397 ret[COL_5 * 3 + 0] = 1.0F; 1398 ret[COL_5 * 3 + 1] = 0.0F; 1399 ret[COL_5 * 3 + 2] = 1.0F; 1400 1401 ret[COL_6 * 3 + 0] = 0.0F; 1402 ret[COL_6 * 3 + 1] = 0.8F; 1403 ret[COL_6 * 3 + 2] = 0.8F; 1404 1405 ret[COL_7 * 3 + 0] = 0.5F; 1406 ret[COL_7 * 3 + 1] = 0.5F; 1407 ret[COL_7 * 3 + 2] = 1.0F; 1408 1409 ret[COL_8 * 3 + 0] = 0.2F; 1410 ret[COL_8 * 3 + 1] = 0.8F; 1411 ret[COL_8 * 3 + 2] = 0.2F; 1412 1413 ret[COL_9 * 3 + 0] = 1.0F; 1414 ret[COL_9 * 3 + 1] = 0.5F; 1415 ret[COL_9 * 3 + 2] = 0.5F; 1416 1417 ret[COL_IMPOSSIBLE * 3 + 0] = 0.0F; 1418 ret[COL_IMPOSSIBLE * 3 + 1] = 0.0F; 1419 ret[COL_IMPOSSIBLE * 3 + 2] = 0.0F; 1420 1421 ret[COL_SEL * 3 + 0] = 1.0F; 1422 ret[COL_SEL * 3 + 1] = 1.0F; 1423 ret[COL_SEL * 3 + 2] = 1.0F; 1424 1425 *ncolours = NCOLOURS; 1426 return ret; 1427} 1428 1429static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) 1430{ 1431 struct game_drawstate *ds = snew(struct game_drawstate); 1432 int i; 1433 1434 ds->started = false; 1435 ds->tileinner = ds->tilegap = 0; /* not decided yet */ 1436 ds->tiles = snewn(state->n, int); 1437 ds->bgcolour = -1; 1438 for (i = 0; i < state->n; i++) 1439 ds->tiles[i] = -1; 1440 1441 return ds; 1442} 1443 1444static void game_free_drawstate(drawing *dr, game_drawstate *ds) 1445{ 1446 sfree(ds->tiles); 1447 sfree(ds); 1448} 1449 1450/* Drawing routing for the tile at (x,y) is responsible for drawing 1451 * itself and the gaps to its right and below. If we're the same colour 1452 * as the tile to our right, then we fill in the gap; ditto below, and if 1453 * both then we fill the teeny tiny square in the corner as well. 1454 */ 1455 1456static void tile_redraw(drawing *dr, game_drawstate *ds, 1457 int x, int y, bool dright, bool dbelow, 1458 int tile, int bgcolour) 1459{ 1460 int outer = bgcolour, inner = outer, col = tile & TILE_COLMASK; 1461 int tile_w, tile_h, outer_w, outer_h; 1462 1463 if (col) { 1464 if (tile & TILE_IMPOSSIBLE) { 1465 outer = col; 1466 inner = COL_IMPOSSIBLE; 1467 } else if (tile & TILE_SELECTED) { 1468 outer = COL_SEL; 1469 inner = col; 1470 } else { 1471 outer = inner = col; 1472 } 1473 } 1474 tile_w = dright ? TILE_SIZE : TILE_INNER; 1475 tile_h = dbelow ? TILE_SIZE : TILE_INNER; 1476 outer_w = (tile & TILE_JOINRIGHT) ? tile_w : TILE_INNER; 1477 outer_h = (tile & TILE_JOINDOWN) ? tile_h : TILE_INNER; 1478 /* Draw the background if any of it will be visible. */ 1479 if (outer_w != tile_w || outer_h != tile_h || outer == bgcolour) 1480 draw_rect(dr, COORD(x), COORD(y), tile_w, tile_h, bgcolour); 1481 /* Draw the piece. */ 1482 if (outer != bgcolour) 1483 draw_rect(dr, COORD(x), COORD(y), outer_w, outer_h, outer); 1484 if (inner != outer) 1485 draw_rect(dr, COORD(x)+TILE_INNER/4, COORD(y)+TILE_INNER/4, 1486 TILE_INNER/2, TILE_INNER/2, inner); 1487 /* Reset bottom-right corner if necessary. */ 1488 if ((tile & (TILE_JOINRIGHT | TILE_JOINDOWN | TILE_JOINDIAG)) == 1489 (TILE_JOINRIGHT | TILE_JOINDOWN) && outer != bgcolour && 1490 TILE_GAP != 0) 1491 draw_rect(dr, COORD(x)+TILE_INNER, COORD(y)+TILE_INNER, 1492 TILE_GAP, TILE_GAP, bgcolour); 1493 1494 if (tile & TILE_HASSEL) { 1495 int sx = COORD(x)+2, sy = COORD(y)+2, ssz = TILE_INNER-5; 1496 int scol = (outer == COL_SEL) ? COL_LOWLIGHT : COL_HIGHLIGHT; 1497 draw_line(dr, sx, sy, sx+ssz, sy, scol); 1498 draw_line(dr, sx+ssz, sy, sx+ssz, sy+ssz, scol); 1499 draw_line(dr, sx+ssz, sy+ssz, sx, sy+ssz, scol); 1500 draw_line(dr, sx, sy+ssz, sx, sy, scol); 1501 } 1502 1503 draw_update(dr, COORD(x), COORD(y), TILE_SIZE, TILE_SIZE); 1504} 1505 1506static void game_redraw(drawing *dr, game_drawstate *ds, 1507 const game_state *oldstate, const game_state *state, 1508 int dir, const game_ui *ui, 1509 float animtime, float flashtime) 1510{ 1511 int bgcolour, x, y; 1512 1513 /* This was entirely cloned from fifteen.c; it should probably be 1514 * moved into some generic 'draw-recessed-rectangle' utility fn. */ 1515 if (!ds->started) { 1516 int coords[10]; 1517 1518 /* 1519 * Recessed area containing the whole puzzle. 1520 */ 1521 coords[0] = COORD(state->params.w) + HIGHLIGHT_WIDTH - 1 - TILE_GAP; 1522 coords[1] = COORD(state->params.h) + HIGHLIGHT_WIDTH - 1 - TILE_GAP; 1523 coords[2] = COORD(state->params.w) + HIGHLIGHT_WIDTH - 1 - TILE_GAP; 1524 coords[3] = COORD(0) - HIGHLIGHT_WIDTH; 1525 coords[4] = coords[2] - TILE_SIZE; 1526 coords[5] = coords[3] + TILE_SIZE; 1527 coords[8] = COORD(0) - HIGHLIGHT_WIDTH; 1528 coords[9] = COORD(state->params.h) + HIGHLIGHT_WIDTH - 1 - TILE_GAP; 1529 coords[6] = coords[8] + TILE_SIZE; 1530 coords[7] = coords[9] - TILE_SIZE; 1531 draw_polygon(dr, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT); 1532 1533 coords[1] = COORD(0) - HIGHLIGHT_WIDTH; 1534 coords[0] = COORD(0) - HIGHLIGHT_WIDTH; 1535 draw_polygon(dr, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT); 1536 1537 ds->started = true; 1538 } 1539 1540 if (flashtime > 0.0F) { 1541 int frame = (int)(flashtime / FLASH_FRAME); 1542 bgcolour = (frame % 2 ? COL_LOWLIGHT : COL_HIGHLIGHT); 1543 } else 1544 bgcolour = COL_BACKGROUND; 1545 1546 for (x = 0; x < state->params.w; x++) { 1547 for (y = 0; y < state->params.h; y++) { 1548 int i = (state->params.w * y) + x; 1549 int col = COL(state,x,y), tile = col; 1550 bool dright = (x+1 < state->params.w); 1551 bool dbelow = (y+1 < state->params.h); 1552 1553 tile |= ISSEL(ui,x,y); 1554 if (state->impossible) 1555 tile |= TILE_IMPOSSIBLE; 1556 if (dright && COL(state,x+1,y) == col) 1557 tile |= TILE_JOINRIGHT; 1558 if (dbelow && COL(state,x,y+1) == col) 1559 tile |= TILE_JOINDOWN; 1560 if ((tile & TILE_JOINRIGHT) && (tile & TILE_JOINDOWN) && 1561 COL(state,x+1,y+1) == col) 1562 tile |= TILE_JOINDIAG; 1563 /* 1564 * If the game state is an unplayable one (either 1565 * completed or impossible), we hide the keyboard-control 1566 * cursor. 1567 */ 1568 if (ui->displaysel && ui->xsel == x && ui->ysel == y && 1569 !(state->complete || state->impossible)) 1570 tile |= TILE_HASSEL; 1571 1572 /* For now we're never expecting oldstate at all (because we have 1573 * no animation); when we do we might well want to be looking 1574 * at the tile colours from oldstate, not state. */ 1575 if ((oldstate && COL(oldstate,x,y) != col) || 1576 (ds->bgcolour != bgcolour) || 1577 (tile != ds->tiles[i])) { 1578 tile_redraw(dr, ds, x, y, dright, dbelow, tile, bgcolour); 1579 ds->tiles[i] = tile; 1580 } 1581 } 1582 } 1583 ds->bgcolour = bgcolour; 1584 1585 { 1586 char status[255], score[80]; 1587 1588 sprintf(score, "Score: %d", state->score); 1589 1590 if (state->complete) 1591 sprintf(status, "COMPLETE! %s", score); 1592 else if (state->impossible) 1593 sprintf(status, "Cannot move! %s", score); 1594 else if (ui->nselected) 1595 sprintf(status, "%s Selected: %d (%d)", 1596 score, ui->nselected, npoints(&state->params, ui->nselected)); 1597 else 1598 sprintf(status, "%s", score); 1599 status_bar(dr, status); 1600 } 1601} 1602 1603static float game_anim_length(const game_state *oldstate, 1604 const game_state *newstate, int dir, game_ui *ui) 1605{ 1606 return 0.0F; 1607} 1608 1609static float game_flash_length(const game_state *oldstate, 1610 const game_state *newstate, int dir, game_ui *ui) 1611{ 1612 if ((!oldstate->complete && newstate->complete) || 1613 (!oldstate->impossible && newstate->impossible)) 1614 return 2 * FLASH_FRAME; 1615 else 1616 return 0.0F; 1617} 1618 1619static void game_get_cursor_location(const game_ui *ui, 1620 const game_drawstate *ds, 1621 const game_state *state, 1622 const game_params *params, 1623 int *x, int *y, int *w, int *h) 1624{ 1625 if(ui->displaysel) { 1626 *x = COORD(ui->xsel); 1627 *y = COORD(ui->ysel); 1628 *w = *h = TILE_SIZE; 1629 } 1630} 1631 1632static int game_status(const game_state *state) 1633{ 1634 /* 1635 * Dead-end situations are assumed to be rescuable by Undo, so we 1636 * don't bother to identify them and return -1. 1637 */ 1638 return state->complete ? +1 : 0; 1639} 1640 1641#ifdef COMBINED 1642#define thegame samegame 1643#endif 1644 1645const struct game thegame = { 1646 "Same Game", "games.samegame", "samegame", 1647 default_params, 1648 game_fetch_preset, NULL, 1649 decode_params, 1650 encode_params, 1651 free_params, 1652 dup_params, 1653 true, game_configure, custom_params, 1654 validate_params, 1655 new_game_desc, 1656 validate_desc, 1657 new_game, 1658 dup_game, 1659 free_game, 1660 false, NULL, /* solve */ 1661 true, game_can_format_as_text_now, game_text_format, 1662 NULL, NULL, /* get_prefs, set_prefs */ 1663 new_ui, 1664 free_ui, 1665 NULL, /* encode_ui */ 1666 NULL, /* decode_ui */ 1667 NULL, /* game_request_keys */ 1668 game_changed_state, 1669 current_key_label, 1670 interpret_move, 1671 execute_move, 1672 PREFERRED_TILE_SIZE, game_compute_size, game_set_size, 1673 game_colours, 1674 game_new_drawstate, 1675 game_free_drawstate, 1676 game_redraw, 1677 game_anim_length, 1678 game_flash_length, 1679 game_get_cursor_location, 1680 game_status, 1681 false, false, NULL, NULL, /* print_size, print */ 1682 true, /* wants_statusbar */ 1683 false, NULL, /* timing_state */ 1684 0, /* flags */ 1685};