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
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>

