]> git.cworth.org Git - lmno.games/commitdiff
Use new /place endpoint to tell server aboud tile placements
authorCarl Worth <cworth@cworth.org>
Fri, 6 Mar 2026 14:37:27 +0000 (09:37 -0500)
committerCarl Worth <cworth@cworth.org>
Fri, 6 Mar 2026 16:29:48 +0000 (11:29 -0500)
And accept the board contents when the server provides it.

This is all so that a browser refresh doesn't destroy board state.

letterrip/letterrip.jsx

index ca07ee71cf1ca4d7096ac27255ba39171da388cd..4d58ca8e58afe68091ff562d8568f999d91fb69f 100644 (file)
@@ -356,11 +356,61 @@ class Game extends React.Component {
     document.addEventListener("touchend", this._onTouchEnd);
   }
 
+  componentDidUpdate(prevProps, prevState) {
+    if (prevState.grid !== this.state.grid) {
+      if (this._skip_sync) {
+        this._skip_sync = false;
+      } else {
+        this.sync_placements(prevState.grid, this.state.grid);
+      }
+    }
+  }
+
   componentWillUnmount() {
     document.removeEventListener("touchmove", this._onTouchMove);
     document.removeEventListener("touchend", this._onTouchEnd);
   }
 
+  /* Send tile placement changes to the server. */
+  sync_placements(old_grid, new_grid) {
+    /* Find tiles that were removed or moved. */
+    for (const key of Object.keys(old_grid)) {
+      const old_cell = old_grid[key];
+      const new_cell = new_grid[key];
+      if (!new_cell || new_cell.tileIndex !== old_cell.tileIndex) {
+        /* This tile was removed from this position. Check if it moved
+         * to a new position (handled below) or returned to rack. */
+        const still_on_grid = Object.values(new_grid).some(
+          c => c.tileIndex === old_cell.tileIndex
+        );
+        if (!still_on_grid) {
+          fetch("place", {
+            method: "POST",
+            headers: { "Content-Type": "application/json" },
+            body: JSON.stringify({ tileIndex: old_cell.tileIndex,
+                                   r: null, c: null })
+          });
+        }
+      }
+    }
+
+    /* Find tiles that were placed or moved to a new position. */
+    for (const key of Object.keys(new_grid)) {
+      const new_cell = new_grid[key];
+      const old_cell = old_grid[key];
+      if (!old_cell || old_cell.tileIndex !== new_cell.tileIndex) {
+        const [r, c] = key.split(",").map(Number);
+        fetch("place", {
+          method: "POST",
+          headers: { "Content-Type": "application/json" },
+          body: JSON.stringify({ tileIndex: new_cell.tileIndex, r, c,
+                                 letter: new_cell.letter,
+                                 isBlank: new_cell.isBlank })
+        });
+      }
+    }
+  }
+
   /*****************************************************
    * SSE event handlers (called from window.game)      *
    *****************************************************/
@@ -392,6 +442,22 @@ class Game extends React.Component {
     this.setState({ joined: true, tiles: data.tiles, rack: data.tiles.map((_, i) => i) });
   }
 
+  receive_board(board) {
+    /* Restore grid from server on reconnect. Board is an object
+     * like { "r,c": { tileIndex, letter, isBlank } }. */
+    const grid = {};
+    const placed = new Set();
+    for (const key of Object.keys(board)) {
+      grid[key] = board[key];
+      placed.add(board[key].tileIndex);
+    }
+    /* Remove placed tiles from the rack. Skip syncing back to server
+     * since this data came from the server. */
+    const rack = this.state.rack.filter(i => !placed.has(i));
+    this._skip_sync = true;
+    this.setState({ grid, rack });
+  }
+
   receive_new_tiles(data) {
     const old_len = this.state.tiles.length;
     const new_tiles = [...this.state.tiles, ...data.tiles];
@@ -1072,6 +1138,10 @@ events.addEventListener("tiles", event => {
   window.game.receive_tiles(JSON.parse(event.data));
 });
 
+events.addEventListener("board", event => {
+  window.game.receive_board(JSON.parse(event.data));
+});
+
 events.addEventListener("new-tiles", event => {
   window.game.receive_new_tiles(JSON.parse(event.data));
 });