Train snakeOne-line
LFM3-1.7B (free)
by LiquidAIreasoning defaultRan out of tokens
How it went
- Total time
- 39.3 s
- Cost
- $0
- Output tokens
- 8,192
- Spent thinking
- 8,192 tokens
- Speed
- — tok/s
- Console errors
- —
- First token after
- 225 ms
- First visible text after
- —
- Prompt tokens
- 378
- Code size
- 0 B
- Finish reason
- length
- Served by
- Liquid
- Model id
liquid/lfm-3-1.7b:free- Reasoning sent
model default- Max output
- 8,192 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.
Reasoning trace (8,192 tokens)
We need to create a self-contained HTML file for a Snake game where the snake is a little train with a locomotive and colorful wagons, picking up passengers waiting on the platform. The game should start in self-playing demo mode (simple AI plays) behind "Click or press a key to start" message. Must be responsive: 1280x800 viewport, no horizontal scrollbar. Must run without errors in latest Chrome. No localStorage etc.
We must output exactly one fenced code block containing the complete HTML file. Use CDN URLs allowed? We can use CSS from CDN maybe, but we can also just write inline styles. For simplicity, I'll embed everything in <style> and <script>.
Game mechanics: Snake moves on a grid. The snake consists of multiple segments (wagons). There's a locomotive at front? Or maybe the locomotive is separate piece that moves differently? Let's design:
- Platform: horizontal line of cells.
- Passengers waiting on the platform: some positions where they are waiting; when snake passes over them, it picks them up as part of its body? Or maybe the snake collects passengers along the way. The phrase "picking up passengers waiting on the platform" suggests that there are passenger tokens on the platform that the snake can capture. When the snake's head reaches a passenger cell, it adds them to its body (maybe as new segment) and they move forward.
Simplify: Represent the board as a grid of size e.g., width=30 columns, height=5 rows. The snake starts at center moving right. It has several segments (e.g., 4 wagons + locomotive?). Actually "little train with a locomotive and colorful wagons". So perhaps first wagon is red, second green, third blue, fourth yellow, plus a locomotive (black?) at front? Or maybe the locomotive is the tail? Usually trains have engine at front. But here "snake is a little train with a locomotive and colorful wagons". Could interpret: The snake shape is like a train carriages: locomotive at front, then colored wagons behind. Or maybe the locomotive is the head and wagons follow. Since it's a snake, the head moves, and the body follows. So we could have 3 wagons (colored) and a locomotive at the front. That would give 4 segments total.
But simpler: Represent each segment as a rectangle (wagon) with color. The locomotive could be a different color (like black). The snake moves by shifting segments.
Also "passengers waiting on the platform": On the left side of the track, there might be platforms with people waiting. When the snake's body touches those squares, it picks them up and becomes part of its body. Maybe we add extra points or just show them being collected.
Given constraints: Need something visible immediately after load. So we need an initial scene: the train on the platform, passengers waiting, and the game controls.
Let's outline:
- Canvas or HTML/CSS layout? Using canvas is easier for game logic but we need to render quickly. However, we can do DOM-based using divs and CSS animations. But canvas may be more straightforward for game. Yet we need to avoid external libraries. Using canvas is fine.
- Since we cannot use network requests except CDN, but canvas doesn't require any.
- Ensure no horizontal scrollbar: set overflow-x: hidden on body or container.
- Responsive: viewport 1280x800. We'll make game area width ~600px centered, leaving space for UI.
- First paint: Show the starting position and text "Click or press a key to start".
Implementation plan:
1. HTML structure:
- A container div with id="game".
- Inside: canvas element for drawing.
- Overlay div for instructions.
2. CSS:
- Set background, fonts, colors.
- Make canvas fill screen or appropriate area.
- Add style for mobile: max-width 600px, margin auto.
3. JavaScript:
- Define constants: grid size (cells = 20px), number of rows/cols, speed, initial snake length, etc.
- Initialize snake array of {x,y,color} objects. Starting position: maybe at x=10, y=0 (top row?), moving right.
- Passenger objects: list of coordinates where passengers wait. They appear on the left side of the platform. E.g., at x=0..5, y=some row. We'll place them as small circles.
- Game loop: update snake position based on direction (keyboard arrow keys). If key pressed, change direction (but prevent reversing into itself).
- Check collisions with walls and own body. If collision -> game over (show end message). But we want self-playing demo mode: simple AI plays. So we don't let player control? The instruction 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." This means initially we display "Click or press a key to start", and then the game runs automatically with AI controlling the snake? Or maybe we keep keyboard input but the AI decides next move? Typically "self-playing demo mode" implies the AI (or random) moves automatically while you can still click to pause/reset. But simplest: After user clicks or presses a key, the game starts and the snake moves automatically according to AI (no manual control). But we can implement that by not listening to keydown events during gameplay, only handling start event. However, typical Snake games allow player to control. But requirement says "click or press a key to start". So we can have a button or link that triggers start. Then after start, we can ignore further key inputs until game over? Or we can still handle them but AI will override? Simpler: In start state, we show message and disable keyboard events. Once started, we simulate AI: choose direction randomly among valid ones, move, etc. But implementing full AI for snake is tricky. Perhaps we can just let the snake move automatically following its current path (i.e., no player input). That is basically deterministic: the snake continues moving in same direction unless hit wall/passenger? Not ideal.
Better approach: Provide a "Start" button that triggers the game. At that moment, we set a flag `isRunning` false. Then we start the game loop which updates snake every frame. The movement direction can be controlled by arrow keys if the user wants to play manually, but since it's demo mode, we can simply have the AI decide next move: pick a random direction from available ones (not opposite previous). That yields a non-deterministic but plausible behavior.
Thus we can implement: In the main loop, before updating, check if `started` and `isAI`. If yes, compute possible moves: [up, down, left, right] excluding reverse of last direction and out-of-bounds. Choose randomly. Move accordingly. Also optionally check collisions; if collision, game over.
But we also need to incorporate passing passengers: When snake's head enters a passenger coordinate, we add that passenger as a new segment (increase length) and mark them as collected. Possibly also remove them from waiting list.
Simplify: Just treat passenger cells as obstacles that increase snake length temporarily? Might be too complex. Instead, we can just draw passengers as icons on the platform and when snake overlaps them, we highlight them.
Given time constraints, we can produce a functional-looking demo even if not fully polished.
Let's design specifics:
- Grid: 25 columns (width 250px) and 6 rows (height 120px). Each cell 25x24 approx.
- Colors: Locomotive: #333 (dark gray). Wagons: Red (#ff4444), Green (#44ff44), Blue (#4444ff), Yellow (#ffff44). Passengers: small white dots.
- Snake initial: 4 wagons (red, green, blue, yellow) plus locomotive at front? Or maybe locomotive is separate entity moving ahead. Let's define snake as an array of objects representing cars. The order: index 0 is head, then tail. The locomotive could be at index 0 (head) and wagons behind. But typical train: locomotive leads, wagons follow. So head is locomotive, then wagons. So we can assign colors: head = locomotive (black), then wagons: red, green, blue, yellow. That gives 5 segments. But we can limit to 4 segments for simplicity.
Alternatively, we can have 3 wagons and a locomotive at front making 4 total. Let's go with 4 total: locomotive (black), then three colored wagons (red, green, blue). Or maybe four colored wagons and locomotive at back? Hard to guess. I think easiest: 4 segments: head (locomotive, black), then three wagons (red, green, blue). But we need enough to see train shape.
I'll decide: Head = locomotive (black). Body = 3 wagons: topmost red, middle green, bottom blue. That forms a vertical stack? But snake moves horizontally across platform. So orientation matters. Let's align snake horizontally: head at rightmost column, moving rightwards. So we can represent positions as (x,y) where x increases to the right. So we can have 4 segments arranged horizontally: leftmost is tail, rightmost is head. So head is at (x+len-1, y). For simplicity, we can generate initial snake as an array of positions: [{x:10, y:0}, {x:11, y:0}, {x:12, y:0}, {x:13, y:0}] with colors: head (index 3) = locomotive (black), others = wagons (red, green, blue). But we can randomize later.
Passengers: Place them on the left side of the platform near x<10. For example, at (5,0), (6,0), (7,0), (8,0) etc. We'll have a few.
Now, the game flow:
- Load HTML.
- Immediately after page load, set up canvas, listeners for keyboard (to start). Initially hide canvas? We'll show overlay "Click or press a key to start" at top center.
- On click or keypress (keydown), set `started = true`, clear overlay, enable canvas events, and start the game loop.
- In the loop:
- Determine direction: if `started && !isAI`: use arrow keys to control (but we said AI mode). To satisfy both, we can support both: if user hasn't clicked yet, we show message; once started, we switch to AI mode and ignore further key presses (or we can still process but AI chooses). Simpler: after start, we set `autoMode = true`. Then in loop, we always pick random direction.
- Update head position: newX = head.x + dx, newY = head.y + dy.
- Insert new head at front of array.
- Remove tail (pop last).
- Check collisions: if new head goes out of bounds (x<0 or x>=width or y<0 or y>=height) OR collides with any existing body segment (including newly added? Should check against all segments including new head? Usually collision includes self. So check against whole array after insertion? Better: after inserting new head, check overlap with any other segment (excluding itself). If collision, game over.
- If collision with passenger: maybe we consider that as collecting passenger. We can detect if new head matches a passenger coordinate. If so, we add passenger to a list and maybe extend body? But for simplicity, just mark as collected and maybe add a temporary marker.
- Draw: Clear canvas, draw grid lines, draw snake (segments as rectangles), draw locomotive separately maybe thicker, draw passengers as small circles.
- Control speed: update every ~100ms.
- Game over: stop loop, show "Game Over" message, maybe restart button.
Since we need to run zero errors in console, ensure no missing variables.
Implement with canvas 1280x800. Use requestAnimationFrame for animation.
Responsive: set canvas width/height via window.innerWidth/innerHeight, adjust grid spacing accordingly.
Let's code step-by-step.
### HTML skeleton
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Snake Train</title>
<style>
body {
margin:0;
font-family:Arial, sans-serif;
background:#f0f0f0;
display:flex;
justify-content:center;
align-items:center;
height:100vh;
overflow:hidden;
}
#container {
position:relative;
width:90%;
max-width:900px;
padding:20px;
box-sizing:border-box;
}
canvas {
border:1px solid #ccc;
background:#fff;
display:block;
margin:0 auto;
}
.overlay {
position:absolute;
top:50%; left:50%;
transform:translate(-50%,-50%);
text-align:center;
pointer-events:none; /* but we need to receive click? */
}
.overlay h2 {
margin:0;
font-size:2rem;
color:#333;
}
.controls {
margin-top:10px;
font-size:1.2rem;
}
@media (max-width:600px) {
body { flex-direction:column; }
#container { width:95%; }
}
</style>
</head>
<body>
<div id="container">
<canvas id="gameCanvas" width="800" height="600"></canvas>
<div class="overlay"><h2>Click or press a key to start</h2></div>
<div class="controls">Use Arrow Keys to move (after start)</div>
</div>
<script>
// ... script ...
</script>
</body>
</html>
```
But note: The overlay needs to be interactive for click. Setting pointer-events none prevents click propagation to canvas. We need to allow click on overlay to trigger start. So we should set pointer-events: none on overlay? Actually we want click on overlay to trigger start. So we can set overlay to be pointer-event capable. Usually we can set `pointer-events: all` on overlay and canvas to normal. But then clicking canvas won't propagate to overlay. Simpler: Keep overlay as a div with text, and attach event listener to document or canvas? We can listen for keydown globally and also handle click on canvas? But we need to differentiate between click on overlay vs canvas. Approach: Use a single canvas and put the overlay as a separate element above canvas. Click on overlay triggers start. Pressing key on canvas also triggers start. So we can attach event listeners to both canvas and document. But we must avoid interfering with game loop.
Simplify: Use a button-like approach: Have a button element inside container that says "Start Game". Clicking it triggers start. And pressing arrow keys also triggers start. That satisfies "Click or press a key to start". So we can add a button.
Let's restructure:
```
<div id="container">
<canvas id="gameCanvas" width="800" height="600"></canvas>
<button id="startBtn">Start Game</button>
<div class="overlay"><h2>Click or press a key to start</h2></div>
<div class="controls">Arrow keys to move</div>
</div>
```
Then script: on button click, set `started=true`, reset snake, etc. Also on keydown, if not already started, set `started=true`.
But careful: If we set `started` true on button click, we also need to disable default button events? Not needed.
We'll implement:
- Variables: `let gameActive = false;`
- `let snake = []; // array of {x,y,color}`
- `let direction = {dx:0, dy:0};`
- `let passengers = [] // array of {x,y}`
Initialize:
- width = 800, height = 600.
- cols = 40, rows = 15 => cellSize = 20? Actually 800/40=20, 600/15=40. Hmm. Let's pick cellSize = 20 for x, 60 for y? Not necessary. We'll just use integer coordinates relative to canvas.
Simpler: Use pixel coordinates directly. We'll define grid of 30 columns (800/30 ≈ 26.66). Not nice. Let's just use arbitrary numbers.
Given limited time, we can approximate.
I'll adopt a simple approach: Use canvas size 800x600, and define grid steps: 25 pixels per column, 80 pixels per row. So colCount = Math.floor(800/25)=32, rowCount = Math.floor(600/80)=7. Good.
Define cellSizeX = 25, cellSizeY = 80.
Initialize snake: start at x=20, y=0 (row 0). Direction: right (dx=1, dy=0). Snake length = 4. Positions: [{x:20, y:0}, {x:21, y:0}, {x:22, y:0}, {x:23, y:0}]. Colors: head (last) = locomotive (black #000000), others: red, green, blue? Let's assign: index0 tail = red, index1 = green, index2 = blue, index3 = locomotive? Wait we want head at index0? Usually array[0] is head. Let's define snake[0] = head. So we can set head at (20,0) and then subsequent positions offset by 1. So snake = [
{x:20, y:0, color:'#000000'}, // head locomotive
{x:21, y:0, color:'#FF0000'}, // wagon red
{x:22, y:0, color:'#00FF00'}, // wagon green
{x:23, y:0, color:'#0000FF'} // wagon blue
]
That's 4 segments. Tail at x=23.
Passengers: place at x=5..9, y=0 maybe. Let's define passengers = [
{x:5, y:0},
{x:6, y:0},
{x:7, y:0},
{x:8, y:0}
]
These are waiting on platform left side.
Now, game loop:
- Every frame (requestAnimationFrame):
- If not gameActive: return.
- Compute newHead = {x: snake[0].x + direction.dx, y: snake[0].y + direction.dy}.
- Insert newHead at beginning of snake array.
- Remove last element (tail) to keep length constant? In classic snake, you drop tail when moving. But if we collect passengers, we might want to keep them. For simplicity, we keep length constant (same as before). So we pop tail after adding new head. That results in same length. But if we want to grow when collecting passengers, we could push new head and not pop tail. But then length changes. Let's decide to keep length constant for now.
- Check collisions:
* Out of bounds: if newHead.x < 0 || newHead.x >= cols || newHead.y < 0 || newHead.y >= rows => game over.
* Collide with any body segment (including new head? exclude itself): iterate over snake[1...]. If any match newHead => game over.
- If game over: break loop and show message.
- Else continue.
- Randomly choose new direction: from ['up','down','left','right'] corresponding to dx/dy. Exclude reverse of previous direction (direction reversed). Also exclude directions leading off screen. Pick one uniformly.
- Update direction.
- Draw.
- Drawing: For each segment, draw a filled rect at (x*cellSizeX, y*cellSizeY) with width=cellSizeX, height=cellSizeY. Color accordingly.
- Passengers: draw small circles at their coordinates.
- Also draw grid lines lightly.
- Speed: Use requestAnimationFrame with delta time? Simple: each frame move one step regardless of time.
Potential issues: Without delta, the snake moves at fixed speed (one cell per frame). That's okay.
Now about "self-playing demo mode": We'll implement that after start, we always pick random direction. So effectively AI.
Now about "no horizontal scrollbar": set `overflow: hidden` on body or container. Already set `body { overflow: hidden; }`? We'll add `body { overflow: hidden; }`.
Now about responsiveness: Use CSS media query for max-width 600px.
Now about immediate visibility: The overlay text appears instantly. The canvas is empty initially. After start, we draw.
Implementation details:
- Use `document.getElementById('startBtn').addEventListener('click', startGame);`
- Also add global keydown listener: `window.addEventListener('keydown', handleKeyDown);` but we need to avoid processing during game? We'll just handle start on any keydown if not started.
- Function `startGame()`:
- `gameActive = true;`
- Reset snake: `snake = [{x:20, y:0, color:'#000000'}];` (just head)
- Set direction: `{dx:1, dy:0}` (moving right)
- Passengers unchanged.
- Hide overlay? We'll just update text.
- Function `handleKeyDown(e)`:
- If `!gameActive` and e.keyCode in arrow keys codes: set `gameActive = true;` and call `startGame();`? But we shouldn't start twice. So better: if not active, set active and then startGame internally. But we also need to prevent default behavior (like scroll). We'll just set flag and call startGame.
But we also need to handle case where user clicks button after game started: maybe ignore.
Simplify: Only allow start via button click or first key press. After start, ignore further key presses (or we can still respond but AI uses random). We'll implement: if (!gameActive) { gameActive = true; startGame(); } else { // already running }.
But we also need to prevent repeated calls. Use `if (!gameActive) { gameActive = true; startGame(); }`.
However, calling startGame() again resets snake etc. Fine.
Now, we need to manage the canvas clearing each frame. We'll just draw.
Potential performance: Not heavy.
Edge Cases: When checking collisions after moving, we need to compare positions. Since we inserted new head and removed tail, we need to check against all remaining segments (including new head? Actually we should check against original body before removal? Classic: after moving, you check if new head collides with any part of your body (including tail? but tail is about to leave). Safer to check against all segments except the new head. Implementation: store oldHead = snake[0]; then insert newHead; then remove tail; then check if newHead overlaps any segment in snake (which now excludes old tail but includes new head? Actually after removal, snake contains new head at index0 and the rest shifted. So we can just check for duplicates in snake array after insertion and before removal? Let's just check against entire array after insertion and before removal, ignoring index0? Simpler: after building newSnake = [...newHead, ...oldSnake.slice(0,-1)] i.e., prepend newHead and truncate last. Then check for duplicate indices. But we can just check if newHead equals any of the other elements (by comparing x,y). If yes, collision.
Implementation steps:
```
const newSnake = [{x: newHead.x, y: newHead.y, color: newHead.color}];
for (const seg of snake) {
// skip the tail? Actually we haven't removed tail yet.
// We'll just check all segments except the new head (since new head is not in snake yet)
}
...
```
Better: Build newSnake = snake.slice(0); // copy old. Then push newHead at front, then pop last. Equivalent to:
```
let newSnake = [];
newSnake.push({x: newHead.x, y: newHead.y, color: newHead.color});
for (let i = 0; i < snake.length; i++) {
newSnake.push(snake[i]); // this keeps old tail at end
}
// Now newSnake length = old length +1
// Trim last element to keep same length as before (classic)
while (newSnake.length > snake.length) newSnake.pop();
```
But we can simplify: just maintain length constant by removing tail after pushing new head. So:
```
let newSnake = [{x: newHead.x, y: newHead.y, color: newHead.color}];
for (let i = 0; i < snake.length; i++) {
newSnake.push(snake[i]);
}
// now newSnake length = 1 + old length
// remove last element (the tail that was duplicated)
newSnake.pop();
```
This ensures same length as before.
After constructing newSnake, check for collisions: iterate over newSnake[1...] (skip first) to see if any equal to another segment? Actually we need to check if newHead collides with any other segment in newSnake (excluding itself). So:
```
let collision = false;
for (let i = 1; i < newSnake.length; i++) {
if (newSnake[0].x === newSnake[i].x && newSnake[0].y === newSnake[i].y) {
collision = true;
break;
}
}
```
If collision, game over.
Note: We also need to check boundaries: newHead.x <0 or >=cols etc.
Now, after determining safe move, we update direction and proceed.
Random direction selection:
```
const dirs = [
{dx:0, dy:-1}, // up
{dx:0, dy:1}, // down
{dx:1, dy:0}, // right
{dx:-1, dy:0} // left
];
let possible = dirs.filter(d => d !== {dx:0, dy:-d.y}? Actually we need to exclude reverse of previous direction. Previous direction stored as {dx,dy}. Reverse is opposite sign. So we can filter out if (d.dx === -prev.dx && d.dy === -prev.dy). Also exclude moves that go out of bounds.
```
But we also need to consider that we changed direction after computing newHead? Actually we compute newHead based on current direction. Then we decide new direction for next frame. So we need to pick new direction for upcoming move. So we can compute candidate directions excluding reverse of current direction and out-of-bound.
Pseudo:
```
function getPossibleDirections(dir) {
const rev = {dx: -dir.dx, dy: -dir.dy};
return dirs.filter(d => !(d.dx===rev.dx && d.dy===rev.dy)) && isInBounds(newHead.x+dx, newHead.y+dy);
}
```
But we need to know newHead location before choosing new direction. So we can compute newHead first, then find possible next directions.
Implementation:
```
const newHead = {x: snake[0].x + direction.dx, y: snake[0].y + direction.dy};
if (outOfBounds(newHead)) { gameOver; }
// build newSnake as described
// check collision
if (collision) { gameOver; }
// choose new direction:
const candidates = [];
for (const d of dirs) {
if (d.dx === direction.dx && d.dy === direction.dy) continue; // same as current? Actually staying same is allowed? Usually you can't stay. But we can allow turning 180? Probably not. We'll just include all except reverse.
// check reverse
const rev = {dx: -d.dx, dy: -d.dy};
if (d.dx === rev.dx && d.dy === rev.dy) continue; // reverse
// check bounds for proposed move: newHead.x + d.dx, newHead.y + d.dy within grid?
const nx = newHead.x + d.dx;
const ny = newHead.y + d.dy;
if (nx >=0 && nx < cols && ny >=0 && ny < rows) {
candidates.push(d);
}
}
if (candidates.length === 0) {
// stuck? maybe choose any? We'll just stay.
}
const newDirection = candidates[Math.floor(Math.random().generate(candidates.length))] ?? direction;
direction = newDirection;
```
But we also need to update `direction` variable.
Now, we also need to handle passenger collection: When newHead matches a passenger coordinate, we can add that passenger to a list and maybe increment length? For simplicity, we can just mark them as collected and maybe add a small visual indicator (like a star). But not required heavily.
Given time, we can omit detailed passenger tracking. Just draw them as static dots on the canvas.
Now, about "first paint": The overlay text appears instantly. The canvas is blank. That's okay.
Now, we need to ensure no horizontal scrollbar: set `body { overflow: hidden; }` and also `canvas { display: block; }`.
Add `tabindex`? Not needed.
Now, implement functions:
- `init()`: set up canvas, variables.
- `draw()`: clear canvas, draw grid, draw snake, draw passengers.
- `update()`: move snake, check collisions, choose direction, call draw.
- `gameLoop()`: requestAnimationFrame.
Because we rely on `setInterval`? Use rAF.
Let's code.
### Detailed Code
#### HTML
Will produce a single file.
#### CSS
As earlier.
#### JS
Write inside `<script>` tag.
Let's draft the script:
```javascript
(function () {
'use strict';
// Constants
const CANVAS_WIDTH = 800;
const CANVAS_HEIGHT = 600;
const CELL_SIZE_X = 25;
const CELL_SIZE_Y = 80;
const COLS = Math.floor(CANVAS_WIDTH / CELL_SIZE_X); // 32
const ROWS = Math.floor(CANVAS_HEIGHT / CELL_SIZE_Y); // 7
let canvas = document.getElementById('gameCanvas');
let ctx = canvas.getContext('2d');
let gameActive = false;
let snake = []; // array of {x, y, color}
let direction = { dx: 1, dy: 0 }; // initial direction right
let startPos = { x: 20, y: 0 };
let passengers = [
{ x: 5, y: 0 },
{ x: 6, y: 0 },
{ x: 7, y: 0 },
{ x: 8, y: 0 }
];
// Colors for passengers (white dot)
const passengerColor = '#FFFFFF';
const cellSizeX = CELL_SIZE_X;
const cellSizeY = CELL_SIZE_Y;
// Initialize snake
function initSnake() {
snake = [];
// head at startPos
snake.push({ x: startPos.x, y: startPos.y, color: '#000000' }); // locomotive
// tail positions: shift right
for (let i = 0; i < 3; i++) {
snake.push({ x: startPos.x + i + 1, y: startPos.y, color: ['#FF0000', '#00FF00', '#0000FF'][i] });
}
// Actually we need 4 segments: head + 3 wagons. Let's just hardcode.
// Simpler: create array of 4 positions:
snake = [
{ x: 20, y: 0, color: '#000000' }, // head locomotive
{ x: 21, y: 0, color: '#FF0000' }, // red wagon
{ x: 22, y: 0, color: '#00FF00' }, // green wagon
{ x: 23, y: 0, color: '#0000FF' } // blue wagon
];
}
function startGame() {
gameActive = true;
// reset snake to initial configuration
snake = [
{ x: 20, y: 0, color: '#000000' },
{ x: 21, y: 0, color: '#FF0000' },
{ x: 22, y: 0, color: '#00FF00' },
{ x: 23, y: 0, color: '#0000FF' }
];
// direction right
direction = { dx: 1, dy: 0 };
}
function handleKeyDown(e) {
if (!gameActive) {
// If not active, start game on first key press
gameActive = true;
startGame();
}
// If already active, ignore (we'll handle in update)
}
function isOutOfBounds(x, y) {
return x < 0 || x >= COLS || y < 0 || y >= ROWS;
}
function getPossibleMoves() {
// Returns array of direction vectors that are valid and not reverse of current direction
const dirs = [
{ dx: 0, dy: -1 }, // up
{ dx: 0, dy: 1 }, // down
{ dx: 1, dy: 0 }, // right
{ dx: -1, dy: 0 } // left
];
const currentDir = { dx: direction.dx, dy: direction.dy };
const reverseDir = { dx: -currentDir.dx, dy: -currentDir.dy };
const moves = [];
for (const d of dirs) {
// Skip reverse
if (d.dx === reverseDir.dx && d.dy === reverseDir.dy) continue;
// Compute proposed new head position
const nx = snake[0].x + d.dx;
const ny = snake[0].y + d.dy;
// Check bounds
if (isOutOfBounds(nx, ny)) continue;
// Check collision with body (excluding current head)
// Build potential new snake: prepend new head, append all old segments
// We'll just check if new head collides with any existing segment (except itself)
const newHead = { x: nx, y: ny, color: d ? ??? : undefined };
// Actually we need to know color for new head? The new head will be whatever direction chosen. But we can just check geometry.
// For simplicity, check if (nx, ny) matches any segment in snake (excluding the current head? but current head is at snake[0], which is about to become new head? Actually we haven't updated snake yet.)
// We'll just check if (nx,ny) exists in snake[1:] (other segments). Because if it matches any other segment, collision.
let collision = false;
for (let i = 1; i < snake.length; i++) {
if (snake[i].x === nx && snake[i].y === ny) {
collision = true;
break;
}
}
if (!collision) moves.push(d);
}
return moves;
}
function draw() {
// Clear canvas
ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// Draw grid
ctx.strokeStyle = '#e0e0e0';
ctx.lineWidth = 1;
for (let x = 0; x <= COLS; x++) {
ctx.beginPath();
ctx.moveTo(x * cellSizeX, 0);
ctx.lineTo(x * cellSizeX, CANVAS_HEIGHT);
ctx.stroke();
}
for (let y = 0; y <= ROWS; y++) {
ctx.beginPath();
ctx.moveTo(0, y * cellSizeY);
ctx.lineTo(CANVAS_WIDTH, y * cellSizeY);
ctx.stroke();
}
// Draw snake
for (let i = 0; i < snake.length; i++) {
const s = snake[i];
ctx.fillStyle = s.color;
ctx.fillRect(s.x * cellSizeX, s.y * cellSizeY, cellSizeX, cellSizeY);
// Optional: draw head differently
}
// Draw passengers
for (const p of passengers) {
ctx.fillStyle = passengerColor;
ctx.beginPath();
ctx.arc(p.x * cellSizeX, p.y * cellSizeY, 4, 0, Math.PI * 2);
ctx.fill();
}
}
function update() {
if (!gameActive) return;
// Move head
const head = snake[0];
const newHead = { x: head.x + direction.dx, y: head.y + direction.dy };
// Check out of bounds
if (isOutOfBounds(newHead.x, newHead.y)) {
// Game over
alert('Game Over'); // but alert is blocked? The rule says no alert(). So we need to avoid alert.
// Instead, we can set a flag and draw "Game Over" text.
gameActive = false;