ref: a3310ab857f088489b35ebf10733ba284a24d27f
parent: 1d91522babe41fcf7cbfb06633ae6bc5606db367
author: Ben Harris <bjh21@bjh21.me.uk>
date: Sun Dec 4 20:13:26 EST 2022
New backend function: current_key_label() This provides a way for the front end to ask how a particular key should be labelled right now (specifically, for a given game_state and game_ui). This is useful on feature phones where it's conventional to put a small caption above each soft key indicating what it currently does. The function currently provides labels only for CURSOR_SELECT and CURSOR_SELECT2. This is because these are the only keys that need labelling on KaiOS. The concept of labelling keys also turns up in the request_keys() call, but there are quite a few differences. The labels returned by current_key_label() are dynamic and likely to vary with each move, while the labels provided by request_keys() are constant for a given game_params. Also, the keys returned by request_keys() don't generally include CURSOR_SELECT and CURSOR_SELECT2, because those aren't necessary on platforms with pointing devices. It might be possible to provide a unified API covering both of this, but I think it would be quite difficult to work with. Where a key is to be unlabelled, current_key_label() is expected to return an empty string. This leaves open the possibility of NULL indicating a fallback to button2label or the label specified by request_keys() in the future. It's tempting to try to implement current_key_label() by calling interpret_move() and parsing its output. This doesn't work for two reasons. One is that interpret_move() is entitled to modify the game_ui, and there isn't really a practical way to back those changes out. The other is that the information returned by interpret_move() isn't sufficient to generate a label. For instance, in many puzzles it generates moves that toggle the state of a square, but we want the label to reflect which state the square will be toggled to. The result is that I've generally ended up pulling bits of code from interpret_move() and execute_move() together to implement current_key_label(). Alongside the back-end function, there's a midend_current_key_label() that's a thin wrapper around the back-end function. It just adds an assertion about which key's being requested and a default null implementation so that back-ends can avoid defining the function if it will do nothing useful.
--- a/blackbox.c
+++ b/blackbox.c
@@ -532,6 +532,41 @@
ui->newmove = false;
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ if (IS_CURSOR_SELECT(button) && ui->cur_visible && !state->reveal) {
+ int gx = ui->cur_x, gy = ui->cur_y, rangeno = -1;
+ if (gx == 0 && gy == 0 && button == CURSOR_SELECT) return "Check";
+ if (gx >= 1 && gx <= state->w && gy >= 1 && gy <= state->h) {
+ /* Cursor somewhere in the arena. */
+ if (button == CURSOR_SELECT && !(GRID(state, gx,gy) & BALL_LOCK))
+ return (GRID(state, gx, gy) & BALL_GUESS) ? "Clear" : "Ball";
+ if (button == CURSOR_SELECT2)
+ return (GRID(state, gx, gy) & BALL_LOCK) ? "Unlock" : "Lock";
+ }
+ if (grid2range(state, gx, gy, &rangeno)) {
+ if (button == CURSOR_SELECT &&
+ state->exits[rangeno] == LASER_EMPTY)
+ return "Fire";
+ if (button == CURSOR_SELECT2) {
+ int n = 0;
+ /* Row or column lock or unlock. */
+ if (gy == 0 || gy > state->h) { /* Column lock */
+ for (gy = 1; gy <= state->h; gy++)
+ n += !!(GRID(state, gx, gy) & BALL_LOCK);
+ return n > state->h/2 ? "Unlock" : "Lock";
+ } else { /* Row lock */
+ for (gx = 1; gx <= state->w; gx++)
+ n += !!(GRID(state, gx, gy) & BALL_LOCK);
+ return n > state->w/2 ? "Unlock" : "Lock";
+ }
+ }
+ }
+ }
+ return "";
+}
+
#define OFFSET(gx,gy,o) do { \
int off = (4 + (o) % 4) % 4; \
(gx) += offsets[off].x; \
@@ -1543,6 +1578,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
--- a/bridges.c
+++ b/bridges.c
@@ -2138,6 +2138,20 @@
{
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ if (IS_CURSOR_SELECT(button)) {
+ if (!ui->cur_visible)
+ return ""; /* Actually shows cursor. */
+ if (ui->dragging || button == CURSOR_SELECT2)
+ return "Finished";
+ if (GRID(state, ui->cur_x, ui->cur_y) & G_ISLAND)
+ return "Select";
+ }
+ return "";
+}
+
struct game_drawstate {
int tilesize;
int w, h;
@@ -3269,6 +3283,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
--- a/cube.c
+++ b/cube.c
@@ -1774,6 +1774,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ NULL, /* current_key_label */
interpret_move,
execute_move,
PREFERRED_GRID_SCALE, game_compute_size, game_set_size,
--- a/devel.but
+++ b/devel.but
@@ -1692,6 +1692,43 @@
frontends should use \cw{midend_request_keys()}
(\k{midend-request-keys}).
+\S{backend-current-key-label} \cw{current_key_label()}
+
+\c const char *(*current_key_label)(const game_ui *ui,
+\c const game_state *state, int button);
+
+This function is called to ask the back-end how certain keys should be
+labelled on platforms (such a feature phones) where this is
+conventional.
+These labels are expected to reflect what the keys will do right now,
+so they can change depending on the game and UI state.
+
+The \c{ui} and \c{state} arguments describe the state of the game for
+which key labels are required.
+The \c{button} argument is the same as the one passed to
+\cw{interpret_move()}.
+At present, the only values of \c{button} that can be passed to
+\cw{current_key_label()} are \cw{CURSOR_SELECT} and \cw{CURSOR_SELECT2}.
+The return value is a short string describing what the requested key
+will do if pressed.
+Usually the string should be a static string constant.
+If it's really necessary to use a dynamically-allocated string, it
+should remain valid until the next call to \cw{current_key_label()} or
+\cw{free_ui()} with the same \cw{game_ui} (so it can be referenced from
+the \cw{game_ui} and freed at the next one of those calls).
+
+There's no fixed upper limit on the length of string that this
+function can return, but more than about 12 characters is likely to
+cause problems for front-ends. If two buttons have the same effect,
+their labels should be identical so that the front end can detect
+this. Similarly, keys that do different things should have different
+labels. The label should be an empty string (\cw{""}) if the key does
+nothing.
+
+Like \cw{request_keys()}, the \cw{current_key_label} pointer in the
+\c{game} structure is allowed to be \cw{NULL}, in which case the
+mid-end will treat it as though it always returned \cw{""}.
+
\S{backend-flags} \c{flags}
\c int flags;
@@ -3231,6 +3268,20 @@
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-current-key-label} \cw{midend_current_key_label()}
+
+\c const char *midend_current_key_label(midend *me, int button);
+
+This is a thin wrapper around the backend's \cw{current_key_label()}
+function (\k{backend-current-key-label}). Front ends that need to
+label \cw{CURSOR_SELECT} or \cw{CURSOR_SELECT2} should call this
+function after each move (at least after each call to
+\cw{midend_process_key()}) to get the current labels. The front end
+should arrange to copy the returned string somewhere before the next
+call to the mid-end, just in case it's dynamically allocated. If the
+button supplied does nothing, the label returned will be an empty
+string.
\H{midend-colours} \cw{midend_colours()}
--- a/dominosa.c
+++ b/dominosa.c
@@ -2734,6 +2734,33 @@
ui->cur_visible = false;
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ if (IS_CURSOR_SELECT(button)) {
+ int d1, d2, w = state->w;
+
+ if (!((ui->cur_x ^ ui->cur_y) & 1))
+ return ""; /* must have exactly one dimension odd */
+ d1 = (ui->cur_y / 2) * w + (ui->cur_x / 2);
+ d2 = ((ui->cur_y+1) / 2) * w + ((ui->cur_x+1) / 2);
+
+ /* We can't mark an edge next to any domino. */
+ if (button == CURSOR_SELECT2 &&
+ (state->grid[d1] != d1 || state->grid[d2] != d2))
+ return "";
+ if (button == CURSOR_SELECT) {
+ if (state->grid[d1] == d2) return "Remove";
+ return "Place";
+ } else {
+ int edge = d2 == d1 + 1 ? EDGE_R : EDGE_B;
+ if (state->edges[d1] & edge) return "Remove";
+ return "Line";
+ }
+ }
+ return "";
+}
+
#define PREFERRED_TILESIZE 32
#define TILESIZE (ds->tilesize)
#define BORDER (TILESIZE * 3 / 4)
@@ -3417,6 +3444,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILESIZE, game_compute_size, game_set_size,
--- a/fifteen.c
+++ b/fifteen.c
@@ -1110,6 +1110,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ NULL, /* current_key_label */
interpret_move,
execute_move,
PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
--- a/filling.c
+++ b/filling.c
@@ -1429,6 +1429,23 @@
ui->keydragging = false;
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ const int w = state->shared->params.w;
+
+ if (IS_CURSOR_SELECT(button) && ui->cur_visible) {
+ if (button == CURSOR_SELECT) {
+ if (ui->keydragging) return "Stop";
+ return "Multiselect";
+ }
+ if (button == CURSOR_SELECT2 &&
+ !state->shared->clues[w*ui->cur_y + ui->cur_x])
+ return (ui->sel[w*ui->cur_y + ui->cur_x]) ? "Deselect" : "Select";
+ }
+ return "";
+}
+
#define PREFERRED_TILE_SIZE 32
#define TILE_SIZE (ds->tilesize)
#define BORDER (TILE_SIZE / 2)
@@ -2166,6 +2183,7 @@
decode_ui,
game_request_keys,
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
--- a/flip.c
+++ b/flip.c
@@ -928,6 +928,13 @@
{
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ if (IS_CURSOR_SELECT(button)) return "Flip";
+ return "";
+}
+
struct game_drawstate {
int w, h;
bool started;
@@ -1346,6 +1353,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
--- a/flood.c
+++ b/flood.c
@@ -793,6 +793,18 @@
{
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ if (button == CURSOR_SELECT &&
+ state->grid[0] != state->grid[ui->cy*state->w+ui->cx])
+ return "Fill";
+ if (button == CURSOR_SELECT2 &&
+ state->soln && state->solnpos < state->soln->nmoves)
+ return "Advance";
+ return "";
+}
+
struct game_drawstate {
bool started;
int tilesize;
@@ -1357,6 +1369,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILESIZE, game_compute_size, game_set_size,
--- a/galaxies.c
+++ b/galaxies.c
@@ -2539,6 +2539,33 @@
return true;
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ space *sp;
+
+ if (IS_CURSOR_SELECT(button) && ui->cur_visible) {
+ sp = &SPACE(state, ui->cur_x, ui->cur_y);
+ if (ui->dragging) {
+ if (ui->cur_x == ui->srcx && ui->cur_y == ui->srcy)
+ return "Cancel";
+ if (ok_to_add_assoc_with_opposite(
+ state, &SPACE(state, ui->cur_x, ui->cur_y),
+ &SPACE(state, ui->dotx, ui->doty)))
+ return "Place";
+ return (ui->srcx == ui->dotx && ui->srcy == ui->doty) ?
+ "Cancel" : "Remove";
+ } else if (sp->flags & F_DOT)
+ return "New arrow";
+ else if (sp->flags & F_TILE_ASSOC)
+ return "Move arrow";
+ else if (sp->type == s_edge &&
+ edge_placement_legal(state, ui->cur_x, ui->cur_y))
+ return (sp->flags & F_EDGE_SET) ? "Clear" : "Edge";
+ }
+ return "";
+}
+
static char *interpret_move(const game_state *state, game_ui *ui,
const game_drawstate *ds,
int x, int y, int button)
@@ -3813,6 +3840,11 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+#ifdef EDITOR
+ NULL,
+#else
+ current_key_label,
+#endif
interpret_move,
execute_move,
PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
--- a/guess.c
+++ b/guess.c
@@ -510,6 +510,19 @@
ui->peg_cur = 0;
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ if (state->solved) return "";
+ if (button == CURSOR_SELECT) {
+ if (ui->peg_cur == state->params.npegs) return "Submit";
+ return "Place";
+ }
+ if (button == CURSOR_SELECT2 && ui->peg_cur != state->params.npegs)
+ return "Hold";
+ return "";
+}
+
#define PEGSZ (ds->pegsz)
#define PEGOFF (ds->pegsz + ds->gapsz)
#define HINTSZ (ds->hintsz)
@@ -1522,6 +1535,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PEG_PREFER_SZ, game_compute_size, game_set_size,
--- a/inertia.c
+++ b/inertia.c
@@ -1545,6 +1545,15 @@
ui->just_made_move = false;
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ if (IS_CURSOR_SELECT(button) &&
+ state->soln && state->solnpos < state->soln->len)
+ return "Advance";
+ return "";
+}
+
struct game_drawstate {
game_params p;
int tilesize;
@@ -2233,6 +2242,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILESIZE, game_compute_size, game_set_size,
--- a/keen.c
+++ b/keen.c
@@ -1569,6 +1569,14 @@
}
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ if (ui->hshow && (button == CURSOR_SELECT))
+ return ui->hpencil ? "Ink" : "Pencil";
+ return "";
+}
+
#define PREFERRED_TILESIZE 48
#define TILESIZE (ds->tilesize)
#define BORDER (TILESIZE / 2)
@@ -2477,6 +2485,7 @@
decode_ui,
game_request_keys,
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILESIZE, game_compute_size, game_set_size,
--- a/lightup.c
+++ b/lightup.c
@@ -1855,6 +1855,26 @@
ui->cur_visible = false;
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ int cx = ui->cur_x, cy = ui->cur_y;
+ unsigned int flags = GRID(state, flags, cx, cy);
+
+ if (!ui->cur_visible) return "";
+ if (button == CURSOR_SELECT) {
+ if (flags & (F_BLACK | F_IMPOSSIBLE)) return "";
+ if (flags & F_LIGHT) return "Clear";
+ return "Light";
+ }
+ if (button == CURSOR_SELECT2) {
+ if (flags & (F_BLACK | F_LIGHT)) return "";
+ if (flags & F_IMPOSSIBLE) return "Clear";
+ return "Mark";
+ }
+ return "";
+}
+
#define DF_BLACK 1 /* black square */
#define DF_NUMBERED 2 /* black square with number */
#define DF_LIT 4 /* display (white) square lit up */
@@ -2324,6 +2344,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
--- a/loopy.c
+++ b/loopy.c
@@ -3677,6 +3677,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ NULL, /* current_key_label */
interpret_move,
execute_move,
PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
--- a/magnets.c
+++ b/magnets.c
@@ -1745,6 +1745,36 @@
ui->cur_visible = false;
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ int idx;
+
+ if (IS_CURSOR_SELECT(button)) {
+ if (!ui->cur_visible) return "";
+ idx = ui->cur_y * state->w + ui->cur_x;
+ if (button == CURSOR_SELECT) {
+ if (state->grid[idx] == NEUTRAL && state->flags[idx] & GS_SET)
+ return "";
+ switch (state->grid[idx]) {
+ case EMPTY: return "+";
+ case POSITIVE: return "-";
+ case NEGATIVE: return "Clear";
+ }
+ }
+ if (button == CURSOR_SELECT2) {
+ if (state->grid[idx] != NEUTRAL) return "";
+ if (state->flags[idx] & GS_SET) /* neutral */
+ return "?";
+ if (state->flags[idx] & GS_NOTNEUTRAL) /* !neutral */
+ return "Clear";
+ else
+ return "X";
+ }
+ }
+ return "";
+}
+
struct game_drawstate {
int tilesize;
bool started, solved;
@@ -2431,6 +2461,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
--- a/map.c
+++ b/map.c
@@ -2392,6 +2392,26 @@
EPSILON_Y(ui->cur_lastmove));
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ int r;
+
+ if (IS_CURSOR_SELECT(button) && ui->cur_visible) {
+ if (ui->drag_colour == -2) return "Pick";
+ r = region_from_ui_cursor(state, ui);
+ if (state->map->immutable[r]) return "Cancel";
+ if (!ui->cur_moved) return ui->drag_pencil ? "Cancel" : "Clear";
+ if (button == CURSOR_SELECT2) {
+ if (state->colouring[r] >= 0) return "Cancel";
+ if (ui->drag_colour >= 0) return "Stipple";
+ }
+ if (ui->drag_pencil) return "Stipple";
+ return ui->drag_colour >= 0 ? "Fill" : "Clear";
+ }
+ return "";
+}
+
static char *interpret_move(const game_state *state, game_ui *ui,
const game_drawstate *ds,
int x, int y, int button)
@@ -3257,6 +3277,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
20, game_compute_size, game_set_size,
--- a/midend.c
+++ b/midend.c
@@ -1220,6 +1220,15 @@
return keys;
}
+/* Return a good label to show next to a key right now. */
+const char *midend_current_key_label(midend *me, int button)
+{
+ assert(IS_CURSOR_SELECT(button));
+ if (!me->ourgame->current_key_label) return "";
+ return me->ourgame->current_key_label(
+ me->ui, me->states[me->statepos-1].state, button);
+}
+
void midend_redraw(midend *me)
{
assert(me->drawing);
--- a/mines.c
+++ b/mines.c
@@ -2400,6 +2400,35 @@
ui->completed = true;
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ int cx = ui->cur_x, cy = ui->cur_y;
+ int v = state->grid[cy * state->w + cx];
+
+ if (state->dead || state->won || !ui->cur_visible) return "";
+ if (button == CURSOR_SELECT2) {
+ if (v == -2) return "Mark";
+ if (v == -1) return "Unmark";
+ return "";
+ }
+ if (button == CURSOR_SELECT) {
+ int dy, dx, n = 0;
+ if (v == -2 || v == -3) return "Uncover";
+ if (v == 0) return "";
+ /* Count mine markers. */
+ for (dy = -1; dy <= +1; dy++)
+ for (dx = -1; dx <= +1; dx++)
+ if (cx+dx >= 0 && cx+dx < state->w &&
+ cy+dy >= 0 && cy+dy < state->h) {
+ if (state->grid[(cy+dy)*state->w+(cx+dx)] == -1)
+ n++;
+ }
+ if (n == v) return "Clear";
+ }
+ return "";
+}
+
struct game_drawstate {
int w, h, tilesize, bg;
bool started;
@@ -3208,6 +3237,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
--- a/mosaic.c
+++ b/mosaic.c
@@ -1033,6 +1033,26 @@
{
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ char *cell;
+
+ if (IS_CURSOR_SELECT(button)) {
+ if (!ui->cur_visible || state->not_completed_clues == 0) return "";
+ cell = get_coords(state, state->cells_contents, ui->cur_x, ui->cur_y);
+ switch (*cell & STATE_OK_NUM) {
+ case STATE_UNMARKED:
+ return button == CURSOR_SELECT ? "Black" : "White";
+ case STATE_MARKED:
+ return button == CURSOR_SELECT ? "White" : "Empty";
+ case STATE_BLANK:
+ return button == CURSOR_SELECT ? "Empty" : "Black";
+ }
+ }
+ return "";
+}
+
static char *interpret_move(const game_state *state, game_ui *ui,
const game_drawstate *ds, int x, int y,
int button)
@@ -1598,6 +1618,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
DEFAULT_TILE_SIZE, game_compute_size, game_set_size,
--- a/net.c
+++ b/net.c
@@ -2053,6 +2053,18 @@
{
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ if (tile(state, ui->cur_x, ui->cur_y) & LOCKED) {
+ if (button == CURSOR_SELECT2) return "Unlock";
+ } else {
+ if (button == CURSOR_SELECT) return "Rotate";
+ if (button == CURSOR_SELECT2) return "Lock";
+ }
+ return "";
+}
+
struct game_drawstate {
int width, height;
int tilesize;
@@ -3254,6 +3266,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
--- a/netslide.c
+++ b/netslide.c
@@ -1050,6 +1050,14 @@
int cur_x, cur_y;
};
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ if (IS_CURSOR_SELECT(button) && ui->cur_visible)
+ return "Slide";
+ return "";
+}
+
static char *interpret_move(const game_state *state, game_ui *ui,
const game_drawstate *ds,
int x, int y, int button)
@@ -1875,6 +1883,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
--- a/nullgame.c
+++ b/nullgame.c
@@ -287,6 +287,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ NULL, /* current_key_label */
interpret_move,
execute_move,
20 /* FIXME */, game_compute_size, game_set_size,
--- a/palisade.c
+++ b/palisade.c
@@ -1392,6 +1392,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ NULL, /* current_key_label */
interpret_move,
execute_move,
48, game_compute_size, game_set_size,
--- a/pattern.c
+++ b/pattern.c
@@ -1241,6 +1241,23 @@
{
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ if (IS_CURSOR_SELECT(button)) {
+ if (!ui->cur_visible) return "";
+ switch (state->grid[ui->cur_y * state->common->w + ui->cur_x]) {
+ case GRID_UNKNOWN:
+ return button == CURSOR_SELECT ? "Black" : "White";
+ case GRID_FULL:
+ return button == CURSOR_SELECT ? "White" : "Grey";
+ case GRID_EMPTY:
+ return button == CURSOR_SELECT ? "Grey" : "Black";
+ }
+ }
+ return "";
+}
+
struct game_drawstate {
bool started;
int w, h;
@@ -2025,6 +2042,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
--- a/pearl.c
+++ b/pearl.c
@@ -1847,6 +1847,20 @@
{
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ if (IS_CURSOR_SELECT(button) && ui->cursor_active) {
+ if (button == CURSOR_SELECT) {
+ if (ui->ndragcoords == -1) return "Start";
+ return "Stop";
+ }
+ if (button == CURSOR_SELECT2 && ui->ndragcoords >= 0)
+ return "Cancel";
+ }
+ return "";
+}
+
#define PREFERRED_TILE_SIZE 31
#define HALFSZ (ds->halfsz)
#define TILE_SIZE (ds->halfsz*2 + 1)
@@ -2661,6 +2675,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
--- a/pegs.c
+++ b/pegs.c
@@ -800,6 +800,19 @@
ui->cur_jumping = false;
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ int w = state->w;
+
+ if (IS_CURSOR_SELECT(button)) {
+ if (!ui->cur_visible) return "";
+ if (ui->cur_jumping) return "Cancel";
+ if (state->grid[ui->cur_y*w+ui->cur_x] == GRID_PEG) return "Select";
+ }
+ return "";
+}
+
#define PREFERRED_TILE_SIZE 33
#define TILESIZE (ds->tilesize)
#define BORDER (TILESIZE / 2)
@@ -1338,6 +1351,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
--- a/puzzles.h
+++ b/puzzles.h
@@ -307,6 +307,7 @@
void midend_stop_anim(midend *me);
bool midend_process_key(midend *me, int x, int y, int button, bool *handled);
key_label *midend_request_keys(midend *me, int *nkeys);
+const char *midend_current_key_label(midend *me, int button);
void midend_force_redraw(midend *me);
void midend_redraw(midend *me);
float *midend_colours(midend *me, int *ncolours);
@@ -657,6 +658,8 @@
key_label *(*request_keys)(const game_params *params, int *nkeys);
void (*changed_state)(game_ui *ui, const game_state *oldstate,
const game_state *newstate);
+ const char *(*current_key_label)(const game_ui *ui,
+ const game_state *state, int button);
char *(*interpret_move)(const game_state *state, game_ui *ui,
const game_drawstate *ds, int x, int y, int button);
game_state *(*execute_move)(const game_state *state, const char *move);
--- a/range.c
+++ b/range.c
@@ -1245,6 +1245,27 @@
{
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ int cell;
+
+ if (IS_CURSOR_SELECT(button)) {
+ cell = state->grid[idx(ui->r, ui->c, state->params.w)];
+ if (!ui->cursor_show || cell > 0) return "";
+ switch (cell) {
+ case EMPTY:
+ return button == CURSOR_SELECT ? "Fill" : "Dot";
+ case WHITE:
+ return button == CURSOR_SELECT ? "Empty" : "Fill";
+ case BLACK:
+ return button == CURSOR_SELECT ? "Dot" : "Empty";
+ }
+ }
+ return "";
+
+}
+
typedef struct drawcell {
puzzle_size value;
bool error, cursor, flash;
@@ -1818,6 +1839,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
--- a/rect.c
+++ b/rect.c
@@ -2374,6 +2374,21 @@
unsigned long *visible;
};
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ if (IS_CURSOR_SELECT(button) && ui->cur_visible &&
+ !(ui->drag_start_x >= 0 && !ui->cur_dragging)) {
+ if (ui->cur_dragging) {
+ if (!ui->dragged) return "Cancel";
+ if ((button == CURSOR_SELECT2) == ui->erasing) return "Done";
+ return "Cancel";
+ }
+ return button == CURSOR_SELECT ? "Mark" : "Erase";
+ }
+ return "";
+}
+
static char *interpret_move(const game_state *from, game_ui *ui,
const game_drawstate *ds,
int x, int y, int button)
@@ -2992,6 +3007,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
--- a/samegame.c
+++ b/samegame.c
@@ -1109,6 +1109,25 @@
ui->displaysel = false;
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ if (IS_CURSOR_SELECT(button)) {
+ int x = ui->xsel, y = ui->ysel, c = COL(state,x,y);
+ if (c == 0) return "";
+ if (ISSEL(ui, x, y))
+ return button == CURSOR_SELECT2 ? "Unselect" : "Remove";
+ if ((x > 0 && COL(state,x-1,y) == c) ||
+ (x+1 < state->params.w && COL(state,x+1,y) == c) ||
+ (y > 0 && COL(state,x,y-1) == c) ||
+ (y+1 < state->params.h && COL(state,x,y+1) == c))
+ return "Select";
+ /* Cursor is over a lone square, so we can't select it. */
+ if (ui->nselected) return "Unselect";
+ }
+ return "";
+}
+
static char *sel_movedesc(game_ui *ui, const game_state *state)
{
int i;
@@ -1670,6 +1689,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
--- a/signpost.c
+++ b/signpost.c
@@ -1421,6 +1421,26 @@
}
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ if (IS_CURSOR_SELECT(button) && ui->cshow) {
+ if (ui->dragging) {
+ if (ui->drag_is_from) {
+ if (isvalidmove(state, false, ui->sx, ui->sy, ui->cx, ui->cy))
+ return "To here";
+ } else {
+ if (isvalidmove(state, false, ui->cx, ui->cy, ui->sx, ui->sy))
+ return "From here";
+ }
+ return "Cancel";
+ } else {
+ return button == CURSOR_SELECT ? "From here" : "To here";
+ }
+ }
+ return "";
+}
+
struct game_drawstate {
int tilesize;
bool started, solved;
@@ -2277,6 +2297,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
--- a/singles.c
+++ b/singles.c
@@ -1473,6 +1473,18 @@
ui->cshow = false;
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ if (IS_CURSOR_SELECT(button) && ui->cshow) {
+ unsigned int f = state->flags[ui->cy * state->w + ui->cx];
+ if (f & F_BLACK) return "Restore";
+ if (f & F_CIRCLE) return "Remove";
+ return button == CURSOR_SELECT ? "Black" : "Circle";
+ }
+ return "";
+}
+
#define DS_BLACK 0x1
#define DS_CIRCLE 0x2
#define DS_CURSOR 0x4
@@ -1853,6 +1865,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
--- a/sixteen.c
+++ b/sixteen.c
@@ -591,6 +591,21 @@
{
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ if (IS_CURSOR_SELECT(button) && ui->cur_visible) {
+ if (ui->cur_x == -1 || ui->cur_x == state->w ||
+ ui->cur_y == -1 || ui->cur_y == state->h)
+ return button == CURSOR_SELECT2 ? "Back" : "Slide";
+ if (button == CURSOR_SELECT)
+ return ui->cur_mode == lock_tile ? "Unlock" : "Lock tile";
+ if (button == CURSOR_SELECT2)
+ return ui->cur_mode == lock_position ? "Unlock" : "Lock pos";
+ }
+ return "";
+}
+
struct game_drawstate {
bool started;
int w, h, bgcolour;
@@ -1197,6 +1212,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
--- a/slant.c
+++ b/slant.c
@@ -1603,6 +1603,22 @@
{
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ if (IS_CURSOR_SELECT(button) && ui->cur_visible) {
+ switch (state->soln[ui->cur_y*state->p.w+ui->cur_x]) {
+ case 0:
+ return button == CURSOR_SELECT ? "\\" : "/";
+ case -1:
+ return button == CURSOR_SELECT ? "/" : "Blank";
+ case +1:
+ return button == CURSOR_SELECT ? "Blank" : "\\";
+ }
+ }
+ return "";
+}
+
#define PREFERRED_TILESIZE 32
#define TILESIZE (ds->tilesize)
#define BORDER TILESIZE
@@ -2175,6 +2191,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILESIZE, game_compute_size, game_set_size,
--- a/solo.c
+++ b/solo.c
@@ -4594,6 +4594,14 @@
}
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ if (ui->hshow && (button == CURSOR_SELECT))
+ return ui->hpencil ? "Ink" : "Pencil";
+ return "";
+}
+
struct game_drawstate {
bool started, xtype;
int cr;
@@ -5618,6 +5626,7 @@
decode_ui,
game_request_keys,
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
--- a/tents.c
+++ b/tents.c
@@ -1459,6 +1459,22 @@
{
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ int w = state->p.w;
+ int v = state->grid[ui->cy*w+ui->cx];
+
+ if (IS_CURSOR_SELECT(button) && ui->cdisp) {
+ switch (v) {
+ case BLANK:
+ return button == CURSOR_SELECT ? "Tent" : "Green";
+ case TENT: case NONTENT: return "Clear";
+ }
+ }
+ return "";
+}
+
struct game_drawstate {
int tilesize;
bool started;
@@ -2626,6 +2642,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILESIZE, game_compute_size, game_set_size,
--- a/towers.c
+++ b/towers.c
@@ -1198,6 +1198,14 @@
}
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ if (ui->hshow && (button == CURSOR_SELECT))
+ return ui->hpencil ? "Ink" : "Pencil";
+ return "";
+}
+
#define PREFERRED_TILESIZE 48
#define TILESIZE (ds->tilesize)
#define BORDER (TILESIZE * 9 / 8)
@@ -2048,6 +2056,7 @@
decode_ui,
game_request_keys,
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILESIZE, game_compute_size, game_set_size,
--- a/tracks.c
+++ b/tracks.c
@@ -2121,6 +2121,35 @@
return true;
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ if (IS_CURSOR_SELECT(button) && ui->cursor_active) {
+ int gx = ui->curx / 2, gy = ui->cury / 2;
+ int w = state->p.w;
+ int direction =
+ ((ui->curx % 2) == 0) ? L : ((ui->cury % 2) == 0) ? U : 0;
+ if (direction &&
+ ui_can_flip_edge(state, gx, gy, direction,
+ button == CURSOR_SELECT2)) {
+ unsigned ef = S_E_FLAGS(state, gx, gy, direction);
+ switch (button) {
+ case CURSOR_SELECT: return (ef & E_TRACK) ? "Clear" : "Track";
+ case CURSOR_SELECT2: return (ef & E_NOTRACK) ? "Clear" : "X";
+ }
+ }
+ if (!direction &&
+ ui_can_flip_square(state, gx, gy, button == CURSOR_SELECT2)) {
+ unsigned sf = state->sflags[gy*w+gx];
+ switch (button) {
+ case CURSOR_SELECT: return (sf & S_TRACK) ? "Clear" : "Track";
+ case CURSOR_SELECT2: return (sf & S_NOTRACK) ? "Clear" : "X";
+ }
+ }
+ }
+ return "";
+}
+
static char *edge_flip_str(const game_state *state, int x, int y, int dir, bool notrack, char *buf) {
unsigned ef = S_E_FLAGS(state, x, y, dir);
char c;
@@ -2973,6 +3002,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
--- a/twiddle.c
+++ b/twiddle.c
@@ -637,6 +637,17 @@
{
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ if (!ui->cur_visible) return "";
+ switch (button) {
+ case CURSOR_SELECT: return "Turn left";
+ case CURSOR_SELECT2: return "Turn right";
+ }
+ return "";
+}
+
struct game_drawstate {
bool started;
int w, h, bgcolour;
@@ -1313,6 +1324,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
--- a/undead.c
+++ b/undead.c
@@ -1681,6 +1681,20 @@
}
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ int xi;
+
+ if (ui->hshow && button == CURSOR_SELECT)
+ return ui->hpencil ? "Ink" : "Pencil";
+ if (button == CURSOR_SELECT2) {
+ xi = state->common->xinfo[ui->hx + ui->hy*(state->common->params.w+2)];
+ if (xi >= 0 && !state->common->fixed[xi]) return "Clear";
+ }
+ return "";
+}
+
struct game_drawstate {
int tilesize;
bool started, solved;
@@ -2783,6 +2797,7 @@
decode_ui,
game_request_keys,
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
--- a/unequal.c
+++ b/unequal.c
@@ -1470,6 +1470,14 @@
}
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ if (ui->hshow && IS_CURSOR_SELECT(button))
+ return ui->hpencil ? "Ink" : "Pencil";
+ return "";
+}
+
struct game_drawstate {
int tilesize, order;
bool started;
@@ -2139,6 +2147,7 @@
decode_ui,
game_request_keys,
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
--- a/unfinished/group.c
+++ b/unfinished/group.c
@@ -1326,6 +1326,26 @@
}
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ if (ui->hshow && button == CURSOR_SELECT)
+ return ui->hpencil ? "Ink" : "Pencil";
+ if (ui->hshow && button == CURSOR_SELECT2) {
+ int w = state->par.w;
+ int i;
+ for (i = 0; i < ui->odn; i++) {
+ int x = state->sequence[ui->ohx + i*ui->odx];
+ int y = state->sequence[ui->ohy + i*ui->ody];
+ int index = y*w+x;
+ if (ui->hpencil && state->grid[index]) return "";
+ if (state->common->immutable[index]) return "";
+ }
+ return "Clear";
+ }
+ return "";
+}
+
#define PREFERRED_TILESIZE 48
#define TILESIZE (ds->tilesize)
#define BORDER (TILESIZE / 2)
@@ -2311,6 +2331,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
PREFERRED_TILESIZE, game_compute_size, game_set_size,
--- a/unfinished/separate.c
+++ b/unfinished/separate.c
@@ -845,6 +845,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ NULL, /* current_key_label */
interpret_move,
execute_move,
20 /* FIXME */, game_compute_size, game_set_size,
--- a/unfinished/slide.c
+++ b/unfinished/slide.c
@@ -2337,6 +2337,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ NULL, /* current_key_label */
interpret_move,
execute_move,
PREFERRED_TILESIZE, game_compute_size, game_set_size,
--- a/unfinished/sokoban.c
+++ b/unfinished/sokoban.c
@@ -1459,6 +1459,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ NULL, /* current_key_label */
interpret_move,
execute_move,
PREFERRED_TILESIZE, game_compute_size, game_set_size,
--- a/unruly.c
+++ b/unruly.c
@@ -1535,6 +1535,27 @@
{
}
+static const char *current_key_label(const game_ui *ui,
+ const game_state *state, int button)
+{
+ int hx = ui->cx, hy = ui->cy;
+ int w2 = state->w2;
+ char i = state->grid[hy * w2 + hx];
+
+ if (ui->cursor && IS_CURSOR_SELECT(button)) {
+ if (state->common->immutable[hy * w2 + hx]) return "";
+ switch (i) {
+ case EMPTY:
+ return button == CURSOR_SELECT ? "Black" : "White";
+ case N_ONE:
+ return button == CURSOR_SELECT ? "White" : "Empty";
+ case N_ZERO:
+ return button == CURSOR_SELECT ? "Empty" : "Black";
+ }
+ }
+ return "";
+}
+
struct game_drawstate {
int tilesize;
int w2, h2;
@@ -2021,6 +2042,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ current_key_label,
interpret_move,
execute_move,
DEFAULT_TILE_SIZE, game_compute_size, game_set_size,
--- a/untangle.c
+++ b/untangle.c
@@ -1481,6 +1481,7 @@
decode_ui,
NULL, /* game_request_keys */
game_changed_state,
+ NULL, /* current_key_label */
interpret_move,
execute_move,
PREFERRED_TILESIZE, game_compute_size, game_set_size,