Train snakeOne-line
Nemotron 3 Nano Omni (free)
by NVIDIAReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 2 min 29 s
- Cost
- $0
- Output tokens
- 14,639
- Spent thinking
- 12,685 tokens
- Speed
- 98.5 tok/s
- Console errors
- 0
- First token after
- 448 ms
- First visible text after
- 2 min 4 s
- Prompt tokens
- 379
- Code size
- 6.1 KB
- Finish reason
- stop
- Served by
- Nvidia
- Model id
nvidia/nemotron-3-nano-omni-30b-a3b-reasoning: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 · 215 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Train Snake</title>
<style>
body { margin:0; background:#111; overflow:hidden; display:flex; justify-content:center; align-items:center; height:100vh; }
canvas { width:100%; height:auto; max-width:100vw; border:1px solid #444; }
</style>
</head>
<body>
<canvas id="game"></canvas>
<script>
const CELL = 40;
let gameStarted = false;
let direction = {x:0, y:0};
let snake = [];
let passengers = [];
let score = 0;
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
// create overlay
const overlay = document.createElement('div');
overlay.textContent = 'Click or press a key to start';
overlay.style.position = 'absolute';
overlay.style.top = '50%';
overlay.style.left = '50%';
overlay.style.transform = 'translate(-50%, -50%)';
overlay.style.padding = '20px';
overlay.style.background = 'rgba(0,0,0,0.6)';
overlay.style.color = '#fff';
overlay.style.fontSize = '24px';
overlay.style.borderRadius = '8px';
overlay.style.textAlign = 'center';
overlay.style.zIndex = 10;
document.body.appendChild(overlay);
// init game
function init() {
const centerX = Math.floor(canvas.width/2 / CELL);
const centerY = Math.floor(canvas.height/2 / CELL);
// locomotive (2 cells)
snake = [
{x: centerX, y: centerY},
{x: centerX-1, y: centerY},
];
// add 2 wagons
for (let i=0; i<2; i++) {
snake.push({x: centerX-2-i, y: centerY});
}
// generate passengers
const passengerCount = 5;
for (let i=0; i<passengerCount; i++) {
let x, y;
do {
x = Math.floor(Math.random() * (canvas.width/CELL));
y = Math.floor(Math.random() * (canvas.height/CELL));
} while (isInsideTrain({x,y}) || y < (canvas.height/CELL - 2));
passengers.push({x,y});
}
resetDirection();
}
function resetDirection() {
const dirs = [{x:1,y:0},{x:-1,y:0},{x:0,y:1},{x:0,y:-1}];
direction = dirs[Math.floor(Math.random()*dirs.length)];
}
function isInsideTrain(cell) {
return snake.some(seg => seg.x===cell.x && seg.y===cell.y);
}
function moveHead() {
const head = snake[0];
const newX = head.x + direction.x;
const newY = head.y + direction.y;
// wall collision
if (newX < 0 || newX >= canvas.width/CELL || newY < 0 || newY >= canvas.height/CELL) {
// reset
const centerX = Math.floor(canvas.width/2 / CELL);
const centerY = Math.floor(canvas.height/2 / CELL);
snake = [
{x: centerX, y: centerY},
{x: centerX-1, y: centerY},
];
for (let i=0;i<2;i++) snake.push({x: centerX-2-i, y: centerY});
// reposition passengers
passengers = [];
const passengerCount = 5;
for (let i=0;i<passengerCount;i++) {
let x,y;
do {
x = Math.floor(Math.random() * (canvas.width/CELL));
y = Math.floor(Math.random() * (canvas.height/CELL));
} while (isInsideTrain({x,y}) || y < (canvas.height/CELL - 2));
passengers.push({x,y});
}
resetDirection();
return;
}
// self collision
if (snake.length > 1) {
for (let i=1; i<snake.length; i++) {
if (snake[i].x===newX && snake[i].y===newY) {
// reset
const centerX = Math.floor(canvas.width/2 / CELL);
const centerY = Math.floor(canvas.height/2 / CELL);
snake = [
{x: centerX, y: centerY},
{x: centerX-1, y: centerY},
];
for (let i=0;i<2;i++) snake.push({x: centerX-2-i, y: centerY});
// reposition passengers
passengers = [];
const passengerCount = 5;
for (let i=0;i<passengerCount;i++) {
let x,y;
do {
x = Math.floor(Math.random() * (canvas.width/CELL));
y = Math.floor(Math.random() * (canvas.height/CELL));
} while (isInsideTrain({x,y}) || y < (canvas.height/CELL - 2));
passengers.push({x,y});
}
resetDirection();
return;
}
}
}
// add new head
const newHead = {x:newX, y:newY};
snake.unshift(newHead);
// passenger eat
for (let i=passengers.length-1; i>=0; i--) {
if (passengers[i].x===newX && passengers[i].y===newY) {
score++;
passengers.splice(i,1);
// grow
const tail = snake[snake.length-1];
snake.push({x:tail.x, y:tail.y});
break;
}
}
}
function updateDemo() {
if (!gameStarted) {
moveHead();
}
}
function draw() {
ctx.clearRect(0,0,canvas.width,canvas.height);
// platform
ctx.fillStyle = '#2e7d32';
const platformRows = 2;
const platformY = (canvas.height/CELL - platformRows) * CELL;
ctx.fillRect(0, platformY, canvas.width, platformRows*CELL);
// passengers
ctx.fillStyle = '#ffeb3b';
passengers.forEach(p => {
ctx.beginPath();
ctx.arc(p.x*CELL + CELL/2, p.y*CELL + CELL/2, 5, 0, Math.PI*2);
ctx.fill();
});
// train
// locomotive
ctx.fillStyle = '#ff4500';
ctx.fillRect(snake[0].x*CELL, snake[0].y*CELL, CELL, CELL);
// second cell of locomotive
ctx.fillRect(snake[1].x*CELL, snake[1].y*CELL, CELL, CELL);
// wagons
for (let i=2; i<snake.length; i++) {
const hue = Math.random()*360;
ctx.fillStyle = `hsl(${hue}, 80%, 50%)`;
ctx.fillRect(snake[i].x*CELL, snake[i].y*CELL, CELL, CELL);
}
// score
ctx.fillStyle = '#fff';
ctx.font = '20px sans-serif';
ctx.fillText('Score: '+score, 10, 30);
}
function loop() {
updateDemo();
draw();
requestAnimationFrame(loop);
}
canvas.addEventListener('click', () => {
if (!gameStarted) {
gameStarted = true;
overlay.style.display = 'none';
}
});
window.addEventListener('keydown', e => {
if (!gameStarted) {
overlay.style.display = 'none';
gameStarted = true;
return;
}
let newDir = {x:0, y:0};
switch(e.key) {
case 'ArrowUp': newDir = {x:0, y:-1}; break;
case 'ArrowDown': newDir = {x:0, y:1}; break;
case 'ArrowLeft': newDir = {x:-1, y:0}; break;
case 'ArrowRight': newDir = {x:1, y:0}; break;
default: return;
}
// prevent reverse direction
if (snake.length > 1 && newDir.x === -direction.x && newDir.y === -direction.y) {
return;
}
direction = newDir;
});
window.addEventListener('load', () => {
init();
requestAnimationFrame(loop);
});
</script>
</body>
</html>Reasoning trace (12,685 tokens)
We need to produce a self-contained HTML page with CSS and JS inline. Snake game where snake is a little train with locomotive and wagons, picking up passengers on platform.
Constraints: Must start in self-playing demo mode (simple AI) behind "Click or press a key to start" message. Must be visible within first seconds, nothing hidden. Must look right in 1280x800, adapt to 375. Must be zero errors console. Must not use blocked features.
We need to design a simple canvas game. We'll create a canvas 800x600 maybe. The snake will be drawn as a train: locomotive at head, then wagons. The train moves around a grid. Passengers are small circles on platform (maybe a platform area). When snake's head touches a passenger, it picks up (adds to train length). The train length can increase by adding a wagon.
We need demo mode: simple AI that moves randomly or follows a simple pattern, maybe moving in a spiral or just random direction until it hits wall or itself, then restart. But we need "Click or press a key to start". So initially show a message overlay, and we need to start the game when user interacts (click or keypress). While not started, we can have a simple animation of the train moving a bit? Or just static.
We need to ensure that within first seconds something is visible. The message overlay counts, but maybe we can have the train moving slowly in demo mode before click? But requirement: "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 demo mode runs, but we display the message. The AI plays (maybe moving around). The message is shown on top, but the game is running behind. So we need to have a canvas that draws the game, but overlay a semi-transparent message. The AI should move the train (maybe random direction) while the message is displayed. Once user clicks, we hide the message and let user control? Or maybe the AI continues? It says "self-playing demo mode (a simple AI plays) behind a 'Click or press a key to start' message". So after click, maybe the user can start playing (maybe they control the train). But the demo mode is just for judging. So after click, maybe the AI stops and user can start playing (maybe they control). But requirement not explicit about user control; just need to start after click. We'll implement simple controls: arrow keys to move the train.
Simplify: The train will be drawn as a series of rectangles: locomotive (bigger) then wagons (smaller). We'll have a direction vector. In demo mode, we set a simple AI that moves in a fixed direction until hitting wall, then turn randomly.
We'll need to ensure the game adapts to viewport size. Use responsive canvas: set canvas width/height based on container size, maybe using CSS to make it fill width while maintaining aspect ratio. Simpler: set canvas width=800, height=600, and use CSS to scale. But must adapt down to 375px width, so we can set canvas width=100% and height auto? But drawing coordinates need scaling. Simpler: set canvas width=800, height=600, and use CSS to make it responsive: width:100%; height:auto; maybe with a max-width. That will keep aspect ratio.
But requirement: page must look right in 1280x800 viewport and adapt gracefully down to 375px wide. So we can set a container with max-width:100vw; and canvas width set to 100% and height auto using CSS to maintain aspect ratio. However canvas drawing coordinate system is based on actual canvas pixel size; if we set width=100% and height auto, the canvas size will be determined by CSS, not by pixel dimensions. To maintain pixel ratio, we can set canvas width and height attributes to 800 and 600, then style width:100%; height:auto; This will scale drawing accordingly (the drawing will be stretched). But the game grid will be scaled, still fine.
Better: we can set canvas width=800, height=600, and then use CSS: canvas { width: 100%; height: auto; } That ensures it scales.
Now design grid: maybe 20x15 cells (each 40x40). We'll map to canvas coordinates.
Train representation: an array of segments, each segment has x,y. The head is first element. The locomotive maybe larger segment (size 2 cells), wagons maybe 1 cell each. We'll draw each segment as a rectangle with different colors: locomotive red, wagons colors random.
Passengers: small circles (radius 5) placed on platform area (maybe a strip at bottom). They are static but when snake head touches them, we remove passenger and add a new wagon to train (increase length). Also maybe increase score.
Game loop: requestAnimationFrame. In demo mode, we move the train automatically (AI). In user mode after click, we move based on arrow keys.
We need to ensure no external resources. Use only canvas.
Implement "Click or press a key to start". We'll display a message overlay with CSS: position absolute, centered, font-size large, color white with shadow, background rgba(0,0,0,0.6). We'll hide it after first interaction (click or keypress). We'll also stop the demo AI loop? Actually we can keep the loop running but ignore user input until start.
Simplify: Have a variable `gameStarted = false`. In the loop, if not started, we just move AI and draw. When user clicks or presses any key, set gameStarted = true, and maybe change the message to "Use arrow keys to move". Also we can stop the AI random direction and allow user to control via keys.
We'll need to handle keyboard events: if gameStarted, adjust direction based on arrow keys, but prevent reverse direction (can't go directly opposite). Also ensure train doesn't go out of bounds; if hits wall, maybe game over? But requirement not about game over; just need to be visible. Maybe we can let it wrap around or stop. Simpler: treat walls as boundaries; if train hits wall, we reset to start position (like restart). That keeps game going.
But to keep demo mode simple, we can have AI that moves randomly but stays within bounds; if hits wall, we reposition train at center and continue.
Implementation details:
- Canvas setup: const canvas = document.getElementById('game'); const ctx = canvas.getContext('2d');
- Define constants: const CELL = 40; const COLS = canvas.width / CELL; const ROWS = canvas.height / CELL;
- Train: let snake = []; // array of {x, y}. We'll start with locomotive of length 2 (2 cells), then maybe 2 wagons.
- Colors: locomotive: #ff4500 (orange red). Wagons: random bright colors.
- Passengers: array of {x, y}. We'll generate some at start (maybe 5). They appear on platform: platform area maybe bottom 2 rows (y >= ROWS-2). We'll place them randomly within that area, not overlapping train.
- Game loop: function update() { if (!gameStarted) { // demo AI: choose direction if not set, move head, check wall collisions, if out of bounds reset. Also handle eating passengers: if head cell matches passenger position, remove passenger and push a new wagon (increase length). } else { // user control: get key direction, update head, move, same collision logic. }
- Then draw: clear canvas; draw platform (maybe a green strip). Draw passengers as circles. Draw train: for each segment, draw rectangle with appropriate color. Locomotive first: maybe draw a bigger rectangle (2 cells wide). Wagons: each 1 cell.
- Score: maybe display number of passengers collected at top.
- Ensure the page looks right: use CSS to center canvas, maybe set max-width: 100vw; width: 100%; height: auto; Also set body margin 0; background dark.
- Ensure no console errors: avoid using var incorrectly, ensure all functions defined before use. Use strict equality.
- Ensure the demo mode runs for at least 3 seconds before click; but we start the loop immediately, so visible content is the canvas with train moving and passengers, plus overlay message. That's fine.
- Need to ensure the page loads quickly and something visible on first paint: The canvas will be drawn immediately (maybe train at start). The overlay message may cover part, but still visible.
- Ensure the iframe only receives keyboard events after user clicks: we will add event listener for click on canvas to set gameStarted = true and hide message. Also keydown listener only active after start.
- Ensure we don't use any blocked features.
Now write the HTML with inline CSS and JS.
We need to embed everything in one file.
Let's craft.
Potential issues: The canvas size attributes must be set to 800 and 600. Then CSS width:100%; height:auto; So the canvas will scale.
But if we set width=800 height=600, then CSS width:100% will make it width 100% of container, height auto will adjust proportionally (600/800 = 0.75). So in 1280 width, canvas will be 1280px wide, height 960px (maybe beyond viewport). But requirement: page must look right in 1280x800 viewport. That means we need to ensure the game fits within 1280 width and 800 height, not exceed. So maybe we set canvas width=1280, height=800 (the viewport). But then adapt down to 375 width: we can set canvas width=100% and height auto, but we need to maintain aspect ratio. Let's compute aspect ratio: 1280x800 = 1.6. If we set canvas 800x600 = 1.333, not same. Better to set canvas width=1280, height=800 (the target). Then CSS width:100%; height:auto; will scale to container width, preserving aspect ratio (since height auto). In 1280 width, canvas will be 1280 wide, height 800 (fits). In smaller width like 375, canvas width will be 375, height will be 375 * 800/1280 = 234.4, still fits within 800 height. So fine.
Thus set canvas attributes width=1280, height=800. Then CSS: canvas { width:100%; height:auto; max-width:100vw; } Also maybe set border:1px solid #ccc.
Now we need to ensure the game area fits within viewport: 1280x800 is larger than 375 width but still okay.
Now code.
We'll need to handle resizing? Not necessary.
Let's outline variables:
let gameStarted = false;
let direction = {x:0, y:0}; // initial direction none
let snake = []; // segments
let passengers = [];
let score = 0;
// Initialize game
function init() {
// set canvas size
const canvas = document.getElementById('game');
// maybe set size based on CSS? We'll just rely on attributes.
// Create initial snake: locomotive 2 cells, then 2 wagons.
const startX = Math.floor(canvas.width/2 / CELL);
const startY = Math.floor(canvas.height/2 / CELL);
// locomotive (2 cells)
snake = [
{x: startX, y: startY},
{x: startX-1, y: startY},
];
// add wagons
for (let i=0; i<2; i++) {
snake.push({x: startX-2-i, y: startY});
}
// direction initially none; we will set random direction in demo mode.
// generate passengers
const passengerCount = 5;
for (let i=0; i<passengerCount; i++) {
let x, y;
do {
x = Math.floor(Math.random() * (canvas.width/CELL));
y = Math.floor(Math.random() * (canvas.height/CELL));
} while (isInsideTrain({x,y}) || y < (canvas.height/CELL - 2)); // place on lower rows (platform)
passengers.push({x,y});
}
// reset direction for demo AI
resetDirection();
}
// Reset direction for demo AI (random)
function resetDirection() {
// pick a random direction that is not opposite of current (none)
const dirs = [{x:1,y:0},{x:-1,y:0},{x:0,y:1},{x:0,y:-1}];
direction = dirs[Math.floor(Math.random()*dirs.length)];
}
// Check if cell is inside train
function isInsideTrain(cell) {
return snake.some(seg => seg.x===cell.x && seg.y===cell.y);
}
// Move head according to direction, handle walls
function moveHead() {
const head = {x: snake[0].x, y: snake[0].y};
const newX = head.x + direction.x;
const newY = head.y + direction.y;
// wall collision: if out of bounds, reset train to center
if (newX < 0 || newX >= canvas.width/CELL || newY < 0 || newY >= canvas.height/CELL) {
// reset
const centerX = Math.floor(canvas.width/2 / CELL);
const centerY = Math.floor(canvas.height/2 / CELL);
snake = [
{x: centerX, y: centerY},
{x: centerX-1, y: centerY},
];
// re-add wagons
for (let i=0; i<2; i++) {
snake.push({x: centerX-2-i, y: centerY});
}
// reposition passengers
passengers = [];
const passengerCount = 5;
for (let i=0; i<passengerCount; i++) {
let x, y;
do {
x = Math.floor(Math.random() * (canvas.width/CELL));
y = Math.floor(Math.random() * (canvas.height/CELL));
} while (isInsideTrain({x,y}) || y < (canvas.height/CELL - 2));
passengers.push({x,y});
}
// reset direction
resetDirection();
return;
}
// add new head
const newHead = {x: newX, y: newY};
snake.unshift(newHead);
// check passenger
for (let i=passengers.length-1; i>=0; i--) {
if (passengers[i].x===newX && passengers[i].y===newY) {
// eat passenger
score++;
passengers.splice(i,1);
// grow: add a new wagon at end (do not remove tail)
const tail = snake[snake.length-1];
snake.push({x: tail.x, y: tail.y});
break;
}
}
}
// Demo AI: if not started, move automatically; else user controls via key
function updateDemo() {
if (!gameStarted) {
// simple AI: move in current direction; if hitting wall or self, choose new random direction
const head = snake[0];
const newX = head.x + direction.x;
const newY = head.y + direction.y;
// check wall
if (newX < 0 || newX >= canvas.width/CELL || newY < 0 || newY >= canvas.height/CELL ||
snake.some(seg => seg.x===newX && seg.y===newY)) {
// change direction randomly
resetDirection();
// recalc new position after direction change
const head = snake[0];
const newX = head.x + direction.x;
const newY = head.y + direction.y;
}
// move head
moveHead(); // this will handle wall reset etc.
}
}
// Main loop
function loop() {
updateDemo();
draw();
requestAnimationFrame(loop);
}
// Draw function
function draw() {
const ctx = canvas.getContext('2d');
// clear
ctx.clearRect(0,0,canvas.width,canvas.height);
// draw platform (green strip at bottom 2 rows)
ctx.fillStyle = '#2e7d32';
const platformRows = 2;
const platformY = (canvas.height/CELL - platformRows) * CELL;
ctx.fillRect(0, platformY, canvas.width, platformRows*CELL);
// draw passengers
ctx.fillStyle = '#ffeb3b';
passengers.forEach(p => {
ctx.beginPath();
ctx.arc(p.x*CELL + CELL/2, p.y*CELL + CELL/2, 5, 0, Math.PI*2);
ctx.fill();
});
// draw train
// locomotive: first two cells, color orange red
ctx.fillStyle = '#ff4500';
// draw locomotive (2 cells)
ctx.fillRect(snake[0].x*CELL, snake[0].y*CELL, CELL, CELL);
if (snake[1].x === snake[0].x) { // horizontal
ctx.fillRect(snake[1].x*CELL, snake[1].y*CELL, CELL, CELL);
} else {
ctx.fillRect(snake[1].x*CELL, snake[1].y*CELL, CELL, CELL);
}
// wagons: each remaining cell
for (let i=2; i<snake.length; i++) {
// random color per wagon
const hue = Math.random()*360;
ctx.fillStyle = `hsl(${hue}, 80%, 50%)`;
ctx.fillRect(snake[i].x*CELL, snake[i].y*CELL, CELL, CELL);
}
// draw score
ctx.fillStyle = '#fff';
ctx.font = '20px sans-serif';
ctx.fillText('Score: '+score, 10, 30);
}
// Handle click to start
canvas.addEventListener('click', () => {
if (!gameStarted) {
gameStarted = true;
// change direction to user control (initially none)
direction = {x:0,y:0};
// maybe show a different message? We'll just hide overlay via CSS.
}
});
// Keyboard handling
window.addEventListener('keydown', e => {
if (!gameStarted) return;
let newDir = {x:0,y:0};
switch(e.key) {
case 'ArrowUp': newDir = {x:0, y:-1}; break;
case 'ArrowDown': newDir = {x:0, y:1}; break;
case 'ArrowLeft': newDir = {x:-1, y:0}; break;
case 'ArrowRight': newDir = {x:1, y:0}; break;
default: return;
}
// prevent reverse
if (snake.length>1 && newDir.x === -direction.x && newDir.y === -direction.y) {
return;
}
direction = newDir;
});
// Overlay message
const overlay = document.createElement('div');
overlay.textContent = 'Click or press a key to start';
overlay.style.position = 'absolute';
overlay.style.top = '50%';
overlay.style.left = '50%';
overlay.style.transform = 'translate(-50%, -50%)';
overlay.style.padding = '20px';
overlay.style.background = 'rgba(0,0,0,0.6)';
overlay.style.color = '#fff';
overlay.style.fontSize = '24px';
overlay.style.borderRadius = '8px';
overlay.style.textAlign = 'center';
overlay.style.zIndex = 10;
document.body.appendChild(overlay);
// Hide overlay on click or keypress
canvas.addEventListener('click', () => {
overlay.style.display = 'none';
});
window.addEventListener('keydown', e => {
if (!gameStarted) {
overlay.style.display = 'none';
gameStarted = true;
}
});
Now we need to initialize the game after the page loads. We'll call init() in a window.onload or just after script.
But we must ensure that the overlay is displayed initially, and the game loop runs. So after init, start loop.
Potential issue: The canvas attributes width/height are 1280/800, but CSS width:100% may cause scaling; but drawing uses cell coordinates relative to canvas pixel size, so it's fine.
Now we need to ensure no console errors: we must declare CELL constant before using. Let's define at top.
Also ensure that we use const for canvas etc.
Potential bug: In draw, we draw locomotive as two cells; but we used fillRect for each cell. For locomotive we need to draw two cells maybe side by side (if horizontal) or stacked (if vertical). Our code currently draws both cells regardless of orientation. That's okay.
But we need to ensure that the train does not go out of bounds after eating passenger and adding wagon: we already handle wall collision before moving head; after adding wagon, the tail may be near wall but we don't check again until next move.
Now we need to ensure that the overlay is visible initially and covers part of canvas; that's fine.
Now we need to ensure that the page looks right in 1280x800 and adapts down to 375. Since canvas is 1280x800 and CSS width:100% and height:auto, when width is 375, height will be 375 * 800/1280 = 234.4, still fits within viewport (max 800). So fine.
Now we need to ensure that the game starts in demo mode (AI) behind overlay. Our loop runs updateDemo which moves AI automatically. So yes.
Now we need to ensure that after click, the overlay disappears and user can control with arrow keys. We hide overlay on click; also on keypress we hide overlay and set gameStarted = true (so direction can be used). However we also need to allow keypress to start before click? The spec says "Click or press a key to start". So we can also hide overlay on any keypress before click. Our keydown listener hides overlay and sets gameStarted = true (if not started). That's fine.
Now ensure that we don't use any blocked functions. We used only getElementById, addEventListener, etc. No localStorage, etc.
Now ensure that we have exactly one fenced code block with html content. So we need to output:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Train Snake</title>
<style>
body { margin:0; background:#111; overflow:hidden; display:flex; justify-content:center; align-items:center; height:100vh; }
canvas { width:100%; height:auto; max-width:100vw; border:1px solid #444; }
</style>
</head>
<body>
<canvas id="game"></canvas>
<script>
/* code */
</script>
</body>
</html>
```
But we need to embed all CSS and JS inline. The CSS we have above. The JS we wrote.
We need to ensure that the overlay is positioned correctly. Since body is flex center, the canvas will be centered. The overlay absolute positioned relative to body, which is centered; but we need overlay to be centered on the canvas. Since canvas is centered, overlay absolute top 50% left 50% with transform translate will center it relative to the viewport, not the canvas. That's okay; it will appear centered on screen, which is fine.
But maybe better to position overlay relative to canvas: we can set overlay style: position: absolute; top: 0; left:0; right:0; bottom:0; display:flex; align-items:center; justify-content:center; but easier to keep as we have.
Now ensure that the overlay is visible initially. Yes.
Now we need to ensure that the game loop runs immediately after init, which will draw the train moving. That will be visible.
Potential issue: The overlay may cover the canvas, but still visible.
Now we need to ensure that the code runs with zero errors. Let's test mentally:
- CELL defined as const CELL = 40; but we used canvas.width / CELL etc. However we set canvas width=1280, height=800, so canvas.width/CELL = 32, canvas.height/CELL = 20. So grid 32x20.
- In init, we compute startX = Math.floor(canvas.width/2 / CELL); canvas.width/2 = 640; /40 = 16; so startX=16. startY = 10. That's fine.
- We create snake with 2 cells for locomotive (16,10) and (15,10). Then add wagons at (14,10) and (13,10). So snake length 4.
- Passengers placed in rows >= (canvas.height/CELL - 2) = 20-2=18 rows (y >= 18). So they are near bottom.
- In moveHead, we check wall collision: if newX <0 etc. Since we start with direction random, maybe direction is (1,0) initially. Then newX = 16+1 = 17, within 0-31, fine.
- If direction leads out of bounds, we reset train to center (16,10) etc. That's fine.
- In moveHead, after adding new head, we check passenger: loop from end to 0, if passenger matches, remove and grow (push new wagon). Good.
- In updateDemo, if not started, we move head according to direction, but also check wall and self collisions; we call moveHead which already handles wall reset and self collision? Actually moveHead adds new head and then checks passenger, but does not check self collision (snake[0] vs others). However we also need to check if new head collides with existing body (self). In moveHead we only add new head; if new head collides with any segment (except maybe the tail if we are growing?), we should reset. In our moveHead, we only check wall collision before adding head, but not self collision. However after we add new head, we need to check if it collides with any segment (including tail) because that would be self collision. In demo mode, the snake may run into itself. Our code currently doesn't handle that; it will just keep growing and eventually cause errors (like infinite loop). Let's add self collision check after adding new head: if (snake.some(seg => seg.x===newX && seg.y===newY && seg !== newHead)) { reset; } But we already reset on wall collision; but self collision may also cause out-of-bounds? Not necessarily. To be safe, we can add a check after adding new head: if (snake.slice(1).some(seg => seg.x===newX && seg.y===newY)) { // self collision
// reset
resetDirection();
// maybe also reposition head? Let's just reset train.
const centerX = Math.floor(canvas.width/2 / CELL);
const centerY = Math.floor(canvas.height/2 / CELL);
snake = [{x:centerX, y:centerY},{x:centerX-1, y:centerY}];
for (let i=0;i<2;i++) snake.push({x:centerX-2-i, y:centerY});
// reposition passengers
passengers = [];
const passengerCount = 5;
for (let i=0;i<passengerCount;i++) {
let x,y;
do { x=Math.floor(Math.random()* (canvas.width/CELL)); y=Math.floor(Math.random()* (canvas.height/CELL)); } while (isInsideTrain({x,y}) || y < (canvas.height/CELL - 2));
passengers.push({x,y});
}
resetDirection();
return;
}
But this may be heavy; maybe we can avoid self collision by ensuring direction changes when hitting self. In demo AI, we can check before moving: if the next cell is occupied by snake body (excluding the tail if we are about to grow?), we change direction. Simpler: In updateDemo, before moving, we can check if the cell ahead is occupied by snake (except maybe the tail if we are not growing). Since we always add new head before checking passenger, the tail may be same as new head if we are not growing (i.e., not eating). In that case, the new head would be the same as the previous head? Actually we add new head then check passenger; but we also need to ensure that the new head does not occupy a cell that is currently part of snake (except maybe the tail if we are about to grow). Since we add new head at front, the previous head becomes second segment. So if direction is same as previous direction, the new head will be adjacent to previous head, not overlapping. But if direction is opposite (reverse), then new head would be same as previous head? Not exactly. Self collision can happen if the snake turns sharply and runs into its own body. For simplicity, we can add a check after adding new head: if (snake.length > 1 && snake[1].x === newX && snake[1].y === newY) { // hitting the segment right behind head (the one we just moved from) that's okay; but if any other segment (i>=2) matches, that's collision. So we can check: if (snake.some((seg, idx) => idx>0 && seg.x===newX && seg.y===newY)) { reset; } That will catch collisions with any body part except maybe the immediate predecessor if we are moving forward (since we added new head, the old head becomes index 1). Actually if we move forward, the old head becomes index 1, and new head is at index 0; they are adjacent, not same cell, so no collision. So checking all segments except index 0 is fine.
Thus modify moveHead: after adding new head, we can check for self collision:
if (snake.length > 1) {
if (snake.some((seg, i) => i > 0 && seg.x === newX && seg.y === newY)) {
// collision
// reset
const centerX = Math.floor(canvas.width/2 / CELL);
const centerY = Math.floor(canvas.height/2 / CELL);
snake = [{x:centerX, y:centerY},{x:centerX-1, y:centerY}];
for (let i=0;i<2;i++) snake.push({x:centerX-2-i, y:centerY});
// reposition passengers
passengers = [];
const passengerCount = 5;
for (let i=0;i<passengerCount;i++) {
let x,y;
do { x=Math.floor(Math.random()* (canvas.width/CELL)); y=Math.floor(Math.random()* (canvas.height/CELL)); } while (isInsideTrain({x,y}) || y < (canvas.height/CELL - 2));
passengers.push({x,y});
}
resetDirection();
return;
}
}
But this may be too heavy; but okay.
Simplify: In updateDemo, before moving, we can check if the next cell is occupied by snake (excluding tail if we will grow). Since we always add new head first, we can just check after adding.
Given time, maybe we can avoid self collision by making the AI move slowly and random direction changes, reducing chance. But still risk.
Simplify: In moveHead, after adding new head, we can check if the new head is equal to any segment except maybe the tail if we are growing (i.e., if we added a wagon). But we add wagon only when eating passenger; in that case, the tail is the previous last segment, and new head may be same as tail? Not likely.
Given limited time, maybe we can ignore self collision for demo mode; the AI random direction may not cause collision quickly. But to be safe, we can add a simple check: after adding new head, if snake.length > 1 and snake[1].x == newX && snake[1].y == newY, that's okay (adjacent). But if any other segment matches, we reset. Let's implement a check:
if (snake.length > 1) {
for (let i=1; i<snake.length; i++) {
if (snake[i].x===newX && snake[i].y===newY) {
// collision
// reset
const centerX = Math.floor(canvas.width/2 / CELL);
const centerY = Math.floor(canvas.height/2 / CELL);
snake = [{x:centerX, y:centerY},{x:centerX-1, y:centerY}];
for (let i=0;i<2;i++) snake.push({x:centerX-2-i, y:centerY});
// reposition passengers
passengers = [];
const passengerCount = 5;
for (let i=0;i<passengerCount;i++) {
let x,y;
do { x=Math.floor(Math.random()* (canvas.width/CELL)); y=Math.floor(Math.random()* (canvas.height/CELL)); } while (isInsideTrain({x,y}) || y < (canvas.height/CELL - 2));
passengers.push({x,y});
}
resetDirection();
return;
}
}
}
But this is heavy; maybe okay.
Given the constraints, maybe we can keep it simple and not worry about self collision; the demo will still be visible.
Now we need to ensure that the overlay is hidden after click. We have canvas click listener that sets overlay.style.display='none'. Also keydown listener that hides overlay and sets gameStarted = true. However we also need to ensure that after click, the overlay disappears and user can control. Our keydown listener also sets gameStarted = true, but we also need to allow direction changes. In our code, we have direction variable; after click we set direction = {x:0,y:0} (no movement) but then user can press arrow keys to set direction. However we also need to ensure that keydown listener is attached after gameStarted? Actually we attached keydown listener globally; it will fire even before gameStarted, but we have a guard: if (!gameStarted) return; So after click, gameStarted becomes true, and subsequent keypresses will be processed.
But we also hide overlay on click; however keydown may also hide overlay before click (if user presses a key). That's fine.
Potential issue: In the keydown listener, we set direction = newDir; but we also need to prevent the default scrolling of page (if any). Not needed.
Now we need to ensure that the canvas is clicked to start; but also the keydown may start before click (if user presses a key). That's okay; overlay will be hidden and gameStarted set true.
Now ensure that the overlay is visible initially and not covering the entire canvas; it's centered, so it's okay.
Now we need to ensure that the page loads quickly and something visible on first paint. The canvas will be drawn immediately (the train will move). The overlay will be there. So visible.
Now ensure that the code runs with zero errors. Let's think about potential undefined variables: CELL is defined at top. We need to define it before using. We'll add const CELL = 40; after canvas element.
Also we need to ensure that the canvas element exists before we try to get its context. We'll call init after window load.
Let's structure:
<script>
const CELL = 40;
let gameStarted = false;
let direction = {x:0, y:0};
let snake = [];
let passengers = [];
let score = 0;
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
function init() {
// set up snake, passengers, direction
// ...
}
function resetDirection() {
const dirs = [{x:1,y:0},{x:-1,y:0},{x:0,y:1},{x:0,y:-1}];
direction = dirs[Math.floor(Math.random()*dirs.length)];
}
function moveHead() {
// as earlier with wall and self collision checks
}
function updateDemo() {
if (!gameStarted) {
// AI movement
const head = snake[0];
const newX = head.x + direction.x;
const newY = head.y + direction.y;
// wall collision
if (newX < 0 || newX >= canvas.width/CELL || newY < 0 || newY >= canvas.height/CELL) {
// reset
const centerX = Math.floor(canvas.width/2 / CELL);
const centerY = Math.floor(canvas.height/2 / CELL);
snake = [{x:centerX, y:centerY},{x:centerX-1, y:centerY}];
for (let i=0;i<2;i++) snake.push({x:centerX-2-i, y:centerY});
// reposition passengers
passengers = [];
const passengerCount = 5;
for (let i=0;i<passengerCount;i++) {
let x,y;
do { x=Math.floor(Math.random()* (canvas.width/CELL)); y=Math.floor(Math.random()* (canvas.height/CELL)); } while (isInsideTrain({x,y}) || y < (canvas.height/CELL - 2));
passengers.push({x,y});
}
resetDirection();
return;
}
// self collision check
if (snake.length > 1) {
for (let i=1; i<snake.length; i++) {
if (snake[i].x===newX && snake[i].y===newY) {
// reset
const centerX = Math.floor(canvas.width/2 / CELL);
const centerY = Math.floor(canvas.height/2 / CELL);
snake = [{x:centerX, y:centerY},{x:centerX-1, y:centerY}];
for (let i=0;i<2;i++) snake.push({x:centerX-2-i, y:centerY});
passengers = [];
const passengerCount = 5;
for (let i=0;i<passengerCount;i++) {
let x,y;
do { x=Math.floor(Math.random()* (canvas.width/CELL)); y=Math.floor(Math.random()* (canvas.height/CELL)); } while (isInsideTrain({x,y}) || y < (canvas.height/CELL - 2));
passengers.push({x,y});
}
resetDirection();
return;
}
}
}
// add new head
const newHead = {x:newX, y:newY};
snake.unshift(newHead);
// passenger eat
for (let i=passengers.length-1; i>=0; i--) {
if (passengers[i].x===newX && passengers[i].y===newY) {
score++;
passengers.splice(i,1);
// grow
const tail = snake[snake.length-1];
snake.push({x:tail.x, y:tail.y});
break;
}
}
}
}
function draw() {
ctx.clearRect(0,0,canvas.width,canvas.height);
// platform
ctx.fillStyle = '#2e7d32';
const platformRows = 2;
const platformY = (canvas.height/CELL - platformRows) * CELL;
ctx.fillRect(0, platformY, canvas.width, platformRows*CELL);
// passengers
ctx.fillStyle = '#ffeb3b';
passengers.forEach(p => {
ctx.beginPath();
ctx.arc(p.x*CELL + CELL/2, p.y*CELL + CELL/2, 5, 0, Math.PI*2);
ctx.fill();
});
// train
// locomotive
ctx.fillStyle = '#ff4500';
ctx.fillRect(snake[0].x*CELL, snake[0].y*CELL, CELL, CELL);
// add second cell for locomotive if horizontal or vertical
if (snake[1].x === snake[0].x) {
// vertical
ctx.fillRect(snake[1].x*CELL, snake[1].y*CELL, CELL, CELL);
} else {
ctx.fillRect(snake[1].x*CELL, snake[1].y*CELL, CELL, CELL);
}
// wagons
for (let i=2; i<snake.length; i++) {
const hue = Math.random()*360;
ctx.fillStyle = `hsl(${hue}, 80%, 50%)`;
ctx.fillRect(snake[i].x*CELL, snake[i].y*CELL, CELL, CELL);
}
// score
ctx.fillStyle = '#fff';
ctx.font = '20px sans-serif';
ctx.fillText('Score: '+score, 10, 30);
}
function isInsideTrain(cell) {
return snake.some(seg => seg.x===cell.x && seg.y===cell.y);
}
window.addEventListener('load', () => {
init();
requestAnimationFrame(loop);
});
function loop() {
updateDemo();
draw();
requestAnimationFrame(loop);
}
canvas.addEventListener('click', () => {
if (!gameStarted) {
gameStarted = true;
overlay.style.display = 'none';
}
});
window.addEventListener('keydown', e => {
if (!gameStarted) {
overlay.style.display = 'none';
gameStarted = true;
return;
}
let newDir = {x:0, y:0};
switch(e.key) {
case 'ArrowUp': newDir = {x:0, y:-1}; break;
case 'ArrowDown': newDir = {x:0, y:1}; break;
case 'ArrowLeft': newDir = {x:-1, y:0}; break;
case 'ArrowRight': newDir = {x:1, y:0}; break;
default: return;
}
// prevent reverse
if (snake.length>1 && newDir.x === -direction.x && newDir.y === -direction.y) {
return;
}
direction = newDir;
});
// overlay creation
const overlay = document.createElement('div');
overlay.textContent = 'Click or press a key to start';
overlay.style.position = 'absolute';
overlay.style.top = '50%';
overlay.style.left = '50%';
overlay.style.transform = 'translate(-50%, -50%)';
overlay.style.padding = '20px';
overlay.style.background = 'rgba(0,0,0,0.6)';
overlay.style.color = '#fff';
overlay.style.fontSize = '24px';
overlay.style.borderRadius = '8px';
overlay.style.textAlign = 'center';
overlay.style.zIndex = 10;
document.body.appendChild(overlay);
</script>
Now we need to ensure that init() is called after canvas is ready. We'll call init at window load.
Potential bug: In init, we compute startX and startY using canvas.width/2 / CELL; but canvas.width is 1280, height 800. That's fine.
But we also need to set snake length maybe more than 4; but okay.
Now we need to ensure that the overlay is displayed initially; yes.
Now we need to ensure that the page is self-contained; all CSS inline.
Now final code.
Potential minor issues: In draw, we draw locomotive as two cells; but we also draw second cell again (maybe duplicate). Actually we draw locomotive first cell, then second cell (the other part). That's fine.
But we also draw wagons starting from index 2; if snake length is 4 (locomotive 2 + 2 wagons), then i=2,3 will draw two wagons. Good.
Now ensure that the train colors: locomotive orange red, wagons random HSL.
Now ensure that the platform is green strip at bottom 2 rows. Good.
Now ensure that the canvas size is 1280x800; CSS width:100%; height:auto; So in smaller viewport, it scales.
Now ensure that there is no console error: we must avoid using 'let' inside loops incorrectly; but we used let in loops, fine.
Potential error: In updateDemo, we reference 'overlay' variable; but overlay is defined after the function? Actually overlay is defined after loop function; but we use overlay in canvas click listener and keydown. Since overlay is defined before those listeners (the code order matters). In our script, we define overlay after the functions but before the event listeners? Let's see order:
- const CELL...
- let gameStarted...
- function init...
- function resetDirection...
- function moveHead...
- function updateDemo...
- function draw...
- function isInsideTrain...
- window.addEventListener('load', () => { init(); requestAnimationFrame(loop); });
- function loop...
- canvas.addEventListener('click', ...) // uses overlay (maybe not defined yet)
- window.addEventListener('keydown', ...) // uses overlay
- const overlay = document.createElement('div'); // defined after listeners, which may cause ReferenceError because overlay not yet defined when listeners are attached.
We need to define overlay before attaching listeners that reference it. So we should move overlay creation earlier, before the listeners. Let's reorder:
- after defining variables, create overlay element and set its style, then append to body.
- Then define listeners that use overlay.
Thus restructure:
- after const CELL etc, define overlay = document.createElement('div'); set style; document.body.appendChild(overlay);
- then define canvas click listener (which uses overlay.style.display = 'none') – this will work because overlay exists.
- then define keydown listener (also uses overlay).
Thus we need to move overlay creation earlier.
Also ensure that overlay is positioned correctly; maybe we need to set top/left after appending? It's fine.
Now adjust code accordingly.
Let's rewrite the script part in final answer.
We'll produce final HTML.
Potential final code:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Train Snake</title>
<style>
body { margin:0; background:#111; overflow:hidden; display:flex; justify-content:center; align-items:center; height:100vh; }
canvas { width:100%; height:auto; max-width:100vw; border:1px solid #444; }
</style>
</head>
<body>
<canvas id="game"></canvas>
<script>
const CELL = 40;
let gameStarted = false;
let direction = {x:0, y:0};
let snake = [];
let passengers = [];
let score = 0;
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
// create overlay
const overlay = document.createElement('div');
overlay.textContent = 'Click or press a key to start';
overlay.style.position = 'absolute';
overlay.style.top = '50%';
overlay.style.left = '50%';
overlay.style.transform = 'translate(-50%, -50%)';
overlay.style.padding = '20px';
overlay.style.background = 'rgba(0,0,0,0.6)';
overlay.style.color = '#fff';
overlay.style.fontSize = '24px';
overlay.style.borderRadius = '8px';
overlay.style.textAlign = 'center';
overlay.style.zIndex = 10;
document.body.appendChild(overlay);
// init game
function init() {
const centerX = Math.floor(canvas.width/2 / CELL);
const centerY = Math.floor(canvas.height/2 / CELL);
// locomotive (2 cells)
snake = [
{x: centerX, y: centerY},
{x: centerX-1, y: centerY},
];
// add 2 wagons
for (let i=0; i<2; i++) {
snake.push({x: centerX-2-i, y: centerY});
}
// generate passengers
const passengerCount = 5;
for (let i=0; i<passengerCount; i++) {
let x, y;
do {
x = Math.floor(Math.random() * (canvas.width/CELL));
y = Math.floor(Math.random() * (canvas.height/CELL));
} while (isInsideTrain({x,y}) || y < (canvas.height/CELL - 2));
passengers.push({x,y});
}
resetDirection();
}
function resetDirection() {
const dirs = [{x:1,y:0},{x:-1,y:0},{x:0,y:1},{x:0,y:-1}];
direction = dirs[Math.floor(Math.random()*dirs.length)];
}
function isInsideTrain(cell) {
return snake.some(seg => seg.x===cell.x && seg.y===cell.y);
}
function moveHead() {
const head = snake[0];
const newX = head.x + direction.x;
const newY = head.y + direction.y;
// wall collision
if (newX < 0 || newX >= canvas.width/CELL || newY < 0 || newY >= canvas.height/CELL) {
// reset
const centerX = Math.floor(canvas.width/2 / CELL);
const centerY = Math.floor(canvas.height/2 / CELL);
snake = [
{x: centerX, y: centerY},
{x: centerX-1, y: centerY},
];
for (let i=0;i<2;i++) snake.push({x: centerX-2-i, y: centerY});
// reposition passengers
passengers = [];
const passengerCount = 5;
for (let i=0;i<passengerCount;i++) {
let x,y;
do {
x = Math.floor(Math.random() * (canvas.width/CELL));
y = Math.floor(Math.random() * (canvas.height/CELL));
} while (isInsideTrain({x,y}) || y < (canvas.height/CELL - 2));
passengers.push({x,y});
}
resetDirection();
return;
}
// self collision
if (snake.length > 1) {
for (let i=1; i<snake.length; i++) {
if (snake[i].x===newX && snake[i].y===newY) {
// reset
const centerX = Math.floor(canvas.width/2 / CELL);
const centerY = Math.floor(canvas.height/2 / CELL);
snake = [
{x: centerX, y: centerY},
{x: centerX-1, y: centerY},
];
for (let i=0;i<2;i++) snake.push({x: centerX-2-i, y: centerY});
// reposition passengers
passengers = [];
const passengerCount = 5;
for (let i=0;i<passengerCount;i++) {
let x,y;
do {
x = Math.floor(Math.random() * (canvas.width/CELL));
y = Math.floor(Math.random() * (canvas.height/CELL));
} while (isInsideTrain({x,y}) || y < (canvas.height/CELL - 2));
passengers.push({x,y});
}
resetDirection();
return;
}
}
}
// add new head
const newHead = {x:newX, y:newY};
snake.unshift(newHead);
// passenger eat
for (let i=passengers.length-1; i>=0; i--) {
if (passengers[i].x===newX && passengers[i].y===newY) {
score++;
passengers.splice(i,1);
// grow
const tail = snake[snake.length-1];
snake.push({x:tail.x, y:tail.y});
break;
}
}
}
function updateDemo() {
if (!gameStarted) {
const head = snake[0];
const newX = head.x + direction.x;
const newY = head.y + direction.y;
// wall collision handled inside moveHead, but we need to call moveHead after checking direction? Actually moveHead will handle wall and self collision, but we need to ensure direction is set.
// We'll just call moveHead which will handle everything.
moveHead();
}
}
function draw() {
ctx.clearRect(0,0,canvas.width,canvas.height);
// platform
ctx.fillStyle = '#2e7d32';
const platformRows = 2;
const platformY = (canvas.height/CELL - platformRows) * CELL;
ctx.fillRect(0, platformY, canvas.width, platformRows*CELL);
// passengers
ctx.fillStyle = '#ffeb3b';
passengers.forEach(p => {
ctx.beginPath();
ctx.arc(p.x*CELL + CELL/2, p.y*CELL + CELL/2, 5, 0, Math.PI*2);
ctx.fill();
});
// train
// locomotive
ctx.fillStyle = '#ff4500';
ctx.fillRect(snake[0].x*CELL, snake[0].y*CELL, CELL, CELL);
// second cell of locomotive
ctx.fillRect(snake[1].x*CELL, snake[1].y*CELL, CELL, CELL);
// wagons
for (let i=2; i<snake.length; i++) {
const hue = Math.random()*360;
ctx.fillStyle = `hsl(${hue}, 80%, 50%)`;
ctx.fillRect(snake[i].x*CELL, snake[i].y*CELL, CELL, CELL);
}
// score
ctx.fillStyle = '#fff';
ctx.font = '20px sans-serif';
ctx.fillText('Score: '+score, 10, 30);
}
function loop() {
updateDemo();
draw();
requestAnimationFrame(loop);
}
canvas.addEventListener('click', () => {
if (!gameStarted) {
gameStarted = true;
overlay.style.display = 'none';
}
});
window.addEventListener('keydown', e => {
if (!gameStarted) {
overlay.style.display = 'none';
gameStarted = true;
return;
}
let newDir = {x:0, y:0};
switch(e.key) {
case 'ArrowUp': newDir = {x:0, y:-1}; break;
case 'ArrowDown': newDir = {x:0, y:1}; break;
case 'ArrowLeft': newDir = {x:-1, y:0}; break;
case 'ArrowRight': newDir = {x:1, y:0}; break;
default: return;
}
// prevent reverse direction
if (snake.length > 1 && newDir.x === -direction.x && newDir.y === -direction.y) {
return;
}
direction = newDir;
});
window.addEventListener('load', () => {
init();
requestAnimationFrame(loop);
});
</script>
</body>
</html>
```
Check for any missing semicolons or mismatched braces. Looks okay.
Now ensure that the page meets all constraints:
- One fenced code block with html. Yes.
- Inline CSS and JS. Yes.
- No external resources. Yes.
- No blocked functions. Yes.
- Visible on first paint: canvas draws train and passengers immediately; overlay visible.
- Demo mode runs (AI) behind overlay; after click, user can control.
- Adapt to viewport: canvas width 100% max 100vw, height auto; will scale.
- No console errors: we used requestAnimationFrame, etc. Should be fine.
Potential minor: In draw, we draw second cell of locomotive again; but we already drew first cell; second cell may overlap but fine.
Potential issue: In updateDemo, we call moveHead which already handles wall and self collision, but we also have direction set initially random; after resetDirection, direction may be random, but moveHead uses that direction. That's fine.
Potential issue: In init, we set snake length 4 (2 locomotive + 2 wagons). That's okay.
Potential issue: In moveHead, we check self collision after wall; but we also need to check self collision before adding new head? Actually we check after adding new head (the new head may collide with existing body). Our self collision check loops over snake length >1 and checks if any segment (i>=1) matches newX,newY. That includes the segment that was previously the head (i=1) which is adjacent, not same cell, so fine.
Potential issue: In moveHead, after adding new head, we also check for passenger and grow; but if we grow, the snake length increases, and the tail may be at same cell as new head? Not likely.
All good.
Now produce final answer.