/** * js/map.js */ /** * Typos * * Après l'ajout de l'Afrique, on remplace "communes" par "zones" * Zones = Communes ou Pays */ /** * Récupérer le nom du fichier actuel (ex: 971.php) et en extraire le code du département (ex: 971). */ 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. */ let centerCoordinates let zoomLevel let styleFileName switch (codeCarte) { case 'KAMA': centerCoordinates = [21.21, 1.36] // Afrique zoomLevel = 2.7 styleFileName = 'ortho-jwe-KAMA.json' break case 'KARUKERA': centerCoordinates = [-61.4167, 16.25] // Guadeloupe zoomLevel = 9 styleFileName = 'ortho.json' break case 'WANAKAERA': centerCoordinates = [-61.01635, 14.60285] // Martinique zoomLevel = 9.5 styleFileName = 'ortho.json' break case 'GUYANE': centerCoordinates = [-53.1258, 3.9339] // Guyane française zoomLevel = 7 styleFileName = 'ortho-jwe.json' break case 'DINA-MORGABIN': centerCoordinates = [55.52905, -21.13014] // La Réunion styleFileName = 'ortho.json' zoomLevel = 9.5 break default: centerCoordinates = [-61.4167, 16.25] // Par défaut (Guadeloupe) styleFileName = 'ortho.json' zoomLevel = 9.5 } /** * Définit les décalages des popups en fonction de leur position. */ const popupOffsets = { 'top': [0, 0], 'top-left': [0, 0], 'top-right': [0, 0], 'bottom': [0, -30], 'bottom-left': [0, -50], 'bottom-right': [0, -50], 'left': [25, -25], 'right': [-25, -25] } /** * Référence aux éléments HTML pour afficher le score. */ let ok let ko let penaltyElement /** * Gestion du temps écoulé */ let gameTime = 0 let gameMinute = 0 let gameSecond = 0 let gamePenalty = 0 let gameTimeWithPenalty = 0 let gameMinuteWithPenalty = 0 let gameSecondWithPenalty = 0 let intervalId /** * État actuel de la commune survolée. */ let hoveredStateId = null /** * Référence à la commune actuellement sélectionnée pour trouver. */ let currentZone = null /** * Ensemble des codes des communes déjà trouvées correctement. */ const correctAnswers = new Set() /** * Permet de remettre le jeu à 0 */ function resetGame() { ok.innerText = 0 ko.innerText = 0 correctAnswers.clear() clearInterval(intervalId) intervalId = null gameTime = 0 gamePenalty = 0 gameTimeWithPenalty = 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é :

" communeObserver() ok = document.getElementById('OK') ko = document.getElementById('KO') penaltyElement = document.getElementById('penalty') startButton.setAttribute('disabled', true) restartButton.removeAttribute('disabled') resetGame() } /** * Recommencer la partie */ function restartGame() { startGame() } /** * Observe le contenu de l'élément ayant pour id "commune" */ function communeObserver(){ const communeElement = document.getElementById('commune') const timeElement = document.getElementById('time') // 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 (var mutation of mutationsList) { if (mutation.type == "childList" || mutation.type == "characterData") { // Vérifie si le contenu de la balise span n'est pas égal à "..." if (communeElement.textContent.trim() !== "...") { // Vérifie si setInterval n'est pas déjà en cours if (!intervalId) { intervalId = setInterval(() => { gameTime++ gameTimeWithPenalty = gameTime + gamePenalty if (gameTimeWithPenalty >= 60) { gameMinuteWithPenalty = String(gameTimeWithPenalty / 60).split('.')[0] gameMinute = String(gameTime / 60).split('.')[0] gameSecondWithPenalty = gameTimeWithPenalty % 60 gameSecond = gameTime % 60 timeElement.innerText = `${gameMinuteWithPenalty} minute${gameMinuteWithPenalty > 1 ? 's' : ''} ${gameSecondWithPenalty} seconde${gameSecondWithPenalty > 1 ? 's' : ''}` } else { timeElement.innerText = `${gameTimeWithPenalty} seconde${gameTimeWithPenalty > 1 ? 's' : ''}` } }, 1000) } } } } } // Créé une instance de l'observateur lié à 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) } /** * Récupère les propriétés des communes depuis un fichier GeoJSON en fonction du code du département. * * @async * @param {string} codeCarte - Code de la carte. * @returns {Promise} Promesse résolue avec les propriétés des communes. */ async function getFile(codeCarte) { try { const response = await fetch(`../kat/data/${codeCarte}/contours-${codeCarte === 'KAMA' ? 'pays' : 'communes'}.geojson`) if (!response.ok) { throw new Error(`Échec du chargement du fichier JSON : ${response.statusText}`) } data = await response.json() return data.features.map(({properties}) => properties) } catch (error) { console.error('Erreur:', error) throw new Error(error) } } /** * Récupère les coordonnées du centre de la commune depuis le fichier "centres.json" en fonction du code du département. * * @param {string} codeCarte - Code de la carte. * @param {string} codeZone - Code de la zone. * @returns {Promise} Promesse résolue avec les coordonnées du centre de la commune. */ async function getZoneCenterCoordinates(codeCarte, codeZone) { try { // Utiliser fetch pour lire le fichier "centres.json" const response = await fetch(`../kat/data/${codeCarte}/centres.json`) if (!response.ok) { throw new Error(`Échec du chargement du fichier JSON : ${response.statusText}`) } const centerData = await response.json() // Vérifier si le code de la zone existe dans les données if (codeZone in centerData) { const coordinates = centerData[codeZone] return coordinates } else { throw new Error(`Aucune donnée de centre trouvée pour la commune : ${codeZone}`) } } catch (error) { console.error('Erreur lors de la récupération des coordonnées du centre de la commune :', error) throw new Error(error) } } /** * Met à jour le score en fonction de la réponse. * * @param {boolean} isCorrect - Indique si la réponse est correcte. */ function updateScore(isCorrect) { if (isCorrect) { ok.innerText = Number.parseInt(ok.innerText, 10) + 1 } else { ko.innerText = Number.parseInt(ko.innerText, 10) + 1 gamePenalty += 5 penaltyElement.style.opacity = 1 penaltyElement.innerText = '+ 5 secondes' setTimeout(() => { penaltyElement.style.opacity = 0 penaltyElement.innerText = '' }, 2000) } } /** * Sélectionne une commune au hasard parmi celles qui n'ont pas encore été trouvées. * * @param {Array} zones - Liste de toutes les zones. * @returns {Object|null} Retourne la commune sélectionnée ou null si toutes les zones ont été trouvées. */ function pickRandomZone(zones) { const zonesRestantes = zones.filter(commune => !correctAnswers.has(commune.code)) if (zonesRestantes.length === 0) { return null } const index = Math.floor(Math.random() * zonesRestantes.length) return zonesRestantes[index] } /** * Vérifie si le code de la zone cliquée correspond à la commune actuelle à trouver. * * @param {string} codeZone - code de la zone à vérifier. * @returns {boolean} Résultat de la vérification. */ function checkAnswer(codeZone) { return currentZone && codeZone === currentZone.code } /** * Charge la carte */ function loadMap() { /** * Initialise et configure la carte */ const map = new maplibregl.Map({ container: 'map', style: `../kat/styles/${styleFileName}`, center: centerCoordinates, zoom: zoomLevel }) if (codeCarte !== 'GUYANE') { /** * 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() } /** * Code pour la gestion des événements de la carte (load, mousemove, mouseleave, click). */ 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 source de données pour les communes. */ map.addSource('communes', { 'type': 'geojson', 'data': `../kat/data/${codeCarte}/contours-${codeCarte === 'KAMA' ? 'pays' : 'communes'}.geojson`, 'promoteId': 'code' }) /** * 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 = 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) 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) 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 let elapsedTime = `${gameTime} seconde${gameTime > 1 ? 's' : ''}` let elapsedTimeWithPenalty = `${gameTimeWithPenalty} seconde${gameTimeWithPenalty > 1 ? 's' : ''}` if (gameTime >= 60) { elapsedTime = `${gameMinute} minute${gameMinute > 1 ? 's' : ''} ${gameSecond} seconde${gameSecond > 1 ? 's' : ''}` } if (gameTimeWithPenalty >= 60) { elapsedTimeWithPenalty = `${gameMinuteWithPenalty} minute${gameMinuteWithPenalty > 1 ? 's' : ''} ${gameSecondWithPenalty} seconde${gameSecondWithPenalty > 1 ? 's' : ''}` } alert(`${Math.round(percentageCorrectAnswers)}% de bonnes réponses ! \nTemps écoulé : ${elapsedTime} ! ${gamePenalty ? ` \nPénalité : ${gamePenalty} secondes` : '\nAucune pénalité !'} \nTemps total : ${elapsedTimeWithPenalty}`) clearInterval(intervalId) gameTime = 0 gameTimeWithPenalty = 0 setTimeout(() => { location.reload() }, 1000) } } 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) }) }