]> git.cworth.org Git - lmno-server/commitdiff
Empathy: Use word groups to assign scores to players, not their lists
authorCarl Worth <cworth@cworth.org>
Thu, 11 Jun 2020 23:37:43 +0000 (16:37 -0700)
committerCarl Worth <cworth@cworth.org>
Thu, 11 Jun 2020 23:37:43 +0000 (16:37 -0700)
Previously, we were marching through each players list of words and
looking each word up in the word groups to find a score for that
word. This worked in general, but failed in a specific case:

If a player had two different words that were considered by the group
to be equivalent, that player received the score for that word
twice. To avoid giving a palyer in this situation points that the
group clearly didn't consider they deserved, we instead iterative over
the worud groups themselves to assign points out to players.

This means that when a player ends up having two (or more) words that
end up being equivalent, (based on the team consensus when judging),
all but one of those words will be worth nothing.

empathy.js

index 7966d72db185bf4846a83c3b811cb3d457c88d27..1da9b8ef07ce8fde9b221fcb65715ef11c00e9d4 100644 (file)
@@ -196,7 +196,6 @@ class Empathy extends Game {
 
   compute_scores() {
     const word_submitters = {};
-    const scores = [];
 
     /* Perform a (non-strict) majority ruling on equivalencies,
      * dropping all that didn't get enough votes. */
@@ -206,74 +205,88 @@ class Empathy extends Game {
 
     /* And with that agreed set of equivalencies, construct the full
      * groups. */
-    const word_groups = {};
+    const word_maps = {};
 
     for (let e of agreed_equivalencies) {
-      let group = word_groups[e.words[0]];
+      let group = word_maps[e.words[0]];
       if (! group)
-        group = word_groups[e.words[1]];
+        group = word_maps[e.words[1]];
       if (! group)
         group = { words: [], players: new Set()};
 
-      if (! word_groups[e.words[0]]) {
-        word_groups[e.words[0]] = group;
+      if (! word_maps[e.words[0]]) {
+        word_maps[e.words[0]] = group;
         group.words.push(e.words[0]);
       }
 
-      if (! word_groups[e.words[1]]) {
-        word_groups[e.words[1]] = group;
+      if (! word_maps[e.words[1]]) {
+        word_maps[e.words[1]] = group;
         group.words.push(e.words[1]);
       }
     }
 
-    /* Now go through answers from each player and add the player name
+    /* Now go through answers from each player and add the player
      * to the set corresponding to each word group. */
     for (let a of this.answers) {
       for (let word of a.answers) {
         /* If there's no group yet, this is a singleton word. */
-        if (word_groups[word]) {
-          word_groups[word].players.add(a.player.name);
+        if (word_maps[word]) {
+          word_maps[word].players.add(a.player);
         } else {
           const group = { words: [word], players: new Set() };
-          group.players.add(a.player.name);
-          word_groups[word] = group;
+          group.players.add(a.player);
+          word_maps[word] = group;
         }
       }
     }
 
-    /* Finally, go through the answers one more time, this time taking
-     * the length of the player set as a score for the player's
-     * submission of that word. */
-    for (let a of this.answers) {
-      let score = 0;
-      for (let word of a.answers) {
-        score += word_groups[word].players.size;
-      }
-      scores.push({
-        player: a.player.name,
-        score: score
-      });
+    /* Now that we've assigned the players to these word maps, we now
+     * want to collapse the groups down to a single array of
+     * word_groups.
+     *
+     * The difference between "word_maps" and "word_groups" is that
+     * the former has a property for every word that maps to a group,
+     * (so if you iterate over the keys you will see the same group
+     * multiple times). In contrast, iterating over"word_groups" will
+     * have you visit each group only once. */
+    const word_groups = Object.entries(word_maps).filter(
+      entry => entry[0] === entry[1].words[0]).map(entry => entry[1]);
+
+    /* Now, go through each word group and assign the scores out to
+     * the corresponding players.
+     *
+     * Note: We do this by going through the word groups, (as opposed
+     * to the list of words from the players again), specifically to
+     * avoid giving a player points for a wrod group twice (in the
+     * case where a player submits two different words that the group
+     * ends up judging as equivalent).
+     */
+    this.players.forEach(p => p.round_score = 0);
+    for (let group of word_groups) {
+      group.players.forEach(p => p.round_score += group.players.size);
     }
 
+    const scores = this.players.map(p => {
+      return {
+        player: p.name,
+        score: p.round_score
+      };
+    });
+
     scores.sort((a,b) => {
       return b.score - a.score;
     });
 
     /* Put the word groups into a form the client can consume.
-     *
-     * Most significantly, we only want one entry for each group (even
-     * though our current "word_groups" object has a property for each
-     * word considered equivalent).
      */
-    const words_submitted = Object.entries(word_groups).filter(
-      group => group[0] === group[1].words[0]).map(
-        group => {
-          return {
-            word: group[1].words.join('/'),
-            players: Array.from(group[1].players)
-          };
-        }
-      );
+    const words_submitted = word_groups.map(
+      group => {
+        return {
+          word: group.words.join('/'),
+          players: Array.from(group.players).map(p => p.name)
+        };
+      }
+    );
 
     words_submitted.sort((a,b) => {
       return b.players.length - a.players.length;