• Updated 21 Oct, 22:07 UTC
  • Next run 22 Oct, 05:17 UTCNext 22 Oct 05:17
  • Last 7 days: 12 new models, 134 runs7 d: 12 models, 134 runs
Network offOpen alone (new tab)

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)

Raw files: output · meta.json

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>

Compare with

Flint: other attempts