]> git.cworth.org Git - lmno-server/commitdiff
Accept votes to advance game at /end-answers and /end-judging endpoints
authorCarl Worth <cworth@cworth.org>
Sat, 13 Jun 2020 21:38:28 +0000 (14:38 -0700)
committerCarl Worth <cworth@cworth.org>
Sun, 14 Jun 2020 21:58:53 +0000 (14:58 -0700)
The previous logic was far too strict, where the game would advance
only after every registered player had submitted. That made it
possible for the game to become totally stuck with just a single
player that left the game, (or even that accidentally reloaded the
game in a way that lost their previous session).

Now there are endpoints for players to vote that they would like to
advance past either the "answers" or "judging" phases of the game, and
the game will advance as soon as a majority of the registered players
have voted.

This does still make it possible for the game to get stuck if a
sufficient number of players stop playing, but it's at least less
likely to happen. And we've got plans to improve this situation
further soon as well.

empathy.js

index cfe0618d76f211a0e3ceeb69b017c3c90a187100..eaf12bf8deb0a6b74d8358922643052da1afc7c2 100644 (file)
@@ -9,9 +9,13 @@ class Empathy extends Game {
     this.state = {
       prompts: [],
       active_prompt: null,
-      players_answered: 0,
+      players_answered: [],
+      players_answering: new Set(),
+      end_answers: new Set(),
       ambiguities: null,
-      players_judged: 0,
+      players_judged: [],
+      players_judging: new Set(),
+      end_judging: new Set(),
       scores: null
     };
     this.answers = [];
@@ -45,9 +49,13 @@ class Empathy extends Game {
       );
 
     this.state.active_prompt = null;
-    this.state.players_answered = 0;
+    this.state.players_answered = [];
+    this.state.players_answering = new Set();
+    this.state.end_answers = new Set();
     this.state.ambiguities = 0;
-    this.state.players_judged = 0;
+    this.state.players_judged = [];
+    this.state.players_judging = new Set();
+    this.state.end_judging = new Set();
     this.state.scores = null;
 
     this.answers = [];
@@ -127,13 +135,32 @@ class Empathy extends Game {
       answers: answers
     });
 
-    /* And notify players how many players have answered. */
-    this.state.players_answered++;
-    this.broadcast_event_object('answered', this.state.players_answered);
+    /* And notify all players that this player has answered. */
+    this.state.players_answered.push(player.name);
+    this.broadcast_event_object('player-answered', player.name);
 
     return { valid: true };
   }
 
+  /* Returns true if vote toggled, false for player or prompt not found */
+  toggle_end_answers(prompt_id, session_id) {
+    const player = this.players_by_session[session_id];
+
+    const prompt = this.state.prompts.find(p => p.id === prompt_id);
+    if (! prompt || ! player)
+      return false;
+
+    if (this.state.end_answers.has(player.name)) {
+      this.state.end_answers.delete(player.name);
+      this.broadcast_event_object('unvote-end-answers', player.name);
+    } else {
+      this.state.end_answers.add(player.name);
+      this.broadcast_event_object('vote-end-answers', player.name);
+    }
+
+    return true;
+  }
+
   perform_judging() {
     const word_map = {};
 
@@ -198,13 +225,32 @@ class Empathy extends Game {
       }
     }
 
-    /* And notify players how many players have completed judging. */
-    this.state.players_judged++;
-    this.broadcast_event_object('judged', this.state.players_judged);
+    /* And notify all players this this player has judged. */
+    this.state.players_judged.push(player.name);
+    this.broadcast_event_object('player-judged', player.name);
 
     return { valid: true };
   }
 
+  /* Returns true if vote toggled, false for player or prompt not found */
+  toggle_end_judging(prompt_id, session_id) {
+    const player = this.players_by_session[session_id];
+
+    const prompt = this.state.prompts.find(p => p.id === prompt_id);
+    if (! prompt || ! player)
+      return false;
+
+    if (this.state.end_judging.has(player.name)) {
+      this.state.end_judging.delete(player.name);
+      this.broadcast_event_object('unvote-end-judging', player.name);
+    } else {
+      this.state.end_judging.add(player.name);
+      this.broadcast_event_object('vote-end-judging', player.name);
+    }
+
+    return true;
+  }
+
   canonize(word) {
     return word.toLowerCase();
   }
@@ -378,8 +424,18 @@ router.post('/answer/:prompt_id([0-9]+)', (request, response) => {
                                      request.session.id,
                                      request.body.answers);
   response.json(result);
+});
+
+router.post('/end-answers/:prompt_id([0-9]+)', (request, response) => {
+  const game = request.game;
+  const prompt_id = parseInt(request.params.prompt_id, 10);
+
+  if (game.toggle_end_answers(prompt_id, request.session.id))
+    response.send('');
+  else
+    response.sendStatus(404);
 
-  if (game.state.players_answered >= game.players.length)
+  if (game.state.end_answers.size > (game.state.players_answered.length / 2))
     game.perform_judging();
 });
 
@@ -391,8 +447,18 @@ router.post('/judging/:prompt_id([0-9]+)', (request, response) => {
                                       request.session.id,
                                       request.body.word_groups);
   response.json(result);
+});
+
+router.post('/end-judging/:prompt_id([0-9]+)', (request, response) => {
+  const game = request.game;
+  const prompt_id = parseInt(request.params.prompt_id, 10);
+
+  if (game.toggle_end_judging(prompt_id, request.session.id))
+    response.send('');
+  else
+    response.sendStatus(404);
 
-  if (game.state.players_judged >= game.players.length)
+  if (game.state.end_judging.size > (game.state.players_judged.length / 2))
     game.compute_scores();
 });