Claude Haiku 4.5
by AnthropicReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 1 min 3 s
- Cost
- $0.100
- Output tokens
- 19,934
- Spent thinking
- 13,774 tokens
- Speed
- 321.8 tok/s
- Console errors
- 0
- First token after
- 1.0 s
- First visible text after
- 52.8 s
- Prompt tokens
- 583
- Code size
- 21.2 KB
- Finish reason
- stop
- Served by
- Amazon Bedrock
- Model id
anthropic/claude-haiku-4.5- Reasoning sent
{"enabled":true}- Max output
- 64,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 · 596 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Breakout Game</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: #1a1a2e;
font-family: Arial, sans-serif;
overflow: hidden;
}
canvas {
display: block;
border: 3px solid #00d4ff;
background: #0f3460;
width: 100%;
height: 100%;
max-width: 1280px;
max-height: 800px;
aspect-ratio: 1280 / 800;
}
</style>
</head>
<body>
<canvas id="gameCanvas"></canvas>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
canvas.width = 1280;
canvas.height = 800;
// Constants
const PADDLE_WIDTH = 100;
const PADDLE_HEIGHT = 15;
const BALL_RADIUS = 6;
const BRICK_WIDTH = 75;
const BRICK_HEIGHT = 20;
const BRICK_PADDING = 5;
const BRICK_OFFSET_TOP = 60;
const BRICK_OFFSET_LEFT = 40;
const GAME_STATE = { DEMO: 'demo', PLAYING: 'playing', GAME_OVER: 'gameOver', VICTORY: 'victory' };
let gameState = GAME_STATE.DEMO;
let score = 0;
let lives = 3;
let demoMode = true;
// Paddle
const paddle = {
x: canvas.width / 2 - PADDLE_WIDTH / 2,
y: canvas.height - 30,
width: PADDLE_WIDTH,
height: PADDLE_HEIGHT,
baseWidth: PADDLE_WIDTH,
powerUpTimer: 0
};
// Game objects
let balls = [];
let bricks = [];
let particles = [];
let powerUps = [];
// Input
const keys = {};
const mouse = { x: -1, y: -1 };
window.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
mouse.x = (e.clientX - rect.left) * scaleX;
mouse.y = (e.clientY - rect.top) * scaleY;
});
window.addEventListener('keydown', (e) => {
keys[e.key] = true;
if (gameState === GAME_STATE.DEMO && e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') {
startGame();
}
});
window.addEventListener('keyup', (e) => {
keys[e.key] = false;
});
canvas.addEventListener('click', () => {
if (gameState === GAME_STATE.DEMO) {
startGame();
} else if (gameState === GAME_STATE.GAME_OVER || gameState === GAME_STATE.VICTORY) {
restartGame();
}
});
function initBricks() {
bricks = [];
const colors = ['#FF6B6B', '#4ECDC4', '#FFE66D', '#95E1D3', '#F38181', '#AA96DA'];
for (let row = 0; row < 6; row++) {
for (let col = 0; col < 12; col++) {
const isTough = (row + col) % 3 === 0;
const hasBonus = Math.random() < 0.25;
bricks.push({
x: BRICK_OFFSET_LEFT + col * (BRICK_WIDTH + BRICK_PADDING),
y: BRICK_OFFSET_TOP + row * (BRICK_HEIGHT + BRICK_PADDING),
width: BRICK_WIDTH,
height: BRICK_HEIGHT,
tough: isTough,
hits: 0,
hasBonus: hasBonus,
color: colors[row % colors.length],
alive: true
});
}
}
}
function initBalls() {
balls = [];
balls.push({
x: canvas.width / 2,
y: canvas.height - 80,
dx: -4,
dy: -5,
radius: BALL_RADIUS
});
}
function startGame() {
gameState = GAME_STATE.PLAYING;
demoMode = false;
score = 0;
lives = 3;
paddle.width = paddle.baseWidth;
paddle.powerUpTimer = 0;
balls = [];
particles = [];
powerUps = [];
initBricks();
initBalls();
}
function restartGame() {
gameState = GAME_STATE.DEMO;
demoMode = true;
score = 0;
lives = 3;
paddle.x = canvas.width / 2 - paddle.baseWidth / 2;
paddle.width = paddle.baseWidth;
paddle.powerUpTimer = 0;
balls = [];
particles = [];
powerUps = [];
initBricks();
initBalls();
}
function updatePaddle() {
if (!demoMode) {
if (keys['ArrowLeft']) {
paddle.x = Math.max(0, paddle.x - 8);
}
if (keys['ArrowRight']) {
paddle.x = Math.min(canvas.width - paddle.width, paddle.x + 8);
}
if (mouse.x >= 0) {
paddle.x = Math.max(0, Math.min(canvas.width - paddle.width, mouse.x - paddle.width / 2));
}
} else {
// AI
if (balls.length > 0) {
const ball = balls[0];
const paddleCenter = paddle.x + paddle.width / 2;
const distance = ball.x - paddleCenter;
if (Math.abs(distance) > 30) {
paddle.x += Math.sign(distance) * 5;
paddle.x = Math.max(0, Math.min(canvas.width - paddle.width, paddle.x));
}
}
}
if (paddle.powerUpTimer > 0) {
paddle.powerUpTimer--;
if (paddle.powerUpTimer === 0) {
paddle.width = paddle.baseWidth;
}
}
}
function updateBalls() {
for (let i = balls.length - 1; i >= 0; i--) {
const ball = balls[i];
ball.x += ball.dx;
ball.y += ball.dy;
// Wall collisions
if (ball.x - ball.radius < 0) {
ball.x = ball.radius;
ball.dx = Math.abs(ball.dx);
} else if (ball.x + ball.radius > canvas.width) {
ball.x = canvas.width - ball.radius;
ball.dx = -Math.abs(ball.dx);
}
if (ball.y - ball.radius < 0) {
ball.y = ball.radius;
ball.dy = Math.abs(ball.dy);
}
// Bottom
if (ball.y > canvas.height) {
balls.splice(i, 1);
lives--;
if (lives <= 0) {
gameState = GAME_STATE.GAME_OVER;
} else if (gameState === GAME_STATE.PLAYING) {
setTimeout(() => {
if (gameState === GAME_STATE.PLAYING) {
balls.push({
x: paddle.x + paddle.width / 2,
y: paddle.y - 20,
dx: (Math.random() - 0.5) * 6 - 2,
dy: -5,
radius: BALL_RADIUS
});
}
}, 500);
}
continue;
}
// Paddle collision
if (ball.y + ball.radius > paddle.y &&
ball.y - ball.radius < paddle.y + paddle.height &&
ball.x > paddle.x &&
ball.x < paddle.x + paddle.width &&
ball.dy > 0) {
const hitPos = (ball.x - paddle.x) / paddle.width;
const angle = (hitPos - 0.5) * Math.PI / 2.5;
const speed = Math.sqrt(ball.dx * ball.dx + ball.dy * ball.dy);
ball.dx = Math.sin(angle) * speed * 1.05;
ball.dy = -Math.abs(Math.cos(angle) * speed);
ball.y = paddle.y - ball.radius - 2;
}
// Brick collisions
checkBrickCollisions(ball);
}
}
function checkBrickCollisions(ball) {
for (let i = 0; i < bricks.length; i++) {
const brick = bricks[i];
if (!brick.alive) continue;
const closestX = Math.max(brick.x, Math.min(ball.x, brick.x + brick.width));
const closestY = Math.max(brick.y, Math.min(ball.y, brick.y + brick.height));
const dx = ball.x - closestX;
const dy = ball.y - closestY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < ball.radius) {
brick.hits++;
if (brick.tough && brick.hits === 1) {
createParticles(ball.x, ball.y, 3, brick.color);
} else {
brick.alive = false;
score += brick.tough ? 100 : 50;
createParticles(ball.x, ball.y, 8, brick.color);
if (brick.hasBonus) {
powerUps.push({
x: brick.x + brick.width / 2,
y: brick.y + brick.height / 2,
vx: (Math.random() - 0.5) * 2,
vy: 2,
type: Math.random() < 0.5 ? 'wide' : 'multiBall'
});
}
}
const angle = Math.atan2(dy, dx);
if (Math.abs(Math.cos(angle)) > Math.abs(Math.sin(angle))) {
ball.dx *= -1;
} else {
ball.dy *= -1;
}
const overlap = ball.radius - distance;
ball.x += Math.cos(angle) * overlap;
ball.y += Math.sin(angle) * overlap;
break;
}
}
}
function createParticles(x, y, count, color) {
for (let i = 0; i < count; i++) {
const angle = (Math.PI * 2 * i) / count + (Math.random() - 0.5) * 0.5;
const speed = 2 + Math.random() * 3;
particles.push({
x: x,
y: y,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
life: 30,
maxLife: 30,
color: color
});
}
}
function updateParticles() {
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.vx;
p.y += p.vy;
p.vy += 0.1;
p.life--;
if (p.life <= 0) {
particles.splice(i, 1);
}
}
}
function updatePowerUps() {
for (let i = powerUps.length - 1; i >= 0; i--) {
const pu = powerUps[i];
pu.x += pu.vx;
pu.y += pu.vy;
pu.vy += 0.1;
if (pu.x > paddle.x && pu.x < paddle.x + paddle.width &&
pu.y > paddle.y - 20 && pu.y < paddle.y + paddle.height + 20) {
if (pu.type === 'wide') {
paddle.width = paddle.baseWidth * 1.5;
paddle.powerUpTimer = 600;
} else if (pu.type === 'multiBall') {
for (let j = 0; j < 2; j++) {
const angle = Math.PI / 4 * (j - 0.5);
const speed = 5;
balls.push({
x: paddle.x + paddle.width / 2,
y: paddle.y - 30,
dx: Math.sin(angle) * speed,
dy: -Math.abs(Math.cos(angle) * speed),
radius: BALL_RADIUS
});
}
}
powerUps.splice(i, 1);
} else if (pu.y > canvas.height) {
powerUps.splice(i, 1);
}
}
}
function checkVictory() {
if (gameState !== GAME_STATE.PLAYING) return;
const aliveBricks = bricks.filter(b => b.alive).length;
if (aliveBricks === 0) {
gameState = GAME_STATE.VICTORY;
}
}
function draw() {
ctx.fillStyle = '#0f3460';
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (gameState === GAME_STATE.PLAYING || gameState === GAME_STATE.DEMO) {
drawBricks();
drawPaddle();
drawBalls();
drawParticles();
drawPowerUps();
drawUI();
if (gameState === GAME_STATE.DEMO) {
drawDemoOverlay();
}
} else if (gameState === GAME_STATE.GAME_OVER) {
drawBricks();
drawPaddle();
drawBalls();
drawGameOverScreen();
} else if (gameState === GAME_STATE.VICTORY) {
drawBricks();
drawPaddle();
drawBalls();
drawVictoryScreen();
}
}
function drawPaddle() {
ctx.fillStyle = '#00d4ff';
ctx.fillRect(paddle.x, paddle.y, paddle.width, paddle.height);
ctx.strokeStyle = '#00a8cc';
ctx.lineWidth = 2;
ctx.strokeRect(paddle.x, paddle.y, paddle.width, paddle.height);
}
function drawBalls() {
balls.forEach(ball => {
ctx.fillStyle = '#FFE66D';
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#FFCC33';
ctx.lineWidth = 1.5;
ctx.stroke();
});
}
function drawBricks() {
bricks.forEach(brick => {
if (brick.alive) {
ctx.fillStyle = brick.color;
ctx.fillRect(brick.x, brick.y, brick.width, brick.height);
if (brick.tough && brick.hits === 1) {
ctx.strokeStyle = 'rgba(0,0,0,0.6)';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(brick.x + 15, brick.y);
ctx.lineTo(brick.x + 20, brick.y + brick.height);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(brick.x + 55, brick.y);
ctx.lineTo(brick.x + 60, brick.y + brick.height);
ctx.stroke();
}
if (brick.hasBonus) {
ctx.fillStyle = 'rgba(255,255,255,0.7)';
ctx.font = 'bold 12px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(brick.tough ? '★' : '✦', brick.x + brick.width / 2, brick.y + brick.height / 2);
}
ctx.strokeStyle = 'rgba(0,0,0,0.3)';
ctx.lineWidth = 1;
ctx.strokeRect(brick.x, brick.y, brick.width, brick.height);
}
});
}
function drawParticles() {
particles.forEach(p => {
ctx.fillStyle = p.color;
ctx.globalAlpha = p.life / p.maxLife;
ctx.fillRect(p.x - 2, p.y - 2, 4, 4);
ctx.globalAlpha = 1;
});
}
function drawPowerUps() {
powerUps.forEach(pu => {
ctx.fillStyle = pu.type === 'wide' ? '#FFE66D' : '#FF6B6B';
ctx.beginPath();
ctx.arc(pu.x, pu.y, 8, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = 'rgba(0,0,0,0.5)';
ctx.lineWidth = 2;
ctx.stroke();
ctx.fillStyle = '#000';
ctx.font = 'bold 10px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(pu.type === 'wide' ? 'W' : 'M', pu.x, pu.y);
});
}
function drawUI() {
ctx.fillStyle = '#fff';
ctx.font = '20px Arial';
ctx.textAlign = 'left';
ctx.fillText(`Score: ${score}`, 20, 30);
ctx.fillText(`Lives: ${lives}`, canvas.width - 150, 30);
if (paddle.powerUpTimer > 0) {
const remaining = Math.ceil(paddle.powerUpTimer / 60);
ctx.fillStyle = '#FFE66D';
ctx.font = 'bold 16px Arial';
ctx.textAlign = 'center';
ctx.fillText(`Wide Paddle: ${remaining}s`, canvas.width / 2, 30);
}
}
function drawDemoOverlay() {
ctx.fillStyle = 'rgba(0,0,0,0.4)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#00d4ff';
ctx.font = 'bold 50px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('BREAKOUT', canvas.width / 2, canvas.height / 2 - 100);
ctx.fillStyle = '#fff';
ctx.font = '22px Arial';
ctx.fillText('Click or press any key to start', canvas.width / 2, canvas.height / 2 + 60);
}
function drawGameOverScreen() {
ctx.fillStyle = 'rgba(0,0,0,0.8)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#FF6B6B';
ctx.font = 'bold 60px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('GAME OVER', canvas.width / 2, canvas.height / 2 - 80);
ctx.fillStyle = '#fff';
ctx.font = '28px Arial';
ctx.fillText(`Final Score: ${score}`, canvas.width / 2, canvas.height / 2 + 20);
ctx.fillStyle = '#00d4ff';
ctx.font = '20px Arial';
ctx.fillText('Click or press any key to restart', canvas.width / 2, canvas.height / 2 + 100);
}
function drawVictoryScreen() {
ctx.fillStyle = 'rgba(0,0,0,0.8)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#95E1D3';
ctx.font = 'bold 60px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('VICTORY!', canvas.width / 2, canvas.height / 2 - 80);
ctx.fillStyle = '#fff';
ctx.font = '28px Arial';
ctx.fillText(`Final Score: ${score}`, canvas.width / 2, canvas.height / 2 + 20);
ctx.fillStyle = '#00d4ff';
ctx.font = '20px Arial';
ctx.fillText('Click or press any key to restart', canvas.width / 2, canvas.height / 2 + 100);
}
function gameLoop() {
if (gameState === GAME_STATE.PLAYING) {
updatePaddle();
updateBalls();
updateParticles();
updatePowerUps();
checkVictory();
} else if (gameState === GAME_STATE.DEMO) {
updatePaddle();
updateBalls();
updateParticles();
if (balls.length === 0) {
setTimeout(() => {
if (gameState === GAME_STATE.DEMO) {
initBalls();
}
}, 1000);
}
}
draw();
requestAnimationFrame(gameLoop);
}
restartGame();
gameLoop();
</script>
</body>
</html>Reasoning trace (13,774 tokens)
This is a comprehensive task to build a Breakout/brick breaker game. Let me break down the requirements:
1. Single HTML file with vanilla JavaScript and canvas
2. Paddle controlled by mouse and arrow keys
3. Bounce angle depends on where ball hits paddle
4. 1 level with:
- At least 6 rows of bricks
- Normal bricks (1 hit)
- Tough bricks (2 hits, visible crack after first hit)
- Some bricks with power-up icons
- Power-ups: wider paddle (10 sec) or multi-ball (3 balls)
5. Ball can't pass through bricks or paddle
6. Particle effects on brick breaks
7. 3 lives, score display
8. Game over and victory screens with restart
9. Self-playing demo mode initially
10. Must look good in 1280x800 and down to 375px
11. No console errors
Let me structure this:
1. Canvas setup and basic rendering
2. Game objects: ball, paddle, bricks, particles, power-ups
3. Collision detection (ball-paddle, ball-bricks, ball-walls, paddle-power-ups)
4. Game states: menu/demo, playing, game-over, victory
5. Auto-play demo mode initially
6. Responsive UI
Key features to implement:
- Paddle with mouse and keyboard control
- Smooth collision handling (ball shouldn't pass through)
- Variable bounce angle based on hit position
- Two types of bricks with visual difference
- Power-ups that drop and can be caught
- Life system
- Score system
- Particle effects
- Demo mode that plays automatically
Let me code this carefully:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Breakout Game</title>
<style>
body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: #1a1a2e;
font-family: Arial, sans-serif;
overflow: hidden;
}
canvas {
display: block;
border: 2px solid #00d4ff;
background: #0f3460;
max-width: 100vw;
max-height: 100vh;
}
```
I'm setting up the base page styling, centering the canvas with a dark theme and cyan border, and making sure it scales responsively.
Now moving into the game setup - defining canvas dimensions at 1280x800, constants for paddle, ball, and brick sizing, plus a state machine (demo, playing, gameOver, victory) to manage different phases of the game.
Setting up core objects: paddle with position/velocity/power-up tracking, plus arrays for balls, bricks, particles, and power-ups. Adding mouse tracking for paddle control, converting screen coordinates to canvas coordinates for
keydown events trigger game start from demo mode, while canvas clicks handle transitions between demo/game-over/victory states and restarting. Starting to build initBricks() with a color palette array for the brick rows.
for (let col = 0; col < 12; col++) {
const isTough = (row + col) % 3 === 0;
const hasBonus = Math.random() < 0.25;
bricks.push({
x: BRICK_OFFSET_LEFT + col * (BRICK_WIDTH + BRICK_PADDING),
y: BRICK_OFFSET_TOP + row * (BRICK_HEIGHT + BRICK_PADDING),
width: BRICK_WIDTH,
height: BRICK_HEIGHT,
tough: isTough,
hits: 0,
hasBonus: hasBonus,
color: colors[row % colors.length],
alive: true
});
}
}
}
Setting up initBalls() next with a starting position centered horizontally and near the bottom of the canvas, creating the first ball with initial velocity.
I'll write a createBall helper returning an object with position, velocity, and radius. Then startGame() resets score/lives, switches to PLAYING state, and reinitializes bricks/balls. restartGame() goes back to DEMO mode, clears all arrays (balls, bricks, particles, powerUps), resets paddle width and power-up timer, then reinitializes. Moving into updatePaddle() which will check demoMode to decide control logic.
For actual paddle movement, keyboard arrows shift x by 8px with clamping to canvas bounds. Mouse control centers paddle on cursor x. In demo mode, AI tracks the first ball's x position, moving paddle left/right when ball drifts more than 20px from paddle center.
Power-up timer counts down each frame, resetting paddle width to base once it hits zero.
For ball updates, I iterate backwards through the balls array, moving each by its dx/dy, then checking wall collisions to reverse dx and clamp position when hitting left/right edges.
Top wall collision reverses dy similarly, clamping y to radius. If a ball falls past the bottom, I remove it from the array, decrement lives, and check for game over, or else schedule a new ball to spawn above the paddle after a short delay with randomized horizontal velocity.
For paddle collisions, I compute hit position along the paddle's width to derive a bounce angle between -60 and 60 degrees, then recompute dx/dy preserving speed. For bricks, I loop through and detect collisions, incrementing hit count and checking if tough bricks need multiple hits before breaking.
When a tough brick takes its first hit, it just cracks and spawns a few particles. Otherwise the brick is destroyed—marking it dead, adding score (100 for tough, 50 for regular), and possibly spawning a power-up (wide paddle or multi-ball) if the brick had a bonus flag.
After destruction, more particles burst with a bigger count, and the ball bounces by flipping dy. Then I move to collision helpers: checkPaddleCollision verifies the ball's position overlaps the paddle and is moving downward, while checkBrickCollision uses distance calculations from the ball center to the brick's edges to determine overlap with the ball's radius.
For the brick collision side, I calculate overlaps on all four sides—left, right, top, bottom—and whichever is smallest determines whether dx or dy should flip, giving a more realistic bounce direction. Returns true if a collision happened.
Now I set up createParticles, looping through a count and spacing them evenly around a circle using angle calculations, adding some randomness for a natural burst effect.
updateParticles handles movement, applying velocity, gravity (vy += 0.1), decrementing life, and removing particles once their life hits zero.
Starting on updatePowerUps now, moving each one downward with its dy value.
Checking paddle collision for power-ups, verifying pu.x and pu.y fall within paddle bounds. For 'wide' type, paddle width expands to 1.5x base with a 10-second timer. For 'multiBall', spawning two additional balls at angled offsets from the paddle center with calculated velocities.
After handling collision effects, splicing power-ups array to remove collected items, and also removing any that fall past canvas height uncollected.
For victory, checking if all bricks' alive count hits zero while in PLAYING state, triggering VICTORY state transition.
Starting rendering functions - drawPaddle fills the paddle rectangle with cyan color and adds a darker stroke outline. drawBalls iterates through each ball, setting fill style for rendering.
E66D';
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#FFCC33';
ctx.lineWidth = 1;
ctx.stroke();
Each ball gets filled with coral color, drawn as a circle, and outlined with a yellow stroke. drawBricks loops through all bricks, rendering only alive ones with their assigned color, and adding crack lines for tough bricks that have taken one hit already.
I also want a second crack line for depth, then a star or diamond symbol overlay for bricks carrying bonuses, distinguishing tough bricks with a star and regular ones with a diamond. Finally each brick gets a subtle dark border for definition.
Particles fade based on remaining life, power-ups render as colored circles labeled W or M, and UI shows score, lives, and a wide-paddle countdown timer. Game over, victory, and demo screens use semi-transparent overlays with centered text prompting restart or start. The main loop clears the canvas each frame, then branches by state—updating and drawing paddle, balls, bricks, particles, and power-ups during play or demo, and just rendering static elements for end screens—before looping via requestAnimationFrame. I initialize with restartGame() and kick off gameLoop().
Reviewing requirements, I confirm paddle control, bounce angles, six brick rows with tough/power-up variants, particle effects, lives/score, and win/lose/demo states are covered. I want to double check collision robustness so balls don't tunnel through bricks, verify demo mode self-plays convincingly, and ensure responsiveness down to 375px while keeping everything visible on load.
I restructure using a fixed internal resolution (1280x800) with CSS scaling via aspect-ratio, making the canvas responsive while preserving coordinate math. I set up mouse input with scaling to match canvas coordinates, keyboard listeners that also trigger game start from demo mode, and a click handler to transition out of demo or end-game states.