LFM2.5-2.6B (free)
by LiquidAIreasoning default1 console error
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 18.0 s
- Cost
- $0
- Output tokens
- 7,395
- Spent thinking
- 4,522 tokens
- Speed
- 415.2 tok/s
- Console errors
- 1
- First token after
- 203 ms
- First visible text after
- 13.1 s
- Prompt tokens
- 514
- Code size
- 13.1 KB
- Finish reason
- stop
- Served by
- Liquid
- Model id
liquid/lfm-2.5-2.6b:free- Reasoning sent
model default- Max output
- 8,192 tokens
- Generated
- Nov 9, 2026
Console errors
Caught in the first 3 seconds after load, in a sandboxed iframe like the one above.
updateScore is not defined
What it was asked
The prompt
Build a polished Snake game in a single HTML file using vanilla JavaScript and a <canvas>. Requirements: - A grid of at least 20×20 cells, centered on the page. - Arrow keys and WASD control the snake. Quick successive key presses must both be honored, but the snake can never reverse onto itself. - Eating food adds 10 points and grows the snake by one cell; new food never spawns on the snake. - Game over when the snake hits a wall or itself, with an overlay showing the score; Space or Enter restarts, P pauses. - Visual polish: smooth movement, a snake with a distinct head and eyes facing its direction, and a small particle burst when eating. - Display the current score and the best score of the session.
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 · 406 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Snake Game</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100vh;
background: #1a1a2e;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
canvas {
border: 2px solid #333;
border-radius: 8px;
display: block;
}
.score-panel {
position: fixed;
top: 20px;
left: 20px;
display: flex;
gap: 20px;
align-items: center;
}
.score-item {
text-align: center;
background: #16213e;
padding: 8px 16px;
border-radius: 6px;
min-width: 100px;
}
.score-label { color: #aaa; font-size: 14px; }
.score-value { font-size: 24px; font-weight: bold; color: #4ecca3; }
.best-score {
font-size: 18px;
color: #ffd700;
margin-top: 4px;
}
.controls {
margin-top: 16px;
font-size: 14px;
color: #888;
}
.overlay {
position: fixed;
top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
}
.overlay.hidden { display: none; }
.game-over-text {
font-size: 48px;
color: #ff4757;
margin-bottom: 20px;
}
.restart-btn, .pause-btn {
padding: 10px 24px;
font-size: 16px;
margin: 0 10px;
cursor: pointer;
border: none;
border-radius: 4px;
transition: background 0.2s;
}
.restart-btn { background: #4ecca3; color: #1a1a2e; }
.restart-btn:hover { background: #3aa876; }
.pause-btn { background: #555; color: #fff; }
.pause-btn:hover { background: #777; }
</style>
</head>
<body>
<div class="score-panel">
<div class="score-item">
<div class="score-label">Score</div>
<div class="score-value" id="score">0</div>
</div>
<div class="score-item">
<div class="score-label">Best</div>
<div class="score-value" id="best">0</div>
</div>
</div>
<canvas id="gameCanvas" width="640" height="640"></canvas>
<div class="controls">
<p>WASD or Arrow Keys to move | P to pause | Space/Enter to restart</p>
</div>
<div class="overlay" id="overlay">
<div class="game-over-text" id="gameOverText">Game Over!</div>
<button class="restart-btn" id="restartBtn">Play Again</button>
</div>
<script>
// Game configuration
const GRID_SIZE = 20;
const CELL_SIZE = 32;
const CANVAS_SIZE = 640;
// Game state
let snake = [];
let food = [];
let direction = { x: 1, y: 0 };
let nextDirection = { x: 1, y: 0 };
let score = 0;
let bestScore = 0;
let gameRunning = false;
let gamePaused = false;
let particles = [];
let lastTime = 0;
let animationId = null;
// Canvas setup
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
canvas.style.width = CANVAS_SIZE + 'px';
canvas.style.height = CANVAS_SIZE + 'px';
// Score display
const scoreEl = document.getElementById('score');
const bestEl = document.getElementById('best');
// Initialize game
function initGame() {
// Place snake in the middle with 3 segments
const startX = Math.floor(GRID_SIZE / 2);
const startY = Math.floor(GRID_SIZE / 2);
snake = [
{ x: startX, y: startY },
{ x: startX - 1, y: startY },
{ x: startX - 2, y: startY }
];
// Place food
placeFood();
score = 0;
gameRunning = true;
gamePaused = false;
particles = [];
updateScore();
}
// Place food at random position not on snake
function placeFood() {
let newFood;
do {
newFood = {
x: Math.floor(Math.random() * GRID_SIZE),
y: Math.floor(Math.random() * GRID_SIZE)
};
} while (snake.some(seg => seg.x === newFood.x && seg.y === newFood.y));
food = newFood;
}
// Update particles
function updateParticles() {
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.life -= 0.02;
if (p.life <= 0) {
particles.splice(i, 1);
}
}
}
// Spawn particle burst when eating
function spawnParticles(x, y, color) {
for (let i = 0; i < 8; i++) {
const angle = (Math.PI * 2 * i) / 8;
const speed = 2 + Math.random() * 3;
particles.push({
x: x * CELL_SIZE + CELL_SIZE / 2,
y: y * CELL_SIZE + CELL_SIZE / 2,
life: 1,
size: 3 + Math.random() * 4,
color: color
});
}
}
// Check collision with walls
function checkWallCollision() {
return snake.some(seg => seg.x < 0 || seg.x >= GRID_SIZE || seg.y < 0 || seg.y >= GRID_SIZE);
}
// Check collision with self
function checkSelfCollision() {
return snake.some((seg, index) => index !== 0 && seg.x === snake[0].x && seg.y === snake[0].y);
}
// Update game state
function update() {
if (gamePaused) return;
// Move snake
const head = { x: snake[0].x + direction.x, y: snake[0].y + direction.y };
// Check wall collision
if (checkWallCollision()) {
gameOver();
return;
}
// Check self collision (excluding head)
if (checkSelfCollision()) {
gameOver();
return;
}
// Add new head
snake.unshift(head);
// Check if food eaten
if (head.x === food.x && head.y === food.y) {
score += 10;
spawnParticles(food.x, food.y, '#4ecca3');
placeFood();
// Snake grows - don't remove tail
} else {
// Remove tail (snake moves forward)
snake.pop();
}
updateParticles();
}
// Game over
function gameOver() {
gameRunning = false;
const overlay = document.getElementById('overlay');
const gameOverText = document.getElementById('gameOverText');
overlay.classList.remove('hidden');
gameOverText.textContent = 'Game Over!';
bestEl.textContent = bestScore;
}
// Restart game
function restartGame() {
initGame();
}
// Pause game
function togglePause() {
if (!gameRunning) return;
gamePaused = !gamePaused;
const overlay = document.getElementById('overlay');
if (gamePaused) {
overlay.classList.remove('hidden');
} else {
overlay.classList.add('hidden');
}
}
// Handle key presses
function handleKeyDown(e) {
if (!gameRunning) {
if (e.code === 'Space' || e.code === 'Enter') {
initGame();
}
return;
}
switch (e.code) {
case 'ArrowUp':
case 'KeyW':
if (direction.y === 0) nextDirection = { x: 0, y: -1 };
break;
case 'ArrowDown':
case 'KeyS':
if (direction.y === 0) nextDirection = { x: 0, y: 1 };
break;
case 'ArrowLeft':
case 'KeyA':
if (direction.x === 0) nextDirection = { x: -1, y: 0 };
break;
case 'ArrowRight':
case 'KeyD':
if (direction.x === 0) nextDirection = { x: 1, y: 0 };
break;
case 'p':
case 'P':
togglePause();
break;
case ' ':
case 'Enter':
restartGame();
break;
}
e.preventDefault();
}
// Main game loop
function gameLoop(timestamp) {
if (!gameRunning) return;
update();
updateParticles();
// Draw game
draw();
animationId = requestAnimationFrame(gameLoop);
}
// Draw game
function draw() {
// Clear canvas
ctx.fillStyle = '#16213e';
ctx.fillRect(0, 0, CANVAS_SIZE, CANVAS_SIZE);
// Draw grid
ctx.strokeStyle = '#2a2a4a';
ctx.lineWidth = 1;
for (let i = 0; i <= GRID_SIZE; i++) {
const pos = i * CELL_SIZE;
ctx.beginPath();
ctx.moveTo(pos, 0);
ctx.lineTo(pos, CANVAS_SIZE);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, pos);
ctx.lineTo(CANVAS_SIZE, pos);
ctx.stroke();
}
// Draw food
ctx.fillStyle = '#ff6b6b';
ctx.beginPath();
ctx.arc(food.x * CELL_SIZE + CELL_SIZE / 2, food.y * CELL_SIZE + CELL_SIZE / 2, CELL_SIZE / 3, 0, Math.PI * 2);
ctx.fill();
// Draw snake
snake.forEach((seg, index) => {
const x = seg.x * CELL_SIZE;
const y = seg.y * CELL_SIZE;
const padding = 2;
const size = CELL_SIZE - padding * 2;
// Head is different - with eyes
if (index === 0) {
// Eye positions based on direction
let eye1X, eye1Y, eye2X, eye2Y;
if (direction.x === 1) { // right
eye1X = x + 6; eye1Y = y + 4;
eye2X = x + 6; eye2Y = y + CELL_SIZE - 6;
} else if (direction.x === -1) { // left
eye1X = x - 6; eye1Y = y + 4;
eye2X = x - 6; eye2Y = y + CELL_SIZE - 6;
} else if (direction.y === 1) { // down
eye1X = x + 6; eye1Y = y + CELL_SIZE - 4;
eye2X = x - 6; eye2Y = y + CELL_SIZE - 4;
} else { // up
eye1X = x + 6; eye1Y = y - 4;
eye2X = x - 6; eye2Y = y - 4;
}
// Draw eyes
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(eye1X, eye1Y, 2.5, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(eye2X, eye2Y, 2.5, 0, Math.PI * 2);
ctx.fill();
// Body
ctx.fillStyle = '#4ecca3';
ctx.beginPath();
ctx.roundRect(x + padding, y + padding, size, size, 4);
ctx.fill();
} else {
// Body segments
ctx.fillStyle = '#2d6a4f';
ctx.beginPath();
ctx.roundRect(x + padding, y + padding, size, size, 4);
ctx.fill();
}
});
// Draw particles
particles.forEach(p => {
ctx.globalAlpha = p.life;
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size * p.life, 0, Math.PI * 2);
ctx.fill();
});
ctx.globalAlpha = 1;
}
// Start the game
initGame();
requestAnimationFrame(gameLoop);
</script>
</body>
</html>Reasoning trace (4,522 tokens)
The user wants a polished Snake game in a single HTML file with specific requirements. Let me plan the implementation:
1. **Canvas setup**: 20x20 grid, centered on page
2. **Game mechanics**:
- Snake moves continuously (smooth movement)
- Arrow keys and WASD control
- Cannot reverse direction (can't go from right to left, etc.)
- Eat food (10 points, grows snake)
- Game over on wall collision or self-collision
- Score display + best score
- Particle burst when eating
- P key to pause
- Space/Enter to restart
3. **Visual polish**:
- Distinct head with eyes facing direction
- Smooth movement
- Particle effects on eating
- Grid background
4. **Technical constraints**:
- Single HTML file
- Vanilla JavaScript
- No external files, images, fonts, or network requests (except allowed CDN)
- Must work in 1280×800 viewport, adapt to smaller screens
- No alert(), confirm(), prompt(), window.open()
- No localStorage, sessionStorage, cookies
Let me create a well-structured game:
- Grid: 20x20 cells
- Cell size: ~32px (20*32 = 640, fits in 800)
- Canvas centered with padding
- Snake: array of {x, y} positions
- Direction: object with dx, dy
- Food: random position not on snake
- Particles: simple particle system for eating effect
Key features to implement:
1. Smooth movement - update position each frame
2. Direction change - prevent 180-degree turns
3. Collision detection - walls and self
4. Score tracking - current and best
5. Particle burst on eating
6. Pause functionality
7. Restart functionality
Let me write the code:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Snake Game</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100vh;
background: #1a1a2e;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
canvas {
border: 2px solid #333;
border-radius: 8px;
display: block;
}
.score-panel {
position: fixed;
top: 20px;
left: 20px;
display: flex;
gap: 20px;
align-items: center;
}
.score-item {
text-align: center;
background: #16213e;
padding: 8px 16px;
border-radius: 6px;
min-width: 100px;
}
.score-label { color: #aaa; font-size: 14px; }
.score-value { font-size: 24px; font-weight: bold; color: #4ecca3; }
.best-score {
font-size: 18px;
color: #ffd700;
margin-top: 4px;
}
.controls {
margin-top: 16px;
font-size: 14px;
color: #888;
}
.overlay {
position: fixed;
top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
}
.overlay.hidden { display: none; }
.game-over-text {
font-size: 48px;
color: #ff4757;
margin-bottom: 20px;
}
.restart-btn, .pause-btn {
padding: 10px 24px;
font-size: 16px;
margin: 0 10px;
cursor: pointer;
border: none;
border-radius: 4px;
transition: background 0.2s;
}
.restart-btn { background: #4ecca3; color: #1a1a2e; }
.restart-btn:hover { background: #3aa876; }
.pause-btn { background: #555; color: #fff; }
.pause-btn:hover { background: #777; }
</style>
</head>
<body>
<div class="score-panel">
<div class="score-item">
<div class="score-label">Score</div>
<div class="score-value" id="score">0</div>
</div>
<div class="score-item">
<div class="score-label">Best</div>
<div class="score-value" id="best">0</div>
</div>
</div>
<canvas id="gameCanvas" width="640" height="640"></canvas>
<div class="controls">
<p>WASD or Arrow Keys to move | P to pause | Space/Enter to restart</p>
</div>
<div class="overlay" id="overlay">
<div class="game-over-text" id="gameOverText">Game Over!</div>
<button class="restart-btn" id="restartBtn">Play Again</button>
</div>
<script>
// Game configuration
const GRID_SIZE = 20;
const CELL_SIZE = 32;
const CANVAS_SIZE = 640;
// Game state
let snake = [];
let food = [];
let direction = { x: 1, y: 0 };
let nextDirection = { x: 1, y: 0 };
let score = 0;
let bestScore = 0;
let gameRunning = false;
let gamePaused = false;
let particles = [];
let lastTime = 0;
let animationId = null;
// Canvas setup
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
canvas.style.width = CANVAS_SIZE + 'px';
canvas.style.height = CANVAS_SIZE + 'px';
// Score display
const scoreEl = document.getElementById('score');
const bestEl = document.getElementById('best');
// Initialize game
function initGame() {
// Place snake in the middle
const startX = Math.floor(GRID_SIZE / 2);
const startY = Math.floor(GRID_SIZE / 2);
snake = [
{ x: startX, y: startY },
{ x: startX - 1, y: startY },
{ x: startX - 2, y: startY }
];
// Place food
placeFood();
score = 0;
gameRunning = true;
gamePaused = false;
particles = [];
updateScore();
}
// Place food at random position not on snake
function placeFood() {
let newFood;
do {
newFood = {
x: Math.floor(Math.random() * GRID_SIZE),
y: Math.floor(Math.random() * GRID_SIZE)
};
} while (snake.some(seg => seg.x === newFood.x && seg.y === newFood.y));
food = newFood;
}
// Update particles
function updateParticles() {
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.life -= 0.02;
if (p.life <= 0) {
particles.splice(i, 1);
}
}
}
// Spawn particle burst
function spawnParticles(x, y, color) {
for (let i = 0; i < 8; i++) {
const angle = (Math.PI * 2 * i) / 8;
const speed = 2 + Math.random() * 3;
particles.push({
x: x * CELL_SIZE + CELL_SIZE / 2,
y: y * CELL_SIZE + CELL_SIZE / 2,
life: 1,
size: 3 + Math.random() * 4,
color: color
});
}
}
// Check collision with walls
function checkWallCollision() {
return snake.some(seg => seg.x < 0 || seg.x >= GRID_SIZE || seg.y < 0 || seg.y >= GRID_SIZE);
}
// Check collision with self
function checkSelfCollision() {
return snake.some((seg, index) => index !== 0 && seg.x === snake[0].x && seg.y === snake[0].y);
}
// Update game state
function update() {
if (gamePaused) return;
// Move snake
const head = { x: snake[0].x + direction.x, y: snake[0].y + direction.y };
// Check wall collision
if (checkWallCollision()) {
gameOver();
return;
}
// Check self collision (excluding head)
if (checkSelfCollision()) {
gameOver();
return;
}
// Add new head
snake.unshift(head);
// Check if food eaten
if (head.x === food.x && head.y === food.y) {
score += 10;
spawnParticles(food.x, food.y, '#4ecca3');
placeFood();
// Don't remove tail - snake grows
} else {
// Remove tail
snake.pop();
}
updateParticles();
}
// Game over
function gameOver() {
gameRunning = false;
const overlay = document.getElementById('overlay');
const gameOverText = document.getElementById('gameOverText');
overlay.classList.remove('hidden');
gameOverText.textContent = 'Game Over!';
bestEl.textContent = bestScore;
}
// Restart game
function restartGame() {
initGame();
}
// Pause game
function togglePause() {
if (!gameRunning) return;
gamePaused = !gamePaused;
const overlay = document.getElementById('overlay');
if (gamePaused) {
overlay.classList.remove('hidden');
} else {
overlay.classList.add('hidden');
}
}
// Handle key presses
function handleKeyDown(e) {
if (!gameRunning) {
if (e.code === 'Space' || e.code === 'Enter') {
initGame();
}
return;
}
switch (e.code) {
case 'ArrowUp':
case 'KeyW':
if (direction.y === 0) nextDirection = { x: 0, y: -1 };
break;
case 'ArrowDown':
case 'KeyS':
if (direction.y === 0) nextDirection = { x: 0, y: 1 };
break;
case 'ArrowLeft':
case 'KeyA':
if (direction.x === 0) nextDirection = { x: -1, y: 0 };
break;
case 'ArrowRight':
case 'KeyD':
if (direction.x === 0) nextDirection = { x: 1, y: 0 };
break;
case 'p':
case 'P':
togglePause();
break;
case ' ':
case 'Enter':
restartGame();
break;
}
e.preventDefault();
}
// Main game loop
function gameLoop(timestamp) {
if (!gameRunning) return;
update();
updateParticles();
// Draw game
draw();
animationId = requestAnimationFrame(gameLoop);
}
// Draw game
function draw() {
// Clear canvas
ctx.fillStyle = '#16213e';
ctx.fillRect(0, 0, CANVAS_SIZE, CANVAS_SIZE);
// Draw grid
ctx.strokeStyle = '#2a2a4a';
ctx.lineWidth = 1;
for (let i = 0; i <= GRID_SIZE; i++) {
const pos = i * CELL_SIZE;
ctx.beginPath();
ctx.moveTo(pos, 0);
ctx.lineTo(pos, CANVAS_SIZE);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, pos);
ctx.lineTo(CANVAS_SIZE, pos);
ctx.stroke();
}
// Draw food
ctx.fillStyle = '#ff6b6b';
ctx.beginPath();
ctx.arc(food.x * CELL_SIZE + CELL_SIZE / 2, food.y * CELL_SIZE + CELL_SIZE / 2, CELL_SIZE / 3, 0, Math.PI * 2);
ctx.fill();
// Draw snake
snake.forEach((seg, index) => {
const x = seg.x * CELL_SIZE;
const y = seg.y * CELL_SIZE;
const padding = 2;
const size = CELL_SIZE - padding * 2;
// Head is different
if (index === 0) {
// Head with eyes
const headX = x + CELL_SIZE / 2;
const headY = y + CELL_SIZE / 2;
// Eye positions based on direction
let eye1X, eye1Y, eye2X, eye2Y;
if (direction.x === 1) { // right
eye1X = headX + 4; eye1Y = headY + 4;
eye2X = headX + 4; eye2Y = headY + CELL_SIZE - 6;
} else if (direction.x === -1) { // left
eye1X = headX - 4; eye1Y = headY + 4;
eye2X = headX - 4; eye2Y = headY + CELL_SIZE - 6;
} else if (direction.y === 1) { // down
eye1X = headX + 4; eye1Y = headY + CELL_SIZE - 4;
eye2X = headX - 4; eye2Y = headY + CELL_SIZE - 4;
} else { // up
eye1X = headX + 4; eye1Y = headY - 4;
eye2X = headX - 4; eye2Y = headY - 4;
}
// Draw eyes
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(eye1X, eye1Y, 2.5, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(eye2X, eye2Y, 2.5, 0, Math.PI * 2);
ctx.fill();
// Body
ctx.fillStyle = '#4ecca3';
ctx.beginPath();
ctx.roundRect(x + padding, y + padding, size, size, 4);
ctx.fill();
} else {
// Body segments
ctx.fillStyle = '#2d6a4f';
ctx.beginPath();
ctx.roundRect(x + padding, y + padding, size, size, 4);
ctx.fill();
}
});
// Draw particles
particles.forEach(p => {
ctx.globalAlpha = p.life;
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size * p.life, 0, Math.PI * 2);
ctx.fill();
});
ctx.globalAlpha = 1;
}
// Start the game
initGame();
requestAnimationFrame(gameLoop);
</script>
</body>
</html>
```
Wait, I need to check a few things:
1. The grid is 20x20, cell size 32, canvas 640x640 - that's correct (20*32=640)
2. The snake starts in the middle with 3 segments
3. Direction change prevents reversing (checking if direction.y === 0 before setting up up, etc.)
4. Wall collision and self-collision are checked
5. Score updates and best score is tracked
6. Particle system for eating
7. Pause with P key
8. Restart with Space/Enter
9. Game over overlay
Let me review the code more carefully:
- The `roundRect` method is available in modern browsers. It should be fine.
- The `placeFood` function uses a do-while loop to ensure food doesn't spawn on the snake. This is correct.
- The snake movement: unshift adds new head, pop removes tail (unless food eaten). This is correct for smooth movement.
- Direction change: I check `if (direction.y === 0)` before setting up up, etc. This prevents reversing. Good.
- The game loop uses requestAnimationFrame for smooth movement.
- The overlay is shown when game over, hidden when playing.
One potential issue: when the game starts, the snake needs to be visible. The initial position should be fine.
Let me also make sure the canvas is properly centered. The CSS sets it to 640x640 and the body is centered. The canvas has border and should look good.
Actually, I should double-check the `roundRect` usage. In some older browsers it might not be supported, but since we're targeting modern browsers (Chrome), it should be fine. However, to be safe, I could use an alternative approach for drawing rounded rectangles.
Actually, looking at the code more carefully, I realize there might be an issue with the initial snake placement. The snake starts with 3 segments going left from the center. But when the game starts, the first frame should show the snake moving. The movement logic should handle this correctly.
One more thing: the `checkSelfCollision` function checks if any segment (except head) is at the same position as the head. This is correct for preventing self-collision.
The particle system looks good - it spawns when food is eaten and fades out over time.
The visual design:
- Dark background (#16213e)
- Snake body: #4ecca3 (green)
- Snake head: same green but with eyes
- Food: #ff6b6b (orange/red)
- Grid lines: #2a2a4a
- Overlay: semi-transparent black
This should look polished and meet all requirements. Let me finalize the code.