Merge branch 'develop' into timer
This commit is contained in:
commit
b059eaf9e9
|
@ -1,5 +1,5 @@
|
|||
/server/games.backup.json
|
||||
/public/config.js
|
||||
/public/dict/*.dict
|
||||
/public/dict/
|
||||
/public/l10n
|
||||
/.vscode
|
||||
|
|
7
Makefile
7
Makefile
|
@ -10,7 +10,7 @@ endif
|
|||
|
||||
.PHONY: all help lang start-dev-server
|
||||
|
||||
all: lang
|
||||
all: lang emptydictlist
|
||||
|
||||
help:
|
||||
@echo make extract-lang-dists: extract the distributions per language from Wikipedia
|
||||
|
@ -26,6 +26,11 @@ extract-lang-dists:
|
|||
|
||||
lang: public/l10n/js/fr.js
|
||||
|
||||
emptydictlist: public/dict/list.js
|
||||
|
||||
public/dict/list.js:
|
||||
mkdir -p public/dict/ && touch public/dict/list.js
|
||||
|
||||
eslint:
|
||||
-${ESLINT} **/*.js
|
||||
|
||||
|
|
80
README.md
80
README.md
|
@ -135,7 +135,20 @@ We will assume www-data is the UNIX user of your Web server. Adapt if necessary.
|
|||
exec sudo -u www-data DEBUG_LOG=true nodejs trivabble-server.js
|
||||
```
|
||||
|
||||
8. Configure your web server. In the next two sections, we describe the configuration for Apache 2 and Nginx.
|
||||
8. Configure your web server.
|
||||
|
||||
#### Content-Security Policy Header
|
||||
|
||||
Trivabble loads 5 static javascript files, 2 static css files and a sound from its origin, and connects to the same server by SSE or websockets.
|
||||
Here is the recommended HTTP CSP header for Trivabble:
|
||||
|
||||
```
|
||||
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; connect-src 'self'; media-src 'self'
|
||||
```
|
||||
|
||||
We advise you to set this up to improve the security of your setup.
|
||||
|
||||
In the next two sections, we describe the configuration for Apache 2 and Nginx.
|
||||
|
||||
#### Apache 2
|
||||
|
||||
|
@ -193,7 +206,7 @@ server {
|
|||
|
||||
## Enable the spell checker
|
||||
|
||||
This is an experimental feature. The process of building the dictionaries takes a lot of disk space.
|
||||
The process of building the dictionaries takes a lot of disk space and require huge amount of memory (around 1GB).
|
||||
|
||||
Install `wget` and `aspell` (to have the `word-list-compress` command), and run:
|
||||
|
||||
|
@ -202,4 +215,65 @@ cd dict-dists-extractor
|
|||
make
|
||||
```
|
||||
|
||||
Then, set `ENABLE_CHECK_SPELLING` to `true` in `public/config.js`.
|
||||
If you want to limit spelling to a subset of languages, you can define the list by the `LANGS` variable before executing the `make` command such as:
|
||||
|
||||
```sh
|
||||
cd dict-dists-extractor
|
||||
LANGS="de en fr" make
|
||||
```
|
||||
|
||||
This way, you only build English, French and German dictionaries.
|
||||
|
||||
To deactivate spell checker at system level, you need to empty dictionary list contained into file `public/dict/list.js`. Such command can be used:
|
||||
|
||||
```sh
|
||||
echo > public/dict/list.js
|
||||
```
|
||||
|
||||
## Technical appendices
|
||||
|
||||
### Board extractor
|
||||
|
||||
Wikipedia provides tile distributions (content of the bag and points) for various languages. Board languages are built from a Wikipedia page.
|
||||
|
||||
#### General parsing
|
||||
|
||||
Most board languages are extracted from the French version of the Wikipedia page which has a simple document structure. Parsing is done by a JS script named `make_board.js`:
|
||||
- each new language board is proceeded by a level 2 header containing language name,
|
||||
- tile points is followed by `point(s)`
|
||||
- tile letter and number of tiles are easy extracted by a regex which looks like `<b>(letter)</b> <small>x(times)</small>`
|
||||
|
||||
#### Special languages
|
||||
|
||||
Some languages are only defined into the English version. So we copy them into separated text files and parsing is done by an AWK named `make_board.awk`.
|
||||
|
||||
### Dictionary building process
|
||||
|
||||
The spell checker in Trivabble is based on Aspell dictionaries. However, as Trivabble only uses a subset of letters (most accentuated characters have to be replaced by standard ones), we can't use Aspell engine straightforward; we need to build lists of words that only use playable characters.
|
||||
|
||||
#### Overall concept
|
||||
|
||||
In order to build dictionaries for various languages, we first need to retrieve Aspell dictionary lists. Then we retrieve the last versions of dictionaries for every languages and we build them. Thanks to two Aspell commands, we build the full list of words. Based on the distribution of tiles, we translate every derived (most of the time accentuated) character by the appropriate one, and finally we remove duplicated entries.
|
||||
|
||||
#### Details on Makefile rules
|
||||
|
||||
Rule '$(OBJ_DIR)/src.mk' retrieves on Aspell site the last version of the dictionary list, extract dictionary archive URL and build a sub-makefile with rules to retrieve every needed dictionary.
|
||||
|
||||
Rule '%.dir' unpacks dictionary archives and build Aspell dictionary. Result directory is rename as LANG.dir
|
||||
|
||||
Rule '%.low' expands a Aspell dictionary into a list of words (low means "List Of Words"". Phrases are split into words. Finally the list is ordered and clean from duplicated words.
|
||||
|
||||
Rule '$(ROOT_DICT)/%.dict':
|
||||
- excludes words with numbers, dashes, apostrophes and other forbidden signs,
|
||||
- translates accentuated characters into standard ones,
|
||||
- up-case all words,
|
||||
- and remove duplicated words.
|
||||
This file can be used by Trivabble for spell checking.
|
||||
|
||||
Last rule 'check-%' checks that file %.dict contains only words with characters base on language tile bag.
|
||||
|
||||
#### Remarks
|
||||
|
||||
Some lists of words are too huge (for example Hungarian or Turkish are agglutinative languages), so Trivabble will not be able to check spelling for those languages.
|
||||
|
||||
Some languages don't have any Aspell dictionary.
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
/obj
|
|
@ -9,7 +9,7 @@ SMALL = br ca cy da de en es fr ga hr hy is lv nl no pt sv
|
|||
LARGE = bg el eo it pl ro ru sk sl uk
|
||||
# 50MB < dictionary size < 500MB
|
||||
VERY_LARGE = ar cs et fi
|
||||
LANGS = $(SMALL) $(LARGE)
|
||||
LANGS ?= $(SMALL) $(LARGE)
|
||||
|
||||
|
||||
DICT_RULE_PRELUDE = sortuniq() { cat > "$$1"; sort -u -o "$$1" "$$1"; }; \
|
||||
|
@ -24,13 +24,14 @@ LOWS = $(addsuffix .low,$(LANGS))
|
|||
|
||||
DICTS = $(addsuffix .dict,$(LANGS))
|
||||
|
||||
DEPEND = Makefile
|
||||
#DEPEND = Makefile
|
||||
#MAKEFLAGS = -s
|
||||
|
||||
.PHONY: all required count clean low
|
||||
.PHONY: all required count clean low list
|
||||
|
||||
all: required
|
||||
make $(addprefix check-,$(LANGS))
|
||||
make list
|
||||
|
||||
$(ROOT_DICT):
|
||||
mkdir $(ROOT_DICT)
|
||||
|
@ -45,6 +46,15 @@ low: $(LOWS)
|
|||
|
||||
required: ${OBJ_DIR} ${OBJ_DIR}/src.mk ${ROOT_DICT}
|
||||
|
||||
list: $(ROOT_DICT)/list.js
|
||||
|
||||
$(ROOT_DICT)/list.js: $(wildcard $(ROOT_DICT)/*.dict)
|
||||
ls -s ${ROOT_DICT}/*.dict| \
|
||||
awk 'BEGIN { printf "window.DictionaryList = {\n" } \
|
||||
{ $$0 = gensub(/(.+) .*\/(.+)\.dict/, "\\1 \\2", "g"); \
|
||||
printf "\"%s\": %d,\n", $$2, $$1 } \
|
||||
END { printf "\"none\": 0\n};" }' > $@
|
||||
|
||||
$(OBJ_DIR)/src.mk:
|
||||
echo Creation language table
|
||||
wget $(ASPDICT) -q -O - | \
|
||||
|
@ -73,8 +83,9 @@ include $(wildcard ${OBJ_DIR}/src.mk)
|
|||
export DICT_NAME="$$(basename "$<" ".dir")"; \
|
||||
aspell --dict-dir="$$(realpath $<)" -d "$$DICT_NAME" dump master "$$DICT_NAME" | \
|
||||
aspell --dict-dir="$$(realpath $<)" -l "$$DICT_NAME" expand | \
|
||||
tr -s '[:space:]' '\n' > "$@" && \
|
||||
LC_ALL=C sort -S28G -u -o "$@" "$@"
|
||||
tr -s '[:space:]' '\n' > "$@~" && \
|
||||
LC_ALL=C sort -S28G -u -o "$@~" "$@~" && \
|
||||
mv "$@~" "$@"
|
||||
|
||||
$(OBJ_DIR)/no.low:
|
||||
make $(OBJ_DIR)/nb.low
|
||||
|
|
|
@ -164,7 +164,7 @@ msgid "Are you sure you want to change board to '{0}'? This will put all the ti
|
|||
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 :"
|
||||
msgstr "Langue du plateau :"
|
||||
|
||||
msgid "You changed the language of the board to {0}"
|
||||
msgstr "Vous avez changé la langue du plateau en {0}"
|
||||
|
@ -199,6 +199,12 @@ msgstr "La vérification orthographique nécessite que Trivabble télécharge un
|
|||
msgid "Spell checking is based on:"
|
||||
msgstr "La vérification orthographique est basée sur :"
|
||||
|
||||
msgid "{0} points will be added to:"
|
||||
msgstr "{0} points vont être ajoutés à:"
|
||||
|
||||
msgid "There are no points to add."
|
||||
msgstr "Il n’y a pas de point à ajouter."
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Paramètres"
|
||||
|
||||
|
@ -229,6 +235,15 @@ msgstr "Astuce !"
|
|||
msgid "Next tip"
|
||||
msgstr "Astuce suivante"
|
||||
|
||||
msgid "Disable the spell checker"
|
||||
msgstr "Désactiver la vérification orthographique"
|
||||
|
||||
msgid "Spell checking is not available for this language."
|
||||
msgstr "La vérification orthographique n'est pas disponible dans cette langue."
|
||||
|
||||
msgid "Score new words"
|
||||
msgstr "Compter les points"
|
||||
|
||||
msgid "Turn timer:"
|
||||
msgstr "Temps du tour :"
|
||||
|
||||
|
|
|
@ -142,6 +142,12 @@ msgstr ""
|
|||
msgid "Who's turn? Click on the Turn button!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Click on (+) to increase someone's score."
|
||||
msgstr ""
|
||||
|
||||
msgid "Show a cell to everyone by double-clicking on it."
|
||||
msgstr ""
|
||||
|
||||
msgid "Show my rack to other players"
|
||||
msgstr ""
|
||||
|
||||
|
@ -199,6 +205,12 @@ msgstr ""
|
|||
msgid "Spell checking is based on:"
|
||||
msgstr ""
|
||||
|
||||
msgid "{0} points will be added to:"
|
||||
msgstr ""
|
||||
|
||||
msgid "There are no points to add."
|
||||
msgstr: ""
|
||||
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
|
@ -222,18 +234,21 @@ msgstr ""
|
|||
msgid "Flash light color"
|
||||
msgstr ""
|
||||
|
||||
msgid "Click on (+) to increase someone's score."
|
||||
msgstr ""
|
||||
|
||||
msgid "Show a cell to everyone by double-clicking on it."
|
||||
msgstr ""
|
||||
|
||||
msgid "Tip!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Next tip"
|
||||
msgstr ""
|
||||
|
||||
msgid "Disable the spell checker"
|
||||
msgstr ""
|
||||
|
||||
msgid "Spell checking is not available for this language."
|
||||
msgstr ""
|
||||
|
||||
msgid "Score new words"
|
||||
msgstr ""
|
||||
|
||||
msgid "Turn timer:"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ $1 == "-" { sub(/- /, "") }
|
|||
gsub(/[×,]/, "")
|
||||
for (i = 1; i<= NF; i+=2) {
|
||||
lettre = $(i)
|
||||
if (lettre == "blank") {
|
||||
if ((lettre == "blank") || (lettre ~ /[Jj]oker/)) {
|
||||
lettre = " "
|
||||
}
|
||||
nombre = $(i+1)
|
||||
|
|
|
@ -40,8 +40,8 @@
|
|||
margin-top:1em;
|
||||
}
|
||||
|
||||
.alert-content {
|
||||
padding:1ex
|
||||
.alert-content-and-input {
|
||||
padding:1ex;
|
||||
}
|
||||
|
||||
.alert input[type=text], .alert input[type=number], .alert input[type=password] {
|
||||
|
@ -81,3 +81,18 @@
|
|||
border-radius:3px
|
||||
}
|
||||
|
||||
.alert-prompt-buttons {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alert.choice .alert-prompt-buttons, .alert.prompt .alert-prompt-buttons {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.alert.choice.choice-inline .alert-content, .choice.choice-inline .alert-prompt {
|
||||
display:inline-block;
|
||||
}
|
||||
|
||||
.alert.choice.choice-inline .alert-prompt {
|
||||
padding-left: 1ex;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
let divAlertCallbackYes;
|
||||
let divAlertCallbackNo;
|
||||
let alertInput;
|
||||
let alertSelect;
|
||||
let divAlertContent;
|
||||
|
||||
const _ = (window.libD && libD.l10n) ? libD.l10n() : function (s) {
|
||||
|
@ -85,14 +86,26 @@
|
|||
|
||||
divAlertInput.appendChild(alertInput);
|
||||
|
||||
divAlertInput.appendChild(document.createElement("div"));
|
||||
divAlertInput.lastChild.className = "alert-prompt-buttons";
|
||||
divAlertInput.lastChild.appendChild(document.createElement("button"));
|
||||
divAlertInput.lastChild.lastChild.textContent = _("OK");
|
||||
divAlertInput.lastChild.lastChild.onclick = promptOK;
|
||||
divAlertInput.lastChild.appendChild(document.createElement("button"));
|
||||
divAlertInput.lastChild.lastChild.textContent = _("Annuler");
|
||||
divAlertInput.lastChild.lastChild.onclick = promptCancel;
|
||||
alertSelect = document.createElement("select");
|
||||
alertSelect.onchange = function () {
|
||||
alertInput.value = alertSelect.value;
|
||||
};
|
||||
|
||||
divAlertInput.appendChild(alertSelect);
|
||||
|
||||
const divAlertContentAndInput = document.createElement("div");
|
||||
divAlertContentAndInput.className = "alert-content-and-input";
|
||||
divAlertContentAndInput.appendChild(divAlertContent);
|
||||
divAlertContentAndInput.appendChild(divAlertInput);
|
||||
|
||||
const divAlerPromptButton = document.createElement("div");
|
||||
divAlerPromptButton.className = "alert-prompt-buttons";
|
||||
divAlerPromptButton.appendChild(document.createElement("button"));
|
||||
divAlerPromptButton.lastChild.textContent = _("OK");
|
||||
divAlerPromptButton.lastChild.onclick = promptOK;
|
||||
divAlerPromptButton.appendChild(document.createElement("button"));
|
||||
divAlerPromptButton.lastChild.textContent = _("Cancel");
|
||||
divAlerPromptButton.lastChild.onclick = promptCancel;
|
||||
|
||||
divAlertConfirm = document.createElement("div");
|
||||
divAlertConfirm.className = _("alert-confirm");
|
||||
|
@ -111,8 +124,8 @@
|
|||
|
||||
const divAlertOuter = document.createElement("div");
|
||||
divAlertOuter.className = "alert-outer";
|
||||
divAlertOuter.appendChild(divAlertContent);
|
||||
divAlertOuter.appendChild(divAlertInput);
|
||||
divAlertOuter.appendChild(divAlertContentAndInput);
|
||||
divAlertOuter.appendChild(divAlerPromptButton);
|
||||
divAlertOuter.appendChild(divAlertConfirm);
|
||||
divAlertOuter.appendChild(divAlertButton);
|
||||
divAlert.appendChild(divAlertOuter);
|
||||
|
@ -131,6 +144,8 @@
|
|||
}
|
||||
|
||||
divAlert.classList.remove("prompt");
|
||||
divAlert.classList.remove("choice");
|
||||
divAlert.classList.remove("choice-inline");
|
||||
|
||||
divAlertContent.textContent = msg;
|
||||
divAlertInput.style.display = "none";
|
||||
|
@ -149,11 +164,15 @@
|
|||
}
|
||||
|
||||
divAlert.classList.add("prompt");
|
||||
divAlert.classList.remove("choice");
|
||||
divAlert.classList.remove("choice-inline");
|
||||
|
||||
divAlertContent.textContent = msg;
|
||||
divAlertInput.style.display = "";
|
||||
alertInput.style.display = "";
|
||||
alertInput.value = defaultText || "";
|
||||
alertInput.type = (options && options.type) || "text";
|
||||
alertSelect.style.display = "none";
|
||||
divAlertConfirm.style.display = "none";
|
||||
divAlertButton.style.display = "none";
|
||||
divAlertCallback = callback;
|
||||
|
@ -170,6 +189,8 @@
|
|||
}
|
||||
|
||||
divAlert.classList.remove("prompt");
|
||||
divAlert.classList.remove("choice");
|
||||
divAlert.classList.remove("choice-inline");
|
||||
|
||||
divAlertContent.textContent = msg;
|
||||
divAlertInput.style.display = "none";
|
||||
|
@ -180,4 +201,35 @@
|
|||
divAlert.style.display = "";
|
||||
divAlertConfirm.getElementsByTagName("button")[0].focus();
|
||||
};
|
||||
|
||||
global.myChoice = function (msg, options, callback, defaultValue) {
|
||||
if (!divAlert) {
|
||||
prepare();
|
||||
}
|
||||
|
||||
divAlert.classList.remove("prompt");
|
||||
divAlert.classList.add("choice");
|
||||
|
||||
if (options.dispositionInline) {
|
||||
divAlert.classList.add("choice-inline");
|
||||
} else {
|
||||
divAlert.classList.remove("choice-inline");
|
||||
}
|
||||
|
||||
alertSelect.options.length = 0;
|
||||
for (const key of options.choices) {
|
||||
[].push.call(alertSelect.options, new Option(key, key));
|
||||
}
|
||||
alertSelect.value = alertInput.value = defaultValue;
|
||||
|
||||
divAlertContent.textContent = msg;
|
||||
divAlertInput.style.display = "";
|
||||
alertInput.style.display = "none";
|
||||
alertSelect.style.display = "";
|
||||
divAlertConfirm.style.display = "none";
|
||||
divAlertButton.style.display = "none";
|
||||
divAlertCallback = callback;
|
||||
divAlert.style.display = "";
|
||||
divAlertConfirm.getElementsByTagName("button")[0].focus();
|
||||
};
|
||||
}(this));
|
||||
|
|
|
@ -17,8 +17,8 @@ window.TrivabbleConf = {
|
|||
// To tweak only if your webserver is shared with other conflicting resources at / (e.g. Yunohost integration)
|
||||
APP_PATH: "",
|
||||
|
||||
// Whether the spell checker is enabled (dictionaries must be downloaded on the server before enabling this option)
|
||||
ENABLE_SPELL_CHECKER: false,
|
||||
// The API entry point. Default value: APP_PATH + '/:trivabble'
|
||||
API_ENTRY_POINT: "/:trivabble",
|
||||
|
||||
// The color of the flash light when double clicking on a cell
|
||||
FLASH_LIGHT_COLOR: "#EE6633",
|
||||
|
@ -35,6 +35,11 @@ window.TrivabbleConf = {
|
|||
// The defaut double tap duration. If not set, the value at the middle of the previous array is used.
|
||||
DOUBLE_TAP_DURATION: 1800,
|
||||
|
||||
// The default premium for playing seven tiles on a turn
|
||||
PREMIUM_SEVEN_TILES: 50,
|
||||
|
||||
// Score is automically affected to last player. If false, score is automatically affected to the player who pressed the Score button
|
||||
SCORE_LAST_PLAYER: true,
|
||||
|
||||
// I don't like trailing commas, here is a nice message for you reading this file :-)
|
||||
HAVE_FUN: true
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
<script src="config.js"></script>
|
||||
<script src="dict/list.js"></script>
|
||||
<script src="l10n.js"></script>
|
||||
<script src="alert.js"></script>
|
||||
<script src="touch2click.js"></script>
|
||||
|
@ -58,6 +59,17 @@
|
|||
<p><label><input type="checkbox" id="tiles-sound" /><span data-l10n="text-content">Sound of the tiles</span></label></p>
|
||||
<p><label><input type="checkbox" id="msg-sound" /><span data-l10n="text-content">Sound of messages</span></label></p>
|
||||
</div>
|
||||
<div>
|
||||
<p id="disable-spell-checker-p" hide="true">
|
||||
<label>
|
||||
<input type="checkbox" id="disable-spell-checker" />
|
||||
<span data-l10n="text-content">Disable the spell checker</span>
|
||||
</label>
|
||||
</p>
|
||||
<p id="no-spell-checker-p" hide="false">
|
||||
<span data-l10n="text-content">Spell checking is not available for this language.</span>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
<label for="double-tap-duration" data-l10n="text-content">Double tap duration:</label>
|
||||
|
@ -122,6 +134,7 @@
|
|||
<button id="clear-rack" class="minibutton" data-l10n="text-content">Put back all the tiles of your rack in the bag</button>
|
||||
<button id="show-rack" class="minibutton" data-l10n="text-content">Show my rack to other players</button>
|
||||
<button id="check-spelling" class="minibutton" data-l10n="text-content" hidden="true">Check spelling for new words</button>
|
||||
<button id="score-words" class="minibutton" data-l10n="text-content">Score new words</button>
|
||||
</div>
|
||||
<div id="help-box">
|
||||
<p id="help-box-title" data-l10n="text-content">Tip!</p>
|
||||
|
|
|
@ -122,6 +122,8 @@ html, #board, [draggable], .tile {
|
|||
display:inline-block;
|
||||
white-space:pre-wrap;
|
||||
font-size:7px;
|
||||
text-overflow:ellipsis;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
#center-cell .special-cell-label {
|
||||
|
@ -186,8 +188,10 @@ button {
|
|||
}
|
||||
|
||||
#board td {
|
||||
width:32px;
|
||||
height:32px;
|
||||
width: 32px;
|
||||
max-width:32px;
|
||||
max-height:32px;
|
||||
}
|
||||
|
||||
#rack .tile-placeholder {
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
* @source: https://gitlab.com/raphj/trivabble/
|
||||
*/
|
||||
|
||||
/*global libD, myConfirm, myAlert, myPrompt*/
|
||||
/*global libD, myConfirm, myAlert, myPrompt, myChoice*/
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
@ -31,6 +31,8 @@
|
|||
|
||||
const Conf = window.TrivabbleConf || {};
|
||||
|
||||
const DictionaryList = window.DictionaryList || {};
|
||||
|
||||
function setConf(parameterName, defaultValue) {
|
||||
if (typeof Conf[parameterName] === "undefined") {
|
||||
Conf[parameterName] = defaultValue;
|
||||
|
@ -52,12 +54,14 @@
|
|||
setConf("APP_PATH", "");
|
||||
setConf("ENABLE_MSG_SOUND", true);
|
||||
setConf("ENABLE_TILE_SOUND", true);
|
||||
setConf("ENABLE_SPELL_CHECKER", false);
|
||||
setConf("DOUBLE_TAP_DURATIONS", [650, 1100, 1800, 3000, 5000]);
|
||||
setConf("DOUBLE_TAP_DURATION", middle("DOUBLE_TAP_DURATIONS"));
|
||||
setConf("FLASH_LIGHT_DURATIONS", [800, 1600, 3200]);
|
||||
setConf("FLASH_LIGHT_DURATION", middle("FLASH_LIGHT_DURATIONS"));
|
||||
setConf("FLASH_LIGHT_COLOR", "#ee6633");
|
||||
setConf("API_ENTRY_POINT", Conf.APP_PATH + "/:trivabble");
|
||||
setConf("PREMIUM_SEVEN_TILES", 50);
|
||||
setConf("SCORE_LAST_PLAYER", true);
|
||||
setConf("ENABLE_TIMER", true);
|
||||
|
||||
function isSetting(key) {
|
||||
|
@ -69,23 +73,21 @@
|
|||
let type;
|
||||
let value;
|
||||
|
||||
/* get default value from configuration */
|
||||
if (Object.prototype.hasOwnProperty.call(Conf, key)) {
|
||||
// get default value from configuration
|
||||
value = Conf[key];
|
||||
type = typeof value;
|
||||
} else if (Object.prototype.hasOwnProperty.call(SettingsTypes, key)) {
|
||||
type = SettingsTypes[key];
|
||||
}
|
||||
|
||||
/* try to retrieve value from localstorage */
|
||||
if (Object.prototype.hasOwnProperty.call(localStorage, key)) {
|
||||
value = localStorage.getItem(key);
|
||||
if (Object.prototype.hasOwnProperty.call(localStorage, "trivabble" + key)) {
|
||||
value = localStorage.getItem("trivabble" + key);
|
||||
|
||||
/* get type from localStorage if no default is set */
|
||||
if (typeof type === "undefined") {
|
||||
if (Object.prototype.hasOwnProperty.call(localStorage, key + "_type")) {
|
||||
type = localStorage.getItem(key + "_type");
|
||||
} else {
|
||||
type = "string";
|
||||
}
|
||||
type = "string";
|
||||
}
|
||||
|
||||
/* cast from string to type */
|
||||
|
@ -105,6 +107,10 @@
|
|||
return value;
|
||||
}
|
||||
|
||||
function unsetSetting(key) {
|
||||
localStorage.removeItem("trivabble" + key);
|
||||
}
|
||||
|
||||
function setSetting(key, value) {
|
||||
if (getSetting(key) === value) {
|
||||
return;
|
||||
|
@ -113,22 +119,12 @@
|
|||
let type;
|
||||
|
||||
/* try to retrieve type from configuration */
|
||||
if (Object.prototype.hasOwnProperty.call(Conf, key)) {
|
||||
if (Object.prototype.hasOwnProperty.call(SettingsTypes, key)) {
|
||||
type = SettingsTypes[key];
|
||||
} else if (Object.prototype.hasOwnProperty.call(Conf, key)) {
|
||||
type = typeof Conf[key];
|
||||
}
|
||||
|
||||
/* try to retrieve type from localstorage */
|
||||
if (typeof type === "undefined") {
|
||||
if (Object.prototype.hasOwnProperty.call(localStorage, key)) {
|
||||
type = localStorage.getItem(key + "_type");
|
||||
}
|
||||
}
|
||||
|
||||
/* if not set type is defined from value */
|
||||
if (typeof type === "undefined") {
|
||||
type = typeof value;
|
||||
}
|
||||
|
||||
/* storage value in localstorage */
|
||||
if (type === typeof value) {
|
||||
if (type === "object") {
|
||||
|
@ -138,32 +134,23 @@
|
|||
(type === "number") ||
|
||||
(type === "object") ||
|
||||
(type === "string")) {
|
||||
localStorage.setItem(key, value);
|
||||
|
||||
/* store type into localstorage if no default value in configuration */
|
||||
if (!Object.prototype.hasOwnProperty.call(Conf, key)) {
|
||||
localStorage.setItem(key + "_type", type);
|
||||
}
|
||||
localStorage.setItem("trivabble" + key, value);
|
||||
} else {
|
||||
console.error("Unsupported type");
|
||||
}
|
||||
} else {
|
||||
console.error("incoherent type");
|
||||
console.error("Incoherent or missing type. See the SettingsTypes object.");
|
||||
}
|
||||
}
|
||||
|
||||
function migrateSetting(key, type) {
|
||||
if (Object.prototype.hasOwnProperty.call(localStorage, key)) {
|
||||
localStorage.setItem(key + "_type", type);
|
||||
}
|
||||
}
|
||||
|
||||
migrateSetting("spellCheckerEnabled", "boolean");
|
||||
migrateSetting("trivabbleGameNumber", "number");
|
||||
migrateSetting("trivabbleBoardLang", "string");
|
||||
migrateSetting("trivabbleGameNumber", "number");
|
||||
migrateSetting("trivabbleLang", "string");
|
||||
migrateSetting("trivabblePlayerName", "string");
|
||||
const SettingsTypes = {
|
||||
SpellCheckerEnabledOnce: "boolean",
|
||||
DisableSpellChecker: "boolean",
|
||||
GameNumber: "number",
|
||||
BoardLang: "string",
|
||||
Lang: "string",
|
||||
PlayerName: "string"
|
||||
};
|
||||
|
||||
const _ = (window.libD && libD.l10n) ? libD.l10n() : function (s) {
|
||||
return s;
|
||||
|
@ -214,12 +201,9 @@
|
|||
let currentMessageId = 1;
|
||||
const waitingMsgs = [];
|
||||
|
||||
let serverVersion = 0;
|
||||
let lastPlayer = null;
|
||||
|
||||
// HTTP path url prefix for any socket (xhr,ess or ws) from browser to server access
|
||||
function getApiEntryPoint() {
|
||||
return Conf.APP_PATH + "/:trivabble";
|
||||
}
|
||||
let serverVersion = 0;
|
||||
|
||||
function mouseDown(ele, fun, stop) {
|
||||
const meth = stop ? "removeEventListener" : "addEventListener";
|
||||
|
@ -270,6 +254,21 @@
|
|||
}
|
||||
}
|
||||
|
||||
function checkDictionaryExistance() {
|
||||
const code = document.getElementById("board-lang").value;
|
||||
const availableLang = Object.prototype.hasOwnProperty.call(DictionaryList, code);
|
||||
document.getElementById("disable-spell-checker-p").hidden = !availableLang;
|
||||
document.getElementById("no-spell-checker-p").hidden = availableLang;
|
||||
|
||||
if (availableLang && !document.getElementById("disable-spell-checker").checked) {
|
||||
document.getElementById("check-spelling").hidden = false;
|
||||
document.getElementById("info-spell-checking").hidden = false;
|
||||
} else {
|
||||
document.getElementById("check-spelling").hidden = true;
|
||||
document.getElementById("info-spell-checking").hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
function getDictionary(code, callback, force) {
|
||||
if (downloadedDictionaries[code]) {
|
||||
if (downloadedDictionaries[code].length) {
|
||||
|
@ -283,11 +282,11 @@
|
|||
return;
|
||||
}
|
||||
|
||||
if (!force && !getSetting("spellCheckerEnabled")) {
|
||||
if (!force && !getSetting("SpellCheckerEnabledOnce")) {
|
||||
myConfirm(
|
||||
_("Spell checking requires Trivabble to download a dictionary. Do you confirm?"),
|
||||
function () {
|
||||
setSetting("spellCheckerEnabled", true);
|
||||
setSetting("SpellCheckerEnabledOnce", true);
|
||||
getDictionary(code, callback, true);
|
||||
}
|
||||
);
|
||||
|
@ -635,33 +634,32 @@
|
|||
rackBCR = rack.getBoundingClientRect();
|
||||
bagBCR = bag.getBoundingClientRect();
|
||||
|
||||
let from;
|
||||
let from = null;
|
||||
let index;
|
||||
|
||||
let p = movingTile.parentNode;
|
||||
let oldP = movingTile;
|
||||
let oldOldP = null;
|
||||
let p = movingTile;
|
||||
|
||||
while (p) {
|
||||
if (p === board) {
|
||||
while (p && !from) {
|
||||
index = boardCells.indexOf(p);
|
||||
if (index !== -1) {
|
||||
from = "board";
|
||||
index = boardCells.indexOf(oldOldP);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (p === rack) {
|
||||
index = playerLetters.indexOf(p);
|
||||
if (index !== -1) {
|
||||
from = "rack";
|
||||
index = playerLetters.indexOf(oldP);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
oldOldP = oldP;
|
||||
oldP = p;
|
||||
p = p.parentNode;
|
||||
}
|
||||
|
||||
if (!from) {
|
||||
fatalError(new Error("Error: did not find the parent of the moving tile"));
|
||||
return;
|
||||
}
|
||||
|
||||
moveCMD = {
|
||||
cmd: "moveLetter",
|
||||
from: from,
|
||||
|
@ -732,6 +730,14 @@
|
|||
}
|
||||
|
||||
function setCell(index, letter, highlight) {
|
||||
if (lastPlayer) {
|
||||
if (letter) {
|
||||
currentTilePlayed[index] = letter;
|
||||
} else {
|
||||
delete currentTilePlayed[index];
|
||||
}
|
||||
}
|
||||
|
||||
setTileParent(boardCells[index].getElementsByClassName("tile-placeholder")[0], letter, highlight);
|
||||
}
|
||||
|
||||
|
@ -745,15 +751,16 @@
|
|||
switch (key) {
|
||||
case "playerName":
|
||||
name.textContent = value;
|
||||
setSetting("trivabblePlayerName", value);
|
||||
setSetting("PlayerName", value);
|
||||
break;
|
||||
case "gameNumber":
|
||||
document.getElementById("number").textContent = value;
|
||||
setSetting("trivabbleGameNumber", value);
|
||||
setSetting("GameNumber", value);
|
||||
break;
|
||||
case "boardLang":
|
||||
document.getElementById("board-lang").value = value;
|
||||
setSetting("trivabbleBoardLang", value);
|
||||
setSetting("BoardLang", value);
|
||||
checkDictionaryExistance(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -769,7 +776,7 @@
|
|||
myAlert(
|
||||
format(
|
||||
_("You are about to leave the current game. To recover it, please note its number: {0}"),
|
||||
getSetting("trivabbleGameNumber")
|
||||
getSetting("GameNumber")
|
||||
),
|
||||
f
|
||||
);
|
||||
|
@ -850,7 +857,7 @@
|
|||
|
||||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||||
|
||||
if (sender && sender !== getSetting("trivabblePlayerName")) {
|
||||
if (sender && sender !== getSetting("PlayerName")) {
|
||||
if (getSetting("ENABLE_MSG_SOUND")) {
|
||||
audioChat.play();
|
||||
}
|
||||
|
@ -890,7 +897,7 @@
|
|||
case "changeBoardLang": {
|
||||
const newLang = boardLangSelect.querySelector("[value=" + msg.specialMsg.newBoardLang + "]").textContent;
|
||||
infoMessage(
|
||||
(msg.sender === getSetting("trivabblePlayerName"))
|
||||
(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)
|
||||
);
|
||||
|
@ -923,11 +930,14 @@
|
|||
localStorage.timerTurnDate = timerDate();
|
||||
}
|
||||
|
||||
currentTilePlayed = {};
|
||||
currentPlayer = player;
|
||||
refreshCurrentPlayer();
|
||||
}
|
||||
|
||||
function getScoreCell(playerName) {
|
||||
return tablePlayers[playerName].childNodes[2].childNodes[0];
|
||||
}
|
||||
|
||||
function setPlayers(players) {
|
||||
if (participantPlaceholder) {
|
||||
participantPlaceholder.parentNode.removeChild(participantPlaceholder);
|
||||
|
@ -1039,7 +1049,10 @@
|
|||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(player, "score")) {
|
||||
const scoreCell = tablePlayers[playerName].childNodes[2].childNodes[0];
|
||||
const scoreCell = getScoreCell(playerName);
|
||||
if (parseInt(scoreCell.textContent) !== player.score) {
|
||||
currentTilePlayed = {};
|
||||
}
|
||||
scoreCell.textContent = player.score;
|
||||
blink(scoreCell);
|
||||
}
|
||||
|
@ -1054,6 +1067,15 @@
|
|||
refreshCurrentPlayer();
|
||||
}
|
||||
|
||||
function handleLastPlayer(data) {
|
||||
if (data.player) {
|
||||
if (lastPlayer !== data.player) {
|
||||
currentTilePlayed = {};
|
||||
}
|
||||
lastPlayer = data.player;
|
||||
}
|
||||
}
|
||||
|
||||
function applyAction(data) {
|
||||
switch (data.action) {
|
||||
case "pushBag": //TODO
|
||||
|
@ -1068,9 +1090,14 @@
|
|||
participants.removeChild(participants.rows[1]);
|
||||
}
|
||||
sendCmds([{cmd: "hello"}]);
|
||||
|
||||
lastPlayer = null;
|
||||
currentTilePlayed = {};
|
||||
break;
|
||||
|
||||
case "moveLetter":
|
||||
handleLastPlayer(data);
|
||||
|
||||
if (data.from === "board") {
|
||||
setCell(data.indexFrom, "");
|
||||
} else if (data.from === "rack") {
|
||||
|
@ -1078,17 +1105,32 @@
|
|||
}
|
||||
|
||||
if (data.to === "board") {
|
||||
setCell(data.indexTo, data.letter, Object.prototype.hasOwnProperty.call(data, "player") && data.player !== getSetting("trivabblePlayerName"));
|
||||
setCell(
|
||||
data.indexTo,
|
||||
data.letter,
|
||||
data.player && data.player !== getSetting("PlayerName")
|
||||
);
|
||||
} 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 !== getSetting("trivabblePlayerName"));
|
||||
handleLastPlayer(data);
|
||||
|
||||
setCell(
|
||||
data.indexTo,
|
||||
data.letter,
|
||||
data.player && data.player !== getSetting("PlayerName")
|
||||
);
|
||||
|
||||
if ((data.letter !== "") && (currentTilePlayed[data.indexTo] === "-")) {
|
||||
currentTilePlayed[data.indexTo] = data.letter;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "setRackCell":
|
||||
setRackCell(data.indexTo, data.letter);
|
||||
}
|
||||
|
@ -1315,7 +1357,7 @@
|
|||
closeConnections();
|
||||
pollingServer = true;
|
||||
|
||||
eventSource = new EventSource(getApiEntryPoint() + "/sse/" + JSON.stringify(cmdsWithContext()));
|
||||
eventSource = new EventSource(getSetting("API_ENTRY_POINT") + "/sse/" + JSON.stringify(cmdsWithContext()));
|
||||
bindConnectionEvents(eventSource);
|
||||
return;
|
||||
}
|
||||
|
@ -1331,7 +1373,7 @@
|
|||
webSocket = new WebSocket(
|
||||
(window.location.protocol === "http:" ? "ws://" : "wss://") +
|
||||
window.location.host +
|
||||
getApiEntryPoint() + "/ws/" +
|
||||
Conf.API_ENTRY_POINT + "/ws/" +
|
||||
JSON.stringify(cmdsWithContext())
|
||||
);
|
||||
|
||||
|
@ -1345,7 +1387,7 @@
|
|||
function xhrRequest(data, onreadystatechange) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open("POST", getApiEntryPoint(), true);
|
||||
xhr.open("POST", Conf.API_ENTRY_POINT, true);
|
||||
xhr.setRequestHeader("Content-Type", "text/plain");
|
||||
xhr.send(JSON.stringify(data));
|
||||
|
||||
|
@ -1443,9 +1485,9 @@
|
|||
|
||||
function cmdsWithContext(cmds) {
|
||||
return {
|
||||
gameNumber: getSetting("trivabbleGameNumber") || "",
|
||||
playerName: getSetting("trivabblePlayerName"),
|
||||
boardLang: getSetting("trivabbleBoardLang"),
|
||||
gameNumber: getSetting("GameNumber") || "",
|
||||
playerName: getSetting("PlayerName"),
|
||||
boardLang: getSetting("BoardLang"),
|
||||
version: VERSION,
|
||||
cmds: cmds
|
||||
};
|
||||
|
@ -1477,13 +1519,13 @@
|
|||
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}", getSetting("trivabbleGameNumber")),
|
||||
_("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}", getSetting("GameNumber")),
|
||||
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 {
|
||||
setSetting("trivabbleGameNumber", n);
|
||||
setSetting("GameNumber", n);
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
@ -1503,7 +1545,7 @@
|
|||
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.textContent = cell.title = _(specialTypesText[type]);
|
||||
cell.lastChild.lastChild.className = "special-cell-label";
|
||||
}
|
||||
|
||||
|
@ -1512,17 +1554,17 @@
|
|||
_("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()) {
|
||||
setSetting("trivabblePlayerName", newName.trim());
|
||||
name.textContent = getSetting("trivabblePlayerName");
|
||||
setSetting("PlayerName", newName.trim());
|
||||
name.textContent = getSetting("PlayerName");
|
||||
}
|
||||
},
|
||||
getSetting("trivabblePlayerNamer")
|
||||
getSetting("PlayerNamer")
|
||||
);
|
||||
}
|
||||
|
||||
function startGame(number) {
|
||||
if (number) {
|
||||
setSetting("trivabbleGameNumber", number);
|
||||
setSetting("GameNumber", number);
|
||||
}
|
||||
startConnection();
|
||||
}
|
||||
|
@ -1591,7 +1633,7 @@
|
|||
sendCmds([{cmd: "changeBoard", lang: code}]);
|
||||
},
|
||||
function () {
|
||||
boardLangSelect.value = getSetting("trivabbleBoardLang");
|
||||
boardLangSelect.value = getSetting("BoardLang");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -1781,11 +1823,11 @@
|
|||
}
|
||||
|
||||
function checkSpellingClicked() {
|
||||
if (getSetting("spellCheckerEnabled") === "false") {
|
||||
if (getSetting("SpellCheckerEnabledOnce") === "false") {
|
||||
return;
|
||||
}
|
||||
|
||||
getDictionary(getSetting("trivabbleBoardLang"), checkSpelling);
|
||||
getDictionary(getSetting("BoardLang"), checkSpelling);
|
||||
}
|
||||
|
||||
function checkSpelling(dictionary) {
|
||||
|
@ -1837,6 +1879,84 @@
|
|||
return [].indexOf.call(tr.parentNode.rows, tr);
|
||||
}
|
||||
|
||||
function scoreWords() {
|
||||
let totalScore = 0;
|
||||
|
||||
const newWords = searchNewWords();
|
||||
for (const k of Object.keys(newWords)) {
|
||||
let wordFactor = 1;
|
||||
let wordScore = 0;
|
||||
|
||||
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);
|
||||
|
||||
/* Letter score */
|
||||
const tilePlaceholder = boardCells[index].getElementsByClassName("tile-placeholder")[0];
|
||||
const tile = tilePlaceholder.getElementsByClassName("tile")[0];
|
||||
const letterScore = tile.lastChild.textContent;
|
||||
let letterFactor = 1;
|
||||
|
||||
/* Is a freshly played letter? */
|
||||
if (currentTilePlayed[index] === word.letters[l]) {
|
||||
|
||||
/* Letter factor */
|
||||
if (boardCells[index].classList.contains("special-cell-doubleLetter")) {
|
||||
letterFactor = 2;
|
||||
} else if (boardCells[index].classList.contains("special-cell-tripleLetter")) {
|
||||
letterFactor = 3;
|
||||
}
|
||||
|
||||
/* Word factor */
|
||||
if (boardCells[index].classList.contains("special-cell-doubleWord")) {
|
||||
wordFactor *= 2;
|
||||
} else if (boardCells[index].classList.contains("special-cell-tripleWord")) {
|
||||
wordFactor *= 3;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
wordScore += letterScore * letterFactor;
|
||||
}
|
||||
|
||||
totalScore += wordScore * wordFactor;
|
||||
}
|
||||
|
||||
if (!totalScore) {
|
||||
myAlert(_("There are no points to add."));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check for trivabble (the premium score) */
|
||||
if (Object.keys(currentTilePlayed).length === 7) {
|
||||
totalScore += getSetting("PREMIUM_SEVEN_TILES");
|
||||
}
|
||||
|
||||
/* Ѕcore last player or the one who pressed the button */
|
||||
const playerName = getSetting("SCORE_LAST_PLAYER") ? lastPlayer : getSetting("PlayerName");
|
||||
|
||||
myChoice(
|
||||
format(_("{0} points will be added to:"), totalScore), {
|
||||
choices: Object.keys(tablePlayers),
|
||||
dispositionInline: true
|
||||
},
|
||||
function (name) {
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentTilePlayed = {};
|
||||
|
||||
sendCmds([{
|
||||
cmd: "score",
|
||||
player: name,
|
||||
score: parseInt(getScoreCell(name).textContent) + totalScore
|
||||
}]);
|
||||
},
|
||||
playerName || lastPlayer || currentPlayer || getSetting("PlayerName")
|
||||
);
|
||||
}
|
||||
|
||||
function triggerFlashLight(cell) {
|
||||
if (!cell) {
|
||||
return;
|
||||
|
@ -1976,7 +2096,7 @@
|
|||
if (tilesSound) {
|
||||
|
||||
/* migration of old settings for tiles sound */
|
||||
const oldSetting = getSetting("trivabbleTilesSound");
|
||||
const oldSetting = getSetting("TilesSound");
|
||||
if (oldSetting) {
|
||||
setSetting("ENABLE_TILE_SOUND", oldSetting === "true");
|
||||
delete localStorage.trivabbleTilesSound;
|
||||
|
@ -1992,7 +2112,7 @@
|
|||
if (msgSound) {
|
||||
|
||||
/* migration of old settings for message sound */
|
||||
const oldSetting = getSetting("trivabbleMsgSound");
|
||||
const oldSetting = getSetting("MsgSound");
|
||||
if (oldSetting) {
|
||||
setSetting("ENABLE_MSG_SOUND", oldSetting === "true");
|
||||
delete localStorage.trivabbleMsgSound;
|
||||
|
@ -2005,14 +2125,22 @@
|
|||
}
|
||||
}
|
||||
|
||||
function initSpellChecker() {
|
||||
if (getSetting("ENABLE_SPELL_CHECKER")) {
|
||||
document.getElementById("check-spelling").hidden = false;
|
||||
document.getElementById("info-spell-checking").hidden = false;
|
||||
} else {
|
||||
document.getElementById("check-spelling").hidden = true;
|
||||
document.getElementById("info-spell-checking").hidden = true;
|
||||
function toggleSpellChecker(e) {
|
||||
const disabled = document.getElementById("disable-spell-checker").checked;
|
||||
checkDictionaryExistance();
|
||||
|
||||
if (e) {
|
||||
setSetting("DisableSpellChecker", disabled);
|
||||
}
|
||||
|
||||
if (disabled) {
|
||||
unsetSetting("SpellCheckerEnabledOnce");
|
||||
}
|
||||
}
|
||||
|
||||
function initSpellChecker() {
|
||||
document.getElementById("disable-spell-checker").checked = getSetting("DisableSpellChecker");
|
||||
toggleSpellChecker();
|
||||
}
|
||||
|
||||
function translateDuration(x, values) {
|
||||
|
@ -2147,14 +2275,14 @@
|
|||
}
|
||||
|
||||
function repromptName(f) {
|
||||
if (getSetting("trivabblePlayerName") && getSetting("trivabblePlayerName").trim()) {
|
||||
if (getSetting("PlayerName") && getSetting("PlayerName").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()) {
|
||||
setSetting("trivabblePlayerName", name.trim());
|
||||
function (newName) {
|
||||
if (newName && newName.trim()) {
|
||||
setSetting("PlayerName", newName.trim());
|
||||
}
|
||||
|
||||
repromptName(f);
|
||||
|
@ -2203,11 +2331,11 @@
|
|||
);
|
||||
});
|
||||
|
||||
boardLangSelect.value = getSetting("trivabbleBoardLang");
|
||||
boardLangSelect.value = getSetting("BoardLang");
|
||||
}
|
||||
|
||||
function langSelectionChange(e) {
|
||||
setSetting("trivabbleLang", e.target.value);
|
||||
setSetting("Lang", e.target.value);
|
||||
location.reload();
|
||||
}
|
||||
|
||||
|
@ -2245,6 +2373,8 @@
|
|||
document.getElementById("btn-settings").onclick = showSettings;
|
||||
document.getElementById("btn-settings-close").onclick = hideSettings;
|
||||
document.getElementById("next-help-msg").onclick = nextHelpMessage;
|
||||
document.getElementById("disable-spell-checker").onclick = toggleSpellChecker;
|
||||
document.getElementById("score-words").onclick = scoreWords;
|
||||
window.addEventListener("keydown", function (e) {
|
||||
if (e.key === "Escape") {
|
||||
document.querySelector(".modal").classList.remove("show-modal");
|
||||
|
@ -2253,12 +2383,12 @@
|
|||
}
|
||||
|
||||
function initGame() {
|
||||
if (!getSetting("trivabblePlayerName")) {
|
||||
if (!getSetting("PlayerName")) {
|
||||
myPrompt(
|
||||
_("Hello! To begin, enter your name. Your adversaries will see this name when you play with them."),
|
||||
function (name) {
|
||||
if (name && name.trim()) {
|
||||
setSetting("trivabblePlayerName", name);
|
||||
setSetting("PlayerName", name);
|
||||
}
|
||||
repromptName(initGame);
|
||||
}
|
||||
|
@ -2267,7 +2397,7 @@
|
|||
return;
|
||||
}
|
||||
|
||||
name.textContent = getSetting("trivabblePlayerName");
|
||||
name.textContent = getSetting("PlayerName");
|
||||
|
||||
const letters = "ABCDEFGHIJKLMNO";
|
||||
|
||||
|
@ -2286,6 +2416,7 @@
|
|||
};
|
||||
|
||||
let cell;
|
||||
let row;
|
||||
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const span = document.createElement("span");
|
||||
|
@ -2298,7 +2429,7 @@
|
|||
board.rows[0].appendChild(document.createElement("th"));
|
||||
board.rows[0].lastChild.textContent = i + 1;
|
||||
|
||||
const row = board.insertRow(-1);
|
||||
row = board.insertRow(-1);
|
||||
row.appendChild(document.createElement("th"));
|
||||
row.lastChild.textContent = letters[i];
|
||||
|
||||
|
@ -2311,18 +2442,18 @@
|
|||
cell.lastChild.className = "tile-placeholder";
|
||||
|
||||
if (i === j && i === 7) {
|
||||
specialCell("doubleWord", board.lastChild.lastChild);
|
||||
cell = board.lastChild.lastChild.getElementsByClassName("special-cell-label")[0];
|
||||
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) {
|
||||
specialCell("tripleWord", board.lastChild.lastChild);
|
||||
specialCell("tripleWord", row.lastChild);
|
||||
} else if ((i === j || i + j === 14) && (i < 5 || i > 9)) {
|
||||
specialCell("doubleWord", board.lastChild.lastChild);
|
||||
specialCell("doubleWord", row.lastChild);
|
||||
} else if ((i % 4 === 1) && (j % 4 === 1)) {
|
||||
specialCell("tripleLetter", board.lastChild.lastChild);
|
||||
specialCell("tripleLetter", row.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);
|
||||
specialCell("doubleLetter", row.lastChild);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2332,7 +2463,7 @@
|
|||
|
||||
board.rows[0].appendChild(board.rows[0].cells[0].cloneNode(false));
|
||||
|
||||
const row = board.insertRow(-1);
|
||||
row = board.insertRow(-1);
|
||||
row.appendChild(board.rows[0].cells[0].cloneNode(false));
|
||||
|
||||
for (let i = 0; i < 15; i++) {
|
||||
|
@ -2342,19 +2473,19 @@
|
|||
|
||||
row.appendChild(board.rows[0].cells[0].cloneNode(false));
|
||||
|
||||
if (getSetting("trivabbleGameNumber")) {
|
||||
document.getElementById("number").textContent = getSetting("trivabbleGameNumber");
|
||||
if (getSetting("GameNumber")) {
|
||||
document.getElementById("number").textContent = getSetting("GameNumber");
|
||||
}
|
||||
|
||||
if (getSetting("trivabbleBoardLang")) {
|
||||
document.getElementById("board-lang").value = getSetting("trivabbleBoardLang");
|
||||
if (getSetting("BoardLang")) {
|
||||
document.getElementById("board-lang").value = getSetting("BoardLang");
|
||||
}
|
||||
|
||||
startGame(getSetting("trivabbleGameNumber"));
|
||||
startGame(getSetting("GameNumber"));
|
||||
}
|
||||
|
||||
function initLang() {
|
||||
const lang = libD.lang = getSetting("trivabbleLang") || libD.lang;
|
||||
const lang = libD.lang = getSetting("Lang") || libD.lang;
|
||||
|
||||
const langSel = document.getElementById("select-lang");
|
||||
langSel.value = lang;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "Ukrainian",
|
||||
|
||||
"bag": [
|
||||
"Jokers", "Jokers",
|
||||
" ", " ",
|
||||
"О", "О", "О", "О", "О", "О", "О", "О", "О",
|
||||
"А", "А", "А", "А", "А", "А", "А", "А",
|
||||
"И", "И", "И", "И", "И", "И",
|
||||
|
@ -41,7 +41,7 @@
|
|||
],
|
||||
|
||||
"letterValues": {
|
||||
"Jokers": 0,
|
||||
" ": 0,
|
||||
"О": 1,
|
||||
"А": 1,
|
||||
"И": 1,
|
||||
|
|
|
@ -446,9 +446,12 @@ function countTiles(rack) {
|
|||
return count;
|
||||
}
|
||||
|
||||
function joinGame(gameNumber) {
|
||||
function joinGame(gameNumber, message) {
|
||||
if (!gameNumber) {
|
||||
gameNumber = newGameId();
|
||||
if (message) {
|
||||
message.gameNumber = gameNumber;
|
||||
}
|
||||
}
|
||||
|
||||
const game = games[gameNumber] || (games[gameNumber] = new Game());
|
||||
|
@ -719,7 +722,7 @@ function handleCommand(cmdNumber, message, response) {
|
|||
|
||||
function handleCommands(message, responseAndType) {
|
||||
if (!message.cmds || !message.cmds.length) {
|
||||
const {gameNumber, game} = joinGame(message.gameNumber);
|
||||
const {gameNumber, game} = joinGame(message.gameNumber, message);
|
||||
|
||||
writeMessage(responseAndType,
|
||||
JSON.stringify({
|
||||
|
@ -1006,8 +1009,8 @@ function handleRequest(request, response) {
|
|||
"script-src 'self'; " +
|
||||
"style-src 'self'; " +
|
||||
"img-src 'self'; " +
|
||||
(disableCSP ? "" : "connect-src 'self' " + (
|
||||
// uzbl (like Safari 9 / iPad 2) does not like unsecure websockets on the
|
||||
(disableCSP ? "connect-src *; " : "connect-src 'self' " + (
|
||||
// uzbl (like Safari 9 / iPad 2) does not like insecure websockets on the
|
||||
// same port with connect-src 'self'
|
||||
// See https://github.com/w3c/webappsec-csp/issues/7
|
||||
"ws://" + host + ":" + port + " " +
|
||||
|
|
Loading…
Reference in New Issue