shithub: puzzles

Download patch

ref: 3d8e7585b7a9d215e4b40d9f83f7fa5a0fd79b43
parent: 03e486268314c77a0180c7c2c4fbe2bb74c1a553
author: Simon Tatham <anakin@pobox.com>
date: Wed Apr 28 08:07:15 EDT 2004

Add a menu bar, in both Windows and GTK. In particular, game modules
are now expected to provide a list of `presets' (game_params plus a
name) which are selectable from the menu. This means I can play
both Octahedron and Cube without recompiling in between :-)
While I'm here, also enabled a Cygwin makefile, which Just Worked.

[originally from svn r4158]

--- a/Recipe
+++ b/Recipe
@@ -10,6 +10,7 @@
 
 !makefile gtk Makefile
 !makefile vc Makefile.vc
+!makefile cygwin Makefile.cyg
 
 WINDOWS  = windows user32.lib gdi32.lib
 COMMON   = midend malloc
--- a/cube.c
+++ b/cube.c
@@ -21,6 +21,7 @@
     int faces[MAXFACES * MAXORDER];    /* order*nfaces point indices */
     float normals[MAXFACES * 3];       /* 3*npoints vector components */
     float shear;                       /* isometric shear for nice drawing */
+    float border;                      /* border required around arena */
 };
 
 static const struct solid tetrahedron = {
@@ -41,7 +42,7 @@
         0.816496580928, -0.471404520791, 0.333333333334,
         0.0, 0.0, -1.0,
     },
-    0.0
+    0.0, 0.3
 };
 
 static const struct solid cube = {
@@ -57,7 +58,7 @@
     {
         -1,0,0, 0,0,+1, +1,0,0, 0,0,-1, 0,-1,0, 0,+1,0
     },
-    0.3
+    0.3, 0.5
 };
 
 static const struct solid octahedron = {
@@ -84,7 +85,7 @@
         0.816496580928, -0.471404520791, -0.333333333334,
         0.816496580928, 0.471404520791, 0.333333333334,
     },
-    0.0
+    0.0, 0.5
 };
 
 static const struct solid icosahedron = {
@@ -132,7 +133,7 @@
         -0.57735026919, -0.333333333334, -0.745355992501,
         0.57735026919, -0.333333333334, -0.745355992501,
     },
-    0.0
+    0.0, 0.8
 };
 
 enum {
@@ -216,11 +217,58 @@
     return ret;
 }
 
+int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret = snew(game_params);
+    char *str;
+
+    switch (i) {
+      case 0:
+        str = "Cube";
+        ret->solid = CUBE;
+        ret->d1 = 4;
+        ret->d2 = 4;
+        break;
+      case 1:
+        str = "Tetrahedron";
+        ret->solid = TETRAHEDRON;
+        ret->d1 = 2;
+        ret->d2 = 1;
+        break;
+      case 2:
+        str = "Octahedron";
+        ret->solid = OCTAHEDRON;
+        ret->d1 = 2;
+        ret->d2 = 2;
+        break;
+      case 3:
+        str = "Icosahedron";
+        ret->solid = ICOSAHEDRON;
+        ret->d1 = 3;
+        ret->d2 = 3;
+        break;
+      default:
+        sfree(ret);
+        return FALSE;
+    }
+
+    *name = dupstr(str);
+    *params = ret;
+    return TRUE;
+}
+
 void free_params(game_params *params)
 {
     sfree(params);
 }
 
+game_params *dup_params(game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;		       /* structure copy */
+    return ret;
+}
+
 static void enum_grid_squares(game_params *params,
                               void (*callback)(void *, struct grid_square *),
                               void *ctx)
@@ -1083,8 +1131,8 @@
 void game_size(game_params *params, int *x, int *y)
 {
     struct bbox bb = find_bbox(params);
-    *x = (bb.r - bb.l + 2) * GRID_SCALE;
-    *y = (bb.d - bb.u + 2) * GRID_SCALE;
+    *x = (bb.r - bb.l + 2*solids[params->solid]->border) * GRID_SCALE;
+    *y = (bb.d - bb.u + 2*solids[params->solid]->border) * GRID_SCALE;
 }
 
 float *game_colours(frontend *fe, game_state *state, int *ncolours)
@@ -1110,8 +1158,8 @@
     struct game_drawstate *ds = snew(struct game_drawstate);
     struct bbox bb = find_bbox(&state->params);
 
-    ds->ox = -(bb.l - 1) * GRID_SCALE;
-    ds->oy = -(bb.u - 1) * GRID_SCALE;
+    ds->ox = -(bb.l - state->solid->border) * GRID_SCALE;
+    ds->oy = -(bb.u - state->solid->border) * GRID_SCALE;
 
     return ds;
 }
--- a/gtk.c
+++ b/gtk.c
@@ -206,6 +206,9 @@
     frontend *fe = (frontend *)data;
     GdkGC *gc;
 
+    if (fe->pixmap)
+        gdk_pixmap_unref(fe->pixmap);
+
     fe->pixmap = gdk_pixmap_new(widget->window, fe->w, fe->h, -1);
 
     gc = gdk_gc_new(fe->area->window);
@@ -239,10 +242,56 @@
     fe->timer_active = TRUE;
 }
 
+static void menu_key_event(GtkMenuItem *menuitem, gpointer data)
+{
+    frontend *fe = (frontend *)data;
+    int key = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(menuitem),
+                                                  "user-data"));
+    if (!midend_process_key(fe->me, 0, 0, key))
+	gtk_widget_destroy(fe->window);
+}
+
+static void menu_preset_event(GtkMenuItem *menuitem, gpointer data)
+{
+    frontend *fe = (frontend *)data;
+    game_params *params =
+        (game_params *)gtk_object_get_data(GTK_OBJECT(menuitem), "user-data");
+    int x, y;
+
+    midend_set_params(fe->me, params);
+    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)
+{
+    GtkWidget *menuitem = gtk_menu_item_new_with_label(text);
+    gtk_container_add(cont, menuitem);
+    gtk_object_set_data(GTK_OBJECT(menuitem), "user-data",
+                        GINT_TO_POINTER(key));
+    gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
+		       GTK_SIGNAL_FUNC(menu_key_event), fe);
+    gtk_widget_show(menuitem);
+    return menuitem;
+}
+
+static void add_menu_separator(GtkContainer *cont)
+{
+    GtkWidget *menuitem = gtk_menu_item_new();
+    gtk_container_add(cont, menuitem);
+    gtk_widget_show(menuitem);
+}
+
 static frontend *new_window(void)
 {
     frontend *fe;
-    int x, y;
+    GtkBox *vbox;
+    GtkWidget *menubar, *menu, *menuitem;
+    int x, y, n;
 
     fe = snew(frontend);
 
@@ -255,7 +304,56 @@
 #else
     gtk_window_set_policy(GTK_WINDOW(fe->window), FALSE, FALSE, TRUE);
 #endif
+    vbox = GTK_BOX(gtk_vbox_new(FALSE, 0));
+    gtk_container_add(GTK_CONTAINER(fe->window), GTK_WIDGET(vbox));
+    gtk_widget_show(GTK_WIDGET(vbox));
 
+    menubar = gtk_menu_bar_new();
+    gtk_box_pack_start(vbox, menubar, FALSE, FALSE, 0);
+    gtk_widget_show(menubar);
+
+    menuitem = gtk_menu_item_new_with_label("Game");
+    gtk_container_add(GTK_CONTAINER(menubar), menuitem);
+    gtk_widget_show(menuitem);
+
+    menu = gtk_menu_new();
+    gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
+
+    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) {
+        GtkWidget *submenu;
+        int i;
+
+        menuitem = gtk_menu_item_new_with_label("Type");
+        gtk_container_add(GTK_CONTAINER(menu), menuitem);
+        gtk_widget_show(menuitem);
+
+        submenu = gtk_menu_new();
+        gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
+
+        for (i = 0; i < n; i++) {
+            char *name;
+            game_params *params;
+
+            midend_fetch_preset(fe->me, i, &name, &params);
+
+            menuitem = gtk_menu_item_new_with_label(name);
+            gtk_container_add(GTK_CONTAINER(submenu), menuitem);
+            gtk_object_set_data(GTK_OBJECT(menuitem), "user-data", params);
+            gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
+                               GTK_SIGNAL_FUNC(menu_preset_event), fe);
+            gtk_widget_show(menuitem);
+        }
+    }
+
+    add_menu_separator(GTK_CONTAINER(menu));
+    add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Undo", 'u');
+    add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Redo", '\x12');
+    add_menu_separator(GTK_CONTAINER(menu));
+    add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Exit", 'q');
+
     {
         int i, ncolours;
         float *colours;
@@ -288,7 +386,7 @@
     fe->w = x;
     fe->h = y;
 
-    gtk_container_add(GTK_CONTAINER(fe->window), fe->area);
+    gtk_box_pack_end(vbox, fe->area, FALSE, FALSE, 0);
 
     fe->pixmap = NULL;
 
--- a/midend.c
+++ b/midend.c
@@ -14,6 +14,11 @@
     frontend *frontend;
     char *seed;
     int nstates, statesize, statepos;
+
+    game_params **presets;
+    char **preset_names;
+    int npresets, presetsize;
+
     game_params *params;
     game_state **states;
     game_drawstate *drawstate;
@@ -39,6 +44,9 @@
     me->seed = NULL;
     me->drawstate = NULL;
     me->oldstate = NULL;
+    me->presets = NULL;
+    me->preset_names = NULL;
+    me->npresets = me->presetsize = 0;
 
     return me;
 }
@@ -59,7 +67,7 @@
 void midend_set_params(midend_data *me, game_params *params)
 {
     free_params(me->params);
-    me->params = params;
+    me->params = dup_params(params);
 }
 
 void midend_new_game(midend_data *me, char *seed)
@@ -226,4 +234,36 @@
         free_game(state);
 
     return ret;
+}
+
+int midend_num_presets(midend_data *me)
+{
+    if (!me->npresets) {
+        char *name;
+        game_params *preset;
+
+        while (game_fetch_preset(me->npresets, &name, &preset)) {
+            if (me->presetsize <= me->npresets) {
+                me->presetsize = me->npresets + 10;
+                me->presets = sresize(me->presets, me->presetsize,
+                                      game_params *);
+                me->preset_names = sresize(me->preset_names, me->presetsize,
+                                           char *);
+            }
+
+            me->presets[me->npresets] = preset;
+            me->preset_names[me->npresets] = name;
+            me->npresets++;
+        }
+    }
+
+    return me->npresets;
+}
+
+void midend_fetch_preset(midend_data *me, int n,
+                         char **name, game_params **params)
+{
+    assert(n >= 0 && n < me->npresets);
+    *name = me->preset_names[n];
+    *params = me->presets[n];
 }
--- a/net.c
+++ b/net.c
@@ -126,19 +126,60 @@
 {
     game_params *ret = snew(game_params);
 
-    ret->width = 11;
-    ret->height = 11;
-    ret->wrapping = TRUE;
-    ret->barrier_probability = 0.1;
+    ret->width = 5;
+    ret->height = 5;
+    ret->wrapping = FALSE;
+    ret->barrier_probability = 0.0;
 
     return ret;
 }
 
+int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    char str[80];
+    static const struct { int x, y, wrap; } values[] = {
+        {5, 5, FALSE},
+        {7, 7, FALSE},
+        {9, 9, FALSE},
+        {11, 11, FALSE},
+        {13, 11, FALSE},
+        {5, 5, TRUE},
+        {7, 7, TRUE},
+        {9, 9, TRUE},
+        {11, 11, TRUE},
+        {13, 11, TRUE},
+    };
+
+    if (i < 0 || i >= lenof(values))
+        return FALSE;
+
+    ret = snew(game_params);
+    ret->width = values[i].x;
+    ret->height = values[i].y;
+    ret->wrapping = values[i].wrap;
+    ret->barrier_probability = 0.0;
+
+    sprintf(str, "%dx%d%s", ret->width, ret->height,
+            ret->wrapping ? " wrapping" : "");
+
+    *name = dupstr(str);
+    *params = ret;
+    return TRUE;
+}
+
 void free_params(game_params *params)
 {
     sfree(params);
 }
 
+game_params *dup_params(game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;		       /* structure copy */
+    return ret;
+}
+
 /* ----------------------------------------------------------------------
  * Randomly select a new game seed.
  */
@@ -480,13 +521,13 @@
 
                 x1 = x + X(dir), y1 = y + Y(dir);
                 if (x1 >= 0 && x1 < state->width &&
-                    y1 >= 0 && y1 < state->width &&
+                    y1 >= 0 && y1 < state->height &&
                     (barrier(state, x1, y1) & dir2))
                     corner = TRUE;
 
                 x2 = x + X(dir2), y2 = y + Y(dir2);
                 if (x2 >= 0 && x2 < state->width &&
-                    y2 >= 0 && y2 < state->width &&
+                    y2 >= 0 && y2 < state->height &&
                     (barrier(state, x2, y2) & dir))
                     corner = TRUE;
 
@@ -493,14 +534,14 @@
                 if (corner) {
                     barrier(state, x, y) |= (dir << 4);
                     if (x1 >= 0 && x1 < state->width &&
-                        y1 >= 0 && y1 < state->width)
+                        y1 >= 0 && y1 < state->height)
                         barrier(state, x1, y1) |= (A(dir) << 4);
                     if (x2 >= 0 && x2 < state->width &&
-                        y2 >= 0 && y2 < state->width)
+                        y2 >= 0 && y2 < state->height)
                         barrier(state, x2, y2) |= (C(dir) << 4);
                     x3 = x + X(dir) + X(dir2), y3 = y + Y(dir) + Y(dir2);
                     if (x3 >= 0 && x3 < state->width &&
-                        y3 >= 0 && y3 < state->width)
+                        y3 >= 0 && y3 < state->height)
                         barrier(state, x3, y3) |= (F(dir) << 4);
                 }
             }
--- a/puzzles.h
+++ b/puzzles.h
@@ -61,6 +61,9 @@
 void midend_redraw(midend_data *me);
 float *midend_colours(midend_data *me, int *ncolours);
 void midend_timer(midend_data *me, float tplus);
+int midend_num_presets(midend_data *me);
+void midend_fetch_preset(midend_data *me, int n,
+                         char **name, game_params **params);
 
 /*
  * malloc.c
@@ -87,7 +90,9 @@
  * Game-specific routines
  */
 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);
 char *new_game_seed(game_params *params);
 game_state *new_game(game_params *params, char *seed);
 game_state *dup_game(game_state *state);
--- a/windows.c
+++ b/windows.c
@@ -11,6 +11,13 @@
 
 #include "puzzles.h"
 
+#define IDM_NEW       0x0010
+#define IDM_RESTART   0x0020
+#define IDM_UNDO      0x0030
+#define IDM_REDO      0x0040
+#define IDM_QUIT      0x0050
+#define IDM_PRESETS   0x0100
+
 struct frontend {
     midend_data *me;
     HWND hwnd;
@@ -20,6 +27,8 @@
     HBRUSH *brushes;
     HPEN *pens;
     UINT timer;
+    int npresets;
+    game_params **presets;
 };
 
 void fatal(char *fmt, ...)
@@ -178,7 +187,7 @@
     r.bottom = y;
     AdjustWindowRectEx(&r, WS_OVERLAPPEDWINDOW &~
 		       (WS_THICKFRAME | WS_MAXIMIZEBOX | WS_OVERLAPPED),
-		       FALSE, 0);
+		       TRUE, 0);
 
     fe->hwnd = CreateWindowEx(0, "puzzle", "puzzle",
 			      WS_OVERLAPPEDWINDOW &~
@@ -187,6 +196,44 @@
 			      r.right - r.left, r.bottom - r.top,
 			      NULL, NULL, inst, NULL);
 
+    {
+	HMENU bar = CreateMenu();
+	HMENU menu = CreateMenu();
+
+	AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT)menu, "Game");
+	AppendMenu(menu, MF_ENABLED, IDM_NEW, "New");
+	AppendMenu(menu, MF_ENABLED, IDM_RESTART, "Restart");
+
+	if ((fe->npresets = midend_num_presets(fe->me)) > 0) {
+	    HMENU sub = CreateMenu();
+	    int i;
+
+	    AppendMenu(menu, MF_ENABLED|MF_POPUP, (UINT)sub, "Type");
+
+	    fe->presets = snewn(fe->npresets, game_params *);
+
+	    for (i = 0; i < fe->npresets; i++) {
+		char *name;
+
+		midend_fetch_preset(fe->me, i, &name, &fe->presets[i]);
+
+		/*
+		 * FIXME: we ought to go through and do something
+		 * with ampersands here.
+		 */
+
+		AppendMenu(sub, MF_ENABLED, IDM_PRESETS + 0x10 * i, name);
+	    }
+	}
+
+	AppendMenu(menu, MF_SEPARATOR, 0, 0);
+	AppendMenu(menu, MF_ENABLED, IDM_UNDO, "Undo");
+	AppendMenu(menu, MF_ENABLED, IDM_REDO, "Redo");
+	AppendMenu(menu, MF_SEPARATOR, 0, 0);
+	AppendMenu(menu, MF_ENABLED, IDM_QUIT, "Exit");
+	SetMenu(fe->hwnd, bar);
+    }
+
     hdc = GetDC(fe->hwnd);
     fe->bitmap = CreateCompatibleBitmap(hdc, x, y);
     ReleaseDC(fe->hwnd, hdc);
@@ -210,6 +257,65 @@
       case WM_CLOSE:
 	DestroyWindow(hwnd);
 	return 0;
+      case WM_COMMAND:
+	switch (wParam & ~0xF) {       /* low 4 bits reserved to Windows */
+	  case IDM_NEW:
+	    if (!midend_process_key(fe->me, 0, 0, 'n'))
+		PostQuitMessage(0);
+	    break;
+	  case IDM_RESTART:
+	    if (!midend_process_key(fe->me, 0, 0, 'r'))
+		PostQuitMessage(0);
+	    break;
+	  case IDM_UNDO:
+	    if (!midend_process_key(fe->me, 0, 0, 'u'))
+		PostQuitMessage(0);
+	    break;
+	  case IDM_REDO:
+	    if (!midend_process_key(fe->me, 0, 0, '\x12'))
+		PostQuitMessage(0);
+	    break;
+	  case IDM_QUIT:
+	    if (!midend_process_key(fe->me, 0, 0, 'q'))
+		PostQuitMessage(0);
+	    break;
+	  default:
+	    {
+		int p = ((wParam &~ 0xF) - IDM_PRESETS) / 0x10;
+
+		if (p >= 0 && p < fe->npresets) {
+		    RECT r;
+		    HDC hdc;
+		    int x, y;
+
+		    midend_set_params(fe->me, fe->presets[p]);
+		    midend_new_game(fe->me, NULL);
+		    midend_size(fe->me, &x, &y);
+
+		    r.left = r.top = 0;
+		    r.right = x;
+		    r.bottom = y;
+		    AdjustWindowRectEx(&r, WS_OVERLAPPEDWINDOW &~
+				       (WS_THICKFRAME | WS_MAXIMIZEBOX |
+					WS_OVERLAPPED),
+				       TRUE, 0);
+
+		    SetWindowPos(fe->hwnd, NULL, 0, 0,
+				 r.right - r.left, r.bottom - r.top,
+				 SWP_NOMOVE | SWP_NOZORDER);
+
+		    DeleteObject(fe->bitmap);
+
+		    hdc = GetDC(fe->hwnd);
+		    fe->bitmap = CreateCompatibleBitmap(hdc, x, y);
+		    ReleaseDC(fe->hwnd, hdc);
+
+		    midend_redraw(fe->me);
+		}
+	    }
+	    break;
+	}
+	break;
       case WM_DESTROY:
 	PostQuitMessage(0);
 	return 0;
@@ -246,7 +352,7 @@
 	    }
 
 	    if (key != -1) {
-		if (!midend_process_key(fe->me, -1, -1, key))
+		if (!midend_process_key(fe->me, 0, 0, key))
 		    PostQuitMessage(0);
 	    }
 	}
@@ -262,7 +368,7 @@
 	
 	break;
       case WM_CHAR:
-	if (!midend_process_key(fe->me, -1, -1, (unsigned char)wParam))
+	if (!midend_process_key(fe->me, 0, 0, (unsigned char)wParam))
 	    PostQuitMessage(0);
 	return 0;
       case WM_TIMER: