import React, { useState, useEffect, useRef } from 'react'; import { Sun, Moon, Volume2, VolumeX, Pause, Play } from 'lucide-react'; export default function CloudJumper() { // Game state const [gameStarted, setGameStarted] = useState(false); const [gameOver, setGameOver] = useState(false); const [score, setScore] = useState(0); const [height, setHeight] = useState(0); const [highScore, setHighScore] = useState(0); const [maxHeight, setMaxHeight] = useState(0); const [paused, setPaused] = useState(false); const [muted, setMuted] = useState(false); const [nightMode, setNightMode] = useState(false); const [powerUps, setPowerUps] = useState({ jumpBoost: false, shield: false, flight: false, }); // Game canvas refs const gameRef = useRef(null); const requestRef = useRef(null); const lastTimeRef = useRef(0); // Game objects const [player, setPlayer] = useState({ x: 150, y: 300, width: 30, height: 30, velocityY: 0, velocityX: 0, jumping: false, falling: true, jumpPower: 15, gravity: 0.5, color: '#FF6B6B', }); const [clouds, setClouds] = useState([]); const [collectibles, setCollectibles] = useState([]); const [hazards, setHazards] = useState([]); // Game settings const gameWidth = 400; const gameHeight = 600; const cloudTypes = ['normal', 'moving', 'disappearing', 'bouncy']; const cloudColors = { normal: '#FFFFFF', moving: '#E0F7FA', disappearing: '#B3E5FC', bouncy: '#BBDEFB', }; // Weather and time of day settings const skyGradients = { day: 'linear-gradient(to bottom, #87CEEB, #1E88E5)', night: 'linear-gradient(to bottom, #0D47A1, #000080)', sunset: 'linear-gradient(to bottom, #FF9E80, #4527A0)', storm: 'linear-gradient(to bottom, #546E7A, #263238)', }; // Initialize game useEffect(() => { if (gameStarted && !gameOver && !paused) { // Generate initial clouds generateInitialClouds(); // Start game loop lastTimeRef.current = 0; requestRef.current = requestAnimationFrame(gameLoop); } return () => { if (requestRef.current) { cancelAnimationFrame(requestRef.current); } }; }, [gameStarted, gameOver, paused]); // Load high score from localStorage useEffect(() => { const savedHighScore = localStorage.getItem('cloudJumperHighScore'); const savedMaxHeight = localStorage.getItem('cloudJumperMaxHeight'); if (savedHighScore) setHighScore(parseInt(savedHighScore)); if (savedMaxHeight) setMaxHeight(parseInt(savedMaxHeight)); }, []); // Save high score to localStorage when game over useEffect(() => { if (gameOver) { if (score > highScore) { setHighScore(score); localStorage.setItem('cloudJumperHighScore', score.toString()); } if (height > maxHeight) { setMaxHeight(height); localStorage.setItem('cloudJumperMaxHeight', height.toString()); } } }, [gameOver, score, height, highScore, maxHeight]); // Handle keyboard controls useEffect(() => { if (!gameStarted || gameOver || paused) return; const handleKeyDown = (e) => { if (e.code === 'Space') { handleJump(); } else if (e.key === 'ArrowLeft') { setPlayer(prev => ({...prev, velocityX: -5})); } else if (e.key === 'ArrowRight') { setPlayer(prev => ({...prev, velocityX: 5})); } else if (e.key === 'p') { setPaused(prev => !prev); } }; const handleKeyUp = (e) => { if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') { setPlayer(prev => ({...prev, velocityX: 0})); } }; window.addEventListener('keydown', handleKeyDown); window.addEventListener('keyup', handleKeyUp); return () => { window.removeEventListener('keydown', handleKeyDown); window.removeEventListener('keyup', handleKeyUp); }; }, [gameStarted, gameOver, paused]); // Handle touch controls useEffect(() => { if (!gameStarted || gameOver || paused) return; const handleTouchStart = (e) => { e.preventDefault(); const touch = e.touches[0]; const gameRect = gameRef.current.getBoundingClientRect(); const touchX = touch.clientX - gameRect.left; if (touchX < gameWidth / 2) { setPlayer(prev => ({...prev, velocityX: -5})); } else { setPlayer(prev => ({...prev, velocityX: 5})); } handleJump(); }; const handleTouchEnd = () => { setPlayer(prev => ({...prev, velocityX: 0})); }; const gameElement = gameRef.current; if (gameElement) { gameElement.addEventListener('touchstart', handleTouchStart); gameElement.addEventListener('touchend', handleTouchEnd); } return () => { if (gameElement) { gameElement.removeEventListener('touchstart', handleTouchStart); gameElement.removeEventListener('touchend', handleTouchEnd); } }; }, [gameStarted, gameOver, paused]); // Main game loop const gameLoop = (timestamp) => { if (paused) { requestRef.current = requestAnimationFrame(gameLoop); return; } if (!lastTimeRef.current) { lastTimeRef.current = timestamp; } const deltaTime = timestamp - lastTimeRef.current; lastTimeRef.current = timestamp; // Update game state updatePlayer(deltaTime); updateClouds(deltaTime); updateCollectibles(deltaTime); updateHazards(deltaTime); checkCollisions(); // Check if player fell off screen if (player.y > gameHeight + 100) { setGameOver(true); return; } // Request next frame requestRef.current = requestAnimationFrame(gameLoop); }; // Generate initial clouds const generateInitialClouds = () => { const initialClouds = []; // Add a starting cloud under the player initialClouds.push({ x: gameWidth / 2 - 50, y: 350, width: 100, height: 20, type: 'normal', color: cloudColors.normal, velocityX: 0, disappearTimer: null, bounceStrength: 1, }); // Add some more clouds for (let i = 0; i < 10; i++) { initialClouds.push(generateCloud(i * 70)); } setClouds(initialClouds); }; // Generate a new cloud const generateCloud = (yOffset = 0) => { const type = cloudTypes[Math.floor(Math.random() * cloudTypes.length)]; const width = Math.random() * 70 + 50; return { x: Math.random() * (gameWidth - width), y: Math.random() * 100 - yOffset, width, height: 20, type, color: cloudColors[type], velocityX: type === 'moving' ? (Math.random() > 0.5 ? 2 : -2) : 0, disappearTimer: type === 'disappearing' ? 0 : null, bounceStrength: type === 'bouncy' ? 1.5 : 1, }; }; // Generate a new collectible const generateCollectible = () => { const types = ['coin', 'jumpBoost', 'shield', 'flight']; const type = types[Math.floor(Math.random() * types.length)]; const size = type === 'coin' ? 20 : 30; return { x: Math.random() * (gameWidth - size), y: -50 - Math.random() * 200, width: size, height: size, type, collected: false, }; }; // Generate a new hazard const generateHazard = () => { const types = ['rain', 'lightning', 'wind']; const type = types[Math.floor(Math.random() * types.length)]; return { x: Math.random() * gameWidth, y: -50, width: type === 'lightning' ? 10 : 30, height: type === 'lightning' ? 100 : 30, type, active: true, velocityY: 5, }; }; // Update player position and state const updatePlayer = (deltaTime) => { setPlayer(prev => { // Apply gravity let newVelocityY = prev.velocityY + prev.gravity; // Apply power-ups if (powerUps.jumpBoost) { newVelocityY *= 0.9; } if (powerUps.flight) { newVelocityY *= 0.5; } // Update position let newY = prev.y + newVelocityY; let newX = prev.x + prev.velocityX; // Keep player within game bounds (horizontally) if (newX < 0) newX = 0; if (newX > gameWidth - prev.width) newX = gameWidth - prev.width; // Update camera and height if player goes above half screen if (newY < gameHeight / 2 && newVelocityY < 0) { // Move everything down instead of moving player up const diff = gameHeight / 2 - newY; newY = gameHeight / 2; // Move clouds down setClouds(clouds => clouds.map(cloud => ({ ...cloud, y: cloud.y + diff, }))); // Move collectibles down setCollectibles(collectibles => collectibles.map(collectible => ({ ...collectible, y: collectible.y + diff, }))); // Move hazards down setHazards(hazards => hazards.map(hazard => ({ ...hazard, y: hazard.y + diff, }))); // Update height setHeight(prev => Math.floor(prev + diff)); } return { ...prev, x: newX, y: newY, velocityY: newVelocityY, jumping: newVelocityY < 0, falling: newVelocityY > 0, }; }); }; // Update clouds const updateClouds = (deltaTime) => { setClouds(prev => { // Filter out clouds that are too far below let newClouds = prev.filter(cloud => cloud.y < gameHeight + 100); // Update existing clouds newClouds = newClouds.map(cloud => { let newCloud = { ...cloud }; // Move moving clouds if (cloud.type === 'moving') { newCloud.x += cloud.velocityX; // Bounce off edges if (newCloud.x <= 0 || newCloud.x + newCloud.width >= gameWidth) { newCloud.velocityX = -cloud.velocityX; } } // Update disappearing clouds if (cloud.type === 'disappearing' && cloud.disappearTimer !== null) { newCloud.disappearTimer += deltaTime / 1000; // Remove cloud after 3 seconds of contact if (newCloud.disappearTimer > 3) { return null; } } return newCloud; }).filter(Boolean); // Add new clouds if needed while (newClouds.length < 10) { newClouds.push(generateCloud(-50)); } return newClouds; }); }; // Update collectibles const updateCollectibles = (deltaTime) => { setCollectibles(prev => { // Filter out collectibles that are too far below let newCollectibles = prev.filter(collectible => collectible.y < gameHeight + 100 && !collectible.collected ); // Add new collectibles randomly if (Math.random() < 0.01 && newCollectibles.length < 5) { newCollectibles.push(generateCollectible()); } return newCollectibles; }); }; // Update hazards const updateHazards = (deltaTime) => { setHazards(prev => { // Filter out hazards that are too far below let newHazards = prev.filter(hazard => hazard.y < gameHeight + 100 && hazard.active ); // Update hazard positions newHazards = newHazards.map(hazard => ({ ...hazard, y: hazard.y + hazard.velocityY, })); // Add new hazards randomly (more likely at higher heights) const hazardChance = Math.min(0.005 + height / 10000, 0.02); if (Math.random() < hazardChance && newHazards.length < 3) { newHazards.push(generateHazard()); } return newHazards; }); }; // Check collisions between player and game objects const checkCollisions = () => { // Check cloud collisions clouds.forEach(cloud => { if ( player.x < cloud.x + cloud.width && player.x + player.width > cloud.x && player.y + player.height < cloud.y + 10 && player.y + player.height > cloud.y - 10 && player.velocityY > 0 ) { // Land on cloud setPlayer(prev => ({ ...prev, y: cloud.y - prev.height, velocityY: -prev.jumpPower * cloud.bounceStrength, jumping: true, falling: false, })); // Start disappear timer for disappearing clouds if (cloud.type === 'disappearing' && cloud.disappearTimer === null) { setClouds(clouds => clouds.map(c => c === cloud ? { ...c, disappearTimer: 0 } : c )); } // Play jump sound if (!muted) { playSound('jump'); } } }); // Check collectible collisions collectibles.forEach(collectible => { if ( !collectible.collected && player.x < collectible.x + collectible.width && player.x + player.width > collectible.x && player.y < collectible.y + collectible.height && player.y + player.height > collectible.y ) { // Mark as collected setCollectibles(collectibles => collectibles.map(c => c === collectible ? { ...c, collected: true } : c )); // Apply collectible effect if (collectible.type === 'coin') { setScore(prev => prev + 10); } else if (collectible.type === 'jumpBoost') { setPowerUps(prev => ({ ...prev, jumpBoost: true })); setTimeout(() => { setPowerUps(prev => ({ ...prev, jumpBoost: false })); }, 10000); } else if (collectible.type === 'shield') { setPowerUps(prev => ({ ...prev, shield: true })); setTimeout(() => { setPowerUps(prev => ({ ...prev, shield: false })); }, 10000); } else if (collectible.type === 'flight') { setPowerUps(prev => ({ ...prev, flight: true })); setTimeout(() => { setPowerUps(prev => ({ ...prev, flight: false })); }, 5000); } // Play collect sound if (!muted) { playSound('collect'); } } }); // Check hazard collisions if (!powerUps.shield) { hazards.forEach(hazard => { if ( hazard.active && player.x < hazard.x + hazard.width && player.x + player.width > hazard.x && player.y < hazard.y + hazard.height && player.y + player.height > hazard.y ) { // Deactivate hazard setHazards(hazards => hazards.map(h => h === hazard ? { ...h, active: false } : h )); // Apply hazard effect if (hazard.type === 'lightning') { setPlayer(prev => ({ ...prev, velocityY: 10, // Strong downward force })); } else if (hazard.type === 'wind') { setPlayer(prev => ({ ...prev, velocityX: Math.random() > 0.5 ? 8 : -8, // Strong sideways push })); setTimeout(() => { setPlayer(prev => ({ ...prev, velocityX: 0 })); }, 500); } // Play hazard sound if (!muted) { playSound('hazard'); } } }); } }; // Handle jump action const handleJump = () => { if (!player.jumping && !player.falling) { setPlayer(prev => ({ ...prev, velocityY: -prev.jumpPower * (powerUps.jumpBoost ? 1.5 : 1), jumping: true, })); // Play jump sound if (!muted) { playSound('jump'); } } }; // Play sound effects const playSound = (type) => { // In a real implementation, we would play actual sounds here console.log(`Playing ${type} sound`); }; // Start a new game const startGame = () => { setGameStarted(true); setGameOver(false); setScore(0); setHeight(0); setPowerUps({ jumpBoost: false, shield: false, flight: false, }); setPlayer({ x: 150, y: 300, width: 30, height: 30, velocityY: 0, velocityX: 0, jumping: false, falling: true, jumpPower: 15, gravity: 0.5, color: '#FF6B6B', }); setClouds([]); setCollectibles([]); setHazards([]); setPaused(false); }; // Toggle pause state const togglePause = () => { setPaused(prev => !prev); }; // Toggle mute state const toggleMute = () => { setMuted(prev => !prev); }; // Toggle night mode const toggleNightMode = () => { setNightMode(prev => !prev); }; // Render game return (
Jump between clouds and climb as high as you can!
{highScore > 0 && (High Score: {highScore}
Max Height: {maxHeight}m
Controls:
Desktop: Arrow keys to move, Space to jump
Mobile: Tap left/right side to move and jump
Score: {score}
Height: {height}m
{score > highScore && (New High Score!
)} {height > maxHeight && (New Height Record!
)}