GPT-5 Nano
by OpenAIReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 3 min 32 s
- Cost
- $0.0084
- Output tokens
- 20,876
- Spent thinking
- 13,696 tokens
- Speed
- 103.4 tok/s
- Console errors
- 0
- First token after
- 10.6 s
- First visible text after
- 2 min 29 s
- Prompt tokens
- 508
- Code size
- 23.7 KB
- Finish reason
- stop
- Served by
- OpenAI
- Model id
openai/gpt-5-nano- Reasoning sent
{"effort":"high"}- Max output
- 128,000 tokens
- Generated
- Nov 12, 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 · 817 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>VanillaJS Breakout - Self-Playing Demo</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
:root {
--bg: #0a0b18;
--panel: rgba(0,0,0,.6);
--white: #eaeaea;
--green: #4cd964;
--pink: #e06bd8;
--orange: #f4a261;
--blue: #4da3ff;
}
html, body {
height: 100%;
margin: 0;
background: #111;
color: #eee;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
overflow: hidden;
}
#stage {
height: 100vh;
width: 100vw;
display: grid;
place-items: center;
}
/* The game stage has a fixed logical size, then scales to fit nicely on all viewports */
.board {
width: min(96vw, 1280px);
aspect-ratio: 16 / 9;
position: relative;
display: grid;
place-items: center;
}
canvas {
width: 100%;
height: 100%;
display: block;
background: #0b0e1a;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0,0,0,.5);
}
/* HUDs and overlays */
.hud {
position: absolute;
top: 8px;
left: 12px;
color: #fff;
font-family: ui-monospace,SFMono-Regular,Monospace;
font-size: 14px;
text-shadow: 0 1px 2px rgba(0,0,0,.6);
user-select: none;
}
.overlay {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
background: rgba(0,0,0,.4);
color: #fff;
padding: 20px;
border-radius: 12px;
backdrop-filter: blur(2px);
}
.overlay .card {
background: var(--panel);
padding: 28px 34px;
border-radius: 12px;
max-width: 720px;
box-shadow: 0 20px 60px rgba(0,0,0,.45);
}
h1 { margin: 0 0 8px; font-size: 28px; }
p { margin: 6px 0 14px; opacity: .95; }
.startBtn, .restartBtn {
padding: 10px 18px;
font-size: 14px;
border-radius: 8px;
border: 0;
cursor: pointer;
background: #1e88e5;
color: white;
font-weight: bold;
}
.restartBtn { background: #28a745; }
.overlay.win { background: rgba(0, 128, 0, .5); }
.overlay.lose { background: rgba(128, 0, 0, .5); }
.overlay .hint { font-size: 12px; opacity: .9; }
/* Responsive helpers */
@media (max-width: 700px) {
.overlay .card { padding: 22px; }
}
/* Tiny icons inside bricks indicating power-ups */
.PUIcon {
position: absolute;
width: 14px;
height: 14px;
border-radius: 3px;
font-size: 11px;
line-height: 14px;
text-align: center;
color: white;
pointer-events: none;
}
/* Ensure no horizontal scroll when scaling */
body { padding: 0; margin: 0; }
</style>
</head>
<body>
<div id="stage">
<div class="board" id="board">
<canvas id="gameCanvas" width="960" height="540" aria-label="Breakout canvas"></canvas>
<div class="hud" id="hud">Score: 0 | Lives: 3</div>
<!-- Overlay: Start Demo (auto-playing) -->
<div class="overlay" id="overlayStart" aria-label="Click to start">
<div class="card">
<h1>Breakout Demo</h1>
<p>Self-playing demo starts now. After clicking, you can take control with the mouse or arrow keys. Your task is to beat the level before you run out of lives.</p>
<p class="hint">Click anywhere or press a key to take the reins.</p>
<button class="startBtn" id="startBtn">Start Demo</button>
</div>
</div>
<!-- Victory / Game Over overlays -->
<div class="overlay win" id="overlayWin" style="display:none" aria-label="You win">
<div class="card">
<h1>You Win!</h1>
<p>Your score: <span id="finalScoreWin">0</span></p>
<button class="restartBtn" id="restartBtnWin">Restart</button>
</div>
</div>
<div class="overlay lose" id="overlayLose" style="display:none" aria-label="Game Over">
<div class="card">
<h1>Game Over</h1>
<p>Your score: <span id="finalScoreLose">0</span></p>
<button class="restartBtn" id="restartBtnLose">Restart</button>
</div>
</div>
<!-- Optional tiny power-up indicator layer (not separate DOM, drawn in canvas) -->
</div>
</div>
<script>
(function(){
// Game constants (logical resolution)
const GAME_W = 960;
const GAME_H = 540;
// Canvas and rendering
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
// For crisp rendering on HiDPI, scale the canvas drawing surface to devicePixelRatio
function resizeCanvas() {
const dpr = Math.min(window.devicePixelRatio || 1, 2);
// CSS size is the displayed size; internal buffer is set to CSS size * dpr for crispness
const cssW = canvas.clientWidth;
const cssH = canvas.clientHeight;
canvas.width = Math.max(1, Math.floor(cssW * dpr));
canvas.height = Math.max(1, Math.floor(cssH * dpr));
ctx.setTransform(dpr, 0, 0, dpr, 0, 0); // draw in CSS px units
}
// Stage sizing: keep visual content pleasant on various sizes
window.addEventListener('resize', resizeCanvas);
window.addEventListener('orientationchange', resizeCanvas);
// Initial resize once DOM ready
window.addEventListener('load', resizeCanvas);
// Game state
let score = 0;
let lives = 3;
let bricksRemaining = 0;
let started = false; // User has clicked to start or not
let demoMode = true; // Auto-pilot paddle moves while not started
let gameOver = false;
let gameWin = false;
// Paddle
const PADDLE = {
w: 110,
h: 14,
x: (GAME_W - 110) / 2,
y: GAME_H - 40,
speed: 720, // px/s (for keyboard control)
boosting: false
};
// Ball(s)
const BALL_SPEED = 6.8; // base speed
function createBall(x, y, vx, vy) {
return { x, y, vx, vy, r: 6.0, color: '#fff', alive: true };
}
let balls = [];
// Bricks
const BRICK_ROWS = 6;
let bricks = [];
// Power-ups
// powerUp types: 0 = widen paddle, 1 = multi-ball
function createPowerUp(x, y, type) {
return { x, y, vy: 90, size: 18, type, collected: false, color: type === 0 ? '#9b59b6' : '#f39c12' };
}
let activePowerUps = []; // falling items
// Particles on brick break
let particles = [];
// Input
const input = {
mouseX: GAME_W / 2,
left: false,
right: false
};
// Overlay elements
const overlayStart = document.getElementById('overlayStart');
const overlayWin = document.getElementById('overlayWin');
const overlayLose = document.getElementById('overlayLose');
const hud = document.getElementById('hud');
const finalScoreWin = document.getElementById('finalScoreWin');
const finalScoreLose = document.getElementById('finalScoreLose');
const startBtn = document.getElementById('startBtn');
const restartBtnWin = document.getElementById('restartBtnWin');
const restartBtnLose = document.getElementById('restartBtnLose');
// Init game
function initGame() {
// Reset
score = 0;
lives = 3;
started = false;
demoMode = true;
gameOver = false;
gameWin = false;
balls = [];
activePowerUps = [];
particles = [];
// Paddle initial
PADDLE.w = 110;
PADDLE.x = (GAME_W - PADDLE.w) / 2;
PADDLE.y = GAME_H - 40;
// Create initial ball
const b = createBall(GAME_W / 2, PADDLE.y - 18, Math.sin(0.0) * BALL_SPEED, -BALL_SPEED);
balls.push(b);
// Build bricks
bricks = [];
bricksRemaining = 0;
buildBricks();
// Overlay
overlayWin.style.display = 'none';
overlayLose.style.display = 'none';
overlayStart.style.display = 'flex';
}
// Build bricks: 6 rows, mix normal and tougher bricks, some bricks carry power-ups
function buildBricks() {
bricks = [];
bricksRemaining = 0;
const cols = 12;
const gap = 8;
const leftMargin = 28;
const brickW = Math.floor((GAME_W - leftMargin * 2 - gap * (cols - 1)) / cols);
const brickH = 22;
const topMargin = 60;
const powerChance = 0.15; // percent bricks carrying power
for (let r = 0; r < BRICK_ROWS; r++) {
for (let c = 0; c < cols; c++) {
const x = leftMargin + c * (brickW + gap);
const y = topMargin + r * (brickH + 6);
// Randomize brick toughness
let hits = (Math.random() < 0.25) ? 2 : 1; // 25% tough bricks
if (r < 2 && Math.random() < 0.3) hits = 2; // bias for upper rows
// Color by toughness
let color = hits === 1 ? '#2ecc71' : '#e67e22';
let cracked = false;
// Optional power-up
let powerUpType = null;
if (Math.random() < powerChance) {
powerUpType = Math.random() < 0.5 ? 0 : 1;
}
bricks.push({
x, y, w: brickW, h: brickH,
hitsLeft: hits,
color, cracked: false,
powerUpType,
destroyed: false
});
if (! (hits <= 0) ) bricksRemaining++;
}
}
}
// Draw helpers
function drawRect(x, y, w, h, color) {
ctx.fillStyle = color;
ctx.fillRect(x, y, w, h);
}
function drawRoundedRect(x, y, w, h, r, color) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
ctx.fill();
}
function drawBall(ball) {
// glow
ctx.save();
ctx.shadowColor = '#fff';
ctx.shadowBlur = 12;
ctx.fillStyle = ball.color;
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
function clamp(n, a, b) { return Math.max(a, Math.min(n, b)); }
function circleRectCollision(ball, rect) {
const nearestX = clamp(ball.x, rect.x, rect.x + rect.w);
const nearestY = clamp(ball.y, rect.y, rect.y + rect.h);
const dx = ball.x - nearestX;
const dy = ball.y - nearestY;
return (dx * dx + dy * dy) <= (ball.r * ball.r);
}
// Paddle collision bounce angle
function bounceFromPaddle(ball) {
// hit position: -1 (left edge) .. 1 (right edge)
const hitPos = (ball.x - (PADDLE.x + PADDLE.w * 0.5)) / (PADDLE.w * 0.5);
const maxBounce = Math.PI / 3; // 60 degrees
const angle = clamp(hitPos, -1, 1) * maxBounce;
const speed = Math.hypot(ball.vx, ball.vy);
ball.vx = Math.sin(angle) * speed;
ball.vy = -Math.cos(angle) * speed;
}
// Particles for brick breaks
function spawnParticles(x, y, color) {
const count = 8 + Math.floor(Math.random() * 6);
for (let i = 0; i < count; i++) {
const a = Math.random() * Math.PI * 2;
const s = 40 + Math.random() * 60;
particles.push({
x, y,
vx: Math.cos(a) * s,
vy: Math.sin(a) * s,
life: 0.8 + Math.random() * 0.6,
color
});
}
}
// Power-up collection
function applyPowerUp(pow) {
if (pow.type === 0) {
// widen paddle for 10 seconds
if (!PADDLE._boostEnd) {
PADDLE._boostEnd = performance.now() + 10000;
} else {
// extend existing boost
PADDLE._boostEnd = Math.max(PADDLE._boostEnd, performance.now() + 10000);
}
PADDLE.w = Math.min(260, PADDLE.w + 40);
// Remove any friction in keyboard autoplace
} else if (pow.type === 1) {
// multi-ball: spawn 2 extra balls
const currentBall = balls[0];
if (currentBall) {
const speed = Math.hypot(currentBall.vx, currentBall.vy);
const baseAngle = Math.atan2(-currentBall.vy, currentBall.vx);
// Two new balls with slight angle variations
for (let k = -1; k <= 1; k += 2) {
const a = baseAngle + k * 0.25;
const nb = createBall(currentBall.x, currentBall.y, Math.cos(a) * speed, Math.sin(a) * speed);
balls.push(nb);
}
}
}
// Remove power-up
pow.collected = true;
}
// Bricks drawing
function drawBricks() {
for (const br of bricks) {
if (br.destroyed) continue;
// Color intensity by hitsLeft
const base = br.color;
const tint = br.hitsLeft === 2 ? base : '#9bd299';
drawRect(br.x, br.y, br.w, br.h, tint);
// If it's a tough brick and has been hit once, show crack
if (br.hitsLeft === 1 && br.hitsLeft !== 0) {
// crack overlay
ctx.fillStyle = 'rgba(0,0,0,.15)';
ctx.fillRect(br.x, br.y, br.w, br.h);
}
// Power-up icon indicator
if (br.powerUpType != null && br.powerUpType !== undefined) {
ctx.fillStyle = '#fff';
ctx.font = 'bold 12px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// small "PU" icon drawn on brick
ctx.fillText('PU', br.x + br.w * 0.5, br.y + br.h * 0.5);
}
}
}
// Power-ups drawing
function drawPowerUps() {
for (const p of activePowerUps) {
// fall down
ctx.fillStyle = p.color;
ctx.strokeStyle = '#000';
ctx.lineWidth = 1;
ctx.fillRect(p.x - p.size/2, p.y - p.size/2, p.size, p.size);
// simple icon
ctx.fillStyle = '#000';
ctx.font = 'bold 11px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(p.type === 0 ? 'W' : '×', p.x, p.y);
}
}
// Draw particles
function drawParticles() {
ctx.globalAlpha = 1;
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
ctx.fillStyle = p.color;
ctx.fillRect(p.x, p.y, 2, 2);
// update life handled in update loop
}
ctx.globalAlpha = 1;
}
// Canvas render
function render() {
// Clear
ctx.clearRect(0, 0, GAME_W, GAME_H);
// Background subtle gradient
const grad = ctx.createLinearGradient(0, 0, 0, GAME_H);
grad.addColorStop(0, '#0a0f1a');
grad.addColorStop(1, '#0a0b1a');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, GAME_W, GAME_H);
// Bricks
drawBricks();
// Power-ups
drawPowerUps();
// Balls
for (const b of balls) {
if (b.alive) drawBall(b);
}
// Paddle (draw last so it stays on top)
ctx.fillStyle = '#3498db';
drawRoundedRect(PADDLE.x, PADDLE.y, PADDLE.w, PADDLE.h, 6, '#3498db');
// Paddle border
ctx.lineWidth = 2;
ctx.strokeStyle = '#1b6bcf';
ctx.strokeRect(PADDLE.x, PADDLE.y, PADDLE.w, PADDLE.h);
// Particles
if (particles.length > 0) {
// simple particles - give them slight gravity
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.vx * 1/60;
p.y += p.vy * 1/60;
p.vy += 60 * (1/60); // gravity-ish
p.life -= 1/60;
if (p.life <= 0) {
particles.splice(i, 1);
} else {
ctx.fillStyle = p.color;
ctx.fillRect(p.x, p.y, 2, 2);
}
}
}
// Boosted paddle hint (visual)
if (PADDLE._boostEnd) {
// show a glow
const glow = (PADDLE._boostEnd - performance.now()) / 1000;
ctx.strokeStyle = 'rgba(0,255,0,.6)';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(PADDLE.x + PADDLE.w/2, PADDLE.y + PADDLE.h/2, Math.min(PADDLE.w/2, 26) + 4, 0, Math.PI * 2);
ctx.stroke();
if (glow <= 0) {
// boost ends
PADDLE._boostEnd = undefined;
}
}
// HUD: score and lives drawn outside (DOM updates)
}
// Update world (with fixed sub-steps to avoid tunneling)
function update(dt) {
// Keep score HUD updated
hud.textContent = `Score: ${score} | Lives: ${lives}`;
// Start/End conditions
if (lives <= 0) {
gameOver = true;
overlayLose.style.display = 'flex';
finalScoreLose.textContent = String(score);
return;
}
if (bricksRemaining <= 0) {
gameWin = true;
overlayWin.style.display = 'flex';
finalScoreWin.textContent = String(score);
return;
}
// Paddle movement
if (!started) {
// Demo mode: AI follows the ball
demoMode = true;
const targetX = balls[0] ? balls[0].x - PADDLE.w * 0.5 : PADDLE.x;
// Smooth follow
const speed = 900; // px/s
const diff = targetX - PADDLE.x;
const maxStep = speed * dt;
if (Math.abs(diff) > 1) {
PADDLE.x += diff > 0 ? Math.min(diff, maxStep) : Math.max(diff, -maxStep);
PADDLE.x = clamp(PADDLE.x, 0, GAME_W - PADDLE.w);
}
} else {
// User control: mouse or keyboard
const target = input.mouseX - PADDLE.w * 0.5;
if (target !== PADDLE.x) {
// simple easing
const dx = target - PADDLE.x;
const step = 900 * dt;
if (Math.abs(dx) < step) PADDLE.x = target;
else PADDLE.x += dx > 0 ? step : -step;
PADDLE.x = clamp(PADDLE.x, 0, GAME_W - PADDLE.w);
}
// Keyboard optional (left/right)
if (input.left) {
PADDLE.x -= PADDLE.speed * dt;
input.left = false;
}
if (input.right) {
PADDLE.x += PADDLE.speed * dt;
input.right = false;
}
// keep in bounds
PADDLE.x = clamp(PADDLE.x, 0, GAME_W - PADDLE.w);
}
// If boost is active, check end time
if (PADDLE._boostEnd && performance.now() >= PADDLE._boostEnd) {
PADDLE._boostEnd = undefined;
// shrink back to normal width
PADDLE.w = Math.max(110, PADDLE.w - 40);
}
// Move balls with small sub-steps to prevent tunneling
const subStep = 1/120; // 8.33 ms
let t = dt;
while (t > 0) {
const step = Math.min(subStep, t);
t -= step;
// Update each ball
for (let i = balls.length - 1; i >= 0; i--) {
const b = balls[i];
if (!b.alive) continue;
const prevX = b.x;
const prevY = b.y;
// Move
b.x += b.vx * step;
b.y += b.vy * step;
// Wall collisions (left/right/top)
if (b.x - b.r < 0) {
b.x = b.r;
b.vx *= -1;
} else if (b.x + b.r > GAME_W) {
b.x = GAME_W - b.r;
b.vx *= -1;
}
if (b.y - b.r < 0) {
b.y = b.r;
b.vy *= -1;
}
// Bottom (miss)
if (b.y - b.r > GAME_H) {
balls.splice(i, 1);
// If no balls left, lose a life and respawn if possible
if (balls.length === 0) {
lives--;
if (lives > 0) {
// reset a single ball
const nb = createBall(GAME_W / 2, PADDLE.y - 18, Math.sin(0) * BALL_SPEED, -BALL_SPEED);
balls.push(nb);
}
}
continue;
}
// Paddle collision
if (circleRectCollision(b, { x: PADDLE.x, y: PADDLE.y, w: PADDLE.w, h: PADDLE.h })) {
// align ball just above paddle
b.y = PADDLE.y - b.r - 1;
// compute reflection based on hit position
const offset = (b.x - (PADDLE.x + PADDLE.w * 0.5)) / (PADDLE.w * 0.5);
const angle = offset * (Math.PI / 3);
const speed = Math.hypot(b.vx, b.vy);
b.vx = Math.sin(angle) * speed;
b.vy = -Math.cos(angle) * speed;
}
// Brick collisions
// stop after first collision in this substep to avoid multiple bounces in a single step
let collidedBrick = null;
for (let j = 0; j < bricks.length; j++) {
const br = bricks[j];
if (br.destroyed) continue;
if (circleRectCollision(b, br)) {
// Determine side of collision by comparing distances to brick edges
const dxLeft = Math.abs(b.x - br.x);
const dxRight = Math.abs(b.x - (br.x + br.w));
const dyTop = Math.abs(b.y - br.y);
const dyBottom = Math.abs(b.y - (br.y + br.h));
const deltaX = Math.min(dxLeft, dxRight);
const deltaY = Math.min(dyTop, dyBottom);
if (deltaX < deltaY) b.vx *= -1;
else b.vy *= -1;
// Brick hit
br.hitsLeft--;
if (br.hitsLeft <= 0) {
br.destroyed = true;
bricksRemaining--;
score += (br.color === '#2ecc71' ? 100 : 150);
// Drop power-up with small probability
if (br.powerUpType != null && br.powerUpType !== undefined) {
if (Math.random() < 0.65) {
const pu = createPowerUp(br.x + br.w/2, br.y + br.h/2, br.powerUpType);
activePowerUps.push(pu);
}
}
// Particles
spawnParticles(br.x + br.w/2, br.y + br.h/2, br.color);
} else {
// If not destroyed, visually indicate crack
br.cracked = true;
br.color = br.hitsLeft === 1 ? '#d35400' : br.color;
}
// Adjust ball position to avoid sticking into brick
if (deltaX < deltaY) {
// horizontal collision: separate along x
if (b.x < br.x) b.x = br.x - b.r;
else b.x = br.x + br.w + b.r;
} else {
// vertical collision: separate along y
if (b.y < br.y) b.y = br.y - b.r;
else b.y = br.y + br.h + b.r;
}
collidedBrick = br;
break;
}
}
// If a brick was destroyed this substep, create a few extra effects (optional)
// (Handled above)
}
// Power-ups movement
for (let k = activePowerUps.length - 1; k >= 0; k--) {
const pu = activePowerUps[k];
pu.y += pu.vy * step;
// Collect if hits paddle
if (circleRectCollision({ x: pu.x, y: pu.y, r: pu.size/2 }, { x: PADDLE.x, y: PADDLE.y, w: PADDLE.w, h: PADDLE.h })) {
applyPowerUp(pu);
activePowerUps.splice(k, 1);
} else if (pu.y - pu.size/2 > GAME_H) {
activePowerUps.splice(k, 1);
}
}
// Update boost end (if boost expired handled in render/path)
} // end sub-steps
}
// Game loop
let lastT = performance.now();
function loop(now) {
const dt = Math.min(0.033, (now - lastT) / 1000); // cap delta to avoid jumps
lastT = now;
if (!gameOver && !gameWin) {
update(dt);
render();
}
requestAnimationFrame(loop);
}
// Mouse & keyboard input
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
// Convert to CSS px
const px = (e.clientX - rect.left) * (canvas.width / rect.width); // maps to canvas internal coords
input.mouseX = px / (canvas.width / canvas.clientWidth) * GAME_W; // more robust? We'll approximate directly:
// Simpler mapping: keep mouseX in game-space
const scaledX = e.clientX - rect.left;
const scale = GAME_W / rect.width;
input.mouseX = scaledX * scale;
});
window.addEventListener('keydown', (e) => {
// Without starting, we don't rely on keyboard to avoid capturing before click
if (!started) {
// any key starts
startGame();
return;
}
if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') {
input.left = true;
} else if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') {
input.right = true;
}
});
// Start button
startBtn.addEventListener('click', startGame);
restartBtnWin.addEventListener('click', () => { initGame(); overlayWin.style.display = 'none'; startGame(true); });
restartBtnLose.addEventListener('click', () => { initGame(); overlayLose.style.display = 'none'; startGame(true); });
// Start the game flow
function startGame(force = false) {
if (started && !force) return;
started = true;
overlayStart.style.display = 'none';
// If force is used after a reset, put the game in playing mode
// The demo autopilots paddle until user moves mouse/or presses keyboard, which is captured above
}
// Initialize and start the loop
initGame();
// Give a short moment to render the initial frame
requestAnimationFrame(loop);
// Expose a tiny status for debugging in console (optional)
window.__BREAKOUT_STATE__ = {
score, lives, bricksRemaining, balls: balls.length
};
})();
</script>
</body>
</html>Reasoning trace (13,696 tokens)
**Calculating angles and velocities** I'm exploring how hit position affects velocity and angle. A center hit means very little horizontal velocity, while edge hits lead to higher speeds. I see that the angle is related to the ratio of horizontal to vertical speed. If I want to create a dramatic angle, I can adjust the horizontal speed accordingly. For example, to achieve a 75-degree angle, I may need a smaller vertical speed. It’s intriguing how these adjustments change the resulting angles!**Adjusting bounce angles** I’m considering the bounce angle requirement, which allows for angles between 45 to 60 degrees based on hit position. A center hit goes straight up, while a hit at the edge results in a sharper angle. To achieve a balance, I can set vertical velocity to 90% of ball speed and adjust horizontal velocity based on hit position. I want to ensure that I dynamically calculate these velocities to maintain a consistent speed and appropriate angle. It’s quite an interesting challenge!**Calculating bounce dynamics** I'm working on adjusting the bounce angle based on where the ball hits, with center hits resulting in a straight bounce and edge hits yielding sharper angles. The bounce angle can be calculated from the maximum angle of around 75 degrees. I’ll use trigonometric functions to determine horizontal and vertical velocities based on this angle. If a center hit results in no horizontal velocity, the implementation seems plausible, allowing for a smooth and dynamic response when the ball bounces!**Implementing bounce mechanics and bricks** I'm planning to implement the bounce mechanics based on where the ball hits the paddle, capping hit position between -1 and 1. The bounce angle will be determined using this hit position and a maximum angle of π/3. I'll calculate horizontal and vertical velocities while ensuring the overall speed remains constant. For edge cases like hitting bricks or walls, I want to account for these velocities carefully. Regarding the bricks, I’ll create a grid with at least six rows, featuring normal bricks and tougher ones that crack after one hit. This sounds promising!**Designing brick and collision mechanics** I'm creating a Brick object that includes properties like position, size, and hits remaining, along with a boolean for whether it's cracked. Each brick can possibly drop a power-up when destroyed, which adds an exciting feature. I need to ensure the ball never passes through bricks or the paddle, so I’m planning to implement continuous collision detection. This involves dividing time into small steps for accurate collision detection and adjusting ball velocity based on the collision side. It’s a complex but interesting challenge!**Implementing collision and brick mechanics** I'm focusing on implementing the collision detection for the ball and bricks. I'll determine from which side of the brick the ball comes from, and invert its velocity accordingly. To do this, I'll track the ball's previous position before updating its position during each substep. If the ball collides with a brick, I'll decide the axis of collision by comparing distances to each side. Also, I need to handle collisions with walls and the paddle, and manage brick destruction, power-ups, and life reductions if the ball falls off the screen. It's quite a lot to juggle!**Setting up gameplay mechanics** I'll ensure the ball's speed is consistent while handling collisions with the paddle. If the ball hits from above, it bounces; if from below, it doesn't. Power-ups will drop from some bricks, influencing paddle width or adding more balls. I'll implement a basic particle effect when bricks break and maintain a score system. There will be at least 6 rows of bricks with a grid layout, some carrying power-ups. I need to set up game over and victory messages, along with an AI-controlled demo that auto-plays until the user clicks to start. It's quite a detailed setup!**Implementing gameplay features** I’m setting up a timer to have the paddle follow the ball gradually while ensuring the UI shows a "Click to start" message. The autoplayer will run initially until the user clicks. Once started, I plan for user control to replace the autoplayer, which will stop. I’ll also create a responsive canvas design that fits different screen sizes without scrollbars. Device pixel ratio scaling will ensure crisp visuals, and I'll implement resizing functions to adjust canvas dimensions appropriately. It’s all coming together!**Implementing game mechanics** I'm setting up a robust scaling system for a 1280x800 viewport and including a start message overlay to dim the canvas. The game will feature a three lives system and a victory screen. I’ll generate a grid of bricks with normal and tough variations, and implement power-ups that affect paddle width or create new balls. The game states will include intro, playing, game over, and victory states. I’ll also develop AI for paddle control during the demo and allow manual control afterward, ensuring a smooth gameplay experience!**Implementing game mechanics and features** I’ll add a crack property to bricks, indicating their strength; 1 for normal and 2 for tough. Each brick will have a power-up type assigned randomly. Tough bricks will show a crack effect after being hit, changing colors appropriately. Scoring will depend on brick type, with normal bricks giving 100 points and tough ones 150. Power-ups will drop with a chance upon brick destruction, and I’ll create visual icons for them. The game will include lives, score display, and screens for game over or victory with a restart option. The autoplayer will guide the demo mode for three seconds before allowing user control!**Implementing game setup and drawing functions** I’ll create a "Rect" object for bricks, containing properties like position, size, hit points, and power-up type. I’ll also implement functions for drawing elements such as balls, paddles, bricks, power-ups, and the HUD. Constants will define the game’s dimensions, and I’ll manage canvas resizing to ensure crisp graphics across devices by adjusting based on the device pixel ratio. The goal is to set up a seamless user experience while maintaining game integrity, employing techniques for scaling without causing blurriness. I'll also include overlays for start and game results.**Implementing ball mechanics and updating game state** I'm setting up an array for "Balls" and configuring their speed to ensure proper autoplayer functionality. I’ll use a frame rate of 60 FPS and break down the update process into smaller steps to prevent the balls from passing through objects. Handling paddle movement, ball positioning, and collisions with walls and bricks is crucial. I’ll make sure to accurately check and respond to collisions, adjust ball direction, and manage the destruction of bricks, which might spawn particles and power-ups. Overall, my goal is to create a smooth and engaging gameplay experience.**Implementing collision detection and game structure** I’m working on a robust system for detecting collisions between balls and bricks using functions that assess distance between circles and rectangles. I’ll also keep track of the remaining bricks to trigger a victory overlay when they’re all destroyed. There are some key UI elements to implement, like overlays for the start and win conditions, while ensuring the canvas is set up correctly in the DOM. Memory efficiency is important, so I’ll avoid duplicating elements like button IDs across overlays. Let’s get into coding this!**Implementing game mechanics and autoplayer** I'm working on the autoplayer feature, which will start in demo mode. In this mode, the paddle will gradually approach the ball. I need to safely handle key events for manual control and ensure paddles don’t move out of bounds. For power-ups, I'll create a mechanism to spawn and manage them effectively. Each ball will have specific properties like speed and bounce, and I want to ensure the code runs error-free. Let’s get started on coding all of this!