import React, { useState, useEffect, useRef, useCallback } from 'react'; import { ArrowLeft, Trophy, Sparkles, Moon, Sun } from 'lucide-react'; // --- MODEL KOTA (Bez zmian) --- 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 */}
); }; // --- STAŁE WYMIARY LOGICZNE --- const GAME_WIDTH = 650; const GAME_HEIGHT = 340; // --- GŁÓWNY KOMPONENT --- export const KittyGame: React.FC<{ onBack: () => void }> = ({ onBack }) => { // Stany gry 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(GAME_WIDTH); // RWD State: Skala const [scale, setScale] = useState(1); const isNight = Math.floor(score / 10) % 2 === 1; // Referencje fizyczne const kittyYRef = useRef(0); const obstacleXRef = useRef(GAME_WIDTH); const velocityRef = useRef(0); const scoreRef = useRef(0); const requestRef = useRef(0); const lastTimeRef = useRef(0); // STAŁE KONFIGURACYJNE const GRAVITY = 1800; const JUMP_FORCE = -550; const INITIAL_SPEED = 380; const SPEED_INCREMENT = 12; const GROUND_Y = 0; // --- RWD LOGIC --- useEffect(() => { const handleResize = () => { const availableWidth = window.innerWidth - 32; // marginesy boczne const availableHeight = window.innerHeight - 100; // miejsce na nagłówek const scaleX = availableWidth / GAME_WIDTH; const scaleY = availableHeight / GAME_HEIGHT; // Skalujemy w dół jeśli ekran jest mały, ale max 1 (nie powiększamy na dużych ekranach) setScale(Math.min(scaleX, scaleY, 1)); }; window.addEventListener('resize', handleResize); handleResize(); return () => window.removeEventListener('resize', handleResize); }, []); 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 = GAME_WIDTH + 50; velocityRef.current = 0; lastTimeRef.current = performance.now(); setDisplayKittyY(0); setDisplayObstacleX(GAME_WIDTH + 50); }, []); const jump = useCallback(() => { if (kittyYRef.current <= GROUND_Y && !gameOver && isPlaying) { velocityRef.current = JUMP_FORCE; } }, [gameOver, isPlaying]); // --- OBSŁUGA WEJŚCIA (Touch & Mouse) --- const handleAction = useCallback((e?: React.SyntheticEvent) => { // Nie używamy e.preventDefault() tutaj, bo CSS touch-action załatwia sprawę if (e) e.stopPropagation(); if (!isPlaying || gameOver) startGame(); else jump(); }, [isPlaying, gameOver, startGame, jump]); useEffect(() => { const update = (currentTime: number) => { if (gameOver || !isPlaying) return; const dt = (currentTime - lastTimeRef.current) / 1000; lastTimeRef.current = currentTime; const frameTime = Math.min(dt, 0.1); // Fizyka velocityRef.current += GRAVITY * frameTime; kittyYRef.current -= velocityRef.current * frameTime; if (kittyYRef.current <= GROUND_Y) { kittyYRef.current = GROUND_Y; velocityRef.current = 0; } // Przeszkoda const currentSpeed = INITIAL_SPEED + (scoreRef.current * SPEED_INCREMENT); obstacleXRef.current -= currentSpeed * frameTime; if (obstacleXRef.current < -80) { obstacleXRef.current = GAME_WIDTH + 50; scoreRef.current += 1; setScore(scoreRef.current); } // Kolizja if (obstacleXRef.current < 100 && obstacleXRef.current > 20 && kittyYRef.current < 45) { endGame(); return; } 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(); handleAction(); } }; window.addEventListener('keydown', handleKey); return () => window.removeEventListener('keydown', handleKey); }, [handleAction]); return (
{/* KONTENER SKALOWANIA */}
{/* Niebo */}
{/* UI Score */}
Score: {score}
High: {highScore}
{/* KOTEK */}
2} isNight={isNight} isGameOver={gameOver} />
{/* PRZESZKODA */}
{/* Ziemia */}
{[...Array(10)].map((_, i) => 🐾)}
{/* EKRAN STARTOWY */} {!isPlaying && !gameOver && (

Tap or Space

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

Oh No! 😿

Score: {score}
)}
{/* Informacja dla mobile */}
Tap anywhere to jump
); };