Models 44

Claude Haiku 4.5

by AnthropicReasoningClean
Open in new window

Running live in a sandboxed frame. Click inside it before using the keyboard.

How it went

Total time
23.1 s
Cost
$0.035
Output tokens
6,798
Spent thinking
0 tokens
Speed
314.7 tok/s
Console errors
0
First token after
1.5 s
First visible text after
1.5 s
Prompt tokens
553
Code size
24.0 KB
Finish reason
stop
Served by
Amazon Bedrock
Model id
anthropic/claude-haiku-4.5
Reasoning sent
{"effort":"none"}
Max output
32,000 tokens
Generated
Nov 10, 2026

What it was asked

The prompt
Build a polished Breakout (brick breaker) game in a single HTML file using vanilla JavaScript and a <canvas>. Requirements: - A paddle controlled with the mouse and with the left/right arrow keys. - The bounce angle off the paddle depends on where the ball hits it (center = straight up, edges = sharp angle). - One level with at least 6 rows of bricks, mixing normal bricks and tougher bricks that need 2 hits and visibly crack after the first one. - Some bricks visibly carry a power-up icon; when broken they drop it, and catching it with the paddle gives either a wider paddle (10 seconds) or multi-ball (3 balls). - The ball must never pass through bricks or the paddle. - Particles when a brick breaks. - 3 lives, score displayed. Game over and victory screens with a restart option.
System prompt, sent with every prompt
You are generating a self-contained web demo. It will be rendered automatically in a sandboxed iframe and compared side by side with the output of other AI models given the exact same task.

Output rules:
- Reply with exactly ONE fenced code block containing the complete file: ```html (or ```svg when the task asks for an SVG). No text before or after the code block.
- Everything must be inline: CSS in <style>, JavaScript in <script>. No external files, images, fonts or network requests, except the CDN URLs explicitly allowed by the task.
- Do not use localStorage, sessionStorage, cookies, IndexedDB, alert(), confirm(), prompt() or window.open(): they are blocked in the sandbox and will throw.
- The page must look right in a 1280×800 viewport and adapt gracefully down to 375px wide. No horizontal scrollbar.
- Something meaningful must be visible on first paint: a screenshot is taken automatically about 3 seconds after load, without any interaction.
- Everything that makes the demo interesting must be visible within the first seconds: nothing hidden behind a high score, a later level or a long play session.
- Games must start in a self-playing demo mode (a simple AI plays) behind a "Click or press a key to start" message, so the game can be judged in action without playing.
- The iframe only receives keyboard events after the user clicks it: if the demo uses the keyboard, display a clear "Click or press a key to start" message.
- The code must run with zero errors in the console of the latest Chrome.

Code run-1.html · 689 lines

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Breakout</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            font-family: 'Arial', sans-serif;
            padding: 10px;
        }

        canvas {
            display: block;
            background: #1a1a2e;
            border: 3px solid #16c784;
            box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
            max-width: 100%;
            height: auto;
        }

        .container {
            width: 100%;
            max-width: 800px;
        }
    </style>
</head>
<body>
    <div class="container">
        <canvas id="gameCanvas" width="800" height="600"></canvas>
    </div>

    <script>
        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');

        // Game states
        const GAME_STATE = {
            DEMO: 'demo',
            PLAYING: 'playing',
            GAME_OVER: 'gameOver',
            VICTORY: 'victory'
        };

        // Game objects
        class Paddle {
            constructor() {
                this.baseWidth = 80;
                this.width = this.baseWidth;
                this.height = 12;
                this.x = canvas.width / 2 - this.width / 2;
                this.y = canvas.height - 30;
                this.speed = 6;
                this.widePowerUpTime = 0;
            }

            update(mouseX, keyStates) {
                // Mouse control
                if (mouseX !== null) {
                    this.x = mouseX - this.width / 2;
                }

                // Keyboard control
                if (keyStates['ArrowLeft']) {
                    this.x -= this.speed;
                }
                if (keyStates['ArrowRight']) {
                    this.x += this.speed;
                }

                // Clamp paddle to canvas
                this.x = Math.max(0, Math.min(this.x, canvas.width - this.width));

                // Update power-up timer
                if (this.widePowerUpTime > 0) {
                    this.widePowerUpTime--;
                    if (this.widePowerUpTime === 0) {
                        this.width = this.baseWidth;
                    }
                }
            }

            activateWidePowerUp() {
                this.width = this.baseWidth * 1.5;
                this.widePowerUpTime = 600; // 10 seconds at 60 FPS
            }

            draw() {
                // Paddle body
                ctx.fillStyle = '#16c784';
                ctx.fillRect(this.x, this.y, this.width, this.height);

                // Glow effect
                ctx.strokeStyle = '#0ff';
                ctx.lineWidth = 2;
                ctx.globalAlpha = 0.5;
                ctx.strokeRect(this.x - 2, this.y - 2, this.width + 4, this.height + 4);
                ctx.globalAlpha = 1;

                // Power-up indicator
                if (this.widePowerUpTime > 0) {
                    ctx.fillStyle = 'rgba(255, 200, 0, 0.3)';
                    ctx.fillRect(this.x - 2, this.y - 4, this.width + 4, this.height + 4);
                }
            }

            getBounds() {
                return {
                    x: this.x,
                    y: this.y,
                    width: this.width,
                    height: this.height
                };
            }
        }

        class Ball {
            constructor(x, y) {
                this.x = x;
                this.y = y;
                this.radius = 5;
                this.vx = 0;
                this.vy = 0;
                this.speed = 5;
                this.isLaunched = false;
            }

            launch(paddle) {
                if (!this.isLaunched) {
                    this.vx = (Math.random() - 0.5) * this.speed;
                    this.vy = -this.speed;
                    this.isLaunched = true;
                }
            }

            update(paddle, bricks, game) {
                if (!this.isLaunched) {
                    this.x = paddle.x + paddle.width / 2;
                    this.y = paddle.y - this.radius;
                    return true;
                }

                this.x += this.vx;
                this.y += this.vy;

                // Wall collision
                if (this.x - this.radius < 0 || this.x + this.radius > canvas.width) {
                    this.vx = -this.vx;
                    this.x = Math.max(this.radius, Math.min(canvas.width - this.radius, this.x));
                }
                if (this.y - this.radius < 0) {
                    this.vy = -this.vy;
                    this.y = this.radius;
                }

                // Paddle collision
                const paddleBounds = paddle.getBounds();
                if (this.checkPaddleCollision(paddle)) {
                    const hitPos = (this.x - paddleBounds.x) / paddleBounds.width;
                    const bounceAngle = (hitPos - 0.5) * Math.PI * 0.75;
                    const speed = Math.sqrt(this.vx ** 2 + this.vy ** 2);
                    this.vx = Math.sin(bounceAngle) * speed * 1.02;
                    this.vy = -Math.cos(bounceAngle) * speed * 1.02;
                    this.y = paddleBounds.y - this.radius;
                }

                // Brick collision
                for (let i = 0; i < bricks.length; i++) {
                    if (bricks[i].active && this.checkBrickCollision(bricks[i])) {
                        bricks[i].hit();
                        if (bricks[i].active === false) {
                            game.score += bricks[i].health === 2 ? 100 : 50;
                            if (bricks[i].hasPowerUp && Math.random() < 0.7) {
                                game.powerUps.push(new PowerUp(bricks[i].x + bricks[i].width / 2, bricks[i].y + bricks[i].height / 2));
                            }
                            // Particles
                            for (let j = 0; j < 8; j++) {
                                const angle = (j / 8) * Math.PI * 2;
                                game.particles.push(new Particle(
                                    bricks[i].x + bricks[i].width / 2,
                                    bricks[i].y + bricks[i].height / 2,
                                    Math.cos(angle) * 4,
                                    Math.sin(angle) * 4
                                ));
                            }
                        }
                        break;
                    }
                }

                // Bottom (lose life)
                if (this.y > canvas.height) {
                    return false;
                }

                return true;
            }

            checkPaddleCollision(paddle) {
                const bounds = paddle.getBounds();
                const closestX = Math.max(bounds.x, Math.min(this.x, bounds.x + bounds.width));
                const closestY = Math.max(bounds.y, Math.min(this.y, bounds.y + bounds.height));

                const dx = this.x - closestX;
                const dy = this.y - closestY;
                return dx ** 2 + dy ** 2 < this.radius ** 2;
            }

            checkBrickCollision(brick) {
                const closestX = Math.max(brick.x, Math.min(this.x, brick.x + brick.width));
                const closestY = Math.max(brick.y, Math.min(this.y, brick.y + brick.height));

                const dx = this.x - closestX;
                const dy = this.y - closestY;

                if (dx ** 2 + dy ** 2 < this.radius ** 2) {
                    // Determine collision side
                    const overlapLeft = (this.x - brick.x);
                    const overlapRight = (brick.x + brick.width - this.x);
                    const overlapTop = (this.y - brick.y);
                    const overlapBottom = (brick.y + brick.height - this.y);

                    const minOverlap = Math.min(overlapLeft, overlapRight, overlapTop, overlapBottom);

                    if (minOverlap === overlapLeft || minOverlap === overlapRight) {
                        this.vx = -this.vx;
                    } else {
                        this.vy = -this.vy;
                    }

                    return true;
                }

                return false;
            }

            draw() {
                ctx.fillStyle = '#00ff88';
                ctx.beginPath();
                ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
                ctx.fill();

                // Glow
                ctx.strokeStyle = 'rgba(0, 255, 136, 0.5)';
                ctx.lineWidth = 2;
                ctx.stroke();
            }
        }

        class Brick {
            constructor(x, y, health = 1) {
                this.x = x;
                this.y = y;
                this.width = 70;
                this.height = 15;
                this.maxHealth = health;
                this.health = health;
                this.active = true;
                this.hasPowerUp = false;
            }

            hit() {
                this.health--;
                if (this.health <= 0) {
                    this.active = false;
                }
            }

            draw() {
                if (!this.active) return;

                if (this.health === this.maxHealth) {
                    ctx.fillStyle = this.maxHealth === 2 ? '#ff6b6b' : '#4ecdc4';
                } else {
                    ctx.fillStyle = this.maxHealth === 2 ? '#ff9999' : '#7ee8d9';
                }

                ctx.fillRect(this.x, this.y, this.width, this.height);

                // Border
                ctx.strokeStyle = '#fff';
                ctx.lineWidth = 1;
                ctx.strokeRect(this.x, this.y, this.width, this.height);

                // Crack effect for damaged bricks
                if (this.maxHealth === 2 && this.health < this.maxHealth) {
                    ctx.strokeStyle = '#333';
                    ctx.lineWidth = 2;
                    ctx.beginPath();
                    ctx.moveTo(this.x + this.width * 0.2, this.y);
                    ctx.lineTo(this.x + this.width * 0.3, this.y + this.height);
                    ctx.stroke();
                }

                // Power-up icon
                if (this.hasPowerUp) {
                    ctx.fillStyle = '#ffff00';
                    ctx.beginPath();
                    ctx.arc(this.x + this.width / 2, this.y + this.height / 2, 4, 0, Math.PI * 2);
                    ctx.fill();
                    ctx.strokeStyle = '#fff';
                    ctx.lineWidth = 1;
                    ctx.stroke();
                }
            }
        }

        class PowerUp {
            constructor(x, y) {
                this.x = x;
                this.y = y;
                this.radius = 6;
                this.vy = 2;
                this.type = Math.random() < 0.5 ? 'wide' : 'multiBall';
                this.active = true;
            }

            update() {
                this.y += this.vy;
                if (this.y > canvas.height) {
                    this.active = false;
                }
            }

            draw() {
                ctx.fillStyle = this.type === 'wide' ? '#ffff00' : '#ff00ff';
                ctx.beginPath();
                ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
                ctx.fill();

                ctx.fillStyle = '#000';
                ctx.font = 'bold 8px Arial';
                ctx.textAlign = 'center';
                ctx.textBaseline = 'middle';
                ctx.fillText(this.type === 'wide' ? 'W' : 'M', this.x, this.y);
            }
        }

        class Particle {
            constructor(x, y, vx, vy) {
                this.x = x;
                this.y = y;
                this.vx = vx;
                this.vy = vy;
                this.life = 30;
                this.maxLife = 30;
            }

            update() {
                this.x += this.vx;
                this.y += this.vy;
                this.vy += 0.2; // gravity
                this.life--;
            }

            draw() {
                ctx.fillStyle = `rgba(0, 255, 136, ${this.life / this.maxLife})`;
                ctx.beginPath();
                ctx.arc(this.x, this.y, 2, 0, Math.PI * 2);
                ctx.fill();
            }
        }

        class Game {
            constructor() {
                this.paddle = new Paddle();
                this.balls = [new Ball(canvas.width / 2, canvas.height - 50)];
                this.bricks = [];
                this.powerUps = [];
                this.particles = [];
                this.lives = 3;
                this.score = 0;
                this.gameState = GAME_STATE.DEMO;
                this.demoFrames = 0;
                this.mouseX = null;
                this.keyStates = {};
                this.activeBallCount = 1;

                this.createLevel();
                this.setupControls();
            }

            createLevel() {
                this.bricks = [];
                const cols = 10;
                const rows = 6;
                const brickWidth = 70;
                const brickHeight = 15;
                const startX = (canvas.width - cols * brickWidth) / 2;
                const startY = 50;
                const spacing = 2;

                for (let i = 0; i < rows; i++) {
                    for (let j = 0; j < cols; j++) {
                        const health = i < 2 ? 2 : 1;
                        const brick = new Brick(
                            startX + j * (brickWidth + spacing),
                            startY + i * (brickHeight + spacing),
                            health
                        );
                        if (Math.random() < 0.3) {
                            brick.hasPowerUp = true;
                        }
                        this.bricks.push(brick);
                    }
                }
            }

            setupControls() {
                document.addEventListener('mousemove', (e) => {
                    const rect = canvas.getBoundingClientRect();
                    this.mouseX = e.clientX - rect.left;
                });

                document.addEventListener('keydown', (e) => {
                    this.keyStates[e.key] = true;
                    if ((e.key === 'ArrowLeft' || e.key === 'ArrowRight') && this.gameState === GAME_STATE.DEMO) {
                        this.startGame();
                    }
                });

                document.addEventListener('keyup', (e) => {
                    this.keyStates[e.key] = false;
                });

                canvas.addEventListener('click', () => {
                    if (this.gameState === GAME_STATE.DEMO) {
                        this.startGame();
                    } else if (this.gameState === GAME_STATE.GAME_OVER || this.gameState === GAME_STATE.VICTORY) {
                        this.reset();
                    }
                });
            }

            startGame() {
                if (this.gameState === GAME_STATE.DEMO) {
                    this.gameState = GAME_STATE.PLAYING;
                    this.balls.forEach(ball => ball.launch(this.paddle));
                }
            }

            update() {
                if (this.gameState === GAME_STATE.DEMO) {
                    this.updateDemo();
                } else if (this.gameState === GAME_STATE.PLAYING) {
                    this.updateGame();
                }
            }

            updateDemo() {
                this.demoFrames++;
                
                // Simple AI for demo mode
                const ball = this.balls[0];
                if (!ball.isLaunched && this.demoFrames > 60) {
                    ball.launch(this.paddle);
                }

                if (ball.isLaunched) {
                    // Move paddle towards ball
                    const paddleCenter = this.paddle.x + this.paddle.width / 2;
                    if (paddleCenter < ball.x - 5) {
                        this.paddle.x += this.paddle.speed;
                    } else if (paddleCenter > ball.x + 5) {
                        this.paddle.x -= this.paddle.speed;
                    }
                }

                this.paddle.update(null, {});

                const alive = this.balls[0].update(this.paddle, this.bricks, this);
                if (!alive) {
                    this.balls[0] = new Ball(canvas.width / 2, canvas.height - 50);
                    this.balls[0].launch(this.paddle);
                }

                for (let brick of this.bricks) {
                    if (!brick.active) {
                        const allDestroyed = this.bricks.every(b => !b.active);
                        if (allDestroyed) {
                            this.gameState = GAME_STATE.VICTORY;
                        }
                    }
                }

                this.particles = this.particles.filter(p => {
                    p.update();
                    return p.life > 0;
                });
            }

            updateGame() {
                this.paddle.update(this.mouseX, this.keyStates);

                // Update balls
                for (let i = this.balls.length - 1; i >= 0; i--) {
                    const alive = this.balls[i].update(this.paddle, this.bricks, this);
                    if (!alive) {
                        this.balls.splice(i, 1);
                        this.activeBallCount--;
                        if (this.balls.length === 0) {
                            this.lives--;
                            if (this.lives <= 0) {
                                this.gameState = GAME_STATE.GAME_OVER;
                            } else {
                                this.balls.push(new Ball(canvas.width / 2, canvas.height - 50));
                                this.activeBallCount = 1;
                            }
                        }
                    }
                }

                // Update power-ups
                for (let i = this.powerUps.length - 1; i >= 0; i--) {
                    this.powerUps[i].update();

                    // Check paddle collision
                    const dx = this.powerUps[i].x - (this.paddle.x + this.paddle.width / 2);
                    const dy = this.powerUps[i].y - this.paddle.y;
                    if (dx ** 2 + dy ** 2 < (this.powerUps[i].radius + 40) ** 2) {
                        if (this.powerUps[i].type === 'wide') {
                            this.paddle.activateWidePowerUp();
                        } else if (this.powerUps[i].type === 'multiBall') {
                            // Create 2 additional balls
                            for (let j = 0; j < 2; j++) {
                                const newBall = new Ball(this.balls[0].x, this.balls[0].y);
                                newBall.vx = this.balls[0].vx + (Math.random() - 0.5) * 2;
                                newBall.vy = this.balls[0].vy;
                                newBall.isLaunched = true;
                                this.balls.push(newBall);
                                this.activeBallCount++;
                            }
                        }
                        this.powerUps.splice(i, 1);
                    } else if (!this.powerUps[i].active) {
                        this.powerUps.splice(i, 1);
                    }
                }

                // Particles
                this.particles = this.particles.filter(p => {
                    p.update();
                    return p.life > 0;
                });

                // Check victory
                if (this.bricks.every(b => !b.active)) {
                    this.gameState = GAME_STATE.VICTORY;
                }
            }

            draw() {
                // Clear canvas
                ctx.fillStyle = '#1a1a2e';
                ctx.fillRect(0, 0, canvas.width, canvas.height);

                // Draw bricks
                for (let brick of this.bricks) {
                    brick.draw();
                }

                // Draw paddle
                this.paddle.draw();

                // Draw balls
                for (let ball of this.balls) {
                    ball.draw();
                }

                // Draw power-ups
                for (let powerUp of this.powerUps) {
                    powerUp.draw();
                }

                // Draw particles
                for (let particle of this.particles) {
                    particle.draw();
                }

                // Draw UI
                this.drawUI();

                // Draw game state overlays
                if (this.gameState === GAME_STATE.DEMO) {
                    this.drawDemoScreen();
                } else if (this.gameState === GAME_STATE.GAME_OVER) {
                    this.drawGameOver();
                } else if (this.gameState === GAME_STATE.VICTORY) {
                    this.drawVictory();
                }
            }

            drawUI() {
                ctx.fillStyle = '#fff';
                ctx.font = 'bold 16px Arial';
                ctx.textAlign = 'left';
                ctx.textBaseline = 'top';
                ctx.fillText(`Score: ${this.score}`, 20, 20);
                ctx.fillText(`Lives: ${this.lives}`, 20, 50);
                ctx.fillText(`Balls: ${this.balls.length}`, 20, 80);
            }

            drawDemoScreen() {
                ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
                ctx.fillRect(0, 0, canvas.width, canvas.height);

                ctx.fillStyle = '#00ff88';
                ctx.font = 'bold 48px Arial';
                ctx.textAlign = 'center';
                ctx.textBaseline = 'middle';
                ctx.fillText('BREAKOUT', canvas.width / 2, canvas.height / 2 - 80);

                ctx.font = '24px Arial';
                ctx.fillStyle = '#fff';
                ctx.fillText('Click or press a key to start', canvas.width / 2, canvas.height / 2 + 40);
            }

            drawGameOver() {
                ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
                ctx.fillRect(0, 0, canvas.width, canvas.height);

                ctx.fillStyle = '#ff6b6b';
                ctx.font = 'bold 48px Arial';
                ctx.textAlign = 'center';
                ctx.textBaseline = 'middle';
                ctx.fillText('GAME OVER', canvas.width / 2, canvas.height / 2 - 60);

                ctx.fillStyle = '#fff';
                ctx.font = '24px Arial';
                ctx.fillText(`Final Score: ${this.score}`, canvas.width / 2, canvas.height / 2);
                ctx.font = '18px Arial';
                ctx.fillText('Click to restart', canvas.width / 2, canvas.height / 2 + 60);
            }

            drawVictory() {
                ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
                ctx.fillRect(0, 0, canvas.width, canvas.height);

                ctx.fillStyle = '#16c784';
                ctx.font = 'bold 48px Arial';
                ctx.textAlign = 'center';
                ctx.textBaseline = 'middle';
                ctx.fillText('VICTORY!', canvas.width / 2, canvas.height / 2 - 60);

                ctx.fillStyle = '#fff';
                ctx.font = '24px Arial';
                ctx.fillText(`Final Score: ${this.score}`, canvas.width / 2, canvas.height / 2);
                ctx.font = '18px Arial';
                ctx.fillText('Click to restart', canvas.width / 2, canvas.height / 2 + 60);
            }

            reset() {
                this.paddle = new Paddle();
                this.balls = [new Ball(canvas.width / 2, canvas.height - 50)];
                this.powerUps = [];
                this.particles = [];
                this.lives = 3;
                this.score = 0;
                this.gameState = GAME_STATE.DEMO;
                this.demoFrames = 0;
                this.activeBallCount = 1;
                this.createLevel();
            }
        }

        // Game loop
        const game = new Game();

        function gameLoop() {
            game.update();
            game.draw();
            requestAnimationFrame(gameLoop);
        }

        gameLoop();
    </script>
</body>
</html>