From: Carl Worth Date: Mon, 9 Mar 2026 15:34:02 +0000 (-0700) Subject: Implement better support for a queue of claimants X-Git-Url: https://git.cworth.org/git?a=commitdiff_plain;h=013c1f272f40ba5d8f01aa77392dc4c4aeb3fc53;p=lmno-server Implement better support for a queue of claimants And when sending an event to the clients to end one claim, indicate in that event the next claimant so that the client can transition from one to the next cleanly without a tranient "no claim in progress" state. --- diff --git a/anagrams.js b/anagrams.js index db8d23a..51326b0 100644 --- a/anagrams.js +++ b/anagrams.js @@ -144,7 +144,15 @@ class Anagrams extends Game { /* Don't allow if already in queue. */ if (this.state.claim_queue.includes(session_id)) { - response.json({ queued: true, active: session_id === this.active_claimer_session() }); + const pos = this.state.claim_queue.indexOf(session_id); + const active_session = this.active_claimer_session(); + const active_player = this.players_by_session[active_session]; + response.json({ + queued: true, + active: session_id === active_session, + position: pos, + active_player: active_player ? active_player.name : null + }); return; } @@ -152,33 +160,41 @@ class Anagrams extends Game { /* If this is the only person in queue, activate them. */ if (this.state.claim_queue.length === 1) { - this._activate_claimer(); - } else { - /* Let them know they're queued. */ - const player = this.players_by_session[session_id]; - if (player) { - player.send(`event: claim-queued\ndata: ${JSON.stringify({ - position: this.state.claim_queue.indexOf(session_id) - })}\n\n`); - } + this._activate_claimer(true); } - response.json({ queued: true, active: session_id === this.active_claimer_session() }); + const pos = this.state.claim_queue.indexOf(session_id); + const active_session = this.active_claimer_session(); + const active_player = this.players_by_session[active_session]; + response.json({ + queued: true, + active: session_id === active_session, + position: pos, + active_player: active_player ? active_player.name : null + }); } - _activate_claimer() { + /* Set up the active claimer's state and timers. If broadcast is + * true, send a standalone claim-start event (used for the first + * claim when no prior claim is ending). Returns the claim-start + * data for callers that merge it into a claim-end event. */ + _activate_claimer(broadcast) { const session_id = this.active_claimer_session(); - if (!session_id) return; + if (!session_id) return null; this.state.claimed_letters = []; this.state.claimed_words = []; const player = this.players_by_session[session_id]; - this.broadcast_event_object("claim-start", { + const data = { player_name: player ? player.name : "Unknown", timeout_ms: CLAIM_TIMEOUT_MS, warning_ms: CLAIM_WARNING_MS - }); + }; + + if (broadcast) { + this.broadcast_event_object("claim-start", data); + } /* Start claim timeout. */ this._claim_warning_timer = setTimeout(() => { @@ -190,6 +206,8 @@ class Anagrams extends Game { this._claim_timer = setTimeout(() => { this._cancel_claim("timeout"); }, CLAIM_TIMEOUT_MS); + + return data; } /* Take a letter from the center. */ @@ -352,19 +370,23 @@ class Anagrams extends Game { const session_id = this.state.claim_queue.shift(); const player = this.players_by_session[session_id]; + + /* Activate next claimer (if any) and merge into claim-end + * as a single atomic event so clients never see a gap. */ + let next = null; + if (this.state.claim_queue.length > 0) { + next = this._activate_claimer(false); + } + this.broadcast_event_object("claim-end", { player_name: player ? player.name : "Unknown", - reason + reason, + next }); /* Broadcast authoritative state so clients resync. */ this.broadcast_event_object("center", this.state.center); this._broadcast_player_state(); - - /* Activate next claimer if any. */ - if (this.state.claim_queue.length > 0) { - this._activate_claimer(); - } } _clear_claim_timers() { @@ -521,21 +543,22 @@ class Anagrams extends Game { score }); - /* End the claim so clients exit claim mode. */ + /* Activate next claimer (if any) and merge into claim-end. */ + let next = null; + if (this.state.claim_queue.length > 0) { + next = this._activate_claimer(false); + } + this.broadcast_event_object("claim-end", { player_name: player ? player.name : "Unknown", - reason: "accepted" + reason: "accepted", + next }); /* Broadcast authoritative state so clients resync. */ this.broadcast_event_object("center", this.state.center); this._broadcast_player_state(); - /* Activate next claimer if any. */ - if (this.state.claim_queue.length > 0) { - this._activate_claimer(); - } - /* Replenish center if needed. */ this._auto_deal(); } @@ -858,7 +881,7 @@ class Anagrams extends Game { if (this.state.vote_pending) { this._vote_timer = setTimeout(() => this._resolve_vote(), VOTE_TIMEOUT_MS); } else if (this.state.claim_queue.length > 0) { - this._activate_claimer(); + this._activate_claimer(true); } } }