← Mike Higgins Home
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>