asteroids / index.html
gruntech's picture
Add 1 files
e7dac9a verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Asteroids Arcade</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: #000;
color: #0f0;
font-family: 'Orbitron', sans-serif;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
text-shadow: 0 0 5px #0f0;
}
#game-header {
text-align: center;
margin-bottom: 20px;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 0.8; }
50% { opacity: 1; text-shadow: 0 0 15px #0f0; }
100% { opacity: 0.8; }
}
#game-container {
position: relative;
width: 800px;
height: 600px;
border: 3px solid #0f0;
box-shadow: 0 0 20px rgba(0, 255, 0, 0.3),
inset 0 0 20px rgba(0, 255, 0, 0.3);
background-color: #111;
overflow: hidden;
}
canvas {
display: block;
}
#ui {
position: absolute;
top: 15px;
left: 15px;
font-size: 20px;
display: flex;
gap: 30px;
z-index: 10;
}
#controls {
position: absolute;
bottom: 15px;
left: 15px;
font-size: 16px;
color: #0f0;
background-color: rgba(0, 0, 0, 0.5);
padding: 10px;
border-radius: 5px;
border: 1px solid #0f0;
display: flex;
gap: 15px;
}
.control-group {
display: flex;
flex-direction: column;
gap: 5px;
}
.key {
display: inline-block;
width: 30px;
height: 30px;
background-color: #222;
border: 2px solid #0f0;
border-radius: 5px;
text-align: center;
line-height: 26px;
margin-right: 5px;
}
.key.space {
width: 80px;
}
#game-over {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 100;
}
#game-over h2 {
font-size: 72px;
color: #f00;
text-shadow: 0 0 15px #f00;
margin-bottom: 30px;
animation: flicker 0.5s infinite alternate;
}
@keyframes flicker {
0% { opacity: 0.7; }
100% { opacity: 1; }
}
#final-score {
font-size: 24px;
margin-bottom: 30px;
}
#restart {
padding: 12px 25px;
font-family: 'Orbitron', sans-serif;
font-size: 18px;
background: linear-gradient(#222, #111);
color: #0f0;
border: 2px solid #0f0;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s;
}
#restart:hover {
background: linear-gradient(#333, #222);
box-shadow: 0 0 15px #0f0;
transform: scale(1.05);
}
#mobile-controls {
display: none;
position: absolute;
bottom: 80px;
width: 100%;
justify-content: center;
gap: 20px;
z-index: 10;
}
.mobile-btn {
width: 70px;
height: 70px;
background-color: rgba(0, 255, 0, 0.2);
border: 2px solid #0f0;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
color: #0f0;
font-size: 24px;
user-select: none;
}
@media (max-width: 850px) {
#game-container {
width: 100vw;
height: 60vh;
border: none;
border-bottom: 3px solid #0f0;
}
#mobile-controls {
display: flex;
}
#controls {
display: none;
}
}
#start-screen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.9);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 200;
}
#start-screen h1 {
font-size: 60px;
margin-bottom: 30px;
color: #0f0;
text-shadow: 0 0 15px #0f0;
}
#start-screen p {
font-size: 20px;
margin-bottom: 40px;
max-width: 600px;
text-align: center;
line-height: 1.5;
}
#start-btn {
padding: 15px 40px;
font-family: 'Orbitron', sans-serif;
font-size: 24px;
background: linear-gradient(#222, #111);
color: #0f0;
border: 2px solid #0f0;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s;
}
#start-btn:hover {
background: linear-gradient(#333, #222);
box-shadow: 0 0 15px #0f0;
transform: scale(1.05);
}
</style>
</head>
<body>
<div id="game-header">
<h1>ASTEROIDS</h1>
</div>
<div id="game-container">
<canvas id="game-canvas"></canvas>
<div id="ui">
<div id="score-display">SCORE: <span id="score">0</span></div>
<div id="lives-display">LIVES: <span id="lives">3</span></div>
</div>
<div id="controls">
<div class="control-group">
<div>SHIP CONTROLS:</div>
<div><span class="key"></span> Rotate Left</div>
<div><span class="key"></span> Rotate Right</div>
<div><span class="key"></span> Thrust</div>
</div>
<div class="control-group">
<div>WEAPONS:</div>
<div><span class="key space">SPACE</span> Fire</div>
</div>
</div>
<div id="mobile-controls">
<div class="mobile-btn" id="left-btn"></div>
<div class="mobile-btn" id="up-btn"></div>
<div class="mobile-btn" id="right-btn"></div>
<div class="mobile-btn" id="fire-btn">FIRE</div>
</div>
<div id="game-over">
<h2>GAME OVER</h2>
<div id="final-score">FINAL SCORE: <span id="end-score">0</span></div>
<button id="restart">PLAY AGAIN</button>
</div>
<div id="start-screen">
<h1>ASTEROIDS ARCADE</h1>
<p>Navigate your spaceship through an asteroid field. Destroy asteroids to earn points, but be careful - hitting them will cost you a life!</p>
<button id="start-btn">START GAME</button>
</div>
</div>
<script>
// Game setup
const canvas = document.getElementById('game-canvas');
const ctx = canvas.getContext('2d');
const scoreElement = document.getElementById('score');
const livesElement = document.getElementById('lives');
const endScoreElement = document.getElementById('end-score');
const gameOverElement = document.getElementById('game-over');
const restartButton = document.getElementById('restart');
const startScreen = document.getElementById('start-screen');
const startButton = document.getElementById('start-btn');
// Mobile controls
const leftBtn = document.getElementById('left-btn');
const rightBtn = document.getElementById('right-btn');
const upBtn = document.getElementById('up-btn');
const fireBtn = document.getElementById('fire-btn');
// Set canvas dimensions
function resizeCanvas() {
canvas.width = canvas.parentElement.clientWidth;
canvas.height = canvas.parentElement.clientHeight;
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
// Game state
let score = 0;
let lives = 3;
let gameOver = false;
let gameStarted = false;
let asteroids = [];
let bullets = [];
let particles = [];
let keys = {};
// Player ship
const ship = {
x: canvas.width / 2,
y: canvas.height / 2,
radius: 15,
angle: -Math.PI/2, // Point up initially
rotation: 0,
thrusting: false,
thrust: {
x: 0,
y: 0
},
velocity: {
x: 0,
y: 0
},
invulnerable: false,
invulnerableTime: 0
};
// Event listeners for keyboard
window.addEventListener('keydown', (e) => {
if (gameStarted) {
keys[e.key] = true;
// Prevent spacebar from scrolling
if (e.key === ' ' && e.target === document.body) {
e.preventDefault();
}
}
});
window.addEventListener('keyup', (e) => {
if (gameStarted) {
keys[e.key] = false;
}
});
// Mobile touch events
if ('ontouchstart' in window) {
const handleMobileDown = (key) => {
return () => {
if (!gameStarted) return;
if (key === 'ArrowLeft') keys['ArrowLeft'] = true;
if (key === 'ArrowRight') keys['ArrowRight'] = true;
if (key === 'ArrowUp') keys['ArrowUp'] = true;
if (key === ' ') keys[' '] = true;
};
};
const handleMobileUp = (key) => {
return () => {
if (!gameStarted) return;
if (key === 'ArrowLeft') keys['ArrowLeft'] = false;
if (key === 'ArrowRight') keys['ArrowRight'] = false;
if (key === 'ArrowUp') keys['ArrowUp'] = false;
if (key === ' ') keys[' '] = false;
};
};
leftBtn.addEventListener('touchstart', handleMobileDown('ArrowLeft'), {passive: false});
leftBtn.addEventListener('touchend', handleMobileUp('ArrowLeft'), {passive: false});
rightBtn.addEventListener('touchstart', handleMobileDown('ArrowRight'), {passive: false});
rightBtn.addEventListener('touchend', handleMobileUp('ArrowRight'), {passive: false});
upBtn.addEventListener('touchstart', handleMobileDown('ArrowUp'), {passive: false});
upBtn.addEventListener('touchend', handleMobileUp('ArrowUp'), {passive: false});
fireBtn.addEventListener('touchstart', handleMobileDown(' '), {passive: false});
fireBtn.addEventListener('touchend', handleMobileUp(' '), {passive: false});
// Prevent touch scroll
document.body.addEventListener('touchmove', (e) => {
if (gameStarted) e.preventDefault();
}, { passive: false });
}
// Start game button
startButton.addEventListener('click', startGame);
// Restart game button
restartButton.addEventListener('click', resetGame);
function startGame() {
gameStarted = true;
startScreen.style.display = 'none';
resetGame();
gameLoop();
}
function resetGame() {
score = 0;
lives = 3;
gameOver = false;
asteroids = [];
bullets = [];
particles = [];
ship.x = canvas.width / 2;
ship.y = canvas.height / 2;
ship.angle = -Math.PI/2;
ship.velocity.x = 0;
ship.velocity.y = 0;
ship.invulnerable = true;
ship.invulnerableTime = 120; // 2 seconds of invulnerability
scoreElement.textContent = score;
livesElement.textContent = lives;
gameOverElement.style.display = 'none';
createAsteroids(4);
}
// Create initial asteroids
function createAsteroids(count) {
for (let i = 0; i < count; i++) {
const angle = Math.random() * Math.PI * 2;
const distance = Math.max(canvas.width, canvas.height) * 0.4;
const asteroid = createAsteroid(
canvas.width/2 + Math.cos(angle) * distance,
canvas.height/2 + Math.sin(angle) * distance,
Math.random() * Math.PI * 2,
1 // Large asteroid
);
asteroids.push(asteroid);
}
}
// Create a single asteroid
function createAsteroid(x, y, angle, size) {
let radius;
let points;
switch(size) {
case 1: // Large
radius = 40;
points = 20;
break;
case 2: // Medium
radius = 25;
points = 50;
break;
case 3: // Small
radius = 10;
points = 100;
break;
}
const speed = size === 1 ? 1 + Math.random() * 1 :
size === 2 ? 1.5 + Math.random() * 1.5 :
2 + Math.random() * 2;
return {
x,
y,
radius,
size,
points,
vertices: Math.floor(Math.random() * 9) + 5,
angle,
speed,
velocity: {
x: Math.cos(angle) * speed,
y: Math.sin(angle) * speed
},
rotation: (Math.random() - 0.5) * 0.2
};
}
// Main game loop
function gameLoop() {
if (!gameStarted || gameOver) return;
// Clear canvas
ctx.fillStyle = '#111';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw starfield background
drawStarfield();
// Update and draw game elements
updateShip();
drawShip();
updateBullets();
drawBullets();
updateAsteroids();
drawAsteroids();
updateParticles();
drawParticles();
checkCollisions();
spawnAsteroids();
// Update invulnerability timer
if (ship.invulnerable) {
ship.invulnerableTime--;
if (ship.invulnerableTime <= 0) {
ship.invulnerable = false;
}
}
requestAnimationFrame(gameLoop);
}
function drawStarfield() {
ctx.fillStyle = '#fff';
ctx.save();
// Draw some random stars
for (let i = 0; i < 100; i++) {
const x = Math.random() * canvas.width;
const y = Math.random() * canvas.height;
const size = Math.random() * 1.5;
ctx.globalAlpha = Math.random();
ctx.beginPath();
ctx.arc(x, y, size, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
}
function updateShip() {
// Rotate ship
if (keys['ArrowLeft']) {
ship.rotation = -0.1;
} else if (keys['ArrowRight']) {
ship.rotation = 0.1;
} else {
ship.rotation = 0;
}
// Apply rotation
ship.angle += ship.rotation;
// Thrust
if (keys['ArrowUp']) {
ship.thrusting = true;
ship.thrust.x = Math.cos(ship.angle) * 0.1;
ship.thrust.y = Math.sin(ship.angle) * 0.1;
} else {
ship.thrusting = false;
ship.thrust.x = 0;
ship.thrust.y = 0;
}
// Apply thrust to velocity
ship.velocity.x += ship.thrust.x;
ship.velocity.y += ship.thrust.y;
// Friction
ship.velocity.x *= 0.98;
ship.velocity.y *= 0.98;
// Update position
ship.x += ship.velocity.x;
ship.y += ship.velocity.y;
// Screen wrapping
if (ship.x < 0 - ship.radius) {
ship.x = canvas.width + ship.radius;
} else if (ship.x > canvas.width + ship.radius) {
ship.x = 0 - ship.radius;
}
if (ship.y < 0 - ship.radius) {
ship.y = canvas.height + ship.radius;
} else if (ship.y > canvas.height + ship.radius) {
ship.y = 0 - ship.radius;
}
// Fire bullets
if (keys[' '] && !keys['_space']) {
keys['_space'] = true;
fireBullet();
} else if (!keys[' ']) {
keys['_space'] = false;
}
}
function drawShip() {
ctx.save();
ctx.translate(ship.x, ship.y);
ctx.rotate(ship.angle);
// Draw ship with different colors when invulnerable
if (ship.invulnerable) {
ctx.strokeStyle = Date.now() % 200 > 100 ? '#0f0' : '#f80';
} else {
ctx.strokeStyle = '#0f0';
}
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(20, 0);
ctx.lineTo(-10, -10);
ctx.lineTo(-5, 0);
ctx.lineTo(-10, 10);
ctx.closePath();
ctx.stroke();
// Thrust flame
if (ship.thrusting) {
ctx.beginPath();
ctx.moveTo(-15, 0);
ctx.lineTo(-25, 0);
ctx.strokeStyle = '#f80';
ctx.stroke();
}
ctx.restore();
}
function fireBullet() {
if (bullets.length > 4) return; // Limit number of bullets
const bullet = {
x: ship.x + Math.cos(ship.angle) * 20,
y: ship.y + Math.sin(ship.angle) * 20,
velocity: {
x: Math.cos(ship.angle) * 7 + ship.velocity.x,
y: Math.sin(ship.angle) * 7 + ship.velocity.y
},
radius: 3,
life: 60 // Frames until bullet disappears
};
bullets.push(bullet);
// Play sound
playLaserSound();
}
function playLaserSound() {
const oscillator = new (AudioContext || webkitAudioContext)().createOscillator();
const gainNode = oscillator.context.createGain();
oscillator.type = 'square';
oscillator.frequency.value = 440;
gainNode.gain.value = 0.1;
oscillator.connect(gainNode);
gainNode.connect(oscillator.context.destination);
oscillator.start();
gainNode.gain.exponentialRampToValueAtTime(0.001, oscillator.context.currentTime + 0.2);
oscillator.stop(oscillator.context.currentTime + 0.2);
}
function updateBullets() {
for (let i = bullets.length - 1; i >= 0; i--) {
bullets[i].x += bullets[i].velocity.x;
bullets[i].y += bullets[i].velocity.y;
bullets[i].life--;
// Screen wrapping
if (bullets[i].x < 0) bullets[i].x = canvas.width;
else if (bullets[i].x > canvas.width) bullets[i].x = 0;
if (bullets[i].y < 0) bullets[i].y = canvas.height;
else if (bullets[i].y > canvas.height) bullets[i].y = 0;
// Remove old bullets
if (bullets[i].life <= 0) {
bullets.splice(i, 1);
}
}
}
function drawBullets() {
ctx.fillStyle = '#0f0';
for (let bullet of bullets) {
ctx.beginPath();
ctx.arc(bullet.x, bullet.y, bullet.radius, 0, Math.PI * 2);
ctx.fill();
}
}
function updateAsteroids() {
for (let asteroid of asteroids) {
asteroid.x += asteroid.velocity.x;
asteroid.y += asteroid.velocity.y;
asteroid.angle += asteroid.rotation;
// Screen wrapping
if (asteroid.x < 0 - asteroid.radius) {
asteroid.x = canvas.width + asteroid.radius;
} else if (asteroid.x > canvas.width + asteroid.radius) {
asteroid.x = 0 - asteroid.radius;
}
if (asteroid.y < 0 - asteroid.radius) {
asteroid.y = canvas.height + asteroid.radius;
} else if (asteroid.y > canvas.height + asteroid.radius) {
asteroid.y = 0 - asteroid.radius;
}
}
}
function drawAsteroids() {
for (const asteroid of asteroids) {
ctx.save();
ctx.translate(asteroid.x, asteroid.y);
ctx.rotate(asteroid.angle);
ctx.strokeStyle = '#fff';
ctx.lineWidth = 2;
ctx.beginPath();
// Draw irregular polygon
for (let i = 0; i < asteroid.vertices; i++) {
const angle = (i / asteroid.vertices) * Math.PI * 2;
const offset = asteroid.radius * (0.7 + Math.random() * 0.3);
const x = Math.cos(angle) * offset;
const y = Math.sin(angle) * offset;
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.closePath();
ctx.stroke();
ctx.restore();
}
}
function checkCollisions() {
// Bullets vs Asteroids
for (let i = asteroids.length - 1; i >= 0; i--) {
const asteroid = asteroids[i];
for (let j = bullets.length - 1; j >= 0; j--) {
const bullet = bullets[j];
const dx = asteroid.x - bullet.x;
const dy = asteroid.y - bullet.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < asteroid.radius + bullet.radius) {
// Hit!
createParticles(asteroid);
score += asteroid.points;
scoreElement.textContent = score;
// Break apart asteroid if it's large enough
if (asteroid.size < 3) {
const numNew = 2;
for (let n = 0; n < numNew; n++) {
const angle = Math.random() * Math.PI * 2;
const newAsteroid = createAsteroid(
asteroid.x,
asteroid.y,
angle,
asteroid.size + 1
);
asteroids.push(newAsteroid);
}
}
// Remove asteroid and bullet
asteroids.splice(i, 1);
bullets.splice(j, 1);
break;
}
}
}
// Ship vs Asteroids
if (!ship.invulnerable) {
for (let i = 0; i < asteroids.length; i++) {
const asteroid = asteroids[i];
const dx = ship.x - asteroid.x;
const dy = ship.y - asteroid.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < ship.radius + asteroid.radius) {
// Collision!
lives--;
livesElement.textContent = lives;
createParticles(asteroid);
if (lives <= 0) {
gameOver = true;
gameOverElement.style.display = 'flex';
endScoreElement.textContent = score;
return;
}
// Respawn ship with invulnerability
ship.x = canvas.width / 2;
ship.y = canvas.height / 2;
ship.velocity.x = 0;
ship.velocity.y = 0;
ship.angle = -Math.PI/2;
ship.invulnerable = true;
ship.invulnerableTime = 120;
break;
}
}
}
}
function createParticles(asteroid) {
const particleCount = 15 + Math.floor(Math.random() * 10);
const particleSizeBase = asteroid.radius / 10;
for (let i = 0; i < particleCount; i++) {
const size = particleSizeBase + Math.random() * 3;
particles.push({
x: asteroid.x + (Math.random() * 20 - 10),
y: asteroid.y + (Math.random() * 20 - 10),
size: size,
color: `hsl(${Math.random() * 60 + 30}, 100%, 50%)`,
speed: 1 + Math.random() * 3 * (asteroid.radius / 30),
angle: Math.random() * Math.PI * 2,
life: 30 + Math.random() * 40,
decay: 0.95 + Math.random() * 0.04
});
}
// Play explosion sound
playExplosionSound();
}
function playExplosionSound() {
const oscillator = new (AudioContext || webkitAudioContext)().createOscillator();
const gainNode = oscillator.context.createGain();
oscillator.type = 'sawtooth';
oscillator.frequency.value = 100;
gainNode.gain.value = 0.1;
oscillator.connect(gainNode);
gainNode.connect(oscillator.context.destination);
oscillator.start();
gainNode.gain.exponentialRampToValueAtTime(0.001, oscillator.context.currentTime + 0.5);
oscillator.stop(oscillator.context.currentTime + 0.5);
}
function updateParticles() {
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += Math.cos(p.angle) * p.speed;
p.y += Math.sin(p.angle) * p.speed;
p.life--;
p.speed *= p.decay;
if (p.life <= 0) {
particles.splice(i, 1);
}
}
}
function drawParticles() {
for (const particle of particles) {
ctx.fillStyle = particle.color;
ctx.globalAlpha = Math.min(1, particle.life / 30);
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalAlpha = 1;
}
function spawnAsteroids() {
// Only spawn new asteroids if there are 3 or less
if (asteroids.length <= 3 && Math.random() < 0.01) {
const side = Math.floor(Math.random() * 4);
let x, y;
switch(side) {
case 0: // Top
x = Math.random() * canvas.width;
y = 0 - 50;
break;
case 1: // Right
x = canvas.width + 50;
y = Math.random() * canvas.height;
break;
case 2: // Bottom
x = Math.random() * canvas.width;
y = canvas.height + 50;
break;
case 3: // Left
x = 0 - 50;
y = Math.random() * canvas.height;
break;
}
const angle = Math.atan2(
canvas.height/2 - y,
canvas.width/2 - x
) + (Math.random() - 0.5) * 0.5;
asteroids.push(createAsteroid(x, y, angle, 1));
}
}
</script>
</body>
</html>