From 8f11b6b78138e9839298f97536156e5e0924acbd Mon Sep 17 00:00:00 2001 From: Carl Worth Date: Fri, 6 Mar 2026 09:37:27 -0500 Subject: [PATCH] Use new /place endpoint to tell server aboud tile placements 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 | 70 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/letterrip/letterrip.jsx b/letterrip/letterrip.jsx index ca07ee7..4d58ca8 100644 --- a/letterrip/letterrip.jsx +++ b/letterrip/letterrip.jsx @@ -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)); }); -- 2.45.2