diff options
Diffstat (limited to 'apps/plugins/puzzles/src/fifteen.c')
-rw-r--r-- | apps/plugins/puzzles/src/fifteen.c | 259 |
1 files changed, 148 insertions, 111 deletions
diff --git a/apps/plugins/puzzles/src/fifteen.c b/apps/plugins/puzzles/src/fifteen.c index 4b877dc098..a2a9554523 100644 --- a/apps/plugins/puzzles/src/fifteen.c +++ b/apps/plugins/puzzles/src/fifteen.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 | 18 | ||
@@ -138,6 +143,8 @@ static const char *validate_params(const game_params *params, bool full) | |||
138 | { | 143 | { |
139 | if (params->w < 2 || params->h < 2) | 144 | if (params->w < 2 || params->h < 2) |
140 | return "Width and height must both be at least two"; | 145 | return "Width and height must both be at least two"; |
146 | if (params->w > INT_MAX / params->h) | ||
147 | return "Width times height must not be unreasonably large"; | ||
141 | 148 | ||
142 | return NULL; | 149 | return NULL; |
143 | } | 150 | } |
@@ -156,6 +163,14 @@ static int perm_parity(int *perm, int n) | |||
156 | return ret; | 163 | return ret; |
157 | } | 164 | } |
158 | 165 | ||
166 | static int is_completed(int *tiles, int n) { | ||
167 | int p; | ||
168 | for (p = 0; p < n; p++) | ||
169 | if (tiles[p] != (p < n-1 ? p+1 : 0)) | ||
170 | return 0; | ||
171 | return 1; | ||
172 | } | ||
173 | |||
159 | static char *new_game_desc(const game_params *params, random_state *rs, | 174 | static char *new_game_desc(const game_params *params, random_state *rs, |
160 | char **aux, bool interactive) | 175 | char **aux, bool interactive) |
161 | { | 176 | { |
@@ -171,81 +186,83 @@ static char *new_game_desc(const game_params *params, random_state *rs, | |||
171 | tiles = snewn(n, int); | 186 | tiles = snewn(n, int); |
172 | used = snewn(n, bool); | 187 | used = snewn(n, bool); |
173 | 188 | ||
174 | for (i = 0; i < n; i++) { | 189 | do { |
175 | tiles[i] = -1; | 190 | for (i = 0; i < n; i++) { |
176 | used[i] = false; | 191 | tiles[i] = -1; |
177 | } | 192 | used[i] = false; |
193 | } | ||
178 | 194 | ||
179 | gap = random_upto(rs, n); | 195 | gap = random_upto(rs, n); |
180 | tiles[gap] = 0; | 196 | tiles[gap] = 0; |
181 | used[0] = true; | 197 | used[0] = true; |
182 | 198 | ||
183 | /* | 199 | /* |
184 | * Place everything else except the last two tiles. | 200 | * Place everything else except the last two tiles. |
185 | */ | 201 | */ |
186 | for (x = 0, i = n-1; i > 2; i--) { | 202 | for (x = 0, i = n - 1; i > 2; i--) { |
187 | int k = random_upto(rs, i); | 203 | int k = random_upto(rs, i); |
188 | int j; | 204 | int j; |
189 | 205 | ||
190 | for (j = 0; j < n; j++) | 206 | for (j = 0; j < n; j++) |
191 | if (!used[j] && (k-- == 0)) | 207 | if (!used[j] && (k-- == 0)) |
192 | break; | 208 | break; |
193 | 209 | ||
194 | assert(j < n && !used[j]); | 210 | assert(j < n && !used[j]); |
195 | used[j] = true; | 211 | used[j] = true; |
212 | |||
213 | while (tiles[x] >= 0) | ||
214 | x++; | ||
215 | assert(x < n); | ||
216 | tiles[x] = j; | ||
217 | } | ||
196 | 218 | ||
219 | /* | ||
220 | * Find the last two locations, and the last two pieces. | ||
221 | */ | ||
197 | while (tiles[x] >= 0) | 222 | while (tiles[x] >= 0) |
198 | x++; | 223 | x++; |
199 | assert(x < n); | 224 | assert(x < n); |
200 | tiles[x] = j; | 225 | x1 = x; |
201 | } | ||
202 | |||
203 | /* | ||
204 | * Find the last two locations, and the last two pieces. | ||
205 | */ | ||
206 | while (tiles[x] >= 0) | ||
207 | x++; | ||
208 | assert(x < n); | ||
209 | x1 = x; | ||
210 | x++; | ||
211 | while (tiles[x] >= 0) | ||
212 | x++; | 226 | x++; |
213 | assert(x < n); | 227 | while (tiles[x] >= 0) |
214 | x2 = x; | 228 | x++; |
215 | 229 | assert(x < n); | |
216 | for (i = 0; i < n; i++) | 230 | x2 = x; |
217 | if (!used[i]) | ||
218 | break; | ||
219 | p1 = i; | ||
220 | for (i = p1+1; i < n; i++) | ||
221 | if (!used[i]) | ||
222 | break; | ||
223 | p2 = i; | ||
224 | 231 | ||
225 | /* | 232 | for (i = 0; i < n; i++) |
226 | * Determine the required parity of the overall permutation. | 233 | if (!used[i]) |
227 | * This is the XOR of: | 234 | break; |
228 | * | 235 | p1 = i; |
229 | * - The chessboard parity ((x^y)&1) of the gap square. The | 236 | for (i = p1 + 1; i < n; i++) |
230 | * bottom right counts as even. | 237 | if (!used[i]) |
231 | * | 238 | break; |
232 | * - The parity of n. (The target permutation is 1,...,n-1,0 | 239 | p2 = i; |
233 | * rather than 0,...,n-1; this is a cyclic permutation of | ||
234 | * the starting point and hence is odd iff n is even.) | ||
235 | */ | ||
236 | parity = PARITY_P(params, gap); | ||
237 | 240 | ||
238 | /* | 241 | /* |
239 | * Try the last two tiles one way round. If that fails, swap | 242 | * Determine the required parity of the overall permutation. |
240 | * them. | 243 | * This is the XOR of: |
241 | */ | 244 | * |
242 | tiles[x1] = p1; | 245 | * - The chessboard parity ((x^y)&1) of the gap square. The |
243 | tiles[x2] = p2; | 246 | * bottom right counts as even. |
244 | if (perm_parity(tiles, n) != parity) { | 247 | * |
245 | tiles[x1] = p2; | 248 | * - The parity of n. (The target permutation is 1,...,n-1,0 |
246 | tiles[x2] = p1; | 249 | * rather than 0,...,n-1; this is a cyclic permutation of |
247 | assert(perm_parity(tiles, n) == parity); | 250 | * the starting point and hence is odd iff n is even.) |
248 | } | 251 | */ |
252 | parity = PARITY_P(params, gap); | ||
253 | |||
254 | /* | ||
255 | * Try the last two tiles one way round. If that fails, swap | ||
256 | * them. | ||
257 | */ | ||
258 | tiles[x1] = p1; | ||
259 | tiles[x2] = p2; | ||
260 | if (perm_parity(tiles, n) != parity) { | ||
261 | tiles[x1] = p2; | ||
262 | tiles[x2] = p1; | ||
263 | assert(perm_parity(tiles, n) == parity); | ||
264 | } | ||
265 | } while (is_completed(tiles, n)); | ||
249 | 266 | ||
250 | /* | 267 | /* |
251 | * Now construct the game description, by describing the tile | 268 | * Now construct the game description, by describing the tile |
@@ -432,22 +449,68 @@ static char *game_text_format(const game_state *state) | |||
432 | return ret; | 449 | return ret; |
433 | } | 450 | } |
434 | 451 | ||
452 | struct game_ui { | ||
453 | /* | ||
454 | * User-preference option: invert the direction of arrow-key | ||
455 | * control, so that the arrow on the key you press indicates in | ||
456 | * which direction you want the _space_ to move, rather than in | ||
457 | * which direction you want a tile to move to fill the space. | ||
458 | */ | ||
459 | bool invert_cursor; | ||
460 | }; | ||
461 | |||
462 | static void legacy_prefs_override(struct game_ui *ui_out) | ||
463 | { | ||
464 | static bool initialised = false; | ||
465 | static int invert_cursor = -1; | ||
466 | |||
467 | if (!initialised) { | ||
468 | initialised = true; | ||
469 | invert_cursor = getenv_bool("FIFTEEN_INVERT_CURSOR", -1); | ||
470 | } | ||
471 | |||
472 | if (invert_cursor != -1) | ||
473 | ui_out->invert_cursor = invert_cursor; | ||
474 | } | ||
475 | |||
435 | static game_ui *new_ui(const game_state *state) | 476 | static game_ui *new_ui(const game_state *state) |
436 | { | 477 | { |
437 | return NULL; | 478 | struct game_ui *ui = snew(struct game_ui); |
479 | |||
480 | ui->invert_cursor = false; | ||
481 | |||
482 | legacy_prefs_override(ui); | ||
483 | |||
484 | return ui; | ||
438 | } | 485 | } |
439 | 486 | ||
440 | static void free_ui(game_ui *ui) | 487 | static config_item *get_prefs(game_ui *ui) |
441 | { | 488 | { |
489 | config_item *ret; | ||
490 | |||
491 | ret = snewn(2, config_item); | ||
492 | |||
493 | ret[0].name = "Sense of arrow keys"; | ||
494 | ret[0].kw = "arrow-semantics"; | ||
495 | ret[0].type = C_CHOICES; | ||
496 | ret[0].u.choices.choicenames = ":Move the tile:Move the gap"; | ||
497 | ret[0].u.choices.choicekws = ":tile:gap"; | ||
498 | ret[0].u.choices.selected = ui->invert_cursor; | ||
499 | |||
500 | ret[1].name = NULL; | ||
501 | ret[1].type = C_END; | ||
502 | |||
503 | return ret; | ||
442 | } | 504 | } |
443 | 505 | ||
444 | static char *encode_ui(const game_ui *ui) | 506 | static void set_prefs(game_ui *ui, const config_item *cfg) |
445 | { | 507 | { |
446 | return NULL; | 508 | ui->invert_cursor = cfg[0].u.choices.selected; |
447 | } | 509 | } |
448 | 510 | ||
449 | static void decode_ui(game_ui *ui, const char *encoding) | 511 | static void free_ui(game_ui *ui) |
450 | { | 512 | { |
513 | sfree(ui); | ||
451 | } | 514 | } |
452 | 515 | ||
453 | static void game_changed_state(game_ui *ui, const game_state *oldstate, | 516 | static void game_changed_state(game_ui *ui, const game_state *oldstate, |
@@ -692,28 +755,23 @@ static char *interpret_move(const game_state *state, game_ui *ui, | |||
692 | int cy = Y(state, state->gap_pos), ny = cy; | 755 | int cy = Y(state, state->gap_pos), ny = cy; |
693 | char buf[80]; | 756 | char buf[80]; |
694 | 757 | ||
695 | button &= ~MOD_MASK; | 758 | button = STRIP_BUTTON_MODIFIERS(button); |
696 | 759 | ||
697 | if (button == LEFT_BUTTON) { | 760 | if (button == LEFT_BUTTON) { |
698 | nx = FROMCOORD(x); | 761 | nx = FROMCOORD(x); |
699 | ny = FROMCOORD(y); | 762 | ny = FROMCOORD(y); |
700 | if (nx < 0 || nx >= state->w || ny < 0 || ny >= state->h) | 763 | if (nx < 0 || nx >= state->w || ny < 0 || ny >= state->h) |
701 | return NULL; /* out of bounds */ | 764 | return MOVE_UNUSED; /* out of bounds */ |
702 | } else if (IS_CURSOR_MOVE(button)) { | 765 | } else if (IS_CURSOR_MOVE(button)) { |
703 | static int invert_cursor = -1; | ||
704 | if (invert_cursor == -1) { | ||
705 | char *env = getenv("FIFTEEN_INVERT_CURSOR"); | ||
706 | invert_cursor = (env && (env[0] == 'y' || env[0] == 'Y')); | ||
707 | } | ||
708 | button = flip_cursor(button); /* the default */ | 766 | button = flip_cursor(button); /* the default */ |
709 | if (invert_cursor) | 767 | if (ui->invert_cursor) |
710 | button = flip_cursor(button); /* undoes the first flip */ | 768 | button = flip_cursor(button); /* undoes the first flip */ |
711 | move_cursor(button, &nx, &ny, state->w, state->h, false); | 769 | move_cursor(button, &nx, &ny, state->w, state->h, false, NULL); |
712 | } else if ((button == 'h' || button == 'H') && !state->completed) { | 770 | } else if ((button == 'h' || button == 'H') && !state->completed) { |
713 | if (!compute_hint(state, &nx, &ny)) | 771 | if (!compute_hint(state, &nx, &ny)) |
714 | return NULL; /* shouldn't happen, since ^^we^^checked^^ */ | 772 | return MOVE_NO_EFFECT;/* shouldn't happen, since ^^we^^checked^^ */ |
715 | } else | 773 | } else |
716 | return NULL; /* no move */ | 774 | return MOVE_UNUSED; /* no move */ |
717 | 775 | ||
718 | /* | 776 | /* |
719 | * Any click location should be equal to the gap location | 777 | * Any click location should be equal to the gap location |
@@ -724,7 +782,7 @@ static char *interpret_move(const game_state *state, game_ui *ui, | |||
724 | return dupstr(buf); | 782 | return dupstr(buf); |
725 | } | 783 | } |
726 | 784 | ||
727 | return NULL; | 785 | return MOVE_NO_EFFECT; |
728 | } | 786 | } |
729 | 787 | ||
730 | static game_state *execute_move(const game_state *from, const char *move) | 788 | static game_state *execute_move(const game_state *from, const char *move) |
@@ -786,11 +844,8 @@ static game_state *execute_move(const game_state *from, const char *move) | |||
786 | /* | 844 | /* |
787 | * See if the game has been completed. | 845 | * See if the game has been completed. |
788 | */ | 846 | */ |
789 | if (!ret->completed) { | 847 | if (!ret->completed && is_completed(ret->tiles, ret->n)) { |
790 | ret->completed = ret->movecount; | 848 | ret->completed = ret->movecount; |
791 | for (p = 0; p < ret->n; p++) | ||
792 | if (ret->tiles[p] != (p < ret->n-1 ? p+1 : 0)) | ||
793 | ret->completed = 0; | ||
794 | } | 849 | } |
795 | 850 | ||
796 | return ret; | 851 | return ret; |
@@ -801,7 +856,7 @@ static game_state *execute_move(const game_state *from, const char *move) | |||
801 | */ | 856 | */ |
802 | 857 | ||
803 | static void game_compute_size(const game_params *params, int tilesize, | 858 | static void game_compute_size(const game_params *params, int tilesize, |
804 | int *x, int *y) | 859 | const game_ui *ui, int *x, int *y) |
805 | { | 860 | { |
806 | /* Ick: fake up `ds->tilesize' for macro expansion purposes */ | 861 | /* Ick: fake up `ds->tilesize' for macro expansion purposes */ |
807 | struct { int tilesize; } ads, *ds = &ads; | 862 | struct { int tilesize; } ads, *ds = &ads; |
@@ -904,13 +959,6 @@ static void game_redraw(drawing *dr, game_drawstate *ds, | |||
904 | if (!ds->started) { | 959 | if (!ds->started) { |
905 | int coords[10]; | 960 | int coords[10]; |
906 | 961 | ||
907 | draw_rect(dr, 0, 0, | ||
908 | TILE_SIZE * state->w + 2 * BORDER, | ||
909 | TILE_SIZE * state->h + 2 * BORDER, COL_BACKGROUND); | ||
910 | draw_update(dr, 0, 0, | ||
911 | TILE_SIZE * state->w + 2 * BORDER, | ||
912 | TILE_SIZE * state->h + 2 * BORDER); | ||
913 | |||
914 | /* | 962 | /* |
915 | * Recessed area containing the whole puzzle. | 963 | * Recessed area containing the whole puzzle. |
916 | */ | 964 | */ |
@@ -1077,19 +1125,6 @@ static int game_status(const game_state *state) | |||
1077 | return state->completed ? +1 : 0; | 1125 | return state->completed ? +1 : 0; |
1078 | } | 1126 | } |
1079 | 1127 | ||
1080 | static bool game_timing_state(const game_state *state, game_ui *ui) | ||
1081 | { | ||
1082 | return true; | ||
1083 | } | ||
1084 | |||
1085 | static void game_print_size(const game_params *params, float *x, float *y) | ||
1086 | { | ||
1087 | } | ||
1088 | |||
1089 | static void game_print(drawing *dr, const game_state *state, int tilesize) | ||
1090 | { | ||
1091 | } | ||
1092 | |||
1093 | #ifdef COMBINED | 1128 | #ifdef COMBINED |
1094 | #define thegame fifteen | 1129 | #define thegame fifteen |
1095 | #endif | 1130 | #endif |
@@ -1111,12 +1146,14 @@ const struct game thegame = { | |||
1111 | free_game, | 1146 | free_game, |
1112 | true, solve_game, | 1147 | true, solve_game, |
1113 | true, game_can_format_as_text_now, game_text_format, | 1148 | true, game_can_format_as_text_now, game_text_format, |
1149 | get_prefs, set_prefs, | ||
1114 | new_ui, | 1150 | new_ui, |
1115 | free_ui, | 1151 | free_ui, |
1116 | encode_ui, | 1152 | NULL, /* encode_ui */ |
1117 | decode_ui, | 1153 | NULL, /* decode_ui */ |
1118 | NULL, /* game_request_keys */ | 1154 | NULL, /* game_request_keys */ |
1119 | game_changed_state, | 1155 | game_changed_state, |
1156 | NULL, /* current_key_label */ | ||
1120 | interpret_move, | 1157 | interpret_move, |
1121 | execute_move, | 1158 | execute_move, |
1122 | PREFERRED_TILE_SIZE, game_compute_size, game_set_size, | 1159 | PREFERRED_TILE_SIZE, game_compute_size, game_set_size, |
@@ -1128,9 +1165,9 @@ const struct game thegame = { | |||
1128 | game_flash_length, | 1165 | game_flash_length, |
1129 | game_get_cursor_location, | 1166 | game_get_cursor_location, |
1130 | game_status, | 1167 | game_status, |
1131 | false, false, game_print_size, game_print, | 1168 | false, false, NULL, NULL, /* print_size, print */ |
1132 | true, /* wants_statusbar */ | 1169 | true, /* wants_statusbar */ |
1133 | false, game_timing_state, | 1170 | false, NULL, /* timing_state */ |
1134 | 0, /* flags */ | 1171 | 0, /* flags */ |
1135 | }; | 1172 | }; |
1136 | 1173 | ||