Models 44
Train snakeOne-line

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
36.7 s
Cost
$0.027
Output tokens
7,007
Spent thinking
0 tokens
Speed
197.3 tok/s
Console errors
0
First token after
1.2 s
First visible text after
1.2 s
Prompt tokens
367
Code size
17.2 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
“make me a snake game where the snake is a little train with a locomotive and colorful wagons, picking up passengers waiting on the platform”
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 · 703 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Charming Train Snake</title>
<style>
  * {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    user-select: none;
  }
  body {
    background: radial-gradient(circle at center, #2e4438 0%, #17261e 100%);
    color: #fff;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    overflow: hidden;
  }
  #game-container {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    box-shadow: 0 20px 50px rgba(0,0,0,0.6);
    border-radius: 16px;
    background: #3c5a48;
    padding: 12px;
    border: 4px solid #5a3d28;
  }
  header {
    width: 100%;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 6px 16px 12px 16px;
    font-weight: bold;
    font-size: 1.1rem;
    text-shadow: 1px 1px 3px rgba(0,0,0,0.5);
  }
  .stat-badge {
    background: rgba(0,0,0,0.3);
    padding: 6px 14px;
    border-radius: 20px;
    border: 1px solid rgba(255,255,255,0.15);
    display: flex;
    align-items: center;
    gap: 8px;
  }
  canvas {
    background-color: #72a34b;
    border-radius: 8px;
    box-shadow: inset 0 0 20px rgba(0,0,0,0.25);
    display: block;
    cursor: pointer;
  }
  #overlay {
    position: absolute;
    top: 60px;
    left: 12px;
    right: 12px;
    bottom: 12px;
    background: rgba(18, 30, 24, 0.75);
    backdrop-filter: blur(4px);
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    border-radius: 8px;
    pointer-events: none;
    transition: opacity 0.3s;
  }
  .banner {
    background: #e67e22;
    color: #fff;
    padding: 10px 24px;
    border-radius: 30px;
    font-size: 1.25rem;
    font-weight: 800;
    letter-spacing: 1px;
    box-shadow: 0 8px 16px rgba(0,0,0,0.3);
    text-transform: uppercase;
    animation: bounce 1.2s infinite alternate;
  }
  .sub-banner {
    margin-top: 10px;
    font-size: 0.95rem;
    color: #f1c40f;
    font-weight: 600;
  }
  @keyframes bounce {
    0% { transform: translateY(0) scale(1); }
    100% { transform: translateY(-8px) scale(1.03); }
  }
  .hidden {
    opacity: 0 !important;
  }
  #controls-hint {
    margin-top: 10px;
    font-size: 0.85rem;
    color: #a3c2a0;
    display: flex;
    gap: 15px;
  }
  .key {
    background: rgba(255,255,255,0.15);
    padding: 2px 6px;
    border-radius: 4px;
    border: 1px solid rgba(255,255,255,0.3);
  }
</style>
</head>
<body>

<div id="game-container">
  <header>
    <div class="stat-badge">🚂 Passengers: <span id="score-val" style="color:#f1c40f;">0</span></div>
    <div style="font-size:1.3rem; letter-spacing: 2px; color: #ffeb3b; text-shadow: 2px 2px #d35400;">VALLEY EXPRESS</div>
    <div class="stat-badge">⏱️ Status: <span id="status-val" style="color:#2ecc71;">AUTOPILOT</span></div>
  </header>
  
  <canvas id="gameCanvas" width="800" height="520"></canvas>

  <div id="overlay">
    <div class="banner" id="banner-text">AUTOPILOT DEMO</div>
    <div class="sub-banner">CLICK CANVAS OR PRESS ANY ARROW KEY TO DRIVE</div>
  </div>

  <div id="controls-hint">
    <span>Steer: <span class="key">▲</span> <span class="key">▼</span> <span class="key">◀</span> <span class="key">▶</span> or <span class="key">W</span><span class="key">A</span><span class="key">S</span><span class="key">D</span></span>
    <span>•</span>
    <span>Pick up waiting travelers at stations!</span>
  </div>
</div>

<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const scoreEl = document.getElementById('score-val');
const statusEl = document.getElementById('status-val');
const overlayEl = document.getElementById('overlay');
const bannerText = document.getElementById('banner-text');

const GRID_SIZE = 40;
const COLS = canvas.width / GRID_SIZE; // 20
const ROWS = canvas.height / GRID_SIZE; // 13

// Wagon colors cycle
const WAGON_COLORS = ['#e74c3c', '#3498db', '#f1c40f', '#9b59b6', '#e67e22', '#1abc9c', '#e84393'];

// State
let train = [];
let dir = { x: 1, y: 0 };
let nextDir = { x: 1, y: 0 };
let passenger = null;
let station = null;
let trees = [];
let particles = [];
let score = 0;
let isDemo = true;
let gameOver = false;
let lastTick = 0;
const TICK_INTERVAL = 145; // ms per grid move

// Generate static decorative scenery (trees, bushes)
function generateScenery() {
  trees = [];
  for (let i = 0; i < 22; i++) {
    trees.push({
      x: Math.random() * (canvas.width - 40) + 20,
      y: Math.random() * (canvas.height - 40) + 20,
      size: 10 + Math.random() * 8,
      type: Math.random() > 0.4 ? 'pine' : 'oak',
      shade: Math.random() > 0.5 ? '#24592b' : '#1e4823'
    });
  }
}

function spawnPassenger() {
  let valid = false;
  let pX, pY;
  while (!valid) {
    pX = Math.floor(Math.random() * (COLS - 2)) + 1;
    pY = Math.floor(Math.random() * (ROWS - 2)) + 1;
    valid = !train.some(seg => seg.x === pX && seg.y === pY);
  }

  // Passenger character palette
  const shirtColors = ['#e74c3c', '#3498db', '#f39c12', '#9b59b6', '#1abc9c', '#d35400'];
  const hairColors = ['#f1c40f', '#8e44ad', '#2c3e50', '#d35400', '#7f8c8d'];
  
  passenger = {
    x: pX,
    y: pY,
    shirt: shirtColors[Math.floor(Math.random() * shirtColors.length)],
    hair: hairColors[Math.floor(Math.random() * hairColors.length)],
    bobble: 0
  };

  station = {
    x: pX,
    y: pY
  };
}

function resetGame() {
  train = [
    { x: 5, y: 6 },
    { x: 4, y: 6 },
    { x: 3, y: 6 }
  ];
  dir = { x: 1, y: 0 };
  nextDir = { x: 1, y: 0 };
  score = 0;
  scoreEl.textContent = score;
  gameOver = false;
  spawnPassenger();
}

// Basic Pathfinding / Autopilot for Demo Mode
function autoPilotMove() {
  const head = train[0];
  const target = passenger;
  
  const choices = [
    { x: 0, y: -1 }, // Up
    { x: 0, y: 1 },  // Down
    { x: -1, y: 0 }, // Left
    { x: 1, y: 0 }   // Right
  ];

  // Disallow 180 flip
  const validChoices = choices.filter(c => !(c.x === -dir.x && c.y === -dir.y));

  // Score candidate moves (safe from wall and self)
  let best = null;
  let minDistance = Infinity;

  for (const c of validChoices) {
    const nx = head.x + c.x;
    const ny = head.y + c.y;

    // Check bounds
    if (nx < 0 || nx >= COLS || ny < 0 || ny >= ROWS) continue;

    // Check self-collision (exclude tail segment since it will advance)
    let collision = false;
    for (let i = 0; i < train.length - 1; i++) {
      if (train[i].x === nx && train[i].y === ny) {
        collision = true;
        break;
      }
    }
    if (collision) continue;

    // Manhattan distance
    const dist = Math.abs(nx - target.x) + Math.abs(ny - target.y);
    if (dist < minDistance) {
      minDistance = dist;
      best = c;
    }
  }

  if (best) {
    nextDir = best;
  } else {
    // If trapped, pick any legal step
    for (const c of validChoices) {
      const nx = head.x + c.x;
      const ny = head.y + c.y;
      if (nx >= 0 && nx < COLS && ny >= 0 && ny < ROWS) {
        nextDir = c;
        break;
      }
    }
  }
}

function update() {
  if (gameOver) return;

  if (isDemo) {
    autoPilotMove();
  }

  dir = nextDir;
  const head = { x: train[0].x + dir.x, y: train[0].y + dir.y };

  // Wall collisions
  if (head.x < 0 || head.x >= COLS || head.y < 0 || head.y >= ROWS) {
    triggerGameOver();
    return;
  }

  // Self collisions
  for (let i = 0; i < train.length; i++) {
    if (train[i].x === head.x && train[i].y === head.y) {
      triggerGameOver();
      return;
    }
  }

  train.unshift(head);

  // Smoke puffed by locomotive
  createSmoke(head.x * GRID_SIZE + GRID_SIZE/2, head.y * GRID_SIZE + GRID_SIZE/2);

  // Passenger picked up
  if (head.x === passenger.x && head.y === passenger.y) {
    score++;
    scoreEl.textContent = score;
    createConfetti(passenger.x * GRID_SIZE + GRID_SIZE/2, passenger.y * GRID_SIZE + GRID_SIZE/2);
    spawnPassenger();
  } else {
    train.pop();
  }
}

function triggerGameOver() {
  gameOver = true;
  if (isDemo) {
    resetGame();
  } else {
    bannerText.textContent = "GAME OVER";
    overlayEl.querySelector('.sub-banner').textContent = "PRESS ANY ARROW OR CLICK TO RESTART";
    overlayEl.classList.remove('hidden');
    statusEl.textContent = "DERAILED";
    statusEl.style.color = "#e74c3c";
  }
}

function createSmoke(x, y) {
  for (let i = 0; i < 2; i++) {
    particles.push({
      type: 'smoke',
      x: x + (Math.random() - 0.5) * 8,
      y: y + (Math.random() - 0.5) * 8,
      vx: (Math.random() - 0.5) * 0.8 - dir.x * 1.2,
      vy: (Math.random() - 0.5) * 0.8 - dir.y * 1.2 - 0.5,
      size: 5 + Math.random() * 4,
      alpha: 0.7,
      growth: 0.15
    });
  }
}

function createConfetti(x, y) {
  const colors = ['#f1c40f', '#e74c3c', '#3498db', '#2ecc71', '#e67e22'];
  for (let i = 0; i < 20; i++) {
    const angle = Math.random() * Math.PI * 2;
    const speed = 1.5 + Math.random() * 3.5;
    particles.push({
      type: 'confetti',
      x: x,
      y: y,
      vx: Math.cos(angle) * speed,
      vy: Math.sin(angle) * speed,
      size: 4 + Math.random() * 3,
      color: colors[Math.floor(Math.random() * colors.length)],
      alpha: 1,
      life: 25 + Math.random() * 10
    });
  }
}

function updateParticles() {
  for (let i = particles.length - 1; i >= 0; i--) {
    const p = particles[i];
    p.x += p.vx;
    p.y += p.vy;
    if (p.type === 'smoke') {
      p.size += p.growth;
      p.alpha -= 0.025;
      if (p.alpha <= 0) particles.splice(i, 1);
    } else if (p.type === 'confetti') {
      p.vy += 0.08; // gravity
      p.alpha -= 0.03;
      if (p.alpha <= 0) particles.splice(i, 1);
    }
  }
}

// Draw Railway Track Tiles
function drawTracks() {
  ctx.strokeStyle = '#5a7a3b';
  ctx.lineWidth = 1;

  // Subtle grid tracks
  for (let x = 0; x < canvas.width; x += GRID_SIZE) {
    for (let y = 0; y < canvas.height; y += GRID_SIZE) {
      // Grass patch dots
      ctx.fillStyle = '#689643';
      ctx.fillRect(x + 19, y + 19, 2, 2);
    }
  }
}

// Draw Scenery (trees)
function drawScenery() {
  for (const tree of trees) {
    ctx.save();
    ctx.fillStyle = 'rgba(0,0,0,0.15)';
    ctx.beginPath();
    ctx.arc(tree.x + 3, tree.y + 4, tree.size, 0, Math.PI * 2);
    ctx.fill();

    ctx.fillStyle = tree.shade;
    ctx.beginPath();
    ctx.arc(tree.x, tree.y, tree.size, 0, Math.PI * 2);
    ctx.fill();

    // Leaf highlight
    ctx.fillStyle = '#488c3a';
    ctx.beginPath();
    ctx.arc(tree.x - tree.size * 0.3, tree.y - tree.size * 0.3, tree.size * 0.45, 0, Math.PI * 2);
    ctx.fill();
    ctx.restore();
  }
}

// Draw Platform & Passenger
function drawStationAndPassenger(time) {
  if (!passenger) return;
  const px = passenger.x * GRID_SIZE;
  const py = passenger.y * GRID_SIZE;

  // Wooden Station Platform
  ctx.save();
  ctx.fillStyle = 'rgba(0,0,0,0.2)';
  ctx.fillRect(px + 2, py + 2, GRID_SIZE - 2, GRID_SIZE - 2);

  ctx.fillStyle = '#c2a649';
  ctx.fillRect(px + 4, py + 4, GRID_SIZE - 8, GRID_SIZE - 8);

  // Platform planks
  ctx.strokeStyle = '#8d7328';
  ctx.lineWidth = 1.5;
  for (let i = 8; i < GRID_SIZE - 4; i += 7) {
    ctx.beginPath();
    ctx.moveTo(px + 4, py + i);
    ctx.lineTo(px + GRID_SIZE - 4, py + i);
    ctx.stroke();
  }

  // Station roof sign / Lantern post
  ctx.fillStyle = '#e67e22';
  ctx.fillRect(px + 5, py + 2, 8, 4);

  // Passenger Bobbing
  const bob = Math.sin(time * 0.008) * 2;
  const cx = px + GRID_SIZE / 2;
  const cy = py + GRID_SIZE / 2 + bob;

  // Luggage
  ctx.fillStyle = '#8e44ad';
  ctx.fillRect(cx + 6, cy + 2, 7, 6);
  ctx.fillStyle = '#f39c12';
  ctx.fillRect(cx + 8, cy + 1, 3, 2);

  // Shadow
  ctx.fillStyle = 'rgba(0,0,0,0.25)';
  ctx.beginPath();
  ctx.ellipse(cx, py + GRID_SIZE - 8, 6, 3, 0, 0, Math.PI*2);
  ctx.fill();

  // Torso / Shirt
  ctx.fillStyle = passenger.shirt;
  ctx.beginPath();
  ctx.arc(cx, cy + 2, 5, 0, Math.PI * 2);
  ctx.fill();

  // Head
  ctx.fillStyle = '#ffd1a4';
  ctx.beginPath();
  ctx.arc(cx, cy - 4, 4.5, 0, Math.PI * 2);
  ctx.fill();

  // Hair
  ctx.fillStyle = passenger.hair;
  ctx.beginPath();
  ctx.arc(cx, cy - 6, 4.2, Math.PI, Math.PI * 2);
  ctx.fill();

  // Waiting waving arm
  ctx.strokeStyle = '#ffd1a4';
  ctx.lineWidth = 2;
  ctx.beginPath();
  ctx.moveTo(cx - 4, cy);
  ctx.lineTo(cx - 7, cy - 5 + Math.sin(time * 0.015) * 3);
  ctx.stroke();

  ctx.restore();
}

// Draw Realistic Charming Train
function drawTrain() {
  for (let i = train.length - 1; i >= 0; i--) {
    const seg = train[i];
    const x = seg.x * GRID_SIZE;
    const y = seg.y * GRID_SIZE;
    const cx = x + GRID_SIZE / 2;
    const cy = y + GRID_SIZE / 2;

    // Draw rails underneath the train
    ctx.save();
    ctx.strokeStyle = '#4a3b32';
    ctx.lineWidth = 2;
    ctx.strokeRect(x + 4, y + 4, GRID_SIZE - 8, GRID_SIZE - 8);
    ctx.restore();

    if (i === 0) {
      // LOCOMOTIVE
      ctx.save();
      ctx.translate(cx, cy);

      // Angle based on dir
      let angle = 0;
      if (dir.x === 1) angle = 0;
      else if (dir.x === -1) angle = Math.PI;
      else if (dir.y === 1) angle = Math.PI / 2;
      else if (dir.y === -1) angle = -Math.PI / 2;
      ctx.rotate(angle);

      // Shadow
      ctx.fillStyle = 'rgba(0,0,0,0.3)';
      ctx.fillRect(-14, -10, 32, 24);

      // Train Body
      ctx.fillStyle = '#2c3e50'; // Dark iron engine
      ctx.beginPath();
      ctx.roundRect(-16, -12, 32, 24, [4, 8, 8, 4]);
      ctx.fill();

      // Boiler colored plate
      ctx.fillStyle = '#c0392b';
      ctx.fillRect(-6, -10, 18, 20);

      // Cab window
      ctx.fillStyle = '#81ecec';
      ctx.fillRect(-14, -8, 6, 16);

      // Golden Headlight
      ctx.fillStyle = '#f1c40f';
      ctx.beginPath();
      ctx.arc(16, 0, 4, -Math.PI / 2, Math.PI / 2);
      ctx.fill();

      // Funnel / Chimney
      ctx.fillStyle = '#111';
      ctx.beginPath();
      ctx.arc(6, 0, 4.5, 0, Math.PI * 2);
      ctx.fill();

      // Golden Rim
      ctx.strokeStyle = '#f39c12';
      ctx.lineWidth = 1.5;
      ctx.strokeRect(-6, -10, 18, 20);

      ctx.restore();
    } else {
      // WAGONS
      ctx.save();
      ctx.translate(cx, cy);

      // Orientation relative to predecessor segment
      const prev = train[i - 1];
      const dx = prev.x - seg.x;
      const dy = prev.y - seg.y;
      let angle = Math.atan2(dy, dx);
      ctx.rotate(angle);

      // Coupler link
      ctx.strokeStyle = '#2d3436';
      ctx.lineWidth = 3;
      ctx.beginPath();
      ctx.moveTo(12, 0);
      ctx.lineTo(20, 0);
      ctx.stroke();

      // Shadow
      ctx.fillStyle = 'rgba(0,0,0,0.25)';
      ctx.fillRect(-12, -9, 26, 22);

      // Wagon Body
      const wagonColor = WAGON_COLORS[(i - 1) % WAGON_COLORS.length];
      ctx.fillStyle = wagonColor;
      ctx.beginPath();
      ctx.roundRect(-13, -11, 26, 22, 4);
      ctx.fill();

      // Roof panel
      ctx.fillStyle = 'rgba(255,255,255,0.25)';
      ctx.fillRect(-8, -8, 16, 16);

      // Passenger silhouettes inside windows
      ctx.fillStyle = 'rgba(0, 0, 0, 0.4)';
      ctx.beginPath();
      ctx.arc(-2, -4, 2.5, 0, Math.PI * 2);
      ctx.arc(4, -4, 2.5, 0, Math.PI * 2);
      ctx.arc(-2, 4, 2.5, 0, Math.PI * 2);
      ctx.arc(4, 4, 2.5, 0, Math.PI * 2);
      ctx.fill();

      // Wheels
      ctx.fillStyle = '#1e272c';
      ctx.fillRect(-11, -12, 5, 2);
      ctx.fillRect(6, -12, 5, 2);
      ctx.fillRect(-11, 10, 5, 2);
      ctx.fillRect(6, 10, 5, 2);

      ctx.restore();
    }
  }
}

function drawParticles() {
  for (const p of particles) {
    ctx.save();
    if (p.type === 'smoke') {
      ctx.fillStyle = `rgba(240, 240, 240, ${p.alpha})`;
      ctx.beginPath();
      ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
      ctx.fill();
    } else if (p.type === 'confetti') {
      ctx.fillStyle = p.color;
      ctx.globalAlpha = p.alpha;
      ctx.fillRect(p.x, p.y, p.size, p.size);
    }
    ctx.restore();
  }
}

function gameLoop(time) {
  requestAnimationFrame(gameLoop);

  if (time - lastTick >= TICK_INTERVAL) {
    update();
    lastTick = time;
  }

  updateParticles();

  // CLEAR
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // RENDER LAYERS
  drawTracks();
  drawScenery();
  drawStationAndPassenger(time);
  drawTrain();
  drawParticles();
}

// User Interaction to take over controls
function takeControl() {
  if (isDemo || gameOver) {
    isDemo = false;
    statusEl.textContent = "PLAYER IN CAB";
    statusEl.style.color = "#f1c40f";
    overlayEl.classList.add('hidden');
    resetGame();
  }
}

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

  if (['ArrowUp', 'KeyW', 'ArrowDown', 'KeyS', 'ArrowLeft', 'KeyA', 'ArrowRight', 'KeyD', ' '].includes(k) || e.code.startsWith('Arrow')) {
    e.preventDefault();
  }

  if (isDemo || gameOver) {
    takeControl();
  }

  if (k === 'ArrowUp' || k === 'w' || k === 'W') {
    if (dir.y !== 1) nextDir = { x: 0, y: -1 };
  } else if (k === 'ArrowDown' || k === 's' || k === 'S') {
    if (dir.y !== -1) nextDir = { x: 0, y: 1 };
  } else if (k === 'ArrowLeft' || k === 'a' || k === 'A') {
    if (dir.x !== 1) nextDir = { x: -1, y: 0 };
  } else if (k === 'ArrowRight' || k === 'd' || k === 'D') {
    if (dir.x !== -1) nextDir = { x: 1, y: 0 };
  }
});

canvas.addEventListener('mousedown', () => {
  takeControl();
});

// START
generateScenery();
resetGame();
requestAnimationFrame(gameLoop);

</script>
</body>
</html>