diff options
Diffstat (limited to 'apps/plugins/puzzles/src/sixteen.c')
-rw-r--r-- | apps/plugins/puzzles/src/sixteen.c | 1214 |
1 files changed, 1214 insertions, 0 deletions
diff --git a/apps/plugins/puzzles/src/sixteen.c b/apps/plugins/puzzles/src/sixteen.c new file mode 100644 index 0000000000..edc9771867 --- /dev/null +++ b/apps/plugins/puzzles/src/sixteen.c | |||
@@ -0,0 +1,1214 @@ | |||
1 | /* | ||
2 | * sixteen.c: `16-puzzle', a sliding-tiles jigsaw which differs | ||
3 | * from the 15-puzzle in that you toroidally rotate a row or column | ||
4 | * at a time. | ||
5 | */ | ||
6 | |||
7 | #include <stdio.h> | ||
8 | #include <stdlib.h> | ||
9 | #include <string.h> | ||
10 | #include <assert.h> | ||
11 | #include <ctype.h> | ||
12 | #include <math.h> | ||
13 | |||
14 | #include "puzzles.h" | ||
15 | |||
16 | #define PREFERRED_TILE_SIZE 48 | ||
17 | #define TILE_SIZE (ds->tilesize) | ||
18 | #define BORDER TILE_SIZE | ||
19 | #define HIGHLIGHT_WIDTH (TILE_SIZE / 20) | ||
20 | #define COORD(x) ( (x) * TILE_SIZE + BORDER ) | ||
21 | #define FROMCOORD(x) ( ((x) - BORDER + 2*TILE_SIZE) / TILE_SIZE - 2 ) | ||
22 | |||
23 | #define ANIM_TIME 0.13F | ||
24 | #define FLASH_FRAME 0.13F | ||
25 | |||
26 | #define X(state, i) ( (i) % (state)->w ) | ||
27 | #define Y(state, i) ( (i) / (state)->w ) | ||
28 | #define C(state, x, y) ( (y) * (state)->w + (x) ) | ||
29 | |||
30 | #define TILE_CURSOR(i, state, x, y) ((i) == C((state), (x), (y)) && \ | ||
31 | 0 <= (x) && (x) < (state)->w && \ | ||
32 | 0 <= (y) && (y) < (state)->h) | ||
33 | enum { | ||
34 | COL_BACKGROUND, | ||
35 | COL_TEXT, | ||
36 | COL_HIGHLIGHT, | ||
37 | COL_LOWLIGHT, | ||
38 | NCOLOURS | ||
39 | }; | ||
40 | |||
41 | struct game_params { | ||
42 | int w, h; | ||
43 | int movetarget; | ||
44 | }; | ||
45 | |||
46 | struct game_state { | ||
47 | int w, h, n; | ||
48 | int *tiles; | ||
49 | int completed; | ||
50 | int used_solve; /* used to suppress completion flash */ | ||
51 | int movecount, movetarget; | ||
52 | int last_movement_sense; | ||
53 | }; | ||
54 | |||
55 | static game_params *default_params(void) | ||
56 | { | ||
57 | game_params *ret = snew(game_params); | ||
58 | |||
59 | ret->w = ret->h = 4; | ||
60 | ret->movetarget = 0; | ||
61 | |||
62 | return ret; | ||
63 | } | ||
64 | |||
65 | static int game_fetch_preset(int i, char **name, game_params **params) | ||
66 | { | ||
67 | game_params *ret; | ||
68 | int w, h; | ||
69 | char buf[80]; | ||
70 | |||
71 | switch (i) { | ||
72 | case 0: w = 3, h = 3; break; | ||
73 | case 1: w = 4, h = 3; break; | ||
74 | case 2: w = 4, h = 4; break; | ||
75 | case 3: w = 5, h = 4; break; | ||
76 | case 4: w = 5, h = 5; break; | ||
77 | default: return FALSE; | ||
78 | } | ||
79 | |||
80 | sprintf(buf, "%dx%d", w, h); | ||
81 | *name = dupstr(buf); | ||
82 | *params = ret = snew(game_params); | ||
83 | ret->w = w; | ||
84 | ret->h = h; | ||
85 | ret->movetarget = 0; | ||
86 | return TRUE; | ||
87 | } | ||
88 | |||
89 | static void free_params(game_params *params) | ||
90 | { | ||
91 | sfree(params); | ||
92 | } | ||
93 | |||
94 | static game_params *dup_params(const game_params *params) | ||
95 | { | ||
96 | game_params *ret = snew(game_params); | ||
97 | *ret = *params; /* structure copy */ | ||
98 | return ret; | ||
99 | } | ||
100 | |||
101 | static void decode_params(game_params *ret, char const *string) | ||
102 | { | ||
103 | ret->w = ret->h = atoi(string); | ||
104 | ret->movetarget = 0; | ||
105 | while (*string && isdigit((unsigned char)*string)) string++; | ||
106 | if (*string == 'x') { | ||
107 | string++; | ||
108 | ret->h = atoi(string); | ||
109 | while (*string && isdigit((unsigned char)*string)) | ||
110 | string++; | ||
111 | } | ||
112 | if (*string == 'm') { | ||
113 | string++; | ||
114 | ret->movetarget = atoi(string); | ||
115 | while (*string && isdigit((unsigned char)*string)) | ||
116 | string++; | ||
117 | } | ||
118 | } | ||
119 | |||
120 | static char *encode_params(const game_params *params, int full) | ||
121 | { | ||
122 | char data[256]; | ||
123 | |||
124 | sprintf(data, "%dx%d", params->w, params->h); | ||
125 | /* Shuffle limit is part of the limited parameters, because we have to | ||
126 | * supply the target move count. */ | ||
127 | if (params->movetarget) | ||
128 | sprintf(data + strlen(data), "m%d", params->movetarget); | ||
129 | |||
130 | return dupstr(data); | ||
131 | } | ||
132 | |||
133 | static config_item *game_configure(const game_params *params) | ||
134 | { | ||
135 | config_item *ret; | ||
136 | char buf[80]; | ||
137 | |||
138 | ret = snewn(4, config_item); | ||
139 | |||
140 | ret[0].name = "Width"; | ||
141 | ret[0].type = C_STRING; | ||
142 | sprintf(buf, "%d", params->w); | ||
143 | ret[0].sval = dupstr(buf); | ||
144 | ret[0].ival = 0; | ||
145 | |||
146 | ret[1].name = "Height"; | ||
147 | ret[1].type = C_STRING; | ||
148 | sprintf(buf, "%d", params->h); | ||
149 | ret[1].sval = dupstr(buf); | ||
150 | ret[1].ival = 0; | ||
151 | |||
152 | ret[2].name = "Number of shuffling moves"; | ||
153 | ret[2].type = C_STRING; | ||
154 | sprintf(buf, "%d", params->movetarget); | ||
155 | ret[2].sval = dupstr(buf); | ||
156 | ret[2].ival = 0; | ||
157 | |||
158 | ret[3].name = NULL; | ||
159 | ret[3].type = C_END; | ||
160 | ret[3].sval = NULL; | ||
161 | ret[3].ival = 0; | ||
162 | |||
163 | return ret; | ||
164 | } | ||
165 | |||
166 | static game_params *custom_params(const config_item *cfg) | ||
167 | { | ||
168 | game_params *ret = snew(game_params); | ||
169 | |||
170 | ret->w = atoi(cfg[0].sval); | ||
171 | ret->h = atoi(cfg[1].sval); | ||
172 | ret->movetarget = atoi(cfg[2].sval); | ||
173 | |||
174 | return ret; | ||
175 | } | ||
176 | |||
177 | static char *validate_params(const game_params *params, int full) | ||
178 | { | ||
179 | if (params->w < 2 || params->h < 2) | ||
180 | return "Width and height must both be at least two"; | ||
181 | |||
182 | return NULL; | ||
183 | } | ||
184 | |||
185 | static int perm_parity(int *perm, int n) | ||
186 | { | ||
187 | int i, j, ret; | ||
188 | |||
189 | ret = 0; | ||
190 | |||
191 | for (i = 0; i < n-1; i++) | ||
192 | for (j = i+1; j < n; j++) | ||
193 | if (perm[i] > perm[j]) | ||
194 | ret = !ret; | ||
195 | |||
196 | return ret; | ||
197 | } | ||
198 | |||
199 | static char *new_game_desc(const game_params *params, random_state *rs, | ||
200 | char **aux, int interactive) | ||
201 | { | ||
202 | int stop, n, i, x; | ||
203 | int x1, x2, p1, p2; | ||
204 | int *tiles, *used; | ||
205 | char *ret; | ||
206 | int retlen; | ||
207 | |||
208 | n = params->w * params->h; | ||
209 | |||
210 | tiles = snewn(n, int); | ||
211 | |||
212 | if (params->movetarget) { | ||
213 | int prevoffset = -1; | ||
214 | int max = (params->w > params->h ? params->w : params->h); | ||
215 | int *prevmoves = snewn(max, int); | ||
216 | |||
217 | /* | ||
218 | * Shuffle the old-fashioned way, by making a series of | ||
219 | * single moves on the grid. | ||
220 | */ | ||
221 | |||
222 | for (i = 0; i < n; i++) | ||
223 | tiles[i] = i; | ||
224 | |||
225 | for (i = 0; i < params->movetarget; i++) { | ||
226 | int start, offset, len, direction, index; | ||
227 | int j, tmp; | ||
228 | |||
229 | /* | ||
230 | * Choose a move to make. We can choose from any row | ||
231 | * or any column. | ||
232 | */ | ||
233 | while (1) { | ||
234 | j = random_upto(rs, params->w + params->h); | ||
235 | |||
236 | if (j < params->w) { | ||
237 | /* Column. */ | ||
238 | index = j; | ||
239 | start = j; | ||
240 | offset = params->w; | ||
241 | len = params->h; | ||
242 | } else { | ||
243 | /* Row. */ | ||
244 | index = j - params->w; | ||
245 | start = index * params->w; | ||
246 | offset = 1; | ||
247 | len = params->w; | ||
248 | } | ||
249 | |||
250 | direction = -1 + 2 * random_upto(rs, 2); | ||
251 | |||
252 | /* | ||
253 | * To at least _try_ to avoid boring cases, check | ||
254 | * that this move doesn't directly undo a previous | ||
255 | * one, or repeat it so many times as to turn it | ||
256 | * into fewer moves in the opposite direction. (For | ||
257 | * example, in a row of length 4, we're allowed to | ||
258 | * move it the same way twice, but not three | ||
259 | * times.) | ||
260 | * | ||
261 | * We track this for each individual row/column, | ||
262 | * and clear all the counters as soon as a | ||
263 | * perpendicular move is made. This isn't perfect | ||
264 | * (it _can't_ guaranteeably be perfect - there | ||
265 | * will always come a move count beyond which a | ||
266 | * shorter solution will be possible than the one | ||
267 | * which constructed the position) but it should | ||
268 | * sort out all the obvious cases. | ||
269 | */ | ||
270 | if (offset == prevoffset) { | ||
271 | tmp = prevmoves[index] + direction; | ||
272 | if (abs(2*tmp) > len || abs(tmp) < abs(prevmoves[index])) | ||
273 | continue; | ||
274 | } | ||
275 | |||
276 | /* If we didn't `continue', we've found an OK move to make. */ | ||
277 | if (offset != prevoffset) { | ||
278 | int i; | ||
279 | for (i = 0; i < max; i++) | ||
280 | prevmoves[i] = 0; | ||
281 | prevoffset = offset; | ||
282 | } | ||
283 | prevmoves[index] += direction; | ||
284 | break; | ||
285 | } | ||
286 | |||
287 | /* | ||
288 | * Make the move. | ||
289 | */ | ||
290 | if (direction < 0) { | ||
291 | start += (len-1) * offset; | ||
292 | offset = -offset; | ||
293 | } | ||
294 | tmp = tiles[start]; | ||
295 | for (j = 0; j+1 < len; j++) | ||
296 | tiles[start + j*offset] = tiles[start + (j+1)*offset]; | ||
297 | tiles[start + (len-1) * offset] = tmp; | ||
298 | } | ||
299 | |||
300 | sfree(prevmoves); | ||
301 | |||
302 | } else { | ||
303 | |||
304 | used = snewn(n, int); | ||
305 | |||
306 | for (i = 0; i < n; i++) { | ||
307 | tiles[i] = -1; | ||
308 | used[i] = FALSE; | ||
309 | } | ||
310 | |||
311 | /* | ||
312 | * If both dimensions are odd, there is a parity | ||
313 | * constraint. | ||
314 | */ | ||
315 | if (params->w & params->h & 1) | ||
316 | stop = 2; | ||
317 | else | ||
318 | stop = 0; | ||
319 | |||
320 | /* | ||
321 | * Place everything except (possibly) the last two tiles. | ||
322 | */ | ||
323 | for (x = 0, i = n; i > stop; i--) { | ||
324 | int k = i > 1 ? random_upto(rs, i) : 0; | ||
325 | int j; | ||
326 | |||
327 | for (j = 0; j < n; j++) | ||
328 | if (!used[j] && (k-- == 0)) | ||
329 | break; | ||
330 | |||
331 | assert(j < n && !used[j]); | ||
332 | used[j] = TRUE; | ||
333 | |||
334 | while (tiles[x] >= 0) | ||
335 | x++; | ||
336 | assert(x < n); | ||
337 | tiles[x] = j; | ||
338 | } | ||
339 | |||
340 | if (stop) { | ||
341 | /* | ||
342 | * Find the last two locations, and the last two | ||
343 | * pieces. | ||
344 | */ | ||
345 | while (tiles[x] >= 0) | ||
346 | x++; | ||
347 | assert(x < n); | ||
348 | x1 = x; | ||
349 | x++; | ||
350 | while (tiles[x] >= 0) | ||
351 | x++; | ||
352 | assert(x < n); | ||
353 | x2 = x; | ||
354 | |||
355 | for (i = 0; i < n; i++) | ||
356 | if (!used[i]) | ||
357 | break; | ||
358 | p1 = i; | ||
359 | for (i = p1+1; i < n; i++) | ||
360 | if (!used[i]) | ||
361 | break; | ||
362 | p2 = i; | ||
363 | |||
364 | /* | ||
365 | * Try the last two tiles one way round. If that fails, | ||
366 | * swap them. | ||
367 | */ | ||
368 | tiles[x1] = p1; | ||
369 | tiles[x2] = p2; | ||
370 | if (perm_parity(tiles, n) != 0) { | ||
371 | tiles[x1] = p2; | ||
372 | tiles[x2] = p1; | ||
373 | assert(perm_parity(tiles, n) == 0); | ||
374 | } | ||
375 | } | ||
376 | |||
377 | sfree(used); | ||
378 | } | ||
379 | |||
380 | /* | ||
381 | * Now construct the game description, by describing the tile | ||
382 | * array as a simple sequence of comma-separated integers. | ||
383 | */ | ||
384 | ret = NULL; | ||
385 | retlen = 0; | ||
386 | for (i = 0; i < n; i++) { | ||
387 | char buf[80]; | ||
388 | int k; | ||
389 | |||
390 | k = sprintf(buf, "%d,", tiles[i]+1); | ||
391 | |||
392 | ret = sresize(ret, retlen + k + 1, char); | ||
393 | strcpy(ret + retlen, buf); | ||
394 | retlen += k; | ||
395 | } | ||
396 | ret[retlen-1] = '\0'; /* delete last comma */ | ||
397 | |||
398 | sfree(tiles); | ||
399 | |||
400 | return ret; | ||
401 | } | ||
402 | |||
403 | |||
404 | static char *validate_desc(const game_params *params, const char *desc) | ||
405 | { | ||
406 | const char *p; | ||
407 | char *err; | ||
408 | int i, area; | ||
409 | int *used; | ||
410 | |||
411 | area = params->w * params->h; | ||
412 | p = desc; | ||
413 | err = NULL; | ||
414 | |||
415 | used = snewn(area, int); | ||
416 | for (i = 0; i < area; i++) | ||
417 | used[i] = FALSE; | ||
418 | |||
419 | for (i = 0; i < area; i++) { | ||
420 | const char *q = p; | ||
421 | int n; | ||
422 | |||
423 | if (*p < '0' || *p > '9') { | ||
424 | err = "Not enough numbers in string"; | ||
425 | goto leave; | ||
426 | } | ||
427 | while (*p >= '0' && *p <= '9') | ||
428 | p++; | ||
429 | if (i < area-1 && *p != ',') { | ||
430 | err = "Expected comma after number"; | ||
431 | goto leave; | ||
432 | } | ||
433 | else if (i == area-1 && *p) { | ||
434 | err = "Excess junk at end of string"; | ||
435 | goto leave; | ||
436 | } | ||
437 | n = atoi(q); | ||
438 | if (n < 1 || n > area) { | ||
439 | err = "Number out of range"; | ||
440 | goto leave; | ||
441 | } | ||
442 | if (used[n-1]) { | ||
443 | err = "Number used twice"; | ||
444 | goto leave; | ||
445 | } | ||
446 | used[n-1] = TRUE; | ||
447 | |||
448 | if (*p) p++; /* eat comma */ | ||
449 | } | ||
450 | |||
451 | leave: | ||
452 | sfree(used); | ||
453 | return err; | ||
454 | } | ||
455 | |||
456 | static game_state *new_game(midend *me, const game_params *params, | ||
457 | const char *desc) | ||
458 | { | ||
459 | game_state *state = snew(game_state); | ||
460 | int i; | ||
461 | const char *p; | ||
462 | |||
463 | state->w = params->w; | ||
464 | state->h = params->h; | ||
465 | state->n = params->w * params->h; | ||
466 | state->tiles = snewn(state->n, int); | ||
467 | |||
468 | p = desc; | ||
469 | i = 0; | ||
470 | for (i = 0; i < state->n; i++) { | ||
471 | assert(*p); | ||
472 | state->tiles[i] = atoi(p); | ||
473 | while (*p && *p != ',') | ||
474 | p++; | ||
475 | if (*p) p++; /* eat comma */ | ||
476 | } | ||
477 | assert(!*p); | ||
478 | |||
479 | state->completed = state->movecount = 0; | ||
480 | state->movetarget = params->movetarget; | ||
481 | state->used_solve = FALSE; | ||
482 | state->last_movement_sense = 0; | ||
483 | |||
484 | return state; | ||
485 | } | ||
486 | |||
487 | static game_state *dup_game(const game_state *state) | ||
488 | { | ||
489 | game_state *ret = snew(game_state); | ||
490 | |||
491 | ret->w = state->w; | ||
492 | ret->h = state->h; | ||
493 | ret->n = state->n; | ||
494 | ret->tiles = snewn(state->w * state->h, int); | ||
495 | memcpy(ret->tiles, state->tiles, state->w * state->h * sizeof(int)); | ||
496 | ret->completed = state->completed; | ||
497 | ret->movecount = state->movecount; | ||
498 | ret->movetarget = state->movetarget; | ||
499 | ret->used_solve = state->used_solve; | ||
500 | ret->last_movement_sense = state->last_movement_sense; | ||
501 | |||
502 | return ret; | ||
503 | } | ||
504 | |||
505 | static void free_game(game_state *state) | ||
506 | { | ||
507 | sfree(state->tiles); | ||
508 | sfree(state); | ||
509 | } | ||
510 | |||
511 | static char *solve_game(const game_state *state, const game_state *currstate, | ||
512 | const char *aux, char **error) | ||
513 | { | ||
514 | return dupstr("S"); | ||
515 | } | ||
516 | |||
517 | static int game_can_format_as_text_now(const game_params *params) | ||
518 | { | ||
519 | return TRUE; | ||
520 | } | ||
521 | |||
522 | static char *game_text_format(const game_state *state) | ||
523 | { | ||
524 | char *ret, *p, buf[80]; | ||
525 | int x, y, col, maxlen; | ||
526 | |||
527 | /* | ||
528 | * First work out how many characters we need to display each | ||
529 | * number. | ||
530 | */ | ||
531 | col = sprintf(buf, "%d", state->n); | ||
532 | |||
533 | /* | ||
534 | * Now we know the exact total size of the grid we're going to | ||
535 | * produce: it's got h rows, each containing w lots of col, w-1 | ||
536 | * spaces and a trailing newline. | ||
537 | */ | ||
538 | maxlen = state->h * state->w * (col+1); | ||
539 | |||
540 | ret = snewn(maxlen+1, char); | ||
541 | p = ret; | ||
542 | |||
543 | for (y = 0; y < state->h; y++) { | ||
544 | for (x = 0; x < state->w; x++) { | ||
545 | int v = state->tiles[state->w*y+x]; | ||
546 | sprintf(buf, "%*d", col, v); | ||
547 | memcpy(p, buf, col); | ||
548 | p += col; | ||
549 | if (x+1 == state->w) | ||
550 | *p++ = '\n'; | ||
551 | else | ||
552 | *p++ = ' '; | ||
553 | } | ||
554 | } | ||
555 | |||
556 | assert(p - ret == maxlen); | ||
557 | *p = '\0'; | ||
558 | return ret; | ||
559 | } | ||
560 | |||
561 | enum cursor_mode { unlocked, lock_tile, lock_position }; | ||
562 | |||
563 | struct game_ui { | ||
564 | int cur_x, cur_y; | ||
565 | int cur_visible; | ||
566 | enum cursor_mode cur_mode; | ||
567 | }; | ||
568 | |||
569 | static game_ui *new_ui(const game_state *state) | ||
570 | { | ||
571 | game_ui *ui = snew(game_ui); | ||
572 | ui->cur_x = 0; | ||
573 | ui->cur_y = 0; | ||
574 | ui->cur_visible = FALSE; | ||
575 | ui->cur_mode = unlocked; | ||
576 | |||
577 | return ui; | ||
578 | } | ||
579 | |||
580 | static void free_ui(game_ui *ui) | ||
581 | { | ||
582 | sfree(ui); | ||
583 | } | ||
584 | |||
585 | static char *encode_ui(const game_ui *ui) | ||
586 | { | ||
587 | return NULL; | ||
588 | } | ||
589 | |||
590 | static void decode_ui(game_ui *ui, const char *encoding) | ||
591 | { | ||
592 | } | ||
593 | |||
594 | static void game_changed_state(game_ui *ui, const game_state *oldstate, | ||
595 | const game_state *newstate) | ||
596 | { | ||
597 | } | ||
598 | |||
599 | struct game_drawstate { | ||
600 | int started; | ||
601 | int w, h, bgcolour; | ||
602 | int *tiles; | ||
603 | int tilesize; | ||
604 | int cur_x, cur_y; | ||
605 | }; | ||
606 | |||
607 | static char *interpret_move(const game_state *state, game_ui *ui, | ||
608 | const game_drawstate *ds, | ||
609 | int x, int y, int button) | ||
610 | { | ||
611 | int cx = -1, cy = -1, dx, dy; | ||
612 | char buf[80]; | ||
613 | int shift = button & MOD_SHFT, control = button & MOD_CTRL, | ||
614 | pad = button & MOD_NUM_KEYPAD; | ||
615 | |||
616 | button &= ~MOD_MASK; | ||
617 | |||
618 | if (IS_CURSOR_MOVE(button) || pad) { | ||
619 | if (!ui->cur_visible) { | ||
620 | ui->cur_visible = 1; | ||
621 | return ""; | ||
622 | } | ||
623 | |||
624 | if (control || shift || ui->cur_mode) { | ||
625 | int x = ui->cur_x, y = ui->cur_y, xwrap = x, ywrap = y; | ||
626 | if (x < 0 || x >= state->w || y < 0 || y >= state->h) | ||
627 | return NULL; | ||
628 | move_cursor(button | pad, &x, &y, | ||
629 | state->w, state->h, FALSE); | ||
630 | move_cursor(button | pad, &xwrap, &ywrap, | ||
631 | state->w, state->h, TRUE); | ||
632 | |||
633 | if (x != xwrap) { | ||
634 | sprintf(buf, "R%d,%c1", y, x ? '+' : '-'); | ||
635 | } else if (y != ywrap) { | ||
636 | sprintf(buf, "C%d,%c1", x, y ? '+' : '-'); | ||
637 | } else if (x == ui->cur_x) | ||
638 | sprintf(buf, "C%d,%d", x, y - ui->cur_y); | ||
639 | else | ||
640 | sprintf(buf, "R%d,%d", y, x - ui->cur_x); | ||
641 | |||
642 | if (control || (!shift && ui->cur_mode == lock_tile)) { | ||
643 | ui->cur_x = xwrap; | ||
644 | ui->cur_y = ywrap; | ||
645 | } | ||
646 | |||
647 | return dupstr(buf); | ||
648 | } else { | ||
649 | int x = ui->cur_x + 1, y = ui->cur_y + 1; | ||
650 | |||
651 | move_cursor(button | pad, &x, &y, | ||
652 | state->w + 2, state->h + 2, FALSE); | ||
653 | |||
654 | if (x == 0 && y == 0) { | ||
655 | int t = ui->cur_x; | ||
656 | ui->cur_x = ui->cur_y; | ||
657 | ui->cur_y = t; | ||
658 | } else if (x == 0 && y == state->h + 1) { | ||
659 | int t = ui->cur_x; | ||
660 | ui->cur_x = (state->h - 1) - ui->cur_y; | ||
661 | ui->cur_y = (state->h - 1) - t; | ||
662 | } else if (x == state->w + 1 && y == 0) { | ||
663 | int t = ui->cur_x; | ||
664 | ui->cur_x = (state->w - 1) - ui->cur_y; | ||
665 | ui->cur_y = (state->w - 1) - t; | ||
666 | } else if (x == state->w + 1 && y == state->h + 1) { | ||
667 | int t = ui->cur_x; | ||
668 | ui->cur_x = state->w - state->h + ui->cur_y; | ||
669 | ui->cur_y = state->h - state->w + t; | ||
670 | } else { | ||
671 | ui->cur_x = x - 1; | ||
672 | ui->cur_y = y - 1; | ||
673 | } | ||
674 | |||
675 | ui->cur_visible = 1; | ||
676 | return ""; | ||
677 | } | ||
678 | } | ||
679 | |||
680 | if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { | ||
681 | cx = FROMCOORD(x); | ||
682 | cy = FROMCOORD(y); | ||
683 | ui->cur_visible = 0; | ||
684 | } else if (IS_CURSOR_SELECT(button)) { | ||
685 | if (ui->cur_visible) { | ||
686 | if (ui->cur_x == -1 || ui->cur_x == state->w || | ||
687 | ui->cur_y == -1 || ui->cur_y == state->h) { | ||
688 | cx = ui->cur_x; | ||
689 | cy = ui->cur_y; | ||
690 | } else { | ||
691 | const enum cursor_mode m = (button == CURSOR_SELECT2 ? | ||
692 | lock_position : lock_tile); | ||
693 | ui->cur_mode = (ui->cur_mode == m ? unlocked : m); | ||
694 | return ""; | ||
695 | } | ||
696 | } else { | ||
697 | ui->cur_visible = 1; | ||
698 | return ""; | ||
699 | } | ||
700 | } else { | ||
701 | return NULL; | ||
702 | } | ||
703 | |||
704 | if (cx == -1 && cy >= 0 && cy < state->h) | ||
705 | dx = -1, dy = 0; | ||
706 | else if (cx == state->w && cy >= 0 && cy < state->h) | ||
707 | dx = +1, dy = 0; | ||
708 | else if (cy == -1 && cx >= 0 && cx < state->w) | ||
709 | dy = -1, dx = 0; | ||
710 | else if (cy == state->h && cx >= 0 && cx < state->w) | ||
711 | dy = +1, dx = 0; | ||
712 | else | ||
713 | return ""; /* invalid click location */ | ||
714 | |||
715 | /* reverse direction if right hand button is pressed */ | ||
716 | if (button == RIGHT_BUTTON || button == CURSOR_SELECT2) { | ||
717 | dx = -dx; | ||
718 | dy = -dy; | ||
719 | } | ||
720 | |||
721 | if (dx) | ||
722 | sprintf(buf, "R%d,%d", cy, dx); | ||
723 | else | ||
724 | sprintf(buf, "C%d,%d", cx, dy); | ||
725 | return dupstr(buf); | ||
726 | } | ||
727 | |||
728 | static game_state *execute_move(const game_state *from, const char *move) | ||
729 | { | ||
730 | int cx, cy, dx, dy; | ||
731 | int tx, ty, n; | ||
732 | game_state *ret; | ||
733 | |||
734 | if (!strcmp(move, "S")) { | ||
735 | int i; | ||
736 | |||
737 | ret = dup_game(from); | ||
738 | |||
739 | /* | ||
740 | * Simply replace the grid with a solved one. For this game, | ||
741 | * this isn't a useful operation for actually telling the user | ||
742 | * what they should have done, but it is useful for | ||
743 | * conveniently being able to get hold of a clean state from | ||
744 | * which to practise manoeuvres. | ||
745 | */ | ||
746 | for (i = 0; i < ret->n; i++) | ||
747 | ret->tiles[i] = i+1; | ||
748 | ret->used_solve = TRUE; | ||
749 | ret->completed = ret->movecount = 1; | ||
750 | |||
751 | return ret; | ||
752 | } | ||
753 | |||
754 | if (move[0] == 'R' && sscanf(move+1, "%d,%d", &cy, &dx) == 2 && | ||
755 | cy >= 0 && cy < from->h) { | ||
756 | cx = dy = 0; | ||
757 | n = from->w; | ||
758 | } else if (move[0] == 'C' && sscanf(move+1, "%d,%d", &cx, &dy) == 2 && | ||
759 | cx >= 0 && cx < from->w) { | ||
760 | cy = dx = 0; | ||
761 | n = from->h; | ||
762 | } else | ||
763 | return NULL; | ||
764 | |||
765 | ret = dup_game(from); | ||
766 | |||
767 | do { | ||
768 | tx = (cx - dx + from->w) % from->w; | ||
769 | ty = (cy - dy + from->h) % from->h; | ||
770 | ret->tiles[C(ret, cx, cy)] = from->tiles[C(from, tx, ty)]; | ||
771 | cx = tx; | ||
772 | cy = ty; | ||
773 | } while (--n > 0); | ||
774 | |||
775 | ret->movecount++; | ||
776 | |||
777 | ret->last_movement_sense = dx+dy; | ||
778 | |||
779 | /* | ||
780 | * See if the game has been completed. | ||
781 | */ | ||
782 | if (!ret->completed) { | ||
783 | ret->completed = ret->movecount; | ||
784 | for (n = 0; n < ret->n; n++) | ||
785 | if (ret->tiles[n] != n+1) | ||
786 | ret->completed = FALSE; | ||
787 | } | ||
788 | |||
789 | return ret; | ||
790 | } | ||
791 | |||
792 | /* ---------------------------------------------------------------------- | ||
793 | * Drawing routines. | ||
794 | */ | ||
795 | |||
796 | static void game_compute_size(const game_params *params, int tilesize, | ||
797 | int *x, int *y) | ||
798 | { | ||
799 | /* Ick: fake up `ds->tilesize' for macro expansion purposes */ | ||
800 | struct { int tilesize; } ads, *ds = &ads; | ||
801 | ads.tilesize = tilesize; | ||
802 | |||
803 | *x = TILE_SIZE * params->w + 2 * BORDER; | ||
804 | *y = TILE_SIZE * params->h + 2 * BORDER; | ||
805 | } | ||
806 | |||
807 | static void game_set_size(drawing *dr, game_drawstate *ds, | ||
808 | const game_params *params, int tilesize) | ||
809 | { | ||
810 | ds->tilesize = tilesize; | ||
811 | } | ||
812 | |||
813 | static float *game_colours(frontend *fe, int *ncolours) | ||
814 | { | ||
815 | float *ret = snewn(3 * NCOLOURS, float); | ||
816 | int i; | ||
817 | |||
818 | game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT); | ||
819 | |||
820 | for (i = 0; i < 3; i++) | ||
821 | ret[COL_TEXT * 3 + i] = 0.0; | ||
822 | |||
823 | *ncolours = NCOLOURS; | ||
824 | return ret; | ||
825 | } | ||
826 | |||
827 | static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) | ||
828 | { | ||
829 | struct game_drawstate *ds = snew(struct game_drawstate); | ||
830 | int i; | ||
831 | |||
832 | ds->started = FALSE; | ||
833 | ds->w = state->w; | ||
834 | ds->h = state->h; | ||
835 | ds->bgcolour = COL_BACKGROUND; | ||
836 | ds->tiles = snewn(ds->w*ds->h, int); | ||
837 | ds->tilesize = 0; /* haven't decided yet */ | ||
838 | for (i = 0; i < ds->w*ds->h; i++) | ||
839 | ds->tiles[i] = -1; | ||
840 | ds->cur_x = ds->cur_y = -1; | ||
841 | |||
842 | return ds; | ||
843 | } | ||
844 | |||
845 | static void game_free_drawstate(drawing *dr, game_drawstate *ds) | ||
846 | { | ||
847 | sfree(ds->tiles); | ||
848 | sfree(ds); | ||
849 | } | ||
850 | |||
851 | static void draw_tile(drawing *dr, game_drawstate *ds, | ||
852 | const game_state *state, int x, int y, | ||
853 | int tile, int flash_colour) | ||
854 | { | ||
855 | if (tile == 0) { | ||
856 | draw_rect(dr, x, y, TILE_SIZE, TILE_SIZE, | ||
857 | flash_colour); | ||
858 | } else { | ||
859 | int coords[6]; | ||
860 | char str[40]; | ||
861 | |||
862 | coords[0] = x + TILE_SIZE - 1; | ||
863 | coords[1] = y + TILE_SIZE - 1; | ||
864 | coords[2] = x + TILE_SIZE - 1; | ||
865 | coords[3] = y; | ||
866 | coords[4] = x; | ||
867 | coords[5] = y + TILE_SIZE - 1; | ||
868 | draw_polygon(dr, coords, 3, COL_LOWLIGHT, COL_LOWLIGHT); | ||
869 | |||
870 | coords[0] = x; | ||
871 | coords[1] = y; | ||
872 | draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT); | ||
873 | |||
874 | draw_rect(dr, x + HIGHLIGHT_WIDTH, y + HIGHLIGHT_WIDTH, | ||
875 | TILE_SIZE - 2*HIGHLIGHT_WIDTH, TILE_SIZE - 2*HIGHLIGHT_WIDTH, | ||
876 | flash_colour); | ||
877 | |||
878 | sprintf(str, "%d", tile); | ||
879 | draw_text(dr, x + TILE_SIZE/2, y + TILE_SIZE/2, | ||
880 | FONT_VARIABLE, TILE_SIZE/3, ALIGN_VCENTRE | ALIGN_HCENTRE, | ||
881 | COL_TEXT, str); | ||
882 | } | ||
883 | draw_update(dr, x, y, TILE_SIZE, TILE_SIZE); | ||
884 | } | ||
885 | |||
886 | static void draw_arrow(drawing *dr, game_drawstate *ds, | ||
887 | int x, int y, int xdx, int xdy, int cur) | ||
888 | { | ||
889 | int coords[14]; | ||
890 | int ydy = -xdx, ydx = xdy; | ||
891 | |||
892 | #define POINT(n, xx, yy) ( \ | ||
893 | coords[2*(n)+0] = x + (xx)*xdx + (yy)*ydx, \ | ||
894 | coords[2*(n)+1] = y + (xx)*xdy + (yy)*ydy) | ||
895 | |||
896 | POINT(0, TILE_SIZE / 2, 3 * TILE_SIZE / 4); /* top of arrow */ | ||
897 | POINT(1, 3 * TILE_SIZE / 4, TILE_SIZE / 2); /* right corner */ | ||
898 | POINT(2, 5 * TILE_SIZE / 8, TILE_SIZE / 2); /* right concave */ | ||
899 | POINT(3, 5 * TILE_SIZE / 8, TILE_SIZE / 4); /* bottom right */ | ||
900 | POINT(4, 3 * TILE_SIZE / 8, TILE_SIZE / 4); /* bottom left */ | ||
901 | POINT(5, 3 * TILE_SIZE / 8, TILE_SIZE / 2); /* left concave */ | ||
902 | POINT(6, TILE_SIZE / 4, TILE_SIZE / 2); /* left corner */ | ||
903 | |||
904 | draw_polygon(dr, coords, 7, cur ? COL_HIGHLIGHT : COL_LOWLIGHT, COL_TEXT); | ||
905 | } | ||
906 | |||
907 | static void draw_arrow_for_cursor(drawing *dr, game_drawstate *ds, | ||
908 | int cur_x, int cur_y, int cur) | ||
909 | { | ||
910 | if (cur_x == -1 && cur_y == -1) | ||
911 | return; /* 'no cursur here */ | ||
912 | else if (cur_x == -1) /* LH column. */ | ||
913 | draw_arrow(dr, ds, COORD(0), COORD(cur_y+1), 0, -1, cur); | ||
914 | else if (cur_x == ds->w) /* RH column */ | ||
915 | draw_arrow(dr, ds, COORD(ds->w), COORD(cur_y), 0, +1, cur); | ||
916 | else if (cur_y == -1) /* Top row */ | ||
917 | draw_arrow(dr, ds, COORD(cur_x), COORD(0), +1, 0, cur); | ||
918 | else if (cur_y == ds->h) /* Bottom row */ | ||
919 | draw_arrow(dr, ds, COORD(cur_x+1), COORD(ds->h), -1, 0, cur); | ||
920 | else | ||
921 | return; | ||
922 | |||
923 | draw_update(dr, COORD(cur_x), COORD(cur_y), | ||
924 | TILE_SIZE, TILE_SIZE); | ||
925 | } | ||
926 | |||
927 | static void game_redraw(drawing *dr, game_drawstate *ds, | ||
928 | const game_state *oldstate, const game_state *state, | ||
929 | int dir, const game_ui *ui, | ||
930 | float animtime, float flashtime) | ||
931 | { | ||
932 | int i, bgcolour; | ||
933 | int cur_x = -1, cur_y = -1; | ||
934 | |||
935 | if (flashtime > 0) { | ||
936 | int frame = (int)(flashtime / FLASH_FRAME); | ||
937 | bgcolour = (frame % 2 ? COL_LOWLIGHT : COL_HIGHLIGHT); | ||
938 | } else | ||
939 | bgcolour = COL_BACKGROUND; | ||
940 | |||
941 | if (!ds->started) { | ||
942 | int coords[10]; | ||
943 | |||
944 | draw_rect(dr, 0, 0, | ||
945 | TILE_SIZE * state->w + 2 * BORDER, | ||
946 | TILE_SIZE * state->h + 2 * BORDER, COL_BACKGROUND); | ||
947 | draw_update(dr, 0, 0, | ||
948 | TILE_SIZE * state->w + 2 * BORDER, | ||
949 | TILE_SIZE * state->h + 2 * BORDER); | ||
950 | |||
951 | /* | ||
952 | * Recessed area containing the whole puzzle. | ||
953 | */ | ||
954 | coords[0] = COORD(state->w) + HIGHLIGHT_WIDTH - 1; | ||
955 | coords[1] = COORD(state->h) + HIGHLIGHT_WIDTH - 1; | ||
956 | coords[2] = COORD(state->w) + HIGHLIGHT_WIDTH - 1; | ||
957 | coords[3] = COORD(0) - HIGHLIGHT_WIDTH; | ||
958 | coords[4] = coords[2] - TILE_SIZE; | ||
959 | coords[5] = coords[3] + TILE_SIZE; | ||
960 | coords[8] = COORD(0) - HIGHLIGHT_WIDTH; | ||
961 | coords[9] = COORD(state->h) + HIGHLIGHT_WIDTH - 1; | ||
962 | coords[6] = coords[8] + TILE_SIZE; | ||
963 | coords[7] = coords[9] - TILE_SIZE; | ||
964 | draw_polygon(dr, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT); | ||
965 | |||
966 | coords[1] = COORD(0) - HIGHLIGHT_WIDTH; | ||
967 | coords[0] = COORD(0) - HIGHLIGHT_WIDTH; | ||
968 | draw_polygon(dr, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT); | ||
969 | |||
970 | /* | ||
971 | * Arrows for making moves. | ||
972 | */ | ||
973 | for (i = 0; i < state->w; i++) { | ||
974 | draw_arrow(dr, ds, COORD(i), COORD(0), +1, 0, 0); | ||
975 | draw_arrow(dr, ds, COORD(i+1), COORD(state->h), -1, 0, 0); | ||
976 | } | ||
977 | for (i = 0; i < state->h; i++) { | ||
978 | draw_arrow(dr, ds, COORD(state->w), COORD(i), 0, +1, 0); | ||
979 | draw_arrow(dr, ds, COORD(0), COORD(i+1), 0, -1, 0); | ||
980 | } | ||
981 | |||
982 | ds->started = TRUE; | ||
983 | } | ||
984 | /* | ||
985 | * Cursor (highlighted arrow around edge) | ||
986 | */ | ||
987 | if (ui->cur_visible) { | ||
988 | cur_x = ui->cur_x; cur_y = ui->cur_y; | ||
989 | } | ||
990 | |||
991 | if (cur_x != ds->cur_x || cur_y != ds->cur_y) { | ||
992 | /* Cursor has changed; redraw two (prev and curr) arrows. */ | ||
993 | draw_arrow_for_cursor(dr, ds, cur_x, cur_y, 1); | ||
994 | draw_arrow_for_cursor(dr, ds, ds->cur_x, ds->cur_y, 0); | ||
995 | } | ||
996 | |||
997 | /* | ||
998 | * Now draw each tile. | ||
999 | */ | ||
1000 | |||
1001 | clip(dr, COORD(0), COORD(0), TILE_SIZE*state->w, TILE_SIZE*state->h); | ||
1002 | |||
1003 | for (i = 0; i < state->n; i++) { | ||
1004 | int t, t0; | ||
1005 | /* | ||
1006 | * Figure out what should be displayed at this | ||
1007 | * location. It's either a simple tile, or it's a | ||
1008 | * transition between two tiles (in which case we say | ||
1009 | * -1 because it must always be drawn). | ||
1010 | */ | ||
1011 | |||
1012 | if (oldstate && oldstate->tiles[i] != state->tiles[i]) | ||
1013 | t = -1; | ||
1014 | else | ||
1015 | t = state->tiles[i]; | ||
1016 | |||
1017 | t0 = t; | ||
1018 | |||
1019 | if (ds->bgcolour != bgcolour || /* always redraw when flashing */ | ||
1020 | ds->tiles[i] != t || ds->tiles[i] == -1 || t == -1 || | ||
1021 | ((ds->cur_x != cur_x || ds->cur_y != cur_y) && /* cursor moved */ | ||
1022 | (TILE_CURSOR(i, state, ds->cur_x, ds->cur_y) || | ||
1023 | TILE_CURSOR(i, state, cur_x, cur_y)))) { | ||
1024 | int x, y, x2, y2; | ||
1025 | |||
1026 | /* | ||
1027 | * Figure out what to _actually_ draw, and where to | ||
1028 | * draw it. | ||
1029 | */ | ||
1030 | if (t == -1) { | ||
1031 | int x0, y0, x1, y1, dx, dy; | ||
1032 | int j; | ||
1033 | float c; | ||
1034 | int sense; | ||
1035 | |||
1036 | if (dir < 0) { | ||
1037 | assert(oldstate); | ||
1038 | sense = -oldstate->last_movement_sense; | ||
1039 | } else { | ||
1040 | sense = state->last_movement_sense; | ||
1041 | } | ||
1042 | |||
1043 | t = state->tiles[i]; | ||
1044 | |||
1045 | /* | ||
1046 | * FIXME: must be prepared to draw a double | ||
1047 | * tile in some situations. | ||
1048 | */ | ||
1049 | |||
1050 | /* | ||
1051 | * Find the coordinates of this tile in the old and | ||
1052 | * new states. | ||
1053 | */ | ||
1054 | x1 = COORD(X(state, i)); | ||
1055 | y1 = COORD(Y(state, i)); | ||
1056 | for (j = 0; j < oldstate->n; j++) | ||
1057 | if (oldstate->tiles[j] == state->tiles[i]) | ||
1058 | break; | ||
1059 | assert(j < oldstate->n); | ||
1060 | x0 = COORD(X(state, j)); | ||
1061 | y0 = COORD(Y(state, j)); | ||
1062 | |||
1063 | dx = (x1 - x0); | ||
1064 | if (dx != 0 && | ||
1065 | dx != TILE_SIZE * sense) { | ||
1066 | dx = (dx < 0 ? dx + TILE_SIZE * state->w : | ||
1067 | dx - TILE_SIZE * state->w); | ||
1068 | assert(abs(dx) == TILE_SIZE); | ||
1069 | } | ||
1070 | dy = (y1 - y0); | ||
1071 | if (dy != 0 && | ||
1072 | dy != TILE_SIZE * sense) { | ||
1073 | dy = (dy < 0 ? dy + TILE_SIZE * state->h : | ||
1074 | dy - TILE_SIZE * state->h); | ||
1075 | assert(abs(dy) == TILE_SIZE); | ||
1076 | } | ||
1077 | |||
1078 | c = (animtime / ANIM_TIME); | ||
1079 | if (c < 0.0F) c = 0.0F; | ||
1080 | if (c > 1.0F) c = 1.0F; | ||
1081 | |||
1082 | x = x0 + (int)(c * dx); | ||
1083 | y = y0 + (int)(c * dy); | ||
1084 | x2 = x1 - dx + (int)(c * dx); | ||
1085 | y2 = y1 - dy + (int)(c * dy); | ||
1086 | } else { | ||
1087 | x = COORD(X(state, i)); | ||
1088 | y = COORD(Y(state, i)); | ||
1089 | x2 = y2 = -1; | ||
1090 | } | ||
1091 | |||
1092 | draw_tile(dr, ds, state, x, y, t, | ||
1093 | (x2 == -1 && TILE_CURSOR(i, state, cur_x, cur_y)) ? | ||
1094 | COL_LOWLIGHT : bgcolour); | ||
1095 | |||
1096 | if (x2 != -1 || y2 != -1) | ||
1097 | draw_tile(dr, ds, state, x2, y2, t, bgcolour); | ||
1098 | } | ||
1099 | ds->tiles[i] = t0; | ||
1100 | } | ||
1101 | |||
1102 | ds->cur_x = cur_x; | ||
1103 | ds->cur_y = cur_y; | ||
1104 | |||
1105 | unclip(dr); | ||
1106 | |||
1107 | ds->bgcolour = bgcolour; | ||
1108 | |||
1109 | /* | ||
1110 | * Update the status bar. | ||
1111 | */ | ||
1112 | { | ||
1113 | char statusbuf[256]; | ||
1114 | |||
1115 | /* | ||
1116 | * Don't show the new status until we're also showing the | ||
1117 | * new _state_ - after the game animation is complete. | ||
1118 | */ | ||
1119 | if (oldstate) | ||
1120 | state = oldstate; | ||
1121 | |||
1122 | if (state->used_solve) | ||
1123 | sprintf(statusbuf, "Moves since auto-solve: %d", | ||
1124 | state->movecount - state->completed); | ||
1125 | else { | ||
1126 | sprintf(statusbuf, "%sMoves: %d", | ||
1127 | (state->completed ? "COMPLETED! " : ""), | ||
1128 | (state->completed ? state->completed : state->movecount)); | ||
1129 | if (state->movetarget) | ||
1130 | sprintf(statusbuf+strlen(statusbuf), " (target %d)", | ||
1131 | state->movetarget); | ||
1132 | } | ||
1133 | |||
1134 | status_bar(dr, statusbuf); | ||
1135 | } | ||
1136 | } | ||
1137 | |||
1138 | static float game_anim_length(const game_state *oldstate, | ||
1139 | const game_state *newstate, int dir, game_ui *ui) | ||
1140 | { | ||
1141 | return ANIM_TIME; | ||
1142 | } | ||
1143 | |||
1144 | static float game_flash_length(const game_state *oldstate, | ||
1145 | const game_state *newstate, int dir, game_ui *ui) | ||
1146 | { | ||
1147 | if (!oldstate->completed && newstate->completed && | ||
1148 | !oldstate->used_solve && !newstate->used_solve) | ||
1149 | return 2 * FLASH_FRAME; | ||
1150 | else | ||
1151 | return 0.0F; | ||
1152 | } | ||
1153 | |||
1154 | static int game_status(const game_state *state) | ||
1155 | { | ||
1156 | return state->completed ? +1 : 0; | ||
1157 | } | ||
1158 | |||
1159 | static int game_timing_state(const game_state *state, game_ui *ui) | ||
1160 | { | ||
1161 | return TRUE; | ||
1162 | } | ||
1163 | |||
1164 | static void game_print_size(const game_params *params, float *x, float *y) | ||
1165 | { | ||
1166 | } | ||
1167 | |||
1168 | static void game_print(drawing *dr, const game_state *state, int tilesize) | ||
1169 | { | ||
1170 | } | ||
1171 | |||
1172 | #ifdef COMBINED | ||
1173 | #define thegame sixteen | ||
1174 | #endif | ||
1175 | |||
1176 | const struct game thegame = { | ||
1177 | "Sixteen", "games.sixteen", "sixteen", | ||
1178 | default_params, | ||
1179 | game_fetch_preset, NULL, | ||
1180 | decode_params, | ||
1181 | encode_params, | ||
1182 | free_params, | ||
1183 | dup_params, | ||
1184 | TRUE, game_configure, custom_params, | ||
1185 | validate_params, | ||
1186 | new_game_desc, | ||
1187 | validate_desc, | ||
1188 | new_game, | ||
1189 | dup_game, | ||
1190 | free_game, | ||
1191 | TRUE, solve_game, | ||
1192 | TRUE, game_can_format_as_text_now, game_text_format, | ||
1193 | new_ui, | ||
1194 | free_ui, | ||
1195 | encode_ui, | ||
1196 | decode_ui, | ||
1197 | game_changed_state, | ||
1198 | interpret_move, | ||
1199 | execute_move, | ||
1200 | PREFERRED_TILE_SIZE, game_compute_size, game_set_size, | ||
1201 | game_colours, | ||
1202 | game_new_drawstate, | ||
1203 | game_free_drawstate, | ||
1204 | game_redraw, | ||
1205 | game_anim_length, | ||
1206 | game_flash_length, | ||
1207 | game_status, | ||
1208 | FALSE, FALSE, game_print_size, game_print, | ||
1209 | TRUE, /* wants_statusbar */ | ||
1210 | FALSE, game_timing_state, | ||
1211 | 0, /* flags */ | ||
1212 | }; | ||
1213 | |||
1214 | /* vim: set shiftwidth=4 tabstop=8: */ | ||