More serialisation changes: the game_aux_info structure has now been
[sgt/puzzles] / solo.c
diff --git a/solo.c b/solo.c
index d564ad2..0abed8c 100644 (file)
--- a/solo.c
+++ b/solo.c
@@ -113,7 +113,8 @@ typedef unsigned char digit;
 
 #define FLASH_TIME 0.4F
 
-enum { SYMM_NONE, SYMM_ROT2, SYMM_ROT4, SYMM_REF4 };
+enum { SYMM_NONE, SYMM_ROT2, SYMM_ROT4, SYMM_REF2, SYMM_REF2D, SYMM_REF4,
+       SYMM_REF4D, SYMM_REF8 };
 
 enum { DIFF_BLOCK, DIFF_SIMPLE, DIFF_INTERSECT,
        DIFF_SET, DIFF_RECURSIVE, DIFF_AMBIGUOUS, DIFF_IMPOSSIBLE };
@@ -203,12 +204,22 @@ static void decode_params(game_params *ret, char const *string)
     }
     while (*string) {
         if (*string == 'r' || *string == 'm' || *string == 'a') {
-            int sn, sc;
+            int sn, sc, sd;
             sc = *string++;
+            if (*string == 'd') {
+                sd = TRUE;
+                string++;
+            } else {
+                sd = FALSE;
+            }
             sn = atoi(string);
             while (*string && isdigit((unsigned char)*string)) string++;
+            if (sc == 'm' && sn == 8)
+                ret->symm = SYMM_REF8;
             if (sc == 'm' && sn == 4)
-                ret->symm = SYMM_REF4;
+                ret->symm = sd ? SYMM_REF4D : SYMM_REF4;
+            if (sc == 'm' && sn == 2)
+                ret->symm = sd ? SYMM_REF2D : SYMM_REF2;
             if (sc == 'r' && sn == 4)
                 ret->symm = SYMM_ROT4;
             if (sc == 'r' && sn == 2)
@@ -239,7 +250,11 @@ static char *encode_params(game_params *params, int full)
     sprintf(str, "%dx%d", params->c, params->r);
     if (full) {
         switch (params->symm) {
+          case SYMM_REF8: strcat(str, "m8"); break;
           case SYMM_REF4: strcat(str, "m4"); break;
+          case SYMM_REF4D: strcat(str, "md4"); break;
+          case SYMM_REF2: strcat(str, "m2"); break;
+          case SYMM_REF2D: strcat(str, "md2"); break;
           case SYMM_ROT4: strcat(str, "r4"); break;
           /* case SYMM_ROT2: strcat(str, "r2"); break; [default] */
           case SYMM_NONE: strcat(str, "a"); break;
@@ -276,7 +291,9 @@ static config_item *game_configure(game_params *params)
 
     ret[2].name = "Symmetry";
     ret[2].type = C_CHOICES;
-    ret[2].sval = ":None:2-way rotation:4-way rotation:4-way mirror";
+    ret[2].sval = ":None:2-way rotation:4-way rotation:2-way mirror:"
+        "2-way diagonal mirror:4-way mirror:4-way diagonal mirror:"
+        "8-way mirror";
     ret[2].ival = params->symm;
 
     ret[3].name = "Difficulty";
@@ -1350,77 +1367,103 @@ static int check_valid(int c, int r, digit *grid)
     return TRUE;
 }
 
-static void symmetry_limit(game_params *params, int *xlim, int *ylim, int s)
+static int symmetries(game_params *params, int x, int y, int *output, int s)
 {
     int c = params->c, r = params->r, cr = c*r;
+    int i = 0;
+
+#define ADD(x,y) (*output++ = (x), *output++ = (y), i++)
+
+    ADD(x, y);
 
     switch (s) {
       case SYMM_NONE:
-       *xlim = *ylim = cr;
-       break;
+       break;                         /* just x,y is all we need */
       case SYMM_ROT2:
-       *xlim = (cr+1) / 2;
-       *ylim = cr;
-       break;
-      case SYMM_REF4:
+        ADD(cr - 1 - x, cr - 1 - y);
+        break;
       case SYMM_ROT4:
-       *xlim = *ylim = (cr+1) / 2;
-       break;
+        ADD(cr - 1 - y, x);
+        ADD(y, cr - 1 - x);
+        ADD(cr - 1 - x, cr - 1 - y);
+        break;
+      case SYMM_REF2:
+        ADD(cr - 1 - x, y);
+        break;
+      case SYMM_REF2D:
+        ADD(y, x);
+        break;
+      case SYMM_REF4:
+        ADD(cr - 1 - x, y);
+        ADD(x, cr - 1 - y);
+        ADD(cr - 1 - x, cr - 1 - y);
+        break;
+      case SYMM_REF4D:
+        ADD(y, x);
+        ADD(cr - 1 - x, cr - 1 - y);
+        ADD(cr - 1 - y, cr - 1 - x);
+        break;
+      case SYMM_REF8:
+        ADD(cr - 1 - x, y);
+        ADD(x, cr - 1 - y);
+        ADD(cr - 1 - x, cr - 1 - y);
+        ADD(y, x);
+        ADD(y, cr - 1 - x);
+        ADD(cr - 1 - y, x);
+        ADD(cr - 1 - y, cr - 1 - x);
+        break;
     }
+
+#undef ADD
+
+    return i;
 }
 
-static int symmetries(game_params *params, int x, int y, int *output, int s)
+static char *encode_solve_move(int cr, digit *grid)
 {
-    int c = params->c, r = params->r, cr = c*r;
-    int i = 0;
+    int i, len;
+    char *ret, *p, *sep;
 
-    *output++ = x;
-    *output++ = y;
-    i++;
+    /*
+     * It's surprisingly easy to work out _exactly_ how long this
+     * string needs to be. To decimal-encode all the numbers from 1
+     * to n:
+     * 
+     *  - every number has a units digit; total is n.
+     *  - all numbers above 9 have a tens digit; total is max(n-9,0).
+     *  - all numbers above 99 have a hundreds digit; total is max(n-99,0).
+     *  - and so on.
+     */
+    len = 0;
+    for (i = 1; i <= cr; i *= 10)
+       len += max(cr - i + 1, 0);
+    len += cr;                /* don't forget the commas */
+    len *= cr;                /* there are cr rows of these */
 
-    switch (s) {
-      case SYMM_NONE:
-       break;                         /* just x,y is all we need */
-      case SYMM_REF4:
-      case SYMM_ROT4:
-       switch (s) {
-         case SYMM_REF4:
-           *output++ = cr - 1 - x;
-           *output++ = y;
-           i++;
-
-           *output++ = x;
-           *output++ = cr - 1 - y;
-           i++;
-           break;
-         case SYMM_ROT4:
-           *output++ = cr - 1 - y;
-           *output++ = x;
-           i++;
-
-           *output++ = y;
-           *output++ = cr - 1 - x;
-           i++;
-           break;
-       }
-       /* fall through */
-      case SYMM_ROT2:
-       *output++ = cr - 1 - x;
-       *output++ = cr - 1 - y;
-       i++;
-       break;
+    /*
+     * Now len is one bigger than the total size of the
+     * comma-separated numbers (because we counted an
+     * additional leading comma). We need to have a leading S
+     * and a trailing NUL, so we're off by one in total.
+     */
+    len++;
+
+    ret = snewn(len, char);
+    p = ret;
+    *p++ = 'S';
+    sep = "";
+    for (i = 0; i < cr*cr; i++) {
+       p += sprintf(p, "%s%d", sep, grid[i]);
+       sep = ",";
     }
+    *p++ = '\0';
+    assert(p - ret == len);
 
-    return i;
+    return ret;
 }
 
-struct game_aux_info {
-    int c, r;
-    digit *grid;
-};
-
 static char *new_game_desc(game_params *params, random_state *rs,
-                          game_aux_info **aux, int interactive)
+                          char **aux, int interactive)
 {
     int c = params->c, r = params->r, cr = c*r;
     int area = cr*cr;
@@ -1430,7 +1473,7 @@ static char *new_game_desc(game_params *params, random_state *rs,
     int ret;
     char *desc;
     int coords[16], ncoords;
-    int xlim, ylim;
+    int *symmclasses, nsymmclasses;
     int maxdiff, recursing;
 
     /*
@@ -1448,6 +1491,31 @@ static char *new_game_desc(game_params *params, random_state *rs,
     grid2 = snewn(area, digit);
 
     /*
+     * Find the set of equivalence classes of squares permitted
+     * by the selected symmetry. We do this by enumerating all
+     * the grid squares which have no symmetric companion
+     * sorting lower than themselves.
+     */
+    nsymmclasses = 0;
+    symmclasses = snewn(cr * cr, int);
+    {
+        int x, y;
+
+        for (y = 0; y < cr; y++)
+            for (x = 0; x < cr; x++) {
+                int i = y*cr+x;
+                int j;
+
+                ncoords = symmetries(params, x, y, coords, params->symm);
+                for (j = 0; j < ncoords; j++)
+                    if (coords[2*j+1]*cr+coords[2*j] < i)
+                        break;
+                if (j == ncoords)
+                    symmclasses[nsymmclasses++] = i;
+            }
+    }
+
+    /*
      * Loop until we get a grid of the required difficulty. This is
      * nasty, but it seems to be unpleasantly hard to generate
      * difficult grids otherwise.
@@ -1463,32 +1531,25 @@ static char *new_game_desc(game_params *params, random_state *rs,
         assert(check_valid(c, r, grid));
 
        /*
-        * Save the solved grid in the aux_info.
+        * Save the solved grid in aux.
         */
        {
-           game_aux_info *ai = snew(game_aux_info);
-           ai->c = c;
-           ai->r = r;
-           ai->grid = snewn(cr * cr, digit);
-           memcpy(ai->grid, grid, cr * cr * sizeof(digit));
            /*
             * We might already have written *aux the last time we
             * went round this loop, in which case we should free
-            * the old aux_info before overwriting it with the new
-            * one.
+            * the old aux before overwriting it with the new one.
             */
             if (*aux) {
-               sfree((*aux)->grid);
                sfree(*aux);
             }
-           *aux = ai;
+
+            *aux = encode_solve_move(cr, grid);
        }
 
         /*
          * Now we have a solved grid, start removing things from it
          * while preserving solubility.
          */
-        symmetry_limit(params, &xlim, &ylim, params->symm);
        recursing = FALSE;
         while (1) {
             int x, y, i, j;
@@ -1499,13 +1560,15 @@ static char *new_game_desc(game_params *params, random_state *rs,
              */
             nlocs = 0;
 
-            for (x = 0; x < xlim; x++)
-                for (y = 0; y < ylim; y++)
-                    if (grid[y*cr+x]) {
-                        locs[nlocs].x = x;
-                        locs[nlocs].y = y;
-                        nlocs++;
-                    }
+            for (i = 0; i < nsymmclasses; i++) {
+                x = symmclasses[i] % cr;
+                y = symmclasses[i] / cr;
+                if (grid[y*cr+x]) {
+                    locs[nlocs].x = x;
+                    locs[nlocs].y = y;
+                    nlocs++;
+                }
+            }
 
             /*
              * Now shuffle that list.
@@ -1570,6 +1633,8 @@ static char *new_game_desc(game_params *params, random_state *rs,
     sfree(grid2);
     sfree(locs);
 
+    sfree(symmclasses);
+
     /*
      * Now we have the grid as it will be presented to the user.
      * Encode it in a game desc.
@@ -1619,12 +1684,6 @@ static char *new_game_desc(game_params *params, random_state *rs,
     return desc;
 }
 
-static void game_free_aux_info(game_aux_info *aux)
-{
-    sfree(aux->grid);
-    sfree(aux);
-}
-
 static char *validate_desc(game_params *params, char *desc)
 {
     int area = params->r * params->r * params->c * params->c;
@@ -1726,39 +1785,38 @@ static void free_game(game_state *state)
     sfree(state);
 }
 
-static game_state *solve_game(game_state *state, game_aux_info *ai,
-                             char **error)
+static char *solve_game(game_state *state, game_state *currstate,
+                       char *ai, char **error)
 {
-    game_state *ret;
     int c = state->c, r = state->r, cr = c*r;
+    char *ret;
+    digit *grid;
     int rsolve_ret;
 
-    ret = dup_game(state);
-    ret->completed = ret->cheated = TRUE;
-
     /*
-     * If we already have the solution in the aux_info, save
-     * ourselves some time.
+     * If we already have the solution in ai, save ourselves some
+     * time.
      */
-    if (ai) {
+    if (ai)
+        return dupstr(ai);
 
-       assert(c == ai->c);
-       assert(r == ai->r);
-       memcpy(ret->grid, ai->grid, cr * cr * sizeof(digit));
+    grid = snewn(cr*cr, digit);
+    memcpy(grid, state->grid, cr*cr);
+    rsolve_ret = rsolve(c, r, grid, NULL, 2);
 
-    } else {
-       rsolve_ret = rsolve(c, r, ret->grid, NULL, 2);
-
-       if (rsolve_ret != 1) {
-           free_game(ret);
-           if (rsolve_ret == 0)
-               *error = "No solution exists for this puzzle";
-           else
-               *error = "Multiple solutions exist for this puzzle";
-           return NULL;
-       }
+    if (rsolve_ret != 1) {
+        sfree(grid);
+        if (rsolve_ret == 0)
+            *error = "No solution exists for this puzzle";
+        else
+            *error = "Multiple solutions exist for this puzzle";
+        return NULL;
     }
 
+    ret = encode_solve_move(cr, grid);
+
+    sfree(grid);
+
     return ret;
 }
 
@@ -1854,6 +1912,15 @@ static void free_ui(game_ui *ui)
     sfree(ui);
 }
 
+char *encode_ui(game_ui *ui)
+{
+    return NULL;
+}
+
+void decode_ui(game_ui *ui, char *encoding)
+{
+}
+
 static void game_changed_state(game_ui *ui, game_state *oldstate,
                                game_state *newstate)
 {
@@ -1881,12 +1948,12 @@ struct game_drawstate {
     int *entered_items;
 };
 
-static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
-                             int x, int y, int button)
+static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
+                           int x, int y, int button)
 {
-    int c = from->c, r = from->r, cr = c*r;
+    int c = state->c, r = state->r, cr = c*r;
     int tx, ty;
-    game_state *ret;
+    char buf[80];
 
     button &= ~MOD_MASK;
 
@@ -1895,7 +1962,7 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
 
     if (tx >= 0 && tx < cr && ty >= 0 && ty < cr) {
         if (button == LEFT_BUTTON) {
-            if (from->immutable[ty*cr+tx]) {
+            if (state->immutable[ty*cr+tx]) {
                 ui->hx = ui->hy = -1;
             } else if (tx == ui->hx && ty == ui->hy && ui->hpencil == 0) {
                 ui->hx = ui->hy = -1;
@@ -1904,13 +1971,13 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
                 ui->hy = ty;
                 ui->hpencil = 0;
             }
-            return from;                      /* UI activity occurred */
+            return "";                /* UI activity occurred */
         }
         if (button == RIGHT_BUTTON) {
             /*
              * Pencil-mode highlighting for non filled squares.
              */
-            if (from->grid[ty*cr+tx] == 0) {
+            if (state->grid[ty*cr+tx] == 0) {
                 if (tx == ui->hx && ty == ui->hy && ui->hpencil) {
                     ui->hx = ui->hy = -1;
                 } else {
@@ -1921,7 +1988,7 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
             } else {
                 ui->hx = ui->hy = -1;
             }
-            return from;                      /* UI activity occurred */
+            return "";                /* UI activity occurred */
         }
     }
 
@@ -1944,7 +2011,7 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
          * able to highlight the square, but it never hurts to be
          * careful.
          */
-       if (from->immutable[ui->hy*cr+ui->hx])
+       if (state->immutable[ui->hy*cr+ui->hx])
            return NULL;
 
         /*
@@ -1953,16 +2020,57 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
          * have even been able to pencil-highlight the square, but
          * it never hurts to be careful.
          */
-        if (ui->hpencil && from->grid[ui->hy*cr+ui->hx])
+        if (ui->hpencil && state->grid[ui->hy*cr+ui->hx])
             return NULL;
 
+       sprintf(buf, "%c%d,%d,%d",
+               ui->hpencil && n > 0 ? 'P' : 'R', ui->hx, ui->hy, n);
+
+       ui->hx = ui->hy = -1;
+
+       return dupstr(buf);
+    }
+
+    return NULL;
+}
+
+static game_state *execute_move(game_state *from, char *move)
+{
+    int c = from->c, r = from->r, cr = c*r;
+    game_state *ret;
+    int x, y, n;
+
+    if (move[0] == 'S') {
+       char *p;
+
        ret = dup_game(from);
-        if (ui->hpencil && n > 0) {
-            int index = (ui->hy*cr+ui->hx) * cr + (n-1);
+       ret->completed = ret->cheated = TRUE;
+
+       p = move+1;
+       for (n = 0; n < cr*cr; n++) {
+           ret->grid[n] = atoi(p);
+
+           if (!*p || ret->grid[n] < 1 || ret->grid[n] > cr) {
+               free_game(ret);
+               return NULL;
+           }
+
+           while (*p && isdigit((unsigned char)*p)) p++;
+           if (*p == ',') p++;
+       }
+
+       return ret;
+    } else if ((move[0] == 'P' || move[0] == 'R') &&
+       sscanf(move+1, "%d,%d,%d", &x, &y, &n) == 3 &&
+       x >= 0 && x < cr && y >= 0 && y < cr && n >= 0 && n <= cr) {
+
+       ret = dup_game(from);
+        if (move[0] == 'P' && n > 0) {
+            int index = (y*cr+x) * cr + (n-1);
             ret->pencil[index] = !ret->pencil[index];
         } else {
-            ret->grid[ui->hy*cr+ui->hx] = n;
-            memset(ret->pencil + (ui->hy*cr+ui->hx)*cr, 0, cr);
+            ret->grid[y*cr+x] = n;
+            memset(ret->pencil + (y*cr+x)*cr, 0, cr);
 
             /*
              * We've made a real change to the grid. Check to see
@@ -1972,12 +2080,9 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
                 ret->completed = TRUE;
             }
         }
-       ui->hx = ui->hy = -1;
-
-       return ret;                    /* made a valid move */
-    }
-
-    return NULL;
+       return ret;
+    } else
+       return NULL;                   /* couldn't parse move string */
 }
 
 /* ----------------------------------------------------------------------
@@ -2296,7 +2401,6 @@ const struct game thegame = {
     TRUE, game_configure, custom_params,
     validate_params,
     new_game_desc,
-    game_free_aux_info,
     validate_desc,
     new_game,
     dup_game,
@@ -2305,8 +2409,11 @@ const struct game thegame = {
     TRUE, game_text_format,
     new_ui,
     free_ui,
+    encode_ui,
+    decode_ui,
     game_changed_state,
-    make_move,
+    interpret_move,
+    execute_move,
     game_size,
     game_colours,
     game_new_drawstate,