From 094fd8f7b441f99b351b94beca6c38544efb761c Mon Sep 17 00:00:00 2001 From: Ulysse ARNAUD Date: Sun, 3 Mar 2024 16:07:28 +0100 Subject: [PATCH] Spaces does not block word validation --- app.js | 1270 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 633 insertions(+), 637 deletions(-) diff --git a/app.js b/app.js index ce23128..4ca0c3f 100644 --- a/app.js +++ b/app.js @@ -1,596 +1,591 @@ -document.addEventListener('DOMContentLoaded', function() { - - const globals = {}; - - const SECTIONS = { - accueil: document.querySelector('section[aria-labelledby="accueil"]'), - jouer: document.querySelector('section[aria-labelledby="jouer"]'), - fin: document.querySelector('section[aria-labelledby="fin"]'), - history: document.querySelector('section[aria-labelledby="history"]'), - }; - - const DIALOGS = { - 'select-difficulty': document.querySelector('dialog.select-difficulty'), - 'confirm-quit': document.querySelector('dialog.confirm-quit') - }; - - const OUTPUTS = { - 'score': document.querySelectorAll('output[name="score"]'), - 'time': document.querySelectorAll('output[name="time"]'), - 'time-remaining': document.querySelectorAll('output[name="time-remaining"]'), - 'level': document.querySelectorAll('output[name="level"]'), - 'word': document.querySelectorAll('output[name="word"]'), - 'final-score': document.querySelectorAll('output[name="final-score"]') - }; - - const DIFFICULTIES = { - facile: { - 'start-time': 32, - 'min-time': 8, - 'words-per-level': 8, - }, - moyen: { - 'start-time': 24, - 'min-time': 6, - 'words-per-level': 6, - }, - difficile: { - 'start-time': 16, - 'min-time': 2, - 'words-per-level': 4, - }, - default: { - 'start-time': 32, - 'min-time': 8, - 'words-per-level': 8, - } - }; - - const DBOpenRequest = indexedDB.open('typing-game', 1); +document.addEventListener("DOMContentLoaded", function () { + const globals = {}; + + const SECTIONS = { + accueil: document.querySelector('section[aria-labelledby="accueil"]'), + jouer: document.querySelector('section[aria-labelledby="jouer"]'), + fin: document.querySelector('section[aria-labelledby="fin"]'), + history: document.querySelector('section[aria-labelledby="history"]'), + }; + + const DIALOGS = { + "select-difficulty": document.querySelector("dialog.select-difficulty"), + "confirm-quit": document.querySelector("dialog.confirm-quit"), + }; + + const OUTPUTS = { + score: document.querySelectorAll('output[name="score"]'), + time: document.querySelectorAll('output[name="time"]'), + "time-remaining": document.querySelectorAll( + 'output[name="time-remaining"]' + ), + level: document.querySelectorAll('output[name="level"]'), + word: document.querySelectorAll('output[name="word"]'), + "final-score": document.querySelectorAll('output[name="final-score"]'), + }; + + const DIFFICULTIES = { + facile: { + "start-time": 32, + "min-time": 8, + "words-per-level": 8, + }, + moyen: { + "start-time": 24, + "min-time": 6, + "words-per-level": 6, + }, + difficile: { + "start-time": 16, + "min-time": 2, + "words-per-level": 4, + }, + default: { + "start-time": 32, + "min-time": 8, + "words-per-level": 8, + }, + }; + + const DBOpenRequest = indexedDB.open("typing-game", 1); + + DBOpenRequest.onerror = function (event) { + console.error("Erreur lors de l'ouverture de la base de données"); + }; + + DBOpenRequest.onsuccess = function (event) { + globals.db = DBOpenRequest.result; + }; + + DBOpenRequest.onupgradeneeded = function (event) { + const db = event.target.result; + const objectStore = db.createObjectStore("scores", { + keyPath: "id", + autoIncrement: true, + }); + objectStore.createIndex("difficulty", "difficulty", { unique: false }); + objectStore.createIndex("score", "score", { unique: false }); + objectStore.createIndex("date", "date", { unique: false }); + objectStore.createIndex("time", "time", { unique: false }); + }; + + // Common events + (function () { + const headers = document.querySelectorAll( + "section[aria-labelledby] > header" + ); + for (let i = 0; i < headers.length; i++) { + headers[i].addEventListener("click", function ({ target }) { + const section = target.closest("section"); + const ariaLabelledBy = section.getAttribute("aria-labelledby"); + SECTIONS[ariaLabelledBy].hidden = true; + SECTIONS.accueil.hidden = false; + }); + } - DBOpenRequest.onerror = function(event) { - console.error('Erreur lors de l\'ouverture de la base de données'); + window.addEventListener("keydown", handleKeyDown); + window.addEventListener("keyup", handleKeyUp); + })(); + + // Home events + (function () { + const callbacks = { + jouer: () => { + displayModal("select-difficulty"); + }, + history: () => { + loadScores(); + SECTIONS.history.hidden = false; + SECTIONS.accueil.hidden = true; + }, + default: () => {}, }; + const links = SECTIONS.accueil.querySelector("ul").children; + for (let i = 0; i < links.length; i++) { + links[i].addEventListener( + "click", + callbacks[links[i].children[0].getAttribute("aria-labelledby")] ?? + callbacks.default + ); + links[i].addEventListener("mouseenter", function () { + const nav = this.closest("ul"); + const index = Array.from(nav.children).indexOf(this); + nav.style.setProperty("--nav-index", index); + }); + } + })(); - DBOpenRequest.onsuccess = function(event) { - globals.db = DBOpenRequest.result; - }; + // Common dialogs events + (function () { + const children = document.body.children; + const dialogs = document.querySelectorAll("dialog"); - DBOpenRequest.onupgradeneeded = function(event) { - const db = event.target.result; - const objectStore = db.createObjectStore('scores', { keyPath: 'id', autoIncrement: true }); - objectStore.createIndex('difficulty', 'difficulty', { unique: false }); - objectStore.createIndex('score', 'score', { unique: false }); - objectStore.createIndex('date', 'date', { unique: false }); - objectStore.createIndex('time', 'time', { unique: false }); - }; + for (let i = 0; i < dialogs.length; i++) { + const header = dialogs[i].querySelector("header"); - // Common events - (function(){ - const headers = document.querySelectorAll('section[aria-labelledby] > header'); - for (let i = 0; i < headers.length; i++) { - headers[i].addEventListener('click', function({target}) { - const section = target.closest('section'); - const ariaLabelledBy = section.getAttribute('aria-labelledby'); - SECTIONS[ariaLabelledBy].hidden = true; - SECTIONS.accueil.hidden = false; - }); + header.addEventListener("dblclick", function () { + if (dialogs[i].querySelector("section").hidden) { + const maximize = header.querySelector("button.maximize"); + maximize.click(); + return; } - - window.addEventListener('keydown', handleKeyDown); - window.addEventListener('keyup', handleKeyUp); - })(); - - // Home events - (function() { - const callbacks = { - jouer: () => { - displayModal('select-difficulty'); - }, - history: () => { - loadScores(); - SECTIONS.history.hidden = false; - SECTIONS.accueil.hidden = true; - }, - default: () => { - // console.log('default'); + const minimize = header.querySelector("button.minimize"); + minimize.click(); + }); + + header.addEventListener("mousedown", function (e) { + const dialog = e.target.closest("dialog"); + const x = e.clientX - dialog.offsetLeft; + const y = e.clientY - dialog.offsetTop; + document.addEventListener("mousemove", moveDialog); + document.addEventListener("mouseup", function () { + document.removeEventListener("mousemove", moveDialog); + for (let i = 0; i < children.length; i++) { + if (children[i] !== dialog) { + children[i].inert = false; } - }; - const links = SECTIONS.accueil.querySelector('ul').children; - for (let i = 0; i < links.length; i++) { - links[i].addEventListener('click', callbacks[links[i].children[0].getAttribute('aria-labelledby')] ?? callbacks.default); - } - })(); - - // Common dialogs events - (function(){ - const children = document.body.children; - const dialogs = document.querySelectorAll('dialog'); - - for (let i = 0; i < dialogs.length; i++) { - const header = dialogs[i].querySelector('header'); - - header.addEventListener('dblclick', function() { - if (dialogs[i].querySelector('section').hidden) { - const maximize = header.querySelector('button.maximize'); - maximize.click(); - return; - } - const minimize = header.querySelector('button.minimize'); - minimize.click(); - }); - - // Apply drag move to dialog header - header.addEventListener('mousedown', function(e) { - const dialog = e.target.closest('dialog'); - const x = e.clientX - dialog.offsetLeft; - const y = e.clientY - dialog.offsetTop; - document.addEventListener('mousemove', moveDialog); - document.addEventListener('mouseup', function() { - document.removeEventListener('mousemove', moveDialog); - for (let i = 0; i < children.length; i++) { - if (children[i] !== dialog) { - children[i].inert = false; - } - } - }); - function moveDialog(e) { - for (let i = 0; i < children.length; i++) { - if (children[i] !== dialog) { - children[i].inert = true; - } - } - - requestAnimationFrame(function() { - dialog.style.left = Math.max(0, Math.min(e.clientX - x, window.innerWidth - dialog.offsetWidth)) + 'px'; - dialog.style.top = Math.max(0, Math.min(e.clientY - y, window.innerHeight - dialog.offsetHeight)) + 'px'; - }); - } - }); - - const nav = { - close: dialogs[i].querySelector('nav button.close'), - minimize: dialogs[i].querySelector('nav button.minimize'), - maximize: dialogs[i].querySelector('nav button.maximize') - }; - - nav.close.addEventListener('click', function() { - dialogs[i].close(); - }); - - nav.minimize.addEventListener('click', function() { - requestAnimationFrame(function() { - dialogs[i].querySelector('section').hidden = true; - nav.minimize.hidden = true; - nav.maximize.hidden = false; - }); - }); - - nav.maximize.addEventListener('click', function() { - requestAnimationFrame(function() { - dialogs[i].querySelector('section').hidden = false; - nav.maximize.hidden = true; - nav.minimize.hidden = false; - }); - }); - } - })(); - - // Select difficulty dialog events - (function() { - const dialog = DIALOGS['select-difficulty']; - const form = dialog.querySelector('form'); - const inputs = form.querySelectorAll('input[type="number"]'); - const labels = dialog.querySelectorAll('section > form > nav > ul > li > label'); - const callbacks = { - facile: () => { - // console.log('facile'); - }, - moyen: () => { - // console.log('moyen'); - }, - difficile: () => { - // console.log('difficile'); - }, - personnalise: () => { - // console.log('personnalise'); - }, - default: () => { - // console.log('default'); - } - }; - for (let i = 0; i < labels.length; i++) { - const name = labels[i].querySelector('input').value ?? 'default'; - const method = callbacks[name] ?? callbacks.default; - if (!labels[i].onclick) labels[i].onclick = function(){ - method(); - - if ('personnalise' !== name) { - for (let i = 0; i < inputs.length; i++) { - const input = inputs[i]; - input.readOnly = true; - if (DIFFICULTIES[name][input.name]) { - input.value = DIFFICULTIES[name][input.name]; - } - } - return; - } - for (let i = 0; i < inputs.length; i++) { - const input = inputs[i]; - input.readOnly = false; - input.value = DIFFICULTIES.default[input.name]; - } - }; - } - labels[labels.length-1].click(); - - form.addEventListener('submit', async function() { - const data = new FormData(form); - const config = { - 'start-time': +data.get('start-time'), - 'min-time': +data.get('min-time'), - 'words-per-level': +data.get('words-per-level'), - }; - dialog.close(); - SECTIONS.accueil.hidden = true; - SECTIONS.jouer.hidden = false; - await startGame(config); - }); - })(); - - // Confirm quit dialog events - (function() { - const dialog = DIALOGS['confirm-quit']; - const buttons = { - no: dialog.querySelector('button[data-name="no"]'), - yes: dialog.querySelector('button[data-name="yes"]') - } - buttons.no.addEventListener('click', function() { - dialog.close(); - }); - buttons.yes.addEventListener('click', function() { - dialog.close(); - globals.gameOver(); + } }); - })(); - - // Fin events - (function() { - const links = SECTIONS.fin.querySelectorAll('nav > ul > li > a'); - const callbacks = { - quit: () => { - SECTIONS.fin.hidden = true; - SECTIONS.accueil.hidden = false; - }, - replay: () => { - SECTIONS.fin.hidden = true; - SECTIONS.accueil.hidden = false; - displayModal('select-difficulty'); + function moveDialog(e) { + for (let i = 0; i < children.length; i++) { + if (children[i] !== dialog) { + children[i].inert = true; } - }; - for (let i = 0; i < links.length; i++) { - links[i].addEventListener('click', callbacks[links[i].dataset.name]); + } + + requestAnimationFrame(function () { + dialog.style.left = + Math.max( + 0, + Math.min(e.clientX - x, window.innerWidth - dialog.offsetWidth) + ) + "px"; + dialog.style.top = + Math.max( + 0, + Math.min( + e.clientY - y, + window.innerHeight - dialog.offsetHeight + ) + ) + "px"; + }); } - - })(); - - // Jouer events - (function() { - const quit = SECTIONS.jouer.querySelector('button[data-name="quit-game"]'); - quit.addEventListener('click', function() { - displayModal('confirm-quit'); + }); + + const nav = { + close: dialogs[i].querySelector("nav button.close"), + minimize: dialogs[i].querySelector("nav button.minimize"), + maximize: dialogs[i].querySelector("nav button.maximize"), + }; + + nav.close.addEventListener("click", function () { + dialogs[i].close(); + }); + + nav.minimize.addEventListener("click", function () { + requestAnimationFrame(function () { + dialogs[i].querySelector("section").hidden = true; + nav.minimize.hidden = true; + nav.maximize.hidden = false; }); - })(); - - // History events - (function() { - // const section = SECTIONS.history; - // const table = section.querySelector('table'); - // const cells_header = table.querySelectorAll('thead > tr > th'); - // for (let i = 0; cells_header.length; i++) { - // const cell = cells_header[i]; - // cell.addEventListener('click', function() { - // let sort_order = 'asc'; - // if (cell.dataset.sort === 'asc') { - // sort_order = 'desc'; - // } - // sortTable(table, i, sort_order); - // cell.dataset.sort = sort_order; - // }); - // } - })(); - - async function startGame({ - 'start-time': startTime = 32, - 'min-time': minTime = 4, - 'words-per-level': wordsPerLevel = 5 - }) { - - let word; - let score = 0; - let level = 1; - let start_at = Date.now(); - let difficulty = { - 'start-time': startTime, - 'min-time': minTime, - 'words-per-level': wordsPerLevel - }; - let time = startTime; - let words = []; - let interval_time_remaining; - let previous_progress, progress = 1; - - globals.paused = false; - - globals.playing = false; - - await fetch('./text_1.txt') - .then(response => response.text()) - .then(text => { - text = text - .toLowerCase() - .replace(/(\r\n|\n|\r)/gm, ' ') - .replace(/[.,\/#!$%\^&\*;:{}=_`~()]/g, '') - .replace(/([a-z]+)[’']/g, ''); - - words = text.split(' '); - words = [...new Set(words)]; - words = words - .filter(word => !/^\d+$/.test(word)) - .filter(word => word.length > 2); - }); - - const section = SECTIONS.jouer; - const input = section.querySelector('input[name="user-word"]'); - - const asides = section.querySelectorAll('aside[role="status"]'); - - // We set to input.value but this should be an empty string - let word_typed = input.value.trim(), previous_word_typed = ''; - - globals.ctrl_key_pressed = false; - - input.addEventListener('input', function() { - if (globals.ctrl_key_pressed) { - input.value = previous_word_typed; - return; - } - - previous_word_typed = word_typed; - word_typed = input.value; + }); - // Remove all accents of word - const word_typed_normalized = word_typed - .normalize('NFD').replace(/[\u0300-\u036f]/g, '') - .toLowerCase(); - - // if the word typed is in the list of words - if (word === word_typed_normalized) { - nextRound(); - } + nav.maximize.addEventListener("click", function () { + requestAnimationFrame(function () { + dialogs[i].querySelector("section").hidden = false; + nav.maximize.hidden = true; + nav.minimize.hidden = false; }); - - globals.playing = true; - round(); - input.focus(); - - updateOutput('score', score); - updateOutput('level', level); - - // A round is a word to type - function round() { - let time_remaining = time; - word = getRandomWord(); - - updateOutput('word', word); - updateOutput('time', time); - updateOutput('time-remaining', time); - - - progress = time_remaining / time; - for (let i = 0; i < asides.length; i++) { - const aside = asides[i]; - aside.style.setProperty('--progress', progress); - } - - interval_time_remaining = setInterval(function() { - interval() - }, 1000); - function interval() { - if (globals.paused) return; - - if (--time_remaining < 0) { - gameOver(); - return; - } - progress = time_remaining / time; - for (let i = 0; i < asides.length; i++) { - const aside = asides[i]; - if (!aside.style.transition || aside.style.transition === 'none') { - aside.style.transition = 'width 1s linear'; - } - if (previous_progress !== progress) { - aside.style.setProperty('--progress', progress); - } - } - if (previous_progress !== progress) { - previous_progress = progress; - } - updateOutput('time-remaining', time_remaining); + }); + } + })(); + + // Select difficulty dialog events + (function () { + const dialog = DIALOGS["select-difficulty"]; + const form = dialog.querySelector("form"); + const inputs = form.querySelectorAll('input[type="number"]'); + const labels = dialog.querySelectorAll( + "section > form > nav > ul > li > label" + ); + const callbacks = { + facile: () => {}, + moyen: () => {}, + difficile: () => {}, + personnalise: () => {}, + default: () => {}, + }; + for (let i = 0; i < labels.length; i++) { + const name = labels[i].querySelector("input").value ?? "default"; + const method = callbacks[name] ?? callbacks.default; + if (!labels[i].onclick) + labels[i].onclick = function () { + method(); + + if ("personnalise" !== name) { + for (let i = 0; i < inputs.length; i++) { + const input = inputs[i]; + input.readOnly = true; + if (DIFFICULTIES[name][input.name]) { + input.value = DIFFICULTIES[name][input.name]; + } } + return; + } + for (let i = 0; i < inputs.length; i++) { + const input = inputs[i]; + input.readOnly = false; + input.value = DIFFICULTIES.default[input.name]; + } + }; + } + labels[labels.length - 1].click(); + + form.addEventListener("submit", async function () { + const data = new FormData(form); + const config = { + "start-time": +data.get("start-time"), + "min-time": +data.get("min-time"), + "words-per-level": +data.get("words-per-level"), + }; + dialog.close(); + SECTIONS.accueil.hidden = true; + SECTIONS.jouer.hidden = false; + await startGame(config); + }); + })(); + + // Confirm quit dialog events + (function () { + const dialog = DIALOGS["confirm-quit"]; + const buttons = { + no: dialog.querySelector('button[data-name="no"]'), + yes: dialog.querySelector('button[data-name="yes"]'), + }; + buttons.no.addEventListener("click", function () { + dialog.close(); + }); + buttons.yes.addEventListener("click", function () { + dialog.close(); + globals.gameOver(); + }); + })(); + + // Fin events + (function () { + const links = SECTIONS.fin.querySelectorAll("nav > ul > li > a"); + const callbacks = { + quit: () => { + SECTIONS.fin.hidden = true; + SECTIONS.accueil.hidden = false; + }, + replay: () => { + SECTIONS.fin.hidden = true; + SECTIONS.accueil.hidden = false; + displayModal("select-difficulty"); + }, + }; + for (let i = 0; i < links.length; i++) { + links[i].addEventListener("click", callbacks[links[i].dataset.name]); + } + })(); + + // Jouer events + (function () { + const quit = SECTIONS.jouer.querySelector('button[data-name="quit-game"]'); + quit.addEventListener("click", function () { + displayModal("confirm-quit"); + }); + })(); + + // History events + (function () {})(); + + async function startGame({ + "start-time": startTime = 32, + "min-time": minTime = 4, + "words-per-level": wordsPerLevel = 5, + }) { + let word; + let score = 0; + let level = 1; + let start_at = Date.now(); + let difficulty = { + "start-time": startTime, + "min-time": minTime, + "words-per-level": wordsPerLevel, + }; + let time = startTime; + let words = []; + let interval_time_remaining; + let previous_progress, + progress = 1; + + globals.paused = false; + + globals.playing = false; + + await fetch("./text_1.txt") + .then((response) => response.text()) + .then((text) => { + text = text + .toLowerCase() + .replace(/(\r\n|\n|\r)/gm, " ") + .replace(/[.,\/#!$%\^&\*;:{}=_`~()]/g, "") + .replace(/([a-z]+)[’']/g, ""); + + words = text.split(" "); + words = [...new Set(words)]; + words = words + .filter((word) => !/^\d+$/.test(word)) + .filter((word) => word.length > 2); + }); + + const section = SECTIONS.jouer; + const input = section.querySelector('input[name="user-word"]'); + + const asides = section.querySelectorAll('aside[role="status"]'); + + let word_typed = input.value.trim(), + previous_word_typed = ""; + + globals.ctrl_key_pressed = false; + + input.addEventListener("paste", function (e) { + e.preventDefault(); + return false; + }); + + input.addEventListener("input", function (e) { + previous_word_typed = word_typed; + word_typed = input.value; + + const word_typed_normalized = word_typed + .trim() + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, "") + .toLowerCase(); + + if (word === word_typed_normalized) { + nextRound(); + } + }); + + globals.playing = true; + round(); + input.focus(); + + updateOutput("score", score); + updateOutput("level", level); + + function round() { + let time_remaining = time; + word = getRandomWord(); + + updateOutput("word", word); + updateOutput("time", time); + updateOutput("time-remaining", time); + + progress = time_remaining / time; + for (let i = 0; i < asides.length; i++) { + const aside = asides[i]; + aside.style.setProperty("--progress", progress); + } + + interval_time_remaining = setInterval(function () { + interval(); + }, 1000); + function interval() { + if (globals.paused) return; + + if (--time_remaining < 0) { + gameOver(); + return; } - - function getRandomWord() { - let random_word; - do { - random_word = words[Math.floor(Math.random() * words.length)].normalize('NFD').replace(/[\u0300-\u036f]/g, ''); - } while (word == random_word) - return random_word; + progress = time_remaining / time; + for (let i = 0; i < asides.length; i++) { + const aside = asides[i]; + if (!aside.style.transition || aside.style.transition === "none") { + aside.style.transition = "width 1s linear"; + } + if (previous_progress !== progress) { + aside.style.setProperty("--progress", progress); + } } - - function nextRound() { - score++; - if (score % wordsPerLevel === 0) { - level++; - time = Math.max(time - 1, minTime); - time_remaining = time; - performAnimation('level-up'); - updateOutput('level', level); - } - performAnimation('score-up'); - updateOutput('score', score); - - for (let i = 0; i < asides.length; i++) { - const aside = asides[i]; - aside.style.transition = '0'; - aside.style.setProperty('--progress', 1); - } - previous_progress = 1; - - clearRound(); - round(); + if (previous_progress !== progress) { + previous_progress = progress; } + updateOutput("time-remaining", time_remaining); + } + } - function clearRound() { - input.value = ''; - clearInterval(interval_time_remaining); - } + function getRandomWord() { + let random_word; + do { + random_word = words[Math.floor(Math.random() * words.length)] + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, ""); + } while (word == random_word); + return random_word; + } - function gameOver() { - clearRound(); + function nextRound() { + score++; + if (score % wordsPerLevel === 0) { + level++; + time = Math.max(time - 1, minTime); + time_remaining = time; + performAnimation("level-up"); + updateOutput("level", level); + } + performAnimation("score-up"); + updateOutput("score", score); + + for (let i = 0; i < asides.length; i++) { + const aside = asides[i]; + aside.style.transition = "0"; + aside.style.setProperty("--progress", 1); + } + previous_progress = 1; + + clearRound(); + round(); + } - globals.playing = false; + function clearRound() { + input.value = ""; + clearInterval(interval_time_remaining); + } - let ends_at = Date.now(); + function gameOver() { + clearRound(); - let time_played = Math.floor((ends_at - start_at) / 1000); + globals.playing = false; - saveScore({ - difficulty, - time: time_played, - score - }) - updateOutput('final-score', score); - section.hidden = true; - SECTIONS.fin.hidden = false; - } - globals.gameOver = gameOver; - - function performAnimation(name) { - const animations = { - 'level-up': () => { - addUpAnimation(OUTPUTS.level); - }, - 'score-up': () => { - addUpAnimation(OUTPUTS.score); - } - } - - try { - requestAnimationFrame(animations[name]); - } - catch (e) { - console.error(e); - } + let ends_at = Date.now(); - function addUpAnimation(outputs) { - for (let i = 0; i < outputs.length; i++) { - const output = outputs[i]; - const span = document.createElement('span'); - span.textContent = '+1'; - span.style.top = output.offsetTop - 3 + 'px'; - span.style.left = output.offsetLeft + output.offsetWidth + 3 + 'px'; - span.classList.add('animation-plus'); - output.parentElement.insertBefore(span, output); - - setTimeout(() => { - span.remove(); - }, 900 + 500 * i); - } - } - } - } + let time_played = Math.floor((ends_at - start_at) / 1000); - function updateOutput(name, value) { - try { - for (let i = 0; i < OUTPUTS[name].length; i++) { - OUTPUTS[name][i].value = value; - } - } - catch (e) { - console.error(e); - } + saveScore({ + difficulty, + time: time_played, + score, + }); + updateOutput("final-score", score); + section.hidden = true; + SECTIONS.fin.hidden = false; } + globals.gameOver = gameOver; - function displayModal(name) { - const dialog = DIALOGS[name]; - dialog.style.top = (window.innerHeight - dialog.offsetHeight) / 2.5 + 'px'; - dialog.style.left = (window.innerWidth - dialog.offsetWidth) / 2.5 + 'px'; - dialog.showModal(); + function performAnimation(name) { + const animations = { + "level-up": () => { + addUpAnimation(OUTPUTS.level); + }, + "score-up": () => { + addUpAnimation(OUTPUTS.score); + }, + }; + + try { + requestAnimationFrame(animations[name]); + } catch (e) { + console.error(e); + } + + function addUpAnimation(outputs) { + for (let i = 0; i < outputs.length; i++) { + const output = outputs[i]; + const span = document.createElement("span"); + span.textContent = "+1"; + span.style.top = output.offsetTop - 3 + "px"; + span.style.left = output.offsetLeft + output.offsetWidth + 3 + "px"; + span.classList.add("animation-plus"); + output.parentElement.insertBefore(span, output); + + setTimeout(() => { + span.remove(); + }, 900 + 500 * i); + } + } } - - function saveScore({ - difficulty, - time, - score - }) { - const transaction = globals.db.transaction('scores', 'readwrite'); - const store = transaction.objectStore('scores'); - store.add({ - difficulty, - time, - score, - date: new Date() - }); - - transaction.oncomplete = function() { - console.log('Transaction completed: database modification finished.'); - }; - - transaction.onerror = function() { - console.log('Transaction not opened due to error: ' + transaction.error); - }; + } + + function updateOutput(name, value) { + try { + for (let i = 0; i < OUTPUTS[name].length; i++) { + OUTPUTS[name][i].value = value; + } + } catch (e) { + console.error(e); } + } + + function displayModal(name) { + const dialog = DIALOGS[name]; + dialog.style.top = (window.innerHeight - dialog.offsetHeight) / 2.5 + "px"; + dialog.style.left = (window.innerWidth - dialog.offsetWidth) / 2.5 + "px"; + dialog.showModal(); + } + + function saveScore({ difficulty, time, score }) { + const transaction = globals.db.transaction("scores", "readwrite"); + const store = transaction.objectStore("scores"); + store.add({ + difficulty, + time, + score, + date: new Date(), + }); + + transaction.oncomplete = function () { + console.log("Transaction completed: database modification finished."); + }; - async function loadScores() { - const transaction = globals.db.transaction('scores', 'readwrite'); - const store = transaction.objectStore('scores'); - const request = store.getAll(); + transaction.onerror = function () { + console.log("Transaction not opened due to error: " + transaction.error); + }; + } - request.onsuccess = function() { - displayScores(request.result); - } + async function loadScores() { + const transaction = globals.db.transaction("scores", "readwrite"); + const store = transaction.objectStore("scores"); + const request = store.getAll(); - transaction.oncomplete = function() { - console.log('Transaction completed: scores loaded.'); - } + request.onsuccess = function () { + displayScores(request.result); + }; - transaction.onerror = function() { - console.log('Transaction not opened due to error: ' + transaction.error); - } - } + transaction.oncomplete = function () { + console.log("Transaction completed: scores loaded."); + }; - function displayScores(scores) { - const {history: section} = SECTIONS; - const table = section.querySelector('table'); - const tbody = table.querySelector('tbody'); - tbody.innerHTML = ''; - for (let i = scores.length; i--;) { - const score = scores[i]; - const element = (function({ - difficulty, - time, - date, - score - }) { - date = date.toISOString() - date = date.replace('T', ' ').replace('Z', '') - .substring(0, date.lastIndexOf('.')); - - time = Math.floor(time / 60) + "' " + time % 60 + '"'; - - const html = ` - + transaction.onerror = function () { + console.log("Transaction not opened due to error: " + transaction.error); + }; + } + + function displayScores(scores) { + const { history: section } = SECTIONS; + const table = section.querySelector("table"); + const tbody = table.querySelector("tbody"); + tbody.innerHTML = ""; + for (let i = scores.length; i--; ) { + const score = scores[i]; + const element = (function ({ difficulty, time, date, score }) { + date = date.toISOString(); + date = date + .replace("T", " ") + .replace("Z", "") + .substring(0, date.lastIndexOf(".")); + + time = Math.floor(time / 60) + "' " + (time % 60) + '"'; + + const html = ` + @@ -598,97 +593,98 @@ document.addEventListener('DOMContentLoaded', function() { - ${date} - ${time} + ${date} + ${time} ${score} `; - const element = parseHtmlToElement(html); + const element = parseHtmlToElement(html); - const difficulty_icon_link = element.querySelector('td:first-child > a'); - const tooltip = (function() { - const html = `