summaryrefslogtreecommitdiff
path: root/apps/plugins/puzzles/src/palisade.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/puzzles/src/palisade.c')
-rw-r--r--apps/plugins/puzzles/src/palisade.c328
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
49typedef char clue; 50typedef signed char clue;
50typedef unsigned char borderflag; 51typedef unsigned char borderflag;
51 52
52typedef struct shared_state { 53typedef struct shared_state {
@@ -156,13 +157,15 @@ static game_params *custom_params(const config_item *cfg)
156 157
157static const char *validate_params(const game_params *params, bool full) 158static 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)
270static bool connected(solver_ctx *ctx, int i, int j, int dir) 273static 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
276static void disconnect(solver_ctx *ctx, int i, int j, int dir) 279static 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'. */
508static void dfs_dsf(int i, int w, borderflag *border, int *dsf, bool black) 509static 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
568error: 571error:
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
705static const char *validate_desc(const game_params *params, const char *desc) 708static 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
867struct game_ui { 870struct 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
873static game_ui *new_ui(const game_state *state) 880static 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
881static void free_ui(game_ui *ui) 889static 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
886static char *encode_ui(const game_ui *ui) 908static 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
891static void decode_ui(game_ui *ui, const char *encoding) 913static void free_ui(game_ui *ui)
892{ 914{
893 assert (encoding == NULL); 915 sfree(ui);
894} 916}
895 917
896static void game_changed_state(game_ui *ui, const game_state *oldstate, 918static 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
901typedef unsigned short dsflags; 923typedef int dsflags;
902 924
903struct game_drawstate { 925struct 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
1055static void game_compute_size(const game_params *params, int tilesize, 1128static 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
1118static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) 1191static 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
1246static 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
1178static void game_redraw(drawing *dr, game_drawstate *ds, 1289static 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
1275static float game_anim_length(const game_state *oldstate, 1389static 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
1309static bool game_timing_state(const game_state *state, game_ui *ui) 1423static 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
1315static 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
1338static void game_print(drawing *dr, const game_state *state, int tilesize) 1447static 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};