Gemini 3.8 Flash
by GoogleReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 56.0 s
- Cost
- $0.038
- Output tokens
- 9,976
- Spent thinking
- 1,838 tokens
- Speed
- 197.5 tok/s
- Console errors
- 0
- First token after
- 5.5 s
- First visible text after
- 17.6 s
- Prompt tokens
- 507
- Code size
- 21.4 KB
- Finish reason
- stop
- Served by
- Model id
google/gemini-3.8-flash- Reasoning sent
{"effort":"medium"}- Max output
- 65,536 tokens
- Generated
- Nov 12, 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 · 834 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Neon Snake</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
-webkit-user-select: none;
}
body {
background: radial-gradient(circle at center, #121826 0%, #070a10 100%);
color: #f1f5f9;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
}
.game-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 14px;
padding: 16px;
max-width: 100vw;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
max-width: 540px;
padding: 0 4px;
}
.title-area {
display: flex;
align-items: center;
gap: 10px;
}
.title {
font-size: 26px;
font-weight: 800;
letter-spacing: 1.5px;
background: linear-gradient(135deg, #22c55e, #10b981, #06b6d4);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-transform: uppercase;
}
.badge {
font-size: 11px;
font-weight: 700;
letter-spacing: 0.8px;
padding: 3px 8px;
border-radius: 999px;
background: rgba(16, 185, 129, 0.15);
color: #34d399;
border: 1px solid rgba(16, 185, 129, 0.3);
text-transform: uppercase;
}
.badge.demo {
background: rgba(245, 158, 11, 0.15);
color: #fbbf24;
border-color: rgba(245, 158, 11, 0.3);
}
.scores {
display: flex;
gap: 12px;
}
.score-card {
background: rgba(18, 24, 38, 0.8);
border: 1px solid rgba(255, 255, 255, 0.08);
backdrop-filter: blur(8px);
border-radius: 10px;
padding: 6px 14px;
min-width: 80px;
text-align: center;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.score-label {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 1px;
color: #94a3b8;
margin-bottom: 2px;
}
.score-val {
font-size: 18px;
font-weight: 700;
color: #f8fafc;
font-variant-numeric: tabular-nums;
}
.canvas-wrapper {
position: relative;
border-radius: 16px;
padding: 6px;
background: linear-gradient(145deg, rgba(34, 197, 94, 0.25), rgba(6, 182, 212, 0.1), rgba(255, 255, 255, 0.03));
box-shadow: 0 10px 40px -10px rgba(0, 0, 0, 0.7), 0 0 30px rgba(16, 185, 129, 0.12);
}
canvas {
display: block;
background: #0b0f19;
border-radius: 12px;
width: min(86vw, 76vh, 520px);
height: min(86vw, 76vh, 520px);
cursor: pointer;
}
.overlay {
position: absolute;
inset: 6px;
border-radius: 12px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: rgba(7, 10, 16, 0.82);
backdrop-filter: blur(6px);
transition: opacity 0.2s ease;
pointer-events: none;
text-align: center;
padding: 24px;
}
.overlay.hidden {
opacity: 0;
}
.overlay-title {
font-size: 32px;
font-weight: 900;
letter-spacing: 1px;
margin-bottom: 8px;
}
.overlay-title.game-over {
color: #ef4444;
text-shadow: 0 0 20px rgba(239, 68, 68, 0.5);
}
.overlay-title.paused {
color: #38bdf8;
text-shadow: 0 0 20px rgba(56, 189, 248, 0.5);
}
.overlay-title.demo-text {
background: linear-gradient(135deg, #f59e0b, #ec4899);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.overlay-sub {
font-size: 14px;
color: #94a3b8;
margin-bottom: 20px;
}
.btn-start {
pointer-events: auto;
background: linear-gradient(135deg, #10b981, #059669);
color: #ffffff;
font-weight: 700;
font-size: 15px;
padding: 12px 28px;
border: none;
border-radius: 999px;
cursor: pointer;
box-shadow: 0 4px 20px rgba(16, 185, 129, 0.4);
transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.btn-start:hover {
transform: translateY(-1px) scale(1.03);
box-shadow: 0 6px 24px rgba(16, 185, 129, 0.6);
}
.btn-start:active {
transform: translateY(1px) scale(0.98);
}
.controls-hint {
display: flex;
gap: 16px;
font-size: 12px;
color: #64748b;
align-items: center;
}
.key-chip {
display: inline-block;
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 4px;
padding: 2px 6px;
font-family: inherit;
font-weight: 600;
color: #cbd5e1;
}
</style>
</head>
<body>
<div class="game-container">
<div class="header">
<div class="title-area">
<div class="title">Neon Snake</div>
<div class="badge demo" id="modeBadge">AI Demo</div>
</div>
<div class="scores">
<div class="score-card">
<div class="score-label">Score</div>
<div class="score-val" id="scoreEl">0</div>
</div>
<div class="score-card">
<div class="score-label">Best</div>
<div class="score-val" id="bestScoreEl">0</div>
</div>
</div>
</div>
<div class="canvas-wrapper">
<canvas id="gameCanvas" width="520" height="520"></canvas>
<div class="overlay" id="overlay">
<div class="overlay-title demo-text" id="overlayTitle">Watch AI or Play</div>
<div class="overlay-sub" id="overlaySub">Auto-playing demo. Take control anytime!</div>
<button class="btn-start" id="startBtn">Play Now</button>
</div>
</div>
<div class="controls-hint">
<span><span class="key-chip">W A S D</span> or <span class="key-chip">Arrows</span> Move</span>
<span><span class="key-chip">P</span> Pause</span>
<span><span class="key-chip">Space</span> Restart</span>
</div>
</div>
<script>
(() => {
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const scoreEl = document.getElementById('scoreEl');
const bestScoreEl = document.getElementById('bestScoreEl');
const modeBadge = document.getElementById('modeBadge');
const overlay = document.getElementById('overlay');
const overlayTitle = document.getElementById('overlayTitle');
const overlaySub = document.getElementById('overlaySub');
const startBtn = document.getElementById('startBtn');
const GRID = 22; // 22x22 cells
const CELL = canvas.width / GRID;
// Game States: 'DEMO', 'PLAYING', 'PAUSED', 'GAMEOVER'
let state = 'DEMO';
let score = 0;
let bestScore = 0;
// Timing
let lastTickTime = performance.now();
const TICK_INTERVAL = 110; // ms per grid step
// Grid coordinates: {x, y}
let snake = [];
let prevSnake = [];
let dir = { x: 1, y: 0 };
let lastDir = { x: 1, y: 0 };
let inputQueue = [];
let food = { x: 15, y: 11 };
let particles = [];
let floatScores = [];
let foodPulse = 0;
function initSnake() {
const startY = Math.floor(GRID / 2);
snake = [
{ x: 6, y: startY },
{ x: 5, y: startY },
{ x: 4, y: startY },
{ x: 3, y: startY }
];
prevSnake = snake.map(s => ({ ...s }));
dir = { x: 1, y: 0 };
lastDir = { x: 1, y: 0 };
inputQueue = [];
}
function spawnFood() {
const occupied = new Set();
snake.forEach(seg => occupied.add(`${seg.x},${seg.y}`));
const free = [];
for (let x = 0; x < GRID; x++) {
for (let y = 0; y < GRID; y++) {
if (!occupied.has(`${x},${y}`)) free.push({ x, y });
}
}
if (free.length > 0) {
const pick = free[Math.floor(Math.random() * free.length)];
food = pick;
}
}
function createFoodBurst(x, y) {
const cx = (x + 0.5) * CELL;
const cy = (y + 0.5) * CELL;
const count = 18;
for (let i = 0; i < count; i++) {
const angle = (Math.PI * 2 * i) / count + (Math.random() * 0.4 - 0.2);
const speed = Math.random() * 2.8 + 1.2;
particles.push({
x: cx,
y: cy,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
life: 1.0,
decay: Math.random() * 0.03 + 0.025,
color: Math.random() > 0.4 ? '#f43f5e' : '#fbbf24',
size: Math.random() * 3.5 + 2
});
}
floatScores.push({
x: cx,
y: cy - 10,
text: '+10',
life: 1.0
});
}
function startUserGame() {
state = 'PLAYING';
score = 0;
scoreEl.textContent = score;
modeBadge.textContent = 'Live';
modeBadge.className = 'badge';
overlay.classList.add('hidden');
initSnake();
spawnFood();
lastTickTime = performance.now();
}
function gameOver() {
state = 'GAMEOVER';
if (score > bestScore) {
bestScore = score;
bestScoreEl.textContent = bestScore;
}
overlayTitle.textContent = 'Game Over';
overlayTitle.className = 'overlay-title game-over';
overlaySub.textContent = `Score: ${score} • Press Space or Click to Restart`;
startBtn.textContent = 'Play Again';
overlay.classList.remove('hidden');
}
function togglePause() {
if (state === 'PLAYING') {
state = 'PAUSED';
overlayTitle.textContent = 'Paused';
overlayTitle.className = 'overlay-title paused';
overlaySub.textContent = 'Press P or Click to Resume';
startBtn.textContent = 'Resume';
overlay.classList.remove('hidden');
} else if (state === 'PAUSED') {
state = 'PLAYING';
overlay.classList.add('hidden');
lastTickTime = performance.now();
}
}
// Direction Queueing
function requestDir(newDir) {
if (state === 'DEMO') {
startUserGame();
} else if (state === 'GAMEOVER') {
startUserGame();
return;
} else if (state === 'PAUSED') {
togglePause();
return;
}
const reference = inputQueue.length > 0 ? inputQueue[inputQueue.length - 1] : lastDir;
// Disallow 180° turns or redundant moves
if (newDir.x + reference.x === 0 && newDir.y + reference.y === 0) return;
if (newDir.x === reference.x && newDir.y === reference.y) return;
if (inputQueue.length < 2) {
inputQueue.push(newDir);
}
}
// Keyboard controls
window.addEventListener('keydown', (e) => {
switch (e.code) {
case 'ArrowUp':
case 'KeyW':
requestDir({ x: 0, y: -1 });
e.preventDefault();
break;
case 'ArrowDown':
case 'KeyS':
requestDir({ x: 0, y: 1 });
e.preventDefault();
break;
case 'ArrowLeft':
case 'KeyA':
requestDir({ x: -1, y: 0 });
e.preventDefault();
break;
case 'ArrowRight':
case 'KeyD':
requestDir({ x: 1, y: 0 });
e.preventDefault();
break;
case 'KeyP':
if (state === 'PLAYING' || state === 'PAUSED') {
togglePause();
}
e.preventDefault();
break;
case 'Space':
case 'Enter':
if (state === 'GAMEOVER' || state === 'DEMO') {
startUserGame();
} else if (state === 'PAUSED') {
togglePause();
}
e.preventDefault();
break;
}
});
// Click controls
startBtn.addEventListener('click', (e) => {
e.stopPropagation();
if (state === 'PAUSED') togglePause();
else startUserGame();
});
canvas.addEventListener('click', () => {
if (state === 'DEMO' || state === 'GAMEOVER') {
startUserGame();
} else if (state === 'PAUSED') {
togglePause();
}
});
// AI Logic for Demo Mode
function computeAiMove() {
const head = snake[0];
const neighbors = [
{ x: 0, y: -1 },
{ x: 0, y: 1 },
{ x: -1, y: 0 },
{ x: 1, y: 0 }
];
// Build obstacle set
const obstacles = new Set();
// Exclude tail tip since it moves, unless snake just ate
for (let i = 0; i < snake.length - 1; i++) {
obstacles.add(`${snake[i].x},${snake[i].y}`);
}
// BFS to find path to food
const queue = [[head]];
const visited = new Set([`${head.x},${head.y}`]);
let pathToFood = null;
while (queue.length > 0) {
const path = queue.shift();
const curr = path[path.length - 1];
if (curr.x === food.x && curr.y === food.y) {
pathToFood = path;
break;
}
for (const n of neighbors) {
const nx = curr.x + n.x;
const ny = curr.y + n.y;
const key = `${nx},${ny}`;
if (nx >= 0 && nx < GRID && ny >= 0 && ny < GRID && !obstacles.has(key) && !visited.has(key)) {
visited.add(key);
queue.push([...path, { x: nx, y: ny }]);
}
}
}
// Safety flood-fill checker
function getSpaceScore(startPos) {
const fVisited = new Set(obstacles);
fVisited.add(`${startPos.x},${startPos.y}`);
const fQueue = [startPos];
let reachable = 0;
while (fQueue.length > 0 && reachable < 80) {
const cur = fQueue.shift();
reachable++;
for (const n of neighbors) {
const nx = cur.x + n.x;
const ny = cur.y + n.y;
const key = `${nx},${ny}`;
if (nx >= 0 && nx < GRID && ny >= 0 && ny < GRID && !fVisited.has(key)) {
fVisited.add(key);
fQueue.push({ x: nx, y: ny });
}
}
}
return reachable;
}
// Evaluate safe moves
const safeMoves = [];
for (const n of neighbors) {
const nx = head.x + n.x;
const ny = head.y + n.y;
if (nx >= 0 && nx < GRID && ny >= 0 && ny < GRID && !obstacles.has(`${nx},${ny}`)) {
if (!(n.x + lastDir.x === 0 && n.y + lastDir.y === 0)) {
safeMoves.push({ dir: n, score: getSpaceScore({ x: nx, y: ny }) });
}
}
}
if (safeMoves.length === 0) return; // trapped
// If BFS found a path and next step has sufficient space, take it!
if (pathToFood && pathToFood.length > 1) {
const nextStep = pathToFood[1];
const stepDir = { x: nextStep.x - head.x, y: nextStep.y - head.y };
const nextMove = safeMoves.find(m => m.dir.x === stepDir.x && m.dir.y === stepDir.y);
if (nextMove && nextMove.score > 8) {
dir = stepDir;
return;
}
}
// Otherwise choose move with maximum available space
safeMoves.sort((a, b) => b.score - a.score);
dir = safeMoves[0].dir;
}
// Core Game Step
function gameTick() {
if (state === 'DEMO') {
computeAiMove();
} else if (state === 'PLAYING') {
if (inputQueue.length > 0) {
dir = inputQueue.shift();
}
} else {
return;
}
lastDir = { ...dir };
// Record previous positions for smooth lerp rendering
prevSnake = snake.map(s => ({ ...s }));
const head = snake[0];
const newHead = { x: head.x + dir.x, y: head.y + dir.y };
// Wall collision
if (newHead.x < 0 || newHead.x >= GRID || newHead.y < 0 || newHead.y >= GRID) {
if (state === 'DEMO') {
initSnake();
spawnFood();
return;
}
gameOver();
return;
}
// Self collision (ignore tail if not eating, but strict check is safe)
for (let i = 0; i < snake.length - 1; i++) {
if (snake[i].x === newHead.x && snake[i].y === newHead.y) {
if (state === 'DEMO') {
initSnake();
spawnFood();
return;
}
gameOver();
return;
}
}
snake.unshift(newHead);
// Food check
if (newHead.x === food.x && newHead.y === food.y) {
score += 10;
scoreEl.textContent = score;
if (score > bestScore) {
bestScore = score;
bestScoreEl.textContent = bestScore;
}
createFoodBurst(food.x, food.y);
spawnFood();
// Snake grows (do not pop tail)
prevSnake.push({ ...prevSnake[prevSnake.length - 1] });
} else {
snake.pop();
}
}
// Drawing Helpers
function lerp(a, b, t) {
return a + (b - a) * t;
}
function drawGrid() {
ctx.strokeStyle = 'rgba(255, 255, 255, 0.025)';
ctx.lineWidth = 1;
for (let i = 0; i <= GRID; i++) {
const pos = i * CELL;
ctx.beginPath();
ctx.moveTo(pos, 0);
ctx.lineTo(pos, canvas.height);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, pos);
ctx.lineTo(canvas.width, pos);
ctx.stroke();
}
}
function drawFood(time) {
foodPulse = Math.sin(time * 0.006) * 0.15;
const fx = (food.x + 0.5) * CELL;
const fy = (food.y + 0.5) * CELL;
const baseRadius = (CELL * 0.38);
const r = baseRadius * (1 + foodPulse);
// Outer glow
ctx.save();
ctx.shadowBlur = 18;
ctx.shadowColor = '#f43f5e';
const grad = ctx.createRadialGradient(fx - r * 0.3, fy - r * 0.3, r * 0.1, fx, fy, r);
grad.addColorStop(0, '#fecdd3');
grad.addColorStop(0.3, '#f43f5e');
grad.addColorStop(1, '#9f1239');
ctx.fillStyle = grad;
ctx.beginPath();
ctx.arc(fx, fy, r, 0, Math.PI * 2);
ctx.fill();
// Specular shine
ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
ctx.beginPath();
ctx.arc(fx - r * 0.35, fy - r * 0.35, r * 0.28, 0, Math.PI * 2);
ctx.fill();
// Leaf / stem
ctx.shadowBlur = 0;
ctx.fillStyle = '#22c55e';
ctx.beginPath();
ctx.ellipse(fx + r * 0.4, fy - r * 0.8, r * 0.35, r * 0.18, Math.PI / 4, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
function drawSnake(progress) {
if (snake.length === 0) return;
// Interpolate positions for silky smooth movement
const interp = [];
for (let i = 0; i < snake.length; i++) {
const cur = snake[i];
const prev = prevSnake[i] || cur;
interp.push({
x: (lerp(prev.x, cur.x, progress) + 0.5) * CELL,
y: (lerp(prev.y, cur.y, progress) + 0.5) * CELL
});
}
ctx.save();
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
// Draw Body Spine
const radius = CELL * 0.42;
for (let i = interp.length - 1; i > 0; i--) {
const p1 = interp[i];
const p2 = interp[i - 1];
const factor = 1 - (i / interp.length) * 0.45;
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.strokeStyle = `rgb(${Math.floor(16 * factor)}, ${Math.floor(185 * factor)}, ${Math.floor(129 * factor)})`;
ctx.lineWidth = radius * 2 * factor;
ctx.stroke();
}
// Draw individual segment glow joints
for (let i = interp.length - 1; i >= 0; i--) {
const pt = interp[i];
const factor = 1 - (i / interp.length) * 0.4;
const segR = radius * factor;
if (i === 0) {
// Head glow
ctx.shadowBlur = 20;
ctx.shadowColor = '#10b981';
} else {
ctx.shadowBlur = 0;
}
ctx.fillStyle = i === 0 ? '#34d399' : `rgb(${Math.floor(16 * factor)}, ${Math.floor(185 * factor)}, ${Math.floor(129 * factor)})`;
ctx.beginPath();
ctx.arc(pt.x, pt.y, segR, 0, Math.PI * 2);
ctx.fill();
}
// Draw Head & Eyes
const head = interp[0];
const eyeOffset = radius * 0.55;
const eyeForward = radius * 0.35;
const eyeR = radius * 0.32;
const pupilR = radius * 0.18;
// Perpendicular vector for eyes
const px = -lastDir.y;
const py = lastDir.x;
const eye1 = {
x: head.x + lastDir.x * eyeForward + px * eyeOffset,
y: head.y + lastDir.y * eyeForward + py * eyeOffset
};
const eye2 = {
x: head.x + lastDir.x * eyeForward - px * eyeOffset,
y: head.y + lastDir.y * eyeForward - py * eyeOffset
};
// Sclera
ctx.shadowBlur = 0;
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(eye1.x, eye1.y, eyeR, 0, Math.PI * 2);
ctx.arc(eye2.x, eye2.y, eyeR, 0, Math.PI * 2);
ctx.fill();
// Pupils looking in direction
ctx.fillStyle = '#0f172a';
ctx.beginPath();
ctx.arc(eye1.x + lastDir.x * (eyeR * 0.4), eye1.y + lastDir.y * (eyeR * 0.4), pupilR, 0, Math.PI * 2);
ctx.arc(eye2.x + lastDir.x * (eyeR * 0.4), eye2.y + lastDir.y * (eyeR * 0.4), pupilR, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
function drawParticles() {
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.vx;
p.y += p.vy;
p.life -= p.decay;
if (p.life <= 0) {
particles.splice(i, 1);
continue;
}
ctx.save();
ctx.globalAlpha = p.life;
ctx.fillStyle = p.color;
ctx.shadowBlur = 8;
ctx.shadowColor = p.color;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size * p.life, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
}
function drawFloatScores() {
for (let i = floatScores.length - 1; i >= 0; i--) {
const fs = floatScores[i];
fs.y -= 0.6;
fs.life -= 0.02;
if (fs.life <= 0) {
floatScores.splice(i, 1);
continue;
}
ctx.save();
ctx.globalAlpha = fs.life;
ctx.font = 'bold 14px -apple-system, sans-serif';
ctx.fillStyle = '#fbbf24';
ctx.shadowBlur = 6;
ctx.shadowColor = '#f59e0b';
ctx.textAlign = 'center';
ctx.fillText(fs.text, fs.x, fs.y);
ctx.restore();
}
}
// Main Render Loop
function frame(now) {
requestAnimationFrame(frame);
if (state === 'PLAYING' || state === 'DEMO') {
const elapsed = now - lastTickTime;
if (elapsed >= TICK_INTERVAL) {
gameTick();
lastTickTime = now - (elapsed % TICK_INTERVAL);
}
}
const progress = (state === 'PLAYING' || state === 'DEMO')
? Math.min((now - lastTickTime) / TICK_INTERVAL, 1.0)
: 1.0;
// Clear Canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawGrid();
drawFood(now);
drawSnake(progress);
drawParticles();
drawFloatScores();
}
// Setup Initial State (Self-Playing Demo Mode)
initSnake();
spawnFood();
requestAnimationFrame(frame);
})();
</script>
</body>
</html>Reasoning trace (1,838 tokens)
**Clarifying Core Constraints** I'm prioritizing the strict single-file, no-external-assets mandate. My current focus is on ensuring all functionality adheres to this, especially concerning data persistence without browser storage mechanisms and achieving responsive design within these limitations. **Implementing Core Mechanics** I'm now focusing on the game's fundamental logic, specifically getting the snake to move smoothly and react to input. The input queue is crucial for preventing self-collision, and I'm experimenting with interpolation for that polished, gliding feel between grid ticks. **Refining Movement Mechanics** I'm deeply engaged in refining the snake's movement interpolation. The goal is a fluid glide, achieved by tracking segment positions and `tickProgress`. The input queue is also seeing updates to ensure reliable directional changes and prevent self-collision. **Animating Food and Particles** I'm now integrating visual flair, starting with the food's gentle pulse and a vibrant particle burst upon consumption. The focus is on making these elements feel dynamic and responsive within the established theme. **Simulating Smart Movement** I'm working on an AI for demo mode, using BFS to find the food. If that fails, I'm scoring neighboring cells based on how many free cells they can reach, aiming for a surprisingly intelligent and engaging snake. **Managing User Input** My focus is on a robust input queue mechanism. I'm refining how new directions are added and validated against the current and last applied directions to prevent invalid moves and ensure smooth control. **Enhancing Visual Polish** I'm now concentrating on subtle visual enhancements, aiming to create a more polished and engaging user experience through refined animations and effects.