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; + 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; } - free_params(me->params); - me->params = params; - 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: