• Updated 11 Nov, 12:43 UTC
  • Next run 12 Nov, 05:17 UTCNext 12 Nov 05:17
  • Last 7 days: 10 new models · 61 runs7 d: 10 models · 61 runs
Playable Breakout

Hy4Tencent

Network offOpen alone (new tab)

Run details

Model
tencent/hy4
Route
OpenRouter API, one user message
Served by
Tencent
Effort
high, OpenRouter's default for this model
Limits
60 min · 65,536 output tokens (the model's limit)
Date
7 Nov 2026
Time
2 min 17 s
Size
18 kB
Tokens
97 in, 20,371 out (14,347 of them thinking)
List price
$0.026 at today's price ($0.417 in / $1.25 out per M)
Paid
$0.026

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

18 kB 546 lines Raw

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NEON BREAKOUT</title>
<style>
    * { box-sizing: border-box; }
    body, html {
        margin: 0; padding: 0; width: 100%; height: 100%;
        background: #050508;
        display: flex; align-items: center; justify-content: center;
        font-family: "Courier New", Courier, monospace;
        overflow: hidden;
        color: #fff;
        user-select: none;
    }
    #wrapper {
        position: relative;
        width: 100%;
        max-width: 800px;
        aspect-ratio: 4 / 3;
        border: 2px solid #1a1c23;
        border-radius: 6px;
        box-shadow: 0 0 50px rgba(69, 243, 255, 0.1), 0 0 15px rgba(255, 42, 109, 0.08), inset 0 0 20px rgba(0,0,0,0.8);
        background: #0b0c10;
        overflow: hidden;
    }
    /* CRT Scanlines */
    #wrapper::after {
        content: "";
        position: absolute;
        top: 0; left: 0; right: 0; bottom: 0;
        background: repeating-linear-gradient(0deg, rgba(0,0,0,0.12), rgba(0,0,0,0.12) 1px, transparent 1px, transparent 3px);
        pointer-events: none;
        z-index: 10;
    }
    canvas {
        display: block;
        width: 100%;
        height: 100%;
        cursor: crosshair;
    }
</style>
</head>
<body>

<div id="wrapper">
    <canvas id="game" width="800" height="600"></canvas>
</div>

<script>
/** @type {HTMLCanvasElement} */
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');

// --- Audio Synth (No external assets) ---
let audioCtx = null;
function initAudio() {
    if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
    if (audioCtx.state === 'suspended') audioCtx.resume();
}
function playSound(type, pitchMod = 1) {
    if (!audioCtx) return;
    const t = audioCtx.currentTime;
    const osc = audioCtx.createOscillator();
    const gain = audioCtx.createGain();
    osc.connect(gain);
    gain.connect(audioCtx.destination);

    if (type === 'paddle') {
        osc.type = 'square';
        osc.frequency.setValueAtTime(180 * pitchMod, t);
        osc.frequency.exponentialRampToValueAtTime(80 * pitchMod, t + 0.08);
        gain.gain.setValueAtTime(0.06, t);
        gain.gain.exponentialRampToValueAtTime(0.001, t + 0.08);
        osc.start(t); osc.stop(t + 0.08);
    } else if (type === 'brick') {
        osc.type = 'triangle';
        osc.frequency.setValueAtTime(300 * pitchMod, t);
        osc.frequency.exponentialRampToValueAtTime(600 * pitchMod, t + 0.1);
        gain.gain.setValueAtTime(0.08, t);
        gain.gain.exponentialRampToValueAtTime(0.001, t + 0.1);
        osc.start(t); osc.stop(t + 0.1);
    } else if (type === 'lose') {
        osc.type = 'sawtooth';
        osc.frequency.setValueAtTime(120, t);
        osc.frequency.exponentialRampToValueAtTime(40, t + 0.6);
        gain.gain.setValueAtTime(0.12, t);
        gain.gain.exponentialRampToValueAtTime(0.001, t + 0.6);
        osc.start(t); osc.stop(t + 0.6);
    } else if (type === 'clear') {
        osc.type = 'sine';
        osc.frequency.setValueAtTime(400, t);
        osc.frequency.setValueAtTime(500, t + 0.1);
        osc.frequency.setValueAtTime(600, t + 0.2);
        osc.frequency.setValueAtTime(800, t + 0.3);
        gain.gain.setValueAtTime(0.1, t);
        gain.gain.linearRampToValueAtTime(0.001, t + 0.5);
        osc.start(t); osc.stop(t + 0.5);
    }
}

// --- Constants & Levels ---
const COLORS = { 1: '#45f3ff', 2: '#ff2a6d', 3: '#f9cb28' };
const rawLevels = [
    [ // Level 1: Classic Wall
        "111111111111",
        "111111111111",
        "111111111111",
        "111111111111"
    ],
    [ // Level 2: Pyramid
        "000111111000",
        "001222222100",
        "012233332210",
        "123333333321"
    ],
    [ // Level 3: The Fortress
        "330000000033",
        "322222222223",
        "321111111123",
        "321033330123",
        "321000000123",
        "321111111123",
        "322222222223",
        "330000000033"
    ]
];

// --- Game State ---
let state = 'MENU'; // MENU, PLAYING, LEVEL_CLEAR, GAME_OVER, VICTORY
let score = 0, lives = 3, level = 0, combo = 0;
let shake = 0, bgGlow = 0, comboPop = 0;
let bricks = [], particles = [];

const paddle = { x: 345, y: 550, w: 110, h: 16, squash: 0 };
const ball = { x: 400, y: 530, r: 7, vx: 0, vy: 0, trail: [] };
let mouseX = 400;
const keys = { left: false, right: false };

function loadLevel(idx) {
    bricks = [];
    const layout = rawLevels[idx];
    const cols = 12, bw = 60, bh = 24, px = 4, py = 6;
    const startX = (800 - (cols * (bw + px) - px)) / 2;
    const startY = 75;

    for (let r = 0; r < layout.length; r++) {
        // Auto-pad/center rows just in case
        const str = layout[r].padEnd(12, '0').substring(0, 12);
        for (let c = 0; c < cols; c++) {
            const hp = parseInt(str[c]);
            if (hp > 0) {
                bricks.push({
                    x: startX + c * (bw + px),
                    y: startY + r * (bh + py),
                    w: bw, h: bh,
                    hp: hp, flash: 0
                });
            }
        }
    }
}

function resetBall() {
    ball.x = paddle.x + paddle.w / 2;
    ball.y = paddle.y - ball.r - 2;
    ball.vx = 0; ball.vy = 0;
    ball.trail = [];
    combo = 0;
}

function launchBall() {
    const angle = (Math.random() - 0.5) * (Math.PI / 3.5);
    const speed = 6 + level * 0.6;
    ball.vx = Math.sin(angle) * speed;
    ball.vy = -Math.cos(angle) * speed;
}

function resetGame() {
    score = 0; lives = 3; level = 0; combo = 0;
    particles = []; shake = 0; bgGlow = 0;
    loadLevel(level);
    resetBall();
    state = 'MENU';
}

function action() {
    initAudio();
    if (state === 'MENU') {
        launchBall();
        state = 'PLAYING';
    } else if (state === 'GAME_OVER' || state === 'VICTORY') {
        resetGame();
    }
}

// --- Input ---
window.addEventListener('keydown', e => {
    if (['Space', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(e.code)) e.preventDefault();
    if (e.code === 'ArrowLeft' || e.code === 'KeyA') keys.left = true;
    if (e.code === 'ArrowRight' || e.code === 'KeyD') keys.right = true;
    if (e.code === 'Space' && !e.repeat) action();
});
window.addEventListener('keyup', e => {
    if (e.code === 'ArrowLeft' || e.code === 'KeyA') keys.left = false;
    if (e.code === 'ArrowRight' || e.code === 'KeyD') keys.right = false;
});
canvas.addEventListener('mousemove', e => {
    const rect = canvas.getBoundingClientRect();
    mouseX = (e.clientX - rect.left) * (canvas.width / rect.width);
});
canvas.addEventListener('mousedown', action);
// Touch support for mobile bonus
canvas.addEventListener('touchmove', e => {
    e.preventDefault();
    const rect = canvas.getBoundingClientRect();
    mouseX = (e.touches[0].clientX - rect.left) * (canvas.width / rect.width);
}, { passive: false });
canvas.addEventListener('touchstart', e => { e.preventDefault(); action(); }, { passive: false });

// --- Logic ---
function spawnParticles(x, y, color, count = 8) {
    for (let i = 0; i < count; i++) {
        particles.push({
            x: x + (Math.random() - 0.5) * 30,
            y: y + (Math.random() - 0.5) * 15,
            vx: (Math.random() - 0.5) * 12,
            vy: (Math.random() - 0.5) * 12 - 2,
            life: 1, color: color,
            size: Math.random() * 4 + 2
        });
    }
}

function update(f) {
    // Decay juice
    if (shake > 0) { shake *= Math.pow(0.88, f); if (shake < 0.5) shake = 0; }
    if (bgGlow > 0) { bgGlow *= Math.pow(0.94, f); if (bgGlow < 0.01) bgGlow = 0; }
    if (comboPop > 0) { comboPop *= Math.pow(0.85, f); if (comboPop < 0.01) comboPop = 0; }
    if (paddle.squash > 0) { paddle.squash *= Math.pow(0.85, f); if (paddle.squash < 0.01) paddle.squash = 0; }

    // Paddle Move
    if (keys.left) paddle.x -= 9 * f;
    if (keys.right) paddle.x += 9 * f;
    if (!keys.left && !keys.right) {
        paddle.x += (mouseX - paddle.w / 2 - paddle.x) * 0.25 * f;
    }
    paddle.x = Math.max(0, Math.min(800 - paddle.w, paddle.x));

    if (state === 'MENU') {
        ball.x = paddle.x + paddle.w / 2;
        ball.y = paddle.y - ball.r - 2;
    } 
    else if (state === 'PLAYING') {
        ball.trail.unshift({ x: ball.x, y: ball.y });
        if (ball.trail.length > 14) ball.trail.pop();

        // Sub-stepping for perfect collision
        const steps = 4;
        const sx = (ball.vx * f) / steps;
        const sy = (ball.vy * f) / steps;

        for (let s = 0; s < steps; s++) {
            ball.x += sx; ball.y += sy;

            // Walls (Math.abs prevents sticky vibrations)
            if (ball.x - ball.r < 0) { ball.x = ball.r; ball.vx = Math.abs(ball.vx); playSound('paddle', 0.6); }
            if (ball.x + ball.r > 800) { ball.x = 800 - ball.r; ball.vx = -Math.abs(ball.vx); playSound('paddle', 0.6); }
            if (ball.y - ball.r < 0) { ball.y = ball.r; ball.vy = Math.abs(ball.vy); playSound('paddle', 0.6); }

            // Paddle
            if (ball.vy > 0 && ball.y + ball.r > paddle.y && ball.y - ball.r < paddle.y + paddle.h &&
                ball.x > paddle.x && ball.x < paddle.x + paddle.w) {
                
                const hit = (ball.x - (paddle.x + paddle.w / 2)) / (paddle.w / 2);
                const angle = hit * (Math.PI / 3); // Max 60deg
                const speed = Math.min(Math.hypot(ball.vx, ball.vy) * 1.03, 11.5);
                ball.vx = Math.sin(angle) * speed;
                ball.vy = -Math.cos(angle) * speed;
                ball.y = paddle.y - ball.r - 1; // Hard eject
                
                combo = 0;
                paddle.squash = 1;
                shake = 3;
                playSound('paddle');
                break; 
            }

            // Bricks
            let hitBrick = false;
            for (let i = 0; i < bricks.length; i++) {
                const b = bricks[i];
                if (b.hp <= 0) continue;
                
                const cx = Math.max(b.x, Math.min(ball.x, b.x + b.w));
                const cy = Math.max(b.y, Math.min(ball.y, b.y + b.h));
                const dx = ball.x - cx, dy = ball.y - cy;
                
                if (dx * dx + dy * dy < ball.r * ball.r) {
                    const ox = (ball.r + b.w / 2) - Math.abs(ball.x - (b.x + b.w / 2));
                    const oy = (ball.r + b.h / 2) - Math.abs(ball.y - (b.y + b.h / 2));
                    
                    if (ox < oy) { ball.vx *= -1; ball.x += ball.vx > 0 ? ox : -ox; }
                    else { ball.vy *= -1; ball.y += ball.vy > 0 ? oy : -oy; }

                    const oldHp = b.hp;
                    b.hp--;
                    b.flash = 3;
                    combo++;
                    comboPop = 0.4;
                    score += 10 * combo;
                    shake = Math.min(shake + 2 + combo * 0.5, 16);
                    bgGlow = Math.min(bgGlow + 0.3, 1);
                    playSound('brick', 1 + combo * 0.07);
                    spawnParticles(b.x + b.w/2, b.y + b.h/2, COLORS[oldHp]);
                    
                    if (b.hp <= 0) {
                        score += 50 * combo;
                        spawnParticles(b.x + b.w/2, b.y + b.h/2, '#ffffff', 16);
                    }
                    hitBrick = true;
                    break;
                }
            }
            if (hitBrick) break;
            
            // Fall out
            if (ball.y - ball.r > 600) {
                lives--;
                playSound('lose');
                shake = 25;
                bgGlow = 0;
                if (lives > 0) { state = 'MENU'; resetBall(); }
                else { state = 'GAME_OVER'; }
                break;
            }
        }

        // Win check
        if (state === 'PLAYING' && bricks.every(b => b.hp <= 0)) {
            state = 'LEVEL_CLEAR';
            playSound('clear');
            bgGlow = 1;
            setTimeout(() => {
                level++;
                if (level >= rawLevels.length) state = 'VICTORY';
                else { loadLevel(level); resetBall(); state = 'MENU'; }
            }, 1800);
        }
    }

    // Particles
    for (let i = particles.length - 1; i >= 0; i--) {
        const p = particles[i];
        p.x += p.vx * f; p.y += p.vy * f;
        p.vx *= Math.pow(0.94, f);
        p.vy *= Math.pow(0.94, f);
        p.vy += 0.25 * f; // Gravity
        p.life -= 0.025 * f;
        if (p.life <= 0) particles.splice(i, 1);
    }
}

// --- Render ---
function draw() {
    ctx.fillStyle = '#0b0c10';
    ctx.fillRect(0, 0, 800, 600);

    // Background Pulse
    if (bgGlow > 0.01) {
        const grad = ctx.createRadialGradient(400, 300, 0, 400, 300, 450);
        grad.addColorStop(0, `rgba(69, 243, 255, ${bgGlow * 0.12})`);
        grad.addColorStop(1, 'rgba(11,12,16,0)');
        ctx.fillStyle = grad;
        ctx.fillRect(0, 0, 800, 600);
    }

    // Grid
    ctx.strokeStyle = 'rgba(255,255,255,0.025)';
    ctx.lineWidth = 1;
    ctx.beginPath();
    for (let i = 0; i <= 800; i += 40) { ctx.moveTo(i, 0); ctx.lineTo(i, 600); }
    for (let i = 0; i <= 600; i += 40) { ctx.moveTo(0, i); ctx.lineTo(800, i); }
    ctx.stroke();

    ctx.save();
    if (shake > 0) ctx.translate((Math.random() - 0.5) * shake, (Math.random() - 0.5) * shake);

    // Bricks
    bricks.forEach(b => {
        if (b.hp <= 0) return;
        const c = COLORS[b.hp];
        ctx.fillStyle = b.flash > 0 ? '#ffffff' : c;
        ctx.shadowColor = c;
        ctx.shadowBlur = b.flash > 0 ? 25 : 8;
        ctx.fillRect(b.x, b.y, b.w, b.h);
        ctx.shadowBlur = 0;
        
        // Armored Inset
        ctx.fillStyle = 'rgba(11, 12, 16, 0.65)';
        ctx.fillRect(b.x + 3, b.y + 3, b.w - 6, b.h - 6);
        if (b.hp > 1) {
            ctx.fillStyle = b.flash > 0 ? '#fff' : c;
            ctx.fillRect(b.x + b.w/2 - 5, b.y + b.h/2 - 2, 10, 4);
        }
        if (b.flash > 0) b.flash--;
    });

    // Additive Juice (Particles + Trail)
    ctx.globalCompositeOperation = 'lighter';
    particles.forEach(p => {
        ctx.globalAlpha = p.life;
        ctx.fillStyle = p.color;
        ctx.fillRect(p.x, p.y, p.size, p.size);
    });
    
    for (let i = 0; i < ball.trail.length; i++) {
        const t = ball.trail[i];
        const a = 1 - (i / ball.trail.length);
        ctx.beginPath();
        ctx.arc(t.x, t.y, ball.r * a, 0, Math.PI * 2);
        ctx.fillStyle = `rgba(69, 243, 255, ${a * 0.4})`;
        ctx.fill();
    }
    ctx.globalAlpha = 1;
    ctx.globalCompositeOperation = 'source-over';

    // Ball
    ctx.beginPath();
    ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
    ctx.fillStyle = '#fff';
    ctx.shadowColor = '#45f3ff';
    ctx.shadowBlur = 18;
    ctx.fill();
    ctx.shadowBlur = 0;

    // Paddle (with squash)
    const sq = paddle.squash;
    const pw = paddle.w * (1 + sq * 0.25);
    const ph = paddle.h * (1 - sq * 0.45);
    const px = paddle.x - (pw - paddle.w) / 2;
    const py = paddle.y + (paddle.h - ph);
    ctx.fillStyle = '#fff';
    ctx.shadowColor = '#ff2a6d';
    ctx.shadowBlur = 15 + sq * 15;
    ctx.fillRect(px, py, pw, ph);
    ctx.shadowBlur = 0;

    ctx.restore(); // End shake

    // --- UI (Unshaken) ---
    ctx.font = 'bold 20px "Courier New", monospace';
    ctx.fillStyle = '#fff';
    ctx.textAlign = 'left';
    ctx.fillText(`SCORE ${String(score).padStart(6, '0')}`, 20, 32);
    
    ctx.textAlign = 'center';
    ctx.fillStyle = '#888';
    ctx.fillText(`STAGE ${level + 1} / ${rawLevels.length}`, 400, 32);
    
    ctx.textAlign = 'right';
    ctx.fillStyle = '#ff2a6d';
    ctx.fillText('●'.repeat(Math.max(0, lives)), 780, 32);

    // Combo Pop
    if (combo > 1 && state === 'PLAYING') {
        ctx.save();
        ctx.translate(400, 110);
        const scale = 1 + Math.min(combo * 0.08, 1.2) + comboPop;
        ctx.scale(scale, scale);
        ctx.fillStyle = `rgba(249, 203, 40, ${Math.min(1, 0.4 + combo / 4)})`;
        ctx.font = 'bold 34px "Courier New", monospace';
        ctx.shadowColor = '#f9cb28';
        ctx.shadowBlur = 15;
        ctx.fillText(`COMBO ×${combo}`, 0, 0);
        ctx.restore();
    }

    // Overlays
    if (state !== 'PLAYING') {
        ctx.fillStyle = 'rgba(11, 12, 16, 0.75)';
        ctx.fillRect(0, 190, 800, 220);
        ctx.strokeStyle = 'rgba(69, 243, 255, 0.3)';
        ctx.lineWidth = 2;
        ctx.strokeRect(0, 190, 800, 220);

        ctx.textAlign = 'center';
        ctx.shadowBlur = 20;

        if (state === 'MENU') {
            ctx.shadowColor = '#45f3ff';
            ctx.fillStyle = '#fff';
            ctx.font = 'bold 44px "Courier New", monospace';
            ctx.fillText(lives === 3 ? 'NEON BREAKOUT' : 'READY?', 400, 265);
            ctx.shadowBlur = 0;
            ctx.font = '18px "Courier New", monospace';
            ctx.fillStyle = '#aaa';
            ctx.fillText('CLICK / TAP / SPACE  TO LAUNCH', 400, 320);
            ctx.fillStyle = '#666';
            ctx.fillText('MOVE:  MOUSE  •  ← →  •  A D', 400, 355);
        } 
        else if (state === 'LEVEL_CLEAR') {
            ctx.shadowColor = '#45f3ff';
            ctx.fillStyle = '#fff';
            ctx.font = 'bold 44px "Courier New", monospace';
            ctx.fillText('STAGE CLEAR!', 400, 300);
        } 
        else if (state === 'GAME_OVER' || state === 'VICTORY') {
            const win = state === 'VICTORY';
            ctx.shadowColor = win ? '#f9cb28' : '#ff2a6d';
            ctx.fillStyle = '#fff';
            ctx.font = 'bold 48px "Courier New", monospace';
            ctx.fillText(win ? 'SYSTEM CLEARED' : 'GAME OVER', 400, 270);
            ctx.shadowBlur = 0;
            ctx.font = 'bold 20px "Courier New", monospace';
            ctx.fillStyle = '#aaa';
            ctx.fillText(`FINAL SCORE: ${score}`, 400, 320);
            ctx.fillStyle = '#fff';
            // Blinking prompt
            if (Math.floor(performance.now() / 500) % 2 === 0) {
                ctx.fillText('PRESS SPACE TO RESTART', 400, 365);
            }
        }
        ctx.shadowBlur = 0;
    }
}

// --- Boot ---
loadLevel(0);
resetBall();
let lastTime = performance.now();

function loop(time) {
    let dt = (time - lastTime) / 1000;
    lastTime = time;
    if (dt > 0.05) dt = 0.05; // Cap lag spikes
    update(dt * 60); // Normalize to 60fps units
    draw();
    requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
</script>
</body>
</html>

Compare with

Hy4: other attempts