Models 44
Train snakeOne-line

GPT-6 Luna Pro

by OpenAIreasoning defaultClean
Open in new window

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

How it went

Total time
2 min 50 s
Cost
$0.017
Output tokens
27,743
Spent thinking
10,781 tokens
Speed
1148.3 tok/s
Console errors
0
First token after
2 min 26 s
First visible text after
2 min 50 s
Prompt tokens
27,721
Code size
23.7 KB
Finish reason
stop
Served by
OpenAI
Model id
openai/gpt-6-luna-pro
Reasoning sent
model default
Max output
128,000 tokens
Generated
Nov 13, 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 · 535 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Little Passenger Express</title>
<style>
  :root {
    --ink: #233c37;
    --muted: #75847a;
    --paper: #fbf8ef;
    --green: #477b5e;
    --gold: #e7b751;
    --line: #e9e5d9;
  }
  * { box-sizing: border-box; }
  body {
    margin: 0;
    min-height: 100vh;
    color: var(--ink);
    background:
      radial-gradient(ellipse at 88% 2%, rgba(240, 218, 159, .22), transparent 28rem),
      #fbf9f3;
    font-family: ui-rounded, "Trebuchet MS", system-ui, sans-serif;
  }
  .page { max-width: 1200px; margin: 0 auto; padding: 25px 22px 28px; }
  .topbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 23px; }
  .brand { display: flex; align-items: center; gap: 11px; }
  .brandmark {
    width: 44px; height: 44px; display: grid; place-items: center;
    border-radius: 15px; background: #e8f0df; font-size: 25px;
    box-shadow: inset 0 0 0 1px rgba(64, 104, 77, .08);
  }
  .brand-name { font-weight: 850; font-size: 16px; letter-spacing: -.3px; }
  .brand-caption { color: var(--muted); font-size: 11px; margin-top: 2px; letter-spacing: .08em; text-transform: uppercase; }
  .top-note {
    display: flex; align-items: center; gap: 8px; color: #68786c;
    font-size: 12px; font-weight: 700; background: #f1efe6;
    border-radius: 99px; padding: 9px 13px;
  }
  .live-dot { width: 7px; height: 7px; border-radius: 50%; background: #68ad79; box-shadow: 0 0 0 3px #dcebdc; }
  .intro { margin-bottom: 19px; display: flex; justify-content: space-between; align-items: end; gap: 20px; }
  h1 { margin: 0; font-size: clamp(27px, 3.5vw, 39px); letter-spacing: -1.5px; line-height: 1.04; font-weight: 900; }
  .intro p { margin: 7px 0 0; color: var(--muted); font-size: 14px; }
  .route-chip { flex: none; border: 1px solid #e8e3d6; color: #68766b; border-radius: 99px; padding: 9px 13px; font-size: 11px; font-weight: 800; letter-spacing: .08em; }
  .route-chip span { color: #cf9b37; margin-right: 5px; }
  .layout { display: grid; grid-template-columns: minmax(0, 1fr) 260px; align-items: start; gap: 20px; }
  .game-card {
    min-width: 0; overflow: hidden; border-radius: 19px; background: #fff;
    border: 1px solid #e8e4d9; box-shadow: 0 12px 35px rgba(54, 68, 54, .08);
  }
  .canvas-wrap { position: relative; width: 100%; }
  canvas { display: block; width: 100%; height: auto; aspect-ratio: 12 / 7; }
  .start-hint {
    position: absolute; left: 50%; top: 15px; transform: translateX(-50%);
    white-space: nowrap; border: 1px solid rgba(255,255,255,.72);
    border-radius: 99px; padding: 8px 13px; color: #47644e;
    background: rgba(255, 253, 245, .93); box-shadow: 0 3px 12px rgba(49,71,50,.1);
    font-size: 11px; font-weight: 800; letter-spacing: .015em;
    pointer-events: none; transition: opacity .25s;
  }
  .start-hint b { color: #d49c34; }
  .game-footer {
    min-height: 43px; border-top: 1px solid #efede6; padding: 0 16px;
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    color: #849086; font-size: 11px;
  }
  .game-footer strong { color: #627267; font-weight: 800; }
  .keys { display: flex; gap: 4px; align-items: center; }
  kbd { min-width: 19px; padding: 3px 5px; text-align: center; border: 1px solid #e5e2d8; border-radius: 5px; background: #faf9f4; color: #6e7b70; font: 700 10px system-ui; }
  .sidebar { display: grid; gap: 13px; }
  .panel {
    background: #fff; border: 1px solid #e8e4d9; border-radius: 17px;
    box-shadow: 0 8px 24px rgba(54, 68, 54, .045); padding: 17px;
  }
  .panel-kicker { color: #92a093; font-weight: 850; font-size: 10px; letter-spacing: .13em; text-transform: uppercase; }
  .score-row { display: flex; align-items: end; justify-content: space-between; margin-top: 9px; }
  .score { font-size: 39px; line-height: 1; letter-spacing: -2px; font-weight: 900; }
  .score-label { color: #89958a; font-size: 11px; margin-top: 5px; }
  .ticket-icon { width: 43px; height: 38px; display: grid; place-items: center; border-radius: 12px; background: #fff3d6; font-size: 21px; }
  .divider { height: 1px; background: #f0eee7; margin: 15px 0 12px; }
  .stat-line { display: flex; justify-content: space-between; align-items: center; font-size: 12px; color: #7b897d; }
  .stat-line b { color: #41564a; font-size: 13px; }
  .progress { height: 6px; background: #edf0e8; border-radius: 9px; overflow: hidden; margin-top: 9px; }
  .progress > i { display: block; height: 100%; width: 0%; border-radius: inherit; background: linear-gradient(90deg, #75ac75, #d4b34d); transition: width .3s; }
  .waiting-head { display: flex; justify-content: space-between; align-items: center; }
  .count-pill { background: #f1f3eb; color: #748274; border-radius: 99px; padding: 4px 8px; font-size: 10px; font-weight: 800; }
  .queue { display: flex; gap: 8px; margin-top: 15px; min-height: 39px; align-items: center; }
  .queue-person {
    width: 33px; height: 33px; position: relative; display: grid; place-items: center;
    border-radius: 11px; background: #f7f3e8; font-size: 18px;
  }
  .queue-person::after { content: ""; position: absolute; width: 6px; height: 6px; border-radius: 50%; right: 3px; top: 4px; background: var(--dot); border: 1px solid white; }
  .queue-note { margin: 12px 0 0; color: #8a958b; font-size: 11px; line-height: 1.45; }
  .dispatch { background: #eff4e9; border-color: #e4ebdc; }
  .dispatch-title { display: flex; gap: 9px; align-items: center; font-weight: 850; font-size: 13px; }
  .dispatch-icon { width: 30px; height: 30px; display: grid; place-items: center; border-radius: 10px; background: white; font-size: 16px; }
  .dispatch p { margin: 9px 0 0 39px; color: #718073; line-height: 1.5; font-size: 11px; }
  .manual-note { margin-top: 10px; padding-top: 9px; border-top: 1px solid rgba(95,125,91,.14); color: #7e8e7b; font-size: 10px; font-weight: 700; }
  @media (max-width: 760px) {
    .page { padding: 16px 13px 24px; }
    .topbar { margin-bottom: 20px; }
    .intro { align-items: start; margin-bottom: 14px; }
    .route-chip { display: none; }
    .layout { grid-template-columns: 1fr; gap: 12px; }
    .sidebar { grid-template-columns: 1fr 1fr; gap: 10px; }
    .panel { padding: 14px; }
    .dispatch { grid-column: 1 / -1; }
    .score { font-size: 33px; }
    .top-note { font-size: 10px; padding: 8px 10px; }
  }
  @media (max-width: 430px) {
    .brand-name { font-size: 14px; }
    .brandmark { width: 39px; height: 39px; }
    .brand-caption { font-size: 9px; }
    .top-note { max-width: 126px; line-height: 1.2; }
    .start-hint { top: 9px; font-size: 9px; padding: 6px 9px; }
    .game-footer { padding: 0 10px; font-size: 9px; }
    .keys { gap: 2px; }
    kbd { min-width: 16px; padding: 2px 4px; font-size: 9px; }
  }
</style>
</head>
<body>
<div class="page">
  <header class="topbar">
    <div class="brand">
      <div class="brandmark">🚂</div>
      <div><div class="brand-name">Little Passenger Express</div><div class="brand-caption">A tiny train adventure</div></div>
    </div>
    <div class="top-note"><i class="live-dot"></i><span id="modeText">Autopilot is taking a little tour</span></div>
  </header>

  <section class="intro">
    <div>
      <h1>All aboard!</h1>
      <p>Pick up every passenger waiting along the platform.</p>
    </div>
    <div class="route-chip"><span>✦</span> THE MEADOW LOOP &nbsp;·&nbsp; 04</div>
  </section>

  <main class="layout">
    <section class="game-card" aria-label="Train game">
      <div class="canvas-wrap" id="gameArea">
        <canvas id="game" width="960" height="560" aria-label="A colorful train collecting passengers on a railway"></canvas>
        <div class="start-hint" id="hint"><b>Click or press a key to start</b> &nbsp;·&nbsp; demo is rolling</div>
      </div>
      <div class="game-footer">
        <span><strong>Follow the rails</strong> and gather a wagon for every rider.</span>
        <span class="keys"><kbd>↑</kbd><kbd>↓</kbd><kbd>←</kbd><kbd>→</kbd><span style="margin-left:3px">to steer</span></span>
      </div>
    </section>

    <aside class="sidebar">
      <section class="panel">
        <div class="panel-kicker">Journey so far</div>
        <div class="score-row">
          <div><div class="score" id="score">00</div><div class="score-label">passengers aboard</div></div>
          <div class="ticket-icon">🎟️</div>
        </div>
        <div class="divider"></div>
        <div class="stat-line"><span>Colorful wagons</span><b id="wagonCount">04</b></div>
        <div class="progress"><i id="progress"></i></div>
      </section>

      <section class="panel">
        <div class="waiting-head"><div class="panel-kicker">On the platform</div><span class="count-pill" id="waitCount">04 waiting</span></div>
        <div class="queue" id="queue"></div>
        <p class="queue-note">A friendly little whistle means someone has found a seat.</p>
      </section>

      <section class="panel dispatch">
        <div class="dispatch-title"><span class="dispatch-icon">🧭</span><span id="dispatchTitle">The conductor's note</span></div>
        <p id="dispatchText">The train knows the way. Watch it wind through the meadow and stop for its waiting friends.</p>
        <div class="manual-note">Click the railway or tap an arrow to take the controls.</div>
      </section>
    </aside>
  </main>
</div>

<script>
(() => {
  const canvas = document.getElementById('game');
  const ctx = canvas.getContext('2d');
  const W = 960, H = 560, CELL = 40, COLS = 19, ROWS = 11;
  const BX = 100, BY = 75;
  const horizontalRows = [1, 4, 7, 9];
  const verticalCols = [2, 6, 11, 16];
  const platformCols = [1, 3, 5, 7, 9, 12, 14, 17];
  const palette = ['#e8895e', '#73a7c8', '#d4a947', '#a77bbc', '#70a779', '#e27b8d', '#5c9f9a', '#d58a4b'];
  const people = ['🧑🏻', '👩🏽', '🧒🏼', '👨🏾', '👩🏻', '🧑🏿', '👧🏽', '👨🏻'];
  let body = [{c:4,r:7},{c:3,r:7},{c:2,r:7},{c:1,r:7}];
  let oldBody = body.map(p => ({...p}));
  let passengers = [];
  let score = 0, manual = false, desired = null;
  let last = performance.now(), accumulator = 0;
  const stepMs = 185;
  let whistleUntil = 0;

  const key = p => `${p.c},${p.r}`;
  const valid = (c,r) => c >= 0 && c < COLS && r >= 0 && r < ROWS &&
    (horizontalRows.includes(r) || verticalCols.includes(c));
  const neighbors = p => [[p.c+1,p.r],[p.c-1,p.r],[p.c,p.r+1],[p.c,p.r-1]]
    .filter(([c,r]) => valid(c,r)).map(([c,r]) => ({c,r}));
  const center = p => ({x: BX + p.c*CELL + CELL/2, y: BY + p.r*CELL + CELL/2});

  function addPassenger() {
    const occupied = new Set(body.map(key));
    const available = platformCols.map(c => ({c,r:9})).filter(p =>
      !occupied.has(key(p)) && !passengers.some(q => key(q) === key(p)));
    if (!available.length) return;
    const p = available[Math.floor(Math.random() * available.length)];
    passengers.push(p);
  }
  while (passengers.length < 4) addPassenger();

  function route(start, goal, avoidBody = true) {
    const blocked = new Set();
    if (avoidBody) body.slice(1, -1).forEach(p => blocked.add(key(p)));
    const queue = [start], prev = new Map([[key(start), null]]);
    for (let i=0; i<queue.length; i++) {
      const p = queue[i];
      if (key(p) === key(goal)) break;
      for (const n of neighbors(p)) {
        if (blocked.has(key(n)) || prev.has(key(n))) continue;
        prev.set(key(n), p);
        queue.push(n);
      }
    }
    if (!prev.has(key(goal))) return null;
    const path = [];
    let cur = goal;
    while (cur && key(cur) !== key(start)) {
      path.push(cur);
      cur = prev.get(key(cur));
    }
    return path.reverse();
  }

  function chooseNext() {
    const head = body[0];
    if (manual && desired) {
      const target = {c:head.c + desired.c, r:head.r + desired.r};
      if (valid(target.c,target.r) && !body.slice(0,-1).some(p => key(p) === key(target))) return target;
    }
    let targetPassenger = passengers[0], bestPath = null;
    for (const p of passengers) {
      const path = route(head,p,true);
      if (path && (!bestPath || path.length < bestPath.length)) { targetPassenger=p; bestPath=path; }
    }
    if (!bestPath) {
      for (const p of passengers) {
        const path = route(head,p,false);
        if (path && (!bestPath || path.length < bestPath.length)) { targetPassenger=p; bestPath=path; }
      }
    }
    if (bestPath && bestPath.length) return bestPath[0];
    const opts = neighbors(head).filter(n => !body.slice(0,-1).some(p => key(p) === key(n)));
    return opts[0] || head;
  }

  function tick() {
    oldBody = body.map(p => ({...p}));
    const next = chooseNext();
    const got = passengers.findIndex(p => key(p) === key(next));
    body.unshift(next);
    if (got >= 0) {
      passengers.splice(got,1);
      score++;
      whistleUntil = performance.now() + 650;
      addPassenger();
    } else {
      body.pop();
    }
    updateUI();
  }

  function updateUI() {
    document.getElementById('score').textContent = String(score).padStart(2,'0');
    document.getElementById('wagonCount').textContent = String(body.length).padStart(2,'0');
    document.getElementById('waitCount').textContent = `${passengers.length} waiting`;
    document.getElementById('progress').style.width = `${Math.min(100, (score % 8) * 12.5)}%`;
    const q = document.getElementById('queue');
    q.innerHTML = '';
    passengers.slice(0,4).forEach((p,i) => {
      const el = document.createElement('span');
      el.className = 'queue-person';
      el.style.setProperty('--dot', palette[(score+i+2)%palette.length]);
      el.textContent = people[(score+i+1)%people.length];
      q.appendChild(el);
    });
  }

  function roundedRect(x,y,w,h,r,fill,stroke) {
    ctx.beginPath(); ctx.roundRect(x,y,w,h,r);
    if (fill) { ctx.fillStyle=fill; ctx.fill(); }
    if (stroke) { ctx.strokeStyle=stroke; ctx.stroke(); }
  }

  function drawBackground() {
    const bg = ctx.createLinearGradient(0,0,0,H);
    bg.addColorStop(0,'#faf8ef'); bg.addColorStop(1,'#f3f0e5');
    ctx.fillStyle=bg; ctx.fillRect(0,0,W,H);

    ctx.fillStyle='#718a6c'; ctx.font='800 11px system-ui'; ctx.letterSpacing='1.5px';
    ctx.fillText('THE MEADOW LOOP', 102, 43);
    ctx.fillStyle='#a5ac9e'; ctx.font='700 10px system-ui'; ctx.letterSpacing='1px';
    ctx.fillText('A VERY SCENIC ROUTE', 102, 57);
    roundedRect(765,27,93,29,14,'#fffdf6','#e9e4d7');
    ctx.fillStyle='#80917e'; ctx.font='700 10px system-ui'; ctx.textAlign='center';
    ctx.fillText('☀  22°  SUNNY',811.5,45.5); ctx.textAlign='left';

    ctx.save();
    ctx.shadowColor='rgba(52,64,47,.15)'; ctx.shadowBlur=18; ctx.shadowOffsetY=7;
    roundedRect(76,65,808,470,19,'#71946a');
    ctx.restore();
    roundedRect(80,69,800,462,16,'#e7e6d5');
    ctx.save();
    ctx.beginPath(); ctx.roundRect(BX,BY,COLS*CELL,ROWS*CELL,9); ctx.clip();
    for(let r=0;r<ROWS;r++) for(let c=0;c<COLS;c++) {
      const x=BX+c*CELL,y=BY+r*CELL;
      ctx.fillStyle=(r+c)%2 ? '#a9c38d' : '#b1c995';
      ctx.fillRect(x,y,CELL,CELL);
      ctx.fillStyle='rgba(255,255,255,.065)';
      ctx.fillRect(x+2,y+2,CELL-4,CELL-4);
      if ((c*7+r*11)%17===0) {
        ctx.fillStyle='rgba(74,119,71,.23)';
        ctx.beginPath(); ctx.arc(x+8+(c%3)*9,y+10+(r%2)*13,1.5,0,Math.PI*2); ctx.fill();
      }
    }
    drawPlatform();
    drawRails();
    drawScenery();
    passengers.forEach((p,i)=>drawPerson(p,i));
    drawTrain();
    ctx.restore();

    roundedRect(100,496,156,25,9,'rgba(255,253,245,.92)','rgba(255,255,255,.7)');
    ctx.fillStyle='#788675'; ctx.font='800 9px system-ui'; ctx.letterSpacing='.8px';
    ctx.fillText('✦  MEADOW STATION',112,512);
    roundedRect(768,496,92,25,9,'rgba(255,253,245,.92)','rgba(255,255,255,.7)');
    ctx.fillStyle='#788675'; ctx.font='700 9px system-ui'; ctx.letterSpacing='0';
    ctx.textAlign='center'; ctx.fillText('TRACK CLEAR',814,512); ctx.textAlign='left';
  }

  function drawPlatform() {
    const y=BY+9*CELL;
    ctx.fillStyle='#d9d5c4'; ctx.fillRect(BX,y,COLS*CELL,CELL);
    for(let c=0;c<COLS;c++) {
      ctx.fillStyle=c%2 ? '#e9e3d2' : '#e4decc';
      ctx.fillRect(BX+c*CELL+1,y+3,CELL-2,CELL-5);
      ctx.strokeStyle='rgba(161,151,123,.18)'; ctx.lineWidth=1;
      ctx.strokeRect(BX+c*CELL+1,y+3,CELL-2,CELL-5);
    }
    ctx.fillStyle='#e6bd56'; ctx.fillRect(BX,y+3,COLS*CELL,3);
    ctx.fillStyle='rgba(255,255,255,.66)'; ctx.fillRect(BX,y+8,COLS*CELL,2);
    roundedRect(BX+8,y-27,88,20,6,'#f8f2dc','#e9dcb7');
    ctx.fillStyle='#8b805f'; ctx.font='800 9px system-ui'; ctx.fillText('PLATFORM  01',BX+16,y-14);
  }

  function drawRails() {
    ctx.lineCap='round';
    function horizontal(r) {
      const y=BY+r*CELL+CELL/2;
      for(let c=0;c<COLS;c++) {
        const x=BX+c*CELL+CELL/2;
        ctx.strokeStyle='#7b6d54'; ctx.lineWidth=5;
        ctx.beginPath();ctx.moveTo(x,y-10);ctx.lineTo(x,y+10);ctx.stroke();
      }
      for(const off of [-6,6]) {
        ctx.strokeStyle='#61745d';ctx.lineWidth=6;
        ctx.beginPath();ctx.moveTo(BX+CELL/2,y+off);ctx.lineTo(BX+(COLS-.5)*CELL,y+off);ctx.stroke();
        ctx.strokeStyle='#c8d0b4';ctx.lineWidth=2.5;
        ctx.beginPath();ctx.moveTo(BX+CELL/2,y+off);ctx.lineTo(BX+(COLS-.5)*CELL,y+off);ctx.stroke();
        ctx.strokeStyle='rgba(255,255,255,.55)';ctx.lineWidth=1;
        ctx.beginPath();ctx.moveTo(BX+CELL/2,y+off-1);ctx.lineTo(BX+(COLS-.5)*CELL,y+off-1);ctx.stroke();
      }
    }
    function vertical(c) {
      const x=BX+c*CELL+CELL/2;
      for(let r=1;r<9;r++) {
        const y=BY+r*CELL+CELL/2;
        ctx.strokeStyle='#7b6d54';ctx.lineWidth=5;
        ctx.beginPath();ctx.moveTo(x-10,y);ctx.lineTo(x+10,y);ctx.stroke();
      }
      for(const off of [-6,6]) {
        ctx.strokeStyle='#61745d';ctx.lineWidth=6;
        ctx.beginPath();ctx.moveTo(x+off,BY+CELL*1.5);ctx.lineTo(x+off,BY+CELL*9.5);ctx.stroke();
        ctx.strokeStyle='#c8d0b4';ctx.lineWidth=2.5;
        ctx.beginPath();ctx.moveTo(x+off,BY+CELL*1.5);ctx.lineTo(x+off,BY+CELL*9.5);ctx.stroke();
        ctx.strokeStyle='rgba(255,255,255,.55)';ctx.lineWidth=1;
        ctx.beginPath();ctx.moveTo(x+off-1,BY+CELL*1.5);ctx.lineTo(x+off-1,BY+CELL*9.5);ctx.stroke();
      }
    }
    horizontalRows.forEach(horizontal);
    verticalCols.forEach(vertical);
    ctx.lineCap='butt';
  }

  function drawScenery() {
    const patches = [
      [0,0],[5,0],[9,0],[14,0],[18,0],[0,3],[4,3],[8,3],[13,3],[18,3],
      [0,6],[4,6],[9,6],[14,6],[18,6],[0,10],[4,10],[9,10],[15,10],[18,10]
    ];
    for(const [c,r] of patches) {
      if(horizontalRows.includes(r) || verticalCols.includes(c)) continue;
      const x=BX+c*CELL+20,y=BY+r*CELL+20;
      for(let k=0;k<3;k++) {
        const a=(k*2.1)+c;
        ctx.fillStyle=['#809d69','#8ca978','#d1b958'][k];
        ctx.beginPath();ctx.arc(x+Math.cos(a)*6,y+Math.sin(a)*5,2.3,0,Math.PI*2);ctx.fill();
      }
    }
    // A tiny station lamp beside the platform.
    const lx=BX+COLS*CELL-27, ly=BY+9*CELL-31;
    ctx.strokeStyle='#76816a';ctx.lineWidth=2;
    ctx.beginPath();ctx.moveTo(lx,ly+17);ctx.lineTo(lx,ly+35);ctx.stroke();
    ctx.fillStyle='#f4d46d';ctx.beginPath();ctx.arc(lx,ly+8,5,0,Math.PI*2);ctx.fill();
    ctx.strokeStyle='#fff1b8';ctx.lineWidth=2;ctx.beginPath();ctx.arc(lx,ly+8,8,0,Math.PI*2);ctx.stroke();
  }

  function drawPerson(p,i) {
    const {x,y}=center(p);
    const col=palette[(score+i+1)%palette.length];
    ctx.save();
    // Little shadow and waiting figure.
    ctx.fillStyle='rgba(62,65,48,.14)';ctx.beginPath();ctx.ellipse(x,y+13,8,2.5,0,0,Math.PI*2);ctx.fill();
    ctx.fillStyle='#f0c79f';ctx.beginPath();ctx.arc(x,y-9,5.1,0,Math.PI*2);ctx.fill();
    ctx.fillStyle=['#7b614e','#493c35','#bd784b','#574d48'][i%4];
    ctx.beginPath();ctx.arc(x,y-11,5,Math.PI,Math.PI*2);ctx.fill();
    roundedRect(x-5.7,y-3,11.4,12,4,col);
    ctx.strokeStyle='#725c48';ctx.lineWidth=1.5;
    ctx.beginPath();ctx.moveTo(x-2.5,y+8);ctx.lineTo(x-3.5,y+12);ctx.moveTo(x+2.5,y+8);ctx.lineTo(x+3.5,y+12);ctx.stroke();
    ctx.fillStyle='#fff8df';ctx.beginPath();ctx.arc(x+8,y-13,5.5,0,Math.PI*2);ctx.fill();
    ctx.fillStyle='#d5a641';ctx.font='700 7px system-ui';ctx.textAlign='center';ctx.fillText('·',x+8,y-10);
    ctx.restore();
  }

  function drawWagon(pos,angle,index,alpha) {
    const color=palette[(index-1)%palette.length];
    ctx.save();ctx.globalAlpha=alpha;ctx.translate(pos.x,pos.y);ctx.rotate(angle);
    ctx.fillStyle='rgba(42,56,45,.22)';
    ctx.beginPath();ctx.ellipse(0,10,16,5,0,0,Math.PI*2);ctx.fill();
    // wheels
    for(const x of [-9,9]) {
      ctx.fillStyle='#46514a';ctx.beginPath();ctx.arc(x,9,4,0,Math.PI*2);ctx.fill();
      ctx.fillStyle='#d5c79d';ctx.beginPath();ctx.arc(x,9,1.6,0,Math.PI*2);ctx.fill();
    }
    roundedRect(-15,-9,30,18,6,'#fff0c9');
    roundedRect(-14,-11,28,17,6,color);
    roundedRect(-8,-8,8,7,2,'rgba(255,246,213,.78)');
    roundedRect(2,-8,8,7,2,'rgba(255,246,213,.78)');
    ctx.fillStyle='rgba(255,255,255,.22)';ctx.fillRect(-10,-10,17,2);
    ctx.restore();
  }

  function drawEngine(pos,angle,alpha) {
    ctx.save();ctx.globalAlpha=alpha;ctx.translate(pos.x,pos.y);ctx.rotate(angle);
    ctx.fillStyle='rgba(43,55,44,.23)';ctx.beginPath();ctx.ellipse(-1,11,19,5,0,0,Math.PI*2);ctx.fill();
    for(const x of [-11,8]) {
      ctx.fillStyle='#47534b';ctx.beginPath();ctx.arc(x,10,4.2,0,Math.PI*2);ctx.fill();
      ctx.fillStyle='#e6d4a2';ctx.beginPath();ctx.arc(x,10,1.7,0,Math.PI*2);ctx.fill();
    }
    // Boiler and cab
    roundedRect(-15,-10,28,18,7,'#407b69','#315e50');
    roundedRect(-17,-17,16,17,5,'#538d76','#315e50');
    roundedRect(-14,-15,10,8,2,'#c8e0c0');
    roundedRect(7,-7,11,13,5,'#e49a55','#b36a3b');
    // chimney and steam
    roundedRect(-7,-20,7,8,2,'#354f49');
    roundedRect(-9,-22,11,3,2,'#d3a547');
    ctx.fillStyle='#ffdf83';ctx.beginPath();ctx.arc(16,-1,3,0,Math.PI*2);ctx.fill();
    ctx.fillStyle='#fff5ca';ctx.beginPath();ctx.arc(17,-1,1.1,0,Math.PI*2);ctx.fill();
    ctx.strokeStyle='#f7d77a';ctx.lineWidth=2;ctx.beginPath();ctx.moveTo(-3,8);ctx.lineTo(3,8);ctx.stroke();
    ctx.restore();
  }

  function drawTrain() {
    const t=Math.min(1,accumulator/stepMs);
    const positions=[];
    for(let i=0;i<body.length;i++) {
      const now=body[i], was=oldBody[i] || now;
      const a=center(was), b=center(now);
      positions.push({x:a.x+(b.x-a.x)*t,y:a.y+(b.y-a.y)*t});
    }
    for(let i=body.length-1;i>=1;i--) {
      const prev=positions[i-1] || positions[i];
      const cur=positions[i];
      const angle=Math.atan2(cur.y-prev.y,cur.x-prev.x);
      drawWagon(cur,angle,i,1);
    }
    const head=positions[0];
    const was=oldBody[0] || body[0], now=body[0];
    let dx=now.c-was.c,dy=now.r-was.r;
    if(!dx&&!dy) { dx=1; }
    drawEngine(head,Math.atan2(dy,dx),1);
    if(performance.now()<whistleUntil) {
      ctx.save();ctx.fillStyle='#fff8de';ctx.font='800 10px system-ui';ctx.textAlign='center';
      ctx.fillText('toot!',head.x,head.y-25);ctx.restore();
    }
  }

  function draw(now) {
    const dt=Math.min(50,now-last);last=now;accumulator+=dt;
    while(accumulator>=stepMs) { tick(); accumulator-=stepMs; }
    drawBackground();
    requestAnimationFrame(draw);
  }

  function takeControls(e) {
    if (!manual) {
      manual = true;
      document.getElementById('hint').style.opacity = '0';
      document.getElementById('modeText').textContent = 'You’re the conductor now';
      document.getElementById('dispatchTitle').textContent = 'Your train, your route';
      document.getElementById('dispatchText').textContent = 'Use the arrow keys to steer at each junction and collect the colorful platform passengers.';
    }
    if (e && e.key) {
      const directions = {
        ArrowUp:{c:0,r:-1}, w:{c:0,r:-1}, W:{c:0,r:-1},
        ArrowDown:{c:0,r:1}, s:{c:0,r:1}, S:{c:0,r:1},
        ArrowLeft:{c:-1,r:0}, a:{c:-1,r:0}, A:{c:-1,r:0},
        ArrowRight:{c:1,r:0}, d:{c:1,r:0}, D:{c:1,r:0}
      };
      if (directions[e.key]) { desired=directions[e.key]; e.preventDefault(); }
    }
  }
  window.addEventListener('keydown', takeControls);
  document.getElementById('gameArea').addEventListener('pointerdown', () => takeControls(null));
  updateUI();
  requestAnimationFrame(draw);
})();
</script>
</body>
</html>
Reasoning trace (10,781 tokens)
**Designing a game demo**

I need to create a polished game with a self-playing demo triggered by a click or key prompt. The visuals should be engaging, featuring a cute train snake and passengers waiting at the platform. I want to ensure the canvas is responsive, possibly depicting a train moving along grid rails among lush scenery. The demo can display a translucent badge saying "DEMO RUNNING • click or press any key to drive," alongside a message to start the game in a self-playing mode. I'll make sure the layout fits a 900x590 canvas embedded on the right panel of a 1280-wide screen.**Planning the game grid**

I'm visualizing the train snake moving on a grid board with a platform and passengers waiting. A better canvas size would be 960x620, with a 40-pixel tile and 60-pixel margins. The train will move cell by cell with smooth interpolation. Passengers can spawn at the platform edges, and the track will form a rectangular maze. I’ll use a 20x12 grid, drawing the railway tracks across a grassy courtyard. The train should navigate to passengers using BFS while avoiding self-collision, creating a dynamic playfield with a fixed track path and an inviting aesthetic.**Creating the game board**

I can create an 18x12 board with dark green grass tiles and subtle dashed lines for the rails. The train should move in all four directions, leaving a continuous trail beneath it as it travels. I might consider drawing the rails within each cell to form a network lattice, and I can place horizontal rails on alternating rows for variety. However, I need to remember that ensuring AI pathfinding within this constrained network may present some challenges.**Considering pathfinding choices**

I'm deciding that AI should choose BFS for finding the shortest path to a passenger, but I need to think about self-collision. It's possible that the path intersects with its own train, which could create issues. But then again, visuals are rarely a problem. There’s a lot to consider in this scenario, and I want to ensure it's handled properly, even if it means rethinking some assumptions.**Figuring out train wagon colors**

I'm thinking about how to manage the color array for the train path wagons. The initial body length is set to 4 and will grow by 1 with each collection. I'll need to consider the segment count and the color palette. It's also interesting to note that the actual body might overlap if the path is shorter, but that seems alright. I'm curious about how this will all come together!**Considering train structure**

I’m thinking about the train's length and the number of wagons needed. It looks like I should focus on details like the body length, which I’ll set to an initial value of 4. I also need to ensure that the wagon count accurately reflects this and I might keep tracking passengers boarding with a count of 4. I need to use the exact code for all of this. I'll work on refining my calculations.