Pre-shuffle the bag to avoids risks of nasty endless loops
authorRaphaël Jakse <raphael.trivabble@jakse.fr>
Wed, 15 Apr 2020 19:53:32 +0000 (21:53 +0200)
committerRaphaël Jakse <raphael.trivabble@jakse.fr>
Wed, 15 Apr 2020 19:53:32 +0000 (21:53 +0200)
trivabble-server.js

index 858ec7c65b24a3f8b2282dde3a7d73a444e3308c..f88b4bfb415219265ebe4b1b26c205b364988f5f 100644 (file)
@@ -134,6 +134,18 @@ function saveGames(callback) {
     saveTimeout = null;
 }
 
+
+function shuffleInPlace(a) {
+    // https://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array
+
+    for (let i = a.length - 1; i > 0; i--) {
+        const j = Math.floor(Math.random() * (i + 1));
+        const x = a[i];
+        a[i] = a[j];
+        a[j] = x;
+    }
+}
+
 function Game() {
     this.letterValues = frValues;
     this.init();
@@ -247,31 +259,42 @@ function newBoard() {
 Game.prototype.init = function () {
     this.board = newBoard();
     this.bag = frBag.slice();
-    this.remainingLetters = this.bag.length;
     this.racks = {};
     this.scores = {};
     this.lastUpdated = new Date();
+
+    shuffleInPlace(this.bag);
 };
 
 Game.prototype.toJSON = function () {
     return {
-        remainingLetters: this.remainingLetters,
         board:            this.board,
         bag:              this.bag,
         racks:            this.racks,
         scores:           this.scores,
-        lastUpdated:      this.lastUpdated.toISOString()
+        lastUpdated:      this.lastUpdated.toISOString(),
+        bagShuffled:      true // Will be gone soon, needed for compatibility with saved games
     };
 };
 
 Game.fromJSON = function (obj) {
+    // WARNING: modifies the passed parameter because of the suffling.
+    // This is harmless here, and temporary anyway.
+    // Variable bagShuffled is soon to be gone.
+
     const game = new Game();
     game.board            = obj.board  || newBoard();
     game.bag              = obj.bag    || frBag.slice();
-    game.remainingLetters = obj.remainingLetters || game.bag.length;
     game.racks            = obj.racks  || {};
     game.scores           = obj.scores || {};
     game.lastUpdated      = obj.lastUpdated ? new Date(obj.lastUpdated) : new Date();
+
+    // To delete soon
+    if (!obj.bagShuffled) {
+        game.bag = game.bag.filter((tile) => tile !== "");
+        shuffleInPlace(game.bag);
+    }
+
     return game;
 };
 
@@ -377,21 +400,12 @@ Game.prototype.commit = function () {
 };
 
 Game.prototype.bagPopLetter = function (player) {
-    if (this.remainingLetters) {
-        let letter = "";
-        let index;
-
-        while (letter === "") {
-            index = Math.floor(Math.random() * this.bag.length);
-            letter = this.bag[index];
-        }
-
-        this.bag[index] = "";
-        this.remainingLetters--;
+    if (this.bag.length) {
+        const letter = this.bag.pop();
         this.pendingEvents.push({
             player: player,
             action: "popBag",
-            remainingLetters: this.remainingLetters
+            remainingLetters: this.bag.length
         });
 
         return letter;
@@ -416,20 +430,13 @@ Game.prototype.setCell = function (index, letter, player) {
 
 Game.prototype.bagPushLetter = function (letter, player) {
     if (letter) {
-        let index = 0;
-
-        while (this.bag[index] !== "") {
-            index++;
-        }
-
-        this.bag[index] = letter;
-
-        this.remainingLetters++;
+        this.bag.push(letter);
+        shuffleInPlace(this.bag);
 
         this.pendingEvents.push({
             player: player,
             action: "pushBag",
-            remainingLetters: this.remainingLetters
+            remainingLetters: this.bag.length
         });
     }
 };
@@ -440,7 +447,7 @@ Game.prototype.reset = function (player) {
         player: player,
         action: "reset",
         board: this.board,
-        remainingLetters: this.remainingLetters,
+        remainingLetters: this.bag.length,
         rack: []
     });
 
@@ -507,7 +514,7 @@ function handleCommand(cmd, gameNumber, playerName, response) {
                     "playerName": playerName,
                     "rack": game.getPlayerRack(playerName),
                     "board": game.board,
-                    "remainingLetters": game.remainingLetters,
+                    "remainingLetters": game.bag.length,
                     "letterValues": frValues
                 })
             );
@@ -604,7 +611,7 @@ function handleCommand(cmd, gameNumber, playerName, response) {
                     from: cmd.from,
                     to: cmd.to,
                     indexFrom: typeof cmd.indexFrom === "number" ? cmd.indexFrom : -1,
-                    remainingLetters: game.remainingLetters
+                    remainingLetters: game.bag.length
                 })
             );
 
@@ -710,7 +717,7 @@ function handleCommands(cmds, responseAndType) {
                 "letterValues":     game.letterValues,
                 "rack":             game.getPlayerRack(cmds.playerName),
                 "board":            game.board,
-                "remainingLetters": game.remainingLetters
+                "remainingLetters": game.bag.length
             })
         );