/* 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;
}
/* 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(() => {
this._claim_timer = setTimeout(() => {
this._cancel_claim("timeout");
}, CLAIM_TIMEOUT_MS);
+
+ return data;
}
/* Take a letter from the center. */
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() {
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();
}
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);
}
}
}