From: Carl Worth Date: Sun, 8 Mar 2026 22:34:49 +0000 (-0400) Subject: anaagrams: Implement a game-ending modal X-Git-Url: https://git.cworth.org/git?a=commitdiff_plain;h=93ac8cf9b35c0cb27cf6c085f0cbb031d00c3a21;p=lmno.games anaagrams: Implement a game-ending modal Once the bag is empty and all players have gone idle, (nobody has even attempted to claim a word in some time), the server will report that the game is ready to end. The client then displays a modal with a countdown giving the user the chance to say whether they are still looking for words. If anyone is still looking, the server should start its idle counter over. If not, then the server should declare the game over. --- diff --git a/anagrams/anagrams.jsx b/anagrams/anagrams.jsx index 929f57d..8056fe2 100644 --- a/anagrams/anagrams.jsx +++ b/anagrams/anagrams.jsx @@ -128,6 +128,9 @@ class Game extends React.Component { my_vote: null, votes_cast: 0, voters_total: 0, + /* Game ending countdown */ + game_ending: false, + game_ending_seconds: 0, /* Game over */ game_over: false, final_scores: null, @@ -150,6 +153,7 @@ class Game extends React.Component { componentWillUnmount() { document.removeEventListener("keydown", this._onKeyDown); if (this._debug_interval) clearInterval(this._debug_interval); + this._clear_game_ending(); } _check_state() { @@ -422,12 +426,46 @@ class Game extends React.Component { } receive_game_over(data) { + this._clear_game_ending(); this.setState({ game_over: true, final_scores: data.scores }); } + receive_game_ending(data) { + this._clear_game_ending(); + const seconds = Math.ceil(data.countdown_ms / 1000); + this.setState({ game_ending: true, game_ending_seconds: seconds }); + this._game_ending_interval = setInterval(() => { + this.setState(prev => { + const next = prev.game_ending_seconds - 1; + if (next <= 0) { + clearInterval(this._game_ending_interval); + this._game_ending_interval = null; + return { game_ending_seconds: 0 }; + } + return { game_ending_seconds: next }; + }); + }, 1000); + } + + receive_game_continued() { + this._clear_game_ending(); + this.setState({ game_ending: false, game_ending_seconds: 0 }); + } + + _clear_game_ending() { + if (this._game_ending_interval) { + clearInterval(this._game_ending_interval); + this._game_ending_interval = null; + } + } + + async still_looking() { + await fetch_post_json("still-looking"); + } + /***************************************************** * Actions * *****************************************************/ @@ -981,6 +1019,18 @@ class Game extends React.Component { voters_total={state.voters_total} onVote={(accept) => this.vote(accept)} /> + ) : null, + + state.game_ending ? ( +
+
+

Are you still playing?

+

Game ends in {state.game_ending_seconds} seconds

+ +
+
) : null ]; } @@ -1551,6 +1601,14 @@ events.addEventListener("done-update", event => { /* Could show progress, but game-over handles the end. */ }); +events.addEventListener("game-ending", event => { + window.game.receive_game_ending(JSON.parse(event.data)); +}); + +events.addEventListener("game-continued", event => { + window.game.receive_game_continued(); +}); + events.addEventListener("game-over", event => { window.game.receive_game_over(JSON.parse(event.data)); });