Claude Haiku 4.5
by AnthropicReasoningClean
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>