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
35.0 s
Cost
$0.021
Output tokens
5,601
Spent thinking
0 tokens
Speed
167.7 tok/s
Console errors
0
First token after
1.6 s
First visible text after
1.6 s
Prompt tokens
507
Code size
14.4 KB
Finish reason
stop
Served by
Google
Model id
google/gemini-3.8-flash
Reasoning sent
{"effort":"low"}
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 · 583 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;
    user-select: none;
    margin: 0;
    padding: 0;
  }
  body {
    background: radial-gradient(circle at 50% 30%, #111827, #030712);
    color: #f3f4f6;
    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;
  }
  .header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    max-width: 520px;
    margin-bottom: 12px;
    padding: 0 8px;
  }
  .title {
    font-size: 24px;
    font-weight: 800;
    letter-spacing: 2px;
    text-transform: uppercase;
    background: linear-gradient(135deg, #10b981, #06b6d4);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    display: flex;
    align-items: center;
    gap: 8px;
  }
  .scoreboard {
    display: flex;
    gap: 16px;
    font-size: 14px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 1px;
  }
  .score-item {
    background: rgba(31, 41, 55, 0.7);
    border: 1px solid rgba(255, 255, 255, 0.08);
    padding: 6px 14px;
    border-radius: 8px;
    box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
  }
  .score-val {
    color: #10b981;
    font-size: 16px;
    margin-left: 4px;
  }
  .score-item.best .score-val {
    color: #f59e0b;
  }
  .canvas-wrapper {
    position: relative;
    border-radius: 16px;
    box-shadow: 0 20px 40px rgba(0, 0, 0, 0.6), 0 0 40px rgba(16, 185, 129, 0.1);
    border: 2px solid rgba(16, 185, 129, 0.25);
    background: #090d16;
  }
  canvas {
    display: block;
    border-radius: 14px;
  }
  .overlay {
    position: absolute;
    inset: 0;
    background: rgba(3, 7, 18, 0.85);
    backdrop-filter: blur(4px);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    border-radius: 14px;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease;
  }
  .overlay.active {
    opacity: 1;
    pointer-events: auto;
  }
  .banner-badge {
    background: rgba(16, 185, 129, 0.2);
    border: 1px solid #10b981;
    color: #34d399;
    padding: 4px 12px;
    border-radius: 999px;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 1.5px;
    margin-bottom: 12px;
    text-transform: uppercase;
    animation: pulse 2s infinite;
  }
  @keyframes pulse {
    0%, 100% { opacity: 0.8; transform: scale(1); }
    50% { opacity: 1; transform: scale(1.05); }
  }
  .overlay h2 {
    font-size: 32px;
    font-weight: 800;
    color: #f9fafb;
    margin-bottom: 8px;
    letter-spacing: 1px;
  }
  .overlay p {
    font-size: 14px;
    color: #9ca3af;
    margin-bottom: 20px;
    text-align: center;
    line-height: 1.5;
  }
  .controls-hint {
    margin-top: 14px;
    font-size: 12px;
    color: #6b7280;
    letter-spacing: 0.5px;
    text-align: center;
  }
  .keys {
    display: inline-block;
    background: rgba(255, 255, 255, 0.1);
    padding: 2px 6px;
    border-radius: 4px;
    color: #d1d5db;
    font-family: monospace;
  }
</style>
</head>
<body>

<div class="header">
  <div class="title">🐍 Viper</div>
  <div class="scoreboard">
    <div class="score-item">Score <span id="score" class="score-val">0</span></div>
    <div class="score-item best">Best <span id="best" class="score-val">0</span></div>
  </div>
</div>

<div class="canvas-wrapper">
  <canvas id="game" width="500" height="500"></canvas>
  <div id="overlay" class="overlay active">
    <div id="badge" class="banner-badge">Demo Mode</div>
    <h2 id="msg-title">AI is Playing</h2>
    <p id="msg-desc">Click or press any key to take control!<br>Arrow Keys or WASD to turn</p>
  </div>
</div>

<div class="controls-hint">
  <span class="keys">WASD</span> / <span class="keys">Arrows</span> to Move • <span class="keys">P</span> to Pause • <span class="keys">Space</span> to Restart
</div>

<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const scoreEl = document.getElementById('score');
const bestEl = document.getElementById('best');
const overlay = document.getElementById('overlay');
const badge = document.getElementById('badge');
const msgTitle = document.getElementById('msg-title');
const msgDesc = document.getElementById('msg-desc');

const GRID_SIZE = 20;
const CELL_SIZE = canvas.width / GRID_SIZE; // 25px

let snake = [];
let prevSnake = [];
let food = { x: 15, y: 10 };
let dir = { x: 1, y: 0 };
let nextDirs = [];
let isAlive = true;
let isPaused = false;
let isDemo = true;
let score = 0;
let bestScore = 0;

let lastStepTime = 0;
const STEP_INTERVAL = 115; // ms per grid tick
let particles = [];

function resetGame(startDemo = false) {
  isDemo = startDemo;
  snake = [
    { x: 5, y: 10 },
    { x: 4, y: 10 },
    { x: 3, y: 10 }
  ];
  prevSnake = snake.map(p => ({ ...p }));
  dir = { x: 1, y: 0 };
  nextDirs = [];
  score = 0;
  scoreEl.textContent = score;
  isAlive = true;
  isPaused = false;
  particles = [];
  spawnFood();
  updateOverlay();
}

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

function updateOverlay() {
  if (isDemo) {
    overlay.className = 'overlay active';
    badge.textContent = 'Auto Pilot';
    msgTitle.textContent = 'Viper Snake';
    msgDesc.innerHTML = 'Click or press any key to take control!<br>Steer with <span class="keys">WASD</span> or Arrows';
  } else if (!isAlive) {
    overlay.className = 'overlay active';
    badge.textContent = 'Game Over';
    msgTitle.textContent = `Score: ${score}`;
    msgDesc.innerHTML = 'Press <span class="keys">Space</span> or <span class="keys">Enter</span> to play again';
  } else if (isPaused) {
    overlay.className = 'overlay active';
    badge.textContent = 'Paused';
    msgTitle.textContent = 'Game Paused';
    msgDesc.innerHTML = 'Press <span class="keys">P</span> or click to resume';
  } else {
    overlay.className = 'overlay';
  }
}

function addParticle(x, y, color) {
  const count = 18;
  for (let i = 0; i < count; i++) {
    const angle = Math.random() * Math.PI * 2;
    const speed = Math.random() * 4 + 1.5;
    particles.push({
      x: (x + 0.5) * CELL_SIZE,
      y: (y + 0.5) * CELL_SIZE,
      vx: Math.cos(angle) * speed,
      vy: Math.sin(angle) * speed,
      life: 1.0,
      decay: Math.random() * 0.04 + 0.02,
      size: Math.random() * 4 + 2,
      color: color
    });
  }
}

// Simple BFS AI for self-playing demo mode
function runAI() {
  const head = snake[0];
  const occupied = new Set(snake.slice(0, -1).map(p => `${p.x},${p.y}`));
  const queue = [{ x: head.x, y: head.y, firstDir: null }];
  const visited = new Set([`${head.x},${head.y}`]);
  const validDirs = [
    { x: 0, y: -1 },
    { x: 1, y: 0 },
    { x: 0, y: 1 },
    { x: -1, y: 0 }
  ];

  let bestNextDir = null;

  while (queue.length > 0) {
    const curr = queue.shift();
    if (curr.x === food.x && curr.y === food.y) {
      bestNextDir = curr.firstDir;
      break;
    }
    for (const d of validDirs) {
      const nx = curr.x + d.x;
      const ny = curr.y + d.y;
      const key = `${nx},${ny}`;
      if (nx >= 0 && nx < GRID_SIZE && ny >= 0 && ny < GRID_SIZE && !occupied.has(key) && !visited.has(key)) {
        visited.add(key);
        queue.push({ x: nx, y: ny, firstDir: curr.firstDir || d });
      }
    }
  }

  // Fallback: pick any safe neighbor if food path not found
  if (!bestNextDir) {
    for (const d of validDirs) {
      if (dir.x + d.x === 0 && dir.y + d.y === 0) continue;
      const nx = head.x + d.x;
      const ny = head.y + d.y;
      if (nx >= 0 && nx < GRID_SIZE && ny >= 0 && ny < GRID_SIZE && !occupied.has(`${nx},${ny}`)) {
        bestNextDir = d;
        break;
      }
    }
  }

  if (bestNextDir) {
    nextDirs = [bestNextDir];
  }
}

function updateGameLogic() {
  if (isDemo) runAI();

  if (nextDirs.length > 0) {
    dir = nextDirs.shift();
  }

  prevSnake = snake.map(p => ({ ...p }));

  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) {
    gameOver();
    return;
  }

  // Self collision
  for (let i = 0; i < snake.length; i++) {
    if (newHead.x === snake[i].x && newHead.y === snake[i].y) {
      gameOver();
      return;
    }
  }

  snake.unshift(newHead);

  // Check food
  if (newHead.x === food.x && newHead.y === food.y) {
    score += 10;
    scoreEl.textContent = score;
    if (score > bestScore) {
      bestScore = score;
      bestEl.textContent = bestScore;
    }
    addParticle(food.x, food.y, '#34d399');
    spawnFood();
  } else {
    snake.pop();
  }
}

function gameOver() {
  isAlive = false;
  addParticle(snake[0].x, snake[0].y, '#ef4444');
  if (isDemo) {
    setTimeout(() => resetGame(true), 1200);
  } else {
    updateOverlay();
  }
}

function draw(interp) {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // Subtle grid
  ctx.strokeStyle = 'rgba(255, 255, 255, 0.025)';
  ctx.lineWidth = 1;
  for (let i = 0; i <= GRID_SIZE; i++) {
    ctx.beginPath();
    ctx.moveTo(i * CELL_SIZE, 0);
    ctx.lineTo(i * CELL_SIZE, canvas.height);
    ctx.stroke();
    ctx.beginPath();
    ctx.moveTo(0, i * CELL_SIZE);
    ctx.lineTo(canvas.width, i * CELL_SIZE);
    ctx.stroke();
  }

  // Draw Food (Pulsing orb)
  const pulse = Math.sin(Date.now() / 150) * 1.5;
  const fx = (food.x + 0.5) * CELL_SIZE;
  const fy = (food.y + 0.5) * CELL_SIZE;
  const fr = (CELL_SIZE / 2 - 3) + pulse;

  ctx.save();
  ctx.shadowColor = '#10b981';
  ctx.shadowBlur = 12;
  ctx.fillStyle = '#34d399';
  ctx.beginPath();
  ctx.arc(fx, fy, Math.max(2, fr), 0, Math.PI * 2);
  ctx.fill();

  ctx.fillStyle = '#ffffff';
  ctx.beginPath();
  ctx.arc(fx - fr * 0.3, fy - fr * 0.3, fr * 0.28, 0, Math.PI * 2);
  ctx.fill();
  ctx.restore();

  // Draw Particles
  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.fillStyle = p.color;
    ctx.globalAlpha = p.life;
    ctx.beginPath();
    ctx.arc(p.x, p.y, p.size * p.life, 0, Math.PI * 2);
    ctx.fill();
  }
  ctx.globalAlpha = 1.0;

  // Draw Snake with Smooth Interpolation
  const currentInterp = isAlive ? interp : 1.0;
  const renderPoints = [];

  for (let i = 0; i < snake.length; i++) {
    const curr = snake[i];
    const prev = prevSnake[i] || curr;
    renderPoints.push({
      x: (prev.x + (curr.x - prev.x) * currentInterp) * CELL_SIZE + CELL_SIZE / 2,
      y: (prev.y + (curr.y - prev.y) * currentInterp) * CELL_SIZE + CELL_SIZE / 2
    });
  }

  // Snake Body Segments
  for (let i = renderPoints.length - 1; i >= 0; i--) {
    const pt = renderPoints[i];
    const radius = (CELL_SIZE / 2) * (i === 0 ? 0.88 : 0.78);

    ctx.save();
    if (i === 0) {
      ctx.shadowColor = '#10b981';
      ctx.shadowBlur = 14;
      ctx.fillStyle = '#10b981';
    } else {
      const grad = ctx.createRadialGradient(pt.x, pt.y, 2, pt.x, pt.y, radius);
      grad.addColorStop(0, '#059669');
      grad.addColorStop(1, '#047857');
      ctx.fillStyle = grad;
    }

    ctx.beginPath();
    ctx.arc(pt.x, pt.y, radius, 0, Math.PI * 2);
    ctx.fill();
    ctx.restore();

    // Eyes on head
    if (i === 0) {
      const angle = Math.atan2(dir.y, dir.x);
      const eyeOffsetAngle = 0.65;
      const eyeDist = radius * 0.55;
      const eyeRadius = radius * 0.28;

      for (const sign of [-1, 1]) {
        const ex = pt.x + Math.cos(angle + sign * eyeOffsetAngle) * eyeDist;
        const ey = pt.y + Math.sin(angle + sign * eyeOffsetAngle) * eyeDist;

        ctx.fillStyle = '#ffffff';
        ctx.beginPath();
        ctx.arc(ex, ey, eyeRadius, 0, Math.PI * 2);
        ctx.fill();

        // Pupil
        ctx.fillStyle = '#111827';
        ctx.beginPath();
        ctx.arc(ex + Math.cos(angle) * (eyeRadius * 0.4), ey + Math.sin(angle) * (eyeRadius * 0.4), eyeRadius * 0.5, 0, Math.PI * 2);
        ctx.fill();
      }
    }
  }
}

function gameLoop(timestamp) {
  if (!lastStepTime) lastStepTime = timestamp;
  const elapsed = timestamp - lastStepTime;

  if (!isPaused && isAlive) {
    if (elapsed >= STEP_INTERVAL) {
      updateGameLogic();
      lastStepTime = timestamp;
    }
  }

  const interp = Math.min(1.0, elapsed / STEP_INTERVAL);
  draw(interp);
  requestAnimationFrame(gameLoop);
}

// Input Handling with Queuing
function handleDirection(newDir) {
  if (isDemo) {
    resetGame(false);
  }

  const lastQueued = nextDirs.length > 0 ? nextDirs[nextDirs.length - 1] : dir;
  // Prevent immediate 180 reversals
  if (newDir.x + lastQueued.x === 0 && newDir.y + lastQueued.y === 0) return;
  // Maximum 2 buffered inputs to maintain responsiveness
  if (nextDirs.length < 2) {
    nextDirs.push(newDir);
  }
}

window.addEventListener('keydown', (e) => {
  const key = e.key.toLowerCase();

  if (isDemo && ['arrowup','arrowdown','arrowleft','arrowright','w','a','s','d',' '].includes(key)) {
    resetGame(false);
    return;
  }

  if (key === 'p') {
    if (isAlive && !isDemo) {
      isPaused = !isPaused;
      updateOverlay();
    }
    return;
  }

  if (key === ' ' || key === 'enter') {
    if (!isAlive || isDemo) {
      resetGame(false);
    } else if (isPaused) {
      isPaused = false;
      updateOverlay();
    }
    return;
  }

  switch (key) {
    case 'arrowup':
    case 'w':
      handleDirection({ x: 0, y: -1 });
      e.preventDefault();
      break;
    case 'arrowdown':
    case 's':
      handleDirection({ x: 0, y: 1 });
      e.preventDefault();
      break;
    case 'arrowleft':
    case 'a':
      handleDirection({ x: -1, y: 0 });
      e.preventDefault();
      break;
    case 'arrowright':
    case 'd':
      handleDirection({ x: 1, y: 0 });
      e.preventDefault();
      break;
  }
});

overlay.addEventListener('click', () => {
  if (isDemo || !isAlive) {
    resetGame(false);
  } else if (isPaused) {
    isPaused = false;
    updateOverlay();
  }
});

// Initialize on page load in AI Demo Mode
resetGame(true);
requestAnimationFrame(gameLoop);
</script>

</body>
</html>