From: Carl Worth Date: Sun, 8 Mar 2026 15:13:50 +0000 (-0400) Subject: letterrip, anagrams: Make game reload smoother X-Git-Url: https://git.cworth.org/git?a=commitdiff_plain;h=b6072f9d5b3ab8500105f40bcc8607d77bbd91ef;p=lmno-server letterrip, anagrams: Make game reload smoother Previously we were requiring each player to do a separate join_game (even after they were already part of the game). Notably, if the user ever reloaded their browser, they had to click "Join game" yet again. Instead, in this commit we add a game.started state. And we only need one player to start the game, triggering the send of the started game state to all players. Now, if a player reloads their browser, they will just see the game state, and not a "join game" button. --- diff --git a/anagrams.js b/anagrams.js index cdf9e3d..f6b551f 100644 --- a/anagrams.js +++ b/anagrams.js @@ -29,6 +29,7 @@ class Anagrams extends Game { /* Voting state. */ vote_pending: null, /* Game state. */ + started: false, finished: false, done_players: new Set() }; @@ -629,7 +630,7 @@ class Anagrams extends Game { * Join / Events / Game lifecycle * *****************************************************/ - handle_join(request, response) { + handle_start(request, response) { const session_id = request.session.id; const player = this.players_by_session[session_id]; @@ -638,16 +639,17 @@ class Anagrams extends Game { return; } - if (!this.state.player_words[session_id]) { - this.state.player_words[session_id] = []; + this.state.started = true; + + /* Add all active players to the game. */ + for (const p of this.players) { + if (p.active && !this.state.player_words[p.session_id]) { + this.state.player_words[p.session_id] = []; + } } - response.json({ - center: this.state.center, - player_words: this._all_player_words(), - scores: this._compute_all_scores(), - remaining: this.state.bag.length - }); + this.broadcast_event_object("game-started", {}); + response.json({ ok: true }); } handle_done(request, response) { @@ -701,6 +703,14 @@ class Anagrams extends Game { const session_id = request.session.id; + /* If the game has already started, auto-join this player. */ + if (this.state.started) { + if (!this.state.player_words[session_id]) { + this.state.player_words[session_id] = []; + } + response.write(`event: game-started\ndata: {}\n\n`); + } + /* Send center state. */ response.write(`event: center\ndata: ${JSON.stringify( this.state.center @@ -754,8 +764,8 @@ class Anagrams extends Game { Anagrams.router = express.Router(); const router = Anagrams.router; -router.post('/join', (request, response) => { - request.game.handle_join(request, response); +router.post('/start', (request, response) => { + request.game.handle_start(request, response); }); router.post('/claim', (request, response) => { diff --git a/letterrip.js b/letterrip.js index f717aa6..a1e4fc8 100644 --- a/letterrip.js +++ b/letterrip.js @@ -14,6 +14,7 @@ class LetterRip extends Game { player_boards: {}, results: null, stuck: new Set(), + started: false, finished: false }; } @@ -55,7 +56,18 @@ class LetterRip extends Game { return true; } - handle_join(request, response) { + /* Deal initial tiles to a single player. */ + _deal_initial(session_id) { + if (this.state.player_tiles[session_id]) return; + + const tiles = []; + for (let i = 0; i < INITIAL_TILES && this.state.bag.length > 0; i++) { + tiles.push(this.state.bag.pop()); + } + this.state.player_tiles[session_id] = tiles; + } + + handle_start(request, response) { const session_id = request.session.id; const player = this.players_by_session[session_id]; @@ -64,31 +76,31 @@ class LetterRip extends Game { return; } - /* Don't re-deal if player already has tiles. */ - if (this.state.player_tiles[session_id]) { - response.json({ - tiles: this.state.player_tiles[session_id], - remaining: this.state.bag.length - }); - return; - } + this.state.started = true; - /* Deal initial tiles. */ - const tiles = []; - for (let i = 0; i < INITIAL_TILES && this.state.bag.length > 0; i++) { - tiles.push(this.state.bag.pop()); + /* Deal initial tiles to all active players. */ + for (const p of this.players) { + if (p.active) { + this._deal_initial(p.session_id); + } } - this.state.player_tiles[session_id] = tiles; - this.broadcast_event_object("player-joined-game", { - name: player.name - }); + /* Send each player their tiles. */ + for (const p of this.players) { + if (p.active && this.state.player_tiles[p.session_id]) { + const data = JSON.stringify({ + tiles: this.state.player_tiles[p.session_id] + }); + p.send(`event: tiles\ndata: ${data}\n\n`); + } + } + this.broadcast_event_object("game-started", {}); this.broadcast_event_object("dealt", { remaining: this.state.bag.length }); - response.json({ tiles: tiles, remaining: this.state.bag.length }); + response.json({ ok: true }); } handle_stuck(request, response) { @@ -463,6 +475,13 @@ class LetterRip extends Game { super.handle_events(request, response); const session_id = request.session.id; + + /* If the game has already started, auto-join this player. */ + if (this.state.started) { + this._deal_initial(session_id); + response.write(`event: game-started\ndata: {}\n\n`); + } + const tiles = this.state.player_tiles[session_id]; /* Send this player's current tiles so reconnecting players @@ -510,8 +529,8 @@ class LetterRip extends Game { LetterRip.router = express.Router(); const router = LetterRip.router; -router.post('/join', (request, response) => { - request.game.handle_join(request, response); +router.post('/start', (request, response) => { + request.game.handle_start(request, response); }); router.post('/stuck', (request, response) => {