From 6443f8fd0eede0fa20b4d319f2d54cb950c44ffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20FAMIBELLE-PRONZOLA?= Date: Mon, 6 May 2024 21:02:39 +0400 Subject: [PATCH] Add a timer --- js/map.js | 522 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 325 insertions(+), 197 deletions(-) diff --git a/js/map.js b/js/map.js index c1284eb..221df93 100644 --- a/js/map.js +++ b/js/map.js @@ -15,6 +15,11 @@ const currentFileName = window.location.pathname.split('/').pop() const codeCarte = currentFileName.split('.')[0] +const startButton = document.getElementById('start') +const restartButton = document.getElementById('restart') + +restartButton.setAttribute('disabled', true) + /** * Définir le centre , le niveau de zoom et le style de la carte en fonction du code du département. */ @@ -54,42 +59,6 @@ switch (codeCarte) { zoomLevel = 9.5 } -/** - * Initialise et configure la carte - */ -const map = new maplibregl.Map({ - container: 'map', - style: `../kat/styles/${styleFileName}`, - center: centerCoordinates, - zoom: zoomLevel -}) - -if (codeCarte !== '973') { - /** - * Désactive le zoom, le double-clic, le déplacement de la carte pour les cartes autres que celle la Guyane. - */ - map.scrollZoom.disable() - map.doubleClickZoom.disable() - map.dragPan.disable() -} else { - /** - * Ajoute les contrôles de navigation à la carte, et définit les niveaux de zoom minimum et maximum pour la Guyane. - */ - map.addControl(new maplibregl.NavigationControl({showCompass: false})) - map.setMinZoom(7) - map.setMaxZoom(9.5) -} - -if (codeCarte === 'KAMA') { - /** - * Ajoute les contrôles de navigation à la carte, et définit les niveaux de zoom minimum et maximum pour l'Afrique'. - */ - map.addControl(new maplibregl.NavigationControl({showCompass: false})) - map.setMinZoom(2.4) - map.setMaxZoom(9) - map.dragPan.enable() -} - /** * Définit les décalages des popups en fonction de leur position. */ @@ -107,8 +76,16 @@ const popupOffsets = { /** * Référence aux éléments HTML pour afficher le score. */ -const ok = document.getElementById('OK') -const ko = document.getElementById('KO') +let ok +let ko + +/** + * Gestion du temps écoulé + */ +let gameTime = 0 +let gameMinute = 0 +let gameSecond = 0 +let intervalId /** * État actuel de la commune survolée. @@ -125,6 +102,107 @@ let currentZone = null */ const correctAnswers = new Set() +/** + * Permet de remettre le jeu à 0 + */ +function resetGame() { + ok.innerText = 0 + ko.innerText = 0 + correctAnswers.clear() + clearInterval(intervalId) + gameTime = 0 +} + +/** + * Active le bouton "Commencer la partie" au chargement de la page + */ +window.onload = function() { + const startElement = document.getElementById('start') + startElement.removeAttribute('disabled') +} + +function startGame() { + loadMap() + + const question = document.getElementById('question') + const score = document.getElementById('score') + const chrono = document.getElementById('chrono') + + question.innerHTML = `

Trouve ${codeCarte === 'KAMA' ? 'le pays' : 'la commune'}

...

` + score.innerHTML = "

Correct ✅ : 0

Faux ❌ : 0

" + chrono.innerHTML = "

Temps écoulé :

" + + const communeElement = document.getElementById('commune') + const timeElement = document.getElementById('time') + + communeObserver(communeElement, timeElement) + + ok = document.getElementById('OK') + ko = document.getElementById('KO') + + startButton.setAttribute('disabled', true) + restartButton.removeAttribute('disabled') + + resetGame() +} + +/** + * Observe le contenu de l'élément ayant pour id "commune" + * + * @param {object} communeElement + * @param {object} timeElement + */ +function communeObserver(communeElement, timeElement) { + // Options de l'observateur (quelles sont les mutations à observer) + const config = {childList: true, subtree: true, characterData: true} + + // Fonction callback à éxécuter quand une mutation est observée + const callback = function (mutationsList) { + for (const mutation of mutationsList) { + if (mutation.type == "childList" || mutation.type == "characterData") { + if (communeElement.textContent.trim() !== "...") { + updateTime(timeElement) + } else { + clearInterval(intervalId) + intervalId = null + } + } + } + } + + // Crée une instance de l'observateur liée à la fonction de callback + const observer = new MutationObserver(callback) + + // Commence à observer le noeud cible pour les mutations précédemment configurées + observer.observe(communeElement, config) +} + +/** + * Mets à jour le contenu de l'élément ayant l'id "time" + * + * @param {object} timeElement + */ +function updateTime(timeElement) { + intervalId = setInterval(() => { + gameTime++ + + if (gameTime >= 60) { + gameMinute = String(gameTime / 60).split('.')[0] + gameSecond = gameTime % 60 + timeElement.innerText = `${gameMinute} minute${gameMinute > 1 ? 's' : ''} ${gameSecond} seconde${gameSecond > 1 ? 's' : ''}` + } else { + timeElement.innerText = `${gameTime} seconde${gameTime > 1 ? 's' : ''}` + } + }, 1000) +} + +/** + * Recommencer la partie + */ +function restartGame() { + startGame() +} + /** * Récupère les propriétés des communes depuis un fichier GeoJSON en fonction du code du département. * @@ -208,28 +286,6 @@ function pickRandomZone(zones) { return zonesRestantes[index] } -/** - * Définit la zone actuelle à trouver et met à jour l'affichage. - * - * @param {Object} zone - La zone à définir comme actuelle. - */ -function setZone(zone) { - if (currentZone) { - map.setFeatureState( - {source: 'communes', id: currentZone.code}, - {current: false} - ) - } - - currentZone = zone - document.getElementById('commune').innerText = zone.nom - - map.setFeatureState( - {source: 'communes', id: zone.code}, - {current: true} - ) -} - /** * Vérifie si le code de la zone cliquée correspond à la commune actuelle à trouver. * @@ -241,165 +297,237 @@ function checkAnswer(codeZone) { } /** - * Code pour la gestion des événements de la carte (load, mousemove, mouseleave, click). + * Charge la carte */ -map.on('load', async () => { - const zones = await getFile(codeCarte) - +function loadMap() { /** - * Ajouter une source de données pour les communes. + * Initialise et configure la carte */ - map.addSource('communes', { - 'type': 'geojson', - 'data': `../kat/data/${codeCarte}/contours-${codeCarte === 'KAMA' ? 'pays' : 'communes'}.geojson`, - 'promoteId': 'code' + const map = new maplibregl.Map({ + container: 'map', + style: `../kat/styles/${styleFileName}`, + center: centerCoordinates, + zoom: zoomLevel }) + if (codeCarte !== '973') { + /** + * Désactive le zoom, le double-clic, le déplacement de la carte pour les cartes autres que celle la Guyane. + */ + map.scrollZoom.disable() + map.doubleClickZoom.disable() + map.dragPan.disable() + } else { + /** + * Ajoute les contrôles de navigation à la carte, et définit les niveaux de zoom minimum et maximum pour la Guyane. + */ + map.addControl(new maplibregl.NavigationControl({showCompass: false})) + map.setMinZoom(7) + map.setMaxZoom(9.5) + } + + if (codeCarte === 'KAMA') { + /** + * Ajoute les contrôles de navigation à la carte, et définit les niveaux de zoom minimum et maximum pour l'Afrique'. + */ + map.addControl(new maplibregl.NavigationControl({showCompass: false})) + map.setMinZoom(2.4) + map.setMaxZoom(9) + map.dragPan.enable() + } + /** - * Ajouter une couche de remplissage pour les communes. + * Code pour la gestion des événements de la carte (load, mousemove, mouseleave, click). */ - map.addLayer({ - 'id': 'drom', - 'type': 'fill', - 'source': 'communes', - 'layout': {}, - 'paint': { - 'fill-color': '#1F51FF', - 'fill-opacity': [ - 'case', - ['boolean', ['feature-state', 'hover'], false], - 0.8, - 0 - ] + map.on('load', async () => { + const zones = await getFile(codeCarte) + + /** + * Définit la zone actuelle à trouver et met à jour l'affichage. + * + * @param {Object} zone - La zone à définir comme actuelle. + */ + function setZone(zone) { + if (currentZone) { + map.setFeatureState( + {source: 'communes', id: currentZone.code}, + {current: false} + ) + } + + currentZone = zone + document.getElementById('commune').innerText = zone.nom + + map.setFeatureState( + {source: 'communes', id: zone.code}, + {current: true} + ) } - }) - /** - * Ajouter une couche de remplissage pour les communes (2ème couche), en prenant en compte les réponses correctes et incorrectes. - */ - map.addLayer({ - 'id': 'drom-2', - 'type': 'fill', - 'source': 'communes', - 'layout': {}, - 'paint': { - 'fill-color': [ - 'case', - ['boolean', ['feature-state', 'correct'], false], '#00FF00', - ['boolean', ['feature-state', 'incorrect'], false], '#FF0000', - 'rgba(0,0,0,0)' - ], - 'fill-opacity': [ - 'case', - ['boolean', ['feature-state', 'hover'], false], 0.5, - 0.8 - ] - } - }) + /** + * Ajouter une source de données pour les communes. + */ + map.addSource('communes', { + 'type': 'geojson', + 'data': `../kat/data/${codeCarte}/contours-${codeCarte === 'KAMA' ? 'pays' : 'communes'}.geojson`, + 'promoteId': 'code' + }) - /** - * Gère l'evenement mousemove (quand la souris survole la commune) - */ - map.on('mousemove', 'drom', e => { - map.getCanvas().style.cursor = 'pointer' - if (e.features.length > 0) { + /** + * Ajouter une couche de remplissage pour les communes. + */ + map.addLayer({ + 'id': 'drom', + 'type': 'fill', + 'source': 'communes', + 'layout': {}, + 'paint': { + 'fill-color': '#1F51FF', + 'fill-opacity': [ + 'case', + ['boolean', ['feature-state', 'hover'], false], + 0.8, + 0 + ] + } + }) + + /** + * Ajouter une couche de remplissage pour les communes (2ème couche), en prenant en compte les réponses correctes et incorrectes. + */ + map.addLayer({ + 'id': 'drom-2', + 'type': 'fill', + 'source': 'communes', + 'layout': {}, + 'paint': { + 'fill-color': [ + 'case', + ['boolean', ['feature-state', 'correct'], false], '#00FF00', + ['boolean', ['feature-state', 'incorrect'], false], '#FF0000', + 'rgba(0,0,0,0)' + ], + 'fill-opacity': [ + 'case', + ['boolean', ['feature-state', 'hover'], false], 0.5, + 0.8 + ] + } + }) + + /** + * Gère l'evenement mousemove (quand la souris survole la commune) + */ + map.on('mousemove', 'drom', e => { + map.getCanvas().style.cursor = 'pointer' + if (e.features.length > 0) { + if (hoveredStateId) { + map.setFeatureState( + {source: 'communes', id: hoveredStateId}, + {hover: false} + ) + } + hoveredStateId = e.features[0].id + map.setFeatureState( + {source: 'communes', id: hoveredStateId}, + {hover: true} + ) + } + }) + + /** + * Gère l'evenement mouseleave (quand la souris quitte la zone) + */ + map.on('mouseleave', 'drom', () => { + map.getCanvas().style.cursor = '' if (hoveredStateId) { map.setFeatureState( {source: 'communes', id: hoveredStateId}, {hover: false} ) } - hoveredStateId = e.features[0].id - map.setFeatureState( - {source: 'communes', id: hoveredStateId}, - {hover: true} - ) - } - }) + hoveredStateId = null + }) - /** - * Gère l'evenement mouseleave (quand la souris quitte la zone) - */ - map.on('mouseleave', 'drom', () => { - map.getCanvas().style.cursor = '' - if (hoveredStateId) { - map.setFeatureState( - {source: 'communes', id: hoveredStateId}, - {hover: false} - ) - } - hoveredStateId = null - }) + /** + * Gère l'evenement click (quand on clique sur la zone) + */ + map.on('click', 'drom', async e => { + if (e.features.length > 0) { + const clickedCodeZone = e.features[0].id + const clickedZoneNom = e.features[0].properties.nom + const isCorrect = checkAnswer(clickedCodeZone) - /** - * Gère l'evenement click (quand on clique sur la zone) - */ - map.on('click', 'drom', async e => { - if (e.features.length > 0) { - const clickedCodeZone = e.features[0].id - const clickedZoneNom = e.features[0].properties.nom - const isCorrect = checkAnswer(clickedCodeZone) + if (isCorrect) { + updateScore(true) + const centerCoordinates = await getZoneCenterCoordinates(codeCarte, clickedCodeZone) - if (isCorrect) { - updateScore(true) - const centerCoordinates = await getZoneCenterCoordinates(codeCarte, clickedCodeZone) + const popup = new maplibregl.Popup({offset: popupOffsets, closeButton: false, className: 'popup-ok'}) + .setLngLat([centerCoordinates[0], centerCoordinates[1]]) + .setHTML(`

Bravo, c'est ${clickedZoneNom}

`) + .setMaxWidth('200px') + .addTo(map) - const popup = new maplibregl.Popup({offset: popupOffsets, closeButton: false, className: 'popup-ok'}) - .setLngLat([centerCoordinates[0], centerCoordinates[1]]) - .setHTML(`

Bravo, c'est ${clickedZoneNom}

`) - .setMaxWidth('200px') - .addTo(map) - - setTimeout(() => { - popup.remove() - }, 1500) - - correctAnswers.add(clickedCodeZone) - zones.forEach(commune => { - map.setFeatureState( - {source: 'communes', id: commune.code}, - {incorrect: false} - ) - }) - map.setFeatureState( - {source: 'communes', id: clickedCodeZone}, - {correct: true, current: false} - ) - - const newZone = pickRandomZone(zones) - if (newZone) { - setZone(newZone) - } else { - const totalAnswers = Number.parseInt(ok.innerText, 10) + Number.parseInt(ko.innerText, 10) - const percentageCorrectAnswers = (Number.parseInt(ok.innerText, 10) / totalAnswers) * 100 - - alert(`Fin du jeu, ${Math.round(percentageCorrectAnswers)} % de bonnes réponses !`, ) setTimeout(() => { - location.reload() + popup.remove() }, 1500) + + correctAnswers.add(clickedCodeZone) + zones.forEach(commune => { + map.setFeatureState( + {source: 'communes', id: commune.code}, + {incorrect: false} + ) + }) + map.setFeatureState( + {source: 'communes', id: clickedCodeZone}, + {correct: true, current: false} + ) + + const newZone = pickRandomZone(zones) + if (newZone) { + setZone(newZone) + } else { + const totalAnswers = Number.parseInt(ok.innerText, 10) + Number.parseInt(ko.innerText, 10) + const percentageCorrectAnswers = (Number.parseInt(ok.innerText, 10) / totalAnswers) * 100 + + let elapsedTime = `${gameTime} seconde${gameTime > 1 ? 's' : ''}` + + if (gameTime >= 60) { + elapsedTime = `${gameMinute} minute${gameMinute > 1 ? 's' : ''} ${gameSecond} seconde${gameSecond > 1 ? 's' : ''}` + } + + alert(`Fin du jeu, ${Math.round(percentageCorrectAnswers)} % de bonnes réponses ! Temps écoulé : ${elapsedTime}`) + clearInterval(intervalId) + gameTime = 0 + + setTimeout(() => { + location.reload() + }, 1500) + } + } else { + updateScore(false) + const centerCoordinates = await getZoneCenterCoordinates(codeCarte, clickedCodeZone) + + const popup = new maplibregl.Popup({offset: popupOffsets, closeButton: false, className: 'popup-ko'}) + .setLngLat([centerCoordinates[0], centerCoordinates[1]]) + .setHTML(`

Faux, c'est ${clickedZoneNom}

`) + .setMaxWidth('200px') + .addTo(map) + + setTimeout(() => { + popup.remove() + }, 1500) + + map.setFeatureState( + {source: 'communes', id: clickedCodeZone}, + {incorrect: true} + ) } - } else { - updateScore(false) - const centerCoordinates = await getZoneCenterCoordinates(codeCarte, clickedCodeZone) - - const popup = new maplibregl.Popup({offset: popupOffsets, closeButton: false, className: 'popup-ko'}) - .setLngLat([centerCoordinates[0], centerCoordinates[1]]) - .setHTML(`

Faux, c'est ${clickedZoneNom}

`) - .setMaxWidth('200px') - .addTo(map) - - setTimeout(() => { - popup.remove() - }, 1500) - - map.setFeatureState( - {source: 'communes', id: clickedCodeZone}, - {incorrect: true} - ) } - } - }) + }) - const zone = pickRandomZone(zones) - setZone(zone) -}) + const zone = pickRandomZone(zones) + setZone(zone) + }) +}