From cf98af897a813a4969d96127586d5fe084508b0e Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 30 May 2016 23:38:30 +0200 Subject: [PATCH 1/6] Fix alignment of lobby elements I got fooled by the left parenthesis: It has a 1px left padding for kerning, but I made the graphics as though it had no padding. Now it does and we get nice, even numbers like 80 and 160 instead of 81 and 159. --- Zatacka.css | 16 ++++++++-------- resources/kurve-lobby-controls-ready.png | Bin 4984 -> 4978 bytes 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Zatacka.css b/Zatacka.css index 38b5aa0..5ad4015 100644 --- a/Zatacka.css +++ b/Zatacka.css @@ -139,7 +139,7 @@ input[type="checkbox"]:checked + label::before { #lobby #controls { list-style-type: none; - margin-left: 81px; + margin-left: 80px; margin-top: 50px; } @@ -154,7 +154,7 @@ input[type="checkbox"]:checked + label::before { #lobby #controls .controls { background-image: url("resources/kurve-lobby-controls-ready.png"); - width: 159px; + width: 160px; } #lobby #controls .ready { @@ -168,17 +168,17 @@ input[type="checkbox"]:checked + label::before { } #lobby #controls .red.controls { background-position: 0px 0px; } -#lobby #controls .red.ready { background-position: -159px 0px; } +#lobby #controls .red.ready { background-position: -160px 0px; } #lobby #controls .yellow.controls { background-position: 0px -50px; } -#lobby #controls .yellow.ready { background-position: -159px -50px; } +#lobby #controls .yellow.ready { background-position: -160px -50px; } #lobby #controls .orange.controls { background-position: 0px -100px; } -#lobby #controls .orange.ready { background-position: -159px -100px; } +#lobby #controls .orange.ready { background-position: -160px -100px; } #lobby #controls .green.controls { background-position: 0px -150px; } -#lobby #controls .green.ready { background-position: -159px -150px; } +#lobby #controls .green.ready { background-position: -160px -150px; } #lobby #controls .pink.controls { background-position: 0px -200px; } -#lobby #controls .pink.ready { background-position: -159px -200px; } +#lobby #controls .pink.ready { background-position: -160px -200px; } #lobby #controls .blue.controls { background-position: 0px -250px; } -#lobby #controls .blue.ready { background-position: -159px -250px; } +#lobby #controls .blue.ready { background-position: -160px -250px; } #lobby footer { padding: 0 80px; diff --git a/resources/kurve-lobby-controls-ready.png b/resources/kurve-lobby-controls-ready.png index c7b91d31bbe2b5c82cfec691a09ed7c436a09127..a231a0030190a914231080b9f9d63ea65a1e6835 100644 GIT binary patch delta 2286 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 From b8ddde24680bddf53d3e7bc578ebf49e1679d362 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Wed, 1 Jun 2016 01:00:04 +0200 Subject: [PATCH 2/6] Allow multiple keys for default players keyL and keyR may now be either a key/mouse code or an array of such. --- js/Zatacka.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/js/Zatacka.js b/js/Zatacka.js index af3e317..1eecb7c 100644 --- a/js/Zatacka.js +++ b/js/Zatacka.js @@ -183,10 +183,18 @@ const Zatacka = ((window, document) => { } } + function defaultPlayerHasLeftKey(playerData, pressedKey) { + return pressedKey === playerData.keyL || (playerData.keyL instanceof Array && playerData.keyL.includes(pressedKey)); + } + + function defaultPlayerHasRightKey(playerData, pressedKey) { + return pressedKey === playerData.keyR || (playerData.keyR instanceof Array && playerData.keyR.includes(pressedKey)); + } + function addOrRemovePlayer(playerData, pressedKey) { - if (pressedKey === playerData.keyL) { + if (defaultPlayerHasLeftKey(playerData, pressedKey)) { addPlayer(playerData.id); - } else if (pressedKey === playerData.keyR) { + } else if (defaultPlayerHasRightKey(playerData, pressedKey)) { removePlayer(playerData.id); } } From 7276374b68f7d9454faa43546aab94a876661aa9 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Fri, 3 Jun 2016 01:14:31 +0200 Subject: [PATCH 3/6] Message center alignment and text-shadow --- Zatacka.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Zatacka.css b/Zatacka.css index 5ad4015..7886a2a 100644 --- a/Zatacka.css +++ b/Zatacka.css @@ -191,7 +191,8 @@ input[type="checkbox"]:checked + label::before { .message { font-size: 8px; padding: 2px 80px; - background-color: rgba(0, 0, 0, 0.5); + text-align: center; + text-shadow: 0 0 2px black, 0 0 2px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 8px black, 0 0 8px black, 0 0 8px black, 0 0 8px black; } .info.message { From bb82208534a494f92a3286bb0501637f763c773b Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Fri, 3 Jun 2016 12:11:40 +0200 Subject: [PATCH 4/6] Restructure game quit handling Before this commit, we would reload the entire document when the user requested to quit the game (and return to the lobby). That works good when hosting the game locally, but is way too slow when playing online. As of this commit, the user can quit to the lobby without reloading anything. When the user quits the game, a new Game is created and the old one is not used anymore. --- js/GUIController.js | 35 ++++++++++++++++++++++++++++++++++- js/Game.js | 25 ++++++++++++------------- js/Zatacka.js | 24 ++++++++++++++++++++---- 3 files changed, 66 insertions(+), 18 deletions(-) diff --git a/js/GUIController.js b/js/GUIController.js index 710967c..c12666a 100644 --- a/js/GUIController.js +++ b/js/GUIController.js @@ -28,6 +28,11 @@ function GUIController(cfg) { lobby.classList.add(CLASS_HIDDEN); } + function showLobby() { + log("Showing lobby."); + lobby.classList.remove(CLASS_HIDDEN); + } + function isLobbyEntry(element) { return isHTMLElement(element) && element.children.length >= 2; } @@ -40,6 +45,10 @@ function GUIController(cfg) { Array.from(scoreboard.children).forEach(resetScoreboardEntry); } + function resetResults() { + Array.from(results.children).forEach(resetScoreboardEntry); + } + // PUBLIC API @@ -67,15 +76,38 @@ function GUIController(cfg) { } } + function allPlayersUnready() { + for (let id = 1; id <= controls.children.length; id++) { + playerUnready(id); + } + } + function gameStarted() { hideLobby(); } + function gameQuit() { + hideKonecHry(); + showLobby(); + clearMessages(); + resetScoreboard(); + resetResults(); + allPlayersUnready(); + } + function konecHry() { - KONEC_HRY.classList.remove("hidden"); + showKonecHry(); resetScoreboard(); } + function showKonecHry() { + KONEC_HRY.classList.remove(CLASS_HIDDEN); + } + + function hideKonecHry() { + KONEC_HRY.classList.add(CLASS_HIDDEN); + } + function showMessage(message) { if (!currentMessages.includes(message)) { currentMessages.push(message); @@ -138,6 +170,7 @@ function GUIController(cfg) { playerReady, playerUnready, gameStarted, + gameQuit, konecHry, updateScoreOfPlayer, updateMessages, diff --git a/js/Game.js b/js/Game.js index 645bf94..0d8023c 100644 --- a/js/Game.js +++ b/js/Game.js @@ -343,11 +343,6 @@ class Game { this.beginNewRound(); } - /** Quits the game. */ - quit() { - document.location.reload(); - } - /** Announce KONEC HRY, show results etc. */ konecHry() { log(this.constructor.KONEC_HRY); @@ -356,6 +351,11 @@ class Game { this.quitHintTimer = setTimeout(this.showQuitHint.bind(this), this.config.hintDelay); } + quit() { + clearTimeout(this.quitHintTimer); + clearTimeout(this.proceedHintTimer); + } + clearField() { this.pixels.fill(0); this.Render_clearField(); @@ -562,10 +562,7 @@ class Game { proceedKeyPressed() { this.hideProceedHint(); this.hideQuitHint(); - if (this.isEnded()) { - // The game is ended, so a proceed key press should quit: - this.quit(); - } else if (this.isGameOver()) { + if (this.isGameOver()) { // The game is over, so we should show KONEC HRY: this.konecHry(); } else if (this.isPostRound()) { @@ -574,10 +571,12 @@ class Game { } } - quitKeyPressed() { - if (this.isPostRound() && !this.isGameOver()) { - this.quit(); - } + shouldQuitOnQuitKey() { + return this.isPostRound() && !this.isGameOver(); + } + + shouldQuitOnProceedKey() { + return this.isEnded(); } diff --git a/js/Zatacka.js b/js/Zatacka.js index 1eecb7c..427670d 100644 --- a/js/Zatacka.js +++ b/js/Zatacka.js @@ -228,15 +228,27 @@ const Zatacka = ((window, document) => { mouseClickedInLobby(event.button); } + function quitGame() { + removeGameEventListeners(); + addLobbyEventListeners(); + game.quit(); + guiController.gameQuit(); + game = newGame(); + } + function gameKeyHandler(event) { const pressedKey = event.keyCode; if (shouldPreventDefault(pressedKey)) { event.preventDefault(); } if (isProceedKey(pressedKey)) { - game.proceedKeyPressed(); - } else if (isQuitKey(pressedKey)) { - game.quitKeyPressed(); + if (game.shouldQuitOnProceedKey()) { + quitGame(); + } else { + game.proceedKeyPressed(); + } + } else if (isQuitKey(pressedKey) && game.shouldQuitOnQuitKey()) { + quitGame(); } } @@ -281,8 +293,12 @@ const Zatacka = ((window, document) => { addLobbyEventListeners(); + function newGame() { + return new Game(config, Renderer(canvas_main, canvas_overlay), guiController); + } + const guiController = GUIController(config); - const game = new Game(config, Renderer(canvas_main, canvas_overlay), guiController); + let game = newGame(); let hintProceedTimer; let hintPickTimer = setTimeout(() => { From 83a2dcb576ceb01b8e34a2248aa813708c100fbd Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Fri, 3 Jun 2016 14:43:12 +0200 Subject: [PATCH 5/6] Implement preferences backend As of this commit, it has preferences for cursor behavior and hints. --- ZATACKA.html | 7 ++ Zatacka.css | 4 + js/GUIController.js | 43 +++++++-- js/Player.js | 4 + js/Zatacka.js | 45 +++++++++- js/lib/Utilities.js | 4 + js/lib/preferences/BooleanPreference.js | 19 ++++ js/lib/preferences/IntegerRangePreference.js | 27 ++++++ js/lib/preferences/MultichoicePreference.js | 30 +++++++ js/lib/preferences/Preference.js | 33 +++++++ js/lib/preferences/PreferenceManager.js | 93 ++++++++++++++++++++ js/lib/preferences/RangePreference.js | 29 ++++++ js/strings.js | 17 ++++ 13 files changed, 346 insertions(+), 9 deletions(-) create mode 100644 js/lib/preferences/BooleanPreference.js create mode 100644 js/lib/preferences/IntegerRangePreference.js create mode 100644 js/lib/preferences/MultichoicePreference.js create mode 100644 js/lib/preferences/Preference.js create mode 100644 js/lib/preferences/PreferenceManager.js create mode 100644 js/lib/preferences/RangePreference.js create mode 100644 js/strings.js diff --git a/ZATACKA.html b/ZATACKA.html index dc55ddf..9edb4af 100644 --- a/ZATACKA.html +++ b/ZATACKA.html @@ -100,6 +100,7 @@ + @@ -107,6 +108,12 @@ + + + + + + diff --git a/Zatacka.css b/Zatacka.css index 7886a2a..6e8caa3 100644 --- a/Zatacka.css +++ b/Zatacka.css @@ -60,6 +60,10 @@ input[type="checkbox"]:checked + label::before { background-repeat: no-repeat; } +.nocursor { + cursor: none; +} + #debug { background-color: black; border: 1px white solid; diff --git a/js/GUIController.js b/js/GUIController.js index c12666a..6a904fb 100644 --- a/js/GUIController.js +++ b/js/GUIController.js @@ -2,8 +2,9 @@ function GUIController(cfg) { - const CLASS_ACTIVE = "active"; - const CLASS_HIDDEN = "hidden"; + const CURSOR_VISIBLE = "visible"; + const CURSOR_HIDDEN_ON_CANVAS = "hidden_on_canvas"; + const CURSOR_HIDDEN = "hidden"; const config = cfg; const lobby = byID("lobby"); @@ -25,12 +26,12 @@ function GUIController(cfg) { function hideLobby() { log("Hiding lobby."); - lobby.classList.add(CLASS_HIDDEN); + lobby.classList.add(STRINGS.class_hidden); } function showLobby() { log("Showing lobby."); - lobby.classList.remove(CLASS_HIDDEN); + lobby.classList.remove(STRINGS.class_hidden); } function isLobbyEntry(element) { @@ -49,6 +50,27 @@ function GUIController(cfg) { Array.from(results.children).forEach(resetScoreboardEntry); } + function setCursorBehavior(behavior) { + switch (behavior) { + case CURSOR_VISIBLE: + document.body.classList.remove(STRINGS.class_nocursor); + break; + case CURSOR_HIDDEN_ON_CANVAS: + canvas_main.classList.add(STRINGS.class_nocursor); + canvas_overlay.classList.add(STRINGS.class_nocursor); + break; + case CURSOR_HIDDEN: + document.body.classList.add(STRINGS.class_nocursor); + break; + default: + logError(`Cannot set cursor behavior to '${behavior}'.`); + } + } + + function resetCursorBehavior() { + setCursorBehavior(CURSOR_VISIBLE); + } + // PUBLIC API @@ -62,7 +84,7 @@ function GUIController(cfg) { if (!isLobbyEntry(entry)) { logWarning(`Cannot mark player ${id} as ready because controls.children[${index}] (${controls.children[index]}) is not a valid lobby entry.`); } else { - entry.children[1].classList.add(CLASS_ACTIVE); + entry.children[1].classList.add(STRINGS.class_active); } } @@ -72,7 +94,7 @@ function GUIController(cfg) { if (!isLobbyEntry(entry)) { logWarning(`Cannot mark player ${id} as unready because controls.children[${index}] (${controls.children[index]}) is not a valid lobby entry.`); } else { - entry.children[1].classList.remove(CLASS_ACTIVE); + entry.children[1].classList.remove(STRINGS.class_active); } } @@ -93,6 +115,7 @@ function GUIController(cfg) { resetScoreboard(); resetResults(); allPlayersUnready(); + resetCursorBehavior(); } function konecHry() { @@ -101,11 +124,11 @@ function GUIController(cfg) { } function showKonecHry() { - KONEC_HRY.classList.remove(CLASS_HIDDEN); + KONEC_HRY.classList.remove(STRINGS.class_hidden); } function hideKonecHry() { - KONEC_HRY.classList.add(CLASS_HIDDEN); + KONEC_HRY.classList.add(STRINGS.class_hidden); } function showMessage(message) { @@ -167,6 +190,9 @@ function GUIController(cfg) { } return { + CURSOR_VISIBLE, + CURSOR_HIDDEN_ON_CANVAS, + CURSOR_HIDDEN, playerReady, playerUnready, gameStarted, @@ -177,6 +203,7 @@ function GUIController(cfg) { showMessage, hideMessage, clearMessages, + setCursorBehavior, setEdgePadding }; diff --git a/js/Player.js b/js/Player.js index 1593c4e..7d81be0 100644 --- a/js/Player.js +++ b/js/Player.js @@ -90,6 +90,10 @@ class Player { || this.R_keys.includes(button)); } + usesAnyMouseButton() { + return MOUSE_BUTTONS.some((button) => this.hasMouseButton(button)); + } + hasKey(key) { return this.L_keys.includes(key) || this.R_keys.includes(key); diff --git a/js/Zatacka.js b/js/Zatacka.js index 427670d..4a3058e 100644 --- a/js/Zatacka.js +++ b/js/Zatacka.js @@ -48,6 +48,31 @@ const Zatacka = ((window, document) => { ]) }); + const PREFERENCES = Object.freeze([ + { + type: MultichoicePreference, + key: STRINGS.pref_key_cursor, + values: [ + STRINGS.pref_value_cursor_always_visible, + STRINGS.pref_value_cursor_hidden_when_mouse_used_by_player, + STRINGS.pref_value_cursor_always_hidden + ], + default: STRINGS.pref_value_cursor_hidden_when_mouse_used_by_player + }, + { + type: MultichoicePreference, + key: STRINGS.pref_key_hints, + values: [ + STRINGS.pref_value_hints_all, + STRINGS.pref_value_hints_warnings_only, + STRINGS.pref_value_hints_none + ], + default: STRINGS.pref_value_hints_all + } + ]); + + const preferenceManager = new PreferenceManager(PREFERENCES); + function isProceedKey(key) { return config.keys.proceed.includes(key); } @@ -122,6 +147,23 @@ const Zatacka = ((window, document) => { getPaddedHoleConfig()); } + function applyCursorBehavior() { + const mouseIsBeingUsed = game.getPlayers().some(hasMouseButton); + let behavior; + switch (preferenceManager.get(STRINGS.pref_key_cursor)) { + case STRINGS.pref_value_cursor_hidden_when_mouse_used_by_player: + behavior = mouseIsBeingUsed ? guiController.CURSOR_HIDDEN : guiController.CURSOR_VISIBLE; + break; + case STRINGS.pref_value_cursor_always_hidden: + behavior = guiController.CURSOR_HIDDEN; + break; + default: + behavior = guiController.CURSOR_VISIBLE; + } + log(`Setting cursor behavior to ${behavior}.`); + guiController.setCursorBehavior(behavior); + } + function proceedKeyPressedInLobby() { const numberOfReadyPlayers = game.getNumberOfPlayers(); if (numberOfReadyPlayers > 0) { @@ -130,13 +172,14 @@ const Zatacka = ((window, document) => { guiController.clearMessages(); removeLobbyEventListeners(); addGameEventListeners(); + applyCursorBehavior(); game.setMode(numberOfReadyPlayers === 1 ? Game.PRACTICE : Game.COMPETITIVE); game.start(); } } function hasMouseButton(player) { - return Object.keys(MOUSE).some((buttonName) => player.hasMouseButton(MOUSE[buttonName])); + return player.usesAnyMouseButton(); } function checkForDangerousInput() { diff --git a/js/lib/Utilities.js b/js/lib/Utilities.js index f36b9ef..bd0161f 100644 --- a/js/lib/Utilities.js +++ b/js/lib/Utilities.js @@ -28,6 +28,10 @@ const F_KEYS = Object.freeze([ KEY.F1, KEY.F2, KEY.F3, KEY.F4, KEY.F5, KEY.F6, KEY.F7, KEY.F8, KEY.F9, KEY.F10, KEY.F11, KEY.F12 ]); +const MOUSE_BUTTONS = Object.freeze([ + MOUSE.LEFT, MOUSE.RIGHT, MOUSE.MIDDLE, MOUSE.MOUSE4, MOUSE.MOUSE5 +]); + function isObject(obj) { return typeOf(obj) === "object"; } diff --git a/js/lib/preferences/BooleanPreference.js b/js/lib/preferences/BooleanPreference.js new file mode 100644 index 0000000..f0ec9a4 --- /dev/null +++ b/js/lib/preferences/BooleanPreference.js @@ -0,0 +1,19 @@ +"use strict"; + +class BooleanPreference extends MultichoicePreference { + constructor(data) { + super({ + key: data.key, + values: ["true", "false"], + default: data.default + }); + } + + static stringify(value) { + return value.toString(); + } + + static parse(stringifiedValue) { + return stringifiedValue === "true"; + } +} diff --git a/js/lib/preferences/IntegerRangePreference.js b/js/lib/preferences/IntegerRangePreference.js new file mode 100644 index 0000000..92459d4 --- /dev/null +++ b/js/lib/preferences/IntegerRangePreference.js @@ -0,0 +1,27 @@ +"use strict"; + +class IntegerRangePreference extends RangePreference { + constructor(data) { + if (!isInt(data.min) || !isInt(data.max)) { + throw new TypeError(`min and max must be integers (found ${data.min} and ${data.max} for preference '${data.key}').`); + } + super(data); + this.min = data.min; + this.max = data.max; + if (!this.isValidValue(data.default)) { + super.invalidValue(data.default); + } + } + + isValidValue(value) { + return isInt(value) && value >= this.min && value <= this.max; + } + + static stringify(value) { + return value.toString(); + } + + static parse(stringifiedValue) { + return parseInt(stringifiedValue); + } +} diff --git a/js/lib/preferences/MultichoicePreference.js b/js/lib/preferences/MultichoicePreference.js new file mode 100644 index 0000000..73ca245 --- /dev/null +++ b/js/lib/preferences/MultichoicePreference.js @@ -0,0 +1,30 @@ +"use strict"; + +class MultichoicePreference extends Preference { + constructor(data) { + if (!isNonEmptyStringArray(data.values)) { + throw new TypeError(`values must be a non-empty string array (found ${data.values} for preference '${data.key}').`); + } + super(data); + this.values = data.values; + if (!this.isValidValue(data.default)) { + super.invalidValue(data.default); + } + + function isNonEmptyStringArray(strings) { + return strings instanceof Array && strings.length > 0 && strings.every(isString); + } + } + + isValidValue(value) { + return this.values.indexOf(value) > -1; + } + + static stringify(value) { + return value; + } + + static parse(stringifiedValue) { + return stringifiedValue; + } +} diff --git a/js/lib/preferences/Preference.js b/js/lib/preferences/Preference.js new file mode 100644 index 0000000..d82f17e --- /dev/null +++ b/js/lib/preferences/Preference.js @@ -0,0 +1,33 @@ +"use strict"; + +class Preference { + constructor(data) { + if (!isString(data.key)) { + throw new TypeError(`key must be a string (found ${data.key}). More info: ${data}`); + } else if (data.default === undefined) { + throw new TypeError(`Preference '${data.key}' must have a default value.`); + } + this.key = data.key; + this.default = data.default; + } + + isValidValue(value) { + return isString(value); + } + + invalidValue(value) { + throw new TypeError(`${value} is not a valid value for preference '${this.key}'.`); + } + + static stringify(value) { + return value.toString(); + } + + static parse(stringifiedValue) { + return stringifiedValue; + } + + getDefaultValue() { + return this.default; + } +} diff --git a/js/lib/preferences/PreferenceManager.js b/js/lib/preferences/PreferenceManager.js new file mode 100644 index 0000000..3bf58b0 --- /dev/null +++ b/js/lib/preferences/PreferenceManager.js @@ -0,0 +1,93 @@ +"use strict"; + +function PreferenceManager(preferencesData) { + const LOCALSTORAGE_PREFIX = "pref_key_"; + + // Parse and validate preferences: + log("Validating preferences ..."); + const PREFERENCES = parsePreferences(preferencesData); + log("Done."); + + function parsePreferences(preferencesData) { + return preferencesData.map(parsePreference); + } + + function parsePreference(pref, index) { + if (!isString(pref.key)) { + throw new TypeError(`'The preference at index ${index} does not have a valid key (found ${pref.key}).`); + } else if (pref.type === undefined || !(pref.type.prototype instanceof Preference)) { + throw new TypeError(`Preference '${pref.key}' does not use a valid preference type (found ${pref.type}).`); + } else if (pref.default === undefined) { + throw new TypeError(`Preference '${pref.key}' has no default value.`); + } + return new (pref.type)(pref); + } + + function preferenceExists(key) { + return getPreference(key) !== undefined; + } + + function getPreference(key) { + return PREFERENCES.find((pref) => pref.key === key); + } + + function getKey(pref) { + return pref.key; + } + + function isValidPreferenceValue(key, value) { + return getPreference(key).isValidValue(value); + } + + function setToDefaultValue(key) { + set(key, getDefaultValue(key)); + } + + function getDefaultValue(key) { + if (!preferenceExists(key)) { + throw new Error(`Preference ${key} does not exist.`); + } + return getPreference(key).getDefaultValue(); + } + + function LS_prefix(key) { + return LOCALSTORAGE_PREFIX + key; + } + + function set(key, value) { + if (!preferenceExists(key)) { + throw new Error(`There is no preference with key '${key}'.`); + } + const pref = getPreference(key); + if (!isValidPreferenceValue(key, value)) { + pref.invalidValue(value); + } else { + log(`Setting preference ${key} to ${value}.`); + localStorage.setItem(LS_prefix(key), pref.stringify(value)); + } + } + + function get(key) { + if (!preferenceExists(key)) { + throw new Error(`There is no preference with key '${key}'.`); + } + const pref = getPreference(key); + const savedValue = localStorage.getItem(LS_prefix(key)); + return isValidPreferenceValue(key, savedValue) ? pref.parse(savedValue) : getDefaultValue(key); + } + + function setAllToDefault() { + log("Resetting all preferences ..."); + PREFERENCES.map(getKey).forEach(setToDefaultValue); + log("Done."); + } + + return { + isValidPreferenceValue, + set, + get, + setToDefaultValue, + getDefaultValue, + setAllToDefault + } +} \ No newline at end of file diff --git a/js/lib/preferences/RangePreference.js b/js/lib/preferences/RangePreference.js new file mode 100644 index 0000000..38826ee --- /dev/null +++ b/js/lib/preferences/RangePreference.js @@ -0,0 +1,29 @@ +"use strict"; + +class RangePreference extends Preference { + constructor(data) { + if (!isNumber(data.min) || !isNumber(data.max)) { + throw new TypeError(`min and max must be numbers (found ${data.min} and ${data.max} for preference '${data.key}').`); + } else if (data.min > data.max) { + throw new TypeError(`min cannot be greater than max (found ${data.min} and ${data.max} for preference '${data.key}', respectively).`); + } + super(data); + this.min = data.min; + this.max = data.max; + if (!this.isValidValue(data.default)) { + super.invalidValue(data.default) + } + } + + isValidValue(value) { + return isNumber(value) && value >= this.min && value <= this.max; + } + + static stringify(value) { + return value.toString(); + } + + static parse(stringifiedValue) { + return parseFloat(stringifiedValue); + } +} diff --git a/js/strings.js b/js/strings.js new file mode 100644 index 0000000..8bb4816 --- /dev/null +++ b/js/strings.js @@ -0,0 +1,17 @@ +"use strict"; + +const STRINGS = Object.freeze({ + class_hidden: "hidden", + class_active: "active", + class_nocursor: "nocursor", + + pref_key_cursor: "cursor", + pref_value_cursor_always_visible: "always_visible", + pref_value_cursor_hidden_when_mouse_used_by_player: "hidden_when_mouse_used_by_player", + pref_value_cursor_always_hidden: "always_hidden", + + pref_key_hints: "hints", + pref_value_hints_all: "all", + pref_value_hints_warnings_only: "warnings", + pref_value_hints_none: "none", +}); From 9d17f870bc1f5f5849a3d24447b9f11b63ce1d93 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Fri, 3 Jun 2016 14:55:00 +0200 Subject: [PATCH 6/6] Add TKL keys for Pink With this layout for keyboards without numpad, Pink can use either End or PgDn as their LEFT key, and PgUp as their RIGHT key. --- js/Zatacka.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/js/Zatacka.js b/js/Zatacka.js index 4a3058e..9793abf 100644 --- a/js/Zatacka.js +++ b/js/Zatacka.js @@ -39,12 +39,12 @@ const Zatacka = ((window, document) => { mouse: new WarningMessage(text.hint_mouse), }), defaultPlayers: Object.freeze([ - { id: 1, name: "Red" , color: "#FF2800", keyL: KEY["1"] , keyR: KEY.Q }, - { id: 2, name: "Yellow", color: "#C3C300", keyL: KEY.CTRL , keyR: KEY.ALT }, - { id: 3, name: "Orange", color: "#FF7900", keyL: KEY.M , keyR: KEY.COMMA }, - { id: 4, name: "Green" , color: "#00CB00", keyL: KEY.LEFT_ARROW, keyR: KEY.DOWN_ARROW }, - { id: 5, name: "Pink" , color: "#DF51B6", keyL: KEY.DIVIDE , keyR: KEY.MULTIPLY }, - { id: 6, name: "Blue" , color: "#00A2CB", keyL: MOUSE.LEFT , keyR: MOUSE.RIGHT } + { id: 1, name: "Red" , color: "#FF2800", keyL: KEY["1"] , keyR: KEY.Q }, + { id: 2, name: "Yellow", color: "#C3C300", keyL: KEY.CTRL , keyR: KEY.ALT }, + { id: 3, name: "Orange", color: "#FF7900", keyL: KEY.M , keyR: KEY.COMMA }, + { id: 4, name: "Green" , color: "#00CB00", keyL: KEY.LEFT_ARROW , keyR: KEY.DOWN_ARROW }, + { id: 5, name: "Pink" , color: "#DF51B6", keyL: [ KEY.DIVIDE, KEY.END, KEY.PAGE_DOWN ], keyR: [ KEY.MULTIPLY, KEY.PAGE_UP ] }, + { id: 6, name: "Blue" , color: "#00A2CB", keyL: MOUSE.LEFT , keyR: MOUSE.RIGHT } ]) });