diff --git a/kittyurl-frontend/src/components/FlappyCat.tsx b/kittyurl-frontend/src/components/FlappyCat.tsx index be8552c..2989b54 100644 --- a/kittyurl-frontend/src/components/FlappyCat.tsx +++ b/kittyurl-frontend/src/components/FlappyCat.tsx @@ -1,16 +1,12 @@ import React, { useState, useEffect, useRef, useCallback } from 'react'; import { ArrowLeft, Trophy, Sparkles } from 'lucide-react'; -// --- MODEL KOTA --- -interface DetailedKittyProps { - isGameOver: boolean; -} - +// --- 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 (
@@ -36,13 +32,16 @@ const DetailedKitty: React.FC = ({ isGameOver }) => { ); }; -// --- KONFIGURACJA --- -const GAP_SIZE = 170; +// --- 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 = 250; // px/s -const PIPE_SPAWN_RATE = 1.5; // sekundy -const GRAVITY = 1600; // px/s^2 -const FLAP_STRENGTH = -450; // px/s +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 }) => { @@ -64,17 +63,17 @@ export const FlappyCat: React.FC<{ onBack: () => void }> = ({ onBack }) => { 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); - const currentHS = parseInt(localStorage.getItem('flappyKittyHighScore') || '0', 10); - if (scoreRef.current > currentHS) { + if (scoreRef.current > highScore) { setHighScore(scoreRef.current); localStorage.setItem('flappyKittyHighScore', scoreRef.current.toString()); } - }, []); + }, [highScore]); const startGame = useCallback(() => { setIsPlaying(true); @@ -86,29 +85,40 @@ export const FlappyCat: React.FC<{ onBack: () => void }> = ({ onBack }) => { 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; - } + 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.15, -20), 90)); + setRotation(Math.min(Math.max(velocityRef.current * 0.12, -20), 70)); - if (kittyYRef.current > CANVAS_HEIGHT - 40 || kittyYRef.current < -20) { + if (kittyYRef.current > CANVAS_HEIGHT - 40 || kittyYRef.current < -40) { endGame(); return; } @@ -121,26 +131,20 @@ export const FlappyCat: React.FC<{ onBack: () => void }> = ({ onBack }) => { } const updatedPipes = []; - // POPRAWKA: użycie 'const p' zamiast 'let p' 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); - } + if (p.x > -PIPE_WIDTH) updatedPipes.push(p); } pipesRef.current = updatedPipes; @@ -176,7 +180,6 @@ export const FlappyCat: React.FC<{ onBack: () => void }> = ({ onBack }) => { className="relative w-full max-w-[600px] h-[450px] bg-sky-100 rounded-[3rem] shadow-2xl border-4 border-white overflow-hidden cursor-pointer select-none" onClick={() => { if (!isPlaying || gameOver) startGame(); else flap(); }} > - {/* DEKORACJE - 'Sparkles' jest teraz używane i zaimportowane */}
@@ -185,7 +188,7 @@ export const FlappyCat: React.FC<{ onBack: () => void }> = ({ onBack }) => {
Record: {highScore}
-
+
diff --git a/kittyurl-frontend/src/components/KittyGame.tsx b/kittyurl-frontend/src/components/KittyGame.tsx index 23dcd8b..6f0aa64 100644 --- a/kittyurl-frontend/src/components/KittyGame.tsx +++ b/kittyurl-frontend/src/components/KittyGame.tsx @@ -15,7 +15,6 @@ const DetailedKitty: React.FC = ({ isJumping, isNight, isGam return (
- {/* OGON */}
= ({ isJumping, isNight, isGam animation: !isGameOver ? 'tail-wag 0.8s ease-in-out infinite' : 'none' }} /> - - {/* TUŁÓW */}
- - {/* GŁOWA */}
@@ -38,20 +33,14 @@ const DetailedKitty: React.FC = ({ isJumping, isNight, isGam
-
{isGameOver ? 'x' :
}
{isGameOver ? 'x' :
}
- -
-
- - {/* ŁAPKI */}
void }> = ({ onBack }) => { const isNight = Math.floor(score / 10) % 2 === 1; - // Referencje do fizyki i czasu + // --- REF DO FPS I FIZYKI --- const kittyYRef = useRef(0); const obstacleXRef = useRef(650); const velocityRef = useRef(0); const scoreRef = useRef(0); const requestRef = useRef(0); const lastTimeRef = useRef(0); + const lastFrameTimestampRef = useRef(0); - // STAŁE KONFIGURACYJNE (Wartości na sekundę) - const GRAVITY = 1800; // px/s^2 - const JUMP_FORCE = -550; // Moc skoku - const INITIAL_SPEED = 350; // px/s - const SPEED_INCREMENT = 15; // Przyspieszenie za każdy punkt + // --- STAŁE --- + const TARGET_FPS = 60; + const FRAME_MIN_TIME = 1000 / TARGET_FPS; // ok. 16.67ms + + const GRAVITY = 1800; + const JUMP_FORCE = -550; + const INITIAL_SPEED = 350; + const SPEED_INCREMENT = 15; const GROUND_Y = 0; const endGame = useCallback(() => { @@ -121,6 +114,7 @@ export const KittyGame: React.FC<{ onBack: () => void }> = ({ onBack }) => { obstacleXRef.current = 650; velocityRef.current = 0; lastTimeRef.current = performance.now(); + lastFrameTimestampRef.current = performance.now(); setDisplayKittyY(0); setDisplayObstacleX(650); }, []); @@ -135,10 +129,19 @@ export const KittyGame: React.FC<{ onBack: () => void }> = ({ onBack }) => { const update = (time: number) => { if (gameOver || !isPlaying) return; + // --- MECHANIZM FPS CAP --- + const elapsedSinceLastFrame = time - lastFrameTimestampRef.current; + if (elapsedSinceLastFrame < FRAME_MIN_TIME) { + requestRef.current = requestAnimationFrame(update); + return; + } + // Delta time (dt) w sekundach const dt = (time - lastTimeRef.current) / 1000; lastTimeRef.current = time; - const frameTime = Math.min(dt, 0.1); // Limit klatki + lastFrameTimestampRef.current = time; // Aktualizujemy czas klatki + + const frameTime = Math.min(dt, 0.1); // Fizyka kota velocityRef.current += GRAVITY * frameTime; @@ -198,7 +201,6 @@ export const KittyGame: React.FC<{ onBack: () => void }> = ({ onBack }) => { ${isNight ? 'bg-slate-950 border-indigo-500 shadow-indigo-500/20' : 'bg-white border-pink-100 shadow-pink-200/50'}`} onClick={() => { if (!isPlaying || gameOver) startGame(); else jump(); }} > - {/* Niebo (Używamy Moon i Sun) */}
@@ -206,7 +208,6 @@ export const KittyGame: React.FC<{ onBack: () => void }> = ({ onBack }) => {
- {/* Wyniki (Używamy Trophy) */}
Score: {score}
@@ -214,12 +215,10 @@ export const KittyGame: React.FC<{ onBack: () => void }> = ({ onBack }) => {
- {/* KOTEK */}
2} isNight={isNight} isGameOver={gameOver} />
- {/* PRZESZKODA */}
void }> = ({ onBack }) => {
- {/* Ziemia */}
{[...Array(9)].map((_, i) => 🐾)}
- {/* Ekrany informacyjne (Używamy Sparkles) */} {!isPlaying && !gameOver && (
@@ -257,6 +254,7 @@ export const KittyGame: React.FC<{ onBack: () => void }> = ({ onBack }) => {
)}
+

Space / Click to Jump

); }; \ No newline at end of file