Implement selection of game seeds, by reusing the config box
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Mon, 3 May 2004 08:51:31 +0000 (08:51 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Mon, 3 May 2004 08:51:31 +0000 (08:51 +0000)
mechanism I've just invented (the midend handles the standard game
selection configuration). Each game is now required to validate its
own seed data before attempting to base a game on it and potentially
confusing itself.

git-svn-id: svn://svn.tartarus.org/sgt/puzzles@4186 cda61777-01e9-0310-a592-d414129be87e

cube.c
fifteen.c
gtk.c
midend.c
net.c
nullgame.c
puzzles.h
sixteen.c
windows.c

diff --git a/cube.c b/cube.c
index c867471..6c2658f 100644 (file)
--- a/cube.c
+++ b/cube.c
@@ -285,8 +285,8 @@ static void enum_grid_squares(game_params *params,
     if (solid->order == 4) {
         int x, y;
 
-        for (x = 0; x < params->d1; x++)
-            for (y = 0; y < params->d2; y++) {
+       for (y = 0; y < params->d2; y++)
+           for (x = 0; x < params->d1; x++) {
                 struct grid_square sq;
 
                 sq.x = (float)x;
@@ -809,6 +809,34 @@ static struct solid *transform_poly(const struct solid *solid, int flip,
     return ret;
 }
 
+char *validate_seed(game_params *params, char *seed)
+{
+    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];
+       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 */
+    }
+
+    if (seed[i] != ':')
+       return "Expected ':' after hex digits";
+
+    i++;
+    do {
+       if (seed[i] < '0' || seed[i] > '9')
+           return "Expected decimal integer after ':'";
+       i++;
+    } while (seed[i]);
+
+    return NULL;
+}
+
 game_state *new_game(game_params *params, char *seed)
 {
     game_state *state = snew(game_state);
index 04a8f75..74fd4fc 100644 (file)
--- a/fifteen.c
+++ b/fifteen.c
@@ -246,6 +246,57 @@ char *new_game_seed(game_params *params)
     return ret;
 }
 
+char *validate_seed(game_params *params, char *seed)
+{
+    char *p, *err;
+    int i, area;
+    int *used;
+
+    area = params->w * params->h;
+    p = seed;
+    err = NULL;
+
+    used = snewn(area, int);
+    for (i = 0; i < area; i++)
+       used[i] = FALSE;
+
+    for (i = 0; i < area; i++) {
+       char *q = p;
+       int n;
+
+       if (*p < '0' || *p > '9') {
+           err = "Not enough numbers in string";
+           goto leave;
+       }
+       while (*p >= '0' && *p <= '9')
+           p++;
+       if (i < area-1 && *p != ',') {
+           err = "Expected comma after number";
+           goto leave;
+       }
+       else if (i == area-1 && *p) {
+           err = "Excess junk at end of string";
+           goto leave;
+       }
+       n = atoi(q);
+       if (n < 0 || n >= area) {
+           err = "Number out of range";
+           goto leave;
+       }
+       if (used[n]) {
+           err = "Number used twice";
+           goto leave;
+       }
+       used[n] = TRUE;
+
+       if (*p) p++;                   /* eat comma */
+    }
+
+    leave:
+    sfree(used);
+    return err;
+}
+
 game_state *new_game(game_params *params, char *seed)
 {
     game_state *state = snew(game_state);
diff --git a/gtk.c b/gtk.c
index 5e51028..6906f6c 100644 (file)
--- a/gtk.c
+++ b/gtk.c
@@ -66,7 +66,7 @@ struct frontend {
     struct font *fonts;
     int nfonts, fontsize;
     config_item *cfg;
-    int cfgret;
+    int cfg_which, cfgret;
     GtkWidget *cfgbox;
 };
 
@@ -421,7 +421,7 @@ static void config_ok_button_clicked(GtkButton *button, gpointer data)
     frontend *fe = (frontend *)data;
     char *err;
 
-    err = midend_set_config(fe->me, fe->cfg);
+    err = midend_set_config(fe->me, fe->cfg_which, fe->cfg);
 
     if (err)
        error_box(fe->cfgbox, err);
@@ -461,17 +461,20 @@ static void droplist_sel(GtkMenuItem *item, gpointer data)
                                                  "user-data"));
 }
 
-static int get_config(frontend *fe)
+static int get_config(frontend *fe, int which)
 {
     GtkWidget *w, *table;
+    char *title;
     config_item *i;
     int y;
 
-    fe->cfg = midend_get_config(fe->me);
+    fe->cfg = midend_get_config(fe->me, which, &title);
+    fe->cfg_which = which;
     fe->cfgret = FALSE;
 
     fe->cfgbox = gtk_dialog_new();
-    gtk_window_set_title(GTK_WINDOW(fe->cfgbox), "Configure");
+    gtk_window_set_title(GTK_WINDOW(fe->cfgbox), title);
+    sfree(title);
 
     w = gtk_button_new_with_label("OK");
     gtk_box_pack_end(GTK_BOX(GTK_DIALOG(fe->cfgbox)->action_area),
@@ -635,7 +638,7 @@ static void menu_preset_event(GtkMenuItem *menuitem, gpointer data)
     int x, y;
 
     midend_set_params(fe->me, params);
-    midend_new_game(fe->me, NULL);
+    midend_new_game(fe->me);
     midend_size(fe->me, &x, &y);
     gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y);
     fe->w = x;
@@ -645,12 +648,14 @@ static void menu_preset_event(GtkMenuItem *menuitem, gpointer data)
 static void menu_config_event(GtkMenuItem *menuitem, gpointer data)
 {
     frontend *fe = (frontend *)data;
+    int which = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(menuitem),
+                                                   "user-data"));
     int x, y;
 
-    if (!get_config(fe))
+    if (!get_config(fe, which))
        return;
 
-    midend_new_game(fe->me, NULL);
+    midend_new_game(fe->me);
     midend_size(fe->me, &x, &y);
     gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y);
     fe->w = x;
@@ -687,7 +692,7 @@ static frontend *new_window(void)
     fe = snew(frontend);
 
     fe->me = midend_new(fe);
-    midend_new_game(fe->me, NULL);
+    midend_new_game(fe->me);
 
     fe->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
     gtk_window_set_title(GTK_WINDOW(fe->window), game_name);
@@ -714,6 +719,14 @@ static frontend *new_window(void)
     add_menu_item_with_key(fe, GTK_CONTAINER(menu), "New", 'n');
     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_SEED));
+    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);
+
     if ((n = midend_num_presets(fe->me)) > 0 || game_can_configure) {
         GtkWidget *submenu;
         int i;
@@ -741,6 +754,8 @@ static frontend *new_window(void)
 
        if (game_can_configure) {
             menuitem = gtk_menu_item_new_with_label("Custom...");
+            gtk_object_set_data(GTK_OBJECT(menuitem), "user-data",
+                               GPOINTER_TO_INT(CFG_SETTINGS));
             gtk_container_add(GTK_CONTAINER(submenu), menuitem);
             gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
                                GTK_SIGNAL_FUNC(menu_config_event), fe);
index ed8286b..818b278 100644 (file)
--- a/midend.c
+++ b/midend.c
@@ -6,6 +6,7 @@
  */
 
 #include <stdio.h>
+#include <string.h>
 #include <assert.h>
 
 #include "puzzles.h"
@@ -13,6 +14,7 @@
 struct midend_data {
     frontend *frontend;
     char *seed;
+    int fresh_seed;
     int nstates, statesize, statepos;
 
     game_params **presets;
@@ -43,6 +45,7 @@ midend_data *midend_new(frontend *frontend)
     me->states = NULL;
     me->params = default_params();
     me->seed = NULL;
+    me->fresh_seed = FALSE;
     me->drawstate = NULL;
     me->oldstate = NULL;
     me->presets = NULL;
@@ -73,7 +76,7 @@ void midend_set_params(midend_data *me, game_params *params)
     me->params = dup_params(params);
 }
 
-void midend_new_game(midend_data *me, char *seed)
+void midend_new_game(midend_data *me)
 {
     while (me->nstates > 0)
        free_game(me->states[--me->nstates]);
@@ -83,11 +86,11 @@ void midend_new_game(midend_data *me, char *seed)
 
     assert(me->nstates == 0);
 
-    sfree(me->seed);
-    if (seed)
-       me->seed = dupstr(seed);
-    else
+    if (!me->fresh_seed) {
+       sfree(me->seed);
        me->seed = new_game_seed(me->params);
+    } else
+       me->fresh_seed = FALSE;
 
     ensure(me);
     me->states[me->nstates++] = new_game(me->params, me->seed);
@@ -156,7 +159,7 @@ int midend_process_key(midend_data *me, int x, int y, int button)
     }
 
     if (button == 'n' || button == 'N' || button == '\x0E') {
-       midend_new_game(me, NULL);
+       midend_new_game(me);
         midend_redraw(me);
         return 1;                      /* never animate */
     } else if (button == 'r' || button == 'R') {
@@ -300,26 +303,70 @@ int midend_wants_statusbar(midend_data *me)
     return game_wants_statusbar();
 }
 
-config_item *midend_get_config(midend_data *me)
+config_item *midend_get_config(midend_data *me, int which, char **wintitle)
 {
-    return game_configure(me->params);
+    char *titlebuf;
+    config_item *ret;
+
+    titlebuf = snewn(40 + strlen(game_name), char);
+
+    switch (which) {
+      case CFG_SETTINGS:
+       sprintf(titlebuf, "%s configuration", game_name);
+       *wintitle = dupstr(titlebuf);
+       return game_configure(me->params);
+      case CFG_SEED:
+       sprintf(titlebuf, "%s game selection", game_name);
+       *wintitle = dupstr(titlebuf);
+
+       ret = snewn(2, config_item);
+
+       ret[0].type = C_STRING;
+       ret[0].name = "Game ID";
+       ret[0].ival = 0;
+       ret[0].sval = dupstr(me->seed);
+
+       ret[1].type = C_END;
+       ret[1].name = ret[1].sval = NULL;
+       ret[1].ival = 0;
+
+       return ret;
+    }
+
+    assert(!"We shouldn't be here");
+    return NULL;
 }
 
-char *midend_set_config(midend_data *me, config_item *cfg)
+char *midend_set_config(midend_data *me, int which, config_item *cfg)
 {
     char *error;
     game_params *params;
 
-    params = custom_params(cfg);
-    error = validate_params(params);
+    switch (which) {
+      case CFG_SETTINGS:
+       params = custom_params(cfg);
+       error = validate_params(params);
 
-    if (error) {
-       free_params(params);
-       return error;
-    }
+       if (error) {
+           free_params(params);
+           return error;
+       }
 
-    free_params(me->params);
-    me->params = params;
+       free_params(me->params);
+       me->params = params;
+       break;
+
+      case CFG_SEED:
+       error = validate_seed(me->params, cfg[0].sval);
+       if (error)
+           return error;
+
+       sfree(me->seed);
+       me->seed = dupstr(cfg[0].sval);
+       me->fresh_seed = TRUE;
+
+       break;
+    }
 
     return NULL;
 }
diff --git a/net.c b/net.c
index 3c28ccc..cf3fe3d 100644 (file)
--- a/net.c
+++ b/net.c
@@ -272,6 +272,15 @@ char *new_game_seed(game_params *params)
     return dupstr(buf);
 }
 
+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.
  */
index 42682e2..bc0c386 100644 (file)
@@ -81,6 +81,11 @@ char *new_game_seed(game_params *params)
     return dupstr("FIXME");
 }
 
+char *validate_seed(game_params *params, char *seed)
+{
+    return NULL;
+}
+
 game_state *new_game(game_params *params, char *seed)
 {
     game_state *state = snew(game_state);
index 78cecc6..10250a0 100644 (file)
--- a/puzzles.h
+++ b/puzzles.h
@@ -107,7 +107,7 @@ midend_data *midend_new(frontend *fe);
 void midend_free(midend_data *me);
 void midend_set_params(midend_data *me, game_params *params);
 void midend_size(midend_data *me, int *x, int *y);
-void midend_new_game(midend_data *me, char *seed);
+void midend_new_game(midend_data *me);
 void midend_restart_game(midend_data *me);
 int midend_process_key(midend_data *me, int x, int y, int button);
 void midend_redraw(midend_data *me);
@@ -117,8 +117,9 @@ 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);
-config_item *midend_get_config(midend_data *me);
-char *midend_set_config(midend_data *me, config_item *cfg);
+enum { CFG_SETTINGS, CFG_SEED };
+config_item *midend_get_config(midend_data *me, int which, char **wintitle);
+char *midend_set_config(midend_data *me, int which, config_item *cfg);
 
 /*
  * malloc.c
@@ -160,6 +161,7 @@ config_item *game_configure(game_params *params);
 game_params *custom_params(config_item *cfg);
 char *validate_params(game_params *params);
 char *new_game_seed(game_params *params);
+char *validate_seed(game_params *params, char *seed);
 game_state *new_game(game_params *params, char *seed);
 game_state *dup_game(game_state *state);
 void free_game(game_state *state);
index 7217d10..78bd851 100644 (file)
--- a/sixteen.c
+++ b/sixteen.c
@@ -257,6 +257,58 @@ char *new_game_seed(game_params *params)
     return ret;
 }
 
+
+char *validate_seed(game_params *params, char *seed)
+{
+    char *p, *err;
+    int i, area;
+    int *used;
+
+    area = params->w * params->h;
+    p = seed;
+    err = NULL;
+
+    used = snewn(area, int);
+    for (i = 0; i < area; i++)
+       used[i] = FALSE;
+
+    for (i = 0; i < area; i++) {
+       char *q = p;
+       int n;
+
+       if (*p < '0' || *p > '9') {
+           err = "Not enough numbers in string";
+           goto leave;
+       }
+       while (*p >= '0' && *p <= '9')
+           p++;
+       if (i < area-1 && *p != ',') {
+           err = "Expected comma after number";
+           goto leave;
+       }
+       else if (i == area-1 && *p) {
+           err = "Excess junk at end of string";
+           goto leave;
+       }
+       n = atoi(q);
+       if (n < 1 || n > area) {
+           err = "Number out of range";
+           goto leave;
+       }
+       if (used[n-1]) {
+           err = "Number used twice";
+           goto leave;
+       }
+       used[n-1] = TRUE;
+
+       if (*p) p++;                   /* eat comma */
+    }
+
+    leave:
+    sfree(used);
+    return err;
+}
+
 game_state *new_game(game_params *params, char *seed)
 {
     game_state *state = snew(game_state);
index 7288719..8fbcf8e 100644 (file)
--- a/windows.c
+++ b/windows.c
@@ -19,6 +19,7 @@
 #define IDM_REDO      0x0040
 #define IDM_QUIT      0x0050
 #define IDM_CONFIG    0x0060
+#define IDM_SEED      0x0070
 #define IDM_PRESETS   0x0100
 
 #ifdef DEBUG
@@ -93,7 +94,7 @@ struct frontend {
     int nfonts, fontsize;
     config_item *cfg;
     struct cfg_aux *cfgaux;
-    int cfg_done;
+    int cfg_which, cfg_done;
     HFONT cfgfont;
 };
 
@@ -314,7 +315,7 @@ static frontend *new_window(HINSTANCE inst)
     fe = snew(frontend);
     fe->me = midend_new(fe);
     fe->inst = inst;
-    midend_new_game(fe->me, NULL);
+    midend_new_game(fe->me);
     midend_size(fe->me, &x, &y);
 
     fe->timer = 0;
@@ -359,6 +360,7 @@ static frontend *new_window(HINSTANCE inst)
        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...");
 
        if ((fe->npresets = midend_num_presets(fe->me)) > 0 ||
            game_can_configure) {
@@ -443,7 +445,7 @@ static int CALLBACK ConfigDlgProc(HWND hwnd, UINT msg,
             HIWORD(wParam) == BN_DOUBLECLICKED) &&
            (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)) {
            if (LOWORD(wParam) == IDOK) {
-               char *err = midend_set_config(fe->me, fe->cfg);
+               char *err = midend_set_config(fe->me, fe->cfg_which, fe->cfg);
 
                if (err) {
                    MessageBox(hwnd, err, "Validation error",
@@ -505,10 +507,11 @@ HWND mkctrl(frontend *fe, int x1, int x2, int y1, int y2,
     return ret;
 }
 
-static int get_config(frontend *fe)
+static int get_config(frontend *fe, int which)
 {
     config_item *i;
     struct cfg_aux *j;
+    char *title;
     WNDCLASS wc;
     MSG msg;
     TEXTMETRIC tm;
@@ -553,7 +556,8 @@ static int get_config(frontend *fe)
        height = width = 30;
     }
 
-    fe->cfg = midend_get_config(fe->me);
+    fe->cfg = midend_get_config(fe->me, which, &title);
+    fe->cfg_which = which;
 
     /*
      * Figure out the layout of the config box by measuring the
@@ -627,12 +631,13 @@ static int get_config(frontend *fe)
        r.right += r.left;
        r.bottom += r.top;
 
-       fe->cfgbox = CreateWindowEx(0, wc.lpszClassName, "Configuration",
+       fe->cfgbox = CreateWindowEx(0, wc.lpszClassName, title,
                                    DS_MODALFRAME | WS_POPUP | WS_VISIBLE |
                                    WS_CAPTION | WS_SYSMENU,
                                    r.left, r.top,
                                    r.right-r.left, r.bottom-r.top,
                                    fe->hwnd, NULL, fe->inst, NULL);
+       sfree(title);
     }
 
     SendMessage(fe->cfgbox, WM_SETFONT, (WPARAM)fe->cfgfont, FALSE);
@@ -747,7 +752,7 @@ static void new_game_type(frontend *fe)
     HDC hdc;
     int x, y;
 
-    midend_new_game(fe->me, NULL);
+    midend_new_game(fe->me);
     midend_size(fe->me, &x, &y);
 
     r.left = r.top = 0;
@@ -812,7 +817,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
                PostQuitMessage(0);
            break;
          case IDM_CONFIG:
-           if (get_config(fe))
+           if (get_config(fe, CFG_SETTINGS))
+               new_game_type(fe);
+           break;
+         case IDM_SEED:
+           if (get_config(fe, CFG_SEED))
                new_game_type(fe);
            break;
          default: