shithub: puzzles

Download patch

ref: de67801b0fd3dfa11777c1ef86cd617baf376b7b
parent: eeb2db283de9115f7256fa4cc49597d63e06b0ab
author: Simon Tatham <anakin@pobox.com>
date: Sun Oct 1 09:38:35 EDT 2017

Use a proper union in struct config_item.

This allows me to use different types for the mutable, dynamically
allocated string value in a C_STRING control and the fixed constant
list of option names in a C_CHOICES.

--- a/blackbox.c
+++ b/blackbox.c
@@ -148,14 +148,12 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "No. of balls";
     ret[2].type = C_STRING;
@@ -163,13 +161,10 @@
         sprintf(buf, "%d", params->minballs);
     else
         sprintf(buf, "%d-%d", params->minballs, params->maxballs);
-    ret[2].sval = dupstr(buf);
-    ret[2].ival = 0;
+    ret[2].u.string.sval = dupstr(buf);
 
     ret[3].name = NULL;
     ret[3].type = C_END;
-    ret[3].sval = NULL;
-    ret[3].ival = 0;
 
     return ret;
 }
@@ -178,12 +173,13 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->h = atoi(cfg[1].sval);
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->h = atoi(cfg[1].u.string.sval);
 
     /* Allow 'a-b' for a range, otherwise assume a single number. */
-    if (sscanf(cfg[2].sval, "%d-%d", &ret->minballs, &ret->maxballs) < 2)
-        ret->minballs = ret->maxballs = atoi(cfg[2].sval);
+    if (sscanf(cfg[2].u.string.sval, "%d-%d",
+               &ret->minballs, &ret->maxballs) < 2)
+        ret->minballs = ret->maxballs = atoi(cfg[2].u.string.sval);
 
     return ret;
 }
--- a/bridges.c
+++ b/bridges.c
@@ -742,44 +742,40 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "Difficulty";
     ret[2].type = C_CHOICES;
-    ret[2].sval = ":Easy:Medium:Hard";
-    ret[2].ival = params->difficulty;
+    ret[2].u.choices.choicenames = ":Easy:Medium:Hard";
+    ret[2].u.choices.selected = params->difficulty;
 
     ret[3].name = "Allow loops";
     ret[3].type = C_BOOLEAN;
-    ret[3].sval = NULL;
-    ret[3].ival = params->allowloops;
+    ret[3].u.boolean.bval = params->allowloops;
 
     ret[4].name = "Max. bridges per direction";
     ret[4].type = C_CHOICES;
-    ret[4].sval = ":1:2:3:4"; /* keep up-to-date with MAX_BRIDGES */
-    ret[4].ival = params->maxb - 1;
+    ret[4].u.choices.choicenames = ":1:2:3:4"; /* keep up-to-date with
+                                                * MAX_BRIDGES */
+    ret[4].u.choices.selected = params->maxb - 1;
 
     ret[5].name = "%age of island squares";
     ret[5].type = C_CHOICES;
-    ret[5].sval = ":5%:10%:15%:20%:25%:30%";
-    ret[5].ival = (params->islands / 5)-1;
+    ret[5].u.choices.choicenames = ":5%:10%:15%:20%:25%:30%";
+    ret[5].u.choices.selected = (params->islands / 5)-1;
 
     ret[6].name = "Expansion factor (%age)";
     ret[6].type = C_CHOICES;
-    ret[6].sval = ":0%:10%:20%:30%:40%:50%:60%:70%:80%:90%:100%";
-    ret[6].ival = params->expansion / 10;
+    ret[6].u.choices.choicenames = ":0%:10%:20%:30%:40%:50%:60%:70%:80%:90%:100%";
+    ret[6].u.choices.selected = params->expansion / 10;
 
     ret[7].name = NULL;
     ret[7].type = C_END;
-    ret[7].sval = NULL;
-    ret[7].ival = 0;
 
     return ret;
 }
@@ -788,13 +784,13 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w          = atoi(cfg[0].sval);
-    ret->h          = atoi(cfg[1].sval);
-    ret->difficulty = cfg[2].ival;
-    ret->allowloops = cfg[3].ival;
-    ret->maxb       = cfg[4].ival + 1;
-    ret->islands    = (cfg[5].ival + 1) * 5;
-    ret->expansion  = cfg[6].ival * 10;
+    ret->w          = atoi(cfg[0].u.string.sval);
+    ret->h          = atoi(cfg[1].u.string.sval);
+    ret->difficulty = cfg[2].u.choices.selected;
+    ret->allowloops = cfg[3].u.boolean.bval;
+    ret->maxb       = cfg[4].u.choices.selected + 1;
+    ret->islands    = (cfg[5].u.choices.selected + 1) * 5;
+    ret->expansion  = cfg[6].u.choices.selected * 10;
 
     return ret;
 }
--- a/cube.c
+++ b/cube.c
@@ -489,25 +489,21 @@
 
     ret[0].name = "Type of solid";
     ret[0].type = C_CHOICES;
-    ret[0].sval = ":Tetrahedron:Cube:Octahedron:Icosahedron";
-    ret[0].ival = params->solid;
+    ret[0].u.choices.choicenames = ":Tetrahedron:Cube:Octahedron:Icosahedron";
+    ret[0].u.choices.selected = params->solid;
 
     ret[1].name = "Width / top";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->d1);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "Height / bottom";
     ret[2].type = C_STRING;
     sprintf(buf, "%d", params->d2);
-    ret[2].sval = dupstr(buf);
-    ret[2].ival = 0;
+    ret[2].u.string.sval = dupstr(buf);
 
     ret[3].name = NULL;
     ret[3].type = C_END;
-    ret[3].sval = NULL;
-    ret[3].ival = 0;
 
     return ret;
 }
@@ -516,9 +512,9 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->solid = cfg[0].ival;
-    ret->d1 = atoi(cfg[1].sval);
-    ret->d2 = atoi(cfg[2].sval);
+    ret->solid = cfg[0].u.choices.selected;
+    ret->d1 = atoi(cfg[1].u.string.sval);
+    ret->d2 = atoi(cfg[2].u.string.sval);
 
     return ret;
 }
--- a/devel.but
+++ b/devel.but
@@ -555,16 +555,15 @@
 
 \c char *name;
 \c int type;
-\c char *sval;
-\c int ival;
+\c union { /* type-specific fields */ } u;
+\e         iiiiiiiiiiiiiiiiiiiiiiiiii
 
 \c{name} is an ASCII string giving the textual label for a GUI
 control. It is \e{not} expected to be dynamically allocated.
 
 \c{type} contains one of a small number of \c{enum} values defining
-what type of control is being described. The meaning of the \c{sval}
-and \c{ival} fields depends on the value in \c{type}. The valid
-values are:
+what type of control is being described. The usable member of the
+union field \c{u} depends on \c{type}. The valid type values are:
 
 \dt \c{C_STRING}
 
@@ -572,38 +571,64 @@
 input. The back end does not bother informing the front end that the
 box is numeric rather than textual; some front ends do have the
 capacity to take this into account, but I decided it wasn't worth
-the extra complexity in the interface.) For this type, \c{ival} is
-unused, and \c{sval} contains a dynamically allocated string
-representing the contents of the input box.
+the extra complexity in the interface.)
 
+\lcont{
+
+For controls of this type, \c{u.string} contains a single field
+
+\c char *sval;
+
+which stores a dynamically allocated string representing the contents
+of the input box.
+
+}
+
 \dt \c{C_BOOLEAN}
 
-\dd Describes a simple checkbox. For this type, \c{sval} is unused,
-and \c{ival} is \cw{TRUE} or \cw{FALSE}.
+\dd Describes a simple checkbox.
 
+\lcont{
+
+For controls of this type, \c{u.boolean} contains a single field
+
+\c int bval;
+
+which is either \cw{TRUE} or \cw{FALSE}.
+
+}
+
 \dt \c{C_CHOICES}
 
 \dd Describes a drop-down list presenting one of a small number of
-fixed choices. For this type, \c{sval} contains a list of strings
-describing the choices; the very first character of \c{sval} is used
-as a delimiter when processing the rest (so that the strings
-\cq{:zero:one:two}, \cq{!zero!one!two} and \cq{xzeroxonextwo} all
-define a three-element list containing \cq{zero}, \cq{one} and
-\cq{two}). \c{ival} contains the index of the currently selected
-element, numbering from zero (so that in the above example, 0 would
-mean \cq{zero} and 2 would mean \cq{two}).
+fixed choices.
 
 \lcont{
 
-Note that for this control type, \c{sval} is \e{not} dynamically
-allocated, whereas it was for \c{C_STRING}.
+For controls of this type, \c{u.choices} contains two fields:
 
+\c const char *choicenames;
+\c int selected;
+
+\c{choicenames} contains a list of strings describing the choices. The
+very first character of \c{sval} is used as a delimiter when
+processing the rest (so that the strings \cq{:zero:one:two},
+\cq{!zero!one!two} and \cq{xzeroxonextwo} all define a three-element
+list containing \cq{zero}, \cq{one} and \cq{two}).
+
+\c{selected} contains the index of the currently selected element,
+numbering from zero (so that in the above example, 0 would mean
+\cq{zero} and 2 would mean \cq{two}).
+
+Note that \c{u.choices.choicenames} is \e{not} dynamically allocated,
+unlike \c{u.string.sval}.
+
 }
 
 \dt \c{C_END}
 
-\dd Marks the end of the array of \c{config_item}s. All other fields
-are unused.
+\dd Marks the end of the array of \c{config_item}s. There is no
+associated member of the union field \c{u} for this type.
 
 The array returned from this function is expected to have filled in
 the initial values of all the controls according to the input
@@ -3737,10 +3762,10 @@
 
 \c void free_cfg(config_item *cfg);
 
-This function correctly frees an array of \c{config_item}s,
-including walking the array until it gets to the end and freeing
-precisely those \c{sval} fields which are expected to be dynamically
-allocated.
+This function correctly frees an array of \c{config_item}s, including
+walking the array until it gets to the end and freeing any subsidiary
+data items in each \c{u} sub-union which are expected to be
+dynamically allocated.
 
 (See \k{backend-configure} for details of the \c{config_item}
 structure.)
--- a/dominosa.c
+++ b/dominosa.c
@@ -169,18 +169,14 @@
     ret[0].name = "Maximum number on dominoes";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->n);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Ensure unique solution";
     ret[1].type = C_BOOLEAN;
-    ret[1].sval = NULL;
-    ret[1].ival = params->unique;
+    ret[1].u.boolean.bval = params->unique;
 
     ret[2].name = NULL;
     ret[2].type = C_END;
-    ret[2].sval = NULL;
-    ret[2].ival = 0;
 
     return ret;
 }
@@ -189,8 +185,8 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->n = atoi(cfg[0].sval);
-    ret->unique = cfg[1].ival;
+    ret->n = atoi(cfg[0].u.string.sval);
+    ret->unique = cfg[1].u.boolean.bval;
 
     return ret;
 }
--- a/emcc.c
+++ b/emcc.c
@@ -599,13 +599,14 @@
     for (i = 0; cfg[i].type != C_END; i++) {
 	switch (cfg[i].type) {
 	  case C_STRING:
-            js_dialog_string(i, cfg[i].name, cfg[i].sval);
+            js_dialog_string(i, cfg[i].name, cfg[i].u.string.sval);
 	    break;
 	  case C_BOOLEAN:
-            js_dialog_boolean(i, cfg[i].name, cfg[i].ival);
+            js_dialog_boolean(i, cfg[i].name, cfg[i].u.boolean.bval);
 	    break;
 	  case C_CHOICES:
-            js_dialog_choices(i, cfg[i].name, cfg[i].sval, cfg[i].ival);
+            js_dialog_choices(i, cfg[i].name, cfg[i].u.choices.choicenames,
+                              cfg[i].u.choices.selected);
 	    break;
 	}
     }
@@ -619,12 +620,29 @@
  */
 void dlg_return_sval(int index, const char *val)
 {
-    sfree(cfg[index].sval);
-    cfg[index].sval = dupstr(val);
+    config_item *i = cfg + index;
+    switch (i->type) {
+      case C_STRING:
+        sfree(i->u.string.sval);
+        i->u.string.sval = dupstr(val);
+        break;
+      default:
+        assert(0 && "Bad type for return_sval");
+    }
 }
 void dlg_return_ival(int index, int val)
 {
-    cfg[index].ival = val;
+    config_item *i = cfg + index;
+    switch (i->type) {
+      case C_BOOLEAN:
+        i->u.boolean.bval = val;
+        break;
+      case C_CHOICES:
+        i->u.choices.selected = val;
+        break;
+      default:
+        assert(0 && "Bad type for return_ival");
+    }
 }
 
 /*
--- a/fifteen.c
+++ b/fifteen.c
@@ -111,19 +111,15 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = NULL;
     ret[2].type = C_END;
-    ret[2].sval = NULL;
-    ret[2].ival = 0;
 
     return ret;
 }
@@ -132,8 +128,8 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->h = atoi(cfg[1].sval);
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->h = atoi(cfg[1].u.string.sval);
 
     return ret;
 }
--- a/filling.c
+++ b/filling.c
@@ -161,19 +161,15 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = NULL;
     ret[2].type = C_END;
-    ret[2].sval = NULL;
-    ret[2].ival = 0;
 
     return ret;
 }
@@ -182,8 +178,8 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->h = atoi(cfg[1].sval);
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->h = atoi(cfg[1].u.string.sval);
 
     return ret;
 }
--- a/flip.c
+++ b/flip.c
@@ -149,24 +149,20 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "Shape type";
     ret[2].type = C_CHOICES;
-    ret[2].sval = ":Crosses:Random";
-    ret[2].ival = params->matrix_type;
+    ret[2].u.choices.choicenames = ":Crosses:Random";
+    ret[2].u.choices.selected = params->matrix_type;
 
     ret[3].name = NULL;
     ret[3].type = C_END;
-    ret[3].sval = NULL;
-    ret[3].ival = 0;
 
     return ret;
 }
@@ -175,9 +171,9 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->h = atoi(cfg[1].sval);
-    ret->matrix_type = cfg[2].ival;
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->h = atoi(cfg[1].u.string.sval);
+    ret->matrix_type = cfg[2].u.choices.selected;
 
     return ret;
 }
--- a/flood.c
+++ b/flood.c
@@ -170,31 +170,25 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "Colours";
     ret[2].type = C_STRING;
     sprintf(buf, "%d", params->colours);
-    ret[2].sval = dupstr(buf);
-    ret[2].ival = 0;
+    ret[2].u.string.sval = dupstr(buf);
 
     ret[3].name = "Extra moves permitted";
     ret[3].type = C_STRING;
     sprintf(buf, "%d", params->leniency);
-    ret[3].sval = dupstr(buf);
-    ret[3].ival = 0;
+    ret[3].u.string.sval = dupstr(buf);
 
     ret[4].name = NULL;
     ret[4].type = C_END;
-    ret[4].sval = NULL;
-    ret[4].ival = 0;
 
     return ret;
 }
@@ -203,10 +197,10 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->h = atoi(cfg[1].sval);
-    ret->colours = atoi(cfg[2].sval);
-    ret->leniency = atoi(cfg[3].sval);
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->h = atoi(cfg[1].u.string.sval);
+    ret->colours = atoi(cfg[2].u.string.sval);
+    ret->leniency = atoi(cfg[3].u.string.sval);
 
     return ret;
 }
--- a/galaxies.c
+++ b/galaxies.c
@@ -248,24 +248,20 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "Difficulty";
     ret[2].type = C_CHOICES;
-    ret[2].sval = DIFFCONFIG;
-    ret[2].ival = params->diff;
+    ret[2].u.choices.choicenames = DIFFCONFIG;
+    ret[2].u.choices.selected = params->diff;
 
     ret[3].name = NULL;
     ret[3].type = C_END;
-    ret[3].sval = NULL;
-    ret[3].ival = 0;
 
     return ret;
 }
@@ -274,9 +270,9 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->h = atoi(cfg[1].sval);
-    ret->diff = cfg[2].ival;
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->h = atoi(cfg[1].u.string.sval);
+    ret->diff = cfg[2].u.choices.selected;
 
     return ret;
 }
--- a/gtk.c
+++ b/gtk.c
@@ -1641,8 +1641,9 @@
 {
     config_item *i = (config_item *)data;
 
-    sfree(i->sval);
-    i->sval = dupstr(gtk_entry_get_text(GTK_ENTRY(ed)));
+    assert(i->type == C_STRING);
+    sfree(i->u.string.sval);
+    i->u.string.sval = dupstr(gtk_entry_get_text(GTK_ENTRY(ed)));
 }
 
 static void button_toggled(GtkToggleButton *tb, gpointer data)
@@ -1649,7 +1650,8 @@
 {
     config_item *i = (config_item *)data;
 
-    i->ival = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tb));
+    assert(i->type == C_BOOLEAN);
+    i->u.boolean.bval = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tb));
 }
 
 static void droplist_sel(GtkComboBox *combo, gpointer data)
@@ -1656,7 +1658,8 @@
 {
     config_item *i = (config_item *)data;
 
-    i->ival = gtk_combo_box_get_active(combo);
+    assert(i->type == C_CHOICES);
+    i->u.choices.selected = gtk_combo_box_get_active(combo);
 }
 
 static int get_config(frontend *fe, int which)
@@ -1751,7 +1754,7 @@
 			     GTK_EXPAND | GTK_SHRINK | GTK_FILL,
 			     3, 3);
 #endif
-	    gtk_entry_set_text(GTK_ENTRY(w), i->sval);
+	    gtk_entry_set_text(GTK_ENTRY(w), i->u.string.sval);
 	    g_signal_connect(G_OBJECT(w), "changed",
                              G_CALLBACK(editbox_changed), i);
 	    g_signal_connect(G_OBJECT(w), "key_press_event",
@@ -1776,7 +1779,8 @@
 			     GTK_EXPAND | GTK_SHRINK | GTK_FILL,
 			     3, 3);
 #endif
-	    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), i->ival);
+	    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w),
+                                         i->u.boolean.bval);
 	    gtk_widget_show(w);
 	    break;
 
@@ -1799,7 +1803,8 @@
 
             {
 		int c;
-		char *p, *q, *name;
+		const char *p, *q;
+                char *name;
                 GtkListStore *model;
 		GtkCellRenderer *cr;
                 GtkTreeIter iter;
@@ -1806,8 +1811,8 @@
 
                 model = gtk_list_store_new(1, G_TYPE_STRING);
 
-		c = *i->sval;
-		p = i->sval+1;
+		c = *i->u.choices.choicenames;
+		p = i->u.choices.choicenames+1;
 
 		while (*p) {
 		    q = p;
@@ -1828,7 +1833,8 @@
 
                 w = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model));
 
-		gtk_combo_box_set_active(GTK_COMBO_BOX(w), i->ival);
+		gtk_combo_box_set_active(GTK_COMBO_BOX(w),
+                                         i->u.choices.selected);
 
 		cr = gtk_cell_renderer_text_new();
 		gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(w), cr, TRUE);
--- a/guess.c
+++ b/guess.c
@@ -166,35 +166,28 @@
     ret[0].name = "Colours";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->ncolours);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Pegs per guess";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->npegs);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "Guesses";
     ret[2].type = C_STRING;
     sprintf(buf, "%d", params->nguesses);
-    ret[2].sval = dupstr(buf);
-    ret[2].ival = 0;
+    ret[2].u.string.sval = dupstr(buf);
 
     ret[3].name = "Allow blanks";
     ret[3].type = C_BOOLEAN;
-    ret[3].sval = NULL;
-    ret[3].ival = params->allow_blank;
+    ret[3].u.boolean.bval = params->allow_blank;
 
     ret[4].name = "Allow duplicates";
     ret[4].type = C_BOOLEAN;
-    ret[4].sval = NULL;
-    ret[4].ival = params->allow_multiple;
+    ret[4].u.boolean.bval = params->allow_multiple;
 
     ret[5].name = NULL;
     ret[5].type = C_END;
-    ret[5].sval = NULL;
-    ret[5].ival = 0;
 
     return ret;
 }
@@ -203,12 +196,12 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->ncolours = atoi(cfg[0].sval);
-    ret->npegs = atoi(cfg[1].sval);
-    ret->nguesses = atoi(cfg[2].sval);
+    ret->ncolours = atoi(cfg[0].u.string.sval);
+    ret->npegs = atoi(cfg[1].u.string.sval);
+    ret->nguesses = atoi(cfg[2].u.string.sval);
 
-    ret->allow_blank = cfg[3].ival;
-    ret->allow_multiple = cfg[4].ival;
+    ret->allow_blank = cfg[3].u.boolean.bval;
+    ret->allow_multiple = cfg[4].u.boolean.bval;
 
     return ret;
 }
--- a/inertia.c
+++ b/inertia.c
@@ -168,19 +168,15 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = NULL;
     ret[2].type = C_END;
-    ret[2].sval = NULL;
-    ret[2].ival = 0;
 
     return ret;
 }
@@ -189,8 +185,8 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->h = atoi(cfg[1].sval);
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->h = atoi(cfg[1].u.string.sval);
 
     return ret;
 }
--- a/keen.c
+++ b/keen.c
@@ -183,23 +183,19 @@
     ret[0].name = "Grid size";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Difficulty";
     ret[1].type = C_CHOICES;
-    ret[1].sval = DIFFCONFIG;
-    ret[1].ival = params->diff;
+    ret[1].u.choices.choicenames = DIFFCONFIG;
+    ret[1].u.choices.selected = params->diff;
 
     ret[2].name = "Multiplication only";
     ret[2].type = C_BOOLEAN;
-    ret[2].sval = NULL;
-    ret[2].ival = params->multiplication_only;
+    ret[2].u.boolean.bval = params->multiplication_only;
 
     ret[3].name = NULL;
     ret[3].type = C_END;
-    ret[3].sval = NULL;
-    ret[3].ival = 0;
 
     return ret;
 }
@@ -208,9 +204,9 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->diff = cfg[1].ival;
-    ret->multiplication_only = cfg[2].ival;
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->diff = cfg[1].u.choices.selected;
+    ret->multiplication_only = cfg[2].u.boolean.bval;
 
     return ret;
 }
--- a/lightup.c
+++ b/lightup.c
@@ -299,37 +299,32 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "%age of black squares";
     ret[2].type = C_STRING;
     sprintf(buf, "%d", params->blackpc);
-    ret[2].sval = dupstr(buf);
-    ret[2].ival = 0;
+    ret[2].u.string.sval = dupstr(buf);
 
     ret[3].name = "Symmetry";
     ret[3].type = C_CHOICES;
-    ret[3].sval = ":None"
+    ret[3].u.choices.choicenames = ":None"
                   ":2-way mirror:2-way rotational"
                   ":4-way mirror:4-way rotational";
-    ret[3].ival = params->symm;
+    ret[3].u.choices.selected = params->symm;
 
     ret[4].name = "Difficulty";
     ret[4].type = C_CHOICES;
-    ret[4].sval = ":Easy:Tricky:Hard";
-    ret[4].ival = params->difficulty;
+    ret[4].u.choices.choicenames = ":Easy:Tricky:Hard";
+    ret[4].u.choices.selected = params->difficulty;
 
     ret[5].name = NULL;
     ret[5].type = C_END;
-    ret[5].sval = NULL;
-    ret[5].ival = 0;
 
     return ret;
 }
@@ -338,11 +333,11 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w =       atoi(cfg[0].sval);
-    ret->h =       atoi(cfg[1].sval);
-    ret->blackpc = atoi(cfg[2].sval);
-    ret->symm =    cfg[3].ival;
-    ret->difficulty = cfg[4].ival;
+    ret->w =       atoi(cfg[0].u.string.sval);
+    ret->h =       atoi(cfg[1].u.string.sval);
+    ret->blackpc = atoi(cfg[2].u.string.sval);
+    ret->symm =    cfg[3].u.choices.selected;
+    ret->difficulty = cfg[4].u.choices.selected;
 
     return ret;
 }
--- a/loopy.c
+++ b/loopy.c
@@ -640,29 +640,25 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "Grid type";
     ret[2].type = C_CHOICES;
-    ret[2].sval = GRID_CONFIGS;
-    ret[2].ival = params->type;
+    ret[2].u.choices.choicenames = GRID_CONFIGS;
+    ret[2].u.choices.selected = params->type;
 
     ret[3].name = "Difficulty";
     ret[3].type = C_CHOICES;
-    ret[3].sval = DIFFCONFIG;
-    ret[3].ival = params->diff;
+    ret[3].u.choices.choicenames = DIFFCONFIG;
+    ret[3].u.choices.selected = params->diff;
 
     ret[4].name = NULL;
     ret[4].type = C_END;
-    ret[4].sval = NULL;
-    ret[4].ival = 0;
 
     return ret;
 }
@@ -671,10 +667,10 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->h = atoi(cfg[1].sval);
-    ret->type = cfg[2].ival;
-    ret->diff = cfg[3].ival;
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->h = atoi(cfg[1].u.string.sval);
+    ret->type = cfg[2].u.choices.selected;
+    ret->diff = cfg[3].u.choices.selected;
 
     return ret;
 }
--- a/magnets.c
+++ b/magnets.c
@@ -193,29 +193,24 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "Difficulty";
     ret[2].type = C_CHOICES;
-    ret[2].sval = DIFFCONFIG;
-    ret[2].ival = params->diff;
+    ret[2].u.choices.choicenames = DIFFCONFIG;
+    ret[2].u.choices.selected = params->diff;
 
     ret[3].name = "Strip clues";
     ret[3].type = C_BOOLEAN;
-    ret[3].sval = NULL;
-    ret[3].ival = params->stripclues;
+    ret[3].u.boolean.bval = params->stripclues;
 
     ret[4].name = NULL;
     ret[4].type = C_END;
-    ret[4].sval = NULL;
-    ret[4].ival = 0;
 
     return ret;
 }
@@ -224,10 +219,10 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->h = atoi(cfg[1].sval);
-    ret->diff = cfg[2].ival;
-    ret->stripclues = cfg[3].ival;
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->h = atoi(cfg[1].u.string.sval);
+    ret->diff = cfg[2].u.choices.selected;
+    ret->stripclues = cfg[3].u.boolean.bval;
 
     return ret;
 }
--- a/map.c
+++ b/map.c
@@ -213,30 +213,25 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "Regions";
     ret[2].type = C_STRING;
     sprintf(buf, "%d", params->n);
-    ret[2].sval = dupstr(buf);
-    ret[2].ival = 0;
+    ret[2].u.string.sval = dupstr(buf);
 
     ret[3].name = "Difficulty";
     ret[3].type = C_CHOICES;
-    ret[3].sval = DIFFCONFIG;
-    ret[3].ival = params->diff;
+    ret[3].u.choices.choicenames = DIFFCONFIG;
+    ret[3].u.choices.selected = params->diff;
 
     ret[4].name = NULL;
     ret[4].type = C_END;
-    ret[4].sval = NULL;
-    ret[4].ival = 0;
 
     return ret;
 }
@@ -245,10 +240,10 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->h = atoi(cfg[1].sval);
-    ret->n = atoi(cfg[2].sval);
-    ret->diff = cfg[3].ival;
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->h = atoi(cfg[1].u.string.sval);
+    ret->n = atoi(cfg[2].u.string.sval);
+    ret->diff = cfg[3].u.choices.selected;
 
     return ret;
 }
--- a/midend.c
+++ b/midend.c
@@ -1360,7 +1360,6 @@
             ret[0].name = "Game random seed";
         else
             ret[0].name = "Game ID";
-	ret[0].ival = 0;
         /*
          * For CFG_DESC the text going in here will be a string
          * encoding of the restricted parameters, plus a colon,
@@ -1379,13 +1378,12 @@
             rest = me->seedstr ? me->seedstr : "";
             sep = '#';
         }
-        ret[0].sval = snewn(strlen(parstr) + strlen(rest) + 2, char);
-        sprintf(ret[0].sval, "%s%c%s", parstr, sep, rest);
+        ret[0].u.string.sval = snewn(strlen(parstr) + strlen(rest) + 2, char);
+        sprintf(ret[0].u.string.sval, "%s%c%s", parstr, sep, rest);
         sfree(parstr);
 
 	ret[1].type = C_END;
-	ret[1].name = ret[1].sval = NULL;
-	ret[1].ival = 0;
+	ret[1].name = NULL;
 
 	return ret;
     }
@@ -1620,7 +1618,7 @@
 
       case CFG_SEED:
       case CFG_DESC:
-        error = midend_game_id_int(me, cfg[0].sval,
+        error = midend_game_id_int(me, cfg[0].u.string.sval,
                                    (which == CFG_SEED ? DEF_SEED : DEF_DESC));
 	if (error)
 	    return error;
--- a/mines.c
+++ b/mines.c
@@ -203,30 +203,24 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "Mines";
     ret[2].type = C_STRING;
     sprintf(buf, "%d", params->n);
-    ret[2].sval = dupstr(buf);
-    ret[2].ival = 0;
+    ret[2].u.string.sval = dupstr(buf);
 
     ret[3].name = "Ensure solubility";
     ret[3].type = C_BOOLEAN;
-    ret[3].sval = NULL;
-    ret[3].ival = params->unique;
+    ret[3].u.boolean.bval = params->unique;
 
     ret[4].name = NULL;
     ret[4].type = C_END;
-    ret[4].sval = NULL;
-    ret[4].ival = 0;
 
     return ret;
 }
@@ -235,12 +229,12 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->h = atoi(cfg[1].sval);
-    ret->n = atoi(cfg[2].sval);
-    if (strchr(cfg[2].sval, '%'))
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->h = atoi(cfg[1].u.string.sval);
+    ret->n = atoi(cfg[2].u.string.sval);
+    if (strchr(cfg[2].u.string.sval, '%'))
 	ret->n = ret->n * (ret->w * ret->h) / 100;
-    ret->unique = cfg[3].ival;
+    ret->unique = cfg[3].u.boolean.bval;
 
     return ret;
 }
--- a/misc.c
+++ b/misc.c
@@ -17,7 +17,7 @@
 
     for (i = cfg; i->type != C_END; i++)
 	if (i->type == C_STRING)
-	    sfree(i->sval);
+	    sfree(i->u.string.sval);
     sfree(cfg);
 }
 
--- a/nestedvm.c
+++ b/nestedvm.c
@@ -273,19 +273,22 @@
 void jcallback_config_set_string(int item_ptr, int char_ptr) {
     config_item *i = (config_item *)item_ptr;
     char* newval = (char*) char_ptr;
-    sfree(i->sval);
-    i->sval = dupstr(newval);
+    assert(i->type == C_STRING);
+    sfree(i->u.string.sval);
+    i->u.string.sval = dupstr(newval);
     free(newval);
 }
 
 void jcallback_config_set_boolean(int item_ptr, int selected) {
     config_item *i = (config_item *)item_ptr;
-    i->ival = selected != 0 ? TRUE : FALSE;
+    assert(i->type == C_BOOLEAN);
+    i->u.boolean.bval = selected != 0 ? TRUE : FALSE;
 }
 
 void jcallback_config_set_choice(int item_ptr, int selected) {
     config_item *i = (config_item *)item_ptr;
-    i->ival = selected;
+    assert(i->type == C_CHOICES);
+    i->u.choices.selected = selected;
 }
 
 static int get_config(frontend *fe, int which)
@@ -298,7 +301,18 @@
     _call_java(10, (int)title, 0, 0);
     for (i = fe->cfg; i->type != C_END; i++) {
 	_call_java(5, (int)i, i->type, (int)i->name);
-	_call_java(11, (int)i->sval, i->ival, 0);
+        switch (i->type) {
+          case C_STRING:
+            _call_java(11, (int)i->u.string.sval, 0, 0);
+            break;
+          case C_BOOLEAN:
+            _call_java(11, 0, i->u.boolean.bval, 0);
+            break;
+          case C_CHOICES:
+            _call_java(11, (int)i->u.choices.choicenames,
+                       i->u.choices.selected, 0);
+            break;
+        }
     }
     _call_java(12,0,0,0);
     free_cfg(fe->cfg);
--- a/net.c
+++ b/net.c
@@ -267,35 +267,28 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->width);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->height);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "Walls wrap around";
     ret[2].type = C_BOOLEAN;
-    ret[2].sval = NULL;
-    ret[2].ival = params->wrapping;
+    ret[2].u.boolean.bval = params->wrapping;
 
     ret[3].name = "Barrier probability";
     ret[3].type = C_STRING;
     sprintf(buf, "%g", params->barrier_probability);
-    ret[3].sval = dupstr(buf);
-    ret[3].ival = 0;
+    ret[3].u.string.sval = dupstr(buf);
 
     ret[4].name = "Ensure unique solution";
     ret[4].type = C_BOOLEAN;
-    ret[4].sval = NULL;
-    ret[4].ival = params->unique;
+    ret[4].u.boolean.bval = params->unique;
 
     ret[5].name = NULL;
     ret[5].type = C_END;
-    ret[5].sval = NULL;
-    ret[5].ival = 0;
 
     return ret;
 }
@@ -304,11 +297,11 @@
 {
     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 = (float)atof(cfg[3].sval);
-    ret->unique = cfg[4].ival;
+    ret->width = atoi(cfg[0].u.string.sval);
+    ret->height = atoi(cfg[1].u.string.sval);
+    ret->wrapping = cfg[2].u.boolean.bval;
+    ret->barrier_probability = (float)atof(cfg[3].u.string.sval);
+    ret->unique = cfg[4].u.boolean.bval;
 
     return ret;
 }
--- a/netslide.c
+++ b/netslide.c
@@ -262,36 +262,29 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->width);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->height);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "Walls wrap around";
     ret[2].type = C_BOOLEAN;
-    ret[2].sval = NULL;
-    ret[2].ival = params->wrapping;
+    ret[2].u.boolean.bval = params->wrapping;
 
     ret[3].name = "Barrier probability";
     ret[3].type = C_STRING;
     sprintf(buf, "%g", params->barrier_probability);
-    ret[3].sval = dupstr(buf);
-    ret[3].ival = 0;
+    ret[3].u.string.sval = dupstr(buf);
 
     ret[4].name = "Number of shuffling moves";
     ret[4].type = C_STRING;
     sprintf(buf, "%d", params->movetarget);
-    ret[4].sval = dupstr(buf);
-    ret[4].ival = 0;
+    ret[4].u.string.sval = dupstr(buf);
 
     ret[5].name = NULL;
     ret[5].type = C_END;
-    ret[5].sval = NULL;
-    ret[5].ival = 0;
 
     return ret;
 }
@@ -300,11 +293,11 @@
 {
     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 = (float)atof(cfg[3].sval);
-    ret->movetarget = atoi(cfg[4].sval);
+    ret->width = atoi(cfg[0].u.string.sval);
+    ret->height = atoi(cfg[1].u.string.sval);
+    ret->wrapping = cfg[2].u.boolean.bval;
+    ret->barrier_probability = (float)atof(cfg[3].u.string.sval);
+    ret->movetarget = atoi(cfg[4].u.string.sval);
 
     return ret;
 }
--- a/osx.m
+++ b/osx.m
@@ -1103,7 +1103,8 @@
 	    [tf setEditable:YES];
 	    [tf setSelectable:YES];
 	    [tf setBordered:YES];
-	    [[tf cell] setTitle:[NSString stringWithUTF8String:i->sval]];
+	    [[tf cell] setTitle:[NSString
+                                    stringWithUTF8String:i->u.string.sval]];
 	    [tf sizeToFit];
 	    rect = [tf frame];
 	    /*
@@ -1132,7 +1133,7 @@
 	    [b setButtonType:NSSwitchButton];
 	    [b setTitle:[NSString stringWithUTF8String:i->name]];
 	    [b sizeToFit];
-	    [b setState:(i->ival ? NSOnState : NSOffState)];
+	    [b setState:(i->u.boolean.bval ? NSOnState : NSOffState)];
 	    rect = [b frame];
 	    if (totalw < rect.size.width + 1) totalw = rect.size.width + 1;
 	    if (thish < rect.size.height + 1) thish = rect.size.height + 1;
@@ -1161,12 +1162,14 @@
 	    pb = [[NSPopUpButton alloc] initWithFrame:tmprect pullsDown:NO];
 	    [pb setBezelStyle:NSRoundedBezelStyle];
 	    {
-		char c, *p;
+		char c;
+                const char *p;
 
-		p = i->sval;
+		p = i->u.choices.choicenames;
 		c = *p++;
 		while (*p) {
-		    char *q, *copy;
+		    const char *q;
+                    char *copy;
 
 		    q = p;
 		    while (*p && *p != c) p++;
@@ -1180,7 +1183,7 @@
 		    if (*p) p++;
 		}
 	    }
-	    [pb selectItemAtIndex:i->ival];
+	    [pb selectItemAtIndex:i->u.choices.selected];
 	    [pb sizeToFit];
 
 	    rect = [pb frame];
@@ -1303,17 +1306,18 @@
 	for (i = cfg; i->type != C_END; i++) {
 	    switch (i->type) {
 	      case C_STRING:
-		sfree(i->sval);
-		i->sval = dupstr([[[(id)cfg_controls[k+1] cell]
+		sfree(i->u.string.sval);
+		i->u.string.sval = dupstr([[[(id)cfg_controls[k+1] cell]
                                   title] UTF8String]);
 		k += 2;
 		break;
 	      case C_BOOLEAN:
-		i->ival = [(id)cfg_controls[k] state] == NSOnState;
+		i->u.boolean.bval = [(id)cfg_controls[k] state] == NSOnState;
 		k++;
 		break;
 	      case C_CHOICES:
-		i->ival = [(id)cfg_controls[k+1] indexOfSelectedItem];
+		i->u.choices.selected =
+                    [(id)cfg_controls[k+1] indexOfSelectedItem];
 		k += 2;
 		break;
 	    }
--- a/palisade.c
+++ b/palisade.c
@@ -119,11 +119,21 @@
 {
     config_item *ret = snewn(4, config_item);
 
-    CONFIG(0, "Width",       C_STRING, 0, string(20, "%d", params->w));
-    CONFIG(1, "Height",      C_STRING, 0, string(20, "%d", params->h));
-    CONFIG(2, "Region size", C_STRING, 0, string(20, "%d", params->k));
-    CONFIG(3, NULL,          C_END,    0, NULL);
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    ret[0].u.string.sval = string(20, "%d", params->w);
 
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    ret[1].u.string.sval = string(20, "%d", params->h);
+
+    ret[2].name = "Region size";
+    ret[2].type = C_STRING;
+    ret[2].u.string.sval = string(20, "%d", params->k);
+
+    ret[3].name = NULL;
+    ret[3].type = C_END;
+
     return ret;
 }
 
@@ -131,9 +141,9 @@
 {
     game_params *params = snew(game_params);
 
-    params->w = atoi(cfg[0].sval);
-    params->h = atoi(cfg[1].sval);
-    params->k = atoi(cfg[2].sval);
+    params->w = atoi(cfg[0].u.string.sval);
+    params->h = atoi(cfg[1].u.string.sval);
+    params->k = atoi(cfg[2].u.string.sval);
 
     return params;
 }
--- a/pattern.c
+++ b/pattern.c
@@ -148,19 +148,15 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = NULL;
     ret[2].type = C_END;
-    ret[2].sval = NULL;
-    ret[2].ival = 0;
 
     return ret;
 }
@@ -169,8 +165,8 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->h = atoi(cfg[1].sval);
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->h = atoi(cfg[1].u.string.sval);
 
     return ret;
 }
--- a/pearl.c
+++ b/pearl.c
@@ -234,29 +234,24 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "Difficulty";
     ret[2].type = C_CHOICES;
-    ret[2].sval = DIFFCONFIG;
-    ret[2].ival = params->difficulty;
+    ret[2].u.choices.choicenames = DIFFCONFIG;
+    ret[2].u.choices.selected = params->difficulty;
 
     ret[3].name = "Allow unsoluble";
     ret[3].type = C_BOOLEAN;
-    ret[3].sval = NULL;
-    ret[3].ival = params->nosolve;
+    ret[3].u.boolean.bval = params->nosolve;
 
     ret[4].name = NULL;
     ret[4].type = C_END;
-    ret[4].sval = NULL;
-    ret[4].ival = 0;
 
     return ret;
 }
@@ -265,10 +260,10 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->h = atoi(cfg[1].sval);
-    ret->difficulty = cfg[2].ival;
-    ret->nosolve = cfg[3].ival;
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->h = atoi(cfg[1].u.string.sval);
+    ret->difficulty = cfg[2].u.choices.selected;
+    ret->nosolve = cfg[3].u.boolean.bval;
 
     return ret;
 }
--- a/pegs.c
+++ b/pegs.c
@@ -149,24 +149,20 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "Board type";
     ret[2].type = C_CHOICES;
-    ret[2].sval = TYPECONFIG;
-    ret[2].ival = params->type;
+    ret[2].u.choices.choicenames = TYPECONFIG;
+    ret[2].u.choices.selected = params->type;
 
     ret[3].name = NULL;
     ret[3].type = C_END;
-    ret[3].sval = NULL;
-    ret[3].ival = 0;
 
     return ret;
 }
@@ -175,9 +171,9 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->h = atoi(cfg[1].sval);
-    ret->type = cfg[2].ival;
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->h = atoi(cfg[1].u.string.sval);
+    ret->type = cfg[2].u.choices.selected;
 
     return ret;
 }
--- a/puzzles.h
+++ b/puzzles.h
@@ -137,30 +137,36 @@
  */
 enum { C_STRING, C_CHOICES, C_BOOLEAN, C_END };
 struct config_item {
-    /*
-     * `name' is never dynamically allocated.
-     */
-    char *name;
-    /*
-     * `type' contains one of the above values.
-     */
+    /* Not dynamically allocated */
+    const char *name;
+    /* Value from the above C_* enum */
     int type;
-    /*
-     * For C_STRING, `sval' is always dynamically allocated and
-     * non-NULL. For C_BOOLEAN and C_END, `sval' is always NULL.
-     * For C_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 C_BOOLEAN, this is TRUE or FALSE. For C_CHOICES, it
-     * indicates the chosen index from the `sval' list. In the
-     * above example, 0==Foo, 1==Bar and 2==Baz.
-     */
-    int ival;
+    union {
+        struct { /* if type == C_STRING */
+            /* Always dynamically allocated and non-NULL */
+            char *sval;
+        } string;
+        struct { /* if type == C_CHOICES */
+            /*
+             * choicenames is non-NULL, not dynamically allocated, and
+             * contains a set of option strings separated by a
+             * delimiter. The delimiter is also the first character in
+             * the string, so for example ":Foo:Bar:Baz" gives three
+             * options `Foo', `Bar' and `Baz'.
+             */
+            const char *choicenames;
+            /*
+             * Indicates the chosen index from the options in
+             * choicenames. In the above example, 0==Foo, 1==Bar and
+             * 2==Baz.
+             */
+            int selected;
+        } choices;
+        struct {
+            /* just TRUE or FALSE */
+            int bval;
+        } boolean;
+    } u;
 };
 
 /*
--- a/range.c
+++ b/range.c
@@ -170,18 +170,14 @@
 
     ret[0].name = "Width";
     ret[0].type = C_STRING;
-    ret[0].sval = nfmtstr(10, "%d", params->w);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = nfmtstr(10, "%d", params->w);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
-    ret[1].sval = nfmtstr(10, "%d", params->h);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = nfmtstr(10, "%d", params->h);
 
     ret[2].name = NULL;
     ret[2].type = C_END;
-    ret[2].sval = NULL;
-    ret[2].ival = 0;
 
     return ret;
 }
@@ -189,8 +185,8 @@
 static game_params *custom_params(const config_item *configuration)
 {
     game_params *ret = snew(game_params);
-    ret->w = atoi(configuration[0].sval);
-    ret->h = atoi(configuration[1].sval);
+    ret->w = atoi(configuration[0].u.string.sval);
+    ret->h = atoi(configuration[1].u.string.sval);
     return ret;
 }
 
--- a/rect.c
+++ b/rect.c
@@ -180,30 +180,24 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "Expansion factor";
     ret[2].type = C_STRING;
     sprintf(buf, "%g", params->expandfactor);
-    ret[2].sval = dupstr(buf);
-    ret[2].ival = 0;
+    ret[2].u.string.sval = dupstr(buf);
 
     ret[3].name = "Ensure unique solution";
     ret[3].type = C_BOOLEAN;
-    ret[3].sval = NULL;
-    ret[3].ival = params->unique;
+    ret[3].u.boolean.bval = params->unique;
 
     ret[4].name = NULL;
     ret[4].type = C_END;
-    ret[4].sval = NULL;
-    ret[4].ival = 0;
 
     return ret;
 }
@@ -212,10 +206,10 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->h = atoi(cfg[1].sval);
-    ret->expandfactor = (float)atof(cfg[2].sval);
-    ret->unique = cfg[3].ival;
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->h = atoi(cfg[1].u.string.sval);
+    ret->expandfactor = (float)atof(cfg[2].u.string.sval);
+    ret->unique = cfg[3].u.boolean.bval;
 
     return ret;
 }
--- a/samegame.c
+++ b/samegame.c
@@ -241,35 +241,29 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "No. of colours";
     ret[2].type = C_STRING;
     sprintf(buf, "%d", params->ncols);
-    ret[2].sval = dupstr(buf);
-    ret[2].ival = 0;
+    ret[2].u.string.sval = dupstr(buf);
 
     ret[3].name = "Scoring system";
     ret[3].type = C_CHOICES;
-    ret[3].sval = ":(n-1)^2:(n-2)^2";
-    ret[3].ival = params->scoresub-1;
+    ret[3].u.choices.choicenames = ":(n-1)^2:(n-2)^2";
+    ret[3].u.choices.selected = params->scoresub-1;
 
     ret[4].name = "Ensure solubility";
     ret[4].type = C_BOOLEAN;
-    ret[4].sval = NULL;
-    ret[4].ival = params->soluble;
+    ret[4].u.boolean.bval = params->soluble;
 
     ret[5].name = NULL;
     ret[5].type = C_END;
-    ret[5].sval = NULL;
-    ret[5].ival = 0;
 
     return ret;
 }
@@ -278,11 +272,11 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->h = atoi(cfg[1].sval);
-    ret->ncols = atoi(cfg[2].sval);
-    ret->scoresub = cfg[3].ival + 1;
-    ret->soluble = cfg[4].ival;
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->h = atoi(cfg[1].u.string.sval);
+    ret->ncols = atoi(cfg[2].u.string.sval);
+    ret->scoresub = cfg[3].u.choices.selected + 1;
+    ret->soluble = cfg[4].u.boolean.bval;
 
     return ret;
 }
--- a/signpost.c
+++ b/signpost.c
@@ -387,24 +387,19 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "Start and end in corners";
     ret[2].type = C_BOOLEAN;
-    ret[2].sval = NULL;
-    ret[2].ival = params->force_corner_start;
+    ret[2].u.boolean.bval = params->force_corner_start;
 
     ret[3].name = NULL;
     ret[3].type = C_END;
-    ret[3].sval = NULL;
-    ret[3].ival = 0;
 
     return ret;
 }
@@ -413,9 +408,9 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->h = atoi(cfg[1].sval);
-    ret->force_corner_start = cfg[2].ival;
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->h = atoi(cfg[1].u.string.sval);
+    ret->force_corner_start = cfg[2].u.boolean.bval;
 
     return ret;
 }
--- a/singles.c
+++ b/singles.c
@@ -222,24 +222,20 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "Difficulty";
     ret[2].type = C_CHOICES;
-    ret[2].sval = DIFFCONFIG;
-    ret[2].ival = params->diff;
+    ret[2].u.choices.choicenames = DIFFCONFIG;
+    ret[2].u.choices.selected = params->diff;
 
     ret[3].name = NULL;
     ret[3].type = C_END;
-    ret[3].sval = NULL;
-    ret[3].ival = 0;
 
     return ret;
 }
@@ -248,9 +244,9 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->h = atoi(cfg[1].sval);
-    ret->diff = cfg[2].ival;
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->h = atoi(cfg[1].u.string.sval);
+    ret->diff = cfg[2].u.choices.selected;
 
     return ret;
 }
--- a/sixteen.c
+++ b/sixteen.c
@@ -140,25 +140,20 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "Number of shuffling moves";
     ret[2].type = C_STRING;
     sprintf(buf, "%d", params->movetarget);
-    ret[2].sval = dupstr(buf);
-    ret[2].ival = 0;
+    ret[2].u.string.sval = dupstr(buf);
 
     ret[3].name = NULL;
     ret[3].type = C_END;
-    ret[3].sval = NULL;
-    ret[3].ival = 0;
 
     return ret;
 }
@@ -167,9 +162,9 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->h = atoi(cfg[1].sval);
-    ret->movetarget = atoi(cfg[2].sval);
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->h = atoi(cfg[1].u.string.sval);
+    ret->movetarget = atoi(cfg[2].u.string.sval);
 
     return ret;
 }
--- a/slant.c
+++ b/slant.c
@@ -184,24 +184,20 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "Difficulty";
     ret[2].type = C_CHOICES;
-    ret[2].sval = DIFFCONFIG;
-    ret[2].ival = params->diff;
+    ret[2].u.choices.choicenames = DIFFCONFIG;
+    ret[2].u.choices.selected = params->diff;
 
     ret[3].name = NULL;
     ret[3].type = C_END;
-    ret[3].sval = NULL;
-    ret[3].ival = 0;
 
     return ret;
 }
@@ -210,9 +206,9 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->h = atoi(cfg[1].sval);
-    ret->diff = cfg[2].ival;
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->h = atoi(cfg[1].u.string.sval);
+    ret->diff = cfg[2].u.choices.selected;
 
     return ret;
 }
--- a/solo.c
+++ b/solo.c
@@ -445,46 +445,39 @@
     ret[0].name = "Columns of sub-blocks";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->c);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Rows of sub-blocks";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->r);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "\"X\" (require every number in each main diagonal)";
     ret[2].type = C_BOOLEAN;
-    ret[2].sval = NULL;
-    ret[2].ival = params->xtype;
+    ret[2].u.boolean.bval = params->xtype;
 
     ret[3].name = "Jigsaw (irregularly shaped sub-blocks)";
     ret[3].type = C_BOOLEAN;
-    ret[3].sval = NULL;
-    ret[3].ival = (params->r == 1);
+    ret[3].u.boolean.bval = (params->r == 1);
 
     ret[4].name = "Killer (digit sums)";
     ret[4].type = C_BOOLEAN;
-    ret[4].sval = NULL;
-    ret[4].ival = params->killer;
+    ret[4].u.boolean.bval = params->killer;
 
     ret[5].name = "Symmetry";
     ret[5].type = C_CHOICES;
-    ret[5].sval = ":None:2-way rotation:4-way rotation:2-way mirror:"
+    ret[5].u.choices.choicenames = ":None:2-way rotation:4-way rotation:2-way mirror:"
         "2-way diagonal mirror:4-way mirror:4-way diagonal mirror:"
         "8-way mirror";
-    ret[5].ival = params->symm;
+    ret[5].u.choices.selected = params->symm;
 
     ret[6].name = "Difficulty";
     ret[6].type = C_CHOICES;
-    ret[6].sval = ":Trivial:Basic:Intermediate:Advanced:Extreme:Unreasonable";
-    ret[6].ival = params->diff;
+    ret[6].u.choices.choicenames = ":Trivial:Basic:Intermediate:Advanced:Extreme:Unreasonable";
+    ret[6].u.choices.selected = params->diff;
 
     ret[7].name = NULL;
     ret[7].type = C_END;
-    ret[7].sval = NULL;
-    ret[7].ival = 0;
 
     return ret;
 }
@@ -493,16 +486,16 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->c = atoi(cfg[0].sval);
-    ret->r = atoi(cfg[1].sval);
-    ret->xtype = cfg[2].ival;
-    if (cfg[3].ival) {
+    ret->c = atoi(cfg[0].u.string.sval);
+    ret->r = atoi(cfg[1].u.string.sval);
+    ret->xtype = cfg[2].u.boolean.bval;
+    if (cfg[3].u.boolean.bval) {
 	ret->c *= ret->r;
 	ret->r = 1;
     }
-    ret->killer = cfg[4].ival;
-    ret->symm = cfg[5].ival;
-    ret->diff = cfg[6].ival;
+    ret->killer = cfg[4].u.boolean.bval;
+    ret->symm = cfg[5].u.choices.selected;
+    ret->diff = cfg[6].u.choices.selected;
     ret->kdiff = DIFF_KINTERSECT;
 
     return ret;
--- a/tents.c
+++ b/tents.c
@@ -371,24 +371,20 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "Difficulty";
     ret[2].type = C_CHOICES;
-    ret[2].sval = DIFFCONFIG;
-    ret[2].ival = params->diff;
+    ret[2].u.choices.choicenames = DIFFCONFIG;
+    ret[2].u.choices.selected = params->diff;
 
     ret[3].name = NULL;
     ret[3].type = C_END;
-    ret[3].sval = NULL;
-    ret[3].ival = 0;
 
     return ret;
 }
@@ -397,9 +393,9 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->h = atoi(cfg[1].sval);
-    ret->diff = cfg[2].ival;
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->h = atoi(cfg[1].u.string.sval);
+    ret->diff = cfg[2].u.choices.selected;
 
     return ret;
 }
--- a/towers.c
+++ b/towers.c
@@ -212,18 +212,15 @@
     ret[0].name = "Grid size";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Difficulty";
     ret[1].type = C_CHOICES;
-    ret[1].sval = DIFFCONFIG;
-    ret[1].ival = params->diff;
+    ret[1].u.choices.choicenames = DIFFCONFIG;
+    ret[1].u.choices.selected = params->diff;
 
     ret[2].name = NULL;
     ret[2].type = C_END;
-    ret[2].sval = NULL;
-    ret[2].ival = 0;
 
     return ret;
 }
@@ -232,8 +229,8 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->diff = cfg[1].ival;
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->diff = cfg[1].u.choices.selected;
 
     return ret;
 }
--- a/tracks.c
+++ b/tracks.c
@@ -148,28 +148,24 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "Difficulty";
     ret[2].type = C_CHOICES;
-    ret[2].sval = DIFFCONFIG;
-    ret[2].ival = params->diff;
+    ret[2].u.choices.choicenames = DIFFCONFIG;
+    ret[2].u.choices.selected = params->diff;
 
     ret[3].name = "Disallow consecutive 1 clues";
     ret[3].type = C_BOOLEAN;
-    ret[3].ival = params->single_ones;
+    ret[3].u.boolean.bval = params->single_ones;
 
     ret[4].name = NULL;
     ret[4].type = C_END;
-    ret[4].sval = NULL;
-    ret[4].ival = 0;
 
     return ret;
 }
@@ -178,10 +174,10 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->h = atoi(cfg[1].sval);
-    ret->diff = cfg[2].ival;
-    ret->single_ones = cfg[3].ival;
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->h = atoi(cfg[1].u.string.sval);
+    ret->diff = cfg[2].u.choices.selected;
+    ret->single_ones = cfg[3].u.boolean.bval;
 
     return ret;
 }
--- a/twiddle.c
+++ b/twiddle.c
@@ -156,41 +156,33 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "Rotating block size";
     ret[2].type = C_STRING;
     sprintf(buf, "%d", params->n);
-    ret[2].sval = dupstr(buf);
-    ret[2].ival = 0;
+    ret[2].u.string.sval = dupstr(buf);
 
     ret[3].name = "One number per row";
     ret[3].type = C_BOOLEAN;
-    ret[3].sval = NULL;
-    ret[3].ival = params->rowsonly;
+    ret[3].u.boolean.bval = params->rowsonly;
 
     ret[4].name = "Orientation matters";
     ret[4].type = C_BOOLEAN;
-    ret[4].sval = NULL;
-    ret[4].ival = params->orientable;
+    ret[4].u.boolean.bval = params->orientable;
 
     ret[5].name = "Number of shuffling moves";
     ret[5].type = C_STRING;
     sprintf(buf, "%d", params->movetarget);
-    ret[5].sval = dupstr(buf);
-    ret[5].ival = 0;
+    ret[5].u.string.sval = dupstr(buf);
 
     ret[6].name = NULL;
     ret[6].type = C_END;
-    ret[6].sval = NULL;
-    ret[6].ival = 0;
 
     return ret;
 }
@@ -199,12 +191,12 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->h = atoi(cfg[1].sval);
-    ret->n = atoi(cfg[2].sval);
-    ret->rowsonly = cfg[3].ival;
-    ret->orientable = cfg[4].ival;
-    ret->movetarget = atoi(cfg[5].sval);
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->h = atoi(cfg[1].u.string.sval);
+    ret->n = atoi(cfg[2].u.string.sval);
+    ret->rowsonly = cfg[3].u.boolean.bval;
+    ret->orientable = cfg[4].u.boolean.bval;
+    ret->movetarget = atoi(cfg[5].u.string.sval);
 
     return ret;
 }
--- a/undead.c
+++ b/undead.c
@@ -163,24 +163,20 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "Difficulty";
     ret[2].type = C_CHOICES;
-    ret[2].sval = DIFFCONFIG;
-    ret[2].ival = params->diff;
+    ret[2].u.choices.choicenames = DIFFCONFIG;
+    ret[2].u.choices.selected = params->diff;
 
     ret[3].name = NULL;
     ret[3].type = C_END;
-    ret[3].sval = NULL;
-    ret[3].ival = 0;
 
     return ret;
 }
@@ -189,9 +185,9 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->h = atoi(cfg[1].sval);
-    ret->diff = cfg[2].ival;
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->h = atoi(cfg[1].u.string.sval);
+    ret->diff = cfg[2].u.choices.selected;
     return ret;
 }
 
--- a/unequal.c
+++ b/unequal.c
@@ -218,24 +218,21 @@
 
     ret[0].name = "Mode";
     ret[0].type = C_CHOICES;
-    ret[0].sval = ":Unequal:Adjacent";
-    ret[0].ival = params->adjacent;
+    ret[0].u.choices.choicenames = ":Unequal:Adjacent";
+    ret[0].u.choices.selected = params->adjacent;
 
     ret[1].name = "Size (s*s)";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->order);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "Difficulty";
     ret[2].type = C_CHOICES;
-    ret[2].sval = DIFFCONFIG;
-    ret[2].ival = params->diff;
+    ret[2].u.choices.choicenames = DIFFCONFIG;
+    ret[2].u.choices.selected = params->diff;
 
     ret[3].name = NULL;
     ret[3].type = C_END;
-    ret[3].sval = NULL;
-    ret[3].ival = 0;
 
     return ret;
 }
@@ -244,9 +241,9 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->adjacent = cfg[0].ival;
-    ret->order = atoi(cfg[1].sval);
-    ret->diff = cfg[2].ival;
+    ret->adjacent = cfg[0].u.choices.selected;
+    ret->order = atoi(cfg[1].u.string.sval);
+    ret->diff = cfg[2].u.choices.selected;
 
     return ret;
 }
--- a/unfinished/group.c
+++ b/unfinished/group.c
@@ -212,23 +212,19 @@
     ret[0].name = "Grid size";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Difficulty";
     ret[1].type = C_CHOICES;
-    ret[1].sval = DIFFCONFIG;
-    ret[1].ival = params->diff;
+    ret[1].u.choices.choicenames = DIFFCONFIG;
+    ret[1].u.choices.selected = params->diff;
 
     ret[2].name = "Show identity";
     ret[2].type = C_BOOLEAN;
-    ret[2].sval = NULL;
-    ret[2].ival = params->id;
+    ret[2].u.boolean.bval = params->id;
 
     ret[3].name = NULL;
     ret[3].type = C_END;
-    ret[3].sval = NULL;
-    ret[3].ival = 0;
 
     return ret;
 }
@@ -237,9 +233,9 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->diff = cfg[1].ival;
-    ret->id = cfg[2].ival;
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->diff = cfg[1].u.choices.selected;
+    ret->id = cfg[2].u.boolean.bval;
 
     return ret;
 }
--- a/unfinished/slide.c
+++ b/unfinished/slide.c
@@ -244,25 +244,20 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "Solution length limit";
     ret[2].type = C_STRING;
     sprintf(buf, "%d", params->maxmoves);
-    ret[2].sval = dupstr(buf);
-    ret[2].ival = 0;
+    ret[2].u.string.sval = dupstr(buf);
 
     ret[3].name = NULL;
     ret[3].type = C_END;
-    ret[3].sval = NULL;
-    ret[3].ival = 0;
 
     return ret;
 }
@@ -271,9 +266,9 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->h = atoi(cfg[1].sval);
-    ret->maxmoves = atoi(cfg[2].sval);
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->h = atoi(cfg[1].u.string.sval);
+    ret->maxmoves = atoi(cfg[2].u.string.sval);
 
     return ret;
 }
--- a/unfinished/sokoban.c
+++ b/unfinished/sokoban.c
@@ -210,19 +210,15 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = NULL;
     ret[2].type = C_END;
-    ret[2].sval = NULL;
-    ret[2].ival = 0;
 
     return ret;
 }
@@ -231,8 +227,8 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w = atoi(cfg[0].sval);
-    ret->h = atoi(cfg[1].sval);
+    ret->w = atoi(cfg[0].u.string.sval);
+    ret->h = atoi(cfg[1].u.string.sval);
 
     return ret;
 }
--- a/unruly.c
+++ b/unruly.c
@@ -239,28 +239,24 @@
     ret[0].name = "Width";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->w2);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = "Height";
     ret[1].type = C_STRING;
     sprintf(buf, "%d", params->h2);
-    ret[1].sval = dupstr(buf);
-    ret[1].ival = 0;
+    ret[1].u.string.sval = dupstr(buf);
 
     ret[2].name = "Unique rows and columns";
     ret[2].type = C_BOOLEAN;
-    ret[2].ival = params->unique;
+    ret[2].u.boolean.bval = params->unique;
 
     ret[3].name = "Difficulty";
     ret[3].type = C_CHOICES;
-    ret[3].sval = DIFFCONFIG;
-    ret[3].ival = params->diff;
+    ret[3].u.choices.choicenames = DIFFCONFIG;
+    ret[3].u.choices.selected = params->diff;
 
     ret[4].name = NULL;
     ret[4].type = C_END;
-    ret[4].sval = NULL;
-    ret[4].ival = 0;
 
     return ret;
 }
@@ -269,10 +265,10 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->w2 = atoi(cfg[0].sval);
-    ret->h2 = atoi(cfg[1].sval);
-    ret->unique = cfg[2].ival;
-    ret->diff = cfg[3].ival;
+    ret->w2 = atoi(cfg[0].u.string.sval);
+    ret->h2 = atoi(cfg[1].u.string.sval);
+    ret->unique = cfg[2].u.boolean.bval;
+    ret->diff = cfg[3].u.choices.selected;
 
     return ret;
 }
--- a/untangle.c
+++ b/untangle.c
@@ -185,13 +185,10 @@
     ret[0].name = "Number of points";
     ret[0].type = C_STRING;
     sprintf(buf, "%d", params->n);
-    ret[0].sval = dupstr(buf);
-    ret[0].ival = 0;
+    ret[0].u.string.sval = dupstr(buf);
 
     ret[1].name = NULL;
     ret[1].type = C_END;
-    ret[1].sval = NULL;
-    ret[1].ival = 0;
 
     return ret;
 }
@@ -200,7 +197,7 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->n = atoi(cfg[0].sval);
+    ret->n = atoi(cfg[0].u.string.sval);
 
     return ret;
 }
--- a/windows.c
+++ b/windows.c
@@ -2057,52 +2057,43 @@
 
 	ret[i].name = "Number of puzzles to print";
 	ret[i].type = C_STRING;
-	ret[i].sval = dupstr("1");
-	ret[i].ival = 0;
+	ret[i].u.string.sval = dupstr("1");
 	i++;
 
 	ret[i].name = "Number of puzzles across the page";
 	ret[i].type = C_STRING;
-	ret[i].sval = dupstr("1");
-	ret[i].ival = 0;
+	ret[i].u.string.sval = dupstr("1");
 	i++;
 
 	ret[i].name = "Number of puzzles down the page";
 	ret[i].type = C_STRING;
-	ret[i].sval = dupstr("1");
-	ret[i].ival = 0;
+	ret[i].u.string.sval = dupstr("1");
 	i++;
 
 	ret[i].name = "Percentage of standard size";
 	ret[i].type = C_STRING;
-	ret[i].sval = dupstr("100.0");
-	ret[i].ival = 0;
+	ret[i].u.string.sval = dupstr("100.0");
 	i++;
 
 	ret[i].name = "Include currently shown puzzle";
 	ret[i].type = C_BOOLEAN;
-	ret[i].sval = NULL;
-	ret[i].ival = TRUE;
+	ret[i].u.boolean.bval = TRUE;
 	i++;
 
 	ret[i].name = "Print solutions";
 	ret[i].type = C_BOOLEAN;
-	ret[i].sval = NULL;
-	ret[i].ival = FALSE;
+	ret[i].u.boolean.bval = FALSE;
 	i++;
 
 	if (fe->game->can_print_in_colour) {
 	    ret[i].name = "Print in colour";
 	    ret[i].type = C_BOOLEAN;
-	    ret[i].sval = NULL;
-	    ret[i].ival = FALSE;
+	    ret[i].u.boolean.bval = FALSE;
 	    i++;
 	}
 
 	ret[i].name = NULL;
 	ret[i].type = C_END;
-	ret[i].sval = NULL;
-	ret[i].ival = 0;
 	i++;
 
 	return ret;
@@ -2117,17 +2108,18 @@
     if (which < CFG_FRONTEND_SPECIFIC) {
 	return midend_set_config(fe->me, which, cfg);
     } else if (which == CFG_PRINT) {
-	if ((fe->printcount = atoi(cfg[0].sval)) <= 0)
+	if ((fe->printcount = atoi(cfg[0].u.string.sval)) <= 0)
 	    return "Number of puzzles to print should be at least one";
-	if ((fe->printw = atoi(cfg[1].sval)) <= 0)
+	if ((fe->printw = atoi(cfg[1].u.string.sval)) <= 0)
 	    return "Number of puzzles across the page should be at least one";
-	if ((fe->printh = atoi(cfg[2].sval)) <= 0)
+	if ((fe->printh = atoi(cfg[2].u.string.sval)) <= 0)
 	    return "Number of puzzles down the page should be at least one";
-	if ((fe->printscale = (float)atof(cfg[3].sval)) <= 0)
+	if ((fe->printscale = (float)atof(cfg[3].u.string.sval)) <= 0)
 	    return "Print size should be positive";
-	fe->printcurr = cfg[4].ival;
-	fe->printsolns = cfg[5].ival;
-	fe->printcolour = fe->game->can_print_in_colour && cfg[6].ival;
+	fe->printcurr = cfg[4].u.boolean.bval;
+	fe->printsolns = cfg[5].u.boolean.bval;
+	fe->printcolour = fe->game->can_print_in_colour &&
+            cfg[6].u.boolean.bval;
 	return NULL;
     } else {
 	assert(!"We should never get here");
@@ -2191,7 +2183,7 @@
 	    mkctrl(fe, col2l, col2r, y, y + 12,
 		   TEXT("EDIT"), WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL,
 		   0, "", (j->ctlid = id++));
-	    SetDlgItemTextA(fe->cfgbox, j->ctlid, i->sval);
+	    SetDlgItemTextA(fe->cfgbox, j->ctlid, i->u.string.sval);
 	    break;
 
 	  case C_BOOLEAN:
@@ -2201,7 +2193,7 @@
 	    mkctrl(fe, col1l, col2r, y + 1, y + 11, TEXT("BUTTON"),
 		   BS_NOTIFY | BS_AUTOCHECKBOX | WS_TABSTOP,
 		   0, i->name, (j->ctlid = id++));
-	    CheckDlgButton(fe->cfgbox, j->ctlid, (i->ival != 0));
+	    CheckDlgButton(fe->cfgbox, j->ctlid, (i->u.boolean.bval != 0));
 	    break;
 
 	  case C_CHOICES:
@@ -2215,9 +2207,11 @@
 			 CBS_DROPDOWNLIST | CBS_HASSTRINGS,
 			 0, "", (j->ctlid = id++));
 	    {
-		char c, *p, *q, *str;
+		char c;
+                const char *p, *q;
+                char *str;
 
-		p = i->sval;
+		p = i->u.choices.choicenames;
 		c = *p++;
 		while (*p) {
 		    q = p;
@@ -2236,7 +2230,7 @@
 		    p = q;
 		}
 	    }
-	    SendMessage(ctl, CB_SETCURSEL, i->ival, 0);
+	    SendMessage(ctl, CB_SETCURSEL, i->u.choices.selected, 0);
 	    break;
 	}
 
@@ -2324,16 +2318,16 @@
 	    GetDlgItemText(fe->cfgbox, j->ctlid, buffer, lenof(buffer));
 #endif
 	    buffer[lenof(buffer)-1] = '\0';
-	    sfree(i->sval);
-	    i->sval = dupstr(buffer);
+	    sfree(i->u.string.sval);
+	    i->u.string.sval = dupstr(buffer);
 	} else if (i->type == C_BOOLEAN && 
 		   (HIWORD(wParam) == BN_CLICKED ||
 		    HIWORD(wParam) == BN_DBLCLK)) {
-	    i->ival = IsDlgButtonChecked(fe->cfgbox, j->ctlid);
+	    i->u.boolean.bval = IsDlgButtonChecked(fe->cfgbox, j->ctlid);
 	} else if (i->type == C_CHOICES &&
 		   HIWORD(wParam) == CBN_SELCHANGE) {
-	    i->ival = SendDlgItemMessage(fe->cfgbox, j->ctlid,
-					 CB_GETCURSEL, 0, 0);
+	    i->u.choices.selected = SendDlgItemMessage(fe->cfgbox, j->ctlid,
+                                                       CB_GETCURSEL, 0, 0);
 	}
 
 	return 0;
@@ -2683,7 +2677,7 @@
 	    ctl = mkctrl(fe, col2l, col2r, y, y+height*3/2,
 			 "EDIT", WS_TABSTOP | ES_AUTOHSCROLL,
 			 WS_EX_CLIENTEDGE, "", (j->ctlid = id++));
-	    SetWindowText(ctl, i->sval);
+	    SetWindowText(ctl, i->u.string.sval);
 	    y += height*3/2;
 	    break;
 
@@ -2694,7 +2688,7 @@
 	    mkctrl(fe, col1l, col2r, y, y+height, "BUTTON",
 		   BS_NOTIFY | BS_AUTOCHECKBOX | WS_TABSTOP,
 		   0, i->name, (j->ctlid = id++));
-	    CheckDlgButton(fe->cfgbox, j->ctlid, (i->ival != 0));
+	    CheckDlgButton(fe->cfgbox, j->ctlid, (i->u.boolean.bval != 0));
 	    y += height;
 	    break;
 
@@ -2709,10 +2703,12 @@
 			 CBS_DROPDOWNLIST | CBS_HASSTRINGS,
 			 WS_EX_CLIENTEDGE, "", (j->ctlid = id++));
 	    {
-		char c, *p, *q, *str;
+		char c;
+                const char *p, *q;
+                char *str;
 
 		SendMessage(ctl, CB_RESETCONTENT, 0, 0);
-		p = i->sval;
+		p = i->u.choices.choicenames;
 		c = *p++;
 		while (*p) {
 		    q = p;
@@ -2727,7 +2723,7 @@
 		}
 	    }
 
-	    SendMessage(ctl, CB_SETCURSEL, i->ival, 0);
+	    SendMessage(ctl, CB_SETCURSEL, i->u.choices.selected, 0);
 
 	    y += height*3/2;
 	    break;