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/restart/quit.
14 enum { DEF_PARAMS
, DEF_SEED
, DEF_DESC
}; /* for midend_game_id_int */
22 game_aux_info
*aux_info
;
23 enum { GOT_SEED
, GOT_DESC
, GOT_NOTHING
} genmode
;
24 int nstates
, statesize
, statepos
;
26 game_params
**presets
;
28 int npresets
, presetsize
;
30 game_params
*params
, *tmpparams
;
32 game_drawstate
*drawstate
;
35 float anim_time
, anim_pos
;
36 float flash_time
, flash_pos
;
39 int pressed_mouse_button
;
42 #define ensure(me) do { \
43 if ((me)->nstates >= (me)->statesize) { \
44 (me)->statesize = (me)->nstates + 128; \
45 (me)->states = sresize((me)->states, (me)->statesize, game_state *); \
49 midend_data
*midend_new(frontend
*fe
, const game
*ourgame
)
51 midend_data
*me
= snew(midend_data
);
55 get_random_seed(&randseed
, &randseedsize
);
58 me
->ourgame
= ourgame
;
59 me
->random
= random_init(randseed
, randseedsize
);
60 me
->nstates
= me
->statesize
= me
->statepos
= 0;
62 me
->params
= ourgame
->default_params();
67 me
->genmode
= GOT_NOTHING
;
71 me
->preset_names
= NULL
;
72 me
->npresets
= me
->presetsize
= 0;
73 me
->anim_time
= me
->anim_pos
= 0.0F
;
74 me
->flash_time
= me
->flash_pos
= 0.0F
;
77 me
->pressed_mouse_button
= 0;
84 void midend_free(midend_data
*me
)
89 random_free(me
->random
);
91 me
->ourgame
->free_aux_info(me
->aux_info
);
92 me
->ourgame
->free_params(me
->params
);
94 me
->ourgame
->free_params(me
->tmpparams
);
98 void midend_size(midend_data
*me
, int *x
, int *y
)
100 me
->ourgame
->size(me
->params
, x
, y
);
103 void midend_set_params(midend_data
*me
, game_params
*params
)
105 me
->ourgame
->free_params(me
->params
);
106 me
->params
= me
->ourgame
->dup_params(params
);
109 void midend_new_game(midend_data
*me
)
111 while (me
->nstates
> 0)
112 me
->ourgame
->free_game(me
->states
[--me
->nstates
]);
115 me
->ourgame
->free_drawstate(me
->drawstate
);
117 assert(me
->nstates
== 0);
119 if (me
->genmode
== GOT_DESC
) {
120 me
->genmode
= GOT_NOTHING
;
124 if (me
->genmode
== GOT_SEED
) {
125 me
->genmode
= GOT_NOTHING
;
128 * Generate a new random seed. 15 digits comes to about
129 * 48 bits, which should be more than enough.
134 for (i
= 0; i
< 15; i
++)
135 newseed
[i
] = '0' + random_upto(me
->random
, 10);
137 me
->seedstr
= dupstr(newseed
);
142 me
->ourgame
->free_aux_info(me
->aux_info
);
145 rs
= random_init(me
->seedstr
, strlen(me
->seedstr
));
146 me
->desc
= me
->ourgame
->new_desc
147 (me
->tmpparams ? me
->tmpparams
: me
->params
, rs
, &me
->aux_info
);
151 me
->ourgame
->free_params(me
->tmpparams
);
152 me
->tmpparams
= NULL
;
157 me
->states
[me
->nstates
++] = me
->ourgame
->new_game(me
->params
, me
->desc
);
159 me
->drawstate
= me
->ourgame
->new_drawstate(me
->states
[0]);
161 me
->ourgame
->free_ui(me
->ui
);
162 me
->ui
= me
->ourgame
->new_ui(me
->states
[0]);
163 me
->pressed_mouse_button
= 0;
166 void midend_restart_game(midend_data
*me
)
168 while (me
->nstates
> 1)
169 me
->ourgame
->free_game(me
->states
[--me
->nstates
]);
170 me
->statepos
= me
->nstates
;
171 me
->ourgame
->free_ui(me
->ui
);
172 me
->ui
= me
->ourgame
->new_ui(me
->states
[0]);
175 static int midend_undo(midend_data
*me
)
177 if (me
->statepos
> 1) {
185 static int midend_redo(midend_data
*me
)
187 if (me
->statepos
< me
->nstates
) {
195 static void midend_finish_move(midend_data
*me
)
199 if (me
->oldstate
|| me
->statepos
> 1) {
200 flashtime
= me
->ourgame
->flash_length(me
->oldstate ? me
->oldstate
:
201 me
->states
[me
->statepos
-2],
202 me
->states
[me
->statepos
-1],
203 me
->oldstate ? me
->dir
: +1);
205 me
->flash_pos
= 0.0F
;
206 me
->flash_time
= flashtime
;
211 me
->ourgame
->free_game(me
->oldstate
);
213 me
->anim_pos
= me
->anim_time
= 0;
216 if (me
->flash_time
== 0 && me
->anim_time
== 0)
217 deactivate_timer(me
->frontend
);
219 activate_timer(me
->frontend
);
222 static void midend_stop_anim(midend_data
*me
)
224 if (me
->oldstate
|| me
->anim_time
) {
225 midend_finish_move(me
);
230 static int midend_really_process_key(midend_data
*me
, int x
, int y
, int button
)
232 game_state
*oldstate
= me
->ourgame
->dup_game(me
->states
[me
->statepos
- 1]);
235 if (button
== 'n' || button
== 'N' || button
== '\x0E') {
236 midend_stop_anim(me
);
239 return 1; /* never animate */
240 } else if (button
== 'r' || button
== 'R') {
241 midend_stop_anim(me
);
242 midend_restart_game(me
);
244 return 1; /* never animate */
245 } else if (button
== 'u' || button
== 'u' ||
246 button
== '\x1A' || button
== '\x1F') {
247 midend_stop_anim(me
);
248 if (!midend_undo(me
))
250 } else if (button
== '\x12') {
251 midend_stop_anim(me
);
252 if (!midend_redo(me
))
254 } else if (button
== 'q' || button
== 'Q' || button
== '\x11') {
255 me
->ourgame
->free_game(oldstate
);
258 game_state
*s
= me
->ourgame
->make_move(me
->states
[me
->statepos
-1],
259 me
->ui
, x
, y
, button
);
261 if (s
== me
->states
[me
->statepos
-1]) {
263 * make_move() is allowed to return its input state to
264 * indicate that although no move has been made, the UI
265 * state has been updated and a redraw is called for.
270 midend_stop_anim(me
);
271 while (me
->nstates
> me
->statepos
)
272 me
->ourgame
->free_game(me
->states
[--me
->nstates
]);
274 me
->states
[me
->nstates
] = s
;
275 me
->statepos
= ++me
->nstates
;
278 me
->ourgame
->free_game(oldstate
);
284 * See if this move requires an animation.
286 anim_time
= me
->ourgame
->anim_length(oldstate
, me
->states
[me
->statepos
-1],
289 me
->oldstate
= oldstate
;
291 me
->anim_time
= anim_time
;
294 midend_finish_move(me
);
300 activate_timer(me
->frontend
);
305 int midend_process_key(midend_data
*me
, int x
, int y
, int button
)
310 * Harmonise mouse drag and release messages.
312 * Some front ends might accidentally switch from sending, say,
313 * RIGHT_DRAG messages to sending LEFT_DRAG, half way through a
314 * drag. (This can happen on the Mac, for example, since
315 * RIGHT_DRAG is usually done using Command+drag, and if the
316 * user accidentally releases Command half way through the drag
317 * then there will be trouble.)
319 * It would be an O(number of front ends) annoyance to fix this
320 * in the front ends, but an O(number of back ends) annoyance
321 * to have each game capable of dealing with it. Therefore, we
322 * fix it _here_ in the common midend code so that it only has
325 * The possible ways in which things can go screwy in the front
328 * - in a system containing multiple physical buttons button
329 * presses can inadvertently overlap. We can see ABab (caps
330 * meaning button-down and lowercase meaning button-up) when
331 * the user had semantically intended AaBb.
333 * - in a system where one button is simulated by means of a
334 * modifier key and another button, buttons can mutate
335 * between press and release (possibly during drag). So we
336 * can see Ab instead of Aa.
338 * Definite requirements are:
340 * - button _presses_ must never be invented or destroyed. If
341 * the user presses two buttons in succession, the button
342 * presses must be transferred to the backend unchanged. So
343 * if we see AaBb , that's fine; if we see ABab (the button
344 * presses inadvertently overlapped) we must somehow
345 * `correct' it to AaBb.
347 * - every mouse action must end up looking like a press, zero
348 * or more drags, then a release. This allows back ends to
349 * make the _assumption_ that incoming mouse data will be
350 * sane in this regard, and not worry about the details.
352 * So my policy will be:
354 * - treat any button-up as a button-up for the currently
355 * pressed button, or ignore it if there is no currently
358 * - treat any drag as a drag for the currently pressed
359 * button, or ignore it if there is no currently pressed
362 * - if we see a button-down while another button is currently
363 * pressed, invent a button-up for the first one and then
364 * pass the button-down through as before.
367 if (IS_MOUSE_DRAG(button
) || IS_MOUSE_RELEASE(button
)) {
368 if (me
->pressed_mouse_button
) {
369 if (IS_MOUSE_DRAG(button
)) {
370 button
= me
->pressed_mouse_button
+
371 (LEFT_DRAG
- LEFT_BUTTON
);
373 button
= me
->pressed_mouse_button
+
374 (LEFT_RELEASE
- LEFT_BUTTON
);
377 return ret
; /* ignore it */
378 } else if (IS_MOUSE_DOWN(button
) && me
->pressed_mouse_button
) {
380 * Fabricate a button-up for the previously pressed button.
382 ret
= ret
&& midend_really_process_key
383 (me
, x
, y
, (me
->pressed_mouse_button
+
384 (LEFT_RELEASE
- LEFT_BUTTON
)));
388 * Now send on the event we originally received.
390 ret
= ret
&& midend_really_process_key(me
, x
, y
, button
);
393 * And update the currently pressed button.
395 if (IS_MOUSE_RELEASE(button
))
396 me
->pressed_mouse_button
= 0;
397 else if (IS_MOUSE_DOWN(button
))
398 me
->pressed_mouse_button
= button
;
403 void midend_redraw(midend_data
*me
)
405 if (me
->statepos
> 0 && me
->drawstate
) {
406 start_draw(me
->frontend
);
407 if (me
->oldstate
&& me
->anim_time
> 0 &&
408 me
->anim_pos
< me
->anim_time
) {
409 assert(me
->dir
!= 0);
410 me
->ourgame
->redraw(me
->frontend
, me
->drawstate
, me
->oldstate
,
411 me
->states
[me
->statepos
-1], me
->dir
,
412 me
->ui
, me
->anim_pos
, me
->flash_pos
);
414 me
->ourgame
->redraw(me
->frontend
, me
->drawstate
, NULL
,
415 me
->states
[me
->statepos
-1], +1 /*shrug*/,
416 me
->ui
, 0.0, me
->flash_pos
);
418 end_draw(me
->frontend
);
422 void midend_timer(midend_data
*me
, float tplus
)
424 me
->anim_pos
+= tplus
;
425 if (me
->anim_pos
>= me
->anim_time
||
426 me
->anim_time
== 0 || !me
->oldstate
) {
427 if (me
->anim_time
> 0)
428 midend_finish_move(me
);
430 me
->flash_pos
+= tplus
;
431 if (me
->flash_pos
>= me
->flash_time
|| me
->flash_time
== 0) {
432 me
->flash_pos
= me
->flash_time
= 0;
434 if (me
->flash_time
== 0 && me
->anim_time
== 0)
435 deactivate_timer(me
->frontend
);
439 float *midend_colours(midend_data
*me
, int *ncolours
)
441 game_state
*state
= NULL
;
444 if (me
->nstates
== 0) {
445 game_aux_info
*aux
= NULL
;
446 char *desc
= me
->ourgame
->new_desc(me
->params
, me
->random
, &aux
);
447 state
= me
->ourgame
->new_game(me
->params
, desc
);
450 me
->ourgame
->free_aux_info(aux
);
452 state
= me
->states
[0];
454 ret
= me
->ourgame
->colours(me
->frontend
, state
, ncolours
);
456 if (me
->nstates
== 0)
457 me
->ourgame
->free_game(state
);
462 int midend_num_presets(midend_data
*me
)
468 while (me
->ourgame
->fetch_preset(me
->npresets
, &name
, &preset
)) {
469 if (me
->presetsize
<= me
->npresets
) {
470 me
->presetsize
= me
->npresets
+ 10;
471 me
->presets
= sresize(me
->presets
, me
->presetsize
,
473 me
->preset_names
= sresize(me
->preset_names
, me
->presetsize
,
477 me
->presets
[me
->npresets
] = preset
;
478 me
->preset_names
[me
->npresets
] = name
;
486 void midend_fetch_preset(midend_data
*me
, int n
,
487 char **name
, game_params
**params
)
489 assert(n
>= 0 && n
< me
->npresets
);
490 *name
= me
->preset_names
[n
];
491 *params
= me
->presets
[n
];
494 int midend_wants_statusbar(midend_data
*me
)
496 return me
->ourgame
->wants_statusbar();
499 config_item
*midend_get_config(midend_data
*me
, int which
, char **wintitle
)
501 char *titlebuf
, *parstr
;
504 titlebuf
= snewn(40 + strlen(me
->ourgame
->name
), char);
508 sprintf(titlebuf
, "%s configuration", me
->ourgame
->name
);
509 *wintitle
= dupstr(titlebuf
);
510 return me
->ourgame
->configure(me
->params
);
513 sprintf(titlebuf
, "%s %s selection", me
->ourgame
->name
,
514 which
== CFG_SEED ?
"random" : "game");
515 *wintitle
= dupstr(titlebuf
);
517 ret
= snewn(2, config_item
);
519 ret
[0].type
= C_STRING
;
520 if (which
== CFG_SEED
)
521 ret
[0].name
= "Game random seed";
523 ret
[0].name
= "Game ID";
526 * For CFG_DESC the text going in here will be a string
527 * encoding of the restricted parameters, plus a colon,
528 * plus the game description. For CFG_SEED it will be the
529 * full parameters, plus a hash, plus the random seed data.
530 * Either of these is a valid full game ID (although only
531 * the former is likely to persist across many code
534 parstr
= me
->ourgame
->encode_params(me
->params
, which
== CFG_SEED
);
535 if (which
== CFG_DESC
) {
536 ret
[0].sval
= snewn(strlen(parstr
) + strlen(me
->desc
) + 2, char);
537 sprintf(ret
[0].sval
, "%s:%s", parstr
, me
->desc
);
538 } else if (me
->seedstr
) {
539 ret
[0].sval
= snewn(strlen(parstr
) + strlen(me
->seedstr
) + 2, char);
540 sprintf(ret
[0].sval
, "%s#%s", parstr
, me
->seedstr
);
543 * If the current game was not randomly generated, the
544 * best we can do is to give a template for typing a
547 ret
[0].sval
= snewn(strlen(parstr
) + 2, char);
548 sprintf(ret
[0].sval
, "%s#", parstr
);
553 ret
[1].name
= ret
[1].sval
= NULL
;
559 assert(!"We shouldn't be here");
563 static char *midend_game_id_int(midend_data
*me
, char *id
, int defmode
)
565 char *error
, *par
, *desc
, *seed
;
567 seed
= strchr(id
, '#');
568 desc
= strchr(id
, ':');
570 if (desc
&& (!seed
|| desc
< seed
)) {
572 * We have a colon separating parameters from game
573 * description. So `par' now points to the parameters
574 * string, and `desc' to the description string.
579 } else if (seed
&& (!desc
|| seed
< desc
)) {
581 * We have a hash separating parameters from random seed.
582 * So `par' now points to the parameters string, and `seed'
583 * to the seed string.
590 * We only have one string. Depending on `defmode', we take
591 * it to be either parameters, seed or description.
593 if (defmode
== DEF_SEED
) {
596 } else if (defmode
== DEF_DESC
) {
606 game_params
*tmpparams
;
607 tmpparams
= me
->ourgame
->dup_params(me
->params
);
608 me
->ourgame
->decode_params(tmpparams
, par
);
609 error
= me
->ourgame
->validate_params(tmpparams
);
611 me
->ourgame
->free_params(tmpparams
);
615 me
->ourgame
->free_params(me
->tmpparams
);
616 me
->tmpparams
= tmpparams
;
619 * Now filter only the persistent parts of this state into
620 * the long-term params structure, unless we've _only_
621 * received a params string in which case the whole lot is
625 char *tmpstr
= me
->ourgame
->encode_params(tmpparams
, FALSE
);
626 me
->ourgame
->decode_params(me
->params
, tmpstr
);
628 me
->ourgame
->free_params(me
->params
);
629 me
->params
= me
->ourgame
->dup_params(tmpparams
);
634 error
= me
->ourgame
->validate_desc(me
->params
, desc
);
639 me
->desc
= dupstr(desc
);
640 me
->genmode
= GOT_DESC
;
642 me
->ourgame
->free_aux_info(me
->aux_info
);
648 me
->seedstr
= dupstr(seed
);
649 me
->genmode
= GOT_SEED
;
655 char *midend_game_id(midend_data
*me
, char *id
)
657 return midend_game_id_int(me
, id
, DEF_PARAMS
);
660 char *midend_set_config(midend_data
*me
, int which
, config_item
*cfg
)
667 params
= me
->ourgame
->custom_params(cfg
);
668 error
= me
->ourgame
->validate_params(params
);
671 me
->ourgame
->free_params(params
);
675 me
->ourgame
->free_params(me
->params
);
681 error
= midend_game_id_int(me
, cfg
[0].sval
,
682 (which
== CFG_SEED ? DEF_SEED
: DEF_DESC
));
691 char *midend_text_format(midend_data
*me
)
693 if (me
->ourgame
->can_format_as_text
&& me
->statepos
> 0)
694 return me
->ourgame
->text_format(me
->states
[me
->statepos
-1]);
699 char *midend_solve(midend_data
*me
)
704 if (!me
->ourgame
->can_solve
)
705 return "This game does not support the Solve operation";
707 if (me
->statepos
< 1)
708 return "No game set up to solve"; /* _shouldn't_ happen! */
710 msg
= "Solve operation failed"; /* game _should_ overwrite on error */
711 s
= me
->ourgame
->solve(me
->states
[0], me
->aux_info
, &msg
);
716 * Now enter the solved state as the next move.~|~
718 midend_stop_anim(me
);
719 while (me
->nstates
> me
->statepos
)
720 me
->ourgame
->free_game(me
->states
[--me
->nstates
]);
722 me
->states
[me
->nstates
] = s
;
723 me
->statepos
= ++me
->nstates
;
725 midend_finish_move(me
);
727 activate_timer(me
->frontend
);