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
8 #include <stdio.h>
9 #include <string.h>
10 #include <assert.h>
11 #include <stdlib.h>
12 #include <ctype.h>
13
14 #include "puzzles.h"
15
16 enum { DEF_PARAMS, DEF_SEED, DEF_DESC }; /* for midend_game_id_int */
17
18 enum { NEWGAME, MOVE, SOLVE, RESTART };/* for midend_state_entry.movetype */
19
20 #define special(type) ( (type) != MOVE )
21
22 struct midend_state_entry {
23 game_state *state;
24 char *movestr;
25 int movetype;
26 };
27
28 struct midend_serialise_buf {
29 char *buf;
30 int len, size;
31 };
32
33 struct midend {
34 frontend *frontend;
35 random_state *random;
36 const game *ourgame;
37
38 struct preset_menu *preset_menu;
39 char **encoded_presets; /* for midend_which_preset to check against */
40 int n_encoded_presets;
41
42 /*
43 * `desc' and `privdesc' deserve a comment.
44 *
45 * `desc' is the game description as presented to the user when
46 * they ask for Game -> Specific. `privdesc', if non-NULL, is a
47 * different game description used to reconstruct the initial
48 * game_state when de-serialising. If privdesc is NULL, `desc'
49 * is used for both.
50 *
51 * For almost all games, `privdesc' is NULL and never used. The
52 * exception (as usual) is Mines: the initial game state has no
53 * squares open at all, but after the first click `desc' is
54 * rewritten to describe a game state with an initial click and
55 * thus a bunch of squares open. If we used that desc to
56 * serialise and deserialise, then the initial game state after
57 * deserialisation would look unlike the initial game state
58 * beforehand, and worse still execute_move() might fail on the
59 * attempted first click. So `privdesc' is also used in this
60 * case, to provide a game description describing the same
61 * fixed mine layout _but_ no initial click. (These game IDs
62 * may also be typed directly into Mines if you like.)
63 */
64 char *desc, *privdesc, *seedstr;
65 char *aux_info;
66 enum { GOT_SEED, GOT_DESC, GOT_NOTHING } genmode;
67
68 int nstates, statesize, statepos;
69 struct midend_state_entry *states;
70
71 struct midend_serialise_buf newgame_undo, newgame_redo;
72 bool newgame_can_store_undo;
73
74 game_params *params, *curparams;
75 game_drawstate *drawstate;
76 bool first_draw;
77 game_ui *ui;
78
79 game_state *oldstate;
80 float anim_time, anim_pos;
81 float flash_time, flash_pos;
82 int dir;
83
84 bool timing;
85 float elapsed;
86 char *laststatus;
87
88 drawing *drawing;
89
90 int pressed_mouse_button;
91
92 int preferred_tilesize, tilesize, winwidth, winheight;
93
94 void (*game_id_change_notify_function)(void *);
95 void *game_id_change_notify_ctx;
96 };
97
98 #define ensure(me) do { \
99 if ((me)->nstates >= (me)->statesize) { \
100 (me)->statesize = (me)->nstates + 128; \
101 (me)->states = sresize((me)->states, (me)->statesize, \
102 struct midend_state_entry); \
103 } \
104 } while (0)
105
106 /*
107 * Structure storing all the decoded data from reading a serialised
108 * game. We keep it in one of these while we check its sanity, and
109 * only once we're completely satisfied do we install it all in the
110 * midend structure proper.
111 */
112 struct deserialise_data {
113 char *seed, *parstr, *desc, *privdesc;
114 char *auxinfo, *uistr, *cparstr;
115 float elapsed;
116 game_params *params, *cparams;
117 game_ui *ui;
118 struct midend_state_entry *states;
119 int nstates, statepos;
120 };
121
122 /*
123 * Forward reference.
124 */
125 static const char *midend_deserialise_internal(
126 midend *me, bool (*read)(void *ctx, void *buf, int len), void *rctx,
127 const char *(*check)(void *ctx, midend *, const struct deserialise_data *),
128 void *cctx);
129
midend_reset_tilesize(midend * me)130 void midend_reset_tilesize(midend *me)
131 {
132 me->preferred_tilesize = me->ourgame->preferred_tilesize;
133 {
134 /*
135 * Allow an environment-based override for the default tile
136 * size by defining a variable along the lines of
137 * `NET_TILESIZE=15'.
138 */
139
140 char buf[80], *e;
141 int j, k, ts;
142
143 sprintf(buf, "%s_TILESIZE", me->ourgame->name);
144 for (j = k = 0; buf[j]; j++)
145 if (!isspace((unsigned char)buf[j]))
146 buf[k++] = toupper((unsigned char)buf[j]);
147 buf[k] = '\0';
148 if ((e = getenv(buf)) != NULL && sscanf(e, "%d", &ts) == 1 && ts > 0)
149 me->preferred_tilesize = ts;
150 }
151 }
152
midend_new(frontend * fe,const game * ourgame,const drawing_api * drapi,void * drhandle)153 midend *midend_new(frontend *fe, const game *ourgame,
154 const drawing_api *drapi, void *drhandle)
155 {
156 midend *me = snew(midend);
157 void *randseed;
158 int randseedsize;
159
160 get_random_seed(&randseed, &randseedsize);
161
162 me->frontend = fe;
163 me->ourgame = ourgame;
164 me->random = random_new(randseed, randseedsize);
165 me->nstates = me->statesize = me->statepos = 0;
166 me->states = NULL;
167 me->newgame_undo.buf = NULL;
168 me->newgame_undo.size = me->newgame_undo.len = 0;
169 me->newgame_redo.buf = NULL;
170 me->newgame_redo.size = me->newgame_redo.len = 0;
171 me->newgame_can_store_undo = false;
172 me->params = ourgame->default_params();
173 me->game_id_change_notify_function = NULL;
174 me->game_id_change_notify_ctx = NULL;
175 me->encoded_presets = NULL;
176 me->n_encoded_presets = 0;
177
178 /*
179 * Allow environment-based changing of the default settings by
180 * defining a variable along the lines of `NET_DEFAULT=25x25w'
181 * in which the value is an encoded parameter string.
182 */
183 {
184 char buf[80], *e;
185 int j, k;
186 sprintf(buf, "%s_DEFAULT", me->ourgame->name);
187 for (j = k = 0; buf[j]; j++)
188 if (!isspace((unsigned char)buf[j]))
189 buf[k++] = toupper((unsigned char)buf[j]);
190 buf[k] = '\0';
191 if ((e = getenv(buf)) != NULL)
192 me->ourgame->decode_params(me->params, e);
193 }
194 me->curparams = NULL;
195 me->desc = me->privdesc = NULL;
196 me->seedstr = NULL;
197 me->aux_info = NULL;
198 me->genmode = GOT_NOTHING;
199 me->drawstate = NULL;
200 me->first_draw = true;
201 me->oldstate = NULL;
202 me->preset_menu = NULL;
203 me->anim_time = me->anim_pos = 0.0F;
204 me->flash_time = me->flash_pos = 0.0F;
205 me->dir = 0;
206 me->ui = NULL;
207 me->pressed_mouse_button = 0;
208 me->laststatus = NULL;
209 me->timing = false;
210 me->elapsed = 0.0F;
211 me->tilesize = me->winwidth = me->winheight = 0;
212 if (drapi)
213 me->drawing = drawing_new(drapi, me, drhandle);
214 else
215 me->drawing = NULL;
216
217 midend_reset_tilesize(me);
218
219 sfree(randseed);
220
221 return me;
222 }
223
midend_which_game(midend * me)224 const game *midend_which_game(midend *me)
225 {
226 return me->ourgame;
227 }
228
midend_purge_states(midend * me)229 static void midend_purge_states(midend *me)
230 {
231 while (me->nstates > me->statepos) {
232 me->ourgame->free_game(me->states[--me->nstates].state);
233 if (me->states[me->nstates].movestr)
234 sfree(me->states[me->nstates].movestr);
235 }
236 me->newgame_redo.len = 0;
237 }
238
midend_free_game(midend * me)239 static void midend_free_game(midend *me)
240 {
241 while (me->nstates > 0) {
242 me->nstates--;
243 me->ourgame->free_game(me->states[me->nstates].state);
244 sfree(me->states[me->nstates].movestr);
245 }
246
247 if (me->drawstate)
248 me->ourgame->free_drawstate(me->drawing, me->drawstate);
249 }
250
midend_free_preset_menu(midend * me,struct preset_menu * menu)251 static void midend_free_preset_menu(midend *me, struct preset_menu *menu)
252 {
253 if (menu) {
254 int i;
255 for (i = 0; i < menu->n_entries; i++) {
256 sfree(menu->entries[i].title);
257 if (menu->entries[i].params)
258 me->ourgame->free_params(menu->entries[i].params);
259 midend_free_preset_menu(me, menu->entries[i].submenu);
260 }
261 sfree(menu->entries);
262 sfree(menu);
263 }
264 }
265
midend_free(midend * me)266 void midend_free(midend *me)
267 {
268 int i;
269
270 midend_free_game(me);
271
272 for (i = 0; i < me->n_encoded_presets; i++)
273 sfree(me->encoded_presets[i]);
274 sfree(me->encoded_presets);
275 if (me->drawing)
276 drawing_free(me->drawing);
277 random_free(me->random);
278 sfree(me->newgame_undo.buf);
279 sfree(me->newgame_redo.buf);
280 sfree(me->states);
281 sfree(me->desc);
282 sfree(me->privdesc);
283 sfree(me->seedstr);
284 sfree(me->aux_info);
285 me->ourgame->free_params(me->params);
286 midend_free_preset_menu(me, me->preset_menu);
287 if (me->ui)
288 me->ourgame->free_ui(me->ui);
289 if (me->curparams)
290 me->ourgame->free_params(me->curparams);
291 sfree(me->laststatus);
292 sfree(me);
293 }
294
midend_size_new_drawstate(midend * me)295 static void midend_size_new_drawstate(midend *me)
296 {
297 /*
298 * Don't even bother, if we haven't worked out our tile size
299 * anyway yet.
300 */
301 if (me->tilesize > 0) {
302 me->ourgame->compute_size(me->params, me->tilesize,
303 &me->winwidth, &me->winheight);
304 me->ourgame->set_size(me->drawing, me->drawstate,
305 me->params, me->tilesize);
306 }
307 }
308
midend_size(midend * me,int * x,int * y,bool user_size)309 void midend_size(midend *me, int *x, int *y, bool user_size)
310 {
311 int min, max;
312 int rx, ry;
313
314 /*
315 * We can't set the size on the same drawstate twice. So if
316 * we've already sized one drawstate, we must throw it away and
317 * create a new one.
318 */
319 if (me->drawstate && me->tilesize > 0) {
320 me->ourgame->free_drawstate(me->drawing, me->drawstate);
321 me->drawstate = me->ourgame->new_drawstate(me->drawing,
322 me->states[0].state);
323 me->first_draw = true;
324 }
325
326 /*
327 * Find the tile size that best fits within the given space. If
328 * `user_size' is true, we must actually find the _largest_ such
329 * tile size, in order to get as close to the user's explicit
330 * request as possible; otherwise, we bound above at the game's
331 * preferred tile size, so that the game gets what it wants
332 * provided that this doesn't break the constraint from the
333 * front-end (which is likely to be a screen size or similar).
334 */
335 if (user_size) {
336 max = 1;
337 do {
338 max *= 2;
339 me->ourgame->compute_size(me->params, max, &rx, &ry);
340 } while (rx <= *x && ry <= *y);
341 } else
342 max = me->preferred_tilesize + 1;
343 min = 1;
344
345 /*
346 * Now binary-search between min and max. We're looking for a
347 * boundary rather than a value: the point at which tile sizes
348 * stop fitting within the given dimensions. Thus, we stop when
349 * max and min differ by exactly 1.
350 */
351 while (max - min > 1) {
352 int mid = (max + min) / 2;
353 me->ourgame->compute_size(me->params, mid, &rx, &ry);
354 if (rx <= *x && ry <= *y)
355 min = mid;
356 else
357 max = mid;
358 }
359
360 /*
361 * Now `min' is a valid size, and `max' isn't. So use `min'.
362 */
363
364 me->tilesize = min;
365 if (user_size)
366 /* If the user requested a change in size, make it permanent. */
367 me->preferred_tilesize = me->tilesize;
368 midend_size_new_drawstate(me);
369 *x = me->winwidth;
370 *y = me->winheight;
371 }
372
midend_tilesize(midend * me)373 int midend_tilesize(midend *me) { return me->tilesize; }
374
midend_set_params(midend * me,game_params * params)375 void midend_set_params(midend *me, game_params *params)
376 {
377 me->ourgame->free_params(me->params);
378 me->params = me->ourgame->dup_params(params);
379 }
380
midend_get_params(midend * me)381 game_params *midend_get_params(midend *me)
382 {
383 return me->ourgame->dup_params(me->params);
384 }
385
midend_set_timer(midend * me)386 static void midend_set_timer(midend *me)
387 {
388 me->timing = (me->ourgame->is_timed &&
389 me->ourgame->timing_state(me->states[me->statepos-1].state,
390 me->ui));
391 if (me->timing || me->flash_time || me->anim_time)
392 activate_timer(me->frontend);
393 else
394 deactivate_timer(me->frontend);
395 }
396
midend_force_redraw(midend * me)397 void midend_force_redraw(midend *me)
398 {
399 if (me->drawstate)
400 me->ourgame->free_drawstate(me->drawing, me->drawstate);
401 me->drawstate = me->ourgame->new_drawstate(me->drawing,
402 me->states[0].state);
403 me->first_draw = true;
404 midend_size_new_drawstate(me);
405 midend_redraw(me);
406 }
407
newgame_serialise_write(void * ctx,const void * buf,int len)408 static void newgame_serialise_write(void *ctx, const void *buf, int len)
409 {
410 struct midend_serialise_buf *ser = (struct midend_serialise_buf *)ctx;
411 int new_len;
412
413 assert(len < INT_MAX - ser->len);
414 new_len = ser->len + len;
415 if (new_len > ser->size) {
416 ser->size = new_len + new_len / 4 + 1024;
417 ser->buf = sresize(ser->buf, ser->size, char);
418 }
419 memcpy(ser->buf + ser->len, buf, len);
420 ser->len = new_len;
421 }
422
midend_new_game(midend * me)423 void midend_new_game(midend *me)
424 {
425 me->newgame_undo.len = 0;
426 if (me->newgame_can_store_undo) {
427 /*
428 * Serialise the whole of the game that we're about to
429 * supersede, so that the 'New Game' action can be undone
430 * later.
431 *
432 * We omit this in various situations, such as if there
433 * _isn't_ a current game (not even a starting position)
434 * because this is the initial call to midend_new_game when
435 * the midend is first set up, or if the midend state has
436 * already begun to be overwritten by midend_set_config. In
437 * those situations, we want to avoid writing out any
438 * serialisation, because they will be either invalid, or
439 * worse, valid but wrong.
440 */
441 midend_purge_states(me);
442 midend_serialise(me, newgame_serialise_write, &me->newgame_undo);
443 }
444
445 midend_stop_anim(me);
446 midend_free_game(me);
447
448 assert(me->nstates == 0);
449
450 if (me->genmode == GOT_DESC) {
451 me->genmode = GOT_NOTHING;
452 } else {
453 random_state *rs;
454
455 if (me->genmode == GOT_SEED) {
456 me->genmode = GOT_NOTHING;
457 } else {
458 /*
459 * Generate a new random seed. 15 digits comes to about
460 * 48 bits, which should be more than enough.
461 *
462 * I'll avoid putting a leading zero on the number,
463 * just in case it confuses anybody who thinks it's
464 * processed as an integer rather than a string.
465 */
466 char newseed[16];
467 int i;
468 newseed[15] = '\0';
469 newseed[0] = '1' + (char)random_upto(me->random, 9);
470 for (i = 1; i < 15; i++)
471 newseed[i] = '0' + (char)random_upto(me->random, 10);
472 sfree(me->seedstr);
473 me->seedstr = dupstr(newseed);
474
475 if (me->curparams)
476 me->ourgame->free_params(me->curparams);
477 me->curparams = me->ourgame->dup_params(me->params);
478 }
479
480 sfree(me->desc);
481 sfree(me->privdesc);
482 sfree(me->aux_info);
483 me->aux_info = NULL;
484
485 rs = random_new(me->seedstr, strlen(me->seedstr));
486 /*
487 * If this midend has been instantiated without providing a
488 * drawing API, it is non-interactive. This means that it's
489 * being used for bulk game generation, and hence we should
490 * pass the non-interactive flag to new_desc.
491 */
492 me->desc = me->ourgame->new_desc(me->curparams, rs,
493 &me->aux_info, (me->drawing != NULL));
494 me->privdesc = NULL;
495 random_free(rs);
496 }
497
498 ensure(me);
499
500 /*
501 * It might seem a bit odd that we're using me->params to
502 * create the initial game state, rather than me->curparams
503 * which is better tailored to this specific game and which we
504 * always know.
505 *
506 * It's supposed to be an invariant in the midend that
507 * me->params and me->curparams differ in no aspect that is
508 * important after generation (i.e. after new_desc()). By
509 * deliberately passing the _less_ specific of these two
510 * parameter sets, we provoke play-time misbehaviour in the
511 * case where a game has failed to encode a play-time parameter
512 * in the non-full version of encode_params().
513 */
514 me->states[me->nstates].state =
515 me->ourgame->new_game(me, me->params, me->desc);
516
517 /*
518 * As part of our commitment to self-testing, test the aux
519 * string to make sure nothing ghastly went wrong.
520 */
521 if (me->ourgame->can_solve && me->aux_info) {
522 game_state *s;
523 const char *msg;
524 char *movestr;
525
526 msg = NULL;
527 movestr = me->ourgame->solve(me->states[0].state,
528 me->states[0].state,
529 me->aux_info, &msg);
530 assert(movestr && !msg);
531 s = me->ourgame->execute_move(me->states[0].state, movestr);
532 assert(s);
533 me->ourgame->free_game(s);
534 sfree(movestr);
535 }
536
537 me->states[me->nstates].movestr = NULL;
538 me->states[me->nstates].movetype = NEWGAME;
539 me->nstates++;
540 me->statepos = 1;
541 me->drawstate = me->ourgame->new_drawstate(me->drawing,
542 me->states[0].state);
543 me->first_draw = true;
544 midend_size_new_drawstate(me);
545 me->elapsed = 0.0F;
546 me->flash_pos = me->flash_time = 0.0F;
547 me->anim_pos = me->anim_time = 0.0F;
548 if (me->ui)
549 me->ourgame->free_ui(me->ui);
550 me->ui = me->ourgame->new_ui(me->states[0].state);
551 midend_set_timer(me);
552 me->pressed_mouse_button = 0;
553
554 if (me->game_id_change_notify_function)
555 me->game_id_change_notify_function(me->game_id_change_notify_ctx);
556
557 me->newgame_can_store_undo = true;
558 }
559
midend_can_undo(midend * me)560 bool midend_can_undo(midend *me)
561 {
562 return (me->statepos > 1 || me->newgame_undo.len);
563 }
564
midend_can_redo(midend * me)565 bool midend_can_redo(midend *me)
566 {
567 return (me->statepos < me->nstates || me->newgame_redo.len);
568 }
569
570 struct newgame_undo_deserialise_read_ctx {
571 struct midend_serialise_buf *ser;
572 int len, pos;
573 };
574
newgame_undo_deserialise_read(void * ctx,void * buf,int len)575 static bool newgame_undo_deserialise_read(void *ctx, void *buf, int len)
576 {
577 struct newgame_undo_deserialise_read_ctx *const rctx = ctx;
578
579 if (len > rctx->len - rctx->pos)
580 return false;
581
582 memcpy(buf, rctx->ser->buf + rctx->pos, len);
583 rctx->pos += len;
584 return true;
585 }
586
587 struct newgame_undo_deserialise_check_ctx {
588 bool refused;
589 };
590
newgame_undo_deserialise_check(void * vctx,midend * me,const struct deserialise_data * data)591 static const char *newgame_undo_deserialise_check(
592 void *vctx, midend *me, const struct deserialise_data *data)
593 {
594 struct newgame_undo_deserialise_check_ctx *ctx =
595 (struct newgame_undo_deserialise_check_ctx *)vctx;
596 char *old, *new;
597
598 /*
599 * Undoing a New Game operation is only permitted if it doesn't
600 * change the game parameters. The point of having the ability at
601 * all is to recover from the momentary finger error of having hit
602 * the 'n' key (perhaps in place of some other nearby key), or hit
603 * the New Game menu item by mistake when aiming for the adjacent
604 * Restart; in both those situations, the game params are the same
605 * before and after the new-game operation.
606 *
607 * In principle, we could generalise this so that _any_ call to
608 * midend_new_game could be undone, but that would need all front
609 * ends to be alert to the possibility that any keystroke passed
610 * to midend_process_key might (if it turns out to have been one
611 * of the synonyms for undo, which the frontend doesn't
612 * necessarily check for) have various knock-on effects like
613 * needing to select a different preset in the game type menu, or
614 * even resizing the window. At least for the moment, it's easier
615 * not to do that, and to simply disallow any newgame-undo that is
616 * disruptive in either of those ways.
617 *
618 * We check both params and cparams, to be as safe as possible.
619 */
620
621 old = me->ourgame->encode_params(me->params, true);
622 new = me->ourgame->encode_params(data->params, true);
623 if (strcmp(old, new)) {
624 /* Set a flag to distinguish this deserialise failure
625 * from one due to faulty decoding */
626 ctx->refused = true;
627 return "Undoing this new-game operation would change params";
628 }
629
630 old = me->ourgame->encode_params(me->curparams, true);
631 new = me->ourgame->encode_params(data->cparams, true);
632 if (strcmp(old, new)) {
633 ctx->refused = true;
634 return "Undoing this new-game operation would change params";
635 }
636
637 /*
638 * Otherwise, fine, go ahead.
639 */
640 return NULL;
641 }
642
midend_undo(midend * me)643 static bool midend_undo(midend *me)
644 {
645 const char *deserialise_error;
646
647 if (me->statepos > 1) {
648 if (me->ui)
649 me->ourgame->changed_state(me->ui,
650 me->states[me->statepos-1].state,
651 me->states[me->statepos-2].state);
652 me->statepos--;
653 me->dir = -1;
654 return true;
655 } else if (me->newgame_undo.len) {
656 struct newgame_undo_deserialise_read_ctx rctx;
657 struct newgame_undo_deserialise_check_ctx cctx;
658 struct midend_serialise_buf serbuf;
659
660 /*
661 * Serialise the current game so that you can later redo past
662 * this undo. Once we're committed to the undo actually
663 * happening, we'll copy this data into place.
664 */
665 serbuf.buf = NULL;
666 serbuf.len = serbuf.size = 0;
667 midend_serialise(me, newgame_serialise_write, &serbuf);
668
669 rctx.ser = &me->newgame_undo;
670 rctx.len = me->newgame_undo.len; /* copy for reentrancy safety */
671 rctx.pos = 0;
672 cctx.refused = false;
673 deserialise_error = midend_deserialise_internal(
674 me, newgame_undo_deserialise_read, &rctx,
675 newgame_undo_deserialise_check, &cctx);
676 if (cctx.refused) {
677 /*
678 * Our post-deserialisation check shows that we can't use
679 * this saved game after all. (deserialise_error will
680 * contain the dummy error message generated by our check
681 * function, which we ignore.)
682 */
683 sfree(serbuf.buf);
684 return false;
685 } else {
686 /*
687 * There should never be any _other_ deserialisation
688 * error, because this serialised data has been held in
689 * our memory since it was created, and hasn't had any
690 * opportunity to be corrupted on disk, accidentally
691 * replaced by the wrong file, etc., by user error.
692 */
693 assert(!deserialise_error);
694
695 /*
696 * Clear the old newgame_undo serialisation, so that we
697 * don't try to undo past the beginning of the game we've
698 * just gone back to and end up at the front of it again.
699 */
700 me->newgame_undo.len = 0;
701
702 /*
703 * Copy the serialisation of the game we've just left into
704 * the midend so that we can redo back into it later.
705 */
706 me->newgame_redo.len = 0;
707 newgame_serialise_write(&me->newgame_redo, serbuf.buf, serbuf.len);
708
709 sfree(serbuf.buf);
710 return true;
711 }
712 } else
713 return false;
714 }
715
midend_redo(midend * me)716 static bool midend_redo(midend *me)
717 {
718 const char *deserialise_error;
719
720 if (me->statepos < me->nstates) {
721 if (me->ui)
722 me->ourgame->changed_state(me->ui,
723 me->states[me->statepos-1].state,
724 me->states[me->statepos].state);
725 me->statepos++;
726 me->dir = +1;
727 return true;
728 } else if (me->newgame_redo.len) {
729 struct newgame_undo_deserialise_read_ctx rctx;
730 struct newgame_undo_deserialise_check_ctx cctx;
731 struct midend_serialise_buf serbuf;
732
733 /*
734 * Serialise the current game so that you can later undo past
735 * this redo. Once we're committed to the undo actually
736 * happening, we'll copy this data into place.
737 */
738 serbuf.buf = NULL;
739 serbuf.len = serbuf.size = 0;
740 midend_serialise(me, newgame_serialise_write, &serbuf);
741
742 rctx.ser = &me->newgame_redo;
743 rctx.len = me->newgame_redo.len; /* copy for reentrancy safety */
744 rctx.pos = 0;
745 cctx.refused = false;
746 deserialise_error = midend_deserialise_internal(
747 me, newgame_undo_deserialise_read, &rctx,
748 newgame_undo_deserialise_check, &cctx);
749 if (cctx.refused) {
750 /*
751 * Our post-deserialisation check shows that we can't use
752 * this saved game after all. (deserialise_error will
753 * contain the dummy error message generated by our check
754 * function, which we ignore.)
755 */
756 sfree(serbuf.buf);
757 return false;
758 } else {
759 /*
760 * There should never be any _other_ deserialisation
761 * error, because this serialised data has been held in
762 * our memory since it was created, and hasn't had any
763 * opportunity to be corrupted on disk, accidentally
764 * replaced by the wrong file, etc., by user error.
765 */
766 assert(!deserialise_error);
767
768 /*
769 * Clear the old newgame_redo serialisation, so that we
770 * don't try to redo past the end of the game we've just
771 * come into and end up at the back of it again.
772 */
773 me->newgame_redo.len = 0;
774
775 /*
776 * Copy the serialisation of the game we've just left into
777 * the midend so that we can undo back into it later.
778 */
779 me->newgame_undo.len = 0;
780 newgame_serialise_write(&me->newgame_undo, serbuf.buf, serbuf.len);
781
782 sfree(serbuf.buf);
783 return true;
784 }
785 } else
786 return false;
787 }
788
midend_finish_move(midend * me)789 static void midend_finish_move(midend *me)
790 {
791 float flashtime;
792
793 /*
794 * We do not flash if the later of the two states is special.
795 * This covers both forward Solve moves and backward (undone)
796 * Restart moves.
797 */
798 if ((me->oldstate || me->statepos > 1) &&
799 ((me->dir > 0 && !special(me->states[me->statepos-1].movetype)) ||
800 (me->dir < 0 && me->statepos < me->nstates &&
801 !special(me->states[me->statepos].movetype)))) {
802 flashtime = me->ourgame->flash_length(me->oldstate ? me->oldstate :
803 me->states[me->statepos-2].state,
804 me->states[me->statepos-1].state,
805 me->oldstate ? me->dir : +1,
806 me->ui);
807 if (flashtime > 0) {
808 me->flash_pos = 0.0F;
809 me->flash_time = flashtime;
810 }
811 }
812
813 if (me->oldstate)
814 me->ourgame->free_game(me->oldstate);
815 me->oldstate = NULL;
816 me->anim_pos = me->anim_time = 0;
817 me->dir = 0;
818
819 midend_set_timer(me);
820 }
821
midend_stop_anim(midend * me)822 void midend_stop_anim(midend *me)
823 {
824 if (me->oldstate || me->anim_time != 0) {
825 midend_finish_move(me);
826 midend_redraw(me);
827 }
828 }
829
midend_restart_game(midend * me)830 void midend_restart_game(midend *me)
831 {
832 game_state *s;
833
834 assert(me->statepos >= 1);
835 if (me->statepos == 1)
836 return; /* no point doing anything at all! */
837
838 /*
839 * During restart, we reconstruct the game from the (public)
840 * game description rather than from states[0], because that
841 * way Mines gets slightly more sensible behaviour (restart
842 * goes to _after_ the first click so you don't have to
843 * remember where you clicked).
844 */
845 s = me->ourgame->new_game(me, me->params, me->desc);
846
847 /*
848 * Now enter the restarted state as the next move.
849 */
850 midend_stop_anim(me);
851 midend_purge_states(me);
852 ensure(me);
853 me->states[me->nstates].state = s;
854 me->states[me->nstates].movestr = dupstr(me->desc);
855 me->states[me->nstates].movetype = RESTART;
856 me->statepos = ++me->nstates;
857 if (me->ui)
858 me->ourgame->changed_state(me->ui,
859 me->states[me->statepos-2].state,
860 me->states[me->statepos-1].state);
861 me->flash_pos = me->flash_time = 0.0F;
862 midend_finish_move(me);
863 midend_redraw(me);
864 midend_set_timer(me);
865 }
866
midend_really_process_key(midend * me,int x,int y,int button)867 static bool midend_really_process_key(midend *me, int x, int y, int button)
868 {
869 game_state *oldstate =
870 me->ourgame->dup_game(me->states[me->statepos - 1].state);
871 int type = MOVE;
872 bool gottype = false, ret = true;
873 float anim_time;
874 game_state *s;
875 char *movestr = NULL;
876
877 if (!IS_UI_FAKE_KEY(button)) {
878 movestr = me->ourgame->interpret_move(
879 me->states[me->statepos-1].state,
880 me->ui, me->drawstate, x, y, button);
881 }
882
883 if (!movestr) {
884 if (button == 'n' || button == 'N' || button == '\x0E' ||
885 button == UI_NEWGAME) {
886 midend_new_game(me);
887 midend_redraw(me);
888 goto done; /* never animate */
889 } else if (button == 'u' || button == 'U' ||
890 button == '\x1A' || button == '\x1F' ||
891 button == UI_UNDO) {
892 midend_stop_anim(me);
893 type = me->states[me->statepos-1].movetype;
894 gottype = true;
895 if (!midend_undo(me))
896 goto done;
897 } else if (button == 'r' || button == 'R' ||
898 button == '\x12' || button == '\x19' ||
899 button == UI_REDO) {
900 midend_stop_anim(me);
901 if (!midend_redo(me))
902 goto done;
903 } else if ((button == '\x13' || button == UI_SOLVE) &&
904 me->ourgame->can_solve) {
905 if (midend_solve(me))
906 goto done;
907 } else if (button == 'q' || button == 'Q' || button == '\x11' ||
908 button == UI_QUIT) {
909 ret = false;
910 goto done;
911 } else
912 goto done;
913 } else {
914 if (movestr == UI_UPDATE)
915 s = me->states[me->statepos-1].state;
916 else {
917 s = me->ourgame->execute_move(me->states[me->statepos-1].state,
918 movestr);
919 assert(s != NULL);
920 }
921
922 if (s == me->states[me->statepos-1].state) {
923 /*
924 * make_move() is allowed to return its input state to
925 * indicate that although no move has been made, the UI
926 * state has been updated and a redraw is called for.
927 */
928 midend_redraw(me);
929 midend_set_timer(me);
930 goto done;
931 } else if (s) {
932 midend_stop_anim(me);
933 midend_purge_states(me);
934 ensure(me);
935 assert(movestr != NULL);
936 me->states[me->nstates].state = s;
937 me->states[me->nstates].movestr = movestr;
938 me->states[me->nstates].movetype = MOVE;
939 me->statepos = ++me->nstates;
940 me->dir = +1;
941 if (me->ui)
942 me->ourgame->changed_state(me->ui,
943 me->states[me->statepos-2].state,
944 me->states[me->statepos-1].state);
945 } else {
946 goto done;
947 }
948 }
949
950 if (!gottype)
951 type = me->states[me->statepos-1].movetype;
952
953 /*
954 * See if this move requires an animation.
955 */
956 if (special(type) && !(type == SOLVE &&
957 (me->ourgame->flags & SOLVE_ANIMATES))) {
958 anim_time = 0;
959 } else {
960 anim_time = me->ourgame->anim_length(oldstate,
961 me->states[me->statepos-1].state,
962 me->dir, me->ui);
963 }
964
965 me->oldstate = oldstate; oldstate = NULL;
966 if (anim_time > 0) {
967 me->anim_time = anim_time;
968 } else {
969 me->anim_time = 0.0;
970 midend_finish_move(me);
971 }
972 me->anim_pos = 0.0;
973
974 midend_redraw(me);
975
976 midend_set_timer(me);
977
978 done:
979 if (oldstate) me->ourgame->free_game(oldstate);
980 return ret;
981 }
982
midend_process_key(midend * me,int x,int y,int button)983 bool midend_process_key(midend *me, int x, int y, int button)
984 {
985 bool ret = true;
986
987 /*
988 * Harmonise mouse drag and release messages.
989 *
990 * Some front ends might accidentally switch from sending, say,
991 * RIGHT_DRAG messages to sending LEFT_DRAG, half way through a
992 * drag. (This can happen on the Mac, for example, since
993 * RIGHT_DRAG is usually done using Command+drag, and if the
994 * user accidentally releases Command half way through the drag
995 * then there will be trouble.)
996 *
997 * It would be an O(number of front ends) annoyance to fix this
998 * in the front ends, but an O(number of back ends) annoyance
999 * to have each game capable of dealing with it. Therefore, we
1000 * fix it _here_ in the common midend code so that it only has
1001 * to be done once.
1002 *
1003 * The possible ways in which things can go screwy in the front
1004 * end are:
1005 *
1006 * - in a system containing multiple physical buttons button
1007 * presses can inadvertently overlap. We can see ABab (caps
1008 * meaning button-down and lowercase meaning button-up) when
1009 * the user had semantically intended AaBb.
1010 *
1011 * - in a system where one button is simulated by means of a
1012 * modifier key and another button, buttons can mutate
1013 * between press and release (possibly during drag). So we
1014 * can see Ab instead of Aa.
1015 *
1016 * Definite requirements are:
1017 *
1018 * - button _presses_ must never be invented or destroyed. If
1019 * the user presses two buttons in succession, the button
1020 * presses must be transferred to the backend unchanged. So
1021 * if we see AaBb , that's fine; if we see ABab (the button
1022 * presses inadvertently overlapped) we must somehow
1023 * `correct' it to AaBb.
1024 *
1025 * - every mouse action must end up looking like a press, zero
1026 * or more drags, then a release. This allows back ends to
1027 * make the _assumption_ that incoming mouse data will be
1028 * sane in this regard, and not worry about the details.
1029 *
1030 * So my policy will be:
1031 *
1032 * - treat any button-up as a button-up for the currently
1033 * pressed button, or ignore it if there is no currently
1034 * pressed button.
1035 *
1036 * - treat any drag as a drag for the currently pressed
1037 * button, or ignore it if there is no currently pressed
1038 * button.
1039 *
1040 * - if we see a button-down while another button is currently
1041 * pressed, invent a button-up for the first one and then
1042 * pass the button-down through as before.
1043 *
1044 * 2005-05-31: An addendum to the above. Some games might want
1045 * a `priority order' among buttons, such that if one button is
1046 * pressed while another is down then a fixed one of the
1047 * buttons takes priority no matter what order they're pressed
1048 * in. Mines, in particular, wants to treat a left+right click
1049 * like a left click for the benefit of users of other
1050 * implementations. So the last of the above points is modified
1051 * in the presence of an (optional) button priority order.
1052 *
1053 * A further addition: we translate certain keyboard presses to
1054 * cursor key 'select' buttons, so that a) frontends don't have
1055 * to translate these themselves (like they do for CURSOR_UP etc),
1056 * and b) individual games don't have to hard-code button presses
1057 * of '\n' etc for keyboard-based cursors. The choice of buttons
1058 * here could eventually be controlled by a runtime configuration
1059 * option.
1060 */
1061 if (IS_MOUSE_DRAG(button) || IS_MOUSE_RELEASE(button)) {
1062 if (me->pressed_mouse_button) {
1063 if (IS_MOUSE_DRAG(button)) {
1064 button = me->pressed_mouse_button +
1065 (LEFT_DRAG - LEFT_BUTTON);
1066 } else {
1067 button = me->pressed_mouse_button +
1068 (LEFT_RELEASE - LEFT_BUTTON);
1069 }
1070 } else
1071 return ret; /* ignore it */
1072 } else if (IS_MOUSE_DOWN(button) && me->pressed_mouse_button) {
1073 /*
1074 * If the new button has lower priority than the old one,
1075 * don't bother doing this.
1076 */
1077 if (me->ourgame->flags &
1078 BUTTON_BEATS(me->pressed_mouse_button, button))
1079 return ret; /* just ignore it */
1080
1081 /*
1082 * Fabricate a button-up for the previously pressed button.
1083 */
1084 ret = ret && midend_really_process_key
1085 (me, x, y, (me->pressed_mouse_button +
1086 (LEFT_RELEASE - LEFT_BUTTON)));
1087 }
1088
1089 /*
1090 * Translate keyboard presses to cursor selection.
1091 */
1092 if (button == '\n' || button == '\r')
1093 button = CURSOR_SELECT;
1094 if (button == ' ')
1095 button = CURSOR_SELECT2;
1096
1097 /*
1098 * Normalise both backspace characters (8 and 127) to \b. Easier
1099 * to do this once, here, than to require all front ends to
1100 * carefully generate the same one - now each front end can
1101 * generate whichever is easiest.
1102 */
1103 if (button == '\177')
1104 button = '\b';
1105
1106 /*
1107 * Now send on the event we originally received.
1108 */
1109 ret = ret && midend_really_process_key(me, x, y, button);
1110
1111 /*
1112 * And update the currently pressed button.
1113 */
1114 if (IS_MOUSE_RELEASE(button))
1115 me->pressed_mouse_button = 0;
1116 else if (IS_MOUSE_DOWN(button))
1117 me->pressed_mouse_button = button;
1118
1119 return ret;
1120 }
1121
midend_request_keys(midend * me,int * n)1122 key_label *midend_request_keys(midend *me, int *n)
1123 {
1124 key_label *keys = NULL;
1125 int nkeys = 0, i;
1126
1127 if(me->ourgame->request_keys)
1128 {
1129 keys = me->ourgame->request_keys(midend_get_params(me), &nkeys);
1130 for(i = 0; i < nkeys; ++i)
1131 {
1132 if(!keys[i].label)
1133 keys[i].label = button2label(keys[i].button);
1134 }
1135 }
1136
1137 if(n)
1138 *n = nkeys;
1139
1140 return keys;
1141 }
1142
midend_redraw(midend * me)1143 void midend_redraw(midend *me)
1144 {
1145 assert(me->drawing);
1146
1147 if (me->statepos > 0 && me->drawstate) {
1148 bool first_draw = me->first_draw;
1149 me->first_draw = false;
1150
1151 start_draw(me->drawing);
1152
1153 if (first_draw) {
1154 /*
1155 * The initial contents of the window are not guaranteed
1156 * by the front end. But we also don't want to require
1157 * every single game to go to the effort of clearing the
1158 * window on setup. So we centralise here the operation of
1159 * covering the whole window with colour 0 (assumed to be
1160 * the puzzle's background colour) the first time we do a
1161 * redraw operation with a new drawstate.
1162 */
1163 draw_rect(me->drawing, 0, 0, me->winwidth, me->winheight, 0);
1164 }
1165
1166 if (me->oldstate && me->anim_time > 0 &&
1167 me->anim_pos < me->anim_time) {
1168 assert(me->dir != 0);
1169 me->ourgame->redraw(me->drawing, me->drawstate, me->oldstate,
1170 me->states[me->statepos-1].state, me->dir,
1171 me->ui, me->anim_pos, me->flash_pos);
1172 } else {
1173 me->ourgame->redraw(me->drawing, me->drawstate, NULL,
1174 me->states[me->statepos-1].state, +1 /*shrug*/,
1175 me->ui, 0.0, me->flash_pos);
1176 }
1177
1178 if (first_draw) {
1179 /*
1180 * Call a big draw_update on the whole window, in case the
1181 * game backend didn't.
1182 */
1183 draw_update(me->drawing, 0, 0, me->winwidth, me->winheight);
1184 }
1185
1186 end_draw(me->drawing);
1187 }
1188 }
1189
1190 /*
1191 * Nasty hacky function used to implement the --redo option in
1192 * gtk.c. Only used for generating the puzzles' icons.
1193 */
midend_freeze_timer(midend * me,float tprop)1194 void midend_freeze_timer(midend *me, float tprop)
1195 {
1196 me->anim_pos = me->anim_time * tprop;
1197 midend_redraw(me);
1198 deactivate_timer(me->frontend);
1199 }
1200
midend_timer(midend * me,float tplus)1201 void midend_timer(midend *me, float tplus)
1202 {
1203 bool need_redraw = (me->anim_time > 0 || me->flash_time > 0);
1204
1205 me->anim_pos += tplus;
1206 if (me->anim_pos >= me->anim_time ||
1207 me->anim_time == 0 || !me->oldstate) {
1208 if (me->anim_time > 0)
1209 midend_finish_move(me);
1210 }
1211
1212 me->flash_pos += tplus;
1213 if (me->flash_pos >= me->flash_time || me->flash_time == 0) {
1214 me->flash_pos = me->flash_time = 0;
1215 }
1216
1217 if (need_redraw)
1218 midend_redraw(me);
1219
1220 if (me->timing) {
1221 float oldelapsed = me->elapsed;
1222 me->elapsed += tplus;
1223 if ((int)oldelapsed != (int)me->elapsed)
1224 status_bar(me->drawing, me->laststatus ? me->laststatus : "");
1225 }
1226
1227 midend_set_timer(me);
1228 }
1229
midend_colours(midend * me,int * ncolours)1230 float *midend_colours(midend *me, int *ncolours)
1231 {
1232 float *ret;
1233
1234 ret = me->ourgame->colours(me->frontend, ncolours);
1235
1236 {
1237 int i;
1238
1239 /*
1240 * Allow environment-based overrides for the standard
1241 * colours by defining variables along the lines of
1242 * `NET_COLOUR_4=6000c0'.
1243 */
1244
1245 for (i = 0; i < *ncolours; i++) {
1246 char buf[80], *e;
1247 unsigned int r, g, b;
1248 int j, k;
1249
1250 sprintf(buf, "%s_COLOUR_%d", me->ourgame->name, i);
1251 for (j = k = 0; buf[j]; j++)
1252 if (!isspace((unsigned char)buf[j]))
1253 buf[k++] = toupper((unsigned char)buf[j]);
1254 buf[k] = '\0';
1255 if ((e = getenv(buf)) != NULL &&
1256 sscanf(e, "%2x%2x%2x", &r, &g, &b) == 3) {
1257 ret[i*3 + 0] = r / 255.0F;
1258 ret[i*3 + 1] = g / 255.0F;
1259 ret[i*3 + 2] = b / 255.0F;
1260 }
1261 }
1262 }
1263
1264 return ret;
1265 }
1266
preset_menu_new(void)1267 struct preset_menu *preset_menu_new(void)
1268 {
1269 struct preset_menu *menu = snew(struct preset_menu);
1270 menu->n_entries = 0;
1271 menu->entries_size = 0;
1272 menu->entries = NULL;
1273 return menu;
1274 }
1275
preset_menu_add(struct preset_menu * menu,char * title)1276 static struct preset_menu_entry *preset_menu_add(struct preset_menu *menu,
1277 char *title)
1278 {
1279 struct preset_menu_entry *toret;
1280 if (menu->n_entries >= menu->entries_size) {
1281 menu->entries_size = menu->n_entries * 5 / 4 + 10;
1282 menu->entries = sresize(menu->entries, menu->entries_size,
1283 struct preset_menu_entry);
1284 }
1285 toret = &menu->entries[menu->n_entries++];
1286 toret->title = title;
1287 toret->params = NULL;
1288 toret->submenu = NULL;
1289 return toret;
1290 }
1291
preset_menu_add_submenu(struct preset_menu * parent,char * title)1292 struct preset_menu *preset_menu_add_submenu(struct preset_menu *parent,
1293 char *title)
1294 {
1295 struct preset_menu_entry *entry = preset_menu_add(parent, title);
1296 entry->submenu = preset_menu_new();
1297 return entry->submenu;
1298 }
1299
preset_menu_add_preset(struct preset_menu * parent,char * title,game_params * params)1300 void preset_menu_add_preset(struct preset_menu *parent,
1301 char *title, game_params *params)
1302 {
1303 struct preset_menu_entry *entry = preset_menu_add(parent, title);
1304 entry->params = params;
1305 }
1306
preset_menu_lookup_by_id(struct preset_menu * menu,int id)1307 game_params *preset_menu_lookup_by_id(struct preset_menu *menu, int id)
1308 {
1309 int i;
1310 game_params *retd;
1311
1312 for (i = 0; i < menu->n_entries; i++) {
1313 if (id == menu->entries[i].id)
1314 return menu->entries[i].params;
1315 if (menu->entries[i].submenu &&
1316 (retd = preset_menu_lookup_by_id(
1317 menu->entries[i].submenu, id)) != NULL)
1318 return retd;
1319 }
1320
1321 return NULL;
1322 }
1323
preset_menu_add_from_user_env(midend * me,struct preset_menu * menu,char * p,bool top_level)1324 static char *preset_menu_add_from_user_env(
1325 midend *me, struct preset_menu *menu, char *p, bool top_level)
1326 {
1327 while (*p) {
1328 char *name, *val;
1329 game_params *preset;
1330
1331 name = p;
1332 while (*p && *p != ':') p++;
1333 if (*p) *p++ = '\0';
1334 val = p;
1335 while (*p && *p != ':') p++;
1336 if (*p) *p++ = '\0';
1337
1338 if (!strcmp(val, "#")) {
1339 /*
1340 * Special case: either open a new submenu with the given
1341 * title, or terminate the current submenu.
1342 */
1343 if (*name) {
1344 struct preset_menu *submenu =
1345 preset_menu_add_submenu(menu, dupstr(name));
1346 p = preset_menu_add_from_user_env(me, submenu, p, false);
1347 } else {
1348 /*
1349 * If we get a 'close submenu' indication at the top
1350 * level, there's not much we can do but quietly
1351 * ignore it.
1352 */
1353 if (!top_level)
1354 return p;
1355 }
1356 continue;
1357 }
1358
1359 preset = me->ourgame->default_params();
1360 me->ourgame->decode_params(preset, val);
1361
1362 if (me->ourgame->validate_params(preset, true)) {
1363 /* Drop this one from the list. */
1364 me->ourgame->free_params(preset);
1365 continue;
1366 }
1367
1368 preset_menu_add_preset(menu, dupstr(name), preset);
1369 }
1370
1371 return p;
1372 }
1373
preset_menu_alloc_ids(midend * me,struct preset_menu * menu)1374 static void preset_menu_alloc_ids(midend *me, struct preset_menu *menu)
1375 {
1376 int i;
1377
1378 for (i = 0; i < menu->n_entries; i++)
1379 menu->entries[i].id = me->n_encoded_presets++;
1380
1381 for (i = 0; i < menu->n_entries; i++)
1382 if (menu->entries[i].submenu)
1383 preset_menu_alloc_ids(me, menu->entries[i].submenu);
1384 }
1385
preset_menu_encode_params(midend * me,struct preset_menu * menu)1386 static void preset_menu_encode_params(midend *me, struct preset_menu *menu)
1387 {
1388 int i;
1389
1390 for (i = 0; i < menu->n_entries; i++) {
1391 if (menu->entries[i].params) {
1392 me->encoded_presets[menu->entries[i].id] =
1393 me->ourgame->encode_params(menu->entries[i].params, true);
1394 } else {
1395 preset_menu_encode_params(me, menu->entries[i].submenu);
1396 }
1397 }
1398 }
1399
midend_get_presets(midend * me,int * id_limit)1400 struct preset_menu *midend_get_presets(midend *me, int *id_limit)
1401 {
1402 int i;
1403
1404 if (me->preset_menu)
1405 return me->preset_menu;
1406
1407 #if 0
1408 /* Expect the game to implement exactly one of the two preset APIs */
1409 assert(me->ourgame->fetch_preset || me->ourgame->preset_menu);
1410 assert(!(me->ourgame->fetch_preset && me->ourgame->preset_menu));
1411 #endif
1412
1413 if (me->ourgame->fetch_preset) {
1414 char *name;
1415 game_params *preset;
1416
1417 /* Simple one-level menu */
1418 assert(!me->ourgame->preset_menu);
1419 me->preset_menu = preset_menu_new();
1420 for (i = 0; me->ourgame->fetch_preset(i, &name, &preset); i++)
1421 preset_menu_add_preset(me->preset_menu, name, preset);
1422
1423 } else {
1424 /* Hierarchical menu provided by the game backend */
1425 me->preset_menu = me->ourgame->preset_menu();
1426 }
1427
1428 {
1429 /*
1430 * Allow user extensions to the preset list by defining an
1431 * environment variable <gamename>_PRESETS whose value is a
1432 * colon-separated list of items, alternating between textual
1433 * titles in the menu and encoded parameter strings. For
1434 * example, "SOLO_PRESETS=2x3 Advanced:2x3da" would define
1435 * just one additional preset for Solo.
1436 */
1437 char buf[80], *e;
1438 int j, k;
1439
1440 sprintf(buf, "%s_PRESETS", me->ourgame->name);
1441 for (j = k = 0; buf[j]; j++)
1442 if (!isspace((unsigned char)buf[j]))
1443 buf[k++] = toupper((unsigned char)buf[j]);
1444 buf[k] = '\0';
1445
1446 if ((e = getenv(buf)) != NULL) {
1447 e = dupstr(e);
1448 preset_menu_add_from_user_env(me, me->preset_menu, e, true);
1449 sfree(e);
1450 }
1451 }
1452
1453 /*
1454 * Finalise the menu: allocate an integer id to each entry, and
1455 * store string encodings of the presets' parameters in
1456 * me->encoded_presets.
1457 */
1458 me->n_encoded_presets = 0;
1459 preset_menu_alloc_ids(me, me->preset_menu);
1460 me->encoded_presets = snewn(me->n_encoded_presets, char *);
1461 for (i = 0; i < me->n_encoded_presets; i++)
1462 me->encoded_presets[i] = NULL;
1463 preset_menu_encode_params(me, me->preset_menu);
1464
1465 if (id_limit)
1466 *id_limit = me->n_encoded_presets;
1467 return me->preset_menu;
1468 }
1469
midend_which_preset(midend * me)1470 int midend_which_preset(midend *me)
1471 {
1472 char *encoding = me->ourgame->encode_params(me->params, true);
1473 int i, ret;
1474
1475 ret = -1;
1476 for (i = 0; i < me->n_encoded_presets; i++)
1477 if (me->encoded_presets[i] &&
1478 !strcmp(encoding, me->encoded_presets[i])) {
1479 ret = i;
1480 break;
1481 }
1482
1483 sfree(encoding);
1484 return ret;
1485 }
1486
midend_wants_statusbar(midend * me)1487 bool midend_wants_statusbar(midend *me)
1488 {
1489 return me->ourgame->wants_statusbar;
1490 }
1491
midend_request_id_changes(midend * me,void (* notify)(void *),void * ctx)1492 void midend_request_id_changes(midend *me, void (*notify)(void *), void *ctx)
1493 {
1494 me->game_id_change_notify_function = notify;
1495 me->game_id_change_notify_ctx = ctx;
1496 }
1497
midend_get_cursor_location(midend * me,int * x_out,int * y_out,int * w_out,int * h_out)1498 bool midend_get_cursor_location(midend *me,
1499 int *x_out, int *y_out,
1500 int *w_out, int *h_out)
1501 {
1502 int x, y, w, h;
1503 x = y = -1;
1504 w = h = 1;
1505
1506 if(me->ourgame->get_cursor_location)
1507 me->ourgame->get_cursor_location(me->ui,
1508 me->drawstate,
1509 me->states[me->statepos-1].state,
1510 me->params,
1511 &x, &y, &w, &h);
1512
1513 if(x == -1 && y == -1)
1514 return false;
1515
1516 if(x_out)
1517 *x_out = x;
1518 if(y_out)
1519 *y_out = y;
1520 if(w_out)
1521 *w_out = w;
1522 if(h_out)
1523 *h_out = h;
1524 return true;
1525 }
1526
midend_supersede_game_desc(midend * me,const char * desc,const char * privdesc)1527 void midend_supersede_game_desc(midend *me, const char *desc,
1528 const char *privdesc)
1529 {
1530 sfree(me->desc);
1531 sfree(me->privdesc);
1532 me->desc = dupstr(desc);
1533 me->privdesc = privdesc ? dupstr(privdesc) : NULL;
1534 if (me->game_id_change_notify_function)
1535 me->game_id_change_notify_function(me->game_id_change_notify_ctx);
1536 }
1537
midend_get_config(midend * me,int which,char ** wintitle)1538 config_item *midend_get_config(midend *me, int which, char **wintitle)
1539 {
1540 char *titlebuf, *parstr;
1541 const char *rest;
1542 config_item *ret;
1543 char sep;
1544
1545 assert(wintitle);
1546 titlebuf = snewn(40 + strlen(me->ourgame->name), char);
1547
1548 switch (which) {
1549 case CFG_SETTINGS:
1550 sprintf(titlebuf, "%s configuration", me->ourgame->name);
1551 *wintitle = titlebuf;
1552 return me->ourgame->configure(me->params);
1553 case CFG_SEED:
1554 case CFG_DESC:
1555 if (!me->curparams) {
1556 sfree(titlebuf);
1557 return NULL;
1558 }
1559 sprintf(titlebuf, "%s %s selection", me->ourgame->name,
1560 which == CFG_SEED ? "random" : "game");
1561 *wintitle = titlebuf;
1562
1563 ret = snewn(2, config_item);
1564
1565 ret[0].type = C_STRING;
1566 if (which == CFG_SEED)
1567 ret[0].name = "Game random seed";
1568 else
1569 ret[0].name = "Game ID";
1570 /*
1571 * For CFG_DESC the text going in here will be a string
1572 * encoding of the restricted parameters, plus a colon,
1573 * plus the game description. For CFG_SEED it will be the
1574 * full parameters, plus a hash, plus the random seed data.
1575 * Either of these is a valid full game ID (although only
1576 * the former is likely to persist across many code
1577 * changes).
1578 */
1579 parstr = me->ourgame->encode_params(me->curparams, which == CFG_SEED);
1580 assert(parstr);
1581 if (which == CFG_DESC) {
1582 rest = me->desc ? me->desc : "";
1583 sep = ':';
1584 } else {
1585 rest = me->seedstr ? me->seedstr : "";
1586 sep = '#';
1587 }
1588 ret[0].u.string.sval = snewn(strlen(parstr) + strlen(rest) + 2, char);
1589 sprintf(ret[0].u.string.sval, "%s%c%s", parstr, sep, rest);
1590 sfree(parstr);
1591
1592 ret[1].type = C_END;
1593 ret[1].name = NULL;
1594
1595 return ret;
1596 }
1597
1598 assert(!"We shouldn't be here");
1599 return NULL;
1600 }
1601
midend_game_id_int(midend * me,const char * id,int defmode)1602 static const char *midend_game_id_int(midend *me, const char *id, int defmode)
1603 {
1604 const char *error;
1605 char *par = NULL;
1606 const char *desc, *seed;
1607 game_params *newcurparams, *newparams, *oldparams1, *oldparams2;
1608 bool free_params;
1609
1610 seed = strchr(id, '#');
1611 desc = strchr(id, ':');
1612
1613 if (desc && (!seed || desc < seed)) {
1614 /*
1615 * We have a colon separating parameters from game
1616 * description. So `par' now points to the parameters
1617 * string, and `desc' to the description string.
1618 */
1619 par = snewn(desc-id + 1, char);
1620 strncpy(par, id, desc-id);
1621 par[desc-id] = '\0';
1622 desc++;
1623 seed = NULL;
1624 } else if (seed && (!desc || seed < desc)) {
1625 /*
1626 * We have a hash separating parameters from random seed.
1627 * So `par' now points to the parameters string, and `seed'
1628 * to the seed string.
1629 */
1630 par = snewn(seed-id + 1, char);
1631 strncpy(par, id, seed-id);
1632 par[seed-id] = '\0';
1633 seed++;
1634 desc = NULL;
1635 } else {
1636 /*
1637 * We only have one string. Depending on `defmode', we take
1638 * it to be either parameters, seed or description.
1639 */
1640 if (defmode == DEF_SEED) {
1641 seed = id;
1642 par = NULL;
1643 desc = NULL;
1644 } else if (defmode == DEF_DESC) {
1645 desc = id;
1646 par = NULL;
1647 seed = NULL;
1648 } else {
1649 par = dupstr(id);
1650 seed = desc = NULL;
1651 }
1652 }
1653
1654 /*
1655 * We must be reasonably careful here not to modify anything in
1656 * `me' until we have finished validating things. This function
1657 * must either return an error and do nothing to the midend, or
1658 * return success and do everything; nothing in between is
1659 * acceptable.
1660 */
1661 newcurparams = newparams = oldparams1 = oldparams2 = NULL;
1662
1663 if (par) {
1664 /*
1665 * The params string may underspecify the game parameters, so
1666 * we must first initialise newcurparams with a full set of
1667 * params from somewhere else before we decode_params the
1668 * input string over the top.
1669 *
1670 * But which set? It depends on what other data we have.
1671 *
1672 * If we've been given a _descriptive_ game id, then that may
1673 * well underspecify by design, e.g. Solo game descriptions
1674 * often start just '3x3:' without specifying one of Solo's
1675 * difficulty settings, because it isn't necessary once a game
1676 * has been generated (and you might not even know it, if
1677 * you're manually transcribing a game description). In that
1678 * situation, I've always felt that the best thing to set the
1679 * difficulty to (for use if the user hits 'New Game' after
1680 * pasting in that game id) is whatever it was previously set
1681 * to. That is, we use whatever is already in me->params as
1682 * the basis for our decoding of this input string.
1683 *
1684 * A random-seed based game id, however, should use the real,
1685 * built-in default params, and not even check the
1686 * <game>_DEFAULT environment setting, because when people
1687 * paste each other random seeds - whether it's two users
1688 * arranging to generate the same game at the same time to
1689 * race solving them, or a user sending a bug report upstream
1690 * - the whole point is for the random game id to always be
1691 * interpreted the same way, even if it does underspecify.
1692 *
1693 * A parameter string typed in on its own, with no seed _or_
1694 * description, gets treated the same way as a random seed,
1695 * because again I think the most likely reason for doing that
1696 * is to have a portable representation of a set of params.
1697 */
1698 if (desc) {
1699 newcurparams = me->ourgame->dup_params(me->params);
1700 } else {
1701 newcurparams = me->ourgame->default_params();
1702 }
1703 me->ourgame->decode_params(newcurparams, par);
1704 error = me->ourgame->validate_params(newcurparams, desc == NULL);
1705 if (error) {
1706 me->ourgame->free_params(newcurparams);
1707 return error;
1708 }
1709 oldparams1 = me->curparams;
1710
1711 /*
1712 * Now filter only the persistent parts of this state into
1713 * the long-term params structure, unless we've _only_
1714 * received a params string in which case the whole lot is
1715 * persistent.
1716 */
1717 oldparams2 = me->params;
1718 if (seed || desc) {
1719 char *tmpstr;
1720
1721 newparams = me->ourgame->dup_params(me->params);
1722
1723 tmpstr = me->ourgame->encode_params(newcurparams, false);
1724 me->ourgame->decode_params(newparams, tmpstr);
1725
1726 sfree(tmpstr);
1727 } else {
1728 newparams = me->ourgame->dup_params(newcurparams);
1729 }
1730 free_params = true;
1731 } else {
1732 newcurparams = me->curparams;
1733 newparams = me->params;
1734 free_params = false;
1735 }
1736
1737 if (desc) {
1738 error = me->ourgame->validate_desc(newparams, desc);
1739 if (error) {
1740 if (free_params) {
1741 if (newcurparams)
1742 me->ourgame->free_params(newcurparams);
1743 if (newparams)
1744 me->ourgame->free_params(newparams);
1745 }
1746 return error;
1747 }
1748 }
1749
1750 /*
1751 * Now we've got past all possible error points. Update the
1752 * midend itself.
1753 */
1754 me->params = newparams;
1755 me->curparams = newcurparams;
1756 if (oldparams1)
1757 me->ourgame->free_params(oldparams1);
1758 if (oldparams2)
1759 me->ourgame->free_params(oldparams2);
1760
1761 sfree(me->desc);
1762 sfree(me->privdesc);
1763 me->desc = me->privdesc = NULL;
1764 sfree(me->seedstr);
1765 me->seedstr = NULL;
1766
1767 if (desc) {
1768 me->desc = dupstr(desc);
1769 me->genmode = GOT_DESC;
1770 sfree(me->aux_info);
1771 me->aux_info = NULL;
1772 }
1773
1774 if (seed) {
1775 me->seedstr = dupstr(seed);
1776 me->genmode = GOT_SEED;
1777 }
1778
1779 sfree(par);
1780
1781 me->newgame_can_store_undo = false;
1782
1783 return NULL;
1784 }
1785
midend_game_id(midend * me,const char * id)1786 const char *midend_game_id(midend *me, const char *id)
1787 {
1788 return midend_game_id_int(me, id, DEF_PARAMS);
1789 }
1790
midend_get_game_id(midend * me)1791 char *midend_get_game_id(midend *me)
1792 {
1793 char *parstr, *ret;
1794
1795 parstr = me->ourgame->encode_params(me->curparams, false);
1796 assert(parstr);
1797 assert(me->desc);
1798 ret = snewn(strlen(parstr) + strlen(me->desc) + 2, char);
1799 sprintf(ret, "%s:%s", parstr, me->desc);
1800 sfree(parstr);
1801 return ret;
1802 }
1803
midend_get_random_seed(midend * me)1804 char *midend_get_random_seed(midend *me)
1805 {
1806 char *parstr, *ret;
1807
1808 if (!me->seedstr)
1809 return NULL;
1810
1811 parstr = me->ourgame->encode_params(me->curparams, true);
1812 assert(parstr);
1813 ret = snewn(strlen(parstr) + strlen(me->seedstr) + 2, char);
1814 sprintf(ret, "%s#%s", parstr, me->seedstr);
1815 sfree(parstr);
1816 return ret;
1817 }
1818
midend_set_config(midend * me,int which,config_item * cfg)1819 const char *midend_set_config(midend *me, int which, config_item *cfg)
1820 {
1821 const char *error;
1822 game_params *params;
1823
1824 switch (which) {
1825 case CFG_SETTINGS:
1826 params = me->ourgame->custom_params(cfg);
1827 error = me->ourgame->validate_params(params, true);
1828
1829 if (error) {
1830 me->ourgame->free_params(params);
1831 return error;
1832 }
1833
1834 me->ourgame->free_params(me->params);
1835 me->params = params;
1836 break;
1837
1838 case CFG_SEED:
1839 case CFG_DESC:
1840 error = midend_game_id_int(me, cfg[0].u.string.sval,
1841 (which == CFG_SEED ? DEF_SEED : DEF_DESC));
1842 if (error)
1843 return error;
1844 break;
1845 }
1846
1847 return NULL;
1848 }
1849
midend_can_format_as_text_now(midend * me)1850 bool midend_can_format_as_text_now(midend *me)
1851 {
1852 if (me->ourgame->can_format_as_text_ever)
1853 return me->ourgame->can_format_as_text_now(me->params);
1854 else
1855 return false;
1856 }
1857
midend_text_format(midend * me)1858 char *midend_text_format(midend *me)
1859 {
1860 if (me->ourgame->can_format_as_text_ever && me->statepos > 0 &&
1861 me->ourgame->can_format_as_text_now(me->params))
1862 return me->ourgame->text_format(me->states[me->statepos-1].state);
1863 else
1864 return NULL;
1865 }
1866
midend_solve(midend * me)1867 const char *midend_solve(midend *me)
1868 {
1869 game_state *s;
1870 const char *msg;
1871 char *movestr;
1872
1873 if (!me->ourgame->can_solve)
1874 return "This game does not support the Solve operation";
1875
1876 if (me->statepos < 1)
1877 return "No game set up to solve"; /* _shouldn't_ happen! */
1878
1879 msg = NULL;
1880 movestr = me->ourgame->solve(me->states[0].state,
1881 me->states[me->statepos-1].state,
1882 me->aux_info, &msg);
1883 assert(movestr != UI_UPDATE);
1884 if (!movestr) {
1885 if (!msg)
1886 msg = "Solve operation failed"; /* _shouldn't_ happen, but can */
1887 return msg;
1888 }
1889 s = me->ourgame->execute_move(me->states[me->statepos-1].state, movestr);
1890 assert(s);
1891
1892 /*
1893 * Now enter the solved state as the next move.
1894 */
1895 midend_stop_anim(me);
1896 midend_purge_states(me);
1897 ensure(me);
1898 me->states[me->nstates].state = s;
1899 me->states[me->nstates].movestr = movestr;
1900 me->states[me->nstates].movetype = SOLVE;
1901 me->statepos = ++me->nstates;
1902 if (me->ui)
1903 me->ourgame->changed_state(me->ui,
1904 me->states[me->statepos-2].state,
1905 me->states[me->statepos-1].state);
1906 me->dir = +1;
1907 if (me->ourgame->flags & SOLVE_ANIMATES) {
1908 me->oldstate = me->ourgame->dup_game(me->states[me->statepos-2].state);
1909 me->anim_time =
1910 me->ourgame->anim_length(me->states[me->statepos-2].state,
1911 me->states[me->statepos-1].state,
1912 +1, me->ui);
1913 me->anim_pos = 0.0;
1914 } else {
1915 me->anim_time = 0.0;
1916 midend_finish_move(me);
1917 }
1918 if (me->drawing)
1919 midend_redraw(me);
1920 midend_set_timer(me);
1921 return NULL;
1922 }
1923
midend_status(midend * me)1924 int midend_status(midend *me)
1925 {
1926 /*
1927 * We should probably never be called when the state stack has no
1928 * states on it at all - ideally, midends should never be left in
1929 * that state for long enough to get put down and forgotten about.
1930 * But if we are, I think we return _true_ - pedantically speaking
1931 * a midend in that state is 'vacuously solved', and more
1932 * practically, a user whose midend has been left in that state
1933 * probably _does_ want the 'new game' option to be prominent.
1934 */
1935 if (me->statepos == 0)
1936 return +1;
1937
1938 return me->ourgame->status(me->states[me->statepos-1].state);
1939 }
1940
midend_rewrite_statusbar(midend * me,const char * text)1941 char *midend_rewrite_statusbar(midend *me, const char *text)
1942 {
1943 /*
1944 * An important special case is that we are occasionally called
1945 * with our own laststatus, to update the timer.
1946 */
1947 if (me->laststatus != text) {
1948 sfree(me->laststatus);
1949 me->laststatus = dupstr(text);
1950 }
1951
1952 if (me->ourgame->is_timed) {
1953 char timebuf[100], *ret;
1954 int min, sec;
1955
1956 sec = (int)me->elapsed;
1957 min = sec / 60;
1958 sec %= 60;
1959 sprintf(timebuf, "[%d:%02d] ", min, sec);
1960
1961 ret = snewn(strlen(timebuf) + strlen(text) + 1, char);
1962 strcpy(ret, timebuf);
1963 strcat(ret, text);
1964 return ret;
1965
1966 } else {
1967 return dupstr(text);
1968 }
1969 }
1970
1971 #define SERIALISE_MAGIC "Simon Tatham's Portable Puzzle Collection"
1972 #define SERIALISE_VERSION "1"
1973
midend_serialise(midend * me,void (* write)(void * ctx,const void * buf,int len),void * wctx)1974 void midend_serialise(midend *me,
1975 void (*write)(void *ctx, const void *buf, int len),
1976 void *wctx)
1977 {
1978 int i;
1979
1980 /*
1981 * Each line of the save file contains three components. First
1982 * exactly 8 characters of header word indicating what type of
1983 * data is contained on the line; then a colon followed by a
1984 * decimal integer giving the length of the main string on the
1985 * line; then a colon followed by the string itself (exactly as
1986 * many bytes as previously specified, no matter what they
1987 * contain). Then a newline (of reasonably flexible form).
1988 */
1989 #define wr(h,s) do { \
1990 char hbuf[80]; \
1991 const char *str = (s); \
1992 char lbuf[9]; \
1993 copy_left_justified(lbuf, sizeof(lbuf), h); \
1994 sprintf(hbuf, "%s:%d:", lbuf, (int)strlen(str)); \
1995 write(wctx, hbuf, strlen(hbuf)); \
1996 write(wctx, str, strlen(str)); \
1997 write(wctx, "\n", 1); \
1998 } while (0)
1999
2000 /*
2001 * Magic string identifying the file, and version number of the
2002 * file format.
2003 */
2004 wr("SAVEFILE", SERIALISE_MAGIC);
2005 wr("VERSION", SERIALISE_VERSION);
2006
2007 /*
2008 * The game name. (Copied locally to avoid const annoyance.)
2009 */
2010 {
2011 char *s = dupstr(me->ourgame->name);
2012 wr("GAME", s);
2013 sfree(s);
2014 }
2015
2016 /*
2017 * The current long-term parameters structure, in full.
2018 */
2019 if (me->params) {
2020 char *s = me->ourgame->encode_params(me->params, true);
2021 wr("PARAMS", s);
2022 sfree(s);
2023 }
2024
2025 /*
2026 * The current short-term parameters structure, in full.
2027 */
2028 if (me->curparams) {
2029 char *s = me->ourgame->encode_params(me->curparams, true);
2030 wr("CPARAMS", s);
2031 sfree(s);
2032 }
2033
2034 /*
2035 * The current game description, the privdesc, and the random seed.
2036 */
2037 if (me->seedstr)
2038 wr("SEED", me->seedstr);
2039 if (me->desc)
2040 wr("DESC", me->desc);
2041 if (me->privdesc)
2042 wr("PRIVDESC", me->privdesc);
2043
2044 /*
2045 * The game's aux_info. We obfuscate this to prevent spoilers
2046 * (people are likely to run `head' or similar on a saved game
2047 * file simply to find out what it is, and don't necessarily
2048 * want to be told the answer to the puzzle!)
2049 */
2050 if (me->aux_info) {
2051 unsigned char *s1;
2052 char *s2;
2053 int len;
2054
2055 len = strlen(me->aux_info);
2056 s1 = snewn(len, unsigned char);
2057 memcpy(s1, me->aux_info, len);
2058 obfuscate_bitmap(s1, len*8, false);
2059 s2 = bin2hex(s1, len);
2060
2061 wr("AUXINFO", s2);
2062
2063 sfree(s2);
2064 sfree(s1);
2065 }
2066
2067 /*
2068 * Any required serialisation of the game_ui.
2069 */
2070 if (me->ui) {
2071 char *s = me->ourgame->encode_ui(me->ui);
2072 if (s) {
2073 wr("UI", s);
2074 sfree(s);
2075 }
2076 }
2077
2078 /*
2079 * The game time, if it's a timed game.
2080 */
2081 if (me->ourgame->is_timed) {
2082 char buf[80];
2083 sprintf(buf, "%g", me->elapsed);
2084 wr("TIME", buf);
2085 }
2086
2087 /*
2088 * The length of, and position in, the states list.
2089 */
2090 {
2091 char buf[80];
2092 sprintf(buf, "%d", me->nstates);
2093 wr("NSTATES", buf);
2094 sprintf(buf, "%d", me->statepos);
2095 wr("STATEPOS", buf);
2096 }
2097
2098 /*
2099 * For each state after the initial one (which we know is
2100 * constructed from either privdesc or desc), enough
2101 * information for execute_move() to reconstruct it from the
2102 * previous one.
2103 */
2104 for (i = 1; i < me->nstates; i++) {
2105 assert(me->states[i].movetype != NEWGAME); /* only state 0 */
2106 switch (me->states[i].movetype) {
2107 case MOVE:
2108 wr("MOVE", me->states[i].movestr);
2109 break;
2110 case SOLVE:
2111 wr("SOLVE", me->states[i].movestr);
2112 break;
2113 case RESTART:
2114 wr("RESTART", me->states[i].movestr);
2115 break;
2116 }
2117 }
2118
2119 #undef wr
2120 }
2121
2122 /*
2123 * Internal version of midend_deserialise, taking an extra check
2124 * function to be called just before beginning to install things in
2125 * the midend.
2126 *
2127 * Like midend_deserialise proper, this function returns NULL on
2128 * success, or an error message.
2129 */
midend_deserialise_internal(midend * me,bool (* read)(void * ctx,void * buf,int len),void * rctx,const char * (* check)(void * ctx,midend *,const struct deserialise_data *),void * cctx)2130 static const char *midend_deserialise_internal(
2131 midend *me, bool (*read)(void *ctx, void *buf, int len), void *rctx,
2132 const char *(*check)(void *ctx, midend *, const struct deserialise_data *),
2133 void *cctx)
2134 {
2135 struct deserialise_data data;
2136 int gotstates = 0;
2137 bool started = false;
2138 int i;
2139
2140 char *val = NULL;
2141 /* Initially all errors give the same report */
2142 const char *ret = "Data does not appear to be a saved game file";
2143
2144 data.seed = data.parstr = data.desc = data.privdesc = NULL;
2145 data.auxinfo = data.uistr = data.cparstr = NULL;
2146 data.elapsed = 0.0F;
2147 data.params = data.cparams = NULL;
2148 data.ui = NULL;
2149 data.states = NULL;
2150 data.nstates = 0;
2151 data.statepos = -1;
2152
2153 /*
2154 * Loop round and round reading one key/value pair at a time
2155 * from the serialised stream, until we have enough game states
2156 * to finish.
2157 */
2158 while (data.nstates <= 0 || data.statepos < 0 ||
2159 gotstates < data.nstates-1) {
2160 char key[9], c;
2161 int len;
2162
2163 do {
2164 if (!read(rctx, key, 1)) {
2165 /* unexpected EOF */
2166 goto cleanup;
2167 }
2168 } while (key[0] == '\r' || key[0] == '\n');
2169
2170 if (!read(rctx, key+1, 8)) {
2171 /* unexpected EOF */
2172 goto cleanup;
2173 }
2174
2175 if (key[8] != ':') {
2176 if (started)
2177 ret = "Data was incorrectly formatted for a saved game file";
2178 goto cleanup;
2179 }
2180 len = strcspn(key, ": ");
2181 assert(len <= 8);
2182 key[len] = '\0';
2183
2184 len = 0;
2185 while (1) {
2186 if (!read(rctx, &c, 1)) {
2187 /* unexpected EOF */
2188 goto cleanup;
2189 }
2190
2191 if (c == ':') {
2192 break;
2193 } else if (c >= '0' && c <= '9') {
2194 len = (len * 10) + (c - '0');
2195 } else {
2196 if (started)
2197 ret = "Data was incorrectly formatted for a"
2198 " saved game file";
2199 goto cleanup;
2200 }
2201 }
2202
2203 val = snewn(len+1, char);
2204 if (!read(rctx, val, len)) {
2205 if (started)
2206 goto cleanup;
2207 }
2208 val[len] = '\0';
2209
2210 if (!started) {
2211 if (strcmp(key, "SAVEFILE") || strcmp(val, SERIALISE_MAGIC)) {
2212 /* ret already has the right message in it */
2213 goto cleanup;
2214 }
2215 /* Now most errors are this one, unless otherwise specified */
2216 ret = "Saved data ended unexpectedly";
2217 started = true;
2218 } else {
2219 if (!strcmp(key, "VERSION")) {
2220 if (strcmp(val, SERIALISE_VERSION)) {
2221 ret = "Cannot handle this version of the saved game"
2222 " file format";
2223 goto cleanup;
2224 }
2225 } else if (!strcmp(key, "GAME")) {
2226 if (strcmp(val, me->ourgame->name)) {
2227 ret = "Save file is from a different game";
2228 goto cleanup;
2229 }
2230 } else if (!strcmp(key, "PARAMS")) {
2231 sfree(data.parstr);
2232 data.parstr = val;
2233 val = NULL;
2234 } else if (!strcmp(key, "CPARAMS")) {
2235 sfree(data.cparstr);
2236 data.cparstr = val;
2237 val = NULL;
2238 } else if (!strcmp(key, "SEED")) {
2239 sfree(data.seed);
2240 data.seed = val;
2241 val = NULL;
2242 } else if (!strcmp(key, "DESC")) {
2243 sfree(data.desc);
2244 data.desc = val;
2245 val = NULL;
2246 } else if (!strcmp(key, "PRIVDESC")) {
2247 sfree(data.privdesc);
2248 data.privdesc = val;
2249 val = NULL;
2250 } else if (!strcmp(key, "AUXINFO")) {
2251 unsigned char *tmp;
2252 int len = strlen(val) / 2; /* length in bytes */
2253 tmp = hex2bin(val, len);
2254 obfuscate_bitmap(tmp, len*8, true);
2255
2256 sfree(data.auxinfo);
2257 data.auxinfo = snewn(len + 1, char);
2258 memcpy(data.auxinfo, tmp, len);
2259 data.auxinfo[len] = '\0';
2260 sfree(tmp);
2261 } else if (!strcmp(key, "UI")) {
2262 sfree(data.uistr);
2263 data.uistr = val;
2264 val = NULL;
2265 } else if (!strcmp(key, "TIME")) {
2266 data.elapsed = (float)atof(val);
2267 } else if (!strcmp(key, "NSTATES")) {
2268 data.nstates = atoi(val);
2269 if (data.nstates <= 0) {
2270 ret = "Number of states in save file was negative";
2271 goto cleanup;
2272 }
2273 if (data.states) {
2274 ret = "Two state counts provided in save file";
2275 goto cleanup;
2276 }
2277 data.states = snewn(data.nstates, struct midend_state_entry);
2278 for (i = 0; i < data.nstates; i++) {
2279 data.states[i].state = NULL;
2280 data.states[i].movestr = NULL;
2281 data.states[i].movetype = NEWGAME;
2282 }
2283 } else if (!strcmp(key, "STATEPOS")) {
2284 data.statepos = atoi(val);
2285 } else if (!strcmp(key, "MOVE")) {
2286 gotstates++;
2287 data.states[gotstates].movetype = MOVE;
2288 data.states[gotstates].movestr = val;
2289 val = NULL;
2290 } else if (!strcmp(key, "SOLVE")) {
2291 gotstates++;
2292 data.states[gotstates].movetype = SOLVE;
2293 data.states[gotstates].movestr = val;
2294 val = NULL;
2295 } else if (!strcmp(key, "RESTART")) {
2296 gotstates++;
2297 data.states[gotstates].movetype = RESTART;
2298 data.states[gotstates].movestr = val;
2299 val = NULL;
2300 }
2301 }
2302
2303 sfree(val);
2304 val = NULL;
2305 }
2306
2307 data.params = me->ourgame->default_params();
2308 me->ourgame->decode_params(data.params, data.parstr);
2309 if (me->ourgame->validate_params(data.params, true)) {
2310 ret = "Long-term parameters in save file are invalid";
2311 goto cleanup;
2312 }
2313 data.cparams = me->ourgame->default_params();
2314 me->ourgame->decode_params(data.cparams, data.cparstr);
2315 if (me->ourgame->validate_params(data.cparams, false)) {
2316 ret = "Short-term parameters in save file are invalid";
2317 goto cleanup;
2318 }
2319 if (data.seed && me->ourgame->validate_params(data.cparams, true)) {
2320 /*
2321 * The seed's no use with this version, but we can perfectly
2322 * well use the rest of the data.
2323 */
2324 sfree(data.seed);
2325 data.seed = NULL;
2326 }
2327 if (!data.desc) {
2328 ret = "Game description in save file is missing";
2329 goto cleanup;
2330 } else if (me->ourgame->validate_desc(data.cparams, data.desc)) {
2331 ret = "Game description in save file is invalid";
2332 goto cleanup;
2333 }
2334 if (data.privdesc &&
2335 me->ourgame->validate_desc(data.cparams, data.privdesc)) {
2336 ret = "Game private description in save file is invalid";
2337 goto cleanup;
2338 }
2339 if (data.statepos < 0 || data.statepos >= data.nstates) {
2340 ret = "Game position in save file is out of range";
2341 }
2342
2343 data.states[0].state = me->ourgame->new_game(
2344 me, data.cparams, data.privdesc ? data.privdesc : data.desc);
2345
2346 for (i = 1; i < data.nstates; i++) {
2347 assert(data.states[i].movetype != NEWGAME);
2348 switch (data.states[i].movetype) {
2349 case MOVE:
2350 case SOLVE:
2351 data.states[i].state = me->ourgame->execute_move(
2352 data.states[i-1].state, data.states[i].movestr);
2353 if (data.states[i].state == NULL) {
2354 ret = "Save file contained an invalid move";
2355 goto cleanup;
2356 }
2357 break;
2358 case RESTART:
2359 if (me->ourgame->validate_desc(
2360 data.cparams, data.states[i].movestr)) {
2361 ret = "Save file contained an invalid restart move";
2362 goto cleanup;
2363 }
2364 data.states[i].state = me->ourgame->new_game(
2365 me, data.cparams, data.states[i].movestr);
2366 break;
2367 }
2368 }
2369
2370 data.ui = me->ourgame->new_ui(data.states[0].state);
2371 me->ourgame->decode_ui(data.ui, data.uistr);
2372
2373 /*
2374 * Run the externally provided check function, and abort if it
2375 * returns an error message.
2376 */
2377 if (check && (ret = check(cctx, me, &data)) != NULL)
2378 goto cleanup; /* error message is already in ret */
2379
2380 /*
2381 * Now we've run out of possible error conditions, so we're
2382 * ready to start overwriting the real data in the current
2383 * midend. We'll do this by swapping things with the local
2384 * variables, so that the same cleanup code will free the old
2385 * stuff.
2386 */
2387 {
2388 char *tmp;
2389
2390 tmp = me->desc;
2391 me->desc = data.desc;
2392 data.desc = tmp;
2393
2394 tmp = me->privdesc;
2395 me->privdesc = data.privdesc;
2396 data.privdesc = tmp;
2397
2398 tmp = me->seedstr;
2399 me->seedstr = data.seed;
2400 data.seed = tmp;
2401
2402 tmp = me->aux_info;
2403 me->aux_info = data.auxinfo;
2404 data.auxinfo = tmp;
2405 }
2406
2407 me->genmode = GOT_NOTHING;
2408
2409 me->statesize = data.nstates;
2410 data.nstates = me->nstates;
2411 me->nstates = me->statesize;
2412 {
2413 struct midend_state_entry *tmp;
2414 tmp = me->states;
2415 me->states = data.states;
2416 data.states = tmp;
2417 }
2418 me->statepos = data.statepos;
2419
2420 /*
2421 * Don't save the "new game undo/redo" state. So "new game" twice or
2422 * (in some environments) switching away and back, will make a
2423 * "new game" irreversible. Maybe in the future we will have a
2424 * more sophisticated way to decide when to discard the previous
2425 * game state.
2426 */
2427 me->newgame_undo.len = 0;
2428 me->newgame_redo.len = 0;
2429
2430 {
2431 game_params *tmp;
2432
2433 tmp = me->params;
2434 me->params = data.params;
2435 data.params = tmp;
2436
2437 tmp = me->curparams;
2438 me->curparams = data.cparams;
2439 data.cparams = tmp;
2440 }
2441
2442 me->oldstate = NULL;
2443 me->anim_time = me->anim_pos = me->flash_time = me->flash_pos = 0.0F;
2444 me->dir = 0;
2445
2446 {
2447 game_ui *tmp;
2448
2449 tmp = me->ui;
2450 me->ui = data.ui;
2451 data.ui = tmp;
2452 }
2453
2454 me->elapsed = data.elapsed;
2455 me->pressed_mouse_button = 0;
2456
2457 midend_set_timer(me);
2458
2459 if (me->drawstate)
2460 me->ourgame->free_drawstate(me->drawing, me->drawstate);
2461 me->drawstate =
2462 me->ourgame->new_drawstate(me->drawing,
2463 me->states[me->statepos-1].state);
2464 me->first_draw = true;
2465 midend_size_new_drawstate(me);
2466 if (me->game_id_change_notify_function)
2467 me->game_id_change_notify_function(me->game_id_change_notify_ctx);
2468
2469 ret = NULL; /* success! */
2470
2471 cleanup:
2472 sfree(val);
2473 sfree(data.seed);
2474 sfree(data.parstr);
2475 sfree(data.cparstr);
2476 sfree(data.desc);
2477 sfree(data.privdesc);
2478 sfree(data.auxinfo);
2479 sfree(data.uistr);
2480 if (data.params)
2481 me->ourgame->free_params(data.params);
2482 if (data.cparams)
2483 me->ourgame->free_params(data.cparams);
2484 if (data.ui)
2485 me->ourgame->free_ui(data.ui);
2486 if (data.states) {
2487 int i;
2488
2489 for (i = 0; i < data.nstates; i++) {
2490 if (data.states[i].state)
2491 me->ourgame->free_game(data.states[i].state);
2492 sfree(data.states[i].movestr);
2493 }
2494 sfree(data.states);
2495 }
2496
2497 return ret;
2498 }
2499
midend_deserialise(midend * me,bool (* read)(void * ctx,void * buf,int len),void * rctx)2500 const char *midend_deserialise(
2501 midend *me, bool (*read)(void *ctx, void *buf, int len), void *rctx)
2502 {
2503 return midend_deserialise_internal(me, read, rctx, NULL, NULL);
2504 }
2505
2506 /*
2507 * This function examines a saved game file just far enough to
2508 * determine which game type it contains. It returns NULL on success
2509 * and the game name string in 'name' (which will be dynamically
2510 * allocated and should be caller-freed), or an error message on
2511 * failure.
2512 */
identify_game(char ** name,bool (* read)(void * ctx,void * buf,int len),void * rctx)2513 const char *identify_game(char **name,
2514 bool (*read)(void *ctx, void *buf, int len),
2515 void *rctx)
2516 {
2517 int nstates = 0, statepos = -1, gotstates = 0;
2518 bool started = false;
2519
2520 char *val = NULL;
2521 /* Initially all errors give the same report */
2522 const char *ret = "Data does not appear to be a saved game file";
2523
2524 *name = NULL;
2525
2526 /*
2527 * Loop round and round reading one key/value pair at a time from
2528 * the serialised stream, until we've found the game name.
2529 */
2530 while (nstates <= 0 || statepos < 0 || gotstates < nstates-1) {
2531 char key[9], c;
2532 int len;
2533
2534 do {
2535 if (!read(rctx, key, 1)) {
2536 /* unexpected EOF */
2537 goto cleanup;
2538 }
2539 } while (key[0] == '\r' || key[0] == '\n');
2540
2541 if (!read(rctx, key+1, 8)) {
2542 /* unexpected EOF */
2543 goto cleanup;
2544 }
2545
2546 if (key[8] != ':') {
2547 if (started)
2548 ret = "Data was incorrectly formatted for a saved game file";
2549 goto cleanup;
2550 }
2551 len = strcspn(key, ": ");
2552 assert(len <= 8);
2553 key[len] = '\0';
2554
2555 len = 0;
2556 while (1) {
2557 if (!read(rctx, &c, 1)) {
2558 /* unexpected EOF */
2559 goto cleanup;
2560 }
2561
2562 if (c == ':') {
2563 break;
2564 } else if (c >= '0' && c <= '9') {
2565 len = (len * 10) + (c - '0');
2566 } else {
2567 if (started)
2568 ret = "Data was incorrectly formatted for a"
2569 " saved game file";
2570 goto cleanup;
2571 }
2572 }
2573
2574 val = snewn(len+1, char);
2575 if (!read(rctx, val, len)) {
2576 if (started)
2577 goto cleanup;
2578 }
2579 val[len] = '\0';
2580
2581 if (!started) {
2582 if (strcmp(key, "SAVEFILE") || strcmp(val, SERIALISE_MAGIC)) {
2583 /* ret already has the right message in it */
2584 goto cleanup;
2585 }
2586 /* Now most errors are this one, unless otherwise specified */
2587 ret = "Saved data ended unexpectedly";
2588 started = true;
2589 } else {
2590 if (!strcmp(key, "VERSION")) {
2591 if (strcmp(val, SERIALISE_VERSION)) {
2592 ret = "Cannot handle this version of the saved game"
2593 " file format";
2594 goto cleanup;
2595 }
2596 } else if (!strcmp(key, "GAME")) {
2597 *name = dupstr(val);
2598 ret = NULL;
2599 goto cleanup;
2600 }
2601 }
2602
2603 sfree(val);
2604 val = NULL;
2605 }
2606
2607 cleanup:
2608 sfree(val);
2609 return ret;
2610 }
2611
midend_print_puzzle(midend * me,document * doc,bool with_soln)2612 const char *midend_print_puzzle(midend *me, document *doc, bool with_soln)
2613 {
2614 game_state *soln = NULL;
2615
2616 if (me->statepos < 1)
2617 return "No game set up to print";/* _shouldn't_ happen! */
2618
2619 if (with_soln) {
2620 const char *msg;
2621 char *movestr;
2622
2623 if (!me->ourgame->can_solve)
2624 return "This game does not support the Solve operation";
2625
2626 msg = "Solve operation failed";/* game _should_ overwrite on error */
2627 movestr = me->ourgame->solve(me->states[0].state,
2628 me->states[me->statepos-1].state,
2629 me->aux_info, &msg);
2630 if (!movestr)
2631 return msg;
2632 soln = me->ourgame->execute_move(me->states[me->statepos-1].state,
2633 movestr);
2634 assert(soln);
2635
2636 sfree(movestr);
2637 } else
2638 soln = NULL;
2639
2640 /*
2641 * This call passes over ownership of the two game_states and
2642 * the game_params. Hence we duplicate the ones we want to
2643 * keep, and we don't have to bother freeing soln if it was
2644 * non-NULL.
2645 */
2646 document_add_puzzle(doc, me->ourgame,
2647 me->ourgame->dup_params(me->curparams),
2648 me->ourgame->dup_game(me->states[0].state), soln);
2649
2650 return NULL;
2651 }
2652