Merge branch 'custon_board_layout' into 'develop'

WIP: Add custom boards

Closes #21

See merge request raphj/trivabble!25
This commit is contained in:
Laurent Mazet 2021-05-30 19:17:31 +00:00
commit 3fba842e71
8 changed files with 344 additions and 88 deletions

View File

@ -1,6 +1,6 @@
# -*- Makefile -*-
PORT = 3000
PORT?=3000
ESLINT?=eslint

View 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} for language {1}"
msgstr "Vous avez changé le plateau pour {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} for language {2}"
msgstr "{0} a changé le plateau pour {1} en {2}"
msgid "Settings"
msgstr "Paramètres"
@ -277,3 +277,14 @@ msgstr "Pas de légende"
msgid "Short caption"
msgstr "Légendes courtes"
msgid "Board configuration:"
msgstr "Configuration 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."
msgid "Caution, this feature is experimental; any feedback will be appreciated"
msgstr "Attention, cette fonctionnalité est expérimentale ; tout commentaire sera grandement apprécié"

View 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} for language {1}"
msgstr ""
msgid "{0} changed the language of the board to {1}"
msgid "{0} changed the board to {1} for language {2}"
msgstr ""
msgid "Settings"
@ -281,3 +281,15 @@ msgstr ""
msgid "Short caption"
msgstr ""
msgid "Board configuration:"
msgstr ""
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 ""
msgid "Caution, this feature is experimental; any feedback will be appreciated"
msgstr ""

110
public/boards.js Normal file
View File

@ -0,0 +1,110 @@
window.BoardList = {
"8x8-7": {
factor: 0.3,
premium: 50,
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"]
},
"9x9-8": {
factor: 0.4,
premium: 65,
TW: ["A1", "A9", "I1", "I9"],
DW: ["B2", "C7", "G3", "H8"],
TL: ["B5", "C3", "D6", "E2", "E8", "F4", "G7", "H5"],
DL: ["A4", "A6", "B8", "D1", "D4", "D9", "F1", "F6", "F9", "H2", "I4", "I6"],
CS: ["E5"]
},
"8x15-7": {
factor: 0.6,
premium: 50,
TW: ["A1", "A8", "A15", "H1", "H8", "H15"],
DW: ["B2", "C3", "C10", "E9", "F6", "F13", "G14"],
TL: ["B6", "B13", "C14", "D11", "E5", "F2", "G3", "G10"],
DL: ["A5", "A12", "B4", "B9", "B11", "C8", "D2", "D4", "D15", "E1", "E12", "E14", "F8", "G5", "G7", "G12", "H4", "H11"],
CS: ["D7"]
},
"9x17-8": {
factor: 0.7,
premium: 65,
TW: ["A1", "A9", "A17", "I1", "I9", "I17"],
DW: ["B2", "B16", "C3", "C15", "F10", "G3", "G15", "H2", "H16"],
TL: ["B6", "B12", "C7", "C11", "D14", "E5", "E13", "F4", "G7", "G11", "H6", "H12"],
DL: ["A5", "A13", "B4", "C13", "D1", "D4", "D10", "D17", "E2", "E9", "E16", "F1", "F8", "F14", "F17", "G5", "H14", "I5", "I13"],
CS: ["D8"]
},
"13x13-6": {
factor: 0.75,
premium: 35,
TW: ["A1", "A7", "A13", "G1", "G13", "M1", "M7", "M13"],
DW: ["B2", "B12", "C3", "C11", "D4", "D10", "J4", "J10", "K3", "K11", "L2", "L12"],
TL: ["B5", "B9", "E2", "E12", "F6", "F8", "H6", "H8", "I2", "I12", "L5", "L9"],
DL: ["A4", "A10", "C6", "C8", "D1", "D7", "D13", "E5", "E9", "F3", "F11", "G4", "G10", "H3", "H11", "I5", "I9", "J1", "J7", "J13", "K6", "K8", "M4", "M10"],
CS: ["G7"]
},
"8x22-7": {
factor: 0.8,
premium: 50,
TW: ["A1", "A8", "A15", "A22", "H1", "H8", "H15", "H22"],
DW: ["B3", "B10", "B18", "C4", "C11", "C19", "E5", "E21", "G7", "G15", "G23", "H8", "H16", "H24"],
TL: ["B3", "B13", "B17", "C2", "C10", "C16", "D5", "E18", "F7", "F13", "F21", "G6", "G10", "G20"],
DL: ["A5", "A11", "A19", "B9", "C14", "D8", "D19", "D22", "E1", "E4", "E15", "F9", "G14", "H4", "H12", "H18"],
CS: ["D11"]
},
"9x25-8": {
factor: 1,
premium: 65,
TW: ["A1", "A9", "A17", "A25", "I1", "I9", "I17", "I25"],
DW: ["B2", "B10", "B18", "C3", "C19", "E5", "E21", "G7", "G23", "H8", "H16", "H24"],
TL: ["B6", "B14", "B22", "C7", "C15", "D8", "D16", "D24", "F2", "F10", "F18", "G11", "G19", "H4", "H12", "H20"],
DL: ["A5", "A13", "A21", "C11", "C23", "D4", "D12", "D20", "E1", "E9", "E17", "E25", "F6", "F14", "F22", "G3", "G15", "I5", "I13", "I21"],
CS: ["E13"]
},
"15x15-7": {
factor: 1,
premium: 50,
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", "O12"],
CS: ["H8"]
},
"17x17-8": {
factor: 1.3,
premium: 65,
TW: ["A1", "A9", "A17", "I1", "I17", "Q1", "Q9", "Q17"],
DW: ["B2", "B16", "C3", "C15", "D4", "D14", "E5", "E13", "M5", "M13", "N4", "N14", "O3", "O15", "P2", "P16"],
TL: ["B6", "B12", "C7", "C11", "F2", "F16", "G3", "G7", "G11", "G15", "K3", "K7", "K11", "K15", "L2", "L16", "O7", "O11", "P6", "P12"],
DL: ["A5", "A13", "D8", "D10", "E1", "E9", "E17", "F6", "F12", "H4", "H8", "H10", "H14", "I5", "I13", "J4", "J8", "J10", "J14", "L6", "L12", "M1", "M9", "M17", "N8", "N10", "Q5", "Q13"],
CS: ["I9"]
},
"19x19-6": {
factor: 1.6,
premium: 35,
TW: ["A1", "A7", "A13", "A19", "G1", "G7", "G13", "G19", "M1", "M7", "M13", "M19", "S1", "S7", "S13", "S19"],
DW: ["B2", "B8", "B18", "C3", "C17", "D4", "D10", "D16", "H9", "H18", "J4", "J16", "L2", "L11", "P4", "P10", "P16", "Q3", "Q17", "R2", "R12", "R18"],
TL: ["B5", "B15", "C11", "E2", "E8", "E12", "E18", "F6", "F14", "H5", "H15", "I3", "I12", "K8", "K17", "L5", "L15", "N6", "N14", "O2", "O8", "O12", "O18", "Q9", "R5", "R15"],
DL: ["A4", "A10", "A16", "B12", "C6", "C9", "C14", "D1", "D7", "D13", "D19", "E5", "E15", "F3", "F9", "F11", "F17", "G4", "G10", "G16", "H2", "H11", "I6", "I8", "I14", "I17", "J1", "J7", "J13", "J19", "K3", "K6", "K12", "K14", "L9", "L18", "M4", "M10", "M16", "N3", "N9", "N11", "N17", "O5", "O15", "P1", "P7", "P13", "P19", "Q6", "Q11", "Q14", "R8", "S4", "S10", "S16"],
CS: ["J10"]
},
"22x22-7": {
factor: 2,
premium: 50,
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"]
},
"25x25-8": {
factor: 2.8,
premium: 65,
TW: ["A1", "A9", "A17", "A25", "I1", "I9", "I17", "I25", "Q1", "Q9", "Q17", "Q25", "Y1", "Y9", "Y17", "Y25"],
DW: ["B2", "B24", "D4", "D14", "D22", "E5", "E13", "E21", "F12", "H8", "H18", "L4", "L20", "M5", "M21", "N6", "N22", "R8", "R18", "T14", "U5", "U13", "U21", "V4", "V12", "V22", "X2", "X24"],
TL: ["C3", "C7", "C11", "C15", "C19", "C23", "G3", "G7", "G11", "G15", "G19", "G23", "K3", "K7", "K11", "K15", "K19", "K23", "O3", "O7", "O11", "O15", "O19", "O23", "S3", "S7", "S11", "S15", "S19", "S23", "W3", "W7", "W11", "W15", "W19", "W23"],
DL: ["A5", "A13", "A21", "B6", "B12", "B16", "B20", "D10", "D18", "E1", "E9", "E17", "E25", "F2", "F6", "F16", "F20", "F24", "H4", "H14", "I5", "I13", "I21", "J2", "J6", "J10", "J16", "J22", "L8", "L12", "L24", "M1", "M9", "M17", "M25", "N2", "N14", "N18", "P4", "P10", "P16", "P20", "P24", "Q5", "Q13", "Q21", "R12", "R22", "T2", "T6", "T10", "T20", "T24", "U1", "U9", "U17", "U25", "V8", "V16", "X6", "X10", "X14", "X20", "Y5", "Y13", "Y21"],
CS: ["M13"]
}
};

View File

@ -53,6 +53,9 @@ window.TrivabbleConf = {
// Cell captions. Could be "clip", "dots", "none" or "short"
//CELL_CAPTIONS: "dots",
// Enable custom board definitions
//ENABLE_CUSTOM_BOARD: false,
// I don't like trailing commas, here is a nice message for you reading this file :-)
HAVE_FUN: true
};

View 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">
@ -46,6 +47,12 @@
<option value="fr">French</option>
</select>
</p>
<p id="board-label-selection" hidden="true">
<label for="board-label" data-l10n="text-content">Board configuration: </label>
<select id="board-label">
<option value="15x15-7">15x15-7</option>
</select>
</p>
</div>
</div>

View 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;
@ -64,6 +66,7 @@
setConf("SCORE_LAST_PLAYER", true);
setConf("ENABLE_TIMER", false);
setConf("CELL_CAPTIONS", "dots"); // "clip", "dots", "none", "short"
setConf("ENABLE_CUSTOM_BOARD", false);
function isSetting(key) {
return Object.prototype.hasOwnProperty.call(Conf, key) ||
@ -156,6 +159,7 @@
BoardLang: "string",
DisableSpellChecker: "boolean",
GameNumber: "number",
BoardLabel: "string",
Lang: "string",
PlayerName: "string",
SpellCheckerEnabledOnce: "boolean",
@ -171,9 +175,9 @@
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;
@ -182,8 +186,10 @@
let scoreOf;
let bag;
let boardLangSelect;
let boardLabelSelect;
const downloadedDictionaries = {};
const boardDef = {nbRows: 0, nbColumns: 0, rackLength: 0};
let warningCustomBoard = false;
const playerLetters = [];
let currentPlayer = "";
@ -235,13 +241,13 @@
}
function setRack(rack) {
for (let i = 0; i < 7; i++) {
for (let i = 0; i < boardDef.rackLength; i++) {
setTileParent(playerLetters[i], rack[i] || "");
}
}
function getFreeRackSpaceIndex() {
for (let i = 0; i < 7; i++) {
for (let i = 0; i < boardDef.rackLength; i++) {
if (!playerLetters[i].getElementsByClassName("tile")[0]) {
return i;
}
@ -450,7 +456,7 @@
}
}
} else if (tileInitDest.getElementsByClassName("tile")[0]) {
for (let i = 0; i < 7; i++) {
for (let i = 0; i < boardDef.rackLength; i++) {
if (!playerLetters[i].getElementsByClassName("tile")[0]) {
playerLetters[i].appendChild(movingTile);
break;
@ -756,7 +762,7 @@
}
function setBoard(board) {
for (let i = 0; i < 15 * 15; i++) {
for (let i = 0; i < boardDef.nbRows * boardDef.nbColumns; i++) {
setCell(i, board[i]);
}
}
@ -776,6 +782,16 @@
setSetting("BoardLang", value);
checkDictionaryExistance(value);
break;
case "boardLabel":
document.getElementById("board-label").value = value;
setSetting("BoardLabel", 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;
}
}
@ -908,13 +924,18 @@
return;
}
case "changeBoardLang": {
case "changeBoardDef": {
const newLang = boardLangSelect.querySelector("[value=" + msg.specialMsg.newBoardLang + "]").textContent;
const newLabel = msg.specialMsg.newBoardLabel;
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} for language {1}"), newLabel, newLang)
: format(_("{0} changed the board to {1} for language {2}"), msg.sender, newLabel, newLang)
);
if ((newLabel !== "15x15-7") && (!warningCustomBoard)) {
infoMessage(_("Caution, this feature is experimental; any feedback will be appreciated"));
warningCustomBoard = true;
}
break;
}
@ -1214,6 +1235,16 @@
set("boardLang", data.boardLang);
(boardLangSelect || {}).value = data.boardLang;
}
if (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) {
scoreOf = data.letterValues;
@ -1503,6 +1534,8 @@
gameNumber: getSetting("GameNumber") || "",
playerName: getSetting("PlayerName"),
boardLang: getSetting("BoardLang"),
boardLabel: getSetting("BoardLabel"),
bagFactor: (typeof getSetting("BoardLabel") === "undefined") ? 1 : BoardList[getSetting("BoardLabel")].factor,
version: VERSION,
cmds: cmds
};
@ -1652,13 +1685,27 @@
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, label: getSetting("BoardLabel"), factor: BoardList[getSetting("BoardLabel")].factor}]);
},
function () {
boardLangSelect.value = getSetting("BoardLang");
}
);
}
function onChangeBoardLabel() {
const label = document.getElementById("board-label").value;
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, factor: BoardList[label].factor}]);
},
function () {
boardLabelSelect.value = getSetting("BoardLabel");
}
);
}
function clearRack() {
myConfirm(
@ -1722,11 +1769,11 @@
}
function getLetterFromBoard(row, col) {
if ((row < 0) || (row >= 15) || (col < 0) || (col >= 15)) {
if ((row < 0) || (row >= boardDef.nbRows) || (col < 0) || (col >= boardDef.nbColumns)) {
return "";
}
try {
const index = (row * 15) + 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;
@ -1772,13 +1819,12 @@
}
function searchNewWords() {
const words = [];
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 / boardDef.nbColumns);
const col = i % boardDef.nbColumns;
/* Look for word in column */
const newWordInCol = searchWordInLine(row, col, 1, 0);
@ -1811,7 +1857,7 @@
/* 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)) * boardDef.nbColumns) + word.col + (l * word.incCol);
const tilePlaceholder = boardCells[index].getElementsByClassName("tile-placeholder")[0];
const tile = tilePlaceholder.getElementsByClassName("tile")[0];
@ -1821,7 +1867,7 @@
} else {
/* tiles on board */
for (let index = 0; index < 15 * 15; 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") {
@ -1911,7 +1957,7 @@
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)) * boardDef.nbColumns) + word.col + (l * word.incCol);
/* Letter score */
const tilePlaceholder = boardCells[index].getElementsByClassName("tile-placeholder")[0];
@ -1950,8 +1996,12 @@
}
/* Check for trivabble (the premium score) */
if (Object.keys(currentTilePlayed).length === 7) {
totalScore += getSetting("PREMIUM_SEVEN_TILES");
if (Object.keys(currentTilePlayed).length === boardDef.rackLength) {
if (Object.prototype.hasOwnProperty.call(BoardList[getSetting("BoardLabel")], "premium")) {
totalScore += BoardList[getSetting("BoardLabel")].premium;
} else {
totalScore += getSetting("PREMIUM_SEVEN_TILES");
}
}
/* Ѕcore last player or the one who pressed the button */
@ -1984,7 +2034,7 @@
return;
}
const letters = "ABCDEFGHIJKLMNO";
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
const col = cell.cellIndex - 1;
const row = getRowIndex(cell.parentNode) - 1;
@ -1993,7 +2043,7 @@
msg: _("Look at:") + " " + letters[row] + (cell.cellIndex + 1),
specialMsg: {
type: "highlightCell",
cell: (row * 15) + col
cell: (row * boardDef.nbColumns) + col
}
}]);
}
@ -2422,6 +2472,7 @@
chatMessages = document.getElementById("chat-messages");
chatTextarea = document.getElementById("chat-ta");
participantPlaceholder = document.getElementById("participants-placeholder");
boardLabelSelect = document.getElementById("board-label");
}
function initEvents() {
@ -2429,6 +2480,7 @@
document.getElementById("clear-game").onclick = clearGame;
document.getElementById("board-lang").onchange = onChangeBoardLang;
document.getElementById("board-label").onchange = onChangeBoardLabel;
document.getElementById("change-name").onclick = changeName;
document.getElementById("join-game").onclick = joinGame;
document.getElementById("clear-rack").onclick = clearRack;
@ -2462,43 +2514,59 @@
return;
}
if (getSetting("ENABLE_CUSTOM_BOARD") && (Object.keys(BoardList).length > 1)) {
boardLabelSelect.textContent = "";
for (const key of Object.keys(BoardList)) {
boardLabelSelect.add(new Option(key, key));
}
document.getElementById("board-label-selection").hidden = false;
}
name.textContent = getSetting("PlayerName");
const letters = "ABCDEFGHIJKLMNO";
if (getSetting("GameNumber")) {
document.getElementById("number").textContent = getSetting("GameNumber");
}
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
};
if (getSetting("BoardLang")) {
document.getElementById("board-lang").value = getSetting("BoardLang");
}
if (getSetting("BoardLabel")) {
document.getElementById("board-label").value = getSetting("BoardLabel");
}
startGame(getSetting("GameNumber"));
}
function initBoard() {
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
const def = BoardList[getSetting("BoardLabel")];
let cell;
let row;
for (let i = 0; i < 7; i++) {
const span = document.createElement("span");
span.className = "tile-placeholder";
rack.appendChild(span);
playerLetters.push(span);
/* empty board */
while (board.firstChild) {
board.removeChild(board.lastChild);
}
cell = board.insertRow(0).insertCell(0);
cell.className = "corner";
boardCells.length = 0;
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 < 15; i++) {
board.rows[0].appendChild(document.createElement("th"));
board.rows[0].lastChild.textContent = i + 1;
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 < 15; j++) {
for (let j = 0; j < boardDef.nbColumns; j++) {
cell = document.createElement("td");
boardCells.push(cell);
row.appendChild(cell);
@ -2506,18 +2574,19 @@
cell.appendChild(document.createElement("div"));
cell.lastChild.className = "tile-placeholder";
if (i === j && i === 7) {
const cellName = letters[i] + (j + 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 (i % 7 === 0 && j % 7 === 0) {
} else if (def.TW.indexOf(cellName) !== -1) {
specialCell("tripleWord", row.lastChild);
} else if ((i === j || i + j === 14) && (i < 5 || i > 9)) {
} else if (def.DW.indexOf(cellName) !== -1) {
specialCell("doubleWord", row.lastChild);
} else if ((i % 4 === 1) && (j % 4 === 1)) {
} else if (def.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 (def.DL.indexOf(cellName) !== -1) {
specialCell("doubleLetter", row.lastChild);
}
}
@ -2531,22 +2600,26 @@
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 < boardDef.nbColumns; 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");
while (rack.firstChild) {
rack.removeChild(rack.lastChild);
}
playerLetters.length = 0;
for (let i = 0; i < boardDef.rackLength; i++) {
const span = document.createElement("span");
span.className = "tile-placeholder";
rack.appendChild(span);
playerLetters.push(span);
}
if (getSetting("BoardLang")) {
document.getElementById("board-lang").value = getSetting("BoardLang");
}
startGame(getSetting("GameNumber"));
setCellCaptions(getSetting("CELL_CAPTIONS"));
}
function initLang() {

View File

@ -35,6 +35,8 @@ 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_LABEL = process.env.TRIVABBLE_DEFAULT_BOARD_LABEL || "15x15-7";
const DEFAULT_BAG_FACTOR = process.env.TRIVABBLE_DEFAULT_BAG_FACTOR || 1;
const VERSION = 202005070100;
@ -206,26 +208,47 @@ function keepAlive(responseAndType) {
}, KEEP_ALIVE);
}
function newBoard() {
const res = new Array(15 * 15);
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 < 15 * 15; 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) {
this.board = newBoard();
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);
};
@ -233,6 +256,8 @@ Game.prototype.toJSON = function () {
return {
board: this.board,
lang: this.lang,
label: this.label,
factor: this.factor,
bag: this.bag,
letterValues: this.letterValues,
racks: this.racks,
@ -244,7 +269,9 @@ Game.prototype.toJSON = function () {
Game.fromJSON = function (obj) {
const game = new Game();
game.board = obj.board || newBoard();
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();
game.letterValues = boardTilesPerLang[game.lang].letterValues;
@ -402,11 +429,13 @@ Game.prototype.bagPushLetter = function (letter, player) {
};
Game.prototype.reset = function (player) {
this.init(this.lang);
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: []
});
@ -471,6 +500,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];
@ -483,6 +513,8 @@ function handleCommand(cmdNumber, message, response) {
gameNumber: gameNumber,
playerName: playerName,
boardLang: game.lang,
boardLabel: game.label,
bagFactor: game.factor,
availableBoardLangs: availableBoardLangs,
currentPlayer: game.currentPlayer,
rack: game.getPlayerRack(playerName),
@ -496,7 +528,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, boardLabel: game.label, bagFactor: game.factor, version: VERSION});
break;
}
@ -524,7 +556,7 @@ function handleCommand(cmdNumber, message, response) {
case "rack":
rack = game.getPlayerRack(playerName);
if (cmd.indexFrom > 6 || cmd.indexFrom < 0) {
if (cmd.indexFrom > rackLength - 1 || cmd.indexFrom < 0) {
reply(message, response, cmdNumber, {error: 1, reason: "Wrong indexFrom"});
return false;
}
@ -542,7 +574,7 @@ function handleCommand(cmdNumber, message, response) {
break;
case "board":
if (cmd.indexFrom < 0 || cmd.indexFrom >= 15 * 15) {
if (cmd.indexFrom < 0 || cmd.indexFrom >= nbRows * nbColumns) {
reply(message, response, cmdNumber, {error: 1, reason: "Wrong indexFrom"});
return false;
}
@ -569,7 +601,7 @@ function handleCommand(cmdNumber, message, response) {
switch (cmd.to) {
case "rack":
if (cmd.indexTo < 0 || cmd.indexTo > 6) {
if (cmd.indexTo < 0 || cmd.indexTo > rackLength - 1) {
reply(message, response, cmdNumber, {error: 1, reason: "Wrong indexTo"});
return false;
}
@ -587,7 +619,7 @@ function handleCommand(cmdNumber, message, response) {
break;
case "board":
if (cmd.indexTo < 0 || cmd.indexTo >= 15 * 15) {
if (cmd.indexTo < 0 || cmd.indexTo >= nbRows * nbColumns) {
reply(message, response, cmdNumber, {error: 1, reason: "Wrong indexTo"});
return false;
}
@ -630,7 +662,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,
@ -647,7 +679,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,
@ -658,7 +690,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];
}
@ -675,21 +707,27 @@ 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
});
game.pendingEvents.push({
msg: {
sender: playerName,
content: "I changed the language of the board to " + game.lang,
content: "I changed the board to " + game.label + " in language " + game.lang,
specialMsg: {
type: "changeBoardLang",
newBoardLang: game.lang
type: "changeBoardDef",
newBoardLang: game.lang,
newBoardLabel: game.label,
newBagFactor: game.factor
}
}
});
@ -730,6 +768,8 @@ function handleCommands(message, responseAndType) {
currentPlayer: game.currentPlayer,
gameNumber: gameNumber,
boardLang: game.lang,
boardLabel: game.label,
bagFactor: game.factor,
availableBoardLangs: availableBoardLangs,
letterValues: game.letterValues,
rack: game.getPlayerRack(message.playerName),