diff options
author | Franklin Wei <frankhwei536@gmail.com> | 2016-11-20 15:16:41 -0500 |
---|---|---|
committer | Franklin Wei <me@fwei.tk> | 2016-12-18 18:13:22 +0100 |
commit | 1a6a8b52f7aa4e2da6f4c34a0c743c760b8cfd99 (patch) | |
tree | 8e7f2d6b0cbdb5d15c13457b2c3e1de69f598440 /apps/plugins/puzzles/midend.c | |
parent | 3ee79724f6fb033d50e26ef37b33d3f8cedf0c5b (diff) | |
download | rockbox-1a6a8b52f7aa4e2da6f4c34a0c743c760b8cfd99.tar.gz rockbox-1a6a8b52f7aa4e2da6f4c34a0c743c760b8cfd99.zip |
Port of Simon Tatham's Puzzle Collection
Original revision: 5123b1bf68777ffa86e651f178046b26a87cf2d9
MIT Licensed. Some games still crash and others are unplayable due to
issues with controls. Still need a "real" polygon filling algorithm.
Currently builds one plugin per puzzle (about 40 in total, around 100K
each on ARM), but can easily be made to build a single monolithic
overlay (800K or so on ARM).
The following games are at least partially broken for various reasons,
and have been disabled on this commit:
Cube: failed assertion with "Icosahedron" setting
Keen: input issues
Mines: weird stuff happens on target
Palisade: input issues
Solo: input issues, occasional crash on target
Towers: input issues
Undead: input issues
Unequal: input and drawing issues (concave polys)
Untangle: input issues
Features left to do:
- In-game help system
- Figure out the weird bugs
Change-Id: I7c69b6860ab115f973c8d76799502e9bb3d52368
Diffstat (limited to 'apps/plugins/puzzles/midend.c')
-rw-r--r-- | apps/plugins/puzzles/midend.c | 2136 |
1 files changed, 2136 insertions, 0 deletions
diff --git a/apps/plugins/puzzles/midend.c b/apps/plugins/puzzles/midend.c new file mode 100644 index 0000000000..e2938a45bc --- /dev/null +++ b/apps/plugins/puzzles/midend.c | |||
@@ -0,0 +1,2136 @@ | |||
1 | /* | ||
2 | * midend.c: general middle fragment sitting between the | ||
3 | * platform-specific front end and game-specific back end. | ||
4 | * Maintains a move list, takes care of Undo and Redo commands, and | ||
5 | * processes standard keystrokes for undo/redo/new/quit. | ||
6 | */ | ||
7 | #include "puzzles.h" | ||
8 | |||
9 | #include "rbcompat.h" | ||
10 | #include "rbassert.h" | ||
11 | |||
12 | enum { DEF_PARAMS, DEF_SEED, DEF_DESC }; /* for midend_game_id_int */ | ||
13 | |||
14 | enum { NEWGAME, MOVE, SOLVE, RESTART };/* for midend_state_entry.movetype */ | ||
15 | |||
16 | #define special(type) ( (type) != MOVE ) | ||
17 | |||
18 | struct midend_state_entry { | ||
19 | game_state *state; | ||
20 | char *movestr; | ||
21 | int movetype; | ||
22 | }; | ||
23 | |||
24 | struct midend { | ||
25 | frontend *frontend; | ||
26 | random_state *random; | ||
27 | const game *ourgame; | ||
28 | |||
29 | game_params **presets; | ||
30 | char **preset_names, **preset_encodings; | ||
31 | int npresets, presetsize; | ||
32 | |||
33 | /* | ||
34 | * `desc' and `privdesc' deserve a comment. | ||
35 | * | ||
36 | * `desc' is the game description as presented to the user when | ||
37 | * they ask for Game -> Specific. `privdesc', if non-NULL, is a | ||
38 | * different game description used to reconstruct the initial | ||
39 | * game_state when de-serialising. If privdesc is NULL, `desc' | ||
40 | * is used for both. | ||
41 | * | ||
42 | * For almost all games, `privdesc' is NULL and never used. The | ||
43 | * exception (as usual) is Mines: the initial game state has no | ||
44 | * squares open at all, but after the first click `desc' is | ||
45 | * rewritten to describe a game state with an initial click and | ||
46 | * thus a bunch of squares open. If we used that desc to | ||
47 | * serialise and deserialise, then the initial game state after | ||
48 | * deserialisation would look unlike the initial game state | ||
49 | * beforehand, and worse still execute_move() might fail on the | ||
50 | * attempted first click. So `privdesc' is also used in this | ||
51 | * case, to provide a game description describing the same | ||
52 | * fixed mine layout _but_ no initial click. (These game IDs | ||
53 | * may also be typed directly into Mines if you like.) | ||
54 | */ | ||
55 | char *desc, *privdesc, *seedstr; | ||
56 | char *aux_info; | ||
57 | enum { GOT_SEED, GOT_DESC, GOT_NOTHING } genmode; | ||
58 | |||
59 | int nstates, statesize, statepos; | ||
60 | struct midend_state_entry *states; | ||
61 | |||
62 | game_params *params, *curparams; | ||
63 | game_drawstate *drawstate; | ||
64 | game_ui *ui; | ||
65 | |||
66 | game_state *oldstate; | ||
67 | float anim_time, anim_pos; | ||
68 | float flash_time, flash_pos; | ||
69 | int dir; | ||
70 | |||
71 | int timing; | ||
72 | float elapsed; | ||
73 | char *laststatus; | ||
74 | |||
75 | drawing *drawing; | ||
76 | |||
77 | int pressed_mouse_button; | ||
78 | |||
79 | int preferred_tilesize, tilesize, winwidth, winheight; | ||
80 | |||
81 | void (*game_id_change_notify_function)(void *); | ||
82 | void *game_id_change_notify_ctx; | ||
83 | }; | ||
84 | |||
85 | #define ensure(me) do { \ | ||
86 | if ((me)->nstates >= (me)->statesize) { \ | ||
87 | (me)->statesize = (me)->nstates + 128; \ | ||
88 | (me)->states = sresize((me)->states, (me)->statesize, \ | ||
89 | struct midend_state_entry); \ | ||
90 | } \ | ||
91 | } while (0) | ||
92 | |||
93 | void midend_reset_tilesize(midend *me) | ||
94 | { | ||
95 | me->preferred_tilesize = me->ourgame->preferred_tilesize; | ||
96 | { | ||
97 | /* | ||
98 | * Allow an environment-based override for the default tile | ||
99 | * size by defining a variable along the lines of | ||
100 | * `NET_TILESIZE=15'. | ||
101 | */ | ||
102 | |||
103 | char buf[80], *e; | ||
104 | int j, k, ts; | ||
105 | |||
106 | sprintf(buf, "%s_TILESIZE", me->ourgame->name); | ||
107 | for (j = k = 0; buf[j]; j++) | ||
108 | if (!isspace((unsigned char)buf[j])) | ||
109 | buf[k++] = toupper((unsigned char)buf[j]); | ||
110 | buf[k] = '\0'; | ||
111 | if ((e = getenv(buf)) != NULL && sscanf(e, "%d", &ts) == 1 && ts > 0) | ||
112 | me->preferred_tilesize = ts; | ||
113 | } | ||
114 | } | ||
115 | |||
116 | midend *midend_new(frontend *fe, const game *ourgame, | ||
117 | const drawing_api *drapi, void *drhandle) | ||
118 | { | ||
119 | midend *me = snew(midend); | ||
120 | void *randseed; | ||
121 | int randseedsize; | ||
122 | |||
123 | get_random_seed(&randseed, &randseedsize); | ||
124 | |||
125 | me->frontend = fe; | ||
126 | me->ourgame = ourgame; | ||
127 | me->random = random_new(randseed, randseedsize); | ||
128 | me->nstates = me->statesize = me->statepos = 0; | ||
129 | me->states = NULL; | ||
130 | me->params = ourgame->default_params(); | ||
131 | me->game_id_change_notify_function = NULL; | ||
132 | me->game_id_change_notify_ctx = NULL; | ||
133 | |||
134 | /* | ||
135 | * Allow environment-based changing of the default settings by | ||
136 | * defining a variable along the lines of `NET_DEFAULT=25x25w' | ||
137 | * in which the value is an encoded parameter string. | ||
138 | */ | ||
139 | { | ||
140 | char buf[80], *e; | ||
141 | int j, k; | ||
142 | sprintf(buf, "%s_DEFAULT", me->ourgame->name); | ||
143 | for (j = k = 0; buf[j]; j++) | ||
144 | if (!isspace((unsigned char)buf[j])) | ||
145 | buf[k++] = toupper((unsigned char)buf[j]); | ||
146 | buf[k] = '\0'; | ||
147 | if ((e = getenv(buf)) != NULL) | ||
148 | me->ourgame->decode_params(me->params, e); | ||
149 | } | ||
150 | me->curparams = NULL; | ||
151 | me->desc = me->privdesc = NULL; | ||
152 | me->seedstr = NULL; | ||
153 | me->aux_info = NULL; | ||
154 | me->genmode = GOT_NOTHING; | ||
155 | me->drawstate = NULL; | ||
156 | me->oldstate = NULL; | ||
157 | me->presets = NULL; | ||
158 | me->preset_names = NULL; | ||
159 | me->preset_encodings = NULL; | ||
160 | me->npresets = me->presetsize = 0; | ||
161 | me->anim_time = me->anim_pos = 0.0F; | ||
162 | me->flash_time = me->flash_pos = 0.0F; | ||
163 | me->dir = 0; | ||
164 | me->ui = NULL; | ||
165 | me->pressed_mouse_button = 0; | ||
166 | me->laststatus = NULL; | ||
167 | me->timing = FALSE; | ||
168 | me->elapsed = 0.0F; | ||
169 | me->tilesize = me->winwidth = me->winheight = 0; | ||
170 | if (drapi) | ||
171 | me->drawing = drawing_new(drapi, me, drhandle); | ||
172 | else | ||
173 | me->drawing = NULL; | ||
174 | |||
175 | midend_reset_tilesize(me); | ||
176 | |||
177 | sfree(randseed); | ||
178 | |||
179 | return me; | ||
180 | } | ||
181 | |||
182 | const game *midend_which_game(midend *me) | ||
183 | { | ||
184 | return me->ourgame; | ||
185 | } | ||
186 | |||
187 | static void midend_purge_states(midend *me) | ||
188 | { | ||
189 | while (me->nstates > me->statepos) { | ||
190 | me->ourgame->free_game(me->states[--me->nstates].state); | ||
191 | if (me->states[me->nstates].movestr) | ||
192 | sfree(me->states[me->nstates].movestr); | ||
193 | } | ||
194 | } | ||
195 | |||
196 | static void midend_free_game(midend *me) | ||
197 | { | ||
198 | while (me->nstates > 0) { | ||
199 | me->nstates--; | ||
200 | me->ourgame->free_game(me->states[me->nstates].state); | ||
201 | sfree(me->states[me->nstates].movestr); | ||
202 | } | ||
203 | |||
204 | if (me->drawstate) | ||
205 | me->ourgame->free_drawstate(me->drawing, me->drawstate); | ||
206 | } | ||
207 | |||
208 | void midend_free(midend *me) | ||
209 | { | ||
210 | int i; | ||
211 | |||
212 | midend_free_game(me); | ||
213 | |||
214 | if (me->drawing) | ||
215 | drawing_free(me->drawing); | ||
216 | random_free(me->random); | ||
217 | sfree(me->states); | ||
218 | sfree(me->desc); | ||
219 | sfree(me->privdesc); | ||
220 | sfree(me->seedstr); | ||
221 | sfree(me->aux_info); | ||
222 | me->ourgame->free_params(me->params); | ||
223 | if (me->npresets) { | ||
224 | for (i = 0; i < me->npresets; i++) { | ||
225 | sfree(me->presets[i]); | ||
226 | sfree(me->preset_names[i]); | ||
227 | sfree(me->preset_encodings[i]); | ||
228 | } | ||
229 | sfree(me->presets); | ||
230 | sfree(me->preset_names); | ||
231 | sfree(me->preset_encodings); | ||
232 | } | ||
233 | if (me->ui) | ||
234 | me->ourgame->free_ui(me->ui); | ||
235 | if (me->curparams) | ||
236 | me->ourgame->free_params(me->curparams); | ||
237 | sfree(me->laststatus); | ||
238 | sfree(me); | ||
239 | } | ||
240 | |||
241 | static void midend_size_new_drawstate(midend *me) | ||
242 | { | ||
243 | /* | ||
244 | * Don't even bother, if we haven't worked out our tile size | ||
245 | * anyway yet. | ||
246 | */ | ||
247 | if (me->tilesize > 0) { | ||
248 | me->ourgame->compute_size(me->params, me->tilesize, | ||
249 | &me->winwidth, &me->winheight); | ||
250 | me->ourgame->set_size(me->drawing, me->drawstate, | ||
251 | me->params, me->tilesize); | ||
252 | } | ||
253 | } | ||
254 | |||
255 | void midend_size(midend *me, int *x, int *y, int user_size) | ||
256 | { | ||
257 | int min, max; | ||
258 | int rx, ry; | ||
259 | |||
260 | /* | ||
261 | * We can't set the size on the same drawstate twice. So if | ||
262 | * we've already sized one drawstate, we must throw it away and | ||
263 | * create a new one. | ||
264 | */ | ||
265 | if (me->drawstate && me->tilesize > 0) { | ||
266 | me->ourgame->free_drawstate(me->drawing, me->drawstate); | ||
267 | me->drawstate = me->ourgame->new_drawstate(me->drawing, | ||
268 | me->states[0].state); | ||
269 | } | ||
270 | |||
271 | /* | ||
272 | * Find the tile size that best fits within the given space. If | ||
273 | * `user_size' is TRUE, we must actually find the _largest_ such | ||
274 | * tile size, in order to get as close to the user's explicit | ||
275 | * request as possible; otherwise, we bound above at the game's | ||
276 | * preferred tile size, so that the game gets what it wants | ||
277 | * provided that this doesn't break the constraint from the | ||
278 | * front-end (which is likely to be a screen size or similar). | ||
279 | */ | ||
280 | if (user_size) { | ||
281 | max = 1; | ||
282 | do { | ||
283 | max *= 2; | ||
284 | me->ourgame->compute_size(me->params, max, &rx, &ry); | ||
285 | } while (rx <= *x && ry <= *y); | ||
286 | } else | ||
287 | max = me->preferred_tilesize + 1; | ||
288 | min = 1; | ||
289 | |||
290 | /* | ||
291 | * Now binary-search between min and max. We're looking for a | ||
292 | * boundary rather than a value: the point at which tile sizes | ||
293 | * stop fitting within the given dimensions. Thus, we stop when | ||
294 | * max and min differ by exactly 1. | ||
295 | */ | ||
296 | while (max - min > 1) { | ||
297 | int mid = (max + min) / 2; | ||
298 | me->ourgame->compute_size(me->params, mid, &rx, &ry); | ||
299 | if (rx <= *x && ry <= *y) | ||
300 | min = mid; | ||
301 | else | ||
302 | max = mid; | ||
303 | } | ||
304 | |||
305 | /* | ||
306 | * Now `min' is a valid size, and `max' isn't. So use `min'. | ||
307 | */ | ||
308 | |||
309 | me->tilesize = min; | ||
310 | if (user_size) | ||
311 | /* If the user requested a change in size, make it permanent. */ | ||
312 | me->preferred_tilesize = me->tilesize; | ||
313 | midend_size_new_drawstate(me); | ||
314 | *x = me->winwidth; | ||
315 | *y = me->winheight; | ||
316 | } | ||
317 | |||
318 | int midend_tilesize(midend *me) { return me->tilesize; } | ||
319 | |||
320 | void midend_set_params(midend *me, game_params *params) | ||
321 | { | ||
322 | me->ourgame->free_params(me->params); | ||
323 | me->params = me->ourgame->dup_params(params); | ||
324 | } | ||
325 | |||
326 | game_params *midend_get_params(midend *me) | ||
327 | { | ||
328 | return me->ourgame->dup_params(me->params); | ||
329 | } | ||
330 | |||
331 | static void midend_set_timer(midend *me) | ||
332 | { | ||
333 | me->timing = (me->ourgame->is_timed && | ||
334 | me->ourgame->timing_state(me->states[me->statepos-1].state, | ||
335 | me->ui)); | ||
336 | if (me->timing || me->flash_time || me->anim_time) | ||
337 | activate_timer(me->frontend); | ||
338 | else | ||
339 | deactivate_timer(me->frontend); | ||
340 | } | ||
341 | |||
342 | void midend_force_redraw(midend *me) | ||
343 | { | ||
344 | if (me->drawstate) | ||
345 | me->ourgame->free_drawstate(me->drawing, me->drawstate); | ||
346 | me->drawstate = me->ourgame->new_drawstate(me->drawing, | ||
347 | me->states[0].state); | ||
348 | midend_size_new_drawstate(me); | ||
349 | midend_redraw(me); | ||
350 | } | ||
351 | |||
352 | void midend_new_game(midend *me) | ||
353 | { | ||
354 | midend_stop_anim(me); | ||
355 | midend_free_game(me); | ||
356 | |||
357 | assert(me->nstates == 0); | ||
358 | |||
359 | if (me->genmode == GOT_DESC) { | ||
360 | me->genmode = GOT_NOTHING; | ||
361 | } else { | ||
362 | random_state *rs; | ||
363 | |||
364 | if (me->genmode == GOT_SEED) { | ||
365 | me->genmode = GOT_NOTHING; | ||
366 | } else { | ||
367 | /* | ||
368 | * Generate a new random seed. 15 digits comes to about | ||
369 | * 48 bits, which should be more than enough. | ||
370 | * | ||
371 | * I'll avoid putting a leading zero on the number, | ||
372 | * just in case it confuses anybody who thinks it's | ||
373 | * processed as an integer rather than a string. | ||
374 | */ | ||
375 | char newseed[16]; | ||
376 | int i; | ||
377 | newseed[15] = '\0'; | ||
378 | newseed[0] = '1' + (char)random_upto(me->random, 9); | ||
379 | for (i = 1; i < 15; i++) | ||
380 | newseed[i] = '0' + (char)random_upto(me->random, 10); | ||
381 | sfree(me->seedstr); | ||
382 | me->seedstr = dupstr(newseed); | ||
383 | |||
384 | if (me->curparams) | ||
385 | me->ourgame->free_params(me->curparams); | ||
386 | me->curparams = me->ourgame->dup_params(me->params); | ||
387 | } | ||
388 | |||
389 | sfree(me->desc); | ||
390 | sfree(me->privdesc); | ||
391 | sfree(me->aux_info); | ||
392 | me->aux_info = NULL; | ||
393 | |||
394 | rs = random_new(me->seedstr, strlen(me->seedstr)); | ||
395 | /* | ||
396 | * If this midend has been instantiated without providing a | ||
397 | * drawing API, it is non-interactive. This means that it's | ||
398 | * being used for bulk game generation, and hence we should | ||
399 | * pass the non-interactive flag to new_desc. | ||
400 | */ | ||
401 | me->desc = me->ourgame->new_desc(me->curparams, rs, | ||
402 | &me->aux_info, (me->drawing != NULL)); | ||
403 | me->privdesc = NULL; | ||
404 | random_free(rs); | ||
405 | } | ||
406 | |||
407 | ensure(me); | ||
408 | |||
409 | /* | ||
410 | * It might seem a bit odd that we're using me->params to | ||
411 | * create the initial game state, rather than me->curparams | ||
412 | * which is better tailored to this specific game and which we | ||
413 | * always know. | ||
414 | * | ||
415 | * It's supposed to be an invariant in the midend that | ||
416 | * me->params and me->curparams differ in no aspect that is | ||
417 | * important after generation (i.e. after new_desc()). By | ||
418 | * deliberately passing the _less_ specific of these two | ||
419 | * parameter sets, we provoke play-time misbehaviour in the | ||
420 | * case where a game has failed to encode a play-time parameter | ||
421 | * in the non-full version of encode_params(). | ||
422 | */ | ||
423 | me->states[me->nstates].state = | ||
424 | me->ourgame->new_game(me, me->params, me->desc); | ||
425 | |||
426 | /* | ||
427 | * As part of our commitment to self-testing, test the aux | ||
428 | * string to make sure nothing ghastly went wrong. | ||
429 | */ | ||
430 | if (me->ourgame->can_solve && me->aux_info) { | ||
431 | game_state *s; | ||
432 | char *msg, *movestr; | ||
433 | |||
434 | msg = NULL; | ||
435 | movestr = me->ourgame->solve(me->states[0].state, | ||
436 | me->states[0].state, | ||
437 | me->aux_info, &msg); | ||
438 | assert(movestr && !msg); | ||
439 | s = me->ourgame->execute_move(me->states[0].state, movestr); | ||
440 | assert(s); | ||
441 | me->ourgame->free_game(s); | ||
442 | sfree(movestr); | ||
443 | } | ||
444 | |||
445 | me->states[me->nstates].movestr = NULL; | ||
446 | me->states[me->nstates].movetype = NEWGAME; | ||
447 | me->nstates++; | ||
448 | me->statepos = 1; | ||
449 | me->drawstate = me->ourgame->new_drawstate(me->drawing, | ||
450 | me->states[0].state); | ||
451 | midend_size_new_drawstate(me); | ||
452 | me->elapsed = 0.0F; | ||
453 | me->flash_pos = me->flash_time = 0.0F; | ||
454 | me->anim_pos = me->anim_time = 0.0F; | ||
455 | if (me->ui) | ||
456 | me->ourgame->free_ui(me->ui); | ||
457 | me->ui = me->ourgame->new_ui(me->states[0].state); | ||
458 | midend_set_timer(me); | ||
459 | me->pressed_mouse_button = 0; | ||
460 | |||
461 | if (me->game_id_change_notify_function) | ||
462 | me->game_id_change_notify_function(me->game_id_change_notify_ctx); | ||
463 | } | ||
464 | |||
465 | int midend_can_undo(midend *me) | ||
466 | { | ||
467 | return (me->statepos > 1); | ||
468 | } | ||
469 | |||
470 | int midend_can_redo(midend *me) | ||
471 | { | ||
472 | return (me->statepos < me->nstates); | ||
473 | } | ||
474 | |||
475 | static int midend_undo(midend *me) | ||
476 | { | ||
477 | if (me->statepos > 1) { | ||
478 | if (me->ui) | ||
479 | me->ourgame->changed_state(me->ui, | ||
480 | me->states[me->statepos-1].state, | ||
481 | me->states[me->statepos-2].state); | ||
482 | me->statepos--; | ||
483 | me->dir = -1; | ||
484 | return 1; | ||
485 | } else | ||
486 | return 0; | ||
487 | } | ||
488 | |||
489 | static int midend_redo(midend *me) | ||
490 | { | ||
491 | if (me->statepos < me->nstates) { | ||
492 | if (me->ui) | ||
493 | me->ourgame->changed_state(me->ui, | ||
494 | me->states[me->statepos-1].state, | ||
495 | me->states[me->statepos].state); | ||
496 | me->statepos++; | ||
497 | me->dir = +1; | ||
498 | return 1; | ||
499 | } else | ||
500 | return 0; | ||
501 | } | ||
502 | |||
503 | static void midend_finish_move(midend *me) | ||
504 | { | ||
505 | float flashtime; | ||
506 | |||
507 | /* | ||
508 | * We do not flash if the later of the two states is special. | ||
509 | * This covers both forward Solve moves and backward (undone) | ||
510 | * Restart moves. | ||
511 | */ | ||
512 | if ((me->oldstate || me->statepos > 1) && | ||
513 | ((me->dir > 0 && !special(me->states[me->statepos-1].movetype)) || | ||
514 | (me->dir < 0 && me->statepos < me->nstates && | ||
515 | !special(me->states[me->statepos].movetype)))) { | ||
516 | flashtime = me->ourgame->flash_length(me->oldstate ? me->oldstate : | ||
517 | me->states[me->statepos-2].state, | ||
518 | me->states[me->statepos-1].state, | ||
519 | me->oldstate ? me->dir : +1, | ||
520 | me->ui); | ||
521 | if (flashtime > 0) { | ||
522 | me->flash_pos = 0.0F; | ||
523 | me->flash_time = flashtime; | ||
524 | } | ||
525 | } | ||
526 | |||
527 | if (me->oldstate) | ||
528 | me->ourgame->free_game(me->oldstate); | ||
529 | me->oldstate = NULL; | ||
530 | me->anim_pos = me->anim_time = 0; | ||
531 | me->dir = 0; | ||
532 | |||
533 | midend_set_timer(me); | ||
534 | } | ||
535 | |||
536 | void midend_stop_anim(midend *me) | ||
537 | { | ||
538 | if (me->oldstate || me->anim_time != 0) { | ||
539 | midend_finish_move(me); | ||
540 | midend_redraw(me); | ||
541 | } | ||
542 | } | ||
543 | |||
544 | void midend_restart_game(midend *me) | ||
545 | { | ||
546 | game_state *s; | ||
547 | |||
548 | assert(me->statepos >= 1); | ||
549 | if (me->statepos == 1) | ||
550 | return; /* no point doing anything at all! */ | ||
551 | |||
552 | /* | ||
553 | * During restart, we reconstruct the game from the (public) | ||
554 | * game description rather than from states[0], because that | ||
555 | * way Mines gets slightly more sensible behaviour (restart | ||
556 | * goes to _after_ the first click so you don't have to | ||
557 | * remember where you clicked). | ||
558 | */ | ||
559 | s = me->ourgame->new_game(me, me->params, me->desc); | ||
560 | |||
561 | /* | ||
562 | * Now enter the restarted state as the next move. | ||
563 | */ | ||
564 | midend_stop_anim(me); | ||
565 | midend_purge_states(me); | ||
566 | ensure(me); | ||
567 | me->states[me->nstates].state = s; | ||
568 | me->states[me->nstates].movestr = dupstr(me->desc); | ||
569 | me->states[me->nstates].movetype = RESTART; | ||
570 | me->statepos = ++me->nstates; | ||
571 | if (me->ui) | ||
572 | me->ourgame->changed_state(me->ui, | ||
573 | me->states[me->statepos-2].state, | ||
574 | me->states[me->statepos-1].state); | ||
575 | me->flash_pos = me->flash_time = 0.0F; | ||
576 | midend_finish_move(me); | ||
577 | midend_redraw(me); | ||
578 | midend_set_timer(me); | ||
579 | } | ||
580 | |||
581 | static int midend_really_process_key(midend *me, int x, int y, int button) | ||
582 | { | ||
583 | game_state *oldstate = | ||
584 | me->ourgame->dup_game(me->states[me->statepos - 1].state); | ||
585 | int type = MOVE, gottype = FALSE, ret = 1; | ||
586 | float anim_time; | ||
587 | game_state *s; | ||
588 | char *movestr; | ||
589 | |||
590 | movestr = | ||
591 | me->ourgame->interpret_move(me->states[me->statepos-1].state, | ||
592 | me->ui, me->drawstate, x, y, button); | ||
593 | |||
594 | if (!movestr) { | ||
595 | if (button == 'n' || button == 'N' || button == '\x0E') { | ||
596 | midend_new_game(me); | ||
597 | midend_redraw(me); | ||
598 | goto done; /* never animate */ | ||
599 | } else if (button == 'u' || button == 'U' || | ||
600 | button == '\x1A' || button == '\x1F') { | ||
601 | midend_stop_anim(me); | ||
602 | type = me->states[me->statepos-1].movetype; | ||
603 | gottype = TRUE; | ||
604 | if (!midend_undo(me)) | ||
605 | goto done; | ||
606 | } else if (button == 'r' || button == 'R' || | ||
607 | button == '\x12' || button == '\x19') { | ||
608 | midend_stop_anim(me); | ||
609 | if (!midend_redo(me)) | ||
610 | goto done; | ||
611 | } else if (button == '\x13' && me->ourgame->can_solve) { | ||
612 | if (midend_solve(me)) | ||
613 | goto done; | ||
614 | } else if (button == 'q' || button == 'Q' || button == '\x11') { | ||
615 | ret = 0; | ||
616 | goto done; | ||
617 | } else | ||
618 | goto done; | ||
619 | } else { | ||
620 | if (!*movestr) | ||
621 | s = me->states[me->statepos-1].state; | ||
622 | else { | ||
623 | s = me->ourgame->execute_move(me->states[me->statepos-1].state, | ||
624 | movestr); | ||
625 | assert(s != NULL); | ||
626 | } | ||
627 | |||
628 | if (s == me->states[me->statepos-1].state) { | ||
629 | /* | ||
630 | * make_move() is allowed to return its input state to | ||
631 | * indicate that although no move has been made, the UI | ||
632 | * state has been updated and a redraw is called for. | ||
633 | */ | ||
634 | midend_redraw(me); | ||
635 | midend_set_timer(me); | ||
636 | goto done; | ||
637 | } else if (s) { | ||
638 | midend_stop_anim(me); | ||
639 | midend_purge_states(me); | ||
640 | ensure(me); | ||
641 | assert(movestr != NULL); | ||
642 | me->states[me->nstates].state = s; | ||
643 | me->states[me->nstates].movestr = movestr; | ||
644 | me->states[me->nstates].movetype = MOVE; | ||
645 | me->statepos = ++me->nstates; | ||
646 | me->dir = +1; | ||
647 | if (me->ui) | ||
648 | me->ourgame->changed_state(me->ui, | ||
649 | me->states[me->statepos-2].state, | ||
650 | me->states[me->statepos-1].state); | ||
651 | } else { | ||
652 | goto done; | ||
653 | } | ||
654 | } | ||
655 | |||
656 | if (!gottype) | ||
657 | type = me->states[me->statepos-1].movetype; | ||
658 | |||
659 | /* | ||
660 | * See if this move requires an animation. | ||
661 | */ | ||
662 | if (special(type) && !(type == SOLVE && | ||
663 | (me->ourgame->flags & SOLVE_ANIMATES))) { | ||
664 | anim_time = 0; | ||
665 | } else { | ||
666 | anim_time = me->ourgame->anim_length(oldstate, | ||
667 | me->states[me->statepos-1].state, | ||
668 | me->dir, me->ui); | ||
669 | } | ||
670 | |||
671 | me->oldstate = oldstate; oldstate = NULL; | ||
672 | if (anim_time > 0) { | ||
673 | me->anim_time = anim_time; | ||
674 | } else { | ||
675 | me->anim_time = 0.0; | ||
676 | midend_finish_move(me); | ||
677 | } | ||
678 | me->anim_pos = 0.0; | ||
679 | |||
680 | midend_redraw(me); | ||
681 | |||
682 | midend_set_timer(me); | ||
683 | |||
684 | done: | ||
685 | if (oldstate) me->ourgame->free_game(oldstate); | ||
686 | return ret; | ||
687 | } | ||
688 | |||
689 | int midend_process_key(midend *me, int x, int y, int button) | ||
690 | { | ||
691 | int ret = 1; | ||
692 | |||
693 | /* | ||
694 | * Harmonise mouse drag and release messages. | ||
695 | * | ||
696 | * Some front ends might accidentally switch from sending, say, | ||
697 | * RIGHT_DRAG messages to sending LEFT_DRAG, half way through a | ||
698 | * drag. (This can happen on the Mac, for example, since | ||
699 | * RIGHT_DRAG is usually done using Command+drag, and if the | ||
700 | * user accidentally releases Command half way through the drag | ||
701 | * then there will be trouble.) | ||
702 | * | ||
703 | * It would be an O(number of front ends) annoyance to fix this | ||
704 | * in the front ends, but an O(number of back ends) annoyance | ||
705 | * to have each game capable of dealing with it. Therefore, we | ||
706 | * fix it _here_ in the common midend code so that it only has | ||
707 | * to be done once. | ||
708 | * | ||
709 | * The possible ways in which things can go screwy in the front | ||
710 | * end are: | ||
711 | * | ||
712 | * - in a system containing multiple physical buttons button | ||
713 | * presses can inadvertently overlap. We can see ABab (caps | ||
714 | * meaning button-down and lowercase meaning button-up) when | ||
715 | * the user had semantically intended AaBb. | ||
716 | * | ||
717 | * - in a system where one button is simulated by means of a | ||
718 | * modifier key and another button, buttons can mutate | ||
719 | * between press and release (possibly during drag). So we | ||
720 | * can see Ab instead of Aa. | ||
721 | * | ||
722 | * Definite requirements are: | ||
723 | * | ||
724 | * - button _presses_ must never be invented or destroyed. If | ||
725 | * the user presses two buttons in succession, the button | ||
726 | * presses must be transferred to the backend unchanged. So | ||
727 | * if we see AaBb , that's fine; if we see ABab (the button | ||
728 | * presses inadvertently overlapped) we must somehow | ||
729 | * `correct' it to AaBb. | ||
730 | * | ||
731 | * - every mouse action must end up looking like a press, zero | ||
732 | * or more drags, then a release. This allows back ends to | ||
733 | * make the _assumption_ that incoming mouse data will be | ||
734 | * sane in this regard, and not worry about the details. | ||
735 | * | ||
736 | * So my policy will be: | ||
737 | * | ||
738 | * - treat any button-up as a button-up for the currently | ||
739 | * pressed button, or ignore it if there is no currently | ||
740 | * pressed button. | ||
741 | * | ||
742 | * - treat any drag as a drag for the currently pressed | ||
743 | * button, or ignore it if there is no currently pressed | ||
744 | * button. | ||
745 | * | ||
746 | * - if we see a button-down while another button is currently | ||
747 | * pressed, invent a button-up for the first one and then | ||
748 | * pass the button-down through as before. | ||
749 | * | ||
750 | * 2005-05-31: An addendum to the above. Some games might want | ||
751 | * a `priority order' among buttons, such that if one button is | ||
752 | * pressed while another is down then a fixed one of the | ||
753 | * buttons takes priority no matter what order they're pressed | ||
754 | * in. Mines, in particular, wants to treat a left+right click | ||
755 | * like a left click for the benefit of users of other | ||
756 | * implementations. So the last of the above points is modified | ||
757 | * in the presence of an (optional) button priority order. | ||
758 | * | ||
759 | * A further addition: we translate certain keyboard presses to | ||
760 | * cursor key 'select' buttons, so that a) frontends don't have | ||
761 | * to translate these themselves (like they do for CURSOR_UP etc), | ||
762 | * and b) individual games don't have to hard-code button presses | ||
763 | * of '\n' etc for keyboard-based cursors. The choice of buttons | ||
764 | * here could eventually be controlled by a runtime configuration | ||
765 | * option. | ||
766 | */ | ||
767 | if (IS_MOUSE_DRAG(button) || IS_MOUSE_RELEASE(button)) { | ||
768 | if (me->pressed_mouse_button) { | ||
769 | if (IS_MOUSE_DRAG(button)) { | ||
770 | button = me->pressed_mouse_button + | ||
771 | (LEFT_DRAG - LEFT_BUTTON); | ||
772 | } else { | ||
773 | button = me->pressed_mouse_button + | ||
774 | (LEFT_RELEASE - LEFT_BUTTON); | ||
775 | } | ||
776 | } else | ||
777 | return ret; /* ignore it */ | ||
778 | } else if (IS_MOUSE_DOWN(button) && me->pressed_mouse_button) { | ||
779 | /* | ||
780 | * If the new button has lower priority than the old one, | ||
781 | * don't bother doing this. | ||
782 | */ | ||
783 | if (me->ourgame->flags & | ||
784 | BUTTON_BEATS(me->pressed_mouse_button, button)) | ||
785 | return ret; /* just ignore it */ | ||
786 | |||
787 | /* | ||
788 | * Fabricate a button-up for the previously pressed button. | ||
789 | */ | ||
790 | ret = ret && midend_really_process_key | ||
791 | (me, x, y, (me->pressed_mouse_button + | ||
792 | (LEFT_RELEASE - LEFT_BUTTON))); | ||
793 | } | ||
794 | |||
795 | /* | ||
796 | * Translate keyboard presses to cursor selection. | ||
797 | */ | ||
798 | if (button == '\n' || button == '\r') | ||
799 | button = CURSOR_SELECT; | ||
800 | if (button == ' ') | ||
801 | button = CURSOR_SELECT2; | ||
802 | |||
803 | /* | ||
804 | * Normalise both backspace characters (8 and 127) to \b. Easier | ||
805 | * to do this once, here, than to require all front ends to | ||
806 | * carefully generate the same one - now each front end can | ||
807 | * generate whichever is easiest. | ||
808 | */ | ||
809 | if (button == '\177') | ||
810 | button = '\b'; | ||
811 | |||
812 | /* | ||
813 | * Now send on the event we originally received. | ||
814 | */ | ||
815 | ret = ret && midend_really_process_key(me, x, y, button); | ||
816 | |||
817 | /* | ||
818 | * And update the currently pressed button. | ||
819 | */ | ||
820 | if (IS_MOUSE_RELEASE(button)) | ||
821 | me->pressed_mouse_button = 0; | ||
822 | else if (IS_MOUSE_DOWN(button)) | ||
823 | me->pressed_mouse_button = button; | ||
824 | |||
825 | return ret; | ||
826 | } | ||
827 | |||
828 | void midend_redraw(midend *me) | ||
829 | { | ||
830 | assert(me->drawing); | ||
831 | |||
832 | if (me->statepos > 0 && me->drawstate) { | ||
833 | start_draw(me->drawing); | ||
834 | if (me->oldstate && me->anim_time > 0 && | ||
835 | me->anim_pos < me->anim_time) { | ||
836 | assert(me->dir != 0); | ||
837 | me->ourgame->redraw(me->drawing, me->drawstate, me->oldstate, | ||
838 | me->states[me->statepos-1].state, me->dir, | ||
839 | me->ui, me->anim_pos, me->flash_pos); | ||
840 | } else { | ||
841 | me->ourgame->redraw(me->drawing, me->drawstate, NULL, | ||
842 | me->states[me->statepos-1].state, +1 /*shrug*/, | ||
843 | me->ui, 0.0, me->flash_pos); | ||
844 | } | ||
845 | end_draw(me->drawing); | ||
846 | } | ||
847 | } | ||
848 | |||
849 | /* | ||
850 | * Nasty hacky function used to implement the --redo option in | ||
851 | * gtk.c. Only used for generating the puzzles' icons. | ||
852 | */ | ||
853 | void midend_freeze_timer(midend *me, float tprop) | ||
854 | { | ||
855 | me->anim_pos = me->anim_time * tprop; | ||
856 | midend_redraw(me); | ||
857 | deactivate_timer(me->frontend); | ||
858 | } | ||
859 | |||
860 | void midend_timer(midend *me, float tplus) | ||
861 | { | ||
862 | int need_redraw = (me->anim_time > 0 || me->flash_time > 0); | ||
863 | |||
864 | me->anim_pos += tplus; | ||
865 | if (me->anim_pos >= me->anim_time || | ||
866 | me->anim_time == 0 || !me->oldstate) { | ||
867 | if (me->anim_time > 0) | ||
868 | midend_finish_move(me); | ||
869 | } | ||
870 | |||
871 | me->flash_pos += tplus; | ||
872 | if (me->flash_pos >= me->flash_time || me->flash_time == 0) { | ||
873 | me->flash_pos = me->flash_time = 0; | ||
874 | } | ||
875 | |||
876 | if (need_redraw) | ||
877 | midend_redraw(me); | ||
878 | |||
879 | if (me->timing) { | ||
880 | float oldelapsed = me->elapsed; | ||
881 | me->elapsed += tplus; | ||
882 | if ((int)oldelapsed != (int)me->elapsed) | ||
883 | status_bar(me->drawing, me->laststatus ? me->laststatus : ""); | ||
884 | } | ||
885 | |||
886 | midend_set_timer(me); | ||
887 | } | ||
888 | |||
889 | float *midend_colours(midend *me, int *ncolours) | ||
890 | { | ||
891 | float *ret; | ||
892 | |||
893 | ret = me->ourgame->colours(me->frontend, ncolours); | ||
894 | |||
895 | { | ||
896 | int i; | ||
897 | |||
898 | /* | ||
899 | * Allow environment-based overrides for the standard | ||
900 | * colours by defining variables along the lines of | ||
901 | * `NET_COLOUR_4=6000c0'. | ||
902 | */ | ||
903 | |||
904 | for (i = 0; i < *ncolours; i++) { | ||
905 | char buf[80], *e; | ||
906 | unsigned int r, g, b; | ||
907 | int j, k; | ||
908 | |||
909 | sprintf(buf, "%s_COLOUR_%d", me->ourgame->name, i); | ||
910 | for (j = k = 0; buf[j]; j++) | ||
911 | if (!isspace((unsigned char)buf[j])) | ||
912 | buf[k++] = toupper((unsigned char)buf[j]); | ||
913 | buf[k] = '\0'; | ||
914 | if ((e = getenv(buf)) != NULL && | ||
915 | sscanf(e, "%2x%2x%2x", &r, &g, &b) == 3) { | ||
916 | ret[i*3 + 0] = r / 255.0F; | ||
917 | ret[i*3 + 1] = g / 255.0F; | ||
918 | ret[i*3 + 2] = b / 255.0F; | ||
919 | } | ||
920 | } | ||
921 | } | ||
922 | |||
923 | return ret; | ||
924 | } | ||
925 | |||
926 | int midend_num_presets(midend *me) | ||
927 | { | ||
928 | if (!me->npresets) { | ||
929 | char *name; | ||
930 | game_params *preset; | ||
931 | |||
932 | while (me->ourgame->fetch_preset(me->npresets, &name, &preset)) { | ||
933 | if (me->presetsize <= me->npresets) { | ||
934 | me->presetsize = me->npresets + 10; | ||
935 | me->presets = sresize(me->presets, me->presetsize, | ||
936 | game_params *); | ||
937 | me->preset_names = sresize(me->preset_names, me->presetsize, | ||
938 | char *); | ||
939 | me->preset_encodings = sresize(me->preset_encodings, | ||
940 | me->presetsize, char *); | ||
941 | } | ||
942 | |||
943 | me->presets[me->npresets] = preset; | ||
944 | me->preset_names[me->npresets] = name; | ||
945 | me->preset_encodings[me->npresets] = | ||
946 | me->ourgame->encode_params(preset, TRUE);; | ||
947 | me->npresets++; | ||
948 | } | ||
949 | } | ||
950 | |||
951 | { | ||
952 | /* | ||
953 | * Allow environment-based extensions to the preset list by | ||
954 | * defining a variable along the lines of `SOLO_PRESETS=2x3 | ||
955 | * Advanced:2x3da'. Colon-separated list of items, | ||
956 | * alternating between textual titles in the menu and | ||
957 | * encoded parameter strings. | ||
958 | */ | ||
959 | char buf[80], *e, *p; | ||
960 | int j, k; | ||
961 | |||
962 | sprintf(buf, "%s_PRESETS", me->ourgame->name); | ||
963 | for (j = k = 0; buf[j]; j++) | ||
964 | if (!isspace((unsigned char)buf[j])) | ||
965 | buf[k++] = toupper((unsigned char)buf[j]); | ||
966 | buf[k] = '\0'; | ||
967 | |||
968 | if ((e = getenv(buf)) != NULL) { | ||
969 | p = e = dupstr(e); | ||
970 | |||
971 | while (*p) { | ||
972 | char *name, *val; | ||
973 | game_params *preset; | ||
974 | |||
975 | name = p; | ||
976 | while (*p && *p != ':') p++; | ||
977 | if (*p) *p++ = '\0'; | ||
978 | val = p; | ||
979 | while (*p && *p != ':') p++; | ||
980 | if (*p) *p++ = '\0'; | ||
981 | |||
982 | preset = me->ourgame->default_params(); | ||
983 | me->ourgame->decode_params(preset, val); | ||
984 | |||
985 | if (me->ourgame->validate_params(preset, TRUE)) { | ||
986 | /* Drop this one from the list. */ | ||
987 | me->ourgame->free_params(preset); | ||
988 | continue; | ||
989 | } | ||
990 | |||
991 | if (me->presetsize <= me->npresets) { | ||
992 | me->presetsize = me->npresets + 10; | ||
993 | me->presets = sresize(me->presets, me->presetsize, | ||
994 | game_params *); | ||
995 | me->preset_names = sresize(me->preset_names, | ||
996 | me->presetsize, char *); | ||
997 | me->preset_encodings = sresize(me->preset_encodings, | ||
998 | me->presetsize, char *); | ||
999 | } | ||
1000 | |||
1001 | me->presets[me->npresets] = preset; | ||
1002 | me->preset_names[me->npresets] = dupstr(name); | ||
1003 | me->preset_encodings[me->npresets] = | ||
1004 | me->ourgame->encode_params(preset, TRUE); | ||
1005 | me->npresets++; | ||
1006 | } | ||
1007 | sfree(e); | ||
1008 | } | ||
1009 | } | ||
1010 | |||
1011 | return me->npresets; | ||
1012 | } | ||
1013 | |||
1014 | void midend_fetch_preset(midend *me, int n, | ||
1015 | char **name, game_params **params) | ||
1016 | { | ||
1017 | assert(n >= 0 && n < me->npresets); | ||
1018 | *name = me->preset_names[n]; | ||
1019 | *params = me->presets[n]; | ||
1020 | } | ||
1021 | |||
1022 | int midend_which_preset(midend *me) | ||
1023 | { | ||
1024 | char *encoding = me->ourgame->encode_params(me->params, TRUE); | ||
1025 | int i, ret; | ||
1026 | |||
1027 | ret = -1; | ||
1028 | for (i = 0; i < me->npresets; i++) | ||
1029 | if (!strcmp(encoding, me->preset_encodings[i])) { | ||
1030 | ret = i; | ||
1031 | break; | ||
1032 | } | ||
1033 | |||
1034 | sfree(encoding); | ||
1035 | return ret; | ||
1036 | } | ||
1037 | |||
1038 | int midend_wants_statusbar(midend *me) | ||
1039 | { | ||
1040 | return me->ourgame->wants_statusbar; | ||
1041 | } | ||
1042 | |||
1043 | void midend_request_id_changes(midend *me, void (*notify)(void *), void *ctx) | ||
1044 | { | ||
1045 | me->game_id_change_notify_function = notify; | ||
1046 | me->game_id_change_notify_ctx = ctx; | ||
1047 | } | ||
1048 | |||
1049 | void midend_supersede_game_desc(midend *me, char *desc, char *privdesc) | ||
1050 | { | ||
1051 | sfree(me->desc); | ||
1052 | sfree(me->privdesc); | ||
1053 | me->desc = dupstr(desc); | ||
1054 | me->privdesc = privdesc ? dupstr(privdesc) : NULL; | ||
1055 | if (me->game_id_change_notify_function) | ||
1056 | me->game_id_change_notify_function(me->game_id_change_notify_ctx); | ||
1057 | } | ||
1058 | |||
1059 | config_item *midend_get_config(midend *me, int which, char **wintitle) | ||
1060 | { | ||
1061 | char *titlebuf, *parstr, *rest; | ||
1062 | config_item *ret; | ||
1063 | char sep; | ||
1064 | |||
1065 | assert(wintitle); | ||
1066 | titlebuf = snewn(40 + strlen(me->ourgame->name), char); | ||
1067 | |||
1068 | switch (which) { | ||
1069 | case CFG_SETTINGS: | ||
1070 | sprintf(titlebuf, "%s configuration", me->ourgame->name); | ||
1071 | *wintitle = titlebuf; | ||
1072 | return me->ourgame->configure(me->params); | ||
1073 | case CFG_SEED: | ||
1074 | case CFG_DESC: | ||
1075 | if (!me->curparams) { | ||
1076 | sfree(titlebuf); | ||
1077 | return NULL; | ||
1078 | } | ||
1079 | sprintf(titlebuf, "%s %s selection", me->ourgame->name, | ||
1080 | which == CFG_SEED ? "random" : "game"); | ||
1081 | *wintitle = titlebuf; | ||
1082 | |||
1083 | ret = snewn(2, config_item); | ||
1084 | |||
1085 | ret[0].type = C_STRING; | ||
1086 | if (which == CFG_SEED) | ||
1087 | ret[0].name = "Game random seed"; | ||
1088 | else | ||
1089 | ret[0].name = "Game ID"; | ||
1090 | ret[0].ival = 0; | ||
1091 | /* | ||
1092 | * For CFG_DESC the text going in here will be a string | ||
1093 | * encoding of the restricted parameters, plus a colon, | ||
1094 | * plus the game description. For CFG_SEED it will be the | ||
1095 | * full parameters, plus a hash, plus the random seed data. | ||
1096 | * Either of these is a valid full game ID (although only | ||
1097 | * the former is likely to persist across many code | ||
1098 | * changes). | ||
1099 | */ | ||
1100 | parstr = me->ourgame->encode_params(me->curparams, which == CFG_SEED); | ||
1101 | assert(parstr); | ||
1102 | if (which == CFG_DESC) { | ||
1103 | rest = me->desc ? me->desc : ""; | ||
1104 | sep = ':'; | ||
1105 | } else { | ||
1106 | rest = me->seedstr ? me->seedstr : ""; | ||
1107 | sep = '#'; | ||
1108 | } | ||
1109 | ret[0].sval = snewn(strlen(parstr) + strlen(rest) + 2, char); | ||
1110 | sprintf(ret[0].sval, "%s%c%s", parstr, sep, rest); | ||
1111 | sfree(parstr); | ||
1112 | |||
1113 | ret[1].type = C_END; | ||
1114 | ret[1].name = ret[1].sval = NULL; | ||
1115 | ret[1].ival = 0; | ||
1116 | |||
1117 | return ret; | ||
1118 | } | ||
1119 | |||
1120 | assert(!"We shouldn't be here"); | ||
1121 | return NULL; | ||
1122 | } | ||
1123 | |||
1124 | static char *midend_game_id_int(midend *me, char *id, int defmode) | ||
1125 | { | ||
1126 | char *error, *par, *desc, *seed; | ||
1127 | game_params *newcurparams, *newparams, *oldparams1, *oldparams2; | ||
1128 | int free_params; | ||
1129 | |||
1130 | seed = strchr(id, '#'); | ||
1131 | desc = strchr(id, ':'); | ||
1132 | |||
1133 | if (desc && (!seed || desc < seed)) { | ||
1134 | /* | ||
1135 | * We have a colon separating parameters from game | ||
1136 | * description. So `par' now points to the parameters | ||
1137 | * string, and `desc' to the description string. | ||
1138 | */ | ||
1139 | *desc++ = '\0'; | ||
1140 | par = id; | ||
1141 | seed = NULL; | ||
1142 | } else if (seed && (!desc || seed < desc)) { | ||
1143 | /* | ||
1144 | * We have a hash separating parameters from random seed. | ||
1145 | * So `par' now points to the parameters string, and `seed' | ||
1146 | * to the seed string. | ||
1147 | */ | ||
1148 | *seed++ = '\0'; | ||
1149 | par = id; | ||
1150 | desc = NULL; | ||
1151 | } else { | ||
1152 | /* | ||
1153 | * We only have one string. Depending on `defmode', we take | ||
1154 | * it to be either parameters, seed or description. | ||
1155 | */ | ||
1156 | if (defmode == DEF_SEED) { | ||
1157 | seed = id; | ||
1158 | par = desc = NULL; | ||
1159 | } else if (defmode == DEF_DESC) { | ||
1160 | desc = id; | ||
1161 | par = seed = NULL; | ||
1162 | } else { | ||
1163 | par = id; | ||
1164 | seed = desc = NULL; | ||
1165 | } | ||
1166 | } | ||
1167 | |||
1168 | /* | ||
1169 | * We must be reasonably careful here not to modify anything in | ||
1170 | * `me' until we have finished validating things. This function | ||
1171 | * must either return an error and do nothing to the midend, or | ||
1172 | * return success and do everything; nothing in between is | ||
1173 | * acceptable. | ||
1174 | */ | ||
1175 | newcurparams = newparams = oldparams1 = oldparams2 = NULL; | ||
1176 | |||
1177 | if (par) { | ||
1178 | /* | ||
1179 | * The params string may underspecify the game parameters, so | ||
1180 | * we must first initialise newcurparams with a full set of | ||
1181 | * params from somewhere else before we decode_params the | ||
1182 | * input string over the top. | ||
1183 | * | ||
1184 | * But which set? It depends on what other data we have. | ||
1185 | * | ||
1186 | * If we've been given a _descriptive_ game id, then that may | ||
1187 | * well underspecify by design, e.g. Solo game descriptions | ||
1188 | * often start just '3x3:' without specifying one of Solo's | ||
1189 | * difficulty settings, because it isn't necessary once a game | ||
1190 | * has been generated (and you might not even know it, if | ||
1191 | * you're manually transcribing a game description). In that | ||
1192 | * situation, I've always felt that the best thing to set the | ||
1193 | * difficulty to (for use if the user hits 'New Game' after | ||
1194 | * pasting in that game id) is whatever it was previously set | ||
1195 | * to. That is, we use whatever is already in me->params as | ||
1196 | * the basis for our decoding of this input string. | ||
1197 | * | ||
1198 | * A random-seed based game id, however, should use the real, | ||
1199 | * built-in default params, and not even check the | ||
1200 | * <game>_DEFAULT environment setting, because when people | ||
1201 | * paste each other random seeds - whether it's two users | ||
1202 | * arranging to generate the same game at the same time to | ||
1203 | * race solving them, or a user sending a bug report upstream | ||
1204 | * - the whole point is for the random game id to always be | ||
1205 | * interpreted the same way, even if it does underspecify. | ||
1206 | * | ||
1207 | * A parameter string typed in on its own, with no seed _or_ | ||
1208 | * description, gets treated the same way as a random seed, | ||
1209 | * because again I think the most likely reason for doing that | ||
1210 | * is to have a portable representation of a set of params. | ||
1211 | */ | ||
1212 | if (desc) { | ||
1213 | newcurparams = me->ourgame->dup_params(me->params); | ||
1214 | } else { | ||
1215 | newcurparams = me->ourgame->default_params(); | ||
1216 | } | ||
1217 | me->ourgame->decode_params(newcurparams, par); | ||
1218 | error = me->ourgame->validate_params(newcurparams, desc == NULL); | ||
1219 | if (error) { | ||
1220 | me->ourgame->free_params(newcurparams); | ||
1221 | return error; | ||
1222 | } | ||
1223 | oldparams1 = me->curparams; | ||
1224 | |||
1225 | /* | ||
1226 | * Now filter only the persistent parts of this state into | ||
1227 | * the long-term params structure, unless we've _only_ | ||
1228 | * received a params string in which case the whole lot is | ||
1229 | * persistent. | ||
1230 | */ | ||
1231 | oldparams2 = me->params; | ||
1232 | if (seed || desc) { | ||
1233 | char *tmpstr; | ||
1234 | |||
1235 | newparams = me->ourgame->dup_params(me->params); | ||
1236 | |||
1237 | tmpstr = me->ourgame->encode_params(newcurparams, FALSE); | ||
1238 | me->ourgame->decode_params(newparams, tmpstr); | ||
1239 | |||
1240 | sfree(tmpstr); | ||
1241 | } else { | ||
1242 | newparams = me->ourgame->dup_params(newcurparams); | ||
1243 | } | ||
1244 | free_params = TRUE; | ||
1245 | } else { | ||
1246 | newcurparams = me->curparams; | ||
1247 | newparams = me->params; | ||
1248 | free_params = FALSE; | ||
1249 | } | ||
1250 | |||
1251 | if (desc) { | ||
1252 | error = me->ourgame->validate_desc(newparams, desc); | ||
1253 | if (error) { | ||
1254 | if (free_params) { | ||
1255 | if (newcurparams) | ||
1256 | me->ourgame->free_params(newcurparams); | ||
1257 | if (newparams) | ||
1258 | me->ourgame->free_params(newparams); | ||
1259 | } | ||
1260 | return error; | ||
1261 | } | ||
1262 | } | ||
1263 | |||
1264 | /* | ||
1265 | * Now we've got past all possible error points. Update the | ||
1266 | * midend itself. | ||
1267 | */ | ||
1268 | me->params = newparams; | ||
1269 | me->curparams = newcurparams; | ||
1270 | if (oldparams1) | ||
1271 | me->ourgame->free_params(oldparams1); | ||
1272 | if (oldparams2) | ||
1273 | me->ourgame->free_params(oldparams2); | ||
1274 | |||
1275 | sfree(me->desc); | ||
1276 | sfree(me->privdesc); | ||
1277 | me->desc = me->privdesc = NULL; | ||
1278 | sfree(me->seedstr); | ||
1279 | me->seedstr = NULL; | ||
1280 | |||
1281 | if (desc) { | ||
1282 | me->desc = dupstr(desc); | ||
1283 | me->genmode = GOT_DESC; | ||
1284 | sfree(me->aux_info); | ||
1285 | me->aux_info = NULL; | ||
1286 | } | ||
1287 | |||
1288 | if (seed) { | ||
1289 | me->seedstr = dupstr(seed); | ||
1290 | me->genmode = GOT_SEED; | ||
1291 | } | ||
1292 | |||
1293 | return NULL; | ||
1294 | } | ||
1295 | |||
1296 | char *midend_game_id(midend *me, char *id) | ||
1297 | { | ||
1298 | return midend_game_id_int(me, id, DEF_PARAMS); | ||
1299 | } | ||
1300 | |||
1301 | char *midend_get_game_id(midend *me) | ||
1302 | { | ||
1303 | char *parstr, *ret; | ||
1304 | |||
1305 | parstr = me->ourgame->encode_params(me->curparams, FALSE); | ||
1306 | assert(parstr); | ||
1307 | assert(me->desc); | ||
1308 | ret = snewn(strlen(parstr) + strlen(me->desc) + 2, char); | ||
1309 | sprintf(ret, "%s:%s", parstr, me->desc); | ||
1310 | sfree(parstr); | ||
1311 | return ret; | ||
1312 | } | ||
1313 | |||
1314 | char *midend_get_random_seed(midend *me) | ||
1315 | { | ||
1316 | char *parstr, *ret; | ||
1317 | |||
1318 | if (!me->seedstr) | ||
1319 | return NULL; | ||
1320 | |||
1321 | parstr = me->ourgame->encode_params(me->curparams, TRUE); | ||
1322 | assert(parstr); | ||
1323 | ret = snewn(strlen(parstr) + strlen(me->seedstr) + 2, char); | ||
1324 | sprintf(ret, "%s#%s", parstr, me->seedstr); | ||
1325 | sfree(parstr); | ||
1326 | return ret; | ||
1327 | } | ||
1328 | |||
1329 | char *midend_set_config(midend *me, int which, config_item *cfg) | ||
1330 | { | ||
1331 | char *error; | ||
1332 | game_params *params; | ||
1333 | |||
1334 | switch (which) { | ||
1335 | case CFG_SETTINGS: | ||
1336 | params = me->ourgame->custom_params(cfg); | ||
1337 | error = me->ourgame->validate_params(params, TRUE); | ||
1338 | |||
1339 | if (error) { | ||
1340 | me->ourgame->free_params(params); | ||
1341 | return error; | ||
1342 | } | ||
1343 | |||
1344 | me->ourgame->free_params(me->params); | ||
1345 | me->params = params; | ||
1346 | break; | ||
1347 | |||
1348 | case CFG_SEED: | ||
1349 | case CFG_DESC: | ||
1350 | error = midend_game_id_int(me, cfg[0].sval, | ||
1351 | (which == CFG_SEED ? DEF_SEED : DEF_DESC)); | ||
1352 | if (error) | ||
1353 | return error; | ||
1354 | break; | ||
1355 | } | ||
1356 | |||
1357 | return NULL; | ||
1358 | } | ||
1359 | |||
1360 | int midend_can_format_as_text_now(midend *me) | ||
1361 | { | ||
1362 | if (me->ourgame->can_format_as_text_ever) | ||
1363 | return me->ourgame->can_format_as_text_now(me->params); | ||
1364 | else | ||
1365 | return FALSE; | ||
1366 | } | ||
1367 | |||
1368 | char *midend_text_format(midend *me) | ||
1369 | { | ||
1370 | if (me->ourgame->can_format_as_text_ever && me->statepos > 0 && | ||
1371 | me->ourgame->can_format_as_text_now(me->params)) | ||
1372 | return me->ourgame->text_format(me->states[me->statepos-1].state); | ||
1373 | else | ||
1374 | return NULL; | ||
1375 | } | ||
1376 | |||
1377 | char *midend_solve(midend *me) | ||
1378 | { | ||
1379 | game_state *s; | ||
1380 | char *msg, *movestr; | ||
1381 | |||
1382 | if (!me->ourgame->can_solve) | ||
1383 | return "This game does not support the Solve operation"; | ||
1384 | |||
1385 | if (me->statepos < 1) | ||
1386 | return "No game set up to solve"; /* _shouldn't_ happen! */ | ||
1387 | |||
1388 | msg = NULL; | ||
1389 | movestr = me->ourgame->solve(me->states[0].state, | ||
1390 | me->states[me->statepos-1].state, | ||
1391 | me->aux_info, &msg); | ||
1392 | if (!movestr) { | ||
1393 | if (!msg) | ||
1394 | msg = "Solve operation failed"; /* _shouldn't_ happen, but can */ | ||
1395 | return msg; | ||
1396 | } | ||
1397 | s = me->ourgame->execute_move(me->states[me->statepos-1].state, movestr); | ||
1398 | assert(s); | ||
1399 | |||
1400 | /* | ||
1401 | * Now enter the solved state as the next move. | ||
1402 | */ | ||
1403 | midend_stop_anim(me); | ||
1404 | midend_purge_states(me); | ||
1405 | ensure(me); | ||
1406 | me->states[me->nstates].state = s; | ||
1407 | me->states[me->nstates].movestr = movestr; | ||
1408 | me->states[me->nstates].movetype = SOLVE; | ||
1409 | me->statepos = ++me->nstates; | ||
1410 | if (me->ui) | ||
1411 | me->ourgame->changed_state(me->ui, | ||
1412 | me->states[me->statepos-2].state, | ||
1413 | me->states[me->statepos-1].state); | ||
1414 | me->dir = +1; | ||
1415 | if (me->ourgame->flags & SOLVE_ANIMATES) { | ||
1416 | me->oldstate = me->ourgame->dup_game(me->states[me->statepos-2].state); | ||
1417 | me->anim_time = | ||
1418 | me->ourgame->anim_length(me->states[me->statepos-2].state, | ||
1419 | me->states[me->statepos-1].state, | ||
1420 | +1, me->ui); | ||
1421 | me->anim_pos = 0.0; | ||
1422 | } else { | ||
1423 | me->anim_time = 0.0; | ||
1424 | midend_finish_move(me); | ||
1425 | } | ||
1426 | if (me->drawing) | ||
1427 | midend_redraw(me); | ||
1428 | midend_set_timer(me); | ||
1429 | return NULL; | ||
1430 | } | ||
1431 | |||
1432 | int midend_status(midend *me) | ||
1433 | { | ||
1434 | /* | ||
1435 | * We should probably never be called when the state stack has no | ||
1436 | * states on it at all - ideally, midends should never be left in | ||
1437 | * that state for long enough to get put down and forgotten about. | ||
1438 | * But if we are, I think we return _true_ - pedantically speaking | ||
1439 | * a midend in that state is 'vacuously solved', and more | ||
1440 | * practically, a user whose midend has been left in that state | ||
1441 | * probably _does_ want the 'new game' option to be prominent. | ||
1442 | */ | ||
1443 | if (me->statepos == 0) | ||
1444 | return +1; | ||
1445 | |||
1446 | return me->ourgame->status(me->states[me->statepos-1].state); | ||
1447 | } | ||
1448 | |||
1449 | char *midend_rewrite_statusbar(midend *me, char *text) | ||
1450 | { | ||
1451 | /* | ||
1452 | * An important special case is that we are occasionally called | ||
1453 | * with our own laststatus, to update the timer. | ||
1454 | */ | ||
1455 | if (me->laststatus != text) { | ||
1456 | sfree(me->laststatus); | ||
1457 | me->laststatus = dupstr(text); | ||
1458 | } | ||
1459 | |||
1460 | if (me->ourgame->is_timed) { | ||
1461 | char timebuf[100], *ret; | ||
1462 | int min, sec; | ||
1463 | |||
1464 | sec = (int)me->elapsed; | ||
1465 | min = sec / 60; | ||
1466 | sec %= 60; | ||
1467 | sprintf(timebuf, "[%d:%02d] ", min, sec); | ||
1468 | |||
1469 | ret = snewn(strlen(timebuf) + strlen(text) + 1, char); | ||
1470 | strcpy(ret, timebuf); | ||
1471 | strcat(ret, text); | ||
1472 | return ret; | ||
1473 | |||
1474 | } else { | ||
1475 | return dupstr(text); | ||
1476 | } | ||
1477 | } | ||
1478 | |||
1479 | #define SERIALISE_MAGIC "Simon Tatham's Portable Puzzle Collection" | ||
1480 | #define SERIALISE_VERSION "1" | ||
1481 | |||
1482 | /* rockbox kludge */ | ||
1483 | static void copy_left_justified(char *buf, size_t sz, const char *str) | ||
1484 | { | ||
1485 | memset(buf, ' ', sz - 1); | ||
1486 | int len = strlen(str); | ||
1487 | if(len <= sz - 1) | ||
1488 | memcpy(buf, str, len); | ||
1489 | else | ||
1490 | fatal("overrun"); | ||
1491 | buf[sz - 1] = 0; | ||
1492 | } | ||
1493 | |||
1494 | void midend_serialise(midend *me, | ||
1495 | void (*write)(void *ctx, void *buf, int len), | ||
1496 | void *wctx) | ||
1497 | { | ||
1498 | int i; | ||
1499 | |||
1500 | /* | ||
1501 | * Each line of the save file contains three components. First | ||
1502 | * exactly 8 characters of header word indicating what type of | ||
1503 | * data is contained on the line; then a colon followed by a | ||
1504 | * decimal integer giving the length of the main string on the | ||
1505 | * line; then a colon followed by the string itself (exactly as | ||
1506 | * many bytes as previously specified, no matter what they | ||
1507 | * contain). Then a newline (of reasonably flexible form). | ||
1508 | */ | ||
1509 | #define wr(h,s) do { \ | ||
1510 | char hbuf[80]; \ | ||
1511 | char *str = (s); \ | ||
1512 | char lbuf[9]; \ | ||
1513 | copy_left_justified(lbuf, sizeof(lbuf), h); \ | ||
1514 | sprintf(hbuf, "%s:%d:", lbuf, (int)strlen(str)); \ | ||
1515 | write(wctx, hbuf, strlen(hbuf)); \ | ||
1516 | write(wctx, str, strlen(str)); \ | ||
1517 | write(wctx, "\n", 1); \ | ||
1518 | } while (0) | ||
1519 | |||
1520 | /* | ||
1521 | * Magic string identifying the file, and version number of the | ||
1522 | * file format. | ||
1523 | */ | ||
1524 | wr("SAVEFILE", SERIALISE_MAGIC); | ||
1525 | wr("VERSION", SERIALISE_VERSION); | ||
1526 | |||
1527 | /* | ||
1528 | * The game name. (Copied locally to avoid const annoyance.) | ||
1529 | */ | ||
1530 | { | ||
1531 | char *s = dupstr(me->ourgame->name); | ||
1532 | wr("GAME", s); | ||
1533 | sfree(s); | ||
1534 | } | ||
1535 | |||
1536 | /* | ||
1537 | * The current long-term parameters structure, in full. | ||
1538 | */ | ||
1539 | if (me->params) { | ||
1540 | char *s = me->ourgame->encode_params(me->params, TRUE); | ||
1541 | wr("PARAMS", s); | ||
1542 | sfree(s); | ||
1543 | } | ||
1544 | |||
1545 | /* | ||
1546 | * The current short-term parameters structure, in full. | ||
1547 | */ | ||
1548 | if (me->curparams) { | ||
1549 | char *s = me->ourgame->encode_params(me->curparams, TRUE); | ||
1550 | wr("CPARAMS", s); | ||
1551 | sfree(s); | ||
1552 | } | ||
1553 | |||
1554 | /* | ||
1555 | * The current game description, the privdesc, and the random seed. | ||
1556 | */ | ||
1557 | if (me->seedstr) | ||
1558 | wr("SEED", me->seedstr); | ||
1559 | if (me->desc) | ||
1560 | wr("DESC", me->desc); | ||
1561 | if (me->privdesc) | ||
1562 | wr("PRIVDESC", me->privdesc); | ||
1563 | |||
1564 | /* | ||
1565 | * The game's aux_info. We obfuscate this to prevent spoilers | ||
1566 | * (people are likely to run `head' or similar on a saved game | ||
1567 | * file simply to find out what it is, and don't necessarily | ||
1568 | * want to be told the answer to the puzzle!) | ||
1569 | */ | ||
1570 | if (me->aux_info) { | ||
1571 | unsigned char *s1; | ||
1572 | char *s2; | ||
1573 | int len; | ||
1574 | |||
1575 | len = strlen(me->aux_info); | ||
1576 | s1 = snewn(len, unsigned char); | ||
1577 | memcpy(s1, me->aux_info, len); | ||
1578 | obfuscate_bitmap(s1, len*8, FALSE); | ||
1579 | s2 = bin2hex(s1, len); | ||
1580 | |||
1581 | wr("AUXINFO", s2); | ||
1582 | |||
1583 | sfree(s2); | ||
1584 | sfree(s1); | ||
1585 | } | ||
1586 | |||
1587 | /* | ||
1588 | * Any required serialisation of the game_ui. | ||
1589 | */ | ||
1590 | if (me->ui) { | ||
1591 | char *s = me->ourgame->encode_ui(me->ui); | ||
1592 | if (s) { | ||
1593 | wr("UI", s); | ||
1594 | sfree(s); | ||
1595 | } | ||
1596 | } | ||
1597 | |||
1598 | /* | ||
1599 | * The game time, if it's a timed game. | ||
1600 | */ | ||
1601 | if (me->ourgame->is_timed) { | ||
1602 | char buf[80]; | ||
1603 | sprintf(buf, "%g", me->elapsed); | ||
1604 | wr("TIME", buf); | ||
1605 | } | ||
1606 | |||
1607 | /* | ||
1608 | * The length of, and position in, the states list. | ||
1609 | */ | ||
1610 | { | ||
1611 | char buf[80]; | ||
1612 | sprintf(buf, "%d", me->nstates); | ||
1613 | wr("NSTATES", buf); | ||
1614 | sprintf(buf, "%d", me->statepos); | ||
1615 | wr("STATEPOS", buf); | ||
1616 | } | ||
1617 | |||
1618 | /* | ||
1619 | * For each state after the initial one (which we know is | ||
1620 | * constructed from either privdesc or desc), enough | ||
1621 | * information for execute_move() to reconstruct it from the | ||
1622 | * previous one. | ||
1623 | */ | ||
1624 | for (i = 1; i < me->nstates; i++) { | ||
1625 | assert(me->states[i].movetype != NEWGAME); /* only state 0 */ | ||
1626 | switch (me->states[i].movetype) { | ||
1627 | case MOVE: | ||
1628 | wr("MOVE", me->states[i].movestr); | ||
1629 | break; | ||
1630 | case SOLVE: | ||
1631 | wr("SOLVE", me->states[i].movestr); | ||
1632 | break; | ||
1633 | case RESTART: | ||
1634 | wr("RESTART", me->states[i].movestr); | ||
1635 | break; | ||
1636 | } | ||
1637 | } | ||
1638 | |||
1639 | #undef wr | ||
1640 | } | ||
1641 | |||
1642 | /* | ||
1643 | * This function returns NULL on success, or an error message. | ||
1644 | */ | ||
1645 | char *midend_deserialise(midend *me, | ||
1646 | int (*read)(void *ctx, void *buf, int len), | ||
1647 | void *rctx) | ||
1648 | { | ||
1649 | int nstates = 0, statepos = -1, gotstates = 0; | ||
1650 | int started = FALSE; | ||
1651 | int i; | ||
1652 | |||
1653 | char *val = NULL; | ||
1654 | /* Initially all errors give the same report */ | ||
1655 | char *ret = "Data does not appear to be a saved game file"; | ||
1656 | |||
1657 | /* | ||
1658 | * We construct all the new state in local variables while we | ||
1659 | * check its sanity. Only once we have finished reading the | ||
1660 | * serialised data and detected no errors at all do we start | ||
1661 | * modifying stuff in the midend passed in. | ||
1662 | */ | ||
1663 | char *seed = NULL, *parstr = NULL, *desc = NULL, *privdesc = NULL; | ||
1664 | char *auxinfo = NULL, *uistr = NULL, *cparstr = NULL; | ||
1665 | float elapsed = 0.0F; | ||
1666 | game_params *params = NULL, *cparams = NULL; | ||
1667 | game_ui *ui = NULL; | ||
1668 | struct midend_state_entry *states = NULL; | ||
1669 | |||
1670 | /* | ||
1671 | * Loop round and round reading one key/value pair at a time | ||
1672 | * from the serialised stream, until we have enough game states | ||
1673 | * to finish. | ||
1674 | */ | ||
1675 | while (nstates <= 0 || statepos < 0 || gotstates < nstates-1) { | ||
1676 | char key[9], c; | ||
1677 | int len; | ||
1678 | |||
1679 | do { | ||
1680 | if (!read(rctx, key, 1)) { | ||
1681 | /* unexpected EOF */ | ||
1682 | goto cleanup; | ||
1683 | } | ||
1684 | } while (key[0] == '\r' || key[0] == '\n'); | ||
1685 | |||
1686 | if (!read(rctx, key+1, 8)) { | ||
1687 | /* unexpected EOF */ | ||
1688 | goto cleanup; | ||
1689 | } | ||
1690 | |||
1691 | if (key[8] != ':') { | ||
1692 | if (started) | ||
1693 | ret = "Data was incorrectly formatted for a saved game file"; | ||
1694 | goto cleanup; | ||
1695 | } | ||
1696 | len = strcspn(key, ": "); | ||
1697 | assert(len <= 8); | ||
1698 | key[len] = '\0'; | ||
1699 | |||
1700 | len = 0; | ||
1701 | while (1) { | ||
1702 | if (!read(rctx, &c, 1)) { | ||
1703 | /* unexpected EOF */ | ||
1704 | goto cleanup; | ||
1705 | } | ||
1706 | |||
1707 | if (c == ':') { | ||
1708 | break; | ||
1709 | } else if (c >= '0' && c <= '9') { | ||
1710 | len = (len * 10) + (c - '0'); | ||
1711 | } else { | ||
1712 | if (started) | ||
1713 | ret = "Data was incorrectly formatted for a" | ||
1714 | " saved game file"; | ||
1715 | goto cleanup; | ||
1716 | } | ||
1717 | } | ||
1718 | |||
1719 | val = snewn(len+1, char); | ||
1720 | if (!read(rctx, val, len)) { | ||
1721 | if (started) | ||
1722 | goto cleanup; | ||
1723 | } | ||
1724 | val[len] = '\0'; | ||
1725 | |||
1726 | if (!started) { | ||
1727 | if (strcmp(key, "SAVEFILE") || strcmp(val, SERIALISE_MAGIC)) { | ||
1728 | /* ret already has the right message in it */ | ||
1729 | goto cleanup; | ||
1730 | } | ||
1731 | /* Now most errors are this one, unless otherwise specified */ | ||
1732 | ret = "Saved data ended unexpectedly"; | ||
1733 | started = TRUE; | ||
1734 | } else { | ||
1735 | if (!strcmp(key, "VERSION")) { | ||
1736 | if (strcmp(val, SERIALISE_VERSION)) { | ||
1737 | ret = "Cannot handle this version of the saved game" | ||
1738 | " file format"; | ||
1739 | goto cleanup; | ||
1740 | } | ||
1741 | } else if (!strcmp(key, "GAME")) { | ||
1742 | if (strcmp(val, me->ourgame->name)) { | ||
1743 | ret = "Save file is from a different game"; | ||
1744 | goto cleanup; | ||
1745 | } | ||
1746 | } else if (!strcmp(key, "PARAMS")) { | ||
1747 | sfree(parstr); | ||
1748 | parstr = val; | ||
1749 | val = NULL; | ||
1750 | } else if (!strcmp(key, "CPARAMS")) { | ||
1751 | sfree(cparstr); | ||
1752 | cparstr = val; | ||
1753 | val = NULL; | ||
1754 | } else if (!strcmp(key, "SEED")) { | ||
1755 | sfree(seed); | ||
1756 | seed = val; | ||
1757 | val = NULL; | ||
1758 | } else if (!strcmp(key, "DESC")) { | ||
1759 | sfree(desc); | ||
1760 | desc = val; | ||
1761 | val = NULL; | ||
1762 | } else if (!strcmp(key, "PRIVDESC")) { | ||
1763 | sfree(privdesc); | ||
1764 | privdesc = val; | ||
1765 | val = NULL; | ||
1766 | } else if (!strcmp(key, "AUXINFO")) { | ||
1767 | unsigned char *tmp; | ||
1768 | int len = strlen(val) / 2; /* length in bytes */ | ||
1769 | tmp = hex2bin(val, len); | ||
1770 | obfuscate_bitmap(tmp, len*8, TRUE); | ||
1771 | |||
1772 | sfree(auxinfo); | ||
1773 | auxinfo = snewn(len + 1, char); | ||
1774 | memcpy(auxinfo, tmp, len); | ||
1775 | auxinfo[len] = '\0'; | ||
1776 | sfree(tmp); | ||
1777 | } else if (!strcmp(key, "UI")) { | ||
1778 | sfree(uistr); | ||
1779 | uistr = val; | ||
1780 | val = NULL; | ||
1781 | } else if (!strcmp(key, "TIME")) { | ||
1782 | elapsed = (float)atof(val); | ||
1783 | } else if (!strcmp(key, "NSTATES")) { | ||
1784 | nstates = atoi(val); | ||
1785 | if (nstates <= 0) { | ||
1786 | ret = "Number of states in save file was negative"; | ||
1787 | goto cleanup; | ||
1788 | } | ||
1789 | if (states) { | ||
1790 | ret = "Two state counts provided in save file"; | ||
1791 | goto cleanup; | ||
1792 | } | ||
1793 | states = snewn(nstates, struct midend_state_entry); | ||
1794 | for (i = 0; i < nstates; i++) { | ||
1795 | states[i].state = NULL; | ||
1796 | states[i].movestr = NULL; | ||
1797 | states[i].movetype = NEWGAME; | ||
1798 | } | ||
1799 | } else if (!strcmp(key, "STATEPOS")) { | ||
1800 | statepos = atoi(val); | ||
1801 | } else if (!strcmp(key, "MOVE")) { | ||
1802 | gotstates++; | ||
1803 | states[gotstates].movetype = MOVE; | ||
1804 | states[gotstates].movestr = val; | ||
1805 | val = NULL; | ||
1806 | } else if (!strcmp(key, "SOLVE")) { | ||
1807 | gotstates++; | ||
1808 | states[gotstates].movetype = SOLVE; | ||
1809 | states[gotstates].movestr = val; | ||
1810 | val = NULL; | ||
1811 | } else if (!strcmp(key, "RESTART")) { | ||
1812 | gotstates++; | ||
1813 | states[gotstates].movetype = RESTART; | ||
1814 | states[gotstates].movestr = val; | ||
1815 | val = NULL; | ||
1816 | } | ||
1817 | } | ||
1818 | |||
1819 | sfree(val); | ||
1820 | val = NULL; | ||
1821 | } | ||
1822 | |||
1823 | params = me->ourgame->default_params(); | ||
1824 | me->ourgame->decode_params(params, parstr); | ||
1825 | if (me->ourgame->validate_params(params, TRUE)) { | ||
1826 | ret = "Long-term parameters in save file are invalid"; | ||
1827 | goto cleanup; | ||
1828 | } | ||
1829 | cparams = me->ourgame->default_params(); | ||
1830 | me->ourgame->decode_params(cparams, cparstr); | ||
1831 | if (me->ourgame->validate_params(cparams, FALSE)) { | ||
1832 | ret = "Short-term parameters in save file are invalid"; | ||
1833 | goto cleanup; | ||
1834 | } | ||
1835 | if (seed && me->ourgame->validate_params(cparams, TRUE)) { | ||
1836 | /* | ||
1837 | * The seed's no use with this version, but we can perfectly | ||
1838 | * well use the rest of the data. | ||
1839 | */ | ||
1840 | sfree(seed); | ||
1841 | seed = NULL; | ||
1842 | } | ||
1843 | if (!desc) { | ||
1844 | ret = "Game description in save file is missing"; | ||
1845 | goto cleanup; | ||
1846 | } else if (me->ourgame->validate_desc(params, desc)) { | ||
1847 | ret = "Game description in save file is invalid"; | ||
1848 | goto cleanup; | ||
1849 | } | ||
1850 | if (privdesc && me->ourgame->validate_desc(params, privdesc)) { | ||
1851 | ret = "Game private description in save file is invalid"; | ||
1852 | goto cleanup; | ||
1853 | } | ||
1854 | if (statepos < 0 || statepos >= nstates) { | ||
1855 | ret = "Game position in save file is out of range"; | ||
1856 | } | ||
1857 | |||
1858 | states[0].state = me->ourgame->new_game(me, params, | ||
1859 | privdesc ? privdesc : desc); | ||
1860 | for (i = 1; i < nstates; i++) { | ||
1861 | assert(states[i].movetype != NEWGAME); | ||
1862 | switch (states[i].movetype) { | ||
1863 | case MOVE: | ||
1864 | case SOLVE: | ||
1865 | states[i].state = me->ourgame->execute_move(states[i-1].state, | ||
1866 | states[i].movestr); | ||
1867 | if (states[i].state == NULL) { | ||
1868 | ret = "Save file contained an invalid move"; | ||
1869 | goto cleanup; | ||
1870 | } | ||
1871 | break; | ||
1872 | case RESTART: | ||
1873 | if (me->ourgame->validate_desc(params, states[i].movestr)) { | ||
1874 | ret = "Save file contained an invalid restart move"; | ||
1875 | goto cleanup; | ||
1876 | } | ||
1877 | states[i].state = me->ourgame->new_game(me, params, | ||
1878 | states[i].movestr); | ||
1879 | break; | ||
1880 | } | ||
1881 | } | ||
1882 | |||
1883 | ui = me->ourgame->new_ui(states[0].state); | ||
1884 | me->ourgame->decode_ui(ui, uistr); | ||
1885 | |||
1886 | /* | ||
1887 | * Now we've run out of possible error conditions, so we're | ||
1888 | * ready to start overwriting the real data in the current | ||
1889 | * midend. We'll do this by swapping things with the local | ||
1890 | * variables, so that the same cleanup code will free the old | ||
1891 | * stuff. | ||
1892 | */ | ||
1893 | { | ||
1894 | char *tmp; | ||
1895 | |||
1896 | tmp = me->desc; | ||
1897 | me->desc = desc; | ||
1898 | desc = tmp; | ||
1899 | |||
1900 | tmp = me->privdesc; | ||
1901 | me->privdesc = privdesc; | ||
1902 | privdesc = tmp; | ||
1903 | |||
1904 | tmp = me->seedstr; | ||
1905 | me->seedstr = seed; | ||
1906 | seed = tmp; | ||
1907 | |||
1908 | tmp = me->aux_info; | ||
1909 | me->aux_info = auxinfo; | ||
1910 | auxinfo = tmp; | ||
1911 | } | ||
1912 | |||
1913 | me->genmode = GOT_NOTHING; | ||
1914 | |||
1915 | me->statesize = nstates; | ||
1916 | nstates = me->nstates; | ||
1917 | me->nstates = me->statesize; | ||
1918 | { | ||
1919 | struct midend_state_entry *tmp; | ||
1920 | tmp = me->states; | ||
1921 | me->states = states; | ||
1922 | states = tmp; | ||
1923 | } | ||
1924 | me->statepos = statepos; | ||
1925 | |||
1926 | { | ||
1927 | game_params *tmp; | ||
1928 | |||
1929 | tmp = me->params; | ||
1930 | me->params = params; | ||
1931 | params = tmp; | ||
1932 | |||
1933 | tmp = me->curparams; | ||
1934 | me->curparams = cparams; | ||
1935 | cparams = tmp; | ||
1936 | } | ||
1937 | |||
1938 | me->oldstate = NULL; | ||
1939 | me->anim_time = me->anim_pos = me->flash_time = me->flash_pos = 0.0F; | ||
1940 | me->dir = 0; | ||
1941 | |||
1942 | { | ||
1943 | game_ui *tmp; | ||
1944 | |||
1945 | tmp = me->ui; | ||
1946 | me->ui = ui; | ||
1947 | ui = tmp; | ||
1948 | } | ||
1949 | |||
1950 | me->elapsed = elapsed; | ||
1951 | me->pressed_mouse_button = 0; | ||
1952 | |||
1953 | midend_set_timer(me); | ||
1954 | |||
1955 | if (me->drawstate) | ||
1956 | me->ourgame->free_drawstate(me->drawing, me->drawstate); | ||
1957 | me->drawstate = | ||
1958 | me->ourgame->new_drawstate(me->drawing, | ||
1959 | me->states[me->statepos-1].state); | ||
1960 | midend_size_new_drawstate(me); | ||
1961 | |||
1962 | ret = NULL; /* success! */ | ||
1963 | |||
1964 | cleanup: | ||
1965 | sfree(val); | ||
1966 | sfree(seed); | ||
1967 | sfree(parstr); | ||
1968 | sfree(cparstr); | ||
1969 | sfree(desc); | ||
1970 | sfree(privdesc); | ||
1971 | sfree(auxinfo); | ||
1972 | sfree(uistr); | ||
1973 | if (params) | ||
1974 | me->ourgame->free_params(params); | ||
1975 | if (cparams) | ||
1976 | me->ourgame->free_params(cparams); | ||
1977 | if (ui) | ||
1978 | me->ourgame->free_ui(ui); | ||
1979 | if (states) { | ||
1980 | int i; | ||
1981 | |||
1982 | for (i = 0; i < nstates; i++) { | ||
1983 | if (states[i].state) | ||
1984 | me->ourgame->free_game(states[i].state); | ||
1985 | sfree(states[i].movestr); | ||
1986 | } | ||
1987 | sfree(states); | ||
1988 | } | ||
1989 | |||
1990 | return ret; | ||
1991 | } | ||
1992 | |||
1993 | /* | ||
1994 | * This function examines a saved game file just far enough to | ||
1995 | * determine which game type it contains. It returns NULL on success | ||
1996 | * and the game name string in 'name' (which will be dynamically | ||
1997 | * allocated and should be caller-freed), or an error message on | ||
1998 | * failure. | ||
1999 | */ | ||
2000 | char *identify_game(char **name, int (*read)(void *ctx, void *buf, int len), | ||
2001 | void *rctx) | ||
2002 | { | ||
2003 | int nstates = 0, statepos = -1, gotstates = 0; | ||
2004 | int started = FALSE; | ||
2005 | |||
2006 | char *val = NULL; | ||
2007 | /* Initially all errors give the same report */ | ||
2008 | char *ret = "Data does not appear to be a saved game file"; | ||
2009 | |||
2010 | *name = NULL; | ||
2011 | |||
2012 | /* | ||
2013 | * Loop round and round reading one key/value pair at a time from | ||
2014 | * the serialised stream, until we've found the game name. | ||
2015 | */ | ||
2016 | while (nstates <= 0 || statepos < 0 || gotstates < nstates-1) { | ||
2017 | char key[9], c; | ||
2018 | int len; | ||
2019 | |||
2020 | do { | ||
2021 | if (!read(rctx, key, 1)) { | ||
2022 | /* unexpected EOF */ | ||
2023 | goto cleanup; | ||
2024 | } | ||
2025 | } while (key[0] == '\r' || key[0] == '\n'); | ||
2026 | |||
2027 | if (!read(rctx, key+1, 8)) { | ||
2028 | /* unexpected EOF */ | ||
2029 | goto cleanup; | ||
2030 | } | ||
2031 | |||
2032 | if (key[8] != ':') { | ||
2033 | if (started) | ||
2034 | ret = "Data was incorrectly formatted for a saved game file"; | ||
2035 | goto cleanup; | ||
2036 | } | ||
2037 | len = strcspn(key, ": "); | ||
2038 | assert(len <= 8); | ||
2039 | key[len] = '\0'; | ||
2040 | |||
2041 | len = 0; | ||
2042 | while (1) { | ||
2043 | if (!read(rctx, &c, 1)) { | ||
2044 | /* unexpected EOF */ | ||
2045 | goto cleanup; | ||
2046 | } | ||
2047 | |||
2048 | if (c == ':') { | ||
2049 | break; | ||
2050 | } else if (c >= '0' && c <= '9') { | ||
2051 | len = (len * 10) + (c - '0'); | ||
2052 | } else { | ||
2053 | if (started) | ||
2054 | ret = "Data was incorrectly formatted for a" | ||
2055 | " saved game file"; | ||
2056 | goto cleanup; | ||
2057 | } | ||
2058 | } | ||
2059 | |||
2060 | val = snewn(len+1, char); | ||
2061 | if (!read(rctx, val, len)) { | ||
2062 | if (started) | ||
2063 | goto cleanup; | ||
2064 | } | ||
2065 | val[len] = '\0'; | ||
2066 | |||
2067 | if (!started) { | ||
2068 | if (strcmp(key, "SAVEFILE") || strcmp(val, SERIALISE_MAGIC)) { | ||
2069 | /* ret already has the right message in it */ | ||
2070 | goto cleanup; | ||
2071 | } | ||
2072 | /* Now most errors are this one, unless otherwise specified */ | ||
2073 | ret = "Saved data ended unexpectedly"; | ||
2074 | started = TRUE; | ||
2075 | } else { | ||
2076 | if (!strcmp(key, "VERSION")) { | ||
2077 | if (strcmp(val, SERIALISE_VERSION)) { | ||
2078 | ret = "Cannot handle this version of the saved game" | ||
2079 | " file format"; | ||
2080 | goto cleanup; | ||
2081 | } | ||
2082 | } else if (!strcmp(key, "GAME")) { | ||
2083 | *name = dupstr(val); | ||
2084 | ret = NULL; | ||
2085 | goto cleanup; | ||
2086 | } | ||
2087 | } | ||
2088 | |||
2089 | sfree(val); | ||
2090 | val = NULL; | ||
2091 | } | ||
2092 | |||
2093 | cleanup: | ||
2094 | sfree(val); | ||
2095 | return ret; | ||
2096 | } | ||
2097 | |||
2098 | char *midend_print_puzzle(midend *me, document *doc, int with_soln) | ||
2099 | { | ||
2100 | game_state *soln = NULL; | ||
2101 | |||
2102 | if (me->statepos < 1) | ||
2103 | return "No game set up to print";/* _shouldn't_ happen! */ | ||
2104 | |||
2105 | if (with_soln) { | ||
2106 | char *msg, *movestr; | ||
2107 | |||
2108 | if (!me->ourgame->can_solve) | ||
2109 | return "This game does not support the Solve operation"; | ||
2110 | |||
2111 | msg = "Solve operation failed";/* game _should_ overwrite on error */ | ||
2112 | movestr = me->ourgame->solve(me->states[0].state, | ||
2113 | me->states[me->statepos-1].state, | ||
2114 | me->aux_info, &msg); | ||
2115 | if (!movestr) | ||
2116 | return msg; | ||
2117 | soln = me->ourgame->execute_move(me->states[me->statepos-1].state, | ||
2118 | movestr); | ||
2119 | assert(soln); | ||
2120 | |||
2121 | sfree(movestr); | ||
2122 | } else | ||
2123 | soln = NULL; | ||
2124 | |||
2125 | /* | ||
2126 | * This call passes over ownership of the two game_states and | ||
2127 | * the game_params. Hence we duplicate the ones we want to | ||
2128 | * keep, and we don't have to bother freeing soln if it was | ||
2129 | * non-NULL. | ||
2130 | */ | ||
2131 | document_add_puzzle(doc, me->ourgame, | ||
2132 | me->ourgame->dup_params(me->curparams), | ||
2133 | me->ourgame->dup_game(me->states[0].state), soln); | ||
2134 | |||
2135 | return NULL; | ||
2136 | } | ||