A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 1577 lines 50 kB view raw
1/* -*- indent-tabs-mode: nil; tab-width: 1000 -*- */ 2 3/* 4 * palisade.c: Nikoli's `Five Cells' puzzle. 5 * 6 * See http://nikoli.co.jp/en/puzzles/five_cells.html 7 */ 8 9/* TODO: 10 * 11 * - better solver: implement the sketched-out deductions 12 * 13 * - improve the victory flash? 14 * - the LINE_NOs look ugly against COL_FLASH. 15 * - white-blink the edges (instead), a la loopy? 16 */ 17 18#include <assert.h> 19#include <ctype.h> 20#include <limits.h> 21#include <stdarg.h> 22#include <stdio.h> 23#include <stdlib.h> 24#include <string.h> 25 26#include "puzzles.h" 27 28#define setmem(ptr, byte, len) memset((ptr), (byte), (len) * sizeof (ptr)[0]) 29#define scopy(dst, src, len) memcpy((dst), (src), (len) * sizeof (dst)[0]) 30#define dupmem(p, n) memcpy(smalloc(n * sizeof (*p)), p, n * sizeof (*p)) 31#define snewa(ptr, len) (ptr) = smalloc((len) * sizeof (*ptr)) 32#define clone(ptr) (dupmem((ptr), 1)) 33 34static char *string(int n, const char *fmt, ...) 35{ 36 va_list va; 37 char *ret; 38 int m; 39 va_start(va, fmt); 40 m = vsprintf(snewa(ret, n + 1), fmt, va); 41 va_end(va); 42 if (m > n) fatal("memory corruption"); 43 return ret; 44} 45 46enum { 47 PREF_CURSOR_MODE, 48 PREF_CLEAR_COMPLETE_REGIONS, 49 N_PREF_ITEMS 50}; 51 52struct game_params { 53 int w, h, k; 54}; 55 56typedef signed char clue; 57typedef unsigned char borderflag; 58 59typedef struct shared_state { 60 game_params params; 61 clue *clues; 62 int refcount; 63} shared_state; 64 65struct game_state { 66 shared_state *shared; 67 borderflag *borders; /* length w*h */ 68 69 bool completed, cheated; 70}; 71 72#define DEFAULT_PRESET 0 73static struct game_params presets[] = { 74 {5, 5, 5}, {8, 6, 6}, {10, 8, 8}, {15, 12, 10} 75 /* I definitely want 5x5n5 since that gives "Five Cells" its name. 76 * But how about the others? By which criteria do I choose? */ 77}; 78 79static game_params *default_params(void) 80{ 81 return clone(&presets[DEFAULT_PRESET]); 82} 83 84static bool game_fetch_preset(int i, char **name, game_params **params) 85{ 86 if (i < 0 || i >= lenof(presets)) return false; 87 88 *params = clone(&presets[i]); 89 *name = string(60, "%d x %d, regions of size %d", 90 presets[i].w, presets[i].h, presets[i].k); 91 92 return true; 93} 94 95static void free_params(game_params *params) 96{ 97 sfree(params); 98} 99 100static game_params *dup_params(const game_params *params) 101{ 102 return clone(params); 103} 104 105static void decode_params(game_params *params, char const *string) 106{ 107 params->w = params->h = params->k = atoi(string); 108 while (*string && isdigit((unsigned char)*string)) ++string; 109 if (*string == 'x') { 110 params->h = atoi(++string); 111 while (*string && isdigit((unsigned char)*string)) ++string; 112 } 113 if (*string == 'n') params->k = atoi(++string); 114} 115 116static char *encode_params(const game_params *params, bool full) 117{ 118 return string(40, "%dx%dn%d", params->w, params->h, params->k); 119} 120 121#define CONFIG(i, nm, ty, iv, sv) \ 122 (ret[i].name = nm, ret[i].type = ty, ret[i].ival = iv, ret[i].sval = sv) 123 124static config_item *game_configure(const game_params *params) 125{ 126 config_item *ret = snewn(4, config_item); 127 128 ret[0].name = "Width"; 129 ret[0].type = C_STRING; 130 ret[0].u.string.sval = string(20, "%d", params->w); 131 132 ret[1].name = "Height"; 133 ret[1].type = C_STRING; 134 ret[1].u.string.sval = string(20, "%d", params->h); 135 136 ret[2].name = "Region size"; 137 ret[2].type = C_STRING; 138 ret[2].u.string.sval = string(20, "%d", params->k); 139 140 ret[3].name = NULL; 141 ret[3].type = C_END; 142 143 return ret; 144} 145 146static game_params *custom_params(const config_item *cfg) 147{ 148 game_params *params = snew(game_params); 149 150 params->w = atoi(cfg[0].u.string.sval); 151 params->h = atoi(cfg[1].u.string.sval); 152 params->k = atoi(cfg[2].u.string.sval); 153 154 return params; 155} 156 157/* +---+ << The one possible domino (up to symmetry). +---+---+ 158 * | 3 | | 3 | 3 | 159 * | | If two dominos are adjacent as depicted here >> +---+---+ 160 * | 3 | then it's ambiguous whether the edge between | 3 | 3 | 161 * +---+ the dominos is horizontal or vertical. +---+---+ 162 */ 163 164static const char *validate_params(const game_params *params, bool full) 165{ 166 int w = params->w, h = params->h, k = params->k, wh; 167 168 if (k < 1) return "Region size must be at least one"; 169 if (w < 1) return "Width must be at least one"; 170 if (h < 1) return "Height must be at least one"; 171 if (w > INT_MAX / h) 172 return "Width times height must not be unreasonably large"; 173 wh = w * h; 174 if (wh % k) return "Region size must divide grid area"; 175 if (!full) return NULL; /* succeed partial validation */ 176 177 /* MAYBE FIXME: we (just?) don't have the UI for winning these. */ 178 if (k == wh) return "Region size must be less than the grid area"; 179 assert (k < wh); /* or wh % k != 0 */ 180 181 if (k == 2 && w != 1 && h != 1) 182 return "Region size can't be two unless width or height is one"; 183 184 return NULL; /* succeed full validation */ 185} 186 187/* --- Solver ------------------------------------------------------- */ 188 189/* the solver may write at will to these arrays, but shouldn't free them */ 190/* it's up to the client to dup/free as needed */ 191typedef struct solver_ctx { 192 const game_params *params; /* also in shared_state */ 193 clue *clues; /* also in shared_state */ 194 borderflag *borders; /* also in game_state */ 195 DSF *dsf; /* particular to the solver */ 196} solver_ctx; 197 198/* Deductions: 199 * 200 * - If two adjacent clues do not have a border between them, this 201 * gives a lower limit on the size of their region (which is also an 202 * upper limit if both clues are 3). Rule out any non-border which 203 * would make its region either too large or too small. 204 * 205 * - If a clue, k, is adjacent to k borders or (4 - k) non-borders, 206 * the remaining edges incident to the clue are readily decided. 207 * 208 * - If a region has only one other region (e.g. square) to grow into 209 * and it's not of full size yet, grow it into that one region. 210 * 211 * - If two regions are adjacent and their combined size would be too 212 * large, put an edge between them. 213 * 214 * - If a border is adjacent to two non-borders, its last vertex-mate 215 * must also be a border. If a maybe-border is adjacent to three 216 * nonborders, the maybe-border is a non-border. 217 * 218 * - If a clue square is adjacent to several squares belonging to the 219 * same region, and enabling (disabling) those borders would violate 220 * the clue, those borders must be disabled (enabled). 221 * 222 * - If there's a path crossing only non-borders between two squares, 223 * the maybe-border between them is a non-border. 224 * (This is implicitly computed in the dsf representation) 225 */ 226 227/* TODO deductions: 228 * 229 * If a vertex is adjacent to a LINE_YES and (4-3)*LINE_NO, at least 230 * one of the last two edges are LINE_YES. If they're adjacent to a 231 * 1, then the other two edges incident to that 1 are LINE_NO. 232 * 233 * For each square: set all as unknown, then for each k-omino and each 234 * way of placing it on that square, if that way is consistent with 235 * the board, mark its edges and interior as possible LINE_YES and 236 * LINE_NO, respectively. When all k-ominos are through, see what 237 * isn't possible and remove those impossibilities from the board. 238 * (Sounds pretty nasty for k > 4 or so.) 239 * 240 * A black-bordered subregion must have a size divisible by k. So, 241 * draw a graph with one node per dsf component and edges between 242 * those dsf components which have adjacent squares. Identify cut 243 * vertices and edges. If a cut-vertex-delimited component contains a 244 * number of squares not divisible by k, cut vertex not included, then 245 * the cut vertex must belong to the component. If it has exactly one 246 * edge _out_ of the component, the line(s) corresponding to that edge 247 * are all LINE_YES (i.e. a BORDER()). 248 * (This sounds complicated, but visually it is rather easy.) 249 * 250 * [Look at loopy and see how the at-least/-most k out of m edges 251 * thing is done. See how it is propagated across multiple squares.] 252 */ 253 254#define EMPTY (~0) 255 256#define BIT(i) (1 << (i)) 257#define BORDER(i) BIT(i) 258#define BORDER_U BORDER(0) 259#define BORDER_R BORDER(1) 260#define BORDER_D BORDER(2) 261#define BORDER_L BORDER(3) 262#define FLIP(i) ((i) ^ 2) 263#define BORDER_MASK (BORDER_U|BORDER_R|BORDER_D|BORDER_L) 264#define DISABLED(border) ((border) << 4) 265#define UNDISABLED(border) ((border) >> 4) 266 267static const int dx[4] = { 0, +1, 0, -1}; 268static const int dy[4] = {-1, 0, +1, 0}; 269static const int bitcount[16] = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4}; 270/* bitcount[x & BORDER_MASK] == number of enabled borders */ 271 272#define COMPUTE_J (-1) 273 274static void connect(solver_ctx *ctx, int i, int j) 275{ 276 dsf_merge(ctx->dsf, i, j); 277} 278 279static bool connected(solver_ctx *ctx, int i, int j, int dir) 280{ 281 if (j == COMPUTE_J) j = i + dx[dir] + ctx->params->w*dy[dir]; 282 return dsf_equivalent(ctx->dsf, i, j); 283} 284 285static void disconnect(solver_ctx *ctx, int i, int j, int dir) 286{ 287 if (j == COMPUTE_J) j = i + dx[dir] + ctx->params->w*dy[dir]; 288 ctx->borders[i] |= BORDER(dir); 289 ctx->borders[j] |= BORDER(FLIP(dir)); 290} 291 292static bool disconnected(solver_ctx *ctx, int i, int j, int dir) 293{ 294 assert (j == COMPUTE_J || j == i + dx[dir] + ctx->params->w*dy[dir]); 295 return ctx->borders[i] & BORDER(dir); 296} 297 298static bool maybe(solver_ctx *ctx, int i, int j, int dir) 299{ 300 assert (j == COMPUTE_J || j == i + dx[dir] + ctx->params->w*dy[dir]); 301 return !disconnected(ctx, i, j, dir) && !connected(ctx, i, j, dir); 302 /* the ordering is important: disconnected works for invalid 303 * squares (i.e. out of bounds), connected doesn't. */ 304} 305 306static void solver_connected_clues_versus_region_size(solver_ctx *ctx) 307{ 308 int w = ctx->params->w, h = ctx->params->h, wh = w*h, i, dir; 309 310 /* If i is connected to j and i has borders with p of the 311 * remaining three squares and j with q of the remaining three 312 * squares, then the region has size at least 1+(3-p) + 1+(3-q). 313 * If p = q = 3 then the region has size exactly 2. */ 314 315 for (i = 0; i < wh; ++i) { 316 if (ctx->clues[i] == EMPTY) continue; 317 for (dir = 0; dir < 4; ++dir) { 318 int j = i + dx[dir] + w*dy[dir]; 319 if (disconnected(ctx, i, j, dir)) continue; 320 if (ctx->clues[j] == EMPTY) continue; 321 if ((8 - ctx->clues[i] - ctx->clues[j] > ctx->params->k) || 322 (ctx->clues[i] == 3 && ctx->clues[j] == 3 && 323 ctx->params->k != 2)) 324 { 325 disconnect(ctx, i, j, dir); 326 /* changed = true, but this is a one-shot... */ 327 } 328 } 329 } 330} 331 332static bool solver_number_exhausted(solver_ctx *ctx) 333{ 334 int w = ctx->params->w, h = ctx->params->h, wh = w*h, i, dir, off; 335 bool changed = false; 336 337 for (i = 0; i < wh; ++i) { 338 if (ctx->clues[i] == EMPTY) continue; 339 340 if (bitcount[(ctx->borders[i] & BORDER_MASK)] == ctx->clues[i]) { 341 for (dir = 0; dir < 4; ++dir) { 342 int j = i + dx[dir] + w*dy[dir]; 343 if (!maybe(ctx, i, j, dir)) continue; 344 connect(ctx, i, j); 345 changed = true; 346 } 347 continue; 348 } 349 350 for (off = dir = 0; dir < 4; ++dir) { 351 int j = i + dx[dir] + w*dy[dir]; 352 if (!disconnected(ctx, i, j, dir) && connected(ctx, i, j, dir)) 353 ++off; /* ^^^ bounds checking before ^^^^^ */ 354 } 355 356 if (ctx->clues[i] == 4 - off) 357 for (dir = 0; dir < 4; ++dir) { 358 int j = i + dx[dir] + w*dy[dir]; 359 if (!maybe(ctx, i, j, dir)) continue; 360 disconnect(ctx, i, j, dir); 361 changed = true; 362 } 363 } 364 365 return changed; 366} 367 368static bool solver_not_too_big(solver_ctx *ctx) 369{ 370 int w = ctx->params->w, h = ctx->params->h, wh = w*h, i, dir; 371 bool changed = false; 372 373 for (i = 0; i < wh; ++i) { 374 int size = dsf_size(ctx->dsf, i); 375 for (dir = 0; dir < 4; ++dir) { 376 int j = i + dx[dir] + w*dy[dir]; 377 if (!maybe(ctx, i, j, dir)) continue; 378 if (size + dsf_size(ctx->dsf, j) <= ctx->params->k) continue; 379 disconnect(ctx, i, j, dir); 380 changed = true; 381 } 382 } 383 384 return changed; 385} 386 387static bool solver_not_too_small(solver_ctx *ctx) 388{ 389 int w = ctx->params->w, h = ctx->params->h, wh = w*h, i, dir; 390 int *outs, k = ctx->params->k, ci; 391 bool changed = false; 392 393 snewa(outs, wh); 394 setmem(outs, -1, wh); 395 396 for (i = 0; i < wh; ++i) { 397 ci = dsf_canonify(ctx->dsf, i); 398 if (dsf_size(ctx->dsf, ci) == k) continue; 399 for (dir = 0; dir < 4; ++dir) { 400 int j = i + dx[dir] + w*dy[dir]; 401 if (!maybe(ctx, i, j, dir)) continue; 402 if (outs[ci] == -1) outs[ci] = dsf_canonify(ctx->dsf, j); 403 else if (outs[ci] != dsf_canonify(ctx->dsf, j)) outs[ci] = -2; 404 } 405 } 406 407 for (i = 0; i < wh; ++i) { 408 int j = outs[i]; 409 if (i != dsf_canonify(ctx->dsf, i)) continue; 410 if (j < 0) continue; 411 connect(ctx, i, j); /* only one place for i to grow */ 412 changed = true; 413 } 414 415 sfree(outs); 416 return changed; 417} 418 419static bool solver_no_dangling_edges(solver_ctx *ctx) 420{ 421 int w = ctx->params->w, h = ctx->params->h, r, c; 422 bool changed = false; 423 424 /* for each vertex */ 425 for (r = 1; r < h; ++r) 426 for (c = 1; c < w; ++c) { 427 int i = r * w + c, j = i - w - 1, noline = 0, dir; 428 int squares[4], e = -1, f = -1, de = -1, df = -1; 429 430 /* feels hacky: I align these with BORDER_[U0 R1 D2 L3] */ 431 squares[1] = squares[2] = j; 432 squares[0] = squares[3] = i; 433 434 /* for each edge adjacent to the vertex */ 435 for (dir = 0; dir < 4; ++dir) 436 if (!connected(ctx, squares[dir], COMPUTE_J, dir)) { 437 df = dir; 438 f = squares[df]; 439 if (e != -1) continue; 440 e = f; 441 de = df; 442 } else ++noline; 443 444 if (4 - noline == 1) { 445 assert (e != -1); 446 disconnect(ctx, e, COMPUTE_J, de); 447 changed = true; 448 continue; 449 } 450 451 if (4 - noline != 2) continue; 452 453 assert (e != -1); 454 assert (f != -1); 455 456 if (ctx->borders[e] & BORDER(de)) { 457 if (!(ctx->borders[f] & BORDER(df))) { 458 disconnect(ctx, f, COMPUTE_J, df); 459 changed = true; 460 } 461 } else if (ctx->borders[f] & BORDER(df)) { 462 disconnect(ctx, e, COMPUTE_J, de); 463 changed = true; 464 } 465 } 466 467 return changed; 468} 469 470static bool solver_equivalent_edges(solver_ctx *ctx) 471{ 472 int w = ctx->params->w, h = ctx->params->h, wh = w*h, i, dirj; 473 bool changed = false; 474 475 /* if a square is adjacent to two connected squares, the two 476 * borders (i,j) and (i,k) are either both on or both off. */ 477 478 for (i = 0; i < wh; ++i) { 479 int n_on = 0, n_off = 0; 480 if (ctx->clues[i] < 1 || ctx->clues[i] > 3) continue; 481 482 if (ctx->clues[i] == 2 /* don't need it otherwise */) 483 for (dirj = 0; dirj < 4; ++dirj) { 484 int j = i + dx[dirj] + w*dy[dirj]; 485 if (disconnected(ctx, i, j, dirj)) ++n_on; 486 else if (connected(ctx, i, j, dirj)) ++n_off; 487 } 488 489 for (dirj = 0; dirj < 4; ++dirj) { 490 int j = i + dx[dirj] + w*dy[dirj], dirk; 491 if (!maybe(ctx, i, j, dirj)) continue; 492 493 for (dirk = dirj + 1; dirk < 4; ++dirk) { 494 int k = i + dx[dirk] + w*dy[dirk]; 495 if (!maybe(ctx, i, k, dirk)) continue; 496 if (!connected(ctx, j, k, -1)) continue; 497 498 if (n_on + 2 > ctx->clues[i]) { 499 connect(ctx, i, j); 500 connect(ctx, i, k); 501 changed = true; 502 } else if (n_off + 2 > 4 - ctx->clues[i]) { 503 disconnect(ctx, i, j, dirj); 504 disconnect(ctx, i, k, dirk); 505 changed = true; 506 } 507 } 508 } 509 } 510 511 return changed; 512} 513 514/* build connected components in `dsf', along the lines of `borders'. */ 515static void build_dsf(int w, int h, borderflag *border, DSF *dsf, bool black) 516{ 517 int x, y; 518 519 for (y = 0; y < h; y++) { 520 for (x = 0; x < w; x++) { 521 if (x+1 < w && (black ? !(border[y*w+x] & BORDER_R) : 522 (border[y*w+x] & DISABLED(BORDER_R)))) 523 dsf_merge(dsf, y*w+x, y*w+(x+1)); 524 if (y+1 < h && (black ? !(border[y*w+x] & BORDER_D) : 525 (border[y*w+x] & DISABLED(BORDER_D)))) 526 dsf_merge(dsf, y*w+x, (y+1)*w+x); 527 } 528 } 529} 530 531static bool is_solved(const game_params *params, clue *clues, 532 borderflag *border) 533{ 534 int w = params->w, h = params->h, wh = w*h, k = params->k; 535 int i, x, y; 536 DSF *dsf = dsf_new(wh); 537 538 build_dsf(w, h, border, dsf, true); 539 540 /* 541 * A game is solved if: 542 * 543 * - the borders drawn on the grid divide it into connected 544 * components such that every square is in a component of the 545 * correct size 546 * - the borders also satisfy the clue set 547 */ 548 for (i = 0; i < wh; ++i) { 549 if (dsf_size(dsf, i) != k) goto error; 550 if (clues[i] == EMPTY) continue; 551 if (clues[i] != bitcount[border[i] & BORDER_MASK]) goto error; 552 } 553 554 /* 555 * ... and thirdly: 556 * 557 * - there are no *stray* borders, in that every border is 558 * actually part of the division between two components. 559 * Otherwise you could cheat by finding a subdivision which did 560 * not *exceed* any clue square's counter, and then adding a 561 * few extra edges. 562 */ 563 for (y = 0; y < h; y++) { 564 for (x = 0; x < w; x++) { 565 if (x+1 < w && (border[y*w+x] & BORDER_R) && 566 dsf_equivalent(dsf, y*w+x, y*w+(x+1))) 567 goto error; 568 if (y+1 < h && (border[y*w+x] & BORDER_D) && 569 dsf_equivalent(dsf, y*w+x, (y+1)*w+x)) 570 goto error; 571 } 572 } 573 574 dsf_free(dsf); 575 return true; 576 577error: 578 dsf_free(dsf); 579 return false; 580} 581 582static bool solver(const game_params *params, clue *clues, borderflag *borders) 583{ 584 int w = params->w, h = params->h, wh = w*h; 585 bool changed; 586 solver_ctx ctx; 587 588 ctx.params = params; 589 ctx.clues = clues; 590 ctx.borders = borders; 591 ctx.dsf = dsf_new(wh); 592 593 solver_connected_clues_versus_region_size(&ctx); /* idempotent */ 594 do { 595 changed = false; 596 changed |= solver_number_exhausted(&ctx); 597 changed |= solver_not_too_big(&ctx); 598 changed |= solver_not_too_small(&ctx); 599 changed |= solver_no_dangling_edges(&ctx); 600 changed |= solver_equivalent_edges(&ctx); 601 } while (changed); 602 603 dsf_free(ctx.dsf); 604 605 return is_solved(params, clues, borders); 606} 607 608/* --- Generator ---------------------------------------------------- */ 609 610static void init_borders(int w, int h, borderflag *borders) 611{ 612 int r, c; 613 setmem(borders, 0, w*h); 614 for (c = 0; c < w; ++c) { 615 borders[c] |= BORDER_U; 616 borders[w*h-1 - c] |= BORDER_D; 617 } 618 for (r = 0; r < h; ++r) { 619 borders[r*w] |= BORDER_L; 620 borders[w*h-1 - r*w] |= BORDER_R; 621 } 622} 623 624#define OUT_OF_BOUNDS(x, y, w, h) \ 625 ((x) < 0 || (x) >= (w) || (y) < 0 || (y) >= (h)) 626 627#define xshuffle(ptr, len, rs) shuffle((ptr), (len), sizeof (ptr)[0], (rs)) 628 629static char *new_game_desc(const game_params *params, random_state *rs, 630 char **aux, bool interactive) 631{ 632 int w = params->w, h = params->h, wh = w*h, k = params->k; 633 634 clue *numbers = snewn(wh + 1, clue); 635 borderflag *rim = snewn(wh, borderflag); 636 borderflag *scratch_borders = snewn(wh, borderflag); 637 638 char *soln = snewa(*aux, wh + 2); 639 int *shuf = snewn(wh, int); 640 DSF *dsf = NULL; 641 int i, r, c; 642 643 for (i = 0; i < wh; ++i) shuf[i] = i; 644 xshuffle(shuf, wh, rs); 645 646 init_borders(w, h, rim); 647 648 assert (!('@' & BORDER_MASK)); 649 *soln++ = 'S'; 650 soln[wh] = '\0'; 651 652 do { 653 setmem(soln, '@', wh); 654 655 dsf_free(dsf); 656 dsf = divvy_rectangle(w, h, k, rs); 657 658 for (r = 0; r < h; ++r) 659 for (c = 0; c < w; ++c) { 660 int i = r * w + c, dir; 661 numbers[i] = 0; 662 for (dir = 0; dir < 4; ++dir) { 663 int rr = r + dy[dir], cc = c + dx[dir], ii = rr * w + cc; 664 if (OUT_OF_BOUNDS(cc, rr, w, h) || 665 !dsf_equivalent(dsf, i, ii)) { 666 ++numbers[i]; 667 soln[i] |= BORDER(dir); 668 } 669 } 670 } 671 672 scopy(scratch_borders, rim, wh); 673 } while (!solver(params, numbers, scratch_borders)); 674 675 for (i = 0; i < wh; ++i) { 676 int j = shuf[i]; 677 clue copy = numbers[j]; 678 679 scopy(scratch_borders, rim, wh); 680 numbers[j] = EMPTY; /* strip away unnecssary clues */ 681 if (!solver(params, numbers, scratch_borders)) 682 numbers[j] = copy; 683 } 684 685 numbers[wh] = '\0'; 686 687 sfree(scratch_borders); 688 sfree(rim); 689 sfree(shuf); 690 dsf_free(dsf); 691 692 char *output = snewn(wh + 1, char), *p = output; 693 694 r = 0; 695 for (i = 0; i < wh; ++i) { 696 if (numbers[i] != EMPTY) { 697 while (r) { 698 while (r > 26) { 699 *p++ = 'z'; 700 r -= 26; 701 } 702 *p++ = 'a'-1 + r; 703 r = 0; 704 } 705 *p++ = '0' + numbers[i]; 706 } else ++r; 707 } 708 *p++ = '\0'; 709 710 sfree(numbers); 711 return sresize(output, p - output, char); 712} 713 714static const char *validate_desc(const game_params *params, const char *desc) 715{ 716 717 int w = params->w, h = params->h, wh = w*h, squares = 0; 718 719 for (/* nop */; *desc; ++desc) { 720 if (islower((unsigned char)*desc)) { 721 squares += *desc - 'a' + 1; 722 } else if (isdigit((unsigned char)*desc)) { 723 if (*desc > '4') { 724 static char buf[] = "Invalid (too large) number: '5'"; 725 assert (isdigit((unsigned char)buf[lenof(buf) - 3])); 726 buf[lenof(buf) - 3] = *desc; /* ... or 6, 7, 8, 9 :-) */ 727 return buf; 728 } 729 ++squares; 730 } else if (isprint((unsigned char)*desc)) { 731 static char buf[] = "Invalid character in data: '?'"; 732 buf[lenof(buf) - 3] = *desc; 733 return buf; 734 } else return "Invalid (unprintable) character in data"; 735 } 736 737 if (squares > wh) return "Data describes too many squares"; 738 739 return NULL; 740} 741 742static game_state *new_game(midend *me, const game_params *params, 743 const char *desc) 744{ 745 int w = params->w, h = params->h, wh = w*h, i; 746 game_state *state = snew(game_state); 747 748 state->shared = snew(shared_state); 749 state->shared->refcount = 1; 750 state->shared->params = *params; /* struct copy */ 751 snewa(state->shared->clues, wh); 752 753 setmem(state->shared->clues, EMPTY, wh); 754 for (i = 0; *desc; ++desc) { 755 if (isdigit((unsigned char)*desc)) state->shared->clues[i++] = *desc - '0'; 756 else if (isalpha((unsigned char)*desc)) i += *desc - 'a' + 1; 757 } 758 759 snewa(state->borders, wh); 760 init_borders(w, h, state->borders); 761 762 state->completed = (params->k == wh); 763 state->cheated = false; 764 765 return state; 766} 767 768static game_state *dup_game(const game_state *state) 769{ 770 int wh = state->shared->params.w * state->shared->params.h; 771 game_state *ret = snew(game_state); 772 773 ret->borders = dupmem(state->borders, wh); 774 775 ret->shared = state->shared; 776 ++ret->shared->refcount; 777 778 ret->completed = state->completed; 779 ret->cheated = state->cheated; 780 781 return ret; 782} 783 784static void free_game(game_state *state) 785{ 786 if (--state->shared->refcount == 0) { 787 sfree(state->shared->clues); 788 sfree(state->shared); 789 } 790 sfree(state->borders); 791 sfree(state); 792} 793 794static char *solve_game(const game_state *state, const game_state *currstate, 795 const char *aux, const char **error) 796{ 797 int w = state->shared->params.w, h = state->shared->params.h, wh = w*h; 798 borderflag *move; 799 800 if (aux) return dupstr(aux); 801 802 snewa(move, wh + 2); 803 804 move[0] = 'S'; 805 init_borders(w, h, move + 1); 806 move[wh + 1] = '\0'; 807 808 if (solver(&state->shared->params, state->shared->clues, move + 1)) { 809 int i; 810 for (i = 0; i < wh; i++) 811 move[i+1] |= '@'; /* turn into sensible ASCII */ 812 return (char *) move; 813 } 814 815 *error = "Sorry, I can't solve this puzzle"; 816 sfree(move); 817 return NULL; 818 819 { 820 /* compile-time-assert (borderflag is-a-kind-of char). 821 * 822 * depends on zero-size arrays being disallowed. GCC says 823 * ISO C forbids this, pointing to [-Werror=edantic]. Also, 824 * it depends on type-checking of (obviously) dead code. */ 825 borderflag b[sizeof (borderflag) == sizeof (char)]; 826 char c = b[0]; b[0] = c; 827 /* we could at least in principle put this anywhere, but it 828 * seems silly to not put it where the assumption is used. */ 829 } 830} 831 832static bool game_can_format_as_text_now(const game_params *params) 833{ 834 return true; 835} 836 837static char *game_text_format(const game_state *state) 838{ 839 int w = state->shared->params.w, h = state->shared->params.h, r, c; 840 int cw = 4, ch = 2, gw = cw*w + 2, gh = ch * h + 1, len = gw * gh; 841 char *board; 842 843 setmem(snewa(board, len + 1), ' ', len); 844 for (r = 0; r < h; ++r) { 845 for (c = 0; c < w; ++c) { 846 int cell = r*ch*gw + cw*c, center = cell + gw*ch/2 + cw/2; 847 int i = r * w + c, clue = state->shared->clues[i]; 848 849 if (clue != EMPTY) board[center] = '0' + clue; 850 851 board[cell] = '+'; 852 853 if (state->borders[i] & BORDER_U) 854 setmem(board + cell + 1, '-', cw - 1); 855 else if (state->borders[i] & DISABLED(BORDER_U)) 856 board[cell + cw / 2] = 'x'; 857 858 if (state->borders[i] & BORDER_L) 859 board[cell + gw] = '|'; 860 else if (state->borders[i] & DISABLED(BORDER_L)) 861 board[cell + gw] = 'x'; 862 } 863 864 for (c = 0; c < ch; ++c) { 865 board[(r*ch + c)*gw + gw - 2] = c ? '|' : '+'; 866 board[(r*ch + c)*gw + gw - 1] = '\n'; 867 } 868 } 869 870 scopy(board + len - gw, board, gw); 871 board[len] = '\0'; 872 873 return board; 874} 875 876struct game_ui { 877 /* These are half-grid coordinates - (0,0) is the top left corner 878 * of the top left square; (1,1) is the center of the top left 879 * grid square. */ 880 int x, y; 881 bool show; 882 883 bool legacy_cursor; 884 bool clear_complete_regions; 885}; 886 887static game_ui *new_ui(const game_state *state) 888{ 889 game_ui *ui = snew(game_ui); 890 ui->x = ui->y = 1; 891 ui->show = getenv_bool("PUZZLES_SHOW_CURSOR", false); 892 ui->legacy_cursor = false; 893 ui->clear_complete_regions = false; 894 return ui; 895} 896 897static config_item *get_prefs(game_ui *ui) 898{ 899 config_item *cfg; 900 901 cfg = snewn(N_PREF_ITEMS+1, config_item); 902 903 cfg[PREF_CURSOR_MODE].name = "Cursor mode"; 904 cfg[PREF_CURSOR_MODE].kw = "cursor-mode"; 905 cfg[PREF_CURSOR_MODE].type = C_CHOICES; 906 cfg[PREF_CURSOR_MODE].u.choices.choicenames = ":Half-grid:Full-grid"; 907 cfg[PREF_CURSOR_MODE].u.choices.choicekws = ":half:full"; 908 cfg[PREF_CURSOR_MODE].u.choices.selected = ui->legacy_cursor; 909 910 cfg[PREF_CLEAR_COMPLETE_REGIONS].name = 911 "Automatically clear edges in completed regions"; 912 cfg[PREF_CLEAR_COMPLETE_REGIONS].kw = "clear-complete-regions"; 913 cfg[PREF_CLEAR_COMPLETE_REGIONS].type = C_BOOLEAN; 914 cfg[PREF_CLEAR_COMPLETE_REGIONS].u.boolean.bval = 915 ui->clear_complete_regions; 916 917 cfg[N_PREF_ITEMS].name = NULL; 918 cfg[N_PREF_ITEMS].type = C_END; 919 920 return cfg; 921} 922 923static void set_prefs(game_ui *ui, const config_item *cfg) 924{ 925 ui->legacy_cursor = cfg[PREF_CURSOR_MODE].u.choices.selected; 926 ui->clear_complete_regions = 927 cfg[PREF_CLEAR_COMPLETE_REGIONS].u.boolean.bval; 928} 929 930static void free_ui(game_ui *ui) 931{ 932 sfree(ui); 933} 934 935static void game_changed_state(game_ui *ui, const game_state *oldstate, 936 const game_state *newstate) 937{ 938} 939 940typedef int dsflags; 941 942struct game_drawstate { 943 int tilesize; 944 dsflags *grid; 945}; 946 947#define TILESIZE (ds->tilesize) 948#define MARGIN (ds->tilesize / 2) 949#define WIDTH (3*TILESIZE/32 > 1 ? 3*TILESIZE/32 : 1) 950#define CENTER ((ds->tilesize / 2) + WIDTH/2) 951 952#define FROMCOORD(x) (((x) - MARGIN) / TILESIZE) 953 954enum {MAYBE_LEFT, MAYBE_RIGHT, ON_LEFT, ON_RIGHT, OFF_LEFT, OFF_RIGHT}; 955 956static char *interpret_move(const game_state *state, game_ui *ui, 957 const game_drawstate *ds, int x, int y, int button) 958{ 959 int w = state->shared->params.w, h = state->shared->params.h; 960 bool control = button & MOD_CTRL, shift = button & MOD_SHFT; 961 962 button = STRIP_BUTTON_MODIFIERS(button); 963 964 if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { 965 int gx = FROMCOORD(x), gy = FROMCOORD(y), possible = BORDER_MASK; 966 int px = (x - MARGIN) % TILESIZE, py = (y - MARGIN) % TILESIZE; 967 int hx, hy, dir, i; 968 969 if (OUT_OF_BOUNDS(gx, gy, w, h)) return NULL; 970 971 /* find edge closest to click point */ 972 possible &=~ (2*px < TILESIZE ? BORDER_R : BORDER_L); 973 possible &=~ (2*py < TILESIZE ? BORDER_D : BORDER_U); 974 px = min(px, TILESIZE - px); 975 py = min(py, TILESIZE - py); 976 possible &=~ (px < py ? (BORDER_U|BORDER_D) : (BORDER_L|BORDER_R)); 977 978 for (dir = 0; dir < 4 && BORDER(dir) != possible; ++dir); 979 if (dir == 4) return NULL; /* there's not exactly one such edge */ 980 981 ui->x = min(max(2*gx + 1 + dx[dir], 1), 2*w-1); 982 ui->y = min(max(2*gy + 1 + dy[dir], 1), 2*h-1); 983 984 hx = gx + dx[dir]; 985 hy = gy + dy[dir]; 986 987 if (OUT_OF_BOUNDS(hx, hy, w, h)) return NULL; 988 989 ui->show = false; 990 991 i = gy * w + gx; 992 switch ((button == RIGHT_BUTTON) | 993 ((state->borders[i] & BORDER(dir)) >> dir << 1) | 994 ((state->borders[i] & DISABLED(BORDER(dir))) >> dir >> 2)) { 995 996 case MAYBE_LEFT: 997 case ON_LEFT: 998 case ON_RIGHT: 999 return string(80, "F%d,%d,%dF%d,%d,%d", 1000 gx, gy, BORDER(dir), 1001 hx, hy, BORDER(FLIP(dir))); 1002 1003 case MAYBE_RIGHT: 1004 case OFF_LEFT: 1005 case OFF_RIGHT: 1006 return string(80, "F%d,%d,%dF%d,%d,%d", 1007 gx, gy, DISABLED(BORDER(dir)), 1008 hx, hy, DISABLED(BORDER(FLIP(dir)))); 1009 } 1010 } 1011 1012 if (IS_CURSOR_MOVE(button)) { 1013 if(ui->legacy_cursor && (control || shift)) { 1014 borderflag flag = 0, newflag; 1015 int dir, i = (ui->y/2) * w + (ui->x/2); 1016 ui->show = true; 1017 x = ui->x/2; 1018 y = ui->y/2; 1019 move_cursor(button, &x, &y, w, h, false, NULL); 1020 if (OUT_OF_BOUNDS(x, y, w, h)) return NULL; 1021 1022 for (dir = 0; dir < 4; ++dir) 1023 if (dx[dir] == x - ui->x/2 && dy[dir] == y - ui->y/2) break; 1024 if (dir == 4) return NULL; /* how the ... ?! */ 1025 1026 if (control) flag |= BORDER(dir); 1027 if (shift) flag |= DISABLED(BORDER(dir)); 1028 1029 newflag = state->borders[i] ^ flag; 1030 if (newflag & BORDER(dir) && newflag & DISABLED(BORDER(dir))) 1031 return NULL; 1032 1033 newflag = 0; 1034 if (control) newflag |= BORDER(FLIP(dir)); 1035 if (shift) newflag |= DISABLED(BORDER(FLIP(dir))); 1036 return string(80, "F%d,%d,%dF%d,%d,%d", 1037 ui->x/2, ui->y/2, flag, x, y, newflag); 1038 } else { 1039 /* TODO: Refactor this and other half-grid cursor games 1040 * (Tracks, etc.) */ 1041 int dx = (button == CURSOR_LEFT) ? -1 : ((button == CURSOR_RIGHT) ? +1 : 0); 1042 int dy = (button == CURSOR_DOWN) ? +1 : ((button == CURSOR_UP) ? -1 : 0); 1043 1044 if(ui->legacy_cursor) { 1045 dx *= 2; dy *= 2; 1046 1047 ui->x |= 1; 1048 ui->y |= 1; 1049 } 1050 1051 if (!ui->show) { 1052 ui->show = true; 1053 } 1054 1055 ui->x = min(max(ui->x + dx, 1), 2*w-1); 1056 ui->y = min(max(ui->y + dy, 1), 2*h-1); 1057 1058 return MOVE_UI_UPDATE; 1059 } 1060 } else if (IS_CURSOR_SELECT(button)) { 1061 int px = ui->x % 2, py = ui->y % 2; 1062 int gx = ui->x / 2, gy = ui->y / 2; 1063 int dir = (px == 0) ? 3 : 0; /* left = 3; up = 0 */ 1064 int hx = gx + dx[dir]; 1065 int hy = gy + dy[dir]; 1066 1067 int i = gy * w + gx; 1068 1069 if(!ui->show) { 1070 ui->show = true; 1071 return MOVE_UI_UPDATE; 1072 } 1073 1074 /* clicks on square corners and centers do nothing */ 1075 if (px == py) 1076 return MOVE_NO_EFFECT; 1077 1078 /* TODO: Refactor this and the mouse click handling code 1079 * above. */ 1080 switch ((button == CURSOR_SELECT2) | 1081 ((state->borders[i] & BORDER(dir)) >> dir << 1) | 1082 ((state->borders[i] & DISABLED(BORDER(dir))) >> dir >> 2)) { 1083 1084 case MAYBE_LEFT: 1085 case ON_LEFT: 1086 case ON_RIGHT: 1087 return string(80, "F%d,%d,%dF%d,%d,%d", 1088 gx, gy, BORDER(dir), 1089 hx, hy, BORDER(FLIP(dir))); 1090 1091 case MAYBE_RIGHT: 1092 case OFF_LEFT: 1093 case OFF_RIGHT: 1094 return string(80, "F%d,%d,%dF%d,%d,%d", 1095 gx, gy, DISABLED(BORDER(dir)), 1096 hx, hy, DISABLED(BORDER(FLIP(dir)))); 1097 } 1098 } 1099 1100 return NULL; 1101} 1102 1103static game_state *execute_move(const game_state *state, const char *move) 1104{ 1105 int w = state->shared->params.w, h = state->shared->params.h, wh = w * h; 1106 game_state *ret = dup_game(state); 1107 int nchars, x, y, flag, i; 1108 1109 if (*move == 'S') { 1110 ++move; 1111 for (i = 0; i < wh && move[i]; ++i) 1112 ret->borders[i] = 1113 (move[i] & BORDER_MASK) | DISABLED(~move[i] & BORDER_MASK); 1114 if (i < wh || move[i]) goto badmove; 1115 ret->cheated = ret->completed = true; 1116 return ret; 1117 } 1118 1119 while (sscanf(move, "F%d,%d,%d%n", &x, &y, &flag, &nchars) == 3 && 1120 !OUT_OF_BOUNDS(x, y, w, h)) { 1121 move += nchars; 1122 for (i = 0; i < 4; i++) 1123 if ((flag & BORDER(i)) && 1124 OUT_OF_BOUNDS(x+dx[i], y+dy[i], w, h)) 1125 /* No toggling the borders of the grid! */ 1126 goto badmove; 1127 ret->borders[y*w + x] ^= flag; 1128 } 1129 1130 if (*move) goto badmove; 1131 1132 if (!ret->completed) 1133 ret->completed = is_solved(&ret->shared->params, ret->shared->clues, 1134 ret->borders); 1135 1136 return ret; 1137 1138 badmove: 1139 free_game(ret); 1140 return NULL; 1141} 1142 1143/* --- Drawing routines --------------------------------------------- */ 1144 1145static void game_compute_size(const game_params *params, int tilesize, 1146 const game_ui *ui, int *x, int *y) 1147{ 1148 /* Ick: fake up `ds->tilesize' for macro expansion purposes */ 1149 struct { int tilesize; } ads, *ds = &ads; 1150 ads.tilesize = tilesize; 1151 1152 *x = params->w * tilesize + WIDTH + 2 * MARGIN; 1153 *y = params->h * tilesize + WIDTH + 2 * MARGIN; 1154} 1155 1156static void game_set_size(drawing *dr, game_drawstate *ds, 1157 const game_params *params, int tilesize) 1158{ 1159 ds->tilesize = tilesize; 1160} 1161 1162enum { 1163 COL_BACKGROUND, 1164 COL_FLASH, 1165 COL_GRID, 1166 COL_CLUE = COL_GRID, 1167 COL_LINE_YES = COL_GRID, 1168 COL_LINE_MAYBE, 1169 COL_LINE_NO, 1170 COL_ERROR, 1171 1172 NCOLOURS 1173}; 1174 1175#define COLOUR(i, r, g, b) \ 1176 ((ret[3*(i)+0] = (r)), (ret[3*(i)+1] = (g)), (ret[3*(i)+2] = (b))) 1177#define DARKER 0.9F 1178 1179static float *game_colours(frontend *fe, int *ncolours) 1180{ 1181 float *ret = snewn(3 * NCOLOURS, float); 1182 1183 game_mkhighlight(fe, ret, COL_BACKGROUND, -1, COL_FLASH); 1184 1185 COLOUR(COL_GRID, 0.0F, 0.0F, 0.0F); /* black */ 1186 COLOUR(COL_ERROR, 1.0F, 0.0F, 0.0F); /* red */ 1187 1188 COLOUR(COL_LINE_MAYBE, /* yellow */ 1189 ret[COL_BACKGROUND*3 + 0] * DARKER, 1190 ret[COL_BACKGROUND*3 + 1] * DARKER, 1191 0.0F); 1192 1193 COLOUR(COL_LINE_NO, 1194 ret[COL_BACKGROUND*3 + 0] * DARKER, 1195 ret[COL_BACKGROUND*3 + 1] * DARKER, 1196 ret[COL_BACKGROUND*3 + 2] * DARKER); 1197 1198 *ncolours = NCOLOURS; 1199 return ret; 1200} 1201#undef COLOUR 1202 1203#define BORDER_ERROR(x) ((x) << 8) 1204#define F_ERROR_U BORDER_ERROR(BORDER_U) /* BIT( 8) */ 1205#define F_ERROR_R BORDER_ERROR(BORDER_R) /* BIT( 9) */ 1206#define F_ERROR_D BORDER_ERROR(BORDER_D) /* BIT(10) */ 1207#define F_ERROR_L BORDER_ERROR(BORDER_L) /* BIT(11) */ 1208#define F_ERROR_CLUE BIT(12) 1209#define F_FLASH BIT(13) 1210#define CONTAINS_CURSOR(x) ((x) << 14) 1211 1212static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) 1213{ 1214 struct game_drawstate *ds = snew(struct game_drawstate); 1215 1216 ds->tilesize = 0; 1217 ds->grid = NULL; 1218 1219 return ds; 1220} 1221 1222static void game_free_drawstate(drawing *dr, game_drawstate *ds) 1223{ 1224 sfree(ds->grid); 1225 sfree(ds); 1226} 1227 1228#define COLOUR(border) \ 1229 (flags & BORDER_ERROR((border)) ? COL_ERROR : \ 1230 flags & (border) ? COL_LINE_YES : \ 1231 flags & DISABLED((border)) ? COL_LINE_NO : \ 1232 COL_LINE_MAYBE) 1233 1234static void draw_tile(drawing *dr, game_drawstate *ds, int r, int c, 1235 dsflags flags, int clue) 1236{ 1237 int x = MARGIN + TILESIZE * c, y = MARGIN + TILESIZE * r; 1238 1239 clip(dr, x, y, TILESIZE + WIDTH, TILESIZE + WIDTH); /* { */ 1240 1241 draw_rect(dr, x + WIDTH, y + WIDTH, TILESIZE - WIDTH, TILESIZE - WIDTH, 1242 (flags & F_FLASH ? COL_FLASH : COL_BACKGROUND)); 1243 1244 if (clue != EMPTY) { 1245 char buf[2]; 1246 buf[0] = '0' + clue; 1247 buf[1] = '\0'; 1248 draw_text(dr, x + CENTER, y + CENTER, FONT_VARIABLE, 1249 TILESIZE / 2, ALIGN_VCENTRE | ALIGN_HCENTRE, 1250 (flags & F_ERROR_CLUE ? COL_ERROR : COL_CLUE), buf); 1251 } 1252 1253 1254#define ts TILESIZE 1255#define w WIDTH 1256 draw_rect(dr, x + w, y, ts - w, w, COLOUR(BORDER_U)); 1257 draw_rect(dr, x + ts, y + w, w, ts - w, COLOUR(BORDER_R)); 1258 draw_rect(dr, x + w, y + ts, ts - w, w, COLOUR(BORDER_D)); 1259 draw_rect(dr, x, y + w, w, ts - w, COLOUR(BORDER_L)); 1260#undef ts 1261#undef w 1262 1263 unclip(dr); /* } */ 1264 draw_update(dr, x, y, TILESIZE + WIDTH, TILESIZE + WIDTH); 1265} 1266 1267static void get_cursor_location( 1268 const game_drawstate *ds, int cur_x, int cur_y, bool legacy_cursor, 1269 int *center_x, int *center_y, int *w, int *h, bool *corners) 1270{ 1271 int off_x = cur_x % 2, off_y = cur_y % 2; 1272 1273 /* Figure out the tile coordinates corresponding to these cursor 1274 * coordinates. */ 1275 int x = MARGIN + TILESIZE * (cur_x / 2), 1276 y = MARGIN + TILESIZE * (cur_y / 2); 1277 1278 /* off_x and off_y are either 0 or 1. The possible cases are 1279 * therefore: 1280 * 1281 * (0, 0): the cursor is in the top left corner of the tile. 1282 * (0, 1): the cursor is on the left border of the tile. 1283 * (1, 0): the cursor is on the top border of the tile. 1284 * (1, 1): the cursor is in the center of the tile. 1285 */ 1286 enum { 1287 TOP_LEFT_CORNER, LEFT_BORDER, TOP_BORDER, TILE_CENTER 1288 } cur_type = (off_x << 1) + off_y; 1289 1290 *center_x = x + ((off_x == 0) ? WIDTH/2 : CENTER); 1291 *center_y = y + ((off_y == 0) ? WIDTH/2 : CENTER); 1292 1293 struct { int w, h; } cursor_dimensions[] = { 1294 { TILESIZE / 3, TILESIZE / 3 }, /* top left corner */ 1295 { TILESIZE / 3, 2 * TILESIZE / 3}, /* left border */ 1296 { 2 * TILESIZE / 3, TILESIZE / 3}, /* top border */ 1297 { 2 * TILESIZE / 3, 2 * TILESIZE / 3 } /* center */ 1298 }, *dims = cursor_dimensions + cur_type; 1299 1300 *w = dims->w; 1301 *h = dims->h; 1302 *corners = legacy_cursor && cur_type == TILE_CENTER; 1303} 1304 1305static void draw_cursor(drawing *dr, const game_drawstate *ds, 1306 int cur_x, int cur_y, bool legacy_cursor) { 1307 int center_x, center_y, w, h; 1308 bool corners; 1309 get_cursor_location(ds, cur_x, cur_y, legacy_cursor, &center_x, &center_y, 1310 &w, &h, &corners); 1311 1312 if(corners) 1313 draw_rect_corners(dr, center_x, center_y, TILESIZE / 3, COL_GRID); 1314 else 1315 draw_rect_outline(dr, center_x - w / 2, center_y - h / 2, 1316 w, h, COL_GRID); 1317 1318 draw_update(dr, center_x - w / 2, center_y - h / 2, w, h); 1319} 1320 1321#define FLASH_TIME 0.7F 1322 1323static void game_redraw(drawing *dr, game_drawstate *ds, 1324 const game_state *oldstate, const game_state *state, 1325 int dir, const game_ui *ui, 1326 float animtime, float flashtime) 1327{ 1328 int w = state->shared->params.w, h = state->shared->params.h, wh = w*h; 1329 int r, c, flash = ((int) (flashtime * 5 / FLASH_TIME)) % 2; 1330 DSF *black_border_dsf = dsf_new(wh), *yellow_border_dsf = dsf_new(wh); 1331 int k = state->shared->params.k; 1332 1333 if (!ds->grid) { 1334 char buf[40]; 1335 int bgw = w * ds->tilesize + WIDTH + 2 * MARGIN, 1336 bgh = h * ds->tilesize + WIDTH + 2 * MARGIN; 1337 1338 for (r = 0; r <= h; ++r) 1339 for (c = 0; c <= w; ++c) 1340 draw_rect(dr, MARGIN + TILESIZE * c, MARGIN + TILESIZE * r, 1341 WIDTH, WIDTH, COL_GRID); 1342 draw_update(dr, 0, 0, bgw, bgh); 1343 1344 snewa(ds->grid, wh); 1345 setmem(ds->grid, ~0, wh); 1346 1347 sprintf(buf, "Region size: %d", state->shared->params.k); 1348 status_bar(dr, buf); 1349 } 1350 1351 build_dsf(w, h, state->borders, black_border_dsf, true); 1352 build_dsf(w, h, state->borders, yellow_border_dsf, false); 1353 1354 for (r = 0; r < h; ++r) 1355 for (c = 0; c < w; ++c) { 1356 int i = r * w + c, clue = state->shared->clues[i], flags, dir; 1357 int on = bitcount[state->borders[i] & BORDER_MASK]; 1358 int off = bitcount[(state->borders[i] >> 4) & BORDER_MASK]; 1359 1360 flags = state->borders[i]; 1361 1362 if (flash) flags |= F_FLASH; 1363 1364 if (clue != EMPTY && (on > clue || clue > 4 - off)) 1365 flags |= F_ERROR_CLUE; 1366 1367 if (ui->show) { 1368 int u, v; 1369 for(u = 0; u < 3; u++) 1370 for(v = 0; v < 3; v++) 1371 if(ui->x == 2*c+u && ui->y == 2*r+v) 1372 flags |= CONTAINS_CURSOR(BIT(3*u+v)); 1373 } 1374 1375 /* border errors */ 1376 for (dir = 0; dir < 4; ++dir) { 1377 int rr = r + dy[dir], cc = c + dx[dir], ii = rr * w + cc; 1378 1379 if (OUT_OF_BOUNDS(cc, rr, w, h)) continue; 1380 1381 /* we draw each border twice, except the outermost 1382 * big border, so we have to check for errors on 1383 * both sides of each border.*/ 1384 if (/* region too large */ 1385 ((dsf_size(yellow_border_dsf, i) > k || 1386 dsf_size(yellow_border_dsf, ii) > k) && 1387 (dsf_canonify(yellow_border_dsf, i) != 1388 dsf_canonify(yellow_border_dsf, ii))) 1389 1390 || 1391 /* region too small */ 1392 ((dsf_size(black_border_dsf, i) < k || 1393 dsf_size(black_border_dsf, ii) < k) && 1394 dsf_canonify(black_border_dsf, i) != 1395 dsf_canonify(black_border_dsf, ii)) 1396 1397 || 1398 /* dangling borders within a single region */ 1399 ((state->borders[i] & BORDER(dir)) && 1400 /* we know it's a single region because there's a 1401 * path crossing no border from i to ii... */ 1402 (dsf_canonify(yellow_border_dsf, i) == 1403 dsf_canonify(yellow_border_dsf, ii) || 1404 /* or because any such border would be an error */ 1405 (dsf_size(black_border_dsf, i) <= k && 1406 dsf_canonify(black_border_dsf, i) == 1407 dsf_canonify(black_border_dsf, ii))))) 1408 1409 flags |= BORDER_ERROR(BORDER(dir)); 1410 } 1411 1412 if (ui->clear_complete_regions && 1413 dsf_size(black_border_dsf, i) == k) 1414 flags |= BORDER_MASK << 4; 1415 1416 if (flags == ds->grid[i]) continue; 1417 ds->grid[i] = flags; 1418 draw_tile(dr, ds, r, c, ds->grid[i], clue); 1419 } 1420 1421 if (ui->show) 1422 draw_cursor(dr, ds, ui->x, ui->y, ui->legacy_cursor); 1423 1424 dsf_free(black_border_dsf); 1425 dsf_free(yellow_border_dsf); 1426} 1427 1428static float game_anim_length(const game_state *oldstate, 1429 const game_state *newstate, 1430 int dir, game_ui *ui) 1431{ 1432 return 0.0F; 1433} 1434 1435static float game_flash_length(const game_state *oldstate, 1436 const game_state *newstate, 1437 int dir, game_ui *ui) 1438{ 1439 if (newstate->completed && !newstate->cheated && !oldstate->completed) 1440 return FLASH_TIME; 1441 return 0.0F; 1442} 1443 1444static void game_get_cursor_location(const game_ui *ui, 1445 const game_drawstate *ds, 1446 const game_state *state, 1447 const game_params *params, 1448 int *x, int *y, int *w, int *h) 1449{ 1450 if(ui->show) { 1451 int center_x, center_y; 1452 bool corners; 1453 get_cursor_location(ds, ui->x, ui->y, ui->legacy_cursor, 1454 &center_x, &center_y, w, h, &corners); 1455 *x = center_x - *w / 2; 1456 *y = center_y - *h / 2; 1457 } 1458} 1459 1460static int game_status(const game_state *state) 1461{ 1462 return state->completed ? +1 : 0; 1463} 1464 1465static void game_print_size(const game_params *params, const game_ui *ui, 1466 float *x, float *y) 1467{ 1468 int pw, ph; 1469 1470 game_compute_size(params, 700, ui, &pw, &ph); /* 7mm, like loopy */ 1471 1472 *x = pw / 100.0F; 1473 *y = ph / 100.0F; 1474} 1475 1476static void print_line(drawing *dr, int x1, int y1, int x2, int y2, 1477 int colour, bool full) 1478{ 1479 if (!full) { 1480 int i, subdivisions = 8; 1481 for (i = 1; i < subdivisions; ++i) { 1482 int x = (x1 * (subdivisions - i) + x2 * i) / subdivisions; 1483 int y = (y1 * (subdivisions - i) + y2 * i) / subdivisions; 1484 draw_circle(dr, x, y, 3, colour, colour); 1485 } 1486 } else draw_line(dr, x1, y1, x2, y2, colour); 1487} 1488 1489static void game_print(drawing *dr, const game_state *state, const game_ui *ui, 1490 int tilesize) 1491{ 1492 int w = state->shared->params.w, h = state->shared->params.h; 1493 int ink = print_mono_colour(dr, 0); 1494 game_drawstate for_tilesize_macros, *ds = &for_tilesize_macros; 1495 int r, c; 1496 1497 ds->tilesize = tilesize; 1498 1499 for (r = 0; r < h; ++r) 1500 for (c = 0; c < w; ++c) { 1501 int x = MARGIN + TILESIZE * c, y = MARGIN + TILESIZE * r; 1502 int i = r * w + c, clue = state->shared->clues[i]; 1503 1504 if (clue != EMPTY) { 1505 char buf[2]; 1506 buf[0] = '0' + clue; 1507 buf[1] = '\0'; 1508 draw_text(dr, x + CENTER, y + CENTER, FONT_VARIABLE, 1509 TILESIZE / 2, ALIGN_VCENTRE | ALIGN_HCENTRE, 1510 ink, buf); 1511 } 1512 1513#define ts TILESIZE 1514#define FULL(DIR) (state->borders[i] & (BORDER_ ## DIR)) 1515 print_line(dr, x, y, x + ts, y, ink, FULL(U)); 1516 print_line(dr, x + ts, y, x + ts, y + ts, ink, FULL(R)); 1517 print_line(dr, x, y + ts, x + ts, y + ts, ink, FULL(D)); 1518 print_line(dr, x, y, x, y + ts, ink, FULL(L)); 1519#undef ts 1520#undef FULL 1521 } 1522 1523 for (r = 1; r < h; ++r) 1524 for (c = 1; c < w; ++c) { 1525 int j = r * w + c, i = j - 1 - w; 1526 int x = MARGIN + TILESIZE * c, y = MARGIN + TILESIZE * r; 1527 if (state->borders[i] & (BORDER_D|BORDER_R)) continue; 1528 if (state->borders[j] & (BORDER_U|BORDER_L)) continue; 1529 draw_circle(dr, x, y, 3, ink, ink); 1530 } 1531} 1532 1533#ifdef COMBINED 1534#define thegame palisade 1535#endif 1536 1537const struct game thegame = { 1538 "Palisade", "games.palisade", "palisade", 1539 default_params, 1540 game_fetch_preset, NULL, 1541 decode_params, 1542 encode_params, 1543 free_params, 1544 dup_params, 1545 true, game_configure, custom_params, 1546 validate_params, 1547 new_game_desc, 1548 validate_desc, 1549 new_game, 1550 dup_game, 1551 free_game, 1552 true, solve_game, 1553 true, game_can_format_as_text_now, game_text_format, 1554 get_prefs, set_prefs, /* get_prefs, set_prefs */ 1555 new_ui, 1556 free_ui, 1557 NULL, /* encode_ui */ 1558 NULL, /* decode_ui */ 1559 NULL, /* game_request_keys */ 1560 game_changed_state, 1561 NULL, /* current_key_label */ 1562 interpret_move, 1563 execute_move, 1564 48, game_compute_size, game_set_size, 1565 game_colours, 1566 game_new_drawstate, 1567 game_free_drawstate, 1568 game_redraw, 1569 game_anim_length, 1570 game_flash_length, 1571 game_get_cursor_location, 1572 game_status, 1573 true, false, game_print_size, game_print, 1574 true, /* wants_statusbar */ 1575 false, NULL, /* timing_state */ 1576 0, /* flags */ 1577};