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;
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);
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);
struct font *fonts;
int nfonts, fontsize;
config_item *cfg;
- int cfgret;
+ int cfg_which, cfgret;
GtkWidget *cfgbox;
};
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);
"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),
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;
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;
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);
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;
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);
*/
#include <stdio.h>
+#include <string.h>
#include <assert.h>
#include "puzzles.h"
struct midend_data {
frontend *frontend;
char *seed;
+ int fresh_seed;
int nstates, statesize, statepos;
game_params **presets;
me->states = NULL;
me->params = default_params();
me->seed = NULL;
+ me->fresh_seed = FALSE;
me->drawstate = NULL;
me->oldstate = NULL;
me->presets = NULL;
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]);
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);
}
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') {
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;
}
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.
*/
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);
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);
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
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);
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);
#define IDM_REDO 0x0040
#define IDM_QUIT 0x0050
#define IDM_CONFIG 0x0060
+#define IDM_SEED 0x0070
#define IDM_PRESETS 0x0100
#ifdef DEBUG
int nfonts, fontsize;
config_item *cfg;
struct cfg_aux *cfgaux;
- int cfg_done;
+ int cfg_which, cfg_done;
HFONT cfgfont;
};
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;
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) {
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",
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;
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
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);
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;
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: