import rawLieux from './data/lieux.json'; import type { Categorie, Difficulte, ExplorerLieu, GuessResult, Lieu, Region, RoundPlace } from '$lib/types'; const lieux = rawLieux as Lieu[]; const REGIONS: Region[] = ['GUADELOUPE', 'MARTINIQUE', 'GUYANE', 'REUNION']; const CATEGORIES: Categorie[] = ['MONUMENT', 'LIEU']; export const MAX_POINTS = 5000; /** Rayon (km) de la courbe de score selon la difficulté du lieu. */ export const RAYONS: Record = { FACILE: 50, MOYEN: 15, DIFFICILE: 5, EXPERT: 1 }; /** Distance haversine en km. */ export function haversineKm(a: { lat: number; lon: number }, b: { lat: number; lon: number }): number { const R = 6371; const dLat = ((b.lat - a.lat) * Math.PI) / 180; const dLon = ((b.lon - a.lon) * Math.PI) / 180; const s = Math.sin(dLat / 2) ** 2 + Math.cos((a.lat * Math.PI) / 180) * Math.cos((b.lat * Math.PI) / 180) * Math.sin(dLon / 2) ** 2; return 2 * R * Math.asin(Math.sqrt(s)); } function parseRegion(value: string | null): Region | null { return value && REGIONS.includes(value as Region) ? (value as Region) : null; } function parseCategorie(value: string | null): Categorie | null { return value && CATEGORIES.includes(value as Categorie) ? (value as Categorie) : null; } export function getLieuById(id: string): Lieu | undefined { return lieux.find((l) => l.id === id); } /** Sélection aléatoire d'un lieu, avec filtres optionnels. */ export function getRandomLieu(options: { region?: string | null; categorie?: string | null; exclude?: string | null; }): Lieu | null { const region = parseRegion(options.region ?? null); const categorie = parseCategorie(options.categorie ?? null); const excluded = new Set( (options.exclude ?? '') .split(',') .map((s) => s.trim()) .filter(Boolean) ); let pool = lieux.filter((l) => !excluded.has(l.id)); if (region) pool = pool.filter((l) => l.region === region); if (categorie) pool = pool.filter((l) => l.categorie === categorie); // Si tout a été exclu, on réessaie sans les exclusions (le jeu doit continuer). if (pool.length === 0) { pool = lieux; if (region) pool = pool.filter((l) => l.region === region); if (categorie) pool = pool.filter((l) => l.categorie === categorie); } if (pool.length === 0) return null; return pool[Math.floor(Math.random() * pool.length)]; } /** Anti-triche : ne garde que les champs publiques pour /api/round. */ export function sanitizeRound(lieu: Lieu): RoundPlace { return { id: lieu.id, nom: lieu.nom, nom_creole: lieu.nom_creole, categorie: lieu.categorie, difficulte: lieu.difficulte, photo: lieu.photo, indices: lieu.indices }; } /** * Scoring recalculé côté serveur : * score = round(5000 × e^(−d/r)), r selon difficulté ; * pénalité −10 % par indice ; bonus temps ≤ +20 % ; total plafonné à 5000. */ export function scoreGuess( lieu: Lieu, guess: { lat: number; lon: number }, hintsUsed: number, timeMs?: number ): GuessResult { const distanceKm = haversineKm(guess, lieu.coordonnees); const base = Math.round(MAX_POINTS * Math.exp(-distanceKm / RAYONS[lieu.difficulte])); const penalises = base * Math.max(0, 1 - 0.1 * Math.max(0, hintsUsed)); let total = penalises; if (typeof timeMs === 'number' && Number.isFinite(timeMs) && timeMs >= 0) { total += penalises * 0.2 * Math.max(0, 1 - timeMs / 60000); } const score = Math.min(MAX_POINTS, Math.round(total)); return { distanceKm: Math.round(distanceKm * 10) / 10, score, coordonnees: lieu.coordonnees, commune: lieu.commune, region: lieu.region, nom: lieu.nom, nom_creole: lieu.nom_creole, wikidata_id: lieu.wikidata_id }; } /** Réponse « Passer » : la vérité est révélée, score 0, pas de distance. */ export function skipResult(lieu: Lieu): GuessResult { return { distanceKm: null, score: 0, coordonnees: lieu.coordonnees, commune: lieu.commune, region: lieu.region, nom: lieu.nom, nom_creole: lieu.nom_creole, wikidata_id: lieu.wikidata_id }; } /** Catalogue Explorer : coords arrondies à 0.05° (mode sans score, fiches autorisées). */ export function catalogueExplorer(): ExplorerLieu[] { return lieux.map((l) => ({ id: l.id, nom: l.nom, nom_creole: l.nom_creole, region: l.region, commune: l.commune, coordonnees: { lat: Math.round(l.coordonnees.lat / 0.05) * 0.05, lon: Math.round(l.coordonnees.lon / 0.05) * 0.05 }, categorie: l.categorie, difficulte: l.difficulte, wikidata_id: l.wikidata_id, photo: l.photo })); }