Run details
- Model
- recursive/flint-v1.0:free
- Route
- OpenRouter API, one user message
- Served by
- Recursive
- Reasoning
- provider default, 12,341 reasoning tokens
- Limits
- 60 min · 235,929 output tokens
- Date
- 21 Oct 2026
- Time
- 2 min 19 s
- Size
- 13 kB
- Tokens
- 79 in, 18,467 out
- List price
- free (list price $0)
- Paid
- $0 (free route)
Exact prompt
Make a Breakout game I can play right now with the keyboard or the mouse. Give it several levels, a score and some juice. It must be one complete, self-contained HTML file: inline all CSS and JavaScript, and make no network requests of any kind (no CDNs, web fonts or remote images). Reply with the file in a single ```html code block.
Source
13 kB 468 lines Raw
<!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>
html, body { margin:0; padding:0; height:100%; background:#070b14; overflow:hidden; }
body { display:flex; justify-content:center; align-items:center; font-family: monospace; }
canvas { display:block; background:#0b1020; box-shadow:0 0 60px rgba(80,160,255,0.15); }
</style>
</head>
<body>
<canvas id="game"></canvas>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const W = canvas.width = 800;
const H = canvas.height = 600;
const PADDLE_W = 110;
const PADDLE_H = 12;
const PADDLE_Y = H - 50;
const PADDLE_SPEED = 650;
const BALL_R = 8;
let state = 'menu';
let score = 0;
let lives = 3;
let level = 0;
let highScore = 0;
let bricks = [];
let particles = [];
let trail = [];
let shake = 0;
let lastTime = 0;
let levelClearTimer = 0;
let audioCtx = null;
let keys = { left: false, right: false };
let mouseX = W / 2;
let paddle = { x: W / 2 - PADDLE_W / 2, w: PADDLE_W, h: PADDLE_H, y: PADDLE_Y };
const LEVELS = [
{ rows: 4, cols: 8, baseSpeed: 420, color: [255, 90, 90] },
{ rows: 6, cols: 8, baseSpeed: 540, color: [90, 170, 255] },
{ rows: 7, cols: 8, baseSpeed: 650, color: [200, 130, 255] },
];
const PALETTE = [
[255, 90, 90],
[255, 170, 90],
[255, 230, 90],
[120, 255, 150],
[90, 210, 255],
[180, 140, 255],
];
function clamp(v, min, max) { return Math.max(min, Math.min(max, v)); }
function rand(a, b) { return a + Math.random() * (b - a); }
function createLevel() {
const def = LEVELS[level];
bricks = [];
const cols = def.cols;
const rows = def.rows;
const brickW = (W - 60 - (cols - 1) * 6) / cols;
const brickH = 22;
const gap = 6;
const top = 70;
const rowH = brickH + gap;
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
const col = PALETTE[(r + c) % PALETTE.length];
bricks.push({
alive: true,
x: 30 + c * (brickW + gap),
y: top + r * rowH,
w: brickW,
h: brickH,
r: col[0],
g: col[1],
b: col[2],
});
}
}
}
function resetBall() {
const b = { x: paddle.x + paddle.w / 2, y: paddle.y - BALL_R, vx: 0, vy: 0 };
return b;
}
let ball = resetBall();
function loadHighScore() {
try { return parseInt(localStorage.getItem('breakout-high') || '0', 10) || 0; } catch(e) { return 0; }
}
function saveHighScore() { try { localStorage.setItem('breakout-high', String(highScore)); } catch(e) {} }
function startGame() {
score = 0;
lives = 3;
level = 0;
createLevel();
state = 'ready';
levelClearTimer = 0;
ball = resetBall();
updateHighScore();
}
function updateHighScore() {
highScore = loadHighScore();
if (score > highScore) {
highScore = score;
saveHighScore();
}
}
function launch() {
if (state !== 'ready') return;
state = 'playing';
const rel = (ball.x - (paddle.x + paddle.w / 2)) / (paddle.w / 2);
const clamped = clamp(rel, -1, 1);
const angle = clamped * Math.PI / 3;
const speed = LEVELS[level].baseSpeed;
ball.vx = Math.cos(angle) * speed;
ball.vy = -Math.sin(angle) * speed;
}
function beep(freq, dur = 0.1, type = 'square', vol = 0.05, slide = 0) {
if (!audioCtx) return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = type;
osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
if (slide) osc.frequency.exponentialRampToValueAtTime(slide, audioCtx.currentTime + dur);
gain.gain.setValueAtTime(vol, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + dur);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start(audioCtx.currentTime);
osc.stop(audioCtx.currentTime + dur);
}
function initAudio() {
if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
if (audioCtx.state === 'suspended') audioCtx.resume();
}
function soundPaddle() { beep(220, 0.06, 'square', 0.06); }
function soundBrick() { beep(440, 0.06, 'triangle', 0.08); }
function soundBreak() { beep(880, 0.09, 'sawtooth', 0.05, 140); }
function soundWall() { beep(160, 0.03, 'square', 0.03); }
function soundLaunch() { beep(320, 0.08, 'square', 0.05, 620); }
function breakBrick(b) {
const count = 14;
for (let i = 0; i < count; i++) {
particles.push({
x: b.x + b.w / 2,
y: b.y + b.h / 2,
vx: rand(-120, 120),
vy: rand(-160, 40),
life: rand(0.4, 0.8),
maxLife: 0.8,
color: `rgb(${b.r},${b.g},${b.b})`,
size: rand(2, 5),
});
}
shake = Math.max(shake, 7);
}
function checkLevelClear() {
const alive = bricks.filter(b => b.alive).length;
if (alive === 0) {
state = 'levelclear';
levelClearTimer = 0;
}
}
function update(dt) {
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.life -= dt;
if (p.life <= 0) { particles.splice(i, 1); continue; }
p.x += p.vx * dt;
p.y += p.vy * dt;
p.vx *= 0.98;
p.vy *= 0.98;
}
if (state === 'playing') {
if (keys.left) paddle.x -= PADDLE_SPEED * dt;
if (keys.right) paddle.x += PADDLE_SPEED * dt;
if (!keys.left && !keys.right) {
const target = mouseX - paddle.w / 2;
paddle.x += (target - paddle.x) * Math.min(1, 12 * dt);
}
paddle.x = clamp(paddle.x, 0, W - paddle.w);
ball.x += ball.vx * dt;
ball.y += ball.vy * dt;
if (ball.x < BALL_R) { ball.x = BALL_R; ball.vx = Math.abs(ball.vx); soundWall(); }
if (ball.x > W - BALL_R) { ball.x = W - BALL_R; ball.vx = -Math.abs(ball.vx); soundWall(); }
if (ball.y < BALL_R) { ball.y = BALL_R; ball.vy = Math.abs(ball.vy); soundWall(); }
if (ball.vy > 0 && ball.y + BALL_R >= paddle.y && ball.y - BALL_R <= paddle.y + paddle.h && ball.x + BALL_R >= paddle.x && ball.x - BALL_R <= paddle.x + paddle.w) {
const rel = (ball.x - (paddle.x + paddle.w / 2)) / (paddle.w / 2);
const clamped = clamp(rel, -1, 1);
const angle = clamped * Math.PI / 3;
const speed = Math.hypot(ball.vx, ball.vy);
ball.vx = Math.cos(angle) * speed;
ball.vy = -Math.sin(angle) * speed;
ball.y = paddle.y - BALL_R;
soundPaddle();
}
for (let i = bricks.length - 1; i >= 0; i--) {
const b = bricks[i];
if (!b.alive) continue;
if (ball.x + BALL_R > b.x && ball.x - BALL_R < b.x + b.w && ball.y + BALL_R > b.y && ball.y - BALL_R < b.y + b.h) {
if (Math.abs(ball.vx) > Math.abs(ball.vy)) {
if (ball.vx > 0) { ball.x = b.x - BALL_R; ball.vx = -Math.abs(ball.vx); }
else { ball.x = b.x + b.w + BALL_R; ball.vx = Math.abs(ball.vx); }
} else {
if (ball.vy > 0) { ball.y = b.y - BALL_R; ball.vy = -Math.abs(ball.vy); }
else { ball.y = b.y + b.h + BALL_R; ball.vy = Math.abs(ball.vy); }
}
b.alive = false;
score += 50;
updateHighScore();
breakBrick(b);
soundBreak();
const newSpeed = Math.min(1100, Math.hypot(ball.vx, ball.vy) + 30);
const ang = Math.atan2(ball.vy, ball.vx);
ball.vx = Math.cos(ang) * newSpeed;
ball.vy = Math.sin(ang) * newSpeed;
checkLevelClear();
break;
}
}
trail.push({ x: ball.x, y: ball.y });
if (trail.length > 14) trail.shift();
if (ball.y - BALL_R > H + 20) {
lives--;
if (lives > 0) {
state = 'ready';
ball = resetBall();
} else {
state = 'gameover';
updateHighScore();
}
}
} else if (state === 'ready') {
} else if (state === 'levelclear') {
levelClearTimer += dt;
if (levelClearTimer > 2) {
level++;
if (level < LEVELS.length) {
createLevel();
state = 'ready';
levelClearTimer = 0;
ball = resetBall();
} else {
state = 'win';
}
}
}
if (shake > 0) shake = Math.max(0, shake - dt * 30);
}
function render() {
ctx.save();
if (shake > 0) {
ctx.translate((Math.random() - 0.5) * shake, (Math.random() - 0.5) * shake);
}
const bg = ctx.createLinearGradient(0, 0, 0, H);
bg.addColorStop(0, '#0b1020');
bg.addColorStop(1, '#0d1428');
ctx.fillStyle = bg;
ctx.fillRect(-20, -20, W + 40, H + 40);
ctx.strokeStyle = 'rgba(255,255,255,0.04)';
ctx.lineWidth = 1;
ctx.beginPath();
for (let x = 0; x <= W; x += 40) { ctx.moveTo(x, 0); ctx.lineTo(x, H); }
for (let y = 0; y <= H; y += 40) { ctx.moveTo(0, y); ctx.lineTo(W, y); }
ctx.stroke();
for (const b of bricks) {
if (!b.alive) continue;
ctx.save();
ctx.shadowColor = `rgb(${b.r},${b.g},${b.b})`;
ctx.shadowBlur = 12;
ctx.fillStyle = `rgb(${b.r},${b.g},${b.b})`;
ctx.fillRect(b.x, b.y, b.w, b.h);
ctx.shadowBlur = 0;
ctx.strokeStyle = 'rgba(255,255,255,0.35)';
ctx.lineWidth = 1;
ctx.strokeRect(b.x, b.y, b.w, b.h);
ctx.restore();
}
ctx.save();
ctx.shadowColor = '#59c7ff';
ctx.shadowBlur = 18;
const pg = ctx.createLinearGradient(0, paddle.y, 0, paddle.y + paddle.h);
pg.addColorStop(0, '#8fd8ff');
pg.addColorStop(1, '#2f9df0');
ctx.fillStyle = pg;
ctx.fillRect(paddle.x, paddle.y, paddle.w, paddle.h);
ctx.shadowBlur = 0;
ctx.fillStyle = 'rgba(255,255,255,0.35)';
ctx.fillRect(paddle.x, paddle.y, paddle.w, 3);
ctx.restore();
ctx.save();
ctx.shadowColor = '#ffffff';
ctx.shadowBlur = 16;
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(ball.x, ball.y, BALL_R, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
for (let i = 0; i < trail.length; i++) {
const t = trail[i];
const a = (i / trail.length) * 0.25;
ctx.fillStyle = `rgba(255,255,255,${a})`;
ctx.beginPath();
ctx.arc(t.x, t.y, BALL_R * (0.6 + (i / trail.length) * 0.4), 0, Math.PI * 2);
ctx.fill();
}
for (const p of particles) {
ctx.globalAlpha = clamp(p.life / p.maxLife, 0, 1);
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalAlpha = 1;
ctx.restore();
drawUI();
}
function drawUI() {
ctx.save();
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.font = 'bold 20px monospace';
ctx.fillStyle = '#e8f4ff';
ctx.fillText(`SCORE ${score}`, 20, 16);
ctx.font = '14px monospace';
ctx.fillStyle = '#8ea6c2';
ctx.fillText(`BEST ${highScore}`, 20, 42);
ctx.textAlign = 'center';
ctx.font = 'bold 16px monospace';
ctx.fillStyle = '#59c7ff';
ctx.fillText(`LEVEL ${level + 1}`, W / 2, 16);
if (state === 'menu') {
ctx.textAlign = 'center';
ctx.font = 'bold 42px monospace';
ctx.fillStyle = '#ffffff';
ctx.fillText('BREAKOUT', W / 2, H / 2 - 60);
ctx.font = '18px monospace';
ctx.fillStyle = '#cfe6ff';
ctx.fillText('Press Enter or Click to Start', W / 2, H / 2 - 20);
} else if (state === 'ready') {
ctx.textAlign = 'center';
ctx.font = 'bold 28px monospace';
ctx.fillStyle = '#ffd166';
ctx.fillText('GET READY', W / 2, H / 2 - 40);
ctx.font = '14px monospace';
ctx.fillStyle = '#cfe6ff';
ctx.fillText('Press Space or Click to Launch', W / 2, H / 2 - 10);
} else if (state === 'levelclear') {
ctx.textAlign = 'center';
ctx.font = 'bold 34px monospace';
ctx.fillStyle = '#7CFFB2';
ctx.fillText('LEVEL CLEARED!', W / 2, H / 2 - 40);
} else if (state === 'gameover') {
ctx.textAlign = 'center';
ctx.font = 'bold 40px monospace';
ctx.fillStyle = '#ff6b6b';
ctx.fillText('GAME OVER', W / 2, H / 2 - 60);
ctx.font = '18px monospace';
ctx.fillStyle = '#cfe6ff';
ctx.fillText(`SCORE ${score}`, W / 2, H / 2 - 20);
ctx.font = '14px monospace';
ctx.fillStyle = '#8ea6c2';
ctx.fillText('Press Enter to Restart', W / 2, H / 2 + 10);
} else if (state === 'win') {
ctx.textAlign = 'center';
ctx.font = 'bold 40px monospace';
ctx.fillStyle = '#b388ff';
ctx.fillText('YOU WIN!', W / 2, H / 2 - 40);
ctx.font = '18px monospace';
ctx.fillStyle = '#cfe6ff';
ctx.fillText(`FINAL SCORE ${score}`, W / 2, H / 2 - 10);
ctx.font = '14px monospace';
ctx.fillStyle = '#8ea6c2';
ctx.fillText('Press Enter to Play Again', W / 2, H / 2 + 20);
}
ctx.restore();
}
function loop(now) {
let dt = (now - lastTime) / 1000;
dt = Math.min(dt, 0.033);
lastTime = now;
update(dt);
render();
requestAnimationFrame(loop);
}
window.addEventListener('keydown', e => {
initAudio();
if (e.key === 'ArrowLeft') { keys.left = true; e.preventDefault(); }
if (e.key === 'ArrowRight') { keys.right = true; e.preventDefault(); }
if (e.key === ' ') {
e.preventDefault();
if (state === 'ready') launch();
else if (state === 'menu' || state === 'gameover' || state === 'win') startGame();
}
if (e.key === 'Enter') {
if (state === 'menu' || state === 'gameover' || state === 'win') startGame();
}
});
window.addEventListener('keyup', e => {
if (e.key === 'ArrowLeft') keys.left = false;
if (e.key === 'ArrowRight') keys.right = false;
});
canvas.addEventListener('mousemove', e => {
const rect = canvas.getBoundingClientRect();
const scaleX = W / rect.width;
mouseX = (e.clientX - rect.left) * scaleX;
if (state === 'playing' && !keys.left && !keys.right) {
paddle.x = mouseX - paddle.w / 2;
paddle.x = clamp(paddle.x, 0, W - paddle.w);
}
});
canvas.addEventListener('mousedown', e => {
initAudio();
if (state === 'ready') launch();
else if (state === 'menu' || state === 'gameover' || state === 'win') startGame();
});
updateHighScore();
lastTime = performance.now();
requestAnimationFrame(loop);
</script>
</body>
</html>

