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, ¶ms);
+
+ 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: