Freeze tiles on board after playing
authorLaurent Mazet <mazet@softndesign.org>
Fri, 9 May 2025 06:50:26 +0000 (08:50 +0200)
committerRaphaël Jakse <raphael.trivabble@jakse.fr>
Fri, 9 May 2025 06:52:07 +0000 (08:52 +0200)
l10n/po/br/trivabble.po
l10n/po/fr/trivabble.po
l10n/po/ro/trivabble.po
l10n/pot/trivabble.pot
public/index.html
public/trivabble.js
server/trivabble-server.js

index 762d3db104cc338db7a2a2daf3e47cf234d474f0..e3f5c0b1c06f14d18a2db5fbebad09e32ddd00c4 100644 (file)
@@ -174,8 +174,8 @@ msgstr "C'hoarier"
 msgid "Rack"
 msgstr "Douger-pezhioù"
 
-msgid "Score"
-msgstr "Poentoù"
+msgid "Score:"
+msgstr "Poentoù:"
 
 msgid "Waiting for other participants…"
 msgstr "O c'hortoz c'hoarien all..."
index 2469e0eb97c52fa44937a215041d2a6a308152aa..61537e18fc667165c2356fc25ce0a9fcd93dd685 100644 (file)
@@ -52,7 +52,7 @@ msgstr "Lettre\nTriple"
 msgid  "Triple\nWord"
 msgstr "Mot\nTriple"
 
-msgid "Join adversaries"
+msgid  "Join adversaries"
 msgstr "Rejoindre des adversaires"
 
 msgid  "You are:"
@@ -77,7 +77,7 @@ msgid  "Write a message to your adversaries here"
 msgstr "Écrivez un message à vos adversaires ici"
 
 msgid  "Send message"
-msgstr  "Envoyer le message"
+msgstr "Envoyer le message"
 
 msgid  "Sound of the tiles"
 msgstr "Bruit des pièces"
@@ -97,8 +97,8 @@ msgstr "Participant·e"
 msgid  "Rack"
 msgstr "Chevalet"
 
-msgid  "Score"
-msgstr "Score"
+msgid  "Score:"
+msgstr "Score :"
 
 msgid  "Waiting for other participants…"
 msgstr "En attente des autres participants…"
@@ -112,22 +112,22 @@ msgstr "Remettre toutes les pièces du\nchevalet dans le sac"
 msgid  "Interface:"
 msgstr "Interface :"
 
-msgid "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?"
+msgid  "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?"
 msgstr "Désolé pour le dérangement, mais nous avons besoin de recharger le jeu pour que vous poussiez continuer à jouer. Vous ne perdrez rien sauf les messages dans le chat. Êtes-vous prêt·e ?"
 
-msgid "To continue playing, click here"
+msgid  "To continue playing, click here"
 msgstr "Pour continuer à jouer, cliquez ici"
 
-msgid "{0}: "
+msgid  "{0}: "
 msgstr "{0} : "
 
-msgid "Whoops, there is a problem. We are trying to fix it as soon as possible. Please wait a few seconds. If it persists, please contact the person who is able to fix the problem."
+msgid  "Whoops, there is a problem. We are trying to fix it as soon as possible. Please wait a few seconds. If it persists, please contact the person who is able to fix the problem."
 msgstr "Oups, il y a un problème. Nous essayons de corriger cela le plus vite possible. Merci de bien vouloir patienter quelques secondes. Si cela persiste, n’hésitez pas à contacter la personne qui peut s’occuper de ce problème"
 
-msgid "The problem is solved, sorry for the inconvenience!"
+msgid  "The problem is solved, sorry for the inconvenience!"
 msgstr "Le problème est résolu. Désolé pour la gêne occasionnée !"
 
-msgid "It seems your browser went offline. Please reconnect to continue playing."
+msgid  "It seems your browser went offline. Please reconnect to continue playing."
 msgstr "On dirait que votre navigateur est hors ligne. Merci de bien vouloir vous reconnecter pour continuer à jouer."
 
 msgid  "Turn"
@@ -158,7 +158,7 @@ msgid  "{0} shows their rack:"
 msgstr "{0} montre son jeu :"
 
 msgid  "You cannot take another tile: the bag is empty."
-msgstr  "Vous ne pouvez pas tirer de pièce : le sac est vide."
+msgstr "Vous ne pouvez pas tirer de pièce : le sac est vide."
 
 msgid  "Are you sure you want to change board to '{0}'? This will put all the tiles back in the bag and start another game."
 msgstr "Êtes-vous sûr·e de vouloir de changer la langue du plateau à '{0}' ? Cela remettra toutes les lettres du jeu dans le sac et commencera une nouvelle partie."
@@ -202,7 +202,7 @@ msgstr "La vérification orthographique est basée sur :"
 msgid  "{0} points will be added to:"
 msgstr "{0} points vont être ajoutés à:"
 
-msgid "There are no points to add."
+msgid  "There are no points to add."
 msgstr "Il n’y a pas de point à ajouter."
 
 msgid  "Settings"
@@ -230,18 +230,18 @@ msgid  "Flash light color"
 msgstr "Couleur du flash laser"
 
 msgid  "Tip!"
-msgstr  "Astuce !"
+msgstr "Astuce !"
 
-msgid "Next tip"
+msgid  "Next tip"
 msgstr "Astuce suivante"
 
-msgid "Disable the spell checker"
+msgid  "Disable the spell checker"
 msgstr "Désactiver la vérification orthographique"
 
-msgid "Spell checking is not available for this language."
+msgid  "Spell checking is not available for this language."
 msgstr "La vérification orthographique n'est pas disponible dans cette langue."
 
-msgid "Score new words"
+msgid  "Score new words"
 msgstr "Compter les points"
 
 msgid  "Turn:"
@@ -300,3 +300,6 @@ msgstr "Activer la vérification du premier mot"
 
 msgid  "First word must cover center box."
 msgstr "Le premier mot doit couvrir la case centrale."
+
+msgid  "Freeze tiles after playing"
+msgstr "Bloquer les pièces à la fin du tour"
index 11af3cc7a99e26ef8b51b5719e04751da41f389d..dac2c6713efab257cd3478006557f5aad1affb60 100644 (file)
@@ -166,8 +166,8 @@ msgstr "Participant"
 msgid "Rack"
 msgstr "Suport"
 
-msgid "Score"
-msgstr "Scor"
+msgid "Score:"
+msgstr "Scor:"
 
 msgid "Waiting for other participants…"
 msgstr "În așteptarea altor participanți…"
index 2a3d279cdae25387dfe473da234b5f03480e14aa..4dd5c9ffcaf9d14717adb117bdd67806bd7e0ae4 100644 (file)
@@ -18,7 +18,7 @@ msgstr ""
 msgid  "Enter the score to add to {0}:"
 msgstr ""
 
-msgid  "To join a game, please give the number which is displayed on your adversary(ies)' screen.\nIf you do not know it, ask them.\n\nWarning: your adversary must not take your number, (s)he must keep his/her own. If you whish to recover your current game, please not the following number: {0}."
+msgid  "To join a game, please give the number which is displayed on your adversary(ies)' screen.\nIf you do not know it, ask them.\n\nWarning: your adversary must not take your number, (s)he must keep his/her own."
 msgstr ""
 
 msgid  "It seems your did not give a correct number, or you clicked on “Cancel”. As a result, the current game continues, if any. To join a game, click on “Join a game” again."
@@ -54,7 +54,7 @@ msgstr ""
 msgid  "Triple\nWord"
 msgstr ""
 
-msgid "Join adversaries"
+msgid  "Join adversaries"
 msgstr ""
 
 msgid  "You are:"
@@ -99,7 +99,7 @@ msgstr ""
 msgid  "Rack"
 msgstr ""
 
-msgid  "Score"
+msgid  "Score:"
 msgstr ""
 
 msgid  "Waiting for other participants…"
@@ -118,19 +118,19 @@ msgstr ""
 msgid  "Interface:"
 msgstr ""
 
-msgid "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?"
+msgid  "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?"
 msgstr ""
 
-msgid "To continue playing, click here"
+msgid  "To continue playing, click here"
 msgstr ""
 
-msgid "{0}: "
+msgid  "{0}: "
 msgstr ""
 
-msgid "Whoops, there is a problem. We are trying to fix it as soon as possible. Please wait a few seconds. If it persists, please contact the person who is able to fix the problem."
+msgid  "Whoops, there is a problem. We are trying to fix it as soon as possible. Please wait a few seconds. If it persists, please contact the person who is able to fix the problem."
 msgstr ""
 
-msgid "The problem is solved, sorry for the inconvenience!"
+msgid  "The problem is solved, sorry for the inconvenience!"
 msgstr ""
 
 msgid  "It seems your browser went offline. Please reconnect to continue playing."
@@ -208,13 +208,14 @@ msgstr ""
 msgid  "{0} points will be added to:"
 msgstr ""
 
-msgid "There are no points to add."
-msgstr: ""
+msgid  "There are no points to add."
+msgstr ""
 
 msgid  "Settings"
 msgstr ""
 
 msgid  "Configuration window"
+msgstr ""
 
 msgid  "Look at:"
 msgstr ""
@@ -237,16 +238,16 @@ msgstr ""
 msgid  "Tip!"
 msgstr ""
 
-msgid "Next tip"
+msgid  "Next tip"
 msgstr ""
 
-msgid "Disable the spell checker"
+msgid  "Disable the spell checker"
 msgstr ""
 
-msgid "Spell checking is not available for this language."
+msgid  "Spell checking is not available for this language."
 msgstr ""
 
-msgid "Score new words"
+msgid  "Score new words"
 msgstr ""
 
 msgid  "Turn:"
@@ -305,3 +306,6 @@ msgstr ""
 
 msgid  "First word must cover center box."
 msgstr ""
+
+msgid  "Freeze tiles after playing"
+msgstr ""
index 77b37e113585adc5aef709de5a85884b89f6f57f..7b98c1ca37cd135bbca5d745fe382672000a2ec2 100644 (file)
@@ -74,6 +74,9 @@
                 <div>
                     <p><label><input type="checkbox" id="check-first-word" /> <span data-l10n="text-content">Enable first word verification</span></label></p>
                 </div>
+                <div>
+                    <p><label><input type="checkbox" id="freeze-played-tiles" /> <span data-l10n="text-content">Freeze tiles after playing</span></label></p>
+                </div>
                 <div>
                     <p id="disable-spell-checker-p" hidden="true">
                         <label>
index 7abc925c8640f9227d4637495110e328899060cd..fc1a0ab8647921bf0fbbcbc0045ad2b6f6ad6714 100644 (file)
         Lang: "string",
         PlayerName: "string",
         CheckFirstWord: "number",
+        FreezePlayedTiles: "number",
         SpellCheckerEnabledOnce: "boolean",
         TimerGameDate: "number",
         TimerTurnDate: "number"
     let blockMove = 0;
     let remainingLetters = 0;
     let needsRestart = false;
-    let currentTilePlayed = {};
+    let currentTileOwner = {};
 
     let eventSource = null;
     let webSocket = null;
         movingTile.style.width  = "";
         movingTile.style.height = "";
 
-        /* Keep track of letter */
-        if (moveCMD.from === "rack") {
-            if (moveCMD.to === "board") {
-                currentTilePlayed[moveCMD.indexTo] = "-";
-            }
-        } else {/* moveCMD.from === "board" */
-            if (moveCMD.to === "board") {
-                currentTilePlayed[moveCMD.indexTo] = currentTilePlayed[moveCMD.indexFrom];
-            }
-            delete currentTilePlayed[moveCMD.indexFrom];
-        }
-
         if (tileDest === bag) {
             moveCMD.to = "bag";
             moveCMD.indexTo = -1;
             } else {
                 sendCmds([moveCMD]);
             }
+
+            /* Keep track of letter move */
+            if (moveCMD.from === "board") {
+                delete currentTileOwner[moveCMD.indexFrom];
+            }
+            if (moveCMD.to === "board") {
+                currentTileOwner[moveCMD.indexTo] = true;
+            }
         }
 
         cleanupDragTile();
             p = p.parentNode;
         }
 
+        if ((getSetting("FreezePlayedTiles") > 0) && (from === "board") && (!currentTileOwner[index])) {
+            return;
+        }
+
         if (!from) {
             fatalError(new Error("Error: did not find the parent of the moving tile"));
             return;
         }
 
+        //XXXX
+
         moveCMD = {
             cmd: "moveLetter",
             from: from,
         }
     }
 
-    function setCell(index, letter, highlight) {
-        if (lastPlayer) {
-            if (letter) {
-                currentTilePlayed[index] = letter;
-            } else {
-                delete currentTilePlayed[index];
-            }
+    function setCell(index, letter, player, highlight) {
+        if (letter && (player === getSetting("PlayerName"))) {
+            currentTileOwner[index] = true;
+        } else {
+            delete currentTileOwner[index];
         }
-
         setTileParent(boardCells[index].getElementsByClassName("tile-placeholder")[0], letter, highlight);
     }
 
             case "checkFirstWord":
                 setSetting("CheckFirstWord", value);
                 break;
+            case "freezePlayedTiles":
+                setSetting("FreezePlayedTiles", value);
+                break;
         }
     }
 
                                             player: row.firstChild.textContent,
                                             score: res
                                         }]);
+                                        currentTileOwner = {};
                                     }
                                 },
                                 row.lastChild.firstChild.textContent,
                                             player: row.firstChild.textContent,
                                             score: parseInt(row.childNodes[2].childNodes[0].textContent) + res
                                         }]);
+                                        currentTileOwner = {};
                                     }
                                 },
                                 row.lastChild.firstChild.textContent,
 
             if (Object.prototype.hasOwnProperty.call(player, "score")) {
                 const scoreCell = getScoreCell(playerName);
-                if (parseInt(scoreCell.textContent) !== player.score) {
-                    currentTilePlayed = {};
-                }
                 scoreCell.textContent = player.score;
                 blink(scoreCell);
             }
 
     function handleLastPlayer(data) {
         if (data.player) {
-            if (lastPlayer !== data.player) {
-                currentTilePlayed = {};
-            }
             lastPlayer = data.player;
         }
     }
                 sendCmds([{cmd: "hello"}]);
 
                 lastPlayer = null;
-                currentTilePlayed = {};
+                currentTileOwner = {};
                 break;
 
             case "moveLetter":
 
                 if (data.to === "board") {
                     setCell(
-                        data.indexTo,
-                        data.letter,
+                        data.indexTo, data.letter, data.player,
                         data.player && data.player !== getSetting("PlayerName")
                     );
                 } else if (data.to === "rack") {
                 handleLastPlayer(data);
 
                 setCell(
-                    data.indexTo,
-                    data.letter,
+                    data.indexTo, data.letter, data.player,
                     data.player && data.player !== getSetting("PlayerName")
                 );
 
-                if ((data.letter !== "") && (currentTilePlayed[data.indexTo] === "-")) {
-                    currentTilePlayed[data.indexTo] = data.letter;
-                }
-
                 break;
 
             case "setRackCell":
             document.getElementById("check-first-word").checked = (getSetting("CheckFirstWord") === 1);
         }
 
+        if (data.freezePlayedTiles) {
+            set("freezePlayedTiles", data.freezePlayedTiles);
+            document.getElementById("freeze-played-tiles").checked = (getSetting("FreezePlayedTiles") === 1);
+        }
+
         if (data.players) {
             setPlayers(data.players);
         }
             setBoard(data.board);
         }
 
+        if (data.tileOwner) {
+            const player = getSetting("PlayerName");
+            for (const i of Object.keys(data.tileOwner)) {
+                if (data.tileOwner[i] === player) {
+                    currentTileOwner[i] = true;
+                }
+            }
+        }
+
         if (typeof data.remainingLetters === "number") {
             remainingLetters = data.remainingLetters;
             tileCount.textContent = data.remainingLetters;
             boardLabel: getSetting("BoardLabel"),
             bagFactor: (typeof getSetting("BoardLabel") === "undefined") ? 1 : BoardList[getSetting("BoardLabel")].factor,
             checkFirstWord: getSetting("CheckFirstWord"),
+            freezePlayedTiles: getSetting("FreezePlayedTiles"),
             version: VERSION,
             cmds: cmds
         };
 
     function searchNewWords() {
         const words = [];
-        for (const i of Object.keys(currentTilePlayed)) {
+        for (const i of Object.keys(currentTileOwner)) {
 
             /* Get board position */
             const row = Math.floor(i / boardDef.nbColumns);
                 let letterFactor = 1;
 
                 /* Is a freshly played letter? */
-                if (currentTilePlayed[index] === word.letters[l]) {
+                if (getLetterFromBoard(word.row, word.col) === word.letters[l]) {
 
                     /* Letter factor */
                     if (boardCells[index].classList.contains("special-cell-doubleLetter")) {
         }
 
         /* Check for trivabble (the premium score) */
-        if (Object.keys(currentTilePlayed).length === boardDef.rackLength) {
+        if (Object.keys(currentTileOwner).length === boardDef.rackLength) {
             if (Object.prototype.hasOwnProperty.call(BoardList[getSetting("BoardLabel")], "premium")) {
                 totalScore += BoardList[getSetting("BoardLabel")].premium;
             } else {
                     return;
                 }
 
-                currentTilePlayed = {};
-
                 sendCmds([{
                     cmd: "score",
                     player: name,
                     score: parseInt(getScoreCell(name).textContent) + totalScore
                 }]);
+                currentTileOwner = {};
             },
             playerName || lastPlayer || currentPlayer || getSetting("PlayerName")
         );
         };
     }
 
+    function initFreezePlayedTiles() {
+        document.getElementById("freeze-played-tiles").checked = (getSetting("FreezePlayedTiles") === 1);
+        document.getElementById("freeze-played-tiles").onclick = function () {
+            const freezePlayedTiles = (document.getElementById("freeze-played-tiles").checked) ? 1 : -1;
+            set("freezePlayedTiles", freezePlayedTiles);
+            sendCmds([{cmd: "freezePlayedTiles", freezePlayedTiles: freezePlayedTiles}]);
+        };
+    }
+
     function toggleSpellChecker(e) {
         const disabled = document.getElementById("disable-spell-checker").checked;
         checkDictionaryExistance();
         initGame();
         initSound();
         initCheckFirstWord();
+        initFreezePlayedTiles();
         initSpellChecker();
         initFlashLight();
         nextHelpMessage();
index 2af238257a51eea86a1aa42bedb4f34829c1cb5b..35b6214fba260f8152222cf408d1ad0e0490269e 100644 (file)
@@ -37,6 +37,7 @@ const DEFAULT_BOARD_LANG = process.env.TRIVABBLE_DEFAULT_BOARD_LANG || "fr";
 const DEFAULT_BOARD_LABEL = process.env.TRIVABBLE_DEFAULT_BOARD_LABEL || "15x15-7";
 const DEFAULT_BAG_FACTOR = 1;
 const CHECK_FIRST_WORD = 1; // 1: check, 0: not set, -1: no check
+const FREEZE_PLAYED_TILES = -1; // 1: freeze, 0: not set, -1: no freeze
 
 const VERSION = 202005070100;
 
@@ -241,7 +242,8 @@ function newBoard(label) {
     return res;
 }
 
-Game.prototype.init = function (lang, label, factor, checkFirstWord) {
+// eslint-disable-next-line max-params
+Game.prototype.init = function (lang, label, factor, checkFirstWord, freezePlayedTiles) {
     this.label = label || DEFAULT_BOARD_LABEL;
     this.factor = factor || DEFAULT_BAG_FACTOR;
     this.board = newBoard(this.label);
@@ -250,7 +252,9 @@ Game.prototype.init = function (lang, label, factor, checkFirstWord) {
     this.letterValues = boardTilesPerLang[this.lang].letterValues;
     this.racks = {};
     this.scores = {};
+    this.tileOwner = {};
     this.checkFirstWord = checkFirstWord || CHECK_FIRST_WORD;
+    this.freezePlayedTiles = freezePlayedTiles || FREEZE_PLAYED_TILES;
     this.lastUpdated = new Date();
     this.currentPlayer = "";
 
@@ -267,7 +271,9 @@ Game.prototype.toJSON = function () {
         letterValues:     this.letterValues,
         racks:            this.racks,
         scores:           this.scores,
+        tileOwner:        this.tileOwner,
         checkFirstWord:   this.checkFirstWord,
+        freezePlayedTiles: this.freezePlayedTiles,
         lastUpdated:      this.lastUpdated.toISOString(),
         currentPlayer:    this.currentPlayer
     };
@@ -283,7 +289,9 @@ Game.fromJSON = function (obj) {
     game.letterValues     = boardTilesPerLang[game.lang].letterValues;
     game.racks            = obj.racks  || {};
     game.scores           = obj.scores || {};
+    game.tileOwner        = obj.tileOwner || {};
     game.checkFirstWord   = obj.checkFirstWord || CHECK_FIRST_WORD;
+    game.freezePlayedTiles = obj.freezePlayedTiles || FREEZE_PLAYED_TILES;
     game.lastUpdated      = obj.lastUpdated ? new Date(obj.lastUpdated) : new Date();
     game.currentPlayer    = obj.currentPlayer || "";
     return game;
@@ -308,6 +316,12 @@ Game.prototype.setPlayerScore = function (player, score) {
 
     this.scores[playerID] = score;
 
+    for (const index of Object.keys(this.tileOwner)) {
+        if (this.tileOwner[index] === player) {
+            delete (this.tileOwner[index]);
+        }
+    }
+
     this.pendingEvents.push({
         players: [{
             player: player,
@@ -326,6 +340,11 @@ Game.prototype.setCheckFirstWord = function (onOff) {
     this.pendingEvents.push({checkFirstWord: onOff});
 };
 
+Game.prototype.setFreezePlayedTiles = function (onOff) {
+    this.freezePlayedTiles = onOff;
+    this.pendingEvents.push({freezePlayedTiles: onOff});
+};
+
 Game.prototype.playerJoined = function (playerName) {
     if (playerName) {
         this.getPlayerRack(playerName); // Create the player's rack
@@ -436,8 +455,13 @@ Game.prototype.getCell = function (index) {
     return this.board[index];
 };
 
-Game.prototype.setCell = function (index, letter, player) {
+Game.prototype.setCellAndTileOwner = function (index, letter, player) {
     this.board[index] = letter;
+    if ((letter === "") || (player === "")) {
+        delete (this.tileOwner[index]);
+    } else {
+        this.tileOwner[index] = player;
+    }
     this.pendingEvents.push({
         player: player,
         action: "setCell",
@@ -446,6 +470,10 @@ Game.prototype.setCell = function (index, letter, player) {
     });
 };
 
+Game.prototype.getTileOwner = function (index) {
+    return Object.prototype.hasOwnProperty.call(this.tileOwner, index) ? this.tileOwner[index] : "";
+};
+
 Game.prototype.bagPushLetter = function (letter, player) {
     if (letter) {
         this.bag.push(letter);
@@ -460,7 +488,7 @@ Game.prototype.bagPushLetter = function (letter, player) {
 };
 
 Game.prototype.reset = function (player) {
-    this.init(this.lang, this.label, this.factor, this.checkFirstWord);
+    this.init(this.lang, this.label, this.factor, this.checkFirstWord, this.freezePlayedTiles);
     this.pendingEvents.push({
         player: player,
         action: "reset",
@@ -560,7 +588,9 @@ function handleCommand(cmdNumber, message, response) {
                 currentPlayer: game.currentPlayer,
                 rack: game.getPlayerRack(playerName),
                 checkFirstWord: game.checkFirstWord,
+                freezePlayedTiles: game.freezePlayedTiles,
                 board: game.board,
+                tileOwner: game.tileOwner,
                 remainingLetters: game.bag.length,
                 letterValues: game.letterValues,
                 version: VERSION
@@ -586,8 +616,19 @@ function handleCommand(cmdNumber, message, response) {
             break;
         }
 
+        case "freezePlayedTiles": {
+            game.setFreezePlayedTiles(cmd.freezePlayedTiles);
+            reply(message, response, cmdNumber, {error: 0});
+            break;
+        }
+
         case "currentPlayer": {
             game.setCurrentPlayer(cmd.player);
+            //for (const cell of game.board) {
+            //    if ((cell.player != "") && (cell.player != cmd.player)) {
+            //        cell.player = "";
+            //    }
+            //}
             reply(message, response, cmdNumber, {error: 0});
             break;
         }
@@ -628,7 +669,13 @@ function handleCommand(cmdNumber, message, response) {
                     }
 
                     letter = game.getCell(cmd.indexFrom);
-                    operations.push(game.setCell.bind(game, cmd.indexFrom, "", playerName));
+                    //const cell = game.getCell(cmd.indexFrom);
+                    //if (cell.player != playerName) {
+                    //    reply(message, response, cmdNumber, {error: 1, reason: "Move forbidden"});
+                    //    return false;
+                    //}
+                    //letter = cell.letter;
+                    operations.push(game.setCellAndTileOwner.bind(game, cmd.indexFrom, "", playerName));
                     break;
 
                 case "bag":
@@ -672,7 +719,7 @@ function handleCommand(cmdNumber, message, response) {
                         return false;
                     }
 
-                    operations.push(game.setCell.bind(game, cmd.indexTo, letter, playerName));
+                    operations.push(game.setCellAndTileOwner.bind(game, cmd.indexTo, letter, playerName));
                     break;
 
                 case "bag":
@@ -764,6 +811,7 @@ function handleCommand(cmdNumber, message, response) {
             game.label = cmd.label || DEFAULT_BOARD_LABEL;
             game.factor = cmd.factor || DEFAULT_BAG_FACTOR;
             game.checkFirstWord = cmd.checkFirstWord || CHECK_FIRST_WORD;
+            game.freezePlayedTiles = cmd.freezePlayedTiles || FREEZE_PLAYED_TILES;
             game.reset();
 
             reply(message, response, cmdNumber, {
@@ -772,6 +820,7 @@ function handleCommand(cmdNumber, message, response) {
                 boardLabel: game.label,
                 bagFactor: game.factor,
                 checkFirstWord: game.checkFirstWord,
+                freezePlayedTiles: game.freezePlayedTiles,
                 letterValues: game.letterValues
             });
 
@@ -829,8 +878,10 @@ function handleCommands(message, responseAndType) {
                 letterValues:        game.letterValues,
                 rack:                game.getPlayerRack(message.playerName),
                 board:               game.board,
+                tileOwner:           game.tileOwner,
                 remainingLetters:    game.bag.length,
                 checkFirstWord:      game.checkFirstWord,
+                freezePlayedTiles:   game.freezePlayedTiles,
                 version:             VERSION
             })
         );