diff --git a/l10n/po/fr/trivabble.po b/l10n/po/fr/trivabble.po index b8114ce..a626f20 100644 --- a/l10n/po/fr/trivabble.po +++ b/l10n/po/fr/trivabble.po @@ -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 board to {0}x{0} for language {1}" -msgstr "Vous avez changé le plateau pour {0}x{0} en {1}" +msgid "You changed the board to {0} for language {1}" +msgstr "Vous avez changé le plateau pour {0} 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 "{0} changed the board to {1} for language {2}" +msgstr "{0} a changé le plateau pour {1} en {2}" msgid "Settings" msgstr "Paramètres" @@ -262,9 +262,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." +msgid "Board label:" +msgstr "Type du plateau" +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 le type du plateau à {0} ? Cela remettra toutes les lettres du jeu dans le sac et commencera une nouvelle partie." +msgid "Can't find board '{0}'. Change board or start a new game." +msgstr "Impossible de trouver le plateau de type '{0}'. Changez de type de plateau ou démarrez une nouvelle partie." diff --git a/l10n/pot/trivabble.pot b/l10n/pot/trivabble.pot index 4714921..a2b2f2b 100644 --- a/l10n/pot/trivabble.pot +++ b/l10n/pot/trivabble.pot @@ -172,10 +172,10 @@ msgstr "" msgid "Board language:" msgstr "" -msgid "You changed the board to {0}x{0} for language {1}" +msgid "You changed the board to {0} for language {1}" msgstr "" -msgid "{0} changed the board to {1}x{1} for language {2}" +msgid "{0} changed the board to {1} for language {2}" msgstr "" msgid "Settings" @@ -267,8 +267,11 @@ msgstr "" msgid "To measure playing time, activate the timer." msgstr "" -msgid "Board size:" +msgid "Board label:" 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." +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 "" + +msgid "Can't find board '{0}'. Change board or start a new game." msgstr "" diff --git a/public/boards.js b/public/boards.js index 0eb4ac8..62dad83 100644 --- a/public/boards.js +++ b/public/boards.js @@ -1,5 +1,6 @@ window.BoardList = { - "8x8": { + "8x8-7": { + factor: 0.25, TW: [ "A1", "A8", "H1", "H8" @@ -27,14 +28,16 @@ window.BoardList = { "D4" ] }, - "15x15": { + "15x15-7": { + factor: 1, 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": { + "22x22-7": { + factor: 2, TW: [ "A1", "A8", "A15", "A22", "H1", "H8", "H15", "H22", diff --git a/public/index.html b/public/index.html index 6d4244e..6ba07e0 100644 --- a/public/index.html +++ b/public/index.html @@ -47,12 +47,10 @@

-

- - +

diff --git a/public/trivabble.js b/public/trivabble.js index c849d88..53125e5 100644 --- a/public/trivabble.js +++ b/public/trivabble.js @@ -152,7 +152,7 @@ DisableSpellChecker: "boolean", GameNumber: "number", BoardLang: "string", - BoardSize: "number", + BoardLabel: "string", Lang: "string", PlayerName: "string", Timer: "number", @@ -180,7 +180,7 @@ let scoreOf; let bag; let boardLangSelect; - let boardSizeSelect; + let boardLabelSelect; const downloadedDictionaries = {}; const playerLetters = []; @@ -234,13 +234,15 @@ } function setRack(rack) { - for (let i = 0; i < 7; i++) { + const rackLength = getBoardDimensions(getSetting("BoardLabel"))[2]; + for (let i = 0; i < rackLength; i++) { setTileParent(playerLetters[i], rack[i] || ""); } } function getFreeRackSpaceIndex() { - for (let i = 0; i < 7; i++) { + const rackLength = getBoardDimensions(getSetting("BoardLabel"))[2]; + for (let i = 0; i < rackLength; i++) { if (!playerLetters[i].getElementsByClassName("tile")[0]) { return i; } @@ -449,7 +451,8 @@ } } } else if (tileInitDest.getElementsByClassName("tile")[0]) { - for (let i = 0; i < 7; i++) { + const rackLength = getBoardDimensions(getSetting("BoardLabel"))[2]; + for (let i = 0; i < rackLength; i++) { if (!playerLetters[i].getElementsByClassName("tile")[0]) { playerLetters[i].appendChild(movingTile); break; @@ -754,8 +757,17 @@ 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) { - for (let i = 0; i < getSetting("BoardSize") * getSetting("BoardSize"); i++) { + const [nbRows, nbColumns] = getBoardDimensions(getSetting("BoardLabel")); + + for (let i = 0; i < nbRows * nbColumns; i++) { setCell(i, board[i]); } } @@ -775,9 +787,9 @@ setSetting("BoardLang", value); checkDictionaryExistance(value); break; - case "boardSize": - document.getElementById("board-size").value = value; - setSetting("BoardSize", value); + case "boardLabel": + document.getElementById("board-label").value = value; + setSetting("BoardLabel", value); initBoard(value); break; } @@ -914,11 +926,11 @@ case "changeBoardDef": { const newLang = boardLangSelect.querySelector("[value=" + msg.specialMsg.newBoardLang + "]").textContent; - const newSize = msg.specialMsg.newBoardSize; + const newLabel = msg.specialMsg.newBoardLabel; infoMessage( (msg.sender === getSetting("PlayerName")) - ? 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) + ? format(_("You changed the board to {0} for language {1}"), newLabel, newLang) + : format(_("{0} changed the board to {1} for language {2}"), msg.sender, newLabel, newLang) ); break; } @@ -1220,9 +1232,9 @@ (boardLangSelect || {}).value = data.boardLang; } - if (data.boardSize) { - set("boardSize", Number(data.boardSize)); - (boardSizeSelect || {}).value = data.boardSize; + if (data.boardLabel) { + set("boardLabel", data.boardLabel); + (boardLabelSelect || {}).value = data.boardLabel; } if (data.letterValues) { @@ -1513,7 +1525,7 @@ gameNumber: getSetting("GameNumber") || "", playerName: getSetting("PlayerName"), boardLang: getSetting("BoardLang"), - boardSize: getSetting("BoardSize"), + boardLabel: getSetting("BoardLabel"), version: VERSION, cmds: cmds }; @@ -1655,7 +1667,7 @@ 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, size: getSetting("BoardSize")}]); + sendCmds([{cmd: "changeBoard", lang: code, label: getSetting("BoardLabel")}]); }, function () { boardLangSelect.value = getSetting("BoardLang"); @@ -1663,16 +1675,16 @@ ); } - function onChangeBoardSize() { - const size = document.getElementById("board-size").value; + function onChangeBoardLabel() { + const label = document.getElementById("board-label").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), + 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"), size: size}]); + sendCmds([{cmd: "changeBoard", lang: getSetting("BoardLang"), label: label}]); }, function () { - boardSizeSelect.value = getSetting("BoardLang"); + boardLabelSelect.value = getSetting("BoardLabel"); } ); } @@ -1739,11 +1751,13 @@ } function getLetterFromBoard(row, col) { - if ((row < 0) || (row >= getSetting("BoardSize")) || (col < 0) || (col >= getSetting("BoardSize"))) { + const [nbRows, nbColumns] = getBoardDimensions(getSetting("BoardLabel")); + + if ((row < 0) || (row >= nbRows) || (col < 0) || (col >= nbColumns)) { return ""; } try { - const index = (row * getSetting("BoardSize")) + col; + const index = (row * nbColumns) + col; const tilePlaceholder = boardCells[index].getElementsByClassName("tile-placeholder")[0]; const tile = tilePlaceholder.getElementsByClassName("tile")[0]; return tile.firstChild.textContent; @@ -1789,13 +1803,14 @@ } 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 / getSetting("BoardSize")); - const col = i % getSetting("BoardSize"); + const row = Math.floor(i / nbColumns); + const col = i % nbColumns; /* Look for word in column */ const newWordInCol = searchWordInLine(row, col, 1, 0); @@ -1821,6 +1836,7 @@ } function modifyTileClass(word, value, action) { + const [nbRows, nbColumns] = getBoardDimensions(getSetting("BoardLabel")); /* action: true for add, false for remove */ @@ -1828,7 +1844,7 @@ /* only for new words */ for (let l = 0; l < word.letters.length; l++) { - const index = ((word.row + (l * word.incRow)) * getSetting("BoardSize")) + word.col + (l * word.incCol); + const index = ((word.row + (l * word.incRow)) * nbColumns) + word.col + (l * word.incCol); const tilePlaceholder = boardCells[index].getElementsByClassName("tile-placeholder")[0]; const tile = tilePlaceholder.getElementsByClassName("tile")[0]; @@ -1838,7 +1854,7 @@ } else { /* tiles on board */ - for (let index = 0; index < getSetting("BoardSize") * getSetting("BoardSize"); index++) { + for (let index = 0; index < nbRows * nbColumns; index++) { const tilePlaceholder = boardCells[index].getElementsByClassName("tile-placeholder")[0]; const tile = tilePlaceholder.getElementsByClassName("tile")[0]; if (typeof tile !== "undefined") { @@ -1919,6 +1935,7 @@ } function scoreWords() { + const [nbColumns, rackLength] = getBoardDimensions(getSetting("BoardLabel")).slice(1); let totalScore = 0; const newWords = searchNewWords(); @@ -1928,7 +1945,7 @@ const word = newWords[k]; for (let l = 0; l < word.letters.length; l++) { - const index = ((word.row + (l * word.incRow)) * getSetting("BoardSize")) + word.col + (l * word.incCol); + const index = ((word.row + (l * word.incRow)) * nbColumns) + word.col + (l * word.incCol); /* Letter score */ const tilePlaceholder = boardCells[index].getElementsByClassName("tile-placeholder")[0]; @@ -1967,7 +1984,7 @@ } /* Check for trivabble (the premium score) */ - if (Object.keys(currentTilePlayed).length === 7) { + if (Object.keys(currentTilePlayed).length === rackLength) { totalScore += getSetting("PREMIUM_SEVEN_TILES"); } @@ -2001,7 +2018,8 @@ return; } - const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + const nbColumns = getBoardDimensions(getSetting("BoardLabel"))[1]; + const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; const col = cell.cellIndex - 1; const row = getRowIndex(cell.parentNode) - 1; @@ -2010,7 +2028,7 @@ msg: _("Look at:") + " " + letters[row] + (cell.cellIndex + 1), specialMsg: { type: "highlightCell", - cell: (row * getSetting("BoardSize")) + col + cell: (row * nbColumns) + col } }]); } @@ -2412,7 +2430,7 @@ chatMessages = document.getElementById("chat-messages"); chatTextarea = document.getElementById("chat-ta"); participantPlaceholder = document.getElementById("participants-placeholder"); - boardSizeSelect = document.getElementById("board-size"); + boardLabelSelect = document.getElementById("board-label"); } function initEvents() { @@ -2420,7 +2438,7 @@ document.getElementById("clear-game").onclick = clearGame; document.getElementById("board-lang").onchange = onChangeBoardLang; - document.getElementById("board-size").onchange = onChangeBoardSize; + document.getElementById("board-label").onchange = onChangeBoardLabel; document.getElementById("change-name").onclick = changeName; document.getElementById("join-game").onclick = joinGame; document.getElementById("clear-rack").onclick = clearRack; @@ -2453,15 +2471,17 @@ return; } - name.textContent = getSetting("PlayerName"); + if (Object.keys(BoardList).length > 1) { + boardLabelSelect.textContent = ""; - for (let i = 0; i < 7; i++) { - const span = document.createElement("span"); - span.className = "tile-placeholder"; - rack.appendChild(span); - playerLetters.push(span); + for (const key of Object.keys(BoardList)) { + boardLabelSelect.add(new Option(key, key)); + } + + document.getElementById("board-label-selection").hidden = false; } + name.textContent = getSetting("PlayerName"); if (getSetting("GameNumber")) { document.getElementById("number").textContent = getSetting("GameNumber"); @@ -2471,33 +2491,42 @@ document.getElementById("board-lang").value = getSetting("BoardLang"); } - if (getSetting("BoardSize")) { - document.getElementById("board-size").value = getSetting("BoardSize"); + if (getSetting("BoardLabel")) { + document.getElementById("board-label").value = getSetting("BoardLabel"); } startGame(getSetting("GameNumber")); } - function initBoard(boardSize) { + function initBoard(boardLabel) { - const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - const boardDef = BoardList[boardSize + "x" + boardSize]; + 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); let cell; let row; board.innerHTML = ""; - for (let i = 0; i < boardSize; i++) { + for (let j = 0; j < nbColumns; j++) { board.rows[0].appendChild(document.createElement("th")); - board.rows[0].lastChild.textContent = i + 1; + board.rows[0].lastChild.textContent = j + 1; + } + for (let i = 0; i < nbRows; i++) { row = board.insertRow(-1); row.appendChild(document.createElement("th")); row.lastChild.textContent = letters[i]; - for (let j = 0; j < boardSize; j++) { + for (let j = 0; j < nbColumns; j++) { cell = document.createElement("td"); boardCells.push(cell); row.appendChild(cell); @@ -2531,12 +2560,23 @@ row = board.insertRow(-1); row.appendChild(board.rows[0].cells[0].cloneNode(false)); - for (let i = 0; i < boardSize; i++) { + for (let i = 0; i < nbColumns; i++) { row.appendChild(document.createElement("th")); row.lastChild.textContent = i + 1; } row.appendChild(board.rows[0].cells[0].cloneNode(false)); + + rack.innerHTML = ""; + playerLetters.length = 0; + + for (let i = 0; i < rackLength; i++) { + const span = document.createElement("span"); + span.className = "tile-placeholder"; + rack.appendChild(span); + playerLetters.push(span); + } + } function initLang() { diff --git a/server/trivabble-server.js b/server/trivabble-server.js index 8e916c5..61fb576 100644 --- a/server/trivabble-server.js +++ b/server/trivabble-server.js @@ -35,7 +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 DEFAULT_BOARD_LABEL = process.env.TRIVABBLE_DEFAULT_BOARD_LABEL || "15x15-7"; const VERSION = 202005070100; @@ -207,19 +207,28 @@ function keepAlive(responseAndType) { }, KEEP_ALIVE); } -function newBoard(size) { - const res = new Array(size * size); +function getBoardDimensions(boardLabel) { + return boardLabel + .match(/[0-9]*/gu) + .filter(function (x) {return (x.length !== 0);}) + .map(function (x) {return parseInt(x);}); +} - for (let i = 0; i < size * size; i++) { +function newBoard(label) { + const [nbRows, nbColumns] = getBoardDimensions(label); + + const res = new Array(nbRows * nbColumns); + + for (let i = 0; i < nbRows * nbColumns; i++) { res[i] = ""; } return res; } -Game.prototype.init = function (lang, size) { - this.size = size || DEFAULT_BOARD_SIZE; - this.board = newBoard(this.size); +Game.prototype.init = function (lang, label) { + this.label = label || DEFAULT_BOARD_LABEL; + this.board = newBoard(this.label); this.lang = lang || DEFAULT_BOARD_LANG; this.bag = boardTilesPerLang[this.lang].bag.slice(); this.letterValues = boardTilesPerLang[this.lang].letterValues; @@ -235,7 +244,7 @@ Game.prototype.toJSON = function () { return { board: this.board, lang: this.lang, - size: this.size, + label: this.label, bag: this.bag, letterValues: this.letterValues, racks: this.racks, @@ -247,8 +256,8 @@ Game.prototype.toJSON = function () { Game.fromJSON = function (obj) { const game = new Game(); - game.size = obj.size || DEFAULT_BOARD_SIZE; - game.board = obj.board || newBoard(game.size); + game.label = obj.label || DEFAULT_BOARD_LABEL; + game.board = obj.board || newBoard(game.label); game.lang = obj.lang || DEFAULT_BOARD_LANG; game.bag = boardTilesPerLang[game.lang].bag.slice(); game.letterValues = boardTilesPerLang[game.lang].letterValues; @@ -406,12 +415,12 @@ Game.prototype.bagPushLetter = function (letter, player) { }; Game.prototype.reset = function (player) { - this.init(this.lang, this.size); + this.init(this.lang, this.label); this.pendingEvents.push({ player: player, action: "reset", board: this.board, - size: this.size, + label: this.label, remainingLetters: this.bag.length, rack: [] }); @@ -476,6 +485,7 @@ function handleCommand(cmdNumber, message, response) { game.lastUpdated = new Date(); const playerName = message.playerName; + const [nbRows, nbColumns, rackLength] = getBoardDimensions(game.label); let rack = null; const cmd = message.cmds[cmdNumber]; @@ -488,7 +498,7 @@ function handleCommand(cmdNumber, message, response) { gameNumber: gameNumber, playerName: playerName, boardLang: game.lang, - boardSize: game.size, + boardLabel: game.label, availableBoardLangs: availableBoardLangs, currentPlayer: game.currentPlayer, rack: game.getPlayerRack(playerName), @@ -502,7 +512,7 @@ function handleCommand(cmdNumber, message, response) { case "hello": { game.playerJoined(playerName); - reply(message, response, cmdNumber, {error: 0, boardLang: game.lang, boardSize: game.size, version: VERSION}); + reply(message, response, cmdNumber, {error: 0, boardLang: game.lang, boardLabel: game.label, version: VERSION}); break; } @@ -548,7 +558,7 @@ function handleCommand(cmdNumber, message, response) { break; case "board": - if (cmd.indexFrom < 0 || cmd.indexFrom >= game.size * game.size) { + if (cmd.indexFrom < 0 || cmd.indexFrom >= nbRows * nbColumns) { reply(message, response, cmdNumber, {error: 1, reason: "Wrong indexFrom"}); return false; } @@ -593,7 +603,7 @@ function handleCommand(cmdNumber, message, response) { break; case "board": - if (cmd.indexTo < 0 || cmd.indexTo >= game.size * game.size) { + if (cmd.indexTo < 0 || cmd.indexTo >= nbRows * nbColumns) { reply(message, response, cmdNumber, {error: 1, reason: "Wrong indexTo"}); return false; } @@ -636,7 +646,7 @@ function handleCommand(cmdNumber, message, response) { } case "setRack": { - if (cmd.rack.length > 7) { + if (cmd.rack.length > rackLength) { reply(message, response, cmdNumber, { error: 1, rack: rack, @@ -653,7 +663,7 @@ function handleCommand(cmdNumber, message, response) { const newRackSorted = cmd.rack.filter((l) => l); newRackSorted.sort(); - for (let i = 0; i < 7; i++) { + for (let i = 0; i < rackLength; i++) { if ((oldRackSorted[i] !== newRackSorted[i]) && (oldRackSorted[i] || newRackSorted[i])) { reply(message, response, cmdNumber, { error: 1, @@ -664,7 +674,7 @@ function handleCommand(cmdNumber, message, response) { } } - for (let i = 0; i < 7; i++) { + for (let i = 0; i < rackLength; i++) { rack[i] = cmd.rack[i]; } @@ -681,24 +691,24 @@ function handleCommand(cmdNumber, message, response) { case "changeBoard": { game.lang = cmd.lang || DEFAULT_BOARD_LANG; - game.size = cmd.size || DEFAULT_BOARD_SIZE; + game.label = cmd.label || DEFAULT_BOARD_LABEL; game.reset(); reply(message, response, cmdNumber, { error: 0, boardLang: game.lang, - boardSize: game.size, + boardLabel: game.label, letterValues: game.letterValues }); game.pendingEvents.push({ msg: { sender: playerName, - content: "I changed the board to " + game.size + "x" + game.size + " in language " + game.lang, + content: "I changed the board to " + game.label + " in language " + game.lang, specialMsg: { type: "changeBoardDef", newBoardLang: game.lang, - newBoardSize: game.size + newBoardLabel: game.label } } }); @@ -739,7 +749,7 @@ function handleCommands(message, responseAndType) { currentPlayer: game.currentPlayer, gameNumber: gameNumber, boardLang: game.lang, - boardSize: game.size, + boardLabel: game.label, availableBoardLangs: availableBoardLangs, letterValues: game.letterValues, rack: game.getPlayerRack(message.playerName),