A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 1314 lines 37 kB view raw
1/* 2 * twiddle.c: Puzzle involving rearranging a grid of squares by 3 * rotating subsquares. Adapted and generalised from a 4 * door-unlocking puzzle in Metroid Prime 2 (the one in the Main 5 * Gyro Chamber). 6 */ 7 8#include <stdio.h> 9#include <stdlib.h> 10#include <string.h> 11#include <assert.h> 12#include <ctype.h> 13#include <limits.h> 14#ifdef NO_TGMATH_H 15# include <math.h> 16#else 17# include <tgmath.h> 18#endif 19 20#include "puzzles.h" 21 22#define PREFERRED_TILE_SIZE 48 23#define TILE_SIZE (ds->tilesize) 24#define BORDER (TILE_SIZE / 2) 25#define HIGHLIGHT_WIDTH (TILE_SIZE / 20) 26#define COORD(x) ( (x) * TILE_SIZE + BORDER ) 27#define FROMCOORD(x) ( ((x) - BORDER + TILE_SIZE) / TILE_SIZE - 1 ) 28 29#define ANIM_PER_BLKSIZE_UNIT 0.13F 30#define FLASH_FRAME 0.13F 31 32enum { 33 COL_BACKGROUND, 34 COL_TEXT, 35 COL_HIGHLIGHT, 36 COL_HIGHLIGHT_GENTLE, 37 COL_LOWLIGHT, 38 COL_LOWLIGHT_GENTLE, 39 COL_HIGHCURSOR, COL_LOWCURSOR, 40 NCOLOURS 41}; 42 43struct game_params { 44 int w, h, n; 45 bool rowsonly; 46 bool orientable; 47 int movetarget; 48}; 49 50struct game_state { 51 int w, h, n; 52 bool orientable; 53 int *grid; 54 int completed; 55 bool used_solve; /* used to suppress completion flash */ 56 int movecount, movetarget; 57 int lastx, lasty, lastr; /* coordinates of last rotation */ 58}; 59 60static game_params *default_params(void) 61{ 62 game_params *ret = snew(game_params); 63 64 ret->w = ret->h = 3; 65 ret->n = 2; 66 ret->rowsonly = ret->orientable = false; 67 ret->movetarget = 0; 68 69 return ret; 70} 71 72 73static void free_params(game_params *params) 74{ 75 sfree(params); 76} 77 78static game_params *dup_params(const game_params *params) 79{ 80 game_params *ret = snew(game_params); 81 *ret = *params; /* structure copy */ 82 return ret; 83} 84 85static bool game_fetch_preset(int i, char **name, game_params **params) 86{ 87 static struct { 88 const char *title; 89 game_params params; 90 } const presets[] = { 91 { "3x3 rows only", { 3, 3, 2, true, false } }, 92 { "3x3 normal", { 3, 3, 2, false, false } }, 93 { "3x3 orientable", { 3, 3, 2, false, true } }, 94 { "4x4 normal", { 4, 4, 2, false } }, 95 { "4x4 orientable", { 4, 4, 2, false, true } }, 96 { "4x4, rotating 3x3 blocks", { 4, 4, 3, false } }, 97 { "5x5, rotating 3x3 blocks", { 5, 5, 3, false } }, 98 { "6x6, rotating 4x4 blocks", { 6, 6, 4, false } }, 99 }; 100 101 if (i < 0 || i >= lenof(presets)) 102 return false; 103 104 *name = dupstr(presets[i].title); 105 *params = dup_params(&presets[i].params); 106 107 return true; 108} 109 110static void decode_params(game_params *ret, char const *string) 111{ 112 ret->w = ret->h = atoi(string); 113 ret->n = 2; 114 ret->rowsonly = false; 115 ret->orientable = false; 116 ret->movetarget = 0; 117 while (*string && isdigit((unsigned char)*string)) string++; 118 if (*string == 'x') { 119 string++; 120 ret->h = atoi(string); 121 while (*string && isdigit((unsigned char)*string)) string++; 122 } 123 if (*string == 'n') { 124 string++; 125 ret->n = atoi(string); 126 while (*string && isdigit((unsigned char)*string)) string++; 127 } 128 while (*string) { 129 if (*string == 'r') { 130 ret->rowsonly = true; 131 string++; 132 } else if (*string == 'o') { 133 ret->orientable = true; 134 string++; 135 } else if (*string == 'm') { 136 string++; 137 ret->movetarget = atoi(string); 138 while (*string && isdigit((unsigned char)*string)) string++; 139 } else 140 string++; 141 } 142} 143 144static char *encode_params(const game_params *params, bool full) 145{ 146 char buf[256]; 147 sprintf(buf, "%dx%dn%d%s%s", params->w, params->h, params->n, 148 params->rowsonly ? "r" : "", 149 params->orientable ? "o" : ""); 150 /* Shuffle limit is part of the limited parameters, because we have to 151 * supply the target move count. */ 152 if (params->movetarget) 153 sprintf(buf + strlen(buf), "m%d", params->movetarget); 154 return dupstr(buf); 155} 156 157static config_item *game_configure(const game_params *params) 158{ 159 config_item *ret; 160 char buf[80]; 161 162 ret = snewn(7, config_item); 163 164 ret[0].name = "Width"; 165 ret[0].type = C_STRING; 166 sprintf(buf, "%d", params->w); 167 ret[0].u.string.sval = dupstr(buf); 168 169 ret[1].name = "Height"; 170 ret[1].type = C_STRING; 171 sprintf(buf, "%d", params->h); 172 ret[1].u.string.sval = dupstr(buf); 173 174 ret[2].name = "Rotating block size"; 175 ret[2].type = C_STRING; 176 sprintf(buf, "%d", params->n); 177 ret[2].u.string.sval = dupstr(buf); 178 179 ret[3].name = "One number per row"; 180 ret[3].type = C_BOOLEAN; 181 ret[3].u.boolean.bval = params->rowsonly; 182 183 ret[4].name = "Orientation matters"; 184 ret[4].type = C_BOOLEAN; 185 ret[4].u.boolean.bval = params->orientable; 186 187 ret[5].name = "Number of shuffling moves"; 188 ret[5].type = C_STRING; 189 sprintf(buf, "%d", params->movetarget); 190 ret[5].u.string.sval = dupstr(buf); 191 192 ret[6].name = NULL; 193 ret[6].type = C_END; 194 195 return ret; 196} 197 198static game_params *custom_params(const config_item *cfg) 199{ 200 game_params *ret = snew(game_params); 201 202 ret->w = atoi(cfg[0].u.string.sval); 203 ret->h = atoi(cfg[1].u.string.sval); 204 ret->n = atoi(cfg[2].u.string.sval); 205 ret->rowsonly = cfg[3].u.boolean.bval; 206 ret->orientable = cfg[4].u.boolean.bval; 207 ret->movetarget = atoi(cfg[5].u.string.sval); 208 209 return ret; 210} 211 212static const char *validate_params(const game_params *params, bool full) 213{ 214 if (params->n < 2) 215 return "Rotating block size must be at least two"; 216 if (params->w < params->n) 217 return "Width must be at least the rotating block size"; 218 if (params->h < params->n) 219 return "Height must be at least the rotating block size"; 220 if (params->w > INT_MAX / params->h) 221 return "Width times height must not be unreasonably large"; 222 if (params->movetarget < 0) 223 return "Number of shuffling moves may not be negative"; 224 return NULL; 225} 226 227/* 228 * This function actually performs a rotation on a grid. The `x' 229 * and `y' coordinates passed in are the coordinates of the _top 230 * left corner_ of the rotated region. (Using the centre would have 231 * involved half-integers and been annoyingly fiddly. Clicking in 232 * the centre is good for a user interface, but too inconvenient to 233 * use internally.) 234 */ 235static void do_rotate(int *grid, int w, int h, int n, bool orientable, 236 int x, int y, int dir) 237{ 238 int i, j; 239 240 assert(x >= 0 && x+n <= w); 241 assert(y >= 0 && y+n <= h); 242 dir &= 3; 243 if (dir == 0) 244 return; /* nothing to do */ 245 246 grid += y*w+x; /* translate region to top corner */ 247 248 /* 249 * If we were leaving the result of the rotation in a separate 250 * grid, the simple thing to do would be to loop over each 251 * square within the rotated region and assign it from its 252 * source square. However, to do it in place without taking 253 * O(n^2) memory, we need to be marginally more clever. What 254 * I'm going to do is loop over about one _quarter_ of the 255 * rotated region and permute each element within that quarter 256 * with its rotational coset. 257 * 258 * The size of the region I need to loop over is (n+1)/2 by 259 * n/2, which is an obvious exact quarter for even n and is a 260 * rectangle for odd n. (For odd n, this technique leaves out 261 * one element of the square, which is of course the central 262 * one that never moves anyway.) 263 */ 264 for (i = 0; i < (n+1)/2; i++) { 265 for (j = 0; j < n/2; j++) { 266 int k; 267 int g[4]; 268 int p[4]; 269 270 p[0] = j*w+i; 271 p[1] = i*w+(n-j-1); 272 p[2] = (n-j-1)*w+(n-i-1); 273 p[3] = (n-i-1)*w+j; 274 275 for (k = 0; k < 4; k++) 276 g[k] = grid[p[k]]; 277 278 for (k = 0; k < 4; k++) { 279 int v = g[(k+dir) & 3]; 280 if (orientable) 281 v ^= ((v+dir) ^ v) & 3; /* alter orientation */ 282 grid[p[k]] = v; 283 } 284 } 285 } 286 287 /* 288 * Don't forget the orientation on the centre square, if n is 289 * odd. 290 */ 291 if (orientable && (n & 1)) { 292 int v = grid[n/2*(w+1)]; 293 v ^= ((v+dir) ^ v) & 3; /* alter orientation */ 294 grid[n/2*(w+1)] = v; 295 } 296} 297 298static bool grid_complete(int *grid, int wh, bool orientable) 299{ 300 bool ok = true; 301 int i; 302 for (i = 1; i < wh; i++) 303 if (grid[i] < grid[i-1]) 304 ok = false; 305 if (orientable) { 306 for (i = 0; i < wh; i++) 307 if (grid[i] & 3) 308 ok = false; 309 } 310 return ok; 311} 312 313static char *new_game_desc(const game_params *params, random_state *rs, 314 char **aux, bool interactive) 315{ 316 int *grid; 317 int w = params->w, h = params->h, n = params->n, wh = w*h; 318 int i; 319 char *ret; 320 int retlen; 321 int total_moves; 322 323 /* 324 * Set up a solved grid. 325 */ 326 grid = snewn(wh, int); 327 for (i = 0; i < wh; i++) 328 grid[i] = ((params->rowsonly ? i/w : i) + 1) * 4; 329 330 /* 331 * Shuffle it. This game is complex enough that I don't feel up 332 * to analysing its full symmetry properties (particularly at 333 * n=4 and above!), so I'm going to do it the pedestrian way 334 * and simply shuffle the grid by making a long sequence of 335 * randomly chosen moves. 336 */ 337 total_moves = params->movetarget; 338 if (!total_moves) 339 /* Add a random move to avoid parity issues. */ 340 total_moves = w*h*n*n*2 + random_upto(rs, 2); 341 342 do { 343 int *prevmoves; 344 int rw, rh; /* w/h of rotation centre space */ 345 346 rw = w - n + 1; 347 rh = h - n + 1; 348 prevmoves = snewn(rw * rh, int); 349 for (i = 0; i < rw * rh; i++) 350 prevmoves[i] = 0; 351 352 for (i = 0; i < total_moves; i++) { 353 int x, y, r, oldtotal, newtotal, dx, dy; 354 355 do { 356 x = random_upto(rs, w - n + 1); 357 y = random_upto(rs, h - n + 1); 358 r = 2 * random_upto(rs, 2) - 1; 359 360 /* 361 * See if any previous rotations has happened at 362 * this point which nothing has overlapped since. 363 * If so, ensure we haven't either undone a 364 * previous move or repeated one so many times that 365 * it turns into fewer moves in the inverse 366 * direction (i.e. three identical rotations). 367 */ 368 oldtotal = prevmoves[y*rw+x]; 369 newtotal = oldtotal + r; 370 371 /* 372 * Special case here for w==h==n, in which case 373 * there is actually no way to _avoid_ all moves 374 * repeating or undoing previous ones. 375 */ 376 } while ((w != n || h != n) && 377 (abs(newtotal) < abs(oldtotal) || abs(newtotal) > 2)); 378 379 do_rotate(grid, w, h, n, params->orientable, x, y, r); 380 381 /* 382 * Log the rotation we've just performed at this point, 383 * for inversion detection in the next move. 384 * 385 * Also zero a section of the prevmoves array, because 386 * any rotation area which _overlaps_ this one is now 387 * entirely safe to perform further moves in. 388 * 389 * Two rotation areas overlap if their top left 390 * coordinates differ by strictly less than n in both 391 * directions 392 */ 393 prevmoves[y*rw+x] += r; 394 for (dy = -n+1; dy <= n-1; dy++) { 395 if (y + dy < 0 || y + dy >= rh) 396 continue; 397 for (dx = -n+1; dx <= n-1; dx++) { 398 if (x + dx < 0 || x + dx >= rw) 399 continue; 400 if (dx == 0 && dy == 0) 401 continue; 402 prevmoves[(y+dy)*rw+(x+dx)] = 0; 403 } 404 } 405 } 406 407 sfree(prevmoves); 408 409 } while (grid_complete(grid, wh, params->orientable)); 410 411 /* 412 * Now construct the game description, by describing the grid 413 * as a simple sequence of integers. They're comma-separated, 414 * unless the puzzle is orientable in which case they're 415 * separated by orientation letters `u', `d', `l' and `r'. 416 */ 417 ret = NULL; 418 retlen = 0; 419 for (i = 0; i < wh; i++) { 420 char buf[80]; 421 int k; 422 423 k = sprintf(buf, "%d%c", grid[i] / 4, 424 (char)(params->orientable ? "uldr"[grid[i] & 3] : ',')); 425 426 ret = sresize(ret, retlen + k + 1, char); 427 strcpy(ret + retlen, buf); 428 retlen += k; 429 } 430 if (!params->orientable) 431 ret[retlen-1] = '\0'; /* delete last comma */ 432 433 sfree(grid); 434 return ret; 435} 436 437static const char *validate_desc(const game_params *params, const char *desc) 438{ 439 const char *p; 440 int w = params->w, h = params->h, wh = w*h; 441 int i; 442 443 p = desc; 444 445 for (i = 0; i < wh; i++) { 446 if (*p < '0' || *p > '9') 447 return "Not enough numbers in string"; 448 while (*p >= '0' && *p <= '9') 449 p++; 450 if (!params->orientable && i < wh-1) { 451 if (*p != ',') 452 return "Expected comma after number"; 453 } else if (params->orientable && i < wh) { 454 if (*p != 'l' && *p != 'r' && *p != 'u' && *p != 'd') 455 return "Expected orientation letter after number"; 456 } else if (i == wh-1 && *p) { 457 return "Excess junk at end of string"; 458 } 459 460 if (*p) p++; /* eat comma */ 461 } 462 463 return NULL; 464} 465 466static game_state *new_game(midend *me, const game_params *params, 467 const char *desc) 468{ 469 game_state *state = snew(game_state); 470 int w = params->w, h = params->h, n = params->n, wh = w*h; 471 int i; 472 const char *p; 473 474 state->w = w; 475 state->h = h; 476 state->n = n; 477 state->orientable = params->orientable; 478 state->completed = 0; 479 state->used_solve = false; 480 state->movecount = 0; 481 state->movetarget = params->movetarget; 482 state->lastx = state->lasty = state->lastr = -1; 483 484 state->grid = snewn(wh, int); 485 486 p = desc; 487 488 for (i = 0; i < wh; i++) { 489 state->grid[i] = 4 * atoi(p); 490 while (*p >= '0' && *p <= '9') 491 p++; 492 if (*p) { 493 if (params->orientable) { 494 switch (*p) { 495 case 'l': state->grid[i] |= 1; break; 496 case 'd': state->grid[i] |= 2; break; 497 case 'r': state->grid[i] |= 3; break; 498 } 499 } 500 p++; 501 } 502 } 503 504 return state; 505} 506 507static game_state *dup_game(const game_state *state) 508{ 509 game_state *ret = snew(game_state); 510 511 ret->w = state->w; 512 ret->h = state->h; 513 ret->n = state->n; 514 ret->orientable = state->orientable; 515 ret->completed = state->completed; 516 ret->movecount = state->movecount; 517 ret->movetarget = state->movetarget; 518 ret->lastx = state->lastx; 519 ret->lasty = state->lasty; 520 ret->lastr = state->lastr; 521 ret->used_solve = state->used_solve; 522 523 ret->grid = snewn(ret->w * ret->h, int); 524 memcpy(ret->grid, state->grid, ret->w * ret->h * sizeof(int)); 525 526 return ret; 527} 528 529static void free_game(game_state *state) 530{ 531 sfree(state->grid); 532 sfree(state); 533} 534 535static char *solve_game(const game_state *state, const game_state *currstate, 536 const char *aux, const char **error) 537{ 538 return dupstr("S"); 539} 540 541static bool game_can_format_as_text_now(const game_params *params) 542{ 543 return true; 544} 545 546static char *game_text_format(const game_state *state) 547{ 548 char *ret, *p, buf[80]; 549 int i, x, y, col, maxlen; 550 bool o = state->orientable; 551 552 /* Pedantic check: ensure buf is large enough to format an int in 553 * decimal, using the bound log10(2) < 1/3. (Obviously in practice 554 * int is not going to be larger than even 32 bits any time soon, 555 * but.) */ 556 assert(sizeof(buf) >= 1 + sizeof(int) * CHAR_BIT/3); 557 558 /* 559 * First work out how many characters we need to display each 560 * number. We're pretty flexible on grid contents here, so we 561 * have to scan the entire grid. 562 */ 563 col = 0; 564 for (i = 0; i < state->w * state->h; i++) { 565 x = sprintf(buf, "%d", state->grid[i] / 4); 566 if (col < x) col = x; 567 } 568 569 /* Reassure sprintf-checking compilers like gcc that the field 570 * width we've just computed is not now excessive */ 571 if (col >= sizeof(buf)) 572 col = sizeof(buf)-1; 573 574 /* 575 * Now we know the exact total size of the grid we're going to 576 * produce: it's got h rows, each containing w lots of col+o, 577 * w-1 spaces and a trailing newline. 578 */ 579 maxlen = state->h * state->w * (col+o+1); 580 581 ret = snewn(maxlen+1, char); 582 p = ret; 583 584 for (y = 0; y < state->h; y++) { 585 for (x = 0; x < state->w; x++) { 586 int v = state->grid[state->w*y+x]; 587 sprintf(buf, "%*d", col, v/4); 588 memcpy(p, buf, col); 589 p += col; 590 if (o) 591 *p++ = "^<v>"[v & 3]; 592 if (x+1 == state->w) 593 *p++ = '\n'; 594 else 595 *p++ = ' '; 596 } 597 } 598 599 assert(p - ret == maxlen); 600 *p = '\0'; 601 return ret; 602} 603 604struct game_ui { 605 int cur_x, cur_y; 606 bool cur_visible; 607}; 608 609static game_ui *new_ui(const game_state *state) 610{ 611 game_ui *ui = snew(game_ui); 612 613 ui->cur_x = 0; 614 ui->cur_y = 0; 615 ui->cur_visible = getenv_bool("PUZZLES_SHOW_CURSOR", false); 616 617 return ui; 618} 619 620static void free_ui(game_ui *ui) 621{ 622 sfree(ui); 623} 624 625static void game_changed_state(game_ui *ui, const game_state *oldstate, 626 const game_state *newstate) 627{ 628} 629 630static const char *current_key_label(const game_ui *ui, 631 const game_state *state, int button) 632{ 633 if (!ui->cur_visible) return ""; 634 switch (button) { 635 case CURSOR_SELECT: return "Turn left"; 636 case CURSOR_SELECT2: return "Turn right"; 637 } 638 return ""; 639} 640 641struct game_drawstate { 642 bool started; 643 int w, h, bgcolour; 644 int *grid; 645 int tilesize; 646 int cur_x, cur_y; 647}; 648 649static char *interpret_move(const game_state *state, game_ui *ui, 650 const game_drawstate *ds, 651 int x, int y, int button) 652{ 653 int w = state->w, h = state->h, n = state->n /* , wh = w*h */; 654 char buf[80]; 655 int dir; 656 657 button = button & (~MOD_MASK | MOD_NUM_KEYPAD); 658 659 if (IS_CURSOR_MOVE(button)) 660 return move_cursor(button, &ui->cur_x, &ui->cur_y, w-n+1, h-n+1, 661 false, &ui->cur_visible); 662 663 if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { 664 /* 665 * Determine the coordinates of the click. We offset by n-1 666 * half-blocks so that the user must click at the centre of 667 * a rotation region rather than at the corner. 668 */ 669 x -= (n-1) * TILE_SIZE / 2; 670 y -= (n-1) * TILE_SIZE / 2; 671 x = FROMCOORD(x); 672 y = FROMCOORD(y); 673 dir = (button == LEFT_BUTTON ? 1 : -1); 674 if (x < 0 || x > w-n || y < 0 || y > h-n) 675 return NULL; 676 ui->cur_visible = false; 677 } else if (IS_CURSOR_SELECT(button)) { 678 if (ui->cur_visible) { 679 x = ui->cur_x; 680 y = ui->cur_y; 681 dir = (button == CURSOR_SELECT2) ? -1 : +1; 682 } else { 683 ui->cur_visible = true; 684 return MOVE_UI_UPDATE; 685 } 686 } else if (button == 'a' || button == 'A' || button==MOD_NUM_KEYPAD+'7') { 687 x = y = 0; 688 dir = (button == 'A' ? -1 : +1); 689 } else if (button == 'b' || button == 'B' || button==MOD_NUM_KEYPAD+'9') { 690 x = w-n; 691 y = 0; 692 dir = (button == 'B' ? -1 : +1); 693 } else if (button == 'c' || button == 'C' || button==MOD_NUM_KEYPAD+'1') { 694 x = 0; 695 y = h-n; 696 dir = (button == 'C' ? -1 : +1); 697 } else if (button == 'd' || button == 'D' || button==MOD_NUM_KEYPAD+'3') { 698 x = w-n; 699 y = h-n; 700 dir = (button == 'D' ? -1 : +1); 701 } else if (button==MOD_NUM_KEYPAD+'8' && (w-n) % 2 == 0) { 702 x = (w-n) / 2; 703 y = 0; 704 dir = +1; 705 } else if (button==MOD_NUM_KEYPAD+'2' && (w-n) % 2 == 0) { 706 x = (w-n) / 2; 707 y = h-n; 708 dir = +1; 709 } else if (button==MOD_NUM_KEYPAD+'4' && (h-n) % 2 == 0) { 710 x = 0; 711 y = (h-n) / 2; 712 dir = +1; 713 } else if (button==MOD_NUM_KEYPAD+'6' && (h-n) % 2 == 0) { 714 x = w-n; 715 y = (h-n) / 2; 716 dir = +1; 717 } else if (button==MOD_NUM_KEYPAD+'5' && (w-n) % 2 == 0 && (h-n) % 2 == 0){ 718 x = (w-n) / 2; 719 y = (h-n) / 2; 720 dir = +1; 721 } else { 722 return NULL; /* no move to be made */ 723 } 724 725 /* 726 * If we reach here, we have a valid move. 727 */ 728 sprintf(buf, "M%d,%d,%d", x, y, dir); 729 return dupstr(buf); 730} 731 732static game_state *execute_move(const game_state *from, const char *move) 733{ 734 game_state *ret; 735 int w = from->w, h = from->h, n = from->n, wh = w*h; 736 int x, y, dir; 737 738 if (!strcmp(move, "S")) { 739 int i; 740 ret = dup_game(from); 741 742 /* 743 * Simply replace the grid with a solved one. For this game, 744 * this isn't a useful operation for actually telling the user 745 * what they should have done, but it is useful for 746 * conveniently being able to get hold of a clean state from 747 * which to practise manoeuvres. 748 */ 749 qsort(ret->grid, ret->w*ret->h, sizeof(int), compare_integers); 750 for (i = 0; i < ret->w*ret->h; i++) 751 ret->grid[i] &= ~3; 752 ret->used_solve = true; 753 ret->completed = ret->movecount = 1; 754 755 return ret; 756 } 757 758 if (move[0] != 'M' || 759 sscanf(move+1, "%d,%d,%d", &x, &y, &dir) != 3 || 760 x < 0 || y < 0 || x > from->w - n || y > from->h - n) 761 return NULL; /* can't parse this move string */ 762 763 ret = dup_game(from); 764 ret->movecount++; 765 do_rotate(ret->grid, w, h, n, ret->orientable, x, y, dir); 766 ret->lastx = x; 767 ret->lasty = y; 768 ret->lastr = dir; 769 770 /* 771 * See if the game has been completed. To do this we simply 772 * test that the grid contents are in increasing order. 773 */ 774 if (!ret->completed && grid_complete(ret->grid, wh, ret->orientable)) 775 ret->completed = ret->movecount; 776 return ret; 777} 778 779/* ---------------------------------------------------------------------- 780 * Drawing routines. 781 */ 782 783static void game_compute_size(const game_params *params, int tilesize, 784 const game_ui *ui, int *x, int *y) 785{ 786 /* Ick: fake up `ds->tilesize' for macro expansion purposes */ 787 struct { int tilesize; } ads, *ds = &ads; 788 ads.tilesize = tilesize; 789 790 *x = TILE_SIZE * params->w + 2 * BORDER; 791 *y = TILE_SIZE * params->h + 2 * BORDER; 792} 793 794static void game_set_size(drawing *dr, game_drawstate *ds, 795 const game_params *params, int tilesize) 796{ 797 ds->tilesize = tilesize; 798} 799 800static float *game_colours(frontend *fe, int *ncolours) 801{ 802 float *ret = snewn(3 * NCOLOURS, float); 803 int i; 804 805 game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT); 806 807 /* cursor is light-background with a red tinge. */ 808 ret[COL_HIGHCURSOR * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 1.0F; 809 ret[COL_HIGHCURSOR * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 0.5F; 810 ret[COL_HIGHCURSOR * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 0.5F; 811 812 for (i = 0; i < 3; i++) { 813 ret[COL_HIGHLIGHT_GENTLE * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 1.1F; 814 ret[COL_LOWLIGHT_GENTLE * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 0.9F; 815 ret[COL_TEXT * 3 + i] = 0.0; 816 ret[COL_LOWCURSOR * 3 + i] = ret[COL_HIGHCURSOR * 3 + i] * 0.6F; 817 } 818 819 *ncolours = NCOLOURS; 820 return ret; 821} 822 823static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) 824{ 825 struct game_drawstate *ds = snew(struct game_drawstate); 826 int i; 827 828 ds->started = false; 829 ds->w = state->w; 830 ds->h = state->h; 831 ds->bgcolour = COL_BACKGROUND; 832 ds->grid = snewn(ds->w*ds->h, int); 833 ds->tilesize = 0; /* haven't decided yet */ 834 for (i = 0; i < ds->w*ds->h; i++) 835 ds->grid[i] = -1; 836 ds->cur_x = ds->cur_y = -state->n; 837 838 return ds; 839} 840 841static void game_free_drawstate(drawing *dr, game_drawstate *ds) 842{ 843 sfree(ds->grid); 844 sfree(ds); 845} 846 847struct rotation { 848 int cx, cy, cw, ch; /* clip region */ 849 int ox, oy; /* rotation origin */ 850 float c, s; /* cos and sin of rotation angle */ 851 int lc, rc, tc, bc; /* colours of tile edges */ 852}; 853 854static void rotate(int *xy, struct rotation *rot) 855{ 856 if (rot) { 857 float xf = (float)xy[0] - rot->ox, yf = (float)xy[1] - rot->oy; 858 float xf2, yf2; 859 860 xf2 = rot->c * xf + rot->s * yf; 861 yf2 = - rot->s * xf + rot->c * yf; 862 863 xy[0] = (int)(xf2 + rot->ox + 0.5F); /* round to nearest */ 864 xy[1] = (int)(yf2 + rot->oy + 0.5F); /* round to nearest */ 865 } 866} 867 868#define CUR_TOP 1 869#define CUR_RIGHT 2 870#define CUR_BOTTOM 4 871#define CUR_LEFT 8 872 873static void draw_tile(drawing *dr, game_drawstate *ds, const game_state *state, 874 int x, int y, int tile, int flash_colour, 875 struct rotation *rot, unsigned cedges) 876{ 877 int coords[8]; 878 char str[40]; 879 880 /* 881 * If we've been passed a rotation region but we're drawing a 882 * tile which is outside it, we must draw it normally. This can 883 * occur if we're cleaning up after a completion flash while a 884 * new move is also being made. 885 */ 886 if (rot && (x < rot->cx || y < rot->cy || 887 x >= rot->cx+rot->cw || y >= rot->cy+rot->ch)) 888 rot = NULL; 889 890 if (rot) 891 clip(dr, rot->cx, rot->cy, rot->cw, rot->ch); 892 893 /* 894 * We must draw each side of the tile's highlight separately, 895 * because in some cases (during rotation) they will all need 896 * to be different colours. 897 */ 898 899 /* The centre point is common to all sides. */ 900 coords[4] = x + TILE_SIZE / 2; 901 coords[5] = y + TILE_SIZE / 2; 902 rotate(coords+4, rot); 903 904 /* Right side. */ 905 coords[0] = x + TILE_SIZE - 1; 906 coords[1] = y + TILE_SIZE - 1; 907 rotate(coords+0, rot); 908 coords[2] = x + TILE_SIZE - 1; 909 coords[3] = y; 910 rotate(coords+2, rot); 911 draw_polygon(dr, coords, 3, rot ? rot->rc : COL_LOWLIGHT, 912 rot ? rot->rc : (cedges & CUR_RIGHT) ? COL_LOWCURSOR : COL_LOWLIGHT); 913 914 /* Bottom side. */ 915 coords[2] = x; 916 coords[3] = y + TILE_SIZE - 1; 917 rotate(coords+2, rot); 918 draw_polygon(dr, coords, 3, rot ? rot->bc : COL_LOWLIGHT, 919 rot ? rot->bc : (cedges & CUR_BOTTOM) ? COL_LOWCURSOR : COL_LOWLIGHT); 920 921 /* Left side. */ 922 coords[0] = x; 923 coords[1] = y; 924 rotate(coords+0, rot); 925 draw_polygon(dr, coords, 3, rot ? rot->lc : COL_HIGHLIGHT, 926 rot ? rot->lc : (cedges & CUR_LEFT) ? COL_HIGHCURSOR : COL_HIGHLIGHT); 927 928 /* Top side. */ 929 coords[2] = x + TILE_SIZE - 1; 930 coords[3] = y; 931 rotate(coords+2, rot); 932 draw_polygon(dr, coords, 3, rot ? rot->tc : COL_HIGHLIGHT, 933 rot ? rot->tc : (cedges & CUR_TOP) ? COL_HIGHCURSOR : COL_HIGHLIGHT); 934 935 /* 936 * Now the main blank area in the centre of the tile. 937 */ 938 if (rot) { 939 coords[0] = x + HIGHLIGHT_WIDTH; 940 coords[1] = y + HIGHLIGHT_WIDTH; 941 rotate(coords+0, rot); 942 coords[2] = x + HIGHLIGHT_WIDTH; 943 coords[3] = y + TILE_SIZE - 1 - HIGHLIGHT_WIDTH; 944 rotate(coords+2, rot); 945 coords[4] = x + TILE_SIZE - 1 - HIGHLIGHT_WIDTH; 946 coords[5] = y + TILE_SIZE - 1 - HIGHLIGHT_WIDTH; 947 rotate(coords+4, rot); 948 coords[6] = x + TILE_SIZE - 1 - HIGHLIGHT_WIDTH; 949 coords[7] = y + HIGHLIGHT_WIDTH; 950 rotate(coords+6, rot); 951 draw_polygon(dr, coords, 4, flash_colour, flash_colour); 952 } else { 953 draw_rect(dr, x + HIGHLIGHT_WIDTH, y + HIGHLIGHT_WIDTH, 954 TILE_SIZE - 2*HIGHLIGHT_WIDTH, TILE_SIZE - 2*HIGHLIGHT_WIDTH, 955 flash_colour); 956 } 957 958 /* 959 * Next, the triangles for orientation. 960 */ 961 if (state->orientable) { 962 int xdx, xdy, ydx, ydy; 963 int cx, cy, displ, displ2; 964 switch (tile & 3) { 965 case 0: 966 xdx = 1, xdy = 0; 967 ydx = 0, ydy = 1; 968 break; 969 case 1: 970 xdx = 0, xdy = -1; 971 ydx = 1, ydy = 0; 972 break; 973 case 2: 974 xdx = -1, xdy = 0; 975 ydx = 0, ydy = -1; 976 break; 977 default /* case 3 */: 978 xdx = 0, xdy = 1; 979 ydx = -1, ydy = 0; 980 break; 981 } 982 983 cx = x + TILE_SIZE / 2; 984 cy = y + TILE_SIZE / 2; 985 displ = TILE_SIZE / 2 - HIGHLIGHT_WIDTH - 2; 986 displ2 = TILE_SIZE / 3 - HIGHLIGHT_WIDTH; 987 988 coords[0] = cx - displ * xdx + displ2 * ydx; 989 coords[1] = cy - displ * xdy + displ2 * ydy; 990 rotate(coords+0, rot); 991 coords[2] = cx + displ * xdx + displ2 * ydx; 992 coords[3] = cy + displ * xdy + displ2 * ydy; 993 rotate(coords+2, rot); 994 coords[4] = cx - displ * ydx; 995 coords[5] = cy - displ * ydy; 996 rotate(coords+4, rot); 997 draw_polygon(dr, coords, 3, COL_LOWLIGHT_GENTLE, COL_LOWLIGHT_GENTLE); 998 } 999 1000 coords[0] = x + TILE_SIZE/2; 1001 coords[1] = y + TILE_SIZE/2; 1002 rotate(coords+0, rot); 1003 sprintf(str, "%d", tile / 4); 1004 draw_text(dr, coords[0], coords[1], 1005 FONT_VARIABLE, TILE_SIZE/3, ALIGN_VCENTRE | ALIGN_HCENTRE, 1006 COL_TEXT, str); 1007 1008 if (rot) 1009 unclip(dr); 1010 1011 draw_update(dr, x, y, TILE_SIZE, TILE_SIZE); 1012} 1013 1014static int highlight_colour(float angle) 1015{ 1016 int colours[32] = { 1017 COL_LOWLIGHT, 1018 COL_LOWLIGHT_GENTLE, 1019 COL_LOWLIGHT_GENTLE, 1020 COL_LOWLIGHT_GENTLE, 1021 COL_HIGHLIGHT_GENTLE, 1022 COL_HIGHLIGHT_GENTLE, 1023 COL_HIGHLIGHT_GENTLE, 1024 COL_HIGHLIGHT, 1025 COL_HIGHLIGHT, 1026 COL_HIGHLIGHT, 1027 COL_HIGHLIGHT, 1028 COL_HIGHLIGHT, 1029 COL_HIGHLIGHT, 1030 COL_HIGHLIGHT, 1031 COL_HIGHLIGHT, 1032 COL_HIGHLIGHT, 1033 COL_HIGHLIGHT, 1034 COL_HIGHLIGHT_GENTLE, 1035 COL_HIGHLIGHT_GENTLE, 1036 COL_HIGHLIGHT_GENTLE, 1037 COL_LOWLIGHT_GENTLE, 1038 COL_LOWLIGHT_GENTLE, 1039 COL_LOWLIGHT_GENTLE, 1040 COL_LOWLIGHT, 1041 COL_LOWLIGHT, 1042 COL_LOWLIGHT, 1043 COL_LOWLIGHT, 1044 COL_LOWLIGHT, 1045 COL_LOWLIGHT, 1046 COL_LOWLIGHT, 1047 COL_LOWLIGHT, 1048 COL_LOWLIGHT, 1049 }; 1050 1051 return colours[(int)((angle + 2*(float)PI) / ((float)PI/16)) & 31]; 1052} 1053 1054static float game_anim_length_real(const game_state *oldstate, 1055 const game_state *newstate, int dir, 1056 const game_ui *ui) 1057{ 1058 /* 1059 * Our game_anim_length doesn't need to modify its game_ui, so 1060 * this is the real function which declares ui as const. We must 1061 * wrap this for the backend structure with a version that has ui 1062 * non-const, but we still need this version to call from within 1063 * game_redraw which only has a const ui available. 1064 */ 1065 return (float)(ANIM_PER_BLKSIZE_UNIT * sqrt(newstate->n-1)); 1066} 1067 1068static float game_anim_length(const game_state *oldstate, 1069 const game_state *newstate, int dir, game_ui *ui) 1070{ 1071 return game_anim_length_real(oldstate, newstate, dir, ui); 1072 1073} 1074 1075static float game_flash_length(const game_state *oldstate, 1076 const game_state *newstate, int dir, game_ui *ui) 1077{ 1078 if (!oldstate->completed && newstate->completed && 1079 !oldstate->used_solve && !newstate->used_solve) 1080 return 2 * FLASH_FRAME; 1081 else 1082 return 0.0F; 1083} 1084 1085static void game_get_cursor_location(const game_ui *ui, 1086 const game_drawstate *ds, 1087 const game_state *state, 1088 const game_params *params, 1089 int *x, int *y, int *w, int *h) 1090{ 1091 if(ui->cur_visible) { 1092 *x = COORD(ui->cur_x); 1093 *y = COORD(ui->cur_y); 1094 *w = *h = state->n * TILE_SIZE; 1095 } 1096} 1097 1098static int game_status(const game_state *state) 1099{ 1100 return state->completed ? +1 : 0; 1101} 1102 1103static void game_redraw(drawing *dr, game_drawstate *ds, 1104 const game_state *oldstate, const game_state *state, 1105 int dir, const game_ui *ui, 1106 float animtime, float flashtime) 1107{ 1108 int i, bgcolour; 1109 struct rotation srot, *rot; 1110 int lastx = -1, lasty = -1, lastr = -1; 1111 int cx, cy, n = state->n; 1112 bool cmoved = false; 1113 1114 cx = ui->cur_visible ? ui->cur_x : -state->n; 1115 cy = ui->cur_visible ? ui->cur_y : -state->n; 1116 if (cx != ds->cur_x || cy != ds->cur_y) 1117 cmoved = true; 1118 1119 if (flashtime > 0) { 1120 int frame = (int)(flashtime / FLASH_FRAME); 1121 bgcolour = (frame % 2 ? COL_LOWLIGHT : COL_HIGHLIGHT); 1122 } else 1123 bgcolour = COL_BACKGROUND; 1124 1125 if (!ds->started) { 1126 int coords[10]; 1127 1128 /* 1129 * Recessed area containing the whole puzzle. 1130 */ 1131 coords[0] = COORD(state->w) + HIGHLIGHT_WIDTH - 1; 1132 coords[1] = COORD(state->h) + HIGHLIGHT_WIDTH - 1; 1133 coords[2] = COORD(state->w) + HIGHLIGHT_WIDTH - 1; 1134 coords[3] = COORD(0) - HIGHLIGHT_WIDTH; 1135 coords[4] = coords[2] - TILE_SIZE; 1136 coords[5] = coords[3] + TILE_SIZE; 1137 coords[8] = COORD(0) - HIGHLIGHT_WIDTH; 1138 coords[9] = COORD(state->h) + HIGHLIGHT_WIDTH - 1; 1139 coords[6] = coords[8] + TILE_SIZE; 1140 coords[7] = coords[9] - TILE_SIZE; 1141 draw_polygon(dr, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT); 1142 1143 coords[1] = COORD(0) - HIGHLIGHT_WIDTH; 1144 coords[0] = COORD(0) - HIGHLIGHT_WIDTH; 1145 draw_polygon(dr, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT); 1146 1147 ds->started = true; 1148 } 1149 1150 /* 1151 * If we're drawing any rotated tiles, sort out the rotation 1152 * parameters, and also zap the rotation region to the 1153 * background colour before doing anything else. 1154 */ 1155 if (oldstate) { 1156 float angle; 1157 float anim_max = game_anim_length_real(oldstate, state, dir, ui); 1158 1159 if (dir > 0) { 1160 lastx = state->lastx; 1161 lasty = state->lasty; 1162 lastr = state->lastr; 1163 } else { 1164 lastx = oldstate->lastx; 1165 lasty = oldstate->lasty; 1166 lastr = -oldstate->lastr; 1167 } 1168 1169 rot = &srot; 1170 rot->cx = COORD(lastx); 1171 rot->cy = COORD(lasty); 1172 rot->cw = rot->ch = TILE_SIZE * state->n; 1173 rot->ox = rot->cx + rot->cw/2; 1174 rot->oy = rot->cy + rot->ch/2; 1175 angle = ((-(float)PI/2 * lastr) * (1.0F - animtime / anim_max)); 1176 rot->c = (float)cos(angle); 1177 rot->s = (float)sin(angle); 1178 1179 /* 1180 * Sort out the colours of the various sides of the tile. 1181 */ 1182 rot->lc = highlight_colour((float)PI + angle); 1183 rot->rc = highlight_colour(angle); 1184 rot->tc = highlight_colour((float)(PI/2.0) + angle); 1185 rot->bc = highlight_colour((float)(-PI/2.0) + angle); 1186 1187 draw_rect(dr, rot->cx, rot->cy, rot->cw, rot->ch, bgcolour); 1188 } else 1189 rot = NULL; 1190 1191 /* 1192 * Now draw each tile. 1193 */ 1194 for (i = 0; i < state->w * state->h; i++) { 1195 int t; 1196 bool cc = false; 1197 int tx = i % state->w, ty = i / state->w; 1198 1199 /* 1200 * Figure out what should be displayed at this location. 1201 * Usually it will be state->grid[i], unless we're in the 1202 * middle of animating an actual rotation and this cell is 1203 * within the rotation region, in which case we set -1 1204 * (always display). 1205 */ 1206 if (oldstate && lastx >= 0 && lasty >= 0 && 1207 tx >= lastx && tx < lastx + state->n && 1208 ty >= lasty && ty < lasty + state->n) 1209 t = -1; 1210 else 1211 t = state->grid[i]; 1212 1213 if (cmoved) { 1214 /* cursor has moved (or changed visibility)... */ 1215 if (tx == cx || tx == cx+n-1 || ty == cy || ty == cy+n-1) 1216 cc = true; /* ...we're on new cursor, redraw */ 1217 if (tx == ds->cur_x || tx == ds->cur_x+n-1 || 1218 ty == ds->cur_y || ty == ds->cur_y+n-1) 1219 cc = true; /* ...we were on old cursor, redraw */ 1220 } 1221 1222 if (ds->bgcolour != bgcolour || /* always redraw when flashing */ 1223 ds->grid[i] != t || ds->grid[i] == -1 || t == -1 || cc) { 1224 int x = COORD(tx), y = COORD(ty); 1225 unsigned cedges = 0; 1226 1227 if (tx == cx && ty >= cy && ty <= cy+n-1) cedges |= CUR_LEFT; 1228 if (ty == cy && tx >= cx && tx <= cx+n-1) cedges |= CUR_TOP; 1229 if (tx == cx+n-1 && ty >= cy && ty <= cy+n-1) cedges |= CUR_RIGHT; 1230 if (ty == cy+n-1 && tx >= cx && tx <= cx+n-1) cedges |= CUR_BOTTOM; 1231 1232 draw_tile(dr, ds, state, x, y, state->grid[i], bgcolour, rot, cedges); 1233 ds->grid[i] = t; 1234 } 1235 } 1236 ds->bgcolour = bgcolour; 1237 ds->cur_x = cx; ds->cur_y = cy; 1238 1239 /* 1240 * Update the status bar. 1241 */ 1242 { 1243 char statusbuf[256]; 1244 1245 /* 1246 * Don't show the new status until we're also showing the 1247 * new _state_ - after the game animation is complete. 1248 */ 1249 if (oldstate) 1250 state = oldstate; 1251 1252 if (state->used_solve) 1253 sprintf(statusbuf, "Moves since auto-solve: %d", 1254 state->movecount - state->completed); 1255 else { 1256 sprintf(statusbuf, "%sMoves: %d", 1257 (state->completed ? "COMPLETED! " : ""), 1258 (state->completed ? state->completed : state->movecount)); 1259 if (state->movetarget) 1260 sprintf(statusbuf+strlen(statusbuf), " (target %d)", 1261 state->movetarget); 1262 } 1263 1264 status_bar(dr, statusbuf); 1265 } 1266} 1267 1268#ifdef COMBINED 1269#define thegame twiddle 1270#endif 1271 1272const struct game thegame = { 1273 "Twiddle", "games.twiddle", "twiddle", 1274 default_params, 1275 game_fetch_preset, NULL, 1276 decode_params, 1277 encode_params, 1278 free_params, 1279 dup_params, 1280 true, game_configure, custom_params, 1281 validate_params, 1282 new_game_desc, 1283 validate_desc, 1284 new_game, 1285 dup_game, 1286 free_game, 1287 true, solve_game, 1288 true, game_can_format_as_text_now, game_text_format, 1289 NULL, NULL, /* get_prefs, set_prefs */ 1290 new_ui, 1291 free_ui, 1292 NULL, /* encode_ui */ 1293 NULL, /* decode_ui */ 1294 NULL, /* game_request_keys */ 1295 game_changed_state, 1296 current_key_label, 1297 interpret_move, 1298 execute_move, 1299 PREFERRED_TILE_SIZE, game_compute_size, game_set_size, 1300 game_colours, 1301 game_new_drawstate, 1302 game_free_drawstate, 1303 game_redraw, 1304 game_anim_length, 1305 game_flash_length, 1306 game_get_cursor_location, 1307 game_status, 1308 false, false, NULL, NULL, /* print_size, print */ 1309 true, /* wants_statusbar */ 1310 false, NULL, /* timing_state */ 1311 0, /* flags */ 1312}; 1313 1314/* vim: set shiftwidth=4 tabstop=8: */