shithub: puzzles

Download patch

ref: 60a929a250cf4f7f87ac082e5705f9a838a7f8c8
parent: 3d04dd3335a2c4c6007ff4e2a58a2855c7a9c52a
author: Franklin Wei <[email protected]>
date: Tue Apr 17 12:18:16 EDT 2018

Add a request_keys() function with a midend wrapper.

This function gives the front end a way to find out what keys the back
end requires; and as such it is mostly useful for ports without a
keyboard. It is based on changes originally found in Chris Boyle's
Android port, though some modifications were needed to make it more
flexible.

--- a/blackbox.c
+++ b/blackbox.c
@@ -1519,6 +1519,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/bridges.c
+++ b/bridges.c
@@ -3238,6 +3238,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/cube.c
+++ b/cube.c
@@ -1751,6 +1751,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/devel.but
+++ b/devel.but
@@ -1560,6 +1560,41 @@
 freeze the timer thereafter so that the user can undo back through
 their solution process without altering their time.
 
+\S{backend-request-keys} \cw{request_keys()}
+
+\c key_label *(*request_keys)(const game_params *params, int *nkeys);
+
+This function returns a dynamically allocated array of \cw{key_label}
+items containing the buttons the back end deems absolutely
+\e{necessary} for gameplay, not an exhaustive list of every button the
+back end could accept. For example, Keen only returns the digits up to
+the game size and the backspace character, \cw{\\b}, even though it
+\e{could} accept \cw{M}, as only these buttons are actually needed to
+play the game. Each \cw{key_label} item contains the following fields:
+
+\c struct key_label {
+\c     const char *label; /* label for frontend use */
+\c     int button; /* button to pass to midend */
+\c } key_label;
+
+The \cw{label} field of this structure can (and often will) be set by
+the backend to \cw{NULL}, in which case the midend will instead call
+\c{button2label()} (\k{utils-button2label}) and fill in a generic
+label. The \cw{button} field is the associated code that can be passed
+to the midend when the frontend deems appropriate.
+
+The backend should set \cw{*nkeys} to the number of elements in the
+returned array.
+
+The field for this function point in the \cw{game} structure might be
+set to \cw{NULL} (and indeed it is for the majority of the games) to
+indicate that no additional buttons (apart from the cursor keys) are
+required to play the game.
+
+This function should not be called directly by frontends. Instead,
+frontends should use \cw{midend_request_keys()}
+(\k{midend-request-keys}).
+
 \S{backend-flags} \c{flags}
 
 \c int flags;
@@ -2998,6 +3033,18 @@
 program. A front end should shut down the puzzle in response to a
 zero return.
 
+\H{midend-request-keys} \cw{midend_request_keys()}
+
+\c key_label *midend_request_keys(midend *me, int *nkeys);
+
+This function behaves similarly to the backend's \cw{request_keys()}
+function (\k{backend-request-keys}). If the backend does not provide
+\cw{request_keys()}, this function will return \cw{NULL} and set
+\cw{*nkeys} to zero. Otherwise, this function will fill in the generic
+labels (i.e. the \cw{key_label} items that have their \cw{label}
+fields set to \cw{NULL}) by using \cw{button2label()}
+(\k{utils-button2label}).
+
 \H{midend-colours} \cw{midend_colours()}
 
 \c float *midend_colours(midend *me, int *ncolours);
@@ -4214,6 +4261,24 @@
 Thus, \cw{ret[background*3]} to \cw{ret[background*3+2]} will be set
 to RGB values defining a sensible background colour, and similary
 \c{highlight} and \c{lowlight} will be set to sensible colours.
+
+\S{utils-button2label} \cw{button2label()}
+
+\c char *button2label(int button);
+
+This function generates a descriptive text label for \cw{button},
+which should be a button code that can be passed to the midend. For
+example, calling this function with \cw{CURSOR_UP} will result in the
+string \cw{"Up"}. This function should only be called when the
+\cw{key_label} item returned by a backend's \cw{request_keys()}
+(\k{backend-request-keys}) function has its \cw{label} field set to
+\cw{NULL}; in this case, the corresponding \cw{button} field can be
+passed to this function to obtain an appropriate label. If, however,
+the field is not \cw{NULL}, this function should not be called with
+the corresponding \cw{button} field.
+
+The returned string is dynamically allocated and should be
+\cw{sfree}'d by the caller.
 
 \C{writing} How to write a new puzzle
 
--- a/dominosa.c
+++ b/dominosa.c
@@ -1723,6 +1723,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/fifteen.c
+++ b/fifteen.c
@@ -1103,6 +1103,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/filling.c
+++ b/filling.c
@@ -1287,6 +1287,24 @@
     return (area < sz) ? "Not enough data to fill grid" : NULL;
 }
 
+static key_label *game_request_keys(const game_params *params, int *nkeys)
+{
+    key_label *keys = snewn(11, key_label);
+    *nkeys = 11;
+
+    int i;
+
+    for(i = 0; i < 10; ++i)
+    {
+	keys[i].button = '0' + i;
+	keys[i].label = NULL;
+    }
+    keys[10].button = '\b';
+    keys[10].label = NULL;
+
+    return keys;
+}
+
 static game_state *new_game(midend *me, const game_params *params,
                             const char *desc)
 {
@@ -2125,6 +2143,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    game_request_keys,
     game_changed_state,
     interpret_move,
     execute_move,
--- a/flip.c
+++ b/flip.c
@@ -1327,6 +1327,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/flood.c
+++ b/flood.c
@@ -1348,6 +1348,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/galaxies.c
+++ b/galaxies.c
@@ -3652,6 +3652,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/guess.c
+++ b/guess.c
@@ -1493,6 +1493,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/inertia.c
+++ b/inertia.c
@@ -2229,6 +2229,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/keen.c
+++ b/keen.c
@@ -1251,6 +1251,27 @@
     return NULL;
 }
 
+static key_label *game_request_keys(const game_params *params, int *nkeys)
+{
+    int i;
+    int w = params->w;
+
+    key_label *keys = snewn(w+1, key_label);
+    *nkeys = w + 1;
+
+    for (i = 0; i < w; i++) {
+        if (i<9) keys[i].button = '1' + i;
+        else keys[i].button = 'a' + i - 9;
+
+        keys[i].label = NULL;
+    }
+    keys[w].button = '\b';
+    keys[w].label = NULL;
+
+
+    return keys;
+}
+
 static game_state *new_game(midend *me, const game_params *params,
                             const char *desc)
 {
@@ -2354,6 +2375,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    game_request_keys,
     game_changed_state,
     interpret_move,
     execute_move,
--- a/lightup.c
+++ b/lightup.c
@@ -2303,6 +2303,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/loopy.c
+++ b/loopy.c
@@ -3658,6 +3658,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/magnets.c
+++ b/magnets.c
@@ -2409,6 +2409,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/map.c
+++ b/map.c
@@ -3236,6 +3236,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/midend.c
+++ b/midend.c
@@ -1104,6 +1104,27 @@
     return ret;
 }
 
+key_label *midend_request_keys(midend *me, int *n)
+{
+    key_label *keys = NULL;
+    int nkeys = 0, i;
+
+    if(me->ourgame->request_keys)
+    {
+        keys = me->ourgame->request_keys(midend_get_params(me), &nkeys);
+        for(i = 0; i < nkeys; ++i)
+        {
+            if(!keys[i].label)
+                keys[i].label = button2label(keys[i].button);
+        }
+    }
+
+    if(n)
+        *n = nkeys;
+
+    return keys;
+}
+
 void midend_redraw(midend *me)
 {
     assert(me->drawing);
--- a/mines.c
+++ b/mines.c
@@ -3191,6 +3191,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/misc.c
+++ b/misc.c
@@ -21,6 +21,15 @@
     sfree(cfg);
 }
 
+void free_keys(key_label *keys, int nkeys)
+{
+    int i;
+
+    for(i = 0; i < nkeys; i++)
+        sfree(keys->label);
+    sfree(keys);
+}
+
 /*
  * The Mines (among others) game descriptions contain the location of every
  * mine, and can therefore be used to cheat.
@@ -391,6 +400,42 @@
     assert(len <= sz - 1);
     memcpy(buf, str, len);
     buf[sz - 1] = 0;
+}
+
+/* Returns a dynamically allocated label for a generic button.
+ * Game-specific buttons should go into the `label' field of key_label
+ * instead. */
+char *button2label(int button)
+{
+    /* check if it's a keyboard button */
+    if(('A' <= button && button <= 'Z') ||
+       ('a' <= button && button <= 'z') ||
+       ('0' <= button && button <= '9') )
+    {
+        char str[2] = { button, '\0' };
+        return dupstr(str);
+    }
+
+    switch(button)
+    {
+    case CURSOR_UP:
+        return dupstr("Up");
+    case CURSOR_DOWN:
+        return dupstr("Down");
+    case CURSOR_LEFT:
+        return dupstr("Left");
+    case CURSOR_RIGHT:
+        return dupstr("Right");
+    case CURSOR_SELECT:
+        return dupstr("Select");
+    case '\b':
+        return dupstr("Clear");
+    default:
+        fatal("unknown generic key");
+    }
+
+    /* should never get here */
+    return NULL;
 }
 
 /* vim: set shiftwidth=4 tabstop=8: */
--- a/net.c
+++ b/net.c
@@ -3236,6 +3236,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/netslide.c
+++ b/netslide.c
@@ -1866,6 +1866,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/nullgame.c
+++ b/nullgame.c
@@ -285,6 +285,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/palisade.c
+++ b/palisade.c
@@ -1375,6 +1375,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/pattern.c
+++ b/pattern.c
@@ -1998,6 +1998,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/pearl.c
+++ b/pearl.c
@@ -2621,6 +2621,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/pegs.c
+++ b/pegs.c
@@ -1316,6 +1316,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/puzzles.h
+++ b/puzzles.h
@@ -218,6 +218,24 @@
 game_params *preset_menu_lookup_by_id(struct preset_menu *menu, int id);
 
 /*
+ * Small structure specifying a UI button in a keyboardless front
+ * end. The button will have the text of "label" written on it, and
+ * pressing it causes the value "button" to be passed to
+ * midend_process_key() as if typed at the keyboard.
+ *
+ * If `label' is NULL (which it likely will be), a generic label can
+ * be generated with the button2label() function.
+ */
+typedef struct key_label {
+    /* What should be displayed to the user by the frontend. Backends
+     * can set this field to NULL and have it filled in by the midend
+     * with a generic label. Dynamically allocated, but frontends
+     * should probably use free_keys() to free instead. */
+    char *label;
+    int button; /* passed to midend_process_key when button is pressed */
+} key_label;
+
+/*
  * Platform routines
  */
 
@@ -301,6 +319,7 @@
 void midend_restart_game(midend *me);
 void midend_stop_anim(midend *me);
 int midend_process_key(midend *me, int x, int y, int button);
+key_label *midend_request_keys(midend *me, int *nkeys);
 void midend_force_redraw(midend *me);
 void midend_redraw(midend *me);
 float *midend_colours(midend *me, int *ncolours);
@@ -356,6 +375,7 @@
  * misc.c
  */
 void free_cfg(config_item *cfg);
+void free_keys(key_label *keys, int nkeys);
 void obfuscate_bitmap(unsigned char *bmp, int bits, int decode);
 char *fgetline(FILE *fp);
 
@@ -400,6 +420,11 @@
  * less than buffer size. */
 void copy_left_justified(char *buf, size_t sz, const char *str);
 
+/* Returns a generic label based on the value of `button.' To be used
+   whenever a `label' field returned by the request_keys() game
+   function is NULL. Dynamically allocated, to be freed by caller. */
+char *button2label(int button);
+
 /*
  * dsf.c
  */
@@ -610,6 +635,7 @@
     void (*free_ui)(game_ui *ui);
     char *(*encode_ui)(const game_ui *ui);
     void (*decode_ui)(game_ui *ui, const char *encoding);
+    key_label *(*request_keys)(const game_params *params, int *nkeys);
     void (*changed_state)(game_ui *ui, const game_state *oldstate,
                           const game_state *newstate);
     char *(*interpret_move)(const game_state *state, game_ui *ui,
--- a/range.c
+++ b/range.c
@@ -1813,6 +1813,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/rect.c
+++ b/rect.c
@@ -2974,6 +2974,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/samegame.c
+++ b/samegame.c
@@ -1656,6 +1656,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/signpost.c
+++ b/signpost.c
@@ -2246,6 +2246,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/singles.c
+++ b/singles.c
@@ -1828,6 +1828,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/sixteen.c
+++ b/sixteen.c
@@ -1188,6 +1188,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/slant.c
+++ b/slant.c
@@ -2164,6 +2164,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/solo.c
+++ b/solo.c
@@ -3608,6 +3608,26 @@
     return b;
 }
 
+static key_label *game_request_keys(const game_params *params, int *nkeys)
+{
+    int i;
+    int cr = params->c * params->r;
+    key_label *keys = snewn(cr+1, key_label);
+    *nkeys = cr + 1;
+
+    for (i = 0; i < cr; i++) {
+        if (i<9) keys[i].button = '1' + i;
+        else keys[i].button = 'a' + i - 9;
+
+        keys[i].label = NULL;
+    }
+    keys[cr].button = '\b';
+    keys[cr].label = NULL;
+
+
+    return keys;
+}
+
 static char *new_game_desc(const game_params *params, random_state *rs,
 			   char **aux, int interactive)
 {
@@ -5579,6 +5599,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    game_request_keys,
     game_changed_state,
     interpret_move,
     execute_move,
--- a/tents.c
+++ b/tents.c
@@ -2613,6 +2613,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/towers.c
+++ b/towers.c
@@ -864,6 +864,25 @@
     return NULL;
 }
 
+static key_label *game_request_keys(const game_params *params, int *nkeys)
+{
+    int i;
+    int w = params->w;
+    key_label *keys = snewn(w+1, key_label);
+    *nkeys = w + 1;
+
+    for (i = 0; i < w; i++) {
+	if (i<9) keys[i].button = '1' + i;
+	else keys[i].button = 'a' + i - 9;
+
+        keys[i].label = NULL;
+    }
+    keys[w].button = '\b';
+    keys[w].label = NULL;
+
+    return keys;
+}
+
 static game_state *new_game(midend *me, const game_params *params,
                             const char *desc)
 {
@@ -1993,6 +2012,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    game_request_keys,
     game_changed_state,
     interpret_move,
     execute_move,
--- a/tracks.c
+++ b/tracks.c
@@ -2646,6 +2646,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/twiddle.c
+++ b/twiddle.c
@@ -1291,6 +1291,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/undead.c
+++ b/undead.c
@@ -1314,6 +1314,26 @@
     return;
 }
 
+static key_label *game_request_keys(const game_params *params, int *nkeys)
+{
+    key_label *keys = snewn(4, key_label);
+    *nkeys = 4;
+
+    keys[0].button = 'G';
+    keys[0].label = dupstr("Ghost");
+
+    keys[1].button = 'V';
+    keys[1].label = dupstr("Vampire");
+
+    keys[2].button = 'Z';
+    keys[2].label = dupstr("Zombie");
+
+    keys[3].button = '\b';
+    keys[3].label = NULL;
+
+    return keys;
+}
+
 static game_state *new_game(midend *me, const game_params *params,
                             const char *desc)
 {
@@ -2716,6 +2736,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    game_request_keys,
     game_changed_state,
     interpret_move,
     execute_move,
--- a/unequal.c
+++ b/unequal.c
@@ -1280,6 +1280,25 @@
     return NULL;
 }
 
+static key_label *game_request_keys(const game_params *params, int *nkeys)
+{
+    int order = params->order;
+    char off = (order > 9) ? '0' : '1';
+    key_label *keys = snewn(order + 1, key_label);
+    *nkeys = order + 1;
+
+    int i;
+    for(i = 0; i < order; i++) {
+        if (i==10) off = 'a'-10;
+        keys[i].button = i + off;
+        keys[i].label = NULL;
+    }
+    keys[order].button = '\b';
+    keys[order].label = NULL;
+
+    return keys;
+}
+
 static game_state *new_game(midend *me, const game_params *params,
                             const char *desc)
 {
@@ -2011,6 +2030,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    game_request_keys,
     game_changed_state,
     interpret_move,
     execute_move,
--- a/unfinished/group.c
+++ b/unfinished/group.c
@@ -2081,6 +2081,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/unruly.c
+++ b/unruly.c
@@ -1926,6 +1926,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,
--- a/untangle.c
+++ b/untangle.c
@@ -1470,6 +1470,7 @@
     free_ui,
     encode_ui,
     decode_ui,
+    NULL, /* game_request_keys */
     game_changed_state,
     interpret_move,
     execute_move,