import { useState, useEffect, useRef } from 'react'; import { Timer, Trophy, Flag, Play, RotateCcw, ChevronUp, Volume2, VolumeX, Users } from 'lucide-react'; export default function JeuEscaladeVitesse() { // États du jeu const [gameState, setGameState] = useState('menu'); // 'menu', 'countdown', 'playing', 'finished' const [position, setPosition] = useState(0); // Position du grimpeur (0-100) const [time, setTime] = useState(0); // Temps en millisecondes const [bestTime, setBestTime] = useState(null); // Meilleur temps const [lastMove, setLastMove] = useState(null); // 'left' ou 'right' const [clickCount, setClickCount] = useState(0); // Nombre de clics const [countdown, setCountdown] = useState(3); // Compte à rebours avant le départ const [soundEnabled, setSoundEnabled] = useState(true); // Son activé ou désactivé const [gameMode, setGameMode] = useState('solo'); // 'solo' ou 'duel' const [opponentPosition, setOpponentPosition] = useState(0); // Position de l'adversaire en mode duel const [worldRecord, setWorldRecord] = useState(5.48); // Record du monde actuel (en secondes) // Références pour les animations et le timer const timerRef = useRef(null); const countdownRef = useRef(null); const opponentTimerRef = useRef(null); const startTimeRef = useRef(null); const audioRef = useRef(null); // Hauteur totale à grimper (15 mètres standard) const totalHeight = 100; // Positions officielles des prises sur la voie de vitesse (simplifiées) const officialHolds = { left: [5, 12, 20, 28, 36, 44, 52, 60, 68, 76, 84, 92], right: [8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96] }; // Récupération du meilleur temps depuis le stockage local useEffect(() => { const savedBestTime = localStorage.getItem('bestTimeEscaladeOfficielle'); if (savedBestTime) { setBestTime(parseInt(savedBestTime)); } // Créer les éléments audio audioRef.current = { beep: new Audio('data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLHPM+N2TQwUHHFvF/fLEeRsABjOq8Pzow5lQGwAJQc3//tzCp3coBAFV0PH7yLOqiUsQAVrO6/bYxrKqnWcfB1HBzOLc3uDcxJdSKhUyf6qzvsG8qoJKKCUdKFp6kKOyurquj2tKKCUmL0VTZHGAjJeepKeomIVwWUg2IhYQEBUcKDZJXnacprKzsK6nlIBpTjYcCgQDChUkPVh2l6y6xMTAvK+YgGBEJg0EBQ0YKkVjiqS3yNDOyMGwnHZULxQGCRMiOFRxjZuqvMjW2dTLtJt2US8VCQ8dMk1phJOhsr/P3OLg1sKkhF88GQsSIThRbH+PlqW1ydzo7OjbxKeEXDgcDRUkO1VugIuSnbHH2+fx8OXNroRbOB0PGCc9V2x+iZCcssbZ6fHy6NC0i188HhAaKT9XbH2FjZqtxdnm8PLm0LWNYEAhEhwrQFhsfYWNmq3F2efw8ebQtY1gQCESHCtAWGx9hY2arcXZ5/Dw5tC1jWBAIRIcK0BYbH2FjZqtxdnn8PDm0LWNYEAhEhwrQFhsfYWNmq3F2efw8ObQtY1gQCESHCtAWGx9hY2arcXZ5/Dw5tC1jWBAIRIcK0BYbH2FjZqtxdnn8PDm0LWNYEAhEhwrQFhsfYWNmq3F2efw8ObQtY1gQCESHCtAWGx9hY2arcXZ5/Dw5tC1jWBAIRIcK0BYbH2FjZqtxdnn8PDm0LWNYEAhEhwrQFhsfYWNmq3F2efw8ObQtY1gQCESHCtAWGx9hY2arcXZ5/Dw5tC1jWBAIRIcK0BYbH2FjZqtxdnn8PDm0LWNYEAhEhwrQFhsfYWNmq3F2efw8ObQtY1gQCESHCtAWGx9hY2arcXZ5/Dw5tC1jWBAIRIcK0BYbH2FjZqtxdnn8PDm0LWNYEAhEhwrQFhsfYWNmq3F2efw8ObQtY1gQCESHCtAWGx9hY2arcXZ5/Dw5tC1jWBAIRIcK0BYbH2FjZqtxdnn8PDm0LWNYEAhEhwrQFhsfYWNmq3F2efw8ObQtY1gQCESHCtAWGx9hY2arcXZ5/Dw5tC1jWBAIRIcK0BYbH2FjZqtxdnn8PDm0LWNYEAhEhwrQFhsfYWNmq3F2efw8ObQtY1gQCESHCtAWGx9hY2arcXZ5/Dw5tC1jWBAIRIcK0BYbH2FjZqtxdnn8PDm0LWNYEAhEhwrQFhsfYWNmq3F2efw8ObQtY1gQCESHCtAWGx9hY2arcXZ5/Dw5tC1jWBAIRIcK0BYbH2FjZqtxdnn8PDm0LWNYEAhEhwrQFhsfYWNmq3F2efw8ObQtY1gQCESHCtAWGx9hY2arcXZ5/Dw5tC1jWBAIRIcK0BYbH2FjZqtxdnn8PDm0LWNYEAhEhwrQFhsfYWNmq3F2efw8ObQtY1gQCESHCtAWGx9hY2arcXZ5/Dw5tC1jWBAIRIcK0BYbH2FjZqtxdnn8PDm0LWNYA=='), start: new Audio('data:audio/wav;base64,UklGRvwHAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQwHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA')} }, []); // Fonction pour jouer un son (simulé) const playSound = (type) => { if (!soundEnabled || !audioRef.current) return; const audio = audioRef.current[type]; if (audio) { audio.currentTime = 0; audio.play(); } }; // Démarrer le compte à rebours const startCountdown = () => { setGameState('countdown'); setCountdown(3); countdownRef.current = setInterval(() => { setCountdown((prev) => { if (prev <= 1) { clearInterval(countdownRef.current); startGame(); return 0; } playSound('beep'); return prev - 1; }); }, 1000); }; // Démarrer le jeu const startGame = () => { setGameState('playing'); setPosition(0); setTime(0); setLastMove(null); setClickCount(0); setOpponentPosition(0); startTimeRef.current = Date.now(); // Démarrer le timer timerRef.current = setInterval(() => { setTime(Date.now() - startTimeRef.current); }, 10); // En mode duel, démarrer le timer de l'adversaire if (gameMode === 'duel') { const opponentSpeed = worldRecord * 1000 / totalHeight; // Vitesse basée sur le record du monde opponentTimerRef.current = setInterval(() => { setOpponentPosition((prev) => { const newPos = prev + (100 / (opponentSpeed * 100)); if (newPos >= totalHeight) { clearInterval(opponentTimerRef.current); if (position < totalHeight) { // L'adversaire a gagné playSound('lose'); } return totalHeight; } return newPos; }); }, 10); } playSound('start'); }; // Terminer le jeu const endGame = () => { clearInterval(timerRef.current); clearInterval(opponentTimerRef.current); setGameState('finished'); // Déterminer si le joueur a gagné (en mode duel) const playerWon = gameMode === 'solo' || (position >= totalHeight && opponentPosition < totalHeight); if (playerWon) { playSound('win'); } // Mettre à jour le meilleur temps si nécessaire if (bestTime === null || time < bestTime) { setBestTime(time); localStorage.setItem('bestTimeEscaladeOfficielle', time.toString()); } }; // Réinitialiser le jeu const resetGame = () => { clearInterval(countdownRef.current); clearInterval(timerRef.current); clearInterval(opponentTimerRef.current); setGameState('menu'); setPosition(0); setTime(0); setLastMove(null); setClickCount(0); setOpponentPosition(0); }; // Gérer les clics sur les prises const handleGrip = (side) => { if (gameState !== 'playing') return; // Vérifier que le joueur alterne bien entre gauche et droite if (lastMove === side) return; // Mettre à jour la position du grimpeur const newPosition = position + 5; setPosition(newPosition); setLastMove(side); setClickCount(clickCount + 1); playSound('grip'); // Vérifier si le grimpeur a atteint le sommet if (newPosition >= totalHeight) { endGame(); } }; // Basculer le son const toggleSound = () => { setSoundEnabled(!soundEnabled); }; // Changer le mode de jeu const setMode = (mode) => { setGameMode(mode); }; // Formater le temps pour l'affichage (secondes.millisecondes) const formatTime = (timeMs) => { const seconds = Math.floor(timeMs / 1000); const ms = Math.floor((timeMs % 1000) / 10); return `${seconds}.${ms.toString().padStart(2, '0')}`; }; return (
Meilleur temps: {formatTime(bestTime)}s
)}Record du monde: {worldRecord}s
{gameMode === 'solo' ? "Cliquez alternativement sur les prises gauche et droite pour grimper le plus vite possible jusqu'au sommet!" : "Affrontez le record du monde! Pouvez-vous battre le champion?"}
Préparez-vous...
Votre temps: {formatTime(time)}s
{bestTime === time && (Nouveau record personnel!
)} {gameMode === 'duel' && (= totalHeight && opponentPosition < totalHeight ? "text-emerald-600 font-bold" : "text-red-600"}> {position >= totalHeight && opponentPosition < totalHeight ? "Vous avez battu le champion!" : `Le champion a gagné en ${worldRecord}s`}
)}Nombre de mouvements: {clickCount}