]> git.cworth.org Git - lmno.games/commitdiff
letterrip: Fix blank-letter to work even if letter selection is cancelled
authorCarl Worth <cworth@cworth.org>
Sat, 7 Mar 2026 23:05:55 +0000 (18:05 -0500)
committerCarl Worth <cworth@cworth.org>
Sat, 7 Mar 2026 23:05:55 +0000 (18:05 -0500)
If a user is moving a blank tile off of their rack onto the board,
then they are required to choose a letter for the blank. In this
situation, cancelling letter selection cause the blank tile to be
returned to the rack.

In contrast, when moving a blank tile from one space on the board to
the other, the blank will already have a selected letter, and it's
reasonable for that selection to be unchanged. So in this case,
cancelling the letter selection should let the movement work, and
should not send the letter back to the rack.

This commit fixes that scenario by performing the movement prior to
bringing up the selection modal. That way, if the selection modal is
cancelled, there's nothing else to do and the user gets the desired
behavior (the tile moved, unchanged).

letterrip/letterrip.jsx

index a6bc2349ffb8fc44d58414656680f67c17f5d89d..6ea7fdb35ed438df34f0a42650bcbebc1e19050c 100644 (file)
@@ -736,7 +736,7 @@ class Game extends React.Component {
       const updates = { selected: null };
       if (cell && cell.isBlank) {
         const [r, c] = grid_key.split(",").map(Number);
-        updates.blank_pending = { r, c, tileIndex: cell.tileIndex, reassign: true };
+        updates.blank_pending = { r, c, tileIndex: cell.tileIndex };
       }
       this.setState(updates);
       return;
@@ -803,8 +803,16 @@ class Game extends React.Component {
       delete new_grid[sel.grid_key];
     }
 
-    /* Blank tile — always show the modal so the user can pick a letter. */
+    /* Blank tile — show the modal so the user can pick a letter.
+     * If the blank already had a letter (moved from grid), place it
+     * at the destination with that letter first. The modal then just
+     * offers a chance to change it, and cancel is a no-op. */
     if (is_blank) {
+      const existing = sel.from === "grid" ? this.state.grid[sel.grid_key] : null;
+      if (existing && existing.letter) {
+        new_grid[grid_key] = { letter: existing.letter,
+                               tileIndex: tile_index, isBlank: true };
+      }
       this.setState({
         grid: new_grid,
         rack: new_rack,
@@ -888,14 +896,16 @@ class Game extends React.Component {
             <BlankTileModal
               onChoose={(l) => this.choose_blank_letter(l)}
               onCancel={() => {
-                /* Put the blank tile back on the rack if cancelled,
-                 * but only if it was freshly placed (not a reassignment
-                 * of a blank already on the grid). */
-                if (state.blank_pending.reassign) {
+                const pending = state.blank_pending;
+                const key = pending.r + "," + pending.c;
+                if (state.grid[key]) {
+                  /* Tile is already on the grid (moved with an
+                   * existing letter, or reassigning in place).
+                   * Just dismiss the modal. */
                   this.setState({ blank_pending: null });
                 } else {
-                  const { tileIndex } = state.blank_pending;
-                  const new_rack = [...state.rack, tileIndex];
+                  /* Fresh blank from rack — return it. */
+                  const new_rack = [...state.rack, pending.tileIndex];
                   this.setState({ blank_pending: null, rack: new_rack });
                 }
               }}