chiark / gitweb /
Configuration dialog box, on the GTK front end only as yet.
authorSimon Tatham <anakin@pobox.com>
Sat, 1 May 2004 11:32:12 +0000 (11:32 +0000)
committerSimon Tatham <anakin@pobox.com>
Sat, 1 May 2004 11:32:12 +0000 (11:32 +0000)
[originally from svn r4182]

cube.c
fifteen.c
gtk.c
midend.c
net.c
nullgame.c
puzzles.h
sixteen.c

diff --git a/cube.c b/cube.c
index e7790687b8f618dfeaa978c8282c3ed9b202d136..d9c0cadf0b3c0f35f9d297514ed5b06baf720245 100644 (file)
--- 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];
index 338bdfb77480a95734d12603ff2d5428735d150a..f7bcc68650c8cb3e5f32e5f378f48a7a07ac1f6e 100644 (file)
--- 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 15d918a286d448dd8f28205420070e93e850bd6a..d443f5f76cdc0882416d799079906d53ab805147 100644 (file)
--- a/gtk.c
+++ b/gtk.c
@@ -7,6 +7,7 @@
 #include <stdlib.h>
 #include <time.h>
 #include <stdarg.h>
+#include <string.h>
 
 #include <gtk/gtk.h>
 #include <gdk/gdkkeysyms.h>
@@ -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));
index 79e6d7db7c16853524d1dd9b04bc9ceb875ed6a5..ed8286b5332b434d43ee58b1d68e372fe016863a 100644 (file)
--- 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 e85608f658d1e12c52364c54a670d0300c786675..8170bda8828e2a3fb2cd3fc5da099caeadc8c92d 100644 (file)
--- 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.
  */
index 3d278adfa2ed6155b40e4a0cea73c3e5281202ee..42682e2e3655675184668184aea6f9962e2dea1e 100644 (file)
@@ -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");
index 0c5dec430717f34202420a27d4bba4206965b3bb..be933366713777195c5818aafdbd311a0f4ae1e0 100644 (file)
--- 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;
@@ -47,6 +48,38 @@ typedef struct game_drawstate game_drawstate;
 #define FONT_FIXED    0
 #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
  */
@@ -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);
index 3ea115c2dacd879d7c8c1549e33d59671e293e27..22faea9a4273f364c1d24ff67f776a782c0f43aa 100644 (file)
--- 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);