Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Space Invaders</title> | |
| <style> | |
| body { | |
| margin: 0; | |
| padding: 0; | |
| background: #000; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| min-height: 100vh; | |
| font-family: 'Courier New', monospace; | |
| color: #00ff00; | |
| } | |
| #gameContainer { | |
| text-align: center; | |
| } | |
| canvas { | |
| border: 2px solid #00ff00; | |
| background: #000; | |
| } | |
| #ui { | |
| margin-top: 10px; | |
| font-size: 18px; | |
| } | |
| #instructions { | |
| margin-top: 10px; | |
| font-size: 14px; | |
| opacity: 0.8; | |
| } | |
| #gameOver { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| font-size: 32px; | |
| color: #ff0000; | |
| display: none; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="gameContainer"> | |
| <canvas id="gameCanvas" width="800" height="600"></canvas> | |
| <div id="ui"> | |
| <span>Score: <span id="score">0</span></span> | |
| <span style="margin-left: 50px;">Lives: <span id="lives">3</span></span> | |
| </div> | |
| <div id="instructions"> | |
| Use ARROW KEYS or WASD to move in all directions • SPACEBAR for auto-targeted shooting • R to restart | |
| </div> | |
| <div id="gameOver"> | |
| GAME OVER<br> | |
| <span style="font-size: 16px;">Press R to restart</span> | |
| </div> | |
| </div> | |
| <script> | |
| const canvas = document.getElementById('gameCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const scoreElement = document.getElementById('score'); | |
| const livesElement = document.getElementById('lives'); | |
| const gameOverElement = document.getElementById('gameOver'); | |
| // Game state | |
| let gameState = { | |
| score: 0, | |
| lives: 3, | |
| gameRunning: true, | |
| keys: {} | |
| }; | |
| // Player | |
| const player = { | |
| x: canvas.width / 2 - 20, | |
| y: canvas.height - 60, | |
| width: 40, | |
| height: 30, | |
| speed: 5 | |
| }; | |
| // Arrays for game objects | |
| let bullets = []; | |
| let invaders = []; | |
| let invaderBullets = []; | |
| // Game settings | |
| const bulletSpeed = 7; | |
| const invaderBulletSpeed = 3; | |
| const invaderSpeed = 1; | |
| let invaderDirection = 1; | |
| let invaderDropDistance = 30; | |
| // Initialize invaders | |
| function createInvaders() { | |
| invaders = []; | |
| for (let row = 0; row < 5; row++) { | |
| for (let col = 0; col < 10; col++) { | |
| invaders.push({ | |
| x: col * 60 + 80, | |
| y: row * 50 + 50, | |
| width: 40, | |
| height: 30, | |
| alive: true | |
| }); | |
| } | |
| } | |
| } | |
| // Input handling | |
| document.addEventListener('keydown', (e) => { | |
| gameState.keys[e.code] = true; | |
| if (e.code === 'Space') { | |
| e.preventDefault(); | |
| shoot(); | |
| } | |
| if (e.code === 'KeyR') { | |
| restartGame(); | |
| } | |
| }); | |
| document.addEventListener('keyup', (e) => { | |
| gameState.keys[e.code] = false; | |
| }); | |
| // Player movement | |
| function updatePlayer() { | |
| if (gameState.keys['ArrowLeft'] || gameState.keys['KeyA']) { | |
| player.x = Math.max(0, player.x - player.speed); | |
| } | |
| if (gameState.keys['ArrowRight'] || gameState.keys['KeyD']) { | |
| player.x = Math.min(canvas.width - player.width, player.x + player.speed); | |
| } | |
| if (gameState.keys['ArrowUp'] || gameState.keys['KeyW']) { | |
| player.y = Math.max(0, player.y - player.speed); | |
| } | |
| if (gameState.keys['ArrowDown'] || gameState.keys['KeyS']) { | |
| player.y = Math.min(canvas.height - player.height, player.y + player.speed); | |
| } | |
| } | |
| // Shooting with auto-targeting | |
| function shoot() { | |
| if (!gameState.gameRunning) return; | |
| // Find nearest alive invader | |
| let nearestInvader = null; | |
| let minDistance = Infinity; | |
| for (let invader of invaders) { | |
| if (invader.alive) { | |
| const distance = Math.sqrt( | |
| Math.pow(invader.x + invader.width/2 - (player.x + player.width/2), 2) + | |
| Math.pow(invader.y + invader.height/2 - player.y, 2) | |
| ); | |
| if (distance < minDistance) { | |
| minDistance = distance; | |
| nearestInvader = invader; | |
| } | |
| } | |
| } | |
| if (nearestInvader) { | |
| // Calculate direction to target | |
| const dx = (nearestInvader.x + nearestInvader.width/2) - (player.x + player.width/2); | |
| const dy = (nearestInvader.y + nearestInvader.height/2) - player.y; | |
| const distance = Math.sqrt(dx * dx + dy * dy); | |
| bullets.push({ | |
| x: player.x + player.width / 2 - 2, | |
| y: player.y, | |
| width: 4, | |
| height: 10, | |
| vx: (dx / distance) * bulletSpeed, | |
| vy: (dy / distance) * bulletSpeed | |
| }); | |
| } | |
| } | |
| // Update bullets | |
| function updateBullets() { | |
| bullets = bullets.filter(bullet => { | |
| bullet.x += bullet.vx; | |
| bullet.y += bullet.vy; | |
| // Remove bullets that go off screen | |
| return bullet.x > -bullet.width && | |
| bullet.x < canvas.width + bullet.width && | |
| bullet.y > -bullet.height && | |
| bullet.y < canvas.height + bullet.height; | |
| }); | |
| } | |
| // Update invader bullets | |
| function updateInvaderBullets() { | |
| invaderBullets = invaderBullets.filter(bullet => { | |
| bullet.y += bullet.speed; | |
| return bullet.y < canvas.height; | |
| }); | |
| } | |
| // Invader shooting | |
| function invaderShoot() { | |
| if (Math.random() < 0.002 && invaders.some(inv => inv.alive)) { | |
| const aliveInvaders = invaders.filter(inv => inv.alive); | |
| const shooter = aliveInvaders[Math.floor(Math.random() * aliveInvaders.length)]; | |
| invaderBullets.push({ | |
| x: shooter.x + shooter.width / 2 - 2, | |
| y: shooter.y + shooter.height, | |
| width: 4, | |
| height: 10, | |
| speed: invaderBulletSpeed | |
| }); | |
| } | |
| } | |
| // Update invaders | |
| function updateInvaders() { | |
| let shouldDrop = false; | |
| // Check if any invader hit the edge | |
| for (let invader of invaders) { | |
| if (!invader.alive) continue; | |
| if ((invader.x <= 0 && invaderDirection === -1) || | |
| (invader.x + invader.width >= canvas.width && invaderDirection === 1)) { | |
| shouldDrop = true; | |
| break; | |
| } | |
| } | |
| // Move invaders | |
| for (let invader of invaders) { | |
| if (!invader.alive) continue; | |
| if (shouldDrop) { | |
| invader.y += invaderDropDistance; | |
| } else { | |
| invader.x += invaderSpeed * invaderDirection; | |
| } | |
| } | |
| if (shouldDrop) { | |
| invaderDirection *= -1; | |
| } | |
| } | |
| // Collision detection | |
| function checkCollisions() { | |
| // Bullet vs invaders | |
| for (let i = bullets.length - 1; i >= 0; i--) { | |
| const bullet = bullets[i]; | |
| for (let j = 0; j < invaders.length; j++) { | |
| const invader = invaders[j]; | |
| 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; | |
| bullets.splice(i, 1); | |
| gameState.score += 10; | |
| break; | |
| } | |
| } | |
| } | |
| // Invader bullets vs player | |
| for (let i = invaderBullets.length - 1; i >= 0; i--) { | |
| const bullet = invaderBullets[i]; | |
| if (bullet.x < player.x + player.width && | |
| bullet.x + bullet.width > player.x && | |
| bullet.y < player.y + player.height && | |
| bullet.y + bullet.height > player.y) { | |
| invaderBullets.splice(i, 1); | |
| gameState.lives--; | |
| if (gameState.lives <= 0) { | |
| gameOver(); | |
| } | |
| } | |
| } | |
| // Check if invaders reached player | |
| for (let invader of invaders) { | |
| if (invader.alive && invader.y + invader.height >= player.y) { | |
| gameOver(); | |
| return; | |
| } | |
| } | |
| } | |
| // Game over | |
| function gameOver() { | |
| gameState.gameRunning = false; | |
| gameOverElement.style.display = 'block'; | |
| } | |
| // Restart game | |
| function restartGame() { | |
| gameState.score = 0; | |
| gameState.lives = 3; | |
| gameState.gameRunning = true; | |
| bullets = []; | |
| invaderBullets = []; | |
| invaderDirection = 1; | |
| player.x = canvas.width / 2 - 20; | |
| createInvaders(); | |
| gameOverElement.style.display = 'none'; | |
| } | |
| // Check win condition | |
| function checkWin() { | |
| if (invaders.every(inv => !inv.alive)) { | |
| // Create new wave | |
| createInvaders(); | |
| gameState.score += 100; // Bonus for clearing wave | |
| } | |
| } | |
| // Drawing functions | |
| function drawPlayer() { | |
| const x = player.x; | |
| const y = player.y; | |
| // F-16 Fighting Falcon design | |
| ctx.save(); | |
| // Main fuselage | |
| ctx.fillStyle = '#4a5568'; | |
| ctx.fillRect(x + 16, y + 5, 8, 20); | |
| // Nose cone | |
| ctx.fillStyle = '#2d3748'; | |
| ctx.beginPath(); | |
| ctx.moveTo(x + 20, y); | |
| ctx.lineTo(x + 16, y + 5); | |
| ctx.lineTo(x + 24, y + 5); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| // Main wings | |
| ctx.fillStyle = '#718096'; | |
| ctx.fillRect(x + 8, y + 12, 24, 6); | |
| // Wing tips | |
| ctx.fillStyle = '#4a5568'; | |
| ctx.fillRect(x + 6, y + 14, 4, 2); | |
| ctx.fillRect(x + 30, y + 14, 4, 2); | |
| // Canards (small front wings) | |
| ctx.fillStyle = '#718096'; | |
| ctx.fillRect(x + 12, y + 8, 16, 2); | |
| // Engine exhaust | |
| ctx.fillStyle = '#e53e3e'; | |
| ctx.fillRect(x + 17, y + 25, 6, 3); | |
| // Cockpit | |
| ctx.fillStyle = '#3182ce'; | |
| ctx.fillRect(x + 18, y + 6, 4, 4); | |
| // Air intakes | |
| ctx.fillStyle = '#2d3748'; | |
| ctx.fillRect(x + 14, y + 10, 2, 8); | |
| ctx.fillRect(x + 24, y + 10, 2, 8); | |
| // Wing missiles | |
| ctx.fillStyle = '#e2e8f0'; | |
| ctx.fillRect(x + 10, y + 16, 3, 1); | |
| ctx.fillRect(x + 27, y + 16, 3, 1); | |
| ctx.restore(); | |
| } | |
| function drawBullets() { | |
| ctx.fillStyle = '#ffff00'; | |
| for (let bullet of bullets) { | |
| ctx.fillRect(bullet.x, bullet.y, bullet.width, bullet.height); | |
| } | |
| } | |
| function drawInvaderBullets() { | |
| ctx.fillStyle = '#ff0000'; | |
| for (let bullet of invaderBullets) { | |
| ctx.fillRect(bullet.x, bullet.y, bullet.width, bullet.height); | |
| } | |
| } | |
| function drawInvaders() { | |
| for (let i = 0; i < invaders.length; i++) { | |
| const invader = invaders[i]; | |
| if (!invader.alive) continue; | |
| const x = invader.x; | |
| const y = invader.y; | |
| const row = Math.floor(i / 10); | |
| ctx.save(); | |
| // Different aircraft for different rows | |
| if (row === 0) { | |
| // J-10C Vigorous Dragon (Top row) | |
| ctx.fillStyle = '#2d5016'; | |
| ctx.fillRect(x + 16, y + 5, 8, 18); | |
| // Delta wing | |
| ctx.beginPath(); | |
| ctx.moveTo(x + 20, y + 8); | |
| ctx.lineTo(x + 8, y + 20); | |
| ctx.lineTo(x + 32, y + 20); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| // Nose | |
| ctx.fillStyle = '#1a202c'; | |
| ctx.beginPath(); | |
| ctx.moveTo(x + 20, y); | |
| ctx.lineTo(x + 16, y + 5); | |
| ctx.lineTo(x + 24, y + 5); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| // Canards | |
| ctx.fillStyle = '#38a169'; | |
| ctx.fillRect(x + 12, y + 6, 16, 3); | |
| } else if (row === 1) { | |
| // Dassault Rafale (Second row) | |
| ctx.fillStyle = '#2c5282'; | |
| ctx.fillRect(x + 16, y + 5, 8, 18); | |
| // Close-coupled canard delta | |
| ctx.fillStyle = '#3182ce'; | |
| ctx.fillRect(x + 10, y + 12, 20, 8); | |
| // Canards | |
| ctx.fillRect(x + 14, y + 7, 12, 3); | |
| // Twin engines | |
| ctx.fillStyle = '#1a202c'; | |
| ctx.fillRect(x + 14, y + 20, 4, 3); | |
| ctx.fillRect(x + 22, y + 20, 4, 3); | |
| } else if (row === 2) { | |
| // JF-17 Thunder (Third row) | |
| ctx.fillStyle = '#744210'; | |
| ctx.fillRect(x + 16, y + 5, 8, 18); | |
| // Wings | |
| ctx.fillStyle = '#a0522d'; | |
| ctx.fillRect(x + 8, y + 14, 24, 6); | |
| // Single engine | |
| ctx.fillStyle = '#8b4513'; | |
| ctx.fillRect(x + 17, y + 20, 6, 3); | |
| // Side intakes | |
| ctx.fillStyle = '#1a202c'; | |
| ctx.fillRect(x + 12, y + 12, 4, 4); | |
| ctx.fillRect(x + 24, y + 12, 4, 4); | |
| } else if (row === 3) { | |
| // F-16 Fighting Falcon (Fourth row) | |
| ctx.fillStyle = '#4a5568'; | |
| ctx.fillRect(x + 16, y + 5, 8, 18); | |
| // Wings | |
| ctx.fillStyle = '#718096'; | |
| ctx.fillRect(x + 10, y + 14, 20, 6); | |
| // Single engine | |
| ctx.fillStyle = '#e53e3e'; | |
| ctx.fillRect(x + 17, y + 20, 6, 3); | |
| // Side intake | |
| ctx.fillStyle = '#2d3748'; | |
| ctx.fillRect(x + 18, y + 12, 4, 4); | |
| } else { | |
| // Generic enemy fighter (Bottom row) | |
| ctx.fillStyle = '#742a2a'; | |
| ctx.fillRect(x + 16, y + 5, 8, 18); | |
| // Swept wings | |
| ctx.fillStyle = '#9b2c2c'; | |
| ctx.beginPath(); | |
| ctx.moveTo(x + 12, y + 16); | |
| ctx.lineTo(x + 8, y + 20); | |
| ctx.lineTo(x + 32, y + 20); | |
| ctx.lineTo(x + 28, y + 16); | |
| ctx.lineTo(x + 20, y + 12); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| } | |
| // Common elements for all aircraft | |
| // Cockpit | |
| ctx.fillStyle = '#4a90e2'; | |
| ctx.fillRect(x + 18, y + 6, 4, 3); | |
| // Nose cone highlight | |
| ctx.fillStyle = '#ffffff'; | |
| ctx.fillRect(x + 19, y + 2, 2, 2); | |
| ctx.restore(); | |
| } | |
| } | |
| function drawUI() { | |
| scoreElement.textContent = gameState.score; | |
| livesElement.textContent = gameState.lives; | |
| } | |
| // Main game loop | |
| function gameLoop() { | |
| // Clear canvas | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| if (gameState.gameRunning) { | |
| // Update | |
| updatePlayer(); | |
| updateBullets(); | |
| updateInvaderBullets(); | |
| updateInvaders(); | |
| invaderShoot(); | |
| checkCollisions(); | |
| checkWin(); | |
| } | |
| // Draw | |
| drawPlayer(); | |
| drawBullets(); | |
| drawInvaderBullets(); | |
| drawInvaders(); | |
| drawUI(); | |
| requestAnimationFrame(gameLoop); | |
| } | |
| // Initialize game | |
| createInvaders(); | |
| gameLoop(); | |
| </script> | |
| </body> | |
| </html> |