Train snakeOne-line
Muse Ember
by MetaReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 13.3 s
- Cost
- $0.0039
- Output tokens
- 7,762
- Spent thinking
- 1,351 tokens
- Speed
- 705.5 tok/s
- Console errors
- 0
- First token after
- 2.3 s
- First visible text after
- 4.9 s
- Prompt tokens
- 380
- Code size
- 18.1 KB
- Finish reason
- stop
- Served by
- Meta
- Model id
meta/muse-ember- Reasoning sent
{"effort":"low"}- 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 · 329 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sunny Station — Train Snake</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{
font-family:ui-rounded,system-ui,-apple-system,"Segoe UI",Roboto,Arial,sans-serif;
background:linear-gradient(180deg,#7ec8f7 0%,#aee3ff 38%,#fff3c4 100%);
min-height:100vh;color:#19324d;overflow-x:hidden;
display:flex;flex-direction:column;align-items:center;
}
header{width:min(960px,96vw);padding:14px 6px 6px;display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap}
.title{display:flex;align-items:center;gap:10px}
.logo{width:52px;height:52px;background:#e63946;border-radius:14px;display:grid;place-items:center;font-size:30px;box-shadow:0 6px 0 #b81f2d,0 10px 18px rgba(0,0,0,.18);border:2px solid #fff}
h1{font-size:clamp(22px,4vw,34px);line-height:.95;letter-spacing:-.5px}
h1 span{color:#e63946}
.sub{font-size:13px;font-weight:700;opacity:.75;margin-top:3px}
.hud{display:flex;gap:8px;flex-wrap:wrap}
.pill{background:#fff;border:2px solid #19324d;border-radius:999px;padding:7px 12px;font-weight:900;box-shadow:0 3px 0 #19324d;font-size:14px;white-space:nowrap}
.pill b{color:#e63946}
.pill.green b{color:#1a9e4b}
main{width:min(960px,96vw);display:flex;flex-direction:column;align-items:center;gap:10px;padding-bottom:16px}
.frame{
width:100%;background:#fff;border:3px solid #19324d;border-radius:20px;padding:8px;
box-shadow:0 10px 0 #19324d,0 22px 36px rgba(0,0,0,.22);position:relative;overflow:hidden
}
canvas{display:block;width:100%;height:auto;border-radius:12px;background:#79c94f;touch-action:none}
.banner{
position:absolute;left:50%;top:16px;transform:translateX(-50%);z-index:5;
background:rgba(255,255,255,.96);border:2px solid #19324d;border-radius:999px;
padding:8px 14px;font-weight:900;font-size:13px;box-shadow:0 4px 0 #19324d;
display:flex;gap:8px;align-items:center;max-width:92%;text-align:center;transition:.25s;
}
.banner.hide{opacity:0;pointer-events:none;transform:translateX(-50%) translateY(-10px)}
.dot{width:9px;height:9px;background:#1a9e4b;border-radius:50%;box-shadow:0 0 0 4px #d9f7e4;animation:pulse 1s infinite}
@keyframes pulse{50%{transform:scale(1.4)}}
.overlay{
position:absolute;inset:8px;border-radius:12px;background:rgba(20,40,60,.72);backdrop-filter:blur(3px);
display:none;place-items:center;z-index:6;text-align:center;color:#fff;padding:20px
}
.overlay.show{display:grid}
.card{background:#fff;color:#19324d;border:3px solid #19324d;border-radius:18px;padding:20px;box-shadow:0 8px 0 #19324d;max-width:340px}
.card h2{font-size:28px;margin-bottom:6px}
.card p{font-weight:700;opacity:.8;margin-bottom:12px}
button{font-family:inherit;cursor:pointer;border:2px solid #19324d;font-weight:900;border-radius:999px;box-shadow:0 3px 0 #19324d;transition:.12s}
button:active{transform:translateY(3px);box-shadow:0 0 0 #19324d}
.btn{background:#ffd23f;padding:9px 16px;font-size:14px}
.btn.red{background:#e63946;color:#fff}
.btn.white{background:#fff}
.controls{width:100%;display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap}
.hint{background:rgba(255,255,255,.9);border:2px solid #19324d;border-radius:12px;padding:8px 11px;font-weight:800;font-size:13px}
.hint kbd{background:#19324d;color:#fff;padding:2px 7px;border-radius:6px;font-family:inherit}
.dpad{display:none;grid-template-columns:repeat(3,48px);grid-template-rows:repeat(3,42px);gap:5px}
.dpad button{background:#fff;font-size:18px;border-radius:12px;padding:0}
.dpad .up{grid-column:2;grid-row:1}.dpad .left{grid-column:1;grid-row:2}.dpad .down{grid-column:2;grid-row:2}.dpad .right{grid-column:3;grid-row:2}
.actions{display:flex;gap:8px;flex-wrap:wrap}
@media (max-width:600px){.dpad{display:grid}.hint{display:none}header{justify-content:center}.hud{justify-content:center}}
</style>
</head>
<body>
<header>
<div class="title">
<div class="logo">🚂</div>
<div><h1>SUNNY <span>STATION</span></h1><div class="sub">Pick up passengers • grow your train!</div></div>
</div>
<div class="hud">
<div class="pill">🧑 Passengers <b id="score">0</b></div>
<div class="pill green">🚃 Wagons <b id="len">3</b></div>
<div class="pill">⭐ Bonus <b id="bonus">0</b></div>
<div class="pill">🏆 Best <b id="best">0</b></div>
</div>
</header>
<main>
<div class="frame" id="frame">
<div class="banner" id="banner"><span class="dot"></span><span id="bannerText">🤖 Demo train is playing — click / press a key to drive!</span></div>
<canvas id="game" width="800" height="560"></canvas>
<div class="overlay" id="overlay">
<div class="card">
<div style="font-size:44px">💥🚂</div>
<h2>Derailment!</h2>
<p id="overText">You picked up 0 passengers.</p>
<button class="btn red" id="againBtn">↻ Play again</button>
</div>
</div>
</div>
<div class="controls">
<div class="hint"><kbd>←</kbd><kbd>↑</kbd><kbd>↓</kbd><kbd>→</kbd> or <kbd>WASD</kbd> to steer • <kbd>Space</kbd> pause • Avoid walls & your own wagons!</div>
<div class="actions">
<button class="btn white" id="pauseBtn">⏸ Pause</button>
<button class="btn" id="demoBtn">🤖 Auto-drive: ON</button>
<button class="btn white" id="resetBtn">↻ Restart</button>
</div>
<div class="dpad">
<button class="up" data-d="up">▲</button><button class="left" data-d="left">◀</button><button class="down" data-d="down">▼</button><button class="right" data-d="right">▶</button>
</div>
</div>
</main>
<script>
const COLS=20, ROWS=14, CELL=40, W=COLS*CELL, H=ROWS*CELL;
const cvs=document.getElementById('game'), ctx=cvs.getContext('2d');
const scoreEl=document.getElementById('score'), lenEl=document.getElementById('len'), bonusEl=document.getElementById('bonus'), bestEl=document.getElementById('best');
const banner=document.getElementById('banner'), bannerText=document.getElementById('bannerText'), overlay=document.getElementById('overlay'), overText=document.getElementById('overText');
const demoBtn=document.getElementById('demoBtn');
const WAGON_COLORS=['#e63946','#2a9d8f','#f4a261','#4361ee','#9b5de5','#06d6a0','#ff8fa3','#ffd23f','#4cc9f0','#f3722c'];
const SHIRTS=['#e63946','#4361ee','#ffd23f','#2a9d8f','#9b5de5','#ff8fa3','#06d6a0','#f3722c'];
let snake, prevSnake, dir, dirQueue, passenger, bonusItem, score, bonusScore, best=0, speed, lastStep, paused=false, manual=false, gameOver=false, smoke=[], trees=[], flowers=[];
let passengerAnim=0;
function randCell(){ return {x:1+Math.floor(Math.random()*(COLS-2)), y:1+Math.floor(Math.random()*(ROWS-2))}; }
function occupied(p){ return snake.some(s=>s.x===p.x&&s.y===p.y) || (passenger&&passenger.x===p.x&&passenger.y===p.y) || (bonusItem&&bonusItem.x===p.x&&bonusItem.y===p.y); }
function spawnPassenger(){ let p; do{p=randCell()}while(occupied(p)); passenger=p; }
function spawnBonus(){ if(bonusItem) return; let p; do{p=randCell()}while(occupied(p)); bonusItem={...p, t:Date.now()}; }
function reset(){
snake=[{x:9,y:7},{x:8,y:7},{x:7,y:7}];
prevSnake=snake.map(s=>({...s}));
dir={x:1,y:0}; dirQueue=[]; score=0; bonusScore=0; speed=170; lastStep=performance.now(); paused=false; gameOver=false; bonusItem=null; smoke=[];
overlay.classList.remove('show'); spawnPassenger();
if(Math.random()<0.6) spawnBonus();
updateHUD();
}
function updateHUD(){ scoreEl.textContent=score; lenEl.textContent=snake?snake.length:3; bonusEl.textContent=bonusScore; bestEl.textContent=best; }
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 takeControl(){
if(!manual){ manual=true; bannerText.textContent='🎮 You are driving! Use arrows / WASD — good luck, conductor!'; setTimeout(()=>banner.classList.add('hide'),1400); demoBtn.textContent='🤖 Auto-drive: OFF'; }
}
function step(){
if(gameOver||paused) return;
prevSnake=snake.map(s=>({...s}));
if(dirQueue.length) dir=dirQueue.shift();
// AI
if(!manual){
const nd=aiMove();
if(nd) dir=nd;
}
const head={x:snake[0].x+dir.x, y:snake[0].y+dir.y};
if(head.x<0||head.x>=COLS||head.y<0||head.y>=ROWS||snake.some(s=>s.x===head.x&&s.y===head.y)){
gameOver=true; best=Math.max(best,score); updateHUD();
if(manual){ overText.textContent=`You picked up ${score} passengers and ${bonusScore} bonus stars.`; overlay.classList.add('show'); }
else { setTimeout(reset,900); }
return;
}
snake.unshift(head);
let ate=false;
if(passenger && head.x===passenger.x && head.y===passenger.y){ ate=true; score++; speed=Math.max(85,speed-3); spawnPassenger(); burst(head.x,head.y,'#ffd23f'); }
if(bonusItem && head.x===bonusItem.x && head.y===bonusItem.y){ ate=true; bonusScore++; score+=2; bonusItem=null; burst(head.x,head.y,'#ffe066'); setTimeout(spawnBonus,4000); }
if(!ate) snake.pop();
else prevSnake.push({...snake[snake.length-1]}); // grow smoothly
updateHUD();
}
function aiMove(){
// BFS to passenger
const target=passenger; if(!target) return null;
const key=(x,y)=>x+','+y;
const blocked=new Set(snake.slice(0,-1).map(s=>key(s.x,s.y)));
const q=[{...snake[0], first:null}]; const seen=new Set([key(snake[0].x,snake[0].y)]);
const dirs=[{x:1,y:0},{x:-1,y:0},{x:0,y:1},{x:0,y:-1}];
while(q.length){
const cur=q.shift();
if(cur.x===target.x&&cur.y===target.y) return cur.first;
for(const d of dirs){
const nx=cur.x+d.x, ny=cur.y+d.y;
if(nx<0||ny<0||nx>=COLS||ny>=ROWS) continue;
if(blocked.has(key(nx,ny))||seen.has(key(nx,ny))) continue;
// avoid reversing into neck unless it's first step logic already blocked
if(cur===snake[0] && snake[1] && nx===snake[1].x && ny===snake[1].y) continue;
seen.add(key(nx,ny));
q.push({x:nx,y:ny,first:cur.first||d});
}
}
// survival: any safe move
const safe=dirs.filter(d=>{
const nx=snake[0].x+d.x, ny=snake[0].y+d.y;
if(nx<0||ny<0||nx>=COLS||ny>=ROWS) return false;
if(snake.slice(0,-1).some(s=>s.x===nx&&s.y===ny)) return false;
if(d.x===-dir.x&&d.y===-dir.y) return false;
return true;
});
return safe.length?safe[0]:dir;
}
function burst(cx,cy,color){
for(let i=0;i<10;i++) smoke.push({x:cx*CELL+20,y:cy*CELL+20,vx:(Math.random()-.5)*3,vy:-Math.random()*3-1,life:30,color,r:3+Math.random()*4});
}
// scenery generation
trees=[]; for(let i=0;i<14;i++) trees.push({x:Math.random()*W,y:Math.random()*H,s:.7+Math.random()*.6});
flowers=[]; for(let i=0;i<36;i++) flowers.push({x:Math.random()*W,y:Math.random()*H,c:['#fff','#ffd23f','#ff8fa3','#c77dff'][i%4]});
function draw(now){
passengerAnim=now/250;
const t=Math.max(0,Math.min(1,(now-lastStep)/speed));
// grass
for(let y=0;y<ROWS;y++) for(let x=0;x<COLS;x++){ ctx.fillStyle=(x+y)%2? '#7cc94f':'#72c147'; ctx.fillRect(x*CELL,y*CELL,CELL,CELL); }
// tracks - two horizontal lines
ctx.strokeStyle='rgba(90,60,30,.35)'; ctx.lineWidth=3;
[3.5,10.5].forEach(r=>{ ctx.beginPath(); ctx.moveTo(0,r*CELL); ctx.lineTo(W,r*CELL); ctx.stroke(); });
ctx.strokeStyle='#5b4636'; ctx.lineWidth=2;
[3.5,10.5].forEach(r=>{ ctx.beginPath(); ctx.moveTo(0,r*CELL-5); ctx.lineTo(W,r*CELL-5); ctx.moveTo(0,r*CELL+5); ctx.lineTo(W,r*CELL+5); ctx.stroke(); });
ctx.lineWidth=2; ctx.strokeStyle='rgba(90,60,30,.5)';
for(let x=10;x<W;x+=28){ ctx.beginPath(); ctx.moveTo(x,3.5*CELL-6); ctx.lineTo(x,3.5*CELL+6); ctx.moveTo(x,10.5*CELL-6); ctx.lineTo(x,10.5*CELL+6); ctx.stroke(); }
// flowers
flowers.forEach(f=>{ ctx.fillStyle=f.c; ctx.beginPath(); ctx.arc(f.x,f.y,3,0,7); ctx.fill(); });
// trees (behind train, clipped at edges ok)
trees.forEach(tr=>{
ctx.fillStyle='#8b5a2b'; ctx.fillRect(tr.x-3,tr.y+6*tr.s,6,12*tr.s);
ctx.fillStyle='#2f9e44'; ctx.beginPath(); ctx.arc(tr.x,tr.y,13*tr.s,0,7); ctx.arc(tr.x-9*tr.s,tr.y+6*tr.s,9*tr.s,0,7); ctx.arc(tr.x+9*tr.s,tr.y+6*tr.s,9*tr.s,0,7); ctx.fill();
});
// border fence / station edge
ctx.strokeStyle='#19324d'; ctx.lineWidth=6; ctx.strokeRect(3,3,W-6,H-6);
// passenger platform
if(passenger){
const px=passenger.x*CELL, py=passenger.y*CELL;
ctx.fillStyle='#f2d492'; roundRect(px+2,py+16,CELL-4,22,6); ctx.fill();
ctx.strokeStyle='#b8893a'; ctx.lineWidth=2; roundRect(px+2,py+16,CELL-4,22,6); ctx.stroke();
// sign
ctx.fillStyle='#fff'; ctx.fillRect(px+13,py+3,14,13); ctx.strokeStyle='#19324d'; ctx.lineWidth=1.5; ctx.strokeRect(px+13,py+3,14,13);
ctx.fillStyle='#e63946'; ctx.font='bold 10px sans-serif'; ctx.textAlign='center'; ctx.fillText('PLATFORM',px+20,py+12);
// person
const bob=Math.sin(passengerAnim)*2, cx=px+20, cy=py+18+bob;
ctx.fillStyle='#ffdbac'; ctx.beginPath(); ctx.arc(cx,cy-6,5,0,7); ctx.fill();
ctx.fillStyle=SHIRTS[score%SHIRTS.length]; roundRect(cx-5,cy-1,10,11,3); ctx.fill();
ctx.strokeStyle='#ffdbac'; ctx.lineWidth=3; ctx.beginPath(); ctx.moveTo(cx+5,cy+2); ctx.lineTo(cx+9,cy-3+Math.sin(passengerAnim*2)*2); ctx.stroke();
ctx.fillStyle='#19324d'; ctx.fillRect(cx-4,cy+10,3,6); ctx.fillRect(cx+1,cy+10,3,6);
// speech bubble
ctx.fillStyle='#fff'; ctx.beginPath(); ctx.arc(cx+11,cy-14,7,0,7); ctx.fill(); ctx.fillStyle='#e63946'; ctx.font='bold 11px sans-serif'; ctx.fillText('!',cx+11,cy-10);
}
// bonus star
if(bonusItem){
const cx=bonusItem.x*CELL+20, cy=bonusItem.y*CELL+20+Math.sin(now/200)*3;
ctx.fillStyle='#ffd23f'; ctx.strokeStyle='#e6a800'; ctx.lineWidth=2;
ctx.beginPath();
for(let i=0;i<10;i++){ const r=i%2?6:13, a=-Math.PI/2+i*Math.PI/5; ctx[i?'lineTo':'moveTo'](cx+Math.cos(a)*r,cy+Math.sin(a)*r); }
ctx.closePath(); ctx.fill(); ctx.stroke();
}
// smoke particles
smoke=smoke.filter(p=>p.life>0);
smoke.forEach(p=>{ p.x+=p.vx; p.y+=p.vy; p.vy-=.02; p.life--; ctx.fillStyle=p.color; ctx.globalAlpha=Math.max(0,p.life/30)*.8; ctx.beginPath(); ctx.arc(p.x,p.y,p.r,0,7); ctx.fill(); ctx.globalAlpha=1; });
// train - draw wagons first (tail to head)
const interp=snake.map((s,i)=>{
const pr=prevSnake[Math.min(i,prevSnake.length-1)]||s;
return {x:(pr.x+(s.x-pr.x)*t)*CELL, y:(pr.y+(s.y-pr.y)*t)*CELL};
});
for(let i=interp.length-1;i>=1;i--){
const p=interp[i], col=WAGON_COLORS[(i-1)%WAGON_COLORS.length];
ctx.fillStyle='rgba(0,0,0,.18)'; roundRect(p.x+4,p.y+24,32,9,4); ctx.fill();
ctx.fillStyle=col; roundRect(p.x+3,p.y+9,34,22,5); ctx.fill();
ctx.strokeStyle='#19324d'; ctx.lineWidth=2; roundRect(p.x+3,p.y+9,34,22,5); ctx.stroke();
ctx.fillStyle='#fff'; ctx.fillRect(p.x+7,p.y+13,7,8); ctx.fillRect(p.x+17,p.y+13,7,8); ctx.fillRect(p.x+27,p.y+13,7,8);
ctx.fillStyle='#19324d'; ctx.beginPath(); ctx.arc(p.x+10,p.y+33,4,0,7); ctx.arc(p.x+30,p.y+33,4,0,7); ctx.fill();
// tiny passenger face in some wagons
if(i%3===0){ ctx.fillStyle='#ffdbac'; ctx.beginPath(); ctx.arc(p.x+20,p.y+17,3,0,7); ctx.fill(); }
}
// locomotive
const h=interp[0];
const horiz=dir.x!==0;
ctx.fillStyle='rgba(0,0,0,.2)'; roundRect(h.x+2,h.y+26,36,9,4); ctx.fill();
ctx.fillStyle='#c1121f'; roundRect(h.x+2,h.y+8,36,24,6); ctx.fill();
ctx.strokeStyle='#19324d'; ctx.lineWidth=2; roundRect(h.x+2,h.y+8,36,24,6); ctx.stroke();
ctx.fillStyle='#ffd23f'; ctx.fillRect(h.x+5,h.y+11,7,7);
ctx.fillStyle='#19324d'; ctx.fillRect(h.x+26,h.y+10,8,12); // cabin
ctx.fillStyle='#a8dadc'; ctx.fillRect(h.x+28,h.y+12,4,5);
ctx.fillStyle='#555'; ctx.fillRect(h.x+14,h.y+2,7,8); ctx.fillStyle='#333'; ctx.fillRect(h.x+12,h.y,11,4); // chimney
if(Math.random()<.18) smoke.push({x:h.x+17,y:h.y+2,vx:.3,vy:-1.2,life:36,color:'#eaeaea',r:4+Math.random()*3});
ctx.fillStyle='#19324d'; ctx.beginPath(); ctx.arc(h.x+10,h.y+34,5,0,7); ctx.arc(h.x+20,h.y+34,5,0,7); ctx.arc(h.x+30,h.y+34,5,0,7); ctx.fill();
ctx.fillStyle='#fff'; ctx.beginPath(); ctx.arc(h.x+10,h.y+34,2,0,7); ctx.arc(h.x+20,h.y+34,2,0,7); ctx.arc(h.x+30,h.y+34,2,0,7); ctx.fill();
// face on loco
ctx.fillStyle='#ffdbac'; ctx.beginPath(); ctx.arc(h.x+13,h.y+18,4.5,0,7); ctx.fill();
ctx.fillStyle='#19324d'; ctx.beginPath(); ctx.arc(h.x+11.5,h.y+17,1,0,7); ctx.arc(h.x+14.5,h.y+17,1,0,7); ctx.fill();
// vignette text
ctx.fillStyle='rgba(255,255,255,.92)'; roundRect(10,10,196,30,8); ctx.fill(); ctx.strokeStyle='#19324d'; ctx.lineWidth=2; roundRect(10,10,196,30,8); ctx.stroke();
ctx.fillStyle='#19324d'; ctx.font='900 13px sans-serif'; ctx.textAlign='left'; ctx.fillText(manual?'🎮 MANUAL DRIVING':'🤖 AUTO DEMO • SUNNY STATION',18,30);
requestAnimationFrame(draw);
}
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(); }
setInterval(()=>{ if(!paused && !gameOver && performance.now()-lastStep>=speed){ step(); lastStep=performance.now(); } },30);
const DIRS={ArrowUp:[0,-1],KeyW:[0,-1],ArrowDown:[0,1],KeyS:[0,1],ArrowLeft:[-1,0],KeyA:[-1,0],ArrowRight:[1,0],KeyD:[1,0]};
window.addEventListener('keydown',e=>{
if(e.code==='Space'){ e.preventDefault(); togglePause(); return; }
if(DIRS[e.code]){ e.preventDefault(); takeControl(); setDir(...DIRS[e.code]); }
});
cvs.addEventListener('pointerdown',()=>{ takeControl(); });
document.getElementById('frame').addEventListener('pointerdown',()=>takeControl());
document.querySelectorAll('.dpad button').forEach(b=>b.addEventListener('click',()=>{ takeControl(); const d=b.dataset.d; const map={up:[0,-1],down:[0,1],left:[-1,0],right:[1,0]}; setDir(...map[d]); }));
function togglePause(){ if(gameOver) return; paused=!paused; lastStep=performance.now(); document.getElementById('pauseBtn').textContent=paused?'▶ Resume':'⏸ Pause'; }
document.getElementById('pauseBtn').addEventListener('click',togglePause);
document.getElementById('resetBtn').addEventListener('click',reset);
document.getElementById('againBtn').addEventListener('click',reset);
demoBtn.addEventListener('click',()=>{
manual=!manual;
demoBtn.textContent=manual?'🤖 Auto-drive: OFF':'🤖 Auto-drive: ON';
bannerText.textContent=manual?'🎮 You are driving!':'🤖 Demo train is playing — click / press a key to drive!';
banner.classList.toggle('hide',manual);
if(!manual) banner.classList.remove('hide');
});
reset();
requestAnimationFrame(draw);
</script>
</body>
</html>Reasoning trace (1,351 tokens)
Designing a single-file canvas-based snake game with a train and wagons theme, 1280x800 responsive layout, and self-playing AI demo mode.Adding passenger pickup visuals, track and platform layout, and BFS-based AI with manual control fallback.