chiark / gitweb /
The game IDs for Net (and Netslide) have always been random seeds
authorSimon Tatham <anakin@pobox.com>
Mon, 16 May 2005 18:57:09 +0000 (18:57 +0000)
committerSimon Tatham <anakin@pobox.com>
Mon, 16 May 2005 18:57:09 +0000 (18:57 +0000)
rather than literal grid descriptions, which has always faintly
annoyed me because it makes it impossible to type in a grid from
another source. However, Gareth pointed out that short random-seed
game descriptions are useful, because you can read one out to
someone else without having to master the technology of cross-
machine cut and paste, or you can have two people enter the same
random seed simultaneously in order to race against each other to
complete the same puzzle. So both types of game ID seem to have
their uses.

Therefore, here's a reorganisation of the whole game ID concept.
There are now two types of game ID: one has a parameter string then
a hash then a piece of arbitrary random seed text, and the other has
a parameter string then a colon then a literal game description. For
most games, the latter is identical to the game IDs that were
previously valid; for Net and Netslide, old game IDs must be
translated into new ones by turning the colon into a hash, and
there's a new descriptive game ID format.

Random seed IDs are not guaranteed to be portable between software
versions (this is a major reason why I added version reporting
yesterday). Descriptive game IDs have a longer lifespan.

As an added bonus, I've removed the sections of documentation
dealing with game parameter encodings not shown in the game ID
(Rectangles expansion factor, Solo symmetry and difficulty settings
etc), because _all_ parameters must be specified in a random seed ID
and therefore users can easily find out the appropriate parameter
string for any settings they have configured.

[originally from svn r5788]

16 files changed:
cube.c
fifteen.c
gtk.c
midend.c
net.c
netslide.c
nullgame.c
osx.m
pattern.c
puzzles.but
puzzles.h
rect.c
sixteen.c
solo.c
twiddle.c
windows.c

diff --git a/cube.c b/cube.c
index 0933928690f9ab3e1ddec6b758dff0823c66648d..becf5dbe5e2487c8cf285a6ff6914cab068f5160 100644 (file)
--- a/cube.c
+++ b/cube.c
@@ -274,10 +274,8 @@ static game_params *dup_params(game_params *params)
     return ret;
 }
 
-static game_params *decode_params(char const *string)
+static void decode_params(game_params *ret, char const *string)
 {
-    game_params *ret = default_params();
-
     switch (*string) {
       case 't': ret->solid = TETRAHEDRON; string++; break;
       case 'c': ret->solid = CUBE;        string++; break;
@@ -291,11 +289,9 @@ static game_params *decode_params(char const *string)
         string++;
         ret->d2 = atoi(string);
     }
-
-    return ret;
 }
 
-static char *encode_params(game_params *params)
+static char *encode_params(game_params *params, int full)
 {
     char data[256];
 
@@ -589,13 +585,13 @@ static void classify_grid_square_callback(void *ctx, struct grid_square *sq)
        data->squareindex++;
 }
 
-static char *new_game_seed(game_params *params, random_state *rs,
+static char *new_game_desc(game_params *params, random_state *rs,
                           game_aux_info **aux)
 {
     struct grid_data data;
     int i, j, k, m, area, facesperclass;
     int *flags;
-    char *seed, *p;
+    char *desc, *p;
 
     /*
      * Enumerate the grid squares, dividing them into equivalence
@@ -659,8 +655,8 @@ static char *new_game_seed(game_params *params, random_state *rs,
      * the non-blue squares into a list in the now-unused gridptrs
      * array.
      */
-    seed = snewn(area / 4 + 40, char);
-    p = seed;
+    desc = snewn(area / 4 + 40, char);
+    p = desc;
     j = 0;
     k = 8;
     m = 0;
@@ -688,7 +684,7 @@ static char *new_game_seed(game_params *params, random_state *rs,
     sfree(data.gridptrs[0]);
     sfree(flags);
 
-    return seed;
+    return desc;
 }
 
 static void game_free_aux_info(game_aux_info *aux)
@@ -844,35 +840,35 @@ static struct solid *transform_poly(const struct solid *solid, int flip,
     return ret;
 }
 
-static char *validate_seed(game_params *params, char *seed)
+static char *validate_desc(game_params *params, char *desc)
 {
     int area = grid_area(params->d1, params->d2, solids[params->solid]->order);
     int i, j;
 
     i = (area + 3) / 4;
     for (j = 0; j < i; j++) {
-       int c = seed[j];
+       int c = desc[j];
        if (c >= '0' && c <= '9') continue;
        if (c >= 'A' && c <= 'F') continue;
        if (c >= 'a' && c <= 'f') continue;
        return "Not enough hex digits at start of string";
-       /* NB if seed[j]=='\0' that will also be caught here, so we're safe */
+       /* NB if desc[j]=='\0' that will also be caught here, so we're safe */
     }
 
-    if (seed[i] != ',')
+    if (desc[i] != ',')
        return "Expected ',' after hex digits";
 
     i++;
     do {
-       if (seed[i] < '0' || seed[i] > '9')
+       if (desc[i] < '0' || desc[i] > '9')
            return "Expected decimal integer after ','";
        i++;
-    } while (seed[i]);
+    } while (desc[i]);
 
     return NULL;
 }
 
-static game_state *new_game(game_params *params, char *seed)
+static game_state *new_game(game_params *params, char *desc)
 {
     game_state *state = snew(game_state);
     int area;
@@ -891,10 +887,10 @@ static game_state *new_game(game_params *params, char *seed)
 
     /*
      * Set up the blue squares and polyhedron position according to
-     * the game seed.
+     * the game description.
      */
     {
-       char *p = seed;
+       char *p = desc;
        int i, j, v;
 
        j = 8;
@@ -1557,9 +1553,9 @@ const struct game thegame = {
     dup_params,
     TRUE, game_configure, custom_params,
     validate_params,
-    new_game_seed,
+    new_game_desc,
     game_free_aux_info,
-    validate_seed,
+    validate_desc,
     new_game,
     dup_game,
     free_game,
index 3aba7fa1db416063a156069d5b089e240f228bbc..9d138b15a6c09d340bf343e2b5f22296b7467391 100644 (file)
--- a/fifteen.c
+++ b/fifteen.c
@@ -72,21 +72,17 @@ static game_params *dup_params(game_params *params)
     return ret;
 }
 
-static game_params *decode_params(char const *string)
+static void decode_params(game_params *ret, char const *string)
 {
-    game_params *ret = default_params();
-
     ret->w = ret->h = atoi(string);
     while (*string && isdigit(*string)) string++;
     if (*string == 'x') {
         string++;
         ret->h = atoi(string);
     }
-
-    return ret;
 }
 
-static char *encode_params(game_params *params)
+static char *encode_params(game_params *params, int full)
 {
     char data[256];
 
@@ -154,7 +150,7 @@ static int perm_parity(int *perm, int n)
     return ret;
 }
 
-static char *new_game_seed(game_params *params, random_state *rs,
+static char *new_game_desc(game_params *params, random_state *rs,
                           game_aux_info **aux)
 {
     int gap, n, i, x;
@@ -247,8 +243,8 @@ static char *new_game_seed(game_params *params, random_state *rs,
     }
 
     /*
-     * Now construct the game seed, by describing the tile array as
-     * a simple sequence of comma-separated integers.
+     * Now construct the game description, by describing the tile
+     * array as a simple sequence of comma-separated integers.
      */
     ret = NULL;
     retlen = 0;
@@ -275,14 +271,14 @@ static void game_free_aux_info(game_aux_info *aux)
     assert(!"Shouldn't happen");
 }
 
-static char *validate_seed(game_params *params, char *seed)
+static char *validate_desc(game_params *params, char *desc)
 {
     char *p, *err;
     int i, area;
     int *used;
 
     area = params->w * params->h;
-    p = seed;
+    p = desc;
     err = NULL;
 
     used = snewn(area, int);
@@ -326,7 +322,7 @@ static char *validate_seed(game_params *params, char *seed)
     return err;
 }
 
-static game_state *new_game(game_params *params, char *seed)
+static game_state *new_game(game_params *params, char *desc)
 {
     game_state *state = snew(game_state);
     int i;
@@ -338,7 +334,7 @@ static game_state *new_game(game_params *params, char *seed)
     state->tiles = snewn(state->n, int);
 
     state->gap_pos = 0;
-    p = seed;
+    p = desc;
     i = 0;
     for (i = 0; i < state->n; i++) {
         assert(*p);
@@ -818,9 +814,9 @@ const struct game thegame = {
     dup_params,
     TRUE, game_configure, custom_params,
     validate_params,
-    new_game_seed,
+    new_game_desc,
     game_free_aux_info,
-    validate_seed,
+    validate_desc,
     new_game,
     dup_game,
     free_game,
diff --git a/gtk.c b/gtk.c
index 5cd6832b3975dd558bd95b6dce8a59530296d825..5d78f4e05c8d9156582e58b551a2176166d548d5 100644 (file)
--- a/gtk.c
+++ b/gtk.c
@@ -1000,7 +1000,7 @@ static frontend *new_window(char *game_id, char **error)
 
     fe->me = midend_new(fe, &thegame);
     if (game_id) {
-        *error = midend_game_id(fe->me, game_id, FALSE);
+        *error = midend_game_id(fe->me, game_id);
         if (*error) {
             midend_free(fe->me);
             sfree(fe);
@@ -1035,6 +1035,14 @@ static frontend *new_window(char *game_id, char **error)
     add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Restart", 'r');
 
     menuitem = gtk_menu_item_new_with_label("Specific...");
+    gtk_object_set_data(GTK_OBJECT(menuitem), "user-data",
+                       GINT_TO_POINTER(CFG_DESC));
+    gtk_container_add(GTK_CONTAINER(menu), menuitem);
+    gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
+                      GTK_SIGNAL_FUNC(menu_config_event), fe);
+    gtk_widget_show(menuitem);
+
+    menuitem = gtk_menu_item_new_with_label("Random Seed...");
     gtk_object_set_data(GTK_OBJECT(menuitem), "user-data",
                        GINT_TO_POINTER(CFG_SEED));
     gtk_container_add(GTK_CONTAINER(menu), menuitem);
@@ -1239,34 +1247,42 @@ int main(int argc, char **argv)
      */
     if (argc > 1 && !strcmp(argv[1], "--generate")) {
        int n = 1;
-       char *params = NULL;
+       char *params = NULL, *seed = NULL;
        game_params *par;
        random_state *rs;
        char *parstr;
 
-       {
-           void *seed;
-           int seedlen;
-           get_random_seed(&seed, &seedlen);
-           rs = random_init(seed, seedlen);
-       }
-
        if (argc > 2)
            n = atoi(argv[2]);
        if (argc > 3)
            params = argv[3];
 
-       if (params)
-           par = thegame.decode_params(params);
-       else
-           par = thegame.default_params();
-       parstr = thegame.encode_params(par);
+        par = thegame.default_params();
+       if (params) {
+            if ( (seed = strchr(params, '#')) != NULL )
+                *seed++ = '\0';
+           thegame.decode_params(par, params);
+        } else {
+        }
+       parstr = thegame.encode_params(par, FALSE);
+
+       {
+           void *seeddata;
+           int seedlen;
+            if (seed) {
+                seeddata = seed;
+                seedlen = strlen(seed);
+            } else {
+                get_random_seed(&seeddata, &seedlen);
+            }
+           rs = random_init(seeddata, seedlen);
+       }
 
        while (n-- > 0) {
            game_aux_info *aux = NULL;
-           char *seed = thegame.new_seed(par, rs, &aux);
-           printf("%s:%s\n", parstr, seed);
-           sfree(seed);
+           char *desc = thegame.new_desc(par, rs, &aux);
+           printf("%s:%s\n", parstr, desc);
+           sfree(desc);
            if (aux)
                thegame.free_aux_info(aux);
        }
index 084cd1612c610b83172441e2f9f2e6bf04e45420..abae474499ad244645e3531352ddd5988e37152a 100644 (file)
--- a/midend.c
+++ b/midend.c
 
 #include "puzzles.h"
 
+enum { DEF_PARAMS, DEF_SEED, DEF_DESC };   /* for midend_game_id_int */
+
 struct midend_data {
     frontend *frontend;
     random_state *random;
     const game *ourgame;
 
-    char *seed;
+    char *desc, *seedstr;
     game_aux_info *aux_info;
-    int fresh_seed;
+    enum { GOT_SEED, GOT_DESC, GOT_NOTHING } genmode;
     int nstates, statesize, statepos;
 
     game_params **presets;
     char **preset_names;
     int npresets, presetsize;
 
-    game_params *params;
+    game_params *params, *tmpparams;
     game_state **states;
     game_drawstate *drawstate;
     game_state *oldstate;
@@ -58,9 +60,11 @@ midend_data *midend_new(frontend *fe, const game *ourgame)
     me->nstates = me->statesize = me->statepos = 0;
     me->states = NULL;
     me->params = ourgame->default_params();
-    me->seed = NULL;
+    me->tmpparams = NULL;
+    me->desc = NULL;
+    me->seedstr = NULL;
     me->aux_info = NULL;
-    me->fresh_seed = FALSE;
+    me->genmode = GOT_NOTHING;
     me->drawstate = NULL;
     me->oldstate = NULL;
     me->presets = NULL;
@@ -80,10 +84,14 @@ midend_data *midend_new(frontend *fe, const game *ourgame)
 void midend_free(midend_data *me)
 {
     sfree(me->states);
-    sfree(me->seed);
+    sfree(me->desc);
+    sfree(me->seedstr);
+    random_free(me->random);
     if (me->aux_info)
        me->ourgame->free_aux_info(me->aux_info);
     me->ourgame->free_params(me->params);
+    if (me->tmpparams)
+        me->ourgame->free_params(me->tmpparams);
     sfree(me);
 }
 
@@ -108,18 +116,45 @@ void midend_new_game(midend_data *me)
 
     assert(me->nstates == 0);
 
-    if (!me->fresh_seed) {
-       sfree(me->seed);
+    if (me->genmode == GOT_DESC) {
+       me->genmode = GOT_NOTHING;
+    } else {
+        random_state *rs;
+
+        if (me->genmode == GOT_SEED) {
+            me->genmode = GOT_NOTHING;
+        } else {
+            /*
+             * Generate a new random seed. 15 digits comes to about
+             * 48 bits, which should be more than enough.
+             */
+            char newseed[16];
+            int i;
+            newseed[15] = '\0';
+            for (i = 0; i < 15; i++)
+                newseed[i] = '0' + random_upto(me->random, 10);
+            sfree(me->seedstr);
+            me->seedstr = dupstr(newseed);
+        }
+
+       sfree(me->desc);
        if (me->aux_info)
            me->ourgame->free_aux_info(me->aux_info);
        me->aux_info = NULL;
-       me->seed = me->ourgame->new_seed(me->params, me->random,
-                                        &me->aux_info);
-    } else
-       me->fresh_seed = FALSE;
+
+        rs = random_init(me->seedstr, strlen(me->seedstr));
+        me->desc = me->ourgame->new_desc
+            (me->tmpparams ? me->tmpparams : me->params, rs, &me->aux_info);
+        random_free(rs);
+
+        if (me->tmpparams) {
+            me->ourgame->free_params(me->tmpparams);
+            me->tmpparams = NULL;
+        }
+    }
 
     ensure(me);
-    me->states[me->nstates++] = me->ourgame->new_game(me->params, me->seed);
+    me->states[me->nstates++] = me->ourgame->new_game(me->params, me->desc);
     me->statepos = 1;
     me->drawstate = me->ourgame->new_drawstate(me->states[0]);
     if (me->ui)
@@ -408,9 +443,9 @@ float *midend_colours(midend_data *me, int *ncolours)
 
     if (me->nstates == 0) {
        game_aux_info *aux = NULL;
-        char *seed = me->ourgame->new_seed(me->params, me->random, &aux);
-        state = me->ourgame->new_game(me->params, seed);
-        sfree(seed);
+        char *desc = me->ourgame->new_desc(me->params, me->random, &aux);
+        state = me->ourgame->new_game(me->params, desc);
+        sfree(desc);
        if (aux)
            me->ourgame->free_aux_info(aux);
     } else
@@ -474,22 +509,44 @@ config_item *midend_get_config(midend_data *me, int which, char **wintitle)
        *wintitle = dupstr(titlebuf);
        return me->ourgame->configure(me->params);
       case CFG_SEED:
-       sprintf(titlebuf, "%s game selection", me->ourgame->name);
+      case CFG_DESC:
+       sprintf(titlebuf, "%s %s selection", me->ourgame->name,
+                which == CFG_SEED ? "random" : "game");
        *wintitle = dupstr(titlebuf);
 
        ret = snewn(2, config_item);
 
        ret[0].type = C_STRING;
-       ret[0].name = "Game ID";
+        if (which == CFG_SEED)
+            ret[0].name = "Game random seed";
+        else
+            ret[0].name = "Game ID";
        ret[0].ival = 0;
         /*
-         * The text going in here will be a string encoding of the
-         * parameters, plus a colon, plus the game seed. This is a
-         * full game ID.
+         * For CFG_DESC the text going in here will be a string
+         * encoding of the restricted parameters, plus a colon,
+         * plus the game description. For CFG_SEED it will be the
+         * full parameters, plus a hash, plus the random seed data.
+         * Either of these is a valid full game ID (although only
+         * the former is likely to persist across many code
+         * changes).
          */
-        parstr = me->ourgame->encode_params(me->params);
-        ret[0].sval = snewn(strlen(parstr) + strlen(me->seed) + 2, char);
-        sprintf(ret[0].sval, "%s:%s", parstr, me->seed);
+        parstr = me->ourgame->encode_params(me->params, which == CFG_SEED);
+        if (which == CFG_DESC) {
+            ret[0].sval = snewn(strlen(parstr) + strlen(me->desc) + 2, char);
+            sprintf(ret[0].sval, "%s:%s", parstr, me->desc);
+        } else if (me->seedstr) {
+            ret[0].sval = snewn(strlen(parstr) + strlen(me->seedstr) + 2, char);
+            sprintf(ret[0].sval, "%s#%s", parstr, me->seedstr);
+        } else {
+            /*
+             * If the current game was not randomly generated, the
+             * best we can do is to give a template for typing a
+             * new seed in.
+             */
+            ret[0].sval = snewn(strlen(parstr) + 2, char);
+            sprintf(ret[0].sval, "%s#", parstr);
+        }
         sfree(parstr);
 
        ret[1].type = C_END;
@@ -503,62 +560,103 @@ config_item *midend_get_config(midend_data *me, int which, char **wintitle)
     return NULL;
 }
 
-char *midend_game_id(midend_data *me, char *id, int def_seed)
+static char *midend_game_id_int(midend_data *me, char *id, int defmode)
 {
-    char *error, *par, *seed;
-    game_params *params;
+    char *error, *par, *desc, *seed;
 
-    seed = strchr(id, ':');
+    seed = strchr(id, '#');
+    desc = strchr(id, ':');
 
-    if (seed) {
+    if (desc && (!seed || desc < seed)) {
+        /*
+         * We have a colon separating parameters from game
+         * description. So `par' now points to the parameters
+         * string, and `desc' to the description string.
+         */
+        *desc++ = '\0';
+        par = id;
+        seed = NULL;
+    } else if (seed && (!desc || seed < desc)) {
         /*
-         * We have a colon separating parameters from game seed. So
-         * `par' now points to the parameters string, and `seed' to
-         * the seed string.
+         * We have a colon separating parameters from random seed.
+         * So `par' now points to the parameters string, and `seed'
+         * to the seed string.
          */
         *seed++ = '\0';
         par = id;
+        desc = NULL;
     } else {
         /*
-         * We only have one string. Depending on `def_seed', we
-         * take it to be either parameters or seed.
+         * We only have one string. Depending on `defmode', we take
+         * it to be either parameters, seed or description.
          */
-        if (def_seed) {
+        if (defmode == DEF_SEED) {
             seed = id;
-            par = NULL;
+            par = desc = NULL;
+        } else if (defmode == DEF_DESC) {
+            desc = id;
+            par = seed = NULL;
         } else {
-            seed = NULL;
             par = id;
+            seed = desc = NULL;
         }
     }
 
     if (par) {
-        params = me->ourgame->decode_params(par);
-        error = me->ourgame->validate_params(params);
+        game_params *tmpparams;
+        tmpparams = me->ourgame->dup_params(me->params);
+        me->ourgame->decode_params(tmpparams, par);
+        error = me->ourgame->validate_params(tmpparams);
         if (error) {
-            me->ourgame->free_params(params);
+            me->ourgame->free_params(tmpparams);
             return error;
         }
-        me->ourgame->free_params(me->params);
-        me->params = params;
+        if (me->tmpparams)
+            me->ourgame->free_params(me->tmpparams);
+        me->tmpparams = tmpparams;
+
+        /*
+         * Now filter only the persistent parts of this state into
+         * the long-term params structure, unless we've _only_
+         * received a params string in which case the whole lot is
+         * persistent.
+         */
+        if (seed || desc) {
+            char *tmpstr = me->ourgame->encode_params(tmpparams, FALSE);
+            me->ourgame->decode_params(me->params, tmpstr);
+        } else {
+            me->ourgame->free_params(me->params);
+            me->params = me->ourgame->dup_params(tmpparams);
+        }
     }
 
-    if (seed) {
-        error = me->ourgame->validate_seed(me->params, seed);
+    if (desc) {
+        error = me->ourgame->validate_desc(me->params, desc);
         if (error)
             return error;
 
-        sfree(me->seed);
-        me->seed = dupstr(seed);
-        me->fresh_seed = TRUE;
+        sfree(me->desc);
+        me->desc = dupstr(desc);
+        me->genmode = GOT_DESC;
        if (me->aux_info)
            me->ourgame->free_aux_info(me->aux_info);
        me->aux_info = NULL;
     }
 
+    if (seed) {
+        sfree(me->seedstr);
+        me->seedstr = dupstr(seed);
+        me->genmode = GOT_SEED;
+    }
+
     return NULL;
 }
 
+char *midend_game_id(midend_data *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 *error;
@@ -579,7 +677,9 @@ char *midend_set_config(midend_data *me, int which, config_item *cfg)
        break;
 
       case CFG_SEED:
-        error = midend_game_id(me, cfg[0].sval, TRUE);
+      case CFG_DESC:
+        error = midend_game_id_int(me, cfg[0].sval,
+                                   (which == CFG_SEED ? DEF_SEED : DEF_DESC));
        if (error)
            return error;
        break;
diff --git a/net.c b/net.c
index c8cafc18787d055fdcae675466917ed8712c2168..530803160a7e4ca99a56fc48ce4171937069a0d6 100644 (file)
--- a/net.c
+++ b/net.c
@@ -75,18 +75,17 @@ struct game_params {
     float barrier_probability;
 };
 
-struct solved_game_state {
+struct game_aux_info {
     int width, height;
-    int refcount;
     unsigned char *tiles;
 };
 
 struct game_state {
-    int width, height, cx, cy, wrapping, completed, last_rotate_dir;
+    int width, height, cx, cy, wrapping, completed;
+    int last_rotate_x, last_rotate_y, last_rotate_dir;
     int used_solve, just_used_solve;
     unsigned char *tiles;
     unsigned char *barriers;
-    struct solved_game_state *solution;
 };
 
 #define OFFSET(x2,y2,x1,y1,dir,state) \
@@ -189,9 +188,8 @@ static game_params *dup_params(game_params *params)
     return ret;
 }
 
-static game_params *decode_params(char const *string)
+static void decode_params(game_params *ret, char const *string)
 {
-    game_params *ret = default_params();
     char const *p = string;
 
     ret->width = atoi(p);
@@ -207,11 +205,9 @@ static game_params *decode_params(char const *string)
     } else {
         ret->height = ret->width;
     }
-
-    return ret;
 }
 
-static char *encode_params(game_params *params)
+static char *encode_params(game_params *params, int full)
 {
     char ret[400];
     int len;
@@ -219,7 +215,7 @@ static char *encode_params(game_params *params)
     len = sprintf(ret, "%dx%d", params->width, params->height);
     if (params->wrapping)
         ret[len++] = 'w';
-    if (params->barrier_probability)
+    if (full && params->barrier_probability)
         len += sprintf(ret+len, "b%g", params->barrier_probability);
     assert(len < lenof(ret));
     ret[len] = '\0';
@@ -295,90 +291,27 @@ static char *validate_params(game_params *params)
 }
 
 /* ----------------------------------------------------------------------
- * Randomly select a new game seed.
+ * Randomly select a new game description.
  */
 
-static char *new_game_seed(game_params *params, random_state *rs,
+static char *new_game_desc(game_params *params, random_state *rs,
                           game_aux_info **aux)
 {
-    /*
-     * The full description of a Net game is far too large to
-     * encode directly in the seed, so by default we'll have to go
-     * for the simple approach of providing a random-number seed.
-     * 
-     * (This does not restrict me from _later on_ inventing a seed
-     * string syntax which can never be generated by this code -
-     * for example, strings beginning with a letter - allowing me
-     * to type in a precise game, and have new_game detect it and
-     * understand it and do something completely different.)
-     */
-    char buf[40];
-    sprintf(buf, "%lu", random_bits(rs, 32));
-    return dupstr(buf);
-}
-
-static void game_free_aux_info(game_aux_info *aux)
-{
-    assert(!"Shouldn't happen");
-}
+    tree234 *possibilities, *barriertree;
+    int w, h, x, y, cx, cy, nbarriers;
+    unsigned char *tiles, *barriers;
+    char *desc, *p;
 
-static char *validate_seed(game_params *params, char *seed)
-{
-    /*
-     * Since any string at all will suffice to seed the RNG, there
-     * is no validation required.
-     */
-    return NULL;
-}
-
-/* ----------------------------------------------------------------------
- * Construct an initial game state, given a seed and parameters.
- */
+    w = params->width;
+    h = params->height;
 
-static game_state *new_game(game_params *params, char *seed)
-{
-    random_state *rs;
-    game_state *state;
-    tree234 *possibilities, *barriers;
-    int w, h, x, y, nbarriers;
+    tiles = snewn(w * h, unsigned char);
+    memset(tiles, 0, w * h);
+    barriers = snewn(w * h, unsigned char);
+    memset(barriers, 0, w * h);
 
-    assert(params->width > 0 && params->height > 0);
-    assert(params->width > 1 || params->height > 1);
-
-    /*
-     * Create a blank game state.
-     */
-    state = snew(game_state);
-    w = state->width = params->width;
-    h = state->height = params->height;
-    state->cx = state->width / 2;
-    state->cy = state->height / 2;
-    state->wrapping = params->wrapping;
-    state->last_rotate_dir = 0;
-    state->completed = state->used_solve = state->just_used_solve = FALSE;
-    state->tiles = snewn(state->width * state->height, unsigned char);
-    memset(state->tiles, 0, state->width * state->height);
-    state->barriers = snewn(state->width * state->height, unsigned char);
-    memset(state->barriers, 0, state->width * state->height);
-
-    /*
-     * Set up border barriers if this is a non-wrapping game.
-     */
-    if (!state->wrapping) {
-       for (x = 0; x < state->width; x++) {
-           barrier(state, x, 0) |= U;
-           barrier(state, x, state->height-1) |= D;
-       }
-       for (y = 0; y < state->height; y++) {
-           barrier(state, 0, y) |= L;
-           barrier(state, state->width-1, y) |= R;
-       }
-    }
-
-    /*
-     * Seed the internal random number generator.
-     */
-    rs = random_init(seed, strlen(seed));
+    cx = w / 2;
+    cy = h / 2;
 
     /*
      * Construct the unshuffled grid.
@@ -424,14 +357,14 @@ static game_state *new_game(game_params *params, char *seed)
      */
     possibilities = newtree234(xyd_cmp);
 
-    if (state->cx+1 < state->width)
-       add234(possibilities, new_xyd(state->cx, state->cy, R));
-    if (state->cy-1 >= 0)
-       add234(possibilities, new_xyd(state->cx, state->cy, U));
-    if (state->cx-1 >= 0)
-       add234(possibilities, new_xyd(state->cx, state->cy, L));
-    if (state->cy+1 < state->height)
-       add234(possibilities, new_xyd(state->cx, state->cy, D));
+    if (cx+1 < w)
+       add234(possibilities, new_xyd(cx, cy, R));
+    if (cy-1 >= 0)
+       add234(possibilities, new_xyd(cx, cy, U));
+    if (cx-1 >= 0)
+       add234(possibilities, new_xyd(cx, cy, L));
+    if (cy+1 < h)
+       add234(possibilities, new_xyd(cx, cy, D));
 
     while (count234(possibilities) > 0) {
        int i;
@@ -448,7 +381,7 @@ static game_state *new_game(game_params *params, char *seed)
        d1 = xyd->direction;
        sfree(xyd);
 
-       OFFSET(x2, y2, x1, y1, d1, state);
+       OFFSET(x2, y2, x1, y1, d1, params);
        d2 = F(d1);
 #ifdef DEBUG
        printf("picked (%d,%d,%c) <-> (%d,%d,%c)\n",
@@ -459,20 +392,20 @@ static game_state *new_game(game_params *params, char *seed)
         * Make the connection. (We should be moving to an as yet
         * unused tile.)
         */
-       tile(state, x1, y1) |= d1;
-       assert(tile(state, x2, y2) == 0);
-       tile(state, x2, y2) |= d2;
+       index(params, tiles, x1, y1) |= d1;
+       assert(index(params, tiles, x2, y2) == 0);
+       index(params, tiles, x2, y2) |= d2;
 
        /*
         * If we have created a T-piece, remove its last
         * possibility.
         */
-       if (COUNT(tile(state, x1, y1)) == 3) {
+       if (COUNT(index(params, tiles, x1, y1)) == 3) {
            struct xyd xyd1, *xydp;
 
            xyd1.x = x1;
            xyd1.y = y1;
-           xyd1.direction = 0x0F ^ tile(state, x1, y1);
+           xyd1.direction = 0x0F ^ index(params, tiles, x1, y1);
 
            xydp = find234(possibilities, &xyd1, NULL);
 
@@ -494,7 +427,7 @@ static game_state *new_game(game_params *params, char *seed)
            int x3, y3, d3;
            struct xyd xyd1, *xydp;
 
-           OFFSET(x3, y3, x2, y2, d, state);
+           OFFSET(x3, y3, x2, y2, d, params);
            d3 = F(d);
 
            xyd1.x = x3;
@@ -523,20 +456,20 @@ static game_state *new_game(game_params *params, char *seed)
            if (d == d2)
                continue;              /* we've got this one already */
 
-           if (!state->wrapping) {
+           if (!params->wrapping) {
                if (d == U && y2 == 0)
                    continue;
-               if (d == D && y2 == state->height-1)
+               if (d == D && y2 == h-1)
                    continue;
                if (d == L && x2 == 0)
                    continue;
-               if (d == R && x2 == state->width-1)
+               if (d == R && x2 == w-1)
                    continue;
            }
 
-           OFFSET(x3, y3, x2, y2, d, state);
+           OFFSET(x3, y3, x2, y2, d, params);
 
-           if (tile(state, x3, y3))
+           if (index(params, tiles, x3, y3))
                continue;              /* this would create a loop */
 
 #ifdef DEBUG
@@ -553,53 +486,49 @@ static game_state *new_game(game_params *params, char *seed)
     /*
      * Now compute a list of the possible barrier locations.
      */
-    barriers = newtree234(xyd_cmp);
-    for (y = 0; y < state->height; y++) {
-       for (x = 0; x < state->width; x++) {
-
-           if (!(tile(state, x, y) & R) &&
-                (state->wrapping || x < state->width-1))
-               add234(barriers, new_xyd(x, y, R));
-           if (!(tile(state, x, y) & D) &&
-                (state->wrapping || y < state->height-1))
-               add234(barriers, new_xyd(x, y, D));
+    barriertree = newtree234(xyd_cmp);
+    for (y = 0; y < h; y++) {
+       for (x = 0; x < w; x++) {
+
+           if (!(index(params, tiles, x, y) & R) &&
+                (params->wrapping || x < w-1))
+               add234(barriertree, new_xyd(x, y, R));
+           if (!(index(params, tiles, x, y) & D) &&
+                (params->wrapping || y < h-1))
+               add234(barriertree, new_xyd(x, y, D));
        }
     }
 
     /*
-     * Save the unshuffled grid. We do this using a separate
-     * reference-counted structure since it's a large chunk of
-     * memory which we don't want to have to replicate in every
-     * game state while playing.
+     * Save the unshuffled grid in an aux_info.
      */
     {
-       struct solved_game_state *solution;
+       game_aux_info *solution;
 
-       solution = snew(struct solved_game_state);
-       solution->width = state->width;
-       solution->height = state->height;
-       solution->refcount = 1;
-       solution->tiles = snewn(state->width * state->height, unsigned char);
-       memcpy(solution->tiles, state->tiles, state->width * state->height);
+       solution = snew(game_aux_info);
+       solution->width = w;
+       solution->height = h;
+       solution->tiles = snewn(w * h, unsigned char);
+       memcpy(solution->tiles, tiles, w * h);
 
-       state->solution = solution;
+       *aux = solution;
     }
 
     /*
      * Now shuffle the grid.
      */
-    for (y = 0; y < state->height; y++) {
-       for (x = 0; x < state->width; x++) {
-           int orig = tile(state, x, y);
+    for (y = 0; y < h; y++) {
+       for (x = 0; x < w; x++) {
+           int orig = index(params, tiles, x, y);
            int rot = random_upto(rs, 4);
-           tile(state, x, y) = ROT(orig, rot);
+           index(params, tiles, x, y) = ROT(orig, rot);
        }
     }
 
     /*
      * And now choose barrier locations. (We carefully do this
      * _after_ shuffling, so that changing the barrier rate in the
-     * params while keeping the game seed the same will give the
+     * params while keeping the random seed the same will give the
      * same shuffled grid and _only_ change the barrier locations.
      * Also the way we choose barrier locations, by repeatedly
      * choosing one possibility from the list until we have enough,
@@ -610,8 +539,8 @@ static game_state *new_game(game_params *params, char *seed)
      * the original 10 plus 10 more, rather than getting 20 new
      * ones and the chance of remembering your first 10.)
      */
-    nbarriers = (int)(params->barrier_probability * count234(barriers));
-    assert(nbarriers >= 0 && nbarriers <= count234(barriers));
+    nbarriers = (int)(params->barrier_probability * count234(barriertree));
+    assert(nbarriers >= 0 && nbarriers <= count234(barriertree));
 
     while (nbarriers > 0) {
        int i;
@@ -621,8 +550,8 @@ static game_state *new_game(game_params *params, char *seed)
        /*
         * Extract a randomly chosen barrier from the list.
         */
-       i = random_upto(rs, count234(barriers));
-       xyd = delpos234(barriers, i);
+       i = random_upto(rs, count234(barriertree));
+       xyd = delpos234(barriertree, i);
 
        assert(xyd != NULL);
 
@@ -631,11 +560,11 @@ static game_state *new_game(game_params *params, char *seed)
        d1 = xyd->direction;
        sfree(xyd);
 
-       OFFSET(x2, y2, x1, y1, d1, state);
+       OFFSET(x2, y2, x1, y1, d1, params);
        d2 = F(d1);
 
-       barrier(state, x1, y1) |= d1;
-       barrier(state, x2, y2) |= d2;
+       index(params, barriers, x1, y1) |= d1;
+       index(params, barriers, x2, y2) |= d2;
 
        nbarriers--;
     }
@@ -646,10 +575,147 @@ static game_state *new_game(game_params *params, char *seed)
     {
        struct xyd *xyd;
 
-       while ( (xyd = delpos234(barriers, 0)) != NULL)
+       while ( (xyd = delpos234(barriertree, 0)) != NULL)
            sfree(xyd);
 
-       freetree234(barriers);
+       freetree234(barriertree);
+    }
+
+    /*
+     * Finally, encode the grid into a string game description.
+     * 
+     * My syntax is extremely simple: each square is encoded as a
+     * hex digit in which bit 0 means a connection on the right,
+     * bit 1 means up, bit 2 left and bit 3 down. (i.e. the same
+     * encoding as used internally). Each digit is followed by
+     * optional barrier indicators: `v' means a vertical barrier to
+     * the right of it, and `h' means a horizontal barrier below
+     * it.
+     */
+    desc = snewn(w * h * 3 + 1, char);
+    p = desc;
+    for (y = 0; y < h; y++) {
+        for (x = 0; x < w; x++) {
+            *p++ = "0123456789abcdef"[index(params, tiles, x, y)];
+            if ((params->wrapping || x < w-1) &&
+                (index(params, barriers, x, y) & R))
+                *p++ = 'v';
+            if ((params->wrapping || y < h-1) &&
+                (index(params, barriers, x, y) & D))
+                *p++ = 'h';
+        }
+    }
+    assert(p - desc <= w*h*3);
+
+    sfree(tiles);
+    sfree(barriers);
+
+    return desc;
+}
+
+static void game_free_aux_info(game_aux_info *aux)
+{
+    sfree(aux->tiles);
+    sfree(aux);
+}
+
+static char *validate_desc(game_params *params, char *desc)
+{
+    int w = params->width, h = params->height;
+    int i;
+
+    for (i = 0; i < w*h; i++) {
+        if (*desc >= '0' && *desc <= '9')
+            /* OK */;
+        else if (*desc >= 'a' && *desc <= 'f')
+            /* OK */;
+        else if (*desc >= 'A' && *desc <= 'F')
+            /* OK */;
+        else if (!*desc)
+            return "Game description shorter than expected";
+        else
+            return "Game description contained unexpected character";
+        desc++;
+        while (*desc == 'h' || *desc == 'v')
+            desc++;
+    }
+    if (*desc)
+        return "Game description longer than expected";
+
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Construct an initial game state, given a description and parameters.
+ */
+
+static game_state *new_game(game_params *params, char *desc)
+{
+    game_state *state;
+    int w, h, x, y;
+
+    assert(params->width > 0 && params->height > 0);
+    assert(params->width > 1 || params->height > 1);
+
+    /*
+     * Create a blank game state.
+     */
+    state = snew(game_state);
+    w = state->width = params->width;
+    h = state->height = params->height;
+    state->cx = state->width / 2;
+    state->cy = state->height / 2;
+    state->wrapping = params->wrapping;
+    state->last_rotate_dir = state->last_rotate_x = state->last_rotate_y = 0;
+    state->completed = state->used_solve = state->just_used_solve = FALSE;
+    state->tiles = snewn(state->width * state->height, unsigned char);
+    memset(state->tiles, 0, state->width * state->height);
+    state->barriers = snewn(state->width * state->height, unsigned char);
+    memset(state->barriers, 0, state->width * state->height);
+
+    /*
+     * Parse the game description into the grid.
+     */
+    for (y = 0; y < h; y++) {
+        for (x = 0; x < w; x++) {
+            if (*desc >= '0' && *desc <= '9')
+                tile(state, x, y) = *desc - '0';
+            else if (*desc >= 'a' && *desc <= 'f')
+                tile(state, x, y) = *desc - 'a' + 10;
+            else if (*desc >= 'A' && *desc <= 'F')
+                tile(state, x, y) = *desc - 'A' + 10;
+            if (*desc)
+                desc++;
+            while (*desc == 'h' || *desc == 'v') {
+                int x2, y2, d1, d2;
+                if (*desc == 'v')
+                    d1 = R;
+                else
+                    d1 = D;
+
+                OFFSET(x2, y2, x, y, d1, state);
+                d2 = F(d1);
+
+                barrier(state, x, y) |= d1;
+                barrier(state, x2, y2) |= d2;
+
+                desc++;
+            }
+        }
+    }
+
+    /*
+     * Set up border barriers if this is a non-wrapping game.
+     */
+    if (!state->wrapping) {
+       for (x = 0; x < state->width; x++) {
+           barrier(state, x, 0) |= U;
+           barrier(state, x, state->height-1) |= D;
+       }
+       for (y = 0; y < state->height; y++) {
+           barrier(state, 0, y) |= L;
+           barrier(state, state->width-1, y) |= R;
+       }
     }
 
     /*
@@ -700,8 +766,6 @@ static game_state *new_game(game_params *params, char *seed)
        }
     }
 
-    random_free(rs);
-
     return state;
 }
 
@@ -719,23 +783,18 @@ static game_state *dup_game(game_state *state)
     ret->used_solve = state->used_solve;
     ret->just_used_solve = state->just_used_solve;
     ret->last_rotate_dir = state->last_rotate_dir;
+    ret->last_rotate_x = state->last_rotate_x;
+    ret->last_rotate_y = state->last_rotate_y;
     ret->tiles = snewn(state->width * state->height, unsigned char);
     memcpy(ret->tiles, state->tiles, state->width * state->height);
     ret->barriers = snewn(state->width * state->height, unsigned char);
     memcpy(ret->barriers, state->barriers, state->width * state->height);
-    ret->solution = state->solution;
-    if (ret->solution)
-       ret->solution->refcount++;
 
     return ret;
 }
 
 static void free_game(game_state *state)
 {
-    if (state->solution && --state->solution->refcount <= 0) {
-       sfree(state->solution->tiles);
-       sfree(state->solution);
-    }
     sfree(state->tiles);
     sfree(state->barriers);
     sfree(state);
@@ -746,22 +805,15 @@ static game_state *solve_game(game_state *state, game_aux_info *aux,
 {
     game_state *ret;
 
-    if (!state->solution) {
-       /*
-        * 2005-05-02: This shouldn't happen, at the time of
-        * writing, because Net is incapable of receiving a puzzle
-        * description from outside. If in future it becomes so,
-        * then we will have puzzles for which we don't know the
-        * solution.
-        */
+    if (!aux) {
        *error = "Solution not known for this puzzle";
        return NULL;
     }
 
-    assert(state->solution->width == state->width);
-    assert(state->solution->height == state->height);
+    assert(aux->width == state->width);
+    assert(aux->height == state->height);
     ret = dup_game(state);
-    memcpy(ret->tiles, state->solution->tiles, ret->width * ret->height);
+    memcpy(ret->tiles, aux->tiles, ret->width * ret->height);
     ret->used_solve = ret->just_used_solve = TRUE;
     ret->completed = TRUE;
 
@@ -942,7 +994,7 @@ static game_state *make_move(game_state *state, game_ui *ui,
        ret = dup_game(state);
        ret->just_used_solve = FALSE;
        tile(ret, tx, ty) ^= LOCKED;
-       ret->last_rotate_dir = 0;
+       ret->last_rotate_dir = ret->last_rotate_x = ret->last_rotate_y = 0;
        return ret;
 
     } else if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
@@ -968,6 +1020,8 @@ static game_state *make_move(game_state *state, game_ui *ui,
             tile(ret, tx, ty) = C(orig);
             ret->last_rotate_dir = -1;
         }
+        ret->last_rotate_x = tx;
+        ret->last_rotate_y = ty;
 
     } else if (button == 'J') {
 
@@ -987,6 +1041,7 @@ static game_state *make_move(game_state *state, game_ui *ui,
             }
         }
         ret->last_rotate_dir = 0; /* suppress animation */
+        ret->last_rotate_x = ret->last_rotate_y = 0;
 
     } else assert(0);
 
@@ -1000,7 +1055,7 @@ static game_state *make_move(game_state *state, game_ui *ui,
 
        for (x1 = 0; x1 < ret->width; x1++)
            for (y1 = 0; y1 < ret->height; y1++)
-               if (!index(ret, active, x1, y1)) {
+               if ((tile(ret, x1, y1) & 0xF) && !index(ret, active, x1, y1)) {
                    complete = FALSE;
                    goto break_label;  /* break out of two loops at once */
                }
@@ -1421,23 +1476,15 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                                 state->last_rotate_dir;
     if (oldstate && (t < ROTATE_TIME) && last_rotate_dir) {
         /*
-         * We're animating a single tile rotation. Find the turning tile,
-         * if any.
+         * We're animating a single tile rotation. Find the turning
+         * tile.
          */
-        for (x = 0; x < oldstate->width; x++)
-            for (y = 0; y < oldstate->height; y++)
-                if ((tile(oldstate, x, y) ^ tile(state, x, y)) & 0xF) {
-                    tx = x, ty = y;
-                    goto break_label;  /* leave both loops at once */
-                }
-        break_label:
-
-        if (tx >= 0) {
-            angle = last_rotate_dir * dir * 90.0F * (t / ROTATE_TIME);
-            state = oldstate;
-        }
+        tx = (dir==-1 ? oldstate->last_rotate_x : state->last_rotate_x);
+        ty = (dir==-1 ? oldstate->last_rotate_y : state->last_rotate_y);
+        angle = last_rotate_dir * dir * 90.0F * (t / ROTATE_TIME);
+        state = oldstate;
     }
-    
+
     frame = -1;
     if (ft > 0) {
         /*
@@ -1494,16 +1541,19 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
      */
     {
        char statusbuf[256];
-       int i, n, a;
+       int i, n, n2, a;
 
        n = state->width * state->height;
-       for (i = a = 0; i < n; i++)
+       for (i = a = n2 = 0; i < n; i++) {
            if (active[i])
                a++;
+            if (state->tiles[i] & 0xF)
+                n2++;
+        }
 
        sprintf(statusbuf, "%sActive: %d/%d",
                (state->used_solve ? "Auto-solved. " :
-                state->completed ? "COMPLETED! " : ""), a, n);
+                state->completed ? "COMPLETED! " : ""), a, n2);
 
        status_bar(fe, statusbuf);
     }
@@ -1514,7 +1564,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)
 {
-    int x, y, last_rotate_dir;
+    int last_rotate_dir;
 
     /*
      * Don't animate an auto-solve move.
@@ -1528,19 +1578,8 @@ static float game_anim_length(game_state *oldstate,
      */
     last_rotate_dir = dir==-1 ? oldstate->last_rotate_dir :
                                 newstate->last_rotate_dir;
-    if (last_rotate_dir) {
-
-        /*
-         * If there's a tile which has been rotated, allow time to
-         * animate its rotation.
-         */
-        for (x = 0; x < oldstate->width; x++)
-            for (y = 0; y < oldstate->height; y++)
-                if ((tile(oldstate, x, y) ^ tile(newstate, x, y)) & 0xF) {
-                    return ROTATE_TIME;
-                }
-
-    }
+    if (last_rotate_dir)
+        return ROTATE_TIME;
 
     return 0.0F;
 }
@@ -1589,9 +1628,9 @@ const struct game thegame = {
     dup_params,
     TRUE, game_configure, custom_params,
     validate_params,
-    new_game_seed,
+    new_game_desc,
     game_free_aux_info,
-    validate_seed,
+    validate_desc,
     new_game,
     dup_game,
     free_game,
index 02fa43816c1a6d770b74d01517eae22d769eee68..6ae956902b85e421614cf1578aeaa097a120e8db 100644 (file)
@@ -82,9 +82,8 @@ struct game_params {
     float barrier_probability;
 };
 
-struct solved_game_state {
+struct game_aux_info {
     int width, height;
-    int refcount;
     unsigned char *tiles;
 };
 
@@ -101,7 +100,6 @@ struct game_state {
 
     unsigned char *tiles;
     unsigned char *barriers;
-    struct solved_game_state *solution;
 };
 
 #define OFFSET(x2,y2,x1,y1,dir,state) \
@@ -144,7 +142,9 @@ static struct xyd *new_xyd(int x, int y, int direction)
 }
 
 static void slide_col(game_state *state, int dir, int col);
+static void slide_col_int(int w, int h, unsigned char *tiles, int dir, int col);
 static void slide_row(game_state *state, int dir, int row);
+static void slide_row_int(int w, int h, unsigned char *tiles, int dir, int row);
 
 /* ----------------------------------------------------------------------
  * Manage game parameters.
@@ -206,9 +206,8 @@ static game_params *dup_params(game_params *params)
     return ret;
 }
 
-static game_params *decode_params(char const *string)
+static void decode_params(game_params *ret, char const *string)
 {
-    game_params *ret = default_params();
     char const *p = string;
 
     ret->wrapping = FALSE;
@@ -227,11 +226,9 @@ static game_params *decode_params(char const *string)
     } else {
         ret->height = ret->width;
     }
-
-    return ret;
 }
 
-static char *encode_params(game_params *params)
+static char *encode_params(game_params *params, int full)
 {
     char ret[400];
     int len;
@@ -239,7 +236,7 @@ static char *encode_params(game_params *params)
     len = sprintf(ret, "%dx%d", params->width, params->height);
     if (params->wrapping)
         ret[len++] = 'w';
-    if (params->barrier_probability)
+    if (full && params->barrier_probability)
         len += sprintf(ret+len, "b%g", params->barrier_probability);
     assert(len < lenof(ret));
     ret[len] = '\0';
@@ -313,94 +310,27 @@ static char *validate_params(game_params *params)
 }
 
 /* ----------------------------------------------------------------------
- * Randomly select a new game seed.
+ * Randomly select a new game description.
  */
 
-static char *new_game_seed(game_params *params, random_state *rs,
+static char *new_game_desc(game_params *params, random_state *rs,
                           game_aux_info **aux)
 {
-    /*
-     * The full description of a Net game is far too large to
-     * encode directly in the seed, so by default we'll have to go
-     * for the simple approach of providing a random-number seed.
-     * 
-     * (This does not restrict me from _later on_ inventing a seed
-     * string syntax which can never be generated by this code -
-     * for example, strings beginning with a letter - allowing me
-     * to type in a precise game, and have new_game detect it and
-     * understand it and do something completely different.)
-     */
-    char buf[40];
-    sprintf(buf, "%lu", random_bits(rs, 32));
-    return dupstr(buf);
-}
+    tree234 *possibilities, *barriertree;
+    int w, h, x, y, cx, cy, nbarriers;
+    unsigned char *tiles, *barriers;
+    char *desc, *p;
 
-static void game_free_aux_info(game_aux_info *aux)
-{
-    assert(!"Shouldn't happen");
-}
+    w = params->width;
+    h = params->height;
 
-static char *validate_seed(game_params *params, char *seed)
-{
-    /*
-     * Since any string at all will suffice to seed the RNG, there
-     * is no validation required.
-     */
-    return NULL;
-}
-
-/* ----------------------------------------------------------------------
- * Construct an initial game state, given a seed and parameters.
- */
-
-static game_state *new_game(game_params *params, char *seed)
-{
-    random_state *rs;
-    game_state *state;
-    tree234 *possibilities, *barriers;
-    int w, h, x, y, nbarriers;
+    tiles = snewn(w * h, unsigned char);
+    memset(tiles, 0, w * h);
+    barriers = snewn(w * h, unsigned char);
+    memset(barriers, 0, w * h);
 
-    assert(params->width > 0 && params->height > 0);
-    assert(params->width > 1 || params->height > 1);
-
-    /*
-     * Create a blank game state.
-     */
-    state = snew(game_state);
-    w = state->width = params->width;
-    h = state->height = params->height;
-    state->cx = state->width / 2;
-    state->cy = state->height / 2;
-    state->wrapping = params->wrapping;
-    state->completed = 0;
-    state->used_solve = state->just_used_solve = FALSE;
-    state->move_count = 0;
-    state->last_move_row = -1;
-    state->last_move_col = -1;
-    state->last_move_dir = 0;
-    state->tiles = snewn(state->width * state->height, unsigned char);
-    memset(state->tiles, 0, state->width * state->height);
-    state->barriers = snewn(state->width * state->height, unsigned char);
-    memset(state->barriers, 0, state->width * state->height);
-
-    /*
-     * Set up border barriers if this is a non-wrapping game.
-     */
-    if (!state->wrapping) {
-       for (x = 0; x < state->width; x++) {
-           barrier(state, x, 0) |= U;
-           barrier(state, x, state->height-1) |= D;
-       }
-       for (y = 0; y < state->height; y++) {
-           barrier(state, 0, y) |= L;
-           barrier(state, state->width-1, y) |= R;
-       }
-    }
-
-    /*
-     * Seed the internal random number generator.
-     */
-    rs = random_init(seed, strlen(seed));
+    cx = w / 2;
+    cy = h / 2;
 
     /*
      * Construct the unshuffled grid.
@@ -446,14 +376,14 @@ static game_state *new_game(game_params *params, char *seed)
      */
     possibilities = newtree234(xyd_cmp);
 
-    if (state->cx+1 < state->width)
-       add234(possibilities, new_xyd(state->cx, state->cy, R));
-    if (state->cy-1 >= 0)
-       add234(possibilities, new_xyd(state->cx, state->cy, U));
-    if (state->cx-1 >= 0)
-       add234(possibilities, new_xyd(state->cx, state->cy, L));
-    if (state->cy+1 < state->height)
-       add234(possibilities, new_xyd(state->cx, state->cy, D));
+    if (cx+1 < w)
+       add234(possibilities, new_xyd(cx, cy, R));
+    if (cy-1 >= 0)
+       add234(possibilities, new_xyd(cx, cy, U));
+    if (cx-1 >= 0)
+       add234(possibilities, new_xyd(cx, cy, L));
+    if (cy+1 < h)
+       add234(possibilities, new_xyd(cx, cy, D));
 
     while (count234(possibilities) > 0) {
        int i;
@@ -470,7 +400,7 @@ static game_state *new_game(game_params *params, char *seed)
        d1 = xyd->direction;
        sfree(xyd);
 
-       OFFSET(x2, y2, x1, y1, d1, state);
+       OFFSET(x2, y2, x1, y1, d1, params);
        d2 = F(d1);
 #ifdef DEBUG
        printf("picked (%d,%d,%c) <-> (%d,%d,%c)\n",
@@ -481,20 +411,20 @@ static game_state *new_game(game_params *params, char *seed)
         * Make the connection. (We should be moving to an as yet
         * unused tile.)
         */
-       tile(state, x1, y1) |= d1;
-       assert(tile(state, x2, y2) == 0);
-       tile(state, x2, y2) |= d2;
+       index(params, tiles, x1, y1) |= d1;
+       assert(index(params, tiles, x2, y2) == 0);
+       index(params, tiles, x2, y2) |= d2;
 
        /*
         * If we have created a T-piece, remove its last
         * possibility.
         */
-       if (COUNT(tile(state, x1, y1)) == 3) {
+       if (COUNT(index(params, tiles, x1, y1)) == 3) {
            struct xyd xyd1, *xydp;
 
            xyd1.x = x1;
            xyd1.y = y1;
-           xyd1.direction = 0x0F ^ tile(state, x1, y1);
+           xyd1.direction = 0x0F ^ index(params, tiles, x1, y1);
 
            xydp = find234(possibilities, &xyd1, NULL);
 
@@ -516,7 +446,7 @@ static game_state *new_game(game_params *params, char *seed)
            int x3, y3, d3;
            struct xyd xyd1, *xydp;
 
-           OFFSET(x3, y3, x2, y2, d, state);
+           OFFSET(x3, y3, x2, y2, d, params);
            d3 = F(d);
 
            xyd1.x = x3;
@@ -545,20 +475,20 @@ static game_state *new_game(game_params *params, char *seed)
            if (d == d2)
                continue;              /* we've got this one already */
 
-           if (!state->wrapping) {
+           if (!params->wrapping) {
                if (d == U && y2 == 0)
                    continue;
-               if (d == D && y2 == state->height-1)
+               if (d == D && y2 == h-1)
                    continue;
                if (d == L && x2 == 0)
                    continue;
-               if (d == R && x2 == state->width-1)
+               if (d == R && x2 == w-1)
                    continue;
            }
 
-           OFFSET(x3, y3, x2, y2, d, state);
+           OFFSET(x3, y3, x2, y2, d, params);
 
-           if (tile(state, x3, y3))
+           if (index(params, tiles, x3, y3))
                continue;              /* this would create a loop */
 
 #ifdef DEBUG
@@ -575,16 +505,16 @@ static game_state *new_game(game_params *params, char *seed)
     /*
      * Now compute a list of the possible barrier locations.
      */
-    barriers = newtree234(xyd_cmp);
-    for (y = 0; y < state->height; y++) {
-       for (x = 0; x < state->width; x++) {
-
-           if (!(tile(state, x, y) & R) &&
-                (state->wrapping || x < state->width-1))
-               add234(barriers, new_xyd(x, y, R));
-           if (!(tile(state, x, y) & D) &&
-                (state->wrapping || y < state->height-1))
-               add234(barriers, new_xyd(x, y, D));
+    barriertree = newtree234(xyd_cmp);
+    for (y = 0; y < h; y++) {
+       for (x = 0; x < w; x++) {
+
+           if (!(index(params, tiles, x, y) & R) &&
+                (params->wrapping || x < w-1))
+               add234(barriertree, new_xyd(x, y, R));
+           if (!(index(params, tiles, x, y) & D) &&
+                (params->wrapping || y < h-1))
+               add234(barriertree, new_xyd(x, y, D));
        }
     }
 
@@ -595,16 +525,15 @@ static game_state *new_game(game_params *params, char *seed)
      * game state while playing.
      */
     {
-       struct solved_game_state *solution;
+        game_aux_info *solution;
 
-       solution = snew(struct solved_game_state);
-       solution->width = state->width;
-       solution->height = state->height;
-       solution->refcount = 1;
-       solution->tiles = snewn(state->width * state->height, unsigned char);
-       memcpy(solution->tiles, state->tiles, state->width * state->height);
+       solution = snew(game_aux_info);
+       solution->width = w;
+       solution->height = h;
+       solution->tiles = snewn(w * h, unsigned char);
+       memcpy(solution->tiles, tiles, w * h);
 
-       state->solution = solution;
+       *aux = solution;
     }
 
     /*
@@ -618,19 +547,19 @@ static game_state *new_game(game_params *params, char *seed)
      */
     {
         int i;
-        int cols = state->width - 1;
-        int rows = state->height - 1;
+        int cols = w - 1;
+        int rows = h - 1;
         for (i = 0; i < cols * rows * 2; i++) {
             /* Choose a direction: 0,1,2,3 = up, right, down, left. */
             int dir = random_upto(rs, 4);
             if (dir % 2 == 0) {
                 int col = random_upto(rs, cols);
-                if (col >= state->cx) col += 1;
-                slide_col(state, 1 - dir, col);
+                if (col >= cx) col += 1;
+                slide_col_int(w, h, tiles, 1 - dir, col);
             } else {
                 int row = random_upto(rs, rows);
-                if (row >= state->cy) row += 1;
-                slide_row(state, 2 - dir, row);
+                if (row >= cy) row += 1;
+                slide_row_int(w, h, tiles, 2 - dir, row);
             }
         }
     }
@@ -638,7 +567,7 @@ static game_state *new_game(game_params *params, char *seed)
     /*
      * And now choose barrier locations. (We carefully do this
      * _after_ shuffling, so that changing the barrier rate in the
-     * params while keeping the game seed the same will give the
+     * params while keeping the random seed the same will give the
      * same shuffled grid and _only_ change the barrier locations.
      * Also the way we choose barrier locations, by repeatedly
      * choosing one possibility from the list until we have enough,
@@ -649,8 +578,8 @@ static game_state *new_game(game_params *params, char *seed)
      * the original 10 plus 10 more, rather than getting 20 new
      * ones and the chance of remembering your first 10.)
      */
-    nbarriers = (int)(params->barrier_probability * count234(barriers));
-    assert(nbarriers >= 0 && nbarriers <= count234(barriers));
+    nbarriers = (int)(params->barrier_probability * count234(barriertree));
+    assert(nbarriers >= 0 && nbarriers <= count234(barriertree));
 
     while (nbarriers > 0) {
        int i;
@@ -660,8 +589,8 @@ static game_state *new_game(game_params *params, char *seed)
        /*
         * Extract a randomly chosen barrier from the list.
         */
-       i = random_upto(rs, count234(barriers));
-       xyd = delpos234(barriers, i);
+       i = random_upto(rs, count234(barriertree));
+       xyd = delpos234(barriertree, i);
 
        assert(xyd != NULL);
 
@@ -670,11 +599,11 @@ static game_state *new_game(game_params *params, char *seed)
        d1 = xyd->direction;
        sfree(xyd);
 
-       OFFSET(x2, y2, x1, y1, d1, state);
+       OFFSET(x2, y2, x1, y1, d1, params);
        d2 = F(d1);
 
-       barrier(state, x1, y1) |= d1;
-       barrier(state, x2, y2) |= d2;
+       index(params, barriers, x1, y1) |= d1;
+       index(params, barriers, x2, y2) |= d2;
 
        nbarriers--;
     }
@@ -685,10 +614,152 @@ static game_state *new_game(game_params *params, char *seed)
     {
        struct xyd *xyd;
 
-       while ( (xyd = delpos234(barriers, 0)) != NULL)
+       while ( (xyd = delpos234(barriertree, 0)) != NULL)
            sfree(xyd);
 
-       freetree234(barriers);
+       freetree234(barriertree);
+    }
+
+    /*
+     * Finally, encode the grid into a string game description.
+     * 
+     * My syntax is extremely simple: each square is encoded as a
+     * hex digit in which bit 0 means a connection on the right,
+     * bit 1 means up, bit 2 left and bit 3 down. (i.e. the same
+     * encoding as used internally). Each digit is followed by
+     * optional barrier indicators: `v' means a vertical barrier to
+     * the right of it, and `h' means a horizontal barrier below
+     * it.
+     */
+    desc = snewn(w * h * 3 + 1, char);
+    p = desc;
+    for (y = 0; y < h; y++) {
+        for (x = 0; x < w; x++) {
+            *p++ = "0123456789abcdef"[index(params, tiles, x, y)];
+            if ((params->wrapping || x < w-1) &&
+                (index(params, barriers, x, y) & R))
+                *p++ = 'v';
+            if ((params->wrapping || y < h-1) &&
+                (index(params, barriers, x, y) & D))
+                *p++ = 'h';
+        }
+    }
+    assert(p - desc <= w*h*3);
+
+    sfree(tiles);
+    sfree(barriers);
+
+    return desc;
+}
+
+static void game_free_aux_info(game_aux_info *aux)
+{
+    sfree(aux->tiles);
+    sfree(aux);
+}
+
+static char *validate_desc(game_params *params, char *desc)
+{
+    int w = params->width, h = params->height;
+    int i;
+
+    for (i = 0; i < w*h; i++) {
+        if (*desc >= '0' && *desc <= '9')
+            /* OK */;
+        else if (*desc >= 'a' && *desc <= 'f')
+            /* OK */;
+        else if (*desc >= 'A' && *desc <= 'F')
+            /* OK */;
+        else if (!*desc)
+            return "Game description shorter than expected";
+        else
+            return "Game description contained unexpected character";
+        desc++;
+        while (*desc == 'h' || *desc == 'v')
+            desc++;
+    }
+    if (*desc)
+        return "Game description longer than expected";
+
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Construct an initial game state, given a description and parameters.
+ */
+
+static game_state *new_game(game_params *params, char *desc)
+{
+    game_state *state;
+    int w, h, x, y;
+
+    assert(params->width > 0 && params->height > 0);
+    assert(params->width > 1 || params->height > 1);
+
+    /*
+     * Create a blank game state.
+     */
+    state = snew(game_state);
+    w = state->width = params->width;
+    h = state->height = params->height;
+    state->cx = state->width / 2;
+    state->cy = state->height / 2;
+    state->wrapping = params->wrapping;
+    state->completed = 0;
+    state->used_solve = state->just_used_solve = FALSE;
+    state->move_count = 0;
+    state->last_move_row = -1;
+    state->last_move_col = -1;
+    state->last_move_dir = 0;
+    state->tiles = snewn(state->width * state->height, unsigned char);
+    memset(state->tiles, 0, state->width * state->height);
+    state->barriers = snewn(state->width * state->height, unsigned char);
+    memset(state->barriers, 0, state->width * state->height);
+
+
+    /*
+     * Parse the game description into the grid.
+     */
+    for (y = 0; y < h; y++) {
+        for (x = 0; x < w; x++) {
+            if (*desc >= '0' && *desc <= '9')
+                tile(state, x, y) = *desc - '0';
+            else if (*desc >= 'a' && *desc <= 'f')
+                tile(state, x, y) = *desc - 'a' + 10;
+            else if (*desc >= 'A' && *desc <= 'F')
+                tile(state, x, y) = *desc - 'A' + 10;
+            if (*desc)
+                desc++;
+            while (*desc == 'h' || *desc == 'v') {
+                int x2, y2, d1, d2;
+                if (*desc == 'v')
+                    d1 = R;
+                else
+                    d1 = D;
+
+                OFFSET(x2, y2, x, y, d1, state);
+                d2 = F(d1);
+
+                barrier(state, x, y) |= d1;
+                barrier(state, x2, y2) |= d2;
+
+                desc++;
+            }
+        }
+    }
+
+    /*
+     * Set up border barriers if this is a non-wrapping game.
+     */
+    if (!state->wrapping) {
+       for (x = 0; x < state->width; x++) {
+           barrier(state, x, 0) |= U;
+           barrier(state, x, state->height-1) |= D;
+       }
+       for (y = 0; y < state->height; y++) {
+           barrier(state, 0, y) |= L;
+           barrier(state, state->width-1, y) |= R;
+       }
     }
 
     /*
@@ -739,8 +810,6 @@ static game_state *new_game(game_params *params, char *seed)
        }
     }
 
-    random_free(rs);
-
     return state;
 }
 
@@ -765,19 +834,12 @@ static game_state *dup_game(game_state *state)
     memcpy(ret->tiles, state->tiles, state->width * state->height);
     ret->barriers = snewn(state->width * state->height, unsigned char);
     memcpy(ret->barriers, state->barriers, state->width * state->height);
-    ret->solution = state->solution;
-    if (ret->solution)
-       ret->solution->refcount++;
 
     return ret;
 }
 
 static void free_game(game_state *state)
 {
-    if (state->solution && --state->solution->refcount <= 0) {
-       sfree(state->solution->tiles);
-       sfree(state->solution);
-    }
     sfree(state->tiles);
     sfree(state->barriers);
     sfree(state);
@@ -788,22 +850,15 @@ static game_state *solve_game(game_state *state, game_aux_info *aux,
 {
     game_state *ret;
 
-    if (!state->solution) {
-       /*
-        * 2005-05-02: This shouldn't happen, at the time of
-        * writing, because Net is incapable of receiving a puzzle
-        * description from outside. If in future it becomes so,
-        * then we will have puzzles for which we don't know the
-        * solution.
-        */
+    if (!aux) {
        *error = "Solution not known for this puzzle";
        return NULL;
     }
 
-    assert(state->solution->width == state->width);
-    assert(state->solution->height == state->height);
+    assert(aux->width == state->width);
+    assert(aux->height == state->height);
     ret = dup_game(state);
-    memcpy(ret->tiles, state->solution->tiles, ret->width * ret->height);
+    memcpy(ret->tiles, aux->tiles, ret->width * ret->height);
     ret->used_solve = ret->just_used_solve = TRUE;
     ret->completed = ret->move_count = 1;
 
@@ -905,32 +960,42 @@ static void free_ui(game_ui *ui)
  * Process a move.
  */
 
-static void slide_row(game_state *state, int dir, int row)
+static void slide_row_int(int w, int h, unsigned char *tiles, int dir, int row)
 {
-    int x = dir > 0 ? -1 : state->width;
+    int x = dir > 0 ? -1 : w;
     int tx = x + dir;
-    int n = state->width - 1;
-    unsigned char endtile = state->tiles[T(state, tx, row)];
+    int n = w - 1;
+    unsigned char endtile = tiles[row * w + tx];
     do {
         x = tx;
-        tx = (x + dir + state->width) % state->width;
-        state->tiles[T(state, x, row)] = state->tiles[T(state, tx, row)];
+        tx = (x + dir + w) % w;
+        tiles[row * w + x] = tiles[row * w + tx];
     } while (--n > 0);
-    state->tiles[T(state, tx, row)] = endtile;
+    tiles[row * w + tx] = endtile;
 }
 
-static void slide_col(game_state *state, int dir, int col)
+static void slide_col_int(int w, int h, unsigned char *tiles, int dir, int col)
 {
-    int y = dir > 0 ? -1 : state->height;
+    int y = dir > 0 ? -1 : h;
     int ty = y + dir;
-    int n = state->height - 1;
-    unsigned char endtile = state->tiles[T(state, col, ty)];
+    int n = h - 1;
+    unsigned char endtile = tiles[ty * w + col];
     do {
         y = ty;
-        ty = (y + dir + state->height) % state->height;
-        state->tiles[T(state, col, y)] = state->tiles[T(state, col, ty)];
+        ty = (y + dir + h) % h;
+        tiles[y * w + col] = tiles[ty * w + col];
     } while (--n > 0);
-    state->tiles[T(state, col, ty)] = endtile;
+    tiles[ty * w + col] = endtile;
+}
+
+static void slide_row(game_state *state, int dir, int row)
+{
+    slide_row_int(state->width, state->height, state->tiles, dir, row);
+}
+
+static void slide_col(game_state *state, int dir, int col)
+{
+    slide_col_int(state->width, state->height, state->tiles, dir, col);
 }
 
 static game_state *make_move(game_state *state, game_ui *ui,
@@ -1616,9 +1681,9 @@ const struct game thegame = {
     dup_params,
     TRUE, game_configure, custom_params,
     validate_params,
-    new_game_seed,
+    new_game_desc,
     game_free_aux_info,
-    validate_seed,
+    validate_desc,
     new_game,
     dup_game,
     free_game,
index 6eb427cd18cc9f2e6189ace8bece480e84c4d789..da57bac3ca2576baf7cb773cd83c65636fd023ca 100644 (file)
@@ -59,16 +59,11 @@ static game_params *dup_params(game_params *params)
     return ret;
 }
 
-static game_params *decode_params(char const *string)
+static void decode_params(game_params *params, char const *string)
 {
-    game_params *ret = snew(game_params);
-
-    ret->FIXME = 0;
-
-    return ret;
 }
 
-static char *encode_params(game_params *params)
+static char *encode_params(game_params *params, int full)
 {
     return dupstr("FIXME");
 }
@@ -88,7 +83,7 @@ static char *validate_params(game_params *params)
     return NULL;
 }
 
-static char *new_game_seed(game_params *params, random_state *rs,
+static char *new_game_desc(game_params *params, random_state *rs,
                           game_aux_info **aux)
 {
     return dupstr("FIXME");
@@ -99,12 +94,12 @@ static void game_free_aux_info(game_aux_info *aux)
     assert(!"Shouldn't happen");
 }
 
-static char *validate_seed(game_params *params, char *seed)
+static char *validate_desc(game_params *params, char *desc)
 {
     return NULL;
 }
 
-static game_state *new_game(game_params *params, char *seed)
+static game_state *new_game(game_params *params, char *desc)
 {
     game_state *state = snew(game_state);
 
@@ -234,9 +229,9 @@ const struct game thegame = {
     dup_params,
     FALSE, game_configure, custom_params,
     validate_params,
-    new_game_seed,
+    new_game_desc,
     game_free_aux_info,
-    validate_seed,
+    validate_desc,
     new_game,
     dup_game,
     free_game,
diff --git a/osx.m b/osx.m
index 11436a80ded9dc9ac807881ba2a33afc71783d8e..337748d2ef198554092f28606c484cf19a74fb40 100644 (file)
--- a/osx.m
+++ b/osx.m
@@ -1050,6 +1050,11 @@ struct frontend {
 }
 
 - (void)specificGame:(id)sender
+{
+    [self startConfigureSheet:CFG_DESC];
+}
+
+- (void)specificRandomGame:(id)sender
 {
     [self startConfigureSheet:CFG_SEED];
 }
@@ -1340,6 +1345,8 @@ int main(int argc, char **argv)
     item = newitem(menu, "New Game", "n", NULL, @selector(newGame:));
     item = newitem(menu, "Restart Game", "r", NULL, @selector(restartGame:));
     item = newitem(menu, "Specific Game", "", NULL, @selector(specificGame:));
+    item = newitem(menu, "Specific Random Seed", "", NULL,
+                   @selector(specificRandomGame:));
     [menu addItem:[NSMenuItem separatorItem]];
     {
        NSMenu *submenu = newsubmenu(menu, "New Window");
index a728e15d34dc10b8a760f034d63d5490b2ce605b..438243d97fdd295fe549b19e936740c255ff198a 100644 (file)
--- a/pattern.c
+++ b/pattern.c
@@ -100,9 +100,8 @@ static game_params *dup_params(game_params *params)
     return ret;
 }
 
-static game_params *decode_params(char const *string)
+static void decode_params(game_params *ret, char const *string)
 {
-    game_params *ret = default_params();
     char const *p = string;
 
     ret->w = atoi(p);
@@ -114,11 +113,9 @@ static game_params *decode_params(char const *string)
     } else {
         ret->h = ret->w;
     }
-
-    return ret;
 }
 
-static char *encode_params(game_params *params)
+static char *encode_params(game_params *params, int full)
 {
     char ret[400];
     int len;
@@ -477,13 +474,13 @@ struct game_aux_info {
     unsigned char *grid;
 };
 
-static char *new_game_seed(game_params *params, random_state *rs,
+static char *new_game_desc(game_params *params, random_state *rs,
                           game_aux_info **aux)
 {
     unsigned char *grid;
     int i, j, max, rowlen, *rowdata;
-    char intbuf[80], *seed;
-    int seedlen, seedpos;
+    char intbuf[80], *desc;
+    int desclen, descpos;
 
     grid = generate_soluble(rs, params->w, params->h);
     max = max(params->w, params->h);
@@ -513,7 +510,7 @@ static char *new_game_seed(game_params *params, random_state *rs,
      * passes, first computing the seed size and then writing it
      * out.
      */
-    seedlen = 0;
+    desclen = 0;
     for (i = 0; i < params->w + params->h; i++) {
         if (i < params->w)
             rowlen = compute_rowdata(rowdata, grid+i, params->h, params->w);
@@ -522,14 +519,14 @@ static char *new_game_seed(game_params *params, random_state *rs,
                                      params->w, 1);
         if (rowlen > 0) {
             for (j = 0; j < rowlen; j++) {
-                seedlen += 1 + sprintf(intbuf, "%d", rowdata[j]);
+                desclen += 1 + sprintf(intbuf, "%d", rowdata[j]);
             }
         } else {
-            seedlen++;
+            desclen++;
         }
     }
-    seed = snewn(seedlen, char);
-    seedpos = 0;
+    desc = snewn(desclen, char);
+    descpos = 0;
     for (i = 0; i < params->w + params->h; i++) {
         if (i < params->w)
             rowlen = compute_rowdata(rowdata, grid+i, params->h, params->w);
@@ -538,22 +535,22 @@ static char *new_game_seed(game_params *params, random_state *rs,
                                      params->w, 1);
         if (rowlen > 0) {
             for (j = 0; j < rowlen; j++) {
-                int len = sprintf(seed+seedpos, "%d", rowdata[j]);
+                int len = sprintf(desc+descpos, "%d", rowdata[j]);
                 if (j+1 < rowlen)
-                    seed[seedpos + len] = '.';
+                    desc[descpos + len] = '.';
                 else
-                    seed[seedpos + len] = '/';
-                seedpos += len+1;
+                    desc[descpos + len] = '/';
+                descpos += len+1;
             }
         } else {
-            seed[seedpos++] = '/';
+            desc[descpos++] = '/';
         }
     }
-    assert(seedpos == seedlen);
-    assert(seed[seedlen-1] == '/');
-    seed[seedlen-1] = '\0';
+    assert(descpos == desclen);
+    assert(desc[desclen-1] == '/');
+    desc[desclen-1] = '\0';
     sfree(rowdata);
-    return seed;
+    return desc;
 }
 
 static void game_free_aux_info(game_aux_info *aux)
@@ -562,7 +559,7 @@ static void game_free_aux_info(game_aux_info *aux)
     sfree(aux);
 }
 
-static char *validate_seed(game_params *params, char *seed)
+static char *validate_desc(game_params *params, char *desc)
 {
     int i, n, rowspace;
     char *p;
@@ -573,10 +570,10 @@ static char *validate_seed(game_params *params, char *seed)
         else
             rowspace = params->w + 1;
 
-        if (*seed && isdigit((unsigned char)*seed)) {
+        if (*desc && isdigit((unsigned char)*desc)) {
             do {
-                p = seed;
-                while (seed && isdigit((unsigned char)*seed)) seed++;
+                p = desc;
+                while (desc && isdigit((unsigned char)*desc)) desc++;
                 n = atoi(p);
                 rowspace -= n+1;
 
@@ -586,15 +583,15 @@ static char *validate_seed(game_params *params, char *seed)
                     else
                         return "at least one row contains more numbers than will fit";
                 }
-            } while (*seed++ == '.');
+            } while (*desc++ == '.');
         } else {
-            seed++;                    /* expect a slash immediately */
+            desc++;                    /* expect a slash immediately */
         }
 
-        if (seed[-1] == '/') {
+        if (desc[-1] == '/') {
             if (i+1 == params->w + params->h)
                 return "too many row/column specifications";
-        } else if (seed[-1] == '\0') {
+        } else if (desc[-1] == '\0') {
             if (i+1 < params->w + params->h)
                 return "too few row/column specifications";
         } else
@@ -604,7 +601,7 @@ static char *validate_seed(game_params *params, char *seed)
     return NULL;
 }
 
-static game_state *new_game(game_params *params, char *seed)
+static game_state *new_game(game_params *params, char *desc)
 {
     int i;
     char *p;
@@ -624,15 +621,15 @@ static game_state *new_game(game_params *params, char *seed)
 
     for (i = 0; i < params->w + params->h; i++) {
         state->rowlen[i] = 0;
-        if (*seed && isdigit((unsigned char)*seed)) {
+        if (*desc && isdigit((unsigned char)*desc)) {
             do {
-                p = seed;
-                while (seed && isdigit((unsigned char)*seed)) seed++;
+                p = desc;
+                while (desc && isdigit((unsigned char)*desc)) desc++;
                 state->rowdata[state->rowsize * i + state->rowlen[i]++] =
                     atoi(p);
-            } while (*seed++ == '.');
+            } while (*desc++ == '.');
         } else {
-            seed++;                    /* expect a slash immediately */
+            desc++;                    /* expect a slash immediately */
         }
     }
 
@@ -1120,9 +1117,9 @@ const struct game thegame = {
     dup_params,
     TRUE, game_configure, custom_params,
     validate_params,
-    new_game_seed,
+    new_game_desc,
     game_free_aux_info,
-    validate_seed,
+    validate_desc,
     new_game,
     dup_game,
     free_game,
@@ -1183,7 +1180,7 @@ int main(int argc, char **argv)
     game_params *p;
     game_state *s;
     int recurse = TRUE;
-    char *id = NULL, *seed, *err;
+    char *id = NULL, *desc, *err;
     int y, x;
     int grade = FALSE;
 
@@ -1202,20 +1199,20 @@ int main(int argc, char **argv)
         return 1;
     }
 
-    seed = strchr(id, ':');
-    if (!seed) {
+    desc = strchr(id, ':');
+    if (!desc) {
         fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]);
         return 1;
     }
-    *seed++ = '\0';
+    *desc++ = '\0';
 
     p = decode_params(id);
-    err = validate_seed(p, seed);
+    err = validate_desc(p, desc);
     if (err) {
         fprintf(stderr, "%s: %s\n", argv[0], err);
         return 1;
     }
-    s = new_game(p, seed);
+    s = new_game(p, desc);
 
     {
        int w = p->w, h = p->h, i, j, done_any, max;
index 5f8bff4b2e0f6f23c26e4e18d2d1325c2cfc490e..17bf47610876829ea16e5041e204dc5b2d5daa19 100644 (file)
@@ -141,27 +141,90 @@ solving it yourself after seeing the answer, you can just press Undo.
 
 \dd Closes the application entirely.
 
-\H{common-id} Recreating games with the \ii{game ID}
-
-The \q{\i{Specific...}} option from the \I{Game menu}\q{Game} menu
-(or the \q{File} menu, on Mac OS X) lets you see a short string (the
-\q{game ID}) that captures the initial state of the current game.
-
-The precise \I{ID format}format of the ID is specific to each game.
-It consists of two parts delimited by a colon (e.g., \c{c4x4:4F01,0});
-the first part encodes \i\e{parameters} (such as grid size), while the
-second part encodes a \i\e{seed}, which determines the \i{initial
-state} of the game within those parameters.
-
-You can specify a new ID (or just a seed) here. Pressing \q{OK} starts
-a new game with the specified ID (whether you changed it or not).
-Pressing \q{Cancel} returns to the current game.
-
-You can also use the game ID (or just the encoded parameters) as a
-\i{command line} argument; see \k{common-cmdline} for more detail.
-
-Game IDs are portable across platforms; you can use a game ID
-generated by the Windows version of a game on the Unix version, etc.
+\H{common-id} Specifying games with the \ii{game ID}
+
+There are two ways to save a game specification out of a puzzle and
+recreate it later, or recreate it in somebody else's copy of the
+same puzzle.
+
+The \q{\i{Specific}} and \q{\i{Random Seed}} options from the
+\I{Game menu}\q{Game} menu (or the \q{File} menu, on Mac OS X) each
+show a piece of text (a \q{game ID}) which is sufficient to
+reconstruct precisely the same game at a later date.
+
+You can enter either of these pieces of text back into the program
+(via the same \q{Specific} or \q{Random Seed} menu options) at a
+later point, and it will recreate the same game. You can also use
+either one as a \i{command line} argument (on Windows or Unix); see
+\k{common-cmdline} for more detail.
+
+The difference between the two forms is that a descriptive game ID
+is a literal \e{description} of the \i{initial state} of the game,
+whereas a random seed is just a piece of arbitrary text which was
+provided as input to the random number generator used to create the
+puzzle. This means that:
+
+\b Descriptive game IDs tend to be longer in many puzzles (although
+some, such as Cube (\k{cube}), only need very short descriptions).
+So a random seed is often a \e{quicker} way to note down the puzzle
+you're currently playing, or to tell it to somebody else so they can
+play the same one as you.
+
+\b Any text at all is a valid random seed. The automatically
+generated ones are fifteen-digit numbers, but anything will do; you
+can type in your full name, or a word you just made up, and a valid
+puzzle will be generated from it. This provides a way for two or
+more people to race to complete the same puzzle: you think of a
+random seed, then everybody types it in at the same time, and nobody
+has an advantage due to having seen the generated puzzle before
+anybody else.
+
+\b It is often possible to convert puzzles from other sources (such
+as \q{nonograms} or \q{sudoku} from newspapers) into descriptive
+game IDs suitable for use with these programs.
+
+\b Random seeds are not guaranteed to produce the same result if you
+use them with a different \i\e{version} of the puzzle program. This
+is because the generation algorithm might have been improved or
+modified in later versions of the code, and will therefore produce a
+different result when given the same sequence of random numbers. Use
+a descriptive game ID if you aren't sure that it will be used on the
+same version of the program as yours.
+
+\lcont{(Use the \q{About} menu option to find out the version number
+of the program. Programs with the same version number running on
+different platforms should still be random-seed compatible.)}
+
+\I{ID format}A descriptive game ID starts with a piece of text which
+encodes the \i\e{parameters} of the current game (such as grid
+size). Then there is a colon, and after that is the description of
+the game's initial state. A random seed starts with a similar string
+of parameters, but then it contains a hash sign followed by
+arbitrary data.
+
+If you enter a descriptive game ID, the program will not be able to
+show you the random seed which generated it, since it wasn't
+generated \e{from} a random seed. If you \e{enter} a random seed,
+however, the program will be able to show you the descriptive game
+ID derived from that random seed.
+
+Note that the game parameter strings are not always identical
+between the two forms. For some games, there will be parameter data
+provided with the random seed which is not included in the
+descriptive game ID. This is because that parameter information is
+only relevant when \e{generating} puzzle grids, and is not important
+when playing them. Thus, for example, the difficulty level in Solo
+(\k{solo}) is not mentioned in the descriptive game ID.
+
+These additional parameters are also not set permanently if you type
+in a game ID. For example, suppose you have Solo set to \q{Advanced}
+difficulty level, and then a friend wants your help with a
+\q{Trivial} puzzle; so the friend reads out a random seed specifying
+\q{Trivial} difficulty, and you type it in. The program will
+generate you the same \q{Trivial} grid which your friend was having
+trouble with, but once you have finished playing it, when you ask
+for a new game it will automatically go back to the \q{Advanced}
+difficulty which it was previously set on.
 
 \H{common-type} The \q{Type} menu
 
@@ -169,9 +232,10 @@ The \I{Type menu}\q{Type} menu, if present, may contain a list of
 \i{preset} game settings. Selecting one of these will start a new
 random game with the parameters specified.
 
-The \q{Type} menu may also contain a \q{\i{Custom...}} option which
-allows you to fine-tune game \i{parameters}. The parameters available
-are specific to each game and are described in the following sections.
+The \q{Type} menu may also contain a \q{\i{Custom}} option which
+allows you to fine-tune game \i{parameters}. The parameters
+available are specific to each game and are described in the
+following sections.
 
 \H{common-cmdline} Specifying game parameters on the \i{command line}
 
@@ -189,26 +253,31 @@ command line.
 
 The easiest way to do this is to set up the parameters you want
 using the \q{Type} menu (see \k{common-type}), and then to select
-\q{Specific} from the \q{Game} or \q{File} menu (see \k{common-id}).
-The text in the \q{Game ID} box will be composed of two parts,
-separated by a colon. The first of these parts represents the game
-parameters (the size of the playing area, for example, and anything
-else you set using the \q{Type} menu).
+\q{Random Seed} from the \q{Game} or \q{File} menu (see
+\k{common-id}). The text in the \q{Game ID} box will be composed of
+two parts, separated by a hash. The first of these parts represents
+the game parameters (the size of the playing area, for example, and
+anything else you set using the \q{Type} menu).
 
 If you run the game with just that parameter text on the command
 line, it will start up with the settings you specified.
 
 For example: if you run Cube (see \k{cube}), select \q{Octahedron}
 from the \q{Type} menu, and then go to the game ID selection, you
-will see a string of the form \cq{o2x2:911A81,10}. Take only the
-part before the colon (\cq{o2x2}), and start Cube with that text on
-the command line: \cq{cube o2x2}.
+will see a string of the form \cq{o2x2#338686542711620}. Take only
+the part before the hash (\cq{o2x2}), and start Cube with that text
+on the command line: \cq{cube o2x2}.
 
 If you copy the \e{entire} game ID on to the command line, the game
 will start up in the specific game that was described. This is
 occasionally a more convenient way to start a particular game ID
 than by pasting it into the game ID selection box.
 
+(You could also retrieve the encoded game parameters using the
+\q{Specific} menu option instead of \q{Random Seed}, but if you do
+then some options, such as the difficulty level in Solo, will be
+missing. See \k{common-id} for more details on this.)
+
 \C{net} \i{Net}
 
 \cfg{winhelp-topic}{games.net}
@@ -281,13 +350,14 @@ act as constraints on the solution (i.e., hints).
 
 The grid generation in Net has been carefully arranged so that the
 barriers are independent of the rest of the grid. This means that if
-you change the \e{Barrier probability} parameter, and then re-enter
-the same game ID you were playing before (see \k{common-id}), you
-should see exactly the same starting grid, with the only change
-being the number of barriers. So if you're stuck on a particular
-grid and need a hint, you could start up another instance of Net,
-set up the same parameters but a higher barrier probability, and
-enter the game seed from the original Net window.
+you note down the random seed used to generate the current puzzle
+(see \k{common-id}), change the \e{Barrier probability} parameter,
+and then re-enter the same random seed, you should see exactly the
+same starting grid, with the only change being the number of
+barriers. So if you're stuck on a particular grid and need a hint,
+you could start up another instance of Net, set up the same
+parameters but a higher barrier probability, and enter the game seed
+from the original Net window.
 
 }
 
@@ -430,27 +500,6 @@ set of shuffling moves and invert them exactly, so that you answer
 more moves you ask for, the more likely it is that solutions shorter
 than the target length will turn out to be possible.
 
-\H{sixteen-cmdline} \I{command line, for Sixteen}Additional
-command-line configuration
-
-The limited shuffle parameter, described in \k{sixteen-params}, is
-not mentioned by default in the game ID (see \k{common-id}). So if
-you set your shuffling move count to (say) 4, and then you generate
-a normal 4\by\.4 grid, then the game ID will simply say
-\c{4x4:}\e{numbers}. This means that if you send the game ID to
-another player and they paste it into their copy of Sixteen, their
-game will not be automatically configured to use the same shuffle
-limit in any subsequent grids it generates. (I don't think the
-average person examining a single grid sent to them by another
-player would want their configuration modified to that extent.)
-
-If you are specifying a game ID or game parameters on the command
-line (see \k{common-cmdline}) and you do want to configure the
-shuffle limit, you can do it by suffixing the letter \cq{m} to the
-parameters, followed by the move count as a decimal number. For
-example, \cq{sixteen 4x4m4} will start up Sixteen with a problem
-guaranteed to be soluble in four moves or fewer.
-
 
 \C{twiddle} \i{Twiddle}
 
@@ -517,26 +566,6 @@ shuffle with a four-move solution. Note that the more moves you ask
 for, the more likely it is that solutions shorter than the target
 length will turn out to be possible.
 
-\H{twiddle-cmdline} \I{command line, for Twiddle}Additional
-command-line configuration
-
-The limited shuffle parameter, described in \k{twiddle-parameters},
-is not mentioned by default in the game ID (see \k{common-id}). So
-if you set your shuffling move count to (say) 4, and then you
-generate a normal 3\by\.3 grid, then the game ID will simply say
-\c{3x3n2:}\e{numbers}. This means that if you send the game ID to
-another player and they paste it into their copy of Twiddle, their
-game will not be automatically configured to use the same shuffle
-limit in any subsequent grids it generates. (I don't think the
-average person examining a single grid sent to them by another
-player would want their configuration modified to that extent.)
-
-If you are specifying a game ID or game parameters on the command
-line (see \k{common-cmdline}) and you do want to configure the
-shuffle limit, you can do it by suffixing the letter \cq{m} to the
-parameters, followed by the move count as a decimal number. For
-example, \cq{twiddle 3x3n2m4} will start up Twiddle with a problem
-guaranteed to be soluble in four moves or fewer.
 
 \C{rectangles} \i{Rectangles}
 
@@ -598,34 +627,6 @@ and more intuitive playing style. If you set it \e{too} high,
 though, the game simply cannot generate more than a few rectangles
 to cover the entire grid, and the game becomes trivial.
 
-\H{rectangles-cmdline} \I{command line, for Rectangles}Additional
-command-line configuration
-
-The expansion factor parameter, described in \k{rectangles-params},
-is not mentioned by default in the game ID (see \k{common-id}). So
-if you set your expansion factor to (say) 0.75, and then you
-generate an 11\by\.11 grid, then the game ID will simply say
-\c{11x11:}\e{numbers}. This means that if you send the game ID to
-another player and they paste it into their copy of Rectangles,
-their game will not be automatically configured to use the same
-expansion factor in any subsequent grids it generates. (I don't
-think the average person examining a single grid sent to them by
-another player would want their configuration modified to that
-extent.)
-
-If you are specifying a game ID or game parameters on the command
-line (see \k{common-cmdline}) and you do want to configure the
-expansion factor, you can do it by suffixing the letter \cq{e} to
-the parameters, followed by the expansion factor as a decimal
-number. For example:
-
-\b \cq{rect 11x11e0.75} starts Rectangles with a grid size of
-11\u00d7{x}11 and an expansion factor of 0.75.
-
-\b \cq{rect 11x11e0.75:g11c6e5e4a2_4e9c3b3d3b5g2b6c4k4g30a8n3j1g6a2}
-starts Rectangles with a grid size of 11\u00d7{x}11, an expansion
-factor of 0.75, \e{and} a specific game selected.
-
 
 \C{netslide} \i{Netslide}
 
@@ -764,50 +765,6 @@ many attempts at generating a puzzle before it finds one hard enough
 for you. Be prepared to wait, especially if you have also configured
 a large puzzle size.
 
-\H{solo-cmdline} \I{command line, for Solo}Additional command-line
-configuration
-
-The symmetry and difficulty parameters (described in
-\k{solo-parameters}) are not mentioned by default in the game ID
-(see \k{common-id}). So if (for example) you set your symmetry to
-4-way rotational and your difficulty to \q{Advanced}, and then you
-generate a 3\by\.4 grid, then the game ID will simply say
-\c{3x4:}\e{numbers}. This means that if you send the game ID to
-another player and they paste it into their copy of Solo, their game
-will not be automatically configured to use the same symmetry and
-difficulty settings in any subsequent grids it generates. (I don't
-think the average person examining a single grid sent to them by
-another player would want their configuration modified to that
-extent.)
-
-If you are specifying a game ID or game parameters on the command
-line (see \k{common-cmdline}) and you do want to configure the
-symmetry, you can do it by suffixing additional text to the
-parameters:
-
-\b \cq{m4} for 4-way mirror symmetry
-
-\b \cq{r4} for 4-way rotational symmetry
-
-\b \cq{r2} for 2-way rotational symmetry
-
-\b \cq{a} for no symmetry at all (stands for \q{asymmetric})
-
-\b \cq{dt} for Trivial difficulty level
-
-\b \cq{db} for Basic difficulty level
-
-\b \cq{di} for Intermediate difficulty level
-
-\b \cq{da} for Advanced difficulty level
-
-\b \cq{du} for Unreasonable difficulty level
-
-So, for example, you can make Solo generate asymmetric 3x4 grids by
-running \cq{solo 3x4a}, or 4-way rotationally symmetric 2x3 grids by
-running \cq{solo 2x3r4}, or \q{Advanced}-level 2x3 grids by running
-\cq{solo 2x3da}.
-
 
 \A{licence} \I{MIT licence}\ii{Licence}
 
@@ -835,8 +792,8 @@ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.
 
-\IM{specific...} Specific..., menu option
-\IM{custom...} Custom..., menu option
+\IM{specific} \q{Specific}, menu option
+\IM{custom} \q{Custom}, menu option
 
 \IM{game ID} game ID
 \IM{game ID} ID, game
index 63de880563dd110495594ce1899632a0d1b1a8fa..a18aefa5473452f5db6e4c5f1ab083b3ff0a8770 100644 (file)
--- a/puzzles.h
+++ b/puzzles.h
@@ -135,10 +135,10 @@ int midend_num_presets(midend_data *me);
 void midend_fetch_preset(midend_data *me, int n,
                          char **name, game_params **params);
 int midend_wants_statusbar(midend_data *me);
-enum { CFG_SETTINGS, CFG_SEED };
+enum { CFG_SETTINGS, CFG_SEED, CFG_DESC };
 config_item *midend_get_config(midend_data *me, int which, char **wintitle);
 char *midend_set_config(midend_data *me, int which, config_item *cfg);
-char *midend_game_id(midend_data *me, char *id, int def_seed);
+char *midend_game_id(midend_data *me, char *id);
 char *midend_text_format(midend_data *me);
 char *midend_solve(midend_data *me);
 
@@ -186,19 +186,19 @@ struct game {
     const char *winhelp_topic;
     game_params *(*default_params)(void);
     int (*fetch_preset)(int i, char **name, game_params **params);
-    game_params *(*decode_params)(char const *string);
-    char *(*encode_params)(game_params *);
+    void (*decode_params)(game_params *, char const *string);
+    char *(*encode_params)(game_params *, int full);
     void (*free_params)(game_params *params);
     game_params *(*dup_params)(game_params *params);
     int can_configure;
     config_item *(*configure)(game_params *params);
     game_params *(*custom_params)(config_item *cfg);
     char *(*validate_params)(game_params *params);
-    char *(*new_seed)(game_params *params, random_state *rs,
+    char *(*new_desc)(game_params *params, random_state *rs,
                      game_aux_info **aux);
     void (*free_aux_info)(game_aux_info *aux);
-    char *(*validate_seed)(game_params *params, char *seed);
-    game_state *(*new_game)(game_params *params, char *seed);
+    char *(*validate_desc)(game_params *params, char *desc);
+    game_state *(*new_game)(game_params *params, char *desc);
     game_state *(*dup_game)(game_state *state);
     void (*free_game)(game_state *state);
     int can_solve;
diff --git a/rect.c b/rect.c
index ffb3725c246987ea41e06a8147a3f335d41912d7..78c9e083f805f9192e7df531bf9ced947c5ba875 100644 (file)
--- a/rect.c
+++ b/rect.c
@@ -130,12 +130,9 @@ static game_params *dup_params(game_params *params)
     return ret;
 }
 
-static game_params *decode_params(char const *string)
+static void decode_params(game_params *ret, char const *string)
 {
-    game_params *ret = default_params();
-
     ret->w = ret->h = atoi(string);
-    ret->expandfactor = 0.0F;
     while (*string && isdigit((unsigned char)*string)) string++;
     if (*string == 'x') {
         string++;
@@ -146,15 +143,15 @@ static game_params *decode_params(char const *string)
        string++;
        ret->expandfactor = atof(string);
     }
-
-    return ret;
 }
 
-static char *encode_params(game_params *params)
+static char *encode_params(game_params *params, int full)
 {
     char data[256];
 
     sprintf(data, "%dx%d", params->w, params->h);
+    if (full)
+        sprintf(data + strlen(data), "e%g", params->expandfactor);
 
     return dupstr(data);
 }
@@ -392,13 +389,13 @@ struct game_aux_info {
     unsigned char *hedge;             /* w x (h+1) */
 };
 
-static char *new_game_seed(game_params *params, random_state *rs,
+static char *new_game_desc(game_params *params, random_state *rs,
                           game_aux_info **aux)
 {
     int *grid, *numbers;
     struct rectlist *list;
     int x, y, y2, y2last, yx, run, i;
-    char *seed, *p;
+    char *desc, *p;
     game_params params2real, *params2 = &params2real;
 
     /*
@@ -891,8 +888,8 @@ static char *new_game_seed(game_params *params, random_state *rs,
     display_grid(params, grid, numbers, FALSE);
 #endif
 
-    seed = snewn(11 * params->w * params->h, char);
-    p = seed;
+    desc = snewn(11 * params->w * params->h, char);
+    p = desc;
     run = 0;
     for (i = 0; i <= params->w * params->h; i++) {
         int n = (i < params->w * params->h ? numbers[i] : -1);
@@ -914,7 +911,7 @@ static char *new_game_seed(game_params *params, random_state *rs,
                  * bottom right, there's no point putting an
                  * unnecessary _ before or after it.
                  */
-                if (p > seed && n > 0)
+                if (p > desc && n > 0)
                     *p++ = '_';
             }
             if (n > 0)
@@ -927,7 +924,7 @@ static char *new_game_seed(game_params *params, random_state *rs,
     sfree(grid);
     sfree(numbers);
 
-    return seed;
+    return desc;
 }
 
 static void game_free_aux_info(game_aux_info *ai)
@@ -937,23 +934,23 @@ static void game_free_aux_info(game_aux_info *ai)
     sfree(ai);
 }
 
-static char *validate_seed(game_params *params, char *seed)
+static char *validate_desc(game_params *params, char *desc)
 {
     int area = params->w * params->h;
     int squares = 0;
 
-    while (*seed) {
-        int n = *seed++;
+    while (*desc) {
+        int n = *desc++;
         if (n >= 'a' && n <= 'z') {
             squares += n - 'a' + 1;
         } else if (n == '_') {
             /* do nothing */;
         } else if (n > '0' && n <= '9') {
             squares++;
-            while (*seed >= '0' && *seed <= '9')
-                seed++;
+            while (*desc >= '0' && *desc <= '9')
+                desc++;
         } else
-            return "Invalid character in game specification";
+            return "Invalid character in game description";
     }
 
     if (squares < area)
@@ -965,7 +962,7 @@ static char *validate_seed(game_params *params, char *seed)
     return NULL;
 }
 
-static game_state *new_game(game_params *params, char *seed)
+static game_state *new_game(game_params *params, char *desc)
 {
     game_state *state = snew(game_state);
     int x, y, i, area;
@@ -981,8 +978,8 @@ static game_state *new_game(game_params *params, char *seed)
     state->completed = state->cheated = FALSE;
 
     i = 0;
-    while (*seed) {
-        int n = *seed++;
+    while (*desc) {
+        int n = *desc++;
         if (n >= 'a' && n <= 'z') {
             int run = n - 'a' + 1;
             assert(i + run <= area);
@@ -992,9 +989,9 @@ static game_state *new_game(game_params *params, char *seed)
             /* do nothing */;
         } else if (n > '0' && n <= '9') {
             assert(i < area);
-            state->grid[i++] = atoi(seed-1);
-            while (*seed >= '0' && *seed <= '9')
-                seed++;
+            state->grid[i++] = atoi(desc-1);
+            while (*desc >= '0' && *desc <= '9')
+                desc++;
         } else {
             assert(!"We can't get here");
         }
@@ -1764,9 +1761,9 @@ const struct game thegame = {
     dup_params,
     TRUE, game_configure, custom_params,
     validate_params,
-    new_game_seed,
+    new_game_desc,
     game_free_aux_info,
-    validate_seed,
+    validate_desc,
     new_game,
     dup_game,
     free_game,
index 2751be492fc936ae24ecbddccd6965116f051ebf..966cc0f6a5c8fa5f2f25878eab48b35003483503 100644 (file)
--- a/sixteen.c
+++ b/sixteen.c
@@ -95,11 +95,10 @@ static game_params *dup_params(game_params *params)
     return ret;
 }
 
-static game_params *decode_params(char const *string)
+static void decode_params(game_params *ret, char const *string)
 {
-    game_params *ret = default_params();
-
     ret->w = ret->h = atoi(string);
+    ret->movetarget = 0;
     while (*string && isdigit(*string)) string++;
     if (*string == 'x') {
         string++;
@@ -113,15 +112,17 @@ static game_params *decode_params(char const *string)
        while (*string && isdigit((unsigned char)*string))
            string++;
     }
-
-    return ret;
 }
 
-static char *encode_params(game_params *params)
+static char *encode_params(game_params *params, int full)
 {
     char data[256];
 
     sprintf(data, "%dx%d", params->w, params->h);
+    /* Shuffle limit is part of the limited parameters, because we have to
+     * supply the target move count. */
+    if (params->movetarget)
+        sprintf(data + strlen(data), "m%d", params->movetarget);
 
     return dupstr(data);
 }
@@ -192,7 +193,7 @@ static int perm_parity(int *perm, int n)
     return ret;
 }
 
-static char *new_game_seed(game_params *params, random_state *rs,
+static char *new_game_desc(game_params *params, random_state *rs,
                           game_aux_info **aux)
 {
     int stop, n, i, x;
@@ -362,8 +363,8 @@ static char *new_game_seed(game_params *params, random_state *rs,
     }
 
     /*
-     * Now construct the game seed, by describing the tile array as
-     * a simple sequence of comma-separated integers.
+     * Now construct the game description, by describing the tile
+     * array as a simple sequence of comma-separated integers.
      */
     ret = NULL;
     retlen = 0;
@@ -390,14 +391,14 @@ static void game_free_aux_info(game_aux_info *aux)
 }
 
 
-static char *validate_seed(game_params *params, char *seed)
+static char *validate_desc(game_params *params, char *desc)
 {
     char *p, *err;
     int i, area;
     int *used;
 
     area = params->w * params->h;
-    p = seed;
+    p = desc;
     err = NULL;
 
     used = snewn(area, int);
@@ -441,7 +442,7 @@ static char *validate_seed(game_params *params, char *seed)
     return err;
 }
 
-static game_state *new_game(game_params *params, char *seed)
+static game_state *new_game(game_params *params, char *desc)
 {
     game_state *state = snew(game_state);
     int i;
@@ -452,7 +453,7 @@ static game_state *new_game(game_params *params, char *seed)
     state->n = params->w * params->h;
     state->tiles = snewn(state->n, int);
 
-    p = seed;
+    p = desc;
     i = 0;
     for (i = 0; i < state->n; i++) {
         assert(*p);
@@ -973,9 +974,9 @@ const struct game thegame = {
     dup_params,
     TRUE, game_configure, custom_params,
     validate_params,
-    new_game_seed,
+    new_game_desc,
     game_free_aux_info,
-    validate_seed,
+    validate_desc,
     new_game,
     dup_game,
     free_game,
diff --git a/solo.c b/solo.c
index c9c64127da8102e2cc50efd1268d7d92fbb389fd..20c288f92f4d2fdb57a1e2fa556ab83747566cc7 100644 (file)
--- a/solo.c
+++ b/solo.c
@@ -159,13 +159,9 @@ static int game_fetch_preset(int i, char **name, game_params **params)
     return TRUE;
 }
 
-static game_params *decode_params(char const *string)
+static void decode_params(game_params *ret, char const *string)
 {
-    game_params *ret = default_params();
-
     ret->c = ret->r = atoi(string);
-    ret->symm = SYMM_ROT2;
-    ret->diff = DIFF_BLOCK;
     while (*string && isdigit((unsigned char)*string)) string++;
     if (*string == 'x') {
         string++;
@@ -201,20 +197,28 @@ static game_params *decode_params(char const *string)
         } else
             string++;                  /* eat unknown character */
     }
-
-    return ret;
 }
 
-static char *encode_params(game_params *params)
+static char *encode_params(game_params *params, int full)
 {
     char str[80];
 
-    /*
-     * Symmetry is a game generation preference and hence is left
-     * out of the encoding. Users can add it back in as they see
-     * fit.
-     */
     sprintf(str, "%dx%d", params->c, params->r);
+    if (full) {
+        switch (params->symm) {
+          case SYMM_REF4: strcat(str, "m4"); break;
+          case SYMM_ROT4: strcat(str, "r4"); break;
+          /* case SYMM_ROT2: strcat(str, "r2"); break; [default] */
+          case SYMM_NONE: strcat(str, "a"); break;
+        }
+        switch (params->diff) {
+          /* case DIFF_BLOCK: strcat(str, "dt"); break; [default] */
+          case DIFF_SIMPLE: strcat(str, "db"); break;
+          case DIFF_INTERSECT: strcat(str, "di"); break;
+          case DIFF_SET: strcat(str, "da"); break;
+          case DIFF_RECURSIVE: strcat(str, "du"); break;
+        }
+    }
     return dupstr(str);
 }
 
@@ -1361,7 +1365,7 @@ struct game_aux_info {
     digit *grid;
 };
 
-static char *new_game_seed(game_params *params, random_state *rs,
+static char *new_game_desc(game_params *params, random_state *rs,
                           game_aux_info **aux)
 {
     int c = params->c, r = params->r, cr = c*r;
@@ -1370,7 +1374,7 @@ static char *new_game_seed(game_params *params, random_state *rs,
     struct xy { int x, y; } *locs;
     int nlocs;
     int ret;
-    char *seed;
+    char *desc;
     int coords[16], ncoords;
     int xlim, ylim;
     int maxdiff, recursing;
@@ -1504,14 +1508,14 @@ static char *new_game_seed(game_params *params, random_state *rs,
 
     /*
      * Now we have the grid as it will be presented to the user.
-     * Encode it in a game seed.
+     * Encode it in a game desc.
      */
     {
        char *p;
        int run, i;
 
-       seed = snewn(5 * area, char);
-       p = seed;
+       desc = snewn(5 * area, char);
+       p = desc;
        run = 0;
        for (i = 0; i <= area; i++) {
            int n = (i < area ? grid[i] : -1);
@@ -1533,7 +1537,7 @@ static char *new_game_seed(game_params *params, random_state *rs,
                     * bottom right, there's no point putting an
                     * unnecessary _ before or after it.
                     */
-                   if (p > seed && n > 0)
+                   if (p > desc && n > 0)
                        *p++ = '_';
                }
                if (n > 0)
@@ -1541,14 +1545,14 @@ static char *new_game_seed(game_params *params, random_state *rs,
                run = 0;
            }
        }
-       assert(p - seed < 5 * area);
+       assert(p - desc < 5 * area);
        *p++ = '\0';
-       seed = sresize(seed, p - seed, char);
+       desc = sresize(desc, p - desc, char);
     }
 
     sfree(grid);
 
-    return seed;
+    return desc;
 }
 
 static void game_free_aux_info(game_aux_info *aux)
@@ -1557,23 +1561,23 @@ static void game_free_aux_info(game_aux_info *aux)
     sfree(aux);
 }
 
-static char *validate_seed(game_params *params, char *seed)
+static char *validate_desc(game_params *params, char *desc)
 {
     int area = params->r * params->r * params->c * params->c;
     int squares = 0;
 
-    while (*seed) {
-        int n = *seed++;
+    while (*desc) {
+        int n = *desc++;
         if (n >= 'a' && n <= 'z') {
             squares += n - 'a' + 1;
         } else if (n == '_') {
             /* do nothing */;
         } else if (n > '0' && n <= '9') {
             squares++;
-            while (*seed >= '0' && *seed <= '9')
-                seed++;
+            while (*desc >= '0' && *desc <= '9')
+                desc++;
         } else
-            return "Invalid character in game specification";
+            return "Invalid character in game description";
     }
 
     if (squares < area)
@@ -1585,7 +1589,7 @@ static char *validate_seed(game_params *params, char *seed)
     return NULL;
 }
 
-static game_state *new_game(game_params *params, char *seed)
+static game_state *new_game(game_params *params, char *desc)
 {
     game_state *state = snew(game_state);
     int c = params->c, r = params->r, cr = c*r, area = cr * cr;
@@ -1601,8 +1605,8 @@ static game_state *new_game(game_params *params, char *seed)
     state->completed = state->cheated = FALSE;
 
     i = 0;
-    while (*seed) {
-        int n = *seed++;
+    while (*desc) {
+        int n = *desc++;
         if (n >= 'a' && n <= 'z') {
             int run = n - 'a' + 1;
             assert(i + run <= area);
@@ -1613,9 +1617,9 @@ static game_state *new_game(game_params *params, char *seed)
         } else if (n > '0' && n <= '9') {
             assert(i < area);
            state->immutable[i] = TRUE;
-            state->grid[i++] = atoi(seed-1);
-            while (*seed >= '0' && *seed <= '9')
-                seed++;
+            state->grid[i++] = atoi(desc-1);
+            while (*desc >= '0' && *desc <= '9')
+                desc++;
         } else {
             assert(!"We can't get here");
         }
@@ -2042,9 +2046,9 @@ const struct game thegame = {
     dup_params,
     TRUE, game_configure, custom_params,
     validate_params,
-    new_game_seed,
+    new_game_desc,
     game_free_aux_info,
-    validate_seed,
+    validate_desc,
     new_game,
     dup_game,
     free_game,
@@ -2105,7 +2109,7 @@ int main(int argc, char **argv)
     game_params *p;
     game_state *s;
     int recurse = TRUE;
-    char *id = NULL, *seed, *err;
+    char *id = NULL, *desc, *err;
     int y, x;
     int grade = FALSE;
 
@@ -2134,20 +2138,20 @@ int main(int argc, char **argv)
         return 1;
     }
 
-    seed = strchr(id, ':');
-    if (!seed) {
+    desc = strchr(id, ':');
+    if (!desc) {
         fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]);
         return 1;
     }
-    *seed++ = '\0';
+    *desc++ = '\0';
 
     p = decode_params(id);
-    err = validate_seed(p, seed);
+    err = validate_desc(p, desc);
     if (err) {
         fprintf(stderr, "%s: %s\n", argv[0], err);
         return 1;
     }
-    s = new_game(p, seed);
+    s = new_game(p, desc);
 
     if (recurse) {
         int ret = rsolve(p->c, p->r, s->grid, NULL, 2);
index 707eab7c4cf1a698f166a95232d6c291f97d7d63..07a735ff3b423068b783c50665370d57c786b036 100644 (file)
--- a/twiddle.c
+++ b/twiddle.c
@@ -103,10 +103,8 @@ static int game_fetch_preset(int i, char **name, game_params **params)
     return TRUE;
 }
 
-static game_params *decode_params(char const *string)
+static void decode_params(game_params *ret, char const *string)
 {
-    game_params *ret = snew(game_params);
-
     ret->w = ret->h = atoi(string);
     ret->n = 2;
     ret->rowsonly = ret->orientable = FALSE;
@@ -134,16 +132,18 @@ static game_params *decode_params(char const *string)
        }
        string++;
     }
-
-    return ret;
 }
 
-static char *encode_params(game_params *params)
+static char *encode_params(game_params *params, int full)
 {
     char buf[256];
     sprintf(buf, "%dx%dn%d%s%s", params->w, params->h, params->n,
            params->rowsonly ? "r" : "",
            params->orientable ? "o" : "");
+    /* Shuffle limit is part of the limited parameters, because we have to
+     * supply the target move count. */
+    if (params->movetarget)
+        sprintf(buf + strlen(buf), "m%d", params->movetarget);
     return dupstr(buf);
 }
 
@@ -307,7 +307,7 @@ static int grid_complete(int *grid, int wh, int orientable)
     return ok;
 }
 
-static char *new_game_seed(game_params *params, random_state *rs,
+static char *new_game_desc(game_params *params, random_state *rs,
                           game_aux_info **aux)
 {
     int *grid;
@@ -368,10 +368,10 @@ static char *new_game_seed(game_params *params, random_state *rs,
     } while (grid_complete(grid, wh, params->orientable));
 
     /*
-     * Now construct the game seed, by describing the grid as a
-     * simple sequence of integers. They're comma-separated, unless
-     * the puzzle is orientable in which case they're separated by
-     * orientation letters `u', `d', `l' and `r'.
+     * Now construct the game description, by describing the grid
+     * as a simple sequence of integers. They're comma-separated,
+     * unless the puzzle is orientable in which case they're
+     * separated by orientation letters `u', `d', `l' and `r'.
      */
     ret = NULL;
     retlen = 0;
@@ -398,13 +398,13 @@ static void game_free_aux_info(game_aux_info *aux)
     assert(!"Shouldn't happen");
 }
 
-static char *validate_seed(game_params *params, char *seed)
+static char *validate_desc(game_params *params, char *desc)
 {
     char *p, *err;
     int w = params->w, h = params->h, wh = w*h;
     int i;
 
-    p = seed;
+    p = desc;
     err = NULL;
 
     for (i = 0; i < wh; i++) {
@@ -428,7 +428,7 @@ static char *validate_seed(game_params *params, char *seed)
     return NULL;
 }
 
-static game_state *new_game(game_params *params, char *seed)
+static game_state *new_game(game_params *params, char *desc)
 {
     game_state *state = snew(game_state);
     int w = params->w, h = params->h, n = params->n, wh = w*h;
@@ -447,7 +447,7 @@ static game_state *new_game(game_params *params, char *seed)
 
     state->grid = snewn(wh, int);
 
-    p = seed;
+    p = desc;
 
     for (i = 0; i < wh; i++) {
        state->grid[i] = 4 * atoi(p);
@@ -1087,9 +1087,9 @@ const struct game thegame = {
     dup_params,
     TRUE, game_configure, custom_params,
     validate_params,
-    new_game_seed,
+    new_game_desc,
     game_free_aux_info,
-    validate_seed,
+    validate_desc,
     new_game,
     dup_game,
     free_game,
index feee3cfaa8d5b3cfb7f74b9af5133afa0fc38468..da324b13386ad952cb557ae0f858d2836d758d00 100644 (file)
--- a/windows.c
+++ b/windows.c
 #define IDM_SOLVE     0x0060
 #define IDM_QUIT      0x0070
 #define IDM_CONFIG    0x0080
-#define IDM_SEED      0x0090
-#define IDM_HELPC     0x00A0
-#define IDM_GAMEHELP  0x00B0
+#define IDM_DESC      0x0090
+#define IDM_SEED      0x00A0
+#define IDM_HELPC     0x00B0
+#define IDM_GAMEHELP  0x00C0
 #define IDM_PRESETS   0x0100
 #define IDM_ABOUT     0x0110
 
@@ -416,7 +417,7 @@ static frontend *new_window(HINSTANCE inst, char *game_id, char **error)
     fe->me = midend_new(fe, &thegame);
 
     if (game_id) {
-        *error = midend_game_id(fe->me, game_id, FALSE);
+        *error = midend_game_id(fe->me, game_id);
         if (*error) {
             midend_free(fe->me);
             sfree(fe);
@@ -476,7 +477,8 @@ static frontend *new_window(HINSTANCE inst, char *game_id, char **error)
        AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT)menu, "Game");
        AppendMenu(menu, MF_ENABLED, IDM_NEW, "New");
        AppendMenu(menu, MF_ENABLED, IDM_RESTART, "Restart");
-       AppendMenu(menu, MF_ENABLED, IDM_SEED, "Specific...");
+       AppendMenu(menu, MF_ENABLED, IDM_DESC, "Specific...");
+       AppendMenu(menu, MF_ENABLED, IDM_SEED, "Random Seed...");
 
        if ((fe->npresets = midend_num_presets(fe->me)) > 0 ||
            thegame.can_configure) {
@@ -1159,6 +1161,10 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
            if (get_config(fe, CFG_SEED))
                new_game_type(fe);
            break;
+         case IDM_DESC:
+           if (get_config(fe, CFG_DESC))
+               new_game_type(fe);
+           break;
           case IDM_ABOUT:
            about(fe);
             break;