Models 44
Train snakeOne-line

GPT-5 Nano

by OpenAIReasoningClean
Open in new window

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

How it went

Total time
25.8 s
Cost
$0.0016
Output tokens
3,835
Spent thinking
0 tokens
Speed
153.6 tok/s
Console errors
0
First token after
855 ms
First visible text after
855 ms
Prompt tokens
361
Code size
11.7 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

What it was asked

The prompt
“make me a snake game where the snake is a little train with a locomotive and colorful wagons, picking up passengers waiting on the platform”
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 · 410 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Train Snake - Passenger Hop</title>
<style>
  :root{
    --bg: #0b1020;
    --track: #2b2b2b;
    --grid: rgba(255,255,255,0.05);
    --locomotive: #ff4d4d;
    --wagon1: #4db6ff;
    --wagon2: #7cffb5;
    --wagon3: #ffd166;
    --passenger: #fff;
  }
  *{box-sizing:border-box}
  html,body{height:100%}
  body{
    margin:0;
    background: radial-gradient(circle at 30% -10%, rgba(255,255,255,0.15) 0 20%, transparent 40%), 
                radial-gradient(circle at 90% 0%, rgba(0,0,0,0.25) 0 30%, transparent 40%),
                var(--bg);
    color:#eaf2ff;
    font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial;
  }
  .wrap{
    display:flex;
    flex-direction:column;
    align-items:center;
    justify-content:center;
    min-height:100vh;
    padding:20px;
  }
  h1{margin:0 0 6px 0;font-size:1.1rem;letter-spacing:0.3px;color:#e8f0ff}
  canvas{background: #0a0f1a; border-radius:12px; box-shadow:0 8px 30px rgba(0,0,0,.4); display:block}
  .hint{
    margin-top:8px;
    font-size:0.9rem;
    color:#cbd7ff;
    opacity:.9;
  }
  .startOverlay{
    position:absolute; inset:0;
    display:flex; align-items:center; justify-content:center;
    background: rgba(0,0,0,.5);
    color:#eaf2ff;
    font-family: inherit;
    font-size:1.05rem;
    text-align:center;
    padding:20px;
  }
  @media (max-width: 700px){
    canvas{width:95vw; height:auto}
  }
</style>
</head>
<body>
<div class="wrap" aria-label="Train Snake game container">
  <h1>Train Snake: Passengers on the Platform</h1>
  <canvas id="game" width="960" height="540" aria-label="Train Snake game canvas"></canvas>
  <div class="hint" id="hint">Click the canvas or press any key to start. Control the train with Arrow Keys or WASD.</div>
  <div class="startOverlay" id="startOverlay" role="dialog" aria-label="Start screen" style="display:flex">
    <div>
      <div style="font-weight:700;font-size:1.1rem;margin-bottom:6px">Click or press a key to start</div>
      <div>Guide the locomotive to pick up waiting passengers on the platform. The train grows by adding colorful wagons. Avoid colliding with yourself.</div>
    </div>
  </div>
</div>

<script>
(() => {
  const canvas = document.getElementById('game');
  const ctx = canvas.getContext('2d');
  const overlay = document.getElementById('startOverlay');
  const hint = document.getElementById('hint');

  // Game settings
  const COLS = 48; // grid columns
  const ROWS = 28; // grid rows
  const CELL = 20; // px per cell (base, computed)
  // Adjust canvas scale to fit viewport
  function fitCanvas(){
    const w = Math.min(window.innerWidth * 0.95, 1100);
    const h = Math.max(420, Math.min(window.innerHeight * 0.85, 600));
    // compute cell size to fit cols/rows
    const cw = Math.floor(w / COLS);
    const ch = Math.floor(h / ROWS);
    const s = Math.max(6, Math.min(cw, ch)); // cell size
    canvas.width = COLS * s;
    canvas.height = ROWS * s;
    window.__cell = s;
  }
  fitCanvas();
  window.addEventListener('resize', fitCanvas);

  // Colors
  const LOC = '#ff4d4d';
  const WAGON_COLORS = ['#4db6ff','#7cffb5','#ffd166','#9b87ff'];
  const BG_GRID = '#1a1f2b';
  const PASSENGER = '#fff';

  // Train snake (train)
  let dir = {x:1, y:0};
  let nextDir = {x:1, y:0};
  let head = {x:5, y:5};
  let body = [{x: head.x, y: head.y}]; // locomotive at head
  let wagons = []; // array of wagons segments after locomotive
  let pendingGrowth = 0;

  // Passenger on platform to pick up
  let passenger = null; // position {x,y}
  let score = 0;
  let gameOver = false;
  let started = false;

  function randInt(min, max){
    return Math.floor(Math.random()*(max-min+1))+min;
  }

  function placePassenger(){
    // place on grid not colliding with train
    let tries = 0;
    while(tries<100){
      const p = {x: randInt(0, COLS-1), y: randInt(0, ROWS-1)};
      const collision = [head, ...body, ...wagons].some(seg => seg.x===p.x && seg.y===p.y);
      if(!collision){
        passenger = p;
        return;
      }
      tries++;
    }
    passenger = null;
  }

  function init(){
    dir = {x:1, y:0};
    nextDir = {x:1, y:0};
    head = {x:5, y:5};
    body = [{x: head.x, y: head.y}];
    wagons = [];
    pendingGrowth = 0;
    score = 0;
    gameOver = false;
    started = false;
    placePassenger();
    render(); // first paint
  }

  // AI autoplay: simple path follower toward next target (passenger). Keeps moving forward with basic avoidance.
  function aiDecide(){
    // If has no growth, keep moving toward passenger when possible
    if(!passenger) return;
    // simple heuristic: try to move towards passenger without immediate collision with self
    const desired = {
      x: passenger.x - head.x,
      y: passenger.y - head.y
    };
    // prefer horizontal if abs dx > dy
    const tries = [
      {x: Math.sign(desired.x) || 0, y: 0},
      {x: 0, y: Math.sign(desired.y) || 0},
      {x: Math.sign(desired.x) || 0, y: Math.sign(desired.y) || 0},
      {x: -Math.sign(desired.x) || 0, y: 0},
      {x: 0, y: -Math.sign(desired.y) || 0}
    ];
    for(let t of tries){
      const cand = {x: head.x + t.x, y: head.y + t.y};
      if(cand.x<0||cand.y<0||cand.x>=COLS||cand.y>=ROWS) continue;
      // collision check
      if(isCollisionPoint(cand.x, cand.y)) continue;
      nextDir = t;
      return;
    }
    // else no safe move, random safe
    const dirs = [{x:1,y:0},{x:-1,y:0},{x:0,y:1},{x:0,y:-1}];
    for(let d of dirs){
      const cand = {x: head.x + d.x, y: head.y + d.y};
      if(cand.x<0||cand.y<0||cand.x>=COLS||cand.y>=ROWS) continue;
      if(!isCollisionPoint(cand.x, cand.y)){
        nextDir = d;
        return;
      }
    }
  }

  function isCollisionPoint(x,y){
    // collision with self (excluding tail which moves)
    for(let i=0;i<headIndex();i++){
      const s = i===0 ? head : body[i];
      if(s.x===x && s.y===y) return true;
    }
    // wagons
    for(let w of wagons){
      if(w.x===x && w.y===y) return true;
    }
    return false;
  }
  function headIndex(){ return 0; }

  // Move
  function step(){
    if(gameOver) return;
    // AI planning
    aiDecide();
    // Apply direction
    dir = {x: nextDir.x, y: nextDir.y};
    const nx = head.x + dir.x;
    const ny = head.y + dir.y;
    // wrap? We'll keep walls as borders (game over on mistake)
    if(nx<0 || ny<0 || nx>=COLS || ny>=ROWS){
      gameOver = true;
      render();
      return;
    }
    // check collision with body or wagons
    if(isCollisionPoint(nx, ny)){
      gameOver = true;
      render();
      return;
    }
    // move
    body.unshift({x: head.x, y: head.y});
    head = {x: nx, y: ny};

    // check passenger pickup
    if(passenger && head.x===passenger.x && head.y===passenger.y){
      score += 1;
      pendingGrowth += 3; // add 3 wagons
      placePassenger();
    }

    // handle growth
    if(pendingGrowth>0){
      wagons.unshift({x: body[0].x, y: body[0].y});
      pendingGrowth--;
    } else if(wagons.length>0){
      // move wagons: each wagon takes behind previous
      wagons.unshift({x: body[0].x, y: body[0].y});
      wagons.pop();
    }

    render();
  }

  // Rendering
  function render(){
    const s = window.__cell || 20;
    // clear
    ctx.clearRect(0,0,canvas.width, canvas.height);

    // background grid
    ctx.fillStyle = BG_GRID;
    ctx.fillRect(0,0,canvas.width, canvas.height);
    // subtle grid lines
    ctx.strokeStyle = 'rgba(255,255,255,0.04)';
    ctx.lineWidth = 1;
    for(let i=0;i<=COLS;i++){
      ctx.beginPath();
      ctx.moveTo(i*s,0);
      ctx.lineTo(i*s, ROWS*s);
      ctx.stroke();
    }
    for(let j=0;j<=ROWS;j++){
      ctx.beginPath();
      ctx.moveTo(0, j*s);
      ctx.lineTo(COLS*s, j*s);
      ctx.stroke();
    }

    // platform line (bottom)
    ctx.fillStyle = '#2a2a2a';
    ctx.fillRect(0, ROWS*s - s/2, COLS*s, s/2);

    // draw passenger if waiting
    if(passenger){
      ctx.fillStyle = PASSENGER;
      ctx.fillRect(passenger.x*s + s*0.2, passenger.y*s + s*0.2, s*0.6, s*0.6);
      // little seat icon
      ctx.fillStyle = 'rgba(0,0,0,0.15)';
      ctx.fillRect(passenger.x*s + s*0.25, passenger.y*s + s*0.25, s*0.3, s*0.3);
    }

    // draw wagons first so locomotive on top
    for(let i=wagons.length-1; i>=0; i--){
      const w = wagons[i];
      drawCoach(w.x, w.y, i+1);
    }

    // locomotive
    drawLocomotive(head.x, head.y);

    // score
    ctx.fillStyle = '#e9f2ff';
    ctx.font = `${Math.max(12, Math.floor(s*0.9))}px system-ui, Arial`;
    ctx.textAlign = 'left';
    ctx.fillText('Passengers picked: ' + score, 10, 20);
    if(gameOver){
      ctx.fillStyle = 'rgba(0,0,0,0.6)';
      ctx.fillRect(0, canvas.height/2 - 40, canvas.width, 80);
      ctx.fillStyle = '#ffdddd';
      ctx.textAlign = 'center';
      ctx.font = `${Math.max(14, Math.floor(s*0.9))}px system-ui, Arial`;
      ctx.fillText('Game over! Refresh to try again.', canvas.width/2, canvas.height/2);
    } else if(!started){
      ctx.fillStyle = 'rgba(0,0,0,0.25)';
      ctx.fillRect(0,0,canvas.width,canvas.height);
    }

  }

  function drawLocomotive(x,y){
    const s = window.__cell;
    // locomotive body
    const px = x*s, py = y*s;
    // engine
    ctx.fillStyle = LOC;
    ctx.fillRect(px+2, py+4, s-4, s-6);
    // cabin
    ctx.fillStyle = '#ff7f50';
    ctx.fillRect(px+4, py+2, s-8, s-9);
    // chimney
    ctx.fillStyle = '#333';
    ctx.fillRect(px + s*0.65, py+0.5, s*0.25, s*0.5);
    // wheels
    ctx.fillStyle = '#111';
    ctx.fillRect(px+6, py+s-4, s*0.8, 4);
    // windows
    ctx.fillStyle = '#9bd4ff';
    ctx.fillRect(px+6, py+6, s-12, s-8);
  }

  function drawCoach(x,y,idx){
    const s = window.__cell;
    const px = x*s, py = y*s;
    const color = WAGON_COLORS[(idx-1) % WAGON_COLORS.length];
    // wagon body
    ctx.fillStyle = color;
    ctx.fillRect(px+2, py+4, s-4, s-6);
    // wagon roof
    ctx.fillStyle = shade(color, -20);
    ctx.fillRect(px+4, py+2, s-8, 6);
    // wheels
    ctx.fillStyle = '#111';
    ctx.fillRect(px+6, py+s-3, s-12, 3);
    // windows
    ctx.fillStyle = '#d9f0ff';
    for(let wx=6; wx<=s-12; wx+=s/2){
      ctx.fillRect(px+wx, py+6, s/4, s-12);
    }
  }

  // simple color shade helper
  function shade(hex, percent){
    // hex like #rrggbb
    const f = parseInt(hex.slice(1),16);
    const r = Math.max(0, Math.min(255, (f>>16) + Math.round( (percent/100)*255 )));
    const g = Math.max(0, Math.min(255, ((f>>8)&0xff) + Math.round( (percent/100)*255 )));
    const b = Math.max(0, Math.min(255, (f&0xff) + Math.round( (percent/100)*255 )));
    return `rgb(${r},${g},${b})`;
  }

  // Input handling
  window.addEventListener('keydown', (e) => {
    if(!started){
      started = true;
      overlay.style.display = 'none';
      hint.textContent = 'Use arrow keys or WASD to steer. Train grows by picking up passengers.';
    }
    if(gameOver) return;
    const k = e.key.toLowerCase();
    if(['arrowleft','a'].includes(e.key)){
      nextDir = {x:-1, y:0};
    } else if(['arrowright','d'].includes(e.key)){
      nextDir = {x:1, y:0};
    } else if(['arrowup','w'].includes(e.key)){
      nextDir = {x:0, y:-1};
    } else if(['arrowdown','s'].includes(e.key)){
      nextDir = {x:0, y:1};
    }
  });
  canvas.addEventListener('mousedown', () => {
    if(!started){
      started = true;
      overlay.style.display = 'none';
      hint.textContent = 'Use arrow keys or WASD to steer. Train grows by picking up passengers.';
    }
  });

  // Loop
  let last = performance.now();
  const INTERVAL = 120; // ms per step for smooth AI movement
  function loop(now){
    const dt = now - last;
    if(dt > INTERVAL){
      step();
      last = now;
    }
    requestAnimationFrame(loop);
  }

  // Init and start
  init();
  // start in auto-run mode: overlay shown until click/keypress
  requestAnimationFrame(loop);
})();
</script>
</body>
</html>