shithub: puzzles

Download patch

ref: eeb2db283de9115f7256fa4cc49597d63e06b0ab
parent: edcf839d4c557c3993d681665829390697353344
author: Simon Tatham <anakin@pobox.com>
date: Sun Oct 1 08:52:12 EDT 2017

New name UI_UPDATE for interpret_move's return "".

Now midend.c directly tests the returned pointer for equality to this
value, instead of checking whether it's the empty string.

A minor effect of this is that games may now return a dynamically
allocated empty string from interpret_move() and treat it as just
another legal move description. But I don't expect anyone to be
perverse enough to actually do that! The main purpose is that it
avoids returning a string literal from a function whose return type is
a pointer to _non-const_ char, i.e. we are now one step closer to
being able to make this code base clean under -Wwrite-strings.

--- a/blackbox.c
+++ b/blackbox.c
@@ -902,7 +902,7 @@
         ui->cur_x = cx;
         ui->cur_y = cy;
         ui->cur_visible = 1;
-        return "";
+        return UI_UPDATE;
     }
 
     if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
@@ -912,7 +912,7 @@
         wouldflash = 1;
     } else if (button == LEFT_RELEASE) {
         ui->flash_laser = 0;
-        return "";
+        return UI_UPDATE;
     } else if (IS_CURSOR_SELECT(button)) {
         if (ui->cur_visible) {
             gx = ui->cur_x;
@@ -921,7 +921,7 @@
             wouldflash = 2;
         } else {
             ui->cur_visible = 1;
-            return "";
+            return UI_UPDATE;
         }
         /* Fix up 'button' for the below logic. */
         if (button == CURSOR_SELECT2) button = RIGHT_BUTTON;
@@ -970,9 +970,9 @@
 	    return nullret;
         ui->flash_laserno = rangeno;
         ui->flash_laser = wouldflash;
-        nullret = "";
+        nullret = UI_UPDATE;
         if (state->exits[rangeno] != LASER_EMPTY)
-            return "";
+            return UI_UPDATE;
         sprintf(buf, "F%d", rangeno);
         break;
 
--- a/bridges.c
+++ b/bridges.c
@@ -2094,7 +2094,7 @@
     ui->dragx_src = ui->dragy_src = -1;
     ui->dragx_dst = ui->dragy_dst = -1;
     ui->dragging = 0;
-    return "";
+    return UI_UPDATE;
 }
 
 static game_ui *new_ui(const game_state *state)
@@ -2282,7 +2282,7 @@
     /*debug(("update_drag src (%d,%d) d(%d,%d) dst (%d,%d)\n",
            ui->dragx_src, ui->dragy_src, dx, dy,
            ui->dragx_dst, ui->dragy_dst));*/
-    return "";
+    return UI_UPDATE;
 }
 
 static char *finish_drag(const game_state *state, game_ui *ui)
@@ -2325,7 +2325,7 @@
         if (ggrid & G_ISLAND) {
             ui->dragx_src = gx;
             ui->dragy_src = gy;
-            return "";
+            return UI_UPDATE;
         } else
             return ui_cancel_drag(ui);
     } else if (button == LEFT_DRAG || button == RIGHT_DRAG) {
@@ -2339,7 +2339,7 @@
             /* cancel a drag when we go back to the starting point */
             ui->dragx_dst = -1;
             ui->dragy_dst = -1;
-            return "";
+            return UI_UPDATE;
         }
     } else if (button == LEFT_RELEASE || button == RIGHT_RELEASE) {
         if (ui->dragging) {
@@ -2424,7 +2424,7 @@
 
                     if (!dingrid) break;
                 }
-                if (!oingrid) return "";
+                if (!oingrid) return UI_UPDATE;
             }
             /* not reached */
 
@@ -2431,12 +2431,12 @@
 found:
             ui->cur_x = nx;
             ui->cur_y = ny;
-            return "";
+            return UI_UPDATE;
         }
     } else if (IS_CURSOR_SELECT(button)) {
         if (!ui->cur_visible) {
             ui->cur_visible = 1;
-            return "";
+            return UI_UPDATE;
         }
         if (ui->dragging || button == CURSOR_SELECT2) {
             ui_cancel_drag(ui);
@@ -2444,7 +2444,7 @@
                 sprintf(buf, "M%d,%d", ui->cur_x, ui->cur_y);
                 return dupstr(buf);
             } else
-                return "";
+                return UI_UPDATE;
         } else {
             grid_type v = GRID(state, ui->cur_x, ui->cur_y);
             if (v & G_ISLAND) {
@@ -2453,7 +2453,7 @@
                 ui->dragy_src = ui->cur_y;
                 ui->dragx_dst = ui->dragy_dst = -1;
                 ui->drag_is_noline = (button == CURSOR_SELECT2) ? 1 : 0;
-                return "";
+                return UI_UPDATE;
             }
         }
     } else if ((button >= '0' && button <= '9') ||
@@ -2471,7 +2471,7 @@
 
         if (!ui->cur_visible) {
             ui->cur_visible = 1;
-            return "";
+            return UI_UPDATE;
         }
 
         for (i = 0; i < state->n_islands; ++i) {
@@ -2498,12 +2498,12 @@
         if (best_x != -1 && best_y != -1) {
             ui->cur_x = best_x;
             ui->cur_y = best_y;
-            return "";
+            return UI_UPDATE;
         } else
             return NULL;
     } else if (button == 'g' || button == 'G') {
         ui->show_hints = 1 - ui->show_hints;
-        return "";
+        return UI_UPDATE;
     }
 
     return NULL;
--- a/devel.but
+++ b/devel.but
@@ -907,10 +907,10 @@
 in response to the input event; the puzzle was not interested in it
 at all.
 
-\b Returning the empty string (\cw{""}) indicates that the input
+\b Returning the special value \cw{UI_UPDATE} indicates that the input
 event has resulted in a change being made to the \c{game_ui} which
-will require a redraw of the game window, but that no actual
-\e{move} was made (i.e. no new \c{game_state} needs to be created).
+will require a redraw of the game window, but that no actual \e{move}
+was made (i.e. no new \c{game_state} needs to be created).
 
 \b Returning anything else indicates that a move was made and that a
 new \c{game_state} must be created. However, instead of actually
@@ -925,7 +925,7 @@
 
 The return value from \cw{interpret_move()} is expected to be
 dynamically allocated if and only if it is not either \cw{NULL}
-\e{or} the empty string.
+\e{or} the special string constant \c{UI_UPDATE}.
 
 After this function is called, the back end is permitted to rely on
 some subsequent operations happening in sequence:
--- a/dominosa.c
+++ b/dominosa.c
@@ -1119,7 +1119,7 @@
 
         move_cursor(button, &ui->cur_x, &ui->cur_y, 2*w-1, 2*h-1, 0);
 
-	return "";
+	return UI_UPDATE;
     } else if (IS_CURSOR_SELECT(button)) {
         int d1, d2;
 
@@ -1152,7 +1152,7 @@
         } else {
             return NULL;
         }
-        return "";
+        return UI_UPDATE;
     }
 
     return NULL;
--- a/filling.c
+++ b/filling.c
@@ -1449,7 +1449,7 @@
                 ui->sel[w*ty+tx] = 1;
         }
         ui->cur_visible = 0;
-        return ""; /* redraw */
+        return UI_UPDATE;
     }
 
     if (IS_CURSOR_MOVE(button)) {
@@ -1456,15 +1456,15 @@
         ui->cur_visible = 1;
         move_cursor(button, &ui->cur_x, &ui->cur_y, w, h, 0);
 	if (ui->keydragging) goto select_square;
-        return "";
+        return UI_UPDATE;
     }
     if (button == CURSOR_SELECT) {
         if (!ui->cur_visible) {
             ui->cur_visible = 1;
-            return "";
+            return UI_UPDATE;
         }
 	ui->keydragging = !ui->keydragging;
-	if (!ui->keydragging) return "";
+	if (!ui->keydragging) return UI_UPDATE;
 
       select_square:
         if (!ui->sel) {
@@ -1473,12 +1473,12 @@
         }
 	if (!state->shared->clues[w*ui->cur_y + ui->cur_x])
 	    ui->sel[w*ui->cur_y + ui->cur_x] = 1;
-	return "";
+	return UI_UPDATE;
     }
     if (button == CURSOR_SELECT2) {
 	if (!ui->cur_visible) {
 	    ui->cur_visible = 1;
-	    return "";
+	    return UI_UPDATE;
 	}
         if (!ui->sel) {
             ui->sel = snewn(w*h, int);
@@ -1492,7 +1492,7 @@
 	    sfree(ui->sel);
 	    ui->sel = NULL;
 	}
-	return "";
+	return UI_UPDATE;
     }
 
     if (button == '\b' || button == 27) {
@@ -1499,7 +1499,7 @@
 	sfree(ui->sel);
 	ui->sel = NULL;
 	ui->keydragging = FALSE;
-	return "";
+	return UI_UPDATE;
     }
 
     if (button < '0' || button > '9') return NULL;
@@ -1534,7 +1534,7 @@
     sfree(ui->sel);
     ui->sel = NULL;
     /* Need to update UI at least, as we cleared the selection */
-    return move ? move : "";
+    return move ? move : UI_UPDATE;
 }
 
 static game_state *execute_move(const game_state *state, const char *move)
--- a/flip.c
+++ b/flip.c
@@ -951,7 +951,7 @@
             tx = ui->cx; ty = ui->cy;
             ui->cdraw = 1;
         }
-        nullret = "";
+        nullret = UI_UPDATE;
 
         if (tx >= 0 && tx < w && ty >= 0 && ty < h) {
             /*
@@ -985,7 +985,7 @@
         ui->cx = min(max(ui->cx, 0), state->w - 1);
         ui->cy = min(max(ui->cy, 0), state->h - 1);
         ui->cdraw = 1;
-        nullret = "";
+        nullret = UI_UPDATE;
     }
 
     return nullret;
--- a/flood.c
+++ b/flood.c
@@ -830,19 +830,19 @@
     } else if (button == CURSOR_LEFT && ui->cx > 0) {
         ui->cx--;
         ui->cursor_visible = TRUE;
-        return "";
+        return UI_UPDATE;
     } else if (button == CURSOR_RIGHT && ui->cx+1 < w) {
         ui->cx++;
         ui->cursor_visible = TRUE;
-        return "";
+        return UI_UPDATE;
     } else if (button == CURSOR_UP && ui->cy > 0) {
         ui->cy--;
         ui->cursor_visible = TRUE;
-        return "";
+        return UI_UPDATE;
     } else if (button == CURSOR_DOWN && ui->cy+1 < h) {
         ui->cy++;
         ui->cursor_visible = TRUE;
-        return "";
+        return UI_UPDATE;
     } else if (button == CURSOR_SELECT) {
         tx = ui->cx;
         ty = ui->cy;
--- a/galaxies.c
+++ b/galaxies.c
@@ -2553,13 +2553,13 @@
             ui->dy = y;
             ui->dotx = dot->x;
             ui->doty = dot->y;
-            return "";
+            return UI_UPDATE;
         }
     } else if (button == RIGHT_DRAG && ui->dragging) {
         /* just move the drag coords. */
         ui->dx = x;
         ui->dy = y;
-        return "";
+        return UI_UPDATE;
     } else if (button == RIGHT_RELEASE && ui->dragging) {
         ui->dragging = FALSE;
 
@@ -2574,7 +2574,7 @@
 	 * is a null move; just update the ui and finish.
 	 */
 	if (px == ui->srcx && py == ui->srcy)
-	    return "";
+	    return UI_UPDATE;
 
 	/*
 	 * Otherwise, we remove the arrow from its starting
@@ -2601,7 +2601,7 @@
 	if (buf[0])
 	    return dupstr(buf);
 	else
-	    return "";
+	    return UI_UPDATE;
     } else if (IS_CURSOR_MOVE(button)) {
         move_cursor(button, &ui->cur_x, &ui->cur_y, state->sx-1, state->sy-1, 0);
         if (ui->cur_x < 1) ui->cur_x = 1;
@@ -2611,11 +2611,11 @@
             ui->dx = SCOORD(ui->cur_x);
             ui->dy = SCOORD(ui->cur_y);
         }
-        return "";
+        return UI_UPDATE;
     } else if (IS_CURSOR_SELECT(button)) {
         if (!ui->cur_visible) {
             ui->cur_visible = 1;
-            return "";
+            return UI_UPDATE;
         }
         sp = &SPACE(state, ui->cur_x, ui->cur_y);
         if (ui->dragging) {
@@ -2637,7 +2637,7 @@
             ui->dy = SCOORD(ui->cur_y);
             ui->dotx = ui->srcx = ui->cur_x;
             ui->doty = ui->srcy = ui->cur_y;
-            return "";
+            return UI_UPDATE;
         } else if (sp->flags & F_TILE_ASSOC) {
             assert(sp->type == s_tile);
             ui->dragging = TRUE;
@@ -2647,7 +2647,7 @@
             ui->doty = sp->doty;
             ui->srcx = ui->cur_x;
             ui->srcy = ui->cur_y;
-            return "";
+            return UI_UPDATE;
         } else if (sp->type == s_edge) {
             sprintf(buf, "E%d,%d", ui->cur_x, ui->cur_y);
             return dupstr(buf);
--- a/guess.c
+++ b/guess.c
@@ -779,7 +779,7 @@
      */
     if (button == 'l' || button == 'L') {
         ui->show_labels = !ui->show_labels;
-        return "";
+        return UI_UPDATE;
     }
 
     if (from->solved) return NULL;
@@ -836,13 +836,13 @@
             ui->drag_y = y;
             debug(("Start dragging, col = %d, (%d,%d)",
                    ui->drag_col, ui->drag_x, ui->drag_y));
-            ret = "";
+            ret = UI_UPDATE;
         }
     } else if (button == LEFT_DRAG && ui->drag_col) {
         ui->drag_x = x;
         ui->drag_y = y;
         debug(("Keep dragging, (%d,%d)", ui->drag_x, ui->drag_y));
-        ret = "";
+        ret = UI_UPDATE;
     } else if (button == LEFT_RELEASE && ui->drag_col) {
         if (over_guess > -1) {
             debug(("Dropping colour %d onto guess peg %d",
@@ -859,13 +859,13 @@
         ui->drag_opeg = -1;
         ui->display_cur = 0;
         debug(("Stop dragging."));
-        ret = "";
+        ret = UI_UPDATE;
     } else if (button == RIGHT_BUTTON) {
         if (over_guess > -1) {
             /* we use ths feedback in the game_ui to signify
              * 'carry this peg to the next guess as well'. */
             ui->holds[over_guess] = 1 - ui->holds[over_guess];
-            ret = "";
+            ret = UI_UPDATE;
         }
     } else if (button == LEFT_RELEASE && over_hint && ui->markable) {
         /* NB this won't trigger if on the end of a drag; that's on
@@ -880,10 +880,10 @@
             ui->colour_cur++;
         if (button == CURSOR_UP && ui->colour_cur > 0)
             ui->colour_cur--;
-        ret = "";
+        ret = UI_UPDATE;
     } else if (button == 'h' || button == 'H' || button == '?') {
         compute_hint(from, ui);
-        ret = "";
+        ret = UI_UPDATE;
     } else if (button == CURSOR_LEFT || button == CURSOR_RIGHT) {
         int maxcur = from->params.npegs;
         if (ui->markable) maxcur++;
@@ -893,7 +893,7 @@
             ui->peg_cur++;
         if (button == CURSOR_LEFT && ui->peg_cur > 0)
             ui->peg_cur--;
-        ret = "";
+        ret = UI_UPDATE;
     } else if (IS_CURSOR_SELECT(button)) {
         ui->display_cur = 1;
         if (ui->peg_cur == from->params.npegs) {
@@ -900,18 +900,18 @@
             ret = encode_move(from, ui);
         } else {
             set_peg(&from->params, ui, ui->peg_cur, ui->colour_cur+1);
-            ret = "";
+            ret = UI_UPDATE;
         }
     } else if (button == 'D' || button == 'd' || button == '\b') {
         ui->display_cur = 1;
         set_peg(&from->params, ui, ui->peg_cur, 0);
-        ret = "";
+        ret = UI_UPDATE;
     } else if (button == CURSOR_SELECT2) {
         if (ui->peg_cur == from->params.npegs)
             return NULL;
         ui->display_cur = 1;
         ui->holds[ui->peg_cur] = 1 - ui->holds[ui->peg_cur];
-        ret = "";
+        ret = UI_UPDATE;
     }
     return ret;
 }
--- a/keen.c
+++ b/keen.c
@@ -1616,7 +1616,7 @@
                 ui->hpencil = 0;
             }
             ui->hcursor = 0;
-            return "";		       /* UI activity occurred */
+            return UI_UPDATE;
         }
         if (button == RIGHT_BUTTON) {
             /*
@@ -1636,19 +1636,19 @@
                 ui->hshow = 0;
             }
             ui->hcursor = 0;
-            return "";		       /* UI activity occurred */
+            return UI_UPDATE;
         }
     }
     if (IS_CURSOR_MOVE(button)) {
         move_cursor(button, &ui->hx, &ui->hy, w, w, 0);
         ui->hshow = ui->hcursor = 1;
-        return "";
+        return UI_UPDATE;
     }
     if (ui->hshow &&
         (button == CURSOR_SELECT)) {
         ui->hpencil = 1 - ui->hpencil;
         ui->hcursor = 1;
-        return "";
+        return UI_UPDATE;
     }
 
     if (ui->hshow &&
--- a/lightup.c
+++ b/lightup.c
@@ -1882,7 +1882,7 @@
     enum { NONE, FLIP_LIGHT, FLIP_IMPOSSIBLE } action = NONE;
     int cx = -1, cy = -1;
     unsigned int flags;
-    char buf[80], *nullret = NULL, *empty = "", c;
+    char buf[80], *nullret = UI_UPDATE, *empty = UI_UPDATE, c;
 
     if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
         if (ui->cur_visible)
--- a/magnets.c
+++ b/magnets.c
@@ -1804,11 +1804,11 @@
     if (IS_CURSOR_MOVE(button)) {
         move_cursor(button, &ui->cur_x, &ui->cur_y, state->w, state->h, 0);
         ui->cur_visible = 1;
-        return "";
+        return UI_UPDATE;
     } else if (IS_CURSOR_SELECT(button)) {
         if (!ui->cur_visible) {
             ui->cur_visible = 1;
-            return "";
+            return UI_UPDATE;
         }
         action = (button == CURSOR_SELECT) ? CYCLE_MAGNET : CYCLE_NEUTRAL;
         gx = ui->cur_x;
@@ -1817,7 +1817,7 @@
                (button == LEFT_BUTTON || button == RIGHT_BUTTON)) {
         if (ui->cur_visible) {
             ui->cur_visible = 0;
-            nullret = "";
+            nullret = UI_UPDATE;
         }
         action = (button == LEFT_BUTTON) ? CYCLE_MAGNET : CYCLE_NEUTRAL;
     } else if (button == LEFT_BUTTON && is_clue(state, gx, gy)) {
--- a/map.c
+++ b/map.c
@@ -2375,7 +2375,7 @@
      */
     if (button == 'l' || button == 'L') {
         ui->show_numbers = !ui->show_numbers;
-        return "";
+        return UI_UPDATE;
     }
 
     if (IS_CURSOR_MOVE(button)) {
@@ -2385,7 +2385,7 @@
         ui->cur_lastmove = button;
         ui->dragx = COORD(ui->cur_x) + TILESIZE/2 + EPSILON_X(button);
         ui->dragy = COORD(ui->cur_y) + TILESIZE/2 + EPSILON_Y(button);
-        return "";
+        return UI_UPDATE;
     }
     if (IS_CURSOR_SELECT(button)) {
         if (!ui->cur_visible) {
@@ -2392,7 +2392,7 @@
             ui->dragx = COORD(ui->cur_x) + TILESIZE/2 + EPSILON_X(ui->cur_lastmove);
             ui->dragy = COORD(ui->cur_y) + TILESIZE/2 + EPSILON_Y(ui->cur_lastmove);
             ui->cur_visible = 1;
-            return "";
+            return UI_UPDATE;
         }
         if (ui->drag_colour == -2) { /* not currently cursor-dragging, start. */
             int r = region_from_coords(state, ds, ui->dragx, ui->dragy);
@@ -2404,7 +2404,7 @@
                 ui->drag_pencil = 0;
             }
             ui->cur_moved = 0;
-            return "";
+            return UI_UPDATE;
         } else { /* currently cursor-dragging; drop the colour in the new region. */
             x = COORD(ui->cur_x) + TILESIZE/2 + EPSILON_X(ui->cur_lastmove);
             y = COORD(ui->cur_y) + TILESIZE/2 + EPSILON_Y(ui->cur_lastmove);
@@ -2430,7 +2430,7 @@
         ui->dragx = x;
         ui->dragy = y;
         ui->cur_visible = 0;
-        return "";
+        return UI_UPDATE;
     }
 
     if ((button == LEFT_DRAG || button == RIGHT_DRAG) &&
@@ -2437,7 +2437,7 @@
         ui->drag_colour > -2) {
         ui->dragx = x;
         ui->dragy = y;
-        return "";
+        return UI_UPDATE;
     }
 
     if ((button == LEFT_RELEASE || button == RIGHT_RELEASE) &&
@@ -2461,18 +2461,18 @@
         ui->drag_colour = -2;
 
 	if (r < 0)
-            return "";                 /* drag into border; do nothing else */
+            return UI_UPDATE;          /* drag into border; do nothing else */
 
 	if (state->map->immutable[r])
-	    return "";                 /* can't change this region */
+	    return UI_UPDATE;          /* can't change this region */
 
         if (state->colouring[r] == c && state->pencil[r] == p)
-            return "";                 /* don't _need_ to change this region */
+            return UI_UPDATE;          /* don't _need_ to change this region */
 
 	if (alt_button) {
 	    if (state->colouring[r] >= 0) {
 		/* Can't pencil on a coloured region */
-		return "";
+		return UI_UPDATE;
 	    } else if (c >= 0) {
 		/* Right-dragging from colour to blank toggles one pencil */
 		p = state->pencil[r] ^ (1 << c);
--- a/midend.c
+++ b/midend.c
@@ -782,7 +782,7 @@
 	} else
 	    goto done;
     } else {
-	if (!*movestr)
+	if (movestr == UI_UPDATE)
 	    s = me->states[me->statepos-1].state;
 	else {
 	    s = me->ourgame->execute_move(me->states[me->statepos-1].state,
@@ -1662,6 +1662,7 @@
     movestr = me->ourgame->solve(me->states[0].state,
 				 me->states[me->statepos-1].state,
 				 me->aux_info, &msg);
+    assert(movestr != UI_UPDATE);
     if (!movestr) {
 	if (!msg)
 	    msg = "Solve operation failed";   /* _shouldn't_ happen, but can */
--- a/mines.c
+++ b/mines.c
@@ -2432,7 +2432,7 @@
     if (IS_CURSOR_MOVE(button)) {
         move_cursor(button, &ui->cur_x, &ui->cur_y, from->w, from->h, 0);
         ui->cur_visible = 1;
-        return "";
+        return UI_UPDATE;
     }
     if (IS_CURSOR_SELECT(button)) {
         int v = from->grid[ui->cur_y * from->w + ui->cur_x];
@@ -2439,7 +2439,7 @@
 
         if (!ui->cur_visible) {
             ui->cur_visible = 1;
-            return "";
+            return UI_UPDATE;
         }
         if (button == CURSOR_SELECT2) {
             /* As for RIGHT_BUTTON; only works on covered square. */
@@ -2479,7 +2479,7 @@
 	else if (button == MIDDLE_BUTTON)
 	    ui->validradius = 1;
         ui->cur_visible = 0;
-	return "";
+	return UI_UPDATE;
     }
 
     if (button == RIGHT_BUTTON) {
@@ -2507,10 +2507,10 @@
 
 	/*
 	 * At this stage we must never return NULL: we have adjusted
-	 * the ui, so at worst we return "".
+	 * the ui, so at worst we return UI_UPDATE.
 	 */
 	if (cx < 0 || cx >= from->w || cy < 0 || cy >= from->h)
-	    return "";
+	    return UI_UPDATE;
 
 	/*
 	 * Left-clicking on a covered square opens a tile. Not
@@ -2588,7 +2588,7 @@
 	    }
 	}
 
-	return "";
+	return UI_UPDATE;
     }
 }
 
--- a/misc.c
+++ b/misc.c
@@ -9,6 +9,8 @@
 
 #include "puzzles.h"
 
+char UI_UPDATE[] = "";
+
 void free_cfg(config_item *cfg)
 {
     config_item *i;
--- a/net.c
+++ b/net.c
@@ -2096,7 +2096,7 @@
 
 	if (ui->cur_visible) {
 	    ui->cur_visible = FALSE;
-	    nullret = "";
+	    nullret = UI_UPDATE;
 	}
 
 	/*
@@ -2336,7 +2336,7 @@
             OFFSET(ui->cur_x, ui->cur_y, ui->cur_x, ui->cur_y, dir, state);
             ui->cur_visible = TRUE;
         }
-        return "";
+        return UI_UPDATE;
     } else {
 	return NULL;
     }
--- a/netslide.c
+++ b/netslide.c
@@ -1079,7 +1079,7 @@
         }
 
         ui->cur_visible = 1;
-        return "";
+        return UI_UPDATE;
     }
 
     if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
@@ -1093,7 +1093,7 @@
         } else {
             /* 'click' when cursor is invisible just makes cursor visible. */
             ui->cur_visible = 1;
-            return "";
+            return UI_UPDATE;
         }
     } else
         return NULL;
--- a/palisade.c
+++ b/palisade.c
@@ -986,7 +986,7 @@
                           ui->x, ui->y, flag, x, y, newflag);
         } else {
             move_cursor(button, &ui->x, &ui->y, w, h, FALSE);
-            return "";
+            return UI_UPDATE;
         }
     }
 
--- a/pattern.c
+++ b/pattern.c
@@ -1285,7 +1285,7 @@
         ui->drag_start_y = ui->drag_end_y = y;
         ui->cur_visible = 0;
 
-        return "";		       /* UI activity occurred */
+        return UI_UPDATE;
     }
 
     if (ui->dragging && button == ui->drag) {
@@ -1314,7 +1314,7 @@
         ui->drag_end_x = x;
         ui->drag_end_y = y;
 
-        return "";		       /* UI activity occurred */
+        return UI_UPDATE;
     }
 
     if (ui->dragging && button == ui->release) {
@@ -1342,7 +1342,7 @@
 		    x1, y1, x2-x1+1, y2-y1+1);
 	    return dupstr(buf);
         } else
-            return "";		       /* UI activity occurred */
+            return UI_UPDATE;
     }
 
     if (IS_CURSOR_MOVE(button)) {
@@ -1350,12 +1350,12 @@
 	char buf[80];
         move_cursor(button, &ui->cur_x, &ui->cur_y, state->common->w, state->common->h, 0);
         ui->cur_visible = 1;
-	if (!control && !shift) return "";
+	if (!control && !shift) return UI_UPDATE;
 
 	newstate = control ? shift ? GRID_UNKNOWN : GRID_FULL : GRID_EMPTY;
 	if (state->grid[y * state->common->w + x] == newstate &&
 	    state->grid[ui->cur_y * state->common->w + ui->cur_x] == newstate)
-	    return "";
+	    return UI_UPDATE;
 
 	sprintf(buf, "%c%d,%d,%d,%d", control ? shift ? 'U' : 'F' : 'E',
 		min(x, ui->cur_x), min(y, ui->cur_y),
@@ -1370,7 +1370,7 @@
 
         if (!ui->cur_visible) {
             ui->cur_visible = 1;
-            return "";
+            return UI_UPDATE;
         }
 
         if (button == CURSOR_SELECT2)
--- a/pearl.c
+++ b/pearl.c
@@ -2022,11 +2022,11 @@
 
     char ch = primary ? 'F' : 'M', *other;
 
-    if (!INGRID(state, x, y) || !INGRID(state, x2, y2)) return "";
+    if (!INGRID(state, x, y) || !INGRID(state, x2, y2)) return UI_UPDATE;
 
     /* disallow laying a mark over a line, or vice versa. */
     other = primary ? state->marks : state->lines;
-    if (other[y*w+x] & dir || other[y2*w+x2] & dir2) return "";
+    if (other[y*w+x] & dir || other[y2*w+x2] & dir2) return UI_UPDATE;
     
     sprintf(buf, "%c%d,%d,%d;%c%d,%d,%d", ch, dir, x, y, ch, dir2, x2, y2);
     return dupstr(buf);
@@ -2060,12 +2060,12 @@
         ui->dragcoords[0] = gy * w + gx;
         ui->ndragcoords = 0;           /* will be 1 once drag is confirmed */
 
-        return "";
+        return UI_UPDATE;
     }
 
     if (button == LEFT_DRAG && ui->ndragcoords >= 0) {
         update_ui_drag(state, ui, gx, gy);
-        return "";
+        return UI_UPDATE;
     }
 
     if (IS_MOUSE_RELEASE(button)) release = TRUE;
@@ -2087,13 +2087,13 @@
 	    if (ui->ndragcoords >= 0)
 		update_ui_drag(state, ui, ui->curx, ui->cury);
 	}
-	return "";
+	return UI_UPDATE;
     }
 
     if (IS_CURSOR_SELECT(button)) {
 	if (!ui->cursor_active) {
 	    ui->cursor_active = TRUE;
-	    return "";
+	    return UI_UPDATE;
 	} else if (button == CURSOR_SELECT) {
 	    if (ui->ndragcoords == -1) {
 		ui->ndragcoords = 0;
@@ -2100,17 +2100,17 @@
 		ui->dragcoords[0] = ui->cury * w + ui->curx;
 		ui->clickx = CENTERED_COORD(ui->curx);
 		ui->clicky = CENTERED_COORD(ui->cury);
-		return "";
+		return UI_UPDATE;
 	    } else release = TRUE;
 	} else if (button == CURSOR_SELECT2 && ui->ndragcoords >= 0) {
 	    ui->ndragcoords = -1;
-	    return "";
+	    return UI_UPDATE;
 	}
     }
 
     if (button == 27 || button == '\b') {
         ui->ndragcoords = -1;
-        return "";
+        return UI_UPDATE;
     }
 
     if (release) {
@@ -2142,7 +2142,7 @@
 
             ui->ndragcoords = -1;
 
-            return buf ? buf : "";
+            return buf ? buf : UI_UPDATE;
         } else if (ui->ndragcoords == 0) {
             /* Click (or tiny drag). Work out which edge we were
              * closest to. */
@@ -2163,12 +2163,12 @@
             cx = CENTERED_COORD(gx);
             cy = CENTERED_COORD(gy);
 
-            if (!INGRID(state, gx, gy)) return "";
+            if (!INGRID(state, gx, gy)) return UI_UPDATE;
 
             if (max(abs(x-cx),abs(y-cy)) < TILE_SIZE/4) {
                 /* TODO closer to centre of grid: process as a cell click not an edge click. */
 
-                return "";
+                return UI_UPDATE;
             } else {
 		int direction;
                 if (abs(x-cx) < abs(y-cy)) {
--- a/pegs.c
+++ b/pegs.c
@@ -848,7 +848,7 @@
 	    ui->dx = x;
 	    ui->dy = y;
             ui->cur_visible = ui->cur_jumping = 0;
-	    return "";		       /* ui modified */
+	    return UI_UPDATE;
 	}
     } else if (button == LEFT_DRAG && ui->dragging) {
 	/*
@@ -856,7 +856,7 @@
 	 */
 	ui->dx = x;
 	ui->dy = y;
-	return "";		       /* ui modified */
+	return UI_UPDATE;
     } else if (button == LEFT_RELEASE && ui->dragging) {
 	int tx, ty, dx, dy;
 
@@ -868,11 +868,11 @@
 	tx = FROMCOORD(x);
 	ty = FROMCOORD(y);
 	if (tx < 0 || tx >= w || ty < 0 || ty >= h)
-	    return "";		       /* target out of range */
+	    return UI_UPDATE;	       /* target out of range */
 	dx = tx - ui->sx;
 	dy = ty - ui->sy;
 	if (max(abs(dx),abs(dy)) != 2 || min(abs(dx),abs(dy)) != 0)
-	    return "";		       /* move length was wrong */
+	    return UI_UPDATE;	       /* move length was wrong */
 	dx /= 2;
 	dy /= 2;
 
@@ -879,7 +879,7 @@
 	if (state->grid[ty*w+tx] != GRID_HOLE ||
 	    state->grid[(ty-dy)*w+(tx-dx)] != GRID_PEG ||
 	    state->grid[ui->sy*w+ui->sx] != GRID_PEG)
-	    return "";		       /* grid contents were invalid */
+	    return UI_UPDATE;	       /* grid contents were invalid */
 
 	/*
 	 * We have a valid move. Encode it simply as source and
@@ -899,7 +899,7 @@
                 ui->cur_x = cx;
                 ui->cur_y = cy;
             }
-            return "";
+            return UI_UPDATE;
         } else {
             int dx, dy, mx, my, jx, jy;
 
@@ -922,21 +922,21 @@
                 ui->cur_x = jx; ui->cur_y = jy;
                 return dupstr(buf);
             }
-            return "";
+            return UI_UPDATE;
         }
     } else if (IS_CURSOR_SELECT(button)) {
         if (!ui->cur_visible) {
             ui->cur_visible = 1;
-            return "";
+            return UI_UPDATE;
         }
         if (ui->cur_jumping) {
             ui->cur_jumping = 0;
-            return "";
+            return UI_UPDATE;
         }
         if (state->grid[ui->cur_y*w+ui->cur_x] == GRID_PEG) {
             /* cursor is on peg: next arrow-move wil jump. */
             ui->cur_jumping = 1;
-            return "";
+            return UI_UPDATE;
         }
         return NULL;
     }
--- a/puzzles.h
+++ b/puzzles.h
@@ -684,6 +684,14 @@
 extern const game thegame;
 #endif
 
+/*
+ * Special string value to return from interpret_move in the case
+ * where the game UI has been updated but no actual move is being
+ * appended to the undo chain. Must be declared as a non-const char,
+ * but should never actually be modified by anyone.
+ */
+extern char UI_UPDATE[];
+
 /* A little bit of help to lazy developers */
 #define DEFAULT_STATUSBAR_TEXT "Use status_bar() to fill this in."
 
--- a/range.c
+++ b/range.c
@@ -1360,7 +1360,7 @@
                 else if (do_post)
                     return nfmtstr(40, "W,%d,%d", ui->r, ui->c);
                 else
-                    return "";
+                    return UI_UPDATE;
 
             } else if (!out_of_bounds(ui->r + dr[i], ui->c + dc[i], w, h)) {
                 ui->r += dr[i];
@@ -1367,7 +1367,7 @@
                 ui->c += dc[i];
             }
         } else ui->cursor_show = TRUE;
-        return "";
+        return UI_UPDATE;
     }
 
     if (action == hint) {
--- a/rect.c
+++ b/rect.c
@@ -2407,7 +2407,7 @@
         move_cursor(button, &ui->cur_x, &ui->cur_y, from->w, from->h, 0);
         ui->cur_visible = TRUE;
         active = TRUE;
-        if (!ui->cur_dragging) return "";
+        if (!ui->cur_dragging) return UI_UPDATE;
         coord_round((float)ui->cur_x + 0.5F, (float)ui->cur_y + 0.5F, &xc, &yc);
     } else if (IS_CURSOR_SELECT(button)) {
         if (ui->drag_start_x >= 0 && !ui->cur_dragging) {
@@ -2420,7 +2420,7 @@
         if (!ui->cur_visible) {
             assert(!ui->cur_dragging);
             ui->cur_visible = TRUE;
-            return "";
+            return UI_UPDATE;
         }
         coord_round((float)ui->cur_x + 0.5F, (float)ui->cur_y + 0.5F, &xc, &yc);
         erasing = (button == CURSOR_SELECT2);
@@ -2441,7 +2441,7 @@
             reset_ui(ui); /* cancel keyboard dragging */
             ui->cur_dragging = FALSE;
         }
-        return "";
+        return UI_UPDATE;
     } else if (button != LEFT_DRAG && button != RIGHT_DRAG) {
         return NULL;
     }
@@ -2525,7 +2525,7 @@
     if (ret)
 	return ret;		       /* a move has been made */
     else if (active)
-        return "";		       /* UI activity has occurred */
+        return UI_UPDATE;
     else
 	return NULL;
 }
--- a/samegame.c
+++ b/samegame.c
@@ -1273,7 +1273,7 @@
                             int x, int y, int button)
 {
     int tx, ty;
-    char *ret = "";
+    char *ret = UI_UPDATE;
 
     ui->displaysel = 0;
 
--- a/signpost.c
+++ b/signpost.c
@@ -1441,18 +1441,20 @@
             ui->dx = COORD(ui->cx) + TILE_SIZE/2;
             ui->dy = COORD(ui->cy) + TILE_SIZE/2;
         }
-        return "";
+        return UI_UPDATE;
     } else if (IS_CURSOR_SELECT(button)) {
         if (!ui->cshow)
             ui->cshow = 1;
         else if (ui->dragging) {
             ui->dragging = FALSE;
-            if (ui->sx == ui->cx && ui->sy == ui->cy) return "";
+            if (ui->sx == ui->cx && ui->sy == ui->cy) return UI_UPDATE;
             if (ui->drag_is_from) {
-                if (!isvalidmove(state, 0, ui->sx, ui->sy, ui->cx, ui->cy)) return "";
+                if (!isvalidmove(state, 0, ui->sx, ui->sy, ui->cx, ui->cy))
+                    return UI_UPDATE;
                 sprintf(buf, "L%d,%d-%d,%d", ui->sx, ui->sy, ui->cx, ui->cy);
             } else {
-                if (!isvalidmove(state, 0, ui->cx, ui->cy, ui->sx, ui->sy)) return "";
+                if (!isvalidmove(state, 0, ui->cx, ui->cy, ui->sx, ui->sy))
+                    return UI_UPDATE;
                 sprintf(buf, "L%d,%d-%d,%d", ui->cx, ui->cy, ui->sx, ui->sy);
             }
             return dupstr(buf);
@@ -1464,7 +1466,7 @@
             ui->dy = COORD(ui->cy) + TILE_SIZE/2;
             ui->drag_is_from = (button == CURSOR_SELECT) ? 1 : 0;
         }
-        return "";
+        return UI_UPDATE;
     }
     if (IS_MOUSE_DOWN(button)) {
         if (ui->cshow) {
@@ -1492,19 +1494,19 @@
         ui->dx = mx;
         ui->dy = my;
         ui->cshow = 0;
-        return "";
+        return UI_UPDATE;
     } else if (IS_MOUSE_DRAG(button) && ui->dragging) {
         ui->dx = mx;
         ui->dy = my;
-        return "";
+        return UI_UPDATE;
     } else if (IS_MOUSE_RELEASE(button) && ui->dragging) {
         ui->dragging = FALSE;
-        if (ui->sx == x && ui->sy == y) return ""; /* single click */
+        if (ui->sx == x && ui->sy == y) return UI_UPDATE; /* single click */
 
         if (!INGRID(state, x, y)) {
             int si = ui->sy*w+ui->sx;
             if (state->prev[si] == -1 && state->next[si] == -1)
-                return "";
+                return UI_UPDATE;
             sprintf(buf, "%c%d,%d",
                     (int)(ui->drag_is_from ? 'C' : 'X'), ui->sx, ui->sy);
             return dupstr(buf);
@@ -1511,10 +1513,12 @@
         }
 
         if (ui->drag_is_from) {
-            if (!isvalidmove(state, 0, ui->sx, ui->sy, x, y)) return "";
+            if (!isvalidmove(state, 0, ui->sx, ui->sy, x, y))
+                return UI_UPDATE;
             sprintf(buf, "L%d,%d-%d,%d", ui->sx, ui->sy, x, y);
         } else {
-            if (!isvalidmove(state, 0, x, y, ui->sx, ui->sy)) return "";
+            if (!isvalidmove(state, 0, x, y, ui->sx, ui->sy))
+                return UI_UPDATE;
             sprintf(buf, "L%d,%d-%d,%d", x, y, ui->sx, ui->sy);
         }
         return dupstr(buf);
@@ -1523,7 +1527,7 @@
     else if ((button == 'x' || button == 'X') && ui->cshow) {
         int si = ui->cy*w + ui->cx;
         if (state->prev[si] == -1 && state->next[si] == -1)
-            return "";
+            return UI_UPDATE;
         sprintf(buf, "%c%d,%d",
                 (int)((button == 'x') ? 'C' : 'X'), ui->cx, ui->cy);
         return dupstr(buf);
--- a/singles.c
+++ b/singles.c
@@ -1522,7 +1522,7 @@
             action = TOGGLE_CIRCLE;
         }
     }
-    if (action == UI) return "";
+    if (action == UI) return UI_UPDATE;
 
     if (action == TOGGLE_BLACK || action == TOGGLE_CIRCLE) {
         i = y * state->w + x;
--- a/sixteen.c
+++ b/sixteen.c
@@ -618,7 +618,7 @@
     if (IS_CURSOR_MOVE(button) || pad) {
         if (!ui->cur_visible) {
             ui->cur_visible = 1;
-            return "";
+            return UI_UPDATE;
         }
 
         if (control || shift || ui->cur_mode) {
@@ -673,7 +673,7 @@
             }
 
             ui->cur_visible = 1;
-            return "";
+            return UI_UPDATE;
         }
     }
 
@@ -691,11 +691,11 @@
                 const enum cursor_mode m = (button == CURSOR_SELECT2 ?
                                             lock_position : lock_tile);
                 ui->cur_mode = (ui->cur_mode == m ? unlocked : m);
-                return "";
+                return UI_UPDATE;
             }
         } else {
             ui->cur_visible = 1;
-            return "";
+            return UI_UPDATE;
         }
     } else {
 	return NULL;
@@ -710,7 +710,7 @@
     else if (cy == state->h && cx >= 0 && cx < state->w)
         dy = +1, dx = 0;
     else
-        return "";                   /* invalid click location */
+        return UI_UPDATE;            /* invalid click location */
 
     /* reverse direction if right hand button is pressed */
     if (button == RIGHT_BUTTON || button == CURSOR_SELECT2) {
--- a/slant.c
+++ b/slant.c
@@ -1683,7 +1683,7 @@
     } else if (IS_CURSOR_SELECT(button)) {
         if (!ui->cur_visible) {
             ui->cur_visible = 1;
-            return "";
+            return UI_UPDATE;
         }
         x = ui->cur_x;
         y = ui->cur_y;
@@ -1692,7 +1692,7 @@
     } else if (IS_CURSOR_MOVE(button)) {
         move_cursor(button, &ui->cur_x, &ui->cur_y, w, h, 0);
         ui->cur_visible = 1;
-        return "";
+        return UI_UPDATE;
     } else if (button == '\\' || button == '\b' || button == '/') {
 	int x = ui->cur_x, y = ui->cur_y;
 	if (button == ("\\" "\b" "/")[state->soln[y*w + x] + 1]) return NULL;
--- a/solo.c
+++ b/solo.c
@@ -4586,7 +4586,7 @@
                 ui->hpencil = 0;
             }
             ui->hcursor = 0;
-            return "";		       /* UI activity occurred */
+            return UI_UPDATE;
         }
         if (button == RIGHT_BUTTON) {
             /*
@@ -4606,19 +4606,19 @@
                 ui->hshow = 0;
             }
             ui->hcursor = 0;
-            return "";		       /* UI activity occurred */
+            return UI_UPDATE;
         }
     }
     if (IS_CURSOR_MOVE(button)) {
         move_cursor(button, &ui->hx, &ui->hy, cr, cr, 0);
         ui->hshow = ui->hcursor = 1;
-        return "";
+        return UI_UPDATE;
     }
     if (ui->hshow &&
         (button == CURSOR_SELECT)) {
         ui->hpencil = 1 - ui->hpencil;
         ui->hcursor = 1;
-        return "";
+        return UI_UPDATE;
     }
 
     if (ui->hshow &&
--- a/tents.c
+++ b/tents.c
@@ -1559,7 +1559,7 @@
         ui->dsy = ui->dey = y;
         ui->drag_ok = TRUE;
         ui->cdisp = 0;
-        return "";             /* ui updated */
+        return UI_UPDATE;
     }
 
     if ((IS_MOUSE_DRAG(button) || IS_MOUSE_RELEASE(button)) &&
@@ -1590,7 +1590,7 @@
         }
 
         if (IS_MOUSE_DRAG(button))
-            return "";                 /* ui updated */
+            return UI_UPDATE;
 
         /*
          * The drag has been released. Enact it.
@@ -1597,7 +1597,7 @@
          */
         if (!ui->drag_ok) {
             ui->drag_button = -1;
-            return "";                 /* drag was just cancelled */
+            return UI_UPDATE;          /* drag was just cancelled */
         }
 
         xmin = min(ui->dsx, ui->dex);
@@ -1635,7 +1635,7 @@
 
         if (buflen == 0) {
             sfree(buf);
-            return "";                 /* ui updated (drag was terminated) */
+            return UI_UPDATE;          /* drag was terminated */
         } else {
             buf[buflen] = '\0';
             return buf;
@@ -1663,7 +1663,7 @@
             if (len) return dupstr(tmpbuf);
         } else
             move_cursor(button, &ui->cx, &ui->cy, w, h, 0);
-        return "";
+        return UI_UPDATE;
     }
     if (ui->cdisp) {
         char rep = 0;
@@ -1690,7 +1690,7 @@
         }
     } else if (IS_CURSOR_SELECT(button)) {
         ui->cdisp = 1;
-        return "";
+        return UI_UPDATE;
     }
 
     return NULL;
--- a/towers.c
+++ b/towers.c
@@ -1349,7 +1349,7 @@
                 ui->hpencil = 0;
             }
             ui->hcursor = 0;
-            return "";		       /* UI activity occurred */
+            return UI_UPDATE;
         }
         if (button == RIGHT_BUTTON) {
             /*
@@ -1369,7 +1369,7 @@
                 ui->hshow = 0;
             }
             ui->hcursor = 0;
-            return "";		       /* UI activity occurred */
+            return UI_UPDATE;
         }
     } else if (button == LEFT_BUTTON) {
         if (is_clue(state, tx, ty)) {
@@ -1394,13 +1394,13 @@
         }
         move_cursor(button, &ui->hx, &ui->hy, w, w, 0);
         ui->hshow = ui->hcursor = 1;
-        return "";
+        return UI_UPDATE;
     }
     if (ui->hshow &&
         (button == CURSOR_SELECT)) {
         ui->hpencil = 1 - ui->hpencil;
         ui->hcursor = 1;
-        return "";
+        return UI_UPDATE;
     }
 
     if (ui->hshow &&
--- a/tracks.c
+++ b/tracks.c
@@ -1922,13 +1922,13 @@
         ui->drag_sx = ui->drag_ex = gx;
         ui->drag_sy = ui->drag_ey = gy;
 
-        return "";
+        return UI_UPDATE;
     }
 
     if (IS_MOUSE_DRAG(button)) {
         ui->cursor_active = FALSE;
         update_ui_drag(state, ui, gx, gy);
-        return "";
+        return UI_UPDATE;
     }
 
     if (IS_MOUSE_RELEASE(button)) {
@@ -1965,12 +1965,12 @@
             cy = CENTERED_COORD(gy);
 
             if (!INGRID(state, gx, gy) || FROMCOORD(x) != gx || FROMCOORD(y) != gy)
-                return "";
+                return UI_UPDATE;
 
             if (max(abs(x-cx),abs(y-cy)) < TILE_SIZE/4) {
                 if (ui_can_flip_square(state, gx, gy, button == RIGHT_RELEASE))
                     return square_flip_str(state, gx, gy, button == RIGHT_RELEASE, tmpbuf);
-                return "";
+                return UI_UPDATE;
             } else {
                 if (abs(x-cx) < abs(y-cy)) {
                     /* Closest to top/bottom edge. */
@@ -1984,7 +1984,7 @@
                     return edge_flip_str(state, gx, gy, direction,
                             button == RIGHT_RELEASE, tmpbuf);
                 else
-                    return "";
+                    return UI_UPDATE;
             }
         }
     }
@@ -1997,7 +1997,7 @@
 
         if (!ui->cursor_active) {
             ui->cursor_active = TRUE;
-            return "";
+            return UI_UPDATE;
         }
 
         ui->curx = ui->curx + dx;
@@ -2008,17 +2008,17 @@
         }
         ui->curx = min(max(ui->curx, 1), 2*w-1);
         ui->cury = min(max(ui->cury, 1), 2*h-1);
-        return "";
+        return UI_UPDATE;
     }
 
     if (IS_CURSOR_SELECT(button)) {
         if (!ui->cursor_active) {
             ui->cursor_active = TRUE;
-            return "";
+            return UI_UPDATE;
         }
         /* click on square corner does nothing (shouldn't get here) */
         if ((ui->curx % 2) == 0 && (ui->cury % 2 == 0))
-            return "";
+            return UI_UPDATE;
 
         gx = ui->curx / 2;
         gy = ui->cury / 2;
@@ -2030,7 +2030,7 @@
         else if (!direction &&
                  ui_can_flip_square(state, gx, gy, button == CURSOR_SELECT2))
             return square_flip_str(state, gx, gy, button == CURSOR_SELECT2, tmpbuf);
-        return "";
+        return UI_UPDATE;
     }
 
 #if 0
--- a/twiddle.c
+++ b/twiddle.c
@@ -661,7 +661,7 @@
         if (button == CURSOR_DOWN && (ui->cur_y+n) < (h))
             ui->cur_y++;
         ui->cur_visible = 1;
-        return "";
+        return UI_UPDATE;
     }
 
     if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
@@ -685,7 +685,7 @@
             dir = (button == CURSOR_SELECT2) ? -1 : +1;
         } else {
             ui->cur_visible = 1;
-            return "";
+            return UI_UPDATE;
         }
     } else if (button == 'a' || button == 'A' || button==MOD_NUM_KEYPAD+'7') {
         x = y = 0;
--- a/undead.c
+++ b/undead.c
@@ -1725,7 +1725,7 @@
 
     if (button == 'a' || button == 'A') {
         ui->ascii = !ui->ascii;
-        return "";      
+        return UI_UPDATE;
     }
 
     if (button == 'm' || button == 'M') {
@@ -1771,12 +1771,12 @@
               case CURSOR_LEFT:   ui->hx -= (ui->hx > 1)     ? 1 : 0; break;
             }
         ui->hshow = ui->hcursor = 1;
-        return "";
+        return UI_UPDATE;
     }
     if (ui->hshow && button == CURSOR_SELECT) {
         ui->hpencil = 1 - ui->hpencil;
         ui->hcursor = 1;
-        return "";
+        return UI_UPDATE;
     }
 
     if (ui->hshow == 1 && ui->hpencil == 1) {
@@ -1814,12 +1814,12 @@
                 if (button == LEFT_BUTTON) {
                     ui->hshow = 1; ui->hpencil = 0; ui->hcursor = 0;
                     ui->hx = gx; ui->hy = gy;
-                    return "";
+                    return UI_UPDATE;
                 }
                 else if (button == RIGHT_BUTTON && g == 7) {
                     ui->hshow = 1; ui->hpencil = 1; ui->hcursor = 0;
                     ui->hx = gx; ui->hy = gy;
-                    return "";
+                    return UI_UPDATE;
                 }
             }
             else if (ui->hshow == 1) {
@@ -1828,18 +1828,18 @@
                         if (gx == ui->hx && gy == ui->hy) {
                             ui->hshow = 0; ui->hpencil = 0; ui->hcursor = 0;
                             ui->hx = 0; ui->hy = 0;
-                            return "";
+                            return UI_UPDATE;
                         }
                         else {
                             ui->hshow = 1; ui->hpencil = 0; ui->hcursor = 0;
                             ui->hx = gx; ui->hy = gy;
-                            return "";
+                            return UI_UPDATE;
                         }
                     }
                     else {
                         ui->hshow = 1; ui->hpencil = 0; ui->hcursor = 0;
                         ui->hx = gx; ui->hy = gy;
-                        return "";
+                        return UI_UPDATE;
                     }
                 }
                 else if (button == RIGHT_BUTTON) {
@@ -1846,18 +1846,18 @@
                     if (ui->hpencil == 0 && g == 7) {
                         ui->hshow = 1; ui->hpencil = 1; ui->hcursor = 0;
                         ui->hx = gx; ui->hy = gy;
-                        return "";
+                        return UI_UPDATE;
                     }
                     else {
                         if (gx == ui->hx && gy == ui->hy) {
                             ui->hshow = 0; ui->hpencil = 0; ui->hcursor = 0;
                             ui->hx = 0; ui->hy = 0;
-                            return "";
+                            return UI_UPDATE;
                         }
                         else if (g == 7) {
                             ui->hshow = 1; ui->hpencil = 1; ui->hcursor = 0;
                             ui->hx = gx; ui->hy = gy;
-                            return "";
+                            return UI_UPDATE;
                         }
                     }
                 }
--- a/unequal.c
+++ b/unequal.c
@@ -1425,7 +1425,7 @@
                 ui->hshow = 1;
             }
             ui->hcursor = 0;
-            return "";
+            return UI_UPDATE;
         }
         if (button == RIGHT_BUTTON) {
             /* pencil highlighting for non-filled squares */
@@ -1439,7 +1439,7 @@
                 ui->hshow = 1;
             }
             ui->hcursor = 0;
-            return "";
+            return UI_UPDATE;
         }
     }
 
@@ -1453,11 +1453,12 @@
 				  ny != ui->hy + adjthan[i].dy); ++i);
 
 	    if (i == 4)
-		return ""; /* invalid direction, i.e. out of the board */
+		return UI_UPDATE; /* invalid direction, i.e. out of
+                                   * the board */
 
 	    if (!(GRID(state, flags, ui->hx, ui->hy) & adjthan[i].f ||
 		  GRID(state, flags, nx,     ny    ) & adjthan[i].fo))
-		return ""; /* no clue to toggle */
+		return UI_UPDATE; /* no clue to toggle */
 
 	    if (state->adjacent)
 		self = (adjthan[i].dx >= 0 && adjthan[i].dy >= 0);
@@ -1475,13 +1476,13 @@
 	} else {
 	    move_cursor(button, &ui->hx, &ui->hy, ds->order, ds->order, FALSE);
 	    ui->hshow = ui->hcursor = 1;
-	    return "";
+	    return UI_UPDATE;
 	}
     }
     if (ui->hshow && IS_CURSOR_SELECT(button)) {
         ui->hpencil = 1 - ui->hpencil;
         ui->hcursor = 1;
-        return "";
+        return UI_UPDATE;
     }
 
     n = c2n(button, state->order);
--- a/unfinished/group.c
+++ b/unfinished/group.c
@@ -1281,13 +1281,13 @@
             ui->drag |= 4;             /* some movement has happened */
             if (tcoord >= 0 && tcoord < w) {
                 ui->dragpos = tcoord;
-                return "";
+                return UI_UPDATE;
             }
         } else if (IS_MOUSE_RELEASE(button)) {
             if (ui->drag & 4) {
                 ui->drag = 0;          /* end drag */
                 if (state->sequence[ui->dragpos] == ui->dragnum)
-                    return "";         /* drag was a no-op overall */
+                    return UI_UPDATE;  /* drag was a no-op overall */
                 sprintf(buf, "D%d,%d", ui->dragnum, ui->dragpos);
                 return dupstr(buf);
             } else {
@@ -1298,7 +1298,7 @@
                             state->sequence[ui->edgepos]);
                     return dupstr(buf);
                 } else
-                    return "";         /* no-op */
+                    return UI_UPDATE;  /* no-op */
             }
         }
     } else if (IS_MOUSE_DOWN(button)) {
@@ -1321,7 +1321,7 @@
                     ui->hpencil = 0;
                 }
                 ui->hcursor = 0;
-                return "";		       /* UI activity occurred */
+                return UI_UPDATE;
             }
             if (button == RIGHT_BUTTON) {
                 /*
@@ -1345,7 +1345,7 @@
                     ui->hshow = 0;
                 }
                 ui->hcursor = 0;
-                return "";		       /* UI activity occurred */
+                return UI_UPDATE;
             }
         } else if (tx >= 0 && tx < w && ty == -1) {
             ui->drag = 2;
@@ -1352,13 +1352,13 @@
             ui->dragnum = state->sequence[tx];
             ui->dragpos = tx;
             ui->edgepos = FROMCOORD(x + TILESIZE/2);
-            return "";
+            return UI_UPDATE;
         } else if (ty >= 0 && ty < w && tx == -1) {
             ui->drag = 1;
             ui->dragnum = state->sequence[ty];
             ui->dragpos = ty;
             ui->edgepos = FROMCOORD(y + TILESIZE/2);
-            return "";
+            return UI_UPDATE;
         }
     } else if (IS_MOUSE_DRAG(button)) {
         if (!ui->hpencil &&
@@ -1371,7 +1371,7 @@
             ui->odx = ui->ody = 0;
             ui->odn = 1;
         }
-        return "";
+        return UI_UPDATE;
     }
 
     if (IS_CURSOR_MOVE(button)) {
@@ -1381,13 +1381,13 @@
         ui->hx = state->sequence[cx];
         ui->hy = state->sequence[cy];
         ui->hshow = ui->hcursor = 1;
-        return "";
+        return UI_UPDATE;
     }
     if (ui->hshow &&
         (button == CURSOR_SELECT)) {
         ui->hpencil = 1 - ui->hpencil;
         ui->hcursor = 1;
-        return "";
+        return UI_UPDATE;
     }
 
     if (ui->hshow &&
--- a/unfinished/slide.c
+++ b/unfinished/slide.c
@@ -1349,7 +1349,7 @@
 	 * And that's it. Update the display to reflect the start
 	 * of a drag.
 	 */
-	return "";
+	return UI_UPDATE;
     } else if (button == LEFT_DRAG && ui->dragging) {
 	int dist, distlimit, dx, dy, s, px, py;
 
@@ -1376,7 +1376,7 @@
 		    if (px >= 0 && px < w && py >= 0 && py < h &&
 			ui->reachable[py*w+px]) {
 			ui->drag_currpos = py*w+px;
-			return "";
+			return UI_UPDATE;
 		    }
 		}
 	}
--- a/unruly.c
+++ b/unruly.c
@@ -1531,7 +1531,7 @@
     if (IS_CURSOR_MOVE(button)) {
         move_cursor(button, &ui->cx, &ui->cy, w2, h2, 0);
         ui->cursor = TRUE;
-        return "";
+        return UI_UPDATE;
     }
 
     /* Place one */
--- a/untangle.c
+++ b/untangle.c
@@ -1117,7 +1117,7 @@
 	    ui->newpoint.x = x;
 	    ui->newpoint.y = y;
 	    ui->newpoint.d = ds->tilesize;
-	    return "";
+	    return UI_UPDATE;
 	}
 
     } else if (IS_MOUSE_DRAG(button) && ui->dragpoint >= 0) {
@@ -1124,7 +1124,7 @@
 	ui->newpoint.x = x;
 	ui->newpoint.y = y;
 	ui->newpoint.d = ds->tilesize;
-	return "";
+	return UI_UPDATE;
     } else if (IS_MOUSE_RELEASE(button) && ui->dragpoint >= 0) {
 	int p = ui->dragpoint;
 	char buf[80];
@@ -1139,7 +1139,7 @@
             ui->newpoint.x >= (long)state->w*ui->newpoint.d ||
 	    ui->newpoint.y < 0 ||
             ui->newpoint.y >= (long)state->h*ui->newpoint.d)
-	    return "";
+	    return UI_UPDATE;
 
 	/*
 	 * We aren't cancelling the drag. Construct a move string