Cleanup: remove the game_state parameter to game_colours(). No game
[sgt/puzzles] / midend.c
index ceef686..0d19beb 100644 (file)
--- a/midend.c
+++ b/midend.c
@@ -25,7 +25,7 @@ struct midend_state_entry {
     int movetype;
 };
 
-struct midend_data {
+struct midend {
     frontend *frontend;
     random_state *random;
     const game *ourgame;
@@ -76,9 +76,11 @@ struct midend_data {
     float elapsed;
     char *laststatus;
 
+    drawing *drawing;
+
     int pressed_mouse_button;
 
-    int tilesize, winwidth, winheight;
+    int preferred_tilesize, tilesize, winwidth, winheight;
 };
 
 #define ensure(me) do { \
@@ -89,9 +91,10 @@ struct midend_data {
     } \
 } while (0)
 
-midend_data *midend_new(frontend *fe, const game *ourgame)
+midend *midend_new(frontend *fe, const game *ourgame,
+                  const drawing_api *drapi, void *drhandle)
 {
-    midend_data *me = snew(midend_data);
+    midend *me = snew(midend);
     void *randseed;
     int randseedsize;
 
@@ -99,7 +102,7 @@ midend_data *midend_new(frontend *fe, const game *ourgame)
 
     me->frontend = fe;
     me->ourgame = ourgame;
-    me->random = random_init(randseed, randseedsize);
+    me->random = random_new(randseed, randseedsize);
     me->nstates = me->statesize = me->statepos = 0;
     me->states = NULL;
     me->params = ourgame->default_params();
@@ -122,13 +125,37 @@ midend_data *midend_new(frontend *fe, const game *ourgame)
     me->timing = FALSE;
     me->elapsed = 0.0F;
     me->tilesize = me->winwidth = me->winheight = 0;
+    if (drapi)
+       me->drawing = drawing_init(drapi, drhandle);
+    else
+       me->drawing = NULL;
+
+    me->preferred_tilesize = ourgame->preferred_tilesize;
+    {
+        /*
+         * Allow an environment-based override for the default tile
+         * size by defining a variable along the lines of
+         * `NET_TILESIZE=15'.
+         */
+
+       char buf[80], *e;
+       int j, k, ts;
+
+       sprintf(buf, "%s_TILESIZE", me->ourgame->name);
+       for (j = k = 0; buf[j]; j++)
+           if (!isspace((unsigned char)buf[j]))
+               buf[k++] = toupper((unsigned char)buf[j]);
+       buf[k] = '\0';
+       if ((e = getenv(buf)) != NULL && sscanf(e, "%d", &ts) == 1 && ts > 0)
+           me->preferred_tilesize = ts;
+    }
 
     sfree(randseed);
 
     return me;
 }
 
-static void midend_free_game(midend_data *me)
+static void midend_free_game(midend *me)
 {
     while (me->nstates > 0) {
         me->nstates--;
@@ -137,15 +164,17 @@ static void midend_free_game(midend_data *me)
     }
 
     if (me->drawstate)
-        me->ourgame->free_drawstate(me->drawstate);
+        me->ourgame->free_drawstate(me->drawing, me->drawstate);
 }
 
-void midend_free(midend_data *me)
+void midend_free(midend *me)
 {
     int i;
 
     midend_free_game(me);
 
+    if (me->drawing)
+       drawing_free(me->drawing);
     random_free(me->random);
     sfree(me->states);
     sfree(me->desc);
@@ -169,7 +198,7 @@ void midend_free(midend_data *me)
     sfree(me);
 }
 
-static void midend_size_new_drawstate(midend_data *me)
+static void midend_size_new_drawstate(midend *me)
 {
     /*
      * Don't even bother, if we haven't worked out our tile size
@@ -178,16 +207,28 @@ static void midend_size_new_drawstate(midend_data *me)
     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);
+       me->ourgame->set_size(me->drawing, me->drawstate,
+                             me->params, me->tilesize);
     }
 }
 
-void midend_size(midend_data *me, int *x, int *y, int expand)
+void midend_size(midend *me, int *x, int *y, int expand)
 {
     int min, max;
     int rx, ry;
 
     /*
+     * We can't set the size on the same drawstate twice. So if
+     * we've already sized one drawstate, we must throw it away and
+     * create a new one.
+     */
+    if (me->drawstate && me->tilesize > 0) {
+        me->ourgame->free_drawstate(me->drawing, me->drawstate);
+        me->drawstate = me->ourgame->new_drawstate(me->drawing,
+                                                   me->states[0].state);
+    }
+
+    /*
      * 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
@@ -200,7 +241,7 @@ void midend_size(midend_data *me, int *x, int *y, int expand)
            me->ourgame->compute_size(me->params, max, &rx, &ry);
        } while (rx <= *x && ry <= *y);
     } else
-       max = me->ourgame->preferred_tilesize + 1;
+       max = me->preferred_tilesize + 1;
     min = 1;
 
     /*
@@ -228,13 +269,18 @@ void midend_size(midend_data *me, int *x, int *y, int expand)
     *y = me->winheight;
 }
 
-void midend_set_params(midend_data *me, game_params *params)
+void midend_set_params(midend *me, game_params *params)
 {
     me->ourgame->free_params(me->params);
     me->params = me->ourgame->dup_params(params);
 }
 
-static void midend_set_timer(midend_data *me)
+game_params *midend_get_params(midend *me)
+{
+    return me->ourgame->dup_params(me->params);
+}
+
+static void midend_set_timer(midend *me)
 {
     me->timing = (me->ourgame->is_timed &&
                  me->ourgame->timing_state(me->states[me->statepos-1].state,
@@ -245,16 +291,17 @@ static void midend_set_timer(midend_data *me)
        deactivate_timer(me->frontend);
 }
 
-void midend_force_redraw(midend_data *me)
+void midend_force_redraw(midend *me)
 {
     if (me->drawstate)
-        me->ourgame->free_drawstate(me->drawstate);
-    me->drawstate = me->ourgame->new_drawstate(me->states[0].state);
+        me->ourgame->free_drawstate(me->drawing, me->drawstate);
+    me->drawstate = me->ourgame->new_drawstate(me->drawing,
+                                              me->states[0].state);
     midend_size_new_drawstate(me);
     midend_redraw(me);
 }
 
-void midend_new_game(midend_data *me)
+void midend_new_game(midend *me)
 {
     midend_free_game(me);
 
@@ -295,21 +342,44 @@ void midend_new_game(midend_data *me)
         sfree(me->aux_info);
        me->aux_info = NULL;
 
-        rs = random_init(me->seedstr, strlen(me->seedstr));
+        rs = random_new(me->seedstr, strlen(me->seedstr));
+       /*
+        * If this midend has been instantiated without providing a
+        * drawing API, it is non-interactive. This means that it's
+        * being used for bulk game generation, and hence we should
+        * pass the non-interactive flag to new_desc.
+        */
         me->desc = me->ourgame->new_desc(me->curparams, rs,
-                                        &me->aux_info, TRUE);
+                                        &me->aux_info, (me->drawing != NULL));
        me->privdesc = NULL;
         random_free(rs);
     }
 
     ensure(me);
+
+    /*
+     * It might seem a bit odd that we're using me->params to
+     * create the initial game state, rather than me->curparams
+     * which is better tailored to this specific game and which we
+     * always know.
+     * 
+     * It's supposed to be an invariant in the midend that
+     * me->params and me->curparams differ in no aspect that is
+     * important after generation (i.e. after new_desc()). By
+     * deliberately passing the _less_ specific of these two
+     * parameter sets, we provoke play-time misbehaviour in the
+     * case where a game has failed to encode a play-time parameter
+     * in the non-full version of encode_params().
+     */
     me->states[me->nstates].state =
        me->ourgame->new_game(me, me->params, me->desc);
+
     me->states[me->nstates].movestr = NULL;
     me->states[me->nstates].movetype = NEWGAME;
     me->nstates++;
     me->statepos = 1;
-    me->drawstate = me->ourgame->new_drawstate(me->states[0].state);
+    me->drawstate = me->ourgame->new_drawstate(me->drawing,
+                                              me->states[0].state);
     midend_size_new_drawstate(me);
     me->elapsed = 0.0F;
     if (me->ui)
@@ -319,7 +389,7 @@ void midend_new_game(midend_data *me)
     me->pressed_mouse_button = 0;
 }
 
-static int midend_undo(midend_data *me)
+static int midend_undo(midend *me)
 {
     if (me->statepos > 1) {
         if (me->ui)
@@ -333,7 +403,7 @@ static int midend_undo(midend_data *me)
         return 0;
 }
 
-static int midend_redo(midend_data *me)
+static int midend_redo(midend *me)
 {
     if (me->statepos < me->nstates) {
         if (me->ui)
@@ -347,7 +417,7 @@ static int midend_redo(midend_data *me)
         return 0;
 }
 
-static void midend_finish_move(midend_data *me)
+static void midend_finish_move(midend *me)
 {
     float flashtime;
 
@@ -380,7 +450,7 @@ static void midend_finish_move(midend_data *me)
     midend_set_timer(me);
 }
 
-void midend_stop_anim(midend_data *me)
+void midend_stop_anim(midend *me)
 {
     if (me->oldstate || me->anim_time != 0) {
        midend_finish_move(me);
@@ -388,7 +458,7 @@ void midend_stop_anim(midend_data *me)
     }
 }
 
-void midend_restart_game(midend_data *me)
+void midend_restart_game(midend *me)
 {
     game_state *s;
 
@@ -428,7 +498,7 @@ void midend_restart_game(midend_data *me)
     midend_set_timer(me);
 }
 
-static int midend_really_process_key(midend_data *me, int x, int y, int button)
+static int midend_really_process_key(midend *me, int x, int y, int button)
 {
     game_state *oldstate =
         me->ourgame->dup_game(me->states[me->statepos - 1].state);
@@ -508,7 +578,7 @@ static int midend_really_process_key(midend_data *me, int x, int y, int button)
      * See if this move requires an animation.
      */
     if (special(type) && !(type == SOLVE &&
-                          (me->ourgame->mouse_priorities & SOLVE_ANIMATES))) {
+                          (me->ourgame->flags & SOLVE_ANIMATES))) {
         anim_time = 0;
     } else {
         anim_time = me->ourgame->anim_length(oldstate,
@@ -534,7 +604,7 @@ static int midend_really_process_key(midend_data *me, int x, int y, int button)
     return ret;
 }
 
-int midend_process_key(midend_data *me, int x, int y, int button)
+int midend_process_key(midend *me, int x, int y, int button)
 {
     int ret = 1;
 
@@ -620,7 +690,7 @@ int midend_process_key(midend_data *me, int x, int y, int button)
         * If the new button has lower priority than the old one,
         * don't bother doing this.
         */
-       if (me->ourgame->mouse_priorities &
+       if (me->ourgame->flags &
            BUTTON_BEATS(me->pressed_mouse_button, button))
            return ret;                /* just ignore it */
 
@@ -648,26 +718,28 @@ int midend_process_key(midend_data *me, int x, int y, int button)
     return ret;
 }
 
-void midend_redraw(midend_data *me)
+void midend_redraw(midend *me)
 {
+    assert(me->drawing);
+
     if (me->statepos > 0 && me->drawstate) {
-        start_draw(me->frontend);
+        start_draw(me->drawing);
         if (me->oldstate && me->anim_time > 0 &&
             me->anim_pos < me->anim_time) {
             assert(me->dir != 0);
-            me->ourgame->redraw(me->frontend, me->drawstate, me->oldstate,
+            me->ourgame->redraw(me->drawing, me->drawstate, me->oldstate,
                                me->states[me->statepos-1].state, me->dir,
                                me->ui, me->anim_pos, me->flash_pos);
         } else {
-            me->ourgame->redraw(me->frontend, me->drawstate, NULL,
+            me->ourgame->redraw(me->drawing, me->drawstate, NULL,
                                me->states[me->statepos-1].state, +1 /*shrug*/,
                                me->ui, 0.0, me->flash_pos);
         }
-        end_draw(me->frontend);
+        end_draw(me->drawing);
     }
 }
 
-void midend_timer(midend_data *me, float tplus)
+void midend_timer(midend *me, float tplus)
 {
     me->anim_pos += tplus;
     if (me->anim_pos >= me->anim_time ||
@@ -687,28 +759,17 @@ void midend_timer(midend_data *me, float tplus)
        float oldelapsed = me->elapsed;
        me->elapsed += tplus;
        if ((int)oldelapsed != (int)me->elapsed)
-           status_bar(me->frontend, me->laststatus ? me->laststatus : "");
+           status_bar(me->drawing, me->laststatus ? me->laststatus : "");
     }
 
     midend_set_timer(me);
 }
 
-float *midend_colours(midend_data *me, int *ncolours)
+float *midend_colours(midend *me, int *ncolours)
 {
-    game_state *state = NULL;
     float *ret;
 
-    if (me->nstates == 0) {
-       char *aux = NULL;
-        char *desc = me->ourgame->new_desc(me->params, me->random,
-                                          &aux, TRUE);
-        state = me->ourgame->new_game(me, me->params, desc);
-        sfree(desc);
-        sfree(aux);
-    } else
-        state = me->states[0].state;
-
-    ret = me->ourgame->colours(me->frontend, state, ncolours);
+    ret = me->ourgame->colours(me->frontend, ncolours);
 
     {
         int i;
@@ -738,13 +799,10 @@ float *midend_colours(midend_data *me, int *ncolours)
         }
     }
 
-    if (me->nstates == 0)
-        me->ourgame->free_game(state);
-
     return ret;
 }
 
-int midend_num_presets(midend_data *me)
+int midend_num_presets(midend *me)
 {
     if (!me->npresets) {
         char *name;
@@ -823,7 +881,7 @@ int midend_num_presets(midend_data *me)
     return me->npresets;
 }
 
-void midend_fetch_preset(midend_data *me, int n,
+void midend_fetch_preset(midend *me, int n,
                          char **name, game_params **params)
 {
     assert(n >= 0 && n < me->npresets);
@@ -831,12 +889,12 @@ void midend_fetch_preset(midend_data *me, int n,
     *params = me->presets[n];
 }
 
-int midend_wants_statusbar(midend_data *me)
+int midend_wants_statusbar(midend *me)
 {
     return me->ourgame->wants_statusbar();
 }
 
-void midend_supersede_game_desc(midend_data *me, char *desc, char *privdesc)
+void midend_supersede_game_desc(midend *me, char *desc, char *privdesc)
 {
     sfree(me->desc);
     sfree(me->privdesc);
@@ -844,7 +902,7 @@ void midend_supersede_game_desc(midend_data *me, char *desc, char *privdesc)
     me->privdesc = privdesc ? dupstr(privdesc) : NULL;
 }
 
-config_item *midend_get_config(midend_data *me, int which, char **wintitle)
+config_item *midend_get_config(midend *me, int which, char **wintitle)
 {
     char *titlebuf, *parstr, *rest;
     config_item *ret;
@@ -909,7 +967,7 @@ config_item *midend_get_config(midend_data *me, int which, char **wintitle)
     return NULL;
 }
 
-static char *midend_game_id_int(midend_data *me, char *id, int defmode)
+static char *midend_game_id_int(midend *me, char *id, int defmode)
 {
     char *error, *par, *desc, *seed;
     game_params *newcurparams, *newparams, *oldparams1, *oldparams2;
@@ -1043,12 +1101,25 @@ static char *midend_game_id_int(midend_data *me, char *id, int defmode)
     return NULL;
 }
 
-char *midend_game_id(midend_data *me, char *id)
+char *midend_game_id(midend *me, char *id)
 {
     return midend_game_id_int(me, id, DEF_PARAMS);
 }
 
-char *midend_set_config(midend_data *me, int which, config_item *cfg)
+char *midend_get_game_id(midend *me)
+{
+    char *parstr, *ret;
+
+    parstr = me->ourgame->encode_params(me->curparams, FALSE);
+    assert(parstr);
+    assert(me->desc);
+    ret = snewn(strlen(parstr) + strlen(me->desc) + 2, char);
+    sprintf(ret, "%s:%s", parstr, me->desc);
+    sfree(parstr);
+    return ret;
+}
+
+char *midend_set_config(midend *me, int which, config_item *cfg)
 {
     char *error;
     game_params *params;
@@ -1079,7 +1150,7 @@ char *midend_set_config(midend_data *me, int which, config_item *cfg)
     return NULL;
 }
 
-char *midend_text_format(midend_data *me)
+char *midend_text_format(midend *me)
 {
     if (me->ourgame->can_format_as_text && me->statepos > 0)
        return me->ourgame->text_format(me->states[me->statepos-1].state);
@@ -1087,7 +1158,7 @@ char *midend_text_format(midend_data *me)
        return NULL;
 }
 
-char *midend_solve(midend_data *me)
+char *midend_solve(midend *me)
 {
     game_state *s;
     char *msg, *movestr;
@@ -1098,12 +1169,15 @@ char *midend_solve(midend_data *me)
     if (me->statepos < 1)
        return "No game set up to solve";   /* _shouldn't_ happen! */
 
-    msg = "Solve operation failed";    /* game _should_ overwrite on error */
+    msg = NULL;
     movestr = me->ourgame->solve(me->states[0].state,
                                 me->states[me->statepos-1].state,
                                 me->aux_info, &msg);
-    if (!movestr)
+    if (!movestr) {
+       if (!msg)
+           msg = "Solve operation failed";   /* _shouldn't_ happen, but can */
        return msg;
+    }
     s = me->ourgame->execute_move(me->states[me->statepos-1].state, movestr);
     assert(s);
 
@@ -1111,8 +1185,11 @@ char *midend_solve(midend_data *me)
      * Now enter the solved state as the next move.
      */
     midend_stop_anim(me);
-    while (me->nstates > me->statepos)
+    while (me->nstates > me->statepos) {
        me->ourgame->free_game(me->states[--me->nstates].state);
+        if (me->states[me->nstates].movestr)
+            sfree(me->states[me->nstates].movestr);
+    }
     ensure(me);
     me->states[me->nstates].state = s;
     me->states[me->nstates].movestr = movestr;
@@ -1123,7 +1200,7 @@ char *midend_solve(midend_data *me)
                                    me->states[me->statepos-2].state,
                                    me->states[me->statepos-1].state);
     me->dir = +1;
-    if (me->ourgame->mouse_priorities & SOLVE_ANIMATES) {
+    if (me->ourgame->flags & 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,
@@ -1139,7 +1216,7 @@ char *midend_solve(midend_data *me)
     return NULL;
 }
 
-char *midend_rewrite_statusbar(midend_data *me, char *text)
+char *midend_rewrite_statusbar(midend *me, char *text)
 {
     /*
      * An important special case is that we are occasionally called
@@ -1172,7 +1249,7 @@ char *midend_rewrite_statusbar(midend_data *me, char *text)
 #define SERIALISE_MAGIC "Simon Tatham's Portable Puzzle Collection"
 #define SERIALISE_VERSION "1"
 
-void midend_serialise(midend_data *me,
+void midend_serialise(midend *me,
                       void (*write)(void *ctx, void *buf, int len),
                       void *wctx)
 {
@@ -1321,7 +1398,7 @@ void midend_serialise(midend_data *me,
 /*
  * This function returns NULL on success, or an error message.
  */
-char *midend_deserialise(midend_data *me,
+char *midend_deserialise(midend *me,
                          int (*read)(void *ctx, void *buf, int len),
                          void *rctx)
 {
@@ -1337,7 +1414,7 @@ char *midend_deserialise(midend_data *me,
      * We construct all the new state in local variables while we
      * check its sanity. Only once we have finished reading the
      * serialised data and detected no errors at all do we start
-     * modifying stuff in the midend_data passed in.
+     * modifying stuff in the midend passed in.
      */
     char *seed = NULL, *parstr = NULL, *desc = NULL, *privdesc = NULL;
     char *auxinfo = NULL, *uistr = NULL, *cparstr = NULL;
@@ -1632,9 +1709,10 @@ char *midend_deserialise(midend_data *me,
     midend_set_timer(me);
 
     if (me->drawstate)
-        me->ourgame->free_drawstate(me->drawstate);
+        me->ourgame->free_drawstate(me->drawing, me->drawstate);
     me->drawstate =
-        me->ourgame->new_drawstate(me->states[me->statepos-1].state);
+        me->ourgame->new_drawstate(me->drawing,
+                                  me->states[me->statepos-1].state);
     midend_size_new_drawstate(me);
 
     ret = NULL;                        /* success! */
@@ -1667,3 +1745,43 @@ char *midend_deserialise(midend_data *me,
 
     return ret;
 }
+
+char *midend_print_puzzle(midend *me, document *doc, int with_soln)
+{
+    game_state *soln = NULL;
+
+    if (me->statepos < 1)
+       return "No game set up to print";/* _shouldn't_ happen! */
+
+    if (with_soln) {
+       char *msg, *movestr;
+
+       if (!me->ourgame->can_solve)
+           return "This game does not support the Solve operation";
+
+       msg = "Solve operation failed";/* game _should_ overwrite on error */
+       movestr = me->ourgame->solve(me->states[0].state,
+                                    me->states[me->statepos-1].state,
+                                    me->aux_info, &msg);
+       if (!movestr)
+           return msg;
+       soln = me->ourgame->execute_move(me->states[me->statepos-1].state,
+                                        movestr);
+       assert(soln);
+
+       sfree(movestr);
+    } else
+       soln = NULL;
+
+    /*
+     * This call passes over ownership of the two game_states and
+     * the game_params. Hence we duplicate the ones we want to
+     * keep, and we don't have to bother freeing soln if it was
+     * non-NULL.
+     */
+    document_add_puzzle(doc, me->ourgame,
+                       me->ourgame->dup_params(me->curparams),
+                       me->ourgame->dup_game(me->states[0].state), soln);
+
+    return NULL;
+}