Train snakeOne-line
MiniMax M3.1 Flash
by MiniMaxReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 55.5 s
- Cost
- $0.0066
- Output tokens
- 10,946
- Spent thinking
- 3,306 tokens
- Speed
- 200.9 tok/s
- Console errors
- 0
- First token after
- 999 ms
- First visible text after
- 31.0 s
- Prompt tokens
- 532
- Code size
- 21.7 KB
- Finish reason
- stop
- Served by
- DigitalOcean
- Model id
minimax/minimax-m3.1-flash- Reasoning sent
{"effort":"medium"}- Max output
- 64,000 tokens
- Generated
- Nov 11, 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 · 566 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Platform Express — Snake on the Rails</title>
<style>
*{box-sizing:border-box}
html,body{margin:0;height:100%;overflow-x:hidden}
body{
font-family:"Trebuchet MS","Segoe UI",system-ui,sans-serif;
color:#f7fbff;
background:
radial-gradient(circle at 12% 0%,rgba(68,183,255,.22),transparent 38%),
radial-gradient(circle at 92% 12%,rgba(255,190,92,.16),transparent 34%),
linear-gradient(160deg,#0b1730 0%,#101d38 55%,#091427 100%);
min-height:100vh;
display:flex;justify-content:center;align-items:center;padding:16px;
}
.app{width:min(1120px,100%)}
.topbar{display:flex;align-items:center;gap:18px;margin-bottom:13px;flex-wrap:wrap}
.brand{display:flex;align-items:center;gap:13px;flex:1;min-width:230px}
.badge{
width:54px;height:54px;border-radius:17px;display:grid;place-items:center;
background:linear-gradient(145deg,#55d6ff,#2f7cf6);box-shadow:0 8px 22px rgba(30,120,255,.35),inset 0 2px 0 rgba(255,255,255,.4);
font-size:29px;transform:rotate(-4deg)
}
h1{margin:0;font-size:clamp(23px,3.1vw,35px);letter-spacing:1.5px;line-height:1;text-shadow:0 3px 0 rgba(0,0,0,.25)}
.sub{margin:6px 0 0;font-size:13px;color:#9fb4d4;letter-spacing:.7px}
.stats{display:flex;gap:9px;flex-wrap:wrap}
.stat{
background:rgba(255,255,255,.075);border:1px solid rgba(255,255,255,.12);
border-radius:14px;padding:8px 13px;min-width:82px;box-shadow:inset 0 1px 0 rgba(255,255,255,.08)
}
.stat small{display:block;font-size:9px;letter-spacing:1.6px;color:#9fb4d4;text-transform:uppercase}
.stat b{font-size:20px;color:#fff;line-height:1.15}
.stat.gold b{color:#ffd166}.stat.green b{color:#7ee6a7}.stat.blue b{color:#75d5ff}
.stage{
position:relative;border-radius:23px;padding:8px;
background:linear-gradient(145deg,rgba(255,255,255,.14),rgba(255,255,255,.035));
border:1px solid rgba(255,255,255,.16);box-shadow:0 24px 60px rgba(0,0,0,.42);
}
.board{position:relative;overflow:hidden;border-radius:17px;background:#5f8d55;line-height:0}
canvas{width:100%;height:auto;display:block}
.veil{
position:absolute;inset:0;display:grid;place-items:center;line-height:1.45;
background:radial-gradient(circle at 50% 45%,rgba(5,12,26,.36),rgba(5,12,26,.7));
backdrop-filter:blur(2px);transition:opacity .25s;z-index:4
}
.veil.hidden{opacity:0;pointer-events:none}
.card{
width:min(440px,90%);text-align:center;padding:26px 28px;border-radius:22px;
background:linear-gradient(165deg,rgba(21,39,72,.97),rgba(10,21,43,.97));
border:1px solid rgba(255,255,255,.18);box-shadow:0 18px 50px rgba(0,0,0,.45);
}
.card .train-icon{font-size:42px;line-height:1;margin-bottom:8px;filter:drop-shadow(0 5px 6px rgba(0,0,0,.35))}
.card h2{margin:4px 0 7px;font-size:clamp(21px,3vw,29px);color:#fff}
.card p{margin:0 0 17px;color:#b8c9e3;font-size:14px}
.actions{display:flex;gap:9px;justify-content:center;flex-wrap:wrap}
button{
border:0;border-radius:12px;padding:11px 17px;font:700 13px "Trebuchet MS",sans-serif;
cursor:pointer;letter-spacing:.3px;transition:transform .12s,filter .12s;color:#0c1a33;
background:linear-gradient(#ffe08a,#ffc247);box-shadow:0 5px 0 #b97917
}
button:hover{transform:translateY(-2px);filter:brightness(1.05)}
button:active{transform:translateY(2px);box-shadow:0 2px 0 #b97917}
button.secondary{background:linear-gradient(#d9e5f7,#a9bdd8);box-shadow:0 5px 0 #7188a8;color:#16243d}
.tip{margin-top:15px;font-size:11px;color:#88a0c2;letter-spacing:.4px}
.footer{display:flex;justify-content:space-between;gap:12px;align-items:center;margin-top:12px;flex-wrap:wrap}
.keys{color:#9fb4d4;font-size:12px}
kbd{background:#22314e;border:1px solid #3c5074;border-bottom-width:3px;border-radius:5px;padding:2px 6px;color:#eaf2ff;font:700 11px "Trebuchet MS"}
.mode{font-size:11px;font-weight:700;letter-spacing:1.4px;padding:6px 11px;border-radius:20px;background:rgba(255,209,102,.14);color:#ffd166;border:1px solid rgba(255,209,102,.3)}
.mode.player{background:rgba(126,230,167,.13);color:#7ee6a7;border-color:rgba(126,230,167,.3)}
@media(max-width:700px){
body{padding:9px;align-items:flex-start}
.topbar{gap:10px}.brand{min-width:100%}.stats{width:100%}
.stat{flex:1;min-width:0;padding:7px 9px}.stat b{font-size:17px}
.card{padding:18px 16px}.card .train-icon{font-size:32px}
.footer{justify-content:center;text-align:center}
}
</style>
</head>
<body>
<div class="app">
<header class="topbar">
<div class="brand">
<div class="badge">🚂</div>
<div>
<h1>PLATFORM EXPRESS</h1>
<p class="sub">A little train. A long line of happy passengers.</p>
</div>
</div>
<div class="stats">
<div class="stat gold"><small>Score</small><b id="score">0</b></div>
<div class="stat green"><small>Boarded</small><b id="boarded">0</b></div>
<div class="stat blue"><small>Wagons</small><b id="wagons">3</b></div>
<div class="stat"><small>Best</small><b id="best">0</b></div>
</div>
</header>
<main class="stage">
<div class="board">
<canvas id="game" width="960" height="560" aria-label="Platform Express train game"></canvas>
<div class="veil" id="veil">
<div class="card">
<div class="train-icon">🚂</div>
<h2 id="ovTitle">Click or press a key to start</h2>
<p id="ovText">The conductor is driving a demo run. Pick up every passenger waiting on the platform and grow your train!</p>
<div class="actions">
<button id="takeOver">Take the controls</button>
<button class="secondary" id="keepDemo">Keep watching</button>
</div>
<div class="tip">Arrow keys or WASD to steer • No sharp turns!</div>
</div>
</div>
</div>
</main>
<footer class="footer">
<div class="keys"><kbd>↑</kbd><kbd>↓</kbd><kbd>←</kbd><kbd>→</kbd> or <kbd>W</kbd><kbd>A</kbd><kbd>S</kbd><kbd>D</kbd> to drive · Ride over platform passengers to collect them</div>
<div class="mode" id="mode">● DEMO RIDING</div>
</footer>
</div>
<script>
(() => {
"use strict";
const COLS = 24, ROWS = 14, CELL = 40, W = COLS*CELL, H = ROWS*CELL;
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const DPR = Math.min(window.devicePixelRatio || 1, 2);
canvas.width = W*DPR; canvas.height = H*DPR;
const scoreEl = document.getElementById('score');
const boardedEl = document.getElementById('boarded');
const wagonsEl = document.getElementById('wagons');
const bestEl = document.getElementById('best');
const modeEl = document.getElementById('mode');
const veil = document.getElementById('veil');
const ovTitle = document.getElementById('ovTitle');
const ovText = document.getElementById('ovText');
const DIRS = {
up:{x:0,y:-1}, down:{x:0,y:1}, left:{x:-1,y:0}, right:{x:1,y:0}
};
const COLORS = ['#ef476f','#ffd166','#06d6a0','#4cc9f0','#b388ff','#ff8c42','#90be6d','#f4978e'];
const SKIN = ['#f4c09f','#8d5524','#c68642','#ffd9b3','#6f4228'];
const CLOTH = ['#e63946','#457b9d','#6a994e','#f4a261','#9b5de5','#00b4d8'];
let body, dir, nextDir, passengers, particles, score, boarded, best, gameOver, started, aiMode, timer, tick;
function reset(full){
body = [
{x:12,y:7},{x:11,y:7},{x:10,y:7},{x:9,y:7},{x:8,y:7}
];
dir = {x:1,y:0}; nextDir = {x:1,y:0};
if(full){ score=0; boarded=2; best=0; }
particles = []; gameOver = false; tick = 0;
passengers = [];
spawnPassengers(7);
updateHUD();
}
function occupied(x,y,exceptTail){
for(let i=0;i<body.length;i++){
if(exceptTail && i===body.length-1) continue;
if(body[i].x===x && body[i].y===y) return true;
}
return false;
}
function spawnPassengers(n){
let tries=0;
while(passengers.length<n && tries<500){
tries++;
const x = 1 + Math.floor(Math.random()*(COLS-2));
const y = 1 + Math.floor(Math.random()*(ROWS-2));
if(occupied(x,y,true)) continue;
if(passengers.some(p=>p.x===x&&p.y===y)) continue;
if(body.some((b,i)=>i<3 && Math.abs(b.x-x)<2 && Math.abs(b.y-y)<2)) continue;
passengers.push({
x,y,
skin:SKIN[Math.floor(Math.random()*SKIN.length)],
cloth:CLOTH[Math.floor(Math.random()*CLOTH.length)],
name:'P'+(Math.floor(Math.random()*90)+10),
phase:Math.random()*6
});
}
}
function bfs(start, targets, blocked){
const key=(x,y)=>x+','+y;
const q=[start], prev={}, seen=new Set([key(start.x,start.y)]);
const targetKeys=new Set(targets.map(t=>key(t.x,t.y)));
let found=null;
for(let i=0;i<q.length;i++){
const cur=q[i];
if(targetKeys.has(key(cur.x,cur.y))){found=cur;break;}
for(const d of Object.values(DIRS)){
const nx=cur.x+d.x, ny=cur.y+d.y;
const k=key(nx,ny);
if(nx<1||ny<1||nx>=COLS-1||ny>=ROWS-1||seen.has(k)||blocked.has(k)) continue;
seen.add(k); prev[k]=cur; q.push({x:nx,y:ny});
}
}
if(!found) return null;
const path=[found];
let cur=found;
while(cur.x!==start.x||cur.y!==start.y){
cur=prev[key(cur.x,cur.y)];
path.push(cur);
}
path.reverse();
return path;
}
function blockedSet(skipTail){
const s=new Set();
body.forEach((b,i)=>{
if(skipTail && i===body.length-1) return;
s.add(b.x+','+b.y);
});
return s;
}
function spaceFrom(x,y){
const s=blockedSet(true); s.delete(x+','+y);
const q=[{x,y}], seen=new Set([x+','+y]);
for(let i=0;i<q.length;i++){
const c=q[i];
for(const d of Object.values(DIRS)){
const nx=c.x+d.x, ny=c.y+d.y, k=nx+','+ny;
if(nx<1||ny<1||nx>=COLS-1||ny>=ROWS-1||seen.has(k)||s.has(k)) continue;
seen.add(k); q.push({x:nx,y:ny});
}
}
return seen.size;
}
function aiDirection(){
const blocked=blockedSet(true);
const path=bfs(body[0],passengers,blocked);
const options=[];
if(path && path.length>1){
const n=path[1];
for(const [name,d] of Object.entries(DIRS)){
if(body[0].x+d.x===n.x && body[0].y+d.y===n.y) options.push({name,d});
}
}
if(!options.length){
for(const [name,d] of Object.entries(DIRS)){
const nx=body[0].x+d.x, ny=body[0].y+d.y;
if(nx<1||ny<1||nx>=COLS-1||ny>=ROWS-1) continue;
if(occupied(nx,ny,true)) continue;
options.push({name,d});
}
}
if(!options.length) return dir;
options.sort((a,b)=>{
const sa=spaceFrom(body[0].x+a.d.x,body[0].y+a.d.y);
const sb=spaceFrom(body[0].x+b.d.x,body[0].y+b.d.y);
return sb-sa;
});
// Prefer a productive path when it is safe.
if(path && path.length>1){
for(const o of options){
if(spaceFrom(body[0].x+o.d.x,body[0].y+o.d.y)>7) return o.d;
}
}
return options[0].d;
}
function angleFor(a,b){ return Math.atan2(b.y-a.y,b.x-a.x); }
function move(){
if(gameOver) return;
if(aiMode) dir=aiDirection();
else dir=nextDir;
const head=body[0];
const nx=head.x+dir.x, ny=head.y+dir.y;
const hitWall=nx<1||ny<1||nx>=COLS-1||ny>=ROWS-1;
const pIndex=passengers.findIndex(p=>p.x===nx&&p.y===ny);
const grow=pIndex>=0;
const hitSelf=occupied(nx,ny,!grow);
if(hitWall||hitSelf){ endGame(); return; }
body.unshift({x:nx,y:ny});
if(grow){
const p=passengers.splice(pIndex,1)[0];
boarded++;
score+=100+Math.min(boarded,10)*10;
particles.push({x:nx,y:ny,txt:'+'+ (100+Math.min(boarded,10)*10),life:1,color:'#fff2a8'});
spawnPassengers(7);
}else{
body.pop();
}
tick++;
updateHUD();
}
function endGame(){
gameOver=true;
if(score>best) best=score;
updateHUD();
ovTitle.textContent='End of the line!';
ovText.textContent='You collected '+boarded+' passengers and scored '+score+' points. Ready for another run?';
document.querySelector('#takeOver').textContent='Run it back';
document.querySelector('#keepDemo').textContent='Watch a demo';
veil.classList.remove('hidden');
}
function updateHUD(){
scoreEl.textContent=score;
boardedEl.textContent=boarded;
wagonsEl.textContent=body.length-1;
bestEl.textContent=Math.max(best,score);
}
function setMode(ai){
aiMode=ai;
modeEl.textContent=ai?'● DEMO RIDING':'● YOU’RE DRIVING';
modeEl.classList.toggle('player',!ai);
}
function startControl(){
if(gameOver){ reset(true); }
started=true; setMode(false); veil.classList.add('hidden');
}
function startDemo(){
if(gameOver){ reset(true); }
started=true; setMode(true); veil.classList.add('hidden');
}
document.getElementById('takeOver').addEventListener('click',e=>{e.stopPropagation();startControl();});
document.getElementById('keepDemo').addEventListener('click',e=>{e.stopPropagation();startDemo();});
canvas.addEventListener('click',startControl);
veil.addEventListener('click',e=>{ if(e.target===veil) startControl(); });
document.addEventListener('keydown',e=>{
const k=e.key.toLowerCase();
const map={arrowup:'up',w:'up',arrowdown:'down',s:'down',arrowleft:'left',a:'left',arrowright:'right',d:'right'};
if(map[k]){
e.preventDefault();
if(!started||!veil.classList.contains('hidden')){ startControl(); }
const nd=DIRS[map[k]];
if(!(nd.x===-dir.x&&nd.y===-dir.y)) nextDir=nd;
}else if(k===' '){
e.preventDefault();
if(!started) startControl();
else { setMode(!aiMode); }
}else if(!started){
startControl();
}
});
// ---------- Rendering ----------
function rr(x,y,w,h,r){
r=Math.min(r,w/2,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();
}
function drawBackground(){
const g=ctx.createLinearGradient(0,0,0,H);
g.addColorStop(0,'#7fae69');g.addColorStop(1,'#5d8d55');
ctx.fillStyle=g;ctx.fillRect(0,0,W,H);
// soft grass patches
ctx.fillStyle='rgba(255,255,255,.045)';
for(let i=0;i<70;i++){
const x=(i*97+31)%W, y=(i*53+17)%H;
ctx.beginPath();ctx.ellipse(x,y,15+(i%4)*4,7+(i%3)*3,0,0,Math.PI*2);ctx.fill();
}
// station building hint, top area
ctx.fillStyle='rgba(44,67,58,.35)';
ctx.fillRect(12,12,118,26);
ctx.fillStyle='#6f4936';ctx.fillRect(8,8,126,10);
ctx.fillStyle='#f0d9ae';ctx.fillRect(19,16,22,13);ctx.fillRect(52,16,22,13);ctx.fillRect(85,16,22,13);
ctx.fillStyle='#244636';ctx.fillRect(8,34,126,5);
ctx.fillStyle='rgba(255,255,255,.72)';ctx.font='bold 11px Trebuchet MS';ctx.fillText('CENTRAL STATION',16,52);
}
function drawTracks(){
ctx.save();
ctx.lineCap='round';
// horizontal tracks
for(let r=0;r<ROWS;r++){
const y=r*CELL+CELL/2;
ctx.strokeStyle='rgba(91,66,47,.52)';ctx.lineWidth=9;
ctx.beginPath();ctx.moveTo(0,y);ctx.lineTo(W,y);ctx.stroke();
ctx.strokeStyle='rgba(218,193,156,.62)';ctx.lineWidth=2;
ctx.beginPath();ctx.moveTo(0,y-3);ctx.lineTo(W,y-3);ctx.stroke();
ctx.beginPath();ctx.moveTo(0,y+3);ctx.lineTo(W,y+3);ctx.stroke();
ctx.strokeStyle='rgba(72,52,39,.5)';ctx.lineWidth=3;
for(let x=5;x<W;x+=15){ctx.beginPath();ctx.moveTo(x,y-8);ctx.lineTo(x,y+8);ctx.stroke();}
}
// vertical tracks
for(let c=0;c<COLS;c++){
const x=c*CELL+CELL/2;
ctx.strokeStyle='rgba(91,66,47,.34)';ctx.lineWidth=9;
ctx.beginPath();ctx.moveTo(x,0);ctx.lineTo(x,H);ctx.stroke();
ctx.strokeStyle='rgba(218,193,156,.42)';ctx.lineWidth=2;
ctx.beginPath();ctx.moveTo(x-3,0);ctx.lineTo(x-3,H);ctx.stroke();
ctx.beginPath();ctx.moveTo(x+3,0);ctx.lineTo(x+3,H);ctx.stroke();
ctx.strokeStyle='rgba(72,52,39,.34)';ctx.lineWidth=3;
for(let y=5;y<H;y+=15){ctx.beginPath();ctx.moveTo(x-8,y);ctx.lineTo(x+8,y);ctx.stroke();}
}
ctx.restore();
}
function drawPlatform(p,idx){
const x=p.x*CELL, y=p.y*CELL;
ctx.save();
ctx.shadowColor='rgba(0,0,0,.22)';ctx.shadowBlur=6;ctx.shadowOffsetY=3;
ctx.fillStyle='#e7c489';rr(x+3,y+3,CELL-6,CELL-6,8);ctx.fill();
ctx.shadowColor='transparent';
ctx.fillStyle='#f7dfa9';rr(x+6,y+6,CELL-12,CELL-12,6);ctx.fill();
ctx.fillStyle='#d9a45d';
for(let i=0;i<3;i++){ctx.fillRect(x+7+i*9,y+4,5,4);}
// little platform sign
ctx.fillStyle='#5c6675';ctx.fillRect(x+CELL-11,y+4,3,9);
ctx.fillStyle=idx%2?'#ff7b6b':'#3d8bfd';rr(x+CELL-18,y+2,14,9,3);ctx.fill();
ctx.restore();
}
function drawPassenger(p,idx){
const cx=p.x*CELL+CELL/2, cy=p.y*CELL+CELL/2;
const bob=Math.sin(performance.now()/420+p.phase)*1.3;
ctx.save();
ctx.translate(cx,cy+bob);
// shadow
ctx.fillStyle='rgba(0,0,0,.18)';ctx.beginPath();ctx.ellipse(0,12,10,3.5,0,0,Math.PI*2);ctx.fill();
// legs
ctx.strokeStyle='#334155';ctx.lineWidth=3;ctx.lineCap='round';
ctx.beginPath();ctx.moveTo(-2,7);ctx.lineTo(-3,13);ctx.moveTo(3,7);ctx.lineTo(4,13);ctx.stroke();
// body
ctx.fillStyle=p.cloth;rr(-6,-1,12,11,4);ctx.fill();
// arm waving
ctx.strokeStyle=p.skin;ctx.lineWidth=3;
const wave=Math.sin(performance.now()/300+p.phase)*3;
ctx.beginPath();ctx.moveTo(5,1);ctx.lineTo(10,-3+wave);ctx.stroke();
// head
ctx.fillStyle=p.skin;ctx.beginPath();ctx.arc(0,-6,5,0,Math.PI*2);ctx.fill();
ctx.fillStyle='#3a2b22';ctx.beginPath();ctx.arc(0,-8,5,Math.PI,Math.PI*2);ctx.fill();
ctx.fillStyle='#1f2937';ctx.fillRect(-2.5,-6,1.3,1.3);ctx.fillRect(1.5,-6,1.3,1.3);
ctx.restore();
}
function drawWagon(x,y,angle,index,hasPassenger){
ctx.save();
ctx.translate(x,y);ctx.rotate(angle);
// shadow
ctx.fillStyle='rgba(0,0,0,.2)';ctx.beginPath();ctx.ellipse(0,3,17,11,0,0,Math.PI*2);ctx.fill();
// coupler
ctx.fillStyle='#334155';ctx.fillRect(-19,-2,5,4);
// wagon body
const color=COLORS[index%COLORS.length];
ctx.fillStyle=color;rr(-15,-10,30,20,5);ctx.fill();
ctx.fillStyle='rgba(255,255,255,.22)';rr(-13,-8,26,6,3);ctx.fill();
ctx.strokeStyle='rgba(0,0,0,.25)';ctx.lineWidth=1.5;rr(-15,-10,30,20,5);ctx.stroke();
// window / passenger
ctx.fillStyle='#243b53';rr(-8,-7,16,11,3);ctx.fill();
if(hasPassenger){
ctx.fillStyle='#f2c39b';ctx.beginPath();ctx.arc(0,-2,4.2,0,Math.PI*2);ctx.fill();
ctx.fillStyle='#273449';ctx.beginPath();ctx.arc(0,-4,4.2,Math.PI,Math.PI*2);ctx.fill();
ctx.fillStyle='#ffd166';ctx.fillRect(-8,4,16,2.5);
}else{
ctx.fillStyle='#8fc6e8';ctx.fillRect(-6,-5,5,4);ctx.fillRect(1,-5,5,4);
}
// wheels
ctx.fillStyle='#202a38';
[-9,9].forEach(wx=>{
ctx.beginPath();ctx.arc(wx,11,3.6,0,Math.PI*2);ctx.fill();
ctx.fillStyle='#aab7c6';ctx.beginPath();ctx.arc(wx,11,1.5,0,Math.PI*2);ctx.fill();
ctx.fillStyle='#202a38';
});
ctx.restore();
}
function drawLocomotive(x,y,angle){
ctx.save();
ctx.translate(x,y);ctx.rotate(angle);
// shadow
ctx.fillStyle='rgba(0,0,0,.24)';ctx.beginPath();ctx.ellipse(0,2,20,13,0,0,Math.PI*2);ctx.fill();
// rear coupler
ctx.fillStyle='#334155';ctx.fillRect(-22,-2,6,4);
// wheels
ctx.fillStyle='#1d2735';
[-11,10].forEach(wx=>[-11,11].forEach(wy=>{
ctx.beginPath();ctx.arc(wx,wy,4.4,0,Math.PI*2);ctx.fill();
ctx.fillStyle='#b9c6d6';ctx.beginPath();ctx.arc(wx,wy,1.8,0,Math.PI*2);ctx.fill();
ctx.fillStyle='#1d2735';
}));
// cab
ctx.fillStyle='#174f96';rr(-20,-12,15,23,4);ctx.fill();
ctx.fillStyle='#75d5ff';rr(-17,-8,8,8,2);ctx.fill();
ctx.fillStyle='rgba(255,255,255,.2)';rr(-19,-11,13,4,2);ctx.fill();
// boiler
const grd=ctx.createLinearGradient(0,-12,0,12);
grd.addColorStop(0,'#64b5ff');grd.addColorStop(1,'#2f7cf6');
ctx.fillStyle=grd;rr(-8,-10,25,20,8);ctx.fill();
ctx.strokeStyle='rgba(0,0,0,.25)';ctx.lineWidth=1.5;rr(-8,-10,25,20,8);ctx.stroke();
// chimney
ctx.fillStyle='#233246';rr(8,-16,6,9,2);ctx.fill();
ctx.fillStyle='#ffcf5a';rr(7,-17,8,4,2);ctx.fill();
// headlight
ctx.fillStyle='#ffe9a3';ctx.beginPath();ctx.arc(16,-1,4,0,Math.PI*2);ctx.fill();
// face
ctx.fillStyle='#fff';
ctx.beginPath();ctx.arc(8,-5,3,0,Math.PI*2);ctx.fill();
ctx.beginPath();ctx.arc(8,3,3,0,Math.PI*2);ctx.fill();
ctx.fillStyle='#162235';ctx.beginPath();ctx.arc(9.3,-5,1.4,0,Math.PI*2);ctx.fill();
ctx.beginPath();ctx.arc(9.3,3,1.4,0,Math.PI*2);ctx.fill();
ctx.strokeStyle='#17324f';ctx.lineWidth=1.4;ctx.beginPath();ctx.arc(12,0,2.2,-.4,Math.PI+.4);ctx.stroke();
ctx.restore();
}
function drawParticles(dt){
particles=particles.filter(p=>p.life>0);
particles.forEach(p=>{
p.life-=dt*1.5; p.y-=dt*26;
ctx.save();ctx.globalAlpha=Math.max(0,p.life);
ctx.font='bold 17px Trebuchet MS';ctx.textAlign='center';
ctx.lineWidth=3;ctx.strokeStyle='rgba(30,50,30,.65)';ctx.strokeText(p.txt,p.x,p.y);
ctx.fillStyle=p.color;ctx.fillText(p.txt,p.x,p.y);ctx.restore();
});
}
let last=performance.now();
function draw(now){
const dt=Math.min(.05,(now-last)/1000); last=now;
ctx.setTransform(DPR,0,0,DPR,0,0);
ctx.clearRect(0,0,W,H);
drawBackground();
drawTracks();
passengers.forEach((p,i)=>{drawPlatform(p,i);drawPassenger(p,i);});
// wagons from tail to head
for(let i=body.length-1;i>=1;i--){
const prev=body[i-1], cur=body[i];
const a=angleFor(prev,cur);
drawWagon(cur.x*CELL+CELL/2,cur.y*CELL+CELL/2,a,i-1,i-1<boarded);
}
const h=body[0], h2=body[1]||{x:h.x+dir.x,y:h.y+dir.y};
drawLocomotive(h.x*CELL+CELL/2,h.y*CELL+CELL/2,angleFor(h,h2));
drawParticles(dt);
}
function loop(now){
if(!gameOver && started){
const speed=Math.max(92,138-boarded*2);
if(now-tick>speed){tick=now;move();}
}
draw(now);
requestAnimationFrame(loop);
}
reset(true);
setMode(true);
started=true;
requestAnimationFrame(loop);
})();
</script>
</body>
</html>Reasoning trace (3,306 tokens)
We need produce complete HTML game. Need comply self-playing demo mode, click/press key message. Snake mechanics train on grid, wagons trailing, passengers on platform, pick up by driving over, passengers fill wagons perhaps each pickup adds wagon? Need visually polished. First paint meaningful visible with demo AI playing behind overlay message. Let's design 1280x800 canvas game responsive. Title "Platform Express" style. Track grid in a rounded area, locomotive at center heading right, wagons colorful behind, passengers on platform (little figures standing beside track cells). Snake-like movement on grid no reverse. Collect passengers: drive over passenger platform tile? Need layout track and platform. Simplify: board grid 22x14? Train occupies cells, passengers wait on "platform" cells adjacent to track but collecting needs snake head over passenger tile. Could place passengers directly on track tile in platform zones, visually platform visible under them. Train picks up by overlapping cell, passenger boards nearest open wagon, add colorful wagon. Score, passengers count. Better grid: 24 cols x 14 rows, cell ~40. Canvas area ~960x560. Locomotive snake head plus wagons. Passengers spawned on track cells (with platform stripe graphics). Head passing over collects, adds wagon (up to maybe 8). Each wagon has color and passenger silhouettes once collected. Demo AI: algorithm to seek nearest passenger while avoiding collisions. Grid based, move step every 120ms. Need snake path segments with wagons. AI choose direction toward target using greedy with safety checks against body and boundaries, or BFS treating body as obstacles. Simpler BFS from head to nearest target, avoid body cells (excluding tail) and walls, path step; but head moving may cut body. Keep body shortish. BFS is fine. Also after start overlay? Requirement says "Games must start in a self-playing demo mode ... behind a 'Click or press a key to start' message". So overlay center over game while AI runs. Clicking/press key dismisses and switches to player control, but perhaps also allow "Watch demo" continues. Show banner "DEMO RIDE • Click or press any key to take the controls". That meets. Visual: top HUD title, score, passengers, best; right panel? Canvas central. Let's build whole page with header, board canvas, side info maybe layout grid. 1280x800: header 84, main canvas ~660 height. Board aspect 24/14 =1.714. At width 1100 -> height 641 fits 800 with margins ~ header 90 + board 600. Let's use canvas logical 960x560 (24x14 @40) and CSS width 100%, max 1100. Page background dark navy with subtle gradient, header title. Side panel could be part of canvas? Keep layout: header row, stats chips, canvas with rounded border; footer instructions. Maybe canvas at 1060x618. 800 height: padding 16 top, header 70, stats inside canvas? Let's make header 64, canvas max-height calc. Use aspect ratio 24/14. Draw track: background dark green/night station? A playful toy-train aesthetic. Rails horizontal and vertical on a grid, gravel ties, platform zones in coral-ish stripes. Locomotive blue/teal with chimney, face, wheels. Wagons colorful boxes with passengers. Canvas drawing details. Grid cell 40. Draw background gradient (sky blue night? Let's do bright cheerful daytime board: cream/green landscape with station). Track cells dark slate rounded strips. Draw rails as two lines per row/col? Full grid rails all crossing, like a lattice. Snake game train should move on a grid of tracks. Draw every row/col as track with sleepers, giving network feel. But train drawing on track cells with rails beneath. Could render tracks as horizontal lines along each row and vertical along each col: rails two thin lines with ties every 10px. That could be visually busy. Alternative draw a single continuous line per grid row/col with dark road-like and dashed center. Train wheels align. Maybe board is station yard: green grass background, track grid lines in muted brown, sleeper ties. Passengers on "platform" squares drawn as wooden deck tiles with yellow edge. Draw: - background grass #dff0d4 with subtle dots. - Track: for each row, two horizontal rails at y+12 and y+28? For each col vertical similarly; intersections okay. Actually train occupies a cell and follows cells; draw cell tile with rounded dark #26364a rectangle inset 3, with two lighter rail lines? Like train track segments. Snake-like path on grid of railroad ties. Draw each cell as dark gray rounded rect (12x34?) crossing. Hmm. Simplest polished: draw "track grid": horizontal and vertical light-brown rails on grass, sleepers. Only locomotive direction matters visually. Draw rails per row as two lines 6px apart? vertical too; intersections messy but okay if low alpha. Let's do tracks as full rows/cols: each row: dark brown line width 8 at cell center, plus light line width 2 highlight and ties width 16 perpendicular every 20px. Same for columns. This creates a lattice. At intersections overlap; acceptable. Passengers: standing on platform cell—draw a small platform tile under them (light tan rectangle) in some cells; figure with head circle, body color, waving. Train: head cell size ~34. Locomotive facing direction. Draw body rounded rect blue, boiler circle-ish front, chimney, cab, face (eyes) oriented. Wagons following at each body segment; each wagon a colorful rounded rect with roof, wheels, passenger heads peeking if collected. Occupied cells of body minus head = wagons. If 8 passengers boarded, each wagon shows one passenger. Locomotive drawing oriented: could draw always with rotation by direction; then face rotates. Better draw in local coords rotated so front along direction. Local: length 34 along x, width 24 along y. Facing right: boiler rounded rect x from -16 to +14? cab at rear left. Chimney top. Wheels. Eyes on front. Rotate angle. Wagons: body cells each with color palette; draw rect centered 30x22 with roof line and windows. Score: +100 per passenger, combo multiplier maybe. Collected count and score HUD in DOM updating. Game over on collision (AI or player): restart demo? Show overlay "Trip complete" with score, click to restart. But AI should avoid collisions. Controls: arrows/WASD after start. Space toggles demo? Add button "Demo mode" toggle. Need implement robust resize with devicePixelRatio: canvas fixed internal 960x560 scaled by CSS; use ctx scale for DPR maybe set canvas width=960*dpr and transform. Easier keep canvas width 960 height 560 and CSS scales, slightly blurry but okay. Use dpr for crisp: resize canvas based on bounding client width? Since logical coordinates need mapping. Set canvas.width=960*dpr... but CSS aspect ratio and internal fixed dims. Actually use fixed 960x560 logical, canvas.width = 960*dpr, ctx.setTransform(dpr,0,0,dpr,0,0), CSS width 100%. Good. AI BFS: grid 24x14, walls borders? Add boundary rows/cols as obstacles so train cannot leave. Use cells 1..22, 1..12. Body array head first. target list passengers. BFS from head considering obstacle set = body cells except head and except tail (tail moves) maybe including all body cells except head, plus border. Find nearest target path. First step from path[1]. If no path, choose any safe direction. Also AI might just drive to passenger; ensure targets spawned not under train body. Occupancy: passengers on cells not occupied. Spawn e.g. 6 passengers, each on platform cells. Random but seed-ish. Movement: body.unshift(newHead); body.pop(); if newHead equals passenger cell: collect; score; add wagon count (body length grows by 1 instead of pop). Increase passengers boarded count, remove passenger, spawn new passenger after delay maybe keep 6. Wagon capacity 8; length up to 10. If collision with wall or body: gameOver. Player collision ends; show overlay. In demo AI avoid. Player input: queue direction, no reverse. Prevent opposite. Also train moving continuously every ~135ms, speed increases slightly with score to 110. Overlay HTML positioned over canvas with dim backdrop and card: "Click or press a key to start" and "The conductor is driving the demo — take the wheel any time." This must be behind? "behind a message" means game visible behind message. Overlay semi-transparent so visible. Clicking canvas or key removes overlay and sets playerControl true. Key events only after click in iframe per prompt — but we display message anyway. Add document keydown listener; iframe receives keys after click. Fine. AI path: BFS each move. Also a "Watch demo" button keeps AI control; overlay button "Take the controls" and secondary "Keep watching". Clicking anywhere takes control. Need ensure initial state visible immediately at t=0 with locomotive, wagons, passengers. Start setInterval at load. HUD DOM: score, passengers, wagons, status "DEMO RIDING"/"YOU'RE DRIVING", best? no storage. Also add station details: platforms drawn around some passenger cells; maybe a station building top-left in background? Grid fills area. Could add decorative top area within canvas? Better keep. Rendering decorative: grass tufts random fixed positions outside tracks; small trees? Track grid occupies all. Keep simple. Platform cells: passengers stand on small wooden platform squares 34x34 with striped edge; passengers drawn at center. Draw platform under passenger before train. Need maybe passengers in waiting spots with signs. Draw each passenger with distinct clothing color and skin tone. Let's write code carefully. Layout HTML: body .app header .topbar: logo circle with train icon emoji? Emoji rendering fine. Title "PLATFORM EXPRESS" subtitle "Snake on the rails". stats chips. main .stage: canvas wrapper id boardWrap with canvas and overlay. footer controls. At 375px: header stacks, font smaller, canvas aspect 960/560 = tall-ish? width 343 => height 200, grid cells ~14px but logical still fine. Overlay card fits. Fine. Draw text on canvas for cell labels? "PLATFORM 1" etc small. Let's craft drawing functions. Grid: COLS=24 ROWS=14 CELL=40 => 960x560. Track rendering: ctx.fillStyle grass gradient. For each row r: draw horizontal track centerline at y = r*CELL+CELL/2, width 12 dark #8a6b4f, then width 2 #c3a27c highlight; ties every 16 px: vertical small lines perpendicular (for horizontal track) width 4 length 14 color #6b513f? Similarly cols. Intersections. Maybe tracks too dense; reduce alpha 0.55. Draw platforms: for each passenger cell, rounded rect inset 4 fill #f4d9a8 with border #d09b52; hazard stripes at edges; little sign. Passenger figure at cell center, standing slightly above center: head at y-6 radius4, body rounded rect 10x12, legs. Locomotive: function drawLoco(cx,cy,angle). ctx translate rotate. Dimensions: length 36 (x from -18 to 18), width 22. wheels at (-10,±11),(9,±11) dark circles r3.5 with hub. body: boiler rounded rect (-2,-9,18,18) color #2f80ed; front circle at x=14? use rounded rect x=-14..16. cab at rear: rounded rect x=-20,y=-10,w=14,h=20 fill #174f96; window. chimney: small rect at x=8,y=-15. headlight circle at x=16. face eyes front on boiler at x=8, y±4 (facing right) white circles + pupils. But after rotation, "up" is local -y. Fine. Wagons: draw at cell center angle of direction from head to next? For each segment i>0, direction from body[i-1] to body[i] determines orientation; use that angle. Wagon rect length 30 width 22, color palette[i%], top rail, windows, coupler. If boarded count i-1 <= passengers boarded, show passenger head in wagon. Actually wagons count = body.length-1; each collected passenger fills one wagon in order. So wagon index 0 gets first passenger. Show passenger head peeking from wagon index < boarded. Initial body: head at (12,7), then (11,7),(10,7),(9,7) so three wagons; but boarded count maybe 2? To look colorful with passengers: set boarded=2 and wagons 3. Fine. Spawn passengers away from body and each other. AI: since passengers on track cells, path straightforward. Collision check: new head in body slice(0,-1) (tail moves unless growing). Growing: we pop then push new passenger? implement: const grow = passenger at newHead. body.unshift(newHead) if (!grow) body.pop() else collected++ and remove passenger. Also passengers boarded limited maybe 8; beyond that wagon full? Could still collect score. Limit body length 12; keep growing but AI path may be harder. Fine. Score: 100 + combo*20. AI path avoid body: BFS obstacles = body cells except head and except tail cell (since tail vacates if not growing). Also border. Also when head about to move, ensure chosen next cell not occupied. BFS. Potential AI loops: if nearest unreachable, choose safe move maximizing space; implement safety: for each candidate dir, simulate and check resulting head free and body path ahead free 4 cells via BFS to any free? simpler BFS from candidate avoiding current body gives path length; choose max. Combine: prefer path to target first step; if that step leaves no escape (BFS from candidate over free cells length <3) skip. Compute reachable area BFS from candidate treating body cells (minus tail) blocked; require size >= 6. This prevents trapping. We need target direction path computed with obstacles. getPath returns array from start. Implementation BFS simple. Game over overlay reuse same overlay with dynamic content: card title "End of the line!" text score, button "Run it back". Also offer demo. Since no storage. Status pill updates "DEMO RIDING" yellow / "YOU'RE DRIVING" green. Let's also show combo floating text "+100" on canvas at collection position fading. Sound? skip. Write final code. Ensure no console errors. Use Math.random fine. Pointer click on canvas starts control. Overlay has buttons with stopPropagation. Key handler: if overlay visible (start) -> start control; if gameover -> restart; else set dir. Also prevent scroll for arrows. Let's write. Canvas resize DPR: set attributes width 960*? Use dpr=Math.min(devicePixelRatio,2); canvas.width=960*dpr; canvas.height=560*dpr; style aspect ratio; ctx.setTransform(dpr,0,0,dpr,0,0). On draw, first ctx.setTransform(dpr...) each frame then clearRect(0,0,960,560). CSS canvas width:100%; height:auto; display:block. Wrapper max-width 1060, aspect ratio 12/7 (960/560). canvas absolute? Use canvas width 100%. Overlay position absolute inset 0, backdrop blur small. HUD responsive. Footer keyboard hints with kbd. Let's produce code now.