Train snakeOne-line
Nemotron 3.5 Lightning (free)
by NVIDIAReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 2 min 3 s
- Cost
- $0
- Output tokens
- 9,723
- Spent thinking
- 7,365 tokens
- Speed
- 79.2 tok/s
- Console errors
- 0
- First token after
- 278 ms
- First visible text after
- 1 min 37 s
- Prompt tokens
- 379
- Code size
- 7.7 KB
- Finish reason
- stop
- Served by
- Nvidia
- Model id
nvidia/nemotron-3.5-lightning:free- Reasoning sent
model default- Max output
- 32,000 tokens
- Generated
- Nov 9, 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 · 259 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Train Snake</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
overflow-x: hidden;
background: #87CEEB;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-family: Arial, sans-serif;
}
canvas {
display: block;
border-radius: 8px;
background: #2c3e50;
margin-top: 20px;
}
#start-message {
position: fixed;
top: 20px; left: 50%;
transform: translateX(-50%);
color: white;
font-size: 22px;
pointer-events: auto;
text-shadow: 1px 1px 4px #000;
background: rgba(0,0,0,0.3);
padding: 12px 24px;
border-radius: 4px;
}
</style>
</head>
<body>
<div id="start-message">Click or press a key to start</div>
<canvas id="game"></canvas>
<script>
(() => {
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const startMsg = document.getElementById('start-message');
let w = Math.max(420, Math.min(innerWidth - 40, 920));
let h = Math.max(320, Math.min(innerHeight - 40, 620));
canvas.width = w;
canvas.height = h;
const cols = 20;
const rows = 15;
const cw = w / cols;
const ch = h / rows;
let direction = {x: 1, y: 0};
let nextDir = {x: 1, y: 0};
let train = [];
let wagons = [];
let passengers = [];
let score = 0;
let gameOver = false;
let started = false;
let userInteracted = false;
function resize() {
w = Math.max(420, Math.min(innerWidth - 40, 920));
h = Math.max(320, Math.min(innerHeight - 40, 620));
canvas.width = w;
canvas.height = h;
cw = w / cols;
ch = h / rows;
}
function init() {
train = [];
for (let i = 0; i < 5; i++) {
train.push({x: 10 - i, y: 7});
}
direction = {x: 1, y: 0};
nextDir = {x: 1, y: 0};
wagons = [];
passengers = [];
score = 0;
gameOver = false;
started = true;
userInteracted = true;
startMsg.style.display = 'none';
spawnPassengers();
loop();
}
function spawnPassengers() {
passengers = [];
const count = 3 + Math.floor(score / 4);
for (let i = 0; i < count; i++) {
let x, y, valid;
do {
x = Math.floor(Math.random() * cols);
y = Math.floor(Math.random() * rows);
valid = !train.some(seg => seg.x === x && seg.y === y);
} while (!valid);
passengers.push({x, y});
}
}
function move() {
if (!started || gameOver) return;
// AI steering towards nearest passenger
let nearest = null;
let minDist = Infinity;
passengers.forEach(p => {
const d = Math.abs(p.x - train[0].x) + Math.abs(p.y - train[0].y);
if (d < minDist) { minDist = d; nearest = p; }
});
if (nearest) {
if (train[0].x < nearest.x && direction.x !== -1) nextDir = {x: 1, y: 0};
else if (train[0].x > nearest.x && direction.x !== 1) nextDir = {x: -1, y: 0};
else if (train[0].y < nearest.y && direction.y !== -1) nextDir = {x: 0, y: 1};
else if (train[0].y > nearest.y && direction.y !== 1) nextDir = {x: 0, y: -1};
}
direction = {x: nextDir.x, y: nextDir.y};
const head = {...train[0]};
head.x += direction.x;
head.y += direction.y;
// wrap around
if (head.x < 0) head.x = cols - 1;
if (head.x >= cols) head.x = 0;
if (head.y < 0) head.y = rows - 1;
if (head.y >= rows) head.y = 0;
// self collision
if (train.some(seg => seg.x === head.x && seg.y === head.y)) {
gameOver = true;
return;
}
let ate = false;
passengers.forEach((p, idx) => {
if (p.x === head.x && p.y === head.y) {
ate = true;
passengers.splice(idx, 1);
score++;
}
});
if (ate) {
wagons.unshift(head);
spawnPassengers();
} else {
train.push(head);
if (wagons.length > train.length - 1) wagons.shift();
}
render();
requestAnimationFrame(loop);
}
function render() {
ctx.fillStyle = '#87CEEB';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// grid
ctx.strokeStyle = '#7f8c8d';
ctx.lineWidth = 1;
for (let i = 0; i <= cols; i++) {
ctx.beginPath();
ctx.moveTo(i * cw, 0);
ctx.lineTo(i * cw, canvas.height);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, i * ch);
ctx.lineTo(canvas.width, i * ch);
ctx.stroke();
}
// passengers
passengers.forEach(p => {
const px = p.x * cw + cw / 2;
const py = p.y * ch + ch / 2;
const r = Math.min(cw, ch) * 0.22;
ctx.fillStyle = '#e74c3c';
ctx.beginPath();
ctx.arc(px, py, r, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#fff';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(px - r, py - r);
ctx.lineTo(px + r, py + r);
ctx.moveTo(px + r, py - r);
ctx.lineTo(px - r, py + r);
ctx.stroke();
});
// train
train.forEach((seg, i) => {
const x = seg.x * cw;
const y = seg.y * ch;
if (i === 0) {
// locomotive
ctx.fillStyle = '#e74c3c';
ctx.fillRect(x + w * 0.1, y, w * 0.8, ch);
ctx.fillStyle = '#2c3e50';
ctx.fillRect(x + w * 0.15, y + ch * 0.1, w * 0.7, ch * 0.8);
ctx.fillStyle = '#f1c40f';
ctx.fillRect(x + w * 0.25, y + ch * 0.2, w * 0.3, ch * 0.4);
ctx.fillRect(x + w * 0.55, y + ch * 0.2, w * 0.3, ch * 0.4);
} else {
// colorful wagons
ctx.fillStyle = ['#3498db', '#2ecc71', '#9b59b6', '#f1c40f', '#e67e22'][i % 5];
ctx.fillRect(x, y, w, ch);
}
});
if (gameOver) {
ctx.fillStyle = 'white';
ctx.font = `bold ${Math.min(cw, ch) * 0.6}px Arial`;
ctx.textAlign = 'center';
ctx.fillText('Game Over - Score: ' + score, canvas.width / 2, canvas.height / 2);
}
}
function loop() {
move();
}
// init on first click
canvas.addEventListener('click', () => {
if (!userInteracted) {
init();
}
});
// allow keyboard only after interaction
document.addEventListener('keydown', e => {
if (!userInteracted) return;
const key = e.key;
if (key === 'ArrowUp' && direction.y !== 1) nextDir = {x: 0, y: -1};
else if (key === 'ArrowDown' && direction.y !== -1) nextDir = {x: 0, y: 1};
else if (key === 'ArrowLeft' && direction.x !== 1) nextDir = {x: -1, y: 0};
else if (key === 'ArrowRight' && direction.x !== -1) nextDir = {x: 1, y: 0};
});
// start AI immediately on load, message stays visible but canvas draws underneath
// AI loop via setInterval so it moves before first user interaction
setInterval(() => {
if (!started && !gameOver) {
init();
}
}, 200);
// start the first move after a short delay to let canvas render
setTimeout(() => {
if (!started && !gameOver) init();
}, 300);
})();
</script>
</body>
</html>Reasoning trace (7,365 tokens)
Here's a thinking process:
1. **Analyze the Request:**
- **Game:** Snake game
- **Theme:** Snake is a little train with a locomotive and colorful wagons
- **Objective:** Picking up passengers waiting on the platform
- **Constraints:**
- Output exactly ONE fenced code block with `.html` (or `.svg` if task asks for SVG)
- Everything inline: CSS in `<style>`, JS in `<script>`
- No external files, images, fonts, or network requests except explicitly allowed CDN URLs
- No localStorage, sessionStorage, cookies, IndexedDB, alert(), confirm(), prompt(), window.open()
- Must look right in 1280×800 viewport, adapt down to 375px wide, no horizontal scrollbar
- Something meaningful visible on first paint (screenshot auto-taken ~3s after load)
- Interesting things visible within first seconds, not hidden behind high score/levels/long play
- Games must start in self-playing demo mode (simple AI plays) behind "Click or press a key to start" message
- Iframe only receives keyboard events after user clicks it: display "Click or press a start" message
- Code must run with zero errors in latest Chrome console
2. **Deconstruct the Game Concept:**
- Snake as a train: locomotive head, followed by wagon segments
- Passengers on platform: collectibles that make the train longer/add wagons
- Self-playing demo mode: AI plays automatically initially
- "Click or press a key to start" message
- Responsive: 1280x800 down to 375px wide, no horizontal scroll
- Inline HTML/CSS/JS, no external resources (except maybe I need to check allowed CDN URLs - task says "except the CDN URLs explicitly allowed by the task" but doesn't list any, so I'll avoid CDNs or use data URLs if needed, but better to keep it pure inline)
- No blocked functions (alert, confirm, prompt, etc.)
3. **Design the Implementation:**
- **Canvas-based** for the snake/train movement
- **Grid system**: cells, size based on viewport
- **Train graphics**: Use rectangles/colors to represent locomotive and wagons. Since we can't use external images, I'll draw them with CSS-like colors and shapes on canvas.
- **Passengers**: Small circles or shapes on platform spots
- **AI demo**: Simple pathfinding or random but valid movement that avoids self-collision and collects passengers. Since it's a demo, I'll make it follow a simple algorithm: move towards nearest passenger, otherwise keep direction.
- **Start message**: "Click or press a key to start" - visible on first paint
- **No alert/confirm/prompt**: I'll use DOM updates or canvas text instead
- **Responsive**: Canvas size calculated based on inner width/height, with max constraints. CSS to make it responsive.
- **Self-contained**: Everything in one HTML file, inline style and script.
4. **Drafting the Code Structure:**
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
/* Make canvas responsive, no scroll */
body { margin: 0; padding: 0; overflow-x: hidden; background: #87CEEB; /* sky blue */ }
canvas { display: block; max-width: 100%; height: auto; }
</style>
</head>
<body>
<canvas id="game"></canvas>
<script>
// game logic
</script>
</body>
</html>
```
Wait, the constraint says "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." So I need to output just the fenced code block, with the full HTML inside.
Also: "CSS in <style>, JavaScript in <script>. No external files, images, fonts or network requests, except the CDN URLs explicitly allowed by the task." Since no CDNs are explicitly allowed, I'll avoid them. I'll use inline everything.
Let me think about the game mechanics carefully.
- Grid: Let's say 20x15 cells max, but responsive.
- Train: Locomotive at head, wagons follow. Each segment is a rectangle. Colors: locomotive red/orange, wagons bright colors.
- Passengers: Small figures on platform spots. Maybe just colored circles with a cross or something to indicate passengers.
- Collecting: When head hits passenger, passenger disappears, train grows (add a wagon).
- Game over if train hits itself or wall.
- AI demo: On start, before user input, the AI plays automatically. After user clicks/presses a key, user takes over? Or the AI continues in background? The constraint says: "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." And: "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." So initially, the AI plays. After user clicks, maybe the game switches to user control, but the demo mode should be visible on first paint. I'll make it so: on load, the AI starts moving immediately. A message "Click or press a key to start" is shown. When user clicks, the game either starts user control, or the AI continues but now user can also influence? To keep it simple and compliant: AI plays in demo mode. User clicking "starts" the game proper, but since the constraint says "behind a 'Click or press a key to start' message, so the game can be judged in action without playing", I think it means the AI plays automatically, and the user can click to maybe restart or take over, but the key point is that something meaningful is visible within first seconds.
Let re-read: "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." This implies: when the page loads, there's a "Click or press a key to start" message, and behind it/underneath, an AI is already playing. The user can click/press a key to actually start interacting, but the AI demo is already running so judges can see it in action. I'll implement: on load, show "Click or press a key to start". Immediately start AI movement. When user clicks or presses a key, hide the message and enable user control (or keep AI but user can steer). To keep it simple and avoid complexity, I'll have the AI play demo mode, and upon user click, the game resets or the user takes over. But the constraint also says "so the game can be judged in action without playing" - meaning the AI demo should be playable/judgable right away. I'll make it so: on load, the AI starts moving right away. The "Click or press a key to start" message is overlay. When user clicks, the message disappears and the user can play. But the AI demo is already running during the initial seconds, which satisfies "something meaningful visible on first paint".
Let's design the AI: Simple behavior: move in current direction, if passenger ahead in direction, move towards it. Otherwise, random valid turn. To keep it simple, I'll just have it move and collect passengers. Since it's a demo, I'll make it reasonably functional.
**Key considerations:**
- No alert/confirm/prompt
- No external resources
- Responsive canvas
- Train graphics with ASCII-like or color-based drawing on canvas
- Passengers as collectibles
- Self-playing demo
- Responsive, no horizontal scroll
- Zero console errors
Let me sketch the code mentally:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Train Snake</title>
<style>
body { margin: 0; overflow-x: hidden; background: #e0f7fa; }
canvas { width: 100%; max-width: 900px; height: auto; display: block; margin: 20px auto; }
#start-message { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #333; font-family: sans-serif; pointer-events: auto; }
</style>
</head>
<body>
<div id="start-message">Click or press a key to start</div>
<canvas id="game"></canvas>
<script>
// setup
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const startMsg = document.getElementById('start-message');
let width, height, cellSize;
let gridWidth, gridHeight;
let train = []; // {x, y}
let direction = {x: 1, y: 0};
let nextDirection = {x: 1, y: 0};
let passengers = [];
let wagons = []; // additional segments behind locomotive
let score = 0;
let gameOver = false;
let aiControlled = true; // start with AI
let frame = 0;
function resize() {
const maxW = Math.min(innerWidth - 40, 900);
const maxH = innerHeight - 40;
canvas.width = Math.floor(maxW / 20) * 20; // grid aligned
canvas.height = Math.floor(maxH / 15) * 15;
cellSize = canvas.width / 20; // assume 20 grid, but adapt
// Actually, let's compute grid based on canvas size
gridWidth = Math.floor(canvas.width / 20);
gridHeight = Math.floor(canvas.height / 15);
canvas.width = gridWidth * 20;
canvas.height = gridHeight * 15;
cellSize = canvas.width / gridWidth;
initGame();
}
// But initGame should be called carefully. Let me restructure.
// Actually, to avoid complexity, I'll set canvas size in CSS and use inner dimensions, but ensure no scroll.
// Let me write a cleaner version.
// I'll use a fixed grid size concept but responsive.
// Let's just make the canvas take available space, with 20x15 grid.
// ... this is getting long. Let me just write the full code carefully.
// Actually, given the complexity, I'll write a complete, self-contained HTML with inline CSS/JS that:
// - Sets canvas to responsive size
// - Draws a train (locomotive + wagons) as colored rectangles
// - Passengers as small contrasting shapes
// - AI that moves and collects passengers
// - "Click or press key to start" message
// - On click, message hides, user can control with arrows, but AI demo already played
// Wait, constraint: "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." So I need to ensure keyboard only works after click. I'll handle that.
// Let me just write the code. I'll keep it concise but complete.
// Let's think about the AI: simple. Every frame/tick, compute direction towards nearest passenger, else keep direction, avoid self.
// I need to be very careful with console errors. No unused vars, etc.
// Let me draft the full code now.
</script>
</body>
</html>
```
Given the strict output format (single fenced code block), I need to produce exactly one ```html block with everything inside. No text before/after.
Let me write the complete code. I'll keep it to ~400-500 lines max, but within one block.
Key parts:
- HTML structure with meta viewport
- CSS inline, making body overflow-x hidden, canvas responsive
- JS: setup canvas, draw loop, game logic, AI, input handling
- No alert/confirm/prompt
- No external resources
- "Click or press a key to start" message
- AI plays automatically on load
- On click/message, user can start/control
Let me think about the "press a key to start" part. The constraint says: "if the demo uses the keyboard, display a clear 'Click or press a key to start' message." And "The iframe only receives keyboard events after the user clicks it". So I should listen for click on canvas, and/or keydown after click. I'll make the canvas clickable, and also allow keydown globally but only after click.
Actually, to be safe: I'll display the message, and on first click anywhere (or on canvas click), hide the message and enable keyboard. I won't use prompt/alert.
For the AI demo: I'll have the AI move every ~100ms. Passengers spawn randomly. When train collects, score/wagon grows.
Let me code it mentally and then output.
One important thing: "Something meaningful must be visible on first paint: a screenshot is taken automatically about 3 seconds after load, without any interaction." So within 3 seconds, the AI should have done something visible (collected passengers, train moving). I'll make the AI start immediately on load, and passengers spawn right away.
"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." So the game should be visible and playable quickly.
"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." So on load, message is there, AI is already playing. User can click to start playing themselves, but the AI demo is the default.
Okay, let me write the code. I'll structure it carefully.
I'll use a fixed grid of 20x15 cells. Canvas will be responsive but maintain aspect ratio. I'll use CSS transform scale if needed, but better to set canvas width/height based on viewport.
Actually, to avoid scroll, I'll set body margin 0, overflow hidden, and canvas display block with max-width, height auto. But for responsive, I'll use a wrapping div or just set canvas width to 100% and height via aspect ratio trick. However, pure CSS aspect ratio for canvas might be tricky with drawing. I'll just set canvas size in JS on load and on resize, keeping grid aligned.
Let me just do: canvas width = Math.min(innerWidth - 20, 800), canvas height = Math.min(innerHeight - 20, 600), but ensure minimum size. Then grid cells = Math.floor(width / 20), Math.floor(height / 15). I'll make cellSize = width / cols, height / rows.
Actually, simpler: I'll fix the grid at 20x15, and set canvas width = Math.max(400, Math.min(innerWidth - 40, 900)), height = Math.max(300, Math.min(innerHeight - 40, 600)). But it needs to adapt down to 375px wide. At 375px, width might be ~350, cellSize ~17.5. That's fine.
Let me just write the code. I'll be very careful with JS syntax and avoid any errors.
Let me draft the full code now. I'll write it as if I'm pasting it into the response.
One thing: I must not use `window.open`, `alert`, `confirm`, `prompt`. I also must not use `localStorage` etc. I'll avoid those.
I'll structure the JS as an IIFE or just top-level scripts with functions.
Let me write the code:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Train Snake</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
overflow-x: hidden;
background: #87CEEB;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
font-family: Arial, sans-serif;
}
canvas {
display: block;
border-radius: 8px;
background: #2c3e50;
}
#start-message {
position: fixed;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 24px;
pointer-events: auto;
text-shadow: 1px 1px 4px #000;
}
</style>
</head>
<body>
<div id="start-message">Click or press a key to start</div>
<canvas id="game"></canvas>
<script>
(() => {
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const startMsg = document.getElementById('start-message');
let w = Math.max(400, Math.min(innerWidth - 40, 900));
let h = Math.max(300, Math.min(innerHeight - 40, 600));
canvas.width = w;
canvas.height = h;
const cols = 20;
const rows = 15;
const cw = w / cols;
const ch = h / rows;
let direction = {x: 1, y: 0};
let nextDir = {x: 1, y: 0};
let train = [];
let wagons = [];
let passengers = [];
let score = 0;
let gameOver = false;
let started = false;
let aiMode = true;
function resize() {
w = Math.max(400, Math.min(innerWidth - 40, 900));
h = Math.max(300, Math.min(innerHeight - 40, 600));
canvas.width = w;
canvas.height = h;
cw = w / cols;
ch = h / rows;
}
function spawnPassenger() {
let valid = false;
while (!valid) {
const x = Math.floor(Math.random() * cols);
const y = Math.floor(Math.random() * rows);
valid = !train.some(seg => seg.x === x && seg.y === y) && !passengers.some(p => p.x === x && p.y === y);
}
passengers.push({x: Math.floor(Math.random() * cols), y: Math.floor(Math.random() * rows)});
// Actually let me simplify: just push one, but ensure no overlap in loop
}
// Better: init function
function init() {
train = [];
for (let i = 0; i < 5; i++) {
train.push({x: 10 - i, y: 7});
}
direction = {x: 1, y: 0};
nextDir = {x: 1, y: 0};
wagons = [];
passengers = [];
score = 0;
gameOver = false;
started = true;
aiMode = true;
startMsg.style.display = 'none';
spawnPassengers();
}
function spawnPassengers() {
// clear and spawn new set
passengers = [];
const count = 3 + Math.floor(score / 5);
for (let i = 0; i < count; i++) {
let x, y, valid;
do {
x = Math.floor(Math.random() * cols);
y = Math.floor(Math.random() * rows);
valid = !train.some(seg => seg.x === x && seg.y === y);
} while (!valid);
passengers.push({x, y});
}
}
function moveAI() {
if (!started || gameOver) return;
// Simple AI: move towards nearest passenger, else keep direction
let nearest = null;
let minDist = Infinity;
passengers.forEach(p => {
const dx = Math.abs(p.x - train[0].x);
const dy = Math.abs(p.y - train[0].y);
const d = dx + dy;
if (d < minDist) {
minDist = d;
nearest = p;
}
});
if (nearest) {
if (train[0].x < nearest.x && direction.x !== -1) nextDir = {x: 1, y: 0};
else if (train[0].x > nearest.x && direction.x !== 1) nextDir = {x: -1, y: 0};
else if (train[0].y < nearest.y && direction.y !== -1) nextDir = {x: 0, y: 1};
else if (train[0].y > nearest.y && direction.y !== 1) nextDir = {x: 0, y: -1};
}
direction = {x: nextDir.x, y: nextDir.y};
// move train
const head = {...train[0]};
head.x += direction.x;
head.y += direction.y;
// wall wrap? or die. Let's make it wrap for demo purposes, but can also die.
// I'll make it wrap around so AI doesn't get stuck immediately.
if (head.x < 0) head.x = cols - 1;
if (head.x >= cols) head.x = 0;
if (head.y < 0) head.y = rows - 1;
if (head.y >= rows) head.y = 0;
self-collision: if train.some(seg => seg.x === head.x && seg.y === head.y) { gameOver = true; return; }
// check passenger
let ate = false;
passengers.forEach((p, idx) => {
if (p.x === head.x && p.y === head.y) {
ate = true;
passengers.splice(idx, 1);
score++;
}
});
if (ate) {
wagons.unshift(head); // add new head as wagon, train grows
spawnPassengers();
} else {
train.push(head);
if (wagons.length > train.length - 1) wagons.shift();
}
requestAnimationFrame(render);
}
function render() {
ctx.fillStyle = '#87CEEB';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// draw grid
ctx.strokeStyle = '#7f8c8d';
ctx.lineWidth = 1;
for (let i = 0; i <= cols; i++) {
ctx.beginPath();
ctx.moveTo(i * cw, 0);
ctx.lineTo(i * cw, canvas.height);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, i * ch);
ctx.lineTo(canvas.width, i * ch);
ctx.stroke();
}
// draw passengers
passengers.forEach(p => {
const px = p.x * cw + cw / 2;
const py = p.y * ch + ch / 2;
const r = Math.min(cw, ch) * 0.25;
ctx.fillStyle = '#e74c3c';
ctx.beginPath();
ctx.arc(px, py, r, 0, Math.PI * 2);
ctx.fill();
// little cross
ctx.strokeStyle = '#fff';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(px - r, py - r);
ctx.lineTo(px + r, py + r);
ctx.moveTo(px + r, py - r);
ctx.lineTo(px - r, py + r);
ctx.stroke();
});
// draw train
ctx.fillStyle = '#e67e22';
train.forEach((seg, i) => {
const x = seg.x * cw;
const y = seg.y * ch;
const w = cw;
const h = ch;
if (i === 0) {
// locomotive
ctx.fillStyle = '#e74c3c';
ctx.fillRect(x + w * 0.1, y, w * 0.8, h);
ctx.fillStyle = '#2c3e50';
ctx.fillRect(x + w * 0.15, y + h * 0.1, w * 0.7, h * 0.8);
// windows
ctx.fillStyle = '#f1c40f';
ctx.fillRect(x + w * 0.25, y + h * 0.2, w * 0.3, h * 0.4);
ctx.fillRect(x + w * 0.55, y + h * 0.2, w * 0.3, h * 0.4);
} else {
// wagon
ctx.fillStyle = ['#3498db', '#2ecc71', '#9b59b6', '#f1c40f', '#e67e22'][i % 5];
ctx.fillRect(x, y, w, h);
}
});
if (!gameOver) {
requestAnimationFrame(render);
} else {
ctx.fillStyle = 'white';
ctx.font = `bold ${Math.min(cw, ch)}px Arial`;
ctx.textAlign = 'center';
ctx.fillText('Game Over - Score: ' + score, canvas.width / 2, canvas.height / 2);
}
}
function handleKey(e) {
if (!started) return;
const key = e.key;
if (key === 'ArrowUp' && direction.y !== 1) nextDir = {x: 0, y: -1};
else if (key === 'ArrowDown' && direction.y !== -1) nextDir = {x: 0, y: 1};
else if (key === 'ArrowLeft' && direction.x !== 1) nextDir = {x: -1, y: 0};
else if (key === 'ArrowRight' && direction.x !== -1) nextDir = {x: 1, y: 0};
}
function handleClick() {
if (!started) {
init();
} else {
// just restart on click if already started? Or let user take over.
// To keep AI demo, I'll just reset on first click, then user can play.
// Actually, constraint: "Click or press a key to start" - so after click, game starts.
// I'll make first click init, and then keyboard takes over.
// But I need to track if click has been handled. I'll use a flag.
}
}
let clickHandled = false;
canvas.addEventListener('click', () => {
if (!clickHandled && started === false) {
init();
clickHandled = true;
// start AI loop
setInterval(moveAI, 150);
requestAnimationFrame(render);
}
});
document.addEventListener('keydown', handleKey);
// Auto-start AI demo on load, but show message
// Initially, hide message after 3s? No, constraint says screenshot taken auto 3s after load without interaction.
// So message must remain visible at least 3s, or AI plays during that time.
// I'll make AI start immediately on load, even before click. The message stays but AI moves underneath/behind.
// But canvas is underneath the message? I'll set message z-index high, canvas below.
// AI can move on canvas even if message covers it visually, but the message text blocks view.
// To make something visible on first paint, the canvas itself should draw the train.
// The message is a div overlay. The canvas is visible around it if I position it, but I have it centered.
// Actually, I'll make the message fade out after 3s, or after user click.
// Constraint: "screenshot is taken automatically about 3 seconds after load, without any interaction." So by 3s, something meaningful must be visible. If AI moves immediately, and message is centered, maybe the train appears around the message? I'll position the message lower or make it semi-transparent, or just have the AI draw on canvas and the message is just a UI overlay that doesn't cover the whole canvas.
// Let me rethink: I'll make the "Click or press a key to start" message appear after 3s, or fade it. But the constraint says "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 message is there on load, and AI plays behind it. The screenshot at 3s should capture the AI in action. If the message is centered, maybe part of the train shows. I'll position the message at the top or make it small and centered at top, so the train draws below/around.
// Let me adjust: message at top-center, not covering the canvas area.
// I'll change CSS: #start-message { top: 20px; left: 50%; transform: translateX(-50%); }
// That way, the central canvas area is clear, AI can move there, and the message is at the top.
// Let me restructure the code with that in mind.
// Initialize: on load, start AI movement immediately. Set interval.
// The message is at top center. Canvas draws the train.
// User click or keypress hides message and enables user control, but for the demo, AI continues.
// Actually, to keep it simple and compliant, I'll have:
// - On load, AI starts immediately.
// - Message is visible at top center.
// - After user clicks or presses a key, message hides, and user can control with arrows, but AI mode turns off.
// - The initial AI demo is what's judged in the first seconds.
// Let me just code it. I'll be careful.
// Oops, I'm running out of time mentally. I'll just write the complete, final code block.
// Let me produce the final HTML.
})();
</script>
</body>
</html>
```
Wait, I need to output exactly one fenced code block with ```html. I'll write the entire HTML as a single string in the response, no text before/after.
Let me write the complete, polished code. I'll make sure:
- No console errors
- Responsive, no horizontal scroll
- AI plays automatically on load
- "Click or press a key to start" message at top center
- On click/key, message hides, user can control (but AI demo is prior)
- Train with locomotive + colorful wagons
- Passengers on platform
- Collecting passengers grows train
- Simple but visible
Let me draft the final code carefully. I'll keep it compact.
One concern: "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." I'll make sure the click listener is on the canvas, and the message says "Click or press a key to start". I'll also add a global keydown listener but only enable after click, or just let canvas click handle it.
Actually, to satisfy "The iframe only receives keyboard events after the user clicks it", I should probably only listen for keyboard after a click event on the canvas. I'll set a flag `userInteracted = false`, on canvas click set it to true, and on keydown only process if `userInteracted`. But the message says "Click or press a key to start", implying either works. I'll make canvas click set the flag, and also the first keydown after page load... but constraint says "only receives keyboard events after the user clicks it". So I should NOT process keyboard until user clicks. I'll make the message "Click or press a key to start" but actually only click enables keyboard. Or I can make both work after click. I'll just do: on canvas click, hide message, enable keyboard. For keyboard, I'll allow it only after click, but since the demo starts with AI, keyboard isn't needed immediately. After user clicks, they can take over. That satisfies it.
Let me finalize the code.
I'll write the HTML now.