chiark / gitweb /
More serialisation changes: the game_aux_info structure has now been
[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 struct midend_state_entry {
19     game_state *state;
20     int special;                       /* created by solve or restart */
21 };
22
23 struct midend_data {
24     frontend *frontend;
25     random_state *random;
26     const game *ourgame;
27
28     /*
29      * `desc' and `privdesc' deserve a comment.
30      * 
31      * `desc' is the game description as presented to the user when
32      * they ask for Game -> Specific. `privdesc', if non-NULL, is a
33      * different game description used to reconstruct the initial
34      * game_state when de-serialising. If privdesc is NULL, `desc'
35      * is used for both.
36      * 
37      * For almost all games, `privdesc' is NULL and never used. The
38      * exception (as usual) is Mines: the initial game state has no
39      * squares open at all, but after the first click `desc' is
40      * rewritten to describe a game state with an initial click and
41      * thus a bunch of squares open. If we used that desc to
42      * serialise and deserialise, then the initial game state after
43      * deserialisation would look unlike the initial game state
44      * beforehand, and worse still execute_move() might fail on the
45      * attempted first click. So `privdesc' is also used in this
46      * case, to provide a game description describing the same
47      * fixed mine layout _but_ no initial click. (These game IDs
48      * may also be typed directly into Mines if you like.)
49      */
50     char *desc, *privdesc, *seedstr;
51     char *aux_info;
52     enum { GOT_SEED, GOT_DESC, GOT_NOTHING } genmode;
53     int nstates, statesize, statepos;
54
55     game_params **presets;
56     char **preset_names;
57     int npresets, presetsize;
58
59     game_params *params, *curparams;
60     struct midend_state_entry *states;
61     game_drawstate *drawstate;
62     game_state *oldstate;
63     game_ui *ui;
64     float anim_time, anim_pos;
65     float flash_time, flash_pos;
66     int dir;
67
68     int timing;
69     float elapsed;
70     char *laststatus;
71
72     int pressed_mouse_button;
73
74     int winwidth, winheight;
75 };
76
77 #define ensure(me) do { \
78     if ((me)->nstates >= (me)->statesize) { \
79         (me)->statesize = (me)->nstates + 128; \
80         (me)->states = sresize((me)->states, (me)->statesize, \
81                                struct midend_state_entry); \
82     } \
83 } while (0)
84
85 midend_data *midend_new(frontend *fe, const game *ourgame)
86 {
87     midend_data *me = snew(midend_data);
88     void *randseed;
89     int randseedsize;
90
91     get_random_seed(&randseed, &randseedsize);
92
93     me->frontend = fe;
94     me->ourgame = ourgame;
95     me->random = random_init(randseed, randseedsize);
96     me->nstates = me->statesize = me->statepos = 0;
97     me->states = NULL;
98     me->params = ourgame->default_params();
99     me->curparams = NULL;
100     me->desc = me->privdesc = NULL;
101     me->seedstr = NULL;
102     me->aux_info = NULL;
103     me->genmode = GOT_NOTHING;
104     me->drawstate = NULL;
105     me->oldstate = NULL;
106     me->presets = NULL;
107     me->preset_names = NULL;
108     me->npresets = me->presetsize = 0;
109     me->anim_time = me->anim_pos = 0.0F;
110     me->flash_time = me->flash_pos = 0.0F;
111     me->dir = 0;
112     me->ui = NULL;
113     me->pressed_mouse_button = 0;
114     me->laststatus = NULL;
115     me->timing = FALSE;
116     me->elapsed = 0.0F;
117     me->winwidth = me->winheight = 0;
118
119     sfree(randseed);
120
121     return me;
122 }
123
124 static void midend_free_game(midend_data *me)
125 {
126     while (me->nstates > 0)
127         me->ourgame->free_game(me->states[--me->nstates].state);
128
129     if (me->drawstate)
130         me->ourgame->free_drawstate(me->drawstate);
131 }
132
133 void midend_free(midend_data *me)
134 {
135     int i;
136
137     midend_free_game(me);
138
139     random_free(me->random);
140     sfree(me->states);
141     sfree(me->desc);
142     sfree(me->seedstr);
143     sfree(me->aux_info);
144     me->ourgame->free_params(me->params);
145     if (me->npresets) {
146         for (i = 0; i < me->npresets; i++) {
147             sfree(me->presets[i]);
148             sfree(me->preset_names[i]);
149         }
150         sfree(me->presets);
151         sfree(me->preset_names);
152     }
153     if (me->ui)
154         me->ourgame->free_ui(me->ui);
155     if (me->curparams)
156         me->ourgame->free_params(me->curparams);
157     sfree(me->laststatus);
158     sfree(me);
159 }
160
161 void midend_size(midend_data *me, int *x, int *y, int expand)
162 {
163     me->ourgame->size(me->params, me->drawstate, x, y, expand);
164     me->winwidth = *x;
165     me->winheight = *y;
166 }
167
168 void midend_set_params(midend_data *me, game_params *params)
169 {
170     me->ourgame->free_params(me->params);
171     me->params = me->ourgame->dup_params(params);
172 }
173
174 static void midend_set_timer(midend_data *me)
175 {
176     me->timing = (me->ourgame->is_timed &&
177                   me->ourgame->timing_state(me->states[me->statepos-1].state));
178     if (me->timing || me->flash_time || me->anim_time)
179         activate_timer(me->frontend);
180     else
181         deactivate_timer(me->frontend);
182 }
183
184 static void midend_size_new_drawstate(midend_data *me)
185 {
186     me->ourgame->size(me->params, me->drawstate, &me->winwidth, &me->winheight,
187                       TRUE);
188 }
189
190 void midend_force_redraw(midend_data *me)
191 {
192     if (me->drawstate)
193         me->ourgame->free_drawstate(me->drawstate);
194     me->drawstate = me->ourgame->new_drawstate(me->states[0].state);
195     midend_size_new_drawstate(me);
196     midend_redraw(me);
197 }
198
199 void midend_new_game(midend_data *me)
200 {
201     midend_free_game(me);
202
203     assert(me->nstates == 0);
204
205     if (me->genmode == GOT_DESC) {
206         me->genmode = GOT_NOTHING;
207     } else {
208         random_state *rs;
209
210         if (me->genmode == GOT_SEED) {
211             me->genmode = GOT_NOTHING;
212         } else {
213             /*
214              * Generate a new random seed. 15 digits comes to about
215              * 48 bits, which should be more than enough.
216              * 
217              * I'll avoid putting a leading zero on the number,
218              * just in case it confuses anybody who thinks it's
219              * processed as an integer rather than a string.
220              */
221             char newseed[16];
222             int i;
223             newseed[15] = '\0';
224             newseed[0] = '1' + random_upto(me->random, 9);
225             for (i = 1; i < 15; i++)
226                 newseed[i] = '0' + random_upto(me->random, 10);
227             sfree(me->seedstr);
228             me->seedstr = dupstr(newseed);
229
230             if (me->curparams)
231                 me->ourgame->free_params(me->curparams);
232             me->curparams = me->ourgame->dup_params(me->params);
233         }
234
235         sfree(me->desc);
236         sfree(me->privdesc);
237         sfree(me->aux_info);
238         me->aux_info = NULL;
239
240         rs = random_init(me->seedstr, strlen(me->seedstr));
241         me->desc = me->ourgame->new_desc(me->curparams, rs,
242                                          &me->aux_info, TRUE);
243         me->privdesc = NULL;
244         random_free(rs);
245     }
246
247     ensure(me);
248     me->states[me->nstates].state =
249         me->ourgame->new_game(me, me->params, me->desc);
250     me->states[me->nstates].special = TRUE;
251     me->nstates++;
252     me->statepos = 1;
253     me->drawstate = me->ourgame->new_drawstate(me->states[0].state);
254     midend_size_new_drawstate(me);
255     me->elapsed = 0.0F;
256     midend_set_timer(me);
257     if (me->ui)
258         me->ourgame->free_ui(me->ui);
259     me->ui = me->ourgame->new_ui(me->states[0].state);
260     me->pressed_mouse_button = 0;
261 }
262
263 static int midend_undo(midend_data *me)
264 {
265     if (me->statepos > 1) {
266         if (me->ui)
267             me->ourgame->changed_state(me->ui,
268                                        me->states[me->statepos-1].state,
269                                        me->states[me->statepos-2].state);
270         me->statepos--;
271         me->dir = -1;
272         return 1;
273     } else
274         return 0;
275 }
276
277 static int midend_redo(midend_data *me)
278 {
279     if (me->statepos < me->nstates) {
280         if (me->ui)
281             me->ourgame->changed_state(me->ui,
282                                        me->states[me->statepos-1].state,
283                                        me->states[me->statepos].state);
284         me->statepos++;
285         me->dir = +1;
286         return 1;
287     } else
288         return 0;
289 }
290
291 static void midend_finish_move(midend_data *me)
292 {
293     float flashtime;
294
295     /*
296      * We do not flash if the later of the two states is special.
297      * This covers both forward Solve moves and backward (undone)
298      * Restart moves.
299      */
300     if ((me->oldstate || me->statepos > 1) &&
301         ((me->dir > 0 && !me->states[me->statepos-1].special) ||
302          (me->dir < 0 && me->statepos < me->nstates &&
303           !me->states[me->statepos].special))) {
304         flashtime = me->ourgame->flash_length(me->oldstate ? me->oldstate :
305                                               me->states[me->statepos-2].state,
306                                               me->states[me->statepos-1].state,
307                                               me->oldstate ? me->dir : +1,
308                                               me->ui);
309         if (flashtime > 0) {
310             me->flash_pos = 0.0F;
311             me->flash_time = flashtime;
312         }
313     }
314
315     if (me->oldstate)
316         me->ourgame->free_game(me->oldstate);
317     me->oldstate = NULL;
318     me->anim_pos = me->anim_time = 0;
319     me->dir = 0;
320
321     midend_set_timer(me);
322 }
323
324 void midend_stop_anim(midend_data *me)
325 {
326     if (me->oldstate || me->anim_time) {
327         midend_finish_move(me);
328         midend_redraw(me);
329     }
330 }
331
332 void midend_restart_game(midend_data *me)
333 {
334     game_state *s;
335
336     midend_stop_anim(me);
337
338     assert(me->statepos >= 1);
339     if (me->statepos == 1)
340         return;                        /* no point doing anything at all! */
341
342     /*
343      * During restart, we reconstruct the game from the (public)
344      * game description rather than from states[0], because that
345      * way Mines gets slightly more sensible behaviour (restart
346      * goes to _after_ the first click so you don't have to
347      * remember where you clicked).
348      */
349     s = me->ourgame->new_game(me, me->params, me->desc);
350
351     /*
352      * Now enter the restarted state as the next move.
353      */
354     midend_stop_anim(me);
355     while (me->nstates > me->statepos)
356         me->ourgame->free_game(me->states[--me->nstates].state);
357     ensure(me);
358     me->states[me->nstates].state = s;
359     me->states[me->nstates].special = TRUE;   /* we just restarted */
360     me->statepos = ++me->nstates;
361     if (me->ui)
362         me->ourgame->changed_state(me->ui,
363                                    me->states[me->statepos-2].state,
364                                    me->states[me->statepos-1].state);
365     me->anim_time = 0.0;
366     midend_finish_move(me);
367     midend_redraw(me);
368     midend_set_timer(me);
369 }
370
371 static int midend_really_process_key(midend_data *me, int x, int y, int button)
372 {
373     game_state *oldstate =
374         me->ourgame->dup_game(me->states[me->statepos - 1].state);
375     int special = FALSE, gotspecial = FALSE, ret = 1;
376     float anim_time;
377
378     if (button == 'n' || button == 'N' || button == '\x0E') {
379         midend_stop_anim(me);
380         midend_new_game(me);
381         midend_redraw(me);
382         goto done;                     /* never animate */
383     } else if (button == 'u' || button == 'u' ||
384                button == '\x1A' || button == '\x1F') {
385         midend_stop_anim(me);
386         special = me->states[me->statepos-1].special;
387         gotspecial = TRUE;
388         if (!midend_undo(me))
389             goto done;
390     } else if (button == 'r' || button == 'R' ||
391                button == '\x12' || button == '\x19') {
392         midend_stop_anim(me);
393         if (!midend_redo(me))
394             goto done;
395     } else if (button == 'q' || button == 'Q' || button == '\x11') {
396         ret = 0;
397         goto done;
398     } else {
399         game_state *s;
400         char *movestr;
401         
402         movestr =
403             me->ourgame->interpret_move(me->states[me->statepos-1].state,
404                                         me->ui, me->drawstate, x, y, button);
405         if (!movestr)
406             s = NULL;
407         else if (!*movestr)
408             s = me->states[me->statepos-1].state;
409         else {
410             s = me->ourgame->execute_move(me->states[me->statepos-1].state,
411                                           movestr);
412             assert(s != NULL);
413             sfree(movestr);
414         }
415
416         if (s == me->states[me->statepos-1].state) {
417             /*
418              * make_move() is allowed to return its input state to
419              * indicate that although no move has been made, the UI
420              * state has been updated and a redraw is called for.
421              */
422             midend_redraw(me);
423             goto done;
424         } else if (s) {
425             midend_stop_anim(me);
426             while (me->nstates > me->statepos)
427                 me->ourgame->free_game(me->states[--me->nstates].state);
428             ensure(me);
429             me->states[me->nstates].state = s;
430             me->states[me->nstates].special = FALSE;   /* normal move */
431             me->statepos = ++me->nstates;
432             me->dir = +1;
433         } else {
434           goto done;
435         }
436     }
437
438     if (!gotspecial)
439         special = me->states[me->statepos-1].special;
440
441     /*
442      * See if this move requires an animation.
443      */
444     if (special) {
445         anim_time = 0;
446     } else {
447         anim_time = me->ourgame->anim_length(oldstate,
448                                              me->states[me->statepos-1].state,
449                                              me->dir, me->ui);
450     }
451
452     me->oldstate = oldstate; oldstate = NULL;
453     if (anim_time > 0) {
454         me->anim_time = anim_time;
455     } else {
456         me->anim_time = 0.0;
457         midend_finish_move(me);
458     }
459     me->anim_pos = 0.0;
460
461     midend_redraw(me);
462
463     midend_set_timer(me);
464
465     done:
466     if (oldstate) me->ourgame->free_game(oldstate);
467     return ret;
468 }
469
470 int midend_process_key(midend_data *me, int x, int y, int button)
471 {
472     int ret = 1;
473
474     /*
475      * Harmonise mouse drag and release messages.
476      * 
477      * Some front ends might accidentally switch from sending, say,
478      * RIGHT_DRAG messages to sending LEFT_DRAG, half way through a
479      * drag. (This can happen on the Mac, for example, since
480      * RIGHT_DRAG is usually done using Command+drag, and if the
481      * user accidentally releases Command half way through the drag
482      * then there will be trouble.)
483      * 
484      * It would be an O(number of front ends) annoyance to fix this
485      * in the front ends, but an O(number of back ends) annoyance
486      * to have each game capable of dealing with it. Therefore, we
487      * fix it _here_ in the common midend code so that it only has
488      * to be done once.
489      * 
490      * The possible ways in which things can go screwy in the front
491      * end are:
492      * 
493      *  - in a system containing multiple physical buttons button
494      *    presses can inadvertently overlap. We can see ABab (caps
495      *    meaning button-down and lowercase meaning button-up) when
496      *    the user had semantically intended AaBb.
497      * 
498      *  - in a system where one button is simulated by means of a
499      *    modifier key and another button, buttons can mutate
500      *    between press and release (possibly during drag). So we
501      *    can see Ab instead of Aa.
502      * 
503      * Definite requirements are:
504      * 
505      *  - button _presses_ must never be invented or destroyed. If
506      *    the user presses two buttons in succession, the button
507      *    presses must be transferred to the backend unchanged. So
508      *    if we see AaBb , that's fine; if we see ABab (the button
509      *    presses inadvertently overlapped) we must somehow
510      *    `correct' it to AaBb.
511      * 
512      *  - every mouse action must end up looking like a press, zero
513      *    or more drags, then a release. This allows back ends to
514      *    make the _assumption_ that incoming mouse data will be
515      *    sane in this regard, and not worry about the details.
516      * 
517      * So my policy will be:
518      * 
519      *  - treat any button-up as a button-up for the currently
520      *    pressed button, or ignore it if there is no currently
521      *    pressed button.
522      * 
523      *  - treat any drag as a drag for the currently pressed
524      *    button, or ignore it if there is no currently pressed
525      *    button.
526      * 
527      *  - if we see a button-down while another button is currently
528      *    pressed, invent a button-up for the first one and then
529      *    pass the button-down through as before.
530      * 
531      * 2005-05-31: An addendum to the above. Some games might want
532      * a `priority order' among buttons, such that if one button is
533      * pressed while another is down then a fixed one of the
534      * buttons takes priority no matter what order they're pressed
535      * in. Mines, in particular, wants to treat a left+right click
536      * like a left click for the benefit of users of other
537      * implementations. So the last of the above points is modified
538      * in the presence of an (optional) button priority order.
539      */
540     if (IS_MOUSE_DRAG(button) || IS_MOUSE_RELEASE(button)) {
541         if (me->pressed_mouse_button) {
542             if (IS_MOUSE_DRAG(button)) {
543                 button = me->pressed_mouse_button +
544                     (LEFT_DRAG - LEFT_BUTTON);
545             } else {
546                 button = me->pressed_mouse_button +
547                     (LEFT_RELEASE - LEFT_BUTTON);
548             }
549         } else
550             return ret;                /* ignore it */
551     } else if (IS_MOUSE_DOWN(button) && me->pressed_mouse_button) {
552         /*
553          * If the new button has lower priority than the old one,
554          * don't bother doing this.
555          */
556         if (me->ourgame->mouse_priorities &
557             BUTTON_BEATS(me->pressed_mouse_button, button))
558             return ret;                /* just ignore it */
559
560         /*
561          * Fabricate a button-up for the previously pressed button.
562          */
563         ret = ret && midend_really_process_key
564             (me, x, y, (me->pressed_mouse_button +
565                         (LEFT_RELEASE - LEFT_BUTTON)));
566     }
567
568     /*
569      * Now send on the event we originally received.
570      */
571     ret = ret && midend_really_process_key(me, x, y, button);
572
573     /*
574      * And update the currently pressed button.
575      */
576     if (IS_MOUSE_RELEASE(button))
577         me->pressed_mouse_button = 0;
578     else if (IS_MOUSE_DOWN(button))
579         me->pressed_mouse_button = button;
580
581     return ret;
582 }
583
584 void midend_redraw(midend_data *me)
585 {
586     if (me->statepos > 0 && me->drawstate) {
587         start_draw(me->frontend);
588         if (me->oldstate && me->anim_time > 0 &&
589             me->anim_pos < me->anim_time) {
590             assert(me->dir != 0);
591             me->ourgame->redraw(me->frontend, me->drawstate, me->oldstate,
592                                 me->states[me->statepos-1].state, me->dir,
593                                 me->ui, me->anim_pos, me->flash_pos);
594         } else {
595             me->ourgame->redraw(me->frontend, me->drawstate, NULL,
596                                 me->states[me->statepos-1].state, +1 /*shrug*/,
597                                 me->ui, 0.0, me->flash_pos);
598         }
599         end_draw(me->frontend);
600     }
601 }
602
603 void midend_timer(midend_data *me, float tplus)
604 {
605     me->anim_pos += tplus;
606     if (me->anim_pos >= me->anim_time ||
607         me->anim_time == 0 || !me->oldstate) {
608         if (me->anim_time > 0)
609             midend_finish_move(me);
610     }
611
612     me->flash_pos += tplus;
613     if (me->flash_pos >= me->flash_time || me->flash_time == 0) {
614         me->flash_pos = me->flash_time = 0;
615     }
616
617     midend_redraw(me);
618
619     if (me->timing) {
620         float oldelapsed = me->elapsed;
621         me->elapsed += tplus;
622         if ((int)oldelapsed != (int)me->elapsed)
623             status_bar(me->frontend, me->laststatus ? me->laststatus : "");
624     }
625
626     midend_set_timer(me);
627 }
628
629 float *midend_colours(midend_data *me, int *ncolours)
630 {
631     game_state *state = NULL;
632     float *ret;
633
634     if (me->nstates == 0) {
635         char *aux = NULL;
636         char *desc = me->ourgame->new_desc(me->params, me->random,
637                                            &aux, TRUE);
638         state = me->ourgame->new_game(me, me->params, desc);
639         sfree(desc);
640         sfree(aux);
641     } else
642         state = me->states[0].state;
643
644     ret = me->ourgame->colours(me->frontend, state, ncolours);
645
646     {
647         int i;
648
649         /*
650          * Allow environment-based overrides for the standard
651          * colours by defining variables along the lines of
652          * `NET_COLOUR_4=6000c0'.
653          */
654
655         for (i = 0; i < *ncolours; i++) {
656             char buf[80], *e;
657             unsigned int r, g, b;
658             int j;
659
660             sprintf(buf, "%s_COLOUR_%d", me->ourgame->name, i);
661             for (j = 0; buf[j]; j++)
662                 buf[j] = toupper((unsigned char)buf[j]);
663             if ((e = getenv(buf)) != NULL &&
664                 sscanf(e, "%2x%2x%2x", &r, &g, &b) == 3) {
665                 ret[i*3 + 0] = r / 255.0;
666                 ret[i*3 + 1] = g / 255.0;
667                 ret[i*3 + 2] = b / 255.0;
668             }
669         }
670     }
671
672     if (me->nstates == 0)
673         me->ourgame->free_game(state);
674
675     return ret;
676 }
677
678 int midend_num_presets(midend_data *me)
679 {
680     if (!me->npresets) {
681         char *name;
682         game_params *preset;
683
684         while (me->ourgame->fetch_preset(me->npresets, &name, &preset)) {
685             if (me->presetsize <= me->npresets) {
686                 me->presetsize = me->npresets + 10;
687                 me->presets = sresize(me->presets, me->presetsize,
688                                       game_params *);
689                 me->preset_names = sresize(me->preset_names, me->presetsize,
690                                            char *);
691             }
692
693             me->presets[me->npresets] = preset;
694             me->preset_names[me->npresets] = name;
695             me->npresets++;
696         }
697     }
698
699     {
700         /*
701          * Allow environment-based extensions to the preset list by
702          * defining a variable along the lines of `SOLO_PRESETS=2x3
703          * Advanced:2x3da'. Colon-separated list of items,
704          * alternating between textual titles in the menu and
705          * encoded parameter strings.
706          */
707         char buf[80], *e, *p;
708         int j;
709
710         sprintf(buf, "%s_PRESETS", me->ourgame->name);
711         for (j = 0; buf[j]; j++)
712             buf[j] = toupper((unsigned char)buf[j]);
713
714         if ((e = getenv(buf)) != NULL) {
715             p = e = dupstr(e);
716
717             while (*p) {
718                 char *name, *val;
719                 game_params *preset;
720
721                 name = p;
722                 while (*p && *p != ':') p++;
723                 if (*p) *p++ = '\0';
724                 val = p;
725                 while (*p && *p != ':') p++;
726                 if (*p) *p++ = '\0';
727
728                 preset = me->ourgame->default_params();
729                 me->ourgame->decode_params(preset, val);
730
731                 if (me->ourgame->validate_params(preset)) {
732                     /* Drop this one from the list. */
733                     me->ourgame->free_params(preset);
734                     continue;
735                 }
736
737                 if (me->presetsize <= me->npresets) {
738                     me->presetsize = me->npresets + 10;
739                     me->presets = sresize(me->presets, me->presetsize,
740                                           game_params *);
741                     me->preset_names = sresize(me->preset_names,
742                                                me->presetsize, char *);
743                 }
744
745                 me->presets[me->npresets] = preset;
746                 me->preset_names[me->npresets] = name;
747                 me->npresets++;
748             }
749         }
750     }
751
752     return me->npresets;
753 }
754
755 void midend_fetch_preset(midend_data *me, int n,
756                          char **name, game_params **params)
757 {
758     assert(n >= 0 && n < me->npresets);
759     *name = me->preset_names[n];
760     *params = me->presets[n];
761 }
762
763 int midend_wants_statusbar(midend_data *me)
764 {
765     return me->ourgame->wants_statusbar();
766 }
767
768 void midend_supersede_game_desc(midend_data *me, char *desc, char *privdesc)
769 {
770     sfree(me->desc);
771     sfree(me->privdesc);
772     me->desc = dupstr(desc);
773     me->privdesc = privdesc ? dupstr(privdesc) : NULL;
774 }
775
776 config_item *midend_get_config(midend_data *me, int which, char **wintitle)
777 {
778     char *titlebuf, *parstr, *rest;
779     config_item *ret;
780     char sep;
781
782     assert(wintitle);
783     titlebuf = snewn(40 + strlen(me->ourgame->name), char);
784
785     switch (which) {
786       case CFG_SETTINGS:
787         sprintf(titlebuf, "%s configuration", me->ourgame->name);
788         *wintitle = titlebuf;
789         return me->ourgame->configure(me->params);
790       case CFG_SEED:
791       case CFG_DESC:
792         if (!me->curparams) {
793           sfree(titlebuf);
794           return NULL;
795         }
796         sprintf(titlebuf, "%s %s selection", me->ourgame->name,
797                 which == CFG_SEED ? "random" : "game");
798         *wintitle = titlebuf;
799
800         ret = snewn(2, config_item);
801
802         ret[0].type = C_STRING;
803         if (which == CFG_SEED)
804             ret[0].name = "Game random seed";
805         else
806             ret[0].name = "Game ID";
807         ret[0].ival = 0;
808         /*
809          * For CFG_DESC the text going in here will be a string
810          * encoding of the restricted parameters, plus a colon,
811          * plus the game description. For CFG_SEED it will be the
812          * full parameters, plus a hash, plus the random seed data.
813          * Either of these is a valid full game ID (although only
814          * the former is likely to persist across many code
815          * changes).
816          */
817         parstr = me->ourgame->encode_params(me->curparams, which == CFG_SEED);
818         assert(parstr);
819         if (which == CFG_DESC) {
820             rest = me->desc ? me->desc : "";
821             sep = ':';
822         } else {
823             rest = me->seedstr ? me->seedstr : "";
824             sep = '#';
825         }
826         ret[0].sval = snewn(strlen(parstr) + strlen(rest) + 2, char);
827         sprintf(ret[0].sval, "%s%c%s", parstr, sep, rest);
828         sfree(parstr);
829
830         ret[1].type = C_END;
831         ret[1].name = ret[1].sval = NULL;
832         ret[1].ival = 0;
833
834         return ret;
835     }
836
837     assert(!"We shouldn't be here");
838     return NULL;
839 }
840
841 static char *midend_game_id_int(midend_data *me, char *id, int defmode)
842 {
843     char *error, *par, *desc, *seed;
844
845     seed = strchr(id, '#');
846     desc = strchr(id, ':');
847
848     if (desc && (!seed || desc < seed)) {
849         /*
850          * We have a colon separating parameters from game
851          * description. So `par' now points to the parameters
852          * string, and `desc' to the description string.
853          */
854         *desc++ = '\0';
855         par = id;
856         seed = NULL;
857     } else if (seed && (!desc || seed < desc)) {
858         /*
859          * We have a hash separating parameters from random seed.
860          * So `par' now points to the parameters string, and `seed'
861          * to the seed string.
862          */
863         *seed++ = '\0';
864         par = id;
865         desc = NULL;
866     } else {
867         /*
868          * We only have one string. Depending on `defmode', we take
869          * it to be either parameters, seed or description.
870          */
871         if (defmode == DEF_SEED) {
872             seed = id;
873             par = desc = NULL;
874         } else if (defmode == DEF_DESC) {
875             desc = id;
876             par = seed = NULL;
877         } else {
878             par = id;
879             seed = desc = NULL;
880         }
881     }
882
883     if (par) {
884         game_params *tmpparams;
885         tmpparams = me->ourgame->dup_params(me->params);
886         me->ourgame->decode_params(tmpparams, par);
887         error = me->ourgame->validate_params(tmpparams);
888         if (error) {
889             me->ourgame->free_params(tmpparams);
890             return error;
891         }
892         if (me->curparams)
893             me->ourgame->free_params(me->curparams);
894         me->curparams = tmpparams;
895
896         /*
897          * Now filter only the persistent parts of this state into
898          * the long-term params structure, unless we've _only_
899          * received a params string in which case the whole lot is
900          * persistent.
901          */
902         if (seed || desc) {
903             char *tmpstr = me->ourgame->encode_params(tmpparams, FALSE);
904             me->ourgame->decode_params(me->params, tmpstr);
905             sfree(tmpstr);
906         } else {
907             me->ourgame->free_params(me->params);
908             me->params = me->ourgame->dup_params(tmpparams);
909         }
910     }
911
912     sfree(me->desc);
913     sfree(me->privdesc);
914     me->desc = me->privdesc = NULL;
915     sfree(me->seedstr);
916     me->seedstr = NULL;
917
918     if (desc) {
919         error = me->ourgame->validate_desc(me->params, desc);
920         if (error)
921             return error;
922
923         me->desc = dupstr(desc);
924         me->genmode = GOT_DESC;
925         sfree(me->aux_info);
926         me->aux_info = NULL;
927     }
928
929     if (seed) {
930         me->seedstr = dupstr(seed);
931         me->genmode = GOT_SEED;
932     }
933
934     return NULL;
935 }
936
937 char *midend_game_id(midend_data *me, char *id)
938 {
939     return midend_game_id_int(me, id, DEF_PARAMS);
940 }
941
942 char *midend_set_config(midend_data *me, int which, config_item *cfg)
943 {
944     char *error;
945     game_params *params;
946
947     switch (which) {
948       case CFG_SETTINGS:
949         params = me->ourgame->custom_params(cfg);
950         error = me->ourgame->validate_params(params);
951
952         if (error) {
953             me->ourgame->free_params(params);
954             return error;
955         }
956
957         me->ourgame->free_params(me->params);
958         me->params = params;
959         break;
960
961       case CFG_SEED:
962       case CFG_DESC:
963         error = midend_game_id_int(me, cfg[0].sval,
964                                    (which == CFG_SEED ? DEF_SEED : DEF_DESC));
965         if (error)
966             return error;
967         break;
968     }
969
970     return NULL;
971 }
972
973 char *midend_text_format(midend_data *me)
974 {
975     if (me->ourgame->can_format_as_text && me->statepos > 0)
976         return me->ourgame->text_format(me->states[me->statepos-1].state);
977     else
978         return NULL;
979 }
980
981 char *midend_solve(midend_data *me)
982 {
983     game_state *s;
984     char *msg, *movestr;
985
986     if (!me->ourgame->can_solve)
987         return "This game does not support the Solve operation";
988
989     if (me->statepos < 1)
990         return "No game set up to solve";   /* _shouldn't_ happen! */
991
992     msg = "Solve operation failed";    /* game _should_ overwrite on error */
993     movestr = me->ourgame->solve(me->states[0].state,
994                                  me->states[me->statepos-1].state,
995                                  me->aux_info, &msg);
996     if (!movestr)
997         return msg;
998     s = me->ourgame->execute_move(me->states[me->statepos-1].state, movestr);
999     assert(s);
1000     sfree(movestr);
1001
1002     /*
1003      * Now enter the solved state as the next move.
1004      */
1005     midend_stop_anim(me);
1006     while (me->nstates > me->statepos)
1007         me->ourgame->free_game(me->states[--me->nstates].state);
1008     ensure(me);
1009     me->states[me->nstates].state = s;
1010     me->states[me->nstates].special = TRUE;   /* created using solve */
1011     me->statepos = ++me->nstates;
1012     if (me->ui)
1013         me->ourgame->changed_state(me->ui,
1014                                    me->states[me->statepos-2].state,
1015                                    me->states[me->statepos-1].state);
1016     me->anim_time = 0.0;
1017     midend_finish_move(me);
1018     midend_redraw(me);
1019     midend_set_timer(me);
1020     return NULL;
1021 }
1022
1023 char *midend_rewrite_statusbar(midend_data *me, char *text)
1024 {
1025     /*
1026      * An important special case is that we are occasionally called
1027      * with our own laststatus, to update the timer.
1028      */
1029     if (me->laststatus != text) {
1030         sfree(me->laststatus);
1031         me->laststatus = dupstr(text);
1032     }
1033
1034     if (me->ourgame->is_timed) {
1035         char timebuf[100], *ret;
1036         int min, sec;
1037
1038         sec = me->elapsed;
1039         min = sec / 60;
1040         sec %= 60;
1041         sprintf(timebuf, "[%d:%02d] ", min, sec);
1042
1043         ret = snewn(strlen(timebuf) + strlen(text) + 1, char);
1044         strcpy(ret, timebuf);
1045         strcat(ret, text);
1046         return ret;
1047
1048     } else {
1049         return dupstr(text);
1050     }
1051 }