Models 44
Train snakeOne-line

Muse Ember

Open in new window

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

How it went

Total time
22.4 s
Cost
$0.0054
Output tokens
10,752
Spent thinking
2,865 tokens
Speed
538.8 tok/s
Console errors
0
First token after
2.4 s
First visible text after
10.6 s
Prompt tokens
380
Code size
22.7 KB
Finish reason
stop
Served by
Meta
Model id
meta/muse-ember
Reasoning sent
{"effort":"medium"}
Max output
943,718 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 · 473 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Little Train Snake — Passenger Pickup</title>
<style>
  *{box-sizing:border-box;margin:0;padding:0}
  body{
    font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;
    background:linear-gradient(160deg,#0f2a5a 0%,#1a5a8a 45%,#2a9d8f 100%);
    color:#fff;min-height:100vh;padding:14px;
  }
  .wrap{max-width:1080px;margin:0 auto}
  header{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:10px;margin-bottom:12px}
  h1{font-size:clamp(24px,4vw,38px);letter-spacing:.3px;line-height:1;text-shadow:0 3px 0 rgba(0,0,0,.25)}
  h1 span{color:#ffd93d}
  .sub{opacity:.92;margin-top:5px;font-size:14px}
  .stats{display:flex;gap:8px;flex-wrap:wrap}
  .stat{background:rgba(255,255,255,.96);color:#17375e;border-radius:14px;padding:8px 13px;min-width:92px;text-align:center;box-shadow:0 6px 16px rgba(0,0,0,.25)}
  .stat b{display:block;font-size:22px;line-height:1;color:#e63946}
  .stat small{font-weight:700;text-transform:uppercase;letter-spacing:.6px;font-size:10px;opacity:.75}
  .main{display:flex;gap:14px;align-items:flex-start;flex-wrap:wrap}
  .gameCard{
    background:#fff;padding:10px;border-radius:20px;box-shadow:0 18px 40px rgba(0,0,0,.35);
    flex:1 1 660px;max-width:660px;position:relative;
  }
  .canvasWrap{position:relative;border-radius:14px;overflow:hidden;background:#8ec86e;line-height:0}
  canvas{width:100%;height:auto;display:block;touch-action:none}
  #overlay{
    position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;
    background:rgba(10,25,55,.48);backdrop-filter:blur(2px);text-align:center;padding:20px;line-height:1.3;z-index:5
  }
  #overlay h2{font-size:clamp(24px,4vw,36px);margin-bottom:8px;text-shadow:0 3px 8px rgba(0,0,0,.5)}
  #overlay p{max-width:420px;margin-bottom:14px;font-weight:600;text-shadow:0 2px 6px rgba(0,0,0,.6)}
  button{border:0;cursor:pointer;font-weight:800;border-radius:999px}
  .playBtn{background:#ffd93d;color:#5b3700;font-size:18px;padding:13px 26px;box-shadow:0 8px 0 #d9a400,0 14px 24px rgba(0,0,0,.35)}
  .playBtn:active{transform:translateY(4px);box-shadow:0 4px 0 #d9a400}
  .side{flex:1 1 290px;display:flex;flex-direction:column;gap:12px}
  .panel{background:rgba(255,255,255,.97);color:#1d3557;border-radius:18px;padding:14px;box-shadow:0 10px 24px rgba(0,0,0,.25)}
  .panel h3{font-size:16px;margin-bottom:8px}
  .panel p{font-size:13.5px;line-height:1.45;color:#445}
  .controls{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;margin-top:10px}
  .controls button{background:#1d3557;color:#fff;padding:11px 6px;font-size:18px;border-radius:10px}
  .controls button:active{background:#e63946}
  .rowBtns{display:flex;gap:8px;margin-top:8px;flex-wrap:wrap}
  .rowBtns button{padding:9px 13px;background:#e63946;color:#fff;font-size:13px}
  .rowBtns button:nth-child(2){background:#2a9d8f}
  .rowBtns button:nth-child(3){background:#f59e0b;color:#fff}
  .wagons{display:flex;gap:5px;flex-wrap:wrap;margin-top:6px}
  .w{width:30px;height:22px;border-radius:6px;border:2px solid rgba(0,0,0,.18);position:relative}
  .w:after{content:"😊";position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:13px}
  .hint{font-size:12px;background:rgba(255,217,61,.22);border:1px solid rgba(255,217,61,.7);color:#fff;border-radius:12px;padding:9px 11px;line-height:1.4}
  .legend{display:flex;justify-content:space-between;gap:8px;font-size:12px;margin-top:8px}
  .legend div{background:#eef4ff;padding:7px;border-radius:9px;flex:1;text-align:center}
  footer{text-align:center;opacity:.85;font-size:12px;margin-top:12px}
  @media(max-width:760px){
    body{padding:10px}
    .gameCard{max-width:100%;flex-basis:100%}
    .side{flex-basis:100%}
    .stat{min-width:78px;padding:7px 8px}
  }
</style>
</head>
<body>
<div class="wrap">
  <header>
    <div>
      <h1>🚂 Little <span>Train</span> Snake</h1>
      <div class="sub">Choo-choo! Pick up waiting passengers from the platform — each one boards a new colourful wagon.</div>
    </div>
    <div class="stats">
      <div class="stat"><b id="score">0</b><small>Passengers</small></div>
      <div class="stat"><b id="points">0</b><small>Points</small></div>
      <div class="stat"><b id="wagonsStat">5</b><small>Wagons</small></div>
      <div class="stat"><b id="levelStat">1</b><small>Level</small></div>
    </div>
  </header>

  <div class="main">
    <div class="gameCard">
      <div class="canvasWrap" id="canvasWrap">
        <canvas id="game" width="640" height="512"></canvas>
        <div id="overlay">
          <h2 id="ovTitle">🚂 Demo running!</h2>
          <p id="ovText">The little train is playing by itself.<br>Click or press a key to take the controls and pick up passengers!</p>
          <button class="playBtn" id="playBtn">▶ Start Playing</button>
        </div>
      </div>
    </div>

    <div class="side">
      <div class="panel">
        <h3>🎮 How to play</h3>
        <p>Drive the locomotive with <b>Arrow Keys</b> or <b>WASD</b>. Swipe on mobile or use the pad below. Stop at passengers waiting on the top &amp; bottom platforms to pick them up. Don’t crash into walls or your own wagons!</p>
        <div class="controls">
          <span></span><button data-dir="up" aria-label="Up">▲</button><span></span>
          <button data-dir="left" aria-label="Left">◀</button><button data-dir="down" aria-label="Down">▼</button><button data-dir="right" aria-label="Right">▶</button>
        </div>
        <div class="rowBtns">
          <button id="pauseBtn">⏸ Pause (Space)</button>
          <button id="restartBtn">↻ Restart</button>
          <button id="demoBtn">🤖 Demo</button>
        </div>
        <div class="legend">
          <div>🚂 Locomotive<br><b>you drive it</b></div>
          <div>🧑‍🤝‍🧑 Passengers<br><b>on platform</b></div>
          <div>🎨 Wagons<br><b>grow +1</b></div>
        </div>
      </div>

      <div class="panel">
        <h3>🛻 Your train so far</h3>
        <p>Every passenger who boards gets their own wagon — watch the train grow!</p>
        <div class="wagons" id="wagonStrip"></div>
      </div>

      <div class="hint">💡 Tip: Passengers wave from <b>Platform 1 (top)</b> and <b>Platform 2 (bottom)</b>. The golden prize is 10 points per passenger, and you speed up every 5 pickups!</div>
    </div>
  </div>
  <footer>Made with ❤️ • Little Train Snake • Press <b>P</b> or <b>Space</b> to pause</footer>
</div>

<script>
const COLS=20, ROWS=16, CELL=32;
const cv=document.getElementById('game'), ctx=cv.getContext('2d');
const overlay=document.getElementById('overlay'), ovTitle=document.getElementById('ovTitle'),
      ovText=document.getElementById('ovText'), playBtn=document.getElementById('playBtn');

const WAGON_COLORS=['#ef4444','#f59e0b','#3b82f6','#22c55e','#a855f7','#ec4899','#14b8a6','#f97316'];
const SKINS=['#ffdbac','#f1c27d','#e0ac69','#c68642','#8d5524','#ffe0bd'];
const HAIRS=['#222','#6b3f1d','#d9a441','#8a2be2','#111','#b5522a','#3a6ea5'];
const SHIRTS=['#ef4444','#f59e0b','#3b82f6','#22c55e','#ec4899','#8b5cf6','#06b6d4','#f97316'];

let snake=[], prevSnake=[], dir={x:1,y:0}, nextDir={x:1,y:0}, dirQueue=[];
let passengers=[], floats=[], particles=[], smoke=[];
let score=0, points=0, best=0, stepMs=165, lastStep=performance.now(), lastSmoke=0;
let mode='demo', paused=false, dead=false, audioCtx=null;

function rnd(a,b){return Math.floor(Math.random()*(b-a+1))+a}
function pick(arr){return arr[rnd(0,arr.length-1)]}
function makePerson(x,y){
  return {x,y,skin:pick(SKINS),hair:pick(HAIRS),shirt:pick(SHIRTS),phase:Math.random()*6.28};
}
function resetGame(){
  dir={x:1,y:0}; nextDir={x:1,y:0}; dirQueue=[];
  snake=[];
  for(let i=0;i<5;i++){
    snake.push({x:9-i,y:8,passenger: i===0?null:makePerson(0,0)});
  }
  prevSnake=JSON.parse(JSON.stringify(snake));
  passengers=[]; floats=[]; particles=[]; smoke=[];
  score=0; points=0; stepMs=165; dead=false; paused=false;
  spawnPassenger(); spawnPassenger();
  updateHUD();
}
function isOnSnake(x,y){return snake.some(s=>s.x===x&&s.y===y)}
function spawnPassenger(){
  for(let tries=0;tries<80;tries++){
    const x=rnd(1,18);
    const y=Math.random()<0.5?1:14; // platform rows
    if(isOnSnake(x,y)) continue;
    if(passengers.some(p=>p.x===x&&p.y===y)) continue;
    // keep a little gap from other passengers
    if(passengers.some(p=>Math.abs(p.x-x)+Math.abs(p.y-y)<3)) continue;
    passengers.push(makePerson(x,y));
    return;
  }
}
function updateHUD(){
  document.getElementById('score').textContent=score;
  document.getElementById('points').textContent=points;
  document.getElementById('wagonsStat').textContent=snake.length;
  document.getElementById('levelStat').textContent=1+Math.floor(score/5);
  const strip=document.getElementById('wagonStrip'); strip.innerHTML='';
  for(let i=1;i<snake.length;i++){
    const d=document.createElement('div'); d.className='w';
    d.style.background=WAGON_COLORS[(i-1)%WAGON_COLORS.length]; strip.appendChild(d);
  }
}
function setDir(x,y){
  const last=dirQueue.length?dirQueue[dirQueue.length-1]:dir;
  if(last.x===-x && last.y===-y) return;
  if(last.x===x && last.y===y) return;
  if(dirQueue.length<3) dirQueue.push({x,y});
}
function keyDir(e){
  const k=e.key.toLowerCase();
  if(['arrowup','w'].includes(k)) setDir(0,-1);
  else if(['arrowdown','s'].includes(k)) setDir(0,1);
  else if(['arrowleft','a'].includes(k)) setDir(-1,0);
  else if(['arrowright','d'].includes(k)) setDir(1,0);
}
// BFS for demo AI
function aiDir(){
  const head=snake[0];
  const targets=passengers;
  if(!targets.length) return dir;
  const bodySet=new Set(snake.slice(0,-1).map(s=>s.x+','+s.y));
  const key=(x,y)=>x+','+y;
  const q=[{x:head.x,y:head.y}]; const seen=new Map(); seen.set(key(head.x,head.y),null);
  let found=null;
  while(q.length){
    const cur=q.shift();
    if(targets.some(t=>t.x===cur.x&&t.y===cur.y) && !(cur.x===head.x&&cur.y===head.y)){found=cur;break;}
    for(const d of [{x:1,y:0},{x:-1,y:0},{x:0,y:1},{x:0,y:-1}]){
      const nx=cur.x+d.x, ny=cur.y+d.y;
      if(nx<0||ny<0||nx>=COLS||ny>=ROWS) continue;
      if(bodySet.has(key(nx,ny))) continue;
      if(seen.has(key(nx,ny))) continue;
      seen.set(key(nx,ny),cur); q.push({x:nx,y:ny});
    }
  }
  if(found){
    let cur=found;
    while(seen.get(key(cur.x,cur.y)) && !(seen.get(key(cur.x,cur.y)).x===head.x&&seen.get(key(cur.x,cur.y)).y===head.y)){
      cur=seen.get(key(cur.x,cur.y));
    }
    return {x:cur.x-head.x,y:cur.y-head.y};
  }
  // fallback safe move
  const opts=[{x:1,y:0},{x:-1,y:0},{x:0,y:1},{x:0,y:-1}].filter(d=>{
    const nx=head.x+d.x, ny=head.y+d.y;
    return nx>=0&&ny>=0&&nx<COLS&&ny<ROWS&&!bodySet.has(key(nx,ny))&&!(d.x===-dir.x&&d.y===-dir.y);
  });
  return opts.length?pick(opts):dir;
}
function chime(){
  try{
    if(!audioCtx) audioCtx=new (window.AudioContext||window.webkitAudioContext)();
    const o=audioCtx.createOscillator(), g=audioCtx.createGain();
    o.type='triangle'; o.frequency.setValueAtTime(620,audioCtx.currentTime);
    o.frequency.exponentialRampToValueAtTime(980,audioCtx.currentTime+0.12);
    g.gain.setValueAtTime(0.12,audioCtx.currentTime); g.gain.exponentialRampToValueAtTime(0.001,audioCtx.currentTime+0.22);
    o.connect(g); g.connect(audioCtx.destination); o.start(); o.stop(audioCtx.currentTime+0.24);
  }catch(e){}
}
function step(now){
  prevSnake=JSON.parse(JSON.stringify(snake));
  if(mode==='demo') dir=aiDir();
  else if(dirQueue.length) dir=dirQueue.shift();
  nextDir=dir;
  const head={x:snake[0].x+dir.x, y:snake[0].y+dir.y, passenger:null};
  // wall or self collision
  const willHitSelf = snake.some((s,i)=> i<snake.length-1 && s.x===head.x && s.y===head.y);
  if(head.x<0||head.y<0||head.x>=COLS||head.y>=ROWS||willHitSelf){
    dead=true;
    if(mode==='demo'){
      setTimeout(()=>{ if(mode==='demo') resetGame(); },700);
    }else{
      best=Math.max(best,points); updateHUD();
      ovTitle.textContent='💥 Oh no! Derailment!';
      ovText.innerHTML=`Your train crashed with <b>${score} passengers</b> aboard.<br>Points: <b>${points}</b> • Best: <b>${best}</b><br>Click to ride again!`;
      playBtn.textContent='↻ Ride Again';
      overlay.style.display='flex';
    }
    return;
  }
  snake.unshift(head);
  const idx=passengers.findIndex(p=>p.x===head.x&&p.y===head.y);
  if(idx>=0){
    const p=passengers.splice(idx,1)[0];
    head.passenger=p;
    score++; points+=10; stepMs=Math.max(82,165-score*4);
    floats.push({x:head.x,y:head.y,text:'+1 🧑 aboard! +10',life:1});
    for(let i=0;i<10;i++) particles.push({x:head.x*CELL+16,y:head.y*CELL+16,vx:(Math.random()-0.5)*3,vy:-Math.random()*2-1,life:1,c:pick(['#ffd93d','#ff8fab','#7bf1a8'])});
    chime();
    spawnPassenger();
    updateHUD();
  }else{
    snake.pop();
  }
  lastStep=now;
}
function drawBackground(t){
  // grass
  const g=ctx.createLinearGradient(0,0,0,512); g.addColorStop(0,'#8ec86e'); g.addColorStop(1,'#6fb34f');
  ctx.fillStyle=g; ctx.fillRect(0,0,640,512);
  // flowers deterministic
  ctx.fillStyle='rgba(255,255,255,.55)';
  for(let i=0;i<26;i++){ const x=(i*97)%630+5, y=90+((i*61)%340); ctx.beginPath(); ctx.arc(x,y,2.2,0,7); ctx.fill(); }
  // track rails + sleepers per row
  for(let r=2;r<14;r++){
    const y=r*CELL;
    ctx.fillStyle='rgba(120,72,20,.35)';
    for(let c=0;c<COLS;c++) ctx.fillRect(c*CELL+3,y+12,26,8);
    ctx.strokeStyle='rgba(220,225,230,.75)'; ctx.lineWidth=2;
    ctx.beginPath(); ctx.moveTo(0,y+13); ctx.lineTo(640,y+13); ctx.moveTo(0,y+19); ctx.lineTo(640,y+19); ctx.stroke();
    // vertical crossings faint
    ctx.strokeStyle='rgba(220,225,230,.18)';
  }
  for(let c=0;c<=COLS;c++){ ctx.strokeStyle='rgba(255,255,255,.14)'; ctx.lineWidth=1; ctx.beginPath(); ctx.moveTo(c*CELL,64); ctx.lineTo(c*CELL,448); ctx.stroke(); }

  // platforms top & bottom
  drawPlatform(0,'PLATFORM 1  •  STATION',t);
  drawPlatform(448,'PLATFORM 2  •  STATION',t,true);
}
function drawPlatform(y,label,t,isBottom){
  ctx.fillStyle='#e8d9b0'; ctx.fillRect(0,y,640,64);
  ctx.fillStyle='#d9c7a5';
  for(let x=0;x<640;x+=32) ctx.fillRect(x,y,30,64);
  ctx.strokeStyle='rgba(120,90,40,.25)'; ctx.lineWidth=1;
  for(let x=0;x<=640;x+=32){ctx.beginPath();ctx.moveTo(x,y);ctx.lineTo(x,y+64);ctx.stroke();}
  ctx.fillStyle='#f6c945'; ctx.fillRect(0, isBottom? y+2 : y+54, 640, 6);
  ctx.fillStyle='#7a4a21'; ctx.font='bold 13px system-ui'; ctx.textAlign='center';
  ctx.fillText(label,320, y+(isBottom?48:22));
  // bench
  ctx.fillStyle='#8b5a2b'; ctx.fillRect(70,y+18,54,8); ctx.fillRect(76,y+26,6,12); ctx.fillRect(112,y+26,6,12);
  ctx.fillRect(516,y+18,54,8); ctx.fillRect(522,y+26,6,12); ctx.fillRect(558,y+26,6,12);
  // lamp
  ctx.fillStyle='#333'; ctx.fillRect(318,y+8,4,30); ctx.fillStyle='#fff3a6'; ctx.beginPath(); ctx.arc(320,y+8,7,0,7); ctx.fill();
  // clock
  ctx.fillStyle='#fff'; ctx.beginPath(); ctx.arc(470,y+20,11,0,7); ctx.fill(); ctx.strokeStyle='#333'; ctx.stroke();
  ctx.strokeStyle='#333'; ctx.beginPath(); ctx.moveTo(470,y+20); ctx.lineTo(470,y+13); ctx.moveTo(470,y+20); ctx.lineTo(475,y+22); ctx.stroke();
}
function drawPassenger(p,t){
  const x=p.x*CELL+16, y=p.y*CELL+16+Math.sin(t/280+p.phase)*1.5;
  ctx.fillStyle='rgba(0,0,0,.18)'; ctx.beginmonitor?.beginPath?.(); ctx.beginPath(); ctx.ellipse(x,y+13,10,4,0,0,7); ctx.fill();
  // legs
  ctx.strokeStyle='#26324a'; ctx.lineWidth=4; ctx.lineCap='round';
  ctx.beginPath(); ctx.moveTo(x-3,y+4); ctx.lineTo(x-3,y+13); ctx.moveTo(x+3,y+4); ctx.lineTo(x+3,y+13); ctx.stroke();
  // body
  ctx.fillStyle=p.shirt; roundRect(x-8,y-6,16,12,6); ctx.fill();
  // waving arm
  ctx.strokeStyle=p.skin; ctx.lineWidth=3;
  ctx.beginPath(); ctx.moveTo(x+7,y-3); ctx.lineTo(x+13,y-10+Math.sin(t/200+p.phase)*3); ctx.stroke();
  // head
  ctx.fillStyle=p.skin; ctx.beginPath(); ctx.arc(x,y-11,8,0,7); ctx.fill();
  ctx.fillStyle=p.hair; ctx.beginPath(); ctx.arc(x,y-13,8,Math.PI,0); ctx.fill();
  ctx.fillStyle='#222'; ctx.beginPath(); ctx.arc(x-3,y-11,1.3,0,7); ctx.arc(x+3,y-11,1.3,0,7); ctx.fill();
  ctx.strokeStyle='#222'; ctx.lineWidth=1.2; ctx.beginPath(); ctx.arc(x,y-8,3,0.2,Math.PI-0.2); ctx.stroke();
  // ! bubble
  ctx.fillStyle='#ffd93d'; ctx.beginPath(); ctx.arc(x+13,y-20,7,0,7); ctx.fill(); ctx.strokeStyle='#b8860b'; ctx.stroke();
  ctx.fillStyle='#7a4a00'; ctx.font='bold 11px system-ui'; ctx.textAlign='center'; ctx.fillText('!',x+13,y-16);
}
function roundRect(x,y,w,h,r){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();}
function drawWagon(x,y,ang,color,passenger,isLoco,idx){
  ctx.save(); ctx.translate(x,y); ctx.rotate(ang);
  // link
  if(!isLoco){ctx.fillStyle='#555'; ctx.fillRect(-18,-2,8,4);}
  // wheels
  ctx.fillStyle='#222';
  ctx.beginPath(); ctx.arc(-8,10,5,0,7); ctx.arc(8,10,5,0,7); ctx.fill();
  ctx.fillStyle='#888'; ctx.beginPath(); ctx.arc(-8,10,2,0,7); ctx.arc(8,10,2,0,7); ctx.fill();
  if(isLoco){
    // body
    ctx.fillStyle='#c1272d'; roundRect(-17,-11,34,22,6); ctx.fill();
    ctx.fillStyle='#8f1d22'; roundRect(-4,-15,14,8,3); ctx.fill(); // cabin top
    ctx.fillStyle='#ffd93d'; ctx.fillRect(8,-4,8,8); // front light
    // chimney
    ctx.fillStyle='#333'; ctx.fillRect(-13,-17,8,8); ctx.fillRect(-14,-19,10,4);
    // boiler stripe
    ctx.fillStyle='#ffd93d'; ctx.fillRect(-17,2,34,3);
    // face on front
    ctx.fillStyle='#fff'; ctx.beginPath(); ctx.arc(10,-2,2,0,7); ctx.fill();
    ctx.fillStyle='#222'; ctx.beginPath(); ctx.arc(10.6,-2,1,0,7); ctx.fill();
    // cowcatcher
    ctx.fillStyle='#f59e0b'; ctx.beginPath(); ctx.moveTo(17,-6); ctx.lineTo(24,0); ctx.lineTo(17,6); ctx.closePath(); ctx.fill();
  }else{
    ctx.fillStyle=color; roundRect(-16,-11,32,22,6); ctx.fill();
    ctx.fillStyle='rgba(255,255,255,.85)'; roundRect(-12,-8,24,9,4); ctx.fill(); // window
    ctx.fillStyle='rgba(180,220,255,.9)'; roundRect(-11,-7,22,7,3); ctx.fill();
    if(passenger){
      ctx.fillStyle=passenger.skin; ctx.beginPath(); ctx.arc(0,-3,6,0,7); ctx.fill();
      ctx.fillStyle=passenger.hair; ctx.beginPath(); ctx.arc(0,-5,6,Math.PI,0); ctx.fill();
      ctx.fillStyle='#222'; ctx.beginPath(); ctx.arc(-2,-3,1,0,7); ctx.arc(2,-3,1,0,7); ctx.fill();
      ctx.strokeStyle='#222'; ctx.lineWidth=1; ctx.beginPath(); ctx.arc(0,-1,2.5,0.2,Math.PI-0.2); ctx.stroke();
    }else{
      ctx.fillStyle='rgba(0,0,0,.15)'; ctx.font='11px system-ui'; ctx.textAlign='center'; ctx.fillText('🎒',0,0);
    }
    ctx.fillStyle='rgba(0,0,0,.18)'; ctx.fillRect(-16,6,32,3);
  }
  ctx.restore();
}
function interpPos(i,prog){
  const cur=snake[i];
  const prev=prevSnake[Math.min(i,prevSnake.length-1)]||cur;
  const dx=cur.x-prev.x, dy=cur.y-prev.y;
  if(Math.abs(dx)+Math.abs(dy)!==1) return {x:cur.x*CELL+16,y:cur.y*CELL+16};
  return {x:(prev.x+dx*prog)*CELL+16, y:(prev.y+dy*prog)*CELL+16};
}
function drawTrain(t,prog){
  // tail to head
  for(let i=snake.length-1;i>=0;i--){
    const p=interpPos(i,prog);
    const prevSeg = i===0 ? null : interpPos(i-1,prog);
    let ang=0;
    if(prevSeg){ ang=Math.atan2(prevSeg.y-p.y, prevSeg.x-p.x); }
    else {
      const nxt = i+1<snake.length? interpPos(i+1,prog):p;
      ang=Math.atan2(p.y-nxt.y, p.x-nxt.x);
      if(i===0) ang=Math.atan2(dir.y,dir.x);
    }
    const isLoco=i===0;
    const color=WAGON_COLORS[Math.max(0,i-1)%WAGON_COLORS.length];
    drawWagon(p.x,p.y,ang,color,snake[i].passenger,isLoco,i);
    if(isLoco && t-lastSmoke>90){
      lastSmoke=t;
      smoke.push({x:p.x+Math.cos(ang)* -10, y:p.y-14, r:5, life:1});
    }
  }
}
function loop(t){
  requestAnimationFrame(loop);
  if(!paused && !dead && t-lastStep>=stepMs) step(t);
  const prog=Math.min(1,(t-lastStep)/Math.max(60,stepMs));
  ctx.clearRect(0,0,640,512);
  drawBackground(t);
  // waiting passengers (behind train slightly? drawn before train so train passes in front)
  passengers.forEach(p=>drawPassenger(p,t));
  drawTrain(t,prog);
  // smoke
  smoke=smoke.filter(s=>s.life>0);
  smoke.forEach(s=>{ s.y-=0.6; s.r+=0.25; s.life-=0.02; ctx.fillStyle=`rgba(240,240,240,${s.life*0.7})`; ctx.beginPath(); ctx.arc(s.x,s.y,s.r,0,7); ctx.fill(); });
  // particles
  particles=particles.filter(p=>p.life>0);
  particles.forEach(p=>{p.x+=p.vx;p.y+=p.vy;p.vy+=0.05;p.life-=0.03;ctx.fillStyle=p.c;ctx.globalAlpha=p.life;ctx.beginPath();ctx.arc(p.x,p.y,3,0,7);ctx.fill();ctx.globalAlpha=1;});
  // floats
  floats=floats.filter(f=>f.life>0);
  floats.forEach(f=>{f.life-=0.015; f.y-=0.02; ctx.fillStyle=`rgba(255,255,255,${f.life})`; ctx.font='bold 13px system-ui'; ctx.textAlign='center'; ctx.fillText(f.text,f.x*CELL+16,f.y*CELL);});
  // demo badge
  if(mode==='demo'){
    ctx.fillStyle='rgba(0,0,0,.55)'; roundRect(8,70,118,24,12); ctx.fill();
    ctx.fillStyle='#ffd93d'; ctx.font='bold 12px system-ui'; ctx.textAlign='center'; ctx.fillText('🤖 AUTO DEMO',67,86);
  }
}
function startPlaying(){
  resetGame(); mode='playing'; paused=false; overlay.style.display='none'; lastStep=performance.now();
}
function showDemo(){
  resetGame(); mode='demo';
  ovTitle.textContent='🚂 Demo running!';
  ovText.innerHTML='The little train is playing by itself.<br>Click or press a key to take the controls!';
  playBtn.textContent='▶ Start Playing';
  overlay.style.display='flex';
}
overlay.addEventListener('click',startPlaying);
playBtn.addEventListener('click',e=>{e.stopPropagation();startPlaying();});
document.getElementById('restartBtn').addEventListener('click',()=>{ if(mode==='demo') startPlaying(); else startPlaying(); });
document.getElementById('demoBtn').addEventListener('click',showDemo);
const pauseBtn=document.getElementById('pauseBtn');
function togglePause(){ if(mode!=='playing') return; paused=!paused; pauseBtn.textContent=paused?'▶ Resume (Space)':'⏸ Pause (Space)'; if(!paused) lastStep=performance.now(); }
pauseBtn.addEventListener('click',togglePause);
document.addEventListener('keydown',e=>{
  if(['arrowup','arrowdown','arrowleft','arrowright',' '].includes(e.key.toLowerCase())||e.key===' ') e.preventDefault();
  if(mode!=='playing' && mode!=='demo'){ startPlaying(); return; }
  if(mode==='demo'){ startPlaying(); }
  if(e.key===' '||e.key.toLowerCase()==='p'){ togglePause(); return; }
  keyDir(e);
});
document.querySelectorAll('[data-dir]').forEach(b=>{
  b.addEventListener('click',()=>{
    if(mode==='demo') startPlaying();
    const d=b.dataset.dir;
    if(d==='up')setDir(0,-1); if(d==='down')setDir(0,1); if(d==='left')setDir(-1,0); if(d==='right')setDir(1,0);
  });
});
// swipe
let tx=0,ty=0;
cv.addEventListener('touchstart',e=>{tx=e.touches[0].clientX;ty=e.touches[0].clientY;},{passive:true});
cv.addEventListener('touchend',e=>{
  const dx=e.changedTouches[0].clientX-tx, dy=e.changedTouches[0].clientY-ty;
  if(Math.abs(dx)<18&&Math.abs(dy)<18) return;
  if(mode==='demo') startPlaying();
  if(Math.abs(dx)>Math.abs(dy)) setDir(dx>0?1:-1,0); else setDir(0,dy>0?1:-1);
},{passive:true});
cv.addEventListener('click',()=>{ if(mode==='demo'&&overlay.style.display==='none') startPlaying(); });

resetGame();
requestAnimationFrame(loop);
</script>
</body>
</html>
Reasoning trace (2,865 tokens)
Interpreting train-themed snake game requirements and mapping canvas grid mechanics for locomotive, wagons, and passenger pickup.Refining the grid layout and platform zones for passenger pickup and train rendering.Defining grid size, cell dimensions, and passenger spawn rules on platform rows.Adding train wagon rendering, AI pathfinding, and demo overlay with input handling.Extending rendering with particles, UI controls, AI logic, and passenger state handling.