X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/puzzles/blobdiff_plain/cb64d2dd666eb5d02c6bfd8dcb151fd0a16196d6..aafaa7fbd122f8109c1a7d99faf74d4892bf22de:/midend.c diff --git a/midend.c b/midend.c index a764c77..4c28ec8 100644 --- a/midend.c +++ b/midend.c @@ -78,7 +78,7 @@ struct midend_data { int pressed_mouse_button; - int winwidth, winheight; + int tilesize, winwidth, winheight; }; #define ensure(me) do { \ @@ -121,7 +121,7 @@ midend_data *midend_new(frontend *fe, const game *ourgame) me->laststatus = NULL; me->timing = FALSE; me->elapsed = 0.0F; - me->winwidth = me->winheight = 0; + me->tilesize = me->winwidth = me->winheight = 0; sfree(randseed); @@ -169,11 +169,63 @@ void midend_free(midend_data *me) sfree(me); } +static void midend_size_new_drawstate(midend_data *me) +{ + /* + * Don't even bother, if we haven't worked out our tile size + * anyway yet. + */ + if (me->tilesize > 0) { + me->ourgame->compute_size(me->params, me->tilesize, + &me->winwidth, &me->winheight); + me->ourgame->set_size(me->drawstate, me->params, me->tilesize); + } +} + void midend_size(midend_data *me, int *x, int *y, int expand) { - me->ourgame->size(me->params, me->drawstate, x, y, expand); - me->winwidth = *x; - me->winheight = *y; + int min, max; + int rx, ry; + + /* + * Find the tile size that best fits within the given space. If + * `expand' is TRUE, we must actually find the _largest_ such + * tile size; otherwise, we bound above at the game's preferred + * tile size. + */ + if (expand) { + max = 1; + do { + max *= 2; + me->ourgame->compute_size(me->params, max, &rx, &ry); + } while (rx <= *x && ry <= *y); + } else + max = me->ourgame->preferred_tilesize + 1; + min = 1; + + /* + * Now binary-search between min and max. We're looking for a + * boundary rather than a value: the point at which tile sizes + * stop fitting within the given dimensions. Thus, we stop when + * max and min differ by exactly 1. + */ + while (max - min > 1) { + int mid = (max + min) / 2; + me->ourgame->compute_size(me->params, mid, &rx, &ry); + if (rx <= *x && ry <= *y) + min = mid; + else + max = mid; + } + + /* + * Now `min' is a valid size, and `max' isn't. So use `min'. + */ + + me->tilesize = min; + midend_size_new_drawstate(me); + *x = me->winwidth; + *y = me->winheight; } void midend_set_params(midend_data *me, game_params *params) @@ -185,19 +237,14 @@ void midend_set_params(midend_data *me, game_params *params) static void midend_set_timer(midend_data *me) { me->timing = (me->ourgame->is_timed && - me->ourgame->timing_state(me->states[me->statepos-1].state)); + me->ourgame->timing_state(me->states[me->statepos-1].state, + me->ui)); if (me->timing || me->flash_time || me->anim_time) activate_timer(me->frontend); else deactivate_timer(me->frontend); } -static void midend_size_new_drawstate(midend_data *me) -{ - me->ourgame->size(me->params, me->drawstate, &me->winwidth, &me->winheight, - TRUE); -} - void midend_force_redraw(midend_data *me) { if (me->drawstate) @@ -265,10 +312,10 @@ void midend_new_game(midend_data *me) me->drawstate = me->ourgame->new_drawstate(me->states[0].state); midend_size_new_drawstate(me); me->elapsed = 0.0F; - midend_set_timer(me); if (me->ui) me->ourgame->free_ui(me->ui); me->ui = me->ourgame->new_ui(me->states[0].state); + midend_set_timer(me); me->pressed_mouse_button = 0; } @@ -335,7 +382,7 @@ static void midend_finish_move(midend_data *me) void midend_stop_anim(midend_data *me) { - if (me->oldstate || me->anim_time) { + if (me->oldstate || me->anim_time != 0) { midend_finish_move(me); midend_redraw(me); } @@ -385,39 +432,40 @@ static int midend_really_process_key(midend_data *me, int x, int y, int button) { game_state *oldstate = me->ourgame->dup_game(me->states[me->statepos - 1].state); - int special = FALSE, gotspecial = FALSE, ret = 1; + int type = MOVE, gottype = FALSE, ret = 1; float anim_time; + game_state *s; + char *movestr; + + movestr = + me->ourgame->interpret_move(me->states[me->statepos-1].state, + me->ui, me->drawstate, x, y, button); - if (button == 'n' || button == 'N' || button == '\x0E') { - midend_stop_anim(me); - midend_new_game(me); - midend_redraw(me); - goto done; /* never animate */ - } else if (button == 'u' || button == 'u' || - button == '\x1A' || button == '\x1F') { - midend_stop_anim(me); - special = special(me->states[me->statepos-1].movetype); - gotspecial = TRUE; - if (!midend_undo(me)) + if (!movestr) { + if (button == 'n' || button == 'N' || button == '\x0E') { + midend_stop_anim(me); + midend_new_game(me); + midend_redraw(me); + goto done; /* never animate */ + } else if (button == 'u' || button == 'u' || + button == '\x1A' || button == '\x1F') { + midend_stop_anim(me); + type = me->states[me->statepos-1].movetype; + gottype = TRUE; + if (!midend_undo(me)) + goto done; + } else if (button == 'r' || button == 'R' || + button == '\x12' || button == '\x19') { + midend_stop_anim(me); + if (!midend_redo(me)) + goto done; + } else if (button == 'q' || button == 'Q' || button == '\x11') { + ret = 0; + goto done; + } else goto done; - } else if (button == 'r' || button == 'R' || - button == '\x12' || button == '\x19') { - midend_stop_anim(me); - if (!midend_redo(me)) - goto done; - } else if (button == 'q' || button == 'Q' || button == '\x11') { - ret = 0; - goto done; } else { - game_state *s; - char *movestr; - - movestr = - me->ourgame->interpret_move(me->states[me->statepos-1].state, - me->ui, me->drawstate, x, y, button); - if (!movestr) - s = NULL; - else if (!*movestr) + if (!*movestr) s = me->states[me->statepos-1].state; else { s = me->ourgame->execute_move(me->states[me->statepos-1].state, @@ -444,18 +492,23 @@ static int midend_really_process_key(midend_data *me, int x, int y, int button) me->states[me->nstates].movetype = MOVE; me->statepos = ++me->nstates; me->dir = +1; + if (me->ui) + me->ourgame->changed_state(me->ui, + me->states[me->statepos-2].state, + me->states[me->statepos-1].state); } else { goto done; } } - if (!gotspecial) - special = special(me->states[me->statepos-1].movetype); + if (!gottype) + type = me->states[me->statepos-1].movetype; /* * See if this move requires an animation. */ - if (special) { + if (special(type) && !(type == SOLVE && + (me->ourgame->mouse_priorities & SOLVE_ANIMATES))) { anim_time = 0; } else { anim_time = me->ourgame->anim_length(oldstate, @@ -742,7 +795,7 @@ int midend_num_presets(midend_data *me) preset = me->ourgame->default_params(); me->ourgame->decode_params(preset, val); - if (me->ourgame->validate_params(preset)) { + if (me->ourgame->validate_params(preset, TRUE)) { /* Drop this one from the list. */ me->ourgame->free_params(preset); continue; @@ -855,6 +908,8 @@ config_item *midend_get_config(midend_data *me, int which, char **wintitle) static char *midend_game_id_int(midend_data *me, char *id, int defmode) { char *error, *par, *desc, *seed; + game_params *newcurparams, *newparams, *oldparams1, *oldparams2; + int free_params; seed = strchr(id, '#'); desc = strchr(id, ':'); @@ -894,18 +949,24 @@ static char *midend_game_id_int(midend_data *me, char *id, int defmode) } } + /* + * We must be reasonably careful here not to modify anything in + * `me' until we have finished validating things. This function + * must either return an error and do nothing to the midend, or + * return success and do everything; nothing in between is + * acceptable. + */ + newcurparams = newparams = oldparams1 = oldparams2 = NULL; + if (par) { - game_params *tmpparams; - tmpparams = me->ourgame->dup_params(me->params); - me->ourgame->decode_params(tmpparams, par); - error = me->ourgame->validate_params(tmpparams); + newcurparams = me->ourgame->dup_params(me->params); + me->ourgame->decode_params(newcurparams, par); + error = me->ourgame->validate_params(newcurparams, desc == NULL); if (error) { - me->ourgame->free_params(tmpparams); + me->ourgame->free_params(newcurparams); return error; } - if (me->curparams) - me->ourgame->free_params(me->curparams); - me->curparams = tmpparams; + oldparams1 = me->curparams; /* * Now filter only the persistent parts of this state into @@ -913,16 +974,50 @@ static char *midend_game_id_int(midend_data *me, char *id, int defmode) * received a params string in which case the whole lot is * persistent. */ + oldparams2 = me->params; if (seed || desc) { - char *tmpstr = me->ourgame->encode_params(tmpparams, FALSE); - me->ourgame->decode_params(me->params, tmpstr); + char *tmpstr; + + newparams = me->ourgame->dup_params(me->params); + + tmpstr = me->ourgame->encode_params(newcurparams, FALSE); + me->ourgame->decode_params(newparams, tmpstr); + sfree(tmpstr); } else { - me->ourgame->free_params(me->params); - me->params = me->ourgame->dup_params(tmpparams); + newparams = me->ourgame->dup_params(newcurparams); + } + free_params = TRUE; + } else { + newcurparams = me->curparams; + newparams = me->params; + free_params = FALSE; + } + + if (desc) { + error = me->ourgame->validate_desc(newparams, desc); + if (error) { + if (free_params) { + if (newcurparams) + me->ourgame->free_params(newcurparams); + if (newparams) + me->ourgame->free_params(newparams); + } + return error; } } + /* + * Now we've got past all possible error points. Update the + * midend itself. + */ + me->params = newparams; + me->curparams = newcurparams; + if (oldparams1) + me->ourgame->free_params(oldparams1); + if (oldparams2) + me->ourgame->free_params(oldparams2); + sfree(me->desc); sfree(me->privdesc); me->desc = me->privdesc = NULL; @@ -930,10 +1025,6 @@ static char *midend_game_id_int(midend_data *me, char *id, int defmode) me->seedstr = NULL; if (desc) { - error = me->ourgame->validate_desc(me->params, desc); - if (error) - return error; - me->desc = dupstr(desc); me->genmode = GOT_DESC; sfree(me->aux_info); @@ -961,7 +1052,7 @@ char *midend_set_config(midend_data *me, int which, config_item *cfg) switch (which) { case CFG_SETTINGS: params = me->ourgame->custom_params(cfg); - error = me->ourgame->validate_params(params); + error = me->ourgame->validate_params(params, TRUE); if (error) { me->ourgame->free_params(params); @@ -1027,8 +1118,17 @@ char *midend_solve(midend_data *me) me->ourgame->changed_state(me->ui, me->states[me->statepos-2].state, me->states[me->statepos-1].state); - me->anim_time = 0.0; - midend_finish_move(me); + me->dir = +1; + if (me->ourgame->mouse_priorities & SOLVE_ANIMATES) { + me->oldstate = me->ourgame->dup_game(me->states[me->statepos-2].state); + me->anim_time = + me->ourgame->anim_length(me->states[me->statepos-2].state, + me->states[me->statepos-1].state, + +1, me->ui); + } else { + me->anim_time = 0.0; + midend_finish_move(me); + } midend_redraw(me); midend_set_timer(me); return NULL; @@ -1265,6 +1365,7 @@ char *midend_deserialise(midend_data *me, if (key[8] != ':') { if (started) ret = "Data was incorrectly formatted for a saved game file"; + goto cleanup; } len = strcspn(key, ": "); assert(len <= 8); @@ -1395,16 +1496,24 @@ char *midend_deserialise(midend_data *me, params = me->ourgame->default_params(); me->ourgame->decode_params(params, parstr); - if (me->ourgame->validate_params(params)) { + if (me->ourgame->validate_params(params, TRUE)) { ret = "Long-term parameters in save file are invalid"; goto cleanup; } cparams = me->ourgame->default_params(); me->ourgame->decode_params(cparams, cparstr); - if (me->ourgame->validate_params(cparams)) { + if (me->ourgame->validate_params(cparams, FALSE)) { ret = "Short-term parameters in save file are invalid"; goto cleanup; } + if (seed && me->ourgame->validate_params(cparams, TRUE)) { + /* + * The seed's no use with this version, but we can perfectly + * well use the rest of the data. + */ + sfree(seed); + seed = NULL; + } if (!desc) { ret = "Game description in save file is missing"; goto cleanup;