Space Invaders · qwen3-coder-flash
Build a playable Space Invaders game on a canvas: a player cannon that moves and shoots, a descending grid of aliens that advance and fire back, destructible bunkers, score, and lives. Procedural graphics only, no external assets.
This one-shot run produced no runnable artifact.
agent produced no index.html
The transcript below shows what the model did — read it to see why the one shot fell short.
transcript · 2 messages
Build a playable Space Invaders game on a canvas: a player cannon that moves and shoots, a descending grid of aliens that advance and fire back, destructible bunkers, score, and lives. Procedural graphics only, no external assets.
```html <!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: 20px; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; background-color: #000; color: #0f0; font-family: 'Courier New', monospace; overflow: hidden; } h1 { margin-bottom: 10px; text-shadow: 0 0 10px #0f0; letter-spacing: 3px; } #game-container { position: relative; margin: 20px 0; } canvas { background-color: #000; border: 2px solid #0f0; box-shadow: 0 0 20px rgba(0, 255, 0, 0.3); } #score-display { font-size: 24px; margin-bottom: 10px; text-align: center; } #lives-display { font-size: 24px; margin-bottom: 20px; text-align: center; } #controls { margin-top: 20px; text-align: center; font-size: 16px; color: #0a0; } #game-over, #start-screen { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; background-color: rgba(0, 0, 0, 0.8); padding: 30px; border: 2px solid #0f0; box-shadow: 0 0 20px rgba(0, 255, 0, 0.5); } button { background-color: #002200; color: #0f0; border: 1px solid #0f0; padding: 10px 20px; font-size: 18px; font-family: 'Courier New', monospace; cursor: pointer; margin-top: 20px; transition: all 0.3s; } button:hover { background-color: #004400; box-shadow: 0 0 10px #0f0; } .hidden { display: none; } </style> </head> <body> <h1>SPACE INVADERS</h1> <div id="score-display">SCORE: 0</div> <div id="lives-display">LIVES: 3</div> <div id="game-container"> <canvas id="gameCanvas" width="800" height="600"></canvas> <div id="start-screen"> <h2>SPACE INVADERS</h2> <p>Defend Earth from alien invasion!</p> <button id="start-button">START GAME</button> </div> <div id="game-over" class="hidden"> <h2>GAME OVER</h2> <p id="final-score">Score: 0</p> <button id="restart-button">PLAY AGAIN</button> </div> </div> <div id="controls"> <p>CONTROLS: ← → to move | SPACE to shoot</p> </div> <script> // Game constants const CANVAS_WIDTH = 800; const CANVAS_HEIGHT = 600; const PLAYER_WIDTH = 50; const PLAYER_HEIGHT = 30; const PLAYER_SPEED = 7; const ALIEN_ROWS = 5; const ALIEN_COLS = 10; const ALIEN_WIDTH = 40; const ALIEN_HEIGHT = 30; const ALIEN_PADDING = 15; const ALIEN_SPEED = 1; const ALIEN_DROP = 20; const BUNKER_WIDTH = 80; const BUNKER_HEIGHT = 40; const BUNKER_PADDING = 50; const BULLET_WIDTH = 4; const BULLET_HEIGHT = 15; const PLAYER_BULLET_SPEED = 8; const ALIEN_BULLET_SPEED = 5; const BULLET_COOLDOWN = 20; // frames between shots // Game state let gameState = { player: { x: CANVAS_WIDTH / 2 - PLAYER_WIDTH / 2, y: CANVAS_HEIGHT - 50, width: PLAYER_WIDTH, height: PLAYER_HEIGHT, speed: PLAYER_SPEED, color: '#0f0' }, aliens: [], bullets: [], alienBullets: [], bunkers: [], score: 0, lives: 3, level: 1, alienDirection: 1, alienMoveTimer: 0, alienMoveInterval: 30, playerBulletCooldown: 0, gameOver: false, gameStarted: false }; // Canvas setup const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); const startScreen = document.getElementById('start-screen'); const gameOverScreen = document.getElementById('game-over'); const finalScoreElement = document.getElementById('final-score'); const startButton = document.getElementById('start-button'); const restartButton = document.getElementById('restart-button'); const scoreDisplay = document.getElementById('score-display'); const livesDisplay = document.getElementById('lives-display'); // Initialize game function initGame() { gameState = { player: { x: CANVAS_WIDTH / 2 - PLAYER_WIDTH / 2, y: CANVAS_HEIGHT - 50, width: PLAYER_WIDTH, height: PLAYER_HEIGHT, speed: PLAYER_SPEED, color: '#0f0' }, aliens: [], bullets: [], alienBullets: [], bunkers: [], score: 0, lives: 3, level: 1, alienDirection: 1, alienMoveTimer: 0, alienMoveInterval: 30, playerBulletCooldown: 0, gameOver: false, gameStarted: true }; createAliens(); createBunkers(); updateDisplays(); startScreen.classList.add('hidden'); gameOverScreen.classList.add('hidden'); } // Create aliens grid function createAliens() { gameState.aliens = []; const startX = (CANVAS_WIDTH - (ALIEN_COLS * (ALIEN_WIDTH + ALIEN_PADDING))) / 2; const startY = 50; for (let row = 0; row < ALIEN_ROWS; row++) { for (let col = 0; col < ALIEN_COLS; col++) { gameState.aliens.push({ x: startX + col * (ALIEN_WIDTH + ALIEN_PADDING), y: startY + row * (ALIEN_HEIGHT + ALIEN_PADDING), width: ALIEN_WIDTH, height: ALIEN_HEIGHT, alive: true, color: row === 0 ? '#ff5555' : row < 3 ? '#ffaa55' : '#ffff55' }); } } } // Create bunkers function createBunkers() { gameState.bunkers = []; const startX = (CANVAS_WIDTH - (ALIEN_COLS * (ALIEN_WIDTH + ALIEN_PADDING))) / 2; const bunkerCount = 4; for (let i = 0; i < bunkerCount; i++) { gameState.bunkers.push({ x: startX + i * (BUNKER_WIDTH + BUNKER_PADDING), y: CANVAS_HEIGHT - 120, width: BUNKER_WIDTH, height: BUNKER_HEIGHT, health: 3 }); } } // Draw player ship function drawPlayer() { ctx.fillStyle = gameState.player.color; // Draw ship body ctx.beginPath(); ctx.moveTo(gameState.player.x, gameState.player.y + gameState.player.height); ctx.lineTo(gameState.player.x + gameState.player.width / 2, gameState.player.y); ctx.lineTo(gameState.player.x + gameState.player.width, gameState.player.y + gameState.player.height); ctx.closePath(); ctx.fill(); // Draw ship details ctx.fillStyle = '#00a000'; ctx.fillRect(gameState.player.x + gameState.player.width/2 - 5, gameState.player.y + 10, 10, 10); } // Draw aliens function drawAliens() { gameState.aliens.forEach(alien => { if (!alien.alive) return; ctx.fillStyle = alien.color; // Draw alien body ctx.beginPath(); ctx.arc( alien.x + alien.width/2, alien.y + alien.height/2, alien.width/2, 0, Math.PI * 2 ); ctx.fill(); // Draw alien eyes ctx.fillStyle = '#000'; ctx.beginPath(); ctx.arc( alien.x + alien.width/3, alien.y + alien.height/3, 4, 0, Math.PI * 2 ); ctx.arc( alien.x + 2*alien.width/3, alien.y + alien.height/3, 4, 0, Math.PI * 2 ); ctx.fill(); }); } // Draw bullets function drawBullets() { // Player bullets ctx.fillStyle = '#0f0'; gameState.bullets.forEach(bullet => { ctx.fillRect(bullet.x, bullet.y, BULLET_WIDTH, BULLET_HEIGHT); }); // Alien bullets ctx.fillStyle = '#ff5555'; gameState.alienBullets.forEach(bullet => { ctx.fillRect(bullet.x, bullet.y, BULLET_WIDTH, BULLET_HEIGHT); }); } // Draw bunkers function drawBunkers() { gameState.bunkers.forEach(bunker => { if (bunker.health <= 0) return; ctx.fillStyle = '#5555ff'; ctx.fillRect(bunker.x, bunker.y, bunker.width, bunker.height); // Draw bunker details ctx.fillStyle = '#3333aa'; for (let i = 0; i < 3; i++) { for (let j = 0; j < 3; j++) { if ((i + j) % 2 === 0) { ctx.fillRect( bunker.x + i * (bunker.width/3), bunker.y + j * (bunker.height/3), bunker.width/3, bunker.height/3 ); } } } }); } // Update player position function updatePlayer() { // Move player based on keys pressed if (keys['ArrowLeft'] && gameState.player.x > 0) { gameState.player.x -= gameState.player.speed; } if (keys['ArrowRight'] && gameState.player.x < CANVAS_WIDTH - gameState.player.width) { gameState.player.x += gameState.player.speed; } // Shooting cooldown if (keys[' '] && gameState.playerBulletCooldown <= 0) { gameState.bullets.push({ x: gameState.player.x + gameState.player.width/2 - BULLET_WIDTH/2, y: gameState.player.y, width: BULLET_WIDTH, height: BULLET_HEIGHT }); gameState.playerBulletCooldown = BULLET_COOLDOWN; } // Update cooldown if (gameState.playerBulletCooldown > 0) { gameState.playerBulletCooldown--; } } // Update aliens function updateAliens() { gameState.alienMoveTimer++; if (gameState.alienMoveTimer >= gameState.alienMoveInterval) { gameState.alienMoveTimer = 0; let moveDown = false; // Check if aliens need to change direction gameState.aliens.forEach(alien => { if (!alien.alive) return; if ((alien.x <= 0 && gameState.alienDirection === -1) || (alien.x + alien.width >= CANVAS_WIDTH && gameState.alienDirection === 1)) { moveDown = true; } }); if (moveDown) { gameState.alienDirection *= -1; gameState.aliens.forEach(alien => { if (alien.alive) { alien.y += ALIEN_DROP; } }); } else { // Move aliens horizontally gameState.aliens.forEach(alien => { if (alien.alive) { alien.x += ALIEN_SPEED * gameState.alienDirection; } }); } // Random alien shooting if (Math.random() < 0.02 && gameState.aliens.some(a => a.alive)) { const aliveAliens = gameState.aliens.filter(a => a.alive); if (aliveAliens.length > 0) { const shooter = aliveAliens[Math.floor(Math.random() * aliveAliens.length)]; gameState.alienBullets.push({ x: shooter.x + shooter.width/2 - BULLET_WIDTH/2, y: shooter.y + shooter.height, width: BULLET_WIDTH, height: BULLET_HEIGHT }); } } } } // Update bullets function updateBullets() { // Update player bullets for (let i = gameState.bullets.length - 1; i >= 0; i--) { gameState.bullets[i].y -= PLAYER_BULLET_SPEED; // Remove bullets that go off screen if (gameState.bullets[i].y < 0) { gameState.bullets.splice(i, 1); continue; } // Check collision with aliens for (let j = 0; j < gameState.aliens.length; j++) { const alien = gameState.aliens[j]; if (!alien.alive) continue; if (gameState.bullets[i] && gameState.bullets[i].x < alien.x + alien.width && gameState.bullets[i].x + gameState.bullets[i].width > alien.x && gameState.bullets[i].y < alien.y + alien.height && gameState.bullets[i].y + gameState.bullets[i].height > alien.y) { alien.alive = false; gameState.bullets.splice(i, 1); gameState.score += 10; updateDisplays(); break; } } // Check collision with bunkers for (let j = 0; j < gameState.bunkers.length; j++) { const bunker = gameState.bunkers[j]; if (bunker.health <= 0) continue; if (gameState.bullets[i] && gameState.bullets[i].x < bunker.x + bunker.width && gameState.bullets[i].x + gameState.bullets[i].width > bunker.x && gameState.bullets[i].y < bunker.y + bunker.height && gameState.bullets[i].y + gameState.bullets[i].height > bunker.y) { bunker.health--; gameState.bullets.splice(i, 1); break; } } } // Update alien bullets for (let i = gameState.alienBullets.length - 1; i >= 0; i--) { gameState.alienBullets[i].y += ALIEN_BULLET_SPEED; // Remove bullets that go off screen if (gameState.alienBullets[i].y > CANVAS_HEIGHT) { gameState.alienBullets.splice(i, 1); continue; } // Check collision with player if (gameState.alienBullets[i] && gameState.alienBullets[i].x < gameState.player.x + gameState.player.width && gameState.alienBullets[i].x + gameState.alienBullets[i].width > gameState.player.x && gameState.alienBullets[i].y < gameState.player.y + gameState.player.height && gameState.alienBullets[i].y + gameState.alienBullets[i].height > gameState.player.y) { gameState.alienBullets.splice(i, 1); gameState.lives--; updateDisplays(); if (gameState.lives <= 0) { gameOver(); } break; } // Check collision with bunkers for (let j = 0; j < gameState.bunkers.length; j++) { const bunker = gameState.bunkers[j]; if (bunker.health <= 0) continue; if (gameState.alienBullets[i] && gameState.alienBullets[i].x < bunker.x + bunker.width && gameState.alienBullets[i].x + gameState.alienBullets[i].width > bunker.x && gameState.alienBullets[i].y < bunker.y + bunker.height && gameState.alienBullets[i].y + gameState.alienBullets[i].height > bunker.y) { bunker.health--; gameState.alienBullets.splice(i, 1); break; } } } } // Check win condition function checkWin() { const aliveAliens = gameState.aliens.filter(alien => alien.alive); if (aliveAliens.length === 0) { // Level up gameState.level++; gameState.alienMoveInterval = Math.max(10, gameState.alienMoveInterval - 2); createAliens(); } } // Check lose condition function checkLose() { // Check if aliens reached the bottom for (const alien of gameState.aliens) { if (alien.alive && alien.y + alien.height > gameState.player.y) { gameOver(); return; } } } // Game over function gameOver() { gameState.gameOver = true; finalScoreElement.textContent = `Score: ${gameState.score}`; gameOverScreen.classList.remove('hidden'); } // Update displays function updateDisplays() { scoreDisplay.textContent = `SCORE: ${gameState.score}`; livesDisplay.textContent = `LIVES: ${gameState.lives}`; } // Main game loop function gameLoop() { if (!gameState.gameStarted || gameState.gameOver) return; // Clear canvas ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); // Draw background stars drawStars(); // Update game state updatePlayer(); updateAliens(); updateBullets(); checkWin(); checkLose(); // Draw game objects drawBunkers(); drawAliens(); drawPlayer(); drawBullets(); // Continue game loop requestAnimationFrame(gameLoop); } // Draw starry background function drawStars() { ctx.fillStyle = '#ffffff'; 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); } } // Keyboard input handling const keys = {}; window.addEventListener('keydown', e => { keys[e.key] = true; // Start game on space press if (e.key === ' ' && !gameState.gameStarted) { initGame(); gameLoop(); } }); window.addEventListener('keyup', e => { keys[e.key] = false; }); // Button event listeners startButton.addEventListener('click', () => { initGame(); gameLoop(); }); restartButton.addEventListener('click', () => { initGame(); gameLoop(); }); // Initial draw of start screen drawStars(); </script> </body> </html> ```
TAKE IT TO CODE
This run produced no artifact — reuse the prompt with a different model or your own pi-agent run.