chiark / gitweb /
Add a menu bar, in both Windows and GTK. In particular, game modules
authorSimon Tatham <anakin@pobox.com>
Wed, 28 Apr 2004 12:07:15 +0000 (12:07 +0000)
committerSimon Tatham <anakin@pobox.com>
Wed, 28 Apr 2004 12:07:15 +0000 (12:07 +0000)
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]

Recipe
cube.c
gtk.c
midend.c
net.c
puzzles.h
windows.c

diff --git a/Recipe b/Recipe
index 31650a740036e4cc16ad90a46ec430a789178c07..41962bb0ca45563519a7e9e426ba898d1e3f4da1 100644 (file)
--- 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
diff --git a/cube.c b/cube.c
index 8a19ae7fcdf0897aff547ce619d3f48ed47f7e5a..710845d381423e1667caf6712b9e69d7ffba5c78 100644 (file)
--- a/cube.c
+++ b/cube.c
@@ -21,6 +21,7 @@ struct solid {
     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 @@ static const struct solid tetrahedron = {
         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 @@ static const struct solid cube = {
     {
         -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 @@ static const struct solid octahedron = {
         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 @@ static const struct solid icosahedron = {
         -0.57735026919, -0.333333333334, -0.745355992501,
         0.57735026919, -0.333333333334, -0.745355992501,
     },
-    0.0
+    0.0, 0.8
 };
 
 enum {
@@ -216,11 +217,58 @@ game_params *default_params(void)
     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 @@ static struct bbox find_bbox(game_params *params)
 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 @@ game_drawstate *game_new_drawstate(game_state *state)
     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;
 }
diff --git a/gtk.c b/gtk.c
index b3b453fd89d53674a4c041b8d0107ccff798d787..102ed9c7a22666f1a165ebd3d1ed44807ca57246 100644 (file)
--- a/gtk.c
+++ b/gtk.c
@@ -206,6 +206,9 @@ static gint configure_area(GtkWidget *widget,
     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 @@ void activate_timer(frontend *fe)
     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,6 +304,55 @@ static frontend *new_window(void)
 #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;
@@ -288,7 +386,7 @@ static frontend *new_window(void)
     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;
 
index d309068e874c3adb8919c613ce48d1e58b0c5b34..cafc8ea6fa14bcdcce337efea2c21b8578921ab6 100644 (file)
--- a/midend.c
+++ b/midend.c
@@ -14,6 +14,11 @@ struct midend_data {
     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 @@ midend_data *midend_new(frontend *frontend)
     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_size(midend_data *me, int *x, int *y)
 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)
@@ -227,3 +235,35 @@ float *midend_colours(midend_data *me, int *ncolours)
 
     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];
+}
diff --git a/net.c b/net.c
index 6fd90aa7e6aa13fd336e419197eda8663c601dc4..a295804b8ffbb3c69438ccb1a75b549439105a68 100644 (file)
--- a/net.c
+++ b/net.c
@@ -126,19 +126,60 @@ game_params *default_params(void)
 {
     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,27 +521,27 @@ game_state *new_game(game_params *params, char *seed)
 
                 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;
 
                 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);
                 }
             }
index 6300e742215fc93f72ddce88ebafc112d338524b..86c2cc1828bc4286b8ea5a95fe35cf465bf3608c 100644 (file)
--- a/puzzles.h
+++ b/puzzles.h
@@ -61,6 +61,9 @@ int midend_process_key(midend_data *me, int x, int y, int button);
 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 @@ void random_free(random_state *state);
  * 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);
index 1fda71b64c5900edf7f3ca4bae93b9b0665df40e..618d440fe5bfc90205ddfd873bb721344c05093f 100644 (file)
--- a/windows.c
+++ b/windows.c
 
 #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 @@ struct frontend {
     HBRUSH *brushes;
     HPEN *pens;
     UINT timer;
+    int npresets;
+    game_params **presets;
 };
 
 void fatal(char *fmt, ...)
@@ -178,7 +187,7 @@ static frontend *new_window(HINSTANCE inst)
     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 @@ static frontend *new_window(HINSTANCE inst)
                              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 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
       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 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
            }
 
            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 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
        
        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: