trivabble/trivabble.js

1309 lines
43 KiB
JavaScript
Raw Normal View History

2016-04-03 17:36:33 +02:00
/**
2020-04-02 19:07:03 +02:00
* Copyright (C) 2016-2020 Raphaël Jakse <raphael.trivabble@jakse.fr>
2016-04-03 17:36:33 +02:00
*
* @licstart
* This file is part of Trivabble.
*
* Trivabble is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License (GNU AGPL)
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* Trivabble is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Trivabble. If not, see <http://www.gnu.org/licenses/>.
* @licend
*
* @source: https://trivabble.1s.fr/
* @source: https://gitlab.com/raphj/trivabble/
*/
2020-04-04 16:27:18 +02:00
/*global libD, myConfirm, myAlert, myPrompt*/
2016-02-28 20:23:41 +01:00
(function () {
"use strict";
2020-04-05 15:49:48 +02:00
const VERSION = 202004041733;
const POLLING_DELAY = 2000;
2016-02-28 20:23:41 +01:00
2020-04-05 15:49:48 +02:00
const _ = (window.libD && libD.l10n) ? libD.l10n() : function (s) {return s;};
2016-02-28 20:23:41 +01:00
2020-04-05 15:49:48 +02:00
const trivabble = window.trivabble = {l10n: _};
2016-02-28 20:23:41 +01:00
function format(s, v) {
return s.replace("{0}", v);
}
2020-04-05 15:49:48 +02:00
let board;
let rack;
const boardCells = [];
let scoreOf;
let bag;
2016-02-28 20:23:41 +01:00
2020-04-05 15:49:48 +02:00
const playerLetters = [];
2016-02-28 20:23:41 +01:00
2020-04-05 15:49:48 +02:00
let audioNotification;
let audioChat;
let chatMessages;
2020-04-12 20:14:14 +02:00
let chatTextarea;
2020-04-05 15:49:48 +02:00
let helpBag;
let helpClear;
2016-02-28 20:23:41 +01:00
2020-04-05 15:49:48 +02:00
let tablePlayers = {};
let participantPlaceholder;
let participants;
let name;
2016-02-28 20:23:41 +01:00
2020-04-05 15:49:48 +02:00
let lastDate = 0;
2016-02-28 20:23:41 +01:00
2020-04-05 15:49:48 +02:00
let blockMove = 0;
let needsRestart = false;
const gameInProgress = false;
2016-02-28 20:23:41 +01:00
2020-04-05 15:49:48 +02:00
let eventSource = null;
let blockEventSourceAutoRetry = false;
let connectionLostMessage = null;
let boundEventShowConnectionLost = false;
2016-02-28 20:23:41 +01:00
function mouseDown(ele, fun, stop) {
2020-04-05 15:49:48 +02:00
const meth = stop ? "removeEventListener" : "addEventListener";
2016-02-28 20:23:41 +01:00
ele[meth]("mousedown", fun, false);
}
function mouseUp(ele, fun, stop) {
2020-04-05 15:49:48 +02:00
const meth = stop ? "removeEventListener" : "addEventListener";
2016-02-28 20:23:41 +01:00
ele[meth]("mouseup", fun, false);
}
function mouseMove(ele, fun, stop) {
2020-04-05 15:49:48 +02:00
const meth = stop ? "removeEventListener" : "addEventListener";
2016-02-28 20:23:41 +01:00
ele[meth]("mousemove", fun, false);
}
function setRack(rack) {
2020-04-05 15:49:48 +02:00
for (let i = 0; i < 7; i++) {
2016-02-28 20:23:41 +01:00
setTileParent(playerLetters[i], rack[i] || "");
}
}
function getFreeRackSpaceIndex() {
2020-04-05 15:49:48 +02:00
for (let i = 0; i < 7; i++) {
2016-02-28 20:23:41 +01:00
if (!playerLetters[i].getElementsByClassName("tile")[0]) {
return i;
}
}
return -1;
}
function showConnectionLost() {
unbindEventsShowConnectionLost();
if (!connectionLostMessage) {
2020-04-05 15:49:48 +02:00
connectionLostMessage = chatMessage("", _("Whoops, there is a problem. We are trying to fix it as soon as possible. Please wait a few seconds. If it persists, please contact the person who is able to fix the problem."));
connectionLostMessage.classList.add("error");
}
}
function unbindEventsShowConnectionLost() {
if (boundEventShowConnectionLost) {
boundEventShowConnectionLost = false;
document.body.removeEventListener("mousemove", showConnectionLost);
document.body.removeEventListener("mousedown", showConnectionLost);
document.body.removeEventListener("touchstart", showConnectionLost);
}
}
function removeElem(elem) {
elem.parentNode.removeChild(elem);
}
function connectionReady() {
pollingReady = true;
blockEventSourceAutoRetry = false;
unbindEventsShowConnectionLost();
if (connectionLostMessage) {
connectionLostMessage.querySelector(".msg-content").textContent = _("The problem is solved, sorry for the inconvenience!");
connectionLostMessage.classList.replace("error", "ok");
const removeMessage = removeElem.bind(null, connectionLostMessage);
connectionLostMessage.addEventListener("click", removeMessage);
connectionLostMessage.addEventListener("touch", removeMessage);
connectionLostMessage = null;
}
}
2020-04-05 15:49:48 +02:00
let tileInitCoords = null;
let tileInitMouseCoords = null;
let tileDest = null;
let tileInitDest = null;
let movingTile = null;
let rackBCR = null;
let boardBCR = null;
let bagBCR = null;
let moveCMD = null;
2016-02-28 20:23:41 +01:00
function getLetter(l) {
2020-04-05 15:49:48 +02:00
const tile = l.getElementsByClassName("tile")[0];
2016-02-28 20:23:41 +01:00
if (!tile) {
return "";
}
return tile.getElementsByClassName("tile-letter")[0].textContent;
}
function isParentOf(p, elem) {
while (elem) {
if (elem === p) {
return true;
}
elem = elem.parentNode;
}
return false;
}
2020-04-04 16:27:18 +02:00
function dragTileEnd() {
2016-02-28 20:23:41 +01:00
movingTile.style.left = "";
movingTile.style.top = "";
movingTile.style.width = "";
movingTile.style.height = "";
mouseUp(document, dragTileEnd, true);
mouseMove(document, dragTileMove, true);
if (tileDest === bag) {
moveCMD.to = "bag";
moveCMD.indexTo = -1;
movingTile.parentNode.removeChild(movingTile);
} else if (tileDest) {
tileDest.appendChild(movingTile);
if (isParentOf(board, tileDest)) {
for (const tile of [].slice.call(board.getElementsByClassName("tile-highlight"))) {
2020-04-05 15:49:48 +02:00
tile.classList.remove("tile-highlight");
2016-02-28 20:23:41 +01:00
}
}
} else if (tileInitDest.getElementsByClassName("tile")[0]) {
2020-04-05 15:49:48 +02:00
for (let i = 0; i < 7; i++) {
2016-02-28 20:23:41 +01:00
if (!playerLetters[i].getElementsByClassName("tile")[0]) {
playerLetters[i].appendChild(movingTile);
break;
}
}
} else {
tileInitDest.appendChild(movingTile);
}
if (tileDest) {
tileDest.classList.remove("tile-target");
2020-04-05 15:49:48 +02:00
const moveRack = {
2016-02-28 20:23:41 +01:00
cmd: "setRack",
rack: [
getLetter(playerLetters[0]),
getLetter(playerLetters[1]),
getLetter(playerLetters[2]),
getLetter(playerLetters[3]),
getLetter(playerLetters[4]),
getLetter(playerLetters[5]),
getLetter(playerLetters[6])
]
};
if (moveCMD.to === moveCMD.from && (moveCMD.indexTo === moveCMD.indexFrom || moveCMD.from === "rack")) {
sendCmds([moveRack]);
} else {
sendCmds([moveCMD, moveRack]);
}
}
tileInitMouseCoords = null;
tileInitCoords = null;
tileInitDest = null;
tileDest = null;
movingTile = null;
boardBCR = null;
bagBCR = null;
rackBCR = null;
moveCMD = null;
}
function dragTileMove(e) {
2020-04-05 15:49:48 +02:00
let newLeft = (tileInitCoords.left + (e.clientX - tileInitMouseCoords.clientX));
let newTop = (tileInitCoords.top + (e.clientY - tileInitMouseCoords.clientY));
2016-02-28 20:23:41 +01:00
2020-04-02 19:09:47 +02:00
movingTile.style.left = (newLeft + window.scrollX) + "px";
movingTile.style.top = (newTop + window.scrollY) + "px";
2016-02-28 20:23:41 +01:00
2020-04-05 15:49:48 +02:00
let newDest = null;
2016-02-28 20:23:41 +01:00
newTop += tileInitCoords.height / 2;
newLeft += tileInitCoords.width / 2;
if (
(newTop > boardBCR.top && newTop < (boardBCR.top + boardBCR.height)) &&
(newLeft > boardBCR.left && newLeft < (boardBCR.left + boardBCR.width))
) {
2020-04-05 15:49:48 +02:00
const rowIndex = Math.floor(
2016-02-28 20:23:41 +01:00
(
(newTop - boardBCR.top) / boardBCR.height
) * board.rows.length
);
if (rowIndex > 0 && rowIndex < board.rows.length - 1) {
2020-04-05 15:49:48 +02:00
const row = board.rows[rowIndex];
2016-02-28 20:23:41 +01:00
2020-04-05 15:49:48 +02:00
const colIndex = Math.floor(
2016-02-28 20:23:41 +01:00
(
(newLeft - boardBCR.left) / boardBCR.width
) * row.cells.length
);
if (colIndex > 0 && colIndex < row.cells.length - 1) {
newDest = row.cells[colIndex].firstChild;
}
}
if (newDest && newDest.getElementsByClassName("tile")[0]) {
newDest = null;
}
if (newDest) {
moveCMD.to = "board";
moveCMD.indexTo = boardCells.indexOf(newDest.parentNode);
}
} else if (
(newTop > rackBCR.top && newTop < (rackBCR.top + rackBCR.height)) &&
(newLeft > rackBCR.left && newLeft < (rackBCR.left + rackBCR.width))
) {
2020-04-05 15:49:48 +02:00
const index = Math.floor(
2016-02-28 20:23:41 +01:00
(
(newLeft - rackBCR.left) / rackBCR.width
) * playerLetters.length
);
newDest = playerLetters[index];
if (newDest && newDest.getElementsByClassName("tile")[0]) {
2020-04-05 15:49:48 +02:00
let i = index + 1;
let tile;
2016-02-28 20:23:41 +01:00
while (playerLetters[i]) {
tile = playerLetters[i].getElementsByClassName("tile")[0];
if (!tile) {
2020-04-05 15:49:48 +02:00
let j = i;
2016-02-28 20:23:41 +01:00
while (j > index) {
playerLetters[j].appendChild(
playerLetters[j - 1].getElementsByClassName("tile")[0]
);
j--;
}
break;
}
i++;
}
if (newDest.getElementsByClassName("tile")[0]) {
i = index - 1;
while (playerLetters[i]) {
tile = playerLetters[i].getElementsByClassName("tile")[0];
if (!tile) {
2020-04-05 15:49:48 +02:00
let j = i;
2016-02-28 20:23:41 +01:00
while (j < index) {
playerLetters[j].appendChild(
playerLetters[j + 1].getElementsByClassName("tile")[0]
);
j++;
}
break;
}
i--;
}
}
}
if (newDest.getElementsByClassName("tile")[0]) {
newDest = null;
} else {
moveCMD.to = "rack";
moveCMD.indexTo = index;
}
} else if (
(newTop > bagBCR.top && newTop < (bagBCR.top + bagBCR.height)) &&
(newLeft > bagBCR.left && newLeft < (bagBCR.left + bagBCR.width))
) {
newDest = bag;
}
if (newDest !== tileDest) {
if (tileDest) {
tileDest.classList.remove("tile-target");
}
if (newDest) {
newDest.classList.add("tile-target");
}
}
tileDest = newDest;
}
function dragTileBegin(e) {
2020-04-12 20:14:14 +02:00
preventDefault(e);
2016-02-28 20:23:41 +01:00
if (blockMove) {
return;
}
if (!pollingReady) {
showConnectionLost();
return;
}
2020-04-12 20:14:14 +02:00
loadAudio();
2016-02-28 20:23:41 +01:00
movingTile = e.currentTarget;
tileInitMouseCoords = e;
tileInitCoords = movingTile.getBoundingClientRect();
tileInitDest = movingTile.parentNode;
boardBCR = board.getBoundingClientRect();
rackBCR = rack.getBoundingClientRect();
bagBCR = bag.getBoundingClientRect();
2020-04-05 15:49:48 +02:00
let from;
let index;
2016-02-28 20:23:41 +01:00
2020-04-05 15:49:48 +02:00
let p = movingTile.parentNode;
let oldP = movingTile;
let oldOldP = null;
2016-02-28 20:23:41 +01:00
while (p) {
if (p === board) {
from = "board";
index = boardCells.indexOf(oldOldP);
break;
}
if (p === rack) {
from = "rack";
index = playerLetters.indexOf(oldP);
break;
}
oldOldP = oldP;
oldP = p;
p = p.parentNode;
}
moveCMD = {
cmd: "moveLetter",
from: from,
indexFrom: index
};
mouseMove(document, dragTileMove);
mouseUp(document, dragTileEnd);
2020-04-02 19:09:47 +02:00
movingTile.style.left = tileInitCoords.left + window.scrollX + "px";
movingTile.style.top = tileInitCoords.top + window.scrollY + "px";
2016-02-28 20:23:41 +01:00
movingTile.style.width = tileInitCoords.width + "px";
movingTile.style.height = tileInitCoords.height + "px";
document.body.appendChild(movingTile);
}
function setLetter(tile, letter, highlight) {
tile.firstChild.textContent = letter;
tile.lastChild.textContent = scoreOf[letter] || "";
if (highlight) {
tile.classList.add("tile-highlight");
if (tilesSound.checked) {
audioNotification.play();
}
} else {
tile.classList.remove("tile-highlight");
}
}
2020-04-02 19:09:47 +02:00
function preventDefault(e) {
e.preventDefault();
2020-04-12 20:14:14 +02:00
e.stopPropagation();
2020-04-02 19:09:47 +02:00
return false;
}
2016-02-28 20:23:41 +01:00
function makeLetter(letter, highlight) {
2020-04-05 15:49:48 +02:00
const tile = document.createElement("span");
2016-02-28 20:23:41 +01:00
tile.className = "tile";
tile.appendChild(document.createElement("span"));
tile.lastChild.className = "tile-letter";
tile.appendChild(document.createElement("span"));
tile.lastChild.className = "tile-score";
mouseDown(tile, dragTileBegin);
2020-04-02 19:09:47 +02:00
tile.addEventListener("contextmenu", preventDefault);
2020-04-12 20:14:14 +02:00
tile.addEventListener("touchstart", preventDefault);
2020-04-02 19:09:47 +02:00
tile.addEventListener("touchmove", preventDefault);
2016-02-28 20:23:41 +01:00
setLetter(tile, letter, highlight);
return tile;
}
function setTileParent(p, letter, highlight) {
2020-04-05 15:49:48 +02:00
let tile = p.getElementsByClassName("tile")[0];
2016-02-28 20:23:41 +01:00
if (tile) {
if (letter) {
setLetter(tile, letter, highlight);
} else {
p.removeChild(tile);
}
} else if (letter) {
tile = makeLetter(letter, highlight);
p.appendChild(tile);
}
}
function setCell(index, letter, highlight) {
setTileParent(boardCells[index].getElementsByClassName("tile-placeholder")[0], letter, highlight);
}
function setBoard(board) {
2020-04-05 15:49:48 +02:00
for (let i = 0; i < 15 * 15; i++) {
2016-02-28 20:23:41 +01:00
setCell(i, board[i]);
}
}
function set(key, value) {
switch (key) {
case "playerName":
2020-04-04 16:27:18 +02:00
name.textContent = localStorage.trivabblePlayerName = value;
2016-02-28 20:23:41 +01:00
break;
case "gameNumber":
document.getElementById("number").textContent = localStorage.trivabbleGameNumber = value;
break;
}
}
function checkGameInProgress(f) {
if (!gameInProgress) {
return f();
}
myConfirm(
_("Your game is not over. Are you sure you want to leave now?"),
function () {
myAlert(
format(
_("You are about to leave the current game. To recover it, please note its number: {0}"),
localStorage.trivabbleGameNumber
),
f
);
}
);
}
2020-04-05 15:49:48 +02:00
let pollingServer = false;
let pollingReady = false;
let retriedImmediately = false;
2016-02-28 20:23:41 +01:00
2020-04-05 10:25:52 +02:00
function forceReload(msg) {
needsRestart = true;
2016-02-28 20:23:41 +01:00
myConfirm(
2020-04-05 10:25:52 +02:00
msg,
2016-02-28 20:23:41 +01:00
function () {
location.reload();
}, function () {
blockMove = 1000;
document.getElementById("panel").appendChild(document.createElement("a"));
document.getElementById("panel").lastChild.href = "#";
document.getElementById("panel").lastChild.onclick = location.reload.bind(location);
2020-04-05 10:18:30 +02:00
document.getElementById("panel").lastChild.textContent = _("To continue playing, click here");
2016-02-28 20:23:41 +01:00
}
);
}
2020-04-05 10:25:52 +02:00
function fatalError(e) {
if (!needsRestart) {
// No need to show the user that a problem just happened if a message asking to restart has already been shown.
forceReload(_("Sorry, a problem just happened. The page must be reloaded. If the problem is not too serious, you should be able to keep playing normally. Otherwise, contact the person who is able to fix the problem. Click on “Yes” to reload the page."));
}
throw e;
}
function jsonError(json, e) {
console.error("An error ocurred while parsing this JSON message:", json);
fatalError(e);
}
2016-02-28 20:23:41 +01:00
function setRackCell(index, letter) {
setTileParent(playerLetters[index], letter);
}
function blinkTransitionEnd(element) {
element.removeEventListener("animationend", element._te, false);
element.classList.remove("blink");
}
function blink(element) {
element._te = blinkTransitionEnd.bind(null, element);
element.addEventListener("animationend", element._te, false);
setTimeout(element._te, 5000);
element.classList.add("blink");
}
function chatMessage(sender, content) {
2020-04-05 15:49:48 +02:00
const msgDom = document.createElement("div");
msgDom.className = "msg";
if (sender) {
msgDom.appendChild(document.createElement("span"));
msgDom.lastChild.className = "msg-sender";
msgDom.lastChild.textContent = _("{0}: ").replace("{0}", sender);
}
msgDom.appendChild(document.createElement("span"));
msgDom.lastChild.className = "msg-content";
msgDom.lastChild.textContent = content;
chatMessages.appendChild(msgDom);
chatMessages.scrollTop = chatMessages.scrollHeight;
if (sender && sender !== localStorage.trivabblePlayerName) {
if (msgSound.checked) {
audioChat.play();
}
blink(chatMessages);
}
return msgDom;
}
2016-02-28 20:23:41 +01:00
function handleReceivedData(data) {
if (Array.isArray(data)) {
2020-04-05 15:49:48 +02:00
data.forEach(handleReceivedData);
return;
}
2020-04-05 10:25:52 +02:00
if (data.pleaseRestart) {
forceReload(_("Sorry to disturb you, but we need to reload the game so you can continue playing. You will not lose anything except the messages in the chat. Are you ready?"));
}
if (data.stopping) {
blockEventSourceAutoRetry = true;
if (eventSource) {
eventSource.close();
eventSource = null;
}
2016-02-28 20:23:41 +01:00
retryPolling(
(typeof data.stopping === "number" && data.stopping > 0)
? data.stopping
: 0
);
}
2016-02-28 20:23:41 +01:00
if (data.msg) {
chatMessage(data.msg.sender, data.msg.content);
2016-02-28 20:23:41 +01:00
}
if (data.players) {
if (participantPlaceholder) {
participantPlaceholder.parentNode.removeChild(participantPlaceholder);
participantPlaceholder = null;
}
2020-04-05 15:49:48 +02:00
for (let i = 0; i < data.players.length; i++) {
const player = data.players[i];
const playerName = data.players[i].player;
2016-02-28 20:23:41 +01:00
if (!tablePlayers[playerName]) {
2020-04-05 15:49:48 +02:00
let before = null;
2016-02-28 20:23:41 +01:00
2020-04-05 15:49:48 +02:00
for (let j = 1; j < participants.rows.length; j++) {
2016-02-28 20:23:41 +01:00
if (playerName < participants.rows[j].cells[0].textContent) {
before = participants.rows[j];
break;
}
}
2020-04-05 15:49:48 +02:00
const row = document.createElement("tr");
2016-02-28 20:23:41 +01:00
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.onclick = (
function (row) {
return function () {
if (!pollingReady) {
showConnectionLost();
return;
}
2016-02-28 20:23:41 +01:00
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.textContent
);
2020-04-04 18:08:28 +02:00
};
2016-02-28 20:23:41 +01:00
}
)(row);
tablePlayers[playerName] = row;
}
2020-04-04 16:27:18 +02:00
if (Object.prototype.hasOwnProperty.call(player, "score")) {
2020-04-05 15:49:48 +02:00
const scoreCell = tablePlayers[playerName].childNodes[2];
2016-02-28 20:23:41 +01:00
scoreCell.textContent = player.score;
blink(scoreCell);
}
2020-04-04 16:27:18 +02:00
if (Object.prototype.hasOwnProperty.call(player, "rackCount")) {
2020-04-05 15:49:48 +02:00
const countCell = tablePlayers[playerName].childNodes[1];
2016-02-28 20:23:41 +01:00
countCell.textContent = player.rackCount;
blink(countCell);
}
}
}
if (data.playerName) {
set("playerName", data.playerName);
}
if (data.gameNumber) {
set("gameNumber", data.gameNumber);
}
if (data.letterValues) {
scoreOf = data.letterValues;
}
if (data.board) {
setBoard(data.board);
}
if (typeof data.remainingLetters === "number") {
document.getElementById("remaining-letters").textContent = data.remainingLetters;
if (data.remainingLetters === 0) {
helpBag.style.display = "none";
helpClear.style.display = "";
} else {
helpBag.style.display = "";
helpClear.style.display = "none";
}
}
if (data.rack) {
setRack(data.rack);
}
2020-04-04 16:27:18 +02:00
if (!data.action) {
return;
}
2016-02-28 20:23:41 +01:00
2020-04-04 16:27:18 +02:00
switch (data.action) {
2016-02-28 20:23:41 +01:00
case "pushBag": //TODO
break;
case "popBag": //TODO
break;
case "reset": //TODO
tablePlayers = {};
while (participants.rows[1]) {
participants.removeChild(participants.rows[1]);
}
2020-04-04 18:08:28 +02:00
sendCmds([{"cmd":"hello"}]);
2016-02-28 20:23:41 +01:00
break;
case "moveLetter":
if (data.from === "board") {
setCell(data.indexFrom, "");
} else if (data.from === "rack") {
setRackCell(data.indexFrom, "");
}
if (data.to === "board") {
2020-04-04 16:27:18 +02:00
setCell(data.indexTo, data.letter, Object.prototype.hasOwnProperty.call(data, "player") && data.player !== localStorage.trivabblePlayerName);
2016-02-28 20:23:41 +01:00
} else if (data.to === "rack") {
setRackCell(data.indexTo, data.letter);
}
break;
case "setCell":
2020-04-04 16:27:18 +02:00
setCell(data.indexTo, data.letter, Object.prototype.hasOwnProperty.call(data, "player") && data.player !== localStorage.trivabblePlayerName);
2016-02-28 20:23:41 +01:00
break;
case "setRackCell":
setRackCell(data.indexTo, data.letter);
}
}
function retryPolling(delay) {
2020-04-05 10:25:52 +02:00
if (needsRestart) {
return;
}
pollingServer = false;
pollingReady = false;
blockEventSourceAutoRetry = false;
if (!boundEventShowConnectionLost) {
boundEventShowConnectionLost = true;
document.body.addEventListener("mousemove", showConnectionLost);
document.body.addEventListener("mousedown", showConnectionLost);
document.body.addEventListener("touchstart", showConnectionLost);
}
if (delay || retriedImmediately) {
setTimeout(sendCmds.bind(null), delay || POLLING_DELAY);
} else {
retriedImmediately = true;
2020-04-04 18:08:28 +02:00
sendCmds();
}
}
function parseAndHandleReceivedData(r) {
try {
handleReceivedData(JSON.parse(r));
} catch (e) {
jsonError(r, e);
}
}
2016-02-28 20:23:41 +01:00
function send(data) {
2020-04-05 10:25:52 +02:00
if (needsRestart) {
return;
}
2020-04-05 10:35:53 +02:00
if (!pollingReady && (data.cmds && data.cmds.length)) {
sendCmds();
2020-04-02 19:10:07 +02:00
setTimeout(send.bind(null, data), POLLING_DELAY);
return;
}
2016-02-28 20:23:41 +01:00
2020-04-05 15:49:48 +02:00
let thisRequestIsPolling = false;
let currentIndex = 0;
let expectedLength = 0;
2016-02-28 20:23:41 +01:00
if (!data.cmds || !data.cmds.length) {
2020-04-02 19:10:07 +02:00
if (pollingServer) {
return;
}
2016-02-28 20:23:41 +01:00
pollingServer = true;
2020-04-05 10:25:52 +02:00
data.version = VERSION;
2016-02-28 20:23:41 +01:00
if (window.EventSource) {
2020-04-05 15:49:48 +02:00
eventSource = new EventSource("/:trivabble/sse/" + JSON.stringify(data));
2016-02-28 20:23:41 +01:00
eventSource.onopen = connectionReady;
2020-04-04 16:17:23 +02:00
eventSource.onmessage = function (e) {
connectionReady();
parseAndHandleReceivedData(e.data);
};
2020-04-04 16:17:23 +02:00
eventSource.onerror = function () {
pollingReady = false;
if (needsRestart) {
eventSource.close();
} else if (!blockEventSourceAutoRetry) {
retryPolling();
}
};
2020-04-04 16:17:23 +02:00
return;
}
2020-04-05 10:25:52 +02:00
thisRequestIsPolling = true;
} else {
blockMove++;
2020-04-04 16:17:23 +02:00
}
2020-04-05 15:49:48 +02:00
const xhr = new XMLHttpRequest();
2020-04-02 19:10:07 +02:00
xhr.open("POST", "/:trivabble", true);
xhr.setRequestHeader("Content-Type", "text/plain");
xhr.send(JSON.stringify(data));
2016-02-28 20:23:41 +01:00
xhr.onreadystatechange = function () {
2020-04-02 19:10:07 +02:00
if (xhr.readyState === 2 && thisRequestIsPolling) {
connectionReady();
2020-04-02 19:10:07 +02:00
} else if (xhr.readyState === 3 && thisRequestIsPolling) {
connectionReady();
2016-02-28 20:23:41 +01:00
while (true) {
if (!expectedLength) {
2020-04-05 15:49:48 +02:00
let i = currentIndex;
2016-02-28 20:23:41 +01:00
while (i < xhr.responseText.length) {
if ("0123456789".indexOf(xhr.responseText.charAt(i)) === -1) {
expectedLength = parseInt(xhr.responseText.substring(currentIndex, i));
currentIndex = i;
break;
}
++i;
}
}
if (expectedLength && (xhr.responseText.length >= currentIndex + expectedLength)) {
2020-04-05 15:49:48 +02:00
const end = currentIndex + expectedLength;
let msgs;
2016-02-28 20:23:41 +01:00
try {
2020-04-05 15:49:48 +02:00
msgs = JSON.parse(
2016-02-28 20:23:41 +01:00
xhr.responseText.substring(
currentIndex,
end
)
);
currentIndex = end;
expectedLength = 0;
} catch (e) {
jsonError(xhr.responseText.substring(
currentIndex,
end
), e);
2016-02-28 20:23:41 +01:00
}
2020-04-02 19:10:07 +02:00
retriedImmediately = false;
handleReceivedData(msgs);
2016-02-28 20:23:41 +01:00
} else {
break;
}
}
} else if (xhr.readyState === 4) {
if (thisRequestIsPolling) {
retryPolling();
2020-04-02 19:10:07 +02:00
return;
}
if (xhr.status === 0 || xhr.status >= 300) {
setTimeout(send.bind(null, data), POLLING_DELAY);
2016-02-28 20:23:41 +01:00
return;
}
parseAndHandleReceivedData(xhr.responseText);
2016-02-28 20:23:41 +01:00
blockMove--;
if (!pollingServer) {
sendCmds();
2016-02-28 20:23:41 +01:00
}
}
};
}
function sendCmds(cmds) {
send({
gameNumber: localStorage.trivabbleGameNumber || "",
playerName: localStorage.trivabblePlayerName,
cmds: cmds
});
}
function joinGame() {
checkGameInProgress(
function () {
myPrompt(
_("To join a game, please give the number which is displayed on your adversary(ies)' screen.\nIf you do not know it, ask them.\n\nWarning: your adversary must not take your number, (s)he must keep his/her own. If you whish to recover your current game, please not the following number: {0}.").replace("{0}", localStorage.trivabbleGameNumber),
function (n) {
n = parseInt(n);
if (isNaN(n)) {
myAlert(_("It seems your did not give a correct number, or you clicked on “Cancel”. As a result, the current game continues, if any. To join a game, click on “Join a game” again."));
} else {
localStorage.trivabbleGameNumber = n;
location.reload();
}
}
);
}
);
}
2020-04-05 15:49:48 +02:00
const specialTypesText = {
2016-02-28 20:23:41 +01:00
"doubleLetter" : _("Double\nLetter"),
"doubleWord" : _("Double\nWord"),
"tripleLetter" : _("Triple\nLetter"),
"tripleWord" : _("Triple\nWord")
};
function specialCell(type, cell) {
cell.firstChild.appendChild(document.createElement("span"));
cell.classList.add("special-cell");
cell.classList.add("special-cell-" + type);
cell.lastChild.lastChild.textContent = _(specialTypesText[type]);
cell.lastChild.lastChild.className = "special-cell-label";
}
function changeName() {
myPrompt(
_("To change your name, enter a new one. You can keep using your current name by cancelling. Please note that if you change your name and you have games in progress, you will not be able to keep playing them anymore unless you get back to your current name."),
function (newName) {
if (newName && newName.trim()) {
localStorage.trivabblePlayerName = newName.trim();
2020-04-04 16:27:18 +02:00
name.textContent = localStorage.trivabblePlayerName;
2016-02-28 20:23:41 +01:00
}
},
localStorage.trivabblePlayerName
);
}
function startGame(number) {
if (number) {
localStorage.trivabbleGameNumber = number;
}
2020-04-05 10:35:53 +02:00
sendCmds();
2016-02-28 20:23:41 +01:00
}
2020-04-05 15:49:48 +02:00
let audioTileLoaded = false;
let audioMsgLoaded = false;
let tilesSound;
let msgSound;
2016-02-28 20:23:41 +01:00
function loadAudio() {
if (!audioTileLoaded && tilesSound.checked) {
audioTileLoaded = true;
audioNotification.load();
}
if (!audioMsgLoaded && msgSound.checked) {
audioMsgLoaded = true;
audioChat.load();
}
}
2020-04-12 20:14:14 +02:00
function bagClicked(e) {
preventDefault(e);
2016-02-28 20:23:41 +01:00
if (blockMove) {
return;
}
2020-04-12 20:14:14 +02:00
loadAudio();
if (!pollingReady) {
showConnectionLost();
return;
}
2020-04-05 15:49:48 +02:00
const index = getFreeRackSpaceIndex();
2016-02-28 20:23:41 +01:00
if (index === -1) {
myAlert(_("You cannot take another tile: your rack is full."));
} else {
sendCmds(
[{cmd:"moveLetter", from: "bag", to: "rack", indexTo: index}]
);
}
}
function clearGame() {
myConfirm(
2020-04-02 19:06:04 +02:00
_("Are you sure you want to put all the tiles back in the bag (in order to play another game)?"),
2016-02-28 20:23:41 +01:00
function () {
sendCmds(
[{cmd: "resetGame"}]
);
}
2020-04-04 18:08:28 +02:00
);
2016-02-28 20:23:41 +01:00
}
function clearRack() {
myConfirm(
_("Are you sure you want to put all your tiles back in the bag?"),
function () {
2020-04-12 20:14:16 +02:00
sendCmds([
{cmd: "moveLetter", from: "rack", indexFrom: 0, to: "bag", indexTo: -1},
{cmd: "moveLetter", from: "rack", indexFrom: 1, to: "bag", indexTo: -1},
{cmd: "moveLetter", from: "rack", indexFrom: 2, to: "bag", indexTo: -1},
{cmd: "moveLetter", from: "rack", indexFrom: 3, to: "bag", indexTo: -1},
{cmd: "moveLetter", from: "rack", indexFrom: 4, to: "bag", indexTo: -1},
{cmd: "moveLetter", from: "rack", indexFrom: 5, to: "bag", indexTo: -1},
{cmd: "moveLetter", from: "rack", indexFrom: 6, to: "bag", indexTo: -1}
]);
2016-02-28 20:23:41 +01:00
}
);
}
function initChat() {
chatMessages.style.width = chatMessages.offsetWidth + "px";
2020-04-05 15:49:48 +02:00
const btn = document.getElementById("chat-btn");
2016-02-28 20:23:41 +01:00
2020-04-12 20:14:14 +02:00
chatTextarea.onmouseup = function () {
chatMessages.style.width = chatTextarea.offsetWidth + "px";
2016-02-28 20:23:41 +01:00
};
btn.onclick = function () {
loadAudio();
2020-04-12 20:14:14 +02:00
sendCmds([{
cmd: "msg",
msg: chatTextarea.value
}]);
2016-02-28 20:23:41 +01:00
2020-04-12 20:14:14 +02:00
chatTextarea.value = "";
2016-02-28 20:23:41 +01:00
};
2020-04-12 20:14:14 +02:00
chatTextarea.onkeydown = function (e) {
2016-02-28 20:23:41 +01:00
if (e.keyCode === 13) {
2020-04-12 20:14:14 +02:00
preventDefault(e);
chatTextarea.focus();
2016-02-28 20:23:41 +01:00
btn.onclick();
}
};
}
function initSound() {
audioNotification = new Audio();
audioNotification.preload = "auto";
audioNotification.volume = 1;
2020-04-05 15:49:48 +02:00
let audioSourceOGG = document.createElement("source");
2016-02-28 20:23:41 +01:00
audioSourceOGG.src = "notification.ogg";
2020-04-05 15:49:48 +02:00
let audioSourceMP3 = document.createElement("source");
2016-02-28 20:23:41 +01:00
audioSourceMP3.src = "notification.mp3";
audioNotification.appendChild(audioSourceOGG);
audioNotification.appendChild(audioSourceMP3);
audioChat = new Audio();
audioChat.preload = "auto";
audioChat.volume = 1;
audioSourceOGG = document.createElement("source");
audioSourceOGG.src = "receive.ogg";
2020-04-04 16:27:18 +02:00
audioSourceMP3 = document.createElement("source");
2016-02-28 20:23:41 +01:00
audioSourceMP3.src = "receive.mp3";
audioChat.appendChild(audioSourceOGG);
audioChat.appendChild(audioSourceMP3);
tilesSound = document.getElementById("tiles-sound");
msgSound = document.getElementById("msg-sound");
tilesSound.onclick = function () {
localStorage.trivabbleTileSound = tilesSound.checked;
};
msgSound.onclick = function () {
localStorage.trivabbleMsgSound = msgSound.checked;
};
2020-04-04 16:27:18 +02:00
if (Object.prototype.hasOwnProperty.call(localStorage, "trivabbleMsgSound")) {
2016-02-28 20:23:41 +01:00
msgSound.checked = localStorage.trivabbleMsgSound === "true";
} else {
localStorage.trivabbleMsgSound = msgSound.checked;
}
2020-04-04 16:27:18 +02:00
if (Object.prototype.hasOwnProperty.call(localStorage, "trivabbleTileSound")) {
2016-02-28 20:23:41 +01:00
tilesSound.checked = localStorage.trivabbleTileSound === "true";
} else {
localStorage.trivabbleTilesSound = tilesSound.checked;
}
}
function repromptName(f) {
if (localStorage.trivabblePlayerName && localStorage.trivabblePlayerName.trim()) {
f();
} else {
myPrompt(
_("It seems your did not give your name. You need to do it for the game to run properly."),
function (name) {
if (name && name.trim()) {
localStorage.trivabblePlayerName = name.trim();
}
repromptName(f);
}
);
}
}
trivabble.applyL10n = function () {
for (const node of [].slice.call(document.querySelectorAll("[data-l10n]"))) {
2020-04-05 15:49:48 +02:00
if (node.dataset.l10n === "text-content") {
node.textContent = _(node.textContent.trim());
2016-02-28 20:23:41 +01:00
} else {
2020-04-05 15:49:48 +02:00
node.setAttribute(node.dataset.l10n, _(node.getAttribute(node.dataset.l10n)));
2016-02-28 20:23:41 +01:00
}
}
trivabble.run();
};
function langSelectionChange(e) {
localStorage.trivabbleLang = e.target.value;
location.reload();
}
2016-02-28 20:23:41 +01:00
function initGlobals() {
board = document.getElementById("board");
rack = document.getElementById("rack");
participants = document.getElementById("participants");
name = document.getElementById("name");
bag = document.getElementById("bag");
chatMessages = document.getElementById("chat-messages");
2020-04-12 20:14:14 +02:00
chatTextarea = document.getElementById("chat-ta");
2016-02-28 20:23:41 +01:00
helpBag = document.getElementById("help-bag");
helpClear = document.getElementById("help-clear");
participantPlaceholder = document.getElementById("participants-placeholder");
}
function initEvents() {
mouseDown(bag, bagClicked);
document.getElementById("clear-game").onclick = clearGame;
document.getElementById("change-name").onclick = changeName;
document.getElementById("join-game").onclick = joinGame;
document.getElementById("clear-rack").onclick = clearRack;
helpClear.onclick = clearGame;
}
function initGame() {
if (!localStorage.trivabblePlayerName) {
myPrompt(
_("Hello! To begin, enter your name. Your adversaries will see this name when you play with them."),
function (name) {
if (name && name.trim()) {
localStorage.trivabblePlayerName = name;
}
repromptName(initGame);
}
);
return;
}
2020-04-04 16:27:18 +02:00
name.textContent = localStorage.trivabblePlayerName;
2016-02-28 20:23:41 +01:00
2020-04-05 15:49:48 +02:00
const letters = "ABCDEFGHIJKLMNO";
2016-02-28 20:23:41 +01:00
2020-04-05 15:49:48 +02:00
const doubleLetter = {
2016-02-28 20:23:41 +01:00
"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
};
2020-04-05 15:49:48 +02:00
let cell;
2016-02-28 20:23:41 +01:00
2020-04-05 15:49:48 +02:00
for (let i = 0; i < 7; i++) {
const span = document.createElement("span");
2016-02-28 20:23:41 +01:00
span.className = "tile-placeholder";
rack.appendChild(span);
playerLetters.push(span);
}
2020-04-05 15:49:48 +02:00
for (let i = 0; i < 15; i++) {
2016-02-28 20:23:41 +01:00
board.rows[0].appendChild(document.createElement("th"));
board.rows[0].lastChild.textContent = i + 1;
board.appendChild(document.createElement("tr"));
board.lastChild.appendChild(document.createElement("th"));
board.lastChild.lastChild.textContent = letters[i];
2020-04-05 15:49:48 +02:00
for (let j = 0; j < 15; j++) {
2016-02-28 20:23:41 +01:00
cell = document.createElement("td");
boardCells.push(cell);
board.lastChild.appendChild(cell);
cell.appendChild(document.createElement("div"));
cell.lastChild.className = "tile-placeholder";
if (i === j && i === 7) {
specialCell("doubleWord", board.lastChild.lastChild);
2020-04-04 16:27:18 +02:00
cell = board.lastChild.lastChild.getElementsByClassName("special-cell-label")[0];
2016-02-28 20:23:41 +01:00
cell.textContent = "★";
board.lastChild.lastChild.id = "center-cell";
} else if (i % 7 === 0 && j % 7 === 0) {
specialCell("tripleWord", board.lastChild.lastChild);
} else if ((i === j || i + j === 14) && (i < 5 || i > 9)) {
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))) {
specialCell("doubleLetter", board.lastChild.lastChild);
}
}
board.lastChild.appendChild(document.createElement("th"));
board.lastChild.lastChild.textContent = letters[i];
}
board.rows[0].appendChild(board.rows[0].cells[0].cloneNode(false));
board.appendChild(document.createElement("tr"));
board.lastChild.appendChild(board.rows[0].cells[0].cloneNode(false));
2020-04-05 15:49:48 +02:00
for (let i = 0; i < 15; i++) {
2016-02-28 20:23:41 +01:00
board.lastChild.appendChild(document.createElement("th"));
board.lastChild.lastChild.textContent = i + 1;
}
board.lastChild.appendChild(board.rows[0].cells[0].cloneNode(false));
if (localStorage.trivabbleGameNumber) {
document.getElementById("number").textContent = localStorage.trivabbleGameNumber;
}
startGame(localStorage.trivabbleGameNumber);
}
function initLang() {
2020-04-05 15:49:48 +02:00
const lang = libD.lang = localStorage.trivabbleLang || libD.lang;
2020-04-05 15:49:48 +02:00
const langSel = document.getElementById("select-lang");
langSel.value = lang;
langSel.onchange = langSelectionChange;
2020-04-05 15:49:48 +02:00
const script = document.createElement("script");
script.src = "l10n/js/" + lang + ".js";
script.onerror = trivabble.l10nError;
document.getElementsByTagName("head")[0].appendChild(script);
}
2016-02-28 20:23:41 +01:00
trivabble.run = function () {
initGlobals();
initEvents();
initChat();
initGame();
initSound();
};
trivabble.l10nError = trivabble.run;
window.addEventListener("DOMContentLoaded", initLang);
2016-02-28 20:23:41 +01:00
}());