import React, { useState, useEffect, useRef, useCallback } from 'react'; import { ArrowLeft, Trophy, Sparkles } from 'lucide-react'; // --- MODEL KOTA (bez zmian) --- interface DetailedKittyProps { isGameOver: boolean; } const DetailedKitty: React.FC = ({ isGameOver }) => { const mainColor = isGameOver ? '#cbd5e1' : '#f472b6'; const stripeColor = '#ec4899'; const earColor = '#fbcfe8'; return (
{isGameOver ? 'x' :
}
{isGameOver ? 'x' :
}
); }; // --- KONFIGURACJA STABILNA --- const FPS_LIMIT = 60; const FRAME_MIN_TIME = 1000 / FPS_LIMIT; // ok. 16.67ms const GAP_SIZE = 180; const PIPE_WIDTH = 70; const PIPE_SPEED = 180; const PIPE_SPAWN_RATE = 2.0; const GRAVITY = 1200; const FLAP_STRENGTH = -380; const CANVAS_HEIGHT = 450; export const FlappyCat: 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('flappyKittyHighScore'); return saved ? parseInt(saved, 10) : 0; }); const [displayKittyY, setDisplayKittyY] = useState(150); const [displayPipes, setDisplayPipes] = useState<{ x: number; topHeight: number; id: number }[]>([]); const [rotation, setRotation] = useState(0); const kittyYRef = useRef(150); const velocityRef = useRef(0); const pipesRef = useRef<{ x: number; topHeight: number; id: number; passed?: boolean }[]>([]); const scoreRef = useRef(0); const requestRef = useRef(0); const lastTimeRef = useRef(0); const lastFrameTimestampRef = useRef(0); // Do pilnowania FPS const spawnTimerRef = useRef(0); const endGame = useCallback(() => { setGameOver(true); setIsPlaying(false); if (scoreRef.current > highScore) { setHighScore(scoreRef.current); localStorage.setItem('flappyKittyHighScore', scoreRef.current.toString()); } }, [highScore]); const startGame = useCallback(() => { setIsPlaying(true); setGameOver(false); setScore(0); scoreRef.current = 0; kittyYRef.current = 150; velocityRef.current = 0; pipesRef.current = []; spawnTimerRef.current = 0; lastTimeRef.current = performance.now(); lastFrameTimestampRef.current = performance.now(); setDisplayKittyY(150); setDisplayPipes([]); }, []); const flap = useCallback(() => { if (isPlaying && !gameOver) velocityRef.current = FLAP_STRENGTH; }, [isPlaying, gameOver]); useEffect(() => { const update = (currentTime: number) => { if (gameOver || !isPlaying) return; // --- MECHANIZM FPS CAP --- const elapsedSinceLastFrame = currentTime - lastFrameTimestampRef.current; // Jeśli klatka przyszła za szybko (np. na monitorze 144Hz), pomijamy update if (elapsedSinceLastFrame < FRAME_MIN_TIME) { requestRef.current = requestAnimationFrame(update); return; } // Obliczamy dt na podstawie rzeczywistego czasu, który upłynął const dt = (currentTime - lastTimeRef.current) / 1000; lastTimeRef.current = currentTime; lastFrameTimestampRef.current = currentTime; // Aktualizujemy znacznik klatki const frameTime = Math.min(dt, 0.1); velocityRef.current += GRAVITY * frameTime; kittyYRef.current += velocityRef.current * frameTime; setRotation(Math.min(Math.max(velocityRef.current * 0.12, -20), 70)); if (kittyYRef.current > CANVAS_HEIGHT - 40 || kittyYRef.current < -40) { endGame(); return; } spawnTimerRef.current += frameTime; if (spawnTimerRef.current >= PIPE_SPAWN_RATE) { const topHeight = Math.random() * (220 - 70) + 70; pipesRef.current.push({ x: 650, topHeight, id: Date.now() }); spawnTimerRef.current = 0; } const updatedPipes = []; for (const p of pipesRef.current) { p.x -= PIPE_SPEED * frameTime; if (p.x < 100 && p.x + PIPE_WIDTH > 50) { if (kittyYRef.current < p.topHeight || kittyYRef.current > p.topHeight + GAP_SIZE - 45) { endGame(); return; } } if (p.x < 50 && !p.passed) { p.passed = true; scoreRef.current += 1; setScore(scoreRef.current); } if (p.x > -PIPE_WIDTH) updatedPipes.push(p); } pipesRef.current = updatedPipes; setDisplayKittyY(kittyYRef.current); setDisplayPipes([...pipesRef.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 flap(); } }; window.addEventListener('keydown', handleKey); return () => window.removeEventListener('keydown', handleKey); }, [isPlaying, gameOver, startGame, flap]); return (
{ if (!isPlaying || gameOver) startGame(); else flap(); }} >
Score: {score}
Record: {highScore}
{displayPipes.map(p => (
))} {!isPlaying && !gameOver && (

Flappy Cat 🎈

)} {gameOver && (

Oops! 😿

Score: {score}

)}
); };