Gemini 3.8 Flash
by GoogleReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 3 min 24 s
- Cost
- $0.143
- Output tokens
- 37,986
- Spent thinking
- 21,512 tokens
- Speed
- 188.2 tok/s
- Console errors
- 0
- First token after
- 2.3 s
- First visible text after
- 2 min 12 s
- Prompt tokens
- 524
- Code size
- 42.9 KB
- Finish reason
- stop
- Served by
- Model id
google/gemini-3.8-flash- Reasoning sent
{"effort":"high"}- Max output
- 65,536 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 · 1472 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Breakout Neon</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
-webkit-user-select: none;
}
html, body {
width: 100%;
height: 100%;
background-color: #04060e;
background-image:
radial-gradient(circle at 50% 30%, #0d1938 0%, #050814 60%, #020308 100%);
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 'Helvetica Neue', Arial, sans-serif;
color: #fff;
}
.game-wrapper {
position: relative;
width: 95vw;
max-width: 860px;
aspect-ratio: 840 / 640;
max-height: 94vh;
display: flex;
justify-content: center;
align-items: center;
box-shadow:
0 0 50px rgba(0, 229, 255, 0.12),
0 15px 35px rgba(0, 0, 0, 0.8),
inset 0 0 2px rgba(255, 255, 255, 0.2);
border-radius: 14px;
overflow: hidden;
border: 1px solid rgba(0, 229, 255, 0.2);
background: #060914;
}
canvas {
width: 100%;
height: 100%;
display: block;
touch-action: none;
cursor: none;
}
.touch-indicator {
display: none;
}
@media (max-width: 768px) {
.game-wrapper {
width: 100vw;
height: 100vh;
max-height: 100vh;
border-radius: 0;
border: none;
}
}
</style>
</head>
<body>
<div class="game-wrapper" id="wrapper">
<canvas id="gameCanvas"></canvas>
</div>
<script>
(() => {
// Polyfill roundRect for older canvas engines
if (!CanvasRenderingContext2D.prototype.roundRect) {
CanvasRenderingContext2D.prototype.roundRect = function(x, y, w, h, r) {
if (typeof r === 'undefined') r = 0;
if (typeof r === 'number') r = [r, r, r, r];
const [tl, tr, br, bl] = r;
this.beginPath();
this.moveTo(x + tl, y);
this.lineTo(x + w - tr, y);
this.quadraticCurveTo(x + w, y, x + w, y + tr);
this.lineTo(x + w, y + h - br);
this.quadraticCurveTo(x + w, y + h, x + w - br, y + h);
this.lineTo(x + bl, y + h);
this.quadraticCurveTo(x, y + h, x, y + h - bl);
this.lineTo(x, y + tl);
this.quadraticCurveTo(x, y, x + tl, y);
this.closePath();
return this;
};
}
// --- AUDIO SYNTHESIZER (Web Audio API) ---
class SoundSynth {
constructor() {
this.ctx = null;
this.master = null;
}
init() {
if (!this.ctx) {
const AudioContext = window.AudioContext || window.webkitAudioContext;
if (AudioContext) {
this.ctx = new AudioContext();
this.master = this.ctx.createGain();
this.master.gain.setValueAtTime(0.22, this.ctx.currentTime);
this.master.connect(this.ctx.destination);
}
}
if (this.ctx && this.ctx.state === 'suspended') {
this.ctx.resume();
}
}
tone(freq, type, dur, endFreq = null, gainVal = 0.3) {
if (!this.ctx || this.ctx.state !== 'running') return;
try {
const t = this.ctx.currentTime;
const osc = this.ctx.createOscillator();
const g = this.ctx.createGain();
osc.type = type;
osc.frequency.setValueAtTime(freq, t);
if (endFreq) {
osc.frequency.exponentialRampToValueAtTime(Math.max(10, endFreq), t + dur);
}
g.gain.setValueAtTime(gainVal, t);
g.gain.exponentialRampToValueAtTime(0.001, t + dur);
osc.connect(g);
g.connect(this.master);
osc.start(t);
osc.stop(t + dur);
} catch (e) {}
}
paddleBounce() {
this.tone(320, 'triangle', 0.12, 480, 0.4);
}
wallBounce() {
this.tone(600, 'sine', 0.05, 800, 0.2);
}
brickCrack() {
this.tone(750, 'square', 0.08, 350, 0.25);
}
brickBreak(isTough) {
this.tone(isTough ? 220 : 380, 'triangle', 0.16, 90, 0.35);
if (this.ctx && this.ctx.state === 'running') {
try {
const bs = Math.floor(this.ctx.sampleRate * 0.07);
const buf = this.ctx.createBuffer(1, bs, this.ctx.sampleRate);
const data = buf.getChannelData(0);
for (let i = 0; i < bs; i++) data[i] = (Math.random() * 2 - 1) * 0.5;
const noise = this.ctx.createBufferSource();
noise.buffer = buf;
const filter = this.ctx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.setValueAtTime(isTough ? 900 : 1600, this.ctx.currentTime);
const g = this.ctx.createGain();
g.gain.setValueAtTime(0.2, this.ctx.currentTime);
g.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.07);
noise.connect(filter);
filter.connect(g);
g.connect(this.master);
noise.start();
} catch (e) {}
}
}
powerup() {
if (!this.ctx || this.ctx.state !== 'running') return;
const notes = [523.25, 659.25, 783.99, 1046.5];
notes.forEach((f, i) => {
setTimeout(() => this.tone(f, 'sine', 0.14, f * 1.05, 0.3), i * 45);
});
}
lifeLost() {
this.tone(360, 'sawtooth', 0.32, 110, 0.35);
}
victory() {
if (!this.ctx || this.ctx.state !== 'running') return;
[523.25, 659.25, 783.99, 1046.5, 1318.5].forEach((f, i) => {
setTimeout(() => this.tone(f, 'triangle', 0.22, null, 0.4), i * 110);
});
}
gameOver() {
if (!this.ctx || this.ctx.state !== 'running') return;
[392, 349.2, 311.1, 246.9].forEach((f, i) => {
setTimeout(() => this.tone(f, 'sawtooth', 0.25, f * 0.9, 0.3), i * 160);
});
}
}
const sound = new SoundSynth();
// --- CONSTANTS & CONFIG ---
const V_WIDTH = 840;
const V_HEIGHT = 640;
const TOP_BAR_H = 54;
const BALL_RADIUS = 6.5;
const BASE_BALL_SPEED = 7.5;
const MAX_BALL_SPEED = 12.0;
const BASE_PADDLE_W = 105;
const WIDE_PADDLE_W = 168;
const PADDLE_H = 15;
const PADDLE_Y = V_HEIGHT - 38;
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const wrapper = document.getElementById('wrapper');
// High-DPI Canvas Resizing
function resizeCanvas() {
const dpr = Math.min(window.devicePixelRatio || 1, 2);
canvas.width = V_WIDTH * dpr;
canvas.height = V_HEIGHT * dpr;
ctx.resetTransform?.();
ctx.scale(dpr, dpr);
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
// --- GAME STATE ---
let gameState = 'demo'; // 'demo' | 'playing' | 'gameover' | 'victory'
let score = 0;
let lives = 3;
let combo = 0;
let screenShake = 0;
let wideTimer = 0; // in seconds
let balls = [];
let bricks = [];
let particles = [];
let powerups = [];
let floatingTexts = [];
let fireworks = [];
let launchPromptTimer = 0;
// Paddle definition
const paddle = {
x: V_WIDTH / 2,
y: PADDLE_Y,
w: BASE_BALL_SPEED,
targetW: BASE_PADDLE_W,
h: PADDLE_H,
vx: 0,
squash: 1.0,
update(dt) {
this.w += (this.targetW - this.w) * Math.min(1, dt * 14);
this.squash += (1.0 - this.squash) * Math.min(1, dt * 10);
}
};
// --- CONTROLS ---
let keys = { left: false, right: false };
let mouseActive = false;
let mouseCanvasX = V_WIDTH / 2;
function getCanvasCoords(e) {
const rect = canvas.getBoundingClientRect();
const scaleX = V_WIDTH / rect.width;
const clientX = e.touches ? e.touches[0].clientX : e.clientX;
return (clientX - rect.left) * scaleX;
}
window.addEventListener('keydown', (e) => {
sound.init();
if (['ArrowLeft', 'KeyA'].includes(e.code)) {
keys.left = true;
mouseActive = false;
}
if (['ArrowRight', 'KeyD'].includes(e.code)) {
keys.right = true;
mouseActive = false;
}
if (['Space', 'Enter'].includes(e.code)) {
handleActionPress();
}
if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Space'].includes(e.code)) {
e.preventDefault();
}
if (gameState === 'demo') {
startPlayerGame();
}
});
window.addEventListener('keyup', (e) => {
if (['ArrowLeft', 'KeyA'].includes(e.code)) keys.left = false;
if (['ArrowRight', 'KeyD'].includes(e.code)) keys.right = false;
});
window.addEventListener('mousemove', (e) => {
mouseCanvasX = getCanvasCoords(e);
mouseActive = true;
});
window.addEventListener('touchmove', (e) => {
mouseCanvasX = getCanvasCoords(e);
mouseActive = true;
e.preventDefault();
}, { passive: false });
window.addEventListener('touchstart', (e) => {
sound.init();
mouseCanvasX = getCanvasCoords(e);
mouseActive = true;
handleActionPress();
if (gameState === 'demo') {
startPlayerGame();
}
e.preventDefault();
}, { passive: false });
window.addEventListener('pointerdown', (e) => {
sound.init();
handleActionPress();
if (gameState === 'demo') {
startPlayerGame();
}
});
function handleActionPress() {
if (gameState === 'demo') {
startPlayerGame();
return;
}
if (gameState === 'gameover' || gameState === 'victory') {
startPlayerGame();
return;
}
if (gameState === 'playing') {
// Launch stuck balls if any
let launched = false;
for (const b of balls) {
if (b.stuck) {
b.stuck = false;
b.vx = (Math.random() - 0.5) * 4;
b.vy = -BASE_BALL_SPEED;
launched = true;
}
}
if (launched) sound.paddleBounce();
}
}
// --- PROCEDURAL CRACK GENERATOR FOR TOUGH BRICKS ---
function generateCracks(w, h) {
const cracks = [];
const originX = w * (0.35 + Math.random() * 0.3);
const originY = h * (0.35 + Math.random() * 0.3);
const branches = 3;
for (let b = 0; b < branches; b++) {
const path = [{ x: originX, y: originY }];
let cx = originX;
let cy = originY;
const baseAngle = (b * (Math.PI * 2 / branches)) + (Math.random() - 0.5) * 0.4;
const segments = 3 + Math.floor(Math.random() * 2);
for (let s = 0; s < segments; s++) {
const segLen = 6 + Math.random() * 6;
const ang = baseAngle + (Math.random() - 0.5) * 0.9;
cx += Math.cos(ang) * segLen;
cy += Math.sin(ang) * segLen;
path.push({
x: Math.max(3, Math.min(w - 3, cx)),
y: Math.max(3, Math.min(h - 3, cy))
});
}
cracks.push(path);
}
return cracks;
}
// --- BRICK CREATION ---
function createBricks() {
bricks = [];
const rows = 7;
const cols = 10;
const brickW = 74;
const brickH = 22;
const gapX = 8;
const gapY = 8;
const totalW = cols * brickW + (cols - 1) * gapX;
const startX = (V_WIDTH - totalW) / 2;
const startY = TOP_BAR_H + 28;
// Power-up assignments: [row, col, type]
const powerupMap = {
'0,2': 'multiball',
'0,7': 'multiball',
'1,4': 'expand',
'3,1': 'expand',
'3,8': 'expand',
'4,5': 'multiball',
'5,3': 'multiball',
'5,7': 'expand'
};
const rowColors = [
{ name: 'gold', hp: 2, c1: '#ffd700', c2: '#9a7b00', border: '#ffe866', glow: 'rgba(255, 215, 0, 0.7)' },
{ name: 'slate', hp: 2, c1: '#7c9bb6', c2: '#3d566e', border: '#b0d0ea', glow: 'rgba(124, 155, 182, 0.7)' },
{ name: 'crimson', hp: 1, c1: '#ff2a5f', c2: '#9e0028', border: '#ff7094', glow: 'rgba(255, 42, 95, 0.7)' },
{ name: 'amber', hp: 1, c1: '#ff9100', c2: '#a85000', border: '#ffc166', glow: 'rgba(255, 145, 0, 0.7)' },
{ name: 'emerald', hp: 1, c1: '#00e676', c2: '#007a3d', border: '#69f0ae', glow: 'rgba(0, 230, 118, 0.7)' },
{ name: 'cyan', hp: 1, c1: '#00e5ff', c2: '#007994', border: '#80f3ff', glow: 'rgba(0, 229, 255, 0.7)' },
{ name: 'violet', hp: 1, c1: '#c440ff', c2: '#680099', border: '#e28fff', glow: 'rgba(196, 64, 255, 0.7)' }
];
for (let r = 0; r < rows; r++) {
const rowInfo = rowColors[r];
for (let c = 0; c < cols; c++) {
const x = startX + c * (brickW + gapX);
const y = startY + r * (brickH + gapY);
const puKey = `${r},${c}`;
const pu = powerupMap[puKey] || null;
bricks.push({
r, c, x, y,
w: brickW,
h: brickH,
maxHp: rowInfo.hp,
hp: rowInfo.hp,
c1: rowInfo.c1,
c2: rowInfo.c2,
border: rowInfo.border,
glow: rowInfo.glow,
powerup: pu,
alive: true,
cracks: rowInfo.hp > 1 ? generateCracks(brickW, brickH) : null
});
}
}
}
// --- ENTITIES: BALL, POWERUP, PARTICLES ---
function spawnBall(x, y, vx, vy, stuck = false) {
balls.push({
x, y,
vx, vy,
r: BALL_RADIUS,
stuck,
trail: []
});
}
function spawnPowerup(x, y, type) {
powerups.push({
x, y,
type, // 'expand' | 'multiball'
w: 34,
h: 18,
vy: 2.2,
wobble: Math.random() * Math.PI * 2,
alive: true
});
}
function addDebris(x, y, color, count = 16) {
for (let i = 0; i < count; i++) {
const ang = Math.random() * Math.PI * 2;
const spd = 1.5 + Math.random() * 6;
particles.push({
x, y,
vx: Math.cos(ang) * spd,
vy: Math.sin(ang) * spd - 1.2,
rot: Math.random() * Math.PI * 2,
vRot: (Math.random() - 0.5) * 0.4,
size: 3 + Math.random() * 4,
color,
alpha: 1.0,
decay: 0.015 + Math.random() * 0.02
});
}
}
function addSpark(x, y, count = 10, color = '#00e5ff') {
for (let i = 0; i < count; i++) {
const ang = -Math.PI / 2 + (Math.random() - 0.5) * 1.5;
const spd = 2 + Math.random() * 5;
particles.push({
x, y,
vx: Math.cos(ang) * spd,
vy: Math.sin(ang) * spd,
rot: 0,
vRot: 0,
size: 2 + Math.random() * 2,
color,
alpha: 1.0,
decay: 0.03 + Math.random() * 0.03
});
}
}
function addFloatingText(x, y, text, color = '#fff', size = 16) {
floatingTexts.push({
x, y,
text,
color,
size,
alpha: 1.0,
vy: -1.2
});
}
// --- GAME START & RESETS ---
function startPlayerGame() {
gameState = 'playing';
score = 0;
lives = 3;
combo = 0;
wideTimer = 0;
paddle.targetW = BASE_PADDLE_W;
paddle.w = BASE_PADDLE_W;
paddle.x = V_WIDTH / 2;
powerups = [];
particles = [];
floatingTexts = [];
createBricks();
balls = [];
spawnBall(paddle.x, paddle.y - BALL_RADIUS - 1, 0, 0, true);
}
function initDemoMode() {
gameState = 'demo';
score = 0;
lives = 3;
combo = 0;
wideTimer = 0;
paddle.targetW = BASE_PADDLE_W;
paddle.w = BASE_PADDLE_W;
paddle.x = V_WIDTH / 2;
powerups = [];
particles = [];
floatingTexts = [];
createBricks();
// In demo, pre-damage some bricks so action is immediate & visible on first frame
for (let i = 0; i < 4; i++) {
const tb = bricks.find(b => b.alive && b.maxHp > 1 && b.hp === b.maxHp);
if (tb) tb.hp = 1;
}
// Launch a live moving ball right away!
balls = [];
spawnBall(V_WIDTH / 2 - 40, 360, 4.8, -6.5, false);
}
// --- MAIN UPDATE LOOP ---
let lastTime = performance.now();
function update() {
const now = performance.now();
let dt = (now - lastTime) / 1000;
if (dt > 0.05) dt = 0.05; // Cap delta time
lastTime = now;
// 1. Paddle Timer & Width
if (wideTimer > 0) {
wideTimer -= dt;
if (wideTimer <= 0) {
wideTimer = 0;
paddle.targetW = BASE_PADDLE_W;
}
}
paddle.update(dt);
// 2. Paddle Position Logic
if (gameState === 'demo') {
// AI Controller
let targetX = V_WIDTH / 2;
let targetBall = null;
let lowestY = -1;
// Prioritize downward moving balls
for (const b of balls) {
if (!b.stuck && b.vy > 0 && b.y > lowestY) {
lowestY = b.y;
targetBall = b;
}
}
if (!targetBall && balls.length > 0) {
targetBall = balls[0];
}
if (targetBall) {
// Slight lead prediction
targetX = targetBall.x + targetBall.vx * 0.22;
} else if (powerups.length > 0) {
// Collect power-ups if no ball is in urgent peril
targetX = powerups[0].x;
}
const diff = targetX - paddle.x;
const aiSpeed = 9.2;
paddle.x += Math.sign(diff) * Math.min(Math.abs(diff), aiSpeed);
} else {
// Player Controls (Keyboard & Mouse)
const moveSpeed = 12.0;
if (keys.left) {
paddle.x -= moveSpeed;
}
if (keys.right) {
paddle.x += moveSpeed;
}
if (mouseActive) {
paddle.x += (mouseCanvasX - paddle.x) * Math.min(1, dt * 25);
}
}
// Clamp paddle within playfield
paddle.x = Math.max(paddle.w / 2 + 6, Math.min(V_WIDTH - paddle.w / 2 - 6, paddle.x));
// Thruster engine particles
if (Math.random() < 0.65) {
particles.push({
x: paddle.x + (Math.random() - 0.5) * (paddle.w * 0.6),
y: paddle.y + paddle.h,
vx: (Math.random() - 0.5) * 1.5,
vy: 2 + Math.random() * 2,
rot: 0,
vRot: 0,
size: 2.2,
color: wideTimer > 0 ? '#00e5ff' : '#4488ff',
alpha: 0.8,
decay: 0.05
});
}
// 3. Balls Update & Anti-Tunneling Continuous Collision Sub-stepping
for (let i = balls.length - 1; i >= 0; i--) {
const b = balls[i];
if (b.stuck) {
b.x = paddle.x;
b.y = paddle.y - b.r - 2;
continue;
}
// Trail record
b.trail.unshift({ x: b.x, y: b.y });
if (b.trail.length > 7) b.trail.pop();
// Sub-stepping: divide movement into steps <= 2.5px
const dist = Math.hypot(b.vx, b.vy);
const stepSize = 2.4;
const steps = Math.max(1, Math.ceil(dist / stepSize));
const stepX = b.vx / steps;
const stepY = b.vy / steps;
let ballLost = false;
for (let s = 0; s < steps; s++) {
b.x += stepX;
b.y += stepY;
// Wall Collisions
if (b.x - b.r <= 0) {
b.x = b.r;
b.vx = Math.abs(b.vx);
sound.wallBounce();
addSpark(b.x, b.y, 6, '#80d4ff');
} else if (b.x + b.r >= V_WIDTH) {
b.x = V_WIDTH - b.r;
b.vx = -Math.abs(b.vx);
sound.wallBounce();
addSpark(b.x, b.y, 6, '#80d4ff');
}
if (b.y - b.r <= TOP_BAR_H) {
b.y = TOP_BAR_H + b.r;
b.vy = Math.abs(b.vy);
sound.wallBounce();
addSpark(b.x, b.y, 6, '#80d4ff');
}
// Bottom Boundary (Death)
if (b.y - b.r > V_HEIGHT) {
ballLost = true;
break;
}
// Paddle Collision
if (b.vy > 0 &&
b.y + b.r >= paddle.y &&
b.y - b.r <= paddle.y + paddle.h &&
b.x + b.r >= paddle.x - paddle.w / 2 &&
b.x - b.r <= paddle.x + paddle.w / 2) {
// Snap above paddle
b.y = paddle.y - b.r;
// Angular deflection based on hit location
let hitOffset = (b.x - paddle.x) / (paddle.w / 2);
hitOffset = Math.max(-0.92, Math.min(0.92, hitOffset));
const maxDeflectionAngle = Math.PI * 0.38; // ~68 degrees
const angle = hitOffset * maxDeflectionAngle;
let spd = Math.hypot(b.vx, b.vy);
// Progressive speed increment
spd = Math.min(spd + 0.08, MAX_BALL_SPEED);
b.vx = spd * Math.sin(angle);
b.vy = -spd * Math.cos(angle);
// Prevent nearly-flat horizontal trajectory
if (Math.abs(b.vy) < 2.2) {
b.vy = -2.2;
}
paddle.squash = 0.82;
combo = 0; // Reset combo on paddle return
sound.paddleBounce();
addSpark(b.x, paddle.y, 12, '#00e5ff');
break;
}
// Brick Collision
let hitBrick = null;
for (const brick of bricks) {
if (!brick.alive) continue;
// Closest point on brick to ball center
const cx = Math.max(brick.x, Math.min(b.x, brick.x + brick.w));
const cy = Math.max(brick.y, Math.min(b.y, brick.y + brick.h));
const dx = b.x - cx;
const dy = b.y - cy;
if (dx * dx + dy * dy < b.r * b.r) {
hitBrick = brick;
// Compute overlap on 4 faces
const overlapL = (b.x + b.r) - brick.x;
const overlapR = (brick.x + brick.w) - (b.x - b.r);
const overlapT = (b.y + b.r) - brick.y;
const overlapB = (brick.y + brick.h) - (b.y - b.r);
const minX = Math.min(overlapL, overlapR);
const minY = Math.min(overlapT, overlapB);
if (minX < minY) {
if (overlapL < overlapR) {
b.x = brick.x - b.r;
b.vx = -Math.abs(b.vx);
} else {
b.x = brick.x + brick.w + b.r;
b.vx = Math.abs(b.vx);
}
} else {
if (overlapT < overlapB) {
b.y = brick.y - b.r;
b.vy = -Math.abs(b.vy);
} else {
b.y = brick.y + brick.h + b.r;
b.vy = Math.abs(b.vy);
}
}
// Damage Brick
brick.hp--;
combo++;
const pts = (brick.maxHp > 1 ? 150 : 100) + combo * 10;
score += pts;
if (brick.hp > 0) {
// Brick cracked!
sound.brickCrack();
screenShake = Math.max(screenShake, 3);
addSpark(cx, cy, 8, brick.border);
addFloatingText(brick.x + brick.w / 2, brick.y, `CRACK! +${pts}`, brick.border, 13);
} else {
// Brick destroyed!
brick.alive = false;
sound.brickBreak(brick.maxHp > 1);
screenShake = Math.max(screenShake, brick.maxHp > 1 ? 6 : 4);
addDebris(brick.x + brick.w / 2, brick.y + brick.h / 2, brick.c1, 18);
addFloatingText(brick.x + brick.w / 2, brick.y, `+${pts}`, brick.border, 15);
// Power-up release
if (brick.powerup) {
spawnPowerup(brick.x + brick.w / 2, brick.y + brick.h / 2, brick.powerup);
}
// Victory check
const remaining = bricks.filter(br => br.alive).length;
if (remaining === 0) {
triggerVictory();
}
}
break; // Stop brick checks for this sub-step
}
}
if (hitBrick) break; // Reflected, proceed to next frame
}
if (ballLost) {
balls.splice(i, 1);
}
}
// 4. Handle Ball Depletion (Death / Respawn)
if (balls.length === 0) {
if (gameState === 'demo') {
// Endless loop in demo mode
spawnBall(paddle.x, paddle.y - BALL_RADIUS - 1, (Math.random() - 0.5) * 5, -BASE_BALL_SPEED, false);
} else if (gameState === 'playing') {
lives--;
screenShake = 10;
sound.lifeLost();
if (lives <= 0) {
gameState = 'gameover';
sound.gameOver();
} else {
// Re-arm ball onto paddle
wideTimer = 0;
paddle.targetW = BASE_PADDLE_W;
combo = 0;
spawnBall(paddle.x, paddle.y - BALL_RADIUS - 1, 0, 0, true);
}
}
}
// 5. Power-up Capsules Update & Collection
for (let i = powerups.length - 1; i >= 0; i--) {
const p = powerups[i];
p.y += p.vy * (dt * 60);
p.wobble += 0.06;
// Check collision with paddle
const collides = (
p.x + p.w / 2 >= paddle.x - paddle.w / 2 &&
p.x - p.w / 2 <= paddle.x + paddle.w / 2 &&
p.y + p.h / 2 >= paddle.y &&
p.y - p.h / 2 <= paddle.y + paddle.h
);
if (collides) {
sound.powerup();
score += 200;
if (p.type === 'expand') {
wideTimer = 10.0;
paddle.targetW = WIDE_PADDLE_W;
addFloatingText(paddle.x, paddle.y - 20, 'WIDE PADDLE (10s)!', '#00e5ff', 18);
addSpark(paddle.x, paddle.y, 25, '#00e5ff');
} else if (p.type === 'multiball') {
// Triplicate active balls or launch 3 balls
if (balls.length === 0) {
spawnBall(paddle.x, paddle.y - 10, -3, -7);
spawnBall(paddle.x, paddle.y - 10, 0, -7.5);
spawnBall(paddle.x, paddle.y - 10, 3, -7);
} else {
const baseBall = balls[0];
const spd = Math.hypot(baseBall.vx, baseBall.vy) || BASE_BALL_SPEED;
spawnBall(baseBall.x, baseBall.y, -spd * 0.7, -spd * 0.7);
spawnBall(baseBall.x, baseBall.y, spd * 0.7, -spd * 0.7);
if (baseBall.stuck) baseBall.stuck = false;
}
addFloatingText(paddle.x, paddle.y - 20, 'MULTI-BALL (3X)!', '#ff3df1', 18);
addSpark(paddle.x, paddle.y, 25, '#ff3df1');
}
powerups.splice(i, 1);
continue;
}
if (p.y > V_HEIGHT + 30) {
powerups.splice(i, 1);
}
}
// 6. Particles Update
for (let i = particles.length - 1; i >= 0; i--) {
const pt = particles[i];
pt.x += pt.vx;
pt.y += pt.vy;
pt.vy += 0.14; // gentle gravity
pt.rot += pt.vRot;
pt.alpha -= pt.decay;
if (pt.alpha <= 0) particles.splice(i, 1);
}
// 7. Floating Texts Update
for (let i = floatingTexts.length - 1; i >= 0; i--) {
const ft = floatingTexts[i];
ft.y += ft.vy;
ft.alpha -= 0.02;
if (ft.alpha <= 0) floatingTexts.splice(i, 1);
}
// 8. Victory Fireworks Update
if (gameState === 'victory') {
if (Math.random() < 0.1) {
const fx = 100 + Math.random() * (V_WIDTH - 200);
const fy = 80 + Math.random() * 260;
const col = ['#00e5ff', '#ffd700', '#ff2a5f', '#69f0ae', '#ff3df1'][Math.floor(Math.random() * 5)];
addDebris(fx, fy, col, 22);
}
}
}
function triggerVictory() {
gameState = 'victory';
sound.victory();
const lifeBonus = lives * 1000;
score += lifeBonus;
addFloatingText(V_WIDTH / 2, V_HEIGHT / 2 - 40, `LIFE BONUS +${lifeBonus}!`, '#ffd700', 24);
}
// --- DRAW ROUTINES ---
function draw() {
ctx.save();
// Screen Shake
if (screenShake > 0) {
const shakeX = (Math.random() - 0.5) * screenShake;
const shakeY = (Math.random() - 0.5) * screenShake;
ctx.translate(shakeX, shakeY);
screenShake *= 0.88;
if (screenShake < 0.1) screenShake = 0;
}
// Clean background
ctx.fillStyle = '#060914';
ctx.fillRect(0, 0, V_WIDTH, V_HEIGHT);
// Subtle background space grid
ctx.strokeStyle = 'rgba(0, 229, 255, 0.035)';
ctx.lineWidth = 1;
const gridSize = 40;
for (let x = 0; x < V_WIDTH; x += gridSize) {
ctx.beginPath();
ctx.moveTo(x, TOP_BAR_H);
ctx.lineTo(x, V_HEIGHT);
ctx.stroke();
}
for (let y = TOP_BAR_H; y < V_HEIGHT; y += gridSize) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(V_WIDTH, y);
ctx.stroke();
}
// 1. Draw Bricks
for (const b of bricks) {
if (!b.alive) continue;
ctx.save();
// Glow
ctx.shadowColor = b.glow;
ctx.shadowBlur = b.maxHp > 1 && b.hp === 2 ? 10 : 8;
// Brick body gradient
const grad = ctx.createLinearGradient(b.x, b.y, b.x, b.y + b.h);
if (b.maxHp > 1 && b.hp === 1) {
// Damaged tough brick: darker, cracked look
grad.addColorStop(0, '#555e68');
grad.addColorStop(1, '#252a32');
} else {
grad.addColorStop(0, b.c1);
grad.addColorStop(1, b.c2);
}
ctx.fillStyle = grad;
ctx.beginPath();
ctx.roundRect(b.x, b.y, b.w, b.h, 4);
ctx.fill();
// Border
ctx.strokeStyle = (b.maxHp > 1 && b.hp === 1) ? '#ff5577' : b.border;
ctx.lineWidth = b.maxHp > 1 ? 2 : 1.5;
ctx.stroke();
// Subtle top highlight sheen
ctx.strokeStyle = 'rgba(255, 255, 255, 0.4)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(b.x + 4, b.y + 2);
ctx.lineTo(b.x + b.w - 4, b.y + 2);
ctx.stroke();
// Armor rivets for tough bricks
if (b.maxHp > 1) {
ctx.fillStyle = '#ffffff';
const rivetOffset = 4;
[
[b.x + rivetOffset, b.y + rivetOffset],
[b.x + b.w - rivetOffset, b.y + rivetOffset],
[b.x + rivetOffset, b.y + b.h - rivetOffset],
[b.x + b.w - rivetOffset, b.y + b.h - rivetOffset]
].forEach(([rx, ry]) => {
ctx.beginPath();
ctx.arc(rx, ry, 1.2, 0, Math.PI * 2);
ctx.fill();
});
}
// Procedural Crack lines on damaged tough brick
if (b.maxHp > 1 && b.hp === 1 && b.cracks) {
ctx.shadowColor = '#ff2a5f';
ctx.shadowBlur = 4;
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 1.6;
ctx.beginPath();
for (const branch of b.cracks) {
if (branch.length > 0) {
ctx.moveTo(b.x + branch[0].x, b.y + branch[0].y);
for (let k = 1; k < branch.length; k++) {
ctx.lineTo(b.x + branch[k].x, b.y + branch[k].y);
}
}
}
ctx.stroke();
}
// Power-up Icon Badge on Brick
if (b.powerup) {
ctx.shadowBlur = 6;
if (b.powerup === 'expand') {
ctx.strokeStyle = '#00f7ff';
ctx.fillStyle = '#00f7ff';
ctx.lineWidth = 1.8;
// Dual arrow emblem < ═ >
const midX = b.x + b.w / 2;
const midY = b.y + b.h / 2;
ctx.beginPath();
ctx.moveTo(midX - 12, midY);
ctx.lineTo(midX + 12, midY);
// Left arrowhead
ctx.moveTo(midX - 8, midY - 3.5);
ctx.lineTo(midX - 12, midY);
ctx.lineTo(midX - 8, midY + 3.5);
// Right arrowhead
ctx.moveTo(midX + 8, midY - 3.5);
ctx.lineTo(midX + 12, midY);
ctx.lineTo(midX + 8, midY + 3.5);
ctx.stroke();
} else if (b.powerup === 'multiball') {
ctx.fillStyle = '#ff3df1';
ctx.shadowColor = '#ff3df1';
const midX = b.x + b.w / 2;
const midY = b.y + b.h / 2;
// 3 small glowing spheres
for (let o = -8; o <= 8; o += 8) {
ctx.beginPath();
ctx.arc(midX + o, midY, 2.8, 0, Math.PI * 2);
ctx.fill();
}
}
}
ctx.restore();
}
// 2. Draw Power-up Falling Capsules
for (const p of powerups) {
ctx.save();
ctx.translate(p.x, p.y);
ctx.rotate(Math.sin(p.wobble) * 0.12);
const isExpand = p.type === 'expand';
const glowCol = isExpand ? '#00e5ff' : '#ff3df1';
ctx.shadowColor = glowCol;
ctx.shadowBlur = 12;
ctx.fillStyle = '#090e1f';
ctx.strokeStyle = glowCol;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.roundRect(-p.w / 2, -p.h / 2, p.w, p.h, 9);
ctx.fill();
ctx.stroke();
// Capsule Inner Label/Symbol
if (isExpand) {
ctx.fillStyle = '#00e5ff';
ctx.font = 'bold 11px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('◄►', 0, 0.5);
} else {
ctx.fillStyle = '#ff3df1';
ctx.beginPath();
ctx.arc(-7, 0, 2.6, 0, Math.PI * 2);
ctx.arc(0, 0, 2.6, 0, Math.PI * 2);
ctx.arc(7, 0, 2.6, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
}
// 3. Draw Paddle
ctx.save();
const pw = paddle.w;
const ph = paddle.h * paddle.squash;
const px = paddle.x - pw / 2;
const py = paddle.y + (paddle.h - ph) / 2;
// Paddle outer neon glow
ctx.shadowColor = wideTimer > 0 ? '#00e5ff' : '#00aaff';
ctx.shadowBlur = wideTimer > 0 ? 18 : 12;
// Paddle body
const paddleGrad = ctx.createLinearGradient(px, py, px, py + ph);
paddleGrad.addColorStop(0, wideTimer > 0 ? '#7df9ff' : '#5ce1e6');
paddleGrad.addColorStop(0.5, '#0b1d3a');
paddleGrad.addColorStop(1, wideTimer > 0 ? '#0072b2' : '#003366');
ctx.fillStyle = paddleGrad;
ctx.beginPath();
ctx.roundRect(px, py, pw, ph, 8);
ctx.fill();
// Paddle border
ctx.strokeStyle = wideTimer > 0 ? '#00ffff' : '#80d4ff';
ctx.lineWidth = 2;
ctx.stroke();
// Central Core Pulse
ctx.shadowBlur = 6;
ctx.fillStyle = wideTimer > 0 ? '#ffffff' : '#00e5ff';
ctx.beginPath();
ctx.roundRect(paddle.x - 24, py + ph * 0.3, 48, ph * 0.4, 3);
ctx.fill();
// Bumper Caps
ctx.fillStyle = '#ff3366';
ctx.beginPath();
ctx.arc(px + 4, py + ph / 2, 2.5, 0, Math.PI * 2);
ctx.arc(px + pw - 4, py + ph / 2, 2.5, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
// 4. Draw Balls & Comet Trails
for (const b of balls) {
ctx.save();
// Draw tail
for (let t = 0; t < b.trail.length; t++) {
const pt = b.trail[t];
const prog = (b.trail.length - t) / b.trail.length;
ctx.fillStyle = `rgba(0, 229, 255, ${prog * 0.35})`;
ctx.beginPath();
ctx.arc(pt.x, pt.y, b.r * prog * 0.85, 0, Math.PI * 2);
ctx.fill();
}
// Ball Core
ctx.shadowColor = '#00e5ff';
ctx.shadowBlur = 14;
const bGrad = ctx.createRadialGradient(b.x - 2, b.y - 2, 1, b.x, b.y, b.r);
bGrad.addColorStop(0, '#ffffff');
bGrad.addColorStop(0.6, '#80f0ff');
bGrad.addColorStop(1, '#00a3cc');
ctx.fillStyle = bGrad;
ctx.beginPath();
ctx.arc(b.x, b.y, b.r, 0, Math.PI * 2);
ctx.fill();
// Launch Aim Guidance if Stuck on Paddle
if (b.stuck && gameState === 'playing') {
ctx.shadowBlur = 8;
ctx.strokeStyle = 'rgba(0, 229, 255, 0.7)';
ctx.setLineDash([4, 6]);
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(b.x, b.y - b.r);
ctx.lineTo(b.x, b.y - 70);
ctx.stroke();
ctx.setLineDash([]);
// Chevron arrow
ctx.fillStyle = '#00e5ff';
ctx.beginPath();
ctx.moveTo(b.x, b.y - 80);
ctx.lineTo(b.x - 7, b.y - 68);
ctx.lineTo(b.x + 7, b.y - 68);
ctx.closePath();
ctx.fill();
}
ctx.restore();
}
// 5. Draw Particles
for (const pt of particles) {
ctx.save();
ctx.globalAlpha = Math.max(0, pt.alpha);
ctx.translate(pt.x, pt.y);
ctx.rotate(pt.rot);
ctx.fillStyle = pt.color;
ctx.shadowColor = pt.color;
ctx.shadowBlur = 6;
ctx.fillRect(-pt.size / 2, -pt.size / 2, pt.size, pt.size);
ctx.restore();
}
// 6. Draw Floating Texts
for (const ft of floatingTexts) {
ctx.save();
ctx.globalAlpha = Math.max(0, ft.alpha);
ctx.fillStyle = ft.color;
ctx.shadowColor = ft.color;
ctx.shadowBlur = 8;
ctx.font = `bold ${ft.size}px -apple-system, sans-serif`;
ctx.textAlign = 'center';
ctx.fillText(ft.text, ft.x, ft.y);
ctx.restore();
}
// 7. Top HUD Bar
ctx.save();
// Backdrop
ctx.fillStyle = 'rgba(6, 9, 20, 0.85)';
ctx.fillRect(0, 0, V_WIDTH, TOP_BAR_H);
ctx.strokeStyle = 'rgba(0, 229, 255, 0.25)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, TOP_BAR_H);
ctx.lineTo(V_WIDTH, TOP_BAR_H);
ctx.stroke();
// Score Label
ctx.font = 'bold 13px -apple-system, sans-serif';
ctx.fillStyle = '#7a92ad';
ctx.fillText('SCORE', 24, 22);
ctx.font = 'bold 22px monospace';
ctx.fillStyle = '#ffffff';
ctx.shadowColor = 'rgba(255, 255, 255, 0.4)';
ctx.shadowBlur = 6;
ctx.fillText(score.toLocaleString(), 24, 44);
// Active Power-up Pill in HUD
if (wideTimer > 0) {
const pW = 180;
const pX = V_WIDTH / 2 - pW / 2;
const pY = 12;
ctx.fillStyle = 'rgba(0, 229, 255, 0.12)';
ctx.strokeStyle = '#00e5ff';
ctx.lineWidth = 1.5;
ctx.shadowColor = '#00e5ff';
ctx.shadowBlur = 8;
ctx.beginPath();
ctx.roundRect(pX, pY, pW, 30, 15);
ctx.fill();
ctx.stroke();
ctx.fillStyle = '#00e5ff';
ctx.font = 'bold 12px -apple-system, sans-serif';
ctx.textAlign = 'center';
ctx.fillText(`◄ WIDE PADDLE: ${wideTimer.toFixed(1)}s ►`, V_WIDTH / 2, pY + 19);
} else if (combo > 1) {
ctx.fillStyle = '#ff9100';
ctx.font = 'bold 15px -apple-system, sans-serif';
ctx.textAlign = 'center';
ctx.shadowColor = '#ff9100';
ctx.shadowBlur = 8;
ctx.fillText(`COMBO x${combo}!`, V_WIDTH / 2, 34);
} else {
ctx.fillStyle = 'rgba(255, 255, 255, 0.35)';
ctx.font = 'bold 14px -apple-system, sans-serif';
ctx.textAlign = 'center';
ctx.fillText('BREAKOUT NEON', V_WIDTH / 2, 34);
}
// Lives Indicator (Mini Glowing Shields)
ctx.textAlign = 'right';
ctx.font = 'bold 13px -apple-system, sans-serif';
ctx.fillStyle = '#7a92ad';
ctx.fillText('LIVES', V_WIDTH - 24 - (3 * 22), 34);
for (let l = 0; l < 3; l++) {
const lx = V_WIDTH - 24 - (2 - l) * 22;
const ly = 29;
ctx.save();
if (l < lives) {
ctx.fillStyle = '#00e5ff';
ctx.shadowColor = '#00e5ff';
ctx.shadowBlur = 8;
ctx.beginPath();
ctx.roundRect(lx - 14, ly - 8, 16, 10, 3);
ctx.fill();
} else {
ctx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.roundRect(lx - 14, ly - 8, 16, 10, 3);
ctx.stroke();
}
ctx.restore();
}
ctx.restore();
// 8. Launch prompt during play
if (gameState === 'playing' && balls.some(b => b.stuck)) {
ctx.save();
launchPromptTimer += 0.05;
const alpha = 0.65 + Math.sin(launchPromptTimer * 3) * 0.35;
ctx.fillStyle = `rgba(255, 255, 255, ${alpha})`;
ctx.font = 'bold 15px -apple-system, sans-serif';
ctx.textAlign = 'center';
ctx.shadowColor = '#00e5ff';
ctx.shadowBlur = 10;
ctx.fillText('CLICK OR PRESS SPACE TO LAUNCH', V_WIDTH / 2, paddle.y - 45);
ctx.restore();
}
// 9. Overlay Screen: AUTOPLAY DEMO MODE
if (gameState === 'demo') {
ctx.save();
const cardW = 540;
const cardH = 110;
const cardX = V_WIDTH / 2 - cardW / 2;
const cardY = V_HEIGHT - 170;
// Card Backdrop Glassmorphism
ctx.fillStyle = 'rgba(4, 9, 24, 0.88)';
ctx.strokeStyle = 'rgba(0, 229, 255, 0.45)';
ctx.lineWidth = 2;
ctx.shadowColor = '#00e5ff';
ctx.shadowBlur = 24;
ctx.beginPath();
ctx.roundRect(cardX, cardY, cardW, cardH, 16);
ctx.fill();
ctx.stroke();
// Top Tag
ctx.fillStyle = '#ff9100';
ctx.font = 'bold 12px -apple-system, sans-serif';
ctx.textAlign = 'center';
ctx.letterSpacing = '2px';
ctx.shadowBlur = 6;
ctx.fillText('● AUTOPLAY DEMO MODE', V_WIDTH / 2, cardY + 28);
// Primary Call to Action
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 21px -apple-system, sans-serif';
ctx.shadowColor = '#ffffff';
ctx.shadowBlur = 12;
ctx.fillText('CLICK OR PRESS ANY KEY TO PLAY', V_WIDTH / 2, cardY + 58);
// Subtitle Instructions
ctx.fillStyle = '#8fa3bf';
ctx.font = '13px -apple-system, sans-serif';
ctx.shadowBlur = 0;
ctx.fillText('Mouse / Arrow Keys to Move • Space / Click to Launch', V_WIDTH / 2, cardY + 86);
ctx.restore();
}
// 10. Overlay Screen: GAME OVER
if (gameState === 'gameover') {
ctx.save();
ctx.fillStyle = 'rgba(3, 5, 14, 0.85)';
ctx.fillRect(0, 0, V_WIDTH, V_HEIGHT);
const boxW = 460;
const boxH = 240;
const boxX = V_WIDTH / 2 - boxW / 2;
const boxY = V_HEIGHT / 2 - boxH / 2;
ctx.fillStyle = 'rgba(12, 17, 34, 0.95)';
ctx.strokeStyle = '#ff2a5f';
ctx.lineWidth = 2;
ctx.shadowColor = '#ff2a5f';
ctx.shadowBlur = 28;
ctx.beginPath();
ctx.roundRect(boxX, boxY, boxW, boxH, 16);
ctx.fill();
ctx.stroke();
ctx.textAlign = 'center';
ctx.fillStyle = '#ff2a5f';
ctx.font = 'bold 36px -apple-system, sans-serif';
ctx.fillText('GAME OVER', V_WIDTH / 2, boxY + 54);
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 18px -apple-system, sans-serif';
ctx.shadowBlur = 4;
ctx.fillText(`FINAL SCORE: ${score.toLocaleString()}`, V_WIDTH / 2, boxY + 104);
const brokenCount = bricks.filter(b => !b.alive).length;
ctx.fillStyle = '#9cb0c9';
ctx.font = '14px -apple-system, sans-serif';
ctx.fillText(`Bricks Shattered: ${brokenCount} / ${bricks.length}`, V_WIDTH / 2, boxY + 134);
// Play Again Button Badge
const btnW = 260;
const btnH = 44;
const btnX = V_WIDTH / 2 - btnW / 2;
const btnY = boxY + 164;
ctx.fillStyle = '#ff2a5f';
ctx.shadowColor = '#ff2a5f';
ctx.shadowBlur = 14;
ctx.beginPath();
ctx.roundRect(btnX, btnY, btnW, btnH, 22);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 15px -apple-system, sans-serif';
ctx.fillText('PLAY AGAIN', V_WIDTH / 2, btnY + 27);
ctx.restore();
}
// 11. Overlay Screen: VICTORY
if (gameState === 'victory') {
ctx.save();
ctx.fillStyle = 'rgba(3, 5, 14, 0.85)';
ctx.fillRect(0, 0, V_WIDTH, V_HEIGHT);
const boxW = 500;
const boxH = 260;
const boxX = V_WIDTH / 2 - boxW / 2;
const boxY = V_HEIGHT / 2 - boxH / 2;
ctx.fillStyle = 'rgba(12, 17, 34, 0.95)';
ctx.strokeStyle = '#ffd700';
ctx.lineWidth = 2;
ctx.shadowColor = '#ffd700';
ctx.shadowBlur = 32;
ctx.beginPath();
ctx.roundRect(boxX, boxY, boxW, boxH, 16);
ctx.fill();
ctx.stroke();
ctx.textAlign = 'center';
ctx.fillStyle = '#ffd700';
ctx.font = 'bold 36px -apple-system, sans-serif';
ctx.shadowBlur = 16;
ctx.fillText('VICTORY!', V_WIDTH / 2, boxY + 54);
ctx.fillStyle = '#69f0ae';
ctx.font = 'bold 15px -apple-system, sans-serif';
ctx.shadowBlur = 6;
ctx.fillText('ALL BRICKS CLEARED!', V_WIDTH / 2, boxY + 86);
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 20px -apple-system, sans-serif';
ctx.fillText(`TOTAL SCORE: ${score.toLocaleString()}`, V_WIDTH / 2, boxY + 124);
ctx.fillStyle = '#ffd700';
ctx.font = '14px -apple-system, sans-serif';
ctx.fillText(`Remaining Lives Bonus: +${lives * 1000}`, V_WIDTH / 2, boxY + 152);
// Play Again Button Badge
const btnW = 260;
const btnH = 44;
const btnX = V_WIDTH / 2 - btnW / 2;
const btnY = boxY + 184;
ctx.fillStyle = '#00e5ff';
ctx.shadowColor = '#00e5ff';
ctx.shadowBlur = 14;
ctx.beginPath();
ctx.roundRect(btnX, btnY, btnW, btnH, 22);
ctx.fill();
ctx.fillStyle = '#051024';
ctx.font = 'bold 15px -apple-system, sans-serif';
ctx.fillText('PLAY AGAIN', V_WIDTH / 2, btnY + 27);
ctx.restore();
}
ctx.restore();
}
// --- MAIN ANIMATION LOOP ---
function loop() {
update();
draw();
requestAnimationFrame(loop);
}
// Start in Self-Playing Demo Mode!
initDemoMode();
requestAnimationFrame(loop);
})();
</script>
</body>
</html>Reasoning trace (21,512 tokens)
**Developing Game Core** I'm currently focusing on implementing the core mechanics for our Breakout game. The paddle's mouse and keyboard controls are coming along nicely, and I'm now refining the physics for how the ball bounces off the paddle, ensuring variable angles based on impact point. **Implementing Game Features** I'm now implementing the brick mechanics, including different hit points and visual feedback for tougher bricks. Next, I'll focus on integrating power-ups like paddle expansion and multi-ball, along with robust collision detection to prevent any ball clipping issues. Particle effects and UI elements are also on the horizon. **Visualizing Game Aesthetics** I'm now envisioning the game's visual identity, leaning into a retro-modern neon cyberpunk theme. I'm detailing sleek HUD elements, dynamic particle effects, and vibrant, glowing bricks. The goal is a polished look with smooth rendering and no console errors. **Refining Game Logic** I'm now focusing on the intricate details of ball physics, specifically implementing sub-stepping to combat tunneling issues at higher speeds. This involves breaking down movement into smaller, more manageable steps to ensure accurate collision detection with bricks and walls. **Determining Hit Normals** I'm now refining the ball's collision response, specifically calculating the precise hit normal from the previous sub-step position against the brick's bounding box. This allows for accurate velocity reflection and prevents penetration, while also addressing corner cases for smoother gameplay. **Adding Visual Flair** I'm now exploring particle effects for the ball trails and considering subtle screen shakes for impact. Floating score text and dynamic HUD elements with mini-paddles for lives are also being integrated. Brick layout and resolution are being finalized for optimal visual presentation. **Designing Brick Varieties** I'm detailing the specific types and properties for each row of bricks. This includes their hit points, point values, and the types of power-ups they'll drop, with armored bricks having two hit points and visually cracking. **Visualizing Power-Ups** I'm now focusing on how power-ups will appear visually within the bricks, using distinct icons like arrows for expansion or "3x" for multiball. When a brick breaks, the power-up capsule will fall with a pulsing glow, and catching it will trigger effects like a wider paddle or multiple balls, accompanied by floating text notifications. **Implementing Sub-Step Movement** I'm currently refining the ball's movement logic by implementing sub-stepping. This allows for smaller, more frequent updates to the ball's position, ensuring accurate collision detection even at high speeds and preventing tunneling through bricks or walls. I'm also integrating precise wall collision checks, including bouncing off the HUD. **Calculating Bounce Angles** I'm now finalizing the ball's bounce logic off the paddle. Specifically, I'm refining the calculation of the hit offset and its translation into precise bounce angles, ensuring a dynamic and engaging feel based on where the ball strikes. **Refining Paddle Bounce** I'm now finalizing the ball's bounce calculation off the paddle, incorporating variable deflection based on impact point and paddle velocity for added dynamism. I'm also planning to add visual and audio feedback to enhance the feel of each bounce. **Calculating Bounce Directions** I'm now focused on accurately determining the ball's bounce direction upon hitting a brick. I'm calculating the minimum penetration depth from each side to identify which edge was struck, allowing for precise reflection of the ball's velocity. **Colliding with Bricks** I'm finalizing the logic for when the ball hits a brick. This involves calculating the exact impact point to determine bounce direction, reducing the brick's health, and triggering various visual and audio effects. If a brick is destroyed, I'm handling its particle explosion, potential power-up drops, and score updates. **Improving Collision Detection** I'm now enhancing the ball's collision resolution by ensuring it avoids multi-reversal anomalies with accurate sub-stepping and positional displacement, guaranteeing it won't get stuck or repeatedly bounce within bricks. I'm also testing robustness against maximum speeds. **Implementing Advanced Controls** I'm now integrating robust paddle controls, supporting simultaneous mouse and arrow key input for smooth movement and responsiveness. Touchscreen compatibility will also be added for broader accessibility. **Designing Demo Mode** I'm now designing the self-playing demo mode for initial evaluation, which will feature a simple AI showcasing gameplay behind a "Click or press a key to start" prompt. This will allow judges to observe the game in action without direct input. **Starting Demo Mode** I'm now implementing the self-playing demo mode. The AI will find the most critical ball and smoothly move the paddle to intercept it, also prioritizing power-up collection. A clear "Click or press a key to start" prompt will be visible. **Starting Game Play** I'm finalizing the transition from demo mode to active play. Now, clicking or pressing any key instantly initiates a new game, granting three lives and resetting the score, and the ball is immediately ready to launch. **Finalizing Visuals and Audio** I'm now integrating the final visual flair for the initial 3-second screenshot, focusing on dynamic demo mode gameplay with particles and the start prompt. I'm also setting up the Web Audio API for synthesized sounds, ensuring audio contexts are managed correctly to meet autoplay policies and preparing specific sound effects for bounces, hits, and explosions. **Designing Game Assets** I'm now mapping out the specific audio cues for gameplay events and detailing the brick layout. This includes defining sound effects for various actions, like losing a life or winning, and meticulously planning the dimensions and properties of each brick row, including their hit points and power-up drops. **Detailing Brick Properties** I'm now finalizing the specific attributes for each brick row, including hit points and point values. I've also detailed armored bricks with two hit points, which will crack visually after the first hit and have a distinct metallic sound effect. **Enhancing Power-Up Effects** I'm now detailing the visual and functional aspects of the power-ups. For wide paddle, I'm implementing a 10-second timer with a smooth visual shrink back. For multi-ball, I'm ensuring new balls spawn with independent physics and that lives are only lost when all active balls are gone. **Designing Game Effects** I'm now meticulously planning the visual and auditory feedback for gameplay. This involves detailing particle effects for brick shattering, ball trails, sparks on collision, and subtle screen shakes for impact. I'm also refining the logic for floating score and power-up notifications to enhance player feedback. **Implementing Game States** I'm now mapping out the game's different screens and their logic: demo mode with AI and a clear call to action, standard playing mode, game over, and victory. I'm also ensuring a clean restart process from any state back into active gameplay. **Designing Combo System** I'm detailing the new combo system where consecutive brick hits without paddle contact increase a multiplier for bonus points. This system resets upon a paddle bounce, accompanied by flashy combo text for visual feedback. **Designing Responsive Layout** I'm now focusing on the game's responsive layout, defining the canvas logical size and setting CSS properties for the wrapper container to ensure optimal scaling across various screen sizes. **Finalizing Layout and Styling** I'm now ensuring the canvas scales perfectly across all devices, using CSS for a responsive design and a cyberpunk aesthetic. Precise mouse and touch coordinate mapping is also confirmed, guaranteeing flawless input accuracy. **Developing AI for Demo** I'm now outlining the AI logic for the demo mode. It will prioritize intercepting the lowest falling ball or the lowest ball overall, and will also account for nearby power-ups within reach. The AI will calculate a target X position for the paddle, incorporating smooth interpolation for a more human-like movement. **Improving Demo AI** I'm now refining the demo AI's continuous play and graceful recovery. It will now automatically reset balls to prevent game over states and ensure the demo remains engaging. I'm also designing procedural cracks for tougher bricks, incorporating visual damage and electric-like fracture lines for a distinct aesthetic. **Designing Power-Up Icons** I'm now detailing the visual design for power-up icons within bricks and their dropped capsules. For paddle expansion, I'm using crisp vector arrows, and for multiball, three small glowing dots. The dropped capsules will have a pulsing glow, subtle rotation, and faint trailing sparkles. **Visualizing Power-Up Capsules** I'm detailing the visual appearance of dropped power-up capsules, aiming for a pill shape with a pulsing glow and gradient fill. They'll feature distinct icons for their effect, like arrows for expansion or three dots for multiball, and will sway downwards with faint trailing sparkles. **Synthesizing Game Sounds** I'm now designing the sound synthesizer using the Web Audio API to create rich, internal sound effects. This includes functions for paddle bounces with exponential ramps, wall bounces with short clicks, and brick hits with distinct metallic clangs for tough bricks and punchy noise bursts for destruction. **Synthesizing Sound Effects** I'm now focusing on synthesizing the game's audio. I'm creating distinct sounds for power-up spawns, collections with upward arpeggios, life loss using descending saw waves, victory fanfares, and game over jingles. I'm also refining paddle controls for smooth keyboard acceleration and precise mouse snapping, clamping paddle movement within canvas bounds. **Calculating Paddle Bounce** I'm refining the ball's bounce off the paddle, translating `hitOffset` into precise `angle` for dynamic deflection. Hitting the center sends the ball straight up, while edge impacts create sharp sideways angles, creating intuitive gameplay. I'm also continuing to refine sub-stepping for robust collision detection. **Calculating Paddle Bounce** I'm now refining the ball's collision with the paddle. I'm using the `hitOffset` to precisely control the bounce angle, ensuring a dynamic feel. I'm also implementing a slight speed increase on paddle hits and continuing to integrate sub-stepping for more reliable collision detection. **Calculating Bounce Directions** **Calculating Bounce Directions** I'm refining ball-brick collision by precisely calculating bounce directions. Based on the minimum penetration depth from each side, I'm ensuring accurate velocity reflection to prevent the ball from getting stuck, and I'm finalizing the brick layout details including dimensions and margins. **Finalizing Brick Properties** I'm now meticulously defining the attributes for each row of bricks, including their hit points and point values. Armored bricks, with their two hit points, will visually crack and emit a metallic sound after the first hit, then shatter with distinct fragments and a punchy noise on the second. **Finalizing Power-Up Effects** I'm now detailing the visual and functional aspects of power-ups. For wide paddle, I'm implementing a 10-second timer with a smooth visual shrink back and a glowing pill badge HUD. For multi-ball, I'm ensuring new balls spawn with independent physics and that lives are only lost when all active balls are gone, with a neon magenta text notification. **Starting Game Play** I'm finalizing the transition from demo mode to active play. Now, clicking or pressing any key instantly initiates a new game, granting three lives and resetting the score, and the ball is immediately ready to launch with player control. **Refining Demo AI** I'm now enhancing the demo AI to ensure continuous play and graceful recovery. It will automatically reset balls to prevent game over states, keeping the demo engaging. I'm also designing procedural cracks for tougher bricks, incorporating visual damage and electric-like fracture lines for a distinct aesthetic. **Generating Visual Effects** I'm now focused on the visual flair, detailing particle generation for brick breaks, paddle sparks, and power-up catches. I'm also refining the ball trail effect by tracking its history for a smooth, fading line and considering the HUD's sleek, monospace font for score and combo displays. **Designing UI Elements** I'm now outlining the visual elements for game over and victory screens, including score displays and restart buttons. I'm also designing HUD elements for active power-ups with timers and considering icons for lives. **Refining Input Handling** I'm now ensuring robust input handling across mouse, keyboard, and touch, clamping movement within canvas bounds and supporting simultaneous inputs for a seamless experience. **Ensuring Responsive Input** I'm now ensuring smooth, responsive input across all devices. For mobile, I've disabled default scrolling during touch drag for the paddle. I've also added robust event listeners for clicks, touches, and key presses to initiate the game and correctly manage the AudioContext. **Generating Brick Cracks** I'm now focused on procedurally generating visual cracks for tougher bricks. The logic involves creating jagged fracture branches that start from randomized points within the brick, contributing to a distinct, damaged aesthetic for armored bricks. **Visualizing Brick Details** I'm now defining the visual appearance of our bricks, aiming for a glossy 3D look with gradients and borders. Tough bricks will have an armored aesthetic with distinct cracks and electric fracture lines when damaged. Power-up icons will be clearly visible on their respective bricks. **Crafting Power-Up Visuals** I'm now detailing the visual design of the power-up capsules. They'll be pill-shaped with a pulsing glow and a gradient fill, featuring distinct icons like arrows for expansion or three dots for multiball. These capsules will sway downwards, leaving faint sparkling trails. **Implementing Power-Up Logic** I'm now detailing the functional aspects of power-ups. For multi-ball, new balls spawn with independent physics, and lives are only lost when all active balls are gone, with a neon magenta text notification. I'm also ensuring the paddle expansion power-up has a 10-second timer with a smooth visual shrink back and a glowing HUD badge. **Testing Game End Conditions** I'm now thoroughly testing the ball loss and lives logic, ensuring accurate removal of off-screen balls and proper life deduction. The victory condition is also being verified by checking the remaining brick count. I'm implementing seamless demo respawns and distinct game over/victory states with appropriate audio cues. **Adding End Game Bonuses** I'm now incorporating end-game bonuses, adding life bonuses to the score. I've also implemented the victory fanfare sound and continuous victory fireworks particle effects. **Refining Paddle Bounce** I'm refining the ball's bounce off the paddle to ensure precise angles based on the hit offset, with center impacts going straight up and edge impacts deflecting sharply. I'm also testing simultaneous mouse and keyboard input, prioritizing keyboard if both are active, and ensuring seamless transitions from demo mode. **Initiating Game Start** I'm now refining how the game transitions from demo to active play. A click or key press will immediately launch the game with full player control, resetting lives and score, and also manage the AudioContext to ensure sounds play correctly. **Generating Game Start** I'm now preparing the initial game state, focusing on the dynamic demo mode. The AI will track the ball and display a stylish overlay card with clear instructions to start the game, ensuring a smooth transition to player control. **Initializing Audio System** I'm now finalizing the audio system's initialization to ensure it correctly handles the AudioContext, resuming it on user interaction to allow sound playback. This setup is crucial for smooth transitions from the demo mode to active gameplay, ensuring all synthesized sounds function without errors. **Synthesizing Game Sounds** I'm synthesizing game sounds using the Web Audio API. This involves creating distinct tones for paddle bounces, wall collisions, and brick impacts, with specific metallic clangs for tougher bricks and punchy noise bursts for destruction. **Synthesizing Brick Sounds** I'm synthesizing distinct sounds for brick breaks. Tough bricks now have a metallic clang, while standard bricks emit a crunching noise. I'm also adding white noise puffs for destruction and refining the audio context to ensure sound playback is always running. **Synthesizing Game Sounds** I'm synthesizing game sounds using the Web Audio API, creating distinct tones for paddle bounces, wall collisions, and brick impacts. Tough bricks now have a metallic clang, standard bricks a crunching noise, and destruction a white noise puff. The audio context is also continuously managed. **Coloring Bricks Neon** I'm defining the visual palette for our bricks, aiming for a vibrant neon aesthetic. This includes specific hex codes and glow colors for each row, with tougher bricks featuring distinct gold and platinum hues, metallic accents, and a glossy 3D look with gradients. **Crafting Brick Visuals** I'm defining the visual hierarchy of bricks, from gold armor at the top to electric violet at the bottom, for an arcade feel. I'm also implementing visual cracks for tough bricks when they have one hit point remaining, making their damaged state instantly recognizable. **Visualizing Game Effects** I'm now implementing visual feedback for scoring, combos, and power-up collections with upward floating text that fades out, adding great reward. Screen shake is also being implemented to enhance the physical satisfaction of breaking bricks. The ball will have a bright white core with a neon glow and a dynamic trail effect at high speeds. **Crafting Paddle Details** I'm now focusing on the visual and interactive details of the paddle and power-ups. I'm implementing engine thruster particles for a hovercraft feel, and squash and stretch effects on the paddle when the ball hits it. I'm also detailing power-up timers, HUD countdowns, and the smooth resizing of the paddle. **Enhancing Power-Up Displays** I'm now refining how power-ups are displayed and managed. This includes implementing HUD countdown bars for timed effects like paddle expansion, with visual cues like pulsing gauges. I'm also detailing the physics and spawning logic for multiple balls when the multi-ball power-up is collected. **Designing Game Over UI** I'm designing the game over screen with a dark vignette overlay, featuring a bold "GAME OVER" title in a glowing neon font. Below this, I'm displaying the final score and a prominent "Press any key to restart" prompt, with a subtle pulsating effect. **Designing End Screens** I'm now designing the "Game Over" and "Victory" screens, incorporating specific fonts, colors, and glowing effects. I'm also detailing the interactive restart prompts and ensuring a seamless, glitch-free transition back to gameplay from either state. **Verifying Responsive Scaling** I've confirmed the canvas scales perfectly across various viewports, from desktop to mobile. CSS properties ensure the game container maintains its aspect ratio and fits within screen limits. Mouse and touch input are accurately mapped regardless of scale, ensuring a seamless experience everywhere. **Preparing Demo for Screenshot** I'm now optimizing the demo mode for the crucial 3-second screenshot. The game will start immediately in demo state, with the ball at full speed, actively breaking bricks, and power-ups already dropping. A clear "Click or press a key to start" prompt will overlay this action, ensuring the judging criteria are met visually from the first paint. **Implementing Demo Mode AI** **Playing Game** I'm now refining the transition to active gameplay. A click or key press immediately launches the game with full player control, resetting lives and score. The ball remains attached to the paddle, displaying an aiming line and a clear launch prompt, with an auto-launch after 3.5 seconds if no input is detected. **Optimizing Tunneling Prevention** I'm ensuring robust anti-tunneling by implementing sub-stepping for ball movement. With a step size of 2.5 pixels and a ball radius of 7 pixels, I'm ensuring collisions are accurately detected even at high speeds, preventing the ball from passing through bricks or walls. **Improving Collision Logic** I'm refining how the ball interacts with bricks, ensuring it deflects correctly even when hitting near a corner. My current focus is on handling simultaneous impacts with multiple bricks by processing them sequentially within each sub-step, preventing tunneling and ensuring consistent physics. **Refining Ball Physics** I'm refining the ball's physics, ensuring its bounce angles off the paddle are dynamic and dependent on the impact point. I'm also implementing a subtle acceleration on paddle hits and continuing to integrate sub-stepping to ensure reliable collision detection, preventing the ball from tunneling through game elements. **Integrating Power-Up Logic** I'm detailing how power-ups function now. Bricks will visually display icons, and upon destruction, they'll drop a falling capsule. Catching this capsule will trigger effects like a temporary wider paddle or the spawning of multiple balls. **Adding Victory Fireworks** I'm now implementing celebratory fireworks for victory states, emitting colorful sparks at random intervals to enhance the celebratory feel. **Optimizing for High DPI** I'm ensuring sharp graphics on high-DPI displays by scaling the canvas resolution and context based on the device's pixel ratio, while maintaining consistent logical coordinates. **Reviewing Layout and Input** I'm confirming the canvas sizing and ensuring arrow keys and spacebar don't scroll the page, especially within iframes. My UI font stack is set, and I've detailed the HUD for lives and score, including point values for various actions and the combo multiplier. The HTML structure is also being finalized.