]> git.cworth.org Git - lmno.games/commitdiff
anagrams: Again, follow the rule that server events are the source of truth
authorCarl Worth <cworth@cworth.org>
Mon, 9 Mar 2026 01:20:20 +0000 (21:20 -0400)
committerCarl Worth <cworth@cworth.org>
Mon, 9 Mar 2026 01:20:20 +0000 (21:20 -0400)
Prior code here was returning letters from the rack (claim area) to
the center of the board, but then server events would also return the
letters, resulting in duplicate letters appearing in the center as
ghosts.

Meanwhile, there was an opposite problem in response to the user
pressing backspace in quick succession. Because the client was waiting
for the server to remove the tile from the rack, addiitional presses
of backspace would all apply to the same last tile, ad would fail.

So, now, as of this commit, we remove tiles from the rack with
backspace, (allowing subsequent presses to target subsequent letters)
but wait for the server to tell us the tile has been returned to the
center area. This should keep state in sync and keep things humming
along.

anagrams/anagrams.jsx

index 8056fe213c5385b988900e99779a53520e072471..4afbfd4a69844798c9d94edf387070fd2a5af8c0 100644 (file)
@@ -532,23 +532,31 @@ class Game extends React.Component {
   }
 
   async _return_center_letter(tile, rack_index) {
+    /* Optimistically remove from rack so that rapid backspaces
+     * target different entries instead of repeating the same one. */
+    this.setState(prev => ({
+      claim_rack: prev.claim_rack.filter((_, i) => i !== rack_index),
+      claim_center_tiles: prev.claim_center_tiles.filter(
+        t => t.id !== tile.id),
+      claim_error: null
+    }));
+
     const response = await fetch_post_json("return-letter", {
       letter_id: tile.id
     });
-    if (response.ok) {
-      const positions = { ...this.state.tile_positions };
-      if (!positions[tile.id]) {
-        positions[tile.id] = this._random_position();
-      }
+    if (!response.ok) {
+      /* Revert: put the entry back in the rack. */
       this.setState(prev => ({
-        claim_rack: prev.claim_rack.filter((_, i) => i !== rack_index),
-        claim_center_tiles: prev.claim_center_tiles.filter(
-          t => t.id !== tile.id),
-        center: [...prev.center, tile],
-        tile_positions: positions,
-        claim_error: null
+        claim_rack: [...prev.claim_rack, {
+          letter: tile.letter, source: "center", tile
+        }],
+        claim_center_tiles: [...prev.claim_center_tiles, tile]
       }));
     }
+    /* Don't add tile back to center here. The SSE letter-returned
+     * event (receive_letter_returned) is the single source of truth
+     * for center updates, avoiding the double-add that created
+     * ghost tiles. */
   }
 
   async steal_word(owner_session, word_obj) {