Gemini 4 Hydrogen
by Googlereasoning defaultClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 2 min 3 s
- Cost
- $0.154
- Output tokens
- 20,450
- Spent thinking
- 5,264 tokens
- Speed
- 169.6 tok/s
- Console errors
- 0
- First token after
- 2.4 s
- First visible text after
- 31.9 s
- Prompt tokens
- 546
- Code size
- 42.9 KB
- Finish reason
- stop
- Served by
- Model id
google/gemini-4-hydrogen- Reasoning sent
model default- Max output
- 40,340 tokens
- Generated
- Nov 13, 2026
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 · 1439 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Snake — Arcade Edition</title>
<style>
:root {
--bg-0: #090d16;
--bg-1: #111827;
--panel: rgba(17, 24, 39, 0.82);
--border: rgba(148, 163, 184, 0.16);
--text: #f1f5f9;
--muted: #94a3b8;
--accent: #34d399;
--accent-2: #10b981;
--food: #fb7185;
--gold: #fbbf24;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html, body {
height: 100%;
width: 100%;
overflow-x: hidden;
background:
radial-gradient(900px 600px at 50% 25%, rgba(16, 185, 129, 0.12), transparent 65%),
radial-gradient(700px 500px at 80% 85%, rgba(56, 189, 248, 0.08), transparent 65%),
linear-gradient(180deg, var(--bg-0) 0%, #060910 100%);
color: var(--text);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Inter, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
user-select: none;
}
.app {
min-height: 100%;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 16px 12px;
gap: 12px;
}
.hud {
width: 100%;
max-width: 560px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 10px 14px;
background: var(--panel);
border: 1px solid var(--border);
border-radius: 14px;
backdrop-filter: blur(10px);
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.35);
}
.brand {
display: flex;
align-items: center;
gap: 10px;
min-width: 0;
}
.brand-dot {
width: 12px;
height: 12px;
border-radius: 4px;
background: linear-gradient(135deg, #6ee7b7, #10b981);
box-shadow: 0 0 12px rgba(52, 211, 153, 0.7);
flex-shrink: 0;
}
.brand-title {
font-size: 1rem;
font-weight: 800;
letter-spacing: 0.1em;
color: var(--text);
}
.status-pill {
font-size: 0.68rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
padding: 3px 8px;
border-radius: 999px;
background: rgba(52, 211, 153, 0.14);
color: var(--accent);
border: 1px solid rgba(52, 211, 153, 0.28);
white-space: nowrap;
}
.status-pill.paused {
background: rgba(251, 191, 36, 0.14);
color: var(--gold);
border-color: rgba(251, 191, 36, 0.3);
}
.status-pill.gameover {
background: rgba(251, 113, 133, 0.14);
color: var(--food);
border-color: rgba(251, 113, 133, 0.3);
}
.scores {
display: flex;
align-items: center;
gap: 10px;
}
.score-box {
display: flex;
flex-direction: column;
align-items: flex-end;
line-height: 1.1;
padding: 4px 10px;
border-radius: 10px;
background: rgba(15, 23, 42, 0.65);
border: 1px solid rgba(148, 163, 184, 0.12);
min-width: 70px;
}
.score-label {
font-size: 0.62rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--muted);
}
.score-value {
font-size: 1.15rem;
font-weight: 800;
font-variant-numeric: tabular-nums;
color: var(--text);
margin-top: 2px;
}
.score-box.best .score-value {
color: var(--gold);
}
.icon-btn {
appearance: none;
border: 1px solid rgba(148, 163, 184, 0.2);
background: rgba(15, 23, 42, 0.7);
color: var(--text);
width: 34px;
height: 34px;
border-radius: 9px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: transform 0.12s ease, border-color 0.15s ease, background 0.15s ease;
font-size: 0.85rem;
flex-shrink: 0;
}
.icon-btn:hover {
border-color: rgba(52, 211, 153, 0.5);
background: rgba(30, 41, 59, 0.9);
}
.icon-btn:active {
transform: scale(0.94);
}
.board-wrap {
position: relative;
width: min(92vw, 560px, 68vh);
aspect-ratio: 1 / 1;
border-radius: 18px;
padding: 6px;
background: linear-gradient(145deg, rgba(52, 211, 153, 0.22), rgba(56, 189, 248, 0.12) 50%, rgba(148, 163, 184, 0.12));
box-shadow:
0 24px 60px rgba(0, 0, 0, 0.55),
0 0 40px rgba(16, 185, 129, 0.08);
}
@media (max-width: 480px) {
.board-wrap {
width: min(94vw, 520px);
}
.hud {
padding: 8px 10px;
}
.brand-title {
font-size: 0.9rem;
}
.score-box {
min-width: 58px;
padding: 4px 8px;
}
.score-value {
font-size: 1rem;
}
}
canvas {
display: block;
width: 100%;
height: 100%;
border-radius: 13px;
background: #0b1220;
touch-action: none;
}
.overlay {
position: absolute;
inset: 6px;
border-radius: 13px;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
pointer-events: auto;
transition: opacity 0.2s ease, visibility 0.2s ease;
}
.overlay.hidden {
opacity: 0;
visibility: hidden;
pointer-events: none;
}
.overlay.demo-mode {
background: rgba(6, 10, 19, 0.14);
}
.overlay.solid-dim {
background: rgba(6, 10, 19, 0.72);
backdrop-filter: blur(5px);
}
.overlay-card {
background: rgba(15, 23, 42, 0.86);
border: 1px solid rgba(148, 163, 184, 0.24);
border-radius: 16px;
padding: 22px 26px;
text-align: center;
max-width: 340px;
width: 100%;
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
backdrop-filter: blur(10px);
}
.overlay.demo-mode .overlay-card {
background: rgba(11, 18, 32, 0.74);
border-color: rgba(52, 211, 153, 0.35);
padding: 18px 22px;
}
.overlay-tag {
display: inline-block;
font-size: 0.68rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--accent);
margin-bottom: 6px;
}
.overlay-title {
font-size: 1.45rem;
font-weight: 800;
letter-spacing: -0.01em;
margin-bottom: 8px;
color: var(--text);
}
.overlay-sub {
font-size: 0.88rem;
color: var(--muted);
line-height: 1.45;
margin-bottom: 16px;
}
.final-score-row {
display: flex;
justify-content: center;
gap: 16px;
margin: 12px 0 16px;
}
.final-stat {
background: rgba(15, 23, 42, 0.9);
border: 1px solid rgba(148, 163, 184, 0.16);
border-radius: 10px;
padding: 8px 14px;
min-width: 92px;
}
.final-stat .lbl {
font-size: 0.65rem;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.08em;
font-weight: 700;
}
.final-stat .val {
font-size: 1.35rem;
font-weight: 800;
font-variant-numeric: tabular-nums;
margin-top: 2px;
}
.new-best-badge {
display: inline-block;
margin-bottom: 10px;
padding: 3px 10px;
border-radius: 999px;
font-size: 0.7rem;
font-weight: 800;
letter-spacing: 0.08em;
background: rgba(251, 191, 36, 0.18);
color: var(--gold);
border: 1px solid rgba(251, 191, 36, 0.4);
}
.cta-btn {
appearance: none;
border: none;
border-radius: 10px;
padding: 10px 20px;
font-size: 0.9rem;
font-weight: 700;
color: #052e16;
background: linear-gradient(135deg, #6ee7b7, #34d399);
cursor: pointer;
box-shadow: 0 8px 20px rgba(52, 211, 153, 0.3);
transition: transform 0.12s ease, filter 0.15s ease;
}
.cta-btn:hover {
filter: brightness(1.06);
}
.cta-btn:active {
transform: scale(0.97);
}
.pulse-prompt {
font-size: 0.92rem;
font-weight: 700;
color: #ecfdf5;
letter-spacing: 0.02em;
animation: softPulse 1.8s ease-in-out infinite;
}
@keyframes softPulse {
0%, 100% { opacity: 0.78; transform: scale(1); }
50% { opacity: 1; transform: scale(1.02); }
}
.footer-hints {
width: 100%;
max-width: 560px;
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 10px 16px;
font-size: 0.75rem;
color: var(--muted);
}
.hint-item {
display: inline-flex;
align-items: center;
gap: 5px;
}
kbd {
display: inline-block;
padding: 2px 6px;
font-size: 0.68rem;
font-weight: 700;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
color: #cbd5e1;
background: rgba(30, 41, 59, 0.9);
border: 1px solid rgba(148, 163, 184, 0.25);
border-bottom-width: 2px;
border-radius: 5px;
}
.touch-pad {
display: none;
grid-template-columns: repeat(3, 52px);
grid-template-rows: repeat(2, 46px);
gap: 6px;
margin-top: 2px;
}
.pad-btn {
appearance: none;
border: 1px solid rgba(148, 163, 184, 0.22);
background: rgba(15, 23, 42, 0.85);
color: var(--text);
border-radius: 10px;
font-size: 1.1rem;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
touch-action: manipulation;
}
.pad-btn:active {
background: rgba(52, 211, 153, 0.24);
border-color: var(--accent);
}
.pad-up { grid-column: 2; grid-row: 1; }
.pad-left { grid-column: 1; grid-row: 2; }
.pad-down { grid-column: 2; grid-row: 2; }
.pad-right { grid-column: 3; grid-row: 2; }
@media (hover: none) and (pointer: coarse), (max-width: 600px) {
.touch-pad {
display: grid;
}
.footer-hints {
display: none;
}
}
</style>
</head>
<body>
<main class="app">
<header class="hud">
<div class="brand">
<span class="brand-dot" aria-hidden="true"></span>
<span class="brand-title">SNAKE</span>
<span id="statusPill" class="status-pill">DEMO</span>
</div>
<div class="scores">
<div class="score-box">
<span class="score-label">Score</span>
<span id="scoreVal" class="score-value">0</span>
</div>
<div class="score-box best">
<span class="score-label">Best</span>
<span id="bestVal" class="score-value">0</span>
</div>
<button id="pauseBtn" class="icon-btn" type="button" title="Pause / Resume (P)" aria-label="Pause or Resume">⏸</button>
</div>
</header>
<section class="board-wrap" aria-label="Snake game board">
<canvas id="gameCanvas" width="560" height="560"></canvas>
<div id="overlay" class="overlay demo-mode">
<div id="overlayCard" class="overlay-card">
<div id="overlayTag" class="overlay-tag">Live Demo</div>
<h1 id="overlayTitle" class="overlay-title">Ready to Play</h1>
<p id="overlaySub" class="overlay-sub pulse-prompt">Click or press a key to start</p>
<div id="finalStats" class="final-score-row" style="display: none;">
<div class="final-stat">
<div class="lbl">Score</div>
<div id="finalScore" class="val">0</div>
</div>
<div class="final-stat">
<div class="lbl">Best</div>
<div id="finalBest" class="val" style="color: var(--gold);">0</div>
</div>
</div>
<div id="newBestBadge" class="new-best-badge" style="display: none;">NEW SESSION BEST!</div>
<div>
<button id="overlayBtn" class="cta-btn" type="button">Start Game</button>
</div>
</div>
</div>
</section>
<div class="touch-pad" aria-label="Touch direction controls">
<button class="pad-btn pad-up" data-dir="up" type="button" aria-label="Move Up">▲</button>
<button class="pad-btn pad-left" data-dir="left" type="button" aria-label="Move Left">◀</button>
<button class="pad-btn pad-down" data-dir="down" type="button" aria-label="Move Down">▼</button>
<button class="pad-btn pad-right" data-dir="right" type="button" aria-label="Move Right">▶</button>
</div>
<footer class="footer-hints">
<span class="hint-item"><kbd>↑</kbd><kbd>↓</kbd><kbd>←</kbd><kbd>→</kbd> or <kbd>W</kbd><kbd>A</kbd><kbd>S</kbd><kbd>D</kbd> Move</span>
<span class="hint-item"><kbd>P</kbd> Pause</span>
<span class="hint-item"><kbd>Space</kbd> / <kbd>Enter</kbd> Restart</span>
</footer>
</main>
<script>
(() => {
const GRID_SIZE = 20;
const BASE_TICK_MS = 115;
const MIN_TICK_MS = 72;
const DEMO_TICK_MS = 105;
const DIRS = {
up: { x: 0, y: -1 },
down: { x: 0, y: 1 },
left: { x: -1, y: 0 },
right: { x: 1, y: 0 }
};
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const scoreEl = document.getElementById('scoreVal');
const bestEl = document.getElementById('bestVal');
const statusPill = document.getElementById('statusPill');
const pauseBtn = document.getElementById('pauseBtn');
const overlay = document.getElementById('overlay');
const overlayTag = document.getElementById('overlayTag');
const overlayTitle = document.getElementById('overlayTitle');
const overlaySub = document.getElementById('overlaySub');
const overlayBtn = document.getElementById('overlayBtn');
const finalStats = document.getElementById('finalStats');
const finalScoreEl = document.getElementById('finalScore');
const finalBestEl = document.getElementById('finalBest');
const newBestBadge = document.getElementById('newBestBadge');
// Session-only state (no localStorage / sessionStorage)
let sessionBest = 0;
// Game state: 'demo' | 'playing' | 'paused' | 'gameover'
let mode = 'demo';
let snake = [];
let prevSnake = [];
let dir = { ...DIRS.right };
let inputQueue = [];
let food = { x: 14, y: 10 };
let score = 0;
let tickInterval = DEMO_TICK_MS;
let lastTickTime = performance.now();
let pausedProgress = 0;
let ateThisTick = false;
let headAngle = 0;
let targetHeadAngle = 0;
// Visual FX
let particles = [];
let rings = [];
let floatingTexts = [];
let shakeTime = 0;
let shakeStrength = 0;
// High-DPI canvas sizing
let canvasSize = 560;
let cellSize = canvasSize / GRID_SIZE;
function resizeCanvas() {
const rect = canvas.getBoundingClientRect();
const dpr = Math.min(window.devicePixelRatio || 1, 2);
const w = Math.max(240, Math.round(rect.width));
canvas.width = Math.round(w * dpr);
canvas.height = Math.round(w * dpr);
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
canvasSize = w;
cellSize = canvasSize / GRID_SIZE;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
function isOpposite(a, b) {
return a.x === -b.x && a.y === -b.y;
}
function sameDir(a, b) {
return a.x === b.x && a.y === b.y;
}
function dirToAngle(d) {
return Math.atan2(d.y, d.x);
}
function lerp(a, b, t) {
return a + (b - a) * t;
}
function lerpAngle(a, b, t) {
let diff = b - a;
while (diff > Math.PI) diff -= Math.PI * 2;
while (diff < -Math.PI) diff += Math.PI * 2;
return a + diff * t;
}
function spawnFood() {
const occupied = new Set(snake.map(s => `${s.x},${s.y}`));
const free = [];
for (let y = 0; y < GRID_SIZE; y++) {
for (let x = 0; x < GRID_SIZE; x++) {
if (!occupied.has(`${x},${y}`)) {
free.push({ x, y });
}
}
}
if (free.length === 0) {
return { x: 0, y: 0 };
}
return free[Math.floor(Math.random() * free.length)];
}
function resetWorld(newMode) {
mode = newMode;
const cy = Math.floor(GRID_SIZE / 2);
const cx = Math.floor(GRID_SIZE / 2) - 2;
snake = [
{ x: cx + 2, y: cy },
{ x: cx + 1, y: cy },
{ x: cx, y: cy },
{ x: cx - 1, y: cy }
];
prevSnake = snake.map(p => ({ ...p }));
dir = { ...DIRS.right };
inputQueue = [];
headAngle = dirToAngle(dir);
targetHeadAngle = headAngle;
score = 0;
ateThisTick = false;
food = spawnFood();
particles = [];
rings = [];
floatingTexts = [];
shakeTime = 0;
tickInterval = newMode === 'demo' ? DEMO_TICK_MS : BASE_TICK_MS;
lastTickTime = performance.now();
updateHUD();
updateOverlay();
}
function updateHUD() {
scoreEl.textContent = mode === 'demo' ? '0' : String(score);
bestEl.textContent = String(sessionBest);
statusPill.className = 'status-pill';
if (mode === 'demo') {
statusPill.textContent = 'DEMO';
} else if (mode === 'playing') {
statusPill.textContent = 'LIVE';
} else if (mode === 'paused') {
statusPill.textContent = 'PAUSED';
statusPill.classList.add('paused');
} else if (mode === 'gameover') {
statusPill.textContent = 'GAME OVER';
statusPill.classList.add('gameover');
}
pauseBtn.textContent = mode === 'paused' ? '▶' : '⏸';
pauseBtn.style.opacity = (mode === 'playing' || mode === 'paused') ? '1' : '0.45';
}
function updateOverlay(isNewBest = false) {
overlay.classList.remove('hidden', 'demo-mode', 'solid-dim');
finalStats.style.display = 'none';
newBestBadge.style.display = 'none';
overlaySub.classList.remove('pulse-prompt');
if (mode === 'demo') {
overlay.classList.add('demo-mode');
overlayTag.textContent = 'Self-Playing Demo';
overlayTitle.textContent = 'Snake Arcade';
overlaySub.textContent = 'Click or press a key to start';
overlaySub.classList.add('pulse-prompt');
overlayBtn.textContent = 'Play Now';
} else if (mode === 'playing') {
overlay.classList.add('hidden');
} else if (mode === 'paused') {
overlay.classList.add('solid-dim');
overlayTag.textContent = 'Game Paused';
overlayTitle.textContent = 'Take a Breather';
overlaySub.textContent = 'Press P to resume, or Space / Enter to restart.';
overlayBtn.textContent = 'Resume';
} else if (mode === 'gameover') {
overlay.classList.add('solid-dim');
overlayTag.textContent = 'Collision Detected';
overlayTitle.textContent = 'Game Over';
overlaySub.textContent = 'Press Space or Enter to play again.';
finalStats.style.display = 'flex';
finalScoreEl.textContent = String(score);
finalBestEl.textContent = String(sessionBest);
if (isNewBest) {
newBestBadge.style.display = 'inline-block';
}
overlayBtn.textContent = 'Play Again';
}
}
function startGame(initialDir = null) {
resetWorld('playing');
if (initialDir && !isOpposite(initialDir, dir) && !sameDir(initialDir, dir)) {
inputQueue.push({ ...initialDir });
}
}
function togglePause() {
if (mode === 'playing') {
const now = performance.now();
pausedProgress = Math.min(1, (now - lastTickTime) / tickInterval);
mode = 'paused';
updateHUD();
updateOverlay();
} else if (mode === 'paused') {
const now = performance.now();
lastTickTime = now - pausedProgress * tickInterval;
mode = 'playing';
updateHUD();
updateOverlay();
}
}
function enqueueDirection(nextDir) {
if (mode === 'demo') {
startGame(nextDir);
return;
}
if (mode !== 'playing') return;
const lastPlanned = inputQueue.length > 0 ? inputQueue[inputQueue.length - 1] : dir;
if (sameDir(nextDir, lastPlanned) || isOpposite(nextDir, lastPlanned)) {
return;
}
if (inputQueue.length < 3) {
inputQueue.push({ ...nextDir });
}
}
// Smart BFS-based AI for Demo mode
function chooseDemoDirection() {
const head = snake[0];
const candidates = [DIRS.up, DIRS.down, DIRS.left, DIRS.right].filter(
d => !isOpposite(d, dir)
);
// Tail vacates on next step unless snake is about to grow (we treat tail as free when not eating)
const bodySet = new Set();
for (let i = 0; i < snake.length - 1; i++) {
bodySet.add(`${snake[i].x},${snake[i].y}`);
}
function isInside(x, y) {
return x >= 0 && x < GRID_SIZE && y >= 0 && y < GRID_SIZE;
}
function bfsMetrics(startX, startY) {
const visited = new Set([`${startX},${startY}`]);
const queue = [{ x: startX, y: startY, dist: 1 }];
let foodDist = Infinity;
let idx = 0;
while (idx < queue.length) {
const cur = queue[idx++];
if (cur.x === food.x && cur.y === food.y && foodDist === Infinity) {
foodDist = cur.dist;
}
for (const d of [DIRS.up, DIRS.down, DIRS.left, DIRS.right]) {
const nx = cur.x + d.x;
const ny = cur.y + d.y;
const key = `${nx},${ny}`;
if (!isInside(nx, ny) || visited.has(key) || bodySet.has(key)) continue;
visited.add(key);
queue.push({ x: nx, y: ny, dist: cur.dist + 1 });
}
}
return { space: visited.size, foodDist };
}
let bestDir = null;
let bestScore = -Infinity;
for (const c of candidates) {
const nx = head.x + c.x;
const ny = head.y + c.y;
if (!isInside(nx, ny) || bodySet.has(`${nx},${ny}`)) continue;
const { space, foodDist } = bfsMetrics(nx, ny);
const safeEnough = space >= snake.length + 2;
let evalScore = 0;
if (safeEnough && foodDist < Infinity) {
evalScore = 10000 - foodDist * 10 + space * 0.1;
} else if (safeEnough) {
evalScore = 5000 + space;
} else {
evalScore = space * 10 - (foodDist < Infinity ? foodDist : 0);
}
// Slight preference for continuing straight to look smoother
if (sameDir(c, dir)) evalScore += 0.25;
if (evalScore > bestScore) {
bestScore = evalScore;
bestDir = c;
}
}
return bestDir || dir;
}
function spawnEatBurst(gx, gy) {
const cx = (gx + 0.5) * cellSize;
const cy = (gy + 0.5) * cellSize;
const palette = ['#fb7185', '#fda4af', '#fbbf24', '#34d399', '#ffffff'];
for (let i = 0; i < 16; i++) {
const angle = (Math.PI * 2 * i) / 16 + (Math.random() - 0.5) * 0.35;
const speed = cellSize * (2.2 + Math.random() * 3.4);
particles.push({
x: cx,
y: cy,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
radius: 2 + Math.random() * 2.8,
color: palette[Math.floor(Math.random() * palette.length)],
life: 0,
maxLife: 0.36 + Math.random() * 0.22
});
}
rings.push({
x: cx,
y: cy,
radius: cellSize * 0.2,
maxRadius: cellSize * 1.45,
color: '#fb7185',
life: 0,
maxLife: 0.38
});
if (mode === 'playing') {
floatingTexts.push({
x: cx,
y: cy - cellSize * 0.25,
text: '+10',
life: 0,
maxLife: 0.65
});
}
}
function spawnCrashBurst(gx, gy) {
const cx = (gx + 0.5) * cellSize;
const cy = (gy + 0.5) * cellSize;
for (let i = 0; i < 20; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = cellSize * (1.8 + Math.random() * 4.2);
particles.push({
x: cx,
y: cy,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
radius: 2 + Math.random() * 3,
color: i % 2 === 0 ? '#fb7185' : '#fbbf24',
life: 0,
maxLife: 0.45 + Math.random() * 0.25
});
}
shakeTime = 0.24;
shakeStrength = 6;
}
function stepTick() {
if (mode === 'demo') {
dir = chooseDemoDirection();
} else if (inputQueue.length > 0) {
dir = inputQueue.shift();
}
targetHeadAngle = dirToAngle(dir);
const head = snake[0];
const nextHead = { x: head.x + dir.x, y: head.y + dir.y };
const willEat = nextHead.x === food.x && nextHead.y === food.y;
// Check wall collision
const hitWall =
nextHead.x < 0 ||
nextHead.x >= GRID_SIZE ||
nextHead.y < 0 ||
nextHead.y >= GRID_SIZE;
// Check self collision (tail vacates unless eating)
const checkLen = willEat ? snake.length : snake.length - 1;
let hitSelf = false;
for (let i = 0; i < checkLen; i++) {
if (snake[i].x === nextHead.x && snake[i].y === nextHead.y) {
hitSelf = true;
break;
}
}
if (hitWall || hitSelf) {
if (mode === 'demo') {
resetWorld('demo');
return;
}
spawnCrashBurst(head.x, head.y);
mode = 'gameover';
let isNewBest = false;
if (score > sessionBest) {
sessionBest = score;
isNewBest = true;
}
updateHUD();
updateOverlay(isNewBest);
return;
}
prevSnake = snake.map(p => ({ ...p }));
if (willEat) {
snake = [nextHead, ...snake];
ateThisTick = true;
spawnEatBurst(food.x, food.y);
if (mode === 'playing') {
score += 10;
if (score > sessionBest) {
sessionBest = score;
}
// Subtle speed progression
tickInterval = Math.max(MIN_TICK_MS, BASE_TICK_MS - Math.floor(score / 40) * 3);
updateHUD();
}
food = spawnFood();
} else {
snake = [nextHead, ...snake.slice(0, -1)];
ateThisTick = false;
}
}
function updateFX(dt) {
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.life += dt;
if (p.life >= p.maxLife) {
particles.splice(i, 1);
continue;
}
p.x += p.vx * dt;
p.y += p.vy * dt;
p.vx *= Math.pow(0.06, dt);
p.vy *= Math.pow(0.06, dt);
}
for (let i = rings.length - 1; i >= 0; i--) {
const r = rings[i];
r.life += dt;
if (r.life >= r.maxLife) {
rings.splice(i, 1);
}
}
for (let i = floatingTexts.length - 1; i >= 0; i--) {
const f = floatingTexts[i];
f.life += dt;
if (f.life >= f.maxLife) {
floatingTexts.splice(i, 1);
continue;
}
f.y -= cellSize * 1.1 * dt;
}
if (shakeTime > 0) {
shakeTime = Math.max(0, shakeTime - dt);
}
}
function drawBoard() {
// Base background
ctx.fillStyle = '#0b1220';
ctx.fillRect(0, 0, canvasSize, canvasSize);
// Subtle checkerboard cells
for (let y = 0; y < GRID_SIZE; y++) {
for (let x = 0; x < GRID_SIZE; x++) {
if ((x + y) % 2 === 0) {
ctx.fillStyle = 'rgba(255, 255, 255, 0.018)';
ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize);
}
}
}
// Crisp subtle grid lines
ctx.strokeStyle = 'rgba(148, 163, 184, 0.055)';
ctx.lineWidth = 1;
ctx.beginPath();
for (let i = 1; i < GRID_SIZE; i++) {
const pos = Math.round(i * cellSize) + 0.5;
ctx.moveTo(pos, 0);
ctx.lineTo(pos, canvasSize);
ctx.moveTo(0, pos);
ctx.lineTo(canvasSize, pos);
}
ctx.stroke();
}
function drawFood(now) {
const cx = (food.x + 0.5) * cellSize;
const cy = (food.y + 0.5) * cellSize;
const pulse = 1 + 0.08 * Math.sin(now * 0.0065);
const r = cellSize * 0.34 * pulse;
// Soft outer glow
const glow = ctx.createRadialGradient(cx, cy, r * 0.2, cx, cy, r * 2.1);
glow.addColorStop(0, 'rgba(251, 113, 133, 0.42)');
glow.addColorStop(1, 'rgba(251, 113, 133, 0)');
ctx.fillStyle = glow;
ctx.beginPath();
ctx.arc(cx, cy, r * 2.1, 0, Math.PI * 2);
ctx.fill();
// Main berry body
const grad = ctx.createRadialGradient(cx - r * 0.3, cy - r * 0.3, r * 0.1, cx, cy, r);
grad.addColorStop(0, '#fda4af');
grad.addColorStop(0.55, '#fb7185');
grad.addColorStop(1, '#e11d48');
ctx.fillStyle = grad;
ctx.beginPath();
ctx.arc(cx, cy, r, 0, Math.PI * 2);
ctx.fill();
// Tiny stem/leaf accent
ctx.strokeStyle = '#34d399';
ctx.lineWidth = Math.max(1.5, cellSize * 0.07);
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(cx, cy - r * 0.78);
ctx.quadraticCurveTo(cx + r * 0.28, cy - r * 1.25, cx + r * 0.52, cy - r * 1.02);
ctx.stroke();
// Specular highlight
ctx.fillStyle = 'rgba(255, 255, 255, 0.55)';
ctx.beginPath();
ctx.arc(cx - r * 0.32, cy - r * 0.32, r * 0.22, 0, Math.PI * 2);
ctx.fill();
}
function drawSnake(alpha, now) {
if (snake.length === 0) return;
// Interpolated head and tail positions connected smoothly through exact grid corners
const headX = lerp(prevSnake[0].x, snake[0].x, alpha);
const headY = lerp(prevSnake[0].y, snake[0].y, alpha);
const prevTail = prevSnake[prevSnake.length - 1];
const currTail = snake[snake.length - 1];
const tailX = lerp(prevTail.x, currTail.x, alpha);
const tailY = lerp(prevTail.y, currTail.y, alpha);
const points = [];
points.push({ x: (headX + 0.5) * cellSize, y: (headY + 0.5) * cellSize });
for (let i = 1; i < snake.length - 1; i++) {
points.push({ x: (snake[i].x + 0.5) * cellSize, y: (snake[i].y + 0.5) * cellSize });
}
points.push({ x: (tailX + 0.5) * cellSize, y: (tailY + 0.5) * cellSize });
// Outer subtle glow under snake
ctx.save();
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.strokeStyle = 'rgba(16, 185, 129, 0.18)';
ctx.lineWidth = cellSize * 0.96;
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for (let i = 1; i < points.length; i++) {
ctx.lineTo(points[i].x, points[i].y);
}
ctx.stroke();
// Dark crisp rim around body
ctx.strokeStyle = '#064e3b';
ctx.lineWidth = cellSize * 0.78;
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for (let i = 1; i < points.length; i++) {
ctx.lineTo(points[i].x, points[i].y);
}
ctx.stroke();
// Main gradient body spine
const bodyGrad = ctx.createLinearGradient(
points[0].x,
points[0].y,
points[points.length - 1].x,
points[points.length - 1].y
);
bodyGrad.addColorStop(0, '#34d399');
bodyGrad.addColorStop(0.5, '#10b981');
bodyGrad.addColorStop(1, '#059669');
ctx.strokeStyle = bodyGrad;
ctx.lineWidth = cellSize * 0.66;
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for (let i = 1; i < points.length; i++) {
ctx.lineTo(points[i].x, points[i].y);
}
ctx.stroke();
// Inner dorsal highlight stripe for depth
ctx.strokeStyle = 'rgba(167, 243, 208, 0.28)';
ctx.lineWidth = cellSize * 0.24;
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for (let i = 1; i < points.length; i++) {
ctx.lineTo(points[i].x, points[i].y);
}
ctx.stroke();
ctx.restore();
// Subtle tail tip cap
const tailPt = points[points.length - 1];
ctx.fillStyle = '#047857';
ctx.beginPath();
ctx.arc(tailPt.x, tailPt.y, cellSize * 0.26, 0, Math.PI * 2);
ctx.fill();
// Distinct sculpted head facing movement direction
const hx = points[0].x;
const hy = points[0].y;
headAngle = lerpAngle(headAngle, targetHeadAngle, 0.35);
ctx.save();
ctx.translate(hx, hy);
ctx.rotate(headAngle);
// Occasional subtle forked tongue flick when near food
const distToFood = Math.hypot(food.x - headX, food.y - headY);
if (mode !== 'gameover' && (distToFood < 4.5 || Math.sin(now * 0.005) > 0.88)) {
const flick = 0.5 + 0.5 * Math.sin(now * 0.035);
const tongueLen = cellSize * (0.22 + 0.18 * flick);
ctx.strokeStyle = '#fb7185';
ctx.lineWidth = Math.max(1.5, cellSize * 0.065);
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(cellSize * 0.42, 0);
ctx.lineTo(cellSize * 0.42 + tongueLen, 0);
ctx.lineTo(cellSize * 0.42 + tongueLen + cellSize * 0.1, -cellSize * 0.08);
ctx.moveTo(cellSize * 0.42 + tongueLen, 0);
ctx.lineTo(cellSize * 0.42 + tongueLen + cellSize * 0.1, cellSize * 0.08);
ctx.stroke();
}
// Head shape (rounded capsule slightly wider than body)
const hw = cellSize * 0.9;
const hh = cellSize * 0.82;
const hr = cellSize * 0.36;
ctx.fillStyle = '#065f46';
roundRect(ctx, -hw * 0.48 - 1.5, -hh * 0.5 - 1.5, hw + 3, hh + 3, hr + 1.5);
ctx.fill();
const headGrad = ctx.createLinearGradient(-hw * 0.4, 0, hw * 0.5, 0);
headGrad.addColorStop(0, '#10b981');
headGrad.addColorStop(1, '#6ee7b7');
ctx.fillStyle = headGrad;
roundRect(ctx, -hw * 0.48, -hh * 0.5, hw, hh, hr);
ctx.fill();
// Eyes facing direction (two eyes on front-left & front-right)
const eyeX = cellSize * 0.16;
const eyeY = cellSize * 0.21;
const eyeR = cellSize * 0.135;
const pupilR = cellSize * 0.072;
// Subtle pupil look toward food in local head space
const fx = (food.x + 0.5) * cellSize - hx;
const fy = (food.y + 0.5) * cellSize - hy;
const cosA = Math.cos(-headAngle);
const sinA = Math.sin(-headAngle);
const localFx = fx * cosA - fy * sinA;
const localFy = fx * sinA + fy * cosA;
const fLen = Math.hypot(localFx, localFy) || 1;
const lookX = (localFx / fLen) * eyeR * 0.35;
const lookY = (localFy / fLen) * eyeR * 0.35;
for (const side of [-1, 1]) {
const ey = side * eyeY;
// Sclera
ctx.fillStyle = '#f8fafc';
ctx.beginPath();
ctx.arc(eyeX, ey, eyeR, 0, Math.PI * 2);
ctx.fill();
if (mode === 'gameover') {
// X eyes on game over
ctx.strokeStyle = '#0f172a';
ctx.lineWidth = Math.max(1.5, cellSize * 0.06);
ctx.lineCap = 'round';
const xr = eyeR * 0.48;
ctx.beginPath();
ctx.moveTo(eyeX - xr, ey - xr);
ctx.lineTo(eyeX + xr, ey + xr);
ctx.moveTo(eyeX + xr, ey - xr);
ctx.lineTo(eyeX - xr, ey + xr);
ctx.stroke();
} else {
// Pupil
ctx.fillStyle = '#0f172a';
ctx.beginPath();
ctx.arc(eyeX + lookX, ey + lookY, pupilR, 0, Math.PI * 2);
ctx.fill();
// Catchlight
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(eyeX + lookX - pupilR * 0.35, ey + lookY - pupilR * 0.35, pupilR * 0.34, 0, Math.PI * 2);
ctx.fill();
}
}
ctx.restore();
}
function roundRect(context, x, y, w, h, r) {
const radius = Math.min(r, w / 2, h / 2);
context.beginPath();
context.moveTo(x + radius, y);
context.lineTo(x + w - radius, y);
context.quadraticCurveTo(x + w, y, x + w, y + radius);
context.lineTo(x + w, y + h - radius);
context.quadraticCurveTo(x + w, y + h, x + w - radius, y + h);
context.lineTo(x + radius, y + h);
context.quadraticCurveTo(x, y + h, x, y + h - radius);
context.lineTo(x, y + radius);
context.quadraticCurveTo(x, y, x + radius, y);
context.closePath();
}
function drawFX() {
// Shockwave rings
for (const r of rings) {
const t = r.life / r.maxLife;
const curR = lerp(r.radius, r.maxRadius, 1 - Math.pow(1 - t, 2));
ctx.save();
ctx.globalAlpha = (1 - t) * 0.65;
ctx.strokeStyle = r.color;
ctx.lineWidth = Math.max(1, (1 - t) * 3);
ctx.beginPath();
ctx.arc(r.x, r.y, curR, 0, Math.PI * 2);
ctx.stroke();
ctx.restore();
}
// Particles
for (const p of particles) {
const t = p.life / p.maxLife;
ctx.save();
ctx.globalAlpha = 1 - t;
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(p.x, p.y, Math.max(0.5, p.radius * (1 - t * 0.7)), 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
// Floating +10 text
for (const f of floatingTexts) {
const t = f.life / f.maxLife;
ctx.save();
ctx.globalAlpha = 1 - Math.pow(t, 1.6);
ctx.font = `800 ${Math.round(cellSize * 0.56)}px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = '#fef08a';
ctx.shadowColor = 'rgba(0, 0, 0, 0.7)';
ctx.shadowBlur = 4;
ctx.fillText(f.text, f.x, f.y);
ctx.restore();
}
}
let lastFrameTime = performance.now();
function loop(now) {
const dt = Math.min(0.05, (now - lastFrameTime) / 1000);
lastFrameTime = now;
if (mode === 'demo' || mode === 'playing') {
// Catch up at most 1 tick per frame so tabs returning from background don't jump wildly
if (now - lastTickTime >= tickInterval) {
lastTickTime += tickInterval;
if (now - lastTickTime > tickInterval * 2) {
lastTickTime = now;
}
stepTick();
}
}
updateFX(dt);
let alpha = 1;
if (mode === 'demo' || mode === 'playing') {
alpha = Math.max(0, Math.min(1, (now - lastTickTime) / tickInterval));
} else if (mode === 'paused') {
alpha = pausedProgress;
} else if (mode === 'gameover') {
alpha = 1;
}
ctx.save();
if (shakeTime > 0) {
const mag = shakeStrength * (shakeTime / 0.24);
ctx.translate((Math.random() - 0.5) * mag * 2, (Math.random() - 0.5) * mag * 2);
}
drawBoard();
drawFood(now);
drawSnake(alpha, now);
drawFX();
ctx.restore();
requestAnimationFrame(loop);
}
// Keyboard controls
window.addEventListener('keydown', (e) => {
const key = e.key.toLowerCase();
if (['arrowup', 'arrowdown', 'arrowleft', 'arrowright', ' '].includes(key) || e.code === 'Space') {
e.preventDefault();
}
if (mode === 'demo') {
if (key === 'arrowup' || key === 'w') return startGame(DIRS.up);
if (key === 'arrowdown' || key === 's') return startGame(DIRS.down);
if (key === 'arrowleft' || key === 'a') return startGame(DIRS.left);
if (key === 'arrowright' || key === 'd') return startGame(DIRS.right);
return startGame();
}
if (key === 'p') {
if (mode === 'playing' || mode === 'paused') {
togglePause();
}
return;
}
if (e.code === 'Space' || key === 'enter') {
if (mode === 'gameover' || mode === 'paused') {
startGame();
}
return;
}
if (key === 'arrowup' || key === 'w') {
enqueueDirection(DIRS.up);
} else if (key === 'arrowdown' || key === 's') {
enqueueDirection(DIRS.down);
} else if (key === 'arrowleft' || key === 'a') {
enqueueDirection(DIRS.left);
} else if (key === 'arrowright' || key === 'd') {
enqueueDirection(DIRS.right);
}
});
// Mouse / Touch interactions
overlay.addEventListener('click', () => {
if (mode === 'demo') {
startGame();
} else if (mode === 'paused') {
togglePause();
} else if (mode === 'gameover') {
startGame();
}
});
overlayBtn.addEventListener('click', (e) => {
e.stopPropagation();
if (mode === 'paused') {
togglePause();
} else {
startGame();
}
});
pauseBtn.addEventListener('click', () => {
if (mode === 'playing' || mode === 'paused') {
togglePause();
}
});
// On-screen D-pad buttons
document.querySelectorAll('.pad-btn').forEach((btn) => {
btn.addEventListener('pointerdown', (e) => {
e.preventDefault();
const d = DIRS[btn.dataset.dir];
if (d) enqueueDirection(d);
});
});
// Swipe support on canvas
let touchStart = null;
canvas.addEventListener('touchstart', (e) => {
if (e.touches.length > 0) {
touchStart = { x: e.touches[0].clientX, y: e.touches[0].clientY };
}
}, { passive: true });
canvas.addEventListener('touchmove', (e) => {
if (!touchStart || e.touches.length === 0) return;
const dx = e.touches[0].clientX - touchStart.x;
const dy = e.touches[0].clientY - touchStart.y;
const threshold = 22;
if (Math.abs(dx) < threshold && Math.abs(dy) < threshold) return;
if (Math.abs(dx) > Math.abs(dy)) {
enqueueDirection(dx > 0 ? DIRS.right : DIRS.left);
} else {
enqueueDirection(dy > 0 ? DIRS.down : DIRS.up);
}
touchStart = { x: e.touches[0].clientX, y: e.touches[0].clientY };
}, { passive: true });
// Initialize in self-playing demo mode
resetWorld('demo');
requestAnimationFrame(loop);
})();
</script>
</body>
</html>Reasoning trace (5,264 tokens)
**Designing an All-in-One Snake** I'm focusing on integrating all game logic, styling, and interactivity directly within a single HTML file. My current efforts are dedicated to meticulously adhering to the strict constraints, particularly the exclusion of external resources and any form of persistent or modal browser storage/prompts. **Implementing Game Mechanics** I'm currently refining the core game mechanics. Specifically, I'm implementing the input queue for snake movement, ensuring smooth responsiveness even with rapid key presses. My focus is also on the food spawning logic, guaranteeing it never appears on the snake itself. **Enhancing Visual Experience** I'm actively developing touch controls for mobile, aiming for intuitive D-pad and swipe gestures on smaller screens. Concurrently, I'm implementing sophisticated visual polish, including smooth, interpolated snake movement using `lerp` for a silky-smooth appearance, along with distinct head designs and subtle particle effects upon food consumption. **Refining Visual Smoothness** I'm experimenting with drawing the snake as a continuous rounded polyline, connecting interpolated segments. This approach, combined with potential tapered tail effects, seems promising for achieving a fluid and polished visual appearance, especially around corners. **Connecting Snake Segments** I'm refining how the snake's body connects. Instead of a direct line between interpolated points, I'm exploring drawing through the actual grid corners. This creates a cleaner polyline from the interpolated head to the interpolated tail, passing through intermediate grid points. **Refining Interpolation Logic** I'm verifying that the polyline interpolation `[headInterp, curr[1], tailInterp]` accurately represents the snake's path, especially at grid corners. The current logic appears to correctly handle turns and growth, ensuring smooth visual transitions. **Detecting Game Over** I'm considering implementing the `gameOver` state either before or during the final interpolation step. A brief screen-shake or a slight movement before stopping at `alpha=0` would provide excellent visual feedback. **Developing Demo AI** My focus is now on crafting the attract mode's AI. I'm planning to use a BFS approach to evaluate possible moves, prioritizing avoiding immediate collisions and ensuring smooth, engaging gameplay for the demo. **Strategizing AI Moves** I'm implementing the demo AI's decision-making process. It will now evaluate moves by checking food reachability and, crucially, maximizing remaining open cells. This prioritizes survival and efficient food acquisition, ensuring a dynamic and visually appealing attract mode. **Designing UI Elements** I'm now focusing on the user interface, specifically the header and canvas dimensions. I'm aiming for a dynamic layout that accommodates various screen sizes while maintaining crisp visuals and ensuring a responsive header with score displays and controls. **Implementing UI Layout** I'm defining the layout for the header and canvas. The header will feature the title, status, and score information, while the canvas will scale adaptively to different resolutions and pixel densities, ensuring optimal display across devices. **Developing Overlay Logic** I'm implementing the start overlay. It will be a sleek, semi-transparent element positioned over the canvas, displaying a clear call to action to begin the game while the demo mode plays in the background. **Integrating Control Hints** I'm designing the control hints to be displayed below the canvas. These will provide clear, concise instructions for both keyboard and touch inputs, enhancing accessibility and ease of play for new users. **Enhancing Canvas Adaptability** My focus is on ensuring the canvas scales gracefully across different viewport sizes. I'm utilizing CSS and device pixel ratio adjustments to achieve sharp rendering on high-resolution displays and to fit within limited screen real estate without requiring scrolling. **Optimizing Mobile Controls** I'm refining the touch control implementation for mobile devices. This includes ensuring intuitive D-pad and swipe gesture recognition, integrated seamlessly with the existing game mechanics for a fluid mobile experience. **Refining Overlay Aesthetics** I'm experimenting with glassmorphic styling for the start overlay. This will give it a modern, visually appealing look with a subtle blur effect, making it prominent yet unobtrusive to the demo gameplay. **Implementing Audio Safely** I'm carefully integrating optional Web Audio API sound effects. The system is designed to handle potential context initialization issues before user gesture, ensuring sounds only play during the 'playing' state and are muted by default. **Validating Input Logic** I've rigorously tested the input queue implementation. It correctly handles rapid directional changes, ensuring both presses are honored while preventing the snake from reversing into itself. This logic now seems robust for all movement scenarios. **Refining Game State Transitions** I'm confirming the transitions between demo, playing, and paused states. Player interaction, whether a click or key press, will now seamlessly initiate gameplay with an immediate, valid move if a direction key is used. **Implementing Overlay Functionality** I'm solidifying the pause and game-over overlays. These elements will display clear state information, restart options, and final scores. I've also ensured that overlay interactions, like restarting, properly trigger the necessary game resets. **Enhancing Input Responsiveness** My focus has shifted to optimizing input handling for arrow keys and the space bar. I'm implementing `e.preventDefault()` to eliminate unintended browser scrolling, ensuring a pure game control experience. **Adding Visual Polish** I'm refining the visual presentation with subtle grid lines on the canvas for better cell alignment. The food is now a glowing, animated orb, and I'm developing a layered snake body with gradients and tapering effects for a truly sleek look. **Tailoring Snake Appearance** I'm exploring drawing the main body with `ctx.stroke()` for crisp corners and then overlaying individual segments or a tapered shape for the tail. Alternatively, a single continuous stroke with an inner highlight and a distinct, sculpted head seems promising for a sleek look. **Crafting the Snake Head** I'm designing a rounded, pill-shaped head with subtle shaping, oriented by movement vector. I'm also planning for smoothly interpolating its facing angle during turns and adding crisp, forward-facing eyes with specular highlights. **Adding Animated Details** I'm considering adding a subtle forked tongue flicker when the snake is near food, plus particle bursts and floating score text upon consumption. I'm also planning for a brief screen shake with spark particles on game over. **Animating Snake Movement** I'm solidifying the animation logic for snake movement. The interpolation is working as expected, smoothly transitioning from the previous position to the current one. This applies to both the head and the tail, ensuring a fluid visual flow across all segments.