fix: the disappearing letter is gone
authorRaphaël Jakse <raphael.trivabble@jakse.fr>
Wed, 22 Apr 2020 21:26:45 +0000 (23:26 +0200)
committerRaphaël Jakse <raphael.trivabble@jakse.fr>
Wed, 22 Apr 2020 21:26:45 +0000 (23:26 +0200)
trivabble-server.js
trivabble.js

index a9727c8a1694eaaf86b9408813d53a2dc45961af..b4b12181bfb144ae98d53183e7754c6dc3c74519 100644 (file)
@@ -538,71 +538,85 @@ function handleCommand(cmd, gameNumber, playerName, response) {
         case "moveLetter": {
             let letter = "";
 
+            // This case can fail in various ways. Instead altering the state
+            // of the game immediately, we store the operations to run them
+            // at the very end, if nothing failed.
+            const operations = [];
+
             switch (cmd.from) {
                 case "rack":
                     rack = game.getPlayerRack(playerName);
 
+                    if (cmd.indexFrom > 6 || cmd.indexFrom < 0) {
+                        response.write("{\"error\":1, \"reason\":\"Wrong indexFrom\"}");
+                        return false;
+                    }
+
                     letter = rack[cmd.indexFrom];
-                    rack[cmd.indexFrom] = "";
+                    operations.push(() => {rack[cmd.indexFrom] = ""});
                     break;
 
                 case "board":
-                    if (cmd.indexFrom < 15 * 15) {
-                        letter = game.getCell(cmd.indexFrom);
-                        game.setCell(cmd.indexFrom, "", playerName);
+                    if (cmd.indexFrom < 0 || cmd.indexFrom >= 15 * 15) {
+                        response.write("{\"error\":1, \"reason\":\"Wrong indexFrom\"}");
+                        return false;
                     }
+
+                    letter = game.getCell(cmd.indexFrom);
+                    operations.push(game.setCell.bind(game, cmd.indexFrom, "", playerName));
                     break;
 
                 case "bag":
-                    letter = game.bagPopLetter(playerName);
+                    if (!game.bag.length) {
+                        response.write("{\"error\":1, \"reason\":\"Empty bag\"}");
+                        return false;
+                    }
+
+                    operations.push(() => {letter = game.bagPopLetter(playerName)});
                     break;
 
                 default:
                     response.write("{\"error\":1, \"reason\":\"Moving letter from an unknown place\"}");
-                    return;
+                    return false;
             }
 
             switch (cmd.to) {
                 case "rack":
-                    if (cmd.indexTo > 7) {
-                        response.write("{\"error\":1, \"reason\":\"Moving letter to a index which is too high\"}");
-                        return;
+                    if (cmd.indexTo < 0 || cmd.indexTo > 6) {
+                        response.write("{\"error\":1, \"reason\":\"Wrong indexTo\"}");
+                        return false;
                     }
 
                     rack = rack || game.getPlayerRack(playerName);
-                    rack[cmd.indexTo] = letter;
+
+                    if (rack[cmd.indexTo]) {
+                        response.write("{\"error\":1, \"reason\":\"Moving a tile to a non-empty location\"}");
+                        return false;
+                    }
+
+                    operations.push(() => {rack[cmd.indexTo] = letter});
                     break;
 
                 case "board":
-                    if (cmd.indexTo < 15 * 15) {
-                        game.setCell(cmd.indexTo, letter, playerName);
+                    if (cmd.indexTo < 0 || cmd.indexTo >= 15 * 15) {
+                        response.write("{\"error\":1, \"reason\":\"Wrong indexTo\"}");
+                        return false;
                     }
+
+                    operations.push(game.setCell.bind(game, cmd.indexTo, letter, playerName));
                     break;
 
                 case "bag":
-                    game.bagPushLetter(letter, playerName);
+                    operations.push(game.bagPushLetter.bind(game, letter, playerName));
                     break;
 
                 default:
-                    switch (cmd.from) {
-                        case "rack":
-                            rack[cmd.indexFrom] = letter;
-                            break;
-
-                        case "board":
-                            game.setCell(cmd.indexFrom, letter, playerName);
-                            break;
-
-                        case "bag":
-                            game.bagPushLetter(letter, playerName);
-                            break;
-
-                        default:
-                            console.error("BUG: code should not have been reached.");
-                    }
-
                     response.write("{\"error\":1, \"reason\":\"Moving letter to an unknown place\"}");
-                    return;
+                    return false;
+            }
+
+            for (const operation of operations) {
+                operation();
             }
 
             response.write(
@@ -630,46 +644,39 @@ function handleCommand(cmd, gameNumber, playerName, response) {
         }
 
         case "setRack": {
-            cmd.rack.length = 8;
-
-            rack = game.getPlayerRack(playerName);
-
-            const initialRackCount = countTiles(rack);
-            const rackCount = countTiles(cmd.rack);
-
-            if (initialRackCount !== rackCount) {
+            if (cmd.rack.length > 7) {
                 response.write(
                     JSON.stringify({
                         error: 1,
                         rack: rack,
-                        reason: "the new rack doesn't contain the same number of letters"
+                        reason: "the new rack is not at the right size"
                     })
                 );
-                break;
+                return false;
             }
 
-            const oldRack = rack.slice();
-
-            for (let i = 0; i < 8; i++) {
-                cmd.rack[i] = (cmd.rack[i] || "")[0] || "";
-                if (cmd.rack[i]) {
-                    const indexLetter = oldRack.indexOf(cmd.rack[i]);
-
-                    if (indexLetter === -1) {
-                        response.write(
-                            JSON.stringify({
-                                error: 1,
-                                rack: rack,
-                                reason: "the new rack is not a permutation of the old rack"
-                            })
-                        );
-                        break;
-                    }
-                    oldRack[indexLetter] = false;
+            rack = game.getPlayerRack(playerName);
+
+            let oldRackSorted = rack.filter((l) => l);
+            oldRackSorted.sort();
+
+            let newRackSorted = cmd.rack.filter((l) => l);
+            newRackSorted.sort();
+
+            for (let i = 0; i < 7; i++) {
+                if ((oldRackSorted[i] !== newRackSorted[i]) && (oldRackSorted[i] || newRackSorted[i])) {
+                    response.write(
+                        JSON.stringify({
+                            error: 1,
+                            rack: rack,
+                            reason: "the new rack doesn't contain the same number of letters"
+                        })
+                    );
+                    return false;
                 }
             }
 
-            for (let i = 0; i < 8; i++) {
+            for (let i = 0; i < 7; i++) {
                 rack[i] = cmd.rack[i];
             }
 
@@ -704,9 +711,11 @@ function handleCommand(cmd, gameNumber, playerName, response) {
 
         default: {
             response.write("{\"error\":1,\"reason\":\"Unknown command\"}");
-            break;
+            return false;
         }
     }
+
+    return true;
 }
 
 function handleCommands(cmds, responseAndType) {
@@ -754,7 +763,10 @@ function handleCommands(cmds, responseAndType) {
         if (i) {
             response.write(",");
         }
-        handleCommand(cmds.cmds[i], cmds.gameNumber, cmds.playerName, response);
+
+        if (!handleCommand(cmds.cmds[i], cmds.gameNumber, cmds.playerName, response)) {
+            break;
+        }
     }
 
     response.end("]");
index 9ad0e8a529c32038d86ad95d81276782384f1ee7..6d25cedcb7699241f7d11851a770891af107c11f 100644 (file)
@@ -27,7 +27,7 @@
 (function () {
     "use strict";
 
-    const VERSION = 202004191300;
+    const VERSION = 202004222300;
 
     const Conf = window.TrivabbleConf || {};
 
 
             const moveRack = {
                 cmd: "setRack",
-                rack: [
-                    getLetter(playerLetters[0]),
-                    getLetter(playerLetters[1]),
-                    getLetter(playerLetters[2]),
-                    getLetter(playerLetters[3]),
-                    getLetter(playerLetters[4]),
-                    getLetter(playerLetters[5]),
-                    getLetter(playerLetters[6])
-                ]
+                rack: playerLetters.map(getLetter)
             };
 
-            if (moveCMD.to === moveCMD.from && (moveCMD.indexTo === moveCMD.indexFrom || moveCMD.from === "rack")) {
-                sendCmds([moveRack]);
+            if (moveCMD.to === moveCMD.from) {
+                if (moveCMD.indexTo !== moveCMD.indexFrom) {
+                   if (moveCMD.from === "rack") {
+                        sendCmds([moveRack]);
+                   } else {
+                        sendCmds([moveCMD]);
+                   }
+                }
+            } else if (moveCMD.to === "rack") {
+                moveRack.rack[moveCMD.indexTo] = "";
+                sendCmds([moveRack, moveCMD]);
             } else {
-                sendCmds([moveCMD, moveRack]);
+                sendCmds([moveCMD]);
             }
         }
 
             // No need to show the user that a problem just happened if a message asking to restart has already been shown.
             forceReload(_("Sorry, a problem just happened. The page must be reloaded. If the problem is not too serious, you should be able to keep playing normally. Otherwise, contact the person who is able to fix the problem. Click on “Yes” to reload the page."));
         }
-        throw e;
+
+        throw (e || new Error("interrupting the game because of a fatal error"));
     }
 
     function jsonError(json, e) {
         return msgDom;
     }
 
-    function handleMessage(msg) {
+    function handleChatMessage(msg) {
         if (msg.specialMsg) {
             if (msg.specialMsg.type === "rack") {
                 const content = document.createElement("div");
             forceReload(_("Sorry to disturb you, but we need to reload the game so you can continue playing. You will not lose anything except the messages in the chat. Are you ready?"));
         }
 
+        if (data.error) {
+            fatalError(new Error("Error from the server: " + data.error + " - " + data.reason));
+        }
+
         if (data.stopping) {
             stopConnection();
 
         }
 
         if (data.msg) {
-            handleMessage(data.msg);
+            handleChatMessage(data.msg);
         }
 
         if (typeof data.currentPlayer === "string") {
     }
 
     function parseAndHandleReceivedData(r) {
+        let m;
+
         try {
-            handleReceivedData(JSON.parse(r));
+            m = JSON.parse(r);
         } catch (e) {
             jsonError(r, e);
         }
+
+        try {
+            handleReceivedData(m);
+        } catch (e) {
+            fatalError(e);
+        }
     }
 
     function closeConnections() {