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