Custom Code Experiment •
Asteroids Game
Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Asteroids 3D - Three Levels</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
overflow: hidden;
background: radial-gradient(ellipse at center, #1a1a2e 0%, #0a0a15 100%);
font-family: 'Arial', sans-serif;
}
canvas {
display: block;
width: 100vw;
height: 100vh;
}
.ui {
position: absolute;
top: 20px;
left: 20px;
color: #fff;
font-size: 24px;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.8);
z-index: 10;
}
.ui-item {
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 10px;
}
.label {
color: #a0a0ff;
font-weight: bold;
}
.value {
color: #fff;
font-size: 28px;
}
.controls {
position: absolute;
bottom: 20px;
left: 20px;
color: rgba(255, 255, 255, 0.7);
font-size: 14px;
line-height: 1.6;
text-shadow: 0 2px 5px rgba(0, 0, 0, 0.8);
z-index: 10;
}
.game-over {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #fff;
font-size: 48px;
text-align: center;
text-shadow: 0 0 30px rgba(255, 100, 100, 0.8);
z-index: 10;
background: rgba(0, 0, 0, 0.7);
padding: 40px 60px;
border-radius: 20px;
border: 2px solid rgba(255, 255, 255, 0.3);
}
.game-over-subtitle {
font-size: 20px;
margin-top: 20px;
color: #a0a0ff;
}
.level-up {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #fff;
font-size: 42px;
text-align: center;
text-shadow: 0 0 30px rgba(100, 200, 255, 0.8);
z-index: 10;
background: rgba(0, 0, 0, 0.7);
padding: 40px 60px;
border-radius: 20px;
border: 2px solid rgba(255, 255, 255, 0.3);
display: none;
}
.level-up-subtitle {
font-size: 24px;
margin-top: 20px;
color: #00ffaa;
}
.stars {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 0;
}
#gameCanvas {
position: relative;
z-index: 1;
}
</style>
</head>
<body>
<canvas id="stars" class="stars"></canvas>
<canvas id="gameCanvas"></canvas>
<div class="ui">
<div class="ui-item">
<span class="label">SHIPS:</span>
<span class="value" id="lives">3</span>
</div>
<div class="ui-item">
<span class="label">SCORE:</span>
<span class="value" id="score">0</span>
</div>
<div class="ui-item">
<span class="label">LEVEL:</span>
<span class="value" id="level">1</span>
</div>
</div>
<div class="controls">
LEFT/RIGHT ARROW: Turn<br>
UP ARROW or TAB: Boost<br>
SPACE: Fire
</div>
<div id="gameOver" class="game-over" style="display: none;">
<div id="gameOverText">GAME OVER</div>
<div class="game-over-subtitle">Press R to Restart</div>
</div>
<div id="levelUp" class="level-up" style="display: none;">
<div id="levelUpText">LEVEL 2 UNLOCKED!</div>
<div class="level-up-subtitle">Prepare for DANGEROUS ASTEROIDS!</div>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const starsCanvas = document.getElementById('stars');
const starsCtx = starsCanvas.getContext('2d');
const livesElement = document.getElementById('lives');
const scoreElement = document.getElementById('score');
const levelElement = document.getElementById('level');
const gameOverElement = document.getElementById('gameOver');
const gameOverText = document.getElementById('gameOverText');
const levelUpElement = document.getElementById('levelUp');
const levelUpText = document.getElementById('levelUpText');
let starfield = [];
function initStars() {
starfield = [];
for (let i = 0; i < 200; i++) {
starfield.push({
x: Math.random() * starsCanvas.width,
y: Math.random() * starsCanvas.height,
size: Math.random() * 2,
brightness: Math.random()
});
}
}
function drawStars() {
starsCtx.fillStyle = 'rgba(10, 10, 21, 0.1)';
starsCtx.fillRect(0, 0, starsCanvas.width, starsCanvas.height);
starfield.forEach(star => {
star.brightness = 0.3 + Math.sin(Date.now() * 0.001 + star.x) * 0.3;
starsCtx.fillStyle = `rgba(255, 255, 255, ${star.brightness})`;
starsCtx.beginPath();
starsCtx.arc(star.x, star.y, star.size, 0, Math.PI * 2);
starsCtx.fill();
});
}
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
starsCanvas.width = window.innerWidth;
starsCanvas.height = window.innerHeight;
if (starfield.length === 0) {
initStars();
}
}
window.addEventListener('resize', resizeCanvas);
let gameState = {
lives: 3,
score: 0,
level: 1,
gameOver: false,
won: false,
transitioning: false,
transitionTimer: 0
};
const keys = {};
document.addEventListener('keydown', (e) => {
keys[e.code] = true;
if (e.code === 'Tab' || e.code === 'Space') e.preventDefault();
if (e.code === 'KeyR' && gameState.gameOver) {
restartGame();
}
});
document.addEventListener('keyup', (e) => {
keys[e.code] = false;
});
const ship = {
x: canvas.width / 2,
y: canvas.height / 2,
angle: -Math.PI / 2,
vx: 0,
vy: 0,
size: 12,
boosting: false,
invulnerable: 0
};
let missiles = [];
let asteroids = [];
let particles = [];
function createAsteroids() {
asteroids = [];
let asteroidCount;
let asteroidSize;
let baseSpeed;
if (gameState.level === 1) {
asteroidCount = 5;
asteroidSize = 40;
baseSpeed = 2;
} else if (gameState.level === 2) {
asteroidCount = 7;
asteroidSize = 35;
baseSpeed = 3;
} else {
asteroidCount = 9;
asteroidSize = 30;
baseSpeed = 3.5;
}
for (let i = 0; i < asteroidCount; i++) {
let x, y;
do {
x = Math.random() * canvas.width;
y = Math.random() * canvas.height;
} while (Math.sqrt((x - ship.x) ** 2 + (y - ship.y) ** 2) < 150);
let color;
if (gameState.level === 1) {
color = `hsl(${Math.random() * 40 + 20}, 70%, 50%)`;
} else if (gameState.level === 2) {
color = `hsl(${Math.random() * 60 + 180}, 70%, 50%)`;
} else {
color = `hsl(${Math.random() * 40 + 300}, 80%, 60%)`;
}
asteroids.push({
x: x,
y: y,
vx: (Math.random() - 0.5) * baseSpeed,
vy: (Math.random() - 0.5) * baseSpeed,
size: asteroidSize,
rotation: Math.random() * Math.PI * 2,
rotationSpeed: (Math.random() - 0.5) * (gameState.level === 1 ? 0.08 : gameState.level === 2 ? 0.12 : 0.15),
color: color,
shapeType: gameState.level
});
}
}
function drawAsteroid(asteroid) {
ctx.save();
ctx.translate(asteroid.x, asteroid.y);
ctx.rotate(asteroid.rotation);
if (asteroid.shapeType === 1) {
const points = 10;
const angles = [];
for (let i = 0; i < points; i++) {
angles.push({
angle: (i / points) * Math.PI * 2,
radius: asteroid.size * (0.7 + Math.sin(i * 2.7) * 0.3)
});
}
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
ctx.beginPath();
angles.forEach((point, i) => {
const x = Math.cos(point.angle) * point.radius + 3;
const y = Math.sin(point.angle) * point.radius + 3;
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
});
ctx.closePath();
ctx.fill();
const gradient = ctx.createRadialGradient(
-asteroid.size * 0.3, -asteroid.size * 0.3, 0,
0, 0, asteroid.size
);
const baseColor = asteroid.color;
gradient.addColorStop(0, baseColor.replace('50%', '70%'));
gradient.addColorStop(1, baseColor.replace('50%', '20%'));
ctx.fillStyle = gradient;
ctx.beginPath();
angles.forEach((point, i) => {
const x = Math.cos(point.angle) * point.radius;
const y = Math.sin(point.angle) * point.radius;
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
});
ctx.closePath();
ctx.fill();
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
ctx.lineWidth = 2;
ctx.stroke();
}
else if (asteroid.shapeType === 2) {
const points = 8;
const angles = [];
for (let i = 0; i < points; i++) {
angles.push({
angle: (i / points) * Math.PI * 2,
radius: asteroid.size * (0.5 + Math.sin(i * 3) * 0.5)
});
}
ctx.fillStyle = 'rgba(100, 100, 255, 0.3)';
ctx.beginPath();
angles.forEach((point, i) => {
const x = Math.cos(point.angle) * point.radius + 4;
const y = Math.sin(point.angle) * point.radius + 4;
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
});
ctx.closePath();
ctx.fill();
const gradient = ctx.createRadialGradient(
-asteroid.size * 0.2, -asteroid.size * 0.2, 0,
0, 0, asteroid.size * 1.2
);
gradient.addColorStop(0, asteroid.color.replace('50%', '90%'));
gradient.addColorStop(0.7, asteroid.color.replace('50%', '60%'));
gradient.addColorStop(1, asteroid.color.replace('50%', '30%'));
ctx.fillStyle = gradient;
ctx.beginPath();
angles.forEach((point, i) => {
const x = Math.cos(point.angle) * point.radius;
const y = Math.sin(point.angle) * point.radius;
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
});
ctx.closePath();
ctx.fill();
ctx.strokeStyle = 'rgba(200, 230, 255, 0.8)';
ctx.lineWidth = 3;
ctx.stroke();
ctx.strokeStyle = 'rgba(255, 255, 255, 0.6)';
ctx.lineWidth = 1;
ctx.beginPath();
for (let i = 0; i < points; i += 2) {
const angle1 = angles[i].angle;
const radius1 = angles[i].radius * 0.6;
const angle2 = angles[(i + points/2) % points].angle;
const radius2 = angles[(i + points/2) % points].radius * 0.6;
ctx.moveTo(Math.cos(angle1) * radius1, Math.sin(angle1) * radius1);
ctx.lineTo(Math.cos(angle2) * radius2, Math.sin(angle2) * radius2);
}
ctx.stroke();
}
else if (asteroid.shapeType === 3) {
const points = 12;
const angles = [];
for (let i = 0; i < points; i++) {
angles.push({
angle: (i / points) * Math.PI * 2,
radius: asteroid.size * (0.6 + Math.sin(i * 4) * 0.4)
});
}
ctx.fillStyle = 'rgba(255, 100, 255, 0.4)';
ctx.beginPath();
angles.forEach((point, i) => {
const x = Math.cos(point.angle) * point.radius + 5;
const y = Math.sin(point.angle) * point.radius + 5;
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
});
ctx.closePath();
ctx.fill();
const gradient = ctx.createRadialGradient(
-asteroid.size * 0.1, -asteroid.size * 0.1, 0,
0, 0, asteroid.size * 1.3
);
gradient.addColorStop(0, asteroid.color.replace('60%', '95%'));
gradient.addColorStop(0.5, asteroid.color.replace('60%', '70%'));
gradient.addColorStop(1, asteroid.color.replace('60%', '40%'));
ctx.fillStyle = gradient;
ctx.beginPath();
angles.forEach((point, i) => {
const x = Math.cos(point.angle) * point.radius;
const y = Math.sin(point.angle) * point.radius;
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
});
ctx.closePath();
ctx.fill();
ctx.strokeStyle = 'rgba(255, 150, 255, 0.9)';
ctx.lineWidth = 4;
ctx.stroke();
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
for (let i = 0; i < points; i += 3) {
const angle = angles[i].angle;
const radius = angles[i].radius * 0.3;
const x = Math.cos(angle) * (asteroid.size * 0.7);
const y = Math.sin(angle) * (asteroid.size * 0.7);
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fill();
}
ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
ctx.lineWidth = 1;
ctx.beginPath();
for (let i = 0; i < points; i += 4) {
const angle1 = angles[i].angle;
const radius1 = asteroid.size * 0.5;
const angle2 = angles[(i + points/4) % points].angle;
const radius2 = asteroid.size * 0.5;
ctx.moveTo(Math.cos(angle1) * radius1, Math.sin(angle1) * radius1);
ctx.lineTo(Math.cos(angle2) * radius2, Math.sin(angle2) * radius2);
}
ctx.stroke();
}
ctx.restore();
}
function createMissile(x, y, angle) {
return {
x: x,
y: y,
vx: Math.cos(angle) * 10,
vy: Math.sin(angle) * 10,
life: 80,
trail: []
};
}
function createParticles(x, y, color, count = 12) {
for (let i = 0; i < count; i++) {
const angle = (Math.PI * 2 * i) / count;
const speed = 2 + Math.random() * 3;
particles.push({
x: x,
y: y,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
life: 40,
maxLife: 40,
color: color,
size: 3 + Math.random() * 3
});
}
}
function wrap(obj) {
if (obj.x < 0) obj.x = canvas.width;
if (obj.x > canvas.width) obj.x = 0;
if (obj.y < 0) obj.y = canvas.height;
if (obj.y > canvas.height) obj.y = 0;
}
function checkCollision(obj1, obj2, radius1, radius2) {
const dx = obj1.x - obj2.x;
const dy = obj1.y - obj2.y;
const distance = Math.sqrt(dx * dx + dy * dy);
return distance < radius1 + radius2;
}
function breakAsteroid(asteroid) {
if (asteroid.size > 15) {
const newSize = asteroid.size / 2;
for (let i = 0; i < 2; i++) {
asteroids.push({
x: asteroid.x + (Math.random() - 0.5) * 20,
y: asteroid.y + (Math.random() - 0.5) * 20,
vx: asteroid.vx * 0.8 + (Math.random() - 0.5) * 3,
vy: asteroid.vy * 0.8 + (Math.random() - 0.5) * 3,
size: newSize,
rotation: Math.random() * Math.PI * 2,
rotationSpeed: (Math.random() - 0.5) * 0.15,
color: asteroid.color,
shapeType: asteroid.shapeType
});
}
}
createParticles(asteroid.x, asteroid.y, asteroid.color, 15);
}
function resetShip() {
ship.x = canvas.width / 2;
ship.y = canvas.height / 2;
ship.vx = 0;
ship.vy = 0;
ship.angle = -Math.PI / 2;
ship.invulnerable = 120;
}
function showLevelTransition() {
gameState.transitioning = true;
gameState.transitionTimer = 180;
if (gameState.level === 2) {
levelUpText.textContent = "LEVEL 2 UNLOCKED!";
levelUpElement.querySelector('.level-up-subtitle').textContent = "Prepare for DANGEROUS ASTEROIDS!";
} else if (gameState.level === 3) {
levelUpText.textContent = "FINAL LEVEL!";
levelUpElement.querySelector('.level-up-subtitle').textContent = "EXPERIMENTAL ASTEROIDS AHEAD!";
}
levelUpElement.style.display = 'block';
}
function hideLevelTransition() {
gameState.transitioning = false;
levelUpElement.style.display = 'none';
}
function advanceToNextLevel() {
if (gameState.level < 3) {
gameState.level++;
levelElement.textContent = gameState.level;
showLevelTransition();
createAsteroids();
resetShip();
} else {
gameState.gameOver = true;
gameState.won = true;
gameOverText.textContent = "YOU WIN!";
gameOverElement.style.display = 'block';
}
}
function update() {
if (gameState.gameOver) return;
if (gameState.transitioning) {
gameState.transitionTimer--;
if (gameState.transitionTimer <= 0) {
hideLevelTransition();
}
return;
}
if (keys['ArrowLeft']) {
ship.angle -= 0.12;
}
if (keys['ArrowRight']) {
ship.angle += 0.12;
}
if (keys['Tab'] || keys['ArrowUp']) {
ship.boosting = true;
ship.vx += Math.cos(ship.angle) * 0.25;
ship.vy += Math.sin(ship.angle) * 0.25;
if (Math.random() < 0.3) {
const thrusterX = ship.x - Math.cos(ship.angle) * 18;
const thrusterY = ship.y - Math.sin(ship.angle) * 18;
particles.push({
x: thrusterX + (Math.random() - 0.5) * 6,
y: thrusterY + (Math.random() - 0.5) * 6,
vx: -Math.cos(ship.angle) * 3 + (Math.random() - 0.5) * 2,
vy: -Math.sin(ship.angle) * 3 + (Math.random() - 0.5) * 2,
life: 20,
maxLife: 20,
color: '#ff6600',
size: 2 + Math.random() * 2
});
}
} else {
ship.boosting = false;
}
if (keys['Space']) {
if (!keys.spacePressed) {
missiles.push(createMissile(
ship.x + Math.cos(ship.angle) * 18,
ship.y + Math.sin(ship.angle) * 18,
ship.angle
));
keys.spacePressed = true;
}
} else {
keys.spacePressed = false;
}
ship.x += ship.vx;
ship.y += ship.vy;
ship.vx *= 0.99;
ship.vy *= 0.99;
wrap(ship);
if (ship.invulnerable > 0) {
ship.invulnerable--;
}
missiles = missiles.filter(missile => {
missile.trail.push({ x: missile.x, y: missile.y });
if (missile.trail.length > 8) missile.trail.shift();
missile.x += missile.vx;
missile.y += missile.vy;
wrap(missile);
missile.life--;
return missile.life > 0;
});
asteroids.forEach(asteroid => {
asteroid.x += asteroid.vx;
asteroid.y += asteroid.vy;
asteroid.rotation += asteroid.rotationSpeed;
wrap(asteroid);
});
particles = particles.filter(particle => {
particle.x += particle.vx;
particle.y += particle.vy;
particle.vx *= 0.98;
particle.vy *= 0.98;
particle.life--;
return particle.life > 0;
});
missiles.forEach((missile, mi) => {
asteroids.forEach((asteroid, ai) => {
if (checkCollision(missile, asteroid, 3, asteroid.size)) {
if (asteroid.shapeType === 1) {
gameState.score += asteroid.size > 30 ? 20 : asteroid.size > 15 ? 50 : 100;
} else if (asteroid.shapeType === 2) {
gameState.score += asteroid.size > 30 ? 40 : asteroid.size > 15 ? 100 : 200;
} else {
gameState.score += asteroid.size > 30 ? 80 : asteroid.size > 15 ? 200 : 400;
}
breakAsteroid(asteroid);
asteroids.splice(ai, 1);
missiles.splice(mi, 1);
}
});
});
if (ship.invulnerable === 0) {
asteroids.forEach(asteroid => {
if (checkCollision(ship, asteroid, ship.size, asteroid.size)) {
gameState.lives--;
createParticles(ship.x, ship.y, '#00ccff', 20);
if (gameState.lives <= 0) {
gameState.gameOver = true;
gameOverText.textContent = "GAME OVER";
gameOverElement.style.display = 'block';
} else {
resetShip();
}
}
});
}
if (asteroids.length === 0 && !gameState.transitioning && !gameState.gameOver) {
if (gameState.level < 3) {
advanceToNextLevel();
} else {
gameState.gameOver = true;
gameState.won = true;
gameOverText.textContent = "YOU WIN!";
gameOverElement.style.display = 'block';
}
}
livesElement.textContent = gameState.lives;
scoreElement.textContent = gameState.score;
}
function render() {
ctx.fillStyle = 'rgba(10, 10, 21, 0.2)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (ship.invulnerable === 0 || Math.floor(ship.invulnerable / 5) % 2) {
ctx.save();
ctx.translate(ship.x, ship.y);
ctx.rotate(ship.angle);
ctx.shadowColor = 'rgba(0, 150, 255, 0.8)';
ctx.shadowBlur = 20;
ctx.fillStyle = '#00aaff';
ctx.beginPath();
ctx.moveTo(18, 0);
ctx.lineTo(-10, -9);
ctx.lineTo(-5, 0);
ctx.lineTo(-10, 9);
ctx.closePath();
ctx.fill();
ctx.fillStyle = '#66ccff';
ctx.beginPath();
ctx.moveTo(18, 0);
ctx.lineTo(8, -4);
ctx.lineTo(8, 4);
ctx.closePath();
ctx.fill();
ctx.fillStyle = '#ffaa00';
ctx.beginPath();
ctx.arc(4, 0, 3, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
if (ship.boosting) {
const flameLength = 15 + Math.random() * 8;
const gradient = ctx.createLinearGradient(-5, 0, -flameLength, 0);
gradient.addColorStop(0, 'rgba(255, 150, 0, 0.9)');
gradient.addColorStop(0.5, 'rgba(255, 50, 0, 0.6)');
gradient.addColorStop(1, 'rgba(255, 0, 0, 0)');
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.moveTo(-5, 0);
ctx.lineTo(-flameLength, -5);
ctx.lineTo(-flameLength + 3, 0);
ctx.lineTo(-flameLength, 5);
ctx.closePath();
ctx.fill();
}
ctx.restore();
}
missiles.forEach(missile => {
ctx.strokeStyle = 'rgba(255, 200, 0, 0.5)';
ctx.lineWidth = 3;
ctx.shadowColor = 'rgba(255, 200, 0, 0.8)';
ctx.shadowBlur = 10;
ctx.beginPath();
missile.trail.forEach((point, i) => {
if (i === 0) ctx.moveTo(point.x, point.y);
else ctx.lineTo(point.x, point.y);
});
ctx.stroke();
ctx.fillStyle = '#ffff00';
ctx.shadowBlur = 15;
ctx.beginPath();
ctx.arc(missile.x, missile.y, 4, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
});
asteroids.forEach(asteroid => {
drawAsteroid(asteroid);
});
particles.forEach(particle => {
const alpha = particle.life / particle.maxLife;
ctx.fillStyle = particle.color.replace(')', `, ${alpha})`).replace('rgb', 'rgba').replace('hsl', 'hsla');
ctx.shadowColor = particle.color;
ctx.shadowBlur = 8;
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.size * alpha, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
});
}
function gameLoop() {
drawStars();
update();
render();
requestAnimationFrame(gameLoop);
}
function restartGame() {
gameState = {
lives: 3,
score: 0,
level: 1,
gameOver: false,
won: false,
transitioning: false,
transitionTimer: 0
};
missiles = [];
particles = [];
levelElement.textContent = gameState.level;
resetShip();
createAsteroids();
gameOverElement.style.display = 'none';
levelUpElement.style.display = 'none';
}
resizeCanvas();
createAsteroids();
resetShip();
gameLoop();
</script>
</body>
</html>