import React, { useState, useEffect, useRef } from 'react'; export default function SpaceInvadersBirthday() { const canvasRef = useRef(null); const [gameState, setGameState] = useState('playing'); // 'playing' or 'over' const [score, setScore] = useState(0); const gameRef = useRef({ player: { x: 375, y: 550, width: 50, height: 30, speed: 5 }, bullets: [], invaders: [], invaderBullets: [], invaderDirection: 1, invaderSpeed: 1, lastShot: 0, keys: {} }); useEffect(() => { const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); const game = gameRef.current; // Initialize invaders game.invaders = []; for (let row = 0; row < 4; row++) { for (let col = 0; col < 8; col++) { game.invaders.push({ x: col * 80 + 100, y: row * 60 + 50, width: 40, height: 30, alive: true }); } } // Keyboard controls const handleKeyDown = (e) => { game.keys[e.key] = true; if (e.key === ' ' && gameState === 'playing') { e.preventDefault(); const now = Date.now(); if (now - game.lastShot > 300) { game.bullets.push({ x: game.player.x + game.player.width / 2 - 2, y: game.player.y, width: 4, height: 10, speed: 7 }); game.lastShot = now; } } }; const handleKeyUp = (e) => { game.keys[e.key] = false; }; window.addEventListener('keydown', handleKeyDown); window.addEventListener('keyup', handleKeyUp); // Game loop let animationId; const gameLoop = () => { if (gameState !== 'playing') return; // Clear canvas ctx.fillStyle = '#000'; ctx.fillRect(0, 0, canvas.width, canvas.height); // Move player if (game.keys['ArrowLeft'] && game.player.x > 0) { game.player.x -= game.player.speed; } if (game.keys['ArrowRight'] && game.player.x < canvas.width - game.player.width) { game.player.x += game.player.speed; } // Draw player (spaceship) ctx.fillStyle = '#0f0'; ctx.beginPath(); ctx.moveTo(game.player.x + game.player.width / 2, game.player.y); ctx.lineTo(game.player.x, game.player.y + game.player.height); ctx.lineTo(game.player.x + game.player.width, game.player.y + game.player.height); ctx.closePath(); ctx.fill(); // Move and draw bullets game.bullets = game.bullets.filter(bullet => { bullet.y -= bullet.speed; ctx.fillStyle = '#fff'; ctx.fillRect(bullet.x, bullet.y, bullet.width, bullet.height); return bullet.y > 0; }); // Move invaders let hitEdge = false; game.invaders.forEach(invader => { if (!invader.alive) return; invader.x += game.invaderSpeed * game.invaderDirection; if (invader.x <= 0 || invader.x >= canvas.width - invader.width) { hitEdge = true; } }); if (hitEdge) { game.invaderDirection *= -1; game.invaders.forEach(invader => { if (invader.alive) invader.y += 20; }); } // Draw invaders game.invaders.forEach(invader => { if (!invader.alive) return; ctx.fillStyle = '#f0f'; // Draw alien shape ctx.fillRect(invader.x + 5, invader.y, 30, 10); ctx.fillRect(invader.x, invader.y + 10, 40, 15); ctx.fillRect(invader.x + 10, invader.y + 25, 5, 5); ctx.fillRect(invader.x + 25, invader.y + 25, 5, 5); }); // Invaders shoot randomly if (Math.random() < 0.01) { const aliveInvaders = game.invaders.filter(i => i.alive); if (aliveInvaders.length > 0) { const shooter = aliveInvaders[Math.floor(Math.random() * aliveInvaders.length)]; game.invaderBullets.push({ x: shooter.x + shooter.width / 2, y: shooter.y + shooter.height, width: 4, height: 10, speed: 3 }); } } // Move and draw invader bullets game.invaderBullets = game.invaderBullets.filter(bullet => { bullet.y += bullet.speed; ctx.fillStyle = '#f00'; ctx.fillRect(bullet.x, bullet.y, bullet.width, bullet.height); return bullet.y < canvas.height; }); // Collision detection - bullets hit invaders game.bullets.forEach((bullet, bIndex) => { game.invaders.forEach((invader, iIndex) => { if (invader.alive && bullet.x < invader.x + invader.width && bullet.x + bullet.width > invader.x && bullet.y < invader.y + invader.height && bullet.y + bullet.height > invader.y) { invader.alive = false; game.bullets.splice(bIndex, 1); setScore(prev => prev + 10); } }); }); // Check if player is hit game.invaderBullets.forEach(bullet => { if (bullet.x < game.player.x + game.player.width && bullet.x + bullet.width > game.player.x && bullet.y < game.player.y + game.player.height && bullet.y + bullet.height > game.player.y) { setGameState('over'); } }); // Check if invaders reached bottom game.invaders.forEach(invader => { if (invader.alive && invader.y + invader.height > game.player.y) { setGameState('over'); } }); // Check if all invaders are dead const aliveCount = game.invaders.filter(i => i.alive).length; if (aliveCount === 0) { setGameState('over'); } animationId = requestAnimationFrame(gameLoop); }; gameLoop(); return () => { window.removeEventListener('keydown', handleKeyDown); window.removeEventListener('keyup', handleKeyUp); cancelAnimationFrame(animationId); }; }, [gameState]); const restart = () => { const game = gameRef.current; game.player = { x: 375, y: 550, width: 50, height: 30, speed: 5 }; game.bullets = []; game.invaderBullets = []; game.invaderDirection = 1; game.invaders = []; for (let row = 0; row < 4; row++) { for (let col = 0; col < 8; col++) { game.invaders.push({ x: col * 80 + 100, y: row * 60 + 50, width: 40, height: 30, alive: true }); } } setScore(0); setGameState('playing'); }; return (
← → Arrow keys to move
SPACE to shoot