From c823052456548bc19a37e35a0bfae944004ef049 Mon Sep 17 00:00:00 2001 From: simon Date: Sat, 1 May 2004 11:32:12 +0000 Subject: [PATCH] Configuration dialog box, on the GTK front end only as yet. git-svn-id: svn://svn.tartarus.org/sgt/puzzles@4182 cda61777-01e9-0310-a592-d414129be87e --- cube.c | 106 +++++++++++++++++++++-- fifteen.c | 46 ++++++++++ gtk.c | 277 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- midend.c | 24 ++++++ net.c | 68 +++++++++++++++ nullgame.c | 16 ++++ puzzles.h | 39 +++++++++ sixteen.c | 57 ++++++++++++- 8 files changed, 623 insertions(+), 10 deletions(-) diff --git a/cube.c b/cube.c index e779068..d9c0cad 100644 --- a/cube.c +++ b/cube.c @@ -11,6 +11,7 @@ #include "puzzles.h" const char *const game_name = "Cube"; +const int game_can_configure = TRUE; #define MAXVERTICES 20 #define MAXFACES 20 @@ -238,8 +239,8 @@ int game_fetch_preset(int i, char **name, game_params **params) case 1: str = "Tetrahedron"; ret->solid = TETRAHEDRON; - ret->d1 = 2; - ret->d2 = 1; + ret->d1 = 1; + ret->d2 = 2; break; case 2: str = "Octahedron"; @@ -324,12 +325,12 @@ static void enum_grid_squares(game_params *params, float theight = (float)(sqrt(3) / 2.0); for (row = 0; row < params->d1 + params->d2; row++) { - if (row < params->d1) { + if (row < params->d2) { other = +1; - rowlen = row + params->d2; + rowlen = row + params->d1; } else { other = -1; - rowlen = 2*params->d1 + params->d2 - row; + rowlen = 2*params->d2 + params->d1 - row; } /* @@ -415,7 +416,7 @@ static void enum_grid_squares(game_params *params, sq.flip = FALSE; if (firstix < 0) - firstix = ix; + firstix = (ix - 1) & 3; ix -= firstix; sq.tetra_class = ((row+(ix&1)) & 2) ^ (ix & 3); @@ -443,6 +444,99 @@ static int grid_area(int d1, int d2, int order) return d1*d1 + d2*d2 + 4*d1*d2; } +config_item *game_configure(game_params *params) +{ + config_item *ret = snewn(4, config_item); + char buf[80]; + + ret[0].name = "Type of solid"; + ret[0].type = CHOICES; + ret[0].sval = ":Tetrahedron:Cube:Octahedron:Icosahedron"; + ret[0].ival = params->solid; + + ret[1].name = "Width / top"; + ret[1].type = STRING; + sprintf(buf, "%d", params->d1); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = "Height / bottom"; + ret[2].type = STRING; + sprintf(buf, "%d", params->d2); + ret[2].sval = dupstr(buf); + ret[2].ival = 0; + + ret[3].name = NULL; + ret[3].type = ENDCFG; + ret[3].sval = NULL; + ret[3].ival = 0; + + return ret; +} + +game_params *custom_params(config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->solid = cfg[0].ival; + ret->d1 = atoi(cfg[1].sval); + ret->d2 = atoi(cfg[2].sval); + + return ret; +} + +static void count_grid_square_callback(void *ctx, struct grid_square *sq) +{ + int *classes = (int *)ctx; + int thisclass; + + if (classes[4] == 4) + thisclass = sq->tetra_class; + else if (classes[4] == 2) + thisclass = sq->flip; + else + thisclass = 0; + + classes[thisclass]++; +} + +char *validate_params(game_params *params) +{ + int classes[5]; + int i; + + if (params->solid < 0 || params->solid >= lenof(solids)) + return "Unrecognised solid type"; + + if (solids[params->solid]->order == 4) { + if (params->d1 <= 0 || params->d2 <= 0) + return "Both grid dimensions must be greater than zero"; + } else { + if (params->d1 <= 0 && params->d2 <= 0) + return "At least one grid dimension must be greater than zero"; + } + + for (i = 0; i < 4; i++) + classes[i] = 0; + if (params->solid == TETRAHEDRON) + classes[4] = 4; + else if (params->solid == OCTAHEDRON) + classes[4] = 2; + else + classes[4] = 1; + enum_grid_squares(params, count_grid_square_callback, classes); + + for (i = 0; i < classes[4]; i++) + if (classes[i] < solids[params->solid]->nfaces / classes[4]) + return "Not enough grid space to place all blue faces"; + + if (grid_area(params->d1, params->d2, solids[params->solid]->order) < + solids[params->solid]->nfaces + 1) + return "Not enough space to place the solid on an empty square"; + + return NULL; +} + struct grid_data { int *gridptrs[4]; int nsquares[4]; diff --git a/fifteen.c b/fifteen.c index 338bdfb..f7bcc68 100644 --- a/fifteen.c +++ b/fifteen.c @@ -11,6 +11,7 @@ #include "puzzles.h" const char *const game_name = "Fifteen"; +const int game_can_configure = TRUE; #define TILE_SIZE 48 #define BORDER (TILE_SIZE / 2) @@ -71,6 +72,51 @@ game_params *dup_params(game_params *params) return ret; } +config_item *game_configure(game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(3, config_item); + + ret[0].name = "Width"; + ret[0].type = STRING; + sprintf(buf, "%d", params->w); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = STRING; + sprintf(buf, "%d", params->h); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = NULL; + ret[2].type = ENDCFG; + ret[2].sval = NULL; + ret[2].ival = 0; + + return ret; +} + +game_params *custom_params(config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->w = atoi(cfg[0].sval); + ret->h = atoi(cfg[1].sval); + + return ret; +} + +char *validate_params(game_params *params) +{ + if (params->w < 2 && params->h < 2) + return "Width and height must both be at least two"; + + return NULL; +} + int perm_parity(int *perm, int n) { int i, j, ret; diff --git a/gtk.c b/gtk.c index 15d918a..d443f5f 100644 --- a/gtk.c +++ b/gtk.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -64,6 +65,9 @@ struct frontend { int timer_active, timer_id; struct font *fonts; int nfonts, fontsize; + config_item *cfg; + int cfgret; + GtkWidget *cfgbox; }; void frontend_default_colour(frontend *fe, float *output) @@ -370,6 +374,252 @@ void activate_timer(frontend *fe) fe->timer_active = TRUE; } +static void window_destroy(GtkWidget *widget, gpointer data) +{ + gtk_main_quit(); +} + +static void errmsg_button_clicked(GtkButton *button, gpointer data) +{ + gtk_widget_destroy(GTK_WIDGET(data)); +} + +void error_box(GtkWidget *parent, char *msg) +{ + GtkWidget *window, *hbox, *text, *ok; + + window = gtk_dialog_new(); + text = gtk_label_new(msg); + gtk_misc_set_alignment(GTK_MISC(text), 0.0, 0.0); + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox), text, FALSE, FALSE, 20); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox), + hbox, FALSE, FALSE, 20); + gtk_widget_show(text); + gtk_widget_show(hbox); + gtk_window_set_title(GTK_WINDOW(window), "Error"); + gtk_label_set_line_wrap(GTK_LABEL(text), TRUE); + ok = gtk_button_new_with_label("OK"); + gtk_box_pack_end(GTK_BOX(GTK_DIALOG(window)->action_area), + ok, FALSE, FALSE, 0); + gtk_widget_show(ok); + GTK_WIDGET_SET_FLAGS(ok, GTK_CAN_DEFAULT); + gtk_window_set_default(GTK_WINDOW(window), ok); + gtk_signal_connect(GTK_OBJECT(ok), "clicked", + GTK_SIGNAL_FUNC(errmsg_button_clicked), window); + gtk_signal_connect(GTK_OBJECT(window), "destroy", + GTK_SIGNAL_FUNC(window_destroy), NULL); + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(parent)); + //set_transient_window_pos(parent, window); + gtk_widget_show(window); + gtk_main(); +} + +static void config_ok_button_clicked(GtkButton *button, gpointer data) +{ + frontend *fe = (frontend *)data; + char *err; + + err = midend_set_config(fe->me, fe->cfg); + + if (err) + error_box(fe->cfgbox, err); + else { + fe->cfgret = TRUE; + gtk_widget_destroy(fe->cfgbox); + } +} + +static void config_cancel_button_clicked(GtkButton *button, gpointer data) +{ + frontend *fe = (frontend *)data; + + gtk_widget_destroy(fe->cfgbox); +} + +static void editbox_changed(GtkEditable *ed, gpointer data) +{ + config_item *i = (config_item *)data; + + sfree(i->sval); + i->sval = dupstr(gtk_entry_get_text(GTK_ENTRY(ed))); +} + +static void button_toggled(GtkToggleButton *tb, gpointer data) +{ + config_item *i = (config_item *)data; + + i->ival = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tb)); +} + +static void droplist_sel(GtkMenuItem *item, gpointer data) +{ + config_item *i = (config_item *)data; + + i->ival = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(item), + "user-data")); +} + +static int get_config(frontend *fe) +{ + GtkWidget *w, *table; + config_item *i; + int y; + + fe->cfg = midend_get_config(fe->me); + fe->cfgret = FALSE; + + fe->cfgbox = gtk_dialog_new(); + gtk_window_set_title(GTK_WINDOW(fe->cfgbox), "Configure"); + + w = gtk_button_new_with_label("OK"); + gtk_box_pack_end(GTK_BOX(GTK_DIALOG(fe->cfgbox)->action_area), + w, FALSE, FALSE, 0); + gtk_widget_show(w); + GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT); + gtk_window_set_default(GTK_WINDOW(fe->cfgbox), w); + gtk_signal_connect(GTK_OBJECT(w), "clicked", + GTK_SIGNAL_FUNC(config_ok_button_clicked), fe); + + w = gtk_button_new_with_label("Cancel"); + gtk_box_pack_end(GTK_BOX(GTK_DIALOG(fe->cfgbox)->action_area), + w, FALSE, FALSE, 0); + gtk_widget_show(w); + gtk_signal_connect(GTK_OBJECT(w), "clicked", + GTK_SIGNAL_FUNC(config_cancel_button_clicked), fe); + + table = gtk_table_new(1, 2, FALSE); + y = 0; + gtk_box_pack_end(GTK_BOX(GTK_DIALOG(fe->cfgbox)->vbox), + table, FALSE, FALSE, 0); + gtk_widget_show(table); + + for (i = fe->cfg; i->type != ENDCFG; i++) { + gtk_table_resize(GTK_TABLE(table), y+1, 2); + + switch (i->type) { + case STRING: + /* + * Edit box with a label beside it. + */ + + w = gtk_label_new(i->name); + gtk_misc_set_alignment(GTK_MISC(w), 0.0, 0.5); + gtk_table_attach(GTK_TABLE(table), w, 0, 1, y, y+1, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + 3, 3); + gtk_widget_show(w); + + w = gtk_entry_new(); + gtk_table_attach(GTK_TABLE(table), w, 1, 2, y, y+1, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + 3, 3); + gtk_entry_set_text(GTK_ENTRY(w), i->sval); + gtk_signal_connect(GTK_OBJECT(w), "changed", + GTK_SIGNAL_FUNC(editbox_changed), i); + gtk_widget_show(w); + + break; + + case BOOLEAN: + /* + * Simple checkbox. + */ + w = gtk_check_button_new_with_label(i->name); + gtk_signal_connect(GTK_OBJECT(w), "toggled", + GTK_SIGNAL_FUNC(button_toggled), i); + gtk_table_attach(GTK_TABLE(table), w, 0, 2, y, y+1, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + 3, 3); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), i->ival); + gtk_widget_show(w); + break; + + case CHOICES: + /* + * Drop-down list (GtkOptionMenu). + */ + + w = gtk_label_new(i->name); + gtk_misc_set_alignment(GTK_MISC(w), 0.0, 0.5); + gtk_table_attach(GTK_TABLE(table), w, 0, 1, y, y+1, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + 3, 3); + gtk_widget_show(w); + + w = gtk_option_menu_new(); + gtk_table_attach(GTK_TABLE(table), w, 1, 2, y, y+1, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + 3, 3); + gtk_widget_show(w); + + { + int c, val; + char *p, *q, *name; + GtkWidget *menuitem; + GtkWidget *menu = gtk_menu_new(); + + gtk_option_menu_set_menu(GTK_OPTION_MENU(w), menu); + + c = *i->sval; + p = i->sval+1; + val = 0; + + while (*p) { + q = p; + while (*q && *q != c) + q++; + + name = snewn(q-p+1, char); + strncpy(name, p, q-p); + name[q-p] = '\0'; + + if (*q) q++; /* eat delimiter */ + + menuitem = gtk_menu_item_new_with_label(name); + gtk_container_add(GTK_CONTAINER(menu), menuitem); + gtk_object_set_data(GTK_OBJECT(menuitem), "user-data", + GINT_TO_POINTER(val)); + gtk_signal_connect(GTK_OBJECT(menuitem), "activate", + GTK_SIGNAL_FUNC(droplist_sel), i); + gtk_widget_show(menuitem); + + val++; + + p = q; + } + + gtk_option_menu_set_history(GTK_OPTION_MENU(w), i->ival); + } + + break; + } + + y++; + } + + gtk_signal_connect(GTK_OBJECT(fe->cfgbox), "destroy", + GTK_SIGNAL_FUNC(window_destroy), NULL); + gtk_window_set_modal(GTK_WINDOW(fe->cfgbox), TRUE); + gtk_window_set_transient_for(GTK_WINDOW(fe->cfgbox), + GTK_WINDOW(fe->window)); + //set_transient_window_pos(fe->window, fe->cfgbox); + gtk_widget_show(fe->cfgbox); + gtk_main(); + + /* + * FIXME: free fe->cfg + */ + + return fe->cfgret; +} + static void menu_key_event(GtkMenuItem *menuitem, gpointer data) { frontend *fe = (frontend *)data; @@ -394,6 +644,21 @@ static void menu_preset_event(GtkMenuItem *menuitem, gpointer data) fe->h = y; } +static void menu_config_event(GtkMenuItem *menuitem, gpointer data) +{ + frontend *fe = (frontend *)data; + int x, y; + + if (!get_config(fe)) + return; + + midend_new_game(fe->me, NULL); + midend_size(fe->me, &x, &y); + gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y); + fe->w = x; + fe->h = y; +} + static GtkWidget *add_menu_item_with_key(frontend *fe, GtkContainer *cont, char *text, int key) { @@ -451,12 +716,12 @@ 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'); - if ((n = midend_num_presets(fe->me)) > 0) { + if ((n = midend_num_presets(fe->me)) > 0 || game_can_configure) { GtkWidget *submenu; int i; menuitem = gtk_menu_item_new_with_label("Type"); - gtk_container_add(GTK_CONTAINER(menu), menuitem); + gtk_container_add(GTK_CONTAINER(menubar), menuitem); gtk_widget_show(menuitem); submenu = gtk_menu_new(); @@ -475,6 +740,14 @@ static frontend *new_window(void) GTK_SIGNAL_FUNC(menu_preset_event), fe); gtk_widget_show(menuitem); } + + if (game_can_configure) { + menuitem = gtk_menu_item_new_with_label("Custom..."); + gtk_container_add(GTK_CONTAINER(submenu), menuitem); + gtk_signal_connect(GTK_OBJECT(menuitem), "activate", + GTK_SIGNAL_FUNC(menu_config_event), fe); + gtk_widget_show(menuitem); + } } add_menu_separator(GTK_CONTAINER(menu)); diff --git a/midend.c b/midend.c index 79e6d7d..ed8286b 100644 --- a/midend.c +++ b/midend.c @@ -299,3 +299,27 @@ int midend_wants_statusbar(midend_data *me) { return game_wants_statusbar(); } + +config_item *midend_get_config(midend_data *me) +{ + return game_configure(me->params); +} + +char *midend_set_config(midend_data *me, config_item *cfg) +{ + char *error; + game_params *params; + + params = custom_params(cfg); + error = validate_params(params); + + if (error) { + free_params(params); + return error; + } + + free_params(me->params); + me->params = params; + + return NULL; +} diff --git a/net.c b/net.c index e85608f..8170bda 100644 --- a/net.c +++ b/net.c @@ -12,6 +12,7 @@ #include "tree234.h" const char *const game_name = "Net"; +const int game_can_configure = TRUE; #define PI 3.141592653589793238462643383279502884197169399 @@ -182,6 +183,73 @@ game_params *dup_params(game_params *params) return ret; } +config_item *game_configure(game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(5, config_item); + + ret[0].name = "Width"; + ret[0].type = STRING; + sprintf(buf, "%d", params->width); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = STRING; + sprintf(buf, "%d", params->height); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = "Walls wrap around"; + ret[2].type = BOOLEAN; + ret[2].sval = NULL; + ret[2].ival = params->wrapping; + + ret[3].name = "Barrier probability"; + ret[3].type = STRING; + sprintf(buf, "%g", params->barrier_probability); + ret[3].sval = dupstr(buf); + ret[3].ival = 0; + + ret[4].name = NULL; + ret[4].type = ENDCFG; + ret[4].sval = NULL; + ret[4].ival = 0; + + return ret; +} + +game_params *custom_params(config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->width = atoi(cfg[0].sval); + ret->height = atoi(cfg[1].sval); + ret->wrapping = cfg[2].ival; + ret->barrier_probability = atof(cfg[3].sval); + + return ret; +} + +char *validate_params(game_params *params) +{ + if (params->width <= 0 && params->height <= 0) + return "Width and height must both be greater than zero"; + if (params->width <= 0) + return "Width must be greater than zero"; + if (params->height <= 0) + return "Height must be greater than zero"; + if (params->width <= 1 && params->height <= 1) + return "At least one of width and height must be greater than one"; + if (params->barrier_probability < 0) + return "Barrier probability may not be negative"; + if (params->barrier_probability > 1) + return "Barrier probability may not be greater than 1"; + return NULL; +} + /* ---------------------------------------------------------------------- * Randomly select a new game seed. */ diff --git a/nullgame.c b/nullgame.c index 3d278ad..42682e2 100644 --- a/nullgame.c +++ b/nullgame.c @@ -20,6 +20,7 @@ #include "puzzles.h" const char *const game_name = "Null Game"; +const int game_can_configure = FALSE; enum { COL_BACKGROUND, @@ -60,6 +61,21 @@ game_params *dup_params(game_params *params) return ret; } +config_item *game_configure(game_params *params) +{ + return NULL; +} + +game_params *custom_params(config_item *cfg) +{ + return NULL; +} + +char *validate_params(game_params *params) +{ + return NULL; +} + char *new_game_seed(game_params *params) { return dupstr("FIXME"); diff --git a/puzzles.h b/puzzles.h index 0c5dec4..be93336 100644 --- a/puzzles.h +++ b/puzzles.h @@ -31,6 +31,7 @@ enum { #define IGNOREARG(x) ( (x) = (x) ) typedef struct frontend frontend; +typedef struct config_item config_item; typedef struct midend_data midend_data; typedef struct random_state random_state; typedef struct game_params game_params; @@ -48,6 +49,38 @@ typedef struct game_drawstate game_drawstate; #define FONT_VARIABLE 1 /* + * Structure used to pass configuration data between frontend and + * game + */ +enum { STRING, CHOICES, BOOLEAN, ENDCFG }; +struct config_item { + /* + * `name' is never dynamically allocated. + */ + char *name; + /* + * `type' contains one of the above values. + */ + int type; + /* + * For STRING, `sval' is always dynamically allocated and + * non-NULL. For BOOLEAN and ENDCFG, `sval' is always NULL. For + * CHOICES, `sval' is non-NULL, _not_ dynamically allocated, + * and contains a set of option strings separated by a + * delimiter. The delimeter is also the first character in the + * string, so for example ":Foo:Bar:Baz" gives three options + * `Foo', `Bar' and `Baz'. + */ + char *sval; + /* + * For BOOLEAN, this is TRUE or FALSE. For CHOICES, it + * indicates the chosen index from the `sval' list. In the + * above example, 0==Foo, 1==Bar and 2==Baz. + */ + int ival; +}; + +/* * Platform routines */ void fatal(char *fmt, ...); @@ -84,6 +117,8 @@ 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); /* * malloc.c @@ -115,10 +150,14 @@ void random_free(random_state *state); * Game-specific routines */ extern const char *const game_name; +const int game_can_configure; game_params *default_params(void); int game_fetch_preset(int i, char **name, game_params **params); void free_params(game_params *params); game_params *dup_params(game_params *params); +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); game_state *new_game(game_params *params, char *seed); game_state *dup_game(game_state *state); diff --git a/sixteen.c b/sixteen.c index 3ea115c..22faea9 100644 --- a/sixteen.c +++ b/sixteen.c @@ -13,6 +13,7 @@ #include "puzzles.h" const char *const game_name = "Sixteen"; +const int game_can_configure = TRUE; #define TILE_SIZE 48 #define BORDER TILE_SIZE /* big border to fill with arrows */ @@ -44,6 +45,7 @@ struct game_state { int *tiles; int completed; int movecount; + int last_movement_sense; }; game_params *default_params(void) @@ -90,6 +92,51 @@ game_params *dup_params(game_params *params) return ret; } +config_item *game_configure(game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(3, config_item); + + ret[0].name = "Width"; + ret[0].type = STRING; + sprintf(buf, "%d", params->w); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = STRING; + sprintf(buf, "%d", params->h); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = NULL; + ret[2].type = ENDCFG; + ret[2].sval = NULL; + ret[2].ival = 0; + + return ret; +} + +game_params *custom_params(config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->w = atoi(cfg[0].sval); + ret->h = atoi(cfg[1].sval); + + return ret; +} + +char *validate_params(game_params *params) +{ + if (params->w < 2 && params->h < 2) + return "Width and height must both be at least two"; + + return NULL; +} + int perm_parity(int *perm, int n) { int i, j, ret; @@ -233,6 +280,7 @@ game_state *new_game(game_params *params, char *seed) assert(!*p); state->completed = state->movecount = 0; + state->last_movement_sense = 0; return state; } @@ -248,6 +296,7 @@ game_state *dup_game(game_state *state) memcpy(ret->tiles, state->tiles, state->w * state->h * sizeof(int)); ret->completed = state->completed; ret->movecount = state->movecount; + ret->last_movement_sense = state->last_movement_sense; return ret; } @@ -291,6 +340,8 @@ game_state *make_move(game_state *from, int x, int y, int button) ret->movecount++; + ret->last_movement_sense = -(dx+dy); + /* * See if the game has been completed. */ @@ -551,13 +602,15 @@ void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate, y0 = COORD(Y(state, j)); dx = (x1 - x0); - if (abs(dx) > TILE_SIZE) { + if (dx != 0 && + dx != TILE_SIZE * state->last_movement_sense) { dx = (dx < 0 ? dx + TILE_SIZE * state->w : dx - TILE_SIZE * state->w); assert(abs(dx) == TILE_SIZE); } dy = (y1 - y0); - if (abs(dy) > TILE_SIZE) { + if (dy != 0 && + dy != TILE_SIZE * state->last_movement_sense) { dy = (dy < 0 ? dy + TILE_SIZE * state->h : dy - TILE_SIZE * state->h); assert(abs(dy) == TILE_SIZE); -- 2.11.0