ref: 6e42ddd31b5ca71f48c6260b01fc49b2451d0a56
parent: eb88ee0973c9a19b14e2e0e1579ee17625d73e05
author: Simon Tatham <anakin@pobox.com>
date: Mon May 3 04:51:31 EDT 2004
Implement selection of game seeds, by reusing the config box 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. [originally from svn r4186]
--- a/cube.c
+++ b/cube.c
@@ -285,8 +285,8 @@
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;
@@ -807,6 +807,34 @@
}
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)
--- a/fifteen.c
+++ b/fifteen.c
@@ -246,6 +246,57 @@
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);
--- a/gtk.c
+++ b/gtk.c
@@ -66,7 +66,7 @@
struct font *fonts;
int nfonts, fontsize;
config_item *cfg;
- int cfgret;
+ int cfg_which, cfgret;
GtkWidget *cfgbox;
};
@@ -421,7 +421,7 @@
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 @@
"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 @@
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_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 @@
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 @@
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 @@
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);
--- 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 @@
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 @@
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 @@
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 @@
}
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 @@
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;
}
--- a/net.c
+++ b/net.c
@@ -272,6 +272,15 @@
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.
*/
--- a/nullgame.c
+++ b/nullgame.c
@@ -81,6 +81,11 @@
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);
--- a/puzzles.h
+++ b/puzzles.h
@@ -107,7 +107,7 @@
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 @@
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 @@
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);
--- a/sixteen.c
+++ b/sixteen.c
@@ -257,6 +257,58 @@
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);
--- 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 @@
int nfonts, fontsize;
config_item *cfg;
struct cfg_aux *cfgaux;
- int cfg_done;
+ int cfg_which, cfg_done;
HFONT cfgfont;
};
@@ -314,7 +315,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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: