Models 44

Gemini 3.8 Flash

by GoogleReasoningClean
Open in new window

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

How it went

Total time
1 min 1 s
Cost
$0.036
Output tokens
9,485
Spent thinking
1,993 tokens
Speed
161.2 tok/s
Console errors
0
First token after
2.5 s
First visible text after
18.8 s
Prompt tokens
507
Code size
21.4 KB
Finish reason
stop
Served by
Google
Model id
google/gemini-3.8-flash
Reasoning sent
model default
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 · 780 lines

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Cyber Snake</title>
  <style>
    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
      user-select: none;
    }

    body {
      background: radial-gradient(circle at 50% 30%, #111827 0%, #07090e 100%);
      color: #e2e8f0;
      font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
      min-height: 100vh;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      overflow: hidden;
      padding: 12px;
    }

    .game-wrapper {
      position: relative;
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 12px;
      max-width: 100%;
    }

    /* Score Header */
    .hud {
      width: 100%;
      max-width: 560px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      background: rgba(255, 255, 255, 0.04);
      backdrop-filter: blur(8px);
      border: 1px solid rgba(255, 255, 255, 0.08);
      border-radius: 12px;
      padding: 10px 20px;
      box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
    }

    .hud-stat {
      display: flex;
      flex-direction: column;
      align-items: flex-start;
    }

    .hud-stat.right {
      align-items: flex-end;
    }

    .stat-label {
      font-size: 0.72rem;
      text-transform: uppercase;
      letter-spacing: 0.12em;
      color: #94a3b8;
      font-weight: 600;
    }

    .stat-value {
      font-size: 1.6rem;
      font-weight: 800;
      font-variant-numeric: tabular-nums;
      line-height: 1.1;
      background: linear-gradient(135deg, #38ef7d, #11998e);
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
    }

    .stat-value.best {
      background: linear-gradient(135deg, #fbbf24, #f59e0b);
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
    }

    /* Canvas Frame */
    .canvas-container {
      position: relative;
      border-radius: 16px;
      padding: 4px;
      background: linear-gradient(135deg, rgba(16, 185, 129, 0.35), rgba(59, 130, 246, 0.15), rgba(244, 63, 94, 0.35));
      box-shadow: 0 0 35px rgba(16, 185, 129, 0.15), 0 20px 40px rgba(0, 0, 0, 0.6);
    }

    canvas {
      display: block;
      background: #090d16;
      border-radius: 12px;
      width: 560px;
      height: 560px;
      max-width: 90vw;
      max-height: 70vh;
      aspect-ratio: 1 / 1;
      cursor: pointer;
    }

    /* Overlays */
    .overlay {
      position: absolute;
      inset: 4px;
      border-radius: 12px;
      background: rgba(7, 10, 17, 0.82);
      backdrop-filter: blur(6px);
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      text-align: center;
      padding: 24px;
      pointer-events: none;
      transition: opacity 0.2s ease;
    }

    .overlay.hidden {
      opacity: 0;
      pointer-events: none;
    }

    .overlay h2 {
      font-size: 2.2rem;
      font-weight: 900;
      letter-spacing: -0.02em;
      margin-bottom: 8px;
    }

    .overlay.game-over h2 {
      background: linear-gradient(135deg, #ff4b72, #f43f5e);
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
    }

    .overlay.demo-mode h2 {
      background: linear-gradient(135deg, #38ef7d, #06b6d4);
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
    }

    .overlay.paused h2 {
      background: linear-gradient(135deg, #fcd34d, #f59e0b);
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
    }

    .overlay p {
      color: #94a3b8;
      font-size: 0.95rem;
      margin-bottom: 20px;
    }

    .badge-btn {
      display: inline-flex;
      align-items: center;
      gap: 8px;
      background: rgba(255, 255, 255, 0.08);
      border: 1px solid rgba(255, 255, 255, 0.2);
      color: #f8fafc;
      padding: 10px 22px;
      border-radius: 999px;
      font-size: 0.95rem;
      font-weight: 600;
      box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
      animation: pulse-badge 1.8s infinite ease-in-out;
    }

    .final-score {
      font-size: 1.1rem;
      color: #e2e8f0;
      margin-bottom: 16px;
    }
    .final-score span {
      font-weight: bold;
      color: #38ef7d;
    }

    .controls-hint {
      display: flex;
      gap: 16px;
      font-size: 0.8rem;
      color: #64748b;
      margin-top: 4px;
    }

    .controls-hint kbd {
      background: rgba(255, 255, 255, 0.08);
      padding: 2px 6px;
      border-radius: 4px;
      border: 1px solid rgba(255, 255, 255, 0.15);
      color: #cbd5e1;
      font-family: inherit;
    }

    @keyframes pulse-badge {
      0%, 100% { transform: scale(1); opacity: 0.95; }
      50% { transform: scale(1.05); opacity: 1; box-shadow: 0 0 20px rgba(56, 239, 125, 0.3); }
    }
  </style>
</head>
<body>

  <div class="game-wrapper">
    <div class="hud">
      <div class="hud-stat">
        <span class="stat-label">Score</span>
        <span class="stat-value" id="current-score">0</span>
      </div>
      <div class="hud-stat right">
        <span class="stat-label">Session Best</span>
        <span class="stat-value best" id="best-score">0</span>
      </div>
    </div>

    <div class="canvas-container">
      <canvas id="game-canvas" width="600" height="600"></canvas>

      <!-- Overlay for AI Demo Mode -->
      <div id="demo-overlay" class="overlay demo-mode">
        <h2>CYBER SNAKE</h2>
        <p>AI Auto-Playing Demo</p>
        <div class="badge-btn">Click or Press Any Key to Play</div>
      </div>

      <!-- Overlay for Game Over -->
      <div id="gameover-overlay" class="overlay game-over hidden">
        <h2>GAME OVER</h2>
        <div class="final-score">Final Score: <span id="final-score-val">0</span></div>
        <div class="badge-btn">Press Space or Enter to Restart</div>
      </div>

      <!-- Overlay for Paused -->
      <div id="paused-overlay" class="overlay paused hidden">
        <h2>PAUSED</h2>
        <p>Take a breather</p>
        <div class="badge-btn">Press P to Resume</div>
      </div>
    </div>

    <div class="controls-hint">
      <span><kbd>W</kbd><kbd>A</kbd><kbd>S</kbd><kbd>D</kbd> or <kbd>Arrows</kbd> to Move</span>
      <span><kbd>P</kbd> Pause</span>
    </div>
  </div>

  <script>
    const canvas = document.getElementById('game-canvas');
    const ctx = canvas.getContext('2d');
    const scoreEl = document.getElementById('current-score');
    const bestScoreEl = document.getElementById('best-score');
    const demoOverlay = document.getElementById('demo-overlay');
    const gameoverOverlay = document.getElementById('gameover-overlay');
    const pausedOverlay = document.getElementById('paused-overlay');
    const finalScoreVal = document.getElementById('final-score-val');

    // Canvas size & Grid config
    const GRID_SIZE = 22;
    const CANVAS_PX = 600;
    const CELL = CANVAS_PX / GRID_SIZE;

    // Game States
    const STATE_DEMO = 'demo';
    const STATE_PLAYING = 'playing';
    const STATE_PAUSED = 'paused';
    const STATE_GAMEOVER = 'gameover';

    let gameState = STATE_DEMO;
    let score = 0;
    let bestScore = 0;

    // Movement & Speed
    const BASE_TICK_RATE = 110; // ms
    let tickDuration = BASE_TICK_RATE;
    let lastTickTime = performance.now();

    // Snake State
    let snake = [];
    let prevSnake = []; // For sub-tick render interpolation
    let dir = { x: 1, y: 0 };
    let inputQueue = [];

    // Food State
    let food = { x: 15, y: 11 };
    let foodPulse = 0;

    // Particles System
    let particles = [];

    function resetGame(isDemo = false) {
      const midY = Math.floor(GRID_SIZE / 2);
      snake = [
        { x: 5, y: midY },
        { x: 4, y: midY },
        { x: 3, y: midY }
      ];
      prevSnake = snake.map(s => ({ ...s }));
      dir = { x: 1, y: 0 };
      inputQueue = [];
      score = 0;
      tickDuration = BASE_TICK_RATE;
      scoreEl.textContent = '0';
      spawnFood();
      gameState = isDemo ? STATE_DEMO : STATE_PLAYING;
      lastTickTime = performance.now();
      updateOverlays();
    }

    function spawnFood() {
      const occupied = new Set(snake.map(s => `${s.x},${s.y}`));
      const freeCells = [];
      for (let x = 0; x < GRID_SIZE; x++) {
        for (let y = 0; y < GRID_SIZE; y++) {
          if (!occupied.has(`${x},${y}`)) {
            freeCells.push({ x, y });
          }
        }
      }
      if (freeCells.length > 0) {
        food = freeCells[Math.floor(Math.random() * freeCells.length)];
      }
    }

    function spawnFoodParticles(x, y) {
      const cx = (x + 0.5) * CELL;
      const cy = (y + 0.5) * CELL;
      const count = 20;
      const colors = ['#f43f5e', '#fb7185', '#38ef7d', '#38bdf8', '#fef08a'];

      for (let i = 0; i < count; i++) {
        const angle = (Math.PI * 2 * i) / count + (Math.random() - 0.5);
        const speed = 1.5 + Math.random() * 3.5;
        particles.push({
          x: cx,
          y: cy,
          vx: Math.cos(angle) * speed,
          vy: Math.sin(angle) * speed,
          radius: 2 + Math.random() * 3,
          color: colors[Math.floor(Math.random() * colors.length)],
          life: 1.0,
          decay: 0.025 + Math.random() * 0.035
        });
      }
    }

    // Direction helpers
    function areOpposite(d1, d2) {
      return d1.x + d2.x === 0 && d1.y + d2.y === 0;
    }

    function queueDirection(nextDir) {
      const lastPlanned = inputQueue.length > 0 ? inputQueue[inputQueue.length - 1] : dir;
      if (!areOpposite(lastPlanned, nextDir) && !(lastPlanned.x === nextDir.x && lastPlanned.y === nextDir.y)) {
        if (inputQueue.length < 2) {
          inputQueue.push(nextDir);
        }
      }
    }

    // AI logic for self-playing demo mode
    function getNextAiDirection() {
      const head = snake[0];
      const validMoves = [
        { x: 0, y: -1 },
        { x: 0, y: 1 },
        { x: -1, y: 0 },
        { x: 1, y: 0 }
      ].filter(d => !areOpposite(dir, d));

      // Simple BFS for shortest path to food
      const queue = [[head.x, head.y, []]];
      const visited = new Set();
      visited.add(`${head.x},${head.y}`);

      // Snake body as obstacles (except current tail because it moves)
      const obstacles = new Set(snake.slice(0, -1).map(s => `${s.x},${s.y}`));

      let bestPath = null;
      while (queue.length > 0) {
        const [cx, cy, path] = queue.shift();
        if (cx === food.x && cy === food.y) {
          bestPath = path;
          break;
        }

        const dirs = [
          { x: 0, y: -1 },
          { x: 0, y: 1 },
          { x: -1, y: 0 },
          { x: 1, y: 0 }
        ];

        for (const d of dirs) {
          const nx = cx + d.x;
          const ny = cy + d.y;
          const key = `${nx},${ny}`;
          if (nx >= 0 && nx < GRID_SIZE && ny >= 0 && ny < GRID_SIZE && !obstacles.has(key) && !visited.has(key)) {
            visited.add(key);
            queue.push([nx, ny, [...path, d]]);
          }
        }
      }

      if (bestPath && bestPath.length > 0) {
        return bestPath[0];
      }

      // Fallback: pick safe neighbor with most open adjacent spaces
      let bestMove = validMoves[0] || dir;
      let maxSpace = -1;

      for (const m of validMoves) {
        const nx = head.x + m.x;
        const ny = head.y + m.y;
        if (nx >= 0 && nx < GRID_SIZE && ny >= 0 && ny < GRID_SIZE && !obstacles.has(`${nx},${ny}`)) {
          // Count open spaces around neighbor
          let spaceCount = 0;
          for (const d of validMoves) {
            const nnx = nx + d.x;
            const nny = ny + d.y;
            if (nnx >= 0 && nnx < GRID_SIZE && nny >= 0 && nny < GRID_SIZE && !obstacles.has(`${nnx},${nny}`)) {
              spaceCount++;
            }
          }
          if (spaceCount > maxSpace) {
            maxSpace = spaceCount;
            bestMove = m;
          }
        }
      }

      return bestMove;
    }

    // Tick Game Logic
    function tick() {
      if (gameState === STATE_PAUSED || gameState === STATE_GAMEOVER) return;

      if (gameState === STATE_DEMO) {
        dir = getNextAiDirection();
      } else if (inputQueue.length > 0) {
        dir = inputQueue.shift();
      }

      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_SIZE || newHead.y < 0 || newHead.y >= GRID_SIZE) {
        handleGameOver();
        return;
      }

      // Self collision (ignore tail if not eating, but simplified check against all current except last)
      const selfHit = snake.some((seg, idx) => {
        if (idx === snake.length - 1) return false;
        return seg.x === newHead.x && seg.y === newHead.y;
      });

      if (selfHit) {
        handleGameOver();
        return;
      }

      // Store previous positions for smooth interpolation
      prevSnake = snake.map(s => ({ ...s }));

      snake.unshift(newHead);

      // Check food
      if (newHead.x === food.x && newHead.y === food.y) {
        score += 10;
        scoreEl.textContent = score;
        if (score > bestScore) {
          bestScore = score;
          bestScoreEl.textContent = bestScore;
        }
        spawnFoodParticles(food.x, food.y);
        spawnFood();
        // Speed up slightly as snake grows
        tickDuration = Math.max(70, BASE_TICK_RATE - Math.floor(score / 50) * 4);
      } else {
        snake.pop();
      }
    }

    function handleGameOver() {
      gameState = STATE_GAMEOVER;
      finalScoreVal.textContent = score;
      updateOverlays();
    }

    function updateOverlays() {
      demoOverlay.classList.toggle('hidden', gameState !== STATE_DEMO);
      gameoverOverlay.classList.toggle('hidden', gameState !== STATE_GAMEOVER);
      pausedOverlay.classList.toggle('hidden', gameState !== STATE_PAUSED);
    }

    // Input handlers
    function handleKeyDown(e) {
      const code = e.code;

      // Handle Restart
      if (gameState === STATE_GAMEOVER) {
        if (code === 'Space' || code === 'Enter') {
          e.preventDefault();
          resetGame(false);
        }
        return;
      }

      // If in demo mode, any direction or start key begins user play
      if (gameState === STATE_DEMO) {
        resetGame(false);
        // Fall through to directional keys if applicable
      }

      // Pause toggle
      if (code === 'KeyP') {
        e.preventDefault();
        if (gameState === STATE_PLAYING) {
          gameState = STATE_PAUSED;
        } else if (gameState === STATE_PAUSED) {
          gameState = STATE_PLAYING;
          lastTickTime = performance.now();
        }
        updateOverlays();
        return;
      }

      if (gameState !== STATE_PLAYING) return;

      if (code === 'ArrowUp' || code === 'KeyW') {
        e.preventDefault();
        queueDirection({ x: 0, y: -1 });
      } else if (code === 'ArrowDown' || code === 'KeyS') {
        e.preventDefault();
        queueDirection({ x: 0, y: 1 });
      } else if (code === 'ArrowLeft' || code === 'KeyA') {
        e.preventDefault();
        queueDirection({ x: -1, y: 0 });
      } else if (code === 'ArrowRight' || code === 'KeyD') {
        e.preventDefault();
        queueDirection({ x: 1, y: 0 });
      }
    }

    window.addEventListener('keydown', handleKeyDown);

    canvas.addEventListener('click', () => {
      if (gameState === STATE_DEMO || gameState === STATE_GAMEOVER) {
        resetGame(false);
      } else if (gameState === STATE_PAUSED) {
        gameState = STATE_PLAYING;
        lastTickTime = performance.now();
        updateOverlays();
      }
    });

    // Drawing functions
    function drawGrid() {
      ctx.fillStyle = '#090d16';
      ctx.fillRect(0, 0, CANVAS_PX, CANVAS_PX);

      // Subtle grid dots
      ctx.fillStyle = 'rgba(255, 255, 255, 0.035)';
      for (let x = 0; x <= GRID_SIZE; x++) {
        for (let y = 0; y <= GRID_SIZE; y++) {
          ctx.beginPath();
          ctx.arc(x * CELL, y * CELL, 1.2, 0, Math.PI * 2);
          ctx.fill();
        }
      }
    }

    function drawFood(time) {
      foodPulse = Math.sin(time * 0.005) * 2;
      const cx = (food.x + 0.5) * CELL;
      const cy = (food.y + 0.5) * CELL;
      const baseRadius = CELL * 0.38 + foodPulse;

      // Outer glow
      ctx.save();
      ctx.shadowColor = 'rgba(244, 63, 94, 0.8)';
      ctx.shadowBlur = 18 + foodPulse * 3;

      const grad = ctx.createRadialGradient(cx - 3, cy - 3, 2, cx, cy, baseRadius);
      grad.addColorStop(0, '#fecdd3');
      grad.addColorStop(0.3, '#f43f5e');
      grad.addColorStop(1, '#9f1239');

      ctx.fillStyle = grad;
      ctx.beginPath();
      ctx.arc(cx, cy, Math.max(1, baseRadius), 0, Math.PI * 2);
      ctx.fill();
      ctx.restore();

      // Apple leaf
      ctx.fillStyle = '#34d399';
      ctx.beginPath();
      ctx.ellipse(cx + 3, cy - baseRadius * 0.85, 3.5, 2, Math.PI / 4, 0, Math.PI * 2);
      ctx.fill();
    }

    function drawSnake(progress) {
      if (snake.length === 0) return;

      // Interpolate segments
      const interpolated = [];
      const len = snake.length;

      for (let i = 0; i < len; i++) {
        const curr = snake[i];
        const prev = prevSnake[i] || curr;

        // Sub-tick linear interpolation
        const ix = prev.x + (curr.x - prev.x) * progress;
        const iy = prev.y + (curr.y - prev.y) * progress;
        interpolated.push({
          x: (ix + 0.5) * CELL,
          y: (iy + 0.5) * CELL
        });
      }

      // Draw Body as continuous rounded glowing stream
      if (interpolated.length > 1) {
        ctx.save();
        ctx.lineCap = 'round';
        ctx.lineJoin = 'round';

        // Background glow
        ctx.shadowColor = 'rgba(16, 185, 129, 0.45)';
        ctx.shadowBlur = 12;

        for (let i = interpolated.length - 1; i > 0; i--) {
          const p1 = interpolated[i];
          const p2 = interpolated[i - 1];

          // Taper radius slightly towards the tail
          const t = i / (interpolated.length - 1);
          const segWidth = CELL * (0.55 + (1 - t) * 0.25);

          const grad = ctx.createLinearGradient(p1.x, p1.y, p2.x, p2.y);
          grad.addColorStop(0, '#06b6d4');
          grad.addColorStop(1, '#10b981');

          ctx.strokeStyle = grad;
          ctx.lineWidth = segWidth;
          ctx.beginPath();
          ctx.moveTo(p1.x, p1.y);
          ctx.lineTo(p2.x, p2.y);
          ctx.stroke();
        }
        ctx.restore();
      }

      // Head rendering
      const head = interpolated[0];
      const headRadius = CELL * 0.44;

      ctx.save();
      ctx.shadowColor = 'rgba(56, 239, 125, 0.8)';
      ctx.shadowBlur = 15;

      const headGrad = ctx.createRadialGradient(head.x - 3, head.y - 3, 2, head.x, head.y, headRadius);
      headGrad.addColorStop(0, '#a7f3d0');
      headGrad.addColorStop(0.5, '#10b981');
      headGrad.addColorStop(1, '#047857');

      ctx.fillStyle = headGrad;
      ctx.beginPath();
      ctx.arc(head.x, head.y, headRadius, 0, Math.PI * 2);
      ctx.fill();
      ctx.restore();

      // Eyes facing movement direction
      const angle = Math.atan2(dir.y, dir.x);
      const eyeOffsetForward = headRadius * 0.35;
      const eyeOffsetSide = headRadius * 0.55;

      const cos = Math.cos(angle);
      const sin = Math.sin(angle);
      const perpX = -sin;
      const perpY = cos;

      const eye1 = {
        x: head.x + cos * eyeOffsetForward + perpX * eyeOffsetSide,
        y: head.y + sin * eyeOffsetForward + perpY * eyeOffsetSide
      };
      const eye2 = {
        x: head.x + cos * eyeOffsetForward - perpX * eyeOffsetSide,
        y: head.y + sin * eyeOffsetForward - perpY * eyeOffsetSide
      };

      const eyeRadius = headRadius * 0.3;
      const pupilRadius = eyeRadius * 0.55;

      [eye1, eye2].forEach(eye => {
        // White sclera
        ctx.fillStyle = '#f8fafc';
        ctx.beginPath();
        ctx.arc(eye.x, eye.y, eyeRadius, 0, Math.PI * 2);
        ctx.fill();

        // Dark pupil looking ahead
        ctx.fillStyle = '#0f172a';
        ctx.beginPath();
        ctx.arc(
          eye.x + cos * (eyeRadius - pupilRadius),
          eye.y + sin * (eyeRadius - pupilRadius),
          pupilRadius,
          0,
          Math.PI * 2
        );
        ctx.fill();
      });
    }

    function updateAndDrawParticles() {
      for (let i = particles.length - 1; i >= 0; i--) {
        const p = particles[i];
        p.x += p.vx;
        p.y += p.vy;
        p.vx *= 0.94;
        p.vy *= 0.94;
        p.life -= p.decay;

        if (p.life <= 0) {
          particles.splice(i, 1);
          continue;
        }

        ctx.save();
        ctx.globalAlpha = Math.max(0, p.life);
        ctx.fillStyle = p.color;
        ctx.shadowColor = p.color;
        ctx.shadowBlur = 6;
        ctx.beginPath();
        ctx.arc(p.x, p.y, p.radius * p.life, 0, Math.PI * 2);
        ctx.fill();
        ctx.restore();
      }
    }

    // Main animation loop
    function loop(time) {
      requestAnimationFrame(loop);

      if (gameState !== STATE_PAUSED) {
        const elapsed = time - lastTickTime;
        if (elapsed >= tickDuration) {
          tick();
          lastTickTime = time;
        }
      }

      // Smooth progress between ticks [0, 1]
      let progress = (time - lastTickTime) / tickDuration;
      if (gameState === STATE_PAUSED || gameState === STATE_GAMEOVER) progress = 1;
      progress = Math.min(1, Math.max(0, progress));

      // Draw Scene
      drawGrid();
      drawFood(time);
      drawSnake(progress);
      updateAndDrawParticles();
    }

    // Start in demo mode immediately
    resetGame(true);
    requestAnimationFrame(loop);
  </script>
</body>
</html>
Reasoning trace (1,993 tokens)
**Structuring Game Elements**

I'm focused on setting up the foundational structure for the Snake game. My current work involves defining the grid dimensions, ensuring it's centered on the canvas, and implementing the initial controls using arrow keys.

**Implementing Input Logic**

I'm currently refining the input system, implementing WASD controls alongside arrow keys and handling rapid inputs to prevent immediate self-collision reversals. The core mechanics of food spawning, scoring, and growth are also being solidified.

**Developing Smooth Motion**

I'm now focused on achieving smooth movement for the snake. My current exploration involves interpolating the positions of the head and tail segments between discrete grid movements. I'm also considering how to manage the input queue for more responsive controls.

**Achieving Fluid Movement**

I'm experimenting with sub-tick interpolation to make the snake's movement look fluid. This involves rendering segments between their previous and current grid positions, and I'm also planning an AI auto-play feature using a simple BFS for demonstration.

**Optimizing Pathfinding**

I'm exploring how to handle tricky pathing scenarios where traps exist. My current strategy is to select the neighbor with the highest flood-fill count when a path is blocked or leads to a trap, which is proving very effective and visually appealing for demonstrations.

**Designing Aesthetics**

I'm now focused on the visual presentation. I'm aiming for a dark, modern arcade cyberpunk aesthetic with neon elements for the snake, food, and UI. I'm also planning a particle system for when food is eaten and various overlay screens like start, game over, and pause.

**Enhancing Movement Control**

I've been refining the input queue and the logic that prevents immediate reversals. My current work is on defining specific direction constants (UP: 0, -1, etc.) to ensure smooth and predictable movement responses.

**Managing Input Flow**

I'm refining how inputs are handled, capping the queue at two to prevent excessive queuing. Movement logic now uses specific direction constants and checks against the last queued input to avoid immediate reversals, ensuring smoother transitions.

**Refining Visuals and AI**

I'm now focusing on visually enhancing the snake with interpolated segment centers, rounded heads with eyes, and a tapering tail for a premium look. Simultaneously, I'm implementing a lightweight AI using BFS for an indefinite demo mode, prioritizing escape routes.