Train snakeOne-line
GPT-6.1 Terra
by OpenAIReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 4 min 18 s
- Cost
- $0.102
- Output tokens
- 20,299
- Spent thinking
- 8,398 tokens
- Speed
- 80.1 tok/s
- Console errors
- 0
- First token after
- 4.9 s
- First visible text after
- 2 min 35 s
- Prompt tokens
- 361
- Code size
- 33.9 KB
- Finish reason
- stop
- Served by
- OpenAI
- Model id
openai/gpt-6.1-terra- Reasoning sent
{"effort":"xhigh"}- Max output
- 128,000 tokens
- Generated
- Nov 12, 2026
What it was asked
The prompt
“make me a snake game where the snake is a little train with a locomotive and colorful wagons, picking up passengers waiting on the platform”
System prompt, sent with every prompt
You are generating a self-contained web demo. It will be rendered automatically in a sandboxed iframe and compared side by side with the output of other AI models given the exact same task. Output rules: - Reply with exactly ONE fenced code block containing the complete file: ```html (or ```svg when the task asks for an SVG). No text before or after the code block. - Everything must be inline: CSS in <style>, JavaScript in <script>. No external files, images, fonts or network requests, except the CDN URLs explicitly allowed by the task. - Do not use localStorage, sessionStorage, cookies, IndexedDB, alert(), confirm(), prompt() or window.open(): they are blocked in the sandbox and will throw. - The page must look right in a 1280×800 viewport and adapt gracefully down to 375px wide. No horizontal scrollbar. - Something meaningful must be visible on first paint: a screenshot is taken automatically about 3 seconds after load, without any interaction. - Everything that makes the demo interesting must be visible within the first seconds: nothing hidden behind a high score, a later level or a long play session. - Games must start in a self-playing demo mode (a simple AI plays) behind a "Click or press a key to start" message, so the game can be judged in action without playing. - The iframe only receives keyboard events after the user clicks it: if the demo uses the keyboard, display a clear "Click or press a key to start" message. - The code must run with zero errors in the console of the latest Chrome.
Code run-1.html · 748 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Platform Express</title>
<style>
:root {
--ink: #283f35;
--muted: #718277;
--green: #477e62;
--paper: #f7f6ef;
--line: #e4e9dd;
--orange: #eb795d;
}
* { box-sizing: border-box; }
[hidden] { display: none !important; }
body {
margin: 0;
color: var(--ink);
background: var(--paper);
font-family: "Trebuchet MS", "Segoe UI", Arial, sans-serif;
-webkit-font-smoothing: antialiased;
}
button { font: inherit; }
.page { max-width: 1220px; margin: auto; padding: 0 20px; }
.topbar {
height: 64px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #e5e9dd;
}
.brand { display: flex; align-items: center; gap: 9px; }
.brand-mark {
width: 36px; height: 36px; border-radius: 11px;
background: #477e62; display: grid; place-items: center;
box-shadow: 0 3px 0 #dce7d8;
}
.brand-name { font-weight: 900; font-size: 12px; line-height: 1.05; letter-spacing: .05em; }
.brand-name span { display: block; color: #87a18c; font-size: 9px; letter-spacing: .15em; margin-top: 3px; }
.header-note { color: #8c9b8e; font-size: 9px; font-weight: 800; letter-spacing: .15em; display: flex; align-items: center; gap: 9px; }
.header-note i { width: 7px; height: 7px; border-radius: 50%; background: #eab864; }
.hero {
display: flex; align-items: end; justify-content: space-between;
padding: 15px 0 16px; gap: 20px;
}
.eyebrow { color: #6c9b77; font-size: 9px; font-weight: 900; letter-spacing: .17em; text-transform: uppercase; }
.hero h1 {
margin: 3px 0 3px; font-family: Georgia, "Times New Roman", serif;
font-weight: 700; font-size: 36px; line-height: 1.05; letter-spacing: -.035em;
}
.hero p { margin: 0; color: #7e8c7e; font-size: 13px; }
.ticket {
display: flex; align-items: center; gap: 10px; padding: 8px 12px;
border: 1px dashed #cbd8c7; border-radius: 9px; color: #7a9380;
font-size: 9px; line-height: 1.45; letter-spacing: .09em; font-weight: 800;
}
.ticket strong { display: block; color: #4f765d; font-size: 10px; letter-spacing: .11em; }
.ticket-icon {
width: 30px; height: 30px; border-radius: 50%; background: #e9efe1;
display: grid; place-items: center; font-size: 16px; color: #6c9b77;
}
.layout { display: grid; grid-template-columns: minmax(0, 1fr) 265px; gap: 18px; align-items: stretch; }
.board-card, .panel {
background: #fff;
border: 1px solid #e5eadf;
box-shadow: 0 5px 20px rgba(61, 86, 62, .055);
}
.board-card { border-radius: 17px; overflow: hidden; min-width: 0; }
.board-head {
height: 52px; display: flex; align-items: center; justify-content: space-between;
padding: 0 14px 0 18px; gap: 8px;
}
.board-title { display: flex; align-items: center; gap: 9px; min-width: 0; }
.route-icon {
width: 28px; height: 28px; border-radius: 9px; background: #edf4e8;
display: grid; place-items: center; color: #648e6c; flex: none;
}
.board-title strong { display: block; font-size: 10px; letter-spacing: .1em; white-space: nowrap; }
.board-title small { display: block; color: #a1aea1; font-size: 9px; margin-top: 2px; white-space: nowrap; }
.board-actions { display: flex; align-items: center; gap: 8px; }
.mode-pill {
color: #659573; background: #eff6ec; border-radius: 99px;
padding: 7px 9px; font-size: 9px; font-weight: 900; letter-spacing: .07em;
white-space: nowrap;
}
.mode-pill::before { content: ""; display: inline-block; vertical-align: 1px; width: 6px; height: 6px; background: #80b98a; border-radius: 50%; margin-right: 6px; }
.icon-button {
border: 1px solid #e8ede5; background: white; color: #698271;
height: 30px; min-width: 30px; border-radius: 9px; cursor: pointer;
display: grid; place-items: center; font-size: 17px; line-height: 1;
}
.icon-button:hover { background: #f1f6ee; }
.stage { position: relative; background: #dbead3; cursor: pointer; touch-action: none; }
canvas { display: block; width: 100%; height: auto; aspect-ratio: 8 / 5; }
.demo-hint {
position: absolute; bottom: 15px; left: 50%; transform: translateX(-50%);
display: flex; align-items: center; gap: 11px; padding: 9px 13px 9px 10px;
background: rgba(255, 255, 251, .96); border: 1px solid rgba(255,255,255,.95);
border-radius: 13px; box-shadow: 0 5px 18px rgba(65, 97, 66, .15);
white-space: nowrap; pointer-events: none;
}
.hint-icon {
width: 34px; height: 34px; display: grid; place-items: center;
border-radius: 10px; background: #f9ead1; color: #d58750; flex: none;
}
.hint-copy strong { display: block; font-size: 12px; color: #334d3c; }
.hint-copy small { display: block; font-size: 10px; color: #8b9c8a; margin-top: 3px; }
.hint-arrow { color: #95ad96; font-size: 17px; padding-left: 3px; }
.end-screen {
position: absolute; inset: 0; display: grid; place-items: center;
background: rgba(45, 71, 51, .38); cursor: default;
}
.end-box {
text-align: center; background: #fffef8; padding: 22px 29px; border-radius: 18px;
box-shadow: 0 10px 35px rgba(29, 53, 34, .18); min-width: 225px;
}
.end-box .end-icon { font-size: 28px; line-height: 1; }
.end-box h2 { font: 700 25px/1.1 Georgia, serif; margin: 9px 0 6px; }
.end-box p { color: #819180; font-size: 12px; margin: 0 0 15px; }
.primary {
background: #4d8563; color: white; border: 0; border-radius: 9px;
padding: 10px 18px; font-weight: 800; font-size: 11px; cursor: pointer;
box-shadow: 0 3px 0 #376b4c;
}
.primary:hover { background: #3e7554; }
.side { display: flex; flex-direction: column; gap: 11px; min-width: 0; }
.panel { border-radius: 15px; }
.score-panel { padding: 17px 18px 15px; }
.panel-heading { display: flex; justify-content: space-between; align-items: center; }
.panel-label { font-size: 9px; color: #91a192; font-weight: 900; letter-spacing: .15em; }
.tiny-flower { color: #e6b765; font-size: 17px; line-height: 1; }
.score-main { display: flex; align-items: center; gap: 10px; margin: 12px 0 13px; }
.score-number { font: 700 46px/.9 Georgia, serif; letter-spacing: -.05em; color: #345c45; min-width: 54px; }
.score-text { font-size: 12px; font-weight: 800; line-height: 1.25; }
.score-text span { display: block; font-size: 10px; font-weight: 400; color: #a0aea0; margin-top: 3px; }
.passenger-badge { margin-left: auto; width: 42px; height: 42px; background: #fbf0da; border-radius: 50%; display: grid; place-items: center; }
.wagon-stat { border-top: 1px solid #eff2eb; padding-top: 12px; display: flex; align-items: center; gap: 8px; }
.wagon-icons { display: flex; gap: 2px; align-items: center; }
.wagon-icons i { display: block; width: 12px; height: 9px; border-radius: 3px 3px 2px 2px; position: relative; }
.wagon-icons i:after { content: ""; position: absolute; left: 2px; right: 2px; bottom: -3px; border-bottom: 2px dotted #526a5a; }
.wagon-copy { flex: 1; font-size: 10px; color: #91a092; font-weight: 700; }
.wagon-count { font-size: 12px; font-weight: 900; color: #52775b; }
.rules-panel { padding: 17px 18px; flex: 1; }
.rule-list { display: flex; flex-direction: column; justify-content: space-around; height: calc(100% - 20px); min-height: 210px; }
.rule { display: flex; align-items: center; gap: 10px; padding-top: 10px; }
.rule-icon {
width: 35px; height: 35px; border-radius: 10px; flex: none;
display: grid; place-items: center; background: #edf4e9; color: #64916d;
}
.rule:nth-child(2) .rule-icon { background: #fcf0dd; color: #d49b50; }
.rule:nth-child(3) .rule-icon { background: #fbece6; color: #d9856e; }
.rule strong { display: block; font-size: 11px; }
.rule p { margin: 3px 0 0; color: #9aa99a; font-size: 10px; line-height: 1.45; }
.tip {
border: 1px solid #f1e8cd; border-radius: 14px; background: #fcf7e9;
padding: 13px 15px; display: flex; gap: 10px; align-items: flex-start;
}
.tip-star { color: #dcad59; font-size: 17px; line-height: 1; }
.tip strong { display: block; font-size: 9px; color: #ae905d; letter-spacing: .11em; }
.tip p { margin: 5px 0 0; color: #a99b7a; font-size: 10px; line-height: 1.4; }
.mobile-controls { display: none; }
.footer {
height: 25px; display: flex; justify-content: space-between; align-items: center;
color: #acb9aa; font-size: 9px; font-weight: 800; letter-spacing: .08em;
}
@media (max-width: 900px) {
.layout { grid-template-columns: minmax(0, 1fr) 235px; gap: 12px; }
.score-panel, .rules-panel { padding-left: 14px; padding-right: 14px; }
.rule-list { min-height: 190px; }
}
@media (max-width: 700px) {
.page { padding: 0 15px; }
.topbar { height: 57px; }
.header-note { font-size: 8px; }
.hero { padding: 17px 0 15px; }
.hero h1 { font-size: 32px; }
.hero p { font-size: 12px; }
.ticket { display: none; }
.layout { display: flex; flex-direction: column; gap: 11px; }
.board-head { height: 48px; padding: 0 10px 0 12px; }
.board-title strong { font-size: 9px; }
.board-title small { font-size: 8px; }
.mode-pill { font-size: 8px; padding: 6px 7px; }
.demo-hint { bottom: 8px; padding: 6px 9px 6px 7px; gap: 7px; }
.hint-icon { width: 27px; height: 27px; border-radius: 8px; }
.hint-copy strong { font-size: 10px; }
.hint-copy small { font-size: 8px; margin-top: 2px; }
.hint-arrow { font-size: 13px; }
.end-box { padding: 14px 20px; }
.end-box h2 { font-size: 21px; margin: 5px 0; }
.end-box p { margin-bottom: 9px; }
.mobile-controls {
display: flex; align-items: center; justify-content: space-between; gap: 8px;
background: white; border: 1px solid #e5eadf; border-radius: 13px; padding: 10px 12px;
}
.mobile-controls strong { display: block; font-size: 9px; letter-spacing: .1em; color: #718e77; }
.mobile-controls small { font-size: 9px; color: #a1afa1; }
.arrows { display: flex; gap: 5px; }
.arrows button {
width: 37px; height: 37px; border: 1px solid #dfe9dc; border-radius: 9px;
background: #f2f7ef; color: #527c5e; font-size: 18px; font-weight: 900;
cursor: pointer; touch-action: manipulation;
}
.arrows button:active { background: #dcebd8; }
.side { gap: 10px; }
.rules-panel { padding: 17px; }
.rule-list { min-height: 0; gap: 12px; }
.rule { padding-top: 4px; }
.footer { font-size: 8px; height: 34px; }
.footer span:last-child { display: none; }
}
@media (max-width: 390px) {
.header-note { letter-spacing: .07em; }
.board-title small { display: none; }
.route-icon { width: 25px; height: 25px; }
.arrows button { width: 34px; }
}
</style>
</head>
<body>
<div class="page">
<header class="topbar">
<div class="brand">
<div class="brand-mark" aria-hidden="true">
<svg width="23" height="23" viewBox="0 0 24 24" fill="none">
<rect x="5" y="4" width="14" height="14" rx="4" stroke="#fff" stroke-width="1.8"/>
<path d="M8 8h8v4H8zM8 19l-2 2m10-2 2 2M8 15h.01M16 15h.01" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/>
</svg>
</div>
<div class="brand-name">LITTLE<span>JOURNEYS</span></div>
</div>
<div class="header-note"><i></i> A LITTLE GAME FOR EVERYONE</div>
</header>
<section class="hero">
<div>
<div class="eyebrow">✳ Welcome to the meadow line</div>
<h1>Platform Express</h1>
<p>Collect your passengers. Keep your little train rolling.</p>
</div>
<div class="ticket">
<div class="ticket-icon">✿</div>
<div>YOUR TICKET TO<strong>A LITTLE ADVENTURE</strong></div>
</div>
</section>
<main class="layout">
<section class="board-card" aria-label="Snake game">
<div class="board-head">
<div class="board-title">
<div class="route-icon" aria-hidden="true">
<svg width="17" height="17" viewBox="0 0 20 20" fill="none">
<path d="M4 14c3 0 3-8 6-8s3 8 6 8" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-dasharray="2 2"/>
<circle cx="4" cy="14" r="2" fill="currentColor"/><circle cx="16" cy="14" r="2" fill="currentColor"/>
</svg>
</div>
<div><strong>THE MEADOW LINE</strong><small>A lovely day for a train ride</small></div>
</div>
<div class="board-actions">
<span id="modePill" class="mode-pill">AUTOPILOT</span>
<button id="pauseButton" class="icon-button" type="button" aria-label="Pause game" title="Pause game" hidden>Ⅱ</button>
<button id="restartButton" class="icon-button" type="button" aria-label="Restart game" title="Restart game">↻</button>
</div>
</div>
<div id="stage" class="stage" aria-label="Game board. Click to start; use arrow keys or WASD to steer.">
<canvas id="game" width="960" height="600"></canvas>
<div id="demoHint" class="demo-hint">
<span class="hint-icon" aria-hidden="true">
<svg width="19" height="19" viewBox="0 0 20 20" fill="none"><path d="M7 3v7M10 2v7M13 4v6M7 8 5.5 6.8c-1.3-1-2.6.3-1.5 1.7l3 4.4c.8 1.2 1.7 2.1 3.5 2.1h1.7c2.5 0 4-1.7 4-4V8.3c0-1.4-2-1.6-2-.2" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
</span>
<span class="hint-copy"><strong>Click or press a key to start</strong><small>Autopilot is showing you the way</small></span>
<span class="hint-arrow">↗</span>
</div>
<div id="endScreen" class="end-screen" hidden>
<div class="end-box">
<div class="end-icon" id="endIcon">🚂</div>
<h2 id="endTitle">End of the line!</h2>
<p id="endText">A lovely little journey.</p>
<button class="primary" id="endButton" type="button">All aboard again</button>
</div>
</div>
</div>
</section>
<div class="mobile-controls">
<div><strong>TAP TO STEER</strong><small>Or swipe on the meadow</small></div>
<div class="arrows" aria-label="Direction controls">
<button type="button" data-dir="left" aria-label="Move left">←</button>
<button type="button" data-dir="up" aria-label="Move up">↑</button>
<button type="button" data-dir="down" aria-label="Move down">↓</button>
<button type="button" data-dir="right" aria-label="Move right">→</button>
</div>
</div>
<aside class="side">
<section class="panel score-panel">
<div class="panel-heading"><span class="panel-label">YOUR JOURNEY</span><span class="tiny-flower">✿</span></div>
<div class="score-main">
<div id="score" class="score-number">00</div>
<div class="score-text">passengers<span>picked up</span></div>
<div class="passenger-badge" aria-hidden="true">
<svg width="33" height="33" viewBox="0 0 36 36" fill="none">
<path d="M7 30c.5-6 4-9 11-9s10.5 3 11 9" fill="#E98568"/>
<circle cx="18" cy="15" r="7" fill="#F1C6A1"/>
<path d="M11 15c-.5-6 2-9 7-9 4.7 0 7.5 3.2 7 8-2-1-3.1-3-3.5-4.4-2.7 2.5-6.6 3.6-10.5 3.8" fill="#665044"/>
<circle cx="15.5" cy="15.5" r=".7" fill="#665044"/><circle cx="20.5" cy="15.5" r=".7" fill="#665044"/>
<path d="M16 18c1.3 1 2.7 1 4 0" stroke="#BF7C6A" stroke-width="1" stroke-linecap="round"/>
</svg>
</div>
</div>
<div class="wagon-stat">
<div class="wagon-icons" aria-hidden="true"><i style="background:#70b4b0"></i><i style="background:#f1bd61"></i><i style="background:#e9907a"></i><i style="background:#a99bc6"></i></div>
<span class="wagon-copy">wagons in tow</span><span class="wagon-count" id="wagonCount">4</span>
</div>
</section>
<section class="panel rules-panel">
<div class="panel-heading"><span class="panel-label">HOW TO PLAY</span><span style="color:#c5d1c3;font-size:12px">✳</span></div>
<div class="rule-list">
<div class="rule">
<div class="rule-icon" aria-hidden="true">
<svg width="19" height="19" viewBox="0 0 20 20" fill="none"><path d="M10 3v14M3 10h14M7 6l3-3 3 3M7 14l3 3 3-3M6 7l-3 3 3 3M14 7l3 3-3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
<div><strong>Steer your train</strong><p>Use the arrow keys or WASD.</p></div>
</div>
<div class="rule">
<div class="rule-icon" aria-hidden="true">
<svg width="19" height="19" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="6.5" r="3" stroke="currentColor" stroke-width="1.5"/><path d="M4.5 17c.3-3.5 2.2-5 5.5-5s5.2 1.5 5.5 5M16 4v4m-2-2h4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
</div>
<div><strong>Pick up passengers</strong><p>Visit the little waiting platforms.</p></div>
</div>
<div class="rule">
<div class="rule-icon" aria-hidden="true">
<svg width="19" height="19" viewBox="0 0 20 20" fill="none"><path d="M10 3 17 16H3L10 3Z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/><path d="M10 8v3.5M10 14h.01" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>
</div>
<div><strong>Mind your wagons</strong><p>Avoid the edges and your own train.</p></div>
</div>
</div>
</section>
<div class="tip"><span class="tip-star">✦</span><div><strong>A LITTLE TIP</strong><p>Every passenger adds a colorful new wagon to your train!</p></div></div>
</aside>
</main>
<footer class="footer"><span>MADE FOR THE JOURNEY, NOT THE DESTINATION ✳</span><span>TAKE THE SCENIC ROUTE</span></footer>
</div>
<script>
(() => {
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const stage = document.getElementById('stage');
const hint = document.getElementById('demoHint');
const endScreen = document.getElementById('endScreen');
const endTitle = document.getElementById('endTitle');
const endText = document.getElementById('endText');
const endIcon = document.getElementById('endIcon');
const endButton = document.getElementById('endButton');
const pauseButton = document.getElementById('pauseButton');
const modePill = document.getElementById('modePill');
const scoreEl = document.getElementById('score');
const wagonEl = document.getElementById('wagonCount');
const COLS = 16, ROWS = 10, CELL = 60, SPEED = 190;
const directions = {
up: { x: 0, y: -1 }, down: { x: 0, y: 1 },
left: { x: -1, y: 0 }, right: { x: 1, y: 0 }
};
const wagonColors = ['#70b8b2', '#f0bd5d', '#e98972', '#a99ac8', '#81ad83', '#e9a9a0', '#79a8cc'];
const passengerColors = ['#e57e69', '#68a6a5', '#d9a249', '#a18aba', '#73a87c'];
let snake, previous, passengers, direction, queued, score, mode = 'demo', effects = [], accumulator = 0;
let lastTime = performance.now();
function resetState() {
snake = [{x:7,y:5},{x:6,y:5},{x:5,y:5},{x:4,y:5},{x:3,y:5}];
previous = snake.map(p => ({...p}));
direction = {x:1,y:0};
queued = {x:1,y:0};
passengers = [{x:10,y:5,c:0},{x:13,y:2,c:1},{x:5,y:2,c:2}];
score = 0;
effects = [];
accumulator = 0;
updateUI();
}
function updateUI() {
scoreEl.textContent = String(score).padStart(2, '0');
wagonEl.textContent = String(snake.length - 1);
hint.hidden = mode !== 'demo';
pauseButton.hidden = mode === 'demo' || mode === 'over';
pauseButton.textContent = mode === 'paused' ? '▶' : 'Ⅱ';
pauseButton.setAttribute('aria-label', mode === 'paused' ? 'Resume game' : 'Pause game');
modePill.textContent = mode === 'demo' ? 'AUTOPILOT' : mode === 'paused' ? 'PAUSED' : mode === 'over' ? 'JOURNEY OVER' : 'ALL ABOARD';
}
function startGame(firstDirection) {
mode = 'playing';
resetState();
endScreen.hidden = true;
if (firstDirection) setDirection(firstDirection);
updateUI();
}
function setDirection(d) {
if (!d) return;
if (d.x === -direction.x && d.y === -direction.y) return;
queued = {x:d.x, y:d.y};
}
function togglePause() {
if (mode === 'playing') {
mode = 'paused';
previous = snake.map(p => ({...p}));
endIcon.textContent = '☕';
endTitle.textContent = 'A little rest stop';
endText.textContent = 'Your passengers are enjoying the view.';
endButton.textContent = 'Continue journey';
endScreen.hidden = false;
} else if (mode === 'paused') {
mode = 'playing';
endScreen.hidden = true;
accumulator = 0;
}
updateUI();
}
function gameOver() {
mode = 'over';
previous = snake.map(p => ({...p}));
endIcon.textContent = '🚂';
endTitle.textContent = 'End of the line!';
endText.textContent = `You picked up ${score} ${score === 1 ? 'passenger' : 'passengers'}. Lovely work!`;
endButton.textContent = 'All aboard again';
endScreen.hidden = false;
updateUI();
}
function addPassenger() {
const available = [];
for (let y = 1; y < ROWS - 1; y++) {
for (let x = 1; x < COLS - 1; x++) {
if (!snake.some(p => p.x === x && p.y === y) && !passengers.some(p => p.x === x && p.y === y)) {
available.push({x, y});
}
}
}
if (available.length) {
const p = available[Math.floor(Math.random() * available.length)];
passengers.push({...p, c: Math.floor(Math.random() * passengerColors.length)});
}
}
function demoDirection() {
const head = snake[0];
const blocked = new Set(snake.slice(1, -1).map(p => p.x + ',' + p.y));
const goals = new Set(passengers.map(p => p.x + ',' + p.y));
const seen = new Set([head.x + ',' + head.y]);
const queue = [{x:head.x, y:head.y, first:null}];
const opts = [direction, directions.up, directions.right, directions.down, directions.left];
for (let index = 0; index < queue.length; index++) {
const p = queue[index];
for (const d of opts) {
if (!p.first && d.x === -direction.x && d.y === -direction.y) continue;
const x = p.x + d.x, y = p.y + d.y, key = x + ',' + y;
if (x < 0 || x >= COLS || y < 0 || y >= ROWS || blocked.has(key) || seen.has(key)) continue;
const first = p.first || d;
if (goals.has(key)) return first;
seen.add(key);
queue.push({x, y, first});
}
}
for (const d of opts) {
if (d.x === -direction.x && d.y === -direction.y) continue;
const x = head.x + d.x, y = head.y + d.y;
if (x >= 0 && x < COLS && y >= 0 && y < ROWS && !blocked.has(x + ',' + y)) return d;
}
return direction;
}
function step() {
if (mode === 'demo') queued = demoDirection();
direction = { ...queued };
const head = {x: snake[0].x + direction.x, y: snake[0].y + direction.y};
const foodIndex = passengers.findIndex(p => p.x === head.x && p.y === head.y);
const eating = foodIndex !== -1;
const body = eating ? snake : snake.slice(0, -1);
const crashed = head.x < 0 || head.x >= COLS || head.y < 0 || head.y >= ROWS ||
body.some(p => p.x === head.x && p.y === head.y);
if (crashed) {
if (mode === 'demo') resetState();
else gameOver();
return;
}
previous = snake.map(p => ({...p}));
snake.unshift(head);
if (eating) {
const passenger = passengers.splice(foodIndex, 1)[0];
score++;
effects.push({x:head.x * CELL + 30, y:head.y * CELL + 30, start:performance.now(), color:passengerColors[passenger.c]});
addPassenger();
updateUI();
} else {
snake.pop();
}
}
function roundRect(x, y, w, h, r, fill, stroke, lineWidth = 1) {
ctx.beginPath();
ctx.roundRect(x, y, w, h, r);
if (fill) { ctx.fillStyle = fill; ctx.fill(); }
if (stroke) { ctx.lineWidth = lineWidth; ctx.strokeStyle = stroke; ctx.stroke(); }
}
const flora = [];
let seed = 8324;
function rand() { seed = (seed * 1664525 + 1013904223) >>> 0; return seed / 4294967296; }
for (let i = 0; i < 48; i++) {
flora.push({x: 18 + rand() * 924, y: 20 + rand() * 560, type: Math.floor(rand() * 3), size: 2 + rand() * 2.5});
}
function drawGround() {
ctx.fillStyle = '#dcebd4';
ctx.fillRect(0, 0, 960, 600);
ctx.fillStyle = 'rgba(255,255,248,.12)';
for (let y = 0; y < ROWS; y++) {
for (let x = 0; x < COLS; x++) {
if ((x * 3 + y * 5) % 7 === 0) ctx.fillRect(x * CELL, y * CELL, CELL, CELL);
}
}
ctx.strokeStyle = 'rgba(139,177,139,.16)';
ctx.lineWidth = 1;
ctx.beginPath();
for (let x = 0; x <= COLS; x++) { ctx.moveTo(x * CELL + .5, 0); ctx.lineTo(x * CELL + .5, 600); }
for (let y = 0; y <= ROWS; y++) { ctx.moveTo(0, y * CELL + .5); ctx.lineTo(960, y * CELL + .5); }
ctx.stroke();
for (const f of flora) {
ctx.save();
ctx.translate(f.x, f.y);
if (f.type === 0) {
ctx.strokeStyle = 'rgba(111,157,112,.35)';
ctx.lineWidth = 1.4;
ctx.beginPath(); ctx.moveTo(0, 4); ctx.lineTo(-3, -2); ctx.moveTo(0, 4); ctx.lineTo(3, -3); ctx.stroke();
} else if (f.type === 1) {
ctx.fillStyle = 'rgba(150,190,142,.32)';
ctx.beginPath(); ctx.arc(-3, 1, f.size, 0, Math.PI * 2); ctx.arc(2, -1, f.size + 1, 0, Math.PI * 2); ctx.arc(6, 2, f.size, 0, Math.PI * 2); ctx.fill();
} else {
ctx.fillStyle = 'rgba(255,255,239,.8)';
for (let i = 0; i < 4; i++) {
const a = i * Math.PI / 2;
ctx.beginPath(); ctx.arc(Math.cos(a) * 3, Math.sin(a) * 3, 2, 0, Math.PI * 2); ctx.fill();
}
ctx.fillStyle = '#eac978'; ctx.beginPath(); ctx.arc(0, 0, 1.7, 0, Math.PI * 2); ctx.fill();
}
ctx.restore();
}
roundRect(15, 13, 118, 23, 7, 'rgba(255,255,246,.78)', 'rgba(189,210,180,.7)');
ctx.fillStyle = '#80a786';
ctx.font = 'bold 9px Trebuchet MS, sans-serif';
ctx.letterSpacing = '1px';
ctx.fillText('✿ MEADOW LOOP', 24, 28);
ctx.letterSpacing = '0px';
ctx.fillStyle = 'rgba(112,151,112,.65)';
ctx.font = 'bold 9px Trebuchet MS, sans-serif';
ctx.textAlign = 'right';
ctx.fillText('HAPPY TRAVELS ✳', 940, 579);
ctx.textAlign = 'left';
}
function drawPassenger(p, time) {
const x = p.x * CELL + 30, y = p.y * CELL + 30;
const bounce = Math.sin(time / 350 + p.x) * 1.1;
ctx.save(); ctx.translate(x, y);
ctx.fillStyle = 'rgba(99,130,94,.14)';
roundRect(-27, -21, 54, 49, 7, 'rgba(91,125,91,.16)');
roundRect(-27, -25, 54, 48, 7, '#fff8df', '#d7cfae', 1.5);
roundRect(-24, 13, 48, 7, 2, '#efc56f');
ctx.strokeStyle = 'rgba(185,140,67,.45)'; ctx.lineWidth = 1;
for (let i = -20; i < 23; i += 9) {
ctx.beginPath(); ctx.moveTo(i, 19); ctx.lineTo(i + 5, 13); ctx.stroke();
}
ctx.fillStyle = '#e4ddc8';
roundRect(-22, -21, 44, 3, 1.5, '#e4ddc8');
ctx.save(); ctx.translate(0, bounce);
ctx.strokeStyle = '#71584b'; ctx.lineWidth = 2.5; ctx.lineCap = 'round';
ctx.beginPath(); ctx.moveTo(-4, 8); ctx.lineTo(-5, 13); ctx.moveTo(4, 8); ctx.lineTo(5, 13); ctx.stroke();
ctx.strokeStyle = passengerColors[p.c]; ctx.lineWidth = 3;
ctx.beginPath(); ctx.moveTo(6, -2); ctx.lineTo(11, -7); ctx.moveTo(-6, -2); ctx.lineTo(-10, 3); ctx.stroke();
roundRect(-7.5, -5, 15, 15, 5, passengerColors[p.c]);
ctx.fillStyle = '#edc09d'; ctx.beginPath(); ctx.arc(0, -11, 7, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = '#665044'; ctx.beginPath(); ctx.arc(0, -13, 6.7, Math.PI, Math.PI * 2); ctx.lineTo(6.5, -11); ctx.quadraticCurveTo(2, -12, 0, -15); ctx.quadraticCurveTo(-3, -11, -6.5, -11); ctx.closePath(); ctx.fill();
ctx.fillStyle = '#58463e'; ctx.beginPath(); ctx.arc(-2.3, -10.5, .7, 0, Math.PI * 2); ctx.arc(2.3, -10.5, .7, 0, Math.PI * 2); ctx.fill();
ctx.restore();
ctx.fillStyle = '#fffef5'; ctx.beginPath(); ctx.arc(19, -19, 8, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = '#dfa95a'; ctx.font = 'bold 10px Arial'; ctx.textAlign = 'center'; ctx.fillText('!', 19, -15.5); ctx.textAlign = 'left';
ctx.restore();
}
function drawWheels() {
roundRect(-19, -21, 12, 5, 2.5, '#3f5a4b');
roundRect(7, -21, 12, 5, 2.5, '#3f5a4b');
roundRect(-19, 16, 12, 5, 2.5, '#3f5a4b');
roundRect(7, 16, 12, 5, 2.5, '#3f5a4b');
}
function drawWagon(x, y, angle, index) {
ctx.save(); ctx.translate(x, y); ctx.rotate(angle);
roundRect(-23, -14, 48, 35, 9, 'rgba(58,89,65,.17)');
drawWheels();
roundRect(-24, -17, 48, 34, 9, wagonColors[(index - 1) % wagonColors.length], 'rgba(71,94,72,.24)', 1.3);
roundRect(-19, -12, 38, 24, 6, 'rgba(255,255,247,.30)');
roundRect(-16, -9, 13, 18, 4, 'rgba(255,253,235,.80)');
roundRect(3, -9, 13, 18, 4, 'rgba(255,253,235,.80)');
ctx.fillStyle = 'rgba(101,116,97,.55)';
ctx.beginPath(); ctx.arc(-9.5, -1, 3.4, 0, Math.PI * 2); ctx.arc(9.5, -1, 3.4, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = 'rgba(255,255,255,.35)';
roundRect(-19, -15, 38, 2, 1, 'rgba(255,255,255,.38)');
ctx.restore();
}
function drawEngine(x, y, angle, time) {
ctx.save(); ctx.translate(x, y); ctx.rotate(angle);
const puff = Math.sin(time / 300) * 1.5;
ctx.fillStyle = 'rgba(255,255,250,.65)';
ctx.beginPath(); ctx.arc(-5, -28 + puff, 4, 0, Math.PI * 2); ctx.arc(1, -34 + puff, 5, 0, Math.PI * 2); ctx.arc(8, -38 + puff, 3, 0, Math.PI * 2); ctx.fill();
roundRect(-23, -13, 49, 35, 9, 'rgba(58,89,65,.2)');
drawWheels();
roundRect(-25, -17, 49, 34, 8, '#d96750', '#b95d4b', 1.3);
roundRect(-24, -15, 18, 30, 5, '#efad5d');
roundRect(-20, -11, 10, 9, 3, '#e6f4df');
roundRect(-20, 2, 10, 9, 3, '#e6f4df');
roundRect(-4, -13, 25, 26, 7, '#f18b67');
roundRect(-1, -11, 19, 4, 2, 'rgba(255,205,149,.55)');
ctx.fillStyle = '#526c58'; ctx.beginPath(); ctx.arc(7, 0, 7, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = '#344d40'; ctx.beginPath(); ctx.arc(7, 0, 4.2, 0, Math.PI * 2); ctx.fill();
roundRect(20, -12, 5, 24, 2, '#e4ab5b');
ctx.fillStyle = '#fff3ae'; ctx.beginPath(); ctx.arc(24, 0, 4, 0, Math.PI * 2); ctx.fill();
ctx.restore();
}
function drawEffects(time) {
effects = effects.filter(e => time - e.start < 750);
for (const e of effects) {
const age = (time - e.start) / 750;
ctx.save(); ctx.globalAlpha = 1 - age;
for (let i = 0; i < 8; i++) {
const a = i * Math.PI / 4;
const distance = 13 + age * 32;
ctx.fillStyle = i % 2 ? '#fff9e5' : e.color;
ctx.beginPath(); ctx.arc(e.x + Math.cos(a) * distance, e.y + Math.sin(a) * distance - age * 12, 3.5 * (1 - age * .5), 0, Math.PI * 2); ctx.fill();
}
ctx.fillStyle = '#517d59'; ctx.font = 'bold 15px Trebuchet MS, sans-serif';
ctx.textAlign = 'center'; ctx.fillText('+1', e.x, e.y - 28 - age * 20); ctx.restore();
}
}
function draw(time) {
drawGround();
passengers.forEach(p => drawPassenger(p, time));
const alpha = mode === 'paused' || mode === 'over' ? 1 : Math.min(1, accumulator / SPEED);
const points = snake.map((p, i) => {
const from = previous[Math.min(i, previous.length - 1)] || p;
return {x: (from.x + (p.x - from.x) * alpha) * CELL + 30, y: (from.y + (p.y - from.y) * alpha) * CELL + 30};
});
ctx.save();
ctx.lineCap = 'round'; ctx.lineJoin = 'round';
ctx.beginPath(); points.forEach((p, i) => i ? ctx.lineTo(p.x, p.y) : ctx.moveTo(p.x, p.y));
ctx.strokeStyle = 'rgba(61,91,68,.35)'; ctx.lineWidth = 13; ctx.stroke();
ctx.strokeStyle = '#b7a881'; ctx.lineWidth = 5; ctx.stroke();
ctx.restore();
for (let i = points.length - 1; i >= 1; i--) {
const p = points[i], ahead = points[i - 1];
const angle = Math.atan2(ahead.y - p.y, ahead.x - p.x);
drawWagon(p.x, p.y, angle, i);
}
drawEngine(points[0].x, points[0].y, Math.atan2(direction.y, direction.x), time);
drawEffects(time);
}
function loop(time) {
const delta = Math.min(100, time - lastTime);
lastTime = time;
if (mode === 'demo' || mode === 'playing') {
accumulator += delta;
while (accumulator >= SPEED) {
accumulator -= SPEED;
step();
if (mode !== 'demo' && mode !== 'playing') break;
}
}
draw(time);
requestAnimationFrame(loop);
}
const keyMap = {
ArrowUp: directions.up, w: directions.up, W: directions.up,
ArrowDown: directions.down, s: directions.down, S: directions.down,
ArrowLeft: directions.left, a: directions.left, A: directions.left,
ArrowRight: directions.right, d: directions.right, D: directions.right
};
document.addEventListener('keydown', e => {
if (e.key === 'Tab' || e.ctrlKey || e.metaKey || e.altKey) return;
const d = keyMap[e.key];
if (d || e.key === ' ') e.preventDefault();
if (mode === 'demo') { startGame(d); return; }
if (mode === 'over') { if (d || e.key === ' ' || e.key === 'Enter') startGame(d); return; }
if (e.key === ' ') { togglePause(); return; }
if (mode === 'playing' && d) setDirection(d);
});
stage.addEventListener('click', () => {
if (mode === 'demo') startGame();
});
document.getElementById('restartButton').addEventListener('click', () => startGame());
pauseButton.addEventListener('click', togglePause);
endButton.addEventListener('click', e => {
e.stopPropagation();
if (mode === 'paused') togglePause(); else startGame();
});
document.querySelectorAll('[data-dir]').forEach(button => {
button.addEventListener('click', () => {
const d = directions[button.dataset.dir];
if (mode === 'demo' || mode === 'over') startGame(d);
else if (mode === 'playing') setDirection(d);
});
});
let touchStart = null;
stage.addEventListener('touchstart', e => {
const t = e.changedTouches[0]; touchStart = {x:t.clientX, y:t.clientY};
}, {passive:true});
stage.addEventListener('touchend', e => {
if (!touchStart) return;
const t = e.changedTouches[0], dx = t.clientX - touchStart.x, dy = t.clientY - touchStart.y;
touchStart = null;
if (Math.max(Math.abs(dx), Math.abs(dy)) < 20) return;
e.preventDefault();
const d = Math.abs(dx) > Math.abs(dy) ? (dx > 0 ? directions.right : directions.left) : (dy > 0 ? directions.down : directions.up);
if (mode === 'demo' || mode === 'over') startGame(d);
else if (mode === 'playing') setDirection(d);
}, {passive:false});
resetState();
requestAnimationFrame(loop);
})();
</script>
</body>
</html>Reasoning trace (8,398 tokens)
**Planning interactive game layout** I need to build a complete HTML page for an interactive and polished demo that plays automatically with clicks or key presses. The visuals should be responsive at 1280x800 and 375 without horizontal scrolling. I’m envisioning a colorful snake game with a little train engine picking up passengers at a cheerful train station. The design will incorporate a bright cream background with a blue/green palette and a decorative landscape, including rails and grid elements. Let's keep everything distinctive and clear for easy play.**Designing responsive game layout** I'm focusing on a responsive layout where the header is centered and has a maximum width of 1180. It will include a logo, title, and a button. The main grid of the game will have a structure of 1fr 260, with aspect ratios of 17/12 or 16/11. For the desktop design, I’ll set up the header, intro area, content, and footer to fit nicely. I want a board with a status row, a canvas of 880 x 620, and a snake train moving across a green grid with rail tracks. It’s all about keeping things visually appealing!**Rendering game segments** I need to render each train segment using a canvas and apply rotation based on the orientation of the next segment. The locomotive will have a pointed front with a chimney, headlamp, and train wheels. I want the wagons to be rounded and colorful, complete with windows and little passenger heads. Coupling lines between the segments will connect them visually, and I'll ensure the snake plays in a deterministic, valid way. Passengers waiting on a raised platform will be cute, and they'll be collected when the snake's head reaches their tile!**Figuring out snake movement** I'm working on how to keep the snake's body oriented continuously while storing its positions on a grid. Each tick will involve updating the head's position and the rest of the body based on whether it grows. I need to interpolate the positions to draw smoothly, using the head's direction for orientation and connecting segments accordingly. I want to implement autoplay pathfinding to help it navigate while avoiding collisions. First, I need a game demo rendering a snake with segments and passengers.**Considering game design elements** I'm thinking about the overlay messages for a game, like "YOUR TRAIN IS ON AUTOPILOT" and "Click or press a key to start." It might be better to place the message in the bottom center, and clicking start should set the game mode to play while potentially maintaining the current state. However, should I reset everything for fairness? Maybe a restart button for the user could be helpful. Adding a pause button could also enhance the experience. I’m not sure about the layout yet—it needs to feel balanced!**Exploring art direction** I'm considering the art direction for the project, with a warm off-white paper canvas as the background. For the header, I want it to be 1200 pixels wide, featuring an orange rounded square logo of a train, along with the title "LITTLE JOURNEYS" in uppercase. I might also include "PLATFORM / EXPRESS" for intro. Do I really need a tiny navigation chip that reads "A LITTLE RAILWAY GAME," and should I incorporate sound? I’m unsure about that last part!**Considering layout options** I'm thinking about how to organize the layout. Maybe I could use a light yellow note at the bottom with the tip: "Every passenger adds a wagon." I need to ensure there's enough room on the desktop for balance. A long card might work well for matching height, with a white extension. Perhaps I should also add a separate sidebar card for the score and instructions, using white and yellow for clarity. I want to make the score visuals clear!**Designing wagons** I'm thinking about wagons and their design. The dimensions are 46x34, and I want to include colors like red, yellow, blue, and lilac. For the structure, I'll consider the border and roof. From a top view, I'll show the small windows and maybe the heads of passengers peeking out. For the side perspective, the wagons will look like long, rounded rectangles with darker wheels and light windows arranged in positions to keep it visually appealing.**Evaluating game controls** I'm working on handling the game controls to make sure they function smoothly. I need to avoid a 180° reversal when using the keyboard. Using the arrow keys or WASD should be straightforward, but I need to prevent default actions. The spacebar will pause or resume the game. When starting the demo, pressing any key should activate movement direction. I wonder if clicking on the canvas should trigger the game start as well. There’s a lot to consider here!**Designing for responsiveness** I’m thinking about how to make the layout responsive. On mobile, I can stack elements while using a game canvas width of 343 pixels and a height of 214 pixels, with individual elements around 21 pixels. The main width will be 100%, and I’ll make the title shrink. The D-pad should go right under the board for easy access, and I'll keep the header simple with just text. The sidebar could use a grid with two cards and allow vertical scrolling.**Planning BFS algorithm** I'm considering how to implement a BFS search to find the shortest path to the nearest target. I need to account for visited nodes and check neighboring positions based on current directions. I'll compute the path using BFS while avoiding occupied spaces, except for the tail of the body. For three food items, BFS should return the first direction that leads to a goal. Also, it looks like the food spawn should be based on some random seed for variety.**Planning train artwork** I'm thinking about drawing circles in local coordinates at x -32 and y -10, although the wagon might obscure them. I can add translucent smoke circles above the train, with small white steam dots that have an alpha oscillation for a fun effect. I'll draw them after the car, focusing on just the locomotive head, which I'll color red-orange with dark wheels and a bright front headlamp. The car can be bright blue, ochre, coral, violet, and sage. I need to ensure I get the correct offsets and shadows while using the roundedRect helper for modern Chrome support.**Planning display features** I’m organizing the display style using flex for better layout, starting with messages like "DEMO MODE" and "Click or press a key to start." I need to ensure responsiveness and accessibility, including aria-labels for buttons. I’ll update status indicators, like "DEMO RUNNING" or "NOW BOARDING" based on the game state. For interactions, clicking the board should trigger `startGame()`, and I’ll add pause and restart buttons with appropriate icons and labels while considering visibility and accessibility for users.**Enhancing game mechanics** I'm thinking about adding title labels and how to handle the time loop during pauses to avoid large deltas when unpausing. I'll use a time accumulator to track updates. The rendering loop needs to reset properly with state transitions, calculating the time difference and accumulating it only during demo or play mode. I also need to handle keyboard inputs carefully during the demo, ensuring specific keys like 'r' or arrows don't interfere with game controls. I want it all to function smoothly and intuitively for the user!**Refining user controls** I’m considering the key controls: if the left key is pressed at the start while facing right, it shouldn't allow turning left. The initial action allows any key press to start the game. I want to implement pausing with the spacebar during play and make "R" an optional restart key. Clicking can also pause or restart the game. I’ll add a collision message for manual checks if necessary. Overall, I need to ensure a smooth and clear control experience!**Designing a grassy board** I’m thinking about keeping the decor simple and grassy. I want to add a subtle, repeating grid pattern using a specific formula to fill squares. Also, I plan to make the grid lines quite light to keep focus. I’ll add labels like "MEADOW LOOP" at specific coordinates and perhaps include a small patch with "next stop: anywhere" for readability. Lastly, I want to place a cheerful "HAPPY TRAVELS!" indicator at the bottom. Overall, I want a minimal and clean style.**Planning for a game display** I’m thinking about updating the game’s score and visuals. I’ll set an initial length of 4 and append growth while ensuring the score reflects changes only during specific game logic ticks, not constantly. For visuals, I want to use colorful squares to represent a wagon, possibly with a dynamic mini train made of 5 squares. I should include decorative SVG passengers and a footer with text about the journey. Lastly, I need to output the full HTML without artificial viewport settings.