Merge branch 'lmazet/trivabble-l10n-board'
This commit is contained in:
commit
1815162cd1
|
@ -298,12 +298,16 @@
|
|||
"sort-keys": "off",
|
||||
"sort-vars": "off",
|
||||
"space-before-blocks": "error",
|
||||
"space-before-function-paren": "off",
|
||||
"space-before-function-paren": ["error", {
|
||||
"anonymous": "always",
|
||||
"named": "never",
|
||||
"asyncArrow": "always"
|
||||
}],
|
||||
"space-in-parens": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"space-infix-ops": "off",
|
||||
"space-infix-ops": ["error", { "int32Hint": false }],
|
||||
"space-unary-ops": "error",
|
||||
"spaced-comment": "off",
|
||||
"strict": [
|
||||
|
|
10
Makefile
10
Makefile
|
@ -1,13 +1,21 @@
|
|||
# -*- Makefile -*-
|
||||
|
||||
PORT = 3000
|
||||
ESLINT?=eslint
|
||||
|
||||
ifeq (, $(shell which $(firstword ${ESLINT})))
|
||||
ESLINT?=npx eslint
|
||||
endif
|
||||
|
||||
help:
|
||||
@echo make lang: build translation files
|
||||
@echo make eslint: use ESLint to check code standard conformence
|
||||
@echo make start-dev-server: start a development server
|
||||
|
||||
lang:
|
||||
cd l10n; make
|
||||
|
||||
eslint:
|
||||
-${ESLINT} **/*.js
|
||||
|
||||
start-dev-server:
|
||||
cd server && make start-dev-server
|
||||
|
|
187
l10n/makejs.js
187
l10n/makejs.js
|
@ -1,104 +1,117 @@
|
|||
#!/usr/bin/env node
|
||||
/*eslint strict: [2, "global"]*/
|
||||
/*eslint no-sync: ["error", { allowAtRootLevel: true }]*/
|
||||
|
||||
var ROOT = "../public/l10n/";
|
||||
"use strict";
|
||||
|
||||
/* Builds translation files.*/
|
||||
const ROOT = "../public/l10n/";
|
||||
|
||||
var fs = require('fs');
|
||||
/* Builds translation files. */
|
||||
|
||||
var langs = fs.readdirSync("po");
|
||||
var po, i, len;
|
||||
const fs = require("fs");
|
||||
|
||||
let po;
|
||||
let i;
|
||||
let len;
|
||||
|
||||
function skipLine() {
|
||||
while (i < len && po[i] !== '\n') {
|
||||
++i;
|
||||
}
|
||||
++i;
|
||||
while (i < len && po[i] !== "\n") {
|
||||
++i;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
|
||||
function skipSpaces() {
|
||||
while (i < len && !po[i].trim()) {
|
||||
++i;
|
||||
}
|
||||
while (i < len && !po[i].trim()) {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
function parseString() {
|
||||
skipSpaces();
|
||||
if (po[i] !== '"') {
|
||||
return "";
|
||||
}
|
||||
++i;
|
||||
var deb = i, end;
|
||||
while (i < len) {
|
||||
if (po[i] === "\\") {
|
||||
++i;
|
||||
} else if (po[i] === '"') {
|
||||
var str1 = po.substring(deb, i++);
|
||||
var end = i;
|
||||
skipSpaces();
|
||||
var ndeb = i;
|
||||
var str2 = parseString();
|
||||
if (i === ndeb) { // we did not parse anything
|
||||
i = end;
|
||||
return str1;
|
||||
}
|
||||
skipSpaces();
|
||||
if (po[i] !== '"') {
|
||||
return "";
|
||||
}
|
||||
++i;
|
||||
const deb = i;
|
||||
|
||||
return str1 + str2;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
throw new Error("not ended string at character " + deb);
|
||||
}
|
||||
|
||||
for (var l in langs) {
|
||||
var lang = langs[l];
|
||||
var jsFile = fs.openSync(ROOT + "js/" + lang + ".js", "w");
|
||||
fs.writeSync(jsFile, "(function(){var ");
|
||||
var poFiles = fs.readdirSync("po/" + lang);
|
||||
for (var p in poFiles) {
|
||||
var poFile = poFiles[p];
|
||||
|
||||
var translationFunction = fs.readFileSync("pot/" + poFile + 't', {encoding:'utf-8'})
|
||||
.match(/\#TranslationFunction[\s]+([\S]+)/)[1];
|
||||
|
||||
fs.writeSync(jsFile, "_=" + translationFunction + ".l10n;");
|
||||
|
||||
po = fs.readFileSync("po/" + lang + '/' + poFile, {encoding:'utf-8'});
|
||||
|
||||
i = 0; len = po.length;
|
||||
while (i < len) {
|
||||
skipSpaces();
|
||||
|
||||
if (po[i] === '#') {
|
||||
skipLine();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (po.substr(i, 5) === "msgid") {
|
||||
if (po[i+5].trim() && po[i+5] !== '"') {
|
||||
skipLine(); // don't understand this line
|
||||
continue;
|
||||
}
|
||||
i+=5;
|
||||
while (i < len) {
|
||||
if (po[i] === "\\") {
|
||||
++i;
|
||||
} else if (po[i] === '"') {
|
||||
const str1 = po.substring(deb, i++);
|
||||
const end = i;
|
||||
skipSpaces();
|
||||
msgid = parseString();
|
||||
} else if (po.substr(i, 6) === "msgstr") {
|
||||
if (po[i+6].trim() && po[i+6] !== '"') {
|
||||
skipLine(); // don't understand this line
|
||||
continue;
|
||||
}
|
||||
i+=6;
|
||||
msgstr = parseString();
|
||||
fs.writeSync(jsFile, '_("' + lang + '","' + msgid.replace(/\n/g,"") + '","' + msgstr.replace(/\n/g,"") + '");');
|
||||
}
|
||||
skipLine();
|
||||
}
|
||||
}
|
||||
const ndeb = i;
|
||||
const str2 = parseString();
|
||||
|
||||
fs.writeSync(jsFile, "if(" + translationFunction + ".applyL10n){" + translationFunction + ".applyL10n();}})();");
|
||||
fs.close(jsFile, function (e) {
|
||||
if (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
if (i === ndeb) { // we did not parse anything
|
||||
i = end;
|
||||
return str1;
|
||||
}
|
||||
|
||||
return str1 + str2;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
throw new Error("not ended string at character " + deb);
|
||||
}
|
||||
|
||||
let msgid;
|
||||
let msgstr;
|
||||
|
||||
for (const lang of fs.readdirSync("po")) {
|
||||
const jsFile = fs.openSync(ROOT + "js/" + lang + ".js", "w");
|
||||
fs.writeSync(jsFile, "(function(){var ");
|
||||
let translationFunction = "translationFunction";
|
||||
|
||||
for (const poFile of fs.readdirSync("po/" + lang)) {
|
||||
translationFunction = fs.readFileSync("pot/" + poFile + "t", {encoding: "utf-8"})
|
||||
.match(/#TranslationFunction[\s]+(?<functionName>[\S]+)/u).groups.functionName;
|
||||
|
||||
fs.writeSync(jsFile, "_=" + translationFunction + ".l10n;");
|
||||
|
||||
po = fs.readFileSync("po/" + lang + "/" + poFile, {encoding: "utf-8"});
|
||||
|
||||
i = 0;
|
||||
len = po.length;
|
||||
while (i < len) {
|
||||
skipSpaces();
|
||||
|
||||
if (po.substr(i, 5) === "msgid") {
|
||||
if (po[i + 5].trim() && po[i + 5] !== '"') {
|
||||
skipLine(); // don't understand this line
|
||||
} else {
|
||||
i += 5;
|
||||
skipSpaces();
|
||||
msgid = parseString();
|
||||
}
|
||||
} else if (po.substr(i, 6) === "msgstr") {
|
||||
if (po[i + 6].trim() && po[i + 6] !== '"') {
|
||||
skipLine(); // don't understand this line
|
||||
} else {
|
||||
i += 6;
|
||||
msgstr = parseString();
|
||||
fs.writeSync(
|
||||
jsFile,
|
||||
'_("' + lang + '","' + msgid.replace(/\n/gu, "") + '","' + msgstr.replace(/\n/gu, "") + '");'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// if po[i] === "#", ignore
|
||||
skipLine();
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeSync(
|
||||
jsFile,
|
||||
"if(" + translationFunction + ".applyL10n){" + translationFunction + ".applyL10n();}})();"
|
||||
);
|
||||
|
||||
fs.close(jsFile, function (e) {
|
||||
if (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -153,3 +153,27 @@ msgstr "{0} montre son jeu :"
|
|||
|
||||
msgid "You cannot take another tile: the bag is empty."
|
||||
msgstr "Vous ne pouvez pas tirer de pièce : le sac est vide."
|
||||
|
||||
msgid "Are you sure you want to change board to '{0}'? This will put all the tiles back in the bag and start another game."
|
||||
msgstr "Êtes-vous sûr·e de vouloir de changer la langue du plateau à '{0}' ? Cela remettra toutes les lettres du jeu dans le sac et commencera une nouvelle partie."
|
||||
|
||||
msgid "Board language:"
|
||||
msgstr "Langue du plateau :"
|
||||
|
||||
msgid "English"
|
||||
msgstr "Anglais"
|
||||
|
||||
msgid "French"
|
||||
msgstr "Français"
|
||||
|
||||
msgid "German"
|
||||
msgstr "Allemand"
|
||||
|
||||
msgid "Spanish"
|
||||
msgstr "Espagnol"
|
||||
|
||||
msgid "You changed the language of the board to {0}"
|
||||
msgstr "Vous avez changé la langue du plateau en {0}"
|
||||
|
||||
msgid "{0} changed the language of the board to {1}"
|
||||
msgstr "{0} a changé la langue du plateau en {1}"
|
||||
|
|
|
@ -159,3 +159,27 @@ msgstr ""
|
|||
|
||||
msgid "You cannot take another tile: the bag is empty."
|
||||
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 "Board language:"
|
||||
msgstr ""
|
||||
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
msgid "You changed the language of the board to {0}"
|
||||
msgstr ""
|
||||
|
||||
msgid "{0} changed the language of the board to {1}"
|
||||
msgstr ""
|
||||
|
|
|
@ -14,7 +14,9 @@
|
|||
let alertInput;
|
||||
let divAlertContent;
|
||||
|
||||
const _ = (window.libD && libD.l10n) ? libD.l10n() : function (s) {return s;};
|
||||
const _ = (window.libD && libD.l10n) ? libD.l10n() : function (s) {
|
||||
return s;
|
||||
};
|
||||
|
||||
function promptOK() {
|
||||
divAlert.style.display = "none";
|
||||
|
|
|
@ -39,6 +39,14 @@
|
|||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div id="board-lang-selection" style="display:none">
|
||||
<label>
|
||||
<span data-l10n="text-content">Board language: </span>
|
||||
<select id="board-lang">
|
||||
<option value="fr">French</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="content">
|
||||
|
|
|
@ -6,7 +6,7 @@ window.libD = {
|
|||
|
||||
const t = [];
|
||||
|
||||
function f (lang, orig, translated) {
|
||||
function f(lang, orig, translated) {
|
||||
if (!orig) {
|
||||
return (
|
||||
(libD.lang && t[libD.lang] && t[libD.lang][lang])
|
||||
|
@ -20,7 +20,7 @@ window.libD = {
|
|||
}
|
||||
|
||||
t[lang][orig] = translated;
|
||||
};
|
||||
}
|
||||
|
||||
return f;
|
||||
},
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,58 +1,70 @@
|
|||
//thx http://stackoverflow.com/questions/1517924/javascript-mapping-touch-events-to-mouse-events#1781750
|
||||
/*global jQuery*/
|
||||
|
||||
function touchHandler(event) {
|
||||
var nn = event.target.nodeName.toLowerCase();
|
||||
"use strict";
|
||||
|
||||
const nn = event.target.nodeName.toLowerCase();
|
||||
|
||||
if (nn === "input" || nn === "select" || nn === "button" || nn === "textarea") {
|
||||
return;
|
||||
}
|
||||
|
||||
var touches = event.changedTouches,
|
||||
first = touches ? touches[0] : event,
|
||||
type = "";
|
||||
const touches = event.changedTouches;
|
||||
const first = touches ? touches[0] : event;
|
||||
let type = "";
|
||||
|
||||
switch (event.type) {
|
||||
case "touchstart": type = "mousedown"; break;
|
||||
case "touchmove": type = "mousemove"; break;
|
||||
case "touchend": type = "mouseup"; break;
|
||||
case "tap": type = "click"; break;
|
||||
case "dbltap": type = "dblclick"; break;
|
||||
default: return;
|
||||
case "touchstart":
|
||||
type = "mousedown";
|
||||
break;
|
||||
case "touchmove":
|
||||
type = "mousemove";
|
||||
break;
|
||||
case "touchend":
|
||||
type = "mouseup";
|
||||
break;
|
||||
case "tap":
|
||||
type = "click";
|
||||
break;
|
||||
case "dbltap":
|
||||
type = "dblclick";
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
//initMouseEvent(type, canBubble, cancelable, view, clickCount,
|
||||
// screenX, screenY, clientX, clientY, ctrlKey,
|
||||
// altKey, shiftKey, metaKey, button, relatedTarget);
|
||||
//initMouseEvent(type, canBubble, cancelable, view, clickCount,
|
||||
// screenX, screenY, clientX, clientY, ctrlKey,
|
||||
// altKey, shiftKey, metaKey, button, relatedTarget);
|
||||
|
||||
var simulatedEvent = document.createEvent("MouseEvent");
|
||||
simulatedEvent.initMouseEvent(type, true, true, window, 1,
|
||||
first.screenX, first.screenY,
|
||||
first.clientX, first.clientY, false,
|
||||
false, false, false, 0/*left*/, null);
|
||||
first.target.dispatchEvent(simulatedEvent);
|
||||
event.preventDefault();
|
||||
const simulatedEvent = document.createEvent("MouseEvent");
|
||||
simulatedEvent.initMouseEvent(type, true, true, window, 1,
|
||||
first.screenX, first.screenY,
|
||||
first.clientX, first.clientY, false,
|
||||
false, false, false, 0/*left*/, null);
|
||||
first.target.dispatchEvent(simulatedEvent);
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
document.addEventListener('touchend', (function(speed, distance) {
|
||||
/*
|
||||
* Copyright (c)2012 Stephen M. McKamey.
|
||||
* Licensed under The MIT License.
|
||||
* src: https://raw.github.com/mckamey/doubleTap.js/master/doubleTap.js
|
||||
*/
|
||||
document.addEventListener("touchend", (function (speed, distance) {
|
||||
// Copyright (c)2012 Stephen M. McKamey.
|
||||
// Licensed under The MIT License.
|
||||
// src: https://raw.github.com/mckamey/doubleTap.js/master/doubleTap.js
|
||||
|
||||
"use strict";
|
||||
|
||||
// default dblclick speed to half sec (default for Windows & Mac OS X)
|
||||
speed = Math.abs(+speed) || 500;//ms
|
||||
speed = Math.abs(Number(speed)) || 500;//ms
|
||||
// default dblclick distance to within 40x40 pixel area
|
||||
distance = Math.abs(+distance) || 40;//px
|
||||
distance = Math.abs(Number(distance)) || 40;//px
|
||||
|
||||
// Date.now() polyfill
|
||||
var now = Date.now || function() {
|
||||
return +new Date();
|
||||
const now = Date.now || function () {
|
||||
return Number(new Date());
|
||||
};
|
||||
|
||||
var cancelEvent = function(e) {
|
||||
function cancelEvent(e) {
|
||||
e = (e || window.event);
|
||||
|
||||
if (e) {
|
||||
|
@ -69,24 +81,24 @@ document.addEventListener('touchend', (function(speed, distance) {
|
|||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
var taps = 0,
|
||||
last = 0,
|
||||
// NaN will always test false
|
||||
x = NaN,
|
||||
y = NaN;
|
||||
let taps = 0;
|
||||
let last = 0;
|
||||
// NaN will always test false
|
||||
let x = NaN;
|
||||
let y = NaN;
|
||||
|
||||
return function (e) {
|
||||
e = (e || window.event);
|
||||
|
||||
var time = now(),
|
||||
touch = e.changedTouches ? e.changedTouches[0] : e,
|
||||
nextX = +touch.clientX,
|
||||
nextY = +touch.clientY,
|
||||
target = e.target || e.srcElement,
|
||||
e2,
|
||||
parent;
|
||||
const time = now();
|
||||
const touch = e.changedTouches ? e.changedTouches[0] : e;
|
||||
const nextX = Number(touch.clientX);
|
||||
const nextY = Number(touch.clientY);
|
||||
const target = e.target || e.srcElement;
|
||||
let e2;
|
||||
let parent;
|
||||
|
||||
if ((last + speed) > time &&
|
||||
Math.abs(nextX - x) < distance &&
|
||||
|
@ -106,9 +118,9 @@ document.addEventListener('touchend', (function(speed, distance) {
|
|||
|
||||
// fire tap event
|
||||
if (document.createEvent) {
|
||||
e2 = document.createEvent('MouseEvents');
|
||||
e2 = document.createEvent("MouseEvents");
|
||||
e2.initMouseEvent(
|
||||
'tap',
|
||||
"tap",
|
||||
true, // click bubbles
|
||||
true, // click cancelable
|
||||
e.view, // copy view
|
||||
|
@ -145,18 +157,18 @@ document.addEventListener('touchend', (function(speed, distance) {
|
|||
// DOM Level 0, IE
|
||||
parent.ontap(e);
|
||||
|
||||
} else if (typeof jQuery !== 'undefined') {
|
||||
} else if (typeof jQuery !== "undefined") {
|
||||
// cop out and patch IE6-8 with jQuery
|
||||
jQuery(this).trigger('tap', e);
|
||||
jQuery(this).trigger("tap", e); /* eslint-disable-line no-invalid-this */
|
||||
}
|
||||
}
|
||||
|
||||
if (taps === 2) {
|
||||
// fire dbltap event only for 2nd click
|
||||
if (document.createEvent) {
|
||||
e2 = document.createEvent('MouseEvents');
|
||||
e2 = document.createEvent("MouseEvents");
|
||||
e2.initMouseEvent(
|
||||
'dbltap',
|
||||
"dbltap",
|
||||
true, // dblclick bubbles
|
||||
true, // dblclick cancelable
|
||||
e.view, // copy view
|
||||
|
@ -193,9 +205,9 @@ document.addEventListener('touchend', (function(speed, distance) {
|
|||
// DOM Level 0, IE
|
||||
parent.ondbltap(e);
|
||||
|
||||
} else if (typeof jQuery !== 'undefined') {
|
||||
} else if (typeof jQuery !== "undefined") {
|
||||
// cop out and patch IE6-8 with jQuery
|
||||
jQuery(this).trigger('dbltap', e);
|
||||
jQuery(this).trigger("dbltap", e); /* eslint-disable-line no-invalid-this */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -453,7 +453,7 @@ td.blink {
|
|||
font-size:small
|
||||
}
|
||||
|
||||
#lang-selection {
|
||||
#lang-selection, #board-lang-selection {
|
||||
font-size:small;
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
(function () {
|
||||
"use strict";
|
||||
|
||||
const VERSION = 202005011900;
|
||||
const VERSION = 202005070130;
|
||||
|
||||
const Conf = window.TrivabbleConf || {};
|
||||
|
||||
|
@ -49,8 +49,8 @@
|
|||
|
||||
const trivabble = window.trivabble = {l10n: _};
|
||||
|
||||
function format(s, v) {
|
||||
return s.replace("{0}", v);
|
||||
function format(s, v1, v2) {
|
||||
return s.replace("{0}", v1).replace("{1}", v2);
|
||||
}
|
||||
|
||||
let board;
|
||||
|
@ -58,6 +58,7 @@
|
|||
const boardCells = [];
|
||||
let scoreOf;
|
||||
let bag;
|
||||
let boardLangSelect;
|
||||
|
||||
const playerLetters = [];
|
||||
let currentPlayer = "";
|
||||
|
@ -256,12 +257,10 @@
|
|||
} else if (moveCMD.to === "rack") {
|
||||
moveRack.rack[moveCMD.indexTo] = "";
|
||||
sendCmds([moveRack, moveCMD]);
|
||||
} else if (rackChanged) {
|
||||
sendCmds([moveCMD, moveRack]);
|
||||
} else {
|
||||
if (rackChanged) {
|
||||
sendCmds([moveCMD, moveRack]);
|
||||
} else {
|
||||
sendCmds([moveCMD]);
|
||||
}
|
||||
sendCmds([moveCMD]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -539,6 +538,10 @@
|
|||
case "gameNumber":
|
||||
document.getElementById("number").textContent = localStorage.trivabbleGameNumber = value;
|
||||
break;
|
||||
case "boardLang":
|
||||
localStorage.trivabbleBoardLang = value;
|
||||
document.getElementById("board-lang").value = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -644,24 +647,42 @@
|
|||
return msgDom;
|
||||
}
|
||||
|
||||
function infoMessage(info) {
|
||||
const content = document.createElement("div");
|
||||
content.appendChild(document.createElement("span"));
|
||||
content.lastChild.className = "info";
|
||||
content.lastChild.textContent = info;
|
||||
chatMessage("", content);
|
||||
return content;
|
||||
}
|
||||
|
||||
function handleChatMessage(msg) {
|
||||
if (msg.specialMsg) {
|
||||
if (msg.specialMsg.type === "rack") {
|
||||
const content = document.createElement("div");
|
||||
content.appendChild(document.createElement("span"));
|
||||
content.lastChild.className = "info";
|
||||
content.lastChild.textContent = format(_("{0} shows their rack:"), msg.sender);
|
||||
switch (msg.specialMsg.type) {
|
||||
case "rack": {
|
||||
const content = infoMessage(format(_("{0} shows their rack:"), msg.sender));
|
||||
|
||||
const letters = document.createElement("div");
|
||||
letters.className = "tile-list";
|
||||
const letters = document.createElement("div");
|
||||
letters.className = "tile-list";
|
||||
|
||||
for (let i = 0; i < msg.specialMsg.rack.length; i++) {
|
||||
letters.appendChild(makeLetter(msg.specialMsg.rack[i], false, true));
|
||||
for (let i = 0; i < msg.specialMsg.rack.length; i++) {
|
||||
letters.appendChild(makeLetter(msg.specialMsg.rack[i], false, true));
|
||||
}
|
||||
|
||||
content.appendChild(letters);
|
||||
return;
|
||||
}
|
||||
|
||||
content.appendChild(letters);
|
||||
chatMessage("", content);
|
||||
return;
|
||||
case "changeBoardLang": {
|
||||
const newLang = boardLangSelect.querySelector("[value=" + msg.specialMsg.newBoardLang + "]").textContent;
|
||||
infoMessage(
|
||||
(msg.sender === localStorage.trivabblePlayerName)
|
||||
? format(_("You changed the language of the board to {0}"), newLang)
|
||||
: format(_("{0} changed the language of the board to {1}"), msg.sender, newLang)
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -686,6 +707,167 @@
|
|||
refreshCurrentPlayer();
|
||||
}
|
||||
|
||||
function setPlayers(players) {
|
||||
if (participantPlaceholder) {
|
||||
participantPlaceholder.parentNode.removeChild(participantPlaceholder);
|
||||
participantPlaceholder = null;
|
||||
}
|
||||
|
||||
for (let i = 0; i < players.length; i++) {
|
||||
const player = players[i];
|
||||
const playerName = players[i].player;
|
||||
|
||||
if (!tablePlayers[playerName]) {
|
||||
let before = null;
|
||||
|
||||
for (let j = 1; j < participants.rows.length; j++) {
|
||||
if (playerName < participants.rows[j].cells[0].textContent) {
|
||||
before = participants.rows[j];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const row = document.createElement("tr");
|
||||
participants.insertBefore(row, before);
|
||||
row.appendChild(document.createElement("td"));
|
||||
row.lastChild.textContent = playerName;
|
||||
|
||||
row.appendChild(document.createElement("td"));
|
||||
|
||||
row.appendChild(document.createElement("td"));
|
||||
row.lastChild.className = "score-cell";
|
||||
|
||||
row.lastChild.appendChild(document.createElement("span"));
|
||||
row.lastChild.lastChild.onclick = (
|
||||
function (row) {
|
||||
return function () {
|
||||
if (!pollingReady) {
|
||||
showConnectionLost();
|
||||
return;
|
||||
}
|
||||
|
||||
myPrompt(
|
||||
format(
|
||||
_("Enter the new score of {0}:"),
|
||||
row.firstChild.textContent
|
||||
),
|
||||
function (res) {
|
||||
res = parseInt(res);
|
||||
if (!isNaN(res)) {
|
||||
sendCmds([{
|
||||
cmd: "score",
|
||||
player: row.firstChild.textContent,
|
||||
score: res
|
||||
}]);
|
||||
}
|
||||
},
|
||||
row.lastChild.firstChild.textContent
|
||||
);
|
||||
};
|
||||
}
|
||||
)(row);
|
||||
row.lastChild.appendChild(document.createTextNode(" "));
|
||||
row.lastChild.appendChild(document.createElement("button"));
|
||||
row.lastChild.lastChild.textContent = "+";
|
||||
row.lastChild.lastChild.onclick = (
|
||||
function (row) {
|
||||
return function () {
|
||||
if (!pollingReady) {
|
||||
showConnectionLost();
|
||||
return;
|
||||
}
|
||||
|
||||
myPrompt(
|
||||
format(
|
||||
_("Enter the score to add to {0}:"),
|
||||
row.firstChild.textContent
|
||||
),
|
||||
function (res) {
|
||||
res = parseInt(res);
|
||||
if (!isNaN(res)) {
|
||||
sendCmds([{
|
||||
cmd: "score",
|
||||
player: row.firstChild.textContent,
|
||||
score: parseInt(row.childNodes[2].childNodes[0].textContent) + res
|
||||
}]);
|
||||
}
|
||||
},
|
||||
row.lastChild.firstChild.textContent
|
||||
);
|
||||
};
|
||||
}
|
||||
)(row);
|
||||
|
||||
|
||||
row.appendChild(document.createElement("td"));
|
||||
row.lastChild.className = "turn-cell";
|
||||
row.lastChild.appendChild(document.createElement("button"));
|
||||
const img = new Image();
|
||||
img.src = "baton.svg";
|
||||
row.lastChild.lastChild.appendChild(img);
|
||||
row.lastChild.lastChild.onclick = (function (playerName) {
|
||||
sendCmds([{
|
||||
cmd: "currentPlayer",
|
||||
player: playerName === currentPlayer ? "" : playerName
|
||||
}]);
|
||||
}).bind(null, playerName);
|
||||
|
||||
tablePlayers[playerName] = row;
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(player, "score")) {
|
||||
const scoreCell = tablePlayers[playerName].childNodes[2].childNodes[0];
|
||||
scoreCell.textContent = player.score;
|
||||
blink(scoreCell);
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(player, "rackCount")) {
|
||||
const countCell = tablePlayers[playerName].childNodes[1];
|
||||
countCell.textContent = player.rackCount;
|
||||
blink(countCell);
|
||||
}
|
||||
}
|
||||
|
||||
refreshCurrentPlayer();
|
||||
}
|
||||
|
||||
function applyAction(data) {
|
||||
switch (data.action) {
|
||||
case "pushBag": //TODO
|
||||
break;
|
||||
|
||||
case "popBag": //TODO
|
||||
break;
|
||||
|
||||
case "reset": //TODO
|
||||
tablePlayers = {};
|
||||
while (participants.rows[1]) {
|
||||
participants.removeChild(participants.rows[1]);
|
||||
}
|
||||
sendCmds([{cmd: "hello"}]);
|
||||
break;
|
||||
|
||||
case "moveLetter":
|
||||
if (data.from === "board") {
|
||||
setCell(data.indexFrom, "");
|
||||
} else if (data.from === "rack") {
|
||||
setRackCell(data.indexFrom, "");
|
||||
}
|
||||
|
||||
if (data.to === "board") {
|
||||
setCell(data.indexTo, data.letter, Object.prototype.hasOwnProperty.call(data, "player") && data.player !== localStorage.trivabblePlayerName);
|
||||
} else if (data.to === "rack") {
|
||||
setRackCell(data.indexTo, data.letter);
|
||||
}
|
||||
break;
|
||||
case "setCell":
|
||||
setCell(data.indexTo, data.letter, Object.prototype.hasOwnProperty.call(data, "player") && data.player !== localStorage.trivabblePlayerName);
|
||||
break;
|
||||
case "setRackCell":
|
||||
setRackCell(data.indexTo, data.letter);
|
||||
}
|
||||
}
|
||||
|
||||
function handleReceivedData(data) {
|
||||
if (Array.isArray(data)) {
|
||||
data.forEach(handleReceivedData);
|
||||
|
@ -730,127 +912,7 @@
|
|||
}
|
||||
|
||||
if (data.players) {
|
||||
if (participantPlaceholder) {
|
||||
participantPlaceholder.parentNode.removeChild(participantPlaceholder);
|
||||
participantPlaceholder = null;
|
||||
}
|
||||
|
||||
for (let i = 0; i < data.players.length; i++) {
|
||||
const player = data.players[i];
|
||||
const playerName = data.players[i].player;
|
||||
|
||||
if (!tablePlayers[playerName]) {
|
||||
let before = null;
|
||||
|
||||
for (let j = 1; j < participants.rows.length; j++) {
|
||||
if (playerName < participants.rows[j].cells[0].textContent) {
|
||||
before = participants.rows[j];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const row = document.createElement("tr");
|
||||
participants.insertBefore(row, before);
|
||||
row.appendChild(document.createElement("td"));
|
||||
row.lastChild.textContent = playerName;
|
||||
|
||||
row.appendChild(document.createElement("td"));
|
||||
|
||||
row.appendChild(document.createElement("td"));
|
||||
row.lastChild.className = "score-cell";
|
||||
|
||||
row.lastChild.appendChild(document.createElement("span"));
|
||||
row.lastChild.lastChild.onclick = (
|
||||
function (row) {
|
||||
return function () {
|
||||
if (!pollingReady) {
|
||||
showConnectionLost();
|
||||
return;
|
||||
}
|
||||
|
||||
myPrompt(
|
||||
format(
|
||||
_("Enter the new score of {0}:"),
|
||||
row.firstChild.textContent
|
||||
),
|
||||
function (res) {
|
||||
res = parseInt(res);
|
||||
if (!isNaN(res)) {
|
||||
sendCmds([{
|
||||
cmd: "score",
|
||||
player: row.firstChild.textContent,
|
||||
score: res
|
||||
}]);
|
||||
}
|
||||
},
|
||||
row.lastChild.firstChild.textContent
|
||||
);
|
||||
};
|
||||
}
|
||||
)(row);
|
||||
row.lastChild.appendChild(document.createTextNode(" "));
|
||||
row.lastChild.appendChild(document.createElement("button"));
|
||||
row.lastChild.lastChild.textContent = "+";
|
||||
row.lastChild.lastChild.onclick = (
|
||||
function (row) {
|
||||
return function() {
|
||||
if (!pollingReady) {
|
||||
showConnectionLost();
|
||||
return;
|
||||
}
|
||||
|
||||
myPrompt(
|
||||
format(
|
||||
_("Enter the score to add to {0}:"),
|
||||
row.firstChild.textContent
|
||||
),
|
||||
function (res) {
|
||||
res = parseInt(res);
|
||||
if (!isNaN(res)) {
|
||||
sendCmds([{
|
||||
cmd: "score",
|
||||
player: row.firstChild.textContent,
|
||||
score: parseInt(row.childNodes[2].childNodes[0].textContent) + res
|
||||
}]);
|
||||
}
|
||||
},
|
||||
row.lastChild.firstChild.textContent
|
||||
);
|
||||
};
|
||||
}
|
||||
)(row);
|
||||
|
||||
|
||||
row.appendChild(document.createElement("td"));
|
||||
row.lastChild.className = "turn-cell";
|
||||
row.lastChild.appendChild(document.createElement("button"));
|
||||
const img = new Image();
|
||||
img.src = "baton.svg";
|
||||
row.lastChild.lastChild.appendChild(img);
|
||||
row.lastChild.lastChild.onclick = (function (playerName) {
|
||||
sendCmds([{
|
||||
cmd: "currentPlayer",
|
||||
player: playerName === currentPlayer ? "" : playerName
|
||||
}]);
|
||||
}).bind(null, playerName);
|
||||
|
||||
tablePlayers[playerName] = row;
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(player, "score")) {
|
||||
const scoreCell = tablePlayers[playerName].childNodes[2].childNodes[0];
|
||||
scoreCell.textContent = player.score;
|
||||
blink(scoreCell);
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(player, "rackCount")) {
|
||||
const countCell = tablePlayers[playerName].childNodes[1];
|
||||
countCell.textContent = player.rackCount;
|
||||
blink(countCell);
|
||||
}
|
||||
}
|
||||
|
||||
refreshCurrentPlayer();
|
||||
setPlayers(data.players);
|
||||
}
|
||||
|
||||
if (data.playerName) {
|
||||
|
@ -861,6 +923,14 @@
|
|||
set("gameNumber", data.gameNumber);
|
||||
}
|
||||
|
||||
if (data.availableBoardLangs) {
|
||||
setAvailableBoardLangs(data.availableBoardLangs);
|
||||
}
|
||||
|
||||
if (data.boardLang) {
|
||||
set("boardLang", data.boardLang);
|
||||
(boardLangSelect || {}).value = data.boardLang;
|
||||
}
|
||||
|
||||
if (data.letterValues) {
|
||||
scoreOf = data.letterValues;
|
||||
|
@ -886,43 +956,8 @@
|
|||
setRack(data.rack);
|
||||
}
|
||||
|
||||
if (!data.action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (data.action) {
|
||||
case "pushBag": //TODO
|
||||
break;
|
||||
|
||||
case "popBag": //TODO
|
||||
break;
|
||||
|
||||
case "reset": //TODO
|
||||
tablePlayers = {};
|
||||
while (participants.rows[1]) {
|
||||
participants.removeChild(participants.rows[1]);
|
||||
}
|
||||
sendCmds([{cmd: "hello"}]);
|
||||
break;
|
||||
|
||||
case "moveLetter":
|
||||
if (data.from === "board") {
|
||||
setCell(data.indexFrom, "");
|
||||
} else if (data.from === "rack") {
|
||||
setRackCell(data.indexFrom, "");
|
||||
}
|
||||
|
||||
if (data.to === "board") {
|
||||
setCell(data.indexTo, data.letter, Object.prototype.hasOwnProperty.call(data, "player") && data.player !== localStorage.trivabblePlayerName);
|
||||
} else if (data.to === "rack") {
|
||||
setRackCell(data.indexTo, data.letter);
|
||||
}
|
||||
break;
|
||||
case "setCell":
|
||||
setCell(data.indexTo, data.letter, Object.prototype.hasOwnProperty.call(data, "player") && data.player !== localStorage.trivabblePlayerName);
|
||||
break;
|
||||
case "setRackCell":
|
||||
setRackCell(data.indexTo, data.letter);
|
||||
if (data.action) {
|
||||
applyAction(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1191,6 +1226,7 @@
|
|||
return {
|
||||
gameNumber: localStorage.trivabbleGameNumber || "",
|
||||
playerName: localStorage.trivabblePlayerName,
|
||||
boardLang: localStorage.trivabbleBoardLang,
|
||||
version: VERSION,
|
||||
cmds: cmds
|
||||
};
|
||||
|
@ -1327,6 +1363,21 @@
|
|||
);
|
||||
}
|
||||
|
||||
function onChangeBoardLang() {
|
||||
const code = document.getElementById("board-lang").value;
|
||||
const lang = boardLangSelect.selectedOptions[0].textContent;
|
||||
|
||||
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}]);
|
||||
},
|
||||
function () {
|
||||
boardLangSelect.value = localStorage.trivabbleBoardLang;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function clearRack() {
|
||||
myConfirm(
|
||||
_("Are you sure you want to put all your tiles back in the bag?"),
|
||||
|
@ -1496,6 +1547,34 @@
|
|||
trivabble.run();
|
||||
};
|
||||
|
||||
function setAvailableBoardLangs(availableBoardLangs) {
|
||||
if (!boardLangSelect) {
|
||||
const boardLangSelection = document.getElementById("board-lang-selection");
|
||||
boardLangSelect = boardLangSelection.querySelector("select");
|
||||
boardLangSelection.style.display = "";
|
||||
}
|
||||
|
||||
boardLangSelect.textContent = "";
|
||||
|
||||
for (const key of Object.keys(availableBoardLangs)) {
|
||||
boardLangSelect.add(new Option(_(availableBoardLangs[key]), key));
|
||||
}
|
||||
|
||||
Array.prototype.sort.call(boardLangSelect.options, function (a, b) {
|
||||
return (
|
||||
(a.textContent > b.textContent)
|
||||
? 1
|
||||
: (
|
||||
(b.textContent > a.textContent)
|
||||
? -1
|
||||
: 0
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
boardLangSelect.value = localStorage.trivabbleBoardLang;
|
||||
}
|
||||
|
||||
function langSelectionChange(e) {
|
||||
localStorage.trivabbleLang = e.target.value;
|
||||
location.reload();
|
||||
|
@ -1518,6 +1597,7 @@
|
|||
mouseDown(bag, bagClicked);
|
||||
|
||||
document.getElementById("clear-game").onclick = clearGame;
|
||||
document.getElementById("board-lang").onchange = onChangeBoardLang;
|
||||
document.getElementById("change-name").onclick = changeName;
|
||||
document.getElementById("join-game").onclick = joinGame;
|
||||
document.getElementById("clear-rack").onclick = clearRack;
|
||||
|
@ -1592,7 +1672,7 @@
|
|||
specialCell("doubleWord", board.lastChild.lastChild);
|
||||
} else if ((i % 4 === 1) && (j % 4 === 1)) {
|
||||
specialCell("tripleLetter", board.lastChild.lastChild);
|
||||
} else if ((i < 8 && doubleLetter[i + "," + j]) || (i > 7 && doubleLetter[(14-i) + "," + j]) || (i === 7 && (j === 3 || j === 11))) {
|
||||
} else if ((i < 8 && doubleLetter[i + "," + j]) || (i > 7 && doubleLetter[(14 - i) + "," + j]) || (i === 7 && (j === 3 || j === 11))) {
|
||||
specialCell("doubleLetter", board.lastChild.lastChild);
|
||||
}
|
||||
}
|
||||
|
@ -1617,7 +1697,11 @@
|
|||
document.getElementById("number").textContent = localStorage.trivabbleGameNumber;
|
||||
}
|
||||
|
||||
startGame(localStorage.trivabbleGameNumber);
|
||||
if (localStorage.trivabbleBoardLang) {
|
||||
document.getElementById("board-lang").value = localStorage.trivabbleBoardLang;
|
||||
}
|
||||
|
||||
startGame(localStorage.trivabbleGameNumber, localStorage.trivabbleBoardLang);
|
||||
}
|
||||
|
||||
function initLang() {
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
{
|
||||
"code": "de",
|
||||
"name": "German",
|
||||
|
||||
"bag": [
|
||||
" ", " ",
|
||||
"E", "E", "E", "E", "E", "E", "E", "E", "E", "E", "E", "E", "E", "E", "E",
|
||||
"N", "N", "N", "N", "N", "N", "N", "N", "N",
|
||||
"S", "S", "S", "S", "S", "S", "S",
|
||||
"I", "I", "I", "I", "I", "I",
|
||||
"R", "R", "R", "R", "R", "R",
|
||||
"T", "T", "T", "T", "T", "T",
|
||||
"U", "U", "U", "U", "U", "U",
|
||||
"A", "A", "A", "A", "A",
|
||||
"D", "D", "D", "D",
|
||||
"H", "H", "H", "H",
|
||||
"G", "G", "G",
|
||||
"L", "L", "L",
|
||||
"O", "O", "O",
|
||||
"M", "M", "M", "M",
|
||||
"B", "B",
|
||||
"W",
|
||||
"Z",
|
||||
"C", "C",
|
||||
"F", "F",
|
||||
"K", "K",
|
||||
"P",
|
||||
"Ä",
|
||||
"J",
|
||||
"Ü",
|
||||
"V",
|
||||
"Ö",
|
||||
"X",
|
||||
"Q",
|
||||
"Y"
|
||||
],
|
||||
|
||||
"letterValues": {
|
||||
" ": 0,
|
||||
"E": 1,
|
||||
"N": 1,
|
||||
"S": 1,
|
||||
"I": 1,
|
||||
"R": 1,
|
||||
"T": 1,
|
||||
"U": 1,
|
||||
"A": 1,
|
||||
"D": 1,
|
||||
"H": 2,
|
||||
"G": 2,
|
||||
"L": 2,
|
||||
"O": 2,
|
||||
"M": 3,
|
||||
"B": 3,
|
||||
"W": 3,
|
||||
"Z": 3,
|
||||
"C": 4,
|
||||
"F": 4,
|
||||
"K": 4,
|
||||
"P": 4,
|
||||
"Ä": 6,
|
||||
"J": 6,
|
||||
"Ü": 6,
|
||||
"V": 6,
|
||||
"Ö": 8,
|
||||
"X": 8,
|
||||
"Q": 10,
|
||||
"Y": 10
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"code": "en",
|
||||
"name": "English",
|
||||
|
||||
"bag": [
|
||||
" ", " ",
|
||||
"E", "E", "E", "E", "E", "E", "E", "E", "E", "E", "E", "E",
|
||||
"A", "A", "A", "A", "A", "A", "A", "A", "A",
|
||||
"I", "I", "I", "I", "I", "I", "I", "I", "I",
|
||||
"O", "O", "O", "O", "O", "O", "O", "O",
|
||||
"R", "R", "R", "R", "R", "R",
|
||||
"N", "N", "N", "N", "N", "N",
|
||||
"T", "T", "T", "T", "T", "T",
|
||||
"L", "L", "L", "L",
|
||||
"S", "S", "S", "S",
|
||||
"U", "U", "U", "U",
|
||||
"D", "D", "D", "D",
|
||||
"G", "G", "G",
|
||||
"B", "B",
|
||||
"C", "C",
|
||||
"M", "M",
|
||||
"P", "P",
|
||||
"F", "F",
|
||||
"H", "H",
|
||||
"V", "V",
|
||||
"W", "W",
|
||||
"Y", "Y",
|
||||
"K",
|
||||
"J",
|
||||
"X",
|
||||
"Q",
|
||||
"Z"
|
||||
],
|
||||
|
||||
"letterValues": {
|
||||
" ": 0,
|
||||
"E": 1,
|
||||
"A": 1,
|
||||
"I": 1,
|
||||
"O": 1,
|
||||
"R": 1,
|
||||
"N": 1,
|
||||
"T": 1,
|
||||
"L": 1,
|
||||
"S": 1,
|
||||
"U": 1,
|
||||
"D": 2,
|
||||
"G": 2,
|
||||
"B": 3,
|
||||
"C": 3,
|
||||
"M": 3,
|
||||
"P": 3,
|
||||
"F": 4,
|
||||
"H": 4,
|
||||
"V": 4,
|
||||
"W": 4,
|
||||
"Y": 4,
|
||||
"K": 5,
|
||||
"J": 8,
|
||||
"X": 8,
|
||||
"Q": 10,
|
||||
"Z": 10
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
{
|
||||
"code": "es",
|
||||
"name": "Spanish",
|
||||
|
||||
"bag": [
|
||||
" ", " ",
|
||||
"A", "A", "A", "A", "A", "A", "A", "A", "A", "A", "A", "A",
|
||||
"E", "E", "E", "E", "E", "E", "E", "E", "E", "E", "E", "E",
|
||||
"O", "O", "O", "O", "O", "O", "O", "O", "O",
|
||||
"I", "I", "I", "I", "I", "I",
|
||||
"S", "S", "S", "S", "S", "S",
|
||||
"N", "N", "N", "N", "N",
|
||||
"R", "R", "R", "R", "R",
|
||||
"U", "U", "U", "U", "U",
|
||||
"L", "L", "L", "L",
|
||||
"T", "T", "T", "T",
|
||||
"D", "D", "D", "D", "D",
|
||||
"G", "G",
|
||||
"C", "C", "C", "C",
|
||||
"B", "B",
|
||||
"M", "M",
|
||||
"P", "P",
|
||||
"H", "H",
|
||||
"F",
|
||||
"V",
|
||||
"Y",
|
||||
"CH",
|
||||
"Q",
|
||||
"J",
|
||||
"LL",
|
||||
"Ñ",
|
||||
"RR",
|
||||
"X",
|
||||
"Z"
|
||||
],
|
||||
|
||||
"letterValues": {
|
||||
" ": 0,
|
||||
"A": 1,
|
||||
"E": 1,
|
||||
"O": 1,
|
||||
"I": 1,
|
||||
"S": 1,
|
||||
"N": 1,
|
||||
"R": 1,
|
||||
"U": 1,
|
||||
"L": 1,
|
||||
"T": 1,
|
||||
"D": 2,
|
||||
"G": 2,
|
||||
"C": 3,
|
||||
"B": 3,
|
||||
"M": 3,
|
||||
"P": 3,
|
||||
"H": 4,
|
||||
"F": 4,
|
||||
"V": 4,
|
||||
"Y": 4,
|
||||
"CH": 5,
|
||||
"Q": 5,
|
||||
"J": 8,
|
||||
"LL": 8,
|
||||
"Ñ": 8,
|
||||
"RR": 8,
|
||||
"X": 8,
|
||||
"Z": 10
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"code": "fr",
|
||||
"name": "French",
|
||||
|
||||
"bag": [
|
||||
" ", " ",
|
||||
"E", "E", "E", "E", "E", "E", "E", "E", "E", "E", "E", "E", "E", "E", "E",
|
||||
"A", "A", "A", "A", "A", "A", "A", "A", "A",
|
||||
"I", "I", "I", "I", "I", "I", "I", "I",
|
||||
"N", "N", "N", "N", "N", "N",
|
||||
"O", "O", "O", "O", "O", "O",
|
||||
"R", "R", "R", "R", "R", "R",
|
||||
"S", "S", "S", "S", "S", "S",
|
||||
"T", "T", "T", "T", "T", "T",
|
||||
"U", "U", "U", "U", "U", "U",
|
||||
"L", "L", "L", "L", "L",
|
||||
"D", "D", "D",
|
||||
"M", "M", "M",
|
||||
"G", "G",
|
||||
"B", "B",
|
||||
"C", "C",
|
||||
"P", "P",
|
||||
"F", "F",
|
||||
"H", "H",
|
||||
"V", "V",
|
||||
"J",
|
||||
"Q",
|
||||
"K",
|
||||
"W",
|
||||
"X",
|
||||
"Y",
|
||||
"Z"
|
||||
],
|
||||
|
||||
"letterValues": {
|
||||
" ": 0,
|
||||
"E": 1,
|
||||
"A": 1,
|
||||
"I": 1,
|
||||
"N": 1,
|
||||
"O": 1,
|
||||
"R": 1,
|
||||
"S": 1,
|
||||
"T": 1,
|
||||
"U": 1,
|
||||
"L": 1,
|
||||
"D": 2,
|
||||
"M": 2,
|
||||
"G": 2,
|
||||
"B": 3,
|
||||
"C": 3,
|
||||
"P": 3,
|
||||
"F": 4,
|
||||
"H": 4,
|
||||
"V": 4,
|
||||
"J": 8,
|
||||
"Q": 8,
|
||||
"K": 10,
|
||||
"W": 10,
|
||||
"X": 10,
|
||||
"Y": 10,
|
||||
"Z": 10
|
||||
}
|
||||
}
|
|
@ -33,8 +33,9 @@ const host = process.env.TRIVABBLE_HOST || "localhost"
|
|||
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 VERSION = 202004281800;
|
||||
const VERSION = 202005070100;
|
||||
|
||||
function envTrue(name) {
|
||||
return (process.env[name] || "").toLowerCase() === "true";
|
||||
|
@ -49,77 +50,26 @@ if (DEV_ENABLE_SERVING_FILES) {
|
|||
|
||||
const debuglog = DEBUG_LOG ? console.log.bind(console) : () => null;
|
||||
|
||||
const http = require("http");
|
||||
const fs = require("fs");
|
||||
const http = require("http");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const crypto = require("crypto");
|
||||
|
||||
const REQUEST_TYPE_LONG_POLLING = 1;
|
||||
const REQUEST_TYPE_SSE = 2;
|
||||
const REQUEST_TYPE_WEBSOCKET = 3;
|
||||
|
||||
const frBag = [
|
||||
" ", " ", // Jokers
|
||||
"E", "E", "E", "E", "E", "E", "E", "E", "E", "E", "E", "E", "E", "E", "E",
|
||||
"A", "A", "A", "A", "A", "A", "A", "A", "A",
|
||||
"I", "I", "I", "I", "I", "I", "I", "I",
|
||||
"N", "N", "N", "N", "N", "N",
|
||||
"O", "O", "O", "O", "O", "O",
|
||||
"R", "R", "R", "R", "R", "R",
|
||||
"S", "S", "S", "S", "S", "S",
|
||||
"T", "T", "T", "T", "T", "T",
|
||||
"U", "U", "U", "U", "U", "U",
|
||||
"L", "L", "L", "L", "L",
|
||||
"D", "D", "D",
|
||||
"M", "M", "M",
|
||||
"G", "G",
|
||||
"B", "B",
|
||||
"C", "C",
|
||||
"P", "P",
|
||||
"F", "F",
|
||||
"H", "H",
|
||||
"V", "V",
|
||||
"J",
|
||||
"Q",
|
||||
"K",
|
||||
"W",
|
||||
"X",
|
||||
"Y",
|
||||
"Z"
|
||||
];
|
||||
/* eslint no-sync: ["error", { allowAtRootLevel: true }] */
|
||||
|
||||
/* eslint-disable quote-props */
|
||||
/* Manage multi language board */
|
||||
const boardTilesPerLang = {};
|
||||
const availableBoardLangs = {};
|
||||
|
||||
const frValues = {
|
||||
" ": 0,
|
||||
"E": 1,
|
||||
"A": 1,
|
||||
"I": 1,
|
||||
"N": 1,
|
||||
"O": 1,
|
||||
"R": 1,
|
||||
"S": 1,
|
||||
"T": 1,
|
||||
"U": 1,
|
||||
"L": 1,
|
||||
"D": 2,
|
||||
"M": 2,
|
||||
"G": 2,
|
||||
"B": 3,
|
||||
"C": 3,
|
||||
"P": 3,
|
||||
"F": 4,
|
||||
"H": 4,
|
||||
"V": 4,
|
||||
"J": 8,
|
||||
"Q": 8,
|
||||
"K": 10,
|
||||
"W": 10,
|
||||
"X": 10,
|
||||
"Y": 10,
|
||||
"Z": 10
|
||||
};
|
||||
|
||||
/* eslint-enable quote-props */
|
||||
for (const lang of fs.readdirSync(path.join(__dirname, "lang"))) {
|
||||
const data = require(path.join(__dirname, "lang", lang)); // eslint-disable-line global-require
|
||||
boardTilesPerLang[data.code] = data;
|
||||
availableBoardLangs[data.code] = data.name;
|
||||
}
|
||||
|
||||
const games = {};
|
||||
|
||||
|
@ -155,7 +105,6 @@ function shuffleInPlace(a) {
|
|||
}
|
||||
|
||||
function Game() {
|
||||
this.letterValues = frValues;
|
||||
this.init();
|
||||
this.listeningPlayers = [];
|
||||
this.pendingEvents = [];
|
||||
|
@ -264,9 +213,11 @@ function newBoard() {
|
|||
return res;
|
||||
}
|
||||
|
||||
Game.prototype.init = function () {
|
||||
Game.prototype.init = function (lang) {
|
||||
this.board = newBoard();
|
||||
this.bag = frBag.slice();
|
||||
this.lang = lang || DEFAULT_BOARD_LANG;
|
||||
this.bag = boardTilesPerLang[this.lang].bag.slice();
|
||||
this.letterValues = boardTilesPerLang[this.lang].letterValues;
|
||||
this.racks = {};
|
||||
this.scores = {};
|
||||
this.lastUpdated = new Date();
|
||||
|
@ -278,7 +229,9 @@ Game.prototype.init = function () {
|
|||
Game.prototype.toJSON = function () {
|
||||
return {
|
||||
board: this.board,
|
||||
lang: this.lang,
|
||||
bag: this.bag,
|
||||
letterValues: this.letterValues,
|
||||
racks: this.racks,
|
||||
scores: this.scores,
|
||||
lastUpdated: this.lastUpdated.toISOString(),
|
||||
|
@ -289,7 +242,9 @@ Game.prototype.toJSON = function () {
|
|||
Game.fromJSON = function (obj) {
|
||||
const game = new Game();
|
||||
game.board = obj.board || newBoard();
|
||||
game.bag = obj.bag || frBag.slice();
|
||||
game.lang = obj.lang || DEFAULT_BOARD_LANG;
|
||||
game.bag = boardTilesPerLang[game.lang].bag.slice();
|
||||
game.letterValues = boardTilesPerLang[game.lang].letterValues;
|
||||
game.racks = obj.racks || {};
|
||||
game.scores = obj.scores || {};
|
||||
game.lastUpdated = obj.lastUpdated ? new Date(obj.lastUpdated) : new Date();
|
||||
|
@ -336,15 +291,13 @@ Game.prototype.playerJoined = function (playerName) {
|
|||
|
||||
const players = [];
|
||||
|
||||
for (let player in this.racks) {
|
||||
if (Object.prototype.hasOwnProperty.call(this.racks, player)) {
|
||||
player = player.slice(1); // '#'
|
||||
players.push({
|
||||
player: player,
|
||||
score: this.getPlayerScore(player),
|
||||
rackCount: countTiles(this.getPlayerRack(player))
|
||||
});
|
||||
}
|
||||
for (let player of Object.keys(this.racks)) {
|
||||
player = player.slice(1); // '#'
|
||||
players.push({
|
||||
player: player,
|
||||
score: this.getPlayerScore(player),
|
||||
rackCount: countTiles(this.getPlayerRack(player))
|
||||
});
|
||||
}
|
||||
|
||||
this.pendingEvents.push({players: players});
|
||||
|
@ -358,7 +311,7 @@ Game.prototype.addListeningPlayer = function (playerName, responseAndType) {
|
|||
|
||||
let closed = false;
|
||||
|
||||
function close () {
|
||||
function close() {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
|
@ -446,7 +399,7 @@ Game.prototype.bagPushLetter = function (letter, player) {
|
|||
};
|
||||
|
||||
Game.prototype.reset = function (player) {
|
||||
this.init();
|
||||
this.init(this.lang);
|
||||
this.pendingEvents.push({
|
||||
player: player,
|
||||
action: "reset",
|
||||
|
@ -523,11 +476,13 @@ function handleCommand(cmdNumber, message, response) {
|
|||
error: 0,
|
||||
gameNumber: gameNumber,
|
||||
playerName: playerName,
|
||||
boardLang: game.lang,
|
||||
availableBoardLangs: availableBoardLangs,
|
||||
currentPlayer: game.currentPlayer,
|
||||
rack: game.getPlayerRack(playerName),
|
||||
board: game.board,
|
||||
remainingLetters: game.bag.length,
|
||||
letterValues: frValues,
|
||||
letterletterValues: game.letterValues,
|
||||
version: VERSION
|
||||
});
|
||||
break;
|
||||
|
@ -535,7 +490,7 @@ function handleCommand(cmdNumber, message, response) {
|
|||
|
||||
case "hello": {
|
||||
game.playerJoined(playerName);
|
||||
reply(message, response, cmdNumber, {error: 0, version: VERSION});
|
||||
reply(message, response, cmdNumber, {error: 0, boardLang: game.lang, version: VERSION});
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -712,6 +667,31 @@ function handleCommand(cmdNumber, message, response) {
|
|||
break;
|
||||
}
|
||||
|
||||
case "changeBoard": {
|
||||
game.lang = cmd.lang || DEFAULT_BOARD_LANG;
|
||||
game.reset();
|
||||
|
||||
reply(message, response, cmdNumber, {
|
||||
error: 0,
|
||||
boardLang: game.lang,
|
||||
letterValues: game.letterValues
|
||||
});
|
||||
|
||||
game.pendingEvents.push({
|
||||
msg: {
|
||||
sender: playerName,
|
||||
content: "I changed the language of the board to " + game.lang,
|
||||
specialMsg: {
|
||||
type: "changeBoardLang",
|
||||
newBoardLang: game.lang
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case "msg": {
|
||||
game.pendingEvents.push({
|
||||
msg: {
|
||||
|
@ -740,14 +720,16 @@ function handleCommands(message, responseAndType) {
|
|||
|
||||
writeMessage(responseAndType,
|
||||
JSON.stringify({
|
||||
playerName: message.playerName,
|
||||
currentPlayer: game.currentPlayer,
|
||||
gameNumber: gameNumber,
|
||||
letterValues: game.letterValues,
|
||||
rack: game.getPlayerRack(message.playerName),
|
||||
board: game.board,
|
||||
remainingLetters: game.bag.length,
|
||||
version: VERSION
|
||||
playerName: message.playerName,
|
||||
currentPlayer: game.currentPlayer,
|
||||
gameNumber: gameNumber,
|
||||
boardLang: game.lang,
|
||||
availableBoardLangs: availableBoardLangs,
|
||||
letterValues: game.letterValues,
|
||||
rack: game.getPlayerRack(message.playerName),
|
||||
board: game.board,
|
||||
remainingLetters: game.bag.length,
|
||||
version: VERSION
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -980,9 +962,10 @@ function handleRequest(request, response) {
|
|||
|
||||
debuglog("Serving " + request.url);
|
||||
|
||||
fs.exists("../public/" + request.url, function (exists) {
|
||||
const requestedPath = path.join(__dirname, "..", "public", request.url);
|
||||
fs.exists(requestedPath, function (exists) {
|
||||
if (exists) {
|
||||
fs.readFile("../public/" + request.url, function(err, contents) {
|
||||
fs.readFile(requestedPath, function (err, contents) {
|
||||
if (err) {
|
||||
response.statusCode = 500;
|
||||
response.setHeader("Content-Type", "text/plain; charset=utf-8");
|
||||
|
@ -1056,10 +1039,8 @@ fs.readFile(GAMES_BACKUP, function (err, data) {
|
|||
} else {
|
||||
const backup = JSON.parse(data);
|
||||
|
||||
for (const gameNumber in backup) {
|
||||
if (Object.prototype.hasOwnProperty.call(backup, gameNumber)) {
|
||||
games[gameNumber] = Game.fromJSON(backup[gameNumber]);
|
||||
}
|
||||
for (const gameNumber of Object.keys(backup)) {
|
||||
games[gameNumber] = Game.fromJSON(backup[gameNumber]);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
Loading…
Reference in New Issue