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
33.1 s
Cost
$0.0020
Output tokens
4,903
Spent thinking
0 tokens
Speed
152.5 tok/s
Console errors
1
First token after
973 ms
First visible text after
973 ms
Prompt tokens
508
Code size
16.3 KB
Finish reason
stop
Served by
OpenAI
Model id
openai/gpt-5-nano
Reasoning sent
{"effort":"minimal"}
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.

Cannot set properties of undefined (setting 'w')

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 · 564 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Breakout Mini - Self-Play Demo</title>
<style>
  :root{
    --bg: #0b1020;
    --panel: rgba(20,20,40,.9);
    --brick: #2d9cdb;
    --brick2: #e67e22;
    --paddle: #66ff92;
    --ball: #ffffff;
    --charge: #ffd700;
    --shadow: rgba(0,0,0,.4);
  }
  html,body{height:100%;margin:0}
  body{
    background: radial-gradient(circle at 30% -10%, #1a1f3a 0%, #0b1020 40%, #0b1020 100%), #0b1020;
    color:#e8e8e8;
    font-family: system-ui, -apple-system, "Segoe UI", Roboto, Arial;
    overflow:hidden;
  }
  .wrap{
    width:100%;
    height:100%;
    display:flex;
    align-items:flex-start;
    justify-content:center;
    padding:20px;
    box-sizing:border-box;
  }
  .card{
    width: min(1100px, 92vw);
    aspect-ratio: 16 / 9;
    background: linear-gradient(#11152b, #0b1020);
    border-radius: 14px;
    box-shadow: 0 20px 40px rgba(0,0,0,.5);
    position:relative;
    overflow:hidden;
    display:flex;
    flex-direction:column;
  }
  .hud{
    display:flex; gap:14px; align-items:center;
    padding:10px 14px; font-family:monospace;
    font-size:14px; color:#d5e0ff;
    background: linear-gradient(to bottom, rgba(0,0,0,.25), rgba(0,0,0,.45));
    position:relative; z-index:2;
  }
  .hud .badge{ padding:6px 10px; border-radius:8px; background:#1e2540; border:1px solid #3a4b8a; }
  .hud .grow{ margin-left:auto; }
  .stage{ flex:1; position:relative; }
  canvas{ width:100%; height:100%; display:block; background: linear-gradient(#0a0f1a, #0a0f1a); }
  .overlay{
    position:absolute; inset:0;
    display:flex; align-items:center; justify-content:center;
    background: rgba(8,12,25,.55);
    color:#eaf0ff; font-family: system-ui, sans-serif;
    font-size:20px; text-align:center;
    letter-spacing:.5px;
  }
  .overlay .btn{
    margin-top:14px;
    padding:10px 16px; border-radius:8px;
    border:1px solid #5a7bd8; background:#2a4bd3; color:white;
    cursor:pointer;
  }
  .overlay .btn:focus{ outline:2px solid #89a4ff; outline-offset:2px; }
  @media (max-width: 700px){
    .card{ width:100%; height:auto; aspect-ratio: auto; }
    .hud{ font-size:12px; }
  }
  /* simple flourish for bricks */
  .crack{ opacity:.0; transition:opacity .2s; }
  .crack.active{ opacity:1; }
</style>
</head>
<body>
<div class="wrap">
  <div class="card" id="gameCard" aria-label="Breakout game demo">
    <div class="hud" aria-hidden="true">
      <span class="badge" id="scoreLabel">Score: 0</span>
      <span class="badge" id="livesLabel">Lives: 3</span>
      <span class="badge" id="levelLabel" style="margin-left:8px;">Level: 1</span>
      <span class="grow"></span>
      <span class="badge" id="powerLabel" title="Active power-ups">Power: None</span>
    </div>
    <div class="stage" id="stage" aria-label="Game stage">
      <canvas id="gameCanvas"></canvas>
      <div class="overlay" id="startOverlay" style="display:flex;flex-direction:column;gap:12px;">
        <div>Click or press any key to start</div>
        <button class="btn" id="startBtn" aria-label="Start game">Start</button>
      </div>
      <div class="overlay" id="overOverlay" style="display:none; flex-direction:column;">
        <div id="overText" style="font-size:28px; font-weight:700; text-shadow:0 2px 12px rgba(0,0,0,.5);"></div>
        <button class="btn" id="restartBtn" aria-label="Restart game">Restart</button>
      </div>
    </div>
  </div>
</div>

<script>
(function(){
  const canvas = document.getElementById('gameCanvas');
  const ctx = canvas.getContext('2d');
  const startOverlay = document.getElementById('startOverlay');
  const overOverlay = document.getElementById('overOverlay');
  const overText = document.getElementById('overText');
  const startBtn = document.getElementById('startBtn');
  const restartBtn = document.getElementById('restartBtn');
  const scoreLabel = document.getElementById('scoreLabel');
  const livesLabel = document.getElementById('livesLabel');
  const levelLabel = document.getElementById('levelLabel');
  const powerLabel = document.getElementById('powerLabel');
  const stage = document.getElementById('stage');

  // sizing to fit responsiveness
  function fitCanvas(){
    const rect = stage.getBoundingClientRect();
    const dpr = Math.max(1, Math.min(2, window.devicePixelRatio || 1));
    canvas.width = Math.floor(rect.width * dpr);
    canvas.height = Math.floor(rect.height * dpr);
    ctx.setTransform(dpr,0,0,dpr,0,0);
  }

  window.addEventListener('resize', fitCanvas);
  fitCanvas();

  // Game objects
  const STATE = { READY:0, PLAY:1, PAUSED:2, GAMEOVER:3, WON:4 };
  let gameState = STATE.READY;

  const config = {
    paddleW: 120,
    paddleH: 14,
    paddleSpeed: 7,
    ballR: 6,
    gravity: 0, // not used
    brickRows: 6,
    brickCols: 10,
    brickW: 0, // computed
    brickH: 22,
    brickPadding: 6,
    brickOffsetTop: 60,
    brickOffsetLeft: 20,
  };

  // Entities
  let paddle, balls, bricks, particles, powerUps;
  let score = 0;
  let lives = 3;
  let level = 1;
  let activePower = null;
  let powerTimer = 0;

  // Input
  let mouseX = window.innerWidth / 2;
  let keys = {};

  // Helpers
  function rand(min, max){ return Math.random()*(max-min)+min; }
  function clamp(v, a, b){ return Math.max(a, Math.min(b, v)); }

  // Brick definitions
  // brick: {x,y,w,h, hp, color, cracked:false, isPower:false, crackedOnce:false}
  function createBricks(){
    const bricksArr = [];
    const rows = config.brickRows;
    const cols = config.brickCols;
    const w = (canvas.clientWidth - config.brickOffsetLeft*2 - (cols-1)*config.brickPadding) / cols;
    config.brickW = w;
    for(let r=0;r<rows;r++){
      for(let c=0;c<cols;c++){
        const wBr = w;
        const x = config.brickOffsetLeft + c * (wBr + config.brickPadding);
        const y = config.brickOffsetTop + r * (config.brickH + 10);
        // mix bricks: some tougher that needs 2 hits
        const isTough = (r+ c) % 4 === 0; // variety
        const brick = {
          x, y, w: wBr, h: config.brickH,
          hp: isTough ? 2 : 1,
          color: isTough ? '#e74c3c' : '#2ecc71',
          cracked: false,
          isPower: Math.random() < 0.18, // some bricks carry power
          destroyed: false
        };
        bricksArr.push(brick);
      }
    }
    return bricksArr;
  }

  function resetGame(){
    level = 1;
    score = 0;
    lives = 3;
    powerClear();
    bricks = createBricks();
    balls = [ { x: canvas.clientWidth/2, y: canvas.clientHeight - 60, vx: 4, vy: -4 } ];
  }

  function powerClear(){
    activePower = null;
    powerTimer = 0;
    paddle.w = config.paddleW;
    powerLabel.textContent = 'Power: None';
  }

  // Particles
  function spawnParticles(x, y, color){
    for(let i=0;i<12;i++){
      particles.push({
        x, y,
        vx: rand(-2,2), vy: rand(-2,2),
        life: rand(20, 40),
        color
      });
    }
  }

  function spawnPower(x, y){
    // simple icon as an object with type
    const types = ['wide','multiball'];
    const type = types[Math.floor(Math.random()*types.length)];
    powerUps.push({ x, y, vy: 1.6, type, collected:false });
  }

  // Collision helpers
  function circleRectCollide(cx, cy, r, rx, ry, rw, rh){
    // clamp circle center to rect, then see distance
    const closestX = clamp(cx, rx, rx+rw);
    const closestY = clamp(cy, ry, ry+rh);
    const dx = cx - closestX;
    const dy = cy - closestY;
    return (dx*dx + dy*dy) <= (r*r);
  }

  function reflectBall(ball, hitX){
    // normalize hit position along paddle to angle
    const paddleCenter = paddle.x + paddle.w/2;
    const diff = (hitX - paddleCenter) / (paddle.w/2); // -1..1
    const angle = diff * (Math.PI/3); // max 60 degrees
    const speed = Math.hypot(ball.vx, ball.vy);
    ball.vx = Math.sin(angle) * speed;
    ball.vy = -Math.abs(Math.cos(angle) * speed);
  }

  // Init game
  function init(){
    fitCanvas();
    resetGame();
    // initial overlay
    startOverlay.style.display = 'flex';
  }

  // Movements
  function updatePaddle(){
    // mouse
    paddle.x = mouseX - paddle.w/2;
    // constrain
    paddle.x = clamp(paddle.x, 0, canvas.clientWidth - paddle.w);
    // keyboard
    if (keys['ArrowLeft'] || keys['Left']) paddle.x -= config.paddleSpeed;
    if (keys['ArrowRight'] || keys['Right']) paddle.x += config.paddleSpeed;
    paddle.x = clamp(paddle.x, 0, canvas.clientWidth - paddle.w);
  }

  // Main loop
  let lastTs = 0;
  function loop(ts){
    if(!lastTs) lastTs = ts;
    const dt = Math.min(32, ts - lastTs);
    lastTs = ts;

    // auto-start
    if (gameState === STATE.READY){
      // just render
    }

    // update only when playing
    if (gameState === STATE.PLAY){
      update(dt);
    }

    render();
    requestAnimationFrame(loop);
  }

  function update(dt){
    // dt in ms to seconds
    // dt not used heavily
    // move paddle by input handled in updatePaddle
    updatePaddle();

    // update balls
    for(let i=0;i<balls.length;i++){
      const b = balls[i];
      b.x += b.vx;
      b.y += b.vy;

      // wall collisions
      if (b.x - config.ballR < 0){
        b.x = config.ballR;
        b.vx *= -1;
      } else if (b.x + config.ballR > canvas.clientWidth){
        b.x = canvas.clientWidth - config.ballR;
        b.vx *= -1;
      }
      if (b.y - config.ballR < 0){
        b.y = config.ballR;
        b.vy *= -1;
      }

      // paddle collision
      if (circleRectCollide(b.x, b.y, config.ballR, paddle.x, paddle.y, paddle.w, paddle.h)){
        // compute hit position
        reflectBall(b, b.x);
        // move ball just above paddle to avoid sticking
        b.y = paddle.y - config.ballR - 1;
      }

      // bricks collision
      outer:
      for(let br of bricks){
        if (br.destroyed) continue;
        if (circleRectCollide(b.x, b.y, config.ballR, br.x, br.y, br.w, br.h)){
          // determine side hit to reflect
          // approximate by finding penetration direction
          const prevX = b.x - b.vx;
          const prevY = b.y - b.vy;
          let collidedSide = null;
          // check from previous position which side is closer
          if (prevX < br.x && b.x >= br.x) collidedSide = 'left';
          else if (prevX > br.x + br.w && b.x <= br.x + br.w) collidedSide = 'right';
          else if (prevY < br.y && b.y >= br.y) collidedSide = 'top';
          else if (prevY > br.y + br.h && b.y <= br.y + br.h) collidedSide = 'bottom';
          // fallback
          if (!collidedSide){
            // reflect based on overlap vector
            collidedSide = (Math.abs(b.x - br.x) < Math.abs(b.x - (br.x+br.w))) ? 'left' : 'right';
          }
          if (collidedSide === 'left' || collidedSide === 'right'){
            b.vx *= -1;
          } else {
            b.vy *= -1;
          }

          // brick damage
          br.hp -= 1;
          br.cracked = br.hp === 1;
          br.destroyed = br.hp <= 0;

          // crack visual cue
          if (br.cracked){
            br.color = '#f1c40f';
          }

          // power up drop
          if (br.isPower && !br.destroyed){
            // ensure drop when destroyed only
          }
          if (br.destroyed){
            score += 50;
            // spawn particles
            spawnParticles(br.x + br.w/2, br.y + br.h/2, br.color);
            // maybe spawn power
            if (br.isPower && Math.random() < 0.9){
              spawnPower(br.x + br.w/2, br.y + br.h/2);
            }
          }

          break outer;
        }
      }

      // bottom miss
      if (b.y - config.ballR > canvas.clientHeight){
        balls.splice(i,1);
        i--;
        if (balls.length === 0){
          lives -= 1;
          if (lives <= 0){
            gameOver(false);
          } else {
            // reset ball
            balls.push({ x: canvas.clientWidth/2, y: canvas.clientHeight - 60, vx: 4, vy: -4 });
          }
        }
      }
    }

    // power-ups
    for(let pu of powerUps){
      pu.y += pu.vy;
      // catch paddle
      if (circleRectCollide(pu.x, pu.y, 10, paddle.x, paddle.y, paddle.w, paddle.h)){
        applyPower(pu.type);
        pu.collected = true;
      }
    }
    // remove collected or offscreen
    powerUps = powerUps.filter(p => !p.collected && p.y < canvas.clientHeight + 20);

    // cleanup bricks fully destroyed may reduce level
    const aliveBricks = bricks.filter(b => !b.destroyed);
    if (aliveBricks.length === 0){
      // win
      gameOver(true);
    }

    // update power timer
    if (activePower){
      powerTimer -= dt/1000;
      if (powerTimer <= 0){
        powerClear();
      }
    }

    // small: if no balls, nothing
  }

  function applyPower(type){
    if (type === 'wide'){
      activePower = 'wide';
      powerTimer = 10;
      paddle.w = config.paddleW * 1.6;
      powerLabel.textContent = 'Power: Wide';
    } else if (type === 'multiball'){
      powerTimer = 0;
      // spawn 2 extra balls if not already many
      const current = balls.length;
      for(let i=0;i<Math.min(2, 3 - current);i++){
        const base = balls[0] || { x: canvas.clientWidth/2, y: canvas.clientHeight - 60, vx: 4, vy: -4 };
        balls.push({ x: base.x, y: base.y, vx: (Math.random()<0.5?1:-1)*4, vy: -4 });
      }
      powerLabel.textContent = 'Power: Multiball';
      // multiball lasts some seconds
      powerTimer = 10;
    }
  }

  function render(){
    // clear
    ctx.clearRect(0,0, canvas.clientWidth, canvas.clientHeight);

    // bricks
    for(let br of bricks){
      if (br.destroyed) continue;
      ctx.fillStyle = br.color;
      ctx.fillRect(br.x, br.y, br.w, br.h);
      // crack overlay if cracked
      if (br.cracked){
        ctx.strokeStyle = '#ffffff';
        ctx.lineWidth = 1;
        ctx.strokeRect(br.x+2, br.y+2, br.w-4, br.h-4);
      }
      // power icon indicator
      if (br.isPower){
        ctx.fillStyle = '#ffd700';
        ctx.font = '12px sans-serif';
        ctx.fillText('P', br.x + br.w/2 - 5, br.y + br.h/2 + 4);
      }
    }

    // particles
    for(let p of particles){
      ctx.fillStyle = p.color;
      ctx.globalAlpha = Math.max(0, p.life/40);
      ctx.fillRect(p.x, p.y, 2, 2);
      p.x += p.vx;
      p.y += p.vy;
      p.life -= 1;
    }
    ctx.globalAlpha = 1;

    // balls
    for(let b of balls){
      ctx.fillStyle = '#fff';
      ctx.beginPath();
      ctx.arc(b.x, b.y, config.ballR, 0, Math.PI*2);
      ctx.fill();
    }

    // paddle
    ctx.fillStyle = '#66ff92';
    ctx.fillRect(paddle.x, paddle.y, paddle.w, paddle.h);
  }

  function gameOver(won){
    gameState = STATE.GAMEOVER;
    overOverlay.style.display = 'flex';
    overText.textContent = won ? 'You cleared the bricks! Victory!' : 'Game Over';
  }

  // init stage data
  function setupNewRun(){
    bricks = createBricks();
    balls = [{ x: canvas.clientWidth/2, y: canvas.clientHeight - 60, vx: 4, vy: -4 }];
    paddle = { x: (canvas.clientWidth - config.paddleW)/2, y: canvas.clientHeight - 20, w: config.paddleW, h: 14 };
    particles = [];
    powerUps = [];
  }

  // User input
  canvas.addEventListener('mousemove', (e)=>{
    const rect = canvas.getBoundingClientRect();
    mouseX = e.clientX - rect.left;
  });
  window.addEventListener('keydown', (e)=>{
    if (gameState === STATE.READY){
      // start on key press
      startGame();
    } else {
      keys[e.key] = true;
      // prevent scrolling with arrows
      if (['ArrowLeft','ArrowRight'].includes(e.key)) e.preventDefault();
    }
  }, {passive:false});
  window.addEventListener('keyup', (e)=>{ keys[e.key] = false; });

  function startGame(){
    // init state
    setupNewRun();
    gameState = STATE.PLAY;
    startOverlay.style.display = 'none';
    overOverlay.style.display = 'none';
    levelLabel.textContent = 'Level: 1';
    scoreLabel.textContent = 'Score: 0';
    livesLabel.textContent = 'Lives: 3';
    powerLabel.textContent = 'Power: None';
  }

  // Restart
  restartBtn.addEventListener('click', ()=>{
    // reset same
    resetGame();
    gameState = STATE.PLAY;
    overOverlay.style.display = 'none';
  });
  startBtn.addEventListener('click', ()=> startGame());

  // kick off
  init();
  // start with ready overlay
  // also handle click on stage to start
  stage.addEventListener('click', ()=>{
    if (gameState === STATE.READY){
      startGame();
    }
  });

  // frame loop
  requestAnimationFrame(loop);

  // initial sizing setups
  // ensure paddle exists before first render
  paddle = { x: (canvas.clientWidth - config.paddleW)/2, y: canvas.clientHeight - 20, w: config.paddleW, h: 14 };

})();
</script>
</body>
</html>