implement bag factor
authorLaurent Mazet <mazet@softndesign.org>
Mon, 26 Apr 2021 06:44:59 +0000 (08:44 +0200)
committerLaurent Mazet <mazet@softndesign.org>
Mon, 26 Apr 2021 06:44:59 +0000 (08:44 +0200)
public/trivabble.js
server/trivabble-server.js

index 64589e8146c256530851450e3e7d315fd5ce345b..d333b6984c42294be96fd16671a4546d63ebbd82 100644 (file)
     let boardLangSelect;
     let boardLabelSelect;
     const downloadedDictionaries = {};
+    const boardDef = {nbRows: 0, nbColumns: 0, rackLength: 0};
 
     const playerLetters = [];
     let currentPlayer = "";
     }
 
     function setRack(rack) {
-        const rackLength = getBoardDimensions(getSetting("BoardLabel"))[2];
-        for (let i = 0; i < rackLength; i++) {
+        for (let i = 0; i < boardDef.rackLength; i++) {
             setTileParent(playerLetters[i], rack[i] || "");
         }
     }
 
     function getFreeRackSpaceIndex() {
-        const rackLength = getBoardDimensions(getSetting("BoardLabel"))[2];
-        for (let i = 0; i < rackLength; i++) {
+        for (let i = 0; i < boardDef.rackLength; i++) {
             if (!playerLetters[i].getElementsByClassName("tile")[0]) {
                 return i;
             }
                 }
             }
         } else if (tileInitDest.getElementsByClassName("tile")[0]) {
-            const rackLength = getBoardDimensions(getSetting("BoardLabel"))[2];
-            for (let i = 0; i < rackLength; i++) {
+            for (let i = 0; i < boardDef.rackLength; i++) {
                 if (!playerLetters[i].getElementsByClassName("tile")[0]) {
                     playerLetters[i].appendChild(movingTile);
                     break;
         setTileParent(boardCells[index].getElementsByClassName("tile-placeholder")[0], letter, highlight);
     }
 
-    function getBoardDimensions(boardLabel) {
-        return (typeof boardLabel === "undefined") ? [] : boardLabel
-            .match(/[0-9]*/gu)
-            .filter(function (x) {return (x.length !== 0);})
-            .map(function (x) {return parseInt(x);});
-    }
-
     function setBoard(board) {
-        const [nbRows, nbColumns] = getBoardDimensions(getSetting("BoardLabel"));
-
-        for (let i = 0; i < nbRows * nbColumns; i++) {
+        for (let i = 0; i < boardDef.nbRows * boardDef.nbColumns; i++) {
             setCell(i, board[i]);
         }
     }
             case "boardLabel":
                 document.getElementById("board-label").value = value;
                 setSetting("BoardLabel", value);
-                initBoard(value);
+                [boardDef.nbRows, boardDef.nbColumns, boardDef.rackLength] =
+                    (typeof value === "undefined") ? [0, 0, 0] : value
+                        .match(/[0-9]*/gu)
+                        .filter(function (x) {return (x.length !== 0);})
+                        .map(function (x) {return parseInt(x);});
+                initBoard();
                 break;
         }
     }
         }
         
         if (data.boardLabel) {
-            set("boardLabel", data.boardLabel);
-            (boardLabelSelect || {}).value = data.boardLabel;
+            if (Object.keys(BoardList).indexOf(data.boardLabel) === -1) {
+                myAlert("Can't find board '" + data.boardLabel + "'. Change board or start a new game.");
+                (boardLabelSelect || {}).value = "";
+            } else {
+                set("boardLabel", data.boardLabel);
+                (boardLabelSelect || {}).value = data.boardLabel;
+            }
         }
 
         if (data.letterValues) {
             playerName: getSetting("PlayerName"),
             boardLang: getSetting("BoardLang"),
             boardLabel: getSetting("BoardLabel"),
+            bagFactor: BoardList[getSetting("BoardLabel")].factor,
             version: VERSION,
             cmds: cmds
         };
         myConfirm(
             format(_("Are you sure you want to change board to '{0}'? This will put all the tiles back in the bag and start another game."), _(lang)),
             function () {
-                sendCmds([{cmd: "changeBoard", lang: code, label: getSetting("BoardLabel")}]);
+                sendCmds([{cmd: "changeBoard", lang: code, label: getSetting("BoardLabel"), factor: BoardList[getSetting("BoardLabel")].factor}]);
             },
             function () {
                 boardLangSelect.value = getSetting("BoardLang");
         myConfirm(
             format(_("Are you sure you want to change board to {0}? This will put all the tiles back in the bag and start another game."), label),
             function () {
-                sendCmds([{cmd: "changeBoard", lang: getSetting("BoardLang"), label: label}]);
+                sendCmds([{cmd: "changeBoard", lang: getSetting("BoardLang"), label: label, factor: BoardList[label].factor}]);
             },
             function () {
                 boardLabelSelect.value = getSetting("BoardLabel");
     }
 
     function getLetterFromBoard(row, col) {
-        const [nbRows, nbColumns] = getBoardDimensions(getSetting("BoardLabel"));
-
-        if ((row < 0) || (row >= nbRows) || (col < 0) || (col >= nbColumns)) {
+        if ((row < 0) || (row >= boardDef.nbRows) || (col < 0) || (col >= boardDef.nbColumns)) {
             return "";
         }
         try {
-            const index = (row * nbColumns) + col;
+            const index = (row * boardDef.nbColumns) + col;
             const tilePlaceholder = boardCells[index].getElementsByClassName("tile-placeholder")[0];
             const tile = tilePlaceholder.getElementsByClassName("tile")[0];
             return tile.firstChild.textContent;
     }
 
     function searchNewWords() {
-        const nbColumns = getBoardDimensions(getSetting("BoardLabel"))[1];
-
         const words = [];
         for (const i of Object.keys(currentTilePlayed)) {
 
             /* Get board position */
-            const row = Math.floor(i / nbColumns);
-            const col = i % nbColumns;
+            const row = Math.floor(i / boardDef.nbColumns);
+            const col = i % boardDef.nbColumns;
 
             /* Look for word in column */
             const newWordInCol = searchWordInLine(row, col, 1, 0);
     }
 
     function modifyTileClass(word, value, action) {
-        const [nbRows, nbColumns] = getBoardDimensions(getSetting("BoardLabel"));
 
         /* action: true for add, false for remove */
 
 
             /* only for new words */
             for (let l = 0; l < word.letters.length; l++) {
-                const index = ((word.row + (l * word.incRow)) * nbColumns) + word.col + (l * word.incCol);
+                const index = ((word.row + (l * word.incRow)) * boardDef.nbColumns) + word.col + (l * word.incCol);
                 const tilePlaceholder = boardCells[index].getElementsByClassName("tile-placeholder")[0];
                 const tile = tilePlaceholder.getElementsByClassName("tile")[0];
 
         } else {
 
             /* tiles on board */
-            for (let index = 0; index < nbRows * nbColumns; index++) {
+            for (let index = 0; index < boardDef.nbRows * boardDef.nbColumns; index++) {
                 const tilePlaceholder = boardCells[index].getElementsByClassName("tile-placeholder")[0];
                 const tile = tilePlaceholder.getElementsByClassName("tile")[0];
                 if (typeof tile !== "undefined") {
     }
 
     function scoreWords() {
-        const [nbColumns, rackLength] = getBoardDimensions(getSetting("BoardLabel")).slice(1);
         let totalScore = 0;
 
         const newWords = searchNewWords();
 
             const word = newWords[k];
             for (let l = 0; l < word.letters.length; l++) {
-                const index = ((word.row + (l * word.incRow)) * nbColumns) + word.col + (l * word.incCol);
+                const index = ((word.row + (l * word.incRow)) * boardDef.nbColumns) + word.col + (l * word.incCol);
 
                 /* Letter score */
                 const tilePlaceholder = boardCells[index].getElementsByClassName("tile-placeholder")[0];
         }
 
         /* Check for trivabble (the premium score) */
-        if (Object.keys(currentTilePlayed).length === rackLength) {
+        if (Object.keys(currentTilePlayed).length === boardDef.rackLength) {
             totalScore += getSetting("PREMIUM_SEVEN_TILES");
         }
 
             return;
         }
 
-        const nbColumns = getBoardDimensions(getSetting("BoardLabel"))[1];
         const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
         const col = cell.cellIndex - 1;
         const row = getRowIndex(cell.parentNode) - 1;
             msg: _("Look at:") + " " + letters[row] + (cell.cellIndex + 1),
             specialMsg: {
                 type: "highlightCell",
-                cell: (row * nbColumns) + col
+                cell: (row * boardDef.nbColumns) + col
             }
         }]);
     }
         startGame(getSetting("GameNumber"));
     }
 
-    function initBoard(boardLabel) {
-
+    function initBoard() {
         const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
-
-        if (Object.keys(BoardList).indexOf(boardLabel) === -1) {
-            myAlert("Can't find board '" + boardLabel + "'. Change board or start a new game.");
-            return;
-        }
-
-        const boardDef = BoardList[boardLabel];
-
-        const [nbRows, nbColumns, rackLength] = getBoardDimensions(boardLabel);
+        const def = BoardList[getSetting("BoardLabel")];
 
         let cell;
         let row;
 
         board.innerHTML = "<tr><td class=\"corner\"></td></tr>";
 
-        for (let j = 0; j < nbColumns; j++) {
+        for (let j = 0; j < boardDef.nbColumns; j++) {
             board.rows[0].appendChild(document.createElement("th"));
             board.rows[0].lastChild.textContent = j + 1;
         }
 
-        for (let i = 0; i < nbRows; i++) {
+        for (let i = 0; i < boardDef.nbRows; i++) {
             row = board.insertRow(-1);
             row.appendChild(document.createElement("th"));
             row.lastChild.textContent = letters[i];
 
-            for (let j = 0; j < nbColumns; j++) {
+            for (let j = 0; j < boardDef.nbColumns; j++) {
                 cell = document.createElement("td");
                 boardCells.push(cell);
                 row.appendChild(cell);
                 cell.lastChild.className = "tile-placeholder";
 
                 const cellName = letters[i] + (j + 1);
-                if (boardDef.CS.indexOf(cellName) !== -1) {
+                if (def.CS.indexOf(cellName) !== -1) {
                     specialCell("doubleWord", row.lastChild);
                     cell = row.lastChild.getElementsByClassName("special-cell-label")[0];
                     cell.textContent = "★";
                     row.lastChild.id = "center-cell";
-                } else if (boardDef.TW.indexOf(cellName) !== -1) {
+                } else if (def.TW.indexOf(cellName) !== -1) {
                     specialCell("tripleWord", row.lastChild);
-                } else if (boardDef.DW.indexOf(cellName) !== -1) {
+                } else if (def.DW.indexOf(cellName) !== -1) {
                     specialCell("doubleWord", row.lastChild);
-                } else if (boardDef.TL.indexOf(cellName) !== -1) {
+                } else if (def.TL.indexOf(cellName) !== -1) {
                     specialCell("tripleLetter", row.lastChild);
-                } else if (boardDef.DL.indexOf(cellName) !== -1) {
+                } else if (def.DL.indexOf(cellName) !== -1) {
                     specialCell("doubleLetter", row.lastChild);
                 }
             }
         row = board.insertRow(-1);
         row.appendChild(board.rows[0].cells[0].cloneNode(false));
 
-        for (let i = 0; i < nbColumns; i++) {
+        for (let i = 0; i < boardDef.nbColumns; i++) {
             row.appendChild(document.createElement("th"));
             row.lastChild.textContent = i + 1;
         }
         rack.innerHTML = "";
         playerLetters.length = 0;
 
-        for (let i = 0; i < rackLength; i++) {
+        for (let i = 0; i < boardDef.rackLength; i++) {
             const span = document.createElement("span");
             span.className = "tile-placeholder";
             rack.appendChild(span);
index 61fb576eafff7af18ffabce85920344f58616ca9..a07fafea2fb65cf94fed4ecd68216b6e5ed4713a 100644 (file)
@@ -36,6 +36,7 @@ const KEEP_ALIVE = 30000;
 const GAMES_BACKUP = process.env.TRIVABBLE_GAMES_BACKUP || "games.backup.json";
 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 = process.env.TRIVABBLE_DEFAULT_BAG_FACTOR || 1;
 
 const VERSION = 202005070100;
 
@@ -226,17 +227,28 @@ function newBoard(label) {
     return res;
 }
 
-Game.prototype.init = function (lang, label) {
+Game.prototype.init = function (lang, label, factor) {
     this.label = label || DEFAULT_BOARD_LABEL;
+    this.factor = factor || DEFAULT_BAG_FACTOR;
     this.board = newBoard(this.label);
     this.lang = lang || DEFAULT_BOARD_LANG;
-    this.bag = boardTilesPerLang[this.lang].bag.slice();
+    const bag = boardTilesPerLang[this.lang].bag.slice();
     this.letterValues = boardTilesPerLang[this.lang].letterValues;
     this.racks = {};
     this.scores = {};
     this.lastUpdated = new Date();
     this.currentPlayer = "";
 
+    factor = this.factor;
+    this.bag = [];
+    while (factor > 1) {
+        this.bag = this.bag.concat(bag.slice());
+        factor--;
+    }
+    if (Math.round(bag.length * factor) > 0) {
+        shuffleInPlace(bag);
+        this.bag = this.bag.concat(bag.slice(0, Math.round(bag.length * factor)));
+    }
     shuffleInPlace(this.bag);
 };
 
@@ -245,6 +257,7 @@ Game.prototype.toJSON = function () {
         board:            this.board,
         lang:             this.lang,
         label:            this.label,
+        factor:           this.factor,
         bag:              this.bag,
         letterValues:     this.letterValues,
         racks:            this.racks,
@@ -257,6 +270,7 @@ Game.prototype.toJSON = function () {
 Game.fromJSON = function (obj) {
     const game = new Game();
     game.label            = obj.label  || DEFAULT_BOARD_LABEL;
+    game.factor           = obj.factor || DEFAULT_BAG_FACTOR;
     game.board            = obj.board  || newBoard(game.label);
     game.lang             = obj.lang   || DEFAULT_BOARD_LANG;
     game.bag              = boardTilesPerLang[game.lang].bag.slice();
@@ -415,12 +429,13 @@ Game.prototype.bagPushLetter = function (letter, player) {
 };
 
 Game.prototype.reset = function (player) {
-    this.init(this.lang, this.label);
+    this.init(this.lang, this.label, this.factor);
     this.pendingEvents.push({
         player: player,
         action: "reset",
         board: this.board,
         label: this.label,
+        factor: this.factor,
         remainingLetters: this.bag.length,
         rack: []
     });
@@ -499,6 +514,7 @@ function handleCommand(cmdNumber, message, response) {
                 playerName: playerName,
                 boardLang: game.lang,
                 boardLabel: game.label,
+                bagFactor: game.factor,
                 availableBoardLangs: availableBoardLangs,
                 currentPlayer: game.currentPlayer,
                 rack: game.getPlayerRack(playerName),
@@ -512,7 +528,7 @@ function handleCommand(cmdNumber, message, response) {
 
         case "hello": {
             game.playerJoined(playerName);
-            reply(message, response, cmdNumber, {error: 0, boardLang: game.lang, boardLabel: game.label, version: VERSION});
+            reply(message, response, cmdNumber, {error: 0, boardLang: game.lang, boardLabel: game.label, bagFactor: game.factor, version: VERSION});
             break;
         }
 
@@ -692,12 +708,14 @@ function handleCommand(cmdNumber, message, response) {
         case "changeBoard": {
             game.lang = cmd.lang || DEFAULT_BOARD_LANG;
             game.label = cmd.label || DEFAULT_BOARD_LABEL;
+            game.factor = cmd.factor || DEFAULT_BAG_FACTOR;
             game.reset();
 
             reply(message, response, cmdNumber, {
                 error: 0,
                 boardLang: game.lang,
                 boardLabel: game.label,
+                bagFactor: game.factor,
                 letterValues: game.letterValues
             });
 
@@ -708,7 +726,8 @@ function handleCommand(cmdNumber, message, response) {
                     specialMsg: {
                         type: "changeBoardDef",
                         newBoardLang: game.lang,
-                        newBoardLabel: game.label
+                        newBoardLabel: game.label,
+                        newBagFactor: game.factor
                     }
                 }
             });
@@ -750,6 +769,7 @@ function handleCommands(message, responseAndType) {
                 gameNumber:          gameNumber,
                 boardLang:           game.lang,
                 boardLabel:          game.label,
+                bagFactor:           game.factor,
                 availableBoardLangs: availableBoardLangs,
                 letterValues:        game.letterValues,
                 rack:                game.getPlayerRack(message.playerName),