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
29.9 s
Cost
$0.0018
Output tokens
4,317
Spent thinking
0 tokens
Speed
149.6 tok/s
Console errors
1
First token after
1.1 s
First visible text after
1.1 s
Prompt tokens
493
Code size
13.6 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 read properties of undefined (reading 'x')

What it was asked

The prompt
Build a polished Snake game in a single HTML file using vanilla JavaScript and a <canvas>. Requirements: - A grid of at least 20×20 cells, centered on the page. - Arrow keys and WASD control the snake. Quick successive key presses must both be honored, but the snake can never reverse onto itself. - Eating food adds 10 points and grows the snake by one cell; new food never spawns on the snake. - Game over when the snake hits a wall or itself, with an overlay showing the score; Space or Enter restarts, P pauses. - Visual polish: smooth movement, a snake with a distinct head and eyes facing its direction, and a small particle burst when eating. - Display the current score and the best score of the session.
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 · 538 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Snake - Vanilla JS</title>
<style>
  :root{
    --bg: #0e1020;
    --panel: rgba(20,24,40,.9);
    --grid: rgba(255,255,255,.08);
    --snake: #7bd389;
    --snakeHead: #2bd07f;
    --food: #ff6b6b;
    --overlay: rgba(0,0,0,.66);
    --text: #e6f0ff;
    --shadow: rgba(0,0,0,.25);
  }
  html, body {
    height: 100%;
    margin: 0;
    background: radial-gradient(circle at 50% -10%, #1a1f3a 0%, #0e1020 40%, #0e1020 100%), #0e1020;
    font-family: system-ui, -apple-system, "Segoe UI", Roboto, Inter, Arial;
    color: var(--text);
    overflow: hidden;
  }
  .wrap {
    height: 100%;
    display: grid;
    grid-template-rows: auto 1fr auto;
    place-items: center;
    padding: 20px;
  }
  .card {
    background: linear-gradient(180deg, rgba(255,255,255,.04), rgba(255,255,255,.02) 60%), var(--panel);
    border-radius: 14px;
    padding: 14px;
    box-shadow: 0 10px 30px rgba(0,0,0,.4);
    display: grid;
    gap: 12px;
    align-items: center;
    justify-items: center;
    max-width: 92vw;
    width: 860px;
  }
  canvas {
    display: block;
    background: #0a0f1a;
    border-radius: 8px;
    box-shadow: inset 0 0 0 1px rgba(255,255,255,.04), 0 8px 20px rgba(0,0,0,.35);
  }
  .hud {
    display: flex;
    gap: 14px;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    flex-wrap: wrap;
  }
  .badge {
    padding: 6px 12px;
    border-radius: 999px;
    background: rgba(0,0,0,.25);
    border: 1px solid rgba(255,255,255,.08);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas;
    font-size: 14px;
    letter-spacing: .4px;
  }
  .title {
    font-size: 18px;
    font-weight: 600;
    letter-spacing: .4px;
  }
  .hint {
    font-size: 13px;
    opacity: .9;
  }
  .overlay {
    position: fixed;
    inset: 0;
    display: grid;
    place-items: center;
    background: var(--overlay);
    z-index: 5;
  }
  .panel {
    background: rgba(15,18,40,.95);
    border-radius: 12px;
    padding: 28px 28px;
    text-align: center;
    max-width: 420px;
    box-shadow: 0 6px 20px rgba(0,0,0,.5);
  }
  .panel h2 {
    margin: 0 0 8px 0;
    font-size: 28px;
  }
  .panel p {
    margin: 6px 0 14px 0;
    color: #d9e6ff;
    font-size: 14px;
  }
  .panel .score {
    font-family: ui-mloose, monospace;
    font-weight: 700;
    font-size: 32px;
    margin: 6px 0 12px 0;
  }
  .btns {
    display: flex;
    gap: 10px;
    justify-content: center;
    flex-wrap: wrap;
  }
  button.btn {
    appearance: none;
    border: 0;
    border-radius: 999px;
    padding: 12px 18px;
    background: #2b9c8e;
    color: white;
    font-weight: 600;
    cursor: pointer;
    box-shadow: 0 4px 10px rgba(0,0,0,.25);
  }
  button.btn.secondary {
    background: #3a3f6b;
  }
  @media (max-width: 720px){
    .card { width: 100%; padding: 12px; }
    canvas { width: 100%; height: auto; }
  }
</style>
</head>
<body>
<div class="wrap">
  <div class="card" aria-label="Snake game board">
    <div class="hud" id="hud">
      <div class="badge" id="scoreLabel">Score: 0</div>
      <div class="badge" id="bestLabel">Best: 0</div>
      <div class="badge" id="stateLabel" style="margin-left:auto; opacity:.9">Click or press a key to start</div>
    </div>
    <canvas id="board" width="640" height="640" aria-label="Snake game canvas"></canvas>
    <div class="hint" id="hint" style="font-size:12px; opacity:.8; text-align:center;">
      Use Arrow Keys or WASD to move. Space or Enter to restart after game over. P to pause.
    </div>
  </div>
</div>

<div class="overlay" id="overlay" style="display:none" aria-hidden="true">
  <div class="panel" role="dialog" aria-label="Game over dialog">
    <h2>Game Over</h2>
    <p>Your score</p>
    <div class="score" id="overlayScore">0</div>
    <div class="btns" style="margin-top:6px;">
      <button class="btn" id="restartBtn">Restart (Space / Enter)</button>
      <button class="btn secondary" id="closeBtn">Close</button>
    </div>
  </div>
</div>

<script>
(() => {
  // Configuration
  const GRID = 20; // grid cells per row/col (minimum 20)
  const CELL = 0; // will compute from canvas size
  const INITIAL_SPEED = 6; // steps per second
  const ACCEL_PER_EAT = 0; // could speed up later
  const SCORE_PER_EAT = 10;

  // Canvas setup
  const canvas = document.getElementById('board');
  const ctx = canvas.getContext('2d');
  // Responsive sizing: keep square grid centered
  function resizeCanvas() {
    const wrapW = Math.min(window.innerWidth * 0.92, 860);
    const size = Math.floor(Math.min(wrapW, window.innerHeight * 0.8));
    // ensure grid cells are integers
    const cellSize = Math.floor(size / GRID);
    const boardSize = cellSize * GRID;
    canvas.style.width = boardSize + 'px';
    canvas.style.height = boardSize + 'px';
    canvas.width = boardSize;
    canvas.height = boardSize;
  }
  window.addEventListener('resize', resizeCanvas, {passive:true});
  resizeCanvas();

  // Game state
  let gridSize = GRID;
  let cellSize = canvas.width / gridSize;
  let snake = [];
  let dir = {x:1, y:0}; // current direction
  let nextDir = {x:1, y:0};
  let food = null;
  let growing = 0;
  let score = 0;
  let best = 0;
  let speed = INITIAL_SPEED;
  let frameAccumulator = 0;
  let paused = false;
  let running = false;
  let gameOver = false;
  let lastTime = 0;
  const keys = {};

  // Initialize
  function resetGame() {
    // center snake
    const mid = Math.floor(gridSize/2);
    snake = [{x:mid-1, y:mid}, {x:mid, y:mid}, {x:mid+1, y:mid}];
    dir = {x:1, y:0};
    nextDir = {x:1, y:0};
    placeFood();
    score = 0;
    speed = INITIAL_SPEED;
    frameAccumulator = 0;
    paused = false;
    gameOver = false;
    running = true;
    updateHUD();
  }

  function updateHUD() {
    document.getElementById('scoreLabel').textContent = 'Score: ' + score;
    document.getElementById('bestLabel').textContent = 'Best: ' + best;
    document.getElementById('stateLabel').textContent = gameOver ? 'Game Over' : (paused ? 'Paused' : 'Running');
  }

  function placeFood() {
    // choose a cell not in snake
    let empty = [];
    for (let y = 0; y < gridSize; y++) {
      for (let x = 0; x < gridSize; x++) {
        if (!snake.some(s => s.x === x && s.y === y)) {
          empty.push({x, y});
        }
      }
    }
    if (empty.length === 0) {
      // win condition (unlikely)
      food = null;
      return;
    }
    food = empty[Math.floor(Math.random() * empty.length)];
  }

  // Input handling
  window.addEventListener('keydown', (e) => {
    if (e.repeat) return;
    const k = e.key.toLowerCase();
    if (k === 'arrowup') queueDir(0,-1);
    else if (k === 'arrowdown') queueDir(0,1);
    else if (k === 'arrowleft') queueDir(-1,0);
    else if (k === 'arrowright') queueDir(1,0);
    else if (k === 'w') queueDir(0,-1);
    else if (k === 's') queueDir(0,1);
    else if (k === 'a') queueDir(-1,0);
    else if (k === 'd') queueDir(1,0);
    else if (k === ' ') {
      e.preventDefault();
      if (!running) {
        startGame();
      } else {
        togglePause();
      }
    } else if (k === 'enter') {
      if (gameOver) restartFromOverlay();
      else if (!running) startGame();
      else togglePause();
    } else if (k === 'p') {
      togglePause();
    }
  }, {passive:true});

  function queueDir(dx, dy) {
    // prevent reverse
    if (-dx === dir.x && -dy === dir.y) return;
    nextDir = {x:dx, y:dy};
  }

  // Intro instruction
  function showHintIfNeeded() {
    // Show overlay if not started
  }

  // Rendering
  function drawGrid() {
    ctx.fillStyle = 'transparent';
    // subtle grid lines
    ctx.strokeStyle = 'rgba(255,255,255,.05)';
    ctx.lineWidth = 1;
    for (let i=0; i<=gridSize; i++) {
      // vertical
      ctx.beginPath();
      ctx.moveTo(i*cellSize, 0);
      ctx.lineTo(i*cellSize, gridSize*cellSize);
      ctx.stroke();
      // horizontal
      ctx.beginPath();
      ctx.moveTo(0, i*cellSize);
      ctx.lineTo(gridSize*cellSize, i*cellSize);
      ctx.stroke();
    }
  }

  function clearBoard() {
    ctx.fillStyle = '#0a0f1a';
    ctx.fillRect(0,0, canvas.width, canvas.height);
  }

  function drawSnake() {
    for (let i = 0; i < snake.length; i++) {
      const s = snake[i];
      const px = s.x * cellSize;
      const py = s.y * cellSize;
      // body
      ctx.fillStyle = i === snake.length - 1 ? '#6ce17b' : '#4fb66b';
      ctx.fillRect(px+2, py+2, cellSize-4, cellSize-4);
    }
    // head with eyes facing direction
    const head = snake[snake.length-1];
    const px = head.x * cellSize;
    const py = head.y * cellSize;
    // eyes
    ctx.fillStyle = '#113';
    const ex = head.x + (dir.x>0?1:0);
    const ey = head.y + (dir.y>0?1:0);
    // simple eyes as two small blacks on front
    ctx.fillRect(px + cellSize*0.25, py + cellSize*0.25, 3, 3);
    ctx.fillRect(px + cellSize*0.75 - 3, py + cellSize*0.25, 3, 3);
    // decorative highlight
    ctx.fillStyle = '#a9ffd8';
  }

  function drawFood() {
    if (!food) return;
    const px = food.x * cellSize;
    const py = food.y * cellSize;
    // glowing rounded square
    const r = 6;
    ctx.fillStyle = '#ff5a5a';
    ctx.strokeStyle = '#fff';
    ctx.lineWidth = 2;
    roundRect(ctx, px+2, py+2, cellSize-4, cellSize-4, r, true, true);
  }

  // helper for rounded rect
  function roundRect(ctx, x, y, w, h, r, fill, stroke) {
    if (w < 2*r) r = w/2;
    if (h < 2*r) r = h/2;
    ctx.beginPath();
    ctx.moveTo(x+r, y);
    ctx.arcTo(x+w, y,   x+w, y+h, r);
    ctx.arcTo(x+w, y+h, x,   y+h, r);
    ctx.arcTo(x,   y+h, x,   y,   r);
    ctx.arcTo(x,   y,   x+w, y,   r);
    ctx.closePath();
    if (fill) ctx.fill();
    if (stroke) ctx.stroke();
  }

  // Particles burst when eating
  const particles = [];
  function burst(x, y) {
    for (let i=0; i<12; i++) {
      const a = Math.random()*Math.PI*2;
      const speed = 0.8 + Math.random()*1.6;
      particles.push({
        x: x, y: y,
        vx: Math.cos(a)*speed,
        vy: Math.sin(a)*speed,
        life: 40 + Math.random()*20
      });
    }
  }

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

  function drawParticles() {
    for (const p of particles) {
      ctx.fillStyle = 'rgba(255,230,160,' + (p.life/60).toFixed(2) + ')';
      ctx.fillRect(p.x, p.y, 2, 2);
    }
  }

  // Game loop
  function step(dt) {
    if (!running || paused) return;
    frameAccumulator += dt;
    const interval = 1 / speed;
    while (frameAccumulator >= interval) {
      frameAccumulator -= interval;
      update();
    }
  }

  function update() {
    // apply direction
    dir = nextDir;
    // compute next head
    const head = snake[snake.length-1];
    const nx = head.x + dir.x;
    const ny = head.y + dir.y;

    // wall collision
    if (nx < 0 || ny < 0 || nx >= gridSize || ny >= gridSize) {
      endGame();
      return;
    }

    // self collision
    if (snake.some(s => s.x === nx && s.y === ny)) {
      endGame();
      return;
    }

    // move
    snake.push({x: nx, y: ny});
    if (growing > 0) {
      growing--;
    } else {
      snake.shift();
    }

    // food check
    if (food && nx === food.x && ny === food.y) {
      score += SCORE_PER_EAT;
      growing += 1;
      placeFood();
      burst(nx*cellSize + cellSize/2, ny*cellSize + cellSize/2);
      // could increase speed gradually
      // speed += ACCEL_PER_EAT;
      updateHUD();
    }

    // render
    render();
  }

  function render() {
    clearBoard();
    // grid
    drawGrid();
    // food
    drawFood();
    // snake
    drawSnake();
    // particles
    updateParticles();
    drawParticles();
  }

  function endGame() {
    running = false;
    gameOver = true;
    if (score > best) best = score;
    updateHUD();
    // show overlay with score
    document.getElementById('overlayScore').textContent = String(score);
    document.getElementById('overlay').style.display = 'grid';
  }

  function startGame() {
    resetGame();
    // initial render
    render();
    // countdown-like start: allow first frame after a moment to observe
    running = true;
    gameOver = false;
    updateHUD();
    document.getElementById('overlay').style.display = 'none';
    lastTime = performance.now();
  }

  function restartFromOverlay() {
    document.getElementById('overlay').style.display = 'none';
    startGame();
  }

  function togglePause() {
    if (!running) return;
    paused = !paused;
    updateHUD();
  }

  // initial overlay: click to start
  const overlay = document.getElementById('overlay');
  overlay.addEventListener('click', () => {
    if (!running) startGame();
  });

  // Start state hints
  document.addEventListener('visibilitychange', () => {
    // optional pause when not visible
  });

  // Animation loop
  function loop(now) {
    if (!lastTime) lastTime = now;
    const dt = (now - lastTime) / 1000;
    lastTime = now;
    step(dt);
    requestAnimationFrame(loop);
  }

  // Initialize
  function init() {
    // init best from memory? disallowed to use storage; so zeroed per session
    best = 0;
    updateHUD();
    // render initial static board
    render();
    // show start hint
    document.getElementById('stateLabel').textContent = 'Click or press a key to start';
    // focus canvas to capture keyboard after click
    canvas.setAttribute('tabindex','0');
    canvas.addEventListener('click', () => canvas.focus(), {passive:true});
  }

  init();
  requestAnimationFrame(loop);

  // Ensure keyboard events are delivered only after user interaction
  // Show explicit start prompt if needed
})();
</script>
</body>
</html>