From 5928817c732d591bd446b6b048702a98f5ec1804 Mon Sep 17 00:00:00 2001 From: simon Date: Mon, 3 May 2004 08:51:31 +0000 Subject: [PATCH] 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. git-svn-id: svn://svn.tartarus.org/sgt/puzzles@4186 cda61777-01e9-0310-a592-d414129be87e --- cube.c | 32 +++++++++++++++++++++++-- fifteen.c | 51 +++++++++++++++++++++++++++++++++++++++ gtk.c | 33 ++++++++++++++++++------- midend.c | 81 +++++++++++++++++++++++++++++++++++++++++++++++++------------- net.c | 9 +++++++ nullgame.c | 5 ++++ puzzles.h | 8 ++++--- sixteen.c | 52 ++++++++++++++++++++++++++++++++++++++++ windows.c | 25 ++++++++++++------- 9 files changed, 257 insertions(+), 39 deletions(-) diff --git a/cube.c b/cube.c index c867471..6c2658f 100644 --- 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); diff --git a/fifteen.c b/fifteen.c index 04a8f75..74fd4fc 100644 --- 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 --- 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); diff --git a/midend.c b/midend.c index ed8286b..818b278 100644 --- a/midend.c +++ b/midend.c @@ -6,6 +6,7 @@ */ #include +#include #include #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 --- 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. */ diff --git a/nullgame.c b/nullgame.c index 42682e2..bc0c386 100644 --- a/nullgame.c +++ b/nullgame.c @@ -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); diff --git a/puzzles.h b/puzzles.h index 78cecc6..10250a0 100644 --- 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); diff --git a/sixteen.c b/sixteen.c index 7217d10..78bd851 100644 --- 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); diff --git a/windows.c b/windows.c index 7288719..8fbcf8e 100644 --- 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: -- 2.11.0