diff options
Diffstat (limited to 'apps/plugins/puzzles/src/palisade.c')
-rw-r--r-- | apps/plugins/puzzles/src/palisade.c | 328 |
1 files changed, 220 insertions, 108 deletions
diff --git a/apps/plugins/puzzles/src/palisade.c b/apps/plugins/puzzles/src/palisade.c index e941661a0e..ecbbbb4d6f 100644 --- a/apps/plugins/puzzles/src/palisade.c +++ b/apps/plugins/puzzles/src/palisade.c | |||
@@ -17,6 +17,7 @@ | |||
17 | 17 | ||
18 | #include <assert.h> | 18 | #include <assert.h> |
19 | #include <ctype.h> | 19 | #include <ctype.h> |
20 | #include <limits.h> | ||
20 | #include <stdarg.h> | 21 | #include <stdarg.h> |
21 | #include <stdio.h> | 22 | #include <stdio.h> |
22 | #include <stdlib.h> | 23 | #include <stdlib.h> |
@@ -46,7 +47,7 @@ struct game_params { | |||
46 | int w, h, k; | 47 | int w, h, k; |
47 | }; | 48 | }; |
48 | 49 | ||
49 | typedef char clue; | 50 | typedef signed char clue; |
50 | typedef unsigned char borderflag; | 51 | typedef unsigned char borderflag; |
51 | 52 | ||
52 | typedef struct shared_state { | 53 | typedef struct shared_state { |
@@ -156,13 +157,15 @@ static game_params *custom_params(const config_item *cfg) | |||
156 | 157 | ||
157 | static const char *validate_params(const game_params *params, bool full) | 158 | static const char *validate_params(const game_params *params, bool full) |
158 | { | 159 | { |
159 | int w = params->w, h = params->h, k = params->k, wh = w * h; | 160 | int w = params->w, h = params->h, k = params->k, wh; |
160 | 161 | ||
161 | if (k < 1) return "Region size must be at least one"; | 162 | if (k < 1) return "Region size must be at least one"; |
162 | if (w < 1) return "Width must be at least one"; | 163 | if (w < 1) return "Width must be at least one"; |
163 | if (h < 1) return "Height must be at least one"; | 164 | if (h < 1) return "Height must be at least one"; |
165 | if (w > INT_MAX / h) | ||
166 | return "Width times height must not be unreasonably large"; | ||
167 | wh = w * h; | ||
164 | if (wh % k) return "Region size must divide grid area"; | 168 | if (wh % k) return "Region size must divide grid area"; |
165 | |||
166 | if (!full) return NULL; /* succeed partial validation */ | 169 | if (!full) return NULL; /* succeed partial validation */ |
167 | 170 | ||
168 | /* MAYBE FIXME: we (just?) don't have the UI for winning these. */ | 171 | /* MAYBE FIXME: we (just?) don't have the UI for winning these. */ |
@@ -183,7 +186,7 @@ typedef struct solver_ctx { | |||
183 | const game_params *params; /* also in shared_state */ | 186 | const game_params *params; /* also in shared_state */ |
184 | clue *clues; /* also in shared_state */ | 187 | clue *clues; /* also in shared_state */ |
185 | borderflag *borders; /* also in game_state */ | 188 | borderflag *borders; /* also in game_state */ |
186 | int *dsf; /* particular to the solver */ | 189 | DSF *dsf; /* particular to the solver */ |
187 | } solver_ctx; | 190 | } solver_ctx; |
188 | 191 | ||
189 | /* Deductions: | 192 | /* Deductions: |
@@ -270,7 +273,7 @@ static void connect(solver_ctx *ctx, int i, int j) | |||
270 | static bool connected(solver_ctx *ctx, int i, int j, int dir) | 273 | static bool connected(solver_ctx *ctx, int i, int j, int dir) |
271 | { | 274 | { |
272 | if (j == COMPUTE_J) j = i + dx[dir] + ctx->params->w*dy[dir]; | 275 | if (j == COMPUTE_J) j = i + dx[dir] + ctx->params->w*dy[dir]; |
273 | return dsf_canonify(ctx->dsf, i) == dsf_canonify(ctx->dsf, j); | 276 | return dsf_equivalent(ctx->dsf, i, j); |
274 | } | 277 | } |
275 | 278 | ||
276 | static void disconnect(solver_ctx *ctx, int i, int j, int dir) | 279 | static void disconnect(solver_ctx *ctx, int i, int j, int dir) |
@@ -502,19 +505,20 @@ static bool solver_equivalent_edges(solver_ctx *ctx) | |||
502 | return changed; | 505 | return changed; |
503 | } | 506 | } |
504 | 507 | ||
505 | #define UNVISITED 6 | ||
506 | |||
507 | /* build connected components in `dsf', along the lines of `borders'. */ | 508 | /* build connected components in `dsf', along the lines of `borders'. */ |
508 | static void dfs_dsf(int i, int w, borderflag *border, int *dsf, bool black) | 509 | static void build_dsf(int w, int h, borderflag *border, DSF *dsf, bool black) |
509 | { | 510 | { |
510 | int dir; | 511 | int x, y; |
511 | for (dir = 0; dir < 4; ++dir) { | 512 | |
512 | int ii = i + dx[dir] + w*dy[dir], bdir = BORDER(dir); | 513 | for (y = 0; y < h; y++) { |
513 | if (black ? (border[i] & bdir) : !(border[i] & DISABLED(bdir))) | 514 | for (x = 0; x < w; x++) { |
514 | continue; | 515 | if (x+1 < w && (black ? !(border[y*w+x] & BORDER_R) : |
515 | if (dsf[ii] != UNVISITED) continue; | 516 | (border[y*w+x] & DISABLED(BORDER_R)))) |
516 | dsf_merge(dsf, i, ii); | 517 | dsf_merge(dsf, y*w+x, y*w+(x+1)); |
517 | dfs_dsf(ii, w, border, dsf, black); | 518 | if (y+1 < h && (black ? !(border[y*w+x] & BORDER_D) : |
519 | (border[y*w+x] & DISABLED(BORDER_D)))) | ||
520 | dsf_merge(dsf, y*w+x, (y+1)*w+x); | ||
521 | } | ||
518 | } | 522 | } |
519 | } | 523 | } |
520 | 524 | ||
@@ -523,9 +527,9 @@ static bool is_solved(const game_params *params, clue *clues, | |||
523 | { | 527 | { |
524 | int w = params->w, h = params->h, wh = w*h, k = params->k; | 528 | int w = params->w, h = params->h, wh = w*h, k = params->k; |
525 | int i, x, y; | 529 | int i, x, y; |
526 | int *dsf = snew_dsf(wh); | 530 | DSF *dsf = dsf_new(wh); |
527 | 531 | ||
528 | assert (dsf[0] == UNVISITED); /* check: UNVISITED and dsf.c match up */ | 532 | build_dsf(w, h, border, dsf, true); |
529 | 533 | ||
530 | /* | 534 | /* |
531 | * A game is solved if: | 535 | * A game is solved if: |
@@ -536,7 +540,6 @@ static bool is_solved(const game_params *params, clue *clues, | |||
536 | * - the borders also satisfy the clue set | 540 | * - the borders also satisfy the clue set |
537 | */ | 541 | */ |
538 | for (i = 0; i < wh; ++i) { | 542 | for (i = 0; i < wh; ++i) { |
539 | if (dsf[i] == UNVISITED) dfs_dsf(i, params->w, border, dsf, true); | ||
540 | if (dsf_size(dsf, i) != k) goto error; | 543 | if (dsf_size(dsf, i) != k) goto error; |
541 | if (clues[i] == EMPTY) continue; | 544 | if (clues[i] == EMPTY) continue; |
542 | if (clues[i] != bitcount[border[i] & BORDER_MASK]) goto error; | 545 | if (clues[i] != bitcount[border[i] & BORDER_MASK]) goto error; |
@@ -554,19 +557,19 @@ static bool is_solved(const game_params *params, clue *clues, | |||
554 | for (y = 0; y < h; y++) { | 557 | for (y = 0; y < h; y++) { |
555 | for (x = 0; x < w; x++) { | 558 | for (x = 0; x < w; x++) { |
556 | if (x+1 < w && (border[y*w+x] & BORDER_R) && | 559 | if (x+1 < w && (border[y*w+x] & BORDER_R) && |
557 | dsf_canonify(dsf, y*w+x) == dsf_canonify(dsf, y*w+(x+1))) | 560 | dsf_equivalent(dsf, y*w+x, y*w+(x+1))) |
558 | goto error; | 561 | goto error; |
559 | if (y+1 < h && (border[y*w+x] & BORDER_D) && | 562 | if (y+1 < h && (border[y*w+x] & BORDER_D) && |
560 | dsf_canonify(dsf, y*w+x) == dsf_canonify(dsf, (y+1)*w+x)) | 563 | dsf_equivalent(dsf, y*w+x, (y+1)*w+x)) |
561 | goto error; | 564 | goto error; |
562 | } | 565 | } |
563 | } | 566 | } |
564 | 567 | ||
565 | sfree(dsf); | 568 | dsf_free(dsf); |
566 | return true; | 569 | return true; |
567 | 570 | ||
568 | error: | 571 | error: |
569 | sfree(dsf); | 572 | dsf_free(dsf); |
570 | return false; | 573 | return false; |
571 | } | 574 | } |
572 | 575 | ||
@@ -579,7 +582,7 @@ static bool solver(const game_params *params, clue *clues, borderflag *borders) | |||
579 | ctx.params = params; | 582 | ctx.params = params; |
580 | ctx.clues = clues; | 583 | ctx.clues = clues; |
581 | ctx.borders = borders; | 584 | ctx.borders = borders; |
582 | ctx.dsf = snew_dsf(wh); | 585 | ctx.dsf = dsf_new(wh); |
583 | 586 | ||
584 | solver_connected_clues_versus_region_size(&ctx); /* idempotent */ | 587 | solver_connected_clues_versus_region_size(&ctx); /* idempotent */ |
585 | do { | 588 | do { |
@@ -591,7 +594,7 @@ static bool solver(const game_params *params, clue *clues, borderflag *borders) | |||
591 | changed |= solver_equivalent_edges(&ctx); | 594 | changed |= solver_equivalent_edges(&ctx); |
592 | } while (changed); | 595 | } while (changed); |
593 | 596 | ||
594 | sfree(ctx.dsf); | 597 | dsf_free(ctx.dsf); |
595 | 598 | ||
596 | return is_solved(params, clues, borders); | 599 | return is_solved(params, clues, borders); |
597 | } | 600 | } |
@@ -622,15 +625,14 @@ static char *new_game_desc(const game_params *params, random_state *rs, | |||
622 | { | 625 | { |
623 | int w = params->w, h = params->h, wh = w*h, k = params->k; | 626 | int w = params->w, h = params->h, wh = w*h, k = params->k; |
624 | 627 | ||
625 | clue *numbers = snewn(wh + 1, clue), *p; | 628 | clue *numbers = snewn(wh + 1, clue); |
626 | borderflag *rim = snewn(wh, borderflag); | 629 | borderflag *rim = snewn(wh, borderflag); |
627 | borderflag *scratch_borders = snewn(wh, borderflag); | 630 | borderflag *scratch_borders = snewn(wh, borderflag); |
628 | 631 | ||
629 | char *soln = snewa(*aux, wh + 2); | 632 | char *soln = snewa(*aux, wh + 2); |
630 | int *shuf = snewn(wh, int); | 633 | int *shuf = snewn(wh, int); |
631 | int *dsf = NULL, i, r, c; | 634 | DSF *dsf = NULL; |
632 | 635 | int i, r, c; | |
633 | int attempts = 0; | ||
634 | 636 | ||
635 | for (i = 0; i < wh; ++i) shuf[i] = i; | 637 | for (i = 0; i < wh; ++i) shuf[i] = i; |
636 | xshuffle(shuf, wh, rs); | 638 | xshuffle(shuf, wh, rs); |
@@ -642,10 +644,9 @@ static char *new_game_desc(const game_params *params, random_state *rs, | |||
642 | soln[wh] = '\0'; | 644 | soln[wh] = '\0'; |
643 | 645 | ||
644 | do { | 646 | do { |
645 | ++attempts; | ||
646 | setmem(soln, '@', wh); | 647 | setmem(soln, '@', wh); |
647 | 648 | ||
648 | sfree(dsf); | 649 | dsf_free(dsf); |
649 | dsf = divvy_rectangle(w, h, k, rs); | 650 | dsf = divvy_rectangle(w, h, k, rs); |
650 | 651 | ||
651 | for (r = 0; r < h; ++r) | 652 | for (r = 0; r < h; ++r) |
@@ -655,7 +656,7 @@ static char *new_game_desc(const game_params *params, random_state *rs, | |||
655 | for (dir = 0; dir < 4; ++dir) { | 656 | for (dir = 0; dir < 4; ++dir) { |
656 | int rr = r + dy[dir], cc = c + dx[dir], ii = rr * w + cc; | 657 | int rr = r + dy[dir], cc = c + dx[dir], ii = rr * w + cc; |
657 | if (OUT_OF_BOUNDS(cc, rr, w, h) || | 658 | if (OUT_OF_BOUNDS(cc, rr, w, h) || |
658 | dsf_canonify(dsf, i) != dsf_canonify(dsf, ii)) { | 659 | !dsf_equivalent(dsf, i, ii)) { |
659 | ++numbers[i]; | 660 | ++numbers[i]; |
660 | soln[i] |= BORDER(dir); | 661 | soln[i] |= BORDER(dir); |
661 | } | 662 | } |
@@ -680,9 +681,10 @@ static char *new_game_desc(const game_params *params, random_state *rs, | |||
680 | sfree(scratch_borders); | 681 | sfree(scratch_borders); |
681 | sfree(rim); | 682 | sfree(rim); |
682 | sfree(shuf); | 683 | sfree(shuf); |
683 | sfree(dsf); | 684 | dsf_free(dsf); |
685 | |||
686 | char *output = snewn(wh + 1, char), *p = output; | ||
684 | 687 | ||
685 | p = numbers; | ||
686 | r = 0; | 688 | r = 0; |
687 | for (i = 0; i < wh; ++i) { | 689 | for (i = 0; i < wh; ++i) { |
688 | if (numbers[i] != EMPTY) { | 690 | if (numbers[i] != EMPTY) { |
@@ -699,7 +701,8 @@ static char *new_game_desc(const game_params *params, random_state *rs, | |||
699 | } | 701 | } |
700 | *p++ = '\0'; | 702 | *p++ = '\0'; |
701 | 703 | ||
702 | return sresize(numbers, p - numbers, clue); | 704 | sfree(numbers); |
705 | return sresize(output, p - output, char); | ||
703 | } | 706 | } |
704 | 707 | ||
705 | static const char *validate_desc(const game_params *params, const char *desc) | 708 | static const char *validate_desc(const game_params *params, const char *desc) |
@@ -865,32 +868,51 @@ static char *game_text_format(const game_state *state) | |||
865 | } | 868 | } |
866 | 869 | ||
867 | struct game_ui { | 870 | struct game_ui { |
871 | /* These are half-grid coordinates - (0,0) is the top left corner | ||
872 | * of the top left square; (1,1) is the center of the top left | ||
873 | * grid square. */ | ||
868 | int x, y; | 874 | int x, y; |
869 | bool show; | 875 | bool show; |
870 | bool fake_ctrl, fake_shift; | 876 | |
877 | bool legacy_cursor; | ||
871 | }; | 878 | }; |
872 | 879 | ||
873 | static game_ui *new_ui(const game_state *state) | 880 | static game_ui *new_ui(const game_state *state) |
874 | { | 881 | { |
875 | game_ui *ui = snew(game_ui); | 882 | game_ui *ui = snew(game_ui); |
876 | ui->x = ui->y = 0; | 883 | ui->x = ui->y = 1; |
877 | ui->show = ui->fake_ctrl = ui->fake_shift = false; | 884 | ui->show = getenv_bool("PUZZLES_SHOW_CURSOR", false); |
885 | ui->legacy_cursor = false; | ||
878 | return ui; | 886 | return ui; |
879 | } | 887 | } |
880 | 888 | ||
881 | static void free_ui(game_ui *ui) | 889 | static config_item *get_prefs(game_ui *ui) |
882 | { | 890 | { |
883 | sfree(ui); | 891 | config_item *cfg; |
892 | |||
893 | cfg = snewn(2, config_item); | ||
894 | |||
895 | cfg[0].name = "Cursor mode"; | ||
896 | cfg[0].kw = "cursor-mode"; | ||
897 | cfg[0].type = C_CHOICES; | ||
898 | cfg[0].u.choices.choicenames = ":Half-grid:Full-grid"; | ||
899 | cfg[0].u.choices.choicekws = ":half:full"; | ||
900 | cfg[0].u.choices.selected = ui->legacy_cursor; | ||
901 | |||
902 | cfg[1].name = NULL; | ||
903 | cfg[1].type = C_END; | ||
904 | |||
905 | return cfg; | ||
884 | } | 906 | } |
885 | 907 | ||
886 | static char *encode_ui(const game_ui *ui) | 908 | static void set_prefs(game_ui *ui, const config_item *cfg) |
887 | { | 909 | { |
888 | return NULL; | 910 | ui->legacy_cursor = cfg[0].u.choices.selected; |
889 | } | 911 | } |
890 | 912 | ||
891 | static void decode_ui(game_ui *ui, const char *encoding) | 913 | static void free_ui(game_ui *ui) |
892 | { | 914 | { |
893 | assert (encoding == NULL); | 915 | sfree(ui); |
894 | } | 916 | } |
895 | 917 | ||
896 | static void game_changed_state(game_ui *ui, const game_state *oldstate, | 918 | static void game_changed_state(game_ui *ui, const game_state *oldstate, |
@@ -898,7 +920,7 @@ static void game_changed_state(game_ui *ui, const game_state *oldstate, | |||
898 | { | 920 | { |
899 | } | 921 | } |
900 | 922 | ||
901 | typedef unsigned short dsflags; | 923 | typedef int dsflags; |
902 | 924 | ||
903 | struct game_drawstate { | 925 | struct game_drawstate { |
904 | int tilesize; | 926 | int tilesize; |
@@ -907,7 +929,7 @@ struct game_drawstate { | |||
907 | 929 | ||
908 | #define TILESIZE (ds->tilesize) | 930 | #define TILESIZE (ds->tilesize) |
909 | #define MARGIN (ds->tilesize / 2) | 931 | #define MARGIN (ds->tilesize / 2) |
910 | #define WIDTH (1 + (TILESIZE >= 16) + (TILESIZE >= 32) + (TILESIZE >= 64)) | 932 | #define WIDTH (3*TILESIZE/32 > 1 ? 3*TILESIZE/32 : 1) |
911 | #define CENTER ((ds->tilesize / 2) + WIDTH/2) | 933 | #define CENTER ((ds->tilesize / 2) + WIDTH/2) |
912 | 934 | ||
913 | #define FROMCOORD(x) (((x) - MARGIN) / TILESIZE) | 935 | #define FROMCOORD(x) (((x) - MARGIN) / TILESIZE) |
@@ -918,12 +940,9 @@ static char *interpret_move(const game_state *state, game_ui *ui, | |||
918 | const game_drawstate *ds, int x, int y, int button) | 940 | const game_drawstate *ds, int x, int y, int button) |
919 | { | 941 | { |
920 | int w = state->shared->params.w, h = state->shared->params.h; | 942 | int w = state->shared->params.w, h = state->shared->params.h; |
921 | bool control = (button & MOD_CTRL) | ui->fake_ctrl, shift = (button & MOD_SHFT) | ui->fake_shift; | 943 | bool control = button & MOD_CTRL, shift = button & MOD_SHFT; |
922 | |||
923 | /* reset */ | ||
924 | ui->fake_ctrl = ui->fake_shift = false; | ||
925 | 944 | ||
926 | button &= ~MOD_MASK; | 945 | button = STRIP_BUTTON_MODIFIERS(button); |
927 | 946 | ||
928 | if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { | 947 | if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { |
929 | int gx = FROMCOORD(x), gy = FROMCOORD(y), possible = BORDER_MASK; | 948 | int gx = FROMCOORD(x), gy = FROMCOORD(y), possible = BORDER_MASK; |
@@ -932,9 +951,6 @@ static char *interpret_move(const game_state *state, game_ui *ui, | |||
932 | 951 | ||
933 | if (OUT_OF_BOUNDS(gx, gy, w, h)) return NULL; | 952 | if (OUT_OF_BOUNDS(gx, gy, w, h)) return NULL; |
934 | 953 | ||
935 | ui->x = gx; | ||
936 | ui->y = gy; | ||
937 | |||
938 | /* find edge closest to click point */ | 954 | /* find edge closest to click point */ |
939 | possible &=~ (2*px < TILESIZE ? BORDER_R : BORDER_L); | 955 | possible &=~ (2*px < TILESIZE ? BORDER_R : BORDER_L); |
940 | possible &=~ (2*py < TILESIZE ? BORDER_D : BORDER_U); | 956 | possible &=~ (2*py < TILESIZE ? BORDER_D : BORDER_U); |
@@ -945,6 +961,9 @@ static char *interpret_move(const game_state *state, game_ui *ui, | |||
945 | for (dir = 0; dir < 4 && BORDER(dir) != possible; ++dir); | 961 | for (dir = 0; dir < 4 && BORDER(dir) != possible; ++dir); |
946 | if (dir == 4) return NULL; /* there's not exactly one such edge */ | 962 | if (dir == 4) return NULL; /* there's not exactly one such edge */ |
947 | 963 | ||
964 | ui->x = min(max(2*gx + 1 + dx[dir], 1), 2*w-1); | ||
965 | ui->y = min(max(2*gy + 1 + dy[dir], 1), 2*h-1); | ||
966 | |||
948 | hx = gx + dx[dir]; | 967 | hx = gx + dx[dir]; |
949 | hy = gy + dy[dir]; | 968 | hy = gy + dy[dir]; |
950 | 969 | ||
@@ -974,17 +993,17 @@ static char *interpret_move(const game_state *state, game_ui *ui, | |||
974 | } | 993 | } |
975 | 994 | ||
976 | if (IS_CURSOR_MOVE(button)) { | 995 | if (IS_CURSOR_MOVE(button)) { |
977 | ui->show = true; | 996 | if(ui->legacy_cursor && (control || shift)) { |
978 | if (control || shift) { | ||
979 | borderflag flag = 0, newflag; | 997 | borderflag flag = 0, newflag; |
980 | int dir, i = ui->y * w + ui->x; | 998 | int dir, i = (ui->y/2) * w + (ui->x/2); |
981 | x = ui->x; | 999 | ui->show = true; |
982 | y = ui->y; | 1000 | x = ui->x/2; |
983 | move_cursor(button, &x, &y, w, h, false); | 1001 | y = ui->y/2; |
1002 | move_cursor(button, &x, &y, w, h, false, NULL); | ||
984 | if (OUT_OF_BOUNDS(x, y, w, h)) return NULL; | 1003 | if (OUT_OF_BOUNDS(x, y, w, h)) return NULL; |
985 | 1004 | ||
986 | for (dir = 0; dir < 4; ++dir) | 1005 | for (dir = 0; dir < 4; ++dir) |
987 | if (dx[dir] == x - ui->x && dy[dir] == y - ui->y) break; | 1006 | if (dx[dir] == x - ui->x/2 && dy[dir] == y - ui->y/2) break; |
988 | if (dir == 4) return NULL; /* how the ... ?! */ | 1007 | if (dir == 4) return NULL; /* how the ... ?! */ |
989 | 1008 | ||
990 | if (control) flag |= BORDER(dir); | 1009 | if (control) flag |= BORDER(dir); |
@@ -998,21 +1017,67 @@ static char *interpret_move(const game_state *state, game_ui *ui, | |||
998 | if (control) newflag |= BORDER(FLIP(dir)); | 1017 | if (control) newflag |= BORDER(FLIP(dir)); |
999 | if (shift) newflag |= DISABLED(BORDER(FLIP(dir))); | 1018 | if (shift) newflag |= DISABLED(BORDER(FLIP(dir))); |
1000 | return string(80, "F%d,%d,%dF%d,%d,%d", | 1019 | return string(80, "F%d,%d,%dF%d,%d,%d", |
1001 | ui->x, ui->y, flag, x, y, newflag); | 1020 | ui->x/2, ui->y/2, flag, x, y, newflag); |
1002 | } else { | 1021 | } else { |
1003 | move_cursor(button, &ui->x, &ui->y, w, h, false); | 1022 | /* TODO: Refactor this and other half-grid cursor games |
1004 | return UI_UPDATE; | 1023 | * (Tracks, etc.) */ |
1024 | int dx = (button == CURSOR_LEFT) ? -1 : ((button == CURSOR_RIGHT) ? +1 : 0); | ||
1025 | int dy = (button == CURSOR_DOWN) ? +1 : ((button == CURSOR_UP) ? -1 : 0); | ||
1026 | |||
1027 | if(ui->legacy_cursor) { | ||
1028 | dx *= 2; dy *= 2; | ||
1029 | |||
1030 | ui->x |= 1; | ||
1031 | ui->y |= 1; | ||
1032 | } | ||
1033 | |||
1034 | if (!ui->show) { | ||
1035 | ui->show = true; | ||
1036 | } | ||
1037 | |||
1038 | ui->x = min(max(ui->x + dx, 1), 2*w-1); | ||
1039 | ui->y = min(max(ui->y + dy, 1), 2*h-1); | ||
1040 | |||
1041 | return MOVE_UI_UPDATE; | ||
1042 | } | ||
1043 | } else if (IS_CURSOR_SELECT(button)) { | ||
1044 | int px = ui->x % 2, py = ui->y % 2; | ||
1045 | int gx = ui->x / 2, gy = ui->y / 2; | ||
1046 | int dir = (px == 0) ? 3 : 0; /* left = 3; up = 0 */ | ||
1047 | int hx = gx + dx[dir]; | ||
1048 | int hy = gy + dy[dir]; | ||
1049 | |||
1050 | int i = gy * w + gx; | ||
1051 | |||
1052 | if(!ui->show) { | ||
1053 | ui->show = true; | ||
1054 | return MOVE_UI_UPDATE; | ||
1055 | } | ||
1056 | |||
1057 | /* clicks on square corners and centers do nothing */ | ||
1058 | if (px == py) | ||
1059 | return MOVE_NO_EFFECT; | ||
1060 | |||
1061 | /* TODO: Refactor this and the mouse click handling code | ||
1062 | * above. */ | ||
1063 | switch ((button == CURSOR_SELECT2) | | ||
1064 | ((state->borders[i] & BORDER(dir)) >> dir << 1) | | ||
1065 | ((state->borders[i] & DISABLED(BORDER(dir))) >> dir >> 2)) { | ||
1066 | |||
1067 | case MAYBE_LEFT: | ||
1068 | case ON_LEFT: | ||
1069 | case ON_RIGHT: | ||
1070 | return string(80, "F%d,%d,%dF%d,%d,%d", | ||
1071 | gx, gy, BORDER(dir), | ||
1072 | hx, hy, BORDER(FLIP(dir))); | ||
1073 | |||
1074 | case MAYBE_RIGHT: | ||
1075 | case OFF_LEFT: | ||
1076 | case OFF_RIGHT: | ||
1077 | return string(80, "F%d,%d,%dF%d,%d,%d", | ||
1078 | gx, gy, DISABLED(BORDER(dir)), | ||
1079 | hx, hy, DISABLED(BORDER(FLIP(dir)))); | ||
1005 | } | 1080 | } |
1006 | } | ||
1007 | else if(IS_CURSOR_SELECT(button)) { | ||
1008 | /* CURSOR_SELECT or CURSOR_SELECT2 tells us to toggle whether | ||
1009 | * the button press should be interpreted as having CTRL or | ||
1010 | * shift pressed along with it, respectively. */ | ||
1011 | ui->show = true; | ||
1012 | if(button == CURSOR_SELECT2) | ||
1013 | ui->fake_shift = !ui->fake_shift; | ||
1014 | else | ||
1015 | ui->fake_ctrl = !ui->fake_ctrl; | ||
1016 | } | 1081 | } |
1017 | 1082 | ||
1018 | return NULL; | 1083 | return NULL; |
@@ -1022,15 +1087,14 @@ static game_state *execute_move(const game_state *state, const char *move) | |||
1022 | { | 1087 | { |
1023 | int w = state->shared->params.w, h = state->shared->params.h, wh = w * h; | 1088 | int w = state->shared->params.w, h = state->shared->params.h, wh = w * h; |
1024 | game_state *ret = dup_game(state); | 1089 | game_state *ret = dup_game(state); |
1025 | int nchars, x, y, flag; | 1090 | int nchars, x, y, flag, i; |
1026 | 1091 | ||
1027 | if (*move == 'S') { | 1092 | if (*move == 'S') { |
1028 | int i; | ||
1029 | ++move; | 1093 | ++move; |
1030 | for (i = 0; i < wh && move[i]; ++i) | 1094 | for (i = 0; i < wh && move[i]; ++i) |
1031 | ret->borders[i] = | 1095 | ret->borders[i] = |
1032 | (move[i] & BORDER_MASK) | DISABLED(~move[i] & BORDER_MASK); | 1096 | (move[i] & BORDER_MASK) | DISABLED(~move[i] & BORDER_MASK); |
1033 | if (i < wh || move[i]) return NULL; /* leaks `ret', then we die */ | 1097 | if (i < wh || move[i]) goto badmove; |
1034 | ret->cheated = ret->completed = true; | 1098 | ret->cheated = ret->completed = true; |
1035 | return ret; | 1099 | return ret; |
1036 | } | 1100 | } |
@@ -1038,22 +1102,31 @@ static game_state *execute_move(const game_state *state, const char *move) | |||
1038 | while (sscanf(move, "F%d,%d,%d%n", &x, &y, &flag, &nchars) == 3 && | 1102 | while (sscanf(move, "F%d,%d,%d%n", &x, &y, &flag, &nchars) == 3 && |
1039 | !OUT_OF_BOUNDS(x, y, w, h)) { | 1103 | !OUT_OF_BOUNDS(x, y, w, h)) { |
1040 | move += nchars; | 1104 | move += nchars; |
1105 | for (i = 0; i < 4; i++) | ||
1106 | if ((flag & BORDER(i)) && | ||
1107 | OUT_OF_BOUNDS(x+dx[i], y+dy[i], w, h)) | ||
1108 | /* No toggling the borders of the grid! */ | ||
1109 | goto badmove; | ||
1041 | ret->borders[y*w + x] ^= flag; | 1110 | ret->borders[y*w + x] ^= flag; |
1042 | } | 1111 | } |
1043 | 1112 | ||
1044 | if (*move) return NULL; /* leaks `ret', then we die */ | 1113 | if (*move) goto badmove; |
1045 | 1114 | ||
1046 | if (!ret->completed) | 1115 | if (!ret->completed) |
1047 | ret->completed = is_solved(&ret->shared->params, ret->shared->clues, | 1116 | ret->completed = is_solved(&ret->shared->params, ret->shared->clues, |
1048 | ret->borders); | 1117 | ret->borders); |
1049 | 1118 | ||
1050 | return ret; | 1119 | return ret; |
1120 | |||
1121 | badmove: | ||
1122 | free_game(ret); | ||
1123 | return NULL; | ||
1051 | } | 1124 | } |
1052 | 1125 | ||
1053 | /* --- Drawing routines --------------------------------------------- */ | 1126 | /* --- Drawing routines --------------------------------------------- */ |
1054 | 1127 | ||
1055 | static void game_compute_size(const game_params *params, int tilesize, | 1128 | static void game_compute_size(const game_params *params, int tilesize, |
1056 | int *x, int *y) | 1129 | const game_ui *ui, int *x, int *y) |
1057 | { | 1130 | { |
1058 | *x = (params->w + 1) * tilesize; | 1131 | *x = (params->w + 1) * tilesize; |
1059 | *y = (params->h + 1) * tilesize; | 1132 | *y = (params->h + 1) * tilesize; |
@@ -1113,7 +1186,7 @@ static float *game_colours(frontend *fe, int *ncolours) | |||
1113 | #define F_ERROR_L BORDER_ERROR(BORDER_L) /* BIT(11) */ | 1186 | #define F_ERROR_L BORDER_ERROR(BORDER_L) /* BIT(11) */ |
1114 | #define F_ERROR_CLUE BIT(12) | 1187 | #define F_ERROR_CLUE BIT(12) |
1115 | #define F_FLASH BIT(13) | 1188 | #define F_FLASH BIT(13) |
1116 | #define F_CURSOR BIT(14) | 1189 | #define CONTAINS_CURSOR(x) ((x) << 14) |
1117 | 1190 | ||
1118 | static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) | 1191 | static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) |
1119 | { | 1192 | { |
@@ -1147,9 +1220,6 @@ static void draw_tile(drawing *dr, game_drawstate *ds, int r, int c, | |||
1147 | draw_rect(dr, x + WIDTH, y + WIDTH, TILESIZE - WIDTH, TILESIZE - WIDTH, | 1220 | draw_rect(dr, x + WIDTH, y + WIDTH, TILESIZE - WIDTH, TILESIZE - WIDTH, |
1148 | (flags & F_FLASH ? COL_FLASH : COL_BACKGROUND)); | 1221 | (flags & F_FLASH ? COL_FLASH : COL_BACKGROUND)); |
1149 | 1222 | ||
1150 | if (flags & F_CURSOR) | ||
1151 | draw_rect_corners(dr, x + CENTER, y + CENTER, TILESIZE / 3, COL_GRID); | ||
1152 | |||
1153 | if (clue != EMPTY) { | 1223 | if (clue != EMPTY) { |
1154 | char buf[2]; | 1224 | char buf[2]; |
1155 | buf[0] = '0' + clue; | 1225 | buf[0] = '0' + clue; |
@@ -1173,6 +1243,47 @@ static void draw_tile(drawing *dr, game_drawstate *ds, int r, int c, | |||
1173 | draw_update(dr, x, y, TILESIZE + WIDTH, TILESIZE + WIDTH); | 1243 | draw_update(dr, x, y, TILESIZE + WIDTH, TILESIZE + WIDTH); |
1174 | } | 1244 | } |
1175 | 1245 | ||
1246 | static void draw_cursor(drawing *dr, game_drawstate *ds, | ||
1247 | int cur_x, int cur_y, bool legacy_cursor) | ||
1248 | { | ||
1249 | int off_x = cur_x % 2, off_y = cur_y % 2; | ||
1250 | |||
1251 | /* Figure out the tile coordinates corresponding to these cursor | ||
1252 | * coordinates. */ | ||
1253 | int x = MARGIN + TILESIZE * (cur_x / 2), y = MARGIN + TILESIZE * (cur_y / 2); | ||
1254 | |||
1255 | /* off_x and off_y are either 0 or 1. The possible cases are | ||
1256 | * therefore: | ||
1257 | * | ||
1258 | * (0, 0): the cursor is in the top left corner of the tile. | ||
1259 | * (0, 1): the cursor is on the left border of the tile. | ||
1260 | * (1, 0): the cursor is on the top border of the tile. | ||
1261 | * (1, 1): the cursor is in the center of the tile. | ||
1262 | */ | ||
1263 | enum { TOP_LEFT_CORNER, LEFT_BORDER, TOP_BORDER, TILE_CENTER } cur_type = (off_x << 1) + off_y; | ||
1264 | |||
1265 | int center_x = x + ((off_x == 0) ? WIDTH/2 : CENTER), | ||
1266 | center_y = y + ((off_y == 0) ? WIDTH/2 : CENTER); | ||
1267 | |||
1268 | struct { int w, h; } cursor_dimensions[] = { | ||
1269 | { TILESIZE / 3, TILESIZE / 3 }, /* top left corner */ | ||
1270 | { TILESIZE / 3, 2 * TILESIZE / 3}, /* left border */ | ||
1271 | { 2 * TILESIZE / 3, TILESIZE / 3}, /* top border */ | ||
1272 | { 2 * TILESIZE / 3, 2 * TILESIZE / 3 } /* center */ | ||
1273 | }, *dims = cursor_dimensions + cur_type; | ||
1274 | |||
1275 | if(legacy_cursor && cur_type == TILE_CENTER) | ||
1276 | draw_rect_corners(dr, center_x, center_y, TILESIZE / 3, COL_GRID); | ||
1277 | else | ||
1278 | draw_rect_outline(dr, | ||
1279 | center_x - dims->w / 2, center_y - dims->h / 2, | ||
1280 | dims->w, dims->h, COL_GRID); | ||
1281 | |||
1282 | draw_update(dr, | ||
1283 | center_x - dims->w / 2, center_y - dims->h / 2, | ||
1284 | dims->w, dims->h); | ||
1285 | } | ||
1286 | |||
1176 | #define FLASH_TIME 0.7F | 1287 | #define FLASH_TIME 0.7F |
1177 | 1288 | ||
1178 | static void game_redraw(drawing *dr, game_drawstate *ds, | 1289 | static void game_redraw(drawing *dr, game_drawstate *ds, |
@@ -1181,14 +1292,13 @@ static void game_redraw(drawing *dr, game_drawstate *ds, | |||
1181 | float animtime, float flashtime) | 1292 | float animtime, float flashtime) |
1182 | { | 1293 | { |
1183 | int w = state->shared->params.w, h = state->shared->params.h, wh = w*h; | 1294 | int w = state->shared->params.w, h = state->shared->params.h, wh = w*h; |
1184 | int r, c, i, flash = ((int) (flashtime * 5 / FLASH_TIME)) % 2; | 1295 | int r, c, flash = ((int) (flashtime * 5 / FLASH_TIME)) % 2; |
1185 | int *black_border_dsf = snew_dsf(wh), *yellow_border_dsf = snew_dsf(wh); | 1296 | DSF *black_border_dsf = dsf_new(wh), *yellow_border_dsf = dsf_new(wh); |
1186 | int k = state->shared->params.k; | 1297 | int k = state->shared->params.k; |
1187 | 1298 | ||
1188 | if (!ds->grid) { | 1299 | if (!ds->grid) { |
1189 | char buf[40]; | 1300 | char buf[40]; |
1190 | int bgw = (w+1) * ds->tilesize, bgh = (h+1) * ds->tilesize; | 1301 | int bgw = (w+1) * ds->tilesize, bgh = (h+1) * ds->tilesize; |
1191 | draw_rect(dr, 0, 0, bgw, bgh, COL_BACKGROUND); | ||
1192 | 1302 | ||
1193 | for (r = 0; r <= h; ++r) | 1303 | for (r = 0; r <= h; ++r) |
1194 | for (c = 0; c <= w; ++c) | 1304 | for (c = 0; c <= w; ++c) |
@@ -1203,12 +1313,8 @@ static void game_redraw(drawing *dr, game_drawstate *ds, | |||
1203 | status_bar(dr, buf); | 1313 | status_bar(dr, buf); |
1204 | } | 1314 | } |
1205 | 1315 | ||
1206 | for (i = 0; i < wh; ++i) { | 1316 | build_dsf(w, h, state->borders, black_border_dsf, true); |
1207 | if (black_border_dsf[i] == UNVISITED) | 1317 | build_dsf(w, h, state->borders, yellow_border_dsf, false); |
1208 | dfs_dsf(i, w, state->borders, black_border_dsf, true); | ||
1209 | if (yellow_border_dsf[i] == UNVISITED) | ||
1210 | dfs_dsf(i, w, state->borders, yellow_border_dsf, false); | ||
1211 | } | ||
1212 | 1318 | ||
1213 | for (r = 0; r < h; ++r) | 1319 | for (r = 0; r < h; ++r) |
1214 | for (c = 0; c < w; ++c) { | 1320 | for (c = 0; c < w; ++c) { |
@@ -1223,8 +1329,13 @@ static void game_redraw(drawing *dr, game_drawstate *ds, | |||
1223 | if (clue != EMPTY && (on > clue || clue > 4 - off)) | 1329 | if (clue != EMPTY && (on > clue || clue > 4 - off)) |
1224 | flags |= F_ERROR_CLUE; | 1330 | flags |= F_ERROR_CLUE; |
1225 | 1331 | ||
1226 | if (ui->show && ui->x == c && ui->y == r) | 1332 | if (ui->show) { |
1227 | flags |= F_CURSOR; | 1333 | int u, v; |
1334 | for(u = 0; u < 3; u++) | ||
1335 | for(v = 0; v < 3; v++) | ||
1336 | if(ui->x == 2*c+u && ui->y == 2*r+v) | ||
1337 | flags |= CONTAINS_CURSOR(BIT(3*u+v)); | ||
1338 | } | ||
1228 | 1339 | ||
1229 | /* border errors */ | 1340 | /* border errors */ |
1230 | for (dir = 0; dir < 4; ++dir) { | 1341 | for (dir = 0; dir < 4; ++dir) { |
@@ -1268,8 +1379,11 @@ static void game_redraw(drawing *dr, game_drawstate *ds, | |||
1268 | draw_tile(dr, ds, r, c, ds->grid[i], clue); | 1379 | draw_tile(dr, ds, r, c, ds->grid[i], clue); |
1269 | } | 1380 | } |
1270 | 1381 | ||
1271 | sfree(black_border_dsf); | 1382 | if (ui->show) |
1272 | sfree(yellow_border_dsf); | 1383 | draw_cursor(dr, ds, ui->x, ui->y, ui->legacy_cursor); |
1384 | |||
1385 | dsf_free(black_border_dsf); | ||
1386 | dsf_free(yellow_border_dsf); | ||
1273 | } | 1387 | } |
1274 | 1388 | ||
1275 | static float game_anim_length(const game_state *oldstate, | 1389 | static float game_anim_length(const game_state *oldstate, |
@@ -1306,17 +1420,12 @@ static int game_status(const game_state *state) | |||
1306 | return state->completed ? +1 : 0; | 1420 | return state->completed ? +1 : 0; |
1307 | } | 1421 | } |
1308 | 1422 | ||
1309 | static bool game_timing_state(const game_state *state, game_ui *ui) | 1423 | static void game_print_size(const game_params *params, const game_ui *ui, |
1310 | { | 1424 | float *x, float *y) |
1311 | assert (!"this shouldn't get called"); | ||
1312 | return false; /* placate optimiser */ | ||
1313 | } | ||
1314 | |||
1315 | static void game_print_size(const game_params *params, float *x, float *y) | ||
1316 | { | 1425 | { |
1317 | int pw, ph; | 1426 | int pw, ph; |
1318 | 1427 | ||
1319 | game_compute_size(params, 700, &pw, &ph); /* 7mm, like loopy */ | 1428 | game_compute_size(params, 700, ui, &pw, &ph); /* 7mm, like loopy */ |
1320 | 1429 | ||
1321 | *x = pw / 100.0F; | 1430 | *x = pw / 100.0F; |
1322 | *y = ph / 100.0F; | 1431 | *y = ph / 100.0F; |
@@ -1335,7 +1444,8 @@ static void print_line(drawing *dr, int x1, int y1, int x2, int y2, | |||
1335 | } else draw_line(dr, x1, y1, x2, y2, colour); | 1444 | } else draw_line(dr, x1, y1, x2, y2, colour); |
1336 | } | 1445 | } |
1337 | 1446 | ||
1338 | static void game_print(drawing *dr, const game_state *state, int tilesize) | 1447 | static void game_print(drawing *dr, const game_state *state, const game_ui *ui, |
1448 | int tilesize) | ||
1339 | { | 1449 | { |
1340 | int w = state->shared->params.w, h = state->shared->params.h; | 1450 | int w = state->shared->params.w, h = state->shared->params.h; |
1341 | int ink = print_mono_colour(dr, 0); | 1451 | int ink = print_mono_colour(dr, 0); |
@@ -1399,12 +1509,14 @@ const struct game thegame = { | |||
1399 | free_game, | 1509 | free_game, |
1400 | true, solve_game, | 1510 | true, solve_game, |
1401 | true, game_can_format_as_text_now, game_text_format, | 1511 | true, game_can_format_as_text_now, game_text_format, |
1512 | get_prefs, set_prefs, /* get_prefs, set_prefs */ | ||
1402 | new_ui, | 1513 | new_ui, |
1403 | free_ui, | 1514 | free_ui, |
1404 | encode_ui, | 1515 | NULL, /* encode_ui */ |
1405 | decode_ui, | 1516 | NULL, /* decode_ui */ |
1406 | NULL, /* game_request_keys */ | 1517 | NULL, /* game_request_keys */ |
1407 | game_changed_state, | 1518 | game_changed_state, |
1519 | NULL, /* current_key_label */ | ||
1408 | interpret_move, | 1520 | interpret_move, |
1409 | execute_move, | 1521 | execute_move, |
1410 | 48, game_compute_size, game_set_size, | 1522 | 48, game_compute_size, game_set_size, |
@@ -1418,6 +1530,6 @@ const struct game thegame = { | |||
1418 | game_status, | 1530 | game_status, |
1419 | true, false, game_print_size, game_print, | 1531 | true, false, game_print_size, game_print, |
1420 | true, /* wants_statusbar */ | 1532 | true, /* wants_statusbar */ |
1421 | false, game_timing_state, | 1533 | false, NULL, /* timing_state */ |
1422 | 0, /* flags */ | 1534 | 0, /* flags */ |
1423 | }; | 1535 | }; |