Nemotron 3.5 Lightning (free)
by NVIDIAReasoning1 console error
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 1 min 31 s
- Cost
- $0
- Output tokens
- 7,731
- Spent thinking
- 560 tokens
- Speed
- 85 tok/s
- Console errors
- 1
- First token after
- 239 ms
- First visible text after
- 7.2 s
- Prompt tokens
- 531
- Code size
- 24.9 KB
- Finish reason
- stop
- Served by
- Nvidia
- Model id
nvidia/nemotron-3.5-lightning:free- Reasoning sent
model default- Max output
- 32,000 tokens
- Generated
- Nov 9, 2026
Console errors
Caught in the first 3 seconds after load, in a sandboxed iframe like the one above.
Cannot read properties of undefined (reading 'addEventListener')
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 · 892 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Breakout</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
canvas {
display: block;
}
body {
background: linear-gradient(180deg, #0a0a0f 0%, #1a1a24 100%);
overflow-x: hidden;
min-height: 100vh;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
#ui {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
#title {
position: fixed;
top: 40px;
left: 50%;
transform: translateX(-50%);
color: #fff;
font-size: 28px;
font-weight: 600;
letter-spacing: 4px;
text-transform: uppercase;
text-shadow: 0 0 20px rgba(255, 200, 50, 0.5);
pointer-events: auto;
transition: opacity 0.3s;
}
#status {
position: fixed;
top: 100px;
left: 50%;
transform: translateX(-50%);
color: #e0e0e0;
font-size: 14px;
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
pointer-events: auto;
min-width: 200px;
text-align: center;
}
#start-instruction {
position: fixed;
bottom: 40px;
left: 50%;
transform: translateX(-50%);
color: #666;
font-size: 13px;
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
pointer-events: auto;
transition: opacity 0.3s;
}
#score {
position: fixed;
top: 40px;
left: 20px;
color: #fff;
font-size: 14px;
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
pointer-events: auto;
}
#lives {
position: fixed;
top: 40px;
right: 20px;
color: #fff;
font-size: 14px;
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
pointer-events: auto;
}
#game-over, #victory {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(10, 10, 15, 0.95);
display: none;
align-items: center;
justify-content: center;
flex-direction: column;
pointer-events: auto;
}
#game-over.active, #victory.active {
display: flex;
}
.over-title {
color: #fff;
font-size: 24px;
margin-bottom: 20px;
text-shadow: 0 4px 12px rgba(0,0,0,0.5);
}
.final-score {
color: #ffc800;
font-size: 16px;
margin-bottom: 40px;
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
}
.btn {
padding: 12px 30px;
font-size: 16px;
font-weight: 500;
border: none;
border-radius: 4px;
cursor: pointer;
background: linear-gradient(135deg, #ff6b35, #feb422);
color: #fff;
text-transform: uppercase;
letter-spacing: 1px;
transition: transform 0.1s, box-shadow 0.1s;
}
.btn:active {
transform: scale(0.98);
}
.btn:hover {
box-shadow: 0 4px 20px rgba(255, 107, 53, 0.4);
}
.btn-restart {
background: linear-gradient(135deg, #4facfe, #00f2fe);
}
</style>
</head>
<body>
<div id="ui">
<div id="title">Breakout</div>
<div id="status"></div>
<div id="score">Score: 0</div>
<div id="lives">Lives: 3</div>
<div id="start-instruction">Click or press a key to start</div>
<div id="game-over">
<div class="over-title">Game Over</div>
<div class="final-score" id="final-score">0</div>
<button class="btn btn-restart" id="restart-btn">Restart</button>
</div>
<div id="victory">
<div class="over-title">Victory!</div>
<div class="final-score" id="final-score-victory">0</div>
<button class="btn btn-restart" id="restart-btn-victory">Restart</button>
</div>
</div>
<canvas id="gameCanvas"></canvas>
<script>
// Game configuration
const CONFIG = {
canvasWidth: 1200,
canvasHeight: 800,
paddleWidth: 140,
paddleHeight: 20,
paddleSpeed: 8,
ballRadius: 12,
ballSpeed: 6,
brickRowCount: 6,
brickColumnCount: 8,
brickWidth: 130,
brickHeight: 30,
brickPadding: 8,
brickOffsetTop: 60,
brickOffsetLeft: 60,
lives: 3,
particlesCount: 15
};
// Game state
let canvas, ctx;
let score = 0;
let lives = CONFIG.lives;
let gameStarted = false;
let gamePaused = false;
let particles = [];
let powerUps = [];
let balls = [];
// Paddle state
let paddle = {
x: 0,
y: 0,
width: CONFIG.paddleWidth,
height: CONFIG.paddleHeight
};
// Ball state
let ball = {
x: 0,
y: 0,
radius: CONFIG.ballRadius,
dx: 0,
dy: 0
};
// Bricks
let bricks = [];
// Initialize canvas
function initCanvas() {
canvas = document.getElementById('gameCanvas');
ctx = canvas.getContext('2d');
// Set canvas size with pixel ratio
const dpr = window.devicePixelRatio || 1;
canvas.width = CONFIG.canvasWidth * dpr;
canvas.height = CONFIG.canvasHeight * dpr;
canvas.style.width = `${CONFIG.canvasWidth}px`;
canvas.style.height = `${CONFIG.canvasHeight}px`;
ctx.scale(dpr, dpr);
// Responsive handling
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
}
function resizeCanvas() {
const width = Math.min(1280, window.innerWidth - 20);
const height = Math.max(600, window.innerHeight - 80);
const ratio = width / CONFIG.canvasWidth;
canvas.style.width = `${width}px`;
canvas.style.height = `${height * ratio}px`;
}
// Color palette
const colors = {
normal: '#ff6b35',
tough: '#e8c545',
powerup: '#4facfe',
background: '#0a0a0f',
uiBackground: 'rgba(10, 10, 15, 0.8)'
};
// Brick types: 0 = normal, 1 = tough, 2 = powerup
function createBricks() {
bricks = [];
const totalBricks = CONFIG.brickRowCount * CONFIG.brickColumnCount;
let typeCount = { normal: 0, tough: 0, powerup: 0 };
for (let row = 0; row < CONFIG.brickRowCount; row++) {
bricks[row] = [];
for (let col = 0; col < CONFIG.brickColumnCount; col++) {
// Determine brick type - mix it up
let type;
const rand = Math.random();
if (row < 2) {
type = 0; // normal in first 2 rows
typeCount.normal++;
} else if (row < 4) {
// Mix: 60% normal, 40% tough in middle rows
type = Math.random() > 0.4 ? 0 : 1;
if (type === 0) typeCount.normal++;
else typeCount.tough++;
} else {
type = Math.random() > 0.3 ? 0 : 1;
if (type === 0) typeCount.normal++;
else typeCount.tough++;
}
// Occasionally add powerup to tough bricks
if (type === 1 && Math.random() > 0.7 && typeCount.tough > 4) {
type = 2; // powerup
typeCount.powerup++;
typeCount.tough--;
}
bricks[row][col] = {
x: col * (CONFIG.brickWidth + CONFIG.brickPadding) + CONFIG.brickOffsetLeft,
y: row * (CONFIG.brickHeight + CONFIG.brickPadding) + CONFIG.brickOffsetTop,
width: CONFIG.brickWidth,
height: CONFIG.brickHeight,
status: 1, // 1 = intact, 0 = destroyed
type: type,
cracks: 0 // for tough bricks
};
}
}
return typeCount;
}
// Initialize game objects
function initGame() {
score = 0;
lives = CONFIG.lives;
createBricks();
// Paddle position
paddle.x = (CONFIG.canvasWidth - CONFIG.paddleWidth) / 2;
paddle.y = CONFIG.canvasHeight - 60;
// Ball position and serve
ball.x = CONFIG.canvasWidth / 2;
ball.y = CONFIG.canvasHeight / 2;
ball.dx = CONFIG.ballSpeed * (Math.random() > 0.5 ? 1 : -1);
ball.dy = -CONFIG.ballSpeed;
balls = [{ x: ball.x, y: ball.y, dx: ball.dx, dy: ball.dy }];
particles = [];
powerUps = [];
// Hide UI elements
document.getElementById('start-instruction').style.opacity = '1';
document.getElementById('title').style.opacity = '1';
}
// Handle input - mouse
let mouseX = 0;
canvas.addEventListener('mousemove', (e) => {
if (!gameStarted) return;
const rect = canvas.getBoundingClientRect();
mouseX = e.clientX - rect.left;
updatePaddlePosition(mouseX);
});
// Handle input - keyboard
let leftPressed = false;
let rightPressed = false;
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowLeft') {
leftPressed = true;
} else if (e.key === 'ArrowRight') {
rightPressed = true;
}
if (!gameStarted && (e.key === ' ' || e.keyCode >= 37 && e.keyCode <= 40)) {
gameStarted = true;
document.getElementById('start-instruction').style.opacity = '0';
document.getElementById('title').style.opacity = '0';
}
});
document.addEventListener('keyup', (e) => {
if (e.key === 'ArrowLeft') {
leftPressed = false;
} else if (e.key === 'ArrowRight') {
rightPressed = false;
}
});
// Update paddle position based on control method
function updatePaddlePosition(targetX) {
// Smooth movement to mouse position
const speed = 0.15;
paddle.x += (targetX - paddle.x - CONFIG.paddleWidth / 2) * speed;
// Keep paddle in bounds
if (paddle.x < 0) paddle.x = 0;
if (paddle.x + CONFIG.paddleWidth > CONFIG.canvasWidth) {
paddle.x = CONFIG.canvasWidth - CONFIG.paddleWidth;
}
}
function updatePaddleKeys() {
if (!gameStarted) return;
if (leftPressed) {
paddle.x -= CONFIG.paddleSpeed;
}
if (rightPressed) {
paddle.x += CONFIG.paddleSpeed;
}
// Keep paddle in bounds
if (paddle.x < 0) paddle.x = 0;
if (paddle.x + CONFIG.paddleWidth > CONFIG.canvasWidth) {
paddle.x = CONFIG.canvasWidth - CONFIG.paddleWidth;
}
}
// Spawn particles when brick breaks
function spawnParticles(brick) {
const centerX = brick.x + brick.width / 2;
const centerY = brick.y + brick.height / 2;
for (let i = 0; i < CONFIG.particlesCount; i++) {
const angle = (Math.PI * 2 * i) / CONFIG.particlesCount;
const velocity = 3 + Math.random() * 3;
particles.push({
x: centerX,
y: centerY,
dx: Math.cos(angle) * velocity,
dy: Math.sin(angle) * velocity,
radius: 2 + Math.random() * 3,
color: brick.type === 2 ? '#4facfe' : (brick.type === 1 ? '#e8c545' : '#ff6b35'),
opacity: 1,
friction: 0.99
});
}
}
// Spawn power-up
function spawnPowerUp(brick) {
const centerX = brick.x + brick.width / 2;
const centerY = brick.y + brick.height / 2;
// 50% chance of wide paddle, 50% chance of multi-ball
const type = Math.random() > 0.5 ? 'wide' : 'multi';
powerUps.push({
x: centerX - 15,
y: centerY - 15,
width: 30,
height: 30,
type: type,
dy: 2
});
}
// Draw paddle
function drawPaddle() {
// Handle wide paddle power-up
const displayWidth = paddle.width * (paddle.isWide ? 1.5 : 1);
ctx.fillStyle = '#fff';
ctx.shadowColor = '#ff6b35';
ctx.shadowBlur = 15;
ctx.fillRect(paddle.x, paddle.y, displayWidth, paddle.height);
ctx.shadowBlur = 0;
}
// Draw ball
function drawBall(ballObj) {
ctx.fillStyle = '#fff';
ctx.shadowColor = '#fff';
ctx.shadowBlur = 15;
ctx.beginPath();
ctx.arc(ballObj.x, ballObj.y, ballObj.radius, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
}
// Draw bricks
function drawBricks() {
for (let row = 0; row < CONFIG.brickRowCount; row++) {
for (let col = 0; col < CONFIG.brickColumnCount; col++) {
const brick = bricks[row][col];
if (brick.status === 0) continue;
const brickX = brick.x;
const brickY = brick.y;
// Determine color based on type
let color;
if (brick.type === 2) {
color = '#ff6b35'; // powerup bricks are orange with indicator
} else if (brick.type === 1) {
color = '#e8c545'; // tough bricks are gold
} else {
color = '#ff6b35'; // normal bricks are orange
}
ctx.fillStyle = color;
ctx.shadowColor = color;
ctx.shadowBlur = 5;
// Draw brick
ctx.fillRect(brickX, brickY, brick.width, brick.height);
// Draw crack pattern for tough bricks that have been hit
if (brick.type === 1 && brick.cracks > 0) {
ctx.strokeStyle = 'rgba(0,0,0,0.5)';
ctx.lineWidth = 2;
// Horizontal crack
ctx.beginPath();
ctx.moveTo(brickX + 10, brickY + brick.height / 2);
ctx.lineTo(brickX + brick.width - 10, brickY + brick.height / 2);
ctx.stroke();
// Vertical crack (if heavily cracked)
if (brick.cracks >= 2) {
ctx.beginPath();
ctx.moveTo(brickX + brick.width / 2, brickY + 10);
ctx.lineTo(brickX + brick.width / 2, brickY + brick.height - 10);
ctx.stroke();
}
// Number cracks
if (brick.cracks >= 3) {
ctx.beginPath();
ctx.moveTo(brickX + 15, brickY + 15);
ctx.lineTo(brickX + brick.width - 15, brickY + brick.height - 15);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(brickX + brick.width - 15, brickY + 15);
ctx.lineTo(brickX + 15, brickY + brick.height - 15);
ctx.stroke();
}
}
// Draw power-up icon on powerup bricks
if (brick.type === 2) {
ctx.fillStyle = '#fff';
ctx.font = 'bold 16px system-ui';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillIcon(brickX + brick.width / 2, brickY + brick.height / 2, '⚡');
}
ctx.shadowBlur = 0;
}
}
}
// Helper to draw icon text (since we can't use SVG)
function fillIcon(x, y, text) {
// This will be called in the draw loop, we'll handle it differently
// Actually let's just draw a simple shape instead
ctx.fillStyle = '#fff';
ctx.beginPath();
// Draw a simple triangle/arrow
ctx.moveTo(x - 8, y - 6);
ctx.lineTo(x + 8, y - 6);
ctx.lineTo(x, y + 6);
ctx.closePath();
ctx.fill();
}
// Draw power-ups
function drawPowerUps() {
powerUps.forEach((pu, pi) => {
ctx.fillStyle = '#4facfe';
ctx.shadowColor = '#4facfe';
ctx.shadowBlur = 10;
ctx.beginPath();
ctx.arc(pu.x, pu.y, 12, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
// Draw icon based on type
ctx.fillStyle = '#fff';
ctx.font = 'bold 18px system-ui';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
if (pu.type === 'wide') {
ctx.fillText('↔', pu.x, pu.y);
} else {
ctx.fillText('★', pu.x, pu.y);
}
});
}
// Draw particles
function drawParticles() {
particles.forEach((p, pi) => {
ctx.fillStyle = p.color;
ctx.globalAlpha = p.opacity;
ctx.beginPath();
ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
ctx.fill();
p.x += p.dx;
p.y += p.dy;
p.dx *= p.friction;
p.dy *= p.friction;
p.opacity -= 0.02;
});
ctx.globalAlpha = 1;
}
// Draw score and lives
function drawUI() {
ctx.fillStyle = '#fff';
ctx.font = '14px system-ui';
ctx.fillText(`Score: ${score}`, 20, 35);
ctx.fillText(`Lives: ${lives}`, CONFIG.canvasWidth - 80, 35);
}
// Draw start instruction
function drawStartInstruction() {
ctx.fillStyle = '#666';
ctx.font = '13px system-ui';
ctx.textAlign = 'center';
ctx.fillText('Click or press a key to start', CONFIG.canvasWidth / 2, CONFIG.canvasHeight - 40);
}
// Draw game over
function drawGameOver() {
const goDiv = document.getElementById('game-over');
goDiv.style.display = 'flex';
}
// Draw victory
function drawVictory() {
const finalScoreEl = document.getElementById('final-score-victory');
finalScoreEl.textContent = score;
const victoryDiv = document.getElementById('victory');
victoryDiv.style.display = 'flex';
}
// Reset ball to center
function resetBall() {
ball.x = CONFIG.canvasWidth / 2;
ball.y = CONFIG.canvasHeight / 2;
ball.dx = CONFIG.ballSpeed * (Math.random() > 0.5 ? 1 : -1);
ball.dy = -CONFIG.ballSpeed;
}
// Check ball-paddle collision
function checkPaddleCollision(ballObj) {
if (ballObj.y + ballObj.radius > paddle.y &&
ballObj.y + ballObj.radius < paddle.y + paddle.height) {
if (ballObj.x > paddle.x && ballObj.x < paddle.x + paddle.width) {
// Calculate hit position (-1 to 1 from left to right)
const paddleCenter = paddle.x + paddle.width / 2;
const hitPos = (ballObj.x - paddleCenter) / (paddle.width / 2);
// Angle depends on where ball hits paddle
const maxAngle = Math.PI / 6; // 30 degrees
const angle = hitPos * maxAngle;
const speed = Math.sqrt(ballObj.dx * ballObj.dx + ballObj.dy * ballObj.dy);
ballObj.dx = speed * Math.sin(angle);
ballObj.dy = -speed * Math.cos(angle);
return true;
}
}
return false;
}
// Check brick collision
function checkBrickCollision(ballObj) {
for (let row = 0; row < CONFIG.brickRowCount; row++) {
for (let col = 0; col < CONFIG.brickColumnCount; col++) {
const brick = bricks[row][col];
if (brick.status === 0) continue;
if (ballObj.x + ballObj.radius > brick.x &&
ballObj.x - ballObj.radius < brick.x + brick.width &&
ballObj.y + ballObj.radius > brick.y &&
ballObj.y - ballObj.radius < brick.y + brick.height) {
// Hit! Determine which side was hit
const brickCenterX = brick.x + brick.width / 2;
const brickCenterY = brick.y + brick.height / 2;
// Calculate collision normal
let hitSide = null;
const dx = Math.abs(ballObj.x - brickCenterX) - brick.width / 2;
const dy = Math.abs(ballObj.y - brickCenterY) - brick.height / 2;
if (dx > dy) {
hitSide = 'vertical'; // Hit left or right side
} else {
hitSide = 'horizontal'; // Hit top or bottom
}
// Destroy brick based on type
if (brick.type === 2) {
// Powerup brick - always drops powerup, destroys in 1 hit
brick.status = 0;
score += 50;
spawnPowerUp(brick);
} else if (brick.type === 1) {
// Tough brick - needs 2 hits
brick.cracks++;
if (brick.cracks >= 3) {
brick.status = 0;
score += 70;
spawnParticles(brick);
} else {
score += 30;
}
} else {
// Normal brick
brick.status = 0;
score += 40;
// 20% chance of powerup
if (Math.random() > 0.8) {
spawnPowerUp(brick);
}
}
// Reverse ball direction
if (hitSide === 'horizontal') {
ballObj.dy = -ballObj.dy;
} else {
ballObj.dx = -ballObj.dx;
}
// Add particle effect
spawnParticles(brick);
return true;
}
}
}
return false;
}
// Check power-up collision with paddle
function checkPowerUpCollision() {
powerUps.forEach((pu, pi) => {
if (pu.y + pu.height > paddle.y &&
pu.y < paddle.y + paddle.height &&
pu.x + pu.width > paddle.x &&
pu.x < paddle.x + paddle.width) {
// Apply power-up
if (pu.type === 'wide') {
paddle.isWide = true;
setTimeout(() => {
paddle.isWide = false;
}, 10000);
} else if (pu.type === 'multi') {
// Add 2 extra balls
for (let i = 0; i < 2; i++) {
balls.push({
x: ball.x,
y: ball.y,
dx: ball.dx + (Math.random() - 0.5) * 2,
dy: ball.dy
});
}
}
powerUps.splice(pi, 1);
}
});
}
// Update all balls
function updateBalls() {
// Update each ball
for (let i = balls.length - 1; i >= 0; i--) {
const ballObj = balls[i];
// Move ball
ballObj.x += ballObj.dx;
ballObj.y += ballObj.dy;
// Wall collision (left/right)
if (ballObj.x - ballObj.radius < 0 || ballObj.x + ballObj.radius > CONFIG.canvasWidth) {
ballObj.dx = -ballObj.dx;
ballObj.x = Math.max(ballObj.radius, Math.min(CONFIG.canvasWidth - ballObj.radius, ballObj.x));
}
// Wall collision (top)
if (ballObj.y - ballObj.radius < 0) {
ballObj.dy = -ballObj.dy;
ballObj.y = ballObj.radius;
}
// Paddle collision
if (!checkPaddleCollision(ballObj)) {
// Check brick collision
if (!checkBrickCollision(ballObj)) {
// Check if ball fell below paddle
if (ballObj.y + ballObj.radius > CONFIG.canvasHeight) {
// Lose this ball
balls.splice(i, 1);
// If no balls left, lose life
if (balls.length === 0) {
lives--;
document.getElementById('lives').textContent = `Lives: ${lives}`;
if (lives <= 0) {
drawGameOver();
return;
}
// Reset balls
resetBall();
balls.push({ ...ball });
}
}
}
}
}
}
// Update power-ups
function updatePowerUps() {
powerUps.forEach((pu, pi) => {
pu.y += pu.dy;
// Remove if off screen
if (pu.y > CONFIG.canvasHeight) {
powerUps.splice(pi, 1);
}
});
}
// Update particles
function updateParticles() {
// Particles are drawn and updated in drawParticles
}
// Check victory condition
function checkVictory() {
let remaining = 0;
for (let row = 0; row < CONFIG.brickRowCount; row++) {
for (let col = 0; col < CONFIG.brickColumnCount; col++) {
if (bricks[row][col].status === 1) remaining++;
}
}
if (remaining === 0) {
drawVictory();
}
}
// Main game loop
function gameLoop() {
// Clear canvas
ctx.fillStyle = '#0a0a0f';
ctx.fillRect(0, 0, CONFIG.canvasWidth, CONFIG.canvasHeight);
if (gameStarted && !gamePaused) {
// Update paddle position (mouse or keys)
updatePaddleKeys();
// Update all balls
updateBalls();
// Update power-ups
updatePowerUps();
// Check power-up collection
checkPowerUpCollision();
// Check victory
checkVictory();
}
// Draw everything
drawBricks();
drawPowerUps();
// Draw all balls
balls.forEach(drawBall);
drawPaddle();
drawUI();
if (!gameStarted) {
drawStartInstruction();
}
drawParticles();
}
// Start button / restart
document.getElementById('restart-btn').addEventListener('click', () => {
document.getElementById('game-over').style.display = 'none';
initGame();
});
document.getElementById('restart-btn-victory').addEventListener('click', () => {
document.getElementById('victory').style.display = 'none';
initGame();
});
// Initialize and start
initCanvas();
initGame();
// Game loop
setInterval(gameLoop, 16);
</script>
</body>
</html>Reasoning trace (560 tokens)
Here's my plan for the Breakout game: 1. **Core Structure**: Single HTML file with canvas, vanilla JS 2. **Game Elements**: - Paddle (mouse + arrow keys control) - Ball with physics - Bricks (6 rows, mixed normal/tough bricks that crack) - Power-ups (wider paddle, multi-ball) - Particles on brick break - Lives, score - Game over / victory screens 3. **Key Features**: - Angle depends on hit position on paddle - Cracked tough bricks visible - Power-up dropping and catching - Particles - 3 lives - Self-playing demo mode initially 4. **Constraints**: - No external files, images, fonts - Everything inline - Responsive (1280×800 to 375px) - No localStorage/cookies/alert/confirm/prompt - Must work in sandbox - Auto-play demo mode behind "Click or press a key to start" Let me build this step by step, keeping it all in one file with inline CSS/JS, using system fonts, and making it visually appealing without external assets. For bricks: I'll use colored squares with different opacities/patterns indicated by their appearance. For tough bricks, I'll draw a crack pattern. For power-ups, I'll use simple shapes/icons drawn on the bricks. For particles: simple fading circles with random colors. For power-ups: wider paddle (size increase with timer) and multi-ball (split ball into 3). Let me code this now. I'll need to be careful about: - The canvas being responsive - No horizontal scrollbar - Demo mode with simple AI - Keyboard/mouse controls - All the game mechanics Let me structure the code: 1. HTML skeleton with canvas 2. CSS for full-page canvas, no scrollbar 3. JS game logic For the "images" - I'll draw everything with canvas API. For brick patterns, I'll use different fill colors and maybe simple stripe patterns drawn with rects. For power-up icons, I'll draw simple shapes (triangle for wide paddle, star for multi-ball). Let me think about the brick layout: - 6 rows - Mix of normal and tough bricks - Some with power-up icons I'll make about 8 bricks per row = 48 bricks total, with maybe 1/3 tough, 1/3 with power-ups. For the demo mode: I'll have a simple AI that moves the paddle to follow the ball's predicted position. Let me code this now.