Refactored the game_size() interface, which was getting really
[sgt/puzzles] / midend.c
index e0e9cb0..b5efda6 100644 (file)
--- 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);
 
@@ -149,6 +149,7 @@ void midend_free(midend_data *me)
     random_free(me->random);
     sfree(me->states);
     sfree(me->desc);
+    sfree(me->privdesc);
     sfree(me->seedstr);
     sfree(me->aux_info);
     me->ourgame->free_params(me->params);
@@ -168,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)
@@ -191,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)
@@ -386,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,
@@ -756,7 +804,7 @@ int midend_num_presets(midend_data *me)
                 }
 
                 me->presets[me->npresets] = preset;
-                me->preset_names[me->npresets] = name;
+                me->preset_names[me->npresets] = dupstr(name);
                 me->npresets++;
             }
         }
@@ -854,6 +902,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, ':');
@@ -893,18 +943,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);
         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
@@ -912,16 +968,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;
@@ -929,10 +1019,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);
@@ -1084,7 +1170,7 @@ void midend_serialise(midend_data *me,
 #define wr(h,s) do { \
     char hbuf[80]; \
     char *str = (s); \
-    sprintf(hbuf, "%-8.8s:%d:", (h), strlen(str)); \
+    sprintf(hbuf, "%-8.8s:%d:", (h), (int)strlen(str)); \
     write(wctx, hbuf, strlen(hbuf)); \
     write(wctx, str, strlen(str)); \
     write(wctx, "\n", 1); \
@@ -1351,7 +1437,7 @@ char *midend_deserialise(midend_data *me,
                 uistr = val;
                 val = NULL;
             } else if (!strcmp(key, "TIME")) {
-                elapsed = strtod(val, NULL);
+                elapsed = atof(val);
             } else if (!strcmp(key, "NSTATES")) {
                 nstates = atoi(val);
                 if (nstates <= 0) {
@@ -1516,6 +1602,12 @@ char *midend_deserialise(midend_data *me,
 
     midend_set_timer(me);
 
+    if (me->drawstate)
+        me->ourgame->free_drawstate(me->drawstate);
+    me->drawstate =
+        me->ourgame->new_drawstate(me->states[me->statepos-1].state);
+    midend_size_new_drawstate(me);
+
     ret = NULL;                        /* success! */
 
     cleanup: