import React, { useState, useEffect, useRef, useCallback } from 'react'; import { ArrowLeft, Trophy, Sparkles, Moon, Sun } from 'lucide-react'; // --- MODEL KOTA (Animacje CSS zostają, bo są czasowe, nie klatkowe) --- interface DetailedKittyProps { isJumping: boolean; isNight: boolean; isGameOver: boolean; } const DetailedKitty: React.FC = ({ isJumping, isNight, isGameOver }) => { const mainColor = isGameOver ? (isNight ? '#475569' : '#cbd5e1') : (isNight ? '#f8fafc' : '#f472b6'); const stripeColor = isNight ? '#cbd5e1' : '#ec4899'; const earColor = isNight ? '#334155' : '#fbcfe8'; return (
{isGameOver ? 'x' :
}
{isGameOver ? 'x' :
}
{/* Łapki */}
); }; // --- GŁÓWNY KOMPONENT Z DYNAMICZNYM DELTA TIME --- export const KittyGame: React.FC<{ onBack: () => void }> = ({ onBack }) => { const [isPlaying, setIsPlaying] = useState(false); const [gameOver, setGameOver] = useState(false); const [score, setScore] = useState(0); const [highScore, setHighScore] = useState(() => { const saved = localStorage.getItem('kittyHighScore'); return saved ? parseInt(saved, 10) : 0; }); const [displayKittyY, setDisplayKittyY] = useState(0); const [displayObstacleX, setDisplayObstacleX] = useState(650); const isNight = Math.floor(score / 10) % 2 === 1; // Referencje fizyczne const kittyYRef = useRef(0); const obstacleXRef = useRef(650); const velocityRef = useRef(0); const scoreRef = useRef(0); const requestRef = useRef(0); const lastTimeRef = useRef(0); // STAŁE KONFIGURACYJNE (Wartości na sekundę - niezależne od Hz) const GRAVITY = 1800; // Kot spadnie o 1800px w ciągu sekundy (jeśli nie ma prędkości początkowej) const JUMP_FORCE = -550; // Prędkość startowa skoku const INITIAL_SPEED = 380; // Prędkość przeszkody w px/s const SPEED_INCREMENT = 12; // Przyspieszenie px/s na każdy punkt const GROUND_Y = 0; const endGame = useCallback(() => { setGameOver(true); setIsPlaying(false); const currentHS = parseInt(localStorage.getItem('kittyHighScore') || '0', 10); if (scoreRef.current > currentHS) { setHighScore(scoreRef.current); localStorage.setItem('kittyHighScore', scoreRef.current.toString()); } }, []); const startGame = useCallback(() => { setIsPlaying(true); setGameOver(false); setScore(0); scoreRef.current = 0; kittyYRef.current = 0; obstacleXRef.current = 700; velocityRef.current = 0; // Kluczowe: inicjalizacja czasu startu lastTimeRef.current = performance.now(); setDisplayKittyY(0); setDisplayObstacleX(700); }, []); const jump = useCallback(() => { if (kittyYRef.current <= GROUND_Y && !gameOver && isPlaying) { velocityRef.current = JUMP_FORCE; } }, [gameOver, isPlaying]); useEffect(() => { const update = (currentTime: number) => { if (gameOver || !isPlaying) return; // --- OBLICZANIE DELTA TIME (Dynamiczna szybkość) --- // dt to czas w sekundach, jaki upłynął od ostatniej klatki (np. 0.016 dla 60Hz, 0.007 dla 144Hz) const dt = (currentTime - lastTimeRef.current) / 1000; lastTimeRef.current = currentTime; // Zabezpieczenie przed "skokiem" (np. gdy użytkownik zmieni kartę w przeglądarce) const frameTime = Math.min(dt, 0.1); // Fizyka kota (jednostki * czas) velocityRef.current += GRAVITY * frameTime; kittyYRef.current -= velocityRef.current * frameTime; if (kittyYRef.current <= GROUND_Y) { kittyYRef.current = GROUND_Y; velocityRef.current = 0; } // Ruch przeszkody (prędkość * czas) const currentSpeed = INITIAL_SPEED + (scoreRef.current * SPEED_INCREMENT); obstacleXRef.current -= currentSpeed * frameTime; // Reset przeszkody if (obstacleXRef.current < -80) { obstacleXRef.current = 750; scoreRef.current += 1; setScore(scoreRef.current); } // Kolizja (strefa trafienia dopasowana do czasu) if (obstacleXRef.current < 100 && obstacleXRef.current > 20 && kittyYRef.current < 45) { endGame(); return; } // Renderowanie wizualne setDisplayKittyY(kittyYRef.current); setDisplayObstacleX(obstacleXRef.current); requestRef.current = requestAnimationFrame(update); }; if (isPlaying && !gameOver) { requestRef.current = requestAnimationFrame(update); } return () => cancelAnimationFrame(requestRef.current); }, [isPlaying, gameOver, endGame]); useEffect(() => { const handleKey = (e: KeyboardEvent) => { if (e.code === 'Space') { e.preventDefault(); if (!isPlaying || gameOver) startGame(); else jump(); } }; window.addEventListener('keydown', handleKey); return () => window.removeEventListener('keydown', handleKey); }, [isPlaying, gameOver, startGame, jump]); return (
{ if (!isPlaying || gameOver) startGame(); else jump(); }} > {/* Niebo */}
{/* UI Score */}
Score: {score}
High: {highScore}
{/* KOTEK */}
2} isNight={isNight} isGameOver={gameOver} />
{/* PRZESZKODA */}
{/* Ziemia */}
{[...Array(10)].map((_, i) => 🐾)}
{/* Ekrany start/stop */} {!isPlaying && !gameOver && (

Click or Space

)} {gameOver && (

Oh No! 😿

Score: {score}
)}
); };