Xing4.0 29B A4B (free)
by XingChenReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 2 min 32 s
- Cost
- $0
- Output tokens
- 11,412
- Spent thinking
- 457 tokens
- Speed
- 75.8 tok/s
- Console errors
- 0
- First token after
- 1.7 s
- First visible text after
- 13.7 s
- Prompt tokens
- 537
- Code size
- 38.5 KB
- Finish reason
- stop
- Served by
- Phala
- Model id
xingchen-agi/xing4.0-29b-a4b:free- Reasoning sent
model default- Max output
- 32,000 tokens
- Generated
- Nov 9, 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 · 1063 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Breakout</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: linear-gradient(135deg, #0f2027, #203a43, #2c5364);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
overflow: hidden;
}
#gameContainer {
position: relative;
width: 100%;
max-width: 1280px;
aspect-ratio: 16/9;
max-height: 100vh;
border: 3px solid rgba(255, 255, 255, 0.1);
border-radius: 10px;
box-shadow: 0 0 30px rgba(0, 200, 255, 0.3);
background: #0a1421;
}
canvas {
display: block;
width: 100%;
height: 100%;
image-rendering: pixelated;
}
#overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: rgba(10, 20, 33, 0.85);
z-index: 10;
color: white;
text-align: center;
transition: opacity 0.3s ease;
}
#overlay h1 {
font-size: clamp(2rem, 5vw, 4rem);
margin-bottom: 1rem;
background: linear-gradient(to right, #00dbde, #fc00ff);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
text-shadow: 0 0 20px rgba(0, 219, 222, 0.5);
}
#overlay p {
font-size: clamp(1rem, 2.5vw, 1.5rem);
margin: 0.5rem 0;
opacity: 0.9;
}
#overlay button {
margin-top: 1.5rem;
padding: 0.8rem 2.5rem;
font-size: 1.2rem;
background: linear-gradient(to right, #00c6ff, #0072ff);
border: none;
border-radius: 50px;
color: white;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
box-shadow: 0 4px 15px rgba(0, 114, 255, 0.4);
}
#overlay button:hover {
transform: translateY(-2px) scale(1.05);
box-shadow: 0 6px 20px rgba(0, 114, 255, 0.6);
}
.hidden {
opacity: 0;
pointer-events: none;
}
.instructions {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
color: rgba(255, 255, 255, 0.5);
font-size: clamp(0.8rem, 1.5vw, 1rem);
text-align: center;
pointer-events: none;
}
.controls-hint {
position: absolute;
top: 20px;
right: 20px;
color: rgba(255, 255, 255, 0.5);
font-size: clamp(0.7rem, 1.2vw, 0.9rem);
text-align: right;
}
</style>
</head>
<body>
<div id="gameContainer">
<canvas id="gameCanvas"></canvas>
<div id="overlay" class="hidden">
<h1>BRICK BREAKER</h1>
<p class="instructions">Use MOUSE or ARROW KEYS to move the paddle</p>
<p>Catch power-ups to gain advantages!</p>
<button onclick="startGame()">START GAME</button>
</div>
<div class="controls-hint">
<p>🖱️ Mouse | ⬅️➡️ Keys</p>
</div>
</div>
<script>
// ===================== GAME CONSTANTS =====================
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const overlay = document.getElementById('overlay');
let centerX, centerY;
let targetCanvasSize = { width: 800, height: 600 };
const difficulty = {
ballSpeed: 4.5,
paddleWidth: 100,
brickRows: 6,
brickCols: 10,
brickWidth: 70,
brickHeight: 25,
brickPadding: 8,
brickOffsetTop: 60,
brickOffsetLeft: 40
};
const colors = {
walls: '#0d4f5c',
paddle: '#00b4d8',
ball: '#ffffff',
normalBrick: '#ff9e00',
toughBrick: '#7c3aed',
crackedBrick: '#a78bfa',
particleColors: ['#ffeb3b', '#ff5722', '#9c27b0', '#4caf50', '#2196f3'],
powerUpWiden: '#00e5ff',
powerUpMulti: '#ff00e6'
};
// ===================== GAME STATE =====================
let game = {
state: 'MENU', // MENU, PLAYING, GAMEOVER, VICTORY
score: 0,
lives: 3,
keys: {},
mouseX: 0,
time: 0
};
let paddle = {
x: 0,
y: 0,
width: difficulty.paddleWidth,
height: 15,
baseWidth: difficulty.paddleWidth,
targetWidth: difficulty.paddleWidth,
speed: 0.15
};
let balls = [];
let bricks = [];
let particles = [];
let powerUps = [];
let activeEffects = {
widePaddle: null, // stores expiry time
multiBall: null // stores expiry time
};
// ===================== INITIALIZATION =====================
function init() {
// Canvas scaling for responsiveness
function resizeCanvas() {
const container = document.getElementById('gameContainer');
const rect = container.getBoundingClientRect();
const aspectRatio = rect.width / rect.height;
targetCanvasSize.width = rect.width - 6; // account for border
targetCanvasSize.height = rect.width / aspectRatio;
if (targetCanvasSize.height > 800) {
targetCanvasSize.height = 800;
targetCanvasSize.width = targetCanvasSize.height * aspectRatio;
}
canvas.width = Math.floor(targetCanvasSize.width);
canvas.height = Math.floor(targetCanvasSize.height);
centerX = canvas.width / 2;
centerY = canvas.height / 2;
// Recalculate positions
resetPaddle();
createBricks();
if (game.state === 'PLAYING') {
spawnBall();
}
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// Mouse control
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
game.mouseX = (e.clientX - rect.left) * scaleX;
});
// Keyboard controls
document.addEventListener('keydown', (e) => {
game.keys[e.key] = true;
if (game.state === 'PLAYING') {
// Prevent default scrolling
if (['ArrowLeft', 'ArrowRight'].includes(e.key)) {
e.preventDefault();
}
}
});
document.addEventListener('keyup', (e) => {
game.keys[e.key] = false;
});
// Initial setup
overlay.classList.remove('hidden');
drawMenuBackground();
}
function resetPaddle() {
paddle.x = canvas.width / 2 - paddle.width / 2;
paddle.y = canvas.height - 40;
}
function createBricks() {
bricks = [];
const rows = difficulty.brickRows;
const cols = difficulty.brickCols;
const brickWidth = difficulty.brickWidth;
const brickHeight = difficulty.brickHeight;
const padding = difficulty.brickPadding;
const offsetTop = difficulty.brickOffsetTop;
const offsetLeft = difficulty.brickOffsetLeft;
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
// Determine brick type based on position
const isTough = (r < 2 && (r % 2 === 0 || r % 3 === 0));
const isCrackedAfter = (r >= 3 && r < 5 && c % 2 === 0);
const x = c * (brickWidth + padding) + offsetLeft;
const y = r * (brickHeight + padding) + offsetTop;
bricks.push({
x, y, width: brickWidth, height: brickHeight,
row: r, col: c,
health: isTough ? 2 : 1,
broken: false,
cracked: false,
hasPowerUp: Math.random() < 0.15, // 15% chance per brick
type: isTough ? 'tough' : (isCrackedAfter ? 'mixed' : 'normal')
});
}
}
}
function spawnBall() {
balls = [{
x: centerX,
y: paddle.y - 10,
dx: 0,
dy: -difficulty.ballSpeed,
radius: 8,
active: true
}];
}
function startGame() {
game.state = 'PLAYING';
game.score = 0;
game.lives = 3;
game.time = 0;
particles = [];
powerUps = [];
bricks = [];
createBricks();
resetPaddle();
paddle.width = difficulty.paddleWidth;
paddle.baseWidth = difficulty.paddleWidth;
paddle.targetWidth = difficulty.paddleWidth;
activeEffects.widePaddle = null;
activeEffects.multiBall = null;
spawnBall();
overlay.classList.add('hidden');
requestAnimationFrame(gameLoop);
}
// ===================== GAME LOOP =====================
function gameLoop(timestamp) {
const dt = Math.min((timestamp - (game.lastTime || timestamp)) / 1000, 0.05);
game.lastTime = timestamp;
game.time += dt;
update(dt);
render();
if (game.state !== 'MENU') {
requestAnimationFrame(gameLoop);
}
}
// ===================== UPDATE =====================
function update(dt) {
if (game.state !== 'PLAYING') return;
// Update paddle
updatePaddle(dt);
// Update balls
for (let ball of balls) {
if (!ball.active) continue;
ball.x += ball.dx * dt * 60;
ball.y += ball.dy * dt * 60;
// Wall collisions
if (ball.x - ball.radius <= 0 || ball.x + ball.radius >= canvas.width) {
ball.dx = -ball.dx;
ball.x = ball.x - ball.radius <= 0 ? ball.radius : canvas.width - ball.radius;
createWallParticles(ball.x, ball.y);
}
if (ball.y - ball.radius <= 0) {
ball.dy = -ball.dy;
ball.y = ball.radius;
createWallParticles(ball.x, ball.y);
}
// Floor - lose life
if (ball.y + ball.radius >= canvas.height) {
ball.active = false;
game.lives--;
createDeathParticles(centerX, canvas.height - 20);
if (game.lives <= 0) {
setTimeout(() => endGame(), 300);
return;
} else {
// Reset ball after short delay
setTimeout(() => {
if (game.state === 'PLAYING') {
spawnBall();
}
}, 500);
}
}
// Paddle collision
if (ball.y + ball.radius >= paddle.y &&
ball.y - ball.radius <= paddle.y + paddle.height &&
ball.x >= paddle.x &&
ball.x <= paddle.x + paddle.width) {
// Calculate bounce angle based on hit position
const hitPos = (ball.x - (paddle.x + paddle.width/2)) / (paddle.width/2);
const angle = hitPos * (Math.PI/3); // Max 60 degrees from vertical
const speed = Math.hypot(Math.abs(ball.dx), Math.abs(ball.dy));
ball.dx = speed * Math.sin(angle);
ball.dy = -Math.abs(speed * Math.cos(angle));
// Ensure ball goes upward
if (ball.dy > 0) ball.dy = -ball.dy;
createPaddleParticles(ball.x, paddle.y);
}
// Brick collisions
for (let brick of bricks) {
if (brick.broken) continue;
if (ball.x + ball.radius >= brick.x &&
ball.x - ball.radius <= brick.x + brick.width &&
ball.y + ball.radius >= brick.y &&
ball.y - ball.radius <= brick.y + brick.height) {
// Determine collision side
const ballCenterX = ball.x;
const ballCenterY = ball.y;
const brickCenterX = brick.x + brick.width/2;
const brickCenterY = brick.y + brick.height/2;
const dx = ballCenterX - brickCenterX;
const dy = ballCenterY - brickCenterY;
if (Math.abs(dx/brick.width) > Math.abs(dy/brick.height)) {
ball.dx = -ball.dx;
} else {
ball.dy = -ball.dy;
}
brickHit(brick, dx, dy);
break; // Only handle one brick per frame
}
}
}
// Remove inactive balls
balls = balls.filter(ball => ball.active);
// Update power-ups
updatePowerUps(dt);
updateEffects(dt);
// Update particles
updateParticles(dt);
// Check win condition
const allBricksDestroyed = bricks.every(brick => brick.broken);
if (allBricksDestroyed && bricks.length > 0) {
game.state = 'VICTORY';
showVictoryScreen();
}
}
function updatePaddle(dt) {
let moveDir = 0;
// Keyboard movement
if (game.keys['ArrowLeft'] || game.keys['a']) moveDir = -1;
if (game.keys['ArrowRight'] || game.keys['d']) moveDir = 1;
// Mouse control (always takes priority)
moveDir = 0; // Mouse is primary, but we allow keyboard to work
if (game.mouseX !== undefined && game.mouseX !== null) {
// Smooth mouse movement
const mouseTargetX = game.mouseX - paddle.width / 2;
const diff = mouseTargetX - paddle.x;
paddle.x += diff * paddle.speed;
} else if (moveDir !== 0) {
// Keyboard movement
const moveSpeed = 8;
paddle.x += moveDir * moveSpeed;
}
// Clamp paddle to screen
paddle.x = Math.max(0, Math.min(canvas.width - paddle.width, paddle.x));
}
function brickHit(brick, dx, dy) {
brick.health--;
// Create crack effect if needed
if (brick.type === 'mixed' && brick.health === 1) {
brick.cracked = true;
createCrackEffect(brick);
}
if (brick.health <= 0) {
brick.broken = true;
game.score += brick.col + 1;
createBrickExplosion(brick);
// Chance to drop power-up
if (brick.hasPowerUp) {
const powerType = Math.random() < 0.5 ? 'widen' : 'multi';
powerUps.push({
x: brick.x + brick.width/2,
y: brick.y + brick.height/2,
width: 20,
height: 20,
type: powerType,
vy: 2,
rot: 0
});
}
} else {
// Hit tough brick - add score
game.score += 5;
createHitEffect(brick);
}
}
function updatePowerUps(dt) {
for (let i = powerUps.length - 1; i >= 0; i--) {
const powerUp = powerUps[i];
powerUp.y += powerUp.vy;
powerUp.rot += 0.05;
// Check paddle collision
if (powerUp.y + 10 >= paddle.y &&
powerUp.y - 10 <= paddle.y + paddle.height &&
powerUp.x + 10 >= paddle.x &&
powerUp.x - 10 <= paddle.x + paddle.width) {
applyPowerUp(powerUp.type);
powerUps.splice(i, 1);
} else if (powerUp.y > canvas.height) {
powerUps.splice(i, 1);
}
}
}
function applyPowerUp(type) {
if (type === 'widen') {
activeEffects.widePaddle = game.time + 10; // 10 seconds
createPowerUpNotification('WIDE PADDLE!', colors.powerUpWiden);
} else if (type === 'multi') {
// Create 3 new balls from existing one
if (balls.length > 0) {
const originalBall = balls[0];
for (let i = 0; i < 3; i++) {
const angle = (i - 1) * 0.3; // Spread angles
const speed = Math.hypot(originalBall.dx, originalBall.dy);
balls.push({
x: originalBall.x,
y: originalBall.y,
dx: speed * Math.sin(angle),
dy: -speed * Math.cos(angle),
radius: originalBall.radius,
active: true
});
}
}
activeEffects.multiBall = game.time + 5; // Multi-ball lasts 5 seconds
createPowerUpNotification('MULTI-BALL!', colors.powerUpMulti);
}
}
function updateEffects(dt) {
// Widen paddle effect
if (activeEffects.widePaddle && game.time > activeEffects.widePaddle) {
activeEffects.widePaddle = null;
paddle.width = paddle.baseWidth;
} else if (!activeEffects.widePaddle) {
paddle.width += (paddle.targetWidth - paddle.width) * 0.1;
}
// Multi-ball effect (just ensures there are multiple balls)
if (activeEffects.multiBall && game.time > activeEffects.multiBall) {
activeEffects.multiBall = null;
}
}
function createBrickExplosion(brick) {
for (let i = 0; i < 15; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = 2 + Math.random() * 4;
particles.push({
x: brick.x + brick.width/2,
y: brick.y + brick.height/2,
dx: Math.cos(angle) * speed,
dy: Math.sin(angle) * speed,
life: 1,
decay: 0.02 + Math.random() * 0.03,
size: 3 + Math.random() * 4,
color: brick.type === 'tough' ? colors.toughBrick :
brick.cracked ? colors.crackedBrick : colors.normalBrick
});
}
}
function createHitEffect(brick) {
for (let i = 0; i < 5; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = 1 + Math.random() * 2;
particles.push({
x: brick.x + brick.width/2,
y: brick.y + brick.height/2,
dx: Math.cos(angle) * speed,
dy: Math.sin(angle) * speed,
life: 0.7,
decay: 0.03,
size: 2 + Math.random() * 3,
color: '#ffffff'
});
}
}
function createCrackEffect(brick) {
for (let i = 0; i < 8; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = 1 + Math.random() * 2;
particles.push({
x: brick.x + brick.width/2,
y: brick.y + brick.height/2,
dx: Math.cos(angle) * speed,
dy: Math.sin(angle) * speed,
life: 0.9,
decay: 0.04,
size: 2 + Math.random() * 2,
color: '#94a3b8'
});
}
}
function createWallParticles(x, y) {
for (let i = 0; i < 5; i++) {
particles.push({
x, y,
dx: (Math.random() - 0.5) * 4,
dy: (Math.random() - 0.5) * 4,
life: 0.5,
decay: 0.05,
size: 2 + Math.random() * 2,
color: '#00ffff'
});
}
}
function createPaddleParticles(x, y) {
for (let i = 0; i < 8; i++) {
const angle = (Math.random() - 0.5) * Math.PI;
const speed = 1 + Math.random() * 3;
particles.push({
x, y,
dx: Math.cos(angle) * speed,
dy: Math.sin(angle) * speed,
life: 0.8,
decay: 0.03,
size: 2 + Math.random() * 3,
color: '#00b4d8'
});
}
}
function createDeathParticles(x, y) {
for (let i = 0; i < 20; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = 2 + Math.random() * 5;
particles.push({
x, y,
dx: Math.cos(angle) * speed,
dy: Math.sin(angle) * speed,
life: 1.2,
decay: 0.02,
size: 3 + Math.random() * 5,
color: ['#ff0000', '#ff5722', '#ff9e00'][Math.floor(Math.random() * 3)]
});
}
}
function createPowerUpNotification(text, color) {
// Visual effect could be added here
}
function updateParticles(dt) {
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.dx;
p.y += p.dy;
p.life -= p.decay;
p.dx *= 0.98;
p.dy *= 0.98;
if (p.life <= 0) {
particles.splice(i, 1);
}
}
}
// ===================== RENDER =====================
function render() {
// Clear canvas
ctx.fillStyle = '#0a1421';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw grid background
drawGrid();
// Draw bricks
drawBricks();
// Draw paddle
drawPaddle();
// Draw balls
drawBalls();
// Draw power-ups
drawPowerUps();
// Draw particles
drawParticles();
// Draw HUD
drawHUD();
}
function drawGrid() {
ctx.strokeStyle = 'rgba(255, 255, 255, 0.03)';
ctx.lineWidth = 1;
const gridSize = 40;
for (let x = 0; x < canvas.width; x += gridSize) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
ctx.stroke();
}
for (let y = 0; y < canvas.height; y += gridSize) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
ctx.stroke();
}
}
function drawBricks() {
for (let brick of bricks) {
if (brick.broken) continue;
// Base color
let baseColor = colors.normalBrick;
if (brick.type === 'tough') baseColor = colors.toughBrick;
else if (brick.cracked) baseColor = colors.crackedBrick;
// Fill brick
ctx.fillStyle = baseColor;
ctx.fillRect(brick.x, brick.y, brick.width, brick.height);
// Add gradient for 3D effect
const gradient = ctx.createLinearGradient(
brick.x, brick.y,
brick.x, brick.y + brick.height
);
gradient.addColorStop(0, 'rgba(255,255,255,0.3)');
gradient.addColorStop(0.5, 'rgba(255,255,255,0)');
ctx.fillStyle = gradient;
ctx.fillRect(brick.x, brick.y, brick.width, brick.height);
// Border
ctx.strokeStyle = 'rgba(0,0,0,0.3)';
ctx.lineWidth = 2;
ctx.strokeRect(brick.x, brick.y, brick.width, brick.height);
// Crack pattern if cracked
if (brick.cracked) {
ctx.strokeStyle = 'rgba(0,0,0,0.4)';
ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.moveTo(brick.x + 10, brick.y + 5);
ctx.lineTo(brick.x + 18, brick.y + 15);
ctx.lineTo(brick.x + 12, brick.y + 20);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(brick.x + brick.width - 15, brick.y + 8);
ctx.lineTo(brick.x + brick.width - 10, brick.y + 18);
ctx.lineTo(brick.x + brick.width - 20, brick.y + 12);
ctx.stroke();
}
// Power-up icon
if (brick.hasPowerUp) {
ctx.fillStyle = 'rgba(255,255,255,0.9)';
ctx.beginPath();
ctx.arc(brick.x + brick.width/2, brick.y + brick.height/2, 5, 0, Math.PI*2);
ctx.fill();
// Star shape
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.moveTo(brick.x + brick.width/2, brick.y + 3);
ctx.lineTo(brick.x + brick.width/2 - 2, brick.y + 7);
ctx.lineTo(brick.x + brick.width/2 - 3, brick.y + 10);
ctx.lineTo(brick.x + brick.width/2, brick.y + 13);
ctx.lineTo(brick.x + brick.width/2 + 3, brick.y + 10);
ctx.lineTo(brick.x + brick.width/2 + 2, brick.y + 7);
ctx.closePath();
ctx.fill();
}
}
}
function drawPaddle() {
// Gradient
const gradient = ctx.createLinearGradient(
paddle.x, paddle.y,
paddle.x, paddle.y + paddle.height
);
gradient.addColorStop(0, '#4deeea');
gradient.addColorStop(0.5, '#00b4d8');
gradient.addColorStop(1, '#0077b6');
ctx.fillStyle = gradient;
ctx.fillRect(paddle.x, paddle.y, paddle.width, paddle.height);
// Highlight
ctx.fillStyle = 'rgba(255,255,255,0.3)';
ctx.fillRect(paddle.x, paddle.y, paddle.width, 4);
// Glow effect if wide paddle active
if (activeEffects.widePaddle) {
ctx.shadowColor = colors.powerUpWiden;
ctx.shadowBlur = 20;
ctx.strokeStyle = colors.powerUpWiden;
ctx.lineWidth = 2;
ctx.strokeRect(paddle.x, paddle.y, paddle.width, paddle.height);
ctx.shadowBlur = 0;
}
}
function drawBalls() {
for (let ball of balls) {
if (!ball.active) continue;
// Ball glow
ctx.shadowColor = '#ffffff';
ctx.shadowBlur = 15;
ctx.fillStyle = colors.ball;
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
// Inner highlight
ctx.fillStyle = 'rgba(255,255,255,0.8)';
ctx.beginPath();
ctx.arc(ball.x - 2, ball.y - 2, ball.radius/3, 0, Math.PI * 2);
ctx.fill();
}
}
function drawPowerUps() {
for (let powerUp of powerUps) {
ctx.save();
ctx.translate(powerUp.x, powerUp.y);
ctx.rotate(powerUp.rot);
// Different shapes for different power-ups
if (powerUp.type === 'widen') {
// Diamond shape
ctx.fillStyle = colors.powerUpWiden;
ctx.beginPath();
ctx.moveTo(0, -10);
ctx.lineTo(10, 0);
ctx.lineTo(0, 10);
ctx.lineTo(-10, 0);
ctx.closePath();
ctx.fill();
// Glow
ctx.shadowColor = colors.powerUpWiden;
ctx.shadowBlur = 10;
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 1;
ctx.stroke();
ctx.shadowBlur = 0;
} else {
// Star shape
ctx.fillStyle = colors.powerUpMulti;
ctx.beginPath();
for (let i = 0; i < 5; i++) {
const angle = (i * 2 * Math.PI / 5) - Math.PI/2;
const x = Math.cos(angle) * 10;
const y = Math.sin(angle) * 10;
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
const innerAngle = angle + Math.PI/5;
const innerX = Math.cos(innerAngle) * 4;
const innerY = Math.sin(innerAngle) * 4;
ctx.lineTo(innerX, innerY);
}
ctx.closePath();
ctx.fill();
ctx.shadowColor = colors.powerUpMulti;
ctx.shadowBlur = 10;
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 1;
ctx.stroke();
ctx.shadowBlur = 0;
}
ctx.restore();
}
}
function drawParticles() {
for (let particle of particles) {
ctx.globalAlpha = particle.life;
ctx.fillStyle = particle.color;
ctx.fillRect(
particle.x - particle.size/2,
particle.y - particle.size/2,
particle.size,
particle.size
);
}
ctx.globalAlpha = 1;
}
function drawHUD() {
// Score
ctx.fillStyle = 'rgba(0,0,0,0.6)';
ctx.fillRect(10, 10, 200, 50);
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 20px monospace';
ctx.textAlign = 'left';
ctx.fillText(`SCORE: ${game.score}`, 20, 35);
// Lives
ctx.textAlign = 'right';
ctx.fillText('LIVES:', canvas.width - 50, 30);
// Draw hearts
for (let i = 0; i < game.lives; i++) {
drawHeart(canvas.width - 20 - (i * 30), 10, 12);
}
// Active effects timer
if (activeEffects.widePaddle) {
const remainingTime = Math.ceil(activeEffects.widePaddle - game.time);
ctx.fillStyle = 'rgba(0,0,0,0.6)';
ctx.fillRect(10, 65, 150, 30);
ctx.fillStyle = colors.powerUpWiden;
ctx.font = 'bold 14px monospace';
ctx.fillText(`WIDE PADDLE: ${remainingTime}s`, 20, 85);
}
// Multi-ball count indicator
if (balls.length > 1) {
ctx.fillStyle = 'rgba(0,0,0,0.6)';
ctx.fillRect(canvas.width - 160, 65, 150, 30);
ctx.fillStyle = colors.powerUpMulti;
ctx.font = 'bold 14px monospace';
ctx.textAlign = 'right';
ctx.fillText(`BALLS: ${balls.length}`, canvas.width - 20, 85);
}
}
function drawHeart(x, y, size) {
ctx.fillStyle = '#ff5722';
ctx.beginPath();
const width = size;
const height = size * 1.2;
ctx.moveTo(x + width/2, y + height);
ctx.bezierCurveTo(
x + width, y + height - height/3,
x + width + width/4, y,
x + width/2, y + height/4
);
ctx.bezierCurveTo(
x, y,
x - width/4, y,
x + width/2, y + height/4
);
ctx.bezierCurveTo(
x - width/4, y,
x - width, y + height - height/3,
x + width/2, y + height
);
ctx.fill();
// Glow
ctx.shadowColor = '#ff5722';
ctx.shadowBlur = 10;
ctx.fill();
ctx.shadowBlur = 0;
}
// ===================== SCREENS =====================
function drawMenuBackground() {
// Animated background for menu
ctx.fillStyle = '#0a1421';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Moving stars
const time = Date.now() * 0.001;
for (let i = 0; i < 50; i++) {
const x = (Math.sin(time + i) * 0.5 + 0.5) * canvas.width;
const y = (Math.cos(time * 0.7 + i) * 0.5 + 0.5) * canvas.height;
const size = Math.random() * 2 + 1;
ctx.fillStyle = `rgba(255,255,255,${Math.random() * 0.5})`;
ctx.fillRect(x, y, size, size);
}
}
function showVictoryScreen() {
ctx.fillStyle = 'rgba(0,0,0,0.8)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#ffeb3b';
ctx.font = `bold ${Math.min(48, canvas.width/15)}px Arial`;
ctx.textAlign = 'center';
ctx.fillText('VICTORY!', centerX, canvas.height/2 - 50);
ctx.fillStyle = '#ffffff';
ctx.font = `${Math.min(24, canvas.width/30)}px Arial`;
ctx.fillText(`Final Score: ${game.score}`, centerX, canvas.height/2);
ctx.fillText('Press START to play again', centerX, canvas.height/2 + 50);
const button = document.createElement('button');
button.textContent = 'PLAY AGAIN';
button.style.cssText = `
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 1rem 2.5rem;
font-size: 1.2rem;
background: linear-gradient(to right, #00c6ff, #0072ff);
border: none;
border-radius: 50px;
color: white;
cursor: pointer;
z-index: 100;
`;
button.onclick = () => {
document.body.removeChild(document.querySelector('.controls-hint'));
document.body.removeChild(button);
startGame();
};
document.body.appendChild(button);
}
function endGame() {
game.state = 'GAMEOVER';
ctx.fillStyle = 'rgba(0,0,0,0.85)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#ff5722';
ctx.font = `bold ${Math.min(48, canvas.width/15)}px Arial`;
ctx.textAlign = 'center';
ctx.fillText('GAME OVER', centerX, canvas.height/2 - 50);
ctx.fillStyle = '#ffffff';
ctx.font = `${Math.min(24, canvas.width/30)}px Arial`;
ctx.fillText(`Score: ${game.score}`, centerX, canvas.height/2);
// Show restart button
const button = document.createElement('button');
button.textContent = 'RESTART';
button.style.cssText = `
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 1rem 2.5rem;
font-size: 1.2rem;
background: linear-gradient(to right, #00c6ff, #0072ff);
border: none;
border-radius: 50px;
color: white;
cursor: pointer;
z-index: 100;
`;
button.onclick = () => startGame();
document.body.appendChild(button);
}
// ===================== START =====================
init();
game.lastTime = performance.now();
requestAnimationFrame(gameLoop);
</script>
</body>
</html>Reasoning trace (457 tokens)
We are going to build a polished Breakout game in a single HTML file using vanilla JavaScript and a <canvas>. The game will include: 1. A paddle controlled by mouse and left/right arrow keys. 2. Ball bounce angle based on hit position (center = straight, edges = sharper). 3. One level with at least 6 rows of bricks (mix of normal and tougher bricks that need 2 hits and crack after first hit). 4. Power-ups that drop when bricks break (wider paddle and multi-ball). Catch with paddle to activate for 10 seconds or until ball count changes. 5. Particles when a brick breaks. 6. 3 lives, score display. Game over and victory screens with restart option. We'll structure the code as follows: - HTML structure: a canvas element inside a container. - CSS: basic styling to center the game, but we'll keep it minimal because we are going to use a fixed size for the game area and scale it to fit the viewport. We want it to look good at 1280x800 and adapt down to 375px. - JavaScript: - Define game objects (paddle, ball, bricks, particles, power-ups). - Game loop (update and render). - Input handling (mouse and keyboard). - Collision detection (ball vs walls, paddle, bricks, power-ups). - Game states (menu, playing, game over, victory). - Helper functions for drawing, updating, collision checks. Let's start by writing the HTML and CSS, then the JavaScript. Note: We are not allowed to use external resources except allowed ones. We are allowed to use CDN URLs if needed, but we don't have any required fonts or images. So we'll draw everything with canvas. We'll design the game with a fixed internal resolution (e.g., 800x600) and scale the canvas to fit the viewport while maintaining aspect ratio. Let's code step by step.