diff options
Diffstat (limited to 'apps/plugins/puzzles/src/pegs.c')
-rw-r--r-- | apps/plugins/puzzles/src/pegs.c | 172 |
1 files changed, 103 insertions, 69 deletions
diff --git a/apps/plugins/puzzles/src/pegs.c b/apps/plugins/puzzles/src/pegs.c index ec12aa9552..c770bf3076 100644 --- a/apps/plugins/puzzles/src/pegs.c +++ b/apps/plugins/puzzles/src/pegs.c | |||
@@ -7,7 +7,12 @@ | |||
7 | #include <string.h> | 7 | #include <string.h> |
8 | #include <assert.h> | 8 | #include <assert.h> |
9 | #include <ctype.h> | 9 | #include <ctype.h> |
10 | #include <math.h> | 10 | #include <limits.h> |
11 | #ifdef NO_TGMATH_H | ||
12 | # include <math.h> | ||
13 | #else | ||
14 | # include <tgmath.h> | ||
15 | #endif | ||
11 | 16 | ||
12 | #include "puzzles.h" | 17 | #include "puzzles.h" |
13 | #include "tree234.h" | 18 | #include "tree234.h" |
@@ -70,7 +75,11 @@ static game_params *default_params(void) | |||
70 | } | 75 | } |
71 | 76 | ||
72 | static const struct game_params pegs_presets[] = { | 77 | static const struct game_params pegs_presets[] = { |
78 | {5, 7, TYPE_CROSS}, | ||
73 | {7, 7, TYPE_CROSS}, | 79 | {7, 7, TYPE_CROSS}, |
80 | {5, 9, TYPE_CROSS}, | ||
81 | {7, 9, TYPE_CROSS}, | ||
82 | {9, 9, TYPE_CROSS}, | ||
74 | {7, 7, TYPE_OCTAGON}, | 83 | {7, 7, TYPE_OCTAGON}, |
75 | {5, 5, TYPE_RANDOM}, | 84 | {5, 5, TYPE_RANDOM}, |
76 | {7, 7, TYPE_RANDOM}, | 85 | {7, 7, TYPE_RANDOM}, |
@@ -89,7 +98,7 @@ static bool game_fetch_preset(int i, char **name, game_params **params) | |||
89 | *ret = pegs_presets[i]; | 98 | *ret = pegs_presets[i]; |
90 | 99 | ||
91 | strcpy(str, pegs_titletypes[ret->type]); | 100 | strcpy(str, pegs_titletypes[ret->type]); |
92 | if (ret->type == TYPE_RANDOM) | 101 | if (ret->type == TYPE_CROSS || ret->type == TYPE_RANDOM) |
93 | sprintf(str + strlen(str), " %dx%d", ret->w, ret->h); | 102 | sprintf(str + strlen(str), " %dx%d", ret->w, ret->h); |
94 | 103 | ||
95 | *name = dupstr(str); | 104 | *name = dupstr(str); |
@@ -182,14 +191,38 @@ static const char *validate_params(const game_params *params, bool full) | |||
182 | { | 191 | { |
183 | if (full && (params->w <= 3 || params->h <= 3)) | 192 | if (full && (params->w <= 3 || params->h <= 3)) |
184 | return "Width and height must both be greater than three"; | 193 | return "Width and height must both be greater than three"; |
194 | if (params->w < 1 || params->h < 1) | ||
195 | return "Width and height must both be at least one"; | ||
196 | if (params->w > INT_MAX / params->h) | ||
197 | return "Width times height must not be unreasonably large"; | ||
198 | |||
199 | /* | ||
200 | * At http://www.gibell.net/pegsolitaire/GenCross/GenCrossBoards0.html | ||
201 | * George I. Bell asserts that various generalised cross-shaped | ||
202 | * boards are soluble starting (and finishing) with the centre | ||
203 | * hole. We permit the symmetric ones. Bell's notation for each | ||
204 | * soluble board is listed. | ||
205 | */ | ||
206 | if (full && params->type == TYPE_CROSS) { | ||
207 | if (!((params->w == 9 && params->h == 5) || /* (3,1,3,1) */ | ||
208 | (params->w == 5 && params->h == 9) || /* (1,3,1,3) */ | ||
209 | (params->w == 9 && params->h == 9) || /* (3,3,3,3) */ | ||
210 | (params->w == 7 && params->h == 5) || /* (2,1,2,1) */ | ||
211 | (params->w == 5 && params->h == 7) || /* (1,2,1,2) */ | ||
212 | (params->w == 9 && params->h == 7) || /* (3,2,3,2) */ | ||
213 | (params->w == 7 && params->h == 9) || /* (2,3,2,3) */ | ||
214 | (params->w == 7 && params->h == 7))) /* (2,2,2,2) */ | ||
215 | return "This board type is only supported at " | ||
216 | "5x7, 5x9, 7x7, 7x9, and 9x9"; | ||
217 | } | ||
185 | 218 | ||
186 | /* | 219 | /* |
187 | * It might be possible to implement generalisations of Cross | 220 | * It might be possible to implement generalisations of |
188 | * and Octagon, but only if I can find a proof that they're all | 221 | * Octagon, but only if I can find a proof that they're all |
189 | * soluble. For the moment, therefore, I'm going to disallow | 222 | * soluble. For the moment, therefore, I'm going to disallow |
190 | * them at any size other than the standard one. | 223 | * it at any size other than the standard one. |
191 | */ | 224 | */ |
192 | if (full && (params->type == TYPE_CROSS || params->type == TYPE_OCTAGON)) { | 225 | if (full && params->type == TYPE_OCTAGON) { |
193 | if (params->w != 7 || params->h != 7) | 226 | if (params->w != 7 || params->h != 7) |
194 | return "This board type is only supported at 7x7"; | 227 | return "This board type is only supported at 7x7"; |
195 | } | 228 | } |
@@ -658,12 +691,23 @@ static char *new_game_desc(const game_params *params, random_state *rs, | |||
658 | 691 | ||
659 | static const char *validate_desc(const game_params *params, const char *desc) | 692 | static const char *validate_desc(const game_params *params, const char *desc) |
660 | { | 693 | { |
661 | int len = params->w * params->h; | 694 | int len, i, npeg = 0, nhole = 0; |
695 | |||
696 | len = params->w * params->h; | ||
662 | 697 | ||
663 | if (len != strlen(desc)) | 698 | if (len != strlen(desc)) |
664 | return "Game description is wrong length"; | 699 | return "Game description is wrong length"; |
665 | if (len != strspn(desc, "PHO")) | 700 | if (len != strspn(desc, "PHO")) |
666 | return "Invalid character in game description"; | 701 | return "Invalid character in game description"; |
702 | for (i = 0; i < len; i++) { | ||
703 | npeg += desc[i] == 'P'; | ||
704 | nhole += desc[i] == 'H'; | ||
705 | } | ||
706 | /* The minimal soluble game has two pegs and a hole: "3x1:PPH". */ | ||
707 | if (npeg < 2) | ||
708 | return "Too few pegs in game description"; | ||
709 | if (nhole < 1) | ||
710 | return "Too few holes in game description"; | ||
667 | 711 | ||
668 | return NULL; | 712 | return NULL; |
669 | } | 713 | } |
@@ -706,12 +750,6 @@ static void free_game(game_state *state) | |||
706 | sfree(state); | 750 | sfree(state); |
707 | } | 751 | } |
708 | 752 | ||
709 | static char *solve_game(const game_state *state, const game_state *currstate, | ||
710 | const char *aux, const char **error) | ||
711 | { | ||
712 | return NULL; | ||
713 | } | ||
714 | |||
715 | static bool game_can_format_as_text_now(const game_params *params) | 753 | static bool game_can_format_as_text_now(const game_params *params) |
716 | { | 754 | { |
717 | return true; | 755 | return true; |
@@ -751,7 +789,7 @@ static game_ui *new_ui(const game_state *state) | |||
751 | 789 | ||
752 | ui->sx = ui->sy = ui->dx = ui->dy = 0; | 790 | ui->sx = ui->sy = ui->dx = ui->dy = 0; |
753 | ui->dragging = false; | 791 | ui->dragging = false; |
754 | ui->cur_visible = false; | 792 | ui->cur_visible = getenv_bool("PUZZLES_SHOW_CURSOR", false); |
755 | ui->cur_jumping = false; | 793 | ui->cur_jumping = false; |
756 | 794 | ||
757 | /* make sure we start the cursor somewhere on the grid. */ | 795 | /* make sure we start the cursor somewhere on the grid. */ |
@@ -775,15 +813,6 @@ static void free_ui(game_ui *ui) | |||
775 | sfree(ui); | 813 | sfree(ui); |
776 | } | 814 | } |
777 | 815 | ||
778 | static char *encode_ui(const game_ui *ui) | ||
779 | { | ||
780 | return NULL; | ||
781 | } | ||
782 | |||
783 | static void decode_ui(game_ui *ui, const char *encoding) | ||
784 | { | ||
785 | } | ||
786 | |||
787 | static void game_changed_state(game_ui *ui, const game_state *oldstate, | 816 | static void game_changed_state(game_ui *ui, const game_state *oldstate, |
788 | const game_state *newstate) | 817 | const game_state *newstate) |
789 | { | 818 | { |
@@ -800,6 +829,19 @@ static void game_changed_state(game_ui *ui, const game_state *oldstate, | |||
800 | ui->cur_jumping = false; | 829 | ui->cur_jumping = false; |
801 | } | 830 | } |
802 | 831 | ||
832 | static const char *current_key_label(const game_ui *ui, | ||
833 | const game_state *state, int button) | ||
834 | { | ||
835 | int w = state->w; | ||
836 | |||
837 | if (IS_CURSOR_SELECT(button)) { | ||
838 | if (!ui->cur_visible) return ""; | ||
839 | if (ui->cur_jumping) return "Cancel"; | ||
840 | if (state->grid[ui->cur_y*w+ui->cur_x] == GRID_PEG) return "Select"; | ||
841 | } | ||
842 | return ""; | ||
843 | } | ||
844 | |||
803 | #define PREFERRED_TILE_SIZE 33 | 845 | #define PREFERRED_TILE_SIZE 33 |
804 | #define TILESIZE (ds->tilesize) | 846 | #define TILESIZE (ds->tilesize) |
805 | #define BORDER (TILESIZE / 2) | 847 | #define BORDER (TILESIZE / 2) |
@@ -845,16 +887,23 @@ static char *interpret_move(const game_state *state, game_ui *ui, | |||
845 | 887 | ||
846 | tx = FROMCOORD(x); | 888 | tx = FROMCOORD(x); |
847 | ty = FROMCOORD(y); | 889 | ty = FROMCOORD(y); |
848 | if (tx >= 0 && tx < w && ty >= 0 && ty < h && | 890 | if (tx >= 0 && tx < w && ty >= 0 && ty < h) { |
849 | state->grid[ty*w+tx] == GRID_PEG) { | 891 | switch (state->grid[ty*w+tx]) { |
850 | ui->dragging = true; | 892 | case GRID_PEG: |
851 | ui->sx = tx; | 893 | ui->dragging = true; |
852 | ui->sy = ty; | 894 | ui->sx = tx; |
853 | ui->dx = x; | 895 | ui->sy = ty; |
854 | ui->dy = y; | 896 | ui->dx = x; |
855 | ui->cur_visible = false; | 897 | ui->dy = y; |
856 | ui->cur_jumping = false; | 898 | ui->cur_visible = false; |
857 | return UI_UPDATE; | 899 | ui->cur_jumping = false; |
900 | return MOVE_UI_UPDATE; | ||
901 | case GRID_HOLE: | ||
902 | return MOVE_NO_EFFECT; | ||
903 | case GRID_OBST: | ||
904 | default: | ||
905 | return MOVE_UNUSED; | ||
906 | } | ||
858 | } | 907 | } |
859 | } else if (button == LEFT_DRAG && ui->dragging) { | 908 | } else if (button == LEFT_DRAG && ui->dragging) { |
860 | /* | 909 | /* |
@@ -862,7 +911,7 @@ static char *interpret_move(const game_state *state, game_ui *ui, | |||
862 | */ | 911 | */ |
863 | ui->dx = x; | 912 | ui->dx = x; |
864 | ui->dy = y; | 913 | ui->dy = y; |
865 | return UI_UPDATE; | 914 | return MOVE_UI_UPDATE; |
866 | } else if (button == LEFT_RELEASE && ui->dragging) { | 915 | } else if (button == LEFT_RELEASE && ui->dragging) { |
867 | int tx, ty, dx, dy; | 916 | int tx, ty, dx, dy; |
868 | 917 | ||
@@ -874,18 +923,18 @@ static char *interpret_move(const game_state *state, game_ui *ui, | |||
874 | tx = FROMCOORD(x); | 923 | tx = FROMCOORD(x); |
875 | ty = FROMCOORD(y); | 924 | ty = FROMCOORD(y); |
876 | if (tx < 0 || tx >= w || ty < 0 || ty >= h) | 925 | if (tx < 0 || tx >= w || ty < 0 || ty >= h) |
877 | return UI_UPDATE; /* target out of range */ | 926 | return MOVE_UI_UPDATE; /* target out of range */ |
878 | dx = tx - ui->sx; | 927 | dx = tx - ui->sx; |
879 | dy = ty - ui->sy; | 928 | dy = ty - ui->sy; |
880 | if (max(abs(dx),abs(dy)) != 2 || min(abs(dx),abs(dy)) != 0) | 929 | if (max(abs(dx),abs(dy)) != 2 || min(abs(dx),abs(dy)) != 0) |
881 | return UI_UPDATE; /* move length was wrong */ | 930 | return MOVE_UI_UPDATE; /* move length was wrong */ |
882 | dx /= 2; | 931 | dx /= 2; |
883 | dy /= 2; | 932 | dy /= 2; |
884 | 933 | ||
885 | if (state->grid[ty*w+tx] != GRID_HOLE || | 934 | if (state->grid[ty*w+tx] != GRID_HOLE || |
886 | state->grid[(ty-dy)*w+(tx-dx)] != GRID_PEG || | 935 | state->grid[(ty-dy)*w+(tx-dx)] != GRID_PEG || |
887 | state->grid[ui->sy*w+ui->sx] != GRID_PEG) | 936 | state->grid[ui->sy*w+ui->sx] != GRID_PEG) |
888 | return UI_UPDATE; /* grid contents were invalid */ | 937 | return MOVE_UI_UPDATE; /* grid contents were invalid */ |
889 | 938 | ||
890 | /* | 939 | /* |
891 | * We have a valid move. Encode it simply as source and | 940 | * We have a valid move. Encode it simply as source and |
@@ -898,14 +947,14 @@ static char *interpret_move(const game_state *state, game_ui *ui, | |||
898 | /* Not jumping; move cursor as usual, making sure we don't | 947 | /* Not jumping; move cursor as usual, making sure we don't |
899 | * leave the gameboard (which may be an irregular shape) */ | 948 | * leave the gameboard (which may be an irregular shape) */ |
900 | int cx = ui->cur_x, cy = ui->cur_y; | 949 | int cx = ui->cur_x, cy = ui->cur_y; |
901 | move_cursor(button, &cx, &cy, w, h, false); | 950 | move_cursor(button, &cx, &cy, w, h, false, NULL); |
902 | ui->cur_visible = true; | 951 | ui->cur_visible = true; |
903 | if (state->grid[cy*w+cx] == GRID_HOLE || | 952 | if (state->grid[cy*w+cx] == GRID_HOLE || |
904 | state->grid[cy*w+cx] == GRID_PEG) { | 953 | state->grid[cy*w+cx] == GRID_PEG) { |
905 | ui->cur_x = cx; | 954 | ui->cur_x = cx; |
906 | ui->cur_y = cy; | 955 | ui->cur_y = cy; |
907 | } | 956 | } |
908 | return UI_UPDATE; | 957 | return MOVE_UI_UPDATE; |
909 | } else { | 958 | } else { |
910 | int dx, dy, mx, my, jx, jy; | 959 | int dx, dy, mx, my, jx, jy; |
911 | 960 | ||
@@ -928,26 +977,26 @@ static char *interpret_move(const game_state *state, game_ui *ui, | |||
928 | ui->cur_x = jx; ui->cur_y = jy; | 977 | ui->cur_x = jx; ui->cur_y = jy; |
929 | return dupstr(buf); | 978 | return dupstr(buf); |
930 | } | 979 | } |
931 | return UI_UPDATE; | 980 | return MOVE_UI_UPDATE; |
932 | } | 981 | } |
933 | } else if (IS_CURSOR_SELECT(button)) { | 982 | } else if (IS_CURSOR_SELECT(button)) { |
934 | if (!ui->cur_visible) { | 983 | if (!ui->cur_visible) { |
935 | ui->cur_visible = true; | 984 | ui->cur_visible = true; |
936 | return UI_UPDATE; | 985 | return MOVE_UI_UPDATE; |
937 | } | 986 | } |
938 | if (ui->cur_jumping) { | 987 | if (ui->cur_jumping) { |
939 | ui->cur_jumping = false; | 988 | ui->cur_jumping = false; |
940 | return UI_UPDATE; | 989 | return MOVE_UI_UPDATE; |
941 | } | 990 | } |
942 | if (state->grid[ui->cur_y*w+ui->cur_x] == GRID_PEG) { | 991 | if (state->grid[ui->cur_y*w+ui->cur_x] == GRID_PEG) { |
943 | /* cursor is on peg: next arrow-move wil jump. */ | 992 | /* cursor is on peg: next arrow-move will jump. */ |
944 | ui->cur_jumping = true; | 993 | ui->cur_jumping = true; |
945 | return UI_UPDATE; | 994 | return MOVE_UI_UPDATE; |
946 | } | 995 | } |
947 | return NULL; | 996 | return MOVE_NO_EFFECT; |
948 | } | 997 | } |
949 | 998 | ||
950 | return NULL; | 999 | return MOVE_UNUSED; |
951 | } | 1000 | } |
952 | 1001 | ||
953 | static game_state *execute_move(const game_state *state, const char *move) | 1002 | static game_state *execute_move(const game_state *state, const char *move) |
@@ -1006,7 +1055,7 @@ static game_state *execute_move(const game_state *state, const char *move) | |||
1006 | */ | 1055 | */ |
1007 | 1056 | ||
1008 | static void game_compute_size(const game_params *params, int tilesize, | 1057 | static void game_compute_size(const game_params *params, int tilesize, |
1009 | int *x, int *y) | 1058 | const game_ui *ui, int *x, int *y) |
1010 | { | 1059 | { |
1011 | /* Ick: fake up `ds->tilesize' for macro expansion purposes */ | 1060 | /* Ick: fake up `ds->tilesize' for macro expansion purposes */ |
1012 | struct { int tilesize; } ads, *ds = &ads; | 1061 | struct { int tilesize; } ads, *ds = &ads; |
@@ -1135,10 +1184,6 @@ static void game_redraw(drawing *dr, game_drawstate *ds, | |||
1135 | } | 1184 | } |
1136 | 1185 | ||
1137 | if (!ds->started) { | 1186 | if (!ds->started) { |
1138 | draw_rect(dr, 0, 0, | ||
1139 | TILESIZE * state->w + 2 * BORDER, | ||
1140 | TILESIZE * state->h + 2 * BORDER, COL_BACKGROUND); | ||
1141 | |||
1142 | /* | 1187 | /* |
1143 | * Draw relief marks around all the squares that aren't | 1188 | * Draw relief marks around all the squares that aren't |
1144 | * GRID_OBST. | 1189 | * GRID_OBST. |
@@ -1302,19 +1347,6 @@ static int game_status(const game_state *state) | |||
1302 | return state->completed ? +1 : 0; | 1347 | return state->completed ? +1 : 0; |
1303 | } | 1348 | } |
1304 | 1349 | ||
1305 | static bool game_timing_state(const game_state *state, game_ui *ui) | ||
1306 | { | ||
1307 | return true; | ||
1308 | } | ||
1309 | |||
1310 | static void game_print_size(const game_params *params, float *x, float *y) | ||
1311 | { | ||
1312 | } | ||
1313 | |||
1314 | static void game_print(drawing *dr, const game_state *state, int tilesize) | ||
1315 | { | ||
1316 | } | ||
1317 | |||
1318 | #ifdef COMBINED | 1350 | #ifdef COMBINED |
1319 | #define thegame pegs | 1351 | #define thegame pegs |
1320 | #endif | 1352 | #endif |
@@ -1334,14 +1366,16 @@ const struct game thegame = { | |||
1334 | new_game, | 1366 | new_game, |
1335 | dup_game, | 1367 | dup_game, |
1336 | free_game, | 1368 | free_game, |
1337 | false, solve_game, | 1369 | false, NULL, /* solve */ |
1338 | true, game_can_format_as_text_now, game_text_format, | 1370 | true, game_can_format_as_text_now, game_text_format, |
1371 | NULL, NULL, /* get_prefs, set_prefs */ | ||
1339 | new_ui, | 1372 | new_ui, |
1340 | free_ui, | 1373 | free_ui, |
1341 | encode_ui, | 1374 | NULL, /* encode_ui */ |
1342 | decode_ui, | 1375 | NULL, /* decode_ui */ |
1343 | NULL, /* game_request_keys */ | 1376 | NULL, /* game_request_keys */ |
1344 | game_changed_state, | 1377 | game_changed_state, |
1378 | current_key_label, | ||
1345 | interpret_move, | 1379 | interpret_move, |
1346 | execute_move, | 1380 | execute_move, |
1347 | PREFERRED_TILE_SIZE, game_compute_size, game_set_size, | 1381 | PREFERRED_TILE_SIZE, game_compute_size, game_set_size, |
@@ -1353,9 +1387,9 @@ const struct game thegame = { | |||
1353 | game_flash_length, | 1387 | game_flash_length, |
1354 | game_get_cursor_location, | 1388 | game_get_cursor_location, |
1355 | game_status, | 1389 | game_status, |
1356 | false, false, game_print_size, game_print, | 1390 | false, false, NULL, NULL, /* print_size, print */ |
1357 | false, /* wants_statusbar */ | 1391 | false, /* wants_statusbar */ |
1358 | false, game_timing_state, | 1392 | false, NULL, /* timing_state */ |
1359 | 0, /* flags */ | 1393 | 0, /* flags */ |
1360 | }; | 1394 | }; |
1361 | 1395 | ||