import React, { useState, useEffect, useRef } from 'react'; import { ArrowUp, ArrowDown, ArrowLeft, ArrowRight, RotateCcw, Trophy, Clock, Gauge } from 'lucide-react'; export default function RacingGame() { const canvasRef = useRef(null); const [gameStarted, setGameStarted] = useState(false); const [gameOver, setGameOver] = useState(false); const [score, setScore] = useState(0); const [highScore, setHighScore] = useState(0); const [countdown, setCountdown] = useState(3); const [progress, setProgress] = useState(0); const [currentSpeed, setCurrentSpeed] = useState(0); // Game state const gameStateRef = useRef({ playerCar: { x: 0, y: 0, width: 40, height: 70, speed: 0, maxSpeed: 10, acceleration: 0.2, deceleration: 0.1, handling: 5, angle: 0 }, obstacles: [], particles: [], road: { width: 0, leftBound: 0, rightBound: 0 }, keys: { ArrowUp: false, ArrowDown: false, ArrowLeft: false, ArrowRight: false }, frameCount: 0, lastObstacleFrame: 0, gameTime: 0, finishLine: 5000, // Distance to finish line stars: [] // Background stars }); // Initialize game useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; const resizeCanvas = () => { canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight; // Update road dimensions const roadWidth = canvas.width * 0.7; gameStateRef.current.road = { width: roadWidth, leftBound: (canvas.width - roadWidth) / 2, rightBound: (canvas.width + roadWidth) / 2 }; // Position player car gameStateRef.current.playerCar.x = canvas.width / 2 - gameStateRef.current.playerCar.width / 2; gameStateRef.current.playerCar.y = canvas.height - 150; // Initialize stars initStars(); }; const initStars = () => { const stars = []; for (let i = 0; i < 100; i++) { stars.push({ x: Math.random() * canvas.width, y: Math.random() * canvas.height, size: Math.random() * 2 + 1, speed: Math.random() * 0.5 + 0.1 }); } gameStateRef.current.stars = stars; }; // Ensure canvas is properly sized before starting setTimeout(() => { resizeCanvas(); }, 0); window.addEventListener('resize', resizeCanvas); return () => window.removeEventListener('resize', resizeCanvas); }, []); // Handle keyboard input useEffect(() => { const handleKeyDown = (e) => { if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) { e.preventDefault(); gameStateRef.current.keys[e.key] = true; } }; const handleKeyUp = (e) => { if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) { gameStateRef.current.keys[e.key] = false; } }; window.addEventListener('keydown', handleKeyDown); window.addEventListener('keyup', handleKeyUp); return () => { window.removeEventListener('keydown', handleKeyDown); window.removeEventListener('keyup', handleKeyUp); }; }, []); // Game loop useEffect(() => { if (!gameStarted || gameOver) return; let animationFrameId; let lastTimestamp = 0; const gameLoop = (timestamp) => { if (!lastTimestamp) lastTimestamp = timestamp; const deltaTime = timestamp - lastTimestamp; lastTimestamp = timestamp; updateGame(deltaTime); renderGame(); animationFrameId = requestAnimationFrame(gameLoop); }; // Small delay to ensure canvas is ready setTimeout(() => { animationFrameId = requestAnimationFrame(gameLoop); }, 50); return () => { if (animationFrameId) { cancelAnimationFrame(animationFrameId); } }; }, [gameStarted, gameOver]); // Countdown timer useEffect(() => { if (!gameStarted || countdown <= 0) return; const timer = setTimeout(() => { if (countdown > 1) { setCountdown(countdown - 1); } else { setCountdown(0); } }, 1000); return () => clearTimeout(timer); }, [gameStarted, countdown]); // Start game with countdown const startGame = () => { setGameStarted(true); setGameOver(false); setScore(0); setCountdown(3); setProgress(0); setCurrentSpeed(0); // Reset game state const canvas = canvasRef.current; if (!canvas) return; gameStateRef.current = { ...gameStateRef.current, playerCar: { ...gameStateRef.current.playerCar, x: canvas.width / 2 - gameStateRef.current.playerCar.width / 2, y: canvas.height - 150, speed: 0, angle: 0 }, obstacles: [], particles: [], frameCount: 0, lastObstacleFrame: 0, gameTime: 0, stars: gameStateRef.current.stars }; }; // Create particles const createParticles = (x, y, count, color, speed) => { const particles = []; for (let i = 0; i < count; i++) { particles.push({ x, y, size: Math.random() * 3 + 1, speedX: (Math.random() - 0.5) * speed, speedY: (Math.random() - 0.5) * speed, color, life: Math.random() * 30 + 10 }); } return particles; }; // Update game state const updateGame = (deltaTime) => { const state = gameStateRef.current; const { playerCar, keys, road } = state; const canvas = canvasRef.current; if (!canvas) return; // Only update if countdown is finished if (countdown > 0) return; // Update frame count state.frameCount++; // Update game time (in seconds) state.gameTime += deltaTime / 1000; setScore(Math.floor(state.gameTime * 10)); // Update progress const newProgress = Math.min(100, (state.frameCount / state.finishLine) * 100); setProgress(newProgress); // Check for finish line if (state.frameCount >= state.finishLine) { setGameOver(true); if (score > highScore) setHighScore(score); return; } // Update player car if (keys.ArrowUp) { playerCar.speed = Math.min(playerCar.speed + playerCar.acceleration, playerCar.maxSpeed); } else if (keys.ArrowDown) { playerCar.speed = Math.max(playerCar.speed - playerCar.deceleration * 2, -playerCar.maxSpeed / 2); } else { // Natural deceleration if (playerCar.speed > 0) { playerCar.speed = Math.max(playerCar.speed - playerCar.deceleration, 0); } else if (playerCar.speed < 0) { playerCar.speed = Math.min(playerCar.speed + playerCar.deceleration, 0); } } // Update speed display setCurrentSpeed(Math.abs(Math.floor(playerCar.speed * 20))); // Steering if (playerCar.speed !== 0) { if (keys.ArrowLeft) { playerCar.angle -= (playerCar.handling / 100) * (playerCar.speed > 0 ? 1 : -1); } if (keys.ArrowRight) { playerCar.angle += (playerCar.handling / 100) * (playerCar.speed > 0 ? 1 : -1); } } // Move player car const moveX = Math.sin(playerCar.angle) * playerCar.speed; const moveY = -Math.cos(playerCar.angle) * playerCar.speed; playerCar.x += moveX; playerCar.y += moveY; // Create exhaust particles if (playerCar.speed > 0 && state.frameCount % 2 === 0) { const exhaustX = playerCar.x + playerCar.width / 2 - Math.sin(playerCar.angle) * (playerCar.height / 2); const exhaustY = playerCar.y + playerCar.height / 2 + Math.cos(playerCar.angle) * (playerCar.height / 2); const exhaustParticles = createParticles( exhaustX, exhaustY, 1, playerCar.speed > playerCar.maxSpeed * 0.7 ? '#ff6b6b' : '#aaa', playerCar.speed ); state.particles.push(...exhaustParticles); } // Keep player on road const carCenterX = playerCar.x + playerCar.width / 2; if (carCenterX < road.leftBound) { playerCar.x = road.leftBound - playerCar.width / 2; playerCar.speed *= 0.8; // Slow down when hitting edge // Create impact particles const impactParticles = createParticles( road.leftBound, playerCar.y + playerCar.height / 2, 10, '#ffbe0b', 5 ); state.particles.push(...impactParticles); } if (carCenterX > road.rightBound) { playerCar.x = road.rightBound - playerCar.width / 2; playerCar.speed *= 0.8; // Slow down when hitting edge // Create impact particles const impactParticles = createParticles( road.rightBound, playerCar.y + playerCar.height / 2, 10, '#ffbe0b', 5 ); state.particles.push(...impactParticles); } // Keep player on screen vertically if (playerCar.y < 0) playerCar.y = 0; if (playerCar.y > canvas.height - playerCar.height) { playerCar.y = canvas.height - playerCar.height; } // Generate obstacles if (state.frameCount - state.lastObstacleFrame > 60 && Math.random() < 0.1) { state.lastObstacleFrame = state.frameCount; const obstacleWidth = 40 + Math.random() * 30; const obstacleHeight = 60 + Math.random() * 30; // Random color for obstacle const colors = ['#fb5607', '#ff006e', '#8338ec', '#3a86ff']; const color = colors[Math.floor(Math.random() * colors.length)]; state.obstacles.push({ x: road.leftBound + Math.random() * (road.width - obstacleWidth), y: -obstacleHeight, width: obstacleWidth, height: obstacleHeight, speed: 2 + Math.random() * 3, color }); } // Update obstacles for (let i = state.obstacles.length - 1; i >= 0; i--) { const obstacle = state.obstacles[i]; obstacle.y += obstacle.speed; // Remove obstacles that are off screen if (obstacle.y > canvas.height) { state.obstacles.splice(i, 1); continue; } // Improved collision detection // Calculate car corners after rotation const carCenterX = playerCar.x + playerCar.width / 2; const carCenterY = playerCar.y + playerCar.height / 2; // Simplified collision detection - using a smaller hitbox for the car const carHitboxWidth = playerCar.width * 0.8; const carHitboxHeight = playerCar.height * 0.8; const carHitboxX = carCenterX - carHitboxWidth / 2; const carHitboxY = carCenterY - carHitboxHeight / 2; if ( carHitboxX < obstacle.x + obstacle.width && carHitboxX + carHitboxWidth > obstacle.x && carHitboxY < obstacle.y + obstacle.height && carHitboxY + carHitboxHeight > obstacle.y ) { // Create explosion particles const explosionParticles = createParticles( carCenterX, carCenterY, 30, '#ff6b6b', 8 ); state.particles.push(...explosionParticles); setGameOver(true); if (score > highScore) setHighScore(score); break; } } // Update particles for (let i = state.particles.length - 1; i >= 0; i--) { const particle = state.particles[i]; particle.x += particle.speedX; particle.y += particle.speedY; particle.life--; if (particle.life <= 0) { state.particles.splice(i, 1); } } // Update stars state.stars.forEach(star => { star.y += star.speed * (playerCar.speed / 2 + 0.5); if (star.y > canvas.height) { star.y = 0; star.x = Math.random() * canvas.width; } }); }; // Render game const renderGame = () => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; const state = gameStateRef.current; // Clear canvas const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height); gradient.addColorStop(0, '#0f172a'); gradient.addColorStop(1, '#1e293b'); ctx.fillStyle = gradient; ctx.fillRect(0, 0, canvas.width, canvas.height); // Draw stars ctx.fillStyle = '#fff'; state.stars.forEach(star => { ctx.globalAlpha = Math.random() * 0.3 + 0.7; ctx.beginPath(); ctx.arc(star.x, star.y, star.size, 0, Math.PI * 2); ctx.fill(); }); ctx.globalAlpha = 1; // Draw road const roadGradient = ctx.createLinearGradient(0, 0, 0, canvas.height); roadGradient.addColorStop(0, '#334155'); roadGradient.addColorStop(1, '#1e293b'); ctx.fillStyle = roadGradient; ctx.fillRect(state.road.leftBound, 0, state.road.width, canvas.height); // Draw road edges ctx.fillStyle = '#f59e0b'; ctx.fillRect(state.road.leftBound - 5, 0, 5, canvas.height); ctx.fillRect(state.road.rightBound, 0, 5, canvas.height); // Draw road markings const lineSpacing = 50; const lineWidth = 10; const lineHeight = 30; const offset = (state.frameCount % lineSpacing) / lineSpacing * lineHeight; ctx.fillStyle = '#fff'; for (let y = -lineHeight + offset; y < canvas.height; y += lineSpacing) { // Center line ctx.fillRect(canvas.width / 2 - lineWidth / 2, y, lineWidth, lineHeight); } // Draw finish line if close if (state.finishLine - state.frameCount < 300) { const finishLineY = canvas.height - ((state.finishLine - state.frameCount) / 300) * canvas.height; ctx.fillStyle = '#fff'; for (let x = state.road.leftBound; x < state.road.rightBound; x += 20) { ctx.fillRect(x, finishLineY, 10, 10); ctx.fillRect(x + 10, finishLineY + 10, 10, 10); } // Draw finish flag ctx.fillStyle = '#10b981'; ctx.fillRect(state.road.rightBound + 10, finishLineY - 50, 5, 50); const flagGradient = ctx.createLinearGradient( state.road.rightBound + 15, finishLineY - 50, state.road.rightBound + 45, finishLineY - 30 ); flagGradient.addColorStop(0, '#10b981'); flagGradient.addColorStop(1, '#059669'); ctx.fillStyle = flagGradient; ctx.fillRect(state.road.rightBound + 15, finishLineY - 50, 30, 20); // Checkered pattern ctx.fillStyle = '#fff'; const squareSize = 5; for (let x = 0; x < 6; x++) { for (let y = 0; y < 4; y++) { if ((x + y) % 2 === 0) { ctx.fillRect( state.road.rightBound + 15 + x * squareSize, finishLineY - 50 + y * squareSize, squareSize, squareSize ); } } } } // Draw particles state.particles.forEach(particle => { ctx.globalAlpha = particle.life / 40; ctx.fillStyle = particle.color; ctx.beginPath(); ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2); ctx.fill(); }); ctx.globalAlpha = 1; // Draw obstacles state.obstacles.forEach(obstacle => { ctx.save(); ctx.translate(obstacle.x + obstacle.width / 2, obstacle.y + obstacle.height / 2); // Car body ctx.fillStyle = obstacle.color; ctx.fillRect(-obstacle.width / 2, -obstacle.height / 2, obstacle.width, obstacle.height); // Windows ctx.fillStyle = '#0f172a'; ctx.fillRect(-obstacle.width / 2 + 5, -obstacle.height / 2 + 5, obstacle.width - 10, obstacle.height / 3); // Wheels ctx.fillStyle = '#333'; ctx.fillRect(-obstacle.width / 2 - 5, -obstacle.height / 2 + 10, 5, 15); ctx.fillRect(obstacle.width / 2, -obstacle.height / 2 + 10, 5, 15); ctx.fillRect(-obstacle.width / 2 - 5, obstacle.height / 2 - 25, 5, 15); ctx.fillRect(obstacle.width / 2, obstacle.height / 2 - 25, 5, 15); ctx.restore(); }); // Draw player car const { playerCar } = state; ctx.save(); ctx.translate(playerCar.x + playerCar.width / 2, playerCar.y + playerCar.height / 2); ctx.rotate(playerCar.angle); // Shadow ctx.fillStyle = 'rgba(0, 0, 0, 0.3)'; ctx.fillRect(-playerCar.width / 2 + 5, -playerCar.height / 2 + 5, playerCar.width, playerCar.height); // Car body const carGradient = ctx.createLinearGradient( -playerCar.width / 2, -playerCar.height / 2, playerCar.width / 2, playerCar.height / 2 ); carGradient.addColorStop(0, '#f97316'); carGradient.addColorStop(1, '#ea580c'); ctx.fillStyle = carGradient; ctx.fillRect(-playerCar.width / 2, -playerCar.height / 2, playerCar.width, playerCar.height); // Racing stripes ctx.fillStyle = '#fff'; ctx.fillRect(-playerCar.width / 6, -playerCar.height / 2, playerCar.width / 3, playerCar.height); // Windows ctx.fillStyle = '#0f172a'; ctx.fillRect(-playerCar.width / 2 + 5, -playerCar.height / 2 + 5, playerCar.width - 10, playerCar.height / 3); // Wheels ctx.fillStyle = '#333'; ctx.fillRect(-playerCar.width / 2 - 5, -playerCar.height / 2 + 10, 5, 15); ctx.fillRect(playerCar.width / 2, -playerCar.height / 2 + 10, 5, 15); ctx.fillRect(-playerCar.width / 2 - 5, playerCar.height / 2 - 25, 5, 15); ctx.fillRect(playerCar.width / 2, playerCar.height / 2 - 25, 5, 15); // Headlights ctx.fillStyle = '#fbbf24'; ctx.fillRect(-playerCar.width / 2 + 5, -playerCar.height / 2, 5, 5); ctx.fillRect(playerCar.width / 2 - 10, -playerCar.height / 2, 5, 5); // Taillights ctx.fillStyle = '#ef4444'; ctx.fillRect(-playerCar.width / 2 + 5, playerCar.height / 2 - 5, 5, 5); ctx.fillRect(playerCar.width / 2 - 10, playerCar.height / 2 - 5, 5, 5); ctx.restore(); // Draw countdown if (countdown > 0) { ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.font = 'bold 72px Inter, sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; // Shadow ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; ctx.fillText(countdown, canvas.width / 2 + 3, canvas.height / 2 + 3); // Text const countdownGradient = ctx.createLinearGradient( canvas.width / 2 - 40, canvas.height / 2 - 40, canvas.width / 2 + 40, canvas.height / 2 + 40 ); countdownGradient.addColorStop(0, '#f97316'); countdownGradient.addColorStop(1, '#ea580c'); ctx.fillStyle = countdownGradient; ctx.fillText(countdown, canvas.width / 2, canvas.height / 2); // Circle ctx.strokeStyle = '#fff'; ctx.lineWidth = 5; ctx.beginPath(); ctx.arc(canvas.width / 2, canvas.height / 2, 60, 0, Math.PI * 2); ctx.stroke(); } // Draw game over if (gameOver) { ctx.fillStyle = 'rgba(0, 0, 0, 0.8)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.font = 'bold 48px Inter, sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; // Title const titleGradient = ctx.createLinearGradient( canvas.width / 2 - 150, canvas.height / 2 - 100, canvas.width / 2 + 150, canvas.height / 2 - 50 ); if (state.frameCount >= state.finishLine) { titleGradient.addColorStop(0, '#10b981'); titleGradient.addColorStop(1, '#059669'); // Shadow ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; ctx.fillText('Finish!', canvas.width / 2 + 3, canvas.height / 2 - 50 + 3); // Text ctx.fillStyle = titleGradient; ctx.fillText('Finish!', canvas.width / 2, canvas.height / 2 - 50); } else { titleGradient.addColorStop(0, '#ef4444'); titleGradient.addColorStop(1, '#dc2626'); // Shadow ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; ctx.fillText('Game Over', canvas.width / 2 + 3, canvas.height / 2 - 50 + 3); // Text ctx.fillStyle = titleGradient; ctx.fillText('Game Over', canvas.width / 2, canvas.height / 2 - 50); } // Score ctx.font = '24px Inter, sans-serif'; ctx.fillStyle = '#fff'; ctx.fillText(`Score: ${score}`, canvas.width / 2, canvas.height / 2); ctx.fillText(`High Score: ${highScore}`, canvas.width / 2, canvas.height / 2 + 40); } }; return (
Race to the finish line while avoiding obstacles! Test your reflexes and set a new high score!
Controls:
Race to the finish line while avoiding obstacles!
Controls: