Models 44

GPT-5 Nano

by OpenAIReasoning1 console error
Open in new window

Running live in a sandboxed frame. Click inside it before using the keyboard.

How it went

Total time
40.0 s
Cost
$0.0023
Output tokens
5,711
Spent thinking
384 tokens
Speed
156.1 tok/s
Console errors
1
First token after
3.4 s
First visible text after
6.2 s
Prompt tokens
508
Code size
17.2 KB
Finish reason
stop
Served by
OpenAI
Model id
openai/gpt-5-nano
Reasoning sent
{"effort":"low"}
Max output
128,000 tokens
Generated
Nov 12, 2026

Console errors

Caught in the first 3 seconds after load, in a sandboxed iframe like the one above.

brickHeight is not a function

What it was asked

The prompt
Build a polished Breakout (brick breaker) game in a single HTML file using vanilla JavaScript and a <canvas>. Requirements: - A paddle controlled with the mouse and with the left/right arrow keys. - The bounce angle off the paddle depends on where the ball hits it (center = straight up, edges = sharp angle). - One level with at least 6 rows of bricks, mixing normal bricks and tougher bricks that need 2 hits and visibly crack after the first one. - Some bricks visibly carry a power-up icon; when broken they drop it, and catching it with the paddle gives either a wider paddle (10 seconds) or multi-ball (3 balls). - The ball must never pass through bricks or the paddle. - Particles when a brick breaks. - 3 lives, score displayed. Game over and victory screens with a restart option.
System prompt, sent with every prompt
You are generating a self-contained web demo. It will be rendered automatically in a sandboxed iframe and compared side by side with the output of other AI models given the exact same task.

Output rules:
- Reply with exactly ONE fenced code block containing the complete file: ```html (or ```svg when the task asks for an SVG). No text before or after the code block.
- Everything must be inline: CSS in <style>, JavaScript in <script>. No external files, images, fonts or network requests, except the CDN URLs explicitly allowed by the task.
- Do not use localStorage, sessionStorage, cookies, IndexedDB, alert(), confirm(), prompt() or window.open(): they are blocked in the sandbox and will throw.
- The page must look right in a 1280×800 viewport and adapt gracefully down to 375px wide. No horizontal scrollbar.
- Something meaningful must be visible on first paint: a screenshot is taken automatically about 3 seconds after load, without any interaction.
- Everything that makes the demo interesting must be visible within the first seconds: nothing hidden behind a high score, a later level or a long play session.
- Games must start in a self-playing demo mode (a simple AI plays) behind a "Click or press a key to start" message, so the game can be judged in action without playing.
- The iframe only receives keyboard events after the user clicks it: if the demo uses the keyboard, display a clear "Click or press a key to start" message.
- The code must run with zero errors in the console of the latest Chrome.

Code run-1.html · 637 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Breakout AI Demo</title>
<style>
  :root{
    --bg: #0b0f1a;
    --panel: rgba(255,255,255,.1);
    --brick1: #ff6b6b;
    --brick2: #ffd166;
    --brick3: #8bd8bd;
    --text: #e8f0ff;
    --glow: 0 0 12px rgba(0,200,255,.6);
  }
  *{box-sizing:border-box}
  html,body{height:100%;margin:0;background:radial-gradient(circle at 50% -20%, #213, #0b0f1a 40%), #0b0f1a;
    color:var(--text);font-family:Inter, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    overflow:hidden;}
  canvas{display:block; width:100%; height:auto; max-width:1200px; margin:0 auto; outline:none; }
  .ui {
    position: fixed; top: 12px; left: 50%; transform: translateX(-50%);
    display:flex; gap:12px; align-items:center; padding:10px 14px;
    background: rgba(0,0,0,.35); border-radius:12px; backdrop-filter: blur(4px);
    box-shadow: var(--glow);
    user-select:none; z-index: 5;
  }
  .badge { padding:4px 8px; border-radius:8px; background: rgba(255,255,255,.1); font-weight:600; font-size:12px; }
  .overlay {
    position: fixed; inset:0; display:flex; align-items:center; justify-content:center;
    background: rgba(0,0,0,.5); z-index: 4;
  }
  .panel {
    background: rgba(8,12,22,.95); padding:22px 28px; border-radius:12px;
    text-align:center; max-width:520px; box-shadow: 0 8px 28px rgba(0,0,0,.5);
  }
  .panel h1{ margin:0 0 8px; font-size:28px; letter-spacing:.5px; }
  .panel p{ margin:6px 0 14px; color:#cfe0ff; }
  .btn {
    padding:10px 14px; border-radius:8px; border:0; cursor:pointer;
    background: linear-gradient(#4aa3ff, #1a73e8); color:white; font-weight:700;
    box-shadow: inset 0 -2px 0 rgba(0,0,0,.25);
  }
  .btn.secondary{ background: rgba(255,255,255,.15); color:#e8f0ff; }
  @media (max-width: 700px){
    .ui{flex-direction:column; gap:6px}
  }
</style>
</head>
<body>
<canvas id="gameCanvas"></canvas>

<div class="ui" aria-hidden="true" id="uiHint">
  <span class="badge">Controls: Move with mouse or left/right arrows</span>
  <span class="badge">Click or press any key to start (AI will play)</span>
  <span class="badge" id="scoreDisplay">Score: 0</span>
  <span class="badge" id="livesDisplay">Lives: 3</span>
</div>

<div class="overlay" id="startOverlay" style="display:flex">
  <div class="panel">
    <h1>Breakout: AI Demo</h1>
    <p>Watch the AI play the game. You can still move the paddle with the mouse or arrow keys, but the demo starts in demo mode.</p>
    <p>Click or press any key to start the game.</p>
    <button class="btn" id="startBtn">Start Demo</button>
  </div>
</div>

<script>
(() => {
  const canvas = document.getElementById('gameCanvas');
  const ctx = canvas.getContext('2d');
  const DPR = Math.max(1, Math.min(window.devicePixelRatio || 1, 2));
  let W = 1280, H = 800;
  function resize() {
    const rect = canvas.getBoundingClientRect();
    // adapt canvas to CSS size but keep internal resolution for crispness
    canvas.width = Math.floor(rect.width * DPR);
    canvas.height = Math.floor(rect.height * DPR);
    ctx.setTransform(DPR,0,0,DPR,0,0);
    W = rect.width;
    H = rect.height;
  }
  function fitCanvas() {
    const r = Math.min(window.innerWidth*0.95, 1200);
    canvas.style.width = r + 'px';
    canvas.style.height = (r * (H/W || 0.6)) + 'px';
  }
  window.addEventListener('resize', () => { fitCanvas(); resize(); }, {passive:true});
  // initial sizing
  canvas.style.width = '100%';
  resize();
  fitCanvas();

  // Game objects
  const state = {
    score:0,
    lives:3,
    running:false,
    won:false,
    lost:false,
    started:false
  };

  // Entities
  const paddle = {
    width: 110,
    height: 14,
    x: 0, // center
    y: 0,
    speed: 7,
    dx: 0,
    color: '#6be1ff',
    extendedUntil: 0
  };
  const BALL_RADIUS = 6;
  const Ball = function(x,y,vx,vy){
    this.x=x; this.y=y; this.vx=vx; this.vy=vy; this.radius=BALL_RADIUS;
    this.alive=true;
  };
  const balls = [];
  const maxBalls = 3;

  // Bricks
  const bricks = [];
  const BRICK_ROWS = 6;
  const BRICK_COLS = 10;
  const brickWidth = () => Math.max(48, Math.floor((W-40)/BRICK_COLS) - 2);
  const brickHeight = 20;
  const Brick = function(r,c, type){
    this.r=r; this.c=c; this.type=type; // 1 normal, 2 tough
    this.hit=0;
    this.color = type===1? '#56ccff' : '#f4a261';
    this.alive = true;
  };

  // PowerUps
  const Power = function(x,y,type){
    this.x=x; this.y=y; this.type=type; // 'widen' or 'multiball'
    this.vy = 1.5;
    this.col = type==='widen' ? '#4bd6a8' : '#ffd166';
    this.size = 12;
    this.active = true;
  };
  const powers = [];

  // Particles
  const Part = function(x,y, vx,vy, color){
    this.x=x; this.y=y; this.vx=vx; this.vy=vy;
    this.life=60; this.color=color;
  };
  const particles = [];

  // AI/demo mode
  let aiMode = true; // when true, paddle controlled by AI
  let launchPrepared = false;

  // Init world
  function resetLevel(){
    bricks.length = 0;
    const rows = BRICK_ROWS;
    const cols = BRICK_COLS;
    const offsetX = 20;
    const offsetY = 60;
    const w = brickWidth();
    for(let r=0;r<rows;r++){
      for(let c=0;c<cols;c++){
        // mix durability: two tougher rows
        const type = (r%3===0 && Math.random()<0.5) ? 2 : 1;
        bricks.push(new Brick(r,c,type));
        // position
      }
    }
  }
  function layBricks(){
    const cols = BRICK_COLS;
    const rows = BRICK_ROWS;
    const w = brickWidth();
    const h = brickHeight;
    const startX = 20;
    const startY = 60;
    for(let r=0;r<rows;r++){
      for(let c=0;c<cols;c++){
        // compute brick position
        const x = startX + c*(w+2);
        const y = startY + r*(h+4);
        // create brick
      }
    }
  }

  // Brick array initialization with positions
  function initBricks(){
    bricks.length = 0;
    const cols = BRICK_COLS;
    const rows = BRICK_ROWS;
    const w = brickWidth();
    const h = brickHeight;
    const startX = 20;
    const startY = 60;
    for(let r=0;r<rows;r++){
      for(let c=0;c<cols;c++){
        const type = (r<2)?1: (r===2||r===3?2: (Math.random()<0.2?2:1));
        const b = new Brick(r,c,type);
        // position
        b.x = startX + c*(w+2);
        b.y = startY + r*(h+4);
        bricks.push(b);
      }
    }
  }

  // helper collisions
  function circleRectCollision(cx,cy,cr, rx,ry, rw, rh){
    // closest point
    const closestX = Math.max(rx, Math.min(cx, rx+rw));
    const closestY = Math.max(ry, Math.min(cy, ry+rh));
    const dx = cx - closestX;
    const dy = cy - closestY;
    return (dx*dx + dy*dy) <= cr*cr;
  }

  function spawnParticle(x,y,color){
    for(let i=0;i<8;i++){
      const a = (Math.PI*2) * Math.random();
      const s = Math.random()*1.5 + 0.4;
      particles.push(new Part(x,y, Math.cos(a)*s, Math.sin(a)*s, color));
    }
  }

  // Init
  function init(){
    paddle.x = W/2 - paddle.width/2;
    paddle.y = H - 40;
    paddle.dx = 0;
    balls.length = 0;
    // 1 ball on start
    const b = new Ball(W/2, paddle.y - 12, 3, -3);
    balls.push(b);
    initBricks();
    state.score = 0;
    state.lives = 3;
    state.running = false;
    state.won = false;
    state.lost = false;
    state.started = false;
    aiMode = true;
    launchPrepared = false;
    powers.length = 0;
    particles.length = 0;
  }

  // draw helpers
  function drawPaddle(){
    ctx.fillStyle = paddle.color;
    ctx.beginPath();
    ctx.roundRect(paddle.x, paddle.y, paddle.width, paddle.height, 6);
    ctx.fill();
    // glow
    ctx.fillStyle = 'rgba(107,225,255,.25)';
    ctx.fillRect(paddle.x, paddle.y+paddle.height, paddle.width, 4);
  }

  function drawBall(ball){
    ctx.fillStyle = '#fff';
    ctx.beginPath();
    ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI*2);
    ctx.fill();
  }

  function drawBricks(){
    for(const b of bricks){
      if(!b.alive) continue;
      ctx.fillStyle = b.color;
      ctx.fillRect(b.x, b.y, brickWidth(), brickHeight);
      // crack for durability >1
      if(b.type>1){
        ctx.strokeStyle = '#000';
        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.moveTo(b.x+2, b.y+2);
        ctx.lineTo(b.x+brickWidth()-2, b.y+brickHeight()-2);
        ctx.stroke();
      }
      if(b.hit>0){
        // subtle crack overlay
        ctx.fillStyle = 'rgba(0,0,0,.15)';
        ctx.fillRect(b.x, b.y, brickWidth(), brickHeight());
      }
      // power-up indicator
      if(b.type>0 && b.hit===1){
        // small icon top-right
        ctx.fillStyle = '#fff';
        ctx.fillRect(b.x+brickWidth()-8, b.y+2, 6,6);
      }
    }
  }

  function drawPowers(){
    for(const p of powers){
      if(!p.active) continue;
      ctx.fillStyle = p.col;
      ctx.fillRect(p.x - p.size/2, p.y - p.size/2, p.size, p.size);
      // icon
      ctx.fillStyle = '#000';
      ctx.fillRect(p.x - 2, p.y - 2, 4, 4);
    }
  }

  function drawParticles(){
    for(const pt of particles){
      ctx.fillStyle = pt.color;
      ctx.fillRect(pt.x, pt.y, 2,2);
    }
  }

  function updatePaddle(input){
    // keyboard
    let targetDx = 0;
    if(input.left) targetDx = -paddle.speed;
    if(input.right) targetDx = paddle.speed;
    // smooth
    paddle.dx += (targetDx - paddle.dx) * 0.2;
    paddle.x += paddle.dx;
    // clamp
    const maxX = W - paddle.width;
    if(paddle.x < 0) paddle.x = 0;
    if(paddle.x > maxX) paddle.x = maxX;

    // mouse (handled globally: update paddle.x directly)
  }

  // Input
  const input = { left:false, right:false, mouseX:0, mouseY:0 };
  window.addEventListener('mousemove', (e)=>{
    const rect = canvas.getBoundingClientRect();
    const mx = (e.clientX - rect.left);
    input.mouseX = mx;
  });
  window.addEventListener('keydown', (e)=> {
    if(!state.started){
      // start via key
      startGame();
    }
  }, {passive:true});
  window.addEventListener('mousedown', ()=>{
    if(!state.started){
      startGame();
    }
  });

  // Power handling
  function applyPower(type){
    if(type==='widen'){
      paddle.width = Math.min(W*0.4, paddle.width + 60);
      paddle.extendedUntil = performance.now() + 10000;
    } else if(type==='multiball'){
      // spawn 2 extra balls
      for(let i=0;i<2;i++){
        const base = balls[0];
        const ang = (i===0? -0.5 : 0.5);
        const b = new Ball(base.x, base.y, Math.cos(ang)*3, -3);
        balls.push(b);
      }
    }
  }

  // Start game
  function startGame(){
    if(state.started) return;
    state.started = true;
    state.running = true;
    launchPrepared = true;
    // set initial ball velocity randomized
    for(let i=0;i<balls.length;i++){
      balls[i].vx = (Math.random()*2-1)*3;
      balls[i].vy = -3;
    }
    // hide overlay
    document.getElementById('startOverlay').style.display='none';
  }

  // Collision handling
  function handleCollisions(){
    // Ball vs walls
    for(const b of balls){
      if(!b.alive) continue;
      // left/right walls
      if(b.x - b.radius < 0){
        b.x = b.radius;
        b.vx *= -1;
      } else if (b.x + b.radius > W){
        b.x = W - b.radius;
        b.vx *= -1;
      }
      // top
      if(b.y - b.radius < 0){
        b.y = b.radius;
        b.vy *= -1;
      }
      // bottom (lose life)
      if(b.y - b.radius > H){
        b.alive = false;
      }
    }

    // Ball vs paddle
    const p = paddle;
    for(const b of balls){
      if(!b.alive) continue;
      if(circleRectCollision(b.x,b.y,b.radius, p.x, p.y, p.width, p.height)){
        // compute hit position
        const hitPos = (b.x - (p.x + p.width/2)) / (p.width/2); // -1..1
        const angle = hitPos * Math.PI/3; // max +/-60 deg
        const speed = Math.hypot(b.vx, b.vy);
        b.vx = speed * Math.sin(angle);
        b.vy = -Math.abs(speed * Math.cos(angle));
        // nudge ball out of paddle
        b.y = p.y - b.radius - 1;
      }
    }

    // Ball vs bricks
    for(const br of bricks){
      if(!br.alive) continue;
      const w = brickWidth();
      const h = brickHeight;
      const rx = br.x, ry = br.y;
      const hit = circleRectCollision(0,0,0,0,0,0,0); // placeholder to satisfy lints
      // Use AABB collision for brick hit
      for(const b of balls){
        if(!b.alive) continue;
        const cx = b.x, cy = b.y;
        const insideX = cx > rx && cx < rx + w;
        const insideY = cy > ry && cy < ry + h;
        const nearby = insideX && insideY;
        // but better: simple circle-rect overlap check
        if(circleRectCollision(b.x,b.y,b.radius, rx, ry, w, h)){
          // reflect ball
          // determine side by comparing previous position
          // approximate: invert y velocity
          b.vy *= -1;
          // damage brick
          br.hit += 1;
          if(br.type>1 && br.hit>=2){
            br.alive = false;
            // small chance to drop power
            if(Math.random()<0.5){
              const t = Math.random()<0.5 ? 'widen' : 'multiball';
              powers.push(new Power(br.x + w/2, br.y + h/2, t));
            }
          } else if(br.hit>=1 && br.type===1){
            br.alive = false;
            // drop power sometimes
            if(Math.random()<0.15){
              const t = Math.random()<0.5 ? 'widen' : 'multiball';
              powers.push(new Power(br.x + w/2, br.y + h/2, t));
            }
          }
          // particles
          spawnParticle(b.x, b.y, br.color);
          state.score += br.alive ? 5 : 15;
          break;
        }
      }
    }
  }

  // Update
  function update(dt){
    // mouse control
    // keep paddle within screen
    paddle.width = paddle.width; // no-op

    // AI movement if enabled
    if(aiMode){
      // simple pursuit of ball center
      const targetX = balls[0]?.x ?? (W/2);
      const center = paddle.x + paddle.width/2;
      const diff = targetX - center;
      paddle.dx = diff * 0.05;
      paddle.x += paddle.dx;
      // clamp
      if(paddle.x < 0) paddle.x = 0;
      if(paddle.x > W - paddle.width) paddle.x = W - paddle.width;
    } else {
      // responsive to mouse
      if(input.mouseX){
        paddle.x = input.mouseX - paddle.width/2;
        if(paddle.x < 0) paddle.x = 0;
        if(paddle.x > W - paddle.width) paddle.x = W - paddle.width;
      }
      // keyboard
      updatePaddle(input);
    }

    // update balls
    for(const b of balls){
      if(!b.alive) continue;
      b.x += b.vx;
      b.y += b.vy;
    }

    // powers movement
    for(const p of powers){
      if(!p.active) continue;
      p.y += p.vy;
      // catch paddle
      if(circleRectCollision(p.x, p.y, 6, paddle.x, paddle.y, paddle.width, paddle.height)){
        p.active = false;
        applyPower(p.type);
      }
      // collect off-screen
      if(p.y > H){
        p.active = false;
      }
    }

    // bricks check
    handleCollisions();

    // particles life
    for(let i=particles.length-1; i>=0; i--){
      const pr = particles[i];
      pr.life--;
      pr.x += pr.vx;
      pr.y += pr.vy;
      if(pr.life<=0) particles.splice(i,1);
    }

    // cleanup dead balls
    for(let i=balls.length-1;i>=0;i--){
      if(!balls[i].alive){
        balls.splice(i,1);
      }
    }
    if(balls.length===0){
      // lose a life, reset ball
      state.lives--;
      if(state.lives<=0){
        state.lost = true; state.running = false;
        document.getElementById('startOverlay').style.display='flex';
      } else {
        // new ball on paddle
        const nb = new Ball(W/2, paddle.y - 12, 3, -3);
        balls.push(nb);
      }
    }

    // win condition
    const anyAlive = bricks.some(br => br.alive);
    if(!anyAlive){
      state.won = true; state.running=false;
      document.getElementById('startOverlay').style.display='flex';
      document.getElementById('startOverlay').innerHTML = `
        <div class="panel">
          <h1>You Win!</h1>
          <p>Score: ${state.score}</p>
          <button class="btn" id="startBtn2">Play Again</button>
        </div>
      `;
      document.getElementById('startBtn2').addEventListener('click', ()=> location.reload());
    }

    // update UI
    document.getElementById('scoreDisplay').textContent = 'Score: ' + state.score;
    document.getElementById('livesDisplay').textContent = 'Lives: ' + state.lives;

    // end overlay reset on interaction
  }

  function render(){
    // clear
    ctx.clearRect(0,0,W,H);
    // background gradient
    const g = ctx.createLinearGradient(0,0,0,H);
    g.addColorStop(0,'#0a1a2b');
    g.addColorStop(1,'#091422');
    ctx.fillStyle = g;
    ctx.fillRect(0,0,W,H);

    // bricks
    drawBricks();

    // powerups
    drawPowers();

    // balls
    for(const b of balls) drawBall(b);

    // paddle
    drawPaddle();

    // particles
    drawParticles();

    // overlay early
  }

  // main loop
  let last = performance.now();
  function loop(t){
    const dt = (t - last) / 16.666;
    last = t;
    if(state.running){
      update(dt);
    }
    render();
    requestAnimationFrame(loop);
  }

  // Start
  function initAll(){
    init();
    // create bricks with positions
    bricks.length = 0;
    initBricks();
    // initial paddle width
    paddle.width = 110;
    paddle.y = H - 40;
    paddle.x = W/2 - paddle.width/2;
    // initial ball
    balls.length = 0;
    balls.push(new Ball(W/2, paddle.y - 12, 3, -3));
    // start overlay
    document.getElementById('startOverlay').style.display='flex';
    requestAnimationFrame(loop);
  }

  // kick off
  initAll();

})();
</script>
</body>
</html>
Reasoning trace (384 tokens)
**Planning game mechanics**

I’m planning to implement both mouse movement and arrow keys to adjust paddle position. I also want to handle collisions, where the ball bounces off bricks that lose durability, featuring a crack effect. Power-ups will appear as icons for a wider paddle or multiple balls. When these are collected, they grant effects like a wider paddle for ten seconds or three additional balls. Additionally, I'll ensure there's a requestAnimationFrame loop for smooth gameplay, starting with the ball stationary on the paddle. Let's make sure it's all coded within a single HTML file while being responsive!**Designing user interaction**

I’m planning to implement a "start" function that activates when the user clicks or presses a key. After that, the AI will take over paddle movement automatically, even though the mouse and keys will still work. However, during this AI mode, I’ll need to ignore user input. The requirement mentions a demo mode behind a message, which seems fine. With time limited, I’ll aim for a simplified yet functional version. So, let's get coding!