Models 44
Train snakeOne-line

North Mini Code (free)

by CohereReasoningClean
Open in new window

Running live in a sandboxed frame. Click inside it before using the keyboard.

How it went

Total time
1 min 19 s
Cost
$0
Output tokens
14,989
Spent thinking
13,867 tokens
Speed
189.3 tok/s
Console errors
0
First token after
232 ms
First visible text after
1 min 15 s
Prompt tokens
350
Code size
6.8 KB
Finish reason
stop
Served by
Cohere
Model id
cohere/north-mini-code: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 · 263 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Train Snake Game</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
  margin:0;
  height:100vh;
  display:flex;
  align-items:center;
  justify-content:center;
  overflow:hidden;
  background:#e0e0e0;
  font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
#gameContainer {
  position:relative;
  width:100%;
  max-width:800px;
  aspect-ratio:1/1;
  background:#fafafa;
  box-shadow:0 4px 12px rgba(0,0,0,0.3);
}
canvas {
  position:absolute;
  top:0;
  left:0;
  width:100%;
  height:100%;
}
.overlay {
  position:absolute;
  top:0;
  left:0;
  width:100%;
  height:100%;
  background:rgba(0,0,0,0.7);
  color:#fff;
  display:flex;
  align-items:center;
  justify-content:center;
  flex-direction:column;
  z-index:10;
}
.overlay .message {
  font-size:2rem;
  text-align:center;
  padding:1rem 2rem;
  border:2px solid #fff;
  border-radius:8px;
  cursor:pointer;
  transition:background 0.2s;
}
.overlay .message:hover {
  background:rgba(255,255,255,0.2);
}
#score {
  position:absolute;
  top:10px;
  left:10px;
  font-size:1.5rem;
  color:#333;
  background:rgba(255,255,255,0.85);
  padding:5px 12px;
  border-radius:4px;
  z-index:5;
  pointer-events:none;
}
.hidden {
  display:none;
}
</style>
</head>
<body>
<div id="gameContainer">
  <canvas id="canvas" width="800" height="800"></canvas>
  <div id="score">Score: 0</div>
  <div id="startOverlay" class="overlay">
    <div class="message" tabindex="0" role="button">Click or press a key to start</div>
  </div>
  <div id="gameOverOverlay" class="overlay hidden">
    <div class="message" role="button">Game Over! Final Score: <span id="finalScore">0</span><br>Click to restart</div>
  </div>
</div>

<script>
document.addEventListener('DOMContentLoaded', function() {
  const canvas = document.getElementById('canvas');
  const ctx = canvas.getContext('2d');
  const scoreDisplay = document.getElementById('score');
  const startOverlay = document.getElementById('startOverlay');
  const gameOverOverlay = document.getElementById('gameOverOverlay');
  const finalScoreSpan = document.getElementById('finalScore');

  const COLS = 20;
  const ROWS = 20;
  let cellSize = 0;

  const directions = {
    up: { r: -1, c: 0 },
    down: { r: 1, c: 0 },
    left: { r: 0, c: -1 },
    right: { r: 0, c: 1 }
  };

  let snake = [];
  let direction = 'right';
  let passengers = [];
  let score = 0;
  let gameOver = false;
  const moveInterval = 300;
  let lastMoveTime = 0;

  function resizeCanvas() {
    const rect = canvas.getBoundingClientRect();
    canvas.width = rect.width;
    canvas.height = rect.height;
    cellSize = canvas.width / COLS;
  }
  window.addEventListener('resize', resizeCanvas);

  function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    for (const p of passengers) {
      const x = p.c * cellSize;
      const y = p.r * cellSize;
      ctx.beginPath();
      ctx.arc(x + cellSize/2, y + cellSize/2, cellSize/3, 0, Math.PI*2);
      ctx.fillStyle = '#FFD700';
      ctx.fill();
      ctx.closePath();
    }
    for (let i = 0; i < snake.length; i++) {
      const seg = snake[i];
      const x = seg.c * cellSize;
      const y = seg.r * cellSize;
      if (i === 0) {
        ctx.fillStyle = '#8B4513';
        ctx.fillRect(x, y, cellSize, cellSize);
        ctx.beginPath();
        ctx.moveTo(x + cellSize/2, y);
        ctx.lineTo(x, y - cellSize*0.5);
        ctx.lineTo(x + cellSize, y - cellSize*0.5);
        ctx.closePath();
        ctx.fillStyle = '#555';
        ctx.fill();
      } else {
        const wagonColors = ['#FF6B6B','#4ECDC4','#45B7D1','#96CEB4','#FFEAA7','#FD79A8'];
        const color = wagonColors[(i-1) % wagonColors.length];
        ctx.fillStyle = color;
        ctx.fillRect(x, y, cellSize, cellSize);
        ctx.fillStyle = '#FFF';
        ctx.fillRect(x + cellSize*0.2, y + cellSize*0.2, cellSize*0.6, cellSize*0.6);
      }
    }
  }

  function getNextDirection(snake, current) {
    const opposite = { up: 'down', down: 'up', left: 'right', right: 'left' };
    const all = ['up','down','left','right'];
    const shuffled = all.filter(d => d !== opposite[current]).sort(() => Math.random() - 0.5);
    const head = snake[0];
    for (const d of shuffled) {
      const dr = directions[d].r;
      const dc = directions[d].c;
      const nr = head.r + dr;
      const nc = head.c + dc;
      if (nr < 0 || nr >= ROWS || nc < 0 || nc >= COLS) continue;
      const collides = snake.slice(0, -1).some(seg => seg.r === nr && seg.c === nc);
      if (!collides) return d;
    }
    return current;
  }

  function showGameOver() {
    gameOverOverlay.classList.remove('hidden');
    finalScoreSpan.textContent = score;
    gameOverOverlay.onclick = () => {
      gameOverOverlay.classList.add('hidden');
      startOverlay.classList.remove('hidden');
    };
  }

  function aiStep() {
    if (gameOver) return;
    const now = Date.now();
    if (now - lastMoveTime >= moveInterval) {
      makeMove();
      lastMoveTime = now;
    }
    requestAnimationFrame(aiStep);
  }

  function makeMove() {
    const newDir = getNextDirection(snake, direction);
    direction = newDir;
    const head = snake[0];
    const dr = directions[direction].r;
    const dc = directions[direction].c;
    const newHead = { r: head.r + dr, c: head.c + dc };

    if (newHead.r < 0 || newHead.r >= ROWS || newHead.c < 0 || newHead.c >= COLS) {
      gameOver = true;
      showGameOver();
      return;
    }

    const collides = snake.slice(0, -1).some(seg => seg.r === newHead.r && seg.c === newHead.c);
    if (collides) {
      gameOver = true;
      showGameOver();
      return;
    }

    const passengerIdx = passengers.findIndex(p => p.r === newHead.r && p.c === newHead.c);
    const grew = passengerIdx !== -1;
    if (grew) {
      passengers.splice(passengerIdx, 1);
      score++;
      scoreDisplay.textContent = `Score: ${score}`;
    }

    snake.unshift(newHead);
    if (!grew) snake.pop();
    draw();
  }

  function startGame() {
    startOverlay.classList.add('hidden');
    score = 0;
    scoreDisplay.textContent = 'Score: 0';
    direction = 'right';
    snake = [];
    const midCol = Math.floor(COLS / 2);
    const midRow = Math.floor(ROWS / 2);
    for (let i = 0; i < 3; i++) {
      snake.unshift({ r: midRow, c: midCol - i });
    }
    passengers = [];
    for (let i = 0; i < 12; i++) {
      let r, c;
      do {
        r = Math.floor(Math.random() * ROWS);
        c = Math.floor(Math.random() * COLS);
      } while (snake.some(seg => seg.r === r && seg.c === c));
      passengers.push({ r, c });
    }
    gameOver = false;
    resizeCanvas();
    lastMoveTime = Date.now();
    requestAnimationFrame(aiStep);
  }

  startOverlay.querySelector('.message').addEventListener('click', startGame);
  window.addEventListener('keydown', startGame);

  gameOverOverlay.classList.add('hidden');
});
</script>
</body>
</html>
Reasoning trace (13,867 tokens)
We need to produce a self-contained HTML page, with inline CSS/JS, making a snake game. The snake is a little train with a locomotive and colorful wagons, picking up passengers waiting on the platform. We need to meet the constraints: one fenced code block with HTML (no extra text). It must be 1280x800 viewport, adapt gracefully down to 375px. Something visible on first paint. Game must start in a self-playing demo mode (AI) behind a "Click or press a key to start" message, if the demo uses keyboard. The snake game likely uses arrow keys to control direction. So need to have a start message and then after user clicks or a key press, start the game and then have AI control? Actually "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 is confusing. Usually, you need a start prompt. After user clicks or a key press, the game starts. But to judge the game, you can have AI automatically start after prompt? Possibly we can have a "Click or press a key to start" overlay that when clicked, the game begins and then AI controls snake with maybe random or simple pattern. The snake is a train: locomotive (front) and wagons (body). We need to draw them, maybe using SVG or HTML/CSS. Better to use HTML with canvas for drawing. Let's consider: Use an HTML5 canvas for the game grid. Draw the snake as train: locomotive at head, followed by wagons, maybe with colors. There are also "passengers waiting on the platform", likely stationary items that the snake can "collect". In snake game, typically you collect food (apples) that appear randomly. Here, maybe "passengers" appear on the board and when the snake "eats" them (touches), they disappear and increase score. The snake moves automatically with AI after start.

We need to show something meaningful on first paint: perhaps a background scene with tracks, platform, train elements. The board can be a grid of cells. The locomotive/wagons can be drawn as rectangles or using simple shapes. For simplicity: The board is a 20x20 grid of cells (maybe 30px per cell). The train consists of a locomotive (head) and several wagons (body). Wagons have different colors. The train moves in 4 directions based on direction. At start, direction maybe right. The snake can be controlled by arrow keys; but if we want AI, we can have simple random direction changes at intervals. However the requirement 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." Probably means: The game will start automatically after user clicks the start message, but the AI will control the snake, not the user. So we can ignore user input after start; maybe we can have a simple AI that moves the snake in a certain path. However the spec also says: "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 if we are using keyboard input for control, we need to show start message. So we could allow the user to control direction using arrow keys, but for demonstration, we can let the AI control after start. The spec seems contradictory: "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." Means: When the page loads, we need a start message that is visible, and after clicking it, the game begins (maybe automatically with AI). So the game is pre-set with an AI demonstration, not actual user controlling.

Thus we can create a start overlay with "Click or press a key to start". When clicked or a key is pressed, the overlay disappears, the game loop starts, and we run an AI that moves the snake through the board, picking up passengers. The AI can be simple: change direction at random intervals or follow a pre-defined path. To keep it simple, we can have a deterministic AI that moves in a straight line until hitting a wall, then turns right (like a simple wall-follower). It can also collect passengers placed randomly.

The snake will be drawn as train: locomotive at head, maybe with a simple shape: a rectangle with a triangle on top for the smoke? Wagons can be rectangles with different colors. The snake body length grows when it moves. The passengers are maybe colored circles on the platform.

We need to handle collisions: snake hitting itself or hitting walls ends the game. Score increments when snake "eats" a passenger. The game can be demo, AI continues to replay after game over? We could have infinite loop.

We need to ensure the code has no external resources, no localStorage, etc.

We also need to make it look good on 1280x800 and down to 375px. We can use CSS to make canvas responsive.

We need to ensure the page loads something visible: the start overlay and the game board (maybe hidden initially, but then becomes visible after start). The start overlay should be visible initially.

We need to provide a single HTML file code block.

Now, design details:

- Canvas: Let's set canvas width/height = 600x600, which is > 800? Actually 600 is less. We could use 800x800? But the viewport is 1280x800. We can set canvas size to 800x800, but we need to show train board with many cells; we could have 40x40 cells each 20px = 800x800. That is good. But it may be too large for narrow viewports (375px). We need adapt gracefully. We could set the board to be responsive: use CSS to set canvas width: min(800px, 100vw). But we can also scale down.

Better approach: Use a grid of maybe 30x30 cells, each 20px = 600x600. Then we can set canvas width/height = 600. The container can be a div with width: 100%; max-width: 600px; margin: auto. The canvas will shrink with viewport but maintain aspect ratio. But we need to show something visible on first paint: the start overlay will show, and maybe the board partially visible (still hidden overlay covers). So the board can be present in DOM but hidden.

We also need to display UI: score, maybe "Game Over" after AI finishes? But we can have the AI run forever, so not needed.

Alternatively, we could have the AI play a finite demo that ends after a certain number of moves, then show final score. But requirement says "self-playing demo mode" not necessarily infinite. Could be a short demonstration that ends after some steps. And the page should be meaningful visible, maybe a final score or restart.

Better to have the AI run a fixed sequence for demonstration: maybe a few moves and then game over, then show final score. That will be visible after the AI finishes.

Thus: On start, we start a game loop that runs AI moves at some speed (e.g., 200ms per move). After each move, check for collisions and passenger consumption. Keep track of score. When snake collides (wall or self), stop loop and display a "Game Over" message with final score. Also maybe show "Click to restart" or something to allow user to start again.

The AI can be simple: if not at wall, move forward; else turn right; and random occasional change.

Implementation details:

- Represent grid with rows and cols: e.g., 20 rows, 20 cols.

- Snake: an array of {r,c} coordinates. Initially maybe 3 segments: head at middle, then body cells. The head direction: start moving right. The snake will be drawn: locomotive at head, wagons for each body segment.

- Wagons colors: we can cycle through an array of colors: ["#FF6B6B", "#4ECDC4", "#45B7D1", "#96CEB4", "#FFEAA7"]; locomotive maybe brown or black: "#8B4513" with a simple shape.

- Passengers: random positions not occupied by snake. Maybe up to 10 passengers placed at start. Or we can generate a new passenger each time one is eaten. Simpler: initial static passengers.

- On AI move: compute new head position based on current direction. Check if new head is out of bounds or collides with its own body (except tail if not moving into tail? Actually if snake moves, tail moves, so we need to check collision with body after moving; we can check if new head collides with any body segment (including tail). In snake, if moving into tail, the tail moves away so it's not a collision; but for simplicity, we can check if new head collides with any segment other than tail that will be removed (if moving normally). But we can just check if new head is in body (including tail) after moving? Simpler: after moving, we add new head, then if collides with any existing segment (including tail) we consider collision. But to avoid false collision when moving into tail, we could remove tail before checking; but for demonstration it's okay to treat moving into tail as game over? Might be too strict. Could allow it, but it's fine.

- If new head collides with a passenger: remove that passenger (maybe mark as null), increase score, do not remove tail (snake grows). Else: remove tail (snake length unchanged).

- After each move, draw.

Drawing functions:

- Use canvas context. Clear canvas each frame.

- Draw grid: maybe optional; but we can just draw objects.

- For each snake segment: iterate over snake segments. For head (first element), draw locomotive: perhaps a rectangle of size cellSize x cellSize, with a triangle on top for smoke. For simplicity: draw brown rectangle with a small black triangle.

- For each body segment (wagons): draw a colored rectangle with rounded corners maybe using fillStyle and fillRect.

- For passengers: draw colored circle (e.g., yellow) at cell center.

- UI: Score displayed at top left (maybe using CSS overlay). Game Over overlay also displayed with final score.

Start overlay: full-screen dark overlay with a large "Click or press a key to start" button/texte, maybe a semi-transparent box.

We need to support both mouse click and key press to start. So on click anywhere or any keydown, if start overlay visible, hide overlay and start game loop.

Game loop: Use requestAnimationFrame or setInterval. For smooth demonstration, we can use setInterval.

The AI: Let's create a simple AI:

- It chooses a next direction based on a predetermined path: For example, we could have a route that snakes through grid: right until wall, then down, then left, then up, etc. But easiest: we can let AI randomly change direction with some probability each step.

Simplify: at each move, choose a random direction (up, down, left, right) that is not opposite to current direction (to avoid immediate reverse). That will produce wandering behavior.

That will be simple. Could cause immediate collision. But we can limit AI: if next position is out of bounds or collides with body, we can try other direction options randomly, else change direction.

We can implement AI function:

function getNextDirection(snake, direction) {
    // possible directions: up, down, left, right.
    const dirs = {
        up: {r:-1,c:0},
        down: {r:1,c:0},
        left: {r:0,c:-1},
        right: {r:0,c:1}
    };
    const all = ['up','down','left','right'];
    // Filter out opposite direction
    const opposite = {up:'down',down:'up',left:'right',right:'left'};
    // Random order or shuffle
    let attempts = all.filter(d=>d!==opposite[direction]);
    // Shuffle attempts (or sort)
    attempts = attempts.sort(() => Math.random() - 0.5);
    for (let d of attempts) {
        let nr = snake[0].r + dirs[d].r;
        let nc = snake[0].c + dirs[d].c;
        // Check if valid (within bounds)
        if (nr>=0 && nr<rows && nc>=0 && nc<cols) {
            // Check collision with body (except tail maybe). For simplicity, check full body.
            const willCollide = snake.some((seg, idx) => {
                if (idx===0) return false; // head not colliding with itself yet.
                return seg.r===nr && seg.c===nc;
            });
            if (!willCollide) {
                return d;
            }
        }
    }
    // If no safe direction found, just reverse? but that might cause immediate death. For demo, just keep current direction even if leads to death.
    return direction;
}

Then each AI step, we compute newDirection = getNextDirection(snake, currentDirection). Then set direction = newDirection.

After updating direction, we compute newHead and handle collisions, growth.

Now display: score at top-left overlay using CSS absolute.

Game Over overlay: show final score and maybe "Click to restart". But requirement: if we use keyboard, display start message. But we are using keyboard for start only, not during game? Actually after start, we might still allow keyboard? In the spec: "if the demo uses the keyboard, display a clear 'Click or press a key to start' message." That means if we accept keyboard input for control (which we are not after start, it's AI). So we don't need to show start message during game. So fine.

We need to ensure that code runs without errors. Avoid console logs, but some debugging output is okay? Might be flagged as errors? Not necessarily. But best to avoid console errors like uncaught exceptions.

Let's outline HTML:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Train Snake Game</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
  body { margin:0; padding:0; background:#f0f0f0; font-family:sans-serif; overflow:hidden; }
  #gameContainer { position:relative; width:100%; max-width:800px; height:800px; margin:auto; }
  canvas { display:block; width:100%; height:100%; }
  .overlay {
    position:absolute; inset:0; background:rgba(0,0,0,0.7); color:#fff; display:flex; align-items:center; justify-content:center; flex-direction:column; z-index:10;
  }
  .hidden { display:none; }
  #startOverlay .message { font-size:2rem; text-align:center; padding:1rem 2rem; border:2px solid #fff; border-radius:8px; cursor:pointer; }
  #gameOverOverlay .message { font-size:2rem; text-align:center; padding:1rem 2rem; border:2px solid #fff; border-radius:8px; cursor:pointer; }
  #score { position:absolute; top:10px; left:10px; font-size:1.5rem; color:#333; background:rgba(255,255,255,0.8); padding:5px 10px; border-radius:4px; z-index:5; }
</style>
</head>
<body>
<div id="gameContainer">
  <canvas id="canvas"></canvas>
  <div id="score">Score: 0</div>
  <div id="startOverlay" class="overlay">
    <div class="message" tabindex="0" role="button">Click or press a key to start</div>
  </div>
  <div id="gameOverOverlay" class="overlay hidden">
    <div class="message" role="button">Game Over! Final Score: <span id="finalScore">0</span><br>Click to restart</div>
  </div>
</div>

<script>
 ... JS code ...
</script>
</body>
</html>

Now fill in JS:

Define constants:

const COLS = 20; // adjust based on canvas size
const ROWS = 20;
const CELL = 40; // pixel size; but we will compute based on canvas width/height.

But we need dynamic cell size based on canvas dimensions. We can compute cellSize = Math.floor(canvas.width / COLS); similarly for height. Since we set canvas width/height to container dimensions, we can compute using container size. But if we set canvas size in CSS, we can compute cell size based on COLS and canvas.getBoundingClientRect().width.

But it's easier to set canvas width/height directly in JS based on container size: e.g., const cellSize = 800 / COLS (if container is 800 width). But to be responsive, we can compute cellSize = Math.min(canvas.width, canvas.height) / Math.max(COLS, ROWS). But we can also set container's width and height and rely on CSS to maintain aspect ratio.

Better: In JS after canvas is loaded, we can compute cellSize = canvas.width / COLS. Since we want integer cell size, we can adjust COLS/ROWS accordingly, but it's fine if not integer? We can accept fractional cells? Not ideal.

Simplify: set fixed canvas size: 800x800. In CSS, set container width:100%; max-width:800px; height:800px. Then canvas will be 800x800. This ensures cell size integer if we set COLS=20: cellSize=40.

But on narrow viewport, container width will shrink (max-width 800px but width:100% so actual width will be less, maybe 375px). The canvas will maintain aspect ratio? Actually canvas width is set in CSS to 100% of container width (which is 100% of viewport width). So if viewport width is 375, container width = 375, canvas will be 375x375 if we keep aspect ratio? However we set height:800px in CSS, which will cause layout issues (overflow). The container has fixed height of 800px. So if width shrinks, there will be horizontal scrollbar or content cropping. We need adapt gracefully: we can make container maintain aspect ratio, like set padding-bottom: 100% (or 100% * height/width). But simpler: we can set container to have width:100vw; height:calc(100vw * (800/1280)) maybe? But requirement: adapt gracefully down to 375px wide, no horizontal scrollbar. So we need to ensure the board scales down.

We can set container with a fixed aspect ratio using CSS: e.g., .game-wrapper { position:relative; width:100vw; height:0; padding-bottom:100%; background:#fff; } Then canvas absolute positioned top:0 left:0 width:100% height:100%. This ensures that the container's height is proportional to width (i.e., square). If viewport width is 375, height will be 375 as well (since padding-bottom: 100% of width). But we need board height maybe larger? Actually board is square (800x800). But we can set aspect ratio to 1:1 for simplicity. The viewport is 1280x800 (16:10). But we can have board be square, scaling down. That's okay.

Better approach: set container width:100%; max-width:800px; height:100vh; but ensure that aspect ratio is preserved with object-fit: contain. But we can just set container to position:relative; width:100%; max-width:800px; padding-bottom:100%; (to maintain square). Then canvas will fill container and scale.

Let's design container:

<div id="gameContainer" style="position:relative; width:100%; max-width:800px; margin:auto; padding-bottom:100%; background:#fafafa; box-sizing:border-box;">
  <canvas id="canvas" style="position:absolute; top:0; left:0; width:100%; height:100%;"></canvas>
  ...

But we also need to display the overlay elements over canvas. So we can use absolute positioning for overlays inside the container.

Thus:

<div id="gameContainer" class="container">
  <canvas id="canvas"></canvas>
  <div id="score"></div>
  <div id="startOverlay" class="overlay">
    ...
  </div>
  <div id="gameOverOverlay" classでは ... > (hidden).
</div>

Now we need CSS for container that maintains aspect ratio:

.gameContainer {
  position: relative;
  width: 100%;
  max-width: 800px;
  margin: auto;
  padding-bottom: 100%; /* 1:1 aspect ratio */
  background: #fafafa;
}

Canvas {
  position: absolute;
  top: 0;
  left: 'acc' ; width: 100%;
  height: 100%;
}

Thus the canvas will fill the container. When container's width is 375px, height also 375px.

Now, define constants based on canvas size and cells.

We can in JS after canvas init:

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const cellSize = canvas.width / COLS; // because canvas.width = container width; If container height is same, fine.

But if container's width is not 800 due to responsive, cellSize will be smaller. For COLS=20, if width is 375, cellSize = 18.75, which is fractional but still okay for drawing rect.

But drawing rectangle with fractional size may cause blur, but it's fine.

Alternatively, we could compute cellSize = Math.floor(canvas.width / COLS). That's fine.

Now implement draw functions:

function drawGrid() maybe draw cells? Not needed.

function drawSnake() {
   for i=0..snake.length-1:
      if i==0: draw locomotive
      else: draw wagon
   For locomotive: draw brown rectangle and a black triangle on top (smoke). Let's draw rectangle:

   ctx.fillStyle = '#8B4513';
   ctx.fillRect(x, y, cellSize, cellSize);

   For smoke: draw triangle above rectangle: draw a triangle at top center: maybe draw a small triangle shape. Using ctx.beginPath, moveTo, lineTo. For triangle: points: (x + cellSize/2, y), (x, y - cellSize*0.5), (x + cellSize, y - cellSize*0.5). Fill with '#555'.

   For wagons: cycle colors. Use ctx.fillStyle = colors[i % colors.length]; ctx.fillRect(x, y, cellSize, cellSize). Could add rounded corners? Not needed.

Passengers: circles. At each passenger position: x = col*cellSize + cellSize/2; y = row*cellSize + cellSize/2; radius = cellSize/3; fillStyle = '#FFD700' (gold) or '#FF4500'; fill circle.

Score display: we already have HTML element #score. Update textContent.

Game Over overlay: after AI stops, show finalScore.

Start overlay: on click or keydown, hide startOverlay and start game loop.

Game loop:

let gameRunning = false;
let gameOver = false;
let score = 0;
let snake; // array of segments
let direction; // current direction 'up','down','left','right'.
let passengers = [];
let lastMoveTime = 0;
const moveInterval = 200; // ms per move

function initGame() {
   // reset variables
   score = 0;
   direction = 'right';
   // start snake in middle, length 3
   snake = [];
   const startCol = Math.floor(COLS/2);
   const startRow = Math.floor(ROWS/2);
   for (let i=0; i<3; i++) {
       snake.unshift({r:startRow, c:startCol - i}); // head at startCol
   }
   // place passengers: e.g., 10 random positions not occupied
   passengers = [];
   for (let i=0; i<10; i++) {
       let r,c;
       do {
           r = Math.floor(Math.random()*ROWS);
           c = Math.floor(Math.random()*COLS);
       } while (snake.some(seg=>seg.r===r && seg.c===c));
       passengers.push({r,c});
   }
   gameOver = false;
   draw();
   // start AI loop
   lastMoveTime = Date.now();
   // start interval for AI moves
   if (moveInterval) {
       aiLoop();
   } else {
       requestAnimationFrame(gameLoop);
   }
}

function aiLoop() {
   if (gameOver) return;
   const now = Date.now();
   if (now - lastMoveTime >= moveInterval) {
       makeAIMove();
       lastMoveTime = now;
   }
   // schedule next AI step
   setTimeout(aiLoop, moveInterval);
}

// Or using requestAnimationFrame: but setInterval is simpler.

function makeAIMove() {
   // compute next direction using AI
   const newDir = getNextDirection(snake, direction);
   direction = newDir;
   // compute new head
   const head = snake[0];
   const dr = directions[direction].r;
   const dc = directions[direction].c;
   const newHead = {r: head.r + dr, c: head.c + dc};

   // check for collisions
   // out of bounds
   if (newHead.r < 0 || newHead.r >= ROWS || newHead.c < 0 || newHead.c >= COLS) {
       gameOver = true;
       showGameOver();
       return;
   }

   // check self-collision (excluding tail because tail moves, but for simplicity include all)
   const collides = snake.some((seg, idx) => {
       // ignore tail? For now include all.
       return seg.r === newHead.r && seg.c === newHead.c;
   });
   if (collides) {
       gameOver = true;
       showGameOver();
       return;
   }

   // check if newHead on passenger
   const passengerIndex = passengers.findIndex(p => p.r === newHead.r && p.c === newHead.c);
   let grew = false;
   if (passengerIndex !== -1) {
       // eat passenger
       passengers.splice(passengerIndex, 1);
       grew = true;
       score++;
       // update score display
       document.getElementById('score').textContent = `Score: ${score}`;
   }

   // update snake
   snake.unshift(newHead);
   if (!grew) {
       snake.pop(); // remove tail
   }

   // draw
   draw();

   // continue loop
}

function draw() {
   // clear canvas
   ctx.clearRect(0,0,canvas.width,canvas.height);
   // draw passengers
   for (let p of passengers) {
       const x = p.c * cellSize;
       const y = p.r * cellSize;
       ctx.beginPath();
       ctx.arc(x + cellSize/2, y + cellSize/2, cellSize/3, 0, Math.PI*2);
       ctx.fillStyle = '#FFD700';
       ctx.fill();
       ctx.closePath();
   }
   // draw snake
   for (let i=0; i<snake.length; i++) {
       const seg = snake[i];
       const x = seg.c * cellSize;
       const y = seg.r * cellSize;
       if (i===0) {
           // locomotive
           ctx.fillStyle = '#8B4513';
           ctx.fillRect(x, y, cellSize, cellSize);
           // smoke triangle
           ctx.beginPath();
           ctx.moveTo(x + cellSize/2, y);
           ctx.lineTo(x, y - cellSize*0.5);
           ctx.lineTo(x + cellSize, y - cellSize*0.5);
           ctx.closePath();
           ctx.fillStyle = '#555';
           ctx.fill();
       } else {
           // wagon
           const colors = ['#FF6B6B','#4ECDC4','#45B7D1','#96CEB4','#FFEAA7','#FD79A8'];
           const color = colors[(i-1) % colors.length];
           ctx.fillStyle = color;
           ctx.fillRect(x, y, cellSize, cellSize);
           // optionally draw windows? Not needed.
       }
   }
}

function showGameOver() {
   document.getElementById('finalScore').textContent = score;
   const overOverlay = document.getElementById('gameOverOverlay');
   overOverlay.classList.remove('hidden');
   // maybe allow click to restart
   overOverlay.onclick = function() {
       overOverlay.classList.add('hidden');
       startOverlay.classList.remove('hidden');
       // optionally start again? Or let user click start again.
   };
}

Now we need to define directions mapping:

const directions = {
    up: {r:-1,c:0},
    down: {r:1,c:0},
    left: {r:0,c:-1},
    right: {r:0,c:1}
};

Define getNextDirection:

function getNextDirection(snake, currentDir) {
    const opposite = {up:'down',down:'up',left:'right',right:'left'};
    const allDirs = ['up','down','left','right'];
    // shuffle attempts
    const attempts = allDirs.filter(d=>d!==opposite[currentDir]).sort(() => Math.random() - 0.5);
    const head = snake[0];
    for (const d of attempts) {
        const dr = directions[d].r;
        const dc = directions[d].c;
        const nr = head.r + dr;
        const nc = head.c + dc;
        // check bounds
        if (nr<0 || nr>=ROWS || nc<0 || nc>=COLS) continue;
        // check self-collision (excluding tail maybe)
        // For simplicity, include all.
        const collides = snake.some((seg, idx) => {
            // exclude tail? Not needed.
            return seg.r === nr && seg.c === nc;
        });
        if (!collides) {
            return d;
        }
    }
    // fallback: keep current direction (even if leads to death)
    return currentDir;
}

Now event handling for start:

function startGame(e) {
    // if start overlay visible, hide and init
    const startOverlay = document.getElementById('startOverlay');
    if (!startOverlay.classList.contains('hidden')) {
        startOverlay.classList.add('hidden');
        initGame();
    }
}

// Attach click to startOverlay.message and also any keydown
const startMessage = document.querySelector('#startOverlay .message');
startMessage.addEventListener('click', startGame);
window.addEventListener('keydown', startGame);

But careful: if we also attach to any keydown globally, pressing any key will start game, which is fine. However requirement: 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. Since we have a start overlay that says "Click or press a key to start", it's fine.

Now we need to ensure that after start, we start AI loop. The AI loop we defined uses setTimeout recursively. However, we also need to ensure that the UI is responsive and not blocked.

We also need to ensure that the snake doesn't move too fast; set moveInterval maybe 300ms or 200ms. We can set 300ms.

Now for the final aspects: The page must look right in 1280×800 viewport and adapt gracefully down to 375px. Our container uses aspect ratio 1:1, so at 1280px width, height will be 1280px (exceeds viewport height). But container has max-width 800px, so width will be limited to 800px, height 800px. That's okay. At 375px, width 375px, height 375px. So board scales down.

We need to ensure there is no horizontal scrollbar: we set overflow:hidden on body. The container's width is 100% but max-width 800. So at large width, scroll may appear? But body overflow hidden prevents scroll. However content may overflow beyond viewport width if container width > viewport width, but body overflow hidden will hide overflow. But then the canvas might be clipped. Might be okay.

Alternatively, we can set body to have display:flex, justify-content:center, align-items:center to center the container. And body height 100vh. But if container is larger than viewport, we need to allow scrolling? But we want no horizontal scroll. Maybe set container to be max-width and max-height and center with transform? But we can keep it simple. Use CSS: body { margin:0; height:100vh; display:flex; align-items:center; justify-content:center; overflow:hidden; } .container { width:100%; max-width:800px; aspect-ratio:1/1; background:#fafafa; position:relative; } Then canvas fills container.

Let's design final CSS:

<style>
body {
  margin:0;
  height:100vh;
  display:flex;
  align-items:center;
  justify-content:center;
  overflow:hidden;
  background:#e0e0e0;
  font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
#gameContainer {
  position:relative;
  width:100%;
  max-width:800px;
  aspect-ratio:1/1;
  background:#fafafa;
  box-shadow:0 4px 12px rgba(0,0,0,0.3);
}
canvas {
  position:absolute;
  top:0;
  left:0;
  width:100%;
  height:100%;
}
.overlay {
  position:absolute;
  top:0;
  left:0;
  width:100%;
  height:100%;
  background:rgba(0,0,0,0.7);
  color:#fff;
  display:flex;
  align-items:center;
  justify-content:center;
  flex-direction:column;
  z-index:10;
}
.overlay .message {
  font-size:2rem;
  text-align:center;
  padding:1rem 2rem;
  border:2px solid #fff;
  border-radius:8px;
  cursor:pointer;
  transition:background 0.2s;
}
.overlay .message:hover {
  background:rgba(255,255,255,0.2);
}
#score {
  position:absolute;
  top:10px;
  left:10px;
  font-size:1.5rem;
  color:#333;
  background:rgba(255,255,255,0.85);
  padding:5px 12px;
  border-radius:4px;
  z-index:5;
  pointer-events:none;
}
.hidden {
  display:none;
}
</style>

Now JS modifications to include reference to startOverlay, gameOverOverlay, etc.

Now the page must be self-contained, with everything inline. This is fine.

Now ensure that code runs without errors. We'll need to ensure variable names are correct. Let's write code step by step.

Potential issues:

- The canvas element may not have width/height attribute; we set CSS width/height to 100% of container; but we also need to set canvas.width and canvas.height in JS to match container's pixel size (which may be scaled by devicePixelRatio). For crisp drawing, we could set actual canvas size using offscreen? But we can keep simple: In JS after DOM ready, set canvas.width = canvas.offsetWidth; canvas.height = canvas.offsetHeight; then compute cellSize accordingly.

- However, using CSS to set size may cause fractional pixels; we can still use them for drawing.

- We also need to ensure that canvas size is set before we draw; we can do in initGame after we have canvas dimensions.

Simplify: In DOMContentLoaded, get canvas and ctx. Set canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight. But clientWidth may be 0 initially if not rendered? Usually it's set after layout. We can use requestAnimationFrame to set.

Alternatively, we can set width/height in HTML: <canvas id="canvas" width="800" height="800">, and then set CSS width/height to 100% with object-fit: contain? But setting both width/height attributes and CSS may cause scaling issues.

Better: set width/height attributes to the desired pixel dimensions (800x800). Then set CSS to width:100%; height:100% to fill container while preserving aspect ratio but possibly scaling down (if container smaller). This is okay because canvas will keep original resolution and CSS scaling will downscale. It might cause blur, but it's okay.

Thus: <canvas id="canvas" width="800" height="800"></canvas>. CSS: width:100%; height:100%; object-fit: contain; maybe. But object-fit works for images; for canvas maybe not. We can set width/height to 100% and let CSS scale. That will preserve crispness only if container size matches 800. But on smaller screens, scaling will be subpixel, but it's okay.

Alternatively, we could compute cellSize based on canvas.width (the actual canvas pixel size) and not rely on CSS. If we set canvas width and height to 800, but container is smaller, the canvas will still be 800x800 and may overflow, but because of overflow:hidden it will be clipped. That's not ideal.

Thus better to use JavaScript to resize canvas to container size. So in DOMContentLoaded, we can set canvas.width = canvas.offsetWidth; canvas.height = canvas.offsetHeight. Then we compute cellSize accordingly. We may need to recalc on resize, but we can ignore.

Thus we need a resize handler. Could just compute cellSize on init and after start.

Simplify: We can set the canvas size using CSS to 100% and then after loading, set canvas.width = canvas.clientWidth; height = canvas.clientHeight; and compute cellSize = canvas.width / COLS.

We can also store original logical cell count and let cellSize be fractional.

Let's incorporate this:

window.addEventListener('DOMContentLoaded', function() {
   const canvas = document.getElementById('canvas');
   const ctx = canvas.getContext('2d');
   // initial size
   resizeCanvas();
   window.addEventListener('resize', resizeCanvas);
   function resizeCanvas() {
       const rect = canvas.getBoundingClientRect();
       canvas.width = rect.width;
       canvas.height = rect.height;
   }
   // rest of code will use canvas.width/height.

But we must ensure that we store ctx globally.

Better to move all JS inside <script> and define functions before event listeners.

Let's outline final script:

<script>
document.addEventListener('DOMContentLoaded', function() {
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    const scoreDisplay = document.getElementById('score');
    const startOverlay = document.getElementById('startOverlay');
    const gameOverOverlay = document.getElementById('gameOverOverlay');
    const finalScoreSpan = document.getElementById('finalScore');

    // game constants
    const COLS = 20;
    const ROWS = 20;
    let cellSize = 0;

    // direction definitions
    const directions = {
        up: { r: -1, c: 0 },
        down: { r: 1, c: 0 },
        left: { r: 0, c: -1 },
        right: { r: 0, c: 1 }
    };

    // state variables
    let snake = [];
    let direction = 'right';
    let passengers = [];
    let score = 0;
    let gameOver = false;
    const moveInterval = 300; // ms per move
    let lastMoveTime = 0;

    // helper: resize canvas to fit container
    function resizeCanvas() {
        const rect = canvas.getBoundingClientRect();
        canvas.width = rect.width;
        canvas.height = rect.height;
        cellSize = canvas.width / COLS; // assuming width==height
    }

    window.addEventListener('resize', resizeCanvas);

    // draw everything
    function draw() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        // draw passengers
        for (const p of passengers) {
            const x = p.c * cellSize;
            const y = p.r * cellSize;
            ctx.beginPath();
            ctx.arc(x + cellSize/2, y + cellSize/2, cellSize/3, 0, Math.PI*2);
            ctx.fillStyle = '#FFD700';
            ctx.fill();
            ctx.closePath();
        }
        // draw snake
        for (let i = 0; i < snake.length; i++) {
            const seg = snake[i];
            const x = seg.c * cellSize;
            const y = seg.r * cellSize;
            if (i === 0) {
                // locomotive
                ctx.fillStyle = '#8B4513';
                ctx.fillRect(x, y, cellSize, cellSize);
                // smoke triangle
                ctx.beginPath();
                ctx.moveTo(x + cellSize/2, y);
                ctx.lineTo(x, y - cellSize*0.5);
                ctx.lineTo(x + cellSize, y - cellSize*0.5);
                ctx.closePath();
                ctx.fillStyle = '#555';
                ctx.fill();
            } else {
                // wagon
                const wagonColors = ['#FF6B6B','#4ECDC4','#45B7D1','#96CEB4','#FFEAA7','#FD79A8'];
                const color = wagonColors[(i-1) % wagonColors.length];
                ctx.fillStyle = color;
                ctx.fillRect(x, y, cellSize, cellSize);
                // optional: draw simple window
                ctx.fillStyle = '#FFF';
                ctx.fillRect(x + cellSize*0.2, y + cellSize*0.2, cellSize*0.6, cellSize*0.6);
            }
        }
    }

    // AI: choose next direction
    function getNextDirection(snake, current) {
        const opposite = { up: 'down', down: 'up', left: 'right', right: 'left' };
        const all = ['up','down','left','right'];
        // shuffle order
        const shuffled = all.filter(d => d !== opposite[current]).sort(() => Math.random() - 0.5);
        const head = snake[0];
        for (const d of shuffled) {
            const dr = directions[d].r;
            const dc = directions[d].c;
            const nr = head.r + dr;
            const nc = head.c + dc;
            // bounds check
            if (nr < 0 || nr >= ROWS || nc < 0 || nc >= COLS) continue;
            // self-collision check (ignore tail because it moves away)
            const willHitTail = snake.length > 1 && nr === snake[snake.length-1].r && nc === snake[snake.length-1].c;
            const collides = snake.slice(0, -1).some(seg => seg.r === nr && seg.c === nc);
            if (!collides) {
                return d;
            }
        }
        // fallback: keep current direction
        return current;
    }

    // game over UI
    function showGameOver() {
        gameOverOverlay.classList.remove('hidden');
        finalScoreSpan.textContent = score;
        gameOverOverlay.onclick = () => {
            gameOverOverlay.classList.add('hidden');
            startOverlay.classList.remove('hidden');
            // reset for next play if desired, but for demo just show start
        };
    }

    // main game loop for AI
    function aiStep() {
        if (gameOver) return;
        const now = Date.now();
        if (now - lastMoveTime >= moveInterval) {
            makeMove();
            lastMoveTime = now;
        }
        // schedule next step
        requestAnimationFrame(aiStep);
    }

    function makeMove() {
        const newDir = getNextDirection(snake, direction);
        direction = newDir;
        const head = snake[0];
        const dr = directions[direction].r;
        const dc = directions[direction].c;
        const newHead = { r: head.r + dr, c: head.c + dc };

        // wall collision
        if (newHead.r < 0 || newHead.r >= ROWS || newHead.c < 0 || newHead.c >= COLS) {
            gameOver = true;
            showGameOver();
            return;
        }

        // self-collision (excluding tail)
        const willHitTail = snake.length > 1 && newHead.r === snake[snake.length-1].r && newHead.c === snake[snake.length-1].c;
        const collides = snake.slice(0, -1).some(seg => seg.r === newHead.r && seg.c === newHead.c);
        if (collides) {
            gameOver = true;
            showGameOver();
            return;
        }

        // passenger consumption
        const passengerIdx = passengers.findIndex(p => p.r === newHead.r && p.c === newHead.c);
        const grew = passengerIdx !== -1;
        if (grew) {
            passengers.splice(passengerIdx, 1);
            score++;
            scoreDisplay.textContent = `Score: ${score}`;
        }

        // update snake
        snake.unshift(newHead);
        if (!grew) {
            snake.pop();
        }

        draw();
    }

    // start game
    function startGame() {
        // hide start overlay
        startOverlay.classList.add('hidden');
        // reset state
        score = 0;
        scoreDisplay.textContent = 'Score: 0';
        direction = 'right';
        snake = [];
        const midCol = Math.floor(COLS / 2);
        const midRow = Math.floor(ROWS / 2);
        for (let i = 0; i < 3; i++) {
            snake.unshift({ r: midRow, c: midCol - i });
        }
        // generate passengers
        passengers = [];
        for (let i = 0; i < 12; i++) {
            let r, c;
            do {
                r = Math.floor(Math.random() * ROWS);
                c = Math.floor(Math.random() * COLS);
            } while (snake.some(seg => seg.r === r && seg.c === c));
            passengers.push({ r, c });
        }
        gameOver = false;
        // ensure canvas size
        resizeCanvas();
        // start AI loop
        lastMoveTime = Date.now();
        requestAnimationFrame(aiStep);
    }

    // event listeners
    startOverlay.querySelector('.message').addEventListener('click', startGame);
    window.addEventListener('keydown', startGame);

    // initially hide game over overlay
    gameOverOverlay.classList.add('hidden');
});
</script>

We need to ensure variable names: startOverlay.querySelector('.message') works. We used startOverlay as ID, we can refer to document.getElementById('startOverlay'). But we already have startOverlay variable. So startOverlay.querySelector('.message') is fine.

Now we need to check that after start, the AI continues and eventually collides and ends, showing game over overlay. The start overlay will be hidden; after game over overlay appears, user can click it to restart. But after clicking, we only hide the game over overlay and show start overlay again. That's fine.

We also need to ensure that the start overlay is visible initially. We have class 'overlay' and no hidden. Good.

Now the script uses requestAnimationFrame for aiStep, which uses Date.now() to control interval. Good.

Now check variables: In getNextDirection we use snake.slice(0, -1) to ignore tail. But we also need to check tail move. In our collision detection, we used snake.slice(0, -1).som... but we also later check willHitTail for something else. Not needed.

But note: In makeMove, we compute collides using snake.slice(0, -1).some(...). So that excludes tail, good.

Now we need to ensure that after each move we draw. draw uses cellSize. It also draws windows in wagons (optional). That's fine.

Now check initial size: container width is 100% of viewport with max-width 800px, aspect-ratio 1:1. So at large viewport (e.g., 1280px), container width will be 800px (max-width). So canvas width = 800px, height = 800px (aspect ratio 1:1). cellSize = 800 / 20 = 40. Good.

At narrow viewport (e.g., 375px), container width = 375px, height = 375px. cellSize = 375/20 = 18.75. That may cause drawing artifacts like subpixel. But it's acceptable.

Now we must ensure that no errors appear in console: For example, accessing canvas.width/height after resize might cause type errors? It should be fine.

Potential error: In draw, we use ctx.fillRect for locomotive and fill triangle. For triangle, we need to ensure that ctx.fillStyle is set before fill. We set ctx.fillStyle = '#555' after drawing triangle, but we need to set before filling. Actually the code:

ctx.beginPath();
ctx.moveTo(...);
ctx.lineTo(...);
ctx.lineTo(...);
ctx.closePath();
ctx.fillStyle = '#555';
ctx.fill();

That is fine.

But there is also a small bug: In draw for wagons, we draw windows (optional). We draw ctx.fillStyle = '#FFF'; ctx.fillRect(...). That will override previous fill style for next iteration? Actually we set fillStyle to white then draw window, but after that we move to next iteration; we need to reset fillStyle later. But we do not set it back before next iteration. However, after drawing the window, we don't change fillStyle, but next iteration we assign new color based on wagonColors. That's okay because we set ctx.fillStyle = color before drawing rectangle. But in between, we set fillStyle to '#FFF' for window, but after that we go to next iteration and set fillStyle = color, which overrides. So okay.

But we should ensure that after drawing window we restore fillStyle? Not necessary. It's fine.

Now we need to verify that canvas is visible after page load: Initially start overlay is visible, covering canvas. The canvas is present but not visible because overlay is absolute and covers full size. That's okay. The page must have something meaningful visible on first paint: the start overlay with message. That's meaningful. Good.

Now we need to ensure that the code is within a single fenced code block with language html. Also, we must not include any text before/after the code block. So we need to output:

```html
... entire HTML ...
```

Now we need to ensure that we close tags properly. The script is inside <script> tags.

Now check that we haven't used localStorage or other banned features. We used only canvas, CSS, JS, event listeners, requestAnimationFrame. That's fine.

Now check that we have the required "Click or press a key to start" message. We have that inside startOverlay. It says exactly that. Good.

Now check that if the demo uses keyboard (it does for start), we display clear message. Yes.

Now check that the game is self-playing demo mode (AI plays) behind start message. After start, we start AI via requestAnimationFrame and makeMove. So the snake moves automatically. Good.

Now check that there is no horizontal scrollbar: we set overflow:hidden on body, and container width is max-width 800px but we set width:100% of viewport but no overflow. On large screens, container width is 800px, which may be less than viewport width (1280px). So no overflow. On small screens, container width is less than viewport width, no overflow. So fine.

Now check that the page looks right on 1280×800: The container has max-width 800px, so it will be centered horizontally with margins? Actually we didn't set margin auto, but we have display:flex align-items:center justify-content:center, so container is centered. The container is 800px wide, height 800px, which is larger than viewport height (800px). Actually viewport height is 800px, container height is also 800px. So it will fill height exactly, and be centered horizontally. So it looks right.

Now check that there are no errors. We should test mental simulation.

Potential errors:

- In getNextDirection, we use snake.slice(0, -1) to ignore tail. In that slice, we need to check collisions. That's fine.

- In makeMove, we compute collides using snake.slice(0, -1).some(...). Good.

- In makeMove after game over we call showGameOver. In showGameOver, we set gameOverOverlay.onclick = () => { ... }. But note that the overlay already has class overlay with display:flex. Clicking overlay will trigger start of restart. But we also have startOverlay.onclick defined. So both overlays handle clicks.

- In startGame, we call resizeCanvas. But we also have window.addEventListener('resize', resizeCanvas);. So okay.

- In startGame we also call requestAnimationFrame(aiStep); after resetting variables. However, before the first frame, we need to draw. startGame calls resizeCanvas, then requestAnimationFrame(aiStep). The aiStep function will call requestAnimationFrame recursively, and inside that we have if condition check time to make move. However, the first frame after start may happen before any move interval has passed. That's fine; it will wait.

Potential error: In draw function, we reference cellSize, which is set in resizeCanvas after canvas dimensions. But draw is called only after makeMove, which is after startGame. At that point, cellSize should be set. Good.

One more nuance: The canvas may have been sized originally with default dimensions, but resizeCanvas is called on start and on window resize. However, draw function might be called before resizeCanvas in the first frame if we had earlier draws. But we only call draw after makeMove. So safe.

Potential bug: In draw, we use ctx.fillRect for locomotive and also draw triangle. For triangle, we do ctx.fillStyle = '#555'; ctx.fill(); but after that, we may need to set fillStyle again for next iteration. But that's fine.

One more thing: In draw, after drawing locomotive, we draw a triangle with black fill, but we also set fillStyle for locomotive earlier. After drawing triangle, we didn't reset fillStyle, but next iteration for wagons sets fillStyle = color. So fine.

Potential bug: In getNextDirection we use snake.slice(0,-1) to ignore tail. That's correct. But we also check willHitTail for some reason but never use it. We can remove it, but it's fine.

Potential bug: In makeMove we also compute willHitTail but not used. Could be removed but okay.

Now test the logic:

- On page load, startOverlay visible, canvas visible but covered.

- User clicks startOverlay message or any key -> startGame.

- startGame: hide startOverlay, reset snake, place passengers (12). Reset score. Set direction right. Set gameOver false. Call resizeCanvas: canvas.width = container width (maybe 800). cellSize = 800/20 = 40. Then requestAnimationFrame(aiStep). The AI loop will start.

- In aiStep: requestAnimationFrame(aiStep) schedules next frame. It checks now - lastMoveTime >= moveInterval (300ms). At start, lastMoveTime set to Date.now() after reset, so condition will be true after 300ms.

- makeMove: getNextDirection chooses a random direction not opposite current. Suppose picks right (same direction). New head = (midRow, midCol+1). Check bounds (should be in). Check self-collision (snake slice no tail). Not colliding. Check if passenger at that cell: maybe not (random). If not, no growth. Snake moves (unshift new head, pop tail). Draw: locomotive at new head, etc.

- Over time, snake moves around, eating passengers occasionally, score increments. Eventually snake will collide with wall or itself. When that happens, gameOver set true and showGameOver. showGameOver reveals overlay with final score. The overlay has onclick that hides overlay and shows startOverlay again. So user can click to restart.

Thus the demo works.

Now check that no errors appear in console due to missing variables: In draw, we use ctx. We defined ctx earlier. Good.

Now check that we referenced startOverlay and gameOverOverlay correctly: In script we have startOverlay = document.getElementById('startOverlay'), gameOverOverlay = document.getElementById('gameOverOverlay').

Now check that startOverlay.querySelector('.message') exists. Yes.

Now check that we didn't use localStorage or other banned features. Good.

Now check that we used no external files, CDN URLs, etc. All inline.

Now check that code is wrapped in a single fenced code block. Good.

Now before final, ensure that there are no stray characters or console.log statements. There are no console.log.

Potential errors: In the draw function, we draw windows in wagons using ctx.fillStyle = '#FFF'; ctx.fillRect(...). But after drawing window we don't reset fillStyle. Then in next iteration for wagons, we set ctx.fillStyle = color; and draw rectangle. So it's fine.

But one small nuance: The window draws over the colored wagon rectangle, covering part of it. That's okay.

Now check if we need to ensure the page has a title. Not required, but fine.

Now check that the HTML is valid: self-closing tags etc.

Now check code correctness for syntax errors: Everything appears correct.

Potential bug: In startGame we call requestAnimationFrame(aiStep). The function aiStep will call requestAnimationFrame recursively. But we also have a timeout? We use requestAnimationFrame for AI steps. That's fine.

Now check that we set container's aspect ratio correctly: we used style="aspect-ratio:1/1;". That is supported in modern browsers. If not, might fallback to default. But we also have width and height set in CSS. That should be fine.

Now check that we placed <style> inside <head>. Good.

Now consider potential error: In the CSS, we set overlay .message:hover background. That's fine.

Now check that the startOverlay is visible initially. It has class overlay and no hidden class. Good.

Now check that the gameOverOverlay initially has hidden class. Yes, we set classList.add('hidden') in script after DOMContentLoaded. Good.

Now check that the startOverlay message has tabindex and role, it's a button-like element. That's okay.

Now we need to ensure that the page has something visible on first paint. The startOverlay message is visible.

Now check that the page uses the "Click or press a key to start" message exactly. Yes.

Now check that the snake is a train with locomotive and colorful wagons. Yes, we draw locomotive with brown body and smoke triangle, and wagons with colored rectangles.

Now check that the snake picks up passengers waiting on the platform. Yes, we draw passengers as gold circles, snake eats them (consumes) and score increments.

Now check that the snake game includes snake movement and collision detection. Yes.

Now check that the snake moves automatically (AI). Yes.

Now check that the iframe only receives keyboard events after user clicks it. Since we have a start overlay covering entire screen, keyboard events are captured on window, but they are used to start the game. The spec says iframe only receives keyboard events after the user clicks it: if demo uses keyboard, display clear start message. We meet that: we show start message and any key triggers start.

Now we must ensure that there is no interaction like pointer events before start. The start message is a div with cursor:pointer and role button. That's fine.

Now ensure the code is self-contained. Yes.

Now ensure that there is no extraneous output. The final answer must be only one fenced code block with HTML.

Thus we should output the final answer.

One more check: We used <script> tags inside body. That's okay.

Potential improvements: We might need to ensure the canvas is not blurred on scaling. But it's okay.

Potential bug: The CSS uses aspect-ratio which may not be supported on older browsers, but it's fine for modern browsers. Could also use padding-bottom: 100% for container. However, we used aspect-ratio:1/1; that works.

Alternatively, we can use padding-bottom: 100% and position:relative to maintain aspect ratio. But it's fine.

Now final answer: Provide HTML code.

Let's produce final answer.