]> git.draconx.ca Git - rrace.git/commitdiff
Use gen-tree.awk to produce curses menu labels.
authorNick Bowler <nbowler@draconx.ca>
Sat, 18 Jun 2022 01:53:25 +0000 (21:53 -0400)
committerNick Bowler <nbowler@draconx.ca>
Sat, 18 Jun 2022 01:53:25 +0000 (21:53 -0400)
Instead of hardcoding the labels, we can use gen-tree.awk to
produce a compact description.

Makefile.am
src/.gitignore
src/curses.c
src/cursesui.h [new file with mode: 0644]
src/cursmenu.c [new file with mode: 0644]
src/cursmenu.dat [new file with mode: 0644]

index e5efa40c5410206321cd92a62f10e0050ea26165..d662b18f34f9daf6deb6badfaf1dbb2c32826211 100644 (file)
@@ -43,9 +43,8 @@ rrace_motif_LDADD = $(libmotifmain_a_OBJECTS) $(libmotifui_a_OBJECTS) \
 $(rrace_motif_OBJECTS): $(gnulib_headers)
 
 EXTRA_LIBRARIES += libcursesmain.a
-libcursesmain_a_SOURCES = src/curses.c
-$(libcursesmain_a_OBJECTS): $(gnulib_headers)
-$(libcursesmain_a_OBJECTS): src/cursesopt.h
+libcursesmain_a_SOURCES = src/curses.c src/cursmenu.c
+$(libcursesmain_a_OBJECTS): $(gnulib_headers) src/cursesopt.h src/cursmenu.h
 
 EXTRA_LIBRARIES += libmotifmain.a
 libmotifmain_a_SOURCES = src/motif.c
@@ -71,7 +70,7 @@ $(OPTFILES:.opt=.h): $(DX_BASEDIR)/scripts/gen-options.awk
 DISTCLEANFILES += $(OPTFILES:.opt=.h)
 EXTRA_DIST += $(DX_BASEDIR)/scripts/gen-options.awk $(OPTFILES)
 
-TREEFILES = src/motifgui.dat
+TREEFILES = src/cursmenu.dat src/motifgui.dat
 .dat.h:
        $(AM_V_GEN) :; { \
          $(AWK) -f $(DX_BASEDIR)/scripts/gen-tree.awk $< && \
index 977700cdf761b9bc19db63dc42070b58e2b1ed66..8200e95e1c43e97a310dc9b653ad3aebad62f781 100644 (file)
@@ -1,4 +1,5 @@
 /cursesopt.h
+/cursmenu.h
 /motifgui.h
 /motifopt.h
 /motifstr.h
index 7497395cbaa4f4865f18faf3ea979113a1935bad..cdaf39630b200dcbe88aa2e9b635c0cb57c00c04 100644 (file)
 
 #include "help.h"
 #include "version.h"
-#include "cursesopt.h"
-#include "game.h"
 
-#define MIN(a, b) ((a) < (b) ? (a) : (b))
-#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#include "cursesui.h"
+#include "cursesopt.h"
 
 enum {
        GAME_YPOS = 1 // top row of game and goal areas.
@@ -38,40 +36,7 @@ enum {
 static const char *progname = "rrace";
 static const struct option lopts[] = { LOPTS_INITIALIZER, {0} };
 
-enum {
-       WINDOW_TILEBORDER,
-       WINDOW_TILEFILL,
-       WINDOW_AREA,
-       WINDOW_MAX,
-};
-
-/* Colour pair enumeration */
-enum {
-       /* Pairs 1-6 correspond to tile colours */
-       RR_COLOUR_CURSOR = TILE_MAX, // black on black, for the cursor
-       RR_COLOUR_TOOLBAR,           // cyan on black (use reverse video)
-       RR_COLOUR_MAX
-};
-
-static struct app_state {
-       struct board board;
-
-       WINDOW *gamewin[WINDOW_MAX], *goalwin[WINDOW_MAX];
-       WINDOW *toolbar, *timer;
-       int last_input;
-
-       /* Most recently displayed timer value, for screen redraw. */
-       uint_least32_t timer_ms;
-
-       /* Location of the keyboard cursor */
-       int_least8_t cursor;
-
-       /* If true, the goal will be displayed over the main play area. */
-       uint_least8_t view_goal_on_game;
-
-       /* Clicked toolbar item */
-       uint_least8_t toolbar_click;
-} state;
+static struct app_state state;
 
 static void print_version(void)
 {
@@ -110,7 +75,7 @@ static void print_help(void)
 static void draw_tile(WINDOW **win, unsigned colour, unsigned selected,
                       unsigned x, unsigned y, unsigned start_column)
 {
-       WINDOW *border = win[WINDOW_TILEBORDER], *fill = win[WINDOW_TILEFILL];
+       WINDOW *border = win[PLAYWIN_TILEBORDER], *fill = win[PLAYWIN_TILEFILL];
        int w, h, attr, ch, bc = selected ? '#' : 0;
 
        assert(colour < TILE_MAX);
@@ -123,7 +88,7 @@ static void draw_tile(WINDOW **win, unsigned colour, unsigned selected,
        case TILE_BLUE:   ch = 'o'; attr |= A_BOLD; break;
        case TILE_WHITE:  ch = '.'; attr |= A_BOLD; break;
 
-       case TILE_EMPTY: attr = A_BOLD|COLOR_PAIR(RR_COLOUR_CURSOR);
+       case TILE_EMPTY: attr = A_BOLD|COLOR_PAIR(RR_COLOUR_SHADOW);
        }
 
        getmaxyx(border, h, w);
@@ -168,7 +133,7 @@ redraw_tile(WINDOW **win, unsigned x, unsigned y, unsigned start_column,
 static void redraw_area_border(WINDOW **win, unsigned x, unsigned sz)
 {
        int w, h, tr = 0, rs = 0, br = 0, bs = 0, bl = 0;
-       WINDOW *area = win[WINDOW_AREA];
+       WINDOW *area = win[PLAYWIN_AREA];
 
        getmaxyx(stdscr, h, w);
 
@@ -220,10 +185,10 @@ static void curs_redraw_goal(struct app_state *state, uint_fast32_t mask)
        uint_least16_t *gp = state->board.goal;
        int i, x, y;
 
-       if (!state->goalwin[WINDOW_AREA])
+       if (!state->goalwin[PLAYWIN_AREA])
                return;
 
-       getbegyx(state->goalwin[WINDOW_AREA], y, x);
+       getbegyx(state->goalwin[PLAYWIN_AREA], y, x);
        if (mask == -1)
                redraw_area_border(state->goalwin, x, 3);
 
@@ -257,7 +222,7 @@ static WINDOW *realloc_area(WINDOW **orig, int h, int w, int y, int x)
 
 static void realloc_tiles(WINDOW **win, int h)
 {
-       WINDOW *border = win[WINDOW_TILEBORDER], *fill = win[WINDOW_TILEFILL];
+       WINDOW *border = win[PLAYWIN_TILEBORDER], *fill = win[PLAYWIN_TILEFILL];
        int w = 2*h - 1;
 
        if (fill && border) {
@@ -281,60 +246,8 @@ static void realloc_tiles(WINDOW **win, int h)
        if (border)
                delwin(border);
 
-       win[WINDOW_TILEBORDER] = border = newwin(h, w, 0, 0);
-       win[WINDOW_TILEFILL] = derwin(border, h-2, w-2, 1, 1);
-}
-
-/*
- * Given the toolbar function number (between 1 and 10, inclusive), and the
- * total width of the screen, return the character position for the start of
- * its label display.
- *
- * The intention is to divide the width of the screen into 10 roughly
- * equally-sized areas, spreading out the remainder so that the width of
- * each label is a monotone increasing function of the total width.
- *
- * The minimum size of a label is 6 characters (2 of which are used for the
- * number indicator)
- */
-static int toolbar_xpos(int i, int total_width)
-{
-       int button_width = MAX(6, total_width/10);
-       int rem = total_width - 10*button_width;
-       int pos = (i-1)*button_width;
-
-       switch (rem) {
-       case 9: pos += i > 6;
-       case 8: pos += i > 2;
-       case 7: pos += i > 7;
-       case 6: pos += i > 3;
-       case 5: pos += i > 8;
-       case 4: pos += i > 4;
-       case 3: pos += i > 9;
-       case 2: pos += i > 5;
-       }
-
-       return pos;
-}
-
-static void draw_toolbar(struct app_state *state)
-{
-       WINDOW *toolbar = state->toolbar;
-       int i, w, lw;
-
-       getmaxyx(toolbar, i, w);
-       werase(toolbar);
-
-       lw = MAX(6, w/10);
-       mvwprintw(toolbar, 0, toolbar_xpos( 1, w)+2, "%.*s", lw, "Help");
-       mvwprintw(toolbar, 0, toolbar_xpos( 2, w)+2, "%.*s", lw, "NewGame");
-       mvwprintw(toolbar, 0, toolbar_xpos(10, w)+2, "%.*s", lw, "Exit");
-
-       mvwchgat(toolbar, 0, 0, -1, A_REVERSE, RR_COLOUR_TOOLBAR, 0);
-       for (i = 1; i <= 10; i++)
-               mvwprintw(toolbar, 0, toolbar_xpos(i, w), "%2d", i);
-
-       wnoutrefresh(state->toolbar);
+       win[PLAYWIN_TILEBORDER] = border = newwin(h, w, 0, 0);
+       win[PLAYWIN_TILEFILL] = derwin(border, h-2, w-2, 1, 1);
 }
 
 static void setup_mainwin(struct app_state *state)
@@ -363,12 +276,12 @@ static void setup_mainwin(struct app_state *state)
        /* Frame for game area */
        w = MIN(scr_w-2, 3+10*gamesz);
        h = MIN(scr_h-GAME_YPOS, 2+5*gamesz);
-       realloc_area(&state->gamewin[WINDOW_AREA], h, w, GAME_YPOS, 2);
+       realloc_area(&state->gamewin[PLAYWIN_AREA], h, w, GAME_YPOS, 2);
 
        /* Frame for goal area */
        w = MIN(scr_w-split, 3+6*goalsz);
        h = MIN(scr_h-GAME_YPOS, 2+3*goalsz);
-       realloc_area(&state->goalwin[WINDOW_AREA], h, w, GAME_YPOS, split);
+       realloc_area(&state->goalwin[PLAYWIN_AREA], h, w, GAME_YPOS, split);
 
        /* Status area */
        w = MAX(0, scr_w-split-1);
@@ -376,7 +289,7 @@ static void setup_mainwin(struct app_state *state)
 
        /* Toolbar */
        realloc_area(&state->toolbar, 1, scr_w, scr_h-1, 0);
-       draw_toolbar(state);
+       curs_draw_toolbar(state);
 }
 
 static void app_initialize(int argc, char **argv)
@@ -457,7 +370,7 @@ static void app_initialize(int argc, char **argv)
        init_pair(TILE_GREEN, COLOR_GREEN, COLOR_BLACK);
        init_pair(TILE_BLUE, COLOR_BLUE, COLOR_BLACK);
        init_pair(TILE_WHITE, COLOR_WHITE, COLOR_BLACK);
-       init_pair(RR_COLOUR_CURSOR, COLOR_BLACK, COLOR_BLACK);
+       init_pair(RR_COLOUR_SHADOW, COLOR_BLACK, COLOR_BLACK);
        init_pair(RR_COLOUR_TOOLBAR, COLOR_CYAN, COLOR_BLACK);
 
        setup_mainwin(&state);
@@ -474,7 +387,7 @@ static void update_timer(struct app_state *state, uint_fast32_t ms)
        mvwprintw(state->timer, 0, 0, "Time: %u:%.2u.%.3u",
                  min, sec, (unsigned)ms);
        wclrtoeol(state->timer);
-       wrefresh(state->timer);
+       wnoutrefresh(state->timer);
 }
 
 static uint_fast32_t do_move(struct app_state *state, int x, int y)
@@ -509,7 +422,7 @@ static void do_reset_cursor(struct app_state *state)
        state->cursor = 5*state->board.y + state->board.x;
 }
 
-static void do_new_game(struct app_state *state)
+void curs_new_game(struct app_state *state)
 {
        game_reset(&state->board);
 
@@ -523,42 +436,7 @@ static void do_new_game(struct app_state *state)
        game_begin(&state->board);
 }
 
-static void do_function(struct app_state *state, unsigned func)
-{
-       switch (func) {
-       case 2:
-               do_new_game(state);
-               break;
-       case 10:
-               endwin();
-               exit(0);
-       }
-}
-
 #if HAVE_CURSES_MOUSE_SUPPORT
-/*
- * Returns the toolbar function (1 through 10) under the given x, y screen
- * coordinates, or 0 if the coordinates are outside of the toolbar.
- */
-static int mouse_toolbar_function(struct app_state *state, int x, int y)
-{
-       int toolbar_x, toolbar_y, toolbar_w, toolbar_h, i;
-
-       getbegyx(state->toolbar, toolbar_y, toolbar_x);
-       getmaxyx(state->toolbar, toolbar_h, toolbar_w);
-
-       if ((void)toolbar_x, y != toolbar_y)
-               return 0;
-
-       /* OK, selected a button, determine which one */
-       for (i = 10; i > 1; i--) {
-               if ((void)toolbar_h, x >= toolbar_xpos(i, toolbar_w))
-                       break;
-       }
-
-       return i;
-}
-
 /*
  * Given x, y as screen coordinates, record which (if any) toolbar function
  * label is at that position, to be performed later.
@@ -567,19 +445,21 @@ static int mouse_toolbar_function(struct app_state *state, int x, int y)
  */
 static int press_toolbar(struct app_state *state, int x, int y)
 {
-       return state->toolbar_click = mouse_toolbar_function(state, x, y);
+       return state->toolbar_click = curs_toolbar_mouse_func(state, x, y);
 }
 
 /*
+ * Given x, y as screen coordinates, perform the toolbar action.
+ *
  * Perform the action previously recorded by press_toolbar, if and only if
  * the x, y screen coordinates correspond to the same function.
  */
 static void release_toolbar(struct app_state *state, int x, int y)
 {
-       int func = mouse_toolbar_function(state, x, y);
+       int func = curs_toolbar_mouse_func(state, x, y);
 
        if (func && state->toolbar_click == func) {
-               do_function(state, func);
+               curs_execute_function(state, func);
        }
 
        state->toolbar_click = 0;
@@ -598,8 +478,8 @@ static int press_tile(struct app_state *state, int x, int y)
        int game_x, game_y, tile_w, tile_h;
        uint_fast32_t cursor_mask, move_mask;
 
-       getbegyx(state->gamewin[WINDOW_AREA], game_y, game_x);
-       getmaxyx(state->gamewin[WINDOW_TILEBORDER], tile_h, tile_w);
+       getbegyx(state->gamewin[PLAYWIN_AREA], game_y, game_x);
+       getmaxyx(state->gamewin[PLAYWIN_TILEBORDER], tile_h, tile_w);
        tile_w += tile_w & 1;
 
        /* special case the left spacer column */
@@ -746,12 +626,12 @@ static void do_keystroke(struct app_state *state, int c)
 
        /* ESC+# keys */
        if (last_input == '\33' && c >= '0' && c <= '9') {
-               do_function(state, (c -= '0') == 0 ? 10 : c);
+               curs_execute_function(state, (c -= '0') == 0 ? 10 : c);
        }
 
        /* F# keys */
        if (c >= KEY_F(1) && c <= KEY_F(10)) {
-               do_function(state, c - KEY_F0);
+               curs_execute_function(state, c - KEY_F0);
        }
 }
 
@@ -768,8 +648,9 @@ void do_mainloop(struct app_state *state)
                refresh();
                curs_redraw_game(state, -1);
                curs_redraw_goal(state, -1);
-               draw_toolbar(state);
+               curs_draw_toolbar(state);
                update_timer(state, state->timer_ms);
+               doupdate();
                break;
 #endif
 #if HAVE_CURSES_MOUSE_SUPPORT
@@ -782,8 +663,10 @@ void do_mainloop(struct app_state *state)
        case ERR:;
        }
 
-       if (state->board.x <= 4)
+       if (state->board.x <= 4) {
                update_timer(state, game_elapsed(&state->board));
+               doupdate();
+       }
 }
 
 int main(int argc, char **argv)
@@ -791,7 +674,7 @@ int main(int argc, char **argv)
        setlocale(LC_ALL, "");
        app_initialize(argc, argv);
 
-       do_new_game(&state);
+       curs_new_game(&state);
        while (1)
                do_mainloop(&state);
        abort();
diff --git a/src/cursesui.h b/src/cursesui.h
new file mode 100644 (file)
index 0000000..50d30fe
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Curses UI for slide puzzle game
+ * Copyright © 2022 Nick Bowler
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef RRACE_CURSESUI_H_
+#define RRACE_CURSESUI_H_
+
+#include "game.h"
+
+/* Array indices for gamewin and goalwin window sets. */
+enum {
+       PLAYWIN_TILEBORDER,
+       PLAYWIN_TILEFILL,
+       PLAYWIN_AREA,
+       PLAYWIN_MAX
+};
+
+/* Colour pair enumeration */
+enum {
+       /* Pairs 1-6 correspond to tile colours; use TILE_xxx enumerations. */
+       RR_COLOUR_SHADOW = TILE_MAX, /* black on black (use bold) */
+       RR_COLOUR_TOOLBAR,           /* cyan on black (use reverse video) */
+       RR_COLOUR_MAX
+};
+
+struct app_state {
+       struct board board;
+
+       WINDOW *gamewin[PLAYWIN_MAX], *goalwin[PLAYWIN_MAX];
+       WINDOW *toolbar, *timer;
+
+       /* Previous input returned from getch, for 2-character sequences */
+       int last_input;
+
+       /* Most recently displayed timer value, for screen redraw. */
+       uint_least32_t timer_ms;
+
+       /* Location of the keyboard cursor */
+       int_least8_t cursor;
+
+       /* If true, the goal will be displayed over the main play area. */
+       uint_least8_t view_goal_on_game;
+
+       /* Clicked toolbar item */
+       uint_least8_t toolbar_click;
+
+       /* Current state of the toolbar menu */
+       uint_least8_t toolbar_state;
+};
+
+void curs_new_game(struct app_state *state);
+
+void curs_reenable_dialog(struct app_state *state, const char *heading);
+
+void curs_draw_toolbar(struct app_state *state);
+void curs_execute_function(struct app_state *state, int function);
+
+#if HAVE_CURSES_MOUSE_SUPPORT
+int curs_toolbar_mouse_func(struct app_state *state, int x, int y);
+#else
+static inline int
+curs_toolbar_mouse_func(struct app_state *state, int x, int y)
+{
+       return 0;
+}
+#endif
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
+#endif
diff --git a/src/cursmenu.c b/src/cursmenu.c
new file mode 100644 (file)
index 0000000..e665a32
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+ * Curses UI for slide puzzle game
+ * Copyright © 2022 Nick Bowler
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <curses.h>
+
+#include "cursesui.h"
+#include "cursmenu.h"
+
+enum {
+       ACTION_EXIT = 0x80,
+       ACTION_ABOUT,
+       ACTION_NEWGAME,
+       ACTION_MAX
+};
+
+static const struct menuitem {
+       uint_least8_t label, position, action;
+} menu[] = { FUNC_INITIALIZER };
+
+static void do_function(struct app_state *state, unsigned action)
+{
+       if (action < sizeof menu / sizeof menu[0]) {
+               state->toolbar_state = action;
+               curs_draw_toolbar(state);
+               doupdate();
+       }
+
+       switch (action) {
+       case ACTION_NEWGAME:
+               curs_new_game(state);
+               break;
+       case ACTION_EXIT:
+               endwin();
+               exit(0);
+       };
+}
+
+void curs_execute_function(struct app_state *state, int func)
+{
+       int i;
+
+       for (i = state->toolbar_state; menu[i].position; i++) {
+               if (func == menu[i].position) {
+                       do_function(state, menu[i].action);
+                       return;
+               }
+       }
+}
+
+/*
+ * Return the width of the toolbar and the target width of toolbar labels.
+ */
+static int toolbar_width(WINDOW *toolbar, int *label_width)
+{
+       int w, h;
+
+       getmaxyx(toolbar, h, w);
+       *label_width = MAX(6, w/10);
+       return (void)h, w;
+}
+
+/*
+ * Given the toolbar function number (between 1 and 10, inclusive), and the
+ * total width of the screen, return the character position for the start of
+ * its label display.
+ *
+ * The intention is to divide the width of the screen into 10 roughly
+ * equally-sized areas, spreading out the remainder so that the width of
+ * each label is a monotone increasing function of the total width.
+ *
+ * The minimum size of a label is 6 characters (2 of which are used for the
+ * number indicator)
+ */
+static int toolbar_xpos(int i, int total_width, int label_width)
+{
+       int rem = total_width - 10*label_width;
+       int pos = (i-1)*label_width;
+
+       switch (rem) {
+       case 9: pos += i > 6;
+       case 8: pos += i > 2;
+       case 7: pos += i > 7;
+       case 6: pos += i > 3;
+       case 5: pos += i > 8;
+       case 4: pos += i > 4;
+       case 3: pos += i > 9;
+       case 2: pos += i > 5;
+       }
+
+       return pos;
+}
+
+/*
+ * Redraw the current set of toolbar labels.
+ */
+void curs_draw_toolbar(struct app_state *state)
+{
+       WINDOW *toolbar = state->toolbar;
+       int i, w, lw;
+
+       werase(toolbar);
+
+       w = toolbar_width(toolbar, &lw);
+       for (i = state->toolbar_state; menu[i].position; i++) {
+               int pos = toolbar_xpos(menu[i].position, w, lw);
+               mvwprintw(toolbar, 0, pos+2, "%.*s", lw, strtab+menu[i].label);
+       }
+
+       mvwchgat(toolbar, 0, 0, -1, A_REVERSE, RR_COLOUR_TOOLBAR, 0);
+       for (i = 1; i <= 10; i++) {
+               mvwprintw(toolbar, 0, toolbar_xpos(i, w, lw), "%2d", i);
+       }
+
+       wnoutrefresh(state->toolbar);
+}
+
+#if HAVE_CURSES_MOUSE_SUPPORT
+/*
+ * Returns the toolbar function (1 through 10) under the given x, y screen
+ * coordinates, or 0 if the coordinates are outside of the toolbar.
+ */
+int curs_toolbar_mouse_func(struct app_state *state, int x, int y)
+{
+       int toolbar_x, toolbar_y, i, w, lw;
+
+       getbegyx(state->toolbar, toolbar_y, toolbar_x);
+       if ((void)toolbar_x, y != toolbar_y)
+               return 0;
+
+       /* OK, selected a button, determine which one */
+       w = toolbar_width(state->toolbar, &lw);
+       for (i = 10; i > 1; i--) {
+               if (x >= toolbar_xpos(i, w, lw))
+                       break;
+       }
+
+       return i;
+}
+#endif
diff --git a/src/cursmenu.dat b/src/cursmenu.dat
new file mode 100644 (file)
index 0000000..73f0561
--- /dev/null
@@ -0,0 +1,14 @@
+@nostrtab
+
+#&func_help    Help
+#&func_about    About
+#&func_back     Back
+#&func_newgame NewGame
+#&func_exit    Exit
+
+FUNC
+ func_help,     1, func_help_OFFSET
+  func_about,   1, ACTION_ABOUT
+  func_back,   10
+ func_newgame,  2, ACTION_NEWGAME
+ func_exit,    10, ACTION_EXIT