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
1 min 30 s
Cost
$0.0048
Output tokens
11,973
Spent thinking
7,232 tokens
Speed
142.8 tok/s
Console errors
0
First token after
6.3 s
First visible text after
1 min 1 s
Prompt tokens
361
Code size
15.2 KB
Finish reason
stop
Served by
OpenAI
Model id
openai/gpt-5-nano
Reasoning sent
model default
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 · 568 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Train Terminal - Colorful Passenger Run</title>
<style>
  :root{
    --bg: #0b1020;
    --board: #141a2b;
    --platform: #1f2a4f;
    --rail: #3b3b3b;
    --passenger: #ffd166;
    --locomotive: #e74c3c;
    --wagon1: #4caf50;
    --wagon2: #2196f3;
    --wagon3: #ff9800;
    --wagon4: #9c27b0;
    --shadow: rgba(0,0,0,.25);
  }
  *{box-sizing:border-box}
  html,body{
    height:100%;
    margin:0;
    background: radial-gradient( circle at 20% -10%, rgba(80,90,180,.25), transparent 40% ),
                radial-gradient( circle at 100% 0%, rgba(0,0,0,.25), transparent 40% ),
                linear-gradient(#0b1020, #0b1020 60%, #0a0f1e);
    color:#e8e8e8;
    font-family: system-ui, -apple-system, "Segoe UI", Roboto, Arial;
  }

  .wrapper{
    min-height:100%;
    display:flex;
    align-items:center;
    justify-content:center;
    padding:20px;
  }

  /* Stage container adapts to viewport while keeping 16:10 feel */
  #stage{
    width: min(96vw, 1120px);
    aspect-ratio: 16 / 10;
    position:relative;
    background: linear-gradient(#0f1730, #0b1225);
    border-radius:14px;
    box-shadow: 0 20px 40px var(--shadow);
    display:grid;
    place-items:center;
    overflow:hidden;
  }

  canvas{ 
    width: 100%;
    height: 100%;
    display:block;
    /* crisp lines on high DPI screens */
    image-rendering: crisp-edges;
  }

  /* Overlay message before start */
  #overlay{
    position:absolute;
    inset:0;
    background: rgba(12, 10, 22, 0.78);
    display:grid;
    place-items:center;
    text-align:center;
    padding:20px;
    color:white;
    cursor: pointer;
    user-select:none;
    border-radius:14px;
  }
  #overlay .card{
    background: rgba(8, 6, 14, 0.88);
    padding: 20px 22px;
    border-radius:12px;
    border:1px solid rgba(255,255,255,.15);
  }
  #overlay h1{ font-size:clamp(18px, 3vw, 28px); margin:0 0 8px; letter-spacing:.5px;}
  #overlay p{ margin:0; color:#d6d6d6; font-size:clamp(12px, 2vw, 14px); }
  #overlay .hint{ margin-top:10px; font-size:12px; opacity:.9; }

  #hud{
    position: absolute;
    left: 8px;
    top: 8px;
    display:flex; gap:8px; align-items:center;
    padding:8px 10px;
    border-radius:8px;
    background: rgba(0,0,0,.35);
    font-size: 12px;
    color:#e9e9e9;
  }
  .badge{ padding:4px 8px; border-radius:6px; background: rgba(255,255,255,.08); border:1px solid rgba(255,255,255,.15); }
  .legend{ display:flex; align-items:center; gap:6px; }
  .swatch{ width:12px; height:12px; border-radius:3px; display:inline-block; vertical-align: middle; }

  /* Responsive tweaks for narrow screens */
  @media (max-width: 700px){
    #stage{ border-radius:12px; }
  }
</style>
</head>
<body>
  <div class="wrapper">
    <div id="stage" aria-label="Train Terminal stage" role="main">
      <canvas id="game"></canvas>

      <div id="overlay" tabindex="0" aria-label="Start game overlay">
        <div class="card">
          <h1>Train Terminal</h1>
          <p>A snappy little train crawls along the rails, locomotive at the front and colorful wagons behind. It happily picks up passengers waiting on the platform.</p>
          <p class="hint">Click or press any key to start the AI-controlled run.</p>
        </div>
      </div>

      <div id="hud" aria-live="polite" style="display:flex;gap:12px;align-items:center;">
        <div class="badge">Passengers on platform: <span id="passCount">0</span></div>
        <div class="badge">Score: <span id="score">0</span></div>
        <div class="legend" title="Color key">
          <span class="swatch" style="background:#e74c3c"></span> Locomotive
          <span class="swatch" style="background:#4caf50"></span> Wagon
          <span class="swatch" style="background:#ffd166"></span> Passenger
        </div>
      </div>
    </div>
  </div>

<script>
(function(){
  // Grid configuration
  const COLS = 28;
  const ROWS = 20;

  // Train state
  let head = {x: 5, y: 5};
  let body = [ {x:5, y:5}, {x:4, y:5}, {x:3, y:5} ]; // head first
  let dir = {dx: 1, dy: 0}; // initial direction

  // Passengers on the platform
  let passengers = [];
  const MAX_PASSENGERS = 6;

  // Game state
  let started = false;
  let gameOver = false;
  let score = 0;
  let tickMs = 140; // AI steps
  let timer = null;

  // Rendering
  let canvas, ctx;
  let tileSize = 20;
  let dpr = Math.max(1, window.devicePixelRatio || 1);

  // Stage elements
  const stage = document.getElementById('stage');
  const overlay = document.getElementById('overlay');
  const passCountEl = document.getElementById('passCount');
  const scoreEl = document.getElementById('score');
  const canvasEl = document.getElementById('game');
  canvas = canvasEl;
  ctx = canvas.getContext('2d', { alpha: false });

  // Colors
  const locomotiveColor = '#e74c3c';
  const wagonColors = ['#4caf50', '#2196f3', '#ff9800', '#9c27b0', '#7bc242', '#50e3c2'];
  const platformColor = '#2a3a8a';
  const railColor = '#2f2f2f';
  const passengerColor = '#ffd166';

  function key(x,y){ return x + ',' + y; }

  function initBoard(){
    // reset train
    head = {x: 5, y: 5};
    body = [ {x:5, y:5}, {x:4, y:5}, {x:3, y:5} ];
    dir = {dx: 1, dy: 0};
    score = 0;
    passCountEl.textContent = '0';
    scoreEl.textContent = '0';
    passengers = [];
    spawnPassengerArea();
  }

  function resize(){
    // determine tileSize to fit COLS x ROWS nicely inside stage
    const w = stage.clientWidth;
    const h = stage.clientHeight;
    // Leave tiny padding
    tileSize = Math.floor(Math.min(w / COLS, h / ROWS));
    if (tileSize < 8) tileSize = 8;
    const wPx = COLS * tileSize;
    const hPx = ROWS * tileSize;

    // DPR handling for crisp rendering
    canvas.width = Math.floor(wPx * dpr);
    canvas.height = Math.floor(hPx * dpr);
    canvas.style.width = wPx + 'px';
    canvas.style.height = hPx + 'px';
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    // redraw after resize
    draw();
  }

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

  // Platform spawn: a region near the bottom where passengers wait
  function spawnPassengerArea(){
    // keep some on platform
    while (passengers.length < MAX_PASSENGERS){
      const x = randInt(1, COLS-2);
      const y = ROWS - randInt(3, 4); // platform area near bottom
      // avoid spawning on the train
      if (isOccupied(x,y)) continue;
      // ensure it's on platform row
      passengers.push({x, y, t: Date.now()});
    }
  }

  function isOccupied(x,y){
    if (head.x===x && head.y===y) return true;
    for (let b of body){
      if (b.x===x && b.y===y) return true;
    }
    // allow passengers on platform as not occupying
    return false;
  }

  function nearestPassenger(){
    if (passengers.length===0) return null;
    // choose the closest by Manhattan distance
    let best = passengers[0];
    let bestD = Math.abs(head.x - best.x) + Math.abs(head.y - best.y);
    for (let p of passengers){
      const d = Math.abs(head.x - p.x) + Math.abs(head.y - p.y);
      if (d < bestD){
        bestD = d;
        best = p;
      }
    }
    return best;
  }

  // BFS pathfinding from head to goal, treating body (except tail) as blocked
  function pathTo(goal){
    if (!goal) return null;
    const blocked = new Set();
    for (let i=0;i<body.length-1;i++){
      blocked.add(key(body[i].x, body[i].y));
    }
    const q = [];
    const visited = new Set();
    const parent = new Map();

    q.push({x: head.x, y: head.y});
    visited.add(key(head.x, head.y));

    const dirs = [
      {dx:1, dy:0},
      {dx:0, dy:1},
      {dx:-1, dy:0},
      {dx:0, dy:-1}
    ];

    while (q.length){
      const cur = q.shift();
      if (cur.x===goal.x && cur.y===goal.y){
        // reconstruct path
        const path = [];
        let node = cur;
        path.push({x: node.x, y: node.y});
        while (parent.has(key(node.x, node.y))){
          node = parent.get(key(node.x, node.y));
          path.push({x: node.x, y: node.y});
        }
        path.reverse();
        return path;
      }
      for (const d of dirs){
        const nx = cur.x + d.dx;
        const ny = cur.y + d.dy;
        if (nx<0||ny<0||nx>=COLS||ny>=ROWS) continue;
        const k = key(nx,ny);
        if (blocked.has(k)) continue;
        if (!visited.has(k)){
          visited.add(k);
          parent.set(k,{x:cur.x,y:cur.y});
          q.push({x:nx,y:ny});
        }
      }
    }
    return null;
  }

  // Simple heuristic move if no path
  function greedyMoveTowards(target){
    // choose a valid neighbor that reduces Manhattan distance
    const dirs = [
      {dx:1, dy:0},
      {dx:0, dy:1},
      {dx:-1, dy:0},
      {dx:0, dy:-1}
    ];
    const tryList = dirs.sort((a,b)=>{
      const da = Math.abs((head.x + a.dx) - target.x) + Math.abs((head.y + a.dy) - target.y);
      const db = Math.abs((head.x + b.dx) - target.x) + Math.abs((head.y + b.dy) - target.y);
      return da - db;
    });
    for (const d of tryList){
      const nx = head.x + d.dx;
      const ny = head.y + d.dy;
      if (nx<0||ny<0||nx>=COLS||ny>=ROWS) continue;
      // collision check (excluding tail)
      let coll = false;
      for (let i=0;i<body.length-1;i++){
        if (body[i].x===nx && body[i].y===ny){ coll = true; break; }
      }
      if (!coll){
        return {x:nx, y:ny};
      }
    }
    // no safe move
    return null;
  }

  function moveToCell(nx, ny){
    // collision against walls
    if (nx<0||ny<0||nx>=COLS||ny>=ROWS){
      gameOver = true;
      return false;
    }
    // collision against body (excluding tail)
    for (let i=0; i<body.length-1; i++){
      if (body[i].x===nx && body[i].y===ny){
        gameOver = true;
        return false;
      }
    }

    // determine if we will eat a passenger
    let ate = false;
    let eatenIndex = -1;
    for (let i=0; i<passengers.length; i++){
      if (passengers[i].x===nx && passengers[i].y===ny){
        ate = true;
        eatenIndex = i;
        break;
      }
    }

    // insert new head
    head = {x:nx, y:ny};
    body.unshift(head);
    if (ate){
      // remove passenger, grow by 1 (do not pop tail)
      passengers.splice(eatenIndex, 1);
      score += 1;
      scoreEl.textContent = String(score);
      // spawn more passengers to keep gameplay lively
      spawnPassengerArea();
    } else {
      // normal move: pop tail to stay same length
      body.pop();
    }

    // if no passengers left, repopulate
    if (passengers.length === 0){
      spawnPassengerArea();
    }

    return true;
  }

  function step(){
    if (!started) return;
    if (gameOver){
      // reset after a brief pause
      initBoard();
      gameOver = false;
      return;
    }

    // choose target: nearest passenger
    const target = nearestPassenger();
    if (!target){
      // nothing to do, spawn more
      spawnPassengerArea();
      return;
    }

    // compute path
    const path = pathTo(target);
    let nextCell = null;
    if (path && path.length > 1){
      nextCell = path[1];
    } else {
      // fallback greedy
      nextCell = greedyMoveTowards(target);
    }

    if (!nextCell){
      // no safe move: game over
      gameOver = true;
      return;
    }

    moveToCell(nextCell.x, nextCell.y);
    draw();
  }

  function drawPlatform(){
    // Platform zone across bottom portion
    ctx.fillStyle = platformColor;
    const platTop = ROWS - 4;
    const platH = 4;
    ctx.fillRect(0, platTop * tileSize, COLS * tileSize, platH * tileSize);

    // rails graphic
    ctx.strokeStyle = railColor;
    ctx.lineWidth = 2;
    for (let x=0; x<COLS; x++){
      // rails as two lines
      ctx.beginPath();
      ctx.moveTo(x*tileSize, (platTop)*tileSize + tileSize*0.2);
      ctx.lineTo(x*tileSize + tileSize, (platTop)*tileSize + tileSize*0.2);
      ctx.stroke();
      ctx.beginPath();
      ctx.moveTo(x*tileSize, (platTop)*tileSize + tileSize*0.8);
      ctx.lineTo(x*tileSize + tileSize, (platTop)*tileSize + tileSize*0.8);
      ctx.stroke();
    }
  }

  function drawPassengers(){
    for (let p of passengers){
      const cx = p.x * tileSize + tileSize/2;
      const cy = p.y * tileSize + tileSize/2;
      // passenger as a small circle with a cap
      ctx.fillStyle = passengerColor;
      ctx.beginPath();
      ctx.arc(cx, cy, Math.max(3, tileSize*0.18), 0, Math.PI*2);
      ctx.fill();
      // tiny hat/mark to add character
      ctx.fillStyle = '#111';
      ctx.fillRect(cx - tileSize*0.08, cy - tileSize*0.28, tileSize*0.16, tileSize*0.15);
    }
  }

  function drawTrain(){
    // draw from head to tail
    for (let i = 0; i < body.length; i++){
      const seg = body[i];
      const x = seg.x * tileSize;
      const y = seg.y * tileSize;
      const w = tileSize;
      const h = tileSize;

      // color for segment
      const color = (i === 0) ? locomotiveColor : wagonColors[(i-1) % wagonColors.length];
      ctx.fillStyle = color;
      // slightly rounded rect illusion
      roundRect(ctx, x+4, y+4, w-8, h-8, 4, true, false);

      // small detailing: windows for wagons
      if (i>0){
        ctx.fillStyle = 'rgba(255,255,255,.6)';
        ctx.fillRect(x + tileSize*0.15, y + tileSize*0.25, tileSize*0.25, tileSize*0.5);
      } else {
        // locomotive details: chimney
        ctx.fillStyle = '#2c2c2c';
        ctx.fillRect(x + tileSize*0.24, y + tileSize*0.2, tileSize*0.12, tileSize*0.3);
        // headlight
        ctx.fillStyle = '#fff';
        ctx.fillRect(x + tileSize*0.62, y + tileSize*0.42, tileSize*0.15, tileSize*0.18);
      }
    }
  }

  // helper to draw rounded rect using path
  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();
  }

  function draw(){
    // background
    ctx.fillStyle = '#101428';
    ctx.fillRect(0,0, COLS*tileSize, ROWS*tileSize);

    // grid faint lines (optional aesthetic)
    ctx.strokeStyle = 'rgba(255,255,255,.04)';
    ctx.lineWidth = 1;
    ctx.beginPath();
    for (let x=0; x<=COLS; x++){
      ctx.moveTo(x*tileSize + 0.5, 0);
      ctx.lineTo(x*tileSize + 0.5, ROWS*tileSize);
    }
    for (let y=0; y<=ROWS; y++){
      ctx.moveTo(0, y*tileSize + 0.5);
      ctx.lineTo(COLS*tileSize, y*tileSize + 0.5);
    }
    ctx.stroke();

    // platform and passengers
    drawPlatform();
    drawPassengers();

    // train
    drawTrain();
  }

  // Start handler
  function startGame(){
    if (started) return;
    started = true;
    overlay.style.display = 'none';
    overlay.removeEventListener('click', startGame);
    overlay.blur();

    // Begin or resume loop
    initBoard();
    draw();
    timer = setInterval(step, tickMs);

    // focus to enable keyboard if needed
    stage.focus?.();
  }

  // Init
  function boot(){
    resize();
    initBoard();
    draw();
  }

  // Resize listener
  window.addEventListener('resize', resize);

  // Overlay click / key start
  overlay.addEventListener('click', startGame);
  overlay.addEventListener('keydown', (e) => {
    // any key starts
    if (!started) startGame();
  });
  // allow keyboard focus on overlay by clicking
  overlay.setAttribute('tabindex','0');

  // Kick off
  boot();

})();
</script>
</body>
</html>
Reasoning trace (7,232 tokens)
**Designing a grid-based game**

I'm thinking about a grid-based game where the train picks up passengers waiting on the platform. We'll have the train move around a platform area with spawning passengers represented by simple shapes, like circular nodes on the platform. I plan to draw the train as a small rectangle for the locomotive and rectangular blocks in different colors for the wagons. It’ll all be self-contained with no external assets. Also, we'll need an AI to drive the train to pick up the passengers!**Defining the train mechanics**

The prompt involves a train with a locomotive and colorful wagons picking up passengers from the platform. The goal is to move to platform tiles to collect passengers, similar to how a snake collects food. When the locomotive reaches a passenger tile, we "pick it up" and increase the train's length by adding a wagon at the tail while removing that passenger from the platform. I’ll spawn passenger tokens randomly on the platform, which could be positioned at the bottom or along the left edge.**Implementing AI pathfinding**

I plan to spawn passengers at random spots on a static platform area, where the train can pick them up by moving onto those cells. Using AI to drive the train automatically is essential. I’m considering a simple pathfinding algorithm, like A* or a greedy approach, to guide the train toward the nearest passenger while avoiding its own body. A full A* might be more complex, so I might use a BFS pathfinding method for computing the next step while avoiding obstacles. The grid will be around 25x18 tiles, sized according to the canvas dimensions.**Designing the game start screen**

Upon the first paint, I'll display the message "Click or press a key to start," with the game paused. Once the user starts it, the AI will calculate the path to the nearest passenger. If the game is static, the path will reset. I’m thinking of making the AI deterministic and including a "self-playing demo mode" behind that message. Also, to ensure accessibility, I’ll add a header text like "Train Rush." Since a screenshot is taken around three seconds after loading, it’s crucial to have something meaningful visible, like the overlay, which could be complemented by dimming the game board.**Clarifying the demo mode setup**

The requirements state that the page can't rely on user interaction to start the evaluation, so I need a self-playing demo mode behind an overlay message like "Click or press a key to start." This overlay will ensure something meaningful is visible for the screenshot taken at three seconds after loading. I need the demo to run automatically as soon as the user clicks, while ensuring the AI can start quickly once the overlay is removed. 

I will implement a canvas that adapts to different screen sizes and includes a grid for gameplay. The train will be initialized with an array for movements and a system for picking up passengers.**Planning for passenger spawning and train drawing**

I’m thinking we’ll spawn new passengers on the platform at random intervals or when all are collected. The platform area will be defined along the bottom three rows, and we’ll set the coordinates accordingly. I want to draw the train’s head as a locomotive with a distinct color and colorful wagons trailing behind. Each segment will be a rectangle with varied colors, and I think I’ll add details like a smokestack.

For the AI, I’ll implement a BFS algorithm to navigate towards the nearest passenger while avoiding collisions. The canvas will adapt to different widths using CSS, and I’ll create a resizing function as well. I’ll set up an overlay message to guide users to start the game. As the game updates, the train will follow the computed path and draw itself on the canvas, including any new passengers collected.**Setting up the game mechanics and visuals**

I’m planning to implement a 'line' to connect train segments, drawing each as a rounded rectangle and adding a small chimney for the locomotive. Passengers waiting on the platform will be represented with small circle icons. I’ll define global constants for grid columns and rows, and create a function to spawn passengers at random positions, ensuring no duplicates with the train's head.

For the pathfinding, it’s crucial to treat the tail as non-blocking in our BFS implementation. I also want to make sure all the visuals are appealing, including a background color for the board. I’ll set up the game interface with HTML and CSS to ensure responsiveness, along with a resize function to adjust the canvas size dynamically.**Implementing game logic and collision detection**

I’m focusing on the main game loop, starting with the `startGame` function to set up the game and spawn the initial train and passengers. Then, the `step` function will compute the path to the nearest passenger. If no passengers are present, I’ll make sure to spawn one. 

Now, for movement, I'll include collision detection so if the train hits something, it resets. I need to handle which segments of the train are blocked during movement while allowing the train to move into the tail's position if it hasn't eaten a passenger yet. The BFS algorithm will help find paths while avoiding collisions. Overall, there are detailed steps to follow to ensure smooth game mechanics while handling edge cases.**Implementing game rendering and interactions**

I’m focusing on setting the `nextLive` candidate from the path, checking if a valid path exists, and if not, falling back to a random movement that minimizes the distance to the target.

My `draw` function will clear the canvas and render the train, platform, passengers, and a heads-up display (HUD). I’ll detail how to draw the platform as a colored rectangle, then add passengers as filled circles.

Additionally, I’ll design the train segments with rounded rectangles and shadows, along with a message overlay to instruct players. I need to ensure the keyboard events function correctly, and I’ll also make sure to adapt to different device pixel ratios for optimal display. Let’s implement all this in the compact code structure!**Crafting CSS and JS for the game**

I need to keep the code manageable, but it’s okay if it’s a bit lengthy. For the CSS, I’m defining various color variables for backgrounds, platforms, rails, passengers, and train parts.

I'll set the `#stage` container to maintain a 16:10 aspect ratio and ensure it’s responsive, adapting to smaller widths if necessary. The overlay will show instructions and handle clicks and key presses to start the game.

I will implement an initialization function and also adjust for window resizes, while ensuring everything compiles without errors. Time to put this all together!