| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Asteroids Game</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <style> |
| body { |
| margin: 0; |
| overflow: hidden; |
| background-color: #000; |
| font-family: 'Arial', sans-serif; |
| } |
| canvas { |
| display: block; |
| } |
| #gameUI { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| pointer-events: none; |
| } |
| #score, #lives, #gameOver, #level { |
| color: white; |
| position: absolute; |
| font-size: 1.5rem; |
| } |
| #score { |
| top: 20px; |
| left: 20px; |
| } |
| #lives { |
| top: 20px; |
| right: 20px; |
| } |
| #gameOver { |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| font-size: 3rem; |
| text-align: center; |
| display: none; |
| } |
| #startScreen { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background-color: rgba(0, 0, 0, 0.8); |
| display: flex; |
| flex-direction: column; |
| justify-content: center; |
| align-items: center; |
| color: white; |
| } |
| #startButton { |
| margin-top: 20px; |
| padding: 10px 30px; |
| background-color: #4CAF50; |
| color: white; |
| border: none; |
| border-radius: 5px; |
| font-size: 1.5rem; |
| cursor: pointer; |
| pointer-events: auto; |
| } |
| #controls { |
| margin-top: 20px; |
| text-align: center; |
| font-size: 1rem; |
| } |
| </style> |
| </head> |
| <body> |
| <canvas id="gameCanvas"></canvas> |
| <div id="gameUI"> |
| <div id="score">Score: 0</div> |
| <div id="lives">Lives: 3</div> |
| <div id="level" style="top: 50px; left: 20px;">Level: 1</div> |
| <div id="gameOver"> |
| Game Over<br> |
| <button id="restartButton" class="mt-4 px-6 py-2 bg-green-500 text-white rounded hover:bg-green-600">Play Again</button> |
| </div> |
| </div> |
| <div id="startScreen"> |
| <h1 class="text-4xl font-bold mb-4">ASTEROIDS</h1> |
| <button id="startButton">START GAME</button> |
| <div id="controls" class="mt-8"> |
| <p class="mb-2"><span class="font-bold">Controls:</span></p> |
| <p>WASD or Arrow Keys to move</p> |
| <p>Space to shoot</p> |
| <p>Arrow Down or F to activate shield (4 sec max)</p> |
| </div> |
| </div> |
|
|
| <script> |
| document.addEventListener('DOMContentLoaded', () => { |
| 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'); |
| const startScreen = document.getElementById('startScreen'); |
| const startButton = document.getElementById('startButton'); |
| const restartButton = document.getElementById('restartButton'); |
| |
| |
| canvas.width = window.innerWidth; |
| canvas.height = window.innerHeight; |
| |
| |
| let score = 0; |
| let lives = 3; |
| let gameRunning = false; |
| let animationId; |
| let level = 1; |
| let asteroidsToNextLevel = 0; |
| let shieldActive = false; |
| let shieldDuration = 0; |
| let shieldMaxDuration = 4; |
| let shieldCooldown = 0; |
| let shieldCooldownMax = 4; |
| let shieldRegenRate = 1; |
| let shieldRadius = 40; |
| |
| |
| const player = { |
| x: canvas.width / 2, |
| y: canvas.height / 2, |
| radius: 15, |
| angle: 0, |
| rotation: 0, |
| thrusting: false, |
| thrust: { |
| x: 0, |
| y: 0 |
| }, |
| velocity: { |
| x: 0, |
| y: 0 |
| }, |
| speed: 0.1, |
| friction: 0.98, |
| rotationSpeed: 0.05, |
| cooldown: 0, |
| cooldownMax: 10 |
| }; |
| |
| |
| const bullets = []; |
| const bulletSpeed = 5; |
| const bulletRadius = 2; |
| const bulletLife = 60; |
| |
| |
| const asteroids = []; |
| let asteroidCount = 5; |
| let asteroidSpeed = 1; |
| const asteroidRadius = 30; |
| const asteroidLevels = 3; |
| |
| |
| const keys = { |
| ArrowUp: false, |
| ArrowDown: false, |
| ArrowLeft: false, |
| ArrowRight: false, |
| w: false, |
| a: false, |
| s: false, |
| d: false, |
| ' ': false, |
| 'f': false, |
| 'ArrowDown': false |
| }; |
| |
| |
| window.addEventListener('keydown', (e) => { |
| if (keys.hasOwnProperty(e.key)) { |
| keys[e.key] = true; |
| } |
| }); |
| |
| window.addEventListener('keyup', (e) => { |
| if (keys.hasOwnProperty(e.key)) { |
| keys[e.key] = false; |
| } |
| }); |
| |
| startButton.addEventListener('click', startGame); |
| restartButton.addEventListener('click', startGame); |
| |
| window.addEventListener('resize', () => { |
| canvas.width = window.innerWidth; |
| canvas.height = window.innerHeight; |
| if (gameRunning) { |
| player.x = canvas.width / 2; |
| player.y = canvas.height / 2; |
| } |
| }); |
| |
| |
| function startGame() { |
| score = 0; |
| lives = 3; |
| level = 1; |
| asteroidCount = 5; |
| asteroidSpeed = 1; |
| bullets.length = 0; |
| asteroids.length = 0; |
| |
| player.x = canvas.width / 2; |
| player.y = canvas.height / 2; |
| player.velocity.x = 0; |
| player.velocity.y = 0; |
| player.thrusting = false; |
| player.angle = 0; |
| |
| startScreen.style.display = 'none'; |
| gameOverElement.style.display = 'none'; |
| gameRunning = true; |
| |
| |
| for (let i = 0; i < asteroidCount; i++) { |
| createAsteroid(); |
| } |
| |
| asteroidsToNextLevel = asteroidCount; |
| updateScore(); |
| updateLives(); |
| updateLevel(); |
| animate(); |
| } |
| |
| function gameOver() { |
| gameRunning = false; |
| gameOverElement.style.display = 'block'; |
| cancelAnimationFrame(animationId); |
| } |
| |
| function updateScore() { |
| scoreElement.textContent = `Score: ${score}`; |
| } |
| |
| function updateLives() { |
| livesElement.textContent = `Lives: ${lives}`; |
| } |
| |
| function updateLevel() { |
| document.getElementById('level').textContent = `Level: ${level}`; |
| } |
| |
| function createAsteroid(x, y, level = asteroidLevels) { |
| if (x === undefined || y === undefined) { |
| |
| if (Math.random() < 0.5) { |
| x = Math.random() < 0.5 ? -asteroidRadius : canvas.width + asteroidRadius; |
| y = Math.random() * canvas.height; |
| } else { |
| x = Math.random() * canvas.width; |
| y = Math.random() < 0.5 ? -asteroidRadius : canvas.height + asteroidRadius; |
| } |
| |
| |
| while (Math.sqrt(Math.pow(x - player.x, 2) + Math.pow(y - player.y, 2)) < 200) { |
| x = Math.random() * canvas.width; |
| y = Math.random() * canvas.height; |
| } |
| } |
| |
| const angle = Math.random() * Math.PI * 2; |
| const velocity = { |
| x: Math.cos(angle) * asteroidSpeed * (1 + (asteroidLevels - level) * 0.5), |
| y: Math.sin(angle) * asteroidSpeed * (1 + (asteroidLevels - level) * 0.5) |
| }; |
| |
| const radius = (asteroidRadius * level / asteroidLevels) + Math.random() * 10; |
| |
| asteroids.push({ |
| x, |
| y, |
| radius, |
| velocity, |
| rotation: Math.random() * Math.PI * 2, |
| rotationSpeed: (Math.random() - 0.5) * 0.02, |
| level |
| }); |
| } |
| |
| function shoot() { |
| if (player.cooldown <= 0) { |
| bullets.push({ |
| x: player.x, |
| y: player.y, |
| velocity: { |
| x: Math.cos(player.angle) * bulletSpeed, |
| y: Math.sin(player.angle) * bulletSpeed |
| }, |
| life: bulletLife |
| }); |
| player.cooldown = player.cooldownMax; |
| } |
| } |
| |
| function update() { |
| |
| if (keys.ArrowLeft || keys.a) { |
| player.rotation = -player.rotationSpeed; |
| } else if (keys.ArrowRight || keys.d) { |
| player.rotation = player.rotationSpeed; |
| } else { |
| player.rotation = 0; |
| } |
| |
| player.angle += player.rotation; |
| |
| |
| player.thrusting = false; |
| if (keys.ArrowUp || keys.w) { |
| player.thrusting = true; |
| player.thrust.x = Math.cos(player.angle) * player.speed; |
| player.thrust.y = Math.sin(player.angle) * player.speed; |
| |
| player.velocity.x += player.thrust.x; |
| player.velocity.y += player.thrust.y; |
| } |
| |
| |
| player.velocity.x *= player.friction; |
| player.velocity.y *= player.friction; |
| |
| |
| player.x += player.velocity.x; |
| player.y += player.velocity.y; |
| |
| |
| if (player.x < 0) player.x = canvas.width; |
| if (player.x > canvas.width) player.x = 0; |
| if (player.y < 0) player.y = canvas.height; |
| if (player.y > canvas.height) player.y = 0; |
| |
| |
| if (keys[' ']) { |
| shoot(); |
| } |
| |
| if (player.cooldown > 0) { |
| player.cooldown--; |
| } |
| |
| |
| if ((keys['f'] || keys['ArrowDown']) && shieldDuration > 0 && !shieldActive) { |
| shieldActive = true; |
| } |
| |
| |
| if (shieldActive) { |
| shieldDuration -= 1/60; |
| if (shieldDuration <= 0) { |
| shieldActive = false; |
| shieldCooldown = shieldMaxDuration; |
| } |
| } else if (shieldDuration < shieldMaxDuration) { |
| |
| shieldDuration = Math.min(shieldDuration + shieldRegenRate/60, shieldMaxDuration); |
| } |
| |
| |
| if (shieldActive) { |
| for (let i = asteroids.length - 1; i >= 0; i--) { |
| const asteroid = asteroids[i]; |
| const dist = Math.sqrt(Math.pow(player.x - asteroid.x, 2) + Math.pow(player.y - asteroid.y, 2)); |
| if (dist < shieldRadius + asteroid.radius) { |
| |
| const angle = Math.atan2(asteroid.y - player.y, asteroid.x - player.x); |
| const pushForce = 2; |
| asteroid.velocity.x = Math.cos(angle) * pushForce; |
| asteroid.velocity.y = Math.sin(angle) * pushForce; |
| } |
| } |
| } |
| |
| |
| for (let i = bullets.length - 1; i >= 0; i--) { |
| const bullet = bullets[i]; |
| bullet.x += bullet.velocity.x; |
| bullet.y += bullet.velocity.y; |
| bullet.life--; |
| |
| |
| if (bullet.x < 0) bullet.x = canvas.width; |
| if (bullet.x > canvas.width) bullet.x = 0; |
| if (bullet.y < 0) bullet.y = canvas.height; |
| if (bullet.y > canvas.height) bullet.y = 0; |
| |
| |
| if (bullet.life <= 0) { |
| bullets.splice(i, 1); |
| } |
| } |
| |
| |
| for (let i = asteroids.length - 1; i >= 0; i--) { |
| const asteroid = asteroids[i]; |
| asteroid.x += asteroid.velocity.x; |
| asteroid.y += asteroid.velocity.y; |
| asteroid.rotation += asteroid.rotationSpeed; |
| |
| |
| if (asteroid.x < -asteroid.radius) asteroid.x = canvas.width + asteroid.radius; |
| if (asteroid.x > canvas.width + asteroid.radius) asteroid.x = -asteroid.radius; |
| if (asteroid.y < -asteroid.radius) asteroid.y = canvas.height + asteroid.radius; |
| if (asteroid.y > canvas.height + asteroid.radius) asteroid.y = -asteroid.radius; |
| |
| |
| const dist = Math.sqrt(Math.pow(player.x - asteroid.x, 2) + Math.pow(player.y - asteroid.y, 2)); |
| if (dist < player.radius + asteroid.radius && !shieldActive) { |
| lives--; |
| updateLives(); |
| |
| if (lives <= 0) { |
| gameOver(); |
| return; |
| } |
| |
| |
| player.x = canvas.width / 2; |
| player.y = canvas.height / 2; |
| player.velocity.x = 0; |
| player.velocity.y = 0; |
| |
| |
| asteroids.splice(i, 1); |
| createAsteroid(); |
| continue; |
| } |
| |
| |
| for (let j = bullets.length - 1; j >= 0; j--) { |
| const bullet = bullets[j]; |
| const dist = Math.sqrt(Math.pow(bullet.x - asteroid.x, 2) + Math.pow(bullet.y - asteroid.y, 2)); |
| if (dist < asteroid.radius) { |
| |
| bullets.splice(j, 1); |
| |
| |
| score += 10 * asteroid.level; |
| updateScore(); |
| |
| |
| if (asteroid.level > 1) { |
| |
| const pieces = 2 + Math.floor(Math.random() * 2); |
| for (let k = 0; k < pieces; k++) { |
| createAsteroid(asteroid.x, asteroid.y, asteroid.level - 1); |
| } |
| } |
| |
| |
| asteroids.splice(i, 1); |
| asteroidsToNextLevel--; |
| |
| |
| if (asteroids.length === 0) { |
| level++; |
| asteroidCount = 5 + Math.floor(level * 1.5); |
| asteroidSpeed = 1 + level * 0.2; |
| |
| |
| for (let i = 0; i < asteroidCount; i++) { |
| createAsteroid(); |
| } |
| asteroidsToNextLevel = asteroidCount; |
| } |
| break; |
| } |
| } |
| } |
| } |
| |
| function draw() { |
| |
| ctx.fillStyle = 'black'; |
| ctx.fillRect(0, 0, canvas.width, canvas.height); |
| |
| |
| ctx.fillStyle = 'white'; |
| for (let i = 0; i < 100; i++) { |
| const x = Math.random() * canvas.width; |
| const y = Math.random() * canvas.height; |
| const size = Math.random() * 2; |
| ctx.fillRect(x, y, size, size); |
| } |
| |
| |
| if (shieldActive) { |
| ctx.save(); |
| ctx.translate(player.x, player.y); |
| ctx.beginPath(); |
| ctx.arc(0, 0, shieldRadius, 0, Math.PI * 2); |
| ctx.strokeStyle = 'rgba(0, 150, 255, 0.7)'; |
| ctx.lineWidth = 3; |
| ctx.stroke(); |
| |
| |
| const shieldPercentage = shieldDuration / shieldMaxDuration; |
| ctx.beginPath(); |
| ctx.arc(0, 0, shieldRadius + 5, -Math.PI/2, -Math.PI/2 + shieldPercentage * Math.PI * 2); |
| ctx.strokeStyle = 'rgba(0, 255, 255, 0.9)'; |
| ctx.lineWidth = 2; |
| ctx.stroke(); |
| ctx.restore(); |
| } |
| |
| |
| ctx.save(); |
| ctx.translate(player.x, player.y); |
| ctx.rotate(player.angle); |
| |
| ctx.strokeStyle = 'white'; |
| ctx.lineWidth = 2; |
| ctx.beginPath(); |
| ctx.moveTo(player.radius, 0); |
| ctx.lineTo(-player.radius, -player.radius / 2); |
| ctx.lineTo(-player.radius / 2, 0); |
| ctx.lineTo(-player.radius, player.radius / 2); |
| ctx.closePath(); |
| ctx.stroke(); |
| |
| |
| if (player.thrusting) { |
| ctx.fillStyle = 'orange'; |
| ctx.beginPath(); |
| ctx.moveTo(-player.radius, -player.radius / 3); |
| ctx.lineTo(-player.radius * 1.5, 0); |
| ctx.lineTo(-player.radius, player.radius / 3); |
| ctx.closePath(); |
| ctx.fill(); |
| } |
| |
| ctx.restore(); |
| |
| |
| ctx.fillStyle = 'white'; |
| for (const bullet of bullets) { |
| ctx.beginPath(); |
| ctx.arc(bullet.x, bullet.y, bulletRadius, 0, Math.PI * 2); |
| ctx.fill(); |
| } |
| |
| |
| ctx.strokeStyle = 'white'; |
| ctx.lineWidth = 2; |
| for (const asteroid of asteroids) { |
| ctx.save(); |
| ctx.translate(asteroid.x, asteroid.y); |
| ctx.rotate(asteroid.rotation); |
| |
| ctx.beginPath(); |
| for (let i = 0; i < 8; i++) { |
| const angle = (i / 8) * Math.PI * 2; |
| const radius = asteroid.radius * (0.7 + Math.random() * 0.3); |
| const x = Math.cos(angle) * radius; |
| const y = Math.sin(angle) * radius; |
| |
| if (i === 0) { |
| ctx.moveTo(x, y); |
| } else { |
| ctx.lineTo(x, y); |
| } |
| } |
| ctx.closePath(); |
| ctx.stroke(); |
| |
| ctx.restore(); |
| } |
| } |
| |
| function animate() { |
| if (!gameRunning) return; |
| |
| update(); |
| draw(); |
| |
| animationId = requestAnimationFrame(animate); |
| } |
| }); |
| </script> |
| </body> |
| </html> |
|
|