import React, { useState, useEffect, useRef, useCallback } from 'react'; import { ArrowLeft, Trophy, Sparkles } from 'lucide-react'; // --- MODEL KOTA (Wizualizacja) --- 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 GRY --- const GAP_SIZE = 170; // Przerwa między rurami const PIPE_WIDTH = 70; const PIPE_SPEED = 220; // px/s const PIPE_SPAWN_RATE = 1.6; // co ile sekund rura const GRAVITY = 1400; // siła grawitacji const FLAP_STRENGTH = -420; // siła skoku // Logiczne wymiary gry (fizyka działa na tych wartościach, a ekran je tylko skaluje) const GAME_HEIGHT = 450; const GAME_WIDTH = 600; export const FlappyCat: React.FC<{ onBack: () => void }> = ({ onBack }) => { // --- STANY --- 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; }); // Stany do renderowania const [displayKittyY, setDisplayKittyY] = useState(150); const [displayPipes, setDisplayPipes] = useState<{ x: number; topHeight: number; id: number }[]>([]); const [rotation, setRotation] = useState(0); const [scale, setScale] = useState(1); // Skala RWD // --- REFS (FIZYKA) --- 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 spawnTimerRef = useRef(0); // --- RWD: Obliczanie skali --- useEffect(() => { const handleResize = () => { const availableWidth = window.innerWidth - 32; // margines boczny const availableHeight = window.innerHeight - 100; // miejsce na nagłówek const scaleX = availableWidth / GAME_WIDTH; const scaleY = availableHeight / GAME_HEIGHT; // Dopasuj do ekranu, ale nie powiększaj powyżej 100% (żeby nie tracić jakości) const newScale = Math.min(scaleX, scaleY, 1); setScale(newScale); }; window.addEventListener('resize', handleResize); handleResize(); // Init return () => window.removeEventListener('resize', handleResize); }, []); // --- LOGIKA GRY --- const endGame = useCallback(() => { setGameOver(true); setIsPlaying(false); const currentHS = parseInt(localStorage.getItem('flappyKittyHighScore') || '0', 10); if (scoreRef.current > currentHS) { setHighScore(scoreRef.current); localStorage.setItem('flappyKittyHighScore', scoreRef.current.toString()); } }, []); 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(); setDisplayKittyY(150); setDisplayPipes([]); setRotation(0); }, []); const flap = useCallback(() => { if (isPlaying && !gameOver) velocityRef.current = FLAP_STRENGTH; }, [isPlaying, gameOver]); // --- OBSŁUGA INPUTU (NAPRAWIONA) --- const handleAction = useCallback((e?: React.SyntheticEvent) => { // WAŻNE: Nie używamy e.preventDefault() tutaj, bo to powoduje błąd w Chrome. // Zamiast tego CSS 'touch-action: none' blokuje scrollowanie. if (e) { e.stopPropagation(); } if (!isPlaying || gameOver) { startGame(); } else { flap(); } }, [isPlaying, gameOver, startGame, flap]); // --- PĘTLA GRY --- useEffect(() => { const update = (currentTime: number) => { if (gameOver || !isPlaying) return; // Delta time w sekundach const dt = (currentTime - lastTimeRef.current) / 1000; lastTimeRef.current = currentTime; const frameTime = Math.min(dt, 0.1); // Limit laga // 1. Fizyka grawitacji velocityRef.current += GRAVITY * frameTime; kittyYRef.current += velocityRef.current * frameTime; // Rotacja setRotation(Math.min(Math.max(velocityRef.current * 0.12, -25), 90)); // Kolizja z sufitem/podłogą if (kittyYRef.current > GAME_HEIGHT - 40 || kittyYRef.current < -50) { endGame(); return; } // 2. Generowanie rur spawnTimerRef.current += frameTime; if (spawnTimerRef.current >= PIPE_SPAWN_RATE) { const minPipe = 60; const maxPipe = GAME_HEIGHT - GAP_SIZE - minPipe; const topHeight = Math.random() * (maxPipe - minPipe) + minPipe; pipesRef.current.push({ x: GAME_WIDTH + 50, topHeight, id: Date.now() }); spawnTimerRef.current = 0; } // 3. Ruch rur i kolizje const updatedPipes = []; for (const p of pipesRef.current) { p.x -= PIPE_SPEED * frameTime; // Hitbox rury (marginesy dla łatwiejszej gry) const hitXLeft = p.x + 5; const hitXRight = p.x + PIPE_WIDTH - 5; // Sprawdzenie czy kot jest w poziomie rury if (hitXLeft < 100 && hitXRight > 55) { // Sprawdzenie czy kot uderzył w górę lub dół // (dodajemy marginesy bezpieczeństwa) if (kittyYRef.current < p.topHeight || kittyYRef.current > p.topHeight + GAP_SIZE - 45) { endGame(); return; } } // Naliczanie punktów if (p.x < 50 && !p.passed) { p.passed = true; scoreRef.current += 1; setScore(scoreRef.current); } // Usuwanie starych rur if (p.x > -PIPE_WIDTH - 50) updatedPipes.push(p); } pipesRef.current = updatedPipes; // 4. Renderowanie setDisplayKittyY(kittyYRef.current); setDisplayPipes([...pipesRef.current]); requestRef.current = requestAnimationFrame(update); }; if (isPlaying && !gameOver) { requestRef.current = requestAnimationFrame(update); } return () => cancelAnimationFrame(requestRef.current); }, [isPlaying, gameOver, endGame]); // Obsługa Spacji useEffect(() => { const handleKey = (e: KeyboardEvent) => { if (e.code === 'Space') { e.preventDefault(); // Tu można bezpiecznie użyć preventDefault dla klawiatury handleAction(); } }; window.addEventListener('keydown', handleKey); return () => window.removeEventListener('keydown', handleKey); }, [handleAction]); return (
{/* WRAPPER SKALUJĄCY GRĘ */}
{/* TŁO / DEKORACJE */}
{/* CHMURY (Ozdoba) */}
{/* HUD */}
Score: {score}
Record: {highScore}
{/* GRACZ (KOT) */}
{/* RURY */} {displayPipes.map(p => ( {/* Górna rura */}
{/* Dolna rura */}
))} {/* EKRAN STARTOWY */} {!isPlaying && !gameOver && (

Flappy Cat 🎈

Tap to Jump

START
)} {/* GAME OVER */} {gameOver && (

Oops! 😿

Score: {score}

)}
Tap anywhere to jump
); };