summaryrefslogtreecommitdiff
path: root/apps/plugins/puzzles/src/pegs.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/puzzles/src/pegs.c')
-rw-r--r--apps/plugins/puzzles/src/pegs.c172
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
72static const struct game_params pegs_presets[] = { 77static 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
659static const char *validate_desc(const game_params *params, const char *desc) 692static 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
709static char *solve_game(const game_state *state, const game_state *currstate,
710 const char *aux, const char **error)
711{
712 return NULL;
713}
714
715static bool game_can_format_as_text_now(const game_params *params) 753static 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
778static char *encode_ui(const game_ui *ui)
779{
780 return NULL;
781}
782
783static void decode_ui(game_ui *ui, const char *encoding)
784{
785}
786
787static void game_changed_state(game_ui *ui, const game_state *oldstate, 816static 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
832static 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
953static game_state *execute_move(const game_state *state, const char *move) 1002static 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
1008static void game_compute_size(const game_params *params, int tilesize, 1057static 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
1305static bool game_timing_state(const game_state *state, game_ui *ui)
1306{
1307 return true;
1308}
1309
1310static void game_print_size(const game_params *params, float *x, float *y)
1311{
1312}
1313
1314static 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