• Updated 14 Nov, 12:11 UTC
  • Next run 15 Nov, 05:17 UTCNext 15 Nov 05:17
  • Last 7 days: 9 new models · 71 runs7 d: 9 models · 71 runs
Playable Breakout

Mi:dm K 3.0 ProKT

Network offOpen alone (new tab)

Run details

Model
kt/midm-k-3.0-pro
Route
OpenRouter API, one user message
Served by
KT
Effort
the model's default
Limits
60 min · 235,615 output tokens (the model's limit)
Date
14 Nov 2026
Time
8 s
Size
7.6 kB
Tokens
443 in, 2,764 out (244 of them thinking)
List price
$0.002 at today's price ($0.20 in / $0.80 out per M)
Paid
$0.002

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

7.6 kB 265 lines Raw

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Mini Breakout</title>
<style>
  body,html{
    margin:0;padding:0;width:100%;height:100%;
    background:#111;
    color:#fff;
    font-family:Arial,Helvetica,sans-serif;
    overflow:hidden;
    display:flex;
    justify-content:center;
    align-items:center;
  }
  #gameCanvas{
    background:#222;
    box-shadow:0 0 20px rgba(0,0,0,.7);
    border:4px solid #555;
    display:block;
  }
  #ui{
    position:absolute;
    top:10px;left:10px;
    font-size:18px;
    z-index:10;
    pointer-events:none;
  }
</style>
</head>
<body>
<canvas id="gameCanvas" width="480" height="640"></canvas>
<div id="ui"></div>

<script>
(() => {
  const canvas = document.getElementById('gameCanvas');
  const ctx = canvas.getContext('2d');
  const ui = document.getElementById('ui');

  const COLS = 8;
  const ROWS = 6;
  const BRICK_WIDTH = canvas.width / COLS - 2;
  const BRICK_HEIGHT = 20;
  const BRICK_PADDING_TOP = 20;
  const BRICK_PADDING_LEFT = 30;

  const PADDLE_HEIGHT = 12;
  const PADDLE_WIDTH = 80;
  const PADDLE_SPEED = 7;

  const BALL_RADIUS = 10;
  const BALL_SPEED = 5;

  const LEVELS = [
    // Level 1: simple grid
    Array.from({length: ROWS}, (_, r) => Array.from({length: COLS}, (_, c) => ({
      alive: true,
      color: `hsl(${(r+1)*30},80%,55%)`,
      points: 50 - r*5
    }))),
    // Level 2: alternating colors with gaps
    Array.from({length: ROWS}, (_, r) => Array.from({length: COLS}, (_, c) => {
      const alive = (r + c) % 2 === 0;
      return { alive, color: alive ? `hsl(${c*45},70%,50%)` : null, points: 50 };
    })),
    // Level 3: narrower rows, higher points
    Array.from({length: ROWS}, (_, r) => Array.from({length: COLS}, (_, c) => ({
      alive: r < 4,
      color: r < 4 ? `hsl(${(c*60)%360},70%,55%)` : null,
      points: 100 - r*10
    }))),
  ];

  let paddle = { x: canvas.width/2 - PADDLE_WIDTH/2, y: canvas.height - 30, width: PADDLE_WIDTH, height: PADDLE_HEIGHT };
  let ball = { x: canvas.width/2, y: canvas.height - 50, vx: BALL_SPEED, vy: -BALL_SPEED };
  let particles = [];
  let levelIdx = 0;
  let bricks = [];
  let score = 0;
  let gameOver = false;
  let gameWon = false;

  function initLevel() {
    bricks = LEVELS[levelIdx].map((row, r) => row.map((b, c) => ({
      ...b,
      rect: {
        x: BRICK_PADDING_LEFT + c * BRICK_WIDTH,
        y: BRICK_PADDING_TOP + r * (BRICK_HEIGHT + 4),
        width: BRICK_WIDTH,
        height: BRICK_HEIGHT
      }
    }))).flat();
    ball = { x: canvas.width/2, y: canvas.height/2, vx: BALL_SPEED * (Math.random()<0.5?-1:1), vy: -BALL_SPEED };
    paddle.x = canvas.width/2 - PADDLE_WIDTH/2;
    particles = [];
    gameOver = false;
    gameWon = false;
  }

  function randBetween(a,b) { return a + Math.random()*(b-a); }

  function draw() {
    ctx.clearRect(0,0,canvas.width,canvas.height);
    // bricks
    bricks.forEach(b => {
      if (!b.alive) return;
      ctx.fillStyle = b.color;
      ctx.fillRect(b.rect.x, b.rect.y, b.rect.width, b.rect.height);
      ctx.strokeStyle = '#333';
      ctx.strokeRect(b.rect.x, b.rect.y, b.rect.width, b.rect.height);
    });
    // paddle
    ctx.fillStyle = '#fff';
    ctx.fillRect(paddle.x, paddle.y, paddle.width, paddle.height);
    // ball
    ctx.beginPath();
    ctx.arc(ball.x, ball.y, BALL_RADIUS, 0, Math.PI*2);
    ctx.fillStyle = '#ffeb3b';
    ctx.fill();
    ctx.closePath();
    // particles
    particles.forEach(p => {
      ctx.globalAlpha = p.life;
      ctx.fillStyle = p.color;
      ctx.beginPath();
      ctx.arc(p.x, p.y, p.size, 0, Math.PI*2);
      ctx.fill();
    });
    ctx.globalAlpha = 1;
    // UI
    ui.textContent = `Score: ${score}   Level: ${levelIdx+1}`;
    if (gameOver) {
      ctx.font = 'bold 40px Arial';
      ctx.fillStyle = 'rgba(0,0,0,0.7)';
      ctx.fillRect(0, canvas.height/2 - 30, canvas.width, 70);
      ctx.fillStyle = '#fff';
      ctx.textAlign = 'center';
      ctx.fillText('Game Over', canvas.width/2, canvas.height/2);
    } else if (gameWon) {
      ctx.font = 'bold 40px Arial';
      ctx.fillStyle = 'rgba(0,0,0,0.7)';
      ctx.fillRect(0, canvas.height/2 - 30, canvas.width, 70);
      ctx.fillStyle = '#0f0';
      ctx.fillText('All Levels Cleared!', canvas.width/2, canvas.height/2);
    }
  }

  function update(dt) {
    if (gameOver || gameWon) return;

    // paddle move with mouse or keyboard
    const mouseX = (canvas.lastMouseX || paddle.x);
    paddle.x = mouseX - paddle.width/2;
    if (paddle.x < 0) paddle.x = 0;
    if (paddle.x > canvas.width - paddle.width) paddle.x = canvas.width - paddle.width;

    // update ball
    ball.x += ball.vx;
    ball.y += ball.vy;

    // wall collisions
    if (ball.x - BALL_RADIUS <= 0 || ball.x + BALL_RADIUS >= canvas.width) ball.vx *= -1;
    if (ball.y - BALL_RADIUS <= 0) ball.vy *= -1;

    // 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) {
      const hitX = (ball.x - paddle.x) / paddle.width - 0.5;
      const bounceAngle = hitX * Math.PI / 2; // up to 90 degrees
      const speed = Math.hypot(ball.vx, ball.vy);
      ball.vx = Math.sin(bounceAngle) * speed;
      ball.vy = -Math.cos(bounceAngle) * speed;
    }

    // brick collisions
    for (let i = bricks.length - 1; i >= 0; i--) {
      const b = bricks[i];
      if (!b.alive) continue;
      // simple AABB circle check
      let rect = b.rect;
      if (ball.x + BALL_RADIUS > rect.x && ball.x - BALL_RADIUS < rect.x + rect.width &&
          ball.y + BALL_RADIUS > rect.y && ball.y - BALL_RADIUS < rect.y + rect.height) {
        // delta
        const deltal = ball.x - rect.x;
        const deltar = rect.x + rect.width - ball.x;
        const deltac = ball.y - rect.y;
        const deltad = rect.y + rect.height - ball.y;
        const min = Math.min(deltal, deltar, deltac, deltad);
        if (min === deltal) ball.vx = Math.abs(ball.vx);
        else if (min === deltar) ball.vx = -Math.abs(ball.vx);
        else if (min === deltac) ball.vy = Math.abs(ball.vy);
        else ball.vy = -Math.abs(ball.vy);
        b.alive = false;
        score += b.points;
        // spawn particles
        for(let k=0;k<8;k++){
          particles.push({
            x: rect.x + rect.width/2,
            y: rect.y + rect.height/2,
            vx: randBetween(-3,3),
            vy: randBetween(-3,3),
            size: randBetween(2,5),
            color: b.color,
            life: 1
          });
        }
        break;
      }
    }

    // particles update
    particles = particles.map(p => {
      p.x += p.vx;
      p.y += p.vy;
      p.vx *= 0.98;
      p.vy *= 0.98;
      p.life *= 0.95;
      return p;
    }).filter(p => p.life > 0.05);

    // ball bottom
    if (ball.y - BALL_RADIUS > canvas.height) {
      gameOver = true;
    }

    // level complete
    if (bricks.every(b => !b.alive)) {
      levelIdx++;
      if (levelIdx >= LEVELS.length) {
        gameWon = true;
      } else {
        initLevel();
      }
    }
  }

  // mouse move tracking
  canvas.addEventListener('mousemove', e => {
    canvas.lastMouseX = e.clientX - canvas.getBoundingClientRect().left;
  });

  // keyboard fallback
  document.addEventListener('keydown', e => {
    if (e.key === 'ArrowLeft') paddle.x -= PADDLE_SPEED;
    if (e.key === 'ArrowRight') paddle.x += PADDLE_SPEED;
    paddle.x = Math.max(0, Math.min(paddle.x, canvas.width - paddle.width));
  });

  let last = performance.now();
  function gameLoop(now) {
    const dt = (now - last) / 1000;
    last = now;
    update(dt);
    draw();
    requestAnimationFrame(gameLoop);
  }

  initLevel();
  requestAnimationFrame(gameLoop);
})();
</script>
</body>
</html>

Compare with

Mi:dm K 3.0 Pro: other attempts