summaryrefslogtreecommitdiff
path: root/apps/plugins/puzzles/src/fifteen.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/puzzles/src/fifteen.c')
-rw-r--r--apps/plugins/puzzles/src/fifteen.c259
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
166static 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
159static char *new_game_desc(const game_params *params, random_state *rs, 174static 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
452struct 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
462static 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
435static game_ui *new_ui(const game_state *state) 476static 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
440static void free_ui(game_ui *ui) 487static 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
444static char *encode_ui(const game_ui *ui) 506static 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
449static void decode_ui(game_ui *ui, const char *encoding) 511static void free_ui(game_ui *ui)
450{ 512{
513 sfree(ui);
451} 514}
452 515
453static void game_changed_state(game_ui *ui, const game_state *oldstate, 516static 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
730static game_state *execute_move(const game_state *from, const char *move) 788static 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
803static void game_compute_size(const game_params *params, int tilesize, 858static 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
1080static bool game_timing_state(const game_state *state, game_ui *ui)
1081{
1082 return true;
1083}
1084
1085static void game_print_size(const game_params *params, float *x, float *y)
1086{
1087}
1088
1089static 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