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.
16 enum { DEF_PARAMS
, DEF_SEED
, DEF_DESC
}; /* for midend_game_id_int */
18 enum { NEWGAME
, MOVE
, SOLVE
, RESTART
};/* for midend_state_entry.movetype */
20 #define special(type) ( (type) != MOVE )
22 struct midend_state_entry
{
33 struct preset_menu
*preset_menu
;
34 char **encoded_presets
; /* for midend_which_preset to check against */
35 int n_encoded_presets
;
38 * `desc' and `privdesc' deserve a comment.
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'
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.)
59 char *desc
, *privdesc
, *seedstr
;
61 enum { GOT_SEED
, GOT_DESC
, GOT_NOTHING
} genmode
;
63 int nstates
, statesize
, statepos
;
64 struct midend_state_entry
*states
;
66 game_params
*params
, *curparams
;
67 game_drawstate
*drawstate
;
71 float anim_time
, anim_pos
;
72 float flash_time
, flash_pos
;
81 int pressed_mouse_button
;
83 int preferred_tilesize
, tilesize
, winwidth
, winheight
;
85 void (*game_id_change_notify_function
)(void *);
86 void *game_id_change_notify_ctx
;
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); \
97 void midend_reset_tilesize(midend
*me
)
99 me
->preferred_tilesize
= me
->ourgame
->preferred_tilesize
;
102 * Allow an environment-based override for the default tile
103 * size by defining a variable along the lines of
110 sprintf(buf
, "%s_TILESIZE", me
->ourgame
->name
);
111 for (j
= k
= 0; buf
[j
]; j
++)
112 if (!isspace((unsigned char)buf
[j
]))
113 buf
[k
++] = toupper((unsigned char)buf
[j
]);
115 if ((e
= getenv(buf
)) != NULL
&& sscanf(e
, "%d", &ts
) == 1 && ts
> 0)
116 me
->preferred_tilesize
= ts
;
120 midend
*midend_new(frontend
*fe
, const game
*ourgame
,
121 const drawing_api
*drapi
, void *drhandle
)
123 midend
*me
= snew(midend
);
127 get_random_seed(&randseed
, &randseedsize
);
130 me
->ourgame
= ourgame
;
131 me
->random
= random_new(randseed
, randseedsize
);
132 me
->nstates
= me
->statesize
= me
->statepos
= 0;
134 me
->params
= ourgame
->default_params();
135 me
->game_id_change_notify_function
= NULL
;
136 me
->game_id_change_notify_ctx
= NULL
;
139 * Allow environment-based changing of the default settings by
140 * defining a variable along the lines of `NET_DEFAULT=25x25w'
141 * in which the value is an encoded parameter string.
146 sprintf(buf
, "%s_DEFAULT", me
->ourgame
->name
);
147 for (j
= k
= 0; buf
[j
]; j
++)
148 if (!isspace((unsigned char)buf
[j
]))
149 buf
[k
++] = toupper((unsigned char)buf
[j
]);
151 if ((e
= getenv(buf
)) != NULL
)
152 me
->ourgame
->decode_params(me
->params
, e
);
154 me
->curparams
= NULL
;
155 me
->desc
= me
->privdesc
= NULL
;
158 me
->genmode
= GOT_NOTHING
;
159 me
->drawstate
= NULL
;
161 me
->preset_menu
= NULL
;
162 me
->anim_time
= me
->anim_pos
= 0.0F
;
163 me
->flash_time
= me
->flash_pos
= 0.0F
;
166 me
->pressed_mouse_button
= 0;
167 me
->laststatus
= NULL
;
170 me
->tilesize
= me
->winwidth
= me
->winheight
= 0;
172 me
->drawing
= drawing_new(drapi
, me
, drhandle
);
176 midend_reset_tilesize(me
);
183 const game
*midend_which_game(midend
*me
)
188 static void midend_purge_states(midend
*me
)
190 while (me
->nstates
> me
->statepos
) {
191 me
->ourgame
->free_game(me
->states
[--me
->nstates
].state
);
192 if (me
->states
[me
->nstates
].movestr
)
193 sfree(me
->states
[me
->nstates
].movestr
);
197 static void midend_free_game(midend
*me
)
199 while (me
->nstates
> 0) {
201 me
->ourgame
->free_game(me
->states
[me
->nstates
].state
);
202 sfree(me
->states
[me
->nstates
].movestr
);
206 me
->ourgame
->free_drawstate(me
->drawing
, me
->drawstate
);
209 static void midend_free_preset_menu(midend
*me
, struct preset_menu
*menu
)
213 for (i
= 0; i
< menu
->n_entries
; i
++) {
214 sfree(menu
->entries
[i
].title
);
215 if (menu
->entries
[i
].params
)
216 me
->ourgame
->free_params(menu
->entries
[i
].params
);
217 midend_free_preset_menu(me
, menu
->entries
[i
].submenu
);
219 sfree(menu
->entries
);
224 void midend_free(midend
*me
)
226 midend_free_game(me
);
229 drawing_free(me
->drawing
);
230 random_free(me
->random
);
236 me
->ourgame
->free_params(me
->params
);
237 midend_free_preset_menu(me
, me
->preset_menu
);
239 me
->ourgame
->free_ui(me
->ui
);
241 me
->ourgame
->free_params(me
->curparams
);
242 sfree(me
->laststatus
);
246 static void midend_size_new_drawstate(midend
*me
)
249 * Don't even bother, if we haven't worked out our tile size
252 if (me
->tilesize
> 0) {
253 me
->ourgame
->compute_size(me
->params
, me
->tilesize
,
254 &me
->winwidth
, &me
->winheight
);
255 me
->ourgame
->set_size(me
->drawing
, me
->drawstate
,
256 me
->params
, me
->tilesize
);
260 void midend_size(midend
*me
, int *x
, int *y
, int user_size
)
266 * We can't set the size on the same drawstate twice. So if
267 * we've already sized one drawstate, we must throw it away and
270 if (me
->drawstate
&& me
->tilesize
> 0) {
271 me
->ourgame
->free_drawstate(me
->drawing
, me
->drawstate
);
272 me
->drawstate
= me
->ourgame
->new_drawstate(me
->drawing
,
273 me
->states
[0].state
);
277 * Find the tile size that best fits within the given space. If
278 * `user_size' is TRUE, we must actually find the _largest_ such
279 * tile size, in order to get as close to the user's explicit
280 * request as possible; otherwise, we bound above at the game's
281 * preferred tile size, so that the game gets what it wants
282 * provided that this doesn't break the constraint from the
283 * front-end (which is likely to be a screen size or similar).
289 me
->ourgame
->compute_size(me
->params
, max
, &rx
, &ry
);
290 } while (rx
<= *x
&& ry
<= *y
);
292 max
= me
->preferred_tilesize
+ 1;
296 * Now binary-search between min and max. We're looking for a
297 * boundary rather than a value: the point at which tile sizes
298 * stop fitting within the given dimensions. Thus, we stop when
299 * max and min differ by exactly 1.
301 while (max
- min
> 1) {
302 int mid
= (max
+ min
) / 2;
303 me
->ourgame
->compute_size(me
->params
, mid
, &rx
, &ry
);
304 if (rx
<= *x
&& ry
<= *y
)
311 * Now `min' is a valid size, and `max' isn't. So use `min'.
316 /* If the user requested a change in size, make it permanent. */
317 me
->preferred_tilesize
= me
->tilesize
;
318 midend_size_new_drawstate(me
);
323 int midend_tilesize(midend
*me
) { return me
->tilesize
; }
325 void midend_set_params(midend
*me
, game_params
*params
)
327 me
->ourgame
->free_params(me
->params
);
328 me
->params
= me
->ourgame
->dup_params(params
);
331 game_params
*midend_get_params(midend
*me
)
333 return me
->ourgame
->dup_params(me
->params
);
336 static void midend_set_timer(midend
*me
)
338 me
->timing
= (me
->ourgame
->is_timed
&&
339 me
->ourgame
->timing_state(me
->states
[me
->statepos
-1].state
,
341 if (me
->timing
|| me
->flash_time
|| me
->anim_time
)
342 activate_timer(me
->frontend
);
344 deactivate_timer(me
->frontend
);
347 void midend_force_redraw(midend
*me
)
350 me
->ourgame
->free_drawstate(me
->drawing
, me
->drawstate
);
351 me
->drawstate
= me
->ourgame
->new_drawstate(me
->drawing
,
352 me
->states
[0].state
);
353 midend_size_new_drawstate(me
);
357 void midend_new_game(midend
*me
)
359 midend_stop_anim(me
);
360 midend_free_game(me
);
362 assert(me
->nstates
== 0);
364 if (me
->genmode
== GOT_DESC
) {
365 me
->genmode
= GOT_NOTHING
;
369 if (me
->genmode
== GOT_SEED
) {
370 me
->genmode
= GOT_NOTHING
;
373 * Generate a new random seed. 15 digits comes to about
374 * 48 bits, which should be more than enough.
376 * I'll avoid putting a leading zero on the number,
377 * just in case it confuses anybody who thinks it's
378 * processed as an integer rather than a string.
383 newseed
[0] = '1' + (char)random_upto(me
->random
, 9);
384 for (i
= 1; i
< 15; i
++)
385 newseed
[i
] = '0' + (char)random_upto(me
->random
, 10);
387 me
->seedstr
= dupstr(newseed
);
390 me
->ourgame
->free_params(me
->curparams
);
391 me
->curparams
= me
->ourgame
->dup_params(me
->params
);
399 rs
= random_new(me
->seedstr
, strlen(me
->seedstr
));
401 * If this midend has been instantiated without providing a
402 * drawing API, it is non-interactive. This means that it's
403 * being used for bulk game generation, and hence we should
404 * pass the non-interactive flag to new_desc.
406 me
->desc
= me
->ourgame
->new_desc(me
->curparams
, rs
,
407 &me
->aux_info
, (me
->drawing
!= NULL
));
415 * It might seem a bit odd that we're using me->params to
416 * create the initial game state, rather than me->curparams
417 * which is better tailored to this specific game and which we
420 * It's supposed to be an invariant in the midend that
421 * me->params and me->curparams differ in no aspect that is
422 * important after generation (i.e. after new_desc()). By
423 * deliberately passing the _less_ specific of these two
424 * parameter sets, we provoke play-time misbehaviour in the
425 * case where a game has failed to encode a play-time parameter
426 * in the non-full version of encode_params().
428 me
->states
[me
->nstates
].state
=
429 me
->ourgame
->new_game(me
, me
->params
, me
->desc
);
432 * As part of our commitment to self-testing, test the aux
433 * string to make sure nothing ghastly went wrong.
435 if (me
->ourgame
->can_solve
&& me
->aux_info
) {
440 movestr
= me
->ourgame
->solve(me
->states
[0].state
,
443 assert(movestr
&& !msg
);
444 s
= me
->ourgame
->execute_move(me
->states
[0].state
, movestr
);
446 me
->ourgame
->free_game(s
);
450 me
->states
[me
->nstates
].movestr
= NULL
;
451 me
->states
[me
->nstates
].movetype
= NEWGAME
;
454 me
->drawstate
= me
->ourgame
->new_drawstate(me
->drawing
,
455 me
->states
[0].state
);
456 midend_size_new_drawstate(me
);
458 me
->flash_pos
= me
->flash_time
= 0.0F
;
459 me
->anim_pos
= me
->anim_time
= 0.0F
;
461 me
->ourgame
->free_ui(me
->ui
);
462 me
->ui
= me
->ourgame
->new_ui(me
->states
[0].state
);
463 midend_set_timer(me
);
464 me
->pressed_mouse_button
= 0;
466 if (me
->game_id_change_notify_function
)
467 me
->game_id_change_notify_function(me
->game_id_change_notify_ctx
);
470 int midend_can_undo(midend
*me
)
472 return (me
->statepos
> 1);
475 int midend_can_redo(midend
*me
)
477 return (me
->statepos
< me
->nstates
);
480 static int midend_undo(midend
*me
)
482 if (me
->statepos
> 1) {
484 me
->ourgame
->changed_state(me
->ui
,
485 me
->states
[me
->statepos
-1].state
,
486 me
->states
[me
->statepos
-2].state
);
494 static int midend_redo(midend
*me
)
496 if (me
->statepos
< me
->nstates
) {
498 me
->ourgame
->changed_state(me
->ui
,
499 me
->states
[me
->statepos
-1].state
,
500 me
->states
[me
->statepos
].state
);
508 static void midend_finish_move(midend
*me
)
513 * We do not flash if the later of the two states is special.
514 * This covers both forward Solve moves and backward (undone)
517 if ((me
->oldstate
|| me
->statepos
> 1) &&
518 ((me
->dir
> 0 && !special(me
->states
[me
->statepos
-1].movetype
)) ||
519 (me
->dir
< 0 && me
->statepos
< me
->nstates
&&
520 !special(me
->states
[me
->statepos
].movetype
)))) {
521 flashtime
= me
->ourgame
->flash_length(me
->oldstate
? me
->oldstate
:
522 me
->states
[me
->statepos
-2].state
,
523 me
->states
[me
->statepos
-1].state
,
524 me
->oldstate
? me
->dir
: +1,
527 me
->flash_pos
= 0.0F
;
528 me
->flash_time
= flashtime
;
533 me
->ourgame
->free_game(me
->oldstate
);
535 me
->anim_pos
= me
->anim_time
= 0;
538 midend_set_timer(me
);
541 void midend_stop_anim(midend
*me
)
543 if (me
->oldstate
|| me
->anim_time
!= 0) {
544 midend_finish_move(me
);
549 void midend_restart_game(midend
*me
)
553 assert(me
->statepos
>= 1);
554 if (me
->statepos
== 1)
555 return; /* no point doing anything at all! */
558 * During restart, we reconstruct the game from the (public)
559 * game description rather than from states[0], because that
560 * way Mines gets slightly more sensible behaviour (restart
561 * goes to _after_ the first click so you don't have to
562 * remember where you clicked).
564 s
= me
->ourgame
->new_game(me
, me
->params
, me
->desc
);
567 * Now enter the restarted state as the next move.
569 midend_stop_anim(me
);
570 midend_purge_states(me
);
572 me
->states
[me
->nstates
].state
= s
;
573 me
->states
[me
->nstates
].movestr
= dupstr(me
->desc
);
574 me
->states
[me
->nstates
].movetype
= RESTART
;
575 me
->statepos
= ++me
->nstates
;
577 me
->ourgame
->changed_state(me
->ui
,
578 me
->states
[me
->statepos
-2].state
,
579 me
->states
[me
->statepos
-1].state
);
580 me
->flash_pos
= me
->flash_time
= 0.0F
;
581 midend_finish_move(me
);
583 midend_set_timer(me
);
586 static int midend_really_process_key(midend
*me
, int x
, int y
, int button
)
588 game_state
*oldstate
=
589 me
->ourgame
->dup_game(me
->states
[me
->statepos
- 1].state
);
590 int type
= MOVE
, gottype
= FALSE
, ret
= 1;
596 me
->ourgame
->interpret_move(me
->states
[me
->statepos
-1].state
,
597 me
->ui
, me
->drawstate
, x
, y
, button
);
600 if (button
== 'n' || button
== 'N' || button
== '\x0E') {
603 goto done
; /* never animate */
604 } else if (button
== 'u' || button
== 'U' ||
605 button
== '\x1A' || button
== '\x1F') {
606 midend_stop_anim(me
);
607 type
= me
->states
[me
->statepos
-1].movetype
;
609 if (!midend_undo(me
))
611 } else if (button
== 'r' || button
== 'R' ||
612 button
== '\x12' || button
== '\x19') {
613 midend_stop_anim(me
);
614 if (!midend_redo(me
))
616 } else if (button
== '\x13' && me
->ourgame
->can_solve
) {
617 if (midend_solve(me
))
619 } else if (button
== 'q' || button
== 'Q' || button
== '\x11') {
626 s
= me
->states
[me
->statepos
-1].state
;
628 s
= me
->ourgame
->execute_move(me
->states
[me
->statepos
-1].state
,
633 if (s
== me
->states
[me
->statepos
-1].state
) {
635 * make_move() is allowed to return its input state to
636 * indicate that although no move has been made, the UI
637 * state has been updated and a redraw is called for.
640 midend_set_timer(me
);
643 midend_stop_anim(me
);
644 midend_purge_states(me
);
646 assert(movestr
!= NULL
);
647 me
->states
[me
->nstates
].state
= s
;
648 me
->states
[me
->nstates
].movestr
= movestr
;
649 me
->states
[me
->nstates
].movetype
= MOVE
;
650 me
->statepos
= ++me
->nstates
;
653 me
->ourgame
->changed_state(me
->ui
,
654 me
->states
[me
->statepos
-2].state
,
655 me
->states
[me
->statepos
-1].state
);
662 type
= me
->states
[me
->statepos
-1].movetype
;
665 * See if this move requires an animation.
667 if (special(type
) && !(type
== SOLVE
&&
668 (me
->ourgame
->flags
& SOLVE_ANIMATES
))) {
671 anim_time
= me
->ourgame
->anim_length(oldstate
,
672 me
->states
[me
->statepos
-1].state
,
676 me
->oldstate
= oldstate
; oldstate
= NULL
;
678 me
->anim_time
= anim_time
;
681 midend_finish_move(me
);
687 midend_set_timer(me
);
690 if (oldstate
) me
->ourgame
->free_game(oldstate
);
694 int midend_process_key(midend
*me
, int x
, int y
, int button
)
699 * Harmonise mouse drag and release messages.
701 * Some front ends might accidentally switch from sending, say,
702 * RIGHT_DRAG messages to sending LEFT_DRAG, half way through a
703 * drag. (This can happen on the Mac, for example, since
704 * RIGHT_DRAG is usually done using Command+drag, and if the
705 * user accidentally releases Command half way through the drag
706 * then there will be trouble.)
708 * It would be an O(number of front ends) annoyance to fix this
709 * in the front ends, but an O(number of back ends) annoyance
710 * to have each game capable of dealing with it. Therefore, we
711 * fix it _here_ in the common midend code so that it only has
714 * The possible ways in which things can go screwy in the front
717 * - in a system containing multiple physical buttons button
718 * presses can inadvertently overlap. We can see ABab (caps
719 * meaning button-down and lowercase meaning button-up) when
720 * the user had semantically intended AaBb.
722 * - in a system where one button is simulated by means of a
723 * modifier key and another button, buttons can mutate
724 * between press and release (possibly during drag). So we
725 * can see Ab instead of Aa.
727 * Definite requirements are:
729 * - button _presses_ must never be invented or destroyed. If
730 * the user presses two buttons in succession, the button
731 * presses must be transferred to the backend unchanged. So
732 * if we see AaBb , that's fine; if we see ABab (the button
733 * presses inadvertently overlapped) we must somehow
734 * `correct' it to AaBb.
736 * - every mouse action must end up looking like a press, zero
737 * or more drags, then a release. This allows back ends to
738 * make the _assumption_ that incoming mouse data will be
739 * sane in this regard, and not worry about the details.
741 * So my policy will be:
743 * - treat any button-up as a button-up for the currently
744 * pressed button, or ignore it if there is no currently
747 * - treat any drag as a drag for the currently pressed
748 * button, or ignore it if there is no currently pressed
751 * - if we see a button-down while another button is currently
752 * pressed, invent a button-up for the first one and then
753 * pass the button-down through as before.
755 * 2005-05-31: An addendum to the above. Some games might want
756 * a `priority order' among buttons, such that if one button is
757 * pressed while another is down then a fixed one of the
758 * buttons takes priority no matter what order they're pressed
759 * in. Mines, in particular, wants to treat a left+right click
760 * like a left click for the benefit of users of other
761 * implementations. So the last of the above points is modified
762 * in the presence of an (optional) button priority order.
764 * A further addition: we translate certain keyboard presses to
765 * cursor key 'select' buttons, so that a) frontends don't have
766 * to translate these themselves (like they do for CURSOR_UP etc),
767 * and b) individual games don't have to hard-code button presses
768 * of '\n' etc for keyboard-based cursors. The choice of buttons
769 * here could eventually be controlled by a runtime configuration
772 if (IS_MOUSE_DRAG(button
) || IS_MOUSE_RELEASE(button
)) {
773 if (me
->pressed_mouse_button
) {
774 if (IS_MOUSE_DRAG(button
)) {
775 button
= me
->pressed_mouse_button
+
776 (LEFT_DRAG
- LEFT_BUTTON
);
778 button
= me
->pressed_mouse_button
+
779 (LEFT_RELEASE
- LEFT_BUTTON
);
782 return ret
; /* ignore it */
783 } else if (IS_MOUSE_DOWN(button
) && me
->pressed_mouse_button
) {
785 * If the new button has lower priority than the old one,
786 * don't bother doing this.
788 if (me
->ourgame
->flags
&
789 BUTTON_BEATS(me
->pressed_mouse_button
, button
))
790 return ret
; /* just ignore it */
793 * Fabricate a button-up for the previously pressed button.
795 ret
= ret
&& midend_really_process_key
796 (me
, x
, y
, (me
->pressed_mouse_button
+
797 (LEFT_RELEASE
- LEFT_BUTTON
)));
801 * Translate keyboard presses to cursor selection.
803 if (button
== '\n' || button
== '\r')
804 button
= CURSOR_SELECT
;
806 button
= CURSOR_SELECT2
;
809 * Normalise both backspace characters (8 and 127) to \b. Easier
810 * to do this once, here, than to require all front ends to
811 * carefully generate the same one - now each front end can
812 * generate whichever is easiest.
814 if (button
== '\177')
818 * Now send on the event we originally received.
820 ret
= ret
&& midend_really_process_key(me
, x
, y
, button
);
823 * And update the currently pressed button.
825 if (IS_MOUSE_RELEASE(button
))
826 me
->pressed_mouse_button
= 0;
827 else if (IS_MOUSE_DOWN(button
))
828 me
->pressed_mouse_button
= button
;
833 void midend_redraw(midend
*me
)
837 if (me
->statepos
> 0 && me
->drawstate
) {
838 start_draw(me
->drawing
);
839 if (me
->oldstate
&& me
->anim_time
> 0 &&
840 me
->anim_pos
< me
->anim_time
) {
841 assert(me
->dir
!= 0);
842 me
->ourgame
->redraw(me
->drawing
, me
->drawstate
, me
->oldstate
,
843 me
->states
[me
->statepos
-1].state
, me
->dir
,
844 me
->ui
, me
->anim_pos
, me
->flash_pos
);
846 me
->ourgame
->redraw(me
->drawing
, me
->drawstate
, NULL
,
847 me
->states
[me
->statepos
-1].state
, +1 /*shrug*/,
848 me
->ui
, 0.0, me
->flash_pos
);
850 end_draw(me
->drawing
);
855 * Nasty hacky function used to implement the --redo option in
856 * gtk.c. Only used for generating the puzzles' icons.
858 void midend_freeze_timer(midend
*me
, float tprop
)
860 me
->anim_pos
= me
->anim_time
* tprop
;
862 deactivate_timer(me
->frontend
);
865 void midend_timer(midend
*me
, float tplus
)
867 int need_redraw
= (me
->anim_time
> 0 || me
->flash_time
> 0);
869 me
->anim_pos
+= tplus
;
870 if (me
->anim_pos
>= me
->anim_time
||
871 me
->anim_time
== 0 || !me
->oldstate
) {
872 if (me
->anim_time
> 0)
873 midend_finish_move(me
);
876 me
->flash_pos
+= tplus
;
877 if (me
->flash_pos
>= me
->flash_time
|| me
->flash_time
== 0) {
878 me
->flash_pos
= me
->flash_time
= 0;
885 float oldelapsed
= me
->elapsed
;
886 me
->elapsed
+= tplus
;
887 if ((int)oldelapsed
!= (int)me
->elapsed
)
888 status_bar(me
->drawing
, me
->laststatus
? me
->laststatus
: "");
891 midend_set_timer(me
);
894 float *midend_colours(midend
*me
, int *ncolours
)
898 ret
= me
->ourgame
->colours(me
->frontend
, ncolours
);
904 * Allow environment-based overrides for the standard
905 * colours by defining variables along the lines of
906 * `NET_COLOUR_4=6000c0'.
909 for (i
= 0; i
< *ncolours
; i
++) {
911 unsigned int r
, g
, b
;
914 sprintf(buf
, "%s_COLOUR_%d", me
->ourgame
->name
, i
);
915 for (j
= k
= 0; buf
[j
]; j
++)
916 if (!isspace((unsigned char)buf
[j
]))
917 buf
[k
++] = toupper((unsigned char)buf
[j
]);
919 if ((e
= getenv(buf
)) != NULL
&&
920 sscanf(e
, "%2x%2x%2x", &r
, &g
, &b
) == 3) {
921 ret
[i
*3 + 0] = r
/ 255.0F
;
922 ret
[i
*3 + 1] = g
/ 255.0F
;
923 ret
[i
*3 + 2] = b
/ 255.0F
;
931 struct preset_menu
*preset_menu_new(void)
933 struct preset_menu
*menu
= snew(struct preset_menu
);
935 menu
->entries_size
= 0;
936 menu
->entries
= NULL
;
940 static struct preset_menu_entry
*preset_menu_add(struct preset_menu
*menu
,
943 struct preset_menu_entry
*toret
;
944 if (menu
->n_entries
>= menu
->entries_size
) {
945 menu
->entries_size
= menu
->n_entries
* 5 / 4 + 10;
946 menu
->entries
= sresize(menu
->entries
, menu
->entries_size
,
947 struct preset_menu_entry
);
949 toret
= &menu
->entries
[menu
->n_entries
++];
950 toret
->title
= title
;
951 toret
->params
= NULL
;
952 toret
->submenu
= NULL
;
956 struct preset_menu
*preset_menu_add_submenu(struct preset_menu
*parent
,
959 struct preset_menu_entry
*entry
= preset_menu_add(parent
, title
);
960 entry
->submenu
= preset_menu_new();
961 return entry
->submenu
;
964 void preset_menu_add_preset(struct preset_menu
*parent
,
965 char *title
, game_params
*params
)
967 struct preset_menu_entry
*entry
= preset_menu_add(parent
, title
);
968 entry
->params
= params
;
971 game_params
*preset_menu_lookup_by_id(struct preset_menu
*menu
, int id
)
976 for (i
= 0; i
< menu
->n_entries
; i
++) {
977 if (id
== menu
->entries
[i
].id
)
978 return menu
->entries
[i
].params
;
979 if (menu
->entries
[i
].submenu
&&
980 (retd
= preset_menu_lookup_by_id(
981 menu
->entries
[i
].submenu
, id
)) != NULL
)
988 static char *preset_menu_add_from_user_env(
989 midend
*me
, struct preset_menu
*menu
, char *p
, int top_level
)
996 while (*p
&& *p
!= ':') p
++;
999 while (*p
&& *p
!= ':') p
++;
1000 if (*p
) *p
++ = '\0';
1002 if (!strcmp(val
, "#")) {
1004 * Special case: either open a new submenu with the given
1005 * title, or terminate the current submenu.
1008 struct preset_menu
*submenu
=
1009 preset_menu_add_submenu(menu
, dupstr(name
));
1010 p
= preset_menu_add_from_user_env(me
, submenu
, p
, FALSE
);
1013 * If we get a 'close submenu' indication at the top
1014 * level, there's not much we can do but quietly
1023 preset
= me
->ourgame
->default_params();
1024 me
->ourgame
->decode_params(preset
, val
);
1026 if (me
->ourgame
->validate_params(preset
, TRUE
)) {
1027 /* Drop this one from the list. */
1028 me
->ourgame
->free_params(preset
);
1032 preset_menu_add_preset(menu
, dupstr(name
), preset
);
1038 static void preset_menu_alloc_ids(midend
*me
, struct preset_menu
*menu
)
1042 for (i
= 0; i
< menu
->n_entries
; i
++)
1043 menu
->entries
[i
].id
= me
->n_encoded_presets
++;
1045 for (i
= 0; i
< menu
->n_entries
; i
++)
1046 if (menu
->entries
[i
].submenu
)
1047 preset_menu_alloc_ids(me
, menu
->entries
[i
].submenu
);
1050 static void preset_menu_encode_params(midend
*me
, struct preset_menu
*menu
)
1054 for (i
= 0; i
< menu
->n_entries
; i
++) {
1055 if (menu
->entries
[i
].params
) {
1056 me
->encoded_presets
[menu
->entries
[i
].id
] =
1057 me
->ourgame
->encode_params(menu
->entries
[i
].params
, TRUE
);
1059 preset_menu_encode_params(me
, menu
->entries
[i
].submenu
);
1064 struct preset_menu
*midend_get_presets(midend
*me
, int *id_limit
)
1068 if (me
->preset_menu
)
1069 return me
->preset_menu
;
1072 /* Expect the game to implement exactly one of the two preset APIs */
1073 assert(me
->ourgame
->fetch_preset
|| me
->ourgame
->preset_menu
);
1074 assert(!(me
->ourgame
->fetch_preset
&& me
->ourgame
->preset_menu
));
1077 if (me
->ourgame
->fetch_preset
) {
1079 game_params
*preset
;
1081 /* Simple one-level menu */
1082 assert(!me
->ourgame
->preset_menu
);
1083 me
->preset_menu
= preset_menu_new();
1084 for (i
= 0; me
->ourgame
->fetch_preset(i
, &name
, &preset
); i
++)
1085 preset_menu_add_preset(me
->preset_menu
, name
, preset
);
1088 /* Hierarchical menu provided by the game backend */
1089 me
->preset_menu
= me
->ourgame
->preset_menu();
1094 * Allow user extensions to the preset list by defining an
1095 * environment variable <gamename>_PRESETS whose value is a
1096 * colon-separated list of items, alternating between textual
1097 * titles in the menu and encoded parameter strings. For
1098 * example, "SOLO_PRESETS=2x3 Advanced:2x3da" would define
1099 * just one additional preset for Solo.
1104 sprintf(buf
, "%s_PRESETS", me
->ourgame
->name
);
1105 for (j
= k
= 0; buf
[j
]; j
++)
1106 if (!isspace((unsigned char)buf
[j
]))
1107 buf
[k
++] = toupper((unsigned char)buf
[j
]);
1110 if ((e
= getenv(buf
)) != NULL
) {
1112 preset_menu_add_from_user_env(me
, me
->preset_menu
, e
, TRUE
);
1118 * Finalise the menu: allocate an integer id to each entry, and
1119 * store string encodings of the presets' parameters in
1120 * me->encoded_presets.
1122 me
->n_encoded_presets
= 0;
1123 preset_menu_alloc_ids(me
, me
->preset_menu
);
1124 me
->encoded_presets
= snewn(me
->n_encoded_presets
, char *);
1125 for (i
= 0; i
< me
->n_encoded_presets
; i
++)
1126 me
->encoded_presets
[i
] = NULL
;
1127 preset_menu_encode_params(me
, me
->preset_menu
);
1130 *id_limit
= me
->n_encoded_presets
;
1131 return me
->preset_menu
;
1134 int midend_which_preset(midend
*me
)
1136 char *encoding
= me
->ourgame
->encode_params(me
->params
, TRUE
);
1140 for (i
= 0; i
< me
->n_encoded_presets
; i
++)
1141 if (me
->encoded_presets
[i
] &&
1142 !strcmp(encoding
, me
->encoded_presets
[i
])) {
1151 int midend_wants_statusbar(midend
*me
)
1153 return me
->ourgame
->wants_statusbar
;
1156 void midend_request_id_changes(midend
*me
, void (*notify
)(void *), void *ctx
)
1158 me
->game_id_change_notify_function
= notify
;
1159 me
->game_id_change_notify_ctx
= ctx
;
1162 void midend_supersede_game_desc(midend
*me
, char *desc
, char *privdesc
)
1165 sfree(me
->privdesc
);
1166 me
->desc
= dupstr(desc
);
1167 me
->privdesc
= privdesc
? dupstr(privdesc
) : NULL
;
1168 if (me
->game_id_change_notify_function
)
1169 me
->game_id_change_notify_function(me
->game_id_change_notify_ctx
);
1172 config_item
*midend_get_config(midend
*me
, int which
, char **wintitle
)
1174 char *titlebuf
, *parstr
, *rest
;
1179 titlebuf
= snewn(40 + strlen(me
->ourgame
->name
), char);
1183 sprintf(titlebuf
, "%s configuration", me
->ourgame
->name
);
1184 *wintitle
= titlebuf
;
1185 return me
->ourgame
->configure(me
->params
);
1188 if (!me
->curparams
) {
1192 sprintf(titlebuf
, "%s %s selection", me
->ourgame
->name
,
1193 which
== CFG_SEED
? "random" : "game");
1194 *wintitle
= titlebuf
;
1196 ret
= snewn(2, config_item
);
1198 ret
[0].type
= C_STRING
;
1199 if (which
== CFG_SEED
)
1200 ret
[0].name
= "Game random seed";
1202 ret
[0].name
= "Game ID";
1205 * For CFG_DESC the text going in here will be a string
1206 * encoding of the restricted parameters, plus a colon,
1207 * plus the game description. For CFG_SEED it will be the
1208 * full parameters, plus a hash, plus the random seed data.
1209 * Either of these is a valid full game ID (although only
1210 * the former is likely to persist across many code
1213 parstr
= me
->ourgame
->encode_params(me
->curparams
, which
== CFG_SEED
);
1215 if (which
== CFG_DESC
) {
1216 rest
= me
->desc
? me
->desc
: "";
1219 rest
= me
->seedstr
? me
->seedstr
: "";
1222 ret
[0].sval
= snewn(strlen(parstr
) + strlen(rest
) + 2, char);
1223 sprintf(ret
[0].sval
, "%s%c%s", parstr
, sep
, rest
);
1226 ret
[1].type
= C_END
;
1227 ret
[1].name
= ret
[1].sval
= NULL
;
1233 assert(!"We shouldn't be here");
1237 static char *midend_game_id_int(midend
*me
, char *id
, int defmode
)
1239 char *error
, *par
, *desc
, *seed
;
1240 game_params
*newcurparams
, *newparams
, *oldparams1
, *oldparams2
;
1243 seed
= strchr(id
, '#');
1244 desc
= strchr(id
, ':');
1246 if (desc
&& (!seed
|| desc
< seed
)) {
1248 * We have a colon separating parameters from game
1249 * description. So `par' now points to the parameters
1250 * string, and `desc' to the description string.
1255 } else if (seed
&& (!desc
|| seed
< desc
)) {
1257 * We have a hash separating parameters from random seed.
1258 * So `par' now points to the parameters string, and `seed'
1259 * to the seed string.
1266 * We only have one string. Depending on `defmode', we take
1267 * it to be either parameters, seed or description.
1269 if (defmode
== DEF_SEED
) {
1272 } else if (defmode
== DEF_DESC
) {
1282 * We must be reasonably careful here not to modify anything in
1283 * `me' until we have finished validating things. This function
1284 * must either return an error and do nothing to the midend, or
1285 * return success and do everything; nothing in between is
1288 newcurparams
= newparams
= oldparams1
= oldparams2
= NULL
;
1292 * The params string may underspecify the game parameters, so
1293 * we must first initialise newcurparams with a full set of
1294 * params from somewhere else before we decode_params the
1295 * input string over the top.
1297 * But which set? It depends on what other data we have.
1299 * If we've been given a _descriptive_ game id, then that may
1300 * well underspecify by design, e.g. Solo game descriptions
1301 * often start just '3x3:' without specifying one of Solo's
1302 * difficulty settings, because it isn't necessary once a game
1303 * has been generated (and you might not even know it, if
1304 * you're manually transcribing a game description). In that
1305 * situation, I've always felt that the best thing to set the
1306 * difficulty to (for use if the user hits 'New Game' after
1307 * pasting in that game id) is whatever it was previously set
1308 * to. That is, we use whatever is already in me->params as
1309 * the basis for our decoding of this input string.
1311 * A random-seed based game id, however, should use the real,
1312 * built-in default params, and not even check the
1313 * <game>_DEFAULT environment setting, because when people
1314 * paste each other random seeds - whether it's two users
1315 * arranging to generate the same game at the same time to
1316 * race solving them, or a user sending a bug report upstream
1317 * - the whole point is for the random game id to always be
1318 * interpreted the same way, even if it does underspecify.
1320 * A parameter string typed in on its own, with no seed _or_
1321 * description, gets treated the same way as a random seed,
1322 * because again I think the most likely reason for doing that
1323 * is to have a portable representation of a set of params.
1326 newcurparams
= me
->ourgame
->dup_params(me
->params
);
1328 newcurparams
= me
->ourgame
->default_params();
1330 me
->ourgame
->decode_params(newcurparams
, par
);
1331 error
= me
->ourgame
->validate_params(newcurparams
, desc
== NULL
);
1333 me
->ourgame
->free_params(newcurparams
);
1336 oldparams1
= me
->curparams
;
1339 * Now filter only the persistent parts of this state into
1340 * the long-term params structure, unless we've _only_
1341 * received a params string in which case the whole lot is
1344 oldparams2
= me
->params
;
1348 newparams
= me
->ourgame
->dup_params(me
->params
);
1350 tmpstr
= me
->ourgame
->encode_params(newcurparams
, FALSE
);
1351 me
->ourgame
->decode_params(newparams
, tmpstr
);
1355 newparams
= me
->ourgame
->dup_params(newcurparams
);
1359 newcurparams
= me
->curparams
;
1360 newparams
= me
->params
;
1361 free_params
= FALSE
;
1365 error
= me
->ourgame
->validate_desc(newparams
, desc
);
1369 me
->ourgame
->free_params(newcurparams
);
1371 me
->ourgame
->free_params(newparams
);
1378 * Now we've got past all possible error points. Update the
1381 me
->params
= newparams
;
1382 me
->curparams
= newcurparams
;
1384 me
->ourgame
->free_params(oldparams1
);
1386 me
->ourgame
->free_params(oldparams2
);
1389 sfree(me
->privdesc
);
1390 me
->desc
= me
->privdesc
= NULL
;
1395 me
->desc
= dupstr(desc
);
1396 me
->genmode
= GOT_DESC
;
1397 sfree(me
->aux_info
);
1398 me
->aux_info
= NULL
;
1402 me
->seedstr
= dupstr(seed
);
1403 me
->genmode
= GOT_SEED
;
1409 char *midend_game_id(midend
*me
, char *id
)
1411 return midend_game_id_int(me
, id
, DEF_PARAMS
);
1414 char *midend_get_game_id(midend
*me
)
1418 parstr
= me
->ourgame
->encode_params(me
->curparams
, FALSE
);
1421 ret
= snewn(strlen(parstr
) + strlen(me
->desc
) + 2, char);
1422 sprintf(ret
, "%s:%s", parstr
, me
->desc
);
1427 char *midend_get_random_seed(midend
*me
)
1434 parstr
= me
->ourgame
->encode_params(me
->curparams
, TRUE
);
1436 ret
= snewn(strlen(parstr
) + strlen(me
->seedstr
) + 2, char);
1437 sprintf(ret
, "%s#%s", parstr
, me
->seedstr
);
1442 char *midend_set_config(midend
*me
, int which
, config_item
*cfg
)
1445 game_params
*params
;
1449 params
= me
->ourgame
->custom_params(cfg
);
1450 error
= me
->ourgame
->validate_params(params
, TRUE
);
1453 me
->ourgame
->free_params(params
);
1457 me
->ourgame
->free_params(me
->params
);
1458 me
->params
= params
;
1463 error
= midend_game_id_int(me
, cfg
[0].sval
,
1464 (which
== CFG_SEED
? DEF_SEED
: DEF_DESC
));
1473 int midend_can_format_as_text_now(midend
*me
)
1475 if (me
->ourgame
->can_format_as_text_ever
)
1476 return me
->ourgame
->can_format_as_text_now(me
->params
);
1481 char *midend_text_format(midend
*me
)
1483 if (me
->ourgame
->can_format_as_text_ever
&& me
->statepos
> 0 &&
1484 me
->ourgame
->can_format_as_text_now(me
->params
))
1485 return me
->ourgame
->text_format(me
->states
[me
->statepos
-1].state
);
1490 char *midend_solve(midend
*me
)
1493 char *msg
, *movestr
;
1495 if (!me
->ourgame
->can_solve
)
1496 return "This game does not support the Solve operation";
1498 if (me
->statepos
< 1)
1499 return "No game set up to solve"; /* _shouldn't_ happen! */
1502 movestr
= me
->ourgame
->solve(me
->states
[0].state
,
1503 me
->states
[me
->statepos
-1].state
,
1504 me
->aux_info
, &msg
);
1507 msg
= "Solve operation failed"; /* _shouldn't_ happen, but can */
1510 s
= me
->ourgame
->execute_move(me
->states
[me
->statepos
-1].state
, movestr
);
1514 * Now enter the solved state as the next move.
1516 midend_stop_anim(me
);
1517 midend_purge_states(me
);
1519 me
->states
[me
->nstates
].state
= s
;
1520 me
->states
[me
->nstates
].movestr
= movestr
;
1521 me
->states
[me
->nstates
].movetype
= SOLVE
;
1522 me
->statepos
= ++me
->nstates
;
1524 me
->ourgame
->changed_state(me
->ui
,
1525 me
->states
[me
->statepos
-2].state
,
1526 me
->states
[me
->statepos
-1].state
);
1528 if (me
->ourgame
->flags
& SOLVE_ANIMATES
) {
1529 me
->oldstate
= me
->ourgame
->dup_game(me
->states
[me
->statepos
-2].state
);
1531 me
->ourgame
->anim_length(me
->states
[me
->statepos
-2].state
,
1532 me
->states
[me
->statepos
-1].state
,
1536 me
->anim_time
= 0.0;
1537 midend_finish_move(me
);
1541 midend_set_timer(me
);
1545 int midend_status(midend
*me
)
1548 * We should probably never be called when the state stack has no
1549 * states on it at all - ideally, midends should never be left in
1550 * that state for long enough to get put down and forgotten about.
1551 * But if we are, I think we return _true_ - pedantically speaking
1552 * a midend in that state is 'vacuously solved', and more
1553 * practically, a user whose midend has been left in that state
1554 * probably _does_ want the 'new game' option to be prominent.
1556 if (me
->statepos
== 0)
1559 return me
->ourgame
->status(me
->states
[me
->statepos
-1].state
);
1562 char *midend_rewrite_statusbar(midend
*me
, char *text
)
1565 * An important special case is that we are occasionally called
1566 * with our own laststatus, to update the timer.
1568 if (me
->laststatus
!= text
) {
1569 sfree(me
->laststatus
);
1570 me
->laststatus
= dupstr(text
);
1573 if (me
->ourgame
->is_timed
) {
1574 char timebuf
[100], *ret
;
1577 sec
= (int)me
->elapsed
;
1580 sprintf(timebuf
, "[%d:%02d] ", min
, sec
);
1582 ret
= snewn(strlen(timebuf
) + strlen(text
) + 1, char);
1583 strcpy(ret
, timebuf
);
1588 return dupstr(text
);
1592 #define SERIALISE_MAGIC "Simon Tatham's Portable Puzzle Collection"
1593 #define SERIALISE_VERSION "1"
1595 void midend_serialise(midend
*me
,
1596 void (*write
)(void *ctx
, void *buf
, int len
),
1602 * Each line of the save file contains three components. First
1603 * exactly 8 characters of header word indicating what type of
1604 * data is contained on the line; then a colon followed by a
1605 * decimal integer giving the length of the main string on the
1606 * line; then a colon followed by the string itself (exactly as
1607 * many bytes as previously specified, no matter what they
1608 * contain). Then a newline (of reasonably flexible form).
1610 #define wr(h,s) do { \
1614 copy_left_justified(lbuf, sizeof(lbuf), h); \
1615 sprintf(hbuf, "%s:%d:", lbuf, (int)strlen(str)); \
1616 write(wctx, hbuf, strlen(hbuf)); \
1617 write(wctx, str, strlen(str)); \
1618 write(wctx, "\n", 1); \
1622 * Magic string identifying the file, and version number of the
1625 wr("SAVEFILE", SERIALISE_MAGIC
);
1626 wr("VERSION", SERIALISE_VERSION
);
1629 * The game name. (Copied locally to avoid const annoyance.)
1632 char *s
= dupstr(me
->ourgame
->name
);
1638 * The current long-term parameters structure, in full.
1641 char *s
= me
->ourgame
->encode_params(me
->params
, TRUE
);
1647 * The current short-term parameters structure, in full.
1649 if (me
->curparams
) {
1650 char *s
= me
->ourgame
->encode_params(me
->curparams
, TRUE
);
1656 * The current game description, the privdesc, and the random seed.
1659 wr("SEED", me
->seedstr
);
1661 wr("DESC", me
->desc
);
1663 wr("PRIVDESC", me
->privdesc
);
1666 * The game's aux_info. We obfuscate this to prevent spoilers
1667 * (people are likely to run `head' or similar on a saved game
1668 * file simply to find out what it is, and don't necessarily
1669 * want to be told the answer to the puzzle!)
1676 len
= strlen(me
->aux_info
);
1677 s1
= snewn(len
, unsigned char);
1678 memcpy(s1
, me
->aux_info
, len
);
1679 obfuscate_bitmap(s1
, len
*8, FALSE
);
1680 s2
= bin2hex(s1
, len
);
1689 * Any required serialisation of the game_ui.
1692 char *s
= me
->ourgame
->encode_ui(me
->ui
);
1700 * The game time, if it's a timed game.
1702 if (me
->ourgame
->is_timed
) {
1704 sprintf(buf
, "%g", me
->elapsed
);
1709 * The length of, and position in, the states list.
1713 sprintf(buf
, "%d", me
->nstates
);
1715 sprintf(buf
, "%d", me
->statepos
);
1716 wr("STATEPOS", buf
);
1720 * For each state after the initial one (which we know is
1721 * constructed from either privdesc or desc), enough
1722 * information for execute_move() to reconstruct it from the
1725 for (i
= 1; i
< me
->nstates
; i
++) {
1726 assert(me
->states
[i
].movetype
!= NEWGAME
); /* only state 0 */
1727 switch (me
->states
[i
].movetype
) {
1729 wr("MOVE", me
->states
[i
].movestr
);
1732 wr("SOLVE", me
->states
[i
].movestr
);
1735 wr("RESTART", me
->states
[i
].movestr
);
1744 * This function returns NULL on success, or an error message.
1746 char *midend_deserialise(midend
*me
,
1747 int (*read
)(void *ctx
, void *buf
, int len
),
1750 int nstates
= 0, statepos
= -1, gotstates
= 0;
1751 int started
= FALSE
;
1755 /* Initially all errors give the same report */
1756 char *ret
= "Data does not appear to be a saved game file";
1759 * We construct all the new state in local variables while we
1760 * check its sanity. Only once we have finished reading the
1761 * serialised data and detected no errors at all do we start
1762 * modifying stuff in the midend passed in.
1764 char *seed
= NULL
, *parstr
= NULL
, *desc
= NULL
, *privdesc
= NULL
;
1765 char *auxinfo
= NULL
, *uistr
= NULL
, *cparstr
= NULL
;
1766 float elapsed
= 0.0F
;
1767 game_params
*params
= NULL
, *cparams
= NULL
;
1769 struct midend_state_entry
*states
= NULL
;
1772 * Loop round and round reading one key/value pair at a time
1773 * from the serialised stream, until we have enough game states
1776 while (nstates
<= 0 || statepos
< 0 || gotstates
< nstates
-1) {
1781 if (!read(rctx
, key
, 1)) {
1782 /* unexpected EOF */
1785 } while (key
[0] == '\r' || key
[0] == '\n');
1787 if (!read(rctx
, key
+1, 8)) {
1788 /* unexpected EOF */
1792 if (key
[8] != ':') {
1794 ret
= "Data was incorrectly formatted for a saved game file";
1797 len
= strcspn(key
, ": ");
1803 if (!read(rctx
, &c
, 1)) {
1804 /* unexpected EOF */
1810 } else if (c
>= '0' && c
<= '9') {
1811 len
= (len
* 10) + (c
- '0');
1814 ret
= "Data was incorrectly formatted for a"
1820 val
= snewn(len
+1, char);
1821 if (!read(rctx
, val
, len
)) {
1828 if (strcmp(key
, "SAVEFILE") || strcmp(val
, SERIALISE_MAGIC
)) {
1829 /* ret already has the right message in it */
1832 /* Now most errors are this one, unless otherwise specified */
1833 ret
= "Saved data ended unexpectedly";
1836 if (!strcmp(key
, "VERSION")) {
1837 if (strcmp(val
, SERIALISE_VERSION
)) {
1838 ret
= "Cannot handle this version of the saved game"
1842 } else if (!strcmp(key
, "GAME")) {
1843 if (strcmp(val
, me
->ourgame
->name
)) {
1844 ret
= "Save file is from a different game";
1847 } else if (!strcmp(key
, "PARAMS")) {
1851 } else if (!strcmp(key
, "CPARAMS")) {
1855 } else if (!strcmp(key
, "SEED")) {
1859 } else if (!strcmp(key
, "DESC")) {
1863 } else if (!strcmp(key
, "PRIVDESC")) {
1867 } else if (!strcmp(key
, "AUXINFO")) {
1869 int len
= strlen(val
) / 2; /* length in bytes */
1870 tmp
= hex2bin(val
, len
);
1871 obfuscate_bitmap(tmp
, len
*8, TRUE
);
1874 auxinfo
= snewn(len
+ 1, char);
1875 memcpy(auxinfo
, tmp
, len
);
1876 auxinfo
[len
] = '\0';
1878 } else if (!strcmp(key
, "UI")) {
1882 } else if (!strcmp(key
, "TIME")) {
1883 elapsed
= (float)atof(val
);
1884 } else if (!strcmp(key
, "NSTATES")) {
1885 nstates
= atoi(val
);
1887 ret
= "Number of states in save file was negative";
1891 ret
= "Two state counts provided in save file";
1894 states
= snewn(nstates
, struct midend_state_entry
);
1895 for (i
= 0; i
< nstates
; i
++) {
1896 states
[i
].state
= NULL
;
1897 states
[i
].movestr
= NULL
;
1898 states
[i
].movetype
= NEWGAME
;
1900 } else if (!strcmp(key
, "STATEPOS")) {
1901 statepos
= atoi(val
);
1902 } else if (!strcmp(key
, "MOVE")) {
1904 states
[gotstates
].movetype
= MOVE
;
1905 states
[gotstates
].movestr
= val
;
1907 } else if (!strcmp(key
, "SOLVE")) {
1909 states
[gotstates
].movetype
= SOLVE
;
1910 states
[gotstates
].movestr
= val
;
1912 } else if (!strcmp(key
, "RESTART")) {
1914 states
[gotstates
].movetype
= RESTART
;
1915 states
[gotstates
].movestr
= val
;
1924 params
= me
->ourgame
->default_params();
1925 me
->ourgame
->decode_params(params
, parstr
);
1926 if (me
->ourgame
->validate_params(params
, TRUE
)) {
1927 ret
= "Long-term parameters in save file are invalid";
1930 cparams
= me
->ourgame
->default_params();
1931 me
->ourgame
->decode_params(cparams
, cparstr
);
1932 if (me
->ourgame
->validate_params(cparams
, FALSE
)) {
1933 ret
= "Short-term parameters in save file are invalid";
1936 if (seed
&& me
->ourgame
->validate_params(cparams
, TRUE
)) {
1938 * The seed's no use with this version, but we can perfectly
1939 * well use the rest of the data.
1945 ret
= "Game description in save file is missing";
1947 } else if (me
->ourgame
->validate_desc(params
, desc
)) {
1948 ret
= "Game description in save file is invalid";
1951 if (privdesc
&& me
->ourgame
->validate_desc(params
, privdesc
)) {
1952 ret
= "Game private description in save file is invalid";
1955 if (statepos
< 0 || statepos
>= nstates
) {
1956 ret
= "Game position in save file is out of range";
1959 states
[0].state
= me
->ourgame
->new_game(me
, params
,
1960 privdesc
? privdesc
: desc
);
1961 for (i
= 1; i
< nstates
; i
++) {
1962 assert(states
[i
].movetype
!= NEWGAME
);
1963 switch (states
[i
].movetype
) {
1966 states
[i
].state
= me
->ourgame
->execute_move(states
[i
-1].state
,
1968 if (states
[i
].state
== NULL
) {
1969 ret
= "Save file contained an invalid move";
1974 if (me
->ourgame
->validate_desc(params
, states
[i
].movestr
)) {
1975 ret
= "Save file contained an invalid restart move";
1978 states
[i
].state
= me
->ourgame
->new_game(me
, params
,
1984 ui
= me
->ourgame
->new_ui(states
[0].state
);
1985 me
->ourgame
->decode_ui(ui
, uistr
);
1988 * Now we've run out of possible error conditions, so we're
1989 * ready to start overwriting the real data in the current
1990 * midend. We'll do this by swapping things with the local
1991 * variables, so that the same cleanup code will free the old
2002 me
->privdesc
= privdesc
;
2010 me
->aux_info
= auxinfo
;
2014 me
->genmode
= GOT_NOTHING
;
2016 me
->statesize
= nstates
;
2017 nstates
= me
->nstates
;
2018 me
->nstates
= me
->statesize
;
2020 struct midend_state_entry
*tmp
;
2022 me
->states
= states
;
2025 me
->statepos
= statepos
;
2031 me
->params
= params
;
2034 tmp
= me
->curparams
;
2035 me
->curparams
= cparams
;
2039 me
->oldstate
= NULL
;
2040 me
->anim_time
= me
->anim_pos
= me
->flash_time
= me
->flash_pos
= 0.0F
;
2051 me
->elapsed
= elapsed
;
2052 me
->pressed_mouse_button
= 0;
2054 midend_set_timer(me
);
2057 me
->ourgame
->free_drawstate(me
->drawing
, me
->drawstate
);
2059 me
->ourgame
->new_drawstate(me
->drawing
,
2060 me
->states
[me
->statepos
-1].state
);
2061 midend_size_new_drawstate(me
);
2063 ret
= NULL
; /* success! */
2075 me
->ourgame
->free_params(params
);
2077 me
->ourgame
->free_params(cparams
);
2079 me
->ourgame
->free_ui(ui
);
2083 for (i
= 0; i
< nstates
; i
++) {
2084 if (states
[i
].state
)
2085 me
->ourgame
->free_game(states
[i
].state
);
2086 sfree(states
[i
].movestr
);
2095 * This function examines a saved game file just far enough to
2096 * determine which game type it contains. It returns NULL on success
2097 * and the game name string in 'name' (which will be dynamically
2098 * allocated and should be caller-freed), or an error message on
2101 char *identify_game(char **name
, int (*read
)(void *ctx
, void *buf
, int len
),
2104 int nstates
= 0, statepos
= -1, gotstates
= 0;
2105 int started
= FALSE
;
2108 /* Initially all errors give the same report */
2109 char *ret
= "Data does not appear to be a saved game file";
2114 * Loop round and round reading one key/value pair at a time from
2115 * the serialised stream, until we've found the game name.
2117 while (nstates
<= 0 || statepos
< 0 || gotstates
< nstates
-1) {
2122 if (!read(rctx
, key
, 1)) {
2123 /* unexpected EOF */
2126 } while (key
[0] == '\r' || key
[0] == '\n');
2128 if (!read(rctx
, key
+1, 8)) {
2129 /* unexpected EOF */
2133 if (key
[8] != ':') {
2135 ret
= "Data was incorrectly formatted for a saved game file";
2138 len
= strcspn(key
, ": ");
2144 if (!read(rctx
, &c
, 1)) {
2145 /* unexpected EOF */
2151 } else if (c
>= '0' && c
<= '9') {
2152 len
= (len
* 10) + (c
- '0');
2155 ret
= "Data was incorrectly formatted for a"
2161 val
= snewn(len
+1, char);
2162 if (!read(rctx
, val
, len
)) {
2169 if (strcmp(key
, "SAVEFILE") || strcmp(val
, SERIALISE_MAGIC
)) {
2170 /* ret already has the right message in it */
2173 /* Now most errors are this one, unless otherwise specified */
2174 ret
= "Saved data ended unexpectedly";
2177 if (!strcmp(key
, "VERSION")) {
2178 if (strcmp(val
, SERIALISE_VERSION
)) {
2179 ret
= "Cannot handle this version of the saved game"
2183 } else if (!strcmp(key
, "GAME")) {
2184 *name
= dupstr(val
);
2199 char *midend_print_puzzle(midend
*me
, document
*doc
, int with_soln
)
2201 game_state
*soln
= NULL
;
2203 if (me
->statepos
< 1)
2204 return "No game set up to print";/* _shouldn't_ happen! */
2207 char *msg
, *movestr
;
2209 if (!me
->ourgame
->can_solve
)
2210 return "This game does not support the Solve operation";
2212 msg
= "Solve operation failed";/* game _should_ overwrite on error */
2213 movestr
= me
->ourgame
->solve(me
->states
[0].state
,
2214 me
->states
[me
->statepos
-1].state
,
2215 me
->aux_info
, &msg
);
2218 soln
= me
->ourgame
->execute_move(me
->states
[me
->statepos
-1].state
,
2227 * This call passes over ownership of the two game_states and
2228 * the game_params. Hence we duplicate the ones we want to
2229 * keep, and we don't have to bother freeing soln if it was
2232 document_add_puzzle(doc
, me
->ourgame
,
2233 me
->ourgame
->dup_params(me
->curparams
),
2234 me
->ourgame
->dup_game(me
->states
[0].state
), soln
);