const KEY_NAMES = new Proxy( { ['ArrowLeft']: '←', ['←']: 'ArrowLeft', ['ArrowRight']: '→', ['→']: 'ArrowRight', ['ArrowUp']: '↑', ['↑']: 'ArrowUp', ['ArrowDown']: '↓', ['↓']: 'ArrowDown', [' ']: 'Espace', ['Espace']: ' ', ['Escape']: 'Échap.', ['Échap.']: 'Escape', ['Backspace']: 'Ret. arrière', ['Ret. arrière']: 'Backspace', ['Enter']: 'Entrée', ['Entrée']: 'Enter', }, { get(target, key) { return key in target ? target[key] : key; }, }, ); class Settings { constructor() { this.form = settingsForm; this.load(); this.modal = new bootstrap.Modal('#settingsModal'); settingsModal.addEventListener('shown.bs.modal', () => resumeButton.focus()); } load() { this.form.querySelectorAll('[name]').forEach(element => { if (element.name in localStorage) element.value = localStorage[element.name]; }); if (localStorage['skinURL']) { if ($('#skinURLSelect').find("option[value='" + localStorage['skinURL'] + "']").length) { $('#skinURLSelect').val(localStorage['skinURL']).trigger('change'); } else { var option = new Option( 'Source externe', localStorage['skinURL'], true, true, ); $('#skinURLSelect').append(option).trigger('change'); } document.documentElement.style.setProperty( '--skin-url', `url(${localStorage['skinURL']})`, ); } stylesheetSelect.oninput(); } save() { this.form .querySelectorAll('[name]') .forEach(element => (localStorage[element.name] = element.value)); } init() { this.form.onsubmit = newGame; levelInput.name = 'startLevel'; levelInput.disabled = false; titleHeader.innerHTML = 'QUATUOR'; resumeButton.innerHTML = 'Jouer'; } show() { resumeButton.disabled = false; settings.form.classList.remove('was-validated'); settings.modal.show(); settings.form.reportValidity(); } getInputs() { for (let input of this.form.querySelectorAll("input[type='text']")) { this[input.name] = KEY_NAMES[input.value]; } for (let input of this.form.querySelectorAll("input[type='number'], input[type='range']")) { this[input.name] = input.valueAsNumber; } for (let input of this.form.querySelectorAll("input[type='checkbox']")) { this[input.name] = input.checked == true; } this.keyBind = new Proxy( {}, { get: (target, key) => target[key.toLowerCase()], set: (target, key, value) => (target[key.toLowerCase()] = value), has: (target, key) => key.toLowerCase() in target, }, ); for (let actionName in playerActions) { this.keyBind[settings[actionName]] = playerActions[actionName]; } } } function changeKey(input) { prevValue = input.value; input.value = ''; keyInputs = Array.from(input.form.querySelectorAll("input[type='text']")); input.onkeydown = function (event) { event.preventDefault(); input.value = KEY_NAMES[event.key]; keyInputs.forEach(input => { input.setCustomValidity(''); input.classList.remove('is-invalid'); }); keyInputs.sort((input1, input2) => { if (input1.value == input2.value) { input1.setCustomValidity('Touche déjà utilisée'); input1.classList.add('is-invalid'); input2.setCustomValidity('Touche déjà utilisée'); input2.classList.add('is-invalid'); } return input1.value > input2.value; }); if (input.checkValidity()) { input.blur(); } }; input.onblur = function (event) { if (!input.value) input.value = prevValue; input.onkeydown = null; input.onblur = null; }; } class Stats { constructor() { this.modal = new bootstrap.Modal('#statsModal'); this.load(); } load() { this.highScore = Number(localStorage['highScore']) || 0; } init() { levelInput.value = localStorage['startLevel'] || 1; this.score = 0; this.goal = 0; this.combo = 0; this.b2b = 0; this.startTime = new Date(); this.lockDelay = DELAY.LOCK; this.totalClearedLines = 0; this.nbQuatuors = 0; this.nbTSpin = 0; this.maxCombo = 0; this.maxB2B = 0; } set score(score) { this._score = score; scoreCell.innerText = score.toLocaleString(); if (score > this.highScore) { this.highScore = score; } } get score() { return this._score; } set highScore(highScore) { this._highScore = highScore; highScoreCell.innerText = highScore.toLocaleString(); } get highScore() { return this._highScore; } set level(level) { this._level = level; this.goal += level * 5; if (level <= 20) { this.fallPeriod = 1000 * Math.pow(0.8 - (level - 1) * 0.007, level - 1); } if (level > 15) this.lockDelay = 500 * Math.pow(0.9, level - 15); levelInput.value = level; levelCell.innerText = level; messagesSpan.addNewChild('div', { className: 'show-level-animation', innerHTML: `