add new boards (8x8 and 22x22)
authorLaurent Mazet <mazet@softndesign.org>
Thu, 22 Apr 2021 22:01:03 +0000 (00:01 +0200)
committerLaurent Mazet <mazet@softndesign.org>
Thu, 22 Apr 2021 22:01:03 +0000 (00:01 +0200)
l10n/po/fr/trivabble.po
l10n/pot/trivabble.pot
public/boards.js [new file with mode: 0644]
public/index.html
public/trivabble.js
server/trivabble-server.js

index 5e4bdedf5c43b0e6b674a2a84a19839b95c5dd8d..b8114cea5db2d033238eb909b48de2fde7c75a10 100644 (file)
@@ -166,11 +166,11 @@ msgstr "Êtes-vous sûr·e de vouloir de changer la langue du plateau à '{0}' ?
 msgid  "Board language:"
 msgstr "Langue du plateau :"
 
-msgid  "You changed the language of the board to {0}"
-msgstr "Vous avez changé la langue du plateau en {0}"
+msgid  "You changed the board to {0}x{0} for language {1}"
+msgstr "Vous avez changé le plateau pour {0}x{0} en {1}"
 
-msgid  "{0} changed the language of the board to {1}"
-msgstr "{0} a changé la langue du plateau en {1}"
+msgid  "{0} changed the board to {1}x{1} for language {2}"
+msgstr "{0} a changé le plateau pour {1}x{1} en {2}"
 
 msgid  "Settings"
 msgstr "Paramètres"
@@ -261,3 +261,10 @@ msgstr "Chronométrer la partie"
 
 msgid  "To measure playing time, activate the timer."
 msgstr "Pour mesurer les temps de jeu, activez le chronomètre."
+
+msgid  "Board size:"
+msgstr "Taille du plateau"
+
+msgid  "Are you sure you want to change board to {0}x{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 taille du plateau à {0}x{0} ? Cela remettra toutes les lettres du jeu dans le sac et commencera une nouvelle partie."
+
index 89eb0b8df8fe3573eaac1bb1b25eb944da23abed..4714921176bdd958fb6c366ee70fe8b1b957e8e3 100644 (file)
@@ -172,10 +172,10 @@ msgstr ""
 msgid  "Board language:"
 msgstr ""
 
-msgid  "You changed the language of the board to {0}"
+msgid  "You changed the board to {0}x{0} for language {1}"
 msgstr ""
 
-msgid  "{0} changed the language of the board to {1}"
+msgid  "{0} changed the board to {1}x{1} for language {2}"
 msgstr ""
 
 msgid  "Settings"
@@ -266,3 +266,9 @@ msgstr ""
 
 msgid  "To measure playing time, activate the timer."
 msgstr ""
+
+msgid  "Board size:"
+msgstr ""
+
+msgid  "Are you sure you want to change board to {0}x{0}? This will put all the tiles back in the bag and start another game."
+msgstr ""
diff --git a/public/boards.js b/public/boards.js
new file mode 100644 (file)
index 0000000..0eb4ac8
--- /dev/null
@@ -0,0 +1,100 @@
+window.BoardList = {
+    "8x8":  {
+        TW: [
+            "A1", "A8",
+            "H1", "H8"
+        ],
+        DW: [
+            "B2", "B7",
+            "E5",
+            "G2", "G7"
+        ],
+        TL: [
+            "B5",
+            "E2",
+            "D7",
+            "G4"
+        ],
+        DL: [
+            "A4",
+            "C3", "C6",
+            "D1",
+            "E8",
+            "F3", "F6",
+            "H5"
+        ],
+        CS: [
+            "D4"
+        ]
+    },
+    "15x15": {
+        TW: ["A1", "A8", "A15", "H1", "H15", "O1", "O8", "O15"],
+        DW: ["B2", "B14", "C3", "C13", "D4", "D12", "E5", "E11", "K5", "K11", "L4", "L12", "M3", "M13", "N2", "N14"],
+        TL: ["B6", "B10", "F2", "F6", "F10", "F14", "J2", "J6", "J10", "J14", "N6", "N10"],
+        DL: ["A4", "A12", "C7", "C9", "D1", "D8", "D15", "G3", "G7", "G9", "G13", "H4", "H12", "I3", "I7", "I9", "I13", "L1", "L8", "L15", "M7", "M9", "O4", "O11"],
+        CS: ["H8"]
+    },
+    "22x22": {
+        TW: [
+            "A1", "A8", "A15", "A22",
+            "H1", "H8", "H15", "H22",
+            "O1", "O8", "O15", "O22",
+            "V1", "V8", "V15", "V22"
+        ],
+        DW: [
+            "B2", "B10", "B21",
+            "C3", "C20",
+            "D4", "D12", "D19",
+            "E5", "E18",
+            "I9", "I14",
+            "J21",
+            "K4",
+            "L12", "L19",
+            "M2",
+            "N9", "N14",
+            "R5", "R18",
+            "S4", "S11", "S19",
+            "T3", "T20",
+            "U2", "U13", "U21"
+        ],
+        TL: [
+            "B6", "B17",
+            "E14",
+            "F2", "F6", "F10", "F17", "F21",
+            "I5",
+            "J13", "J17",
+            "M6", "M10",
+            "N18",
+            "Q2", "Q6", "Q13", "Q17", "Q21",
+            "R9",
+            "U6", "U17"
+        ],
+        DL: [
+            "A4", "A12", "A19",
+            "B13",
+            "C7", "C11", "C16",
+            "D1", "D8", "D15", "D22",
+            "E9",
+            "F13",
+            "G3", "G7", "G11", "G16", "G20",
+            "H4", "H12", "H19",
+            "I18",
+            "J2", "J6", "J10",
+            "K1", "K8", "K16", "K20",
+            "L3", "L7", "L15", "L22",
+            "M13", "M17", "M21",
+            "N5",
+            "O4", "O11", "O19",
+            "P3", "P7", "P12", "P16", "P20",
+            "Q10",
+            "R14",
+            "S1", "S8", "S15", "S22",
+            "T7", "T12", "T16",
+            "U10",
+            "V4", "V11", "V19"
+        ],
+        CS: [
+            "K11"
+        ]
+    }
+};
index 8d046a01c185b685d8406879e38ff1149f2dabab..6d4244e3a4dfbb93a029a998f850adb1e4234b70 100644 (file)
@@ -8,6 +8,7 @@
         <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
         <meta name="apple-mobile-web-app-capable" content="yes" />
         <meta name="format-detection" content="telephone=no" />
+        <script src="boards.js"></script>
         <script src="config.js"></script>
         <script src="dict/list.js"></script>
         <script src="l10n.js"></script>
@@ -30,7 +31,7 @@
             <div id="chat-messages"></div>
             <textarea id="chat-ta" placeholder="Write a message to your adversaries here" data-l10n="placeholder"></textarea>
             <button id="chat-btn" class="minibutton" data-l10n="text-content">Send message</button>
-            <div id="lang-selection">
+            <div id="board-selection">
                 <p>
                     <label for="select-lang" data-l10n="text-content">Interface language: </label>
                     <select id="select-lang">
                         <option value="fr">French</option>
                     </select>
                 </p>
+                <p id="board-size-selection">
+                    <label for="board-size" data-l10n="text-content">Board size: </label>
+                    <select id="board-size">
+                        <option value="8">8x8</option>
+                        <option value="15">15x15</option>
+                        <option value="22">22x22</option>
+                    </select>
+                </p>
             </div>
         </div>
 
index 1bfe8d6eca2a2d8e3aca66fe8b1b1b1880608dc8..c849d883ebc11151cfe565e4b39cc5d47834bc2c 100644 (file)
@@ -33,6 +33,8 @@
 
     const DictionaryList = window.DictionaryList || {};
 
+    const BoardList = window.BoardList || {};
+
     function setConf(parameterName, defaultValue) {
         if (typeof Conf[parameterName] === "undefined") {
             Conf[parameterName] = defaultValue;
         DisableSpellChecker: "boolean",
         GameNumber: "number",
         BoardLang: "string",
+        BoardSize: "number",
         Lang: "string",
         PlayerName: "string",
         Timer: "number",
 
     function format(s, v1, v2, v3) {
         return s
-            .replace("{0}", v1)
-            .replace("{1}", v2)
-            .replace("{2}", v3);
+            .replace(/\{0\}/gu, v1)
+            .replace(/\{1\}/gu, v2)
+            .replace(/\{2\}/gu, v3);
     }
 
     let board;
     let scoreOf;
     let bag;
     let boardLangSelect;
+    let boardSizeSelect;
     const downloadedDictionaries = {};
 
     const playerLetters = [];
     }
 
     function setBoard(board) {
-        for (let i = 0; i < 15 * 15; i++) {
+        for (let i = 0; i < getSetting("BoardSize") * getSetting("BoardSize"); i++) {
             setCell(i, board[i]);
         }
     }
                 setSetting("BoardLang", value);
                 checkDictionaryExistance(value);
                 break;
+            case "boardSize":
+                document.getElementById("board-size").value = value;
+                setSetting("BoardSize", value);
+                initBoard(value);
+                break;
         }
     }
 
                     return;
                 }
 
-                case "changeBoardLang": {
+                case "changeBoardDef": {
                     const newLang = boardLangSelect.querySelector("[value=" + msg.specialMsg.newBoardLang + "]").textContent;
+                    const newSize = msg.specialMsg.newBoardSize;
                     infoMessage(
                         (msg.sender === getSetting("PlayerName"))
-                            ? format(_("You changed the language of the board to {0}"), newLang)
-                            : format(_("{0} changed the language of the board to {1}"), msg.sender, newLang)
+                            ? format(_("You changed the board to {0}x{0} for language {1}"), newSize, newLang)
+                            : format(_("{0} changed the board to {1}x{1} for language {2}"), msg.sender, newSize, newLang)
                     );
                     break;
                 }
             set("boardLang", data.boardLang);
             (boardLangSelect || {}).value = data.boardLang;
         }
+        
+        if (data.boardSize) {
+            set("boardSize", Number(data.boardSize));
+            (boardSizeSelect || {}).value = data.boardSize;
+        }
 
         if (data.letterValues) {
             scoreOf = data.letterValues;
             gameNumber: getSetting("GameNumber") || "",
             playerName: getSetting("PlayerName"),
             boardLang: getSetting("BoardLang"),
+            boardSize: getSetting("BoardSize"),
             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}]);
+                sendCmds([{cmd: "changeBoard", lang: code, size: getSetting("BoardSize")}]);
             },
             function () {
                 boardLangSelect.value = getSetting("BoardLang");
             }
         );
     }
+    
+    function onChangeBoardSize() {
+        const size = document.getElementById("board-size").value;
+
+        myConfirm(
+            format(_("Are you sure you want to change board to {0}x{0}? This will put all the tiles back in the bag and start another game."), size),
+            function () {
+                sendCmds([{cmd: "changeBoard", lang: getSetting("BoardLang"), size: size}]);
+            },
+            function () {
+                boardSizeSelect.value = getSetting("BoardLang");
+            }
+        );
+    }
 
     function clearRack() {
         myConfirm(
     }
 
     function getLetterFromBoard(row, col) {
-        if ((row < 0) || (row >= 15) || (col < 0) || (col >= 15)) {
+        if ((row < 0) || (row >= getSetting("BoardSize")) || (col < 0) || (col >= getSetting("BoardSize"))) {
             return "";
         }
         try {
-            const index = (row * 15) + col;
+            const index = (row * getSetting("BoardSize")) + col;
             const tilePlaceholder = boardCells[index].getElementsByClassName("tile-placeholder")[0];
             const tile = tilePlaceholder.getElementsByClassName("tile")[0];
             return tile.firstChild.textContent;
         for (const i of Object.keys(currentTilePlayed)) {
 
             /* Get board position */
-            const row = Math.floor(i / 15);
-            const col = i % 15;
+            const row = Math.floor(i / getSetting("BoardSize"));
+            const col = i % getSetting("BoardSize");
 
             /* Look for word in column */
             const newWordInCol = searchWordInLine(row, col, 1, 0);
 
             /* only for new words */
             for (let l = 0; l < word.letters.length; l++) {
-                const index = ((word.row + (l * word.incRow)) * 15) + word.col + (l * word.incCol);
+                const index = ((word.row + (l * word.incRow)) * getSetting("BoardSize")) + 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 < 15 * 15; index++) {
+            for (let index = 0; index < getSetting("BoardSize") * getSetting("BoardSize"); index++) {
                 const tilePlaceholder = boardCells[index].getElementsByClassName("tile-placeholder")[0];
                 const tile = tilePlaceholder.getElementsByClassName("tile")[0];
                 if (typeof tile !== "undefined") {
 
             const word = newWords[k];
             for (let l = 0; l < word.letters.length; l++) {
-                const index = ((word.row + (l * word.incRow)) * 15) + word.col + (l * word.incCol);
+                const index = ((word.row + (l * word.incRow)) * getSetting("BoardSize")) + word.col + (l * word.incCol);
 
                 /* Letter score */
                 const tilePlaceholder = boardCells[index].getElementsByClassName("tile-placeholder")[0];
             return;
         }
 
-        const letters = "ABCDEFGHIJKLMNO";
+        const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
         const col = cell.cellIndex - 1;
         const row = getRowIndex(cell.parentNode) - 1;
 
             msg: _("Look at:") + " " + letters[row] + (cell.cellIndex + 1),
             specialMsg: {
                 type: "highlightCell",
-                cell: (row * 15) + col
+                cell: (row * getSetting("BoardSize")) + col
             }
         }]);
     }
         chatMessages  = document.getElementById("chat-messages");
         chatTextarea  = document.getElementById("chat-ta");
         participantPlaceholder = document.getElementById("participants-placeholder");
+        boardSizeSelect = document.getElementById("board-size");
     }
 
     function initEvents() {
 
         document.getElementById("clear-game").onclick  = clearGame;
         document.getElementById("board-lang").onchange = onChangeBoardLang;
+        document.getElementById("board-size").onchange = onChangeBoardSize;
         document.getElementById("change-name").onclick = changeName;
         document.getElementById("join-game").onclick   = joinGame;
         document.getElementById("clear-rack").onclick  = clearRack;
 
         name.textContent = getSetting("PlayerName");
 
-        const letters = "ABCDEFGHIJKLMNO";
-
-        const doubleLetter = {
-            "0,3":  true,
-            "0,11": true,
-            "2,6":  true,
-            "2,8":  true,
-            "3,0":  true,
-            "3,7":  true,
-            "3,14": true,
-            "6,2":  true,
-            "6,6":  true,
-            "6,8":  true,
-            "6,12": true
-        };
-
-        let cell;
-        let row;
-
         for (let i = 0; i < 7; i++) {
             const span = document.createElement("span");
             span.className = "tile-placeholder";
             playerLetters.push(span);
         }
 
-        for (let i = 0; i < 15; i++) {
+
+        if (getSetting("GameNumber")) {
+            document.getElementById("number").textContent = getSetting("GameNumber");
+        }
+
+        if (getSetting("BoardLang")) {
+            document.getElementById("board-lang").value = getSetting("BoardLang");
+        }
+
+        if (getSetting("BoardSize")) {
+            document.getElementById("board-size").value = getSetting("BoardSize");
+        }
+
+        startGame(getSetting("GameNumber"));
+    }
+
+    function initBoard(boardSize) {
+
+        const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+        const boardDef = BoardList[boardSize + "x" + boardSize];
+
+        let cell;
+        let row;
+
+        board.innerHTML = "<tr><td class=\"corner\"></td></tr>";
+
+        for (let i = 0; i < boardSize; i++) {
             board.rows[0].appendChild(document.createElement("th"));
             board.rows[0].lastChild.textContent = i + 1;
 
             row.appendChild(document.createElement("th"));
             row.lastChild.textContent = letters[i];
 
-            for (let j = 0; j < 15; j++) {
+            for (let j = 0; j < boardSize; j++) {
                 cell = document.createElement("td");
                 boardCells.push(cell);
                 row.appendChild(cell);
                 cell.appendChild(document.createElement("div"));
                 cell.lastChild.className = "tile-placeholder";
 
-                if (i === j && i === 7) {
+                const cellName = letters[i] + (j + 1);
+                if (boardDef.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 (i % 7 === 0 && j % 7 === 0) {
+                } else if (boardDef.TW.indexOf(cellName) !== -1) {
                     specialCell("tripleWord", row.lastChild);
-                } else if ((i === j || i + j === 14) && (i < 5 || i > 9)) {
+                } else if (boardDef.DW.indexOf(cellName) !== -1) {
                     specialCell("doubleWord", row.lastChild);
-                } else if ((i % 4 === 1) && (j % 4 === 1)) {
+                } else if (boardDef.TL.indexOf(cellName) !== -1) {
                     specialCell("tripleLetter", row.lastChild);
-                } else if ((i < 8 && doubleLetter[i + "," + j]) || (i > 7 && doubleLetter[(14 - i) + "," + j]) || (i === 7 && (j === 3 || j === 11))) {
+                } else if (boardDef.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 < 15; i++) {
+        for (let i = 0; i < boardSize; i++) {
             row.appendChild(document.createElement("th"));
             row.lastChild.textContent = i + 1;
         }
 
         row.appendChild(board.rows[0].cells[0].cloneNode(false));
-
-        if (getSetting("GameNumber")) {
-            document.getElementById("number").textContent = getSetting("GameNumber");
-        }
-
-        if (getSetting("BoardLang")) {
-            document.getElementById("board-lang").value = getSetting("BoardLang");
-        }
-
-        startGame(getSetting("GameNumber"));
     }
 
     function initLang() {
index a5b19a2a9cbab012befb168021febd8c18b9d772..8e916c507df2e4f17c290286f7c4c5b624ff9d5b 100644 (file)
@@ -35,6 +35,7 @@ const SAVE_TIMEOUT = 5000;
 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_SIZE = process.env.TRIVABBLE_DEFAULT_BOARD_SIZE || 15;
 
 const VERSION = 202005070100;
 
@@ -206,18 +207,19 @@ function keepAlive(responseAndType) {
     }, KEEP_ALIVE);
 }
 
-function newBoard() {
-    const res = new Array(15 * 15);
+function newBoard(size) {
+    const res = new Array(size * size);
 
-    for (let i = 0; i < 15 * 15; i++) {
+    for (let i = 0; i < size * size; i++) {
         res[i] = "";
     }
 
     return res;
 }
 
-Game.prototype.init = function (lang) {
-    this.board = newBoard();
+Game.prototype.init = function (lang, size) {
+    this.size = size || DEFAULT_BOARD_SIZE;
+    this.board = newBoard(this.size);
     this.lang = lang || DEFAULT_BOARD_LANG;
     this.bag = boardTilesPerLang[this.lang].bag.slice();
     this.letterValues = boardTilesPerLang[this.lang].letterValues;
@@ -233,6 +235,7 @@ Game.prototype.toJSON = function () {
     return {
         board:            this.board,
         lang:             this.lang,
+        size:             this.size,
         bag:              this.bag,
         letterValues:     this.letterValues,
         racks:            this.racks,
@@ -244,7 +247,8 @@ Game.prototype.toJSON = function () {
 
 Game.fromJSON = function (obj) {
     const game = new Game();
-    game.board            = obj.board  || newBoard();
+    game.size             = obj.size   || DEFAULT_BOARD_SIZE;
+    game.board            = obj.board  || newBoard(game.size);
     game.lang             = obj.lang   || DEFAULT_BOARD_LANG;
     game.bag              = boardTilesPerLang[game.lang].bag.slice();
     game.letterValues     = boardTilesPerLang[game.lang].letterValues;
@@ -402,11 +406,12 @@ Game.prototype.bagPushLetter = function (letter, player) {
 };
 
 Game.prototype.reset = function (player) {
-    this.init(this.lang);
+    this.init(this.lang, this.size);
     this.pendingEvents.push({
         player: player,
         action: "reset",
         board: this.board,
+        size: this.size,
         remainingLetters: this.bag.length,
         rack: []
     });
@@ -483,6 +488,7 @@ function handleCommand(cmdNumber, message, response) {
                 gameNumber: gameNumber,
                 playerName: playerName,
                 boardLang: game.lang,
+                boardSize: game.size,
                 availableBoardLangs: availableBoardLangs,
                 currentPlayer: game.currentPlayer,
                 rack: game.getPlayerRack(playerName),
@@ -496,7 +502,7 @@ function handleCommand(cmdNumber, message, response) {
 
         case "hello": {
             game.playerJoined(playerName);
-            reply(message, response, cmdNumber, {error: 0, boardLang: game.lang, version: VERSION});
+            reply(message, response, cmdNumber, {error: 0, boardLang: game.lang, boardSize: game.size, version: VERSION});
             break;
         }
 
@@ -542,7 +548,7 @@ function handleCommand(cmdNumber, message, response) {
                     break;
 
                 case "board":
-                    if (cmd.indexFrom < 0 || cmd.indexFrom >= 15 * 15) {
+                    if (cmd.indexFrom < 0 || cmd.indexFrom >= game.size * game.size) {
                         reply(message, response, cmdNumber, {error: 1, reason: "Wrong indexFrom"});
                         return false;
                     }
@@ -587,7 +593,7 @@ function handleCommand(cmdNumber, message, response) {
                     break;
 
                 case "board":
-                    if (cmd.indexTo < 0 || cmd.indexTo >= 15 * 15) {
+                    if (cmd.indexTo < 0 || cmd.indexTo >= game.size * game.size) {
                         reply(message, response, cmdNumber, {error: 1, reason: "Wrong indexTo"});
                         return false;
                     }
@@ -675,21 +681,24 @@ function handleCommand(cmdNumber, message, response) {
 
         case "changeBoard": {
             game.lang = cmd.lang || DEFAULT_BOARD_LANG;
+            game.size = cmd.size || DEFAULT_BOARD_SIZE;
             game.reset();
 
             reply(message, response, cmdNumber, {
                 error: 0,
                 boardLang: game.lang,
+                boardSize: game.size,
                 letterValues: game.letterValues
             });
 
             game.pendingEvents.push({
                 msg: {
                     sender: playerName,
-                    content: "I changed the language of the board to " + game.lang,
+                    content: "I changed the board to " + game.size + "x" + game.size + " in language " + game.lang,
                     specialMsg: {
-                        type: "changeBoardLang",
-                        newBoardLang: game.lang
+                        type: "changeBoardDef",
+                        newBoardLang: game.lang,
+                        newBoardSize: game.size
                     }
                 }
             });
@@ -730,6 +739,7 @@ function handleCommands(message, responseAndType) {
                 currentPlayer:       game.currentPlayer,
                 gameNumber:          gameNumber,
                 boardLang:           game.lang,
+                boardSize:           game.size,
                 availableBoardLangs: availableBoardLangs,
                 letterValues:        game.letterValues,
                 rack:                game.getPlayerRack(message.playerName),