]> git.cworth.org Git - lmno-server/commitdiff
letterrip, anagrams: Make game reload smoother
authorCarl Worth <cworth@cworth.org>
Sun, 8 Mar 2026 15:13:50 +0000 (11:13 -0400)
committerCarl Worth <cworth@cworth.org>
Sun, 8 Mar 2026 15:13:50 +0000 (11:13 -0400)
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.

anagrams.js
letterrip.js

index cdf9e3df6cccad3f2b83b44702420b368a0de28b..f6b551f9e522e789ae53e40163969eb7c94c6c6a 100644 (file)
@@ -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) => {
index f717aa6d51e86dc2af518162b78a8a5f1fd60e12..a1e4fc88ffbcefbd94df886efd85d80c37445c90 100644 (file)
@@ -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) => {