1
0
Fork 0
anagram-games/www/entryui.js

457 lines
16 KiB
JavaScript

'use strict'
// Common UI code for anagram games that involve entering letters.
function clearElement(element) {
// From https://stackoverflow.com/a/3955238
while (element.firstChild) {
element.removeChild(element.firstChild);
}
}
class AnagramEntryUI {
constructor() {
this.autoSubmit = document.getElementById('auto_submit');
this.submit = document.getElementById('submit');
this.loadSettings();
let ui = this;
this.submit
.addEventListener('click', _ => ui.submitWord());
this.autoSubmit.addEventListener('change', _ => {
ui.saveSettings();
ui.submit.style.display = ui.autoSubmit.checked ? 'none' : '';
});
document.getElementById('clear')
.addEventListener('click', _ => ui.clearAll());
document.getElementById('clear_nonlocked')
.addEventListener('click', _ => ui.clearUnlocked());
document.getElementById('newgame')
.addEventListener('click', _ => ui.nextGame());
document.getElementById('switch_game')
.addEventListener('click', _ => {
let fragment = document.getElementById('available_games').value;
if (!ui.game || fragment != ui.game.fragment) {
ui.nextGame(ui.gameClass.fromFragment(fragment));
}
});
document.addEventListener('keyup', event => {
if (!ui.game || document.activeElement.nodeName.toUpperCase() === 'INPUT') {
return;
}
if (event.key === "Enter") {
ui.submitWord();
} else if (event.key === "Backspace") {
let lastUnlocked = ui.game.numLetters - 1;
while (lastUnlocked >= 0 && ui.locks[lastUnlocked].checked) {
lastUnlocked--;
}
if (ui.focusedTextbox < lastUnlocked
|| ui.letters[lastUnlocked] == '') {
ui.focusPreviousTextbox();
}
ui.clearCurrent();
} else if (event.key === "Clear" || event.key == "Delete") {
ui.clearCurrent();
} else if (event.key === "ArrowLeft") {
ui.focusPreviousTextbox();
} else if (event.key === "ArrowRight" || event.key === "Space") {
ui.focusNextTextbox();
} else if (event.key === "Home") {
ui.setFocusedTextboxIndex(0);
} else if (event.key === "End") {
ui.setFocusedTextboxIndex(ui.game.numLetters - 1);
} else if (event.key === "/") {
let lock = ui.locks[ui.focusedTextbox];
lock.checked = !lock.checked;
lock.dispatchEvent(new Event('change'));
} else if (event.key === "?") {
ui.locks.forEach(lock => lock.checked = false);
} else if (ui.game.availableLetters.includes(event.key)) {
ui.enterLetter(event.key);
}
});
document.getElementById('copy_permalink')
.addEventListener('click', _ => {
let permalink = document.getElementById('permalink_input');
permalink.focus();
let range = document.createRange();
range.selectNodeContents(permalink);
let s = window.getSelection();
s.removeAllRanges();
s.addRange(range);
permalink.setSelectionRange(0, permalink.value.length);
document.execCommand('copy');
permalink.blur();
});
document.getElementById('newgame_button')
.addEventListener('click', _ => {
if (this.getSettingsObjectFromForm() === null) return;
ui.newGame();
});
}
initializeInputs(len) {
let ui = this;
let shuffle = document.getElementById('shuffle');
// Clear event listeners. From https://stackoverflow.com/a/19470348
let newShuffle = shuffle.cloneNode(true)
shuffle.parentNode.replaceChild(newShuffle, shuffle);
newShuffle.addEventListener('click', function() {
ui.shuffleAvailableLetters();
});
let entry = document.getElementById('letters_entry');
clearElement(entry);
this.textboxes = new Array(len);
this.letters = new Array(len);
this.letters.fill('');
this.locks = new Array(len);
this.focusedTextbox = 0;
for (let i=0; i < len; i++) {
let t = document.getElementById('letter_entry');
let clone = document.importNode(t.content, true);
let lock_checkbox = clone.querySelector('input.lock');
this.locks[i] = lock_checkbox;
lock_checkbox.id = 'lock' + i;
lock_checkbox.addEventListener('change', e => {
if (ui.locks[i].checked && ui.letters[i] == ''
&& ui.game.knownMatches !== undefined
&& ui.game.knownMatches.has(i)) {
let letter = '';
Object.values(ui.game.knownInformation).forEach(info => {
if (info.knownMatches.has(i)) {
letter = info.letter;
}
});
ui.textboxes[i].innerText = letter;
ui.letters[i] = letter;
}
if (ui.focusedTextbox == i
|| ui.locks[ui.focusedTextbox].checked) {
ui.setFocusedTextboxIndex(i);
}
});
clone.querySelector('label.lock_label').htmlFor = 'lock' + i;
let textbox = clone.querySelector('span.letter');
this.textboxes[i] = textbox;
textbox.parentNode.addEventListener('click', e => {
ui.setFocusedTextboxIndex(i);
});
entry.appendChild(clone);
}
this.shuffleAvailableLetters();
ui.setFocusedTextboxIndex(0);
document.getElementById('letters_entry').style.display = '';
document.getElementById('submit_buttons').style.display = '';
document.getElementById('endgame').style.display = 'none';
}
recolorAvailableLetters() {
// To be implemented by subclass.
}
shuffleAvailableLetters() {
let ui = this;
let letters = document.getElementById('available_letters');
clearElement(letters);
let space = document.createElement('button');
space.innerText = '\xA0'; // &nbsp;
space.addEventListener('click', button => ui.clearCurrent());
letters.appendChild(space);
shuffle(this.game.availableLetters).forEach(function(letter) {
let button = document.createElement('button');
button.innerText = letter;
button.addEventListener('click', button => ui.enterLetter(letter));
letters.appendChild(button);
});
this.recolorAvailableLetters();
}
setFocusedTextbox(textbox) {
this.setFocusedTextboxIndex(this.textboxes.indexOf(textbox));
}
setFocusedTextboxIndex(i) {
if (i < 0) i = 0;
this.focusedTextbox = i;
while (this.focusedTextbox < this.game.numLetters
&& this.locks[this.focusedTextbox].checked) {
this.focusedTextbox++;
}
if (this.focusedTextbox >= this.game.numLetters) {
this.focusedTextbox = this.game.numLetters - 1;
while (this.focusedTextbox > 0
&& this.locks[this.focusedTextbox].checked) {
this.focusedTextbox--;
}
if (this.focusedTextbox == 0 && this.locks[0].checked && i != 0) {
this.focusedTextbox = i;
}
}
for (let j = 0; j < this.textboxes.length; j++) {
this.textboxes[j].parentNode
.classList.toggle('focused', j == this.focusedTextbox);
}
this.recolorAvailableLetters();
}
focusPreviousTextbox() {
let i = this.focusedTextbox - 1;
while (i > 0 && this.locks[i].checked) {
i--;
}
this.setFocusedTextboxIndex(i);
}
focusNextTextbox() {
let i = this.focusedTextbox + 1;
if (i < this.game.numLetters) {
this.setFocusedTextboxIndex(i);
}
}
mayEnterLetter(letter) {
return this.game.availableLetters.includes(letter);
}
enterLetter(letter) {
if (!this.mayEnterLetter(letter)) {
this.textboxes[this.focusedTextbox].innerText = this.letters[this.focusedTextbox] || '\xA0';
return;
}
if (this.locks[this.focusedTextbox].checked) {
this.focusNextTextbox();
return;
}
this.textboxes[this.focusedTextbox].innerText = letter;
this.letters[this.focusedTextbox] = letter;
this.setFocusedTextboxIndex(this.focusedTextbox + 1);
if (this.autoSubmit.checked) this.submitWord();
}
textEntered(textbox) {
let index = this.textboxes.indexOf(textbox);
if (this.locks[index].checked) {
textbox.innerText = this.letters[index];
}
else {
this.setFocusedTextboxIndex(index);
this.enterLetter(textbox.innerText.slice(-1).toLowerCase());
}
}
clearAll() {
for (let i = 0; i < this.game.numLetters; i++) {
this.locks[i].checked = false;
}
this.clearUnlocked();
}
clearUnlocked() {
for (let i = 0; i < this.game.numLetters; i++) {
if (!this.locks[i].checked) {
this.textboxes[i].innerText = '\xA0';
this.letters[i] = '';
}
}
this.setFocusedTextboxIndex(0);
}
clearCurrent() {
let i = this.focusedTextbox;
if (this.locks[i].checked) return;
this.textboxes[i].innerText = '\xA0';
this.letters[i] = '';
}
setSettingsForm(obj) {
let els = document.forms['difficulty'].elements;
for (let key in obj) {
if (key === 'auto_submit') continue;
els[key].value = obj[key];
}
}
getSettingsObjectFromForm() {
let els = document.forms['difficulty'].elements;
let res = {};
let default_settings = this.defaultSettings;
for (let key in default_settings) {
if (!els[key].checkValidity()) return null;
res[key] = Number(els[key].value);
}
if (res.min_word_length > res.max_word_length
|| res.min_matches > res.max_matches) {
return null;
}
return res;
}
updateSavedGamesMenu(settings) {
let saves = settings['saved_games'];
let gameSelect = document.getElementById('available_games');
let gameFragment = this.game ? this.game.fragment : null;
clearElement(gameSelect);
for (let fragment in saves) {
let option = document.createElement('option');
option.value = fragment;
option.selected = fragment == gameFragment;
option.innerText = (option.selected ? '[*] ' : '') + new Date(saves[fragment].last_played) + " - [" + saves[fragment].guesses.length + "] " + (saves[fragment].guesses.slice(-1)[0] || "(no guesses yet)");
gameSelect.appendChild(option);
}
}
newGame() {
let settings = this.getSettingsObjectFromForm();
if (settings === null) return;
let game;
do {
game = this.gameClass.generateRandom(settings);
} while (game === null && window.confirm('Unable to generate puzzle with the given difficulty settings. Try again?'));
if (game !== null) this.initialize(game);
}
doPostInitialize() {
let fragment = this.game.fragment;
let currentSettings = this.loadSettingsJson();
if (!currentSettings) currentSettings = this.defaultSettings;
if (!('saved_games' in currentSettings)) {
currentSettings['saved_games'] = {};
}
if (fragment in currentSettings.saved_games) {
currentSettings.saved_games[fragment].guesses.forEach(guess => {
this.submitWord(guess);
});
currentSettings.saved_games[fragment].last_played = new Date().toJSON();
} else {
currentSettings.saved_games[fragment] = {
'guesses': [],
'last_played': new Date().toJSON()
};
}
this.saveSettingsJson(currentSettings);
this.updateSavedGamesMenu(currentSettings);
}
getRecentGameFragment() {
let currentSettings = this.loadSettingsJson();
if (!currentSettings) return null;
if (!('saved_games' in currentSettings)) return null;
let saves = currentSettings.saved_games;
if (!saves) return null;
let recentDate =
Object.keys(saves)
.map(fragment => saves[fragment].last_played)
.sort()
.slice(-1)[0];
for (let fragment in saves) {
if (saves[fragment].last_played == recentDate) {
return fragment;
}
}
return null;
}
nextGame(game) {
if (this.won) {
this.finishGame();
}
if (!game) {
let fragment = this.getRecentGameFragment();
game = this.gameClass.fromFragment(fragment);
}
if (game === null) {
this.newGame();
} else {
this.initialize(game);
}
}
finishGame() {
if (this.won) {
let fragment = this.game.fragment;
let currentSettings = this.loadSettingsJson();
delete currentSettings.saved_games[fragment];
this.saveSettingsJson(currentSettings);
this.updateSavedGamesMenu(currentSettings);
}
}
saveGuess(guess) {
let fragment = this.game.fragment;
let currentSettings = this.loadSettingsJson();
currentSettings.saved_games[fragment].guesses.push(guess);
currentSettings.saved_games[fragment].last_played = new Date().toJSON();
this.saveSettingsJson(currentSettings);
this.updateSavedGamesMenu(currentSettings);
}
loadSettingsJson() {
let json = window.localStorage.getItem(this.settingsKey);
if (json) {
try {
return JSON.parse(json);
} catch (e) {}
}
return null;
}
loadSettings() {
let default_settings = this.defaultSettings;
let obj = this.loadSettingsJson();
if (obj) {
let settings = {};
for (let key in default_settings) {
if (key in obj) {
settings[key] = obj[key];
} else {
settings[key] = default_settings[key];
}
}
if ('auto_submit' in obj) {
this.autoSubmit.checked = obj.auto_submit;
this.submit.style.display = this.autoSubmit.checked ? 'none' : '';
}
if ('saved_games' in obj) {
this.updateSavedGamesMenu(obj);
}
this.setSettingsForm(settings);
} else {
this.setSettingsForm(default_settings);
}
}
saveSettings() {
let settings = this.getSettingsObjectFromForm();
if (settings === null) return;
settings['auto_submit'] = this.autoSubmit.checked;
let currentSettings = this.loadSettingsJson();
if (currentSettings) {
settings['saved_games'] = currentSettings['saved_games'];
}
this.saveSettingsJson(settings);
}
saveSettingsJson(settings) {
let json = JSON.stringify(settings);
window.localStorage.setItem(this.settingsKey, json);
}
}