555 lines
18 KiB
JavaScript
555 lines
18 KiB
JavaScript
/**
|
|
* 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 'WANAKERA':
|
|
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 = `<p style='font-weight: bold;'>Trouve ${codeCarte === 'KAMA' ? 'le pays' : 'la commune'}</p><p id='commune' style='font-size: 30px; font-weight: bold; color: #1F51FF'>...</p>`
|
|
score.innerHTML = "<p style='color: green; font-weight: bold; margin-right: 2em;'>Correct ✅ : <span style='color: black;' id='OK'>0</span></p><p style='color: red; font-weight: bold;'>Faux ❌ : <span style='color: black;' id='KO'>0</span></p>"
|
|
chrono.innerHTML = "<p>Temps écoulé : <span id='time'></span><span style='margin-left: 1em;color: red; font-weight: bold;' id='penalty'></span></p>"
|
|
|
|
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<Object[]>} 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<number[]>} 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(`<h3>Bravo, c'est ${clickedZoneNom}</h3>`)
|
|
.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(`<h3>Faux, c'est ${clickedZoneNom}</h3>`)
|
|
.setMaxWidth('200px')
|
|
.addTo(map)
|
|
|
|
setTimeout(() => {
|
|
popup.remove()
|
|
}, 1500)
|
|
|
|
map.setFeatureState(
|
|
{source: 'communes', id: clickedCodeZone},
|
|
{incorrect: true}
|
|
)
|
|
}
|
|
}
|
|
})
|
|
|
|
const zone = pickRandomZone(zones)
|
|
setZone(zone)
|
|
})
|
|
}
|