chiark / gitweb /
I've dithered a bit in the past about whether or not it's allowable
[sgt-puzzles.git] / midend.c
index b5ce237561197c6b3ba7cd9c56f277b854305dcd..64edfd3394f7544f1ab36b812f2413d77ff85983 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;
 
@@ -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,15 +207,27 @@ 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
@@ -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,32 +269,39 @@ 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));
+                 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);
 }
 
-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,8 +343,14 @@ void midend_new_game(midend_data *me)
        me->aux_info = NULL;
 
         rs = random_init(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);
     }
@@ -308,17 +362,18 @@ void midend_new_game(midend_data *me)
     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;
-    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;
 }
 
-static int midend_undo(midend_data *me)
+static int midend_undo(midend *me)
 {
     if (me->statepos > 1) {
         if (me->ui)
@@ -332,7 +387,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)
@@ -346,7 +401,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;
 
@@ -379,7 +434,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);
@@ -387,7 +442,7 @@ void midend_stop_anim(midend_data *me)
     }
 }
 
-void midend_restart_game(midend_data *me)
+void midend_restart_game(midend *me)
 {
     game_state *s;
 
@@ -427,11 +482,11 @@ 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);
-    int special = FALSE, gotspecial = FALSE, ret = 1;
+    int type = MOVE, gottype = FALSE, ret = 1;
     float anim_time;
     game_state *s;
     char *movestr;
@@ -449,8 +504,8 @@ static int midend_really_process_key(midend_data *me, int x, int y, int button)
        } else if (button == 'u' || button == 'u' ||
                   button == '\x1A' || button == '\x1F') {
            midend_stop_anim(me);
-           special = special(me->states[me->statepos-1].movetype);
-           gotspecial = TRUE;
+           type = me->states[me->statepos-1].movetype;
+           gottype = TRUE;
            if (!midend_undo(me))
                goto done;
        } else if (button == 'r' || button == 'R' ||
@@ -500,13 +555,14 @@ static int midend_really_process_key(midend_data *me, int x, int y, int button)
         }
     }
 
-    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,
@@ -532,7 +588,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;
 
@@ -646,26 +702,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 ||
@@ -685,13 +743,13 @@ 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;
@@ -720,11 +778,13 @@ float *midend_colours(midend_data *me, int *ncolours)
         for (i = 0; i < *ncolours; i++) {
             char buf[80], *e;
             unsigned int r, g, b;
-            int j;
+            int j, k;
 
             sprintf(buf, "%s_COLOUR_%d", me->ourgame->name, i);
-            for (j = 0; buf[j]; j++)
-                buf[j] = toupper((unsigned char)buf[j]);
+            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, "%2x%2x%2x", &r, &g, &b) == 3) {
                 ret[i*3 + 0] = r / 255.0;
@@ -740,7 +800,7 @@ float *midend_colours(midend_data *me, int *ncolours)
     return ret;
 }
 
-int midend_num_presets(midend_data *me)
+int midend_num_presets(midend *me)
 {
     if (!me->npresets) {
         char *name;
@@ -770,11 +830,13 @@ int midend_num_presets(midend_data *me)
          * encoded parameter strings.
          */
         char buf[80], *e, *p;
-        int j;
+        int j, k;
 
         sprintf(buf, "%s_PRESETS", me->ourgame->name);
-        for (j = 0; buf[j]; j++)
-            buf[j] = toupper((unsigned char)buf[j]);
+       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) {
             p = e = dupstr(e);
@@ -817,7 +879,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);
@@ -825,12 +887,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);
@@ -838,7 +900,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;
@@ -903,7 +965,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;
@@ -1037,12 +1099,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;
@@ -1073,7 +1148,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);
@@ -1081,7 +1156,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;
@@ -1105,8 +1180,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;
@@ -1116,14 +1194,24 @@ 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);
+        me->anim_pos = 0.0;
+    } else {
+       me->anim_time = 0.0;
+       midend_finish_move(me);
+    }
     midend_redraw(me);
     midend_set_timer(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
@@ -1156,7 +1244,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)
 {
@@ -1305,7 +1393,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)
 {
@@ -1321,7 +1409,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;
@@ -1616,9 +1704,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! */
@@ -1651,3 +1740,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;
+}