Introduce a new game backend function (there seem to have been a lot
[sgt/puzzles] / netslide.c
index 6bac447..e6751fe 100644 (file)
@@ -13,8 +13,6 @@
 #include "puzzles.h"
 #include "tree234.h"
 
-#define PI 3.141592653589793238462643383279502884197169399
-
 #define MATMUL(xr,yr,m,x,y) do { \
     float rx, ry, xx = (x), yy = (y), *mat = (m); \
     rx = mat[0] * xx + mat[2] * yy; \
@@ -80,6 +78,7 @@ struct game_params {
     int height;
     int wrapping;
     float barrier_probability;
+    int movetarget;
 };
 
 struct game_aux_info {
@@ -90,7 +89,7 @@ struct game_aux_info {
 struct game_state {
     int width, height, cx, cy, wrapping, completed;
     int used_solve, just_used_solve;
-    int move_count;
+    int move_count, movetarget;
 
     /* position (row or col number, starting at 0) of last move. */
     int last_move_row, last_move_col;
@@ -130,7 +129,7 @@ static int xyd_cmp(void *av, void *bv) {
     if (a->direction > b->direction)
        return +1;
     return 0;
-};
+}
 
 static struct xyd *new_xyd(int x, int y, int direction)
 {
@@ -157,37 +156,40 @@ static game_params *default_params(void)
     ret->height = 3;
     ret->wrapping = FALSE;
     ret->barrier_probability = 1.0;
+    ret->movetarget = 0;
 
     return ret;
 }
 
+static const struct { int x, y, wrap, bprob; const char* desc; }
+netslide_presets[] = {
+    {3, 3, FALSE, 1.0, " easy"},
+    {3, 3, FALSE, 0.0, " medium"},
+    {3, 3, TRUE,  0.0, " hard"},
+    {4, 4, FALSE, 1.0, " easy"},
+    {4, 4, FALSE, 0.0, " medium"},
+    {4, 4, TRUE,  0.0, " hard"},
+    {5, 5, FALSE, 1.0, " easy"},
+    {5, 5, FALSE, 0.0, " medium"},
+    {5, 5, TRUE,  0.0, " hard"},
+};
+
 static int game_fetch_preset(int i, char **name, game_params **params)
 {
     game_params *ret;
     char str[80];
-    static const struct { int x, y, wrap, bprob; const char* desc; } values[] = {
-        {3, 3, FALSE, 1.0, " easy"},
-        {3, 3, FALSE, 0.0, " medium"},
-        {3, 3, TRUE,  0.0, " hard"},
-        {4, 4, FALSE, 1.0, " easy"},
-        {4, 4, FALSE, 0.0, " medium"},
-        {4, 4, TRUE,  0.0, " hard"},
-        {5, 5, FALSE, 1.0, " easy"},
-        {5, 5, FALSE, 0.0, " medium"},
-        {5, 5, TRUE,  0.0, " hard"},
-    };
-
-    if (i < 0 || i >= lenof(values))
+
+    if (i < 0 || i >= lenof(netslide_presets))
         return FALSE;
 
     ret = snew(game_params);
-    ret->width = values[i].x;
-    ret->height = values[i].y;
-    ret->wrapping = values[i].wrap;
-    ret->barrier_probability = values[i].bprob;
+    ret->width = netslide_presets[i].x;
+    ret->height = netslide_presets[i].y;
+    ret->wrapping = netslide_presets[i].wrap;
+    ret->barrier_probability = netslide_presets[i].bprob;
+    ret->movetarget = 0;
 
-    sprintf(str, "%dx%d%s", ret->width, ret->height,
-            values[i].desc);
+    sprintf(str, "%dx%d%s", ret->width, ret->height, netslide_presets[i].desc);
 
     *name = dupstr(str);
     *params = ret;
@@ -212,6 +214,7 @@ static void decode_params(game_params *ret, char const *string)
 
     ret->wrapping = FALSE;
     ret->barrier_probability = 0.0;
+    ret->movetarget = 0;
 
     ret->width = atoi(p);
     while (*p && isdigit(*p)) p++;
@@ -221,8 +224,13 @@ static void decode_params(game_params *ret, char const *string)
         while (*p && isdigit(*p)) p++;
         if ( (ret->wrapping = (*p == 'w')) != 0 )
             p++;
-        if (*p == 'b')
-            ret->barrier_probability = atof(p+1);
+        if (*p == 'b') {
+            ret->barrier_probability = atof(++p);
+            while (*p && (isdigit(*p) || *p == '.')) p++;
+        }
+        if (*p == 'm') {
+            ret->movetarget = atoi(++p);
+        }
     } else {
         ret->height = ret->width;
     }
@@ -238,6 +246,10 @@ static char *encode_params(game_params *params, int full)
         ret[len++] = 'w';
     if (full && params->barrier_probability)
         len += sprintf(ret+len, "b%g", params->barrier_probability);
+    /* Shuffle limit is part of the limited parameters, because we have to
+     * provide the target move count. */
+    if (params->movetarget)
+        len += sprintf(ret+len, "m%d", params->movetarget);
     assert(len < lenof(ret));
     ret[len] = '\0';
 
@@ -249,7 +261,7 @@ static config_item *game_configure(game_params *params)
     config_item *ret;
     char buf[80];
 
-    ret = snewn(5, config_item);
+    ret = snewn(6, config_item);
 
     ret[0].name = "Width";
     ret[0].type = C_STRING;
@@ -274,11 +286,17 @@ static config_item *game_configure(game_params *params)
     ret[3].sval = dupstr(buf);
     ret[3].ival = 0;
 
-    ret[4].name = NULL;
-    ret[4].type = C_END;
-    ret[4].sval = NULL;
+    ret[4].name = "Number of shuffling moves";
+    ret[4].type = C_STRING;
+    sprintf(buf, "%d", params->movetarget);
+    ret[4].sval = dupstr(buf);
     ret[4].ival = 0;
 
+    ret[5].name = NULL;
+    ret[5].type = C_END;
+    ret[5].sval = NULL;
+    ret[5].ival = 0;
+
     return ret;
 }
 
@@ -290,18 +308,15 @@ static game_params *custom_params(config_item *cfg)
     ret->height = atoi(cfg[1].sval);
     ret->wrapping = cfg[2].ival;
     ret->barrier_probability = (float)atof(cfg[3].sval);
+    ret->movetarget = atoi(cfg[4].sval);
 
     return ret;
 }
 
 static char *validate_params(game_params *params)
 {
-    if (params->width <= 1 && params->height <= 1)
+    if (params->width <= 1 || params->height <= 1)
        return "Width and height must both be greater than one";
-    if (params->width <= 1)
-       return "Width must be greater than one";
-    if (params->height <= 1)
-       return "Height must be greater than one";
     if (params->barrier_probability < 0)
        return "Barrier probability may not be negative";
     if (params->barrier_probability > 1)
@@ -314,7 +329,7 @@ static char *validate_params(game_params *params)
  */
 
 static char *new_game_desc(game_params *params, random_state *rs,
-                          game_aux_info **aux)
+                          game_aux_info **aux, int interactive)
 {
     tree234 *possibilities, *barriertree;
     int w, h, x, y, cx, cy, nbarriers;
@@ -402,7 +417,7 @@ static char *new_game_desc(game_params *params, random_state *rs,
 
        OFFSET(x2, y2, x1, y1, d1, params);
        d2 = F(d1);
-#ifdef DEBUG
+#ifdef GENERATION_DIAGNOSTICS
        printf("picked (%d,%d,%c) <-> (%d,%d,%c)\n",
               x1, y1, "0RU3L567D9abcdef"[d1], x2, y2, "0RU3L567D9abcdef"[d2]);
 #endif
@@ -429,7 +444,7 @@ static char *new_game_desc(game_params *params, random_state *rs,
            xydp = find234(possibilities, &xyd1, NULL);
 
            if (xydp) {
-#ifdef DEBUG
+#ifdef GENERATION_DIAGNOSTICS
                printf("T-piece; removing (%d,%d,%c)\n",
                       xydp->x, xydp->y, "0RU3L567D9abcdef"[xydp->direction]);
 #endif
@@ -456,7 +471,7 @@ static char *new_game_desc(game_params *params, random_state *rs,
            xydp = find234(possibilities, &xyd1, NULL);
 
            if (xydp) {
-#ifdef DEBUG
+#ifdef GENERATION_DIAGNOSTICS
                printf("Loop avoidance; removing (%d,%d,%c)\n",
                       xydp->x, xydp->y, "0RU3L567D9abcdef"[xydp->direction]);
 #endif
@@ -491,7 +506,7 @@ static char *new_game_desc(game_params *params, random_state *rs,
            if (index(params, tiles, x3, y3))
                continue;              /* this would create a loop */
 
-#ifdef DEBUG
+#ifdef GENERATION_DIAGNOSTICS
            printf("New frontier; adding (%d,%d,%c)\n",
                   x2, y2, "0RU3L567D9abcdef"[d]);
 #endif
@@ -538,7 +553,11 @@ static char *new_game_desc(game_params *params, random_state *rs,
 
     /*
      * Now shuffle the grid.
-     * FIXME - this simply does a set of random moves to shuffle the pieces.
+     * FIXME - this simply does a set of random moves to shuffle the pieces,
+     * although we make a token effort to avoid boring cases by avoiding moves
+     * that directly undo the previous one, or that repeat so often as to
+     * turn into fewer moves.
+     *
      * A better way would be to number all the pieces, generate a placement
      * for all the numbers as for "sixteen", observing parity constraints if
      * neccessary, and then place the pieces according to their numbering.
@@ -549,18 +568,43 @@ static char *new_game_desc(game_params *params, random_state *rs,
         int i;
         int cols = w - 1;
         int rows = h - 1;
-        for (i = 0; i < cols * rows * 2; i++) {
+        int moves = params->movetarget;
+        int prevdir = -1, prevrowcol = -1, nrepeats = 0;
+        if (!moves) moves = cols * rows * 2;
+        for (i = 0; i < moves; /* incremented conditionally */) {
             /* Choose a direction: 0,1,2,3 = up, right, down, left. */
             int dir = random_upto(rs, 4);
+            int rowcol;
             if (dir % 2 == 0) {
                 int col = random_upto(rs, cols);
-                if (col >= cx) col += 1;
+                if (col >= cx) col += 1;    /* avoid centre */
+                if (col == prevrowcol) {
+                    if (dir == 2-prevdir)
+                        continue;   /* undoes last move */
+                    else if ((nrepeats+1)*2 > h)
+                        continue;   /* makes fewer moves */
+                }
                 slide_col_int(w, h, tiles, 1 - dir, col);
+                rowcol = col;
             } else {
                 int row = random_upto(rs, rows);
-                if (row >= cy) row += 1;
+                if (row >= cy) row += 1;    /* avoid centre */
+                if (row == prevrowcol) {
+                    if (dir == 4-prevdir)
+                        continue;   /* undoes last move */
+                    else if ((nrepeats+1)*2 > w)
+                        continue;   /* makes fewer moves */
+                }
                 slide_row_int(w, h, tiles, 2 - dir, row);
+                rowcol = row;
             }
+            if (dir == prevdir && rowcol == prevrowcol)
+                nrepeats++;
+            else
+                nrepeats = 1;
+            prevdir = dir;
+            prevrowcol = rowcol;
+            i++;    /* if we got here, the move was accepted */
         }
     }
 
@@ -689,7 +733,7 @@ static char *validate_desc(game_params *params, char *desc)
  * Construct an initial game state, given a description and parameters.
  */
 
-static game_state *new_game(game_params *params, char *desc)
+static game_state *new_game(midend_data *me, game_params *params, char *desc)
 {
     game_state *state;
     int w, h, x, y;
@@ -706,6 +750,7 @@ static game_state *new_game(game_params *params, char *desc)
     state->cx = state->width / 2;
     state->cy = state->height / 2;
     state->wrapping = params->wrapping;
+    state->movetarget = params->movetarget;
     state->completed = 0;
     state->used_solve = state->just_used_solve = FALSE;
     state->move_count = 0;
@@ -824,6 +869,7 @@ static game_state *dup_game(game_state *state)
     ret->cx = state->cx;
     ret->cy = state->cy;
     ret->wrapping = state->wrapping;
+    ret->movetarget = state->movetarget;
     ret->completed = state->completed;
     ret->used_solve = state->used_solve;
     ret->just_used_solve = state->just_used_solve;
@@ -999,8 +1045,13 @@ static void slide_col(game_state *state, int dir, int col)
     slide_col_int(state->width, state->height, state->tiles, dir, col);
 }
 
+static void game_changed_state(game_ui *ui, game_state *oldstate,
+                               game_state *newstate)
+{
+}
+
 static game_state *make_move(game_state *state, game_ui *ui,
-                            int x, int y, int button)
+                             game_drawstate *ds, int x, int y, int button)
 {
     int cx, cy;
     int n, dx, dy;
@@ -1619,6 +1670,10 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                    (state->completed ? "COMPLETED! " : ""),
                    (state->completed ? state->completed : state->move_count));
 
+        if (state->movetarget)
+            sprintf(statusbuf + strlen(statusbuf), " (target %d)",
+                    state->movetarget);
+
        sprintf(statusbuf + strlen(statusbuf), " Active: %d/%d", a, n);
 
        status_bar(fe, statusbuf);
@@ -1628,7 +1683,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
 }
 
 static float game_anim_length(game_state *oldstate,
-                             game_state *newstate, int dir)
+                             game_state *newstate, int dir, game_ui *ui)
 {
     /*
      * Don't animate an auto-solve move.
@@ -1641,7 +1696,7 @@ static float game_anim_length(game_state *oldstate,
 }
 
 static float game_flash_length(game_state *oldstate,
-                              game_state *newstate, int dir)
+                              game_state *newstate, int dir, game_ui *ui)
 {
     /*
      * If the game has just been completed, we display a completion
@@ -1670,6 +1725,11 @@ static int game_wants_statusbar(void)
     return TRUE;
 }
 
+static int game_timing_state(game_state *state)
+{
+    return FALSE;
+}
+
 #ifdef COMBINED
 #define thegame netslide
 #endif
@@ -1694,6 +1754,7 @@ const struct game thegame = {
     FALSE, game_text_format,
     new_ui,
     free_ui,
+    game_changed_state,
     make_move,
     game_size,
     game_colours,
@@ -1703,4 +1764,6 @@ const struct game thegame = {
     game_anim_length,
     game_flash_length,
     game_wants_statusbar,
+    FALSE, game_timing_state,
+    0,                                /* mouse_priorities */
 };