Skip to content

Instantly share code, notes, and snippets.

@simonw

simonw/space.md Secret

Created July 29, 2025 18:45
Show Gist options
  • Select an option

  • Save simonw/965111fd6fac320b7eec50710c1761db to your computer and use it in GitHub Desktop.

Select an option

Save simonw/965111fd6fac320b7eec50710c1761db to your computer and use it in GitHub Desktop.

https://huggingface.co/lmstudio-community/Qwen3-30B-A3B-Instruct-2507-MLX-8bit in LM Studio.


Write an HTML and JavaScript page implementing space invaders


<!DOCTYPE html>
<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-color: #000;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            font-family: 'Courier New', monospace;
            overflow: hidden;
        }

        #game-container {
            position: relative;
            width: 600px;
            height: 700px;
            border: 2px solid #333;
            overflow: hidden;
        }

        #game-canvas {
            background-color: #000;
            display: block;
        }

        .score-display {
            position: absolute;
            top: 10px;
            left: 10px;
            color: #0f0;
            font-size: 20px;
            font-weight: bold;
            text-shadow: 0 0 5px #0f0;
        }

        .lives-display {
            position: absolute;
            top: 10px;
            right: 10px;
            color: #f00;
            font-size: 20px;
            font-weight: bold;
            text-shadow: 0 0 5px #f00;
        }

        .game-over {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            color: #f00;
            font-size: 48px;
            font-weight: bold;
            text-shadow: 0 0 10px #f00;
            display: none;
        }

        .restart-btn {
            position: absolute;
            top: 60%;
            left: 50%;
            transform: translate(-50%, -50%);
            padding: 10px 20px;
            background-color: #0f0;
            color: #000;
            border: none;
            border-radius: 5px;
            font-size: 20px;
            cursor: pointer;
            display: none;
        }

        .restart-btn:hover {
            background-color: #0c0;
        }
    </style>
</head>
<body>
    <div id="game-container">
        <canvas id="game-canvas" width="600" height="700"></canvas>
        <div id="score" class="score-display">Score: 0</div>
        <div id="lives" class="lives-display">Lives: 3</div>
        <div id="game-over" class="game-over">GAME OVER</div>
        <button id="restart-btn" class="restart-btn">Restart Game</button>
    </div>

    <script>
        // Get canvas and context
        const canvas = document.getElementById('game-canvas');
        const ctx = canvas.getContext('2d');

        // Game elements
        let player;
        let bullets = [];
        let enemies = [];
        let enemyBullets = [];
        let particles = [];
        
        // Game state
        const gameState = {
            score: 0,
            lives: 3,
            gameOver: false,
            level: 1,
            enemySpeed: 0.8
        };

        // Player class
        class Player {
            constructor() {
                this.width = 40;
                this.height = 30;
                this.x = canvas.width / 2 - this.width / 2;
                this.y = canvas.height - this.height - 20;
                this.speed = 5;
                this.color = '#0f0';
                this.shootCooldown = 0;
            }

            draw() {
                // Draw player ship
                ctx.fillStyle = this.color;
                
                // Ship body
                ctx.fillRect(this.x, this.y, this.width, this.height);
                
                // Ship cockpit
                ctx.fillStyle = '#0ff';
                ctx.fillRect(this.x + this.width / 2 - 5, this.y + 5, 10, 8);
                
                // Ship wings
                ctx.fillStyle = '#0f0';
                ctx.fillRect(this.x - 5, this.y + 10, 5, 10);
                ctx.fillRect(this.x + this.width, this.y + 10, 5, 10);
            }

            move(direction) {
                if (direction === 'left' && this.x > 0) {
                    this.x -= this.speed;
                } else if (direction === 'right' && this.x < canvas.width - this.width) {
                    this.x += this.speed;
                }
            }

            shoot() {
                if (this.shootCooldown <= 0) {
                    bullets.push({
                        x: this.x + this.width / 2 - 2,
                        y: this.y,
                        width: 4,
                        height: 10,
                        color: '#fff',
                        speed: -7
                    });
                    this.shootCooldown = 15;
                }
            }

            update() {
                if (this.shootCooldown > 0) {
                    this.shootCooldown--;
                }
            }

            getBounds() {
                return {
                    left: this.x,
                    right: this.x + this.width,
                    top: this.y,
                    bottom: this.y + this.height
                };
            }
        }

        // Enemy class
        class Enemy {
            constructor(x, y, type = 1) {
                this.width = 30;
                this.height = 25;
                this.x = x;
                this.y = y;
                this.type = type; // 1: basic, 2: fast, 3: armored
                this.speed = gameState.enemySpeed * (type === 1 ? 1 : type === 2 ? 1.5 : 0.8);
                this.direction = 1;
                this.shootCooldown = Math.random() * 200 + 50; // Random shooting
                this.color = type === 1 ? '#f00' : type === 2 ? '#ff0' : '#8c1a5d';
            }

            draw() {
                // Draw enemy ship
                ctx.fillStyle = this.color;
                
                // Ship body
                ctx.fillRect(this.x, this.y, this.width, this.height);
                
                // Enemy eyes
                ctx.fillStyle = '#fff';
                ctx.fillRect(this.x + 6, this.y + 5, 4, 4);
                ctx.fillRect(this.x + this.width - 10, this.y + 5, 4, 4);
                
                // Enemy antennae
                ctx.fillStyle = '#f00';
                if (this.type === 1) {
                    // Basic enemy
                    ctx.fillRect(this.x + this.width / 2 - 1, this.y - 5, 2, 5);
                } else if (this.type === 2) {
                    // Fast enemy
                    ctx.fillRect(this.x + this.width / 4 - 1, this.y - 5, 2, 5);
                    ctx.fillRect(this.x + (3 * this.width) / 4 - 1, this.y - 5, 2, 5);
                } else if (this.type === 3) {
                    // Armored enemy
                    ctx.fillRect(this.x + this.width / 2 - 1, this.y - 8, 2, 8);
                    ctx.fillStyle = '#0f0';
                    ctx.fillRect(this.x + this.width / 2 - 1, this.y - 6, 2, 3);
                }
            }

            update() {
                // Move enemy
                this.x += this.speed * this.direction;
                
                // Change direction on edge collision
                if (this.x <= 0 || this.x + this.width >= canvas.width) {
                    this.direction *= -1;
                }
                
                // Check for shooting
                if (this.shootCooldown <= 0) {
                    enemyBullets.push({
                        x: this.x + this.width / 2 - 1,
                        y: this.y + this.height,
                        width: 2,
                        height: 8,
                        color: '#f00',
                        speed: 3
                    });
                    this.shootCooldown = Math.random() * 200 + 150;
                } else {
                    this.shootCooldown--;
                }
            }

            getBounds() {
                return {
                    left: this.x,
                    right: this.x + this.width,
                    top: this.y,
                    bottom: this.y + this.height
                };
            }
        }

        // Bullet class
        class Bullet {
            constructor(x, y, width, height, color, speed) {
                this.x = x;
                this.y = y;
                this.width = width;
                this.height = height;
                this.color = color;
                this.speed = speed;
            }

            draw() {
                ctx.fillStyle = this.color;
                ctx.fillRect(this.x, this.y, this.width, this.height);
            }

            update() {
                this.y += this.speed;
            }

            getBounds() {
                return {
                    left: this.x,
                    right: this.x + this.width,
                    top: this.y,
                    bottom: this.y + this.height
                };
            }
        }

        // Particle class for explosions
        class Particle {
            constructor(x, y) {
                this.x = x;
                this.y = y;
                this.size = Math.random() * 5 + 2;
                this.speedX = Math.random() * 6 - 3;
                this.speedY = Math.random() * 6 - 3;
                this.color = `rgba(${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)}, 1)`;
                this.life = 30;
            }

            draw() {
                ctx.fillStyle = this.color;
                ctx.beginPath();
                ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
                ctx.fill();
            }

            update() {
                this.x += this.speedX;
                this.y += this.speedY;
                this.size -= 0.1;
                this.life--;
            }
        }

        // Initialize game
        function initGame() {
            player = new Player();
            bullets = [];
            enemies = [];
            enemyBullets = [];
            particles = [];
            
            gameState.score = 0;
            gameState.lives = 3;
            gameState.gameOver = false;
            gameState.level = 1;
            gameState.enemySpeed = 0.8;
            
            // Create enemies
            createEnemies();
            
            // Update UI
            updateUI();
        }

        function createEnemies() {
            const rows = Math.min(4, 5 + gameState.level);
            const cols = Math.min(8, 10 + gameState.level * 2);
            
            for (let row = 0; row < rows; row++) {
                const y = 50 + row * (30 + 10);
                
                for (let col = 0; col < cols; col++) {
                    const x = 50 + col * (30 + 15);
                    
                    // Determine enemy type based on row
                    let type = 1;
                    if (row === 0) {
                        // Top row - fast enemies
                        type = 2;
                    } else if (row >= rows - 1) {
                        // Bottom row - armored enemies
                        type = 3;
                    }
                    
                    enemies.push(new Enemy(x, y, type));
                }
            }
        }

        // Update UI elements
        function updateUI() {
            document.getElementById('score').textContent = `Score: ${gameState.score}`;
            document.getElementById('lives').textContent = `Lives: ${gameState.lives}`;
            
            // Update game over screen
            const gameOverScreen = document.getElementById('game-over');
            const restartBtn = document.getElementById('restart-btn');
            
            if (gameState.gameOver) {
                gameOverScreen.style.display = 'block';
                restartBtn.style.display = 'block';
            } else {
                gameOverScreen.style.display = 'none';
                restartBtn.style.display = 'none';
            }
        }

        // Check collisions
        function checkCollisions() {
            // Player bullets vs enemies
            for (let i = bullets.length - 1; i >= 0; i--) {
                const bullet = bullets[i];
                
                for (let j = enemies.length - 1; j >= 0; j--) {
                    const enemy = enemies[j];
                    
                    if (collides(bullet.getBounds(), enemy.getBounds())) {
                        // Remove bullet and enemy
                        bullets.splice(i, 1);
                        
                        // Add explosion particles
                        for (let k = 0; k < 10; k++) {
                            particles.push(new Particle(enemy.x + enemy.width / 2, enemy.y + enemy.height / 2));
                        }
                        
                        // Increase score based on enemy type
                        if (enemy.type === 1) {
                            gameState.score += 10;
                        } else if (enemy.type === 2) {
                            gameState.score += 20;
                        } else {
                            gameState.score += 30;
                        }
                        
                        // Remove enemy
                        enemies.splice(j, 1);
                        
                        break;
                    }
                }
            }

            // Enemy bullets vs player
            for (let i = enemyBullets.length - 1; i >= 0; i--) {
                const bullet = enemyBullets[i];
                
                if (collides(bullet.getBounds(), player.getBounds())) {
                    // Remove bullet
                    enemyBullets.splice(i, 1);
                    
                    // Take damage
                    gameState.lives--;
                    updateUI();
                    
                    // Check game over
                    if (gameState.lives <= 0) {
                        endGame();
                    }
                    
                    // Add explosion particles
                    for (let k = 0; k < 10; k++) {
                        particles.push(new Particle(player.x + player.width / 2, player.y + player.height / 2));
                    }
                    
                    break;
                }
            }

            // Enemy ships hit player
            for (let i = enemies.length - 1; i >= 0; i--) {
                const enemy = enemies[i];
                
                if (collides(enemy.getBounds(), player.getBounds())) {
                    // Player hit by enemy
                    gameState.lives--;
                    updateUI();
                    
                    if (gameState.lives <= 0) {
                        endGame();
                    }
                    
                    // Add explosion particles
                    for (let k = 0; k < 15; k++) {
                        particles.push(new Particle(enemy.x + enemy.width / 2, enemy.y + enemy.height / 2));
                    }
                    
                    // Remove enemy
                    enemies.splice(i, 1);
                    
                    break;
                }
            }

            // Check if all enemies are defeated
            if (enemies.length === 0) {
                gameState.level++;
                gameState.enemySpeed += 0.2;
                
                // Create new wave
                createEnemies();
            }
        }

        function collides(rect1, rect2) {
            return (
                rect1.left < rect2.right &&
                rect1.right > rect2.left &&
                rect1.top < rect2.bottom &&
                rect1.bottom > rect2.top
            );
        }

        // Game over function
        function endGame() {
            gameState.gameOver = true;
            updateUI();
            
            // Show game over screen
            const gameOverScreen = document.getElementById('game-over');
            gameOverScreen.style.display = 'block';
        }

        // Game loop
        function gameLoop() {
            if (gameState.gameOver) return;
            
            // Clear canvas
            ctx.clearRect(0, 0, canvas.width, canvas.height);

            // Update player
            player.update();

            // Update bullets
            for (let i = bullets.length - 1; i >= 0; i--) {
                const bullet = bullets[i];
                bullet.update();
                
                if (bullet.y < 0 || bullet.y > canvas.height) {
                    bullets.splice(i, 1);
                }
            }

            // Update enemy bullets
            for (let i = enemyBullets.length - 1; i >= 0; i--) {
                const bullet = enemyBullets[i];
                bullet.update();
                
                if (bullet.y < 0 || bullet.y > canvas.height) {
                    enemyBullets.splice(i, 1);
                }
            }

            // Update enemies
            for (let i = enemies.length - 1; i >= 0; i--) {
                const enemy = enemies[i];
                enemy.update();
            }

            // Update particles
            for (let i = particles.length - 1; i >= 0; i--) {
                const particle = particles[i];
                particle.update();
                
                if (particle.life <= 0 || particle.size <= 0) {
                    particles.splice(i, 1);
                }
            }

            // Check collisions
            checkCollisions();

            // Draw all elements
            player.draw();
            
            bullets.forEach(bullet => bullet.draw());
            enemies.forEach(enemy => enemy.draw());
            enemyBullets.forEach(bullet => bullet.draw());
            particles.forEach(particle => particle.draw());

            // Request next frame
            requestAnimationFrame(gameLoop);
        }

        // Event listeners
        document.addEventListener('keydown', (e) => {
            if (gameState.gameOver) return;
            
            switch(e.key) {
                case 'ArrowLeft':
                    player.move('left');
                    break;
                case 'ArrowRight':
                    player.move('right');
                    break;
                case ' ':
                    player.shoot();
                    e.preventDefault();
                    break;
            }
        });

        // Restart game button
        document.getElementById('restart-btn').addEventListener('click', () => {
            initGame();
        });

        // Start the game
        initGame();
        gameLoop();
    </script>
</body>
</html>

This HTML and JavaScript implementation of Space Invaders includes the following features:

  • A player-controlled spaceship that moves left/right and shoots
  • Enemy ships in multiple rows with different types (basic, fast, armored)
  • Random enemy shooting
  • Collision detection for bullets and ships
  • Score tracking with different point values based on enemy type
  • Lives system (3 lives)
  • Level progression with increasing difficulty
  • Particle explosions when enemies are destroyed or player is hit
  • Game over screen with restart button
  • Responsive UI elements

Controls:

  • Arrow keys: Move left and right
  • Spacebar: Shoot bullets
  • Restart button: Play again after game over

The game features a retro arcade feel with green player ship, red enemy ships, and animated explosions. The difficulty increases as you progress through levels with faster enemies and more complex formations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment