Train snakeOne-line
GPT-6 Luna
by OpenAIReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 6 min 14 s
- Cost
- $0.015
- Output tokens
- 28,937
- Spent thinking
- 15,081 tokens
- Speed
- 78.4 tok/s
- Console errors
- 0
- First token after
- 4.3 s
- First visible text after
- 3 min 50 s
- Prompt tokens
- 361
- Code size
- 40.3 KB
- Finish reason
- stop
- Served by
- OpenAI
- Model id
openai/gpt-6-luna- Reasoning sent
{"effort":"xhigh"}- Max output
- 64,000 tokens
- Generated
- Nov 10, 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 · 1334 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Little Line — Passenger Train Snake</title>
<style>
:root {
--ink: #173b35;
--muted: #718079;
--cream: #f5f2e8;
--paper: #fffdf7;
--green: #17483f;
--coral: #ef765d;
--gold: #f4bf55;
}
* { box-sizing: border-box; }
html, body { margin: 0; min-height: 100%; }
body {
color: var(--ink);
background:
radial-gradient(ellipse at 8% 0%, rgba(210, 225, 196, .45), transparent 35%),
var(--cream);
font-family: ui-rounded, "SF Pro Rounded", "Segoe UI", system-ui, sans-serif;
}
button { font: inherit; }
.shell {
width: min(1220px, calc(100% - 48px));
margin: 0 auto;
padding: 23px 0 25px;
}
.masthead {
min-height: 61px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 18px;
margin-bottom: 16px;
}
.brand {
display: flex;
align-items: center;
gap: 12px;
min-width: 0;
}
.brand-mark {
display: grid;
place-items: center;
width: 46px;
height: 46px;
flex: 0 0 46px;
border-radius: 15px;
color: #fff7dd;
background: var(--green);
box-shadow: 0 5px 12px rgba(23, 72, 63, .16);
}
.brand-mark svg { width: 31px; height: 31px; }
.brand h1 {
margin: 0;
font-size: 24px;
line-height: 1.05;
letter-spacing: -.8px;
font-weight: 850;
}
.tagline {
display: block;
margin-top: 4px;
color: #7e8980;
font-size: 11px;
font-weight: 600;
letter-spacing: .2px;
}
.header-actions {
display: flex;
align-items: center;
gap: 10px;
}
.mode-badge {
display: flex;
align-items: center;
gap: 8px;
min-height: 36px;
padding: 0 13px;
border: 1px solid #e2e6d9;
border-radius: 999px;
color: #345c4f;
background: rgba(255, 255, 255, .67);
font-size: 10px;
font-weight: 850;
letter-spacing: 1px;
white-space: nowrap;
}
.mode-dot, .hint-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #f3a767;
box-shadow: 0 0 0 4px rgba(243, 167, 103, .14);
animation: blink 1.7s ease-in-out infinite;
}
@keyframes blink { 50% { opacity: .52; } }
.icon-button {
display: grid;
place-items: center;
width: 37px;
height: 37px;
border: 1px solid #e1e5d9;
border-radius: 12px;
color: #45665a;
background: rgba(255, 255, 255, .68);
cursor: pointer;
transition: transform .16s ease, background .16s ease;
}
.icon-button:hover { transform: translateY(-1px); background: white; }
.icon-button svg { width: 17px; height: 17px; }
.layout {
display: grid;
grid-template-columns: minmax(0, 1fr) 302px;
align-items: stretch;
gap: 18px;
}
.canvas-card {
position: relative;
min-width: 0;
overflow: hidden;
border: 1px solid rgba(255,255,255,.9);
border-radius: 23px;
background: #dce8d5;
box-shadow: 0 14px 35px rgba(47, 71, 53, .11), 0 2px 5px rgba(47, 71, 53, .05);
}
canvas {
display: block;
width: 100%;
height: auto;
aspect-ratio: 900 / 640;
cursor: pointer;
touch-action: manipulation;
}
.demo-hint {
position: absolute;
z-index: 2;
top: 12px;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
gap: 10px;
max-width: calc(100% - 24px);
min-height: 35px;
padding: 0 14px;
border: 1px solid rgba(255,255,255,.13);
border-radius: 999px;
color: #fff8e8;
background: rgba(28, 65, 56, .94);
box-shadow: 0 4px 12px rgba(35, 65, 52, .13);
pointer-events: none;
white-space: nowrap;
}
.demo-hint .hint-dot { width: 7px; height: 7px; flex: 0 0 7px; }
.hint-copy { display: flex; align-items: baseline; gap: 8px; }
.hint-copy strong { font-size: 9px; letter-spacing: 1px; }
.hint-copy span { color: #f4ead0; font-size: 11px; font-weight: 600; }
.game-over {
position: absolute;
inset: 0;
z-index: 4;
display: none;
place-items: center;
background: rgba(20, 53, 46, .30);
backdrop-filter: blur(3px);
}
.game-over.visible { display: grid; }
.end-card {
width: min(290px, calc(100% - 36px));
padding: 23px;
border: 1px solid rgba(255,255,255,.8);
border-radius: 21px;
text-align: center;
background: var(--paper);
box-shadow: 0 16px 40px rgba(20,53,46,.2);
}
.end-card .end-icon { font-size: 29px; }
.end-card h2 { margin: 7px 0 4px; font-size: 22px; letter-spacing: -.5px; }
.end-card p { margin: 0 0 16px; color: var(--muted); font-size: 12px; }
.primary-button {
border: 0;
border-radius: 11px;
padding: 10px 16px;
color: #fff8e8;
background: var(--green);
font-size: 12px;
font-weight: 800;
cursor: pointer;
}
.primary-button:hover { background: #236354; }
.sidebar {
display: flex;
min-width: 0;
flex-direction: column;
gap: 12px;
}
.journey-card {
position: relative;
overflow: hidden;
min-height: 191px;
padding: 18px 19px 16px;
border-radius: 21px;
color: #fff9e9;
background: var(--green);
box-shadow: 0 11px 24px rgba(23, 72, 63, .14);
}
.journey-card::after {
content: "";
position: absolute;
width: 152px;
height: 152px;
right: -70px;
top: 49px;
border: 1px solid rgba(255,255,255,.09);
border-radius: 50%;
box-shadow: 0 0 0 17px rgba(255,255,255,.025), 0 0 0 36px rgba(255,255,255,.025);
pointer-events: none;
}
.eyebrow {
color: #93b5a4;
font-size: 9px;
font-weight: 850;
letter-spacing: 1.5px;
text-transform: uppercase;
}
.journey-card h2 {
position: relative;
z-index: 1;
display: flex;
align-items: baseline;
gap: 9px;
margin: 9px 0 0;
font-size: 14px;
font-weight: 650;
letter-spacing: -.2px;
}
.journey-card h2 strong {
color: #ffd278;
font-size: 41px;
font-weight: 850;
line-height: 1;
letter-spacing: -2px;
}
.journey-card h2 span { color: #e3ecdf; }
.stat-rule {
position: relative;
z-index: 1;
height: 1px;
margin: 13px 0 11px;
background: rgba(255,255,255,.15);
}
.mini-stats {
position: relative;
z-index: 1;
display: flex;
align-items: center;
gap: 16px;
}
.mini-stat { display: flex; align-items: center; gap: 7px; color: #dbe9dd; }
.mini-stat svg { width: 16px; height: 16px; color: #f4c365; }
.mini-stat b { font-size: 11px; font-weight: 800; }
.mini-stat small { color: #a9c3b4; font-size: 9px; }
.route-caption {
display: flex;
align-items: center;
gap: 6px;
margin-top: 14px;
color: #b7d0c0;
font-size: 8px;
font-weight: 700;
letter-spacing: .55px;
white-space: nowrap;
}
.route-stop {
display: flex;
align-items: center;
gap: 4px;
opacity: .55;
}
.route-stop i {
display: block;
width: 5px;
height: 5px;
border-radius: 50%;
background: #b9d0be;
}
.route-stop.active { color: #ffcf73; opacity: 1; }
.route-stop.active i { width: 7px; height: 7px; background: #ffcb68; box-shadow: 0 0 0 3px rgba(255,203,104,.16); }
.route-line { width: 10px; height: 1px; background: rgba(215,234,216,.32); }
.stop-card, .controls-card {
border: 1px solid #ebe9dd;
border-radius: 19px;
background: rgba(255,253,247,.91);
box-shadow: 0 5px 16px rgba(49, 68, 51, .045);
}
.stop-card { padding: 16px 17px 14px; }
.stop-topline {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 11px;
}
.stop-topline .eyebrow { color: #879389; }
.stop-count {
padding: 5px 8px;
border-radius: 999px;
color: #a66b31;
background: #fbefd6;
font-size: 8px;
font-weight: 850;
letter-spacing: .65px;
}
.station-row { display: flex; align-items: center; gap: 11px; }
.station-icon {
display: grid;
width: 43px;
height: 43px;
flex: 0 0 43px;
place-items: center;
border-radius: 14px;
color: #356353;
background: #e8f0df;
}
.station-icon svg { width: 25px; height: 25px; }
.station-info { min-width: 0; flex: 1; }
.station-info h3 { overflow: hidden; margin: 0; font-size: 17px; letter-spacing: -.45px; text-overflow: ellipsis; }
.station-info p { margin: 3px 0 0; color: #89938a; font-size: 10px; font-weight: 600; }
.distance { color: #527b66; font-size: 10px; font-weight: 800; white-space: nowrap; }
.progress-track {
overflow: hidden;
height: 5px;
margin-top: 14px;
border-radius: 8px;
background: #e9ece1;
}
.progress-track span {
display: block;
width: 30%;
height: 100%;
border-radius: inherit;
background: linear-gradient(90deg, #ef9b62, #f2c45f);
transition: width .35s ease;
}
.controls-card { padding: 15px 16px; }
.controls-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.controls-head h3 { margin: 0; font-size: 13px; letter-spacing: -.15px; }
.controls-head p { margin: 4px 0 0; color: #89938a; font-size: 9px; line-height: 1.45; }
.key-grid { display: flex; gap: 4px; margin-top: 12px; }
.keycap {
display: grid;
min-width: 25px;
height: 23px;
padding: 0 5px;
place-items: center;
border: 1px solid #e7e6dc;
border-bottom-width: 2px;
border-radius: 6px;
color: #466457;
background: #fffefa;
font-size: 9px;
font-weight: 850;
}
.controls-bottom {
display: flex;
align-items: center;
justify-content: space-between;
gap: 7px;
margin-top: 12px;
}
.take-control {
min-height: 31px;
padding: 0 10px;
border: 0;
border-radius: 9px;
color: #fff8e7;
background: #275d4e;
font-size: 9px;
font-weight: 800;
cursor: pointer;
transition: background .15s ease, transform .15s ease;
}
.take-control:hover { background: #34725f; transform: translateY(-1px); }
.dpad { display: grid; grid-template-columns: repeat(3, 25px); grid-template-rows: repeat(2, 22px); gap: 2px; }
.dpad button {
display: grid;
place-items: center;
padding: 0;
border: 1px solid #e4e6dc;
border-radius: 6px;
color: #557264;
background: #f8f8f0;
font-size: 12px;
line-height: 1;
cursor: pointer;
}
.dpad button:hover { color: #fff; border-color: #58836c; background: #58836c; }
.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; }
.sidebar-note {
margin: auto 2px 1px;
color: #879188;
font-size: 9px;
font-weight: 600;
letter-spacing: .15px;
}
@media (max-width: 880px) {
.shell { width: min(760px, calc(100% - 36px)); }
.layout { grid-template-columns: minmax(0, 1fr); }
.sidebar { display: grid; grid-template-columns: 1fr 1fr; align-items: stretch; }
.journey-card { grid-row: span 2; }
.sidebar-note { grid-column: 1 / -1; margin-top: 1px; }
}
@media (max-width: 560px) {
.shell { width: 100%; padding: 12px 12px 20px; }
.masthead { min-height: 48px; margin-bottom: 12px; gap: 8px; }
.brand { gap: 9px; }
.brand-mark { width: 39px; height: 39px; flex-basis: 39px; border-radius: 13px; }
.brand-mark svg { width: 27px; height: 27px; }
.brand h1 { font-size: 21px; }
.tagline { display: none; }
.header-actions { gap: 6px; }
.mode-badge { min-height: 31px; gap: 7px; padding: 0 9px; font-size: 8px; letter-spacing: .7px; }
.mode-dot { width: 7px; height: 7px; }
.icon-button { width: 33px; height: 33px; border-radius: 10px; }
.layout { gap: 12px; }
.canvas-card { border-radius: 17px; }
.demo-hint { top: 7px; min-height: 30px; gap: 7px; padding: 0 10px; }
.hint-copy { gap: 6px; }
.hint-copy strong { font-size: 8px; }
.hint-copy span { font-size: 9px; }
.sidebar { display: flex; gap: 10px; }
.journey-card { min-height: 183px; padding: 17px 18px 15px; }
.stop-card { padding: 15px 16px 13px; }
.controls-card { padding: 14px 15px; }
.sidebar-note { margin: 1px 2px 0; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}
</style>
</head>
<body>
<div class="shell">
<header class="masthead">
<div class="brand">
<div class="brand-mark" aria-hidden="true">
<svg viewBox="0 0 40 40" fill="none">
<path d="M9 22.5h22v8H9z" fill="#f6bd55"/>
<path d="M13 13h14a4 4 0 0 1 4 4v8H9v-8a4 4 0 0 1 4-4Z" fill="#ef765d"/>
<path d="M14 15.5h5v5h-5zM22 15.5h5v5h-5z" fill="#dff0d9"/>
<path d="M16 9h8v4h-8z" fill="#f6bd55"/>
<circle cx="14" cy="31" r="3" fill="#fff8df"/>
<circle cx="27" cy="31" r="3" fill="#fff8df"/>
<path d="M7 35h26" stroke="#fff8df" stroke-width="2" stroke-linecap="round"/>
</svg>
</div>
<div>
<h1>Little Line</h1>
<span class="tagline">A pocket-sized passenger railway</span>
</div>
</div>
<div class="header-actions">
<div class="mode-badge"><span class="mode-dot"></span><span id="modeLabel">AUTO PILOT</span></div>
<button class="icon-button" id="restartButton" aria-label="Restart train" title="Restart train">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 7v5h-5M19 12a7 7 0 1 1-2-5"/>
</svg>
</button>
</div>
</header>
<main class="layout">
<section class="canvas-card" aria-label="Little Line train game">
<canvas id="gameCanvas" width="900" height="640" aria-label="A colorful little train picks up passengers at garden platforms"></canvas>
<div class="demo-hint" id="demoHint">
<span class="hint-dot"></span>
<span class="hint-copy"><strong>DEMO TRAIN</strong><span>Click or press a key to start</span></span>
</div>
<div class="game-over" id="gameOver">
<div class="end-card">
<div class="end-icon">🚂</div>
<h2>End of the line!</h2>
<p>Your passengers made quite a journey.</p>
<button class="primary-button" id="againButton">Back on track</button>
</div>
</div>
</section>
<aside class="sidebar">
<section class="journey-card">
<div class="eyebrow">Your little line</div>
<h2><strong id="riderCount">0</strong><span>passengers aboard</span></h2>
<div class="stat-rule"></div>
<div class="mini-stats">
<div class="mini-stat">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round">
<path d="M3 16h18M5 16V9h14v7M8 9V6h8v3"/>
<circle cx="7" cy="18" r="2"/><circle cx="17" cy="18" r="2"/>
</svg>
<b id="wagonCount">4</b><small>wagons</small>
</div>
<div class="mini-stat">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round">
<path d="M4 19V7m0 0 3 3m-3-3L1 10M10 19V5m0 0 3 3m-3-3L7 8M17 19V8m0 0 3 3m-3-3-3 3"/>
</svg>
<b>01</b><small>garden route</small>
</div>
</div>
<div class="route-caption" aria-label="Route stops">
<span class="route-stop active"><i></i>MEADOW</span><span class="route-line"></span>
<span class="route-stop"><i></i>RIVER</span><span class="route-line"></span>
<span class="route-stop"><i></i>MARKET</span><span class="route-line"></span>
<span class="route-stop"><i></i>ORCHARD</span>
</div>
</section>
<section class="stop-card">
<div class="stop-topline">
<span class="eyebrow">Next pick-up</span>
<span class="stop-count">1 WAITING</span>
</div>
<div class="station-row">
<div class="station-icon" aria-hidden="true">
<svg viewBox="0 0 32 32" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 25h22M8 25V11h16v14M11 11V7h10v4M11 16h4v4h-4zM18 16h4v4h-4z"/>
<path d="M4 27h24M13 25v2m6-2v2"/>
</svg>
</div>
<div class="station-info">
<h3 id="nextStation">Meadow</h3>
<p>A little traveler is waiting</p>
</div>
<div class="distance" id="distanceLabel">— tiles</div>
</div>
<div class="progress-track"><span id="progressBar"></span></div>
</section>
<section class="controls-card">
<div class="controls-head">
<div>
<h3>Conductor controls</h3>
<p>Steer between stops and collect every rider.</p>
</div>
</div>
<div class="key-grid" aria-label="Keyboard controls">
<span class="keycap">↑</span><span class="keycap">W</span>
<span class="keycap">←</span><span class="keycap">A</span>
<span class="keycap">↓</span><span class="keycap">S</span>
<span class="keycap">→</span><span class="keycap">D</span>
</div>
<div class="controls-bottom">
<button class="take-control" id="takeControl">Take the controls</button>
<div class="dpad" aria-label="Touch controls">
<button class="up" data-dir="up" aria-label="Up">↑</button>
<button class="left" data-dir="left" aria-label="Left">←</button>
<button class="down" data-dir="down" aria-label="Down">↓</button>
<button class="right" data-dir="right" aria-label="Right">→</button>
</div>
</div>
</section>
<p class="sidebar-note">Mind the gap. Make someone’s day. 🌼</p>
</aside>
</main>
</div>
<script>
(() => {
"use strict";
const canvas = document.getElementById("gameCanvas");
const ctx = canvas.getContext("2d");
const demoHint = document.getElementById("demoHint");
const gameOverPanel = document.getElementById("gameOver");
const stations = [
{ name: "MEADOW", display: "Meadow", x: 5, y: 3, px: 2, py: 2, color: "#ec8174" },
{ name: "RIVER", display: "River", x: 20, y: 3, px: 17, py: 2, color: "#77acd0" },
{ name: "MARKET", display: "Market", x: 20, y: 12, px: 17, py: 11, color: "#e7ad50" },
{ name: "ORCHARD", display: "Orchard", x: 5, y: 12, px: 2, py: 11, color: "#aa8ccd" }
];
const W = 900, H = 640, CELL = 35, COLS = 24, ROWS = 15;
const BOARD_X = 30, BOARD_Y = 68;
const palette = ["#4c9ca0", "#e8ab4f", "#b985c4", "#74ad78", "#e78470", "#7795c8", "#d69a6e"];
let dpr = 1;
let snake = [];
let oldSnake = [];
let direction = { x: 1, y: 0 };
let queuedDirection = { x: 1, y: 0 };
let stationIndex = 0;
let riders = 0;
let demoMode = true;
let gameOver = false;
let routePath = [];
let accumulator = 0;
let lastTime = performance.now();
const STEP_MS = 184;
function resizeCanvas() {
dpr = Math.min(window.devicePixelRatio || 1, 2);
canvas.width = W * dpr;
canvas.height = H * dpr;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
render(0, performance.now());
}
if ("ResizeObserver" in window) {
new ResizeObserver(resizeCanvas).observe(canvas.parentElement);
} else {
window.addEventListener("resize", resizeCanvas);
}
function roundedRect(c, x, y, w, h, r, fill, stroke, lineWidth = 1) {
const radius = Math.max(0, Math.min(r, w / 2, h / 2));
c.beginPath();
c.moveTo(x + radius, y);
c.lineTo(x + w - radius, y);
c.quadraticCurveTo(x + w, y, x + w, y + radius);
c.lineTo(x + w, y + h - radius);
c.quadraticCurveTo(x + w, y + h, x + w - radius, y + h);
c.lineTo(x + radius, y + h);
c.quadraticCurveTo(x, y + h, x, y + h - radius);
c.lineTo(x, y + radius);
c.quadraticCurveTo(x, y, x + radius, y);
c.closePath();
if (fill) { c.fillStyle = fill; c.fill(); }
if (stroke) { c.lineWidth = lineWidth; c.strokeStyle = stroke; c.stroke(); }
}
function cellCenter(pos) {
return { x: BOARD_X + (pos.x + .5) * CELL, y: BOARD_Y + (pos.y + .5) * CELL };
}
function keyOf(x, y) { return x + "," + y; }
function findPath() {
if (!snake.length) return [];
const start = snake[0];
const target = stations[stationIndex];
if (start.x === target.x && start.y === target.y) return [];
const blocked = new Set();
for (let i = 1; i < snake.length - 1; i++) blocked.add(keyOf(snake[i].x, snake[i].y));
const startKey = keyOf(start.x, start.y);
const targetKey = keyOf(target.x, target.y);
const queue = [start];
const parents = new Map([[startKey, null]]);
const moves = [{ x: 0, y: -1 }, { x: -1, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 0 }];
let found = false;
for (let qi = 0; qi < queue.length && !found; qi++) {
const point = queue[qi];
for (const move of moves) {
const nx = point.x + move.x, ny = point.y + move.y;
const key = keyOf(nx, ny);
if (nx < 0 || nx >= COLS || ny < 0 || ny >= ROWS || parents.has(key) || blocked.has(key)) continue;
parents.set(key, keyOf(point.x, point.y));
if (key === targetKey) { found = true; break; }
queue.push({ x: nx, y: ny });
}
}
if (!found) return [];
const path = [];
let cursor = targetKey;
while (cursor !== startKey) {
const [x, y] = cursor.split(",").map(Number);
path.push({ x, y });
cursor = parents.get(cursor);
if (cursor == null) return [];
}
path.reverse();
return path;
}
function opposite(a, b) { return a.x === -b.x && a.y === -b.y; }
function chooseAIMove() {
routePath = findPath();
const head = snake[0];
if (routePath.length) {
const next = routePath[0];
const move = { x: next.x - head.x, y: next.y - head.y };
if (!opposite(move, direction)) return move;
}
const options = [
{ x: 0, y: -1 }, { x: 1, y: 0 },
{ x: 0, y: 1 }, { x: -1, y: 0 }
].filter(move => {
if (opposite(move, direction)) return false;
const x = head.x + move.x, y = head.y + move.y;
if (x < 0 || x >= COLS || y < 0 || y >= ROWS) return false;
return !snake.slice(0, -1).some(part => part.x === x && part.y === y);
});
const target = stations[stationIndex];
options.sort((a, b) => {
const da = Math.abs(head.x + a.x - target.x) + Math.abs(head.y + a.y - target.y);
const db = Math.abs(head.x + b.x - target.x) + Math.abs(head.y + b.y - target.y);
return da - db;
});
return options[0] || direction;
}
function updateUI() {
document.getElementById("riderCount").textContent = riders;
document.getElementById("wagonCount").textContent = Math.max(0, snake.length - 1);
document.getElementById("nextStation").textContent = stations[stationIndex].display;
document.getElementById("modeLabel").textContent = demoMode ? "AUTO PILOT" : "YOUR RUN";
const head = snake[0] || { x: 0, y: 0 };
const target = stations[stationIndex];
const distance = Math.abs(head.x - target.x) + Math.abs(head.y - target.y);
document.getElementById("distanceLabel").textContent = distance + " tiles";
const progress = Math.max(9, Math.min(94, 94 - distance * 3.3));
document.getElementById("progressBar").style.width = progress + "%";
document.querySelectorAll(".route-stop").forEach((el, i) => {
el.classList.toggle("active", i === stationIndex);
});
}
function resetGame(asDemo = true) {
snake = [
{ x: 10, y: 8 }, { x: 9, y: 8 }, { x: 8, y: 8 },
{ x: 7, y: 8 }, { x: 6, y: 8 }
];
oldSnake = snake.map(p => ({ ...p }));
direction = { x: 1, y: 0 };
queuedDirection = { ...direction };
stationIndex = 0;
riders = 0;
demoMode = asDemo;
gameOver = false;
accumulator = 0;
routePath = findPath();
gameOverPanel.classList.remove("visible");
demoHint.style.display = demoMode ? "flex" : "none";
updateUI();
}
function startManual() {
if (gameOver || !demoMode) return;
demoMode = false;
demoHint.style.display = "none";
routePath = [];
updateUI();
}
function changeDirection(move) {
if (gameOver) return;
startManual();
if (opposite(move, direction) || opposite(move, queuedDirection)) return;
queuedDirection = move;
}
function stepGame() {
if (gameOver) return;
const move = demoMode ? chooseAIMove() : queuedDirection;
direction = move;
const head = snake[0];
const next = { x: head.x + move.x, y: head.y + move.y };
const target = stations[stationIndex];
const willBoard = next.x === target.x && next.y === target.y;
const outOfBounds = next.x < 0 || next.x >= COLS || next.y < 0 || next.y >= ROWS;
const collisionLength = willBoard ? snake.length : snake.length - 1;
const hitsTrain = snake.slice(0, collisionLength).some(part => part.x === next.x && part.y === next.y);
if (outOfBounds || hitsTrain) {
if (demoMode) {
const fallback = [
{ x: 0, y: -1 }, { x: 1, y: 0 },
{ x: 0, y: 1 }, { x: -1, y: 0 }
].find(m => {
if (opposite(m, direction)) return false;
const x = head.x + m.x, y = head.y + m.y;
return x >= 0 && x < COLS && y >= 0 && y < ROWS &&
!snake.slice(0, -1).some(p => p.x === x && p.y === y);
});
if (!fallback) { resetGame(true); return; }
direction = fallback;
queuedDirection = fallback;
next.x = head.x + fallback.x;
next.y = head.y + fallback.y;
} else {
gameOver = true;
gameOverPanel.classList.add("visible");
return;
}
}
oldSnake = snake.map(p => ({ ...p }));
snake.unshift(next);
if (willBoard) {
riders += 1;
if (snake.length > 13) snake.pop();
for (let offset = 1; offset <= stations.length; offset++) {
const candidate = (stationIndex + offset) % stations.length;
const stop = stations[candidate];
const occupied = snake.some(p => p.x === stop.x && p.y === stop.y);
if (!occupied) { stationIndex = candidate; break; }
}
} else {
snake.pop();
}
if (demoMode) routePath = findPath();
updateUI();
}
function drawFlower(x, y, scale = 1) {
const px = BOARD_X + (x + .5) * CELL;
const py = BOARD_Y + (y + .5) * CELL;
ctx.save();
ctx.globalAlpha = .52;
ctx.fillStyle = "#c4d9bb";
ctx.beginPath();
ctx.ellipse(px, py + 3, 8 * scale, 4 * scale, -.25, 0, Math.PI * 2);
ctx.fill();
const colors = ["#f4c477", "#e8a398", "#f0d58a"];
for (let i = 0; i < 4; i++) {
const a = i * Math.PI / 2;
ctx.beginPath();
ctx.arc(px + Math.cos(a) * 3.5 * scale, py + Math.sin(a) * 3.5 * scale, 1.9 * scale, 0, Math.PI * 2);
ctx.fillStyle = colors[(Math.floor(x + y) + i) % colors.length];
ctx.fill();
}
ctx.beginPath();
ctx.arc(px, py, 1.5 * scale, 0, Math.PI * 2);
ctx.fillStyle = "#f9efd0";
ctx.fill();
ctx.restore();
}
function drawPlatform(station) {
const x = BOARD_X + station.px * CELL;
const y = BOARD_Y + station.py * CELL;
const w = 5 * CELL;
const h = 2 * CELL;
roundedRect(ctx, x + 1, y + 3, w, h, 9, "rgba(55,74,51,.15)");
roundedRect(ctx, x, y, w, h, 9, "#e8d8ad", "#ad9c6e", 1);
roundedRect(ctx, x + 3, y + 3, w - 6, h - 6, 7, "#f1e7c9");
ctx.save();
ctx.beginPath();
ctx.roundRect(x + 3, y + 3, w - 6, h - 6, 7);
ctx.clip();
for (let i = 0; i < 5; i++) {
const sx = x + i * CELL + 7;
ctx.beginPath();
ctx.moveTo(sx, y + 5);
ctx.lineTo(sx, y + h - 5);
ctx.strokeStyle = i % 2 ? "rgba(157,135,84,.15)" : "rgba(157,135,84,.24)";
ctx.lineWidth = 1;
ctx.stroke();
}
ctx.fillStyle = "rgba(202,180,128,.48)";
ctx.fillRect(x + 4, y + h - 10, w - 8, 3);
roundedRect(ctx, x + 7, y + 7, 69, 18, 5, "#48715e");
ctx.beginPath();
ctx.arc(x + 16, y + 16, 3, 0, Math.PI * 2);
ctx.fillStyle = station.color;
ctx.fill();
ctx.fillStyle = "#fff4d9";
ctx.font = "800 8px ui-rounded, system-ui, sans-serif";
ctx.textAlign = "left";
ctx.textBaseline = "middle";
ctx.fillText(station.name, x + 23, y + 16);
// A little timber bench at the quiet end of each platform.
const bx = x + w - 32, by = y + 12;
roundedRect(ctx, bx, by, 22, 5, 2, "#b38b50");
roundedRect(ctx, bx + 2, by + 8, 18, 4, 2, "#d0ae6d");
ctx.fillStyle = "#8c774d";
ctx.fillRect(bx + 3, by + 5, 2, 5);
ctx.fillRect(bx + 17, by + 5, 2, 5);
ctx.restore();
}
function drawPassenger(station, active, time) {
const p = cellCenter(station);
const pulse = 1 + Math.sin(time / 280) * .06;
if (active) {
ctx.save();
ctx.globalAlpha = .15 + (Math.sin(time / 260) + 1) * .07;
ctx.beginPath();
ctx.arc(p.x, p.y, CELL * .48 * pulse, 0, Math.PI * 2);
ctx.fillStyle = "#f3bd58";
ctx.fill();
ctx.restore();
ctx.save();
ctx.setLineDash([3, 3]);
ctx.lineWidth = 1.4;
ctx.strokeStyle = "#b9873b";
ctx.beginPath();
ctx.arc(p.x, p.y, CELL * .43 * pulse, 0, Math.PI * 2);
ctx.stroke();
ctx.restore();
}
ctx.save();
ctx.translate(p.x, p.y);
ctx.fillStyle = "rgba(54,67,45,.17)";
ctx.beginPath();
ctx.ellipse(1, 8, 10, 4, 0, 0, Math.PI * 2);
ctx.fill();
// Small travel bag
roundedRect(ctx, 7, 3, 7, 8, 2, "#b88954", "#96713e", .7);
ctx.beginPath();
ctx.arc(10.5, 3.2, 2.1, Math.PI, Math.PI * 2);
ctx.strokeStyle = "#96713e";
ctx.lineWidth = 1;
ctx.stroke();
// Feet, coat and head
roundedRect(ctx, -6, 5, 4, 6, 1.5, "#6e6655");
roundedRect(ctx, 1, 5, 4, 6, 1.5, "#6e6655");
roundedRect(ctx, -7, -4, 13, 12, 4, station.color, "#fff5dc", .8);
ctx.beginPath();
ctx.arc(-.5, -7, 5.2, 0, Math.PI * 2);
ctx.fillStyle = "#e7bb91";
ctx.fill();
ctx.beginPath();
ctx.arc(-.5, -9.1, 5.1, Math.PI, Math.PI * 2);
ctx.fillStyle = active ? "#526b50" : "#745d4c";
ctx.fill();
ctx.restore();
}
function drawRoute() {
if (!demoMode || !routePath.length) return;
const head = cellCenter(snake[0]);
ctx.save();
ctx.beginPath();
ctx.moveTo(head.x, head.y);
for (const point of routePath) {
const p = cellCenter(point);
ctx.lineTo(p.x, p.y);
}
ctx.strokeStyle = "rgba(86,132,105,.24)";
ctx.lineWidth = 2;
ctx.setLineDash([2, 7]);
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.stroke();
ctx.setLineDash([]);
for (let i = 0; i < routePath.length; i += 3) {
const p = cellCenter(routePath[i]);
ctx.beginPath();
ctx.arc(p.x, p.y, 2, 0, Math.PI * 2);
ctx.fillStyle = "rgba(76,125,98,.38)";
ctx.fill();
}
ctx.restore();
}
function drawRail(center, angle) {
ctx.save();
ctx.translate(center.x, center.y);
ctx.rotate(angle);
ctx.lineCap = "round";
ctx.strokeStyle = "#8b9078";
ctx.lineWidth = 2;
for (const y of [-CELL * .29, CELL * .29]) {
ctx.beginPath();
ctx.moveTo(-CELL * .48, y);
ctx.lineTo(CELL * .48, y);
ctx.stroke();
}
ctx.strokeStyle = "#9a8059";
ctx.lineWidth = 2.1;
for (const x of [-CELL * .36, 0, CELL * .36]) {
ctx.beginPath();
ctx.moveTo(x, -CELL * .35);
ctx.lineTo(x, CELL * .35);
ctx.stroke();
}
ctx.restore();
}
function drawWagon(center, angle, color, index) {
ctx.save();
ctx.translate(center.x, center.y);
ctx.rotate(angle);
// Wheels peek out around the bright passenger coach.
for (const x of [-CELL * .23, CELL * .23]) {
for (const y of [-CELL * .30, CELL * .30]) {
ctx.beginPath();
ctx.arc(x, y, CELL * .105, 0, Math.PI * 2);
ctx.fillStyle = "#315249";
ctx.fill();
ctx.beginPath();
ctx.arc(x, y, CELL * .047, 0, Math.PI * 2);
ctx.fillStyle = "#e8c875";
ctx.fill();
}
}
roundedRect(ctx, -CELL * .45, -CELL * .27, CELL * .9, CELL * .54, CELL * .13, "#355a4b");
roundedRect(ctx, -CELL * .42, -CELL * .30, CELL * .84, CELL * .52, CELL * .14, color, "#f8e4a8", 1);
roundedRect(ctx, -CELL * .34, -CELL * .23, CELL * .68, CELL * .39, CELL * .11, "rgba(255,248,221,.19)");
for (const x of [-CELL * .21, CELL * .10]) {
roundedRect(ctx, x, -CELL * .15, CELL * .20, CELL * .22, 3, "#e6f0d9", "rgba(49,83,69,.35)", .7);
ctx.beginPath();
ctx.arc(x + CELL * .10, -CELL * .05, 2.2, 0, Math.PI * 2);
ctx.fillStyle = index % 2 ? "#e8b778" : "#e4a18f";
ctx.fill();
}
ctx.fillStyle = "rgba(255,244,202,.72)";
ctx.fillRect(-CELL * .30, CELL * .13, CELL * .60, 1.4);
ctx.restore();
}
function drawLocomotive(center, angle, time) {
ctx.save();
ctx.translate(center.x, center.y);
ctx.rotate(angle);
for (const x of [-CELL * .22, CELL * .22]) {
for (const y of [-CELL * .31, CELL * .31]) {
ctx.beginPath();
ctx.arc(x, y, CELL * .11, 0, Math.PI * 2);
ctx.fillStyle = "#315249";
ctx.fill();
ctx.beginPath();
ctx.arc(x, y, CELL * .05, 0, Math.PI * 2);
ctx.fillStyle = "#efc65f";
ctx.fill();
}
}
roundedRect(ctx, -CELL * .45, -CELL * .27, CELL * .88, CELL * .54, CELL * .13, "#355a4b");
roundedRect(ctx, -CELL * .43, -CELL * .30, CELL * .32, CELL * .52, 5, "#347564", "#f4cc6b", 1);
roundedRect(ctx, -CELL * .37, -CELL * .24, CELL * .18, CELL * .19, 3, "#dcebd3");
roundedRect(ctx, -CELL * .13, -CELL * .23, CELL * .57, CELL * .43, CELL * .18, "#ed765e", "#ffdfa0", 1);
roundedRect(ctx, -CELL * .04, -CELL * .17, CELL * .42, CELL * .32, CELL * .14, "#f28b67");
ctx.fillStyle = "#f5cc69";
ctx.fillRect(-CELL * .07, CELL * .12, CELL * .46, 2.3);
// Chimney and bright headlamp, at the front of the little engine.
roundedRect(ctx, CELL * .23, -CELL * .25, CELL * .18, CELL * .17, 3, "#31564a", "#eac86f", .8);
roundedRect(ctx, CELL * .20, -CELL * .29, CELL * .24, CELL * .07, 2, "#31564a");
ctx.beginPath();
ctx.arc(CELL * .44, 0, CELL * .105, 0, Math.PI * 2);
ctx.fillStyle = "#e8bd57";
ctx.fill();
ctx.beginPath();
ctx.arc(CELL * .45, 0, CELL * .055, 0, Math.PI * 2);
ctx.fillStyle = "#fff4cc";
ctx.fill();
const steam = (Math.sin(time / 190) + 1) / 2;
ctx.globalAlpha = .28 + steam * .16;
ctx.fillStyle = "#fff8e6";
ctx.beginPath();
ctx.arc(CELL * .51 + steam * 2, -CELL * .27 - steam * 4, CELL * .10, 0, Math.PI * 2);
ctx.arc(CELL * .60 + steam * 3, -CELL * .34 - steam * 3, CELL * .075, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
function drawTrain(progress, time) {
if (!snake.length) return;
const positions = snake.map((part, i) => {
const from = oldSnake[Math.min(i, oldSnake.length - 1)] || part;
return {
x: from.x + (part.x - from.x) * progress,
y: from.y + (part.y - from.y) * progress
};
});
// Draw the rails first, so the coaches sit neatly on top.
positions.forEach((pos, i) => {
let angle = Math.atan2(direction.y, direction.x);
if (i > 0 && snake[i - 1]) {
const dx = snake[i - 1].x - snake[i].x;
const dy = snake[i - 1].y - snake[i].y;
if (dx || dy) angle = Math.atan2(dy, dx);
}
drawRail(cellCenter(pos), angle);
});
for (let i = positions.length - 1; i >= 0; i--) {
const pos = cellCenter(positions[i]);
let angle = Math.atan2(direction.y, direction.x);
if (i > 0 && snake[i - 1]) {
const dx = snake[i - 1].x - snake[i].x;
const dy = snake[i - 1].y - snake[i].y;
if (dx || dy) angle = Math.atan2(dy, dx);
}
if (i === 0) drawLocomotive(pos, angle, time);
else drawWagon(pos, angle, palette[(i - 1) % palette.length], i);
}
}
function drawBoard(time) {
ctx.clearRect(0, 0, W, H);
ctx.fillStyle = "#e5eddf";
ctx.fillRect(0, 0, W, H);
// Header strip on the game board.
ctx.fillStyle = "#eaf0e5";
ctx.fillRect(0, 0, W, 58);
ctx.beginPath();
ctx.arc(39, 29, 11, 0, Math.PI * 2);
ctx.fillStyle = "#47745e";
ctx.fill();
ctx.beginPath();
ctx.arc(39, 29, 4, 0, Math.PI * 2);
ctx.fillStyle = "#f4c565";
ctx.fill();
ctx.textAlign = "left";
ctx.textBaseline = "middle";
ctx.fillStyle = "#416652";
ctx.font = "800 10px ui-rounded, system-ui, sans-serif";
ctx.fillText("GARDEN DISTRICT", 57, 25);
ctx.fillStyle = "#829184";
ctx.font = "600 8px ui-rounded, system-ui, sans-serif";
ctx.fillText("A LITTLE RAILWAY ADVENTURE", 57, 38);
roundedRect(ctx, 782, 18, 88, 22, 11, "#dce8d5");
ctx.beginPath();
ctx.arc(795, 29, 3, 0, Math.PI * 2);
ctx.fillStyle = "#ee9168";
ctx.fill();
ctx.fillStyle = "#54745f";
ctx.font = "800 8px ui-rounded, system-ui, sans-serif";
ctx.textAlign = "left";
ctx.fillText("LINE 01", 803, 29);
const boardW = COLS * CELL, boardH = ROWS * CELL;
roundedRect(ctx, BOARD_X, BOARD_Y, boardW, boardH, 15, "#d6e4d0", "#c6d6c1", 1);
ctx.save();
ctx.beginPath();
ctx.roundRect(BOARD_X, BOARD_Y, boardW, boardH, 15);
ctx.clip();
// Soft, tidy garden tiles.
for (let y = 0; y < ROWS; y++) {
for (let x = 0; x < COLS; x++) {
const shade = ((x * 5 + y * 3) % 4 === 0) ? "#dce9d5" : ((x + y) % 3 === 0 ? "#d9e7d2" : "#d7e5d0");
ctx.fillStyle = shade;
ctx.fillRect(BOARD_X + x * CELL, BOARD_Y + y * CELL, CELL, CELL);
}
}
ctx.strokeStyle = "rgba(114,145,112,.10)";
ctx.lineWidth = 1;
for (let x = 0; x <= COLS; x++) {
ctx.beginPath();
ctx.moveTo(BOARD_X + x * CELL, BOARD_Y);
ctx.lineTo(BOARD_X + x * CELL, BOARD_Y + boardH);
ctx.stroke();
}
for (let y = 0; y <= ROWS; y++) {
ctx.beginPath();
ctx.moveTo(BOARD_X, BOARD_Y + y * CELL);
ctx.lineTo(BOARD_X + boardW, BOARD_Y + y * CELL);
ctx.stroke();
}
[
[9, 2, 1], [13, 3, .8], [10, 5, .7], [14, 7, 1],
[3, 7, .75], [20, 7, .85], [9, 10, .7], [13, 13, .9],
[10, 1, .65], [14, 12, .7], [8, 6, .65], [15, 5, .7]
].forEach(f => drawFlower(f[0], f[1], f[2]));
stations.forEach(drawPlatform);
drawRoute();
stations.forEach((station, i) => drawPassenger(station, i === stationIndex, time));
drawTrain(Math.min(1, progressForFrame), time);
ctx.restore();
// Quiet border around the playfield.
roundedRect(ctx, BOARD_X, BOARD_Y, boardW, boardH, 15, null, "#c2d3bd", 1);
ctx.textBaseline = "middle";
ctx.textAlign = "left";
ctx.fillStyle = "#758778";
ctx.font = "700 8px ui-rounded, system-ui, sans-serif";
ctx.fillText("FOLLOW THE RAIL · PICK UP THE WAITING RIDERS", 31, 618);
ctx.textAlign = "right";
ctx.fillStyle = "#789080";
ctx.font = "700 8px ui-rounded, system-ui, sans-serif";
ctx.fillText(demoMode ? "AUTOPILOT IS DRIVING" : "ARROWS / WASD TO STEER", 869, 618);
}
let progressForFrame = 0;
function render(progress = 0, time = performance.now()) {
progressForFrame = Math.max(0, Math.min(1, progress));
drawBoard(time);
}
function animationFrame(now) {
const elapsed = Math.min(60, now - lastTime);
lastTime = now;
if (!gameOver) {
accumulator += elapsed;
while (accumulator >= STEP_MS) {
stepGame();
accumulator -= STEP_MS;
}
}
render(accumulator / STEP_MS, now);
requestAnimationFrame(animationFrame);
}
const directions = {
ArrowUp: { x: 0, y: -1 }, w: { x: 0, y: -1 },
ArrowDown: { x: 0, y: 1 }, s: { x: 0, y: 1 },
ArrowLeft: { x: -1, y: 0 }, a: { x: -1, y: 0 },
ArrowRight: { x: 1, y: 0 }, d: { x: 1, y: 0 }
};
window.addEventListener("keydown", event => {
const move = directions[event.key] || directions[event.key.toLowerCase()];
if (move) {
event.preventDefault();
changeDirection(move);
} else if (demoMode && event.key !== "Tab") {
startManual();
}
});
canvas.addEventListener("click", startManual);
document.getElementById("takeControl").addEventListener("click", startManual);
document.getElementById("restartButton").addEventListener("click", () => resetGame(true));
document.getElementById("againButton").addEventListener("click", () => resetGame(false));
document.querySelectorAll(".dpad button").forEach(button => {
button.addEventListener("click", () => {
const map = {
up: { x: 0, y: -1 }, down: { x: 0, y: 1 },
left: { x: -1, y: 0 }, right: { x: 1, y: 0 }
};
changeDirection(map[button.dataset.dir]);
});
});
resetGame(true);
resizeCanvas();
requestAnimationFrame(animationFrame);
})();
</script>
</body>
</html>Reasoning trace (15,081 tokens)
**Planning a Visual Game**
I'm working on creating an HTML visual for a polished game, featuring a self-playing demo. The game will have a snake train with locomotive wagons and passengers waiting for pickup at a city station, maybe with a dark navy theme. The demo should start automatically behind a "Click or press a key to start" message. I need to think about a responsive layout for both desktop and mobile, and design the game board with graphics that include obstacles and icons for pickup.**Designing the Game Board**
I'm planning to create a game board with dimensions of 860x590, alongside a side panel of 300. For a layout on mobile, I’m considering an aspect ratio of 1.15, with an internal canvas of 720x560, and responsive drawing using coordinate cells. The theme could feature vibrant pastel colors, with a warm muted background. The board will be styled as a top-down game, incorporating a train track circuit loop, grid movement for the snake train, and passenger targets placed on the platform cells. I want to ensure the design feels cohesive and visually appealing!**Refining the Train Path**
I'm contemplating creating a rounded rectangular rail path around the station for the snake to navigate across an open grid filled with obstacles. The train will be represented by rendered cells moving smoothly from one grid cell to another. I think I’ll design an ambient tile pattern for the underlying board, placing the platform along the top row and left side, along with rail lines. It might be interesting to indicate the train's trajectory through the grid instead of following traditional tracks. The snake will need to turn often while maintaining its coordinates!**Designing the canvas layout**
I'm thinking about creating a colorful scene with passengers and wagons. The canvas size is set to 760x560, arranged in logical rows of 15 and columns of 21. I need to figure out cell 34 for drawing and ensure it scales appropriately. The CSS will have a width of 100% while maintaining an aspect ratio of 1.42. I'll use JavaScript to manage the canvas dimensions and compute the game geometry with proper margins, keeping the area in a white-ish panel.**Visualizing the scene**
I’m imagining a scene with several waiting passengers onboard a train, and I want to focus on one specific passenger. When the train picks up this highlighted passenger, I see the passenger count increasing, which makes the train length extend as it prepares to pick up the next passenger. It seems like there's a flow of movement and interaction happening here that I need to capture visually. This will help convey the dynamics of boarding.**Analyzing AI pathfinding logic**
I'm working on the game logic for AI pathfinding. The AI needs to navigate safely around the snake's body using a breadth-first search (BFS). I should simplify pathfinding by treating the forbidden body as a barrier, allowing movement only through the tail. I also need to consider wall collisions and ensure the AI isn't trapped. If no valid path is found, it should pick a neighboring cell that keeps its options open while targeting the platform cell.**Designing a train model**
I'm visualizing a vivid coral red/orange train body with golden trim and a blue cab. The body is a horizontal shape, and I’m placing the wheel circles carefully. In local coordinates, I'll position the wheels with their x-values at -0.2 and +0.2, and y-values around +/- 0.34. The cab and other parts like the smokestack and headlight will be positioned accordingly. I want colorful wagons with windows showing tiny passengers seated inside. This is shaping up to be a lively scene!**Considering layout design**
I'm thinking about the canvas header layout. It might be effective to have a label and stats on the side of the HTML. For the header, I'm picturing a top bar with a small logo, perhaps featuring a locomotive emoji. I could use a custom CSS icon or even an inline SVG, which is allowed. I just want to ensure it all looks good visually and maintains a cohesive feel!**Designing the Game Board**
I’m thinking about a game board design where the main canvas features a small left circle icon with "GARDEN DISTRICT • LINE 01" in tiny uppercase letters and a "LIVE DEMO" on the right. The background color could be a muted sage green, with a subtle grid pattern. I want individual tiles to have slightly varying colors, with tiny leaf or dot decals, and maybe even tiny clover icons in every third cell. There could also be bushes or tree icons placed near corners, and a thin track path looping around the outer margins.**Mapping the Route**
I'm considering marking a dashed golden route that connects the starting point to the platform, with changes in the target location. A dotted overlay route from the train to the target could be created using a Manhattan BFS approach. This would give a nice visual guidance that looks like a route track. I should draw the route as a dashed line from the train's head to the target; however, it might end up being a bit distracting.**Considering coordinate setup**
I’m focusing on setting target coordinates at integer cell x+3, y+1. Starting from station at x4, y3, I’ll draw the passenger sprite and possibly add a label. There's a sign at x2.4, y2.25, and I’m thinking about initializing the target station at cell (4,3). I need to work out the targets and platforms, ensuring I use appropriate integer coordinates like x=5 and refining the placement of signs and areas along the way.**Optimizing canvas layout**
I'm thinking about displaying elements on the canvas. I might need to prevent overlapping of the top label when it’s centered. I could allocate the left at 150 and the right at 110, and set the pill width to 385. For mobile, I should ensure scaling is responsive with CSS for an HTML overlay. The canvas coordinates for rendering might need to start from 0-64, with the header positioned around 18 within the canvas. The title should be on the left and the live status on the right.**Visualizing passenger activity**
I'm thinking about how to represent active passengers. It seems like only active ones should be highlighted, while other waiting travelers are still represented but in a different way. I could use different colors for four travelers to show their status effectively. When one is picked, I would mark the next waiting traveler as active, which might create a clearer visual flow. It’s all about making the passengers easy to distinguish!**Visualizing train path and movement**
I'm considering how to draw the train path preview before the platform, using a subtle color and low opacity for better visibility. I’ll create the train movement by interpolating the head and body coordinates for each segment, calculating the center position accurately. The direction of each segment will be based on its previous segment. I want to ensure the rails are drawn under each wagon, connecting smoothly for a realistic track representation.**Improving train drawing method**
I'm thinking it’s better to draw the rail before the train. For each segment, I need to include the center and interpolated orientation, drawing two dark rail lines along the local x-axis. They should be about 0.85 cell wide and positioned at y ± 0.29 cell. I want to ensure the corners overlap smoothly. Also, I’ll add small wooden transverse ties using local points to align properly since the space between the segments is about one cell.**Planning static stations**
Okay, I’m thinking about how platforms target base cell rows, specifically from top=2-3 to bottom=11-12. The board's y position is set at 68. I want to spawn static stations where passengers can wait. Each station should have a person drawn at fixed points, and while a train may pass through, that person remains on the platform if they're not currently active. Collisions aren’t relevant here. For those waiting, I’ll draw them on the platform with a backpack and a circle.**Designing game board elements**
I'm working on background decorations for the board. I think avoiding platforms and instead drawing floral patches might work well. I want to place soft circles, like flowers, at specific coordinates. It's important that the visuals aren't too busy. For the grid, I'm considering alternating colors with slight transparency and no borders for a cleaner look. I'll also focus on the top UI bar with a route badge, an icon, and different labels. The footer and mobile layouts need attention too!**Designing layout adjustments**
I'm considering some layout changes for better responsiveness. I could set aside two-column cards for controls and use a grid layout for the sidebar and main content. At a maximum width of 700 pixels, I can have one column, but at 375 pixels, I aim for a two-column setup to make the content more compact. The goal is to adapt gracefully without introducing vertical scrolls. Altogether, the outer design should have a minimum height of 100vh.**Evaluating game mechanics**
I’m thinking about how the AI can choose its path direction while avoiding reversal. So, if the initial direction is {x:1,y:0} and the snake's head is at x=10, then the positions behind would be 9,8, etc. I need to consider what happens if a direction is set. If the game ends, I think it would be good to overlay a `div.end-message` with a button for restarting. That could help the player!**Considering snake rendering**
I’m thinking about how to render the snake’s body accurately. I need to loop from the last part of the snake’s body backward to ensure the current order is respected. When I’m rendering the snake rotated, I’ll compute the vector between consecutive segments. If they aren’t adjacent, I’ll have to account for that. This approach should help create a smoother and more realistic appearance while maintaining the snake’s integrity when it moves.**Clarifying marker details**
I'm considering how to implement the target marker with a gold ring radius of 0.46 cells. It looks like for inactive people at all stations, I don’t want a halo effect, so it should only get added to each marker visually. I also need to ensure that when the train starts, there's no overlap between stations. I’m thinking through these details to get it right!**Evaluating pathfinding issues**
I need to check if the next station can access the current snake's location and whether the system should update that to the available platform. The station targets are fixed, and if the platform is occupied during the pickup, the next target could be a body cell. The AI's path might struggle since BFS doesn’t set traversal if the target is blocked. Shouldn’t it allow for body cells? This leads to path issues that need addressing to ensure targets aren’t occupied.**Considering design elements**
I'm thinking about the desktop layout and how to make it accessible with a clear focus on aria attributes. The auto demo plays in the background while ensuring the prompt remains upfront. I notice the CSS for hints has specific height and padding settings, and I wonder if placing it over the board header is practical. For smaller screens, I’m considering how to structure the header with the logo and badge using grid display.**Considering canvas resizing logic**
I’m working through some code related to initializing the canvas size and accounting for device pixel ratio. To start, I’ll set `dpr` to the minimum pixel ratio. The `resizeCanvas` function will update the canvas width and height based on `dpr`. If there's no `ResizeObserver`, I'll use the window resize event instead. I'll also check how CSS scales and ensure the canvas can handle pointer events correctly. Lastly, I'm considering layout dimensions and paddings to keep everything well-organized.**Implementing platform features**
I'm focusing on the platform target's active ring and how to use `ctx.setLineDash` effectively, ensuring I restore it afterward. For the station signage, I want to ensure the text is positioned correctly. I also need to handle the color label dot and the status route strip with children route stops properly. I'm considering how users will interact with the mobile touchscreen, aiming to keep it simple without using unnecessary local storage. Let’s get this written out!