X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/puzzles/blobdiff_plain/cb64d2dd666eb5d02c6bfd8dcb151fd0a16196d6..faff1e077e6864b40341b49da46e3840e77a6516:/midend.c diff --git a/midend.c b/midend.c index a764c77..b5ce237 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) @@ -192,12 +244,6 @@ static void midend_set_timer(midend_data *me) 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) @@ -335,7 +381,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); } @@ -387,37 +433,38 @@ static int midend_really_process_key(midend_data *me, int x, int y, int button) me->ourgame->dup_game(me->states[me->statepos - 1].state); int special = FALSE, gotspecial = 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); + special = special(me->states[me->statepos-1].movetype); + gotspecial = 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,6 +491,10 @@ 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; } @@ -742,7 +793,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 +906,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 +947,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 +972,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 +1023,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 +1050,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); @@ -1265,6 +1354,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 +1485,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;