From 3bb9f302ed8c559005672757ae3d9631ec2d47a9 Mon Sep 17 00:00:00 2001 From: adrien Date: Mon, 9 Mar 2026 21:03:45 +0100 Subject: [PATCH] improve jstris skin --- css/jstris-skin.css | 16 +- index.html | 33 ++- js/interface.js | 520 +++++++++++++++++++++++--------------------- 3 files changed, 296 insertions(+), 273 deletions(-) diff --git a/css/jstris-skin.css b/css/jstris-skin.css index 7c577da..1d69015 100644 --- a/css/jstris-skin.css +++ b/css/jstris-skin.css @@ -11,6 +11,7 @@ background-image: var(--skin-url); background-size: cover; background-repeat: no-repeat; + vertical-align: middle; } .result { @@ -20,7 +21,7 @@ .selection { background-position-x: calc(-1 * var(--sprite-pos) * var(--cell-size)); --nb-sprites: 4; - --sprite-pos: 3; + --sprite-pos: 4; } .minoes-table { @@ -30,8 +31,7 @@ #matrixCard { background-image: url(jstris-skin/jstris-grid.png); background-position: bottom; - background-repeat: no-repeat; - background-blend-mode: screen; + background-repeat: no-repeat; } tr.matrix td:not(.mino) { @@ -76,13 +76,19 @@ tr.matrix td:not(.mino) { } .ghost { - --sprite-pos: 0; + --sprite-pos: 1; + opacity: 50%; } .disabled { - --sprite-pos: 1; + --sprite-pos: 0; } .locking.mino { filter: saturate(60%) brightness(180%); } + +#holdTable .mino, +#nextTable .mino { + box-shadow: 4px 4px 10px #0002; +} \ No newline at end of file diff --git a/index.html b/index.html index efc414e..85d967c 100644 --- a/index.html +++ b/index.html @@ -76,19 +76,10 @@
Interface - -
-
-
- - -
-
-
-
Partie diff --git a/js/interface.js b/js/interface.js index 9328126..dc37596 100644 --- a/js/interface.js +++ b/js/interface.js @@ -1,384 +1,404 @@ -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 - } -}) - +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()) + 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] - }) - selectedStyleSheet.href = stylesheetSelect.value - if (stylesheetSelect.value === "css/tetrio-skin.css" || stylesheetSelect.value === "css/jstris-skin.css") { - skinURLdiv.style.setProperty('display', 'flex') - } else { - skinURLdiv.style.setProperty('display', 'none') - } - - fetch('https://konsola5.github.io/jstris-customization-database/jstrisCustomizationDatabase.json') - .then(response => response.json()) - .then(json => { - for (const group in json) { - const optgroup = document.createElement('optgroup'); - optgroup.label = group; - json[group].forEach(skin => { - const option = document.createElement('option'); - option.value = skin.link; - option.textContent = `${skin.name} (${skin.author})`; - optgroup.appendChild(option); - }); - skinURLInput.appendChild(optgroup); - } - - $('#skinURLInput').select2({ - templateResult: state => state.id ? $(``): state.text, - templateSelection: state => state.id ? $(``): state.text, - theme: 'bootstrap-5', - width: '100%', - selectionCssClass: 'form-select', - dropdownAutoWidth: true, - dropdownParent: $('#settingsModal'), - placeholder: "URL de l'image", - tags: true, - createTag: function(params) { - return { - id: params.term, - text: "Ajouté manuellement", - newTag: true - }; - }, - }) - if (localStorage["skinURL"]) { - if ($('#skinURLInput').find("option[value='" + localStorage["skinURL"] + "']").length) { - $('#skinURLInput').val(localStorage["skinURL"]).trigger('change'); - } else { - var option = new Option(localStorage["skinURL"], "Ajouté manuellement", localStorage["skinURL"], true, true); - $('#skinURLInput').append(option).trigger('change'); - } - document.documentElement.style.setProperty('--skin-url', `url(${localStorage["skinURL"]})`) - } + this.form.querySelectorAll('[name]').forEach(element => { + if (element.name in localStorage) element.value = localStorage[element.name]; }); + selectedStyleSheet.href = stylesheetSelect.value; + skinURLSelect.disabled = stylesheetSelect.value !== 'css/jstris-skin.css'; + + fetch( + 'https://konsola5.github.io/jstris-customization-database/jstrisCustomizationDatabase.json', + ) + .then(response => response.json()) + .then(json => { + for (const group in json) { + const optgroup = document.createElement('optgroup'); + optgroup.label = group; + json[group].forEach(skin => { + const option = document.createElement('option'); + option.value = skin.link; + option.textContent = `${skin.name} (${skin.author})`; + optgroup.appendChild(option); + }); + skinURLSelect.appendChild(optgroup); + } + + $('#skinURLSelect').select2({ + templateResult: state => + state.id + ? $(``) + : state.text, + templateSelection: state => + state.id + ? $(``) + : state.text, + theme: 'bootstrap-5', + width: '100%', + selectionCssClass: 'form-select', + dropdownAutoWidth: true, + dropdownParent: $('#settingsModal'), + placeholder: "URL de l'image", + tags: true, + }); + if (localStorage['skinURL']) { + if ( + $('#skinURLSelect').find("option[value='" + localStorage['skinURL'] + "']") + .length + ) { + $('#skinURLSelect').val(localStorage['skinURL']).trigger('change'); + } else { + var option = new Option( + localStorage['skinURL'], + 'Ajouté manuellement', + localStorage['skinURL'], + true, + true, + ); + $('#skinURLSelect').append(option).trigger('change'); + } + document.documentElement.style.setProperty( + '--skin-url', + `url(${localStorage['skinURL']})`, + ); + } + }); } save() { - this.form.querySelectorAll("[name]").forEach(element => localStorage[element.name] = element.value) + 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" + 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() + 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] + 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 + this[input.name] = input.valueAsNumber; } for (let input of this.form.querySelectorAll("input[type='checkbox']")) { - this[input.name] = input.checked == true + 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 - - }) + + 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] + this.keyBind[settings[actionName]] = playerActions[actionName]; } } } function changeKey(input) { - prevValue = input.value - input.value = "" - keyInputs = Array.from(input.form.querySelectorAll("input[type='text']")) + 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] + event.preventDefault(); + input.value = KEY_NAMES[event.key]; keyInputs.forEach(input => { - input.setCustomValidity("") - input.classList.remove("is-invalid") - }) + 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") + 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 - }) + return input1.value > input2.value; + }); if (input.checkValidity()) { - input.blur() + input.blur(); } - } + }; input.onblur = function (event) { - if (!input.value) input.value = prevValue - input.onkeydown = null - input.onblur = null - } + if (!input.value) input.value = prevValue; + input.onkeydown = null; + input.onblur = null; + }; } - class Stats { constructor() { - this.modal = new bootstrap.Modal('#statsModal') - this.load() + this.modal = new bootstrap.Modal('#statsModal'); + this.load(); } load() { - this.highScore = Number(localStorage["highScore"]) || 0 + 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 + 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() + this._score = score; + scoreCell.innerText = score.toLocaleString(); if (score > this.highScore) { - this.highScore = score + this.highScore = score; } } get score() { - return this._score + return this._score; } set highScore(highScore) { - this._highScore = highScore - highScoreCell.innerText = highScore.toLocaleString() + this._highScore = highScore; + highScoreCell.innerText = highScore.toLocaleString(); } get highScore() { - return this._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) + 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: `

NIVEAU
${this.level}

` }) + 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: `

NIVEAU
${this.level}

`, + }); } get level() { - return this._level + return this._level; } set goal(goal) { - this._goal = goal - goalCell.innerText = goal + this._goal = goal; + goalCell.innerText = goal; } get goal() { - return this._goal + return this._goal; } set combo(combo) { - this._combo = combo - if (combo > this.maxCombo) this.maxCombo = combo + this._combo = combo; + if (combo > this.maxCombo) this.maxCombo = combo; } get combo() { - return this._combo + return this._combo; } set b2b(b2b) { - this._b2b = b2b - if (b2b > this.maxB2B) this.maxB2B = b2b + this._b2b = b2b; + if (b2b > this.maxB2B) this.maxB2B = b2b; } get b2b() { - return this._b2b + return this._b2b; } set time(time) { - this.startTime = new Date() - time - ticktack() + this.startTime = new Date() - time; + ticktack(); } get time() { - return new Date() - this.startTime + return new Date() - this.startTime; } lockDown(tSpin, nbClearedLines) { - this.totalClearedLines += nbClearedLines - if (nbClearedLines == 4) this.nbQuatuors++ - if (tSpin == T_SPIN.T_SPIN) this.nbTSpin++ + this.totalClearedLines += nbClearedLines; + if (nbClearedLines == 4) this.nbQuatuors++; + if (tSpin == T_SPIN.T_SPIN) this.nbTSpin++; // Cleared lines & T-Spin - let awardedLineClears = AWARDED_LINE_CLEARS[tSpin][nbClearedLines] - let patternScore = 100 * this.level * awardedLineClears - if (tSpin) messagesSpan.addNewChild("div", { - className: "rotate-in-animation", - innerHTML: tSpin - }) - if (nbClearedLines) messagesSpan.addNewChild("div", { - className: "zoom-in-animation", - innerHTML: CLEARED_LINES_NAMES[nbClearedLines] - }) + let awardedLineClears = AWARDED_LINE_CLEARS[tSpin][nbClearedLines]; + let patternScore = 100 * this.level * awardedLineClears; + if (tSpin) + messagesSpan.addNewChild('div', { + className: 'rotate-in-animation', + innerHTML: tSpin, + }); + if (nbClearedLines) + messagesSpan.addNewChild('div', { + className: 'zoom-in-animation', + innerHTML: CLEARED_LINES_NAMES[nbClearedLines], + }); if (patternScore) { - messagesSpan.addNewChild("div", { - className: "zoom-in-animation", - style: "animation-delay: .2s", - innerHTML: patternScore - }) - this.score += patternScore + messagesSpan.addNewChild('div', { + className: 'zoom-in-animation', + style: 'animation-delay: .2s', + innerHTML: patternScore, + }); + this.score += patternScore; } // Combo if (nbClearedLines) { - this.combo++ + this.combo++; if (this.combo >= 1) { - let comboScore = (nbClearedLines == 1 ? 20 : 50) * this.combo * this.level + let comboScore = (nbClearedLines == 1 ? 20 : 50) * this.combo * this.level; if (this.combo == 1) { - messagesSpan.addNewChild("div", { - className: "zoom-in-animation", - style: "animation-delay: .4s", - innerHTML: `COMBO
${comboScore}` - }) + messagesSpan.addNewChild('div', { + className: 'zoom-in-animation', + style: 'animation-delay: .4s', + innerHTML: `COMBO
${comboScore}`, + }); } else { - messagesSpan.addNewChild("div", { - className: "zoom-in-animation", - style: "animation-delay: .4s", - innerHTML: `COMBO x${this.combo}
${comboScore}` - }) + messagesSpan.addNewChild('div', { + className: 'zoom-in-animation', + style: 'animation-delay: .4s', + innerHTML: `COMBO x${this.combo}
${comboScore}`, + }); } - this.score += comboScore + this.score += comboScore; } } else { - this.combo = -1 + this.combo = -1; } // Back to back sequence - if ((nbClearedLines == 4) || (tSpin && nbClearedLines)) { - this.b2b++ + if (nbClearedLines == 4 || (tSpin && nbClearedLines)) { + this.b2b++; if (this.b2b >= 1) { - let b2bScore = patternScore / 2 + let b2bScore = patternScore / 2; if (this.b2b == 1) { - messagesSpan.addNewChild("div", { - className: "zoom-in-animation", - style: "animation-delay: .4s", - innerHTML: `BOUT À BOUT
${b2bScore}` - }) + messagesSpan.addNewChild('div', { + className: 'zoom-in-animation', + style: 'animation-delay: .4s', + innerHTML: `BOUT À BOUT
${b2bScore}`, + }); } else { - messagesSpan.addNewChild("div", { - className: "zoom-in-animation", - style: "animation-delay: .4s", - innerHTML: `BOUT À BOUT x${this.b2b}
${b2bScore}` - }) + messagesSpan.addNewChild('div', { + className: 'zoom-in-animation', + style: 'animation-delay: .4s', + innerHTML: `BOUT À BOUT x${this.b2b}
${b2bScore}`, + }); } - this.score += b2bScore + this.score += b2bScore; } - } else if (nbClearedLines && !tSpin ) { + } else if (nbClearedLines && !tSpin) { if (this.b2b >= 1) { - messagesSpan.addNewChild("div", { - className: "zoom-in-animation", - style: "animation-delay: .4s", - innerHTML: `FIN DU BOUT À BOUT` - }) + messagesSpan.addNewChild('div', { + className: 'zoom-in-animation', + style: 'animation-delay: .4s', + innerHTML: `FIN DU BOUT À BOUT`, + }); } - this.b2b = -1 + this.b2b = -1; } // Sound if (sfxVolumeRange.value) { - if (nbClearedLines == 4) playSound(quatuorSound, this.combo) - else if (nbClearedLines) playSound(lineClearSound, this.combo) - if (tSpin) playSound(tSpinSound, this.combo) + if (nbClearedLines == 4) playSound(quatuorSound, this.combo); + else if (nbClearedLines) playSound(lineClearSound, this.combo); + if (tSpin) playSound(tSpinSound, this.combo); } - this.goal -= awardedLineClears - if (this.goal <= 0) this.level++ + this.goal -= awardedLineClears; + if (this.goal <= 0) this.level++; } show() { - let time = stats.time - statsModalScoreCell.innerText = this.score.toLocaleString() - statsModalHighScoreCell.innerText = this.highScore.toLocaleString() - statsModalLevelCell.innerText = this.level - statsModalTimeCell.innerText = this.timeFormat.format(time) - statsModaltotalClearedLines.innerText = this.totalClearedLines - statsModaltotalClearedLinesPM.innerText = (stats.totalClearedLines * 60000 / time).toFixed(2) - statsModalNbQuatuors.innerText = this.nbQuatuors - statsModalNbTSpin.innerText = this.nbTSpin - statsModalMaxCombo.innerText = this.maxCombo - statsModalMaxB2B.innerText = this.maxB2B - this.modal.show() + let time = stats.time; + statsModalScoreCell.innerText = this.score.toLocaleString(); + statsModalHighScoreCell.innerText = this.highScore.toLocaleString(); + statsModalLevelCell.innerText = this.level; + statsModalTimeCell.innerText = this.timeFormat.format(time); + statsModaltotalClearedLines.innerText = this.totalClearedLines; + statsModaltotalClearedLinesPM.innerText = ( + (stats.totalClearedLines * 60000) / + time + ).toFixed(2); + statsModalNbQuatuors.innerText = this.nbQuatuors; + statsModalNbTSpin.innerText = this.nbTSpin; + statsModalMaxCombo.innerText = this.maxCombo; + statsModalMaxB2B.innerText = this.maxB2B; + this.modal.show(); } save() { - localStorage["highScore"] = this.highScore + localStorage['highScore'] = this.highScore; } } -Stats.prototype.timeFormat = new Intl.DateTimeFormat("fr-FR", { - hour: "numeric", - minute: "2-digit", - second: "2-digit", - timeZone: "UTC" -}) +Stats.prototype.timeFormat = new Intl.DateTimeFormat('fr-FR', { + hour: 'numeric', + minute: '2-digit', + second: '2-digit', + timeZone: 'UTC', +}); -function playSound(sound, note=0) { - sound.currentTime = 0 - sound.playbackRate = Math.pow(5/4, note) - sound.play() -} \ No newline at end of file +function playSound(sound, note = 0) { + sound.currentTime = 0; + sound.playbackRate = Math.pow(5 / 4, note); + sound.play(); +}