Models 44
Train snakeOne-line

Claude Haiku 5.5

by AnthropicReasoningClean
Open in new window

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

How it went

Total time
24.3 s
Cost
$0.0036
Output tokens
7,132
Spent thinking
0 tokens
Speed
302.3 tok/s
Console errors
0
First token after
712 ms
First visible text after
712 ms
Prompt tokens
547
Code size
14.0 KB
Finish reason
stop
Served by
Claude Platform on AWS
Model id
anthropic/claude-haiku-5.5
Reasoning sent
{"effort":"none"}
Max output
32,000 tokens
Generated
Nov 10, 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 · 439 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Snake Train</title>
<style>
  * { box-sizing: border-box; margin: 0; padding: 0; }
  html, body {
    width: 100%; height: 100%;
    background: #1b2330;
    font-family: "Trebuchet MS", "Segoe UI", Arial, sans-serif;
    color: #f5f1e6;
    overflow: hidden;
  }
  body { display: flex; align-items: center; justify-content: center; }
  .wrap {
    display: flex; flex-direction: column; align-items: center; gap: 10px;
    width: 100%; max-width: 1280px; padding: 12px;
  }
  header {
    display: flex; justify-content: space-between; align-items: center;
    width: 100%; max-width: 640px; font-size: 18px; font-weight: bold;
    flex-wrap: wrap; gap: 6px;
  }
  header .title { color: #ffd166; font-size: 22px; letter-spacing: 1px; }
  header .stats span { margin-left: 14px; }
  #board-holder { position: relative; width: 100%; max-width: 640px; }
  canvas {
    display: block; width: 100%; height: auto;
    border-radius: 14px; border: 6px solid #3a4a5e;
    background: #2e7d4f;
    box-shadow: 0 10px 30px rgba(0,0,0,0.45);
    cursor: pointer;
  }
  #overlay {
    position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
    pointer-events: none;
  }
  #overlay .msg {
    background: rgba(20,25,35,0.82); padding: 14px 22px; border-radius: 12px;
    font-size: 20px; text-align: center; border: 2px solid #ffd166;
  }
  .hint { font-size: 13px; opacity: 0.75; text-align: center; max-width: 640px; }
  @media (max-width: 520px) {
    header { font-size: 15px; }
    header .title { font-size: 18px; }
    .hint { font-size: 11px; }
    #overlay .msg { font-size: 15px; padding: 10px 14px; }
    canvas { border-width: 4px; }
  }
</style>
</head>
<body>
<div class="wrap">
  <header>
    <div class="title">🚂 Snake Train</div>
    <div class="stats">
      <span>Passengers: <b id="score">0</b></span>
      <span>Best: <b id="best">0</b></span>
    </div>
  </header>
  <div id="board-holder">
    <canvas id="game" width="640" height="640"></canvas>
    <div id="overlay"><div class="msg" id="msg">Click or press a key to start</div></div>
  </div>
  <div class="hint">Arrow keys / WASD to steer · Pick up waiting passengers to grow your train · Don't crash!</div>
</div>

<script>
(function () {
  const canvas = document.getElementById('game');
  const ctx = canvas.getContext('2d');
  const scoreEl = document.getElementById('score');
  const bestEl = document.getElementById('best');
  const msgEl = document.getElementById('msg');
  const overlay = document.getElementById('overlay');

  const N = 16;               // grid cells per side
  const CELL = canvas.width / N;
  const TICK = 130;           // ms per move

  const WAGON_COLORS = ['#e63946', '#f4a261', '#2a9d8f', '#8e5ea2', '#ffd166', '#1d8bd6', '#ff6f91', '#86c440'];
  const PASSENGER_COLORS = ['#ffadad', '#ffd6a5', '#caffbf', '#9bf6ff', '#bdb2ff', '#fdffb6'];

  let snake, dir, nextDir, passengers, score, best = 0;
  let state = 'demo'; // 'demo' | 'playing' | 'over'
  let lastTick = 0, deadTimer = 0, particles = [], smoke = [];
  let demoPath = [];

  const dirs = { up: [0,-1], down: [0,1], left: [-1,0], right: [1,0] };

  function reset() {
    const cx = Math.floor(N / 2), cy = Math.floor(N / 2);
    snake = [{x: cx, y: cy}, {x: cx-1, y: cy}, {x: cx-2, y: cy}];
    dir = dirs.right; nextDir = dir;
    score = 0; scoreEl.textContent = score;
    passengers = [];
    for (let i = 0; i < 5; i++) spawnPassenger();
    particles = []; smoke = [];
  }

  function occupied(x, y) {
    return snake.some(s => s.x === x && s.y === y) || passengers.some(p => p.x === x && p.y === y);
  }

  function spawnPassenger() {
    // passengers wait on platforms: pick a free spot
    let tries = 0;
    while (tries++ < 200) {
      const x = Math.floor(Math.random() * N), y = Math.floor(Math.random() * N);
      if (!occupied(x, y)) {
        passengers.push({
          x, y,
          color: PASSENGER_COLORS[Math.floor(Math.random() * PASSENGER_COLORS.length)],
          wave: Math.random() * Math.PI * 2
        });
        return;
      }
    }
  }

  function startGame() {
    reset();
    state = 'playing';
    msgEl.textContent = '';
    overlay.style.display = 'none';
  }

  function gameOver() {
    state = 'over';
    deadTimer = 0;
    best = Math.max(best, score);
    bestEl.textContent = best;
    for (let i = 0; i < 30; i++) {
      particles.push({
        x: snake[0].x * CELL + CELL/2, y: snake[0].y * CELL + CELL/2,
        vx: (Math.random()-0.5)*6, vy: (Math.random()-0.5)*6,
        life: 1, color: Math.random() < 0.5 ? '#ff9f1c' : '#666'
      });
    }
    setTimeout(() => {
      if (state === 'over') {
        overlay.style.display = 'flex';
        msgEl.textContent = 'Derailed! Click or press a key to restart';
      }
    }, 1200);
  }

  // Demo AI: move toward nearest passenger, avoiding collisions when possible
  function aiChoose() {
    const head = snake[0];
    let target = passengers.length ? passengers[0] : null;
    if (target) {
      let best = Infinity;
      passengers.forEach(p => {
        const d = Math.abs(p.x - head.x) + Math.abs(p.y - head.y);
        if (d < best) { best = d; target = p; }
      });
    }
    const options = [];
    for (const k in dirs) {
      const d = dirs[k];
      if (d[0] === -dir[0] && d[1] === -dir[1]) continue;
      const nx = head.x + d[0], ny = head.y + d[1];
      if (nx < 0 || ny < 0 || nx >= N || ny >= N) continue;
      if (snake.some(s => s.x === nx && s.y === ny)) continue;
      const dist = target ? Math.abs(target.x - nx) + Math.abs(target.y - ny) : 0;
      options.push({ d, dist, rnd: Math.random() });
    }
    if (!options.length) return;
    options.sort((a, b) => a.dist - b.dist || a.rnd - b.rnd);
    nextDir = options[0].d;
  }

  function step() {
    dir = nextDir;
    const head = snake[0];
    const nh = { x: head.x + dir[0], y: head.y + dir[1] };
    if (nh.x < 0 || nh.y < 0 || nh.x >= N || nh.y >= N ||
        snake.some((s, i) => i < snake.length - 1 && s.x === nh.x && s.y === nh.y)) {
      if (state === 'demo') { reset(); return; }
      gameOver();
      return;
    }
    snake.unshift(nh);
    const idx = passengers.findIndex(p => p.x === nh.x && p.y === nh.y);
    if (idx >= 0) {
      const p = passengers[idx];
      passengers.splice(idx, 1);
      score++;
      scoreEl.textContent = score;
      snake[snake.length - 1].newWagon = true;
      // mark the newly added wagon by keeping tail (growth)
      spawnPassenger();
      for (let i = 0; i < 12; i++) {
        particles.push({
          x: p.x * CELL + CELL/2, y: p.y * CELL + CELL/2,
          vx: (Math.random()-0.5)*4, vy: (Math.random()-0.5)*4,
          life: 1, color: p.color
        });
      }
    } else {
      snake.pop();
    }
    if (state === 'playing' || state === 'demo') {
      if (passengers.length < 4 && Math.random() < 0.3) spawnPassenger();
    }
    if (Math.random() < 0.25) smoke.push({x: snake[0].x * CELL + CELL/2, y: snake[0].y * CELL + CELL*0.2, r: 4, life: 1});
  }

  function drawBackground(t) {
    // grass with subtle texture
    ctx.fillStyle = '#2e7d4f';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    for (let i = 0; i < N; i++) for (let j = 0; j < N; j++) {
      if ((i + j) % 2 === 0) {
        ctx.fillStyle = 'rgba(255,255,255,0.03)';
        ctx.fillRect(i*CELL, j*CELL, CELL, CELL);
      }
    }
    // tracks: horizontal rails across middle row decoration
    ctx.strokeStyle = '#8a7a66';
    ctx.lineWidth = 2;
    for (let y = 0; y < N; y += 5) {
      ctx.beginPath();
      ctx.moveTo(0, y*CELL + CELL*0.5);
      ctx.lineTo(canvas.width, y*CELL + CELL*0.5);
      ctx.stroke();
    }
  }

  function drawPassengers(t) {
    passengers.forEach(p => {
      const cx = p.x * CELL + CELL/2, cy = p.y * CELL + CELL/2;
      const bob = Math.sin(t/300 + p.wave) * 2;
      // platform
      ctx.fillStyle = '#c9b89a';
      ctx.fillRect(p.x*CELL + 3, p.y*CELL + CELL*0.7, CELL - 6, CELL*0.25);
      // person
      ctx.fillStyle = p.color;
      ctx.beginPath();
      ctx.arc(cx, cy - 6 + bob, CELL*0.22, 0, Math.PI*2);
      ctx.fill();
      ctx.fillStyle = '#333';
      ctx.fillRect(cx - 4, cy + 4 + bob, 8, 10);
      // waving arm
      ctx.strokeStyle = '#333';
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.moveTo(cx + 5, cy + 4 + bob);
      ctx.lineTo(cx + 12, cy - 6 + bob - Math.sin(t/120 + p.wave)*4);
      ctx.stroke();
      // sign
      ctx.fillStyle = '#fff';
      ctx.font = `${Math.floor(CELL*0.2)}px sans-serif`;
      ctx.textAlign = 'center';
      ctx.fillText('?', cx, cy - CELL*0.36 + bob);
    });
  }

  function drawTrain(t) {
    // draw wagons from tail to head so the locomotive sits on top
    for (let i = snake.length - 1; i >= 1; i--) {
      const s = snake[i];
      const x = s.x*CELL + 3, y = s.y*CELL + 3, w = CELL - 6, h = CELL - 6;
      const color = WAGON_COLORS[i % WAGON_COLORS.length];
      ctx.fillStyle = color;
      roundRect(x, y, w, h, 6);
      ctx.fill();
      ctx.strokeStyle = 'rgba(0,0,0,0.35)';
      ctx.lineWidth = 2;
      ctx.stroke();
      // windows
      ctx.fillStyle = 'rgba(200,235,255,0.85)';
      ctx.fillRect(x + w*0.2, y + h*0.25, w*0.3, h*0.25);
      ctx.fillRect(x + w*0.55, y + h*0.25, w*0.25, h*0.25);
      // wheels
      ctx.fillStyle = '#222';
      ctx.beginPath(); ctx.arc(x + w*0.25, y + h*0.85, 3, 0, Math.PI*2); ctx.fill();
      ctx.beginPath(); ctx.arc(x + w*0.75, y + h*0.85, 3, 0, Math.PI*2); ctx.fill();
      // coupling between wagons
      const prev = snake[i-1];
      if (prev) {
        ctx.fillStyle = '#444';
        const cx = (s.x + prev.x)/2*CELL + CELL/2;
        const cy = (s.y + prev.y)/2*CELL + CELL/2;
        ctx.fillRect(cx - CELL*0.12, cy - CELL*0.12, CELL*0.24, CELL*0.24);
      }
    }
    // locomotive
    const head = snake[0];
    const hx = head.x*CELL, hy = head.y*CELL;
    const angle = Math.atan2(dir[1], dir[0]);
    ctx.save();
    ctx.translate(hx + CELL/2, hy + CELL/2);
    ctx.rotate(angle);
    // body
    ctx.fillStyle = state === 'over' ? '#7a7a7a' : '#1d3557';
    roundRect(-CELL*0.45, -CELL*0.38, CELL*0.9, CELL*0.76, 7);
    ctx.fill();
    // cab
    ctx.fillStyle = '#e63946';
    ctx.fillRect(-CELL*0.45, -CELL*0.38, CELL*0.38, CELL*0.76);
    // cab window
    ctx.fillStyle = 'rgba(200,235,255,0.9)';
    ctx.fillRect(-CELL*0.38, -CELL*0.22, CELL*0.22, CELL*0.18);
    // smokestack (back)
    ctx.fillStyle = '#222';
    ctx.fillRect(CELL*0.05, -CELL*0.52, CELL*0.12, CELL*0.2);
    // nose / boiler front
    ctx.fillStyle = '#ffd166';
    ctx.beginPath();
    ctx.arc(CELL*0.45, 0, CELL*0.1, 0, Math.PI*2);
    ctx.fill();
    // headlight glow
    const glow = ctx.createRadialGradient(CELL*0.5, 0, 2, CELL*0.5, 0, CELL*0.9);
    glow.addColorStop(0, 'rgba(255,240,150,0.5)');
    glow.addColorStop(1, 'rgba(255,240,150,0)');
    ctx.fillStyle = glow;
    ctx.beginPath(); ctx.arc(CELL*0.5, 0, CELL*0.9, 0, Math.PI*2); ctx.fill();
    // wheels
    ctx.fillStyle = '#111';
    [-0.3, 0.1, 0.3].forEach(xx => {
      ctx.beginPath(); ctx.arc(CELL*xx, CELL*0.4, 3.5, 0, Math.PI*2); ctx.fill();
    });
    ctx.restore();
  }

  function roundRect(x, y, w, h, r) {
    ctx.beginPath();
    ctx.moveTo(x + r, y);
    ctx.arcTo(x + w, y, x + w, y + h, r);
    ctx.arcTo(x + w, y + h, x, y + h, r);
    ctx.arcTo(x, y + h, x, y, r);
    ctx.arcTo(x, y, x + w, y, r);
    ctx.closePath();
  }

  function drawEffects() {
    smoke.forEach(s => {
      ctx.fillStyle = `rgba(230,230,230,${s.life*0.5})`;
      ctx.beginPath(); ctx.arc(s.x, s.y, s.r, 0, Math.PI*2); ctx.fill();
      s.y -= 0.6; s.x -= 0.3; s.r += 0.4; s.life -= 0.02;
    });
    smoke = smoke.filter(s => s.life > 0);
    particles.forEach(p => {
      ctx.globalAlpha = Math.max(p.life, 0);
      ctx.fillStyle = p.color;
      ctx.beginPath(); ctx.arc(p.x, p.y, 4, 0, Math.PI*2); ctx.fill();
      p.x += p.vx; p.y += p.vy; p.life -= 0.03;
    });
    ctx.globalAlpha = 1;
    particles = particles.filter(p => p.life > 0);
  }

  function render(t) {
    drawBackground(t);
    drawPassengers(t);
    drawTrain(t);
    drawEffects();
    if (state === 'demo') {
      ctx.fillStyle = 'rgba(255,209,102,0.9)';
      ctx.font = 'bold 18px Trebuchet MS, Arial';
      ctx.textAlign = 'center';
      ctx.fillText('DEMO', canvas.width/2, 28);
    }
  }

  function loop(t) {
    if (!lastTick) lastTick = t;
    if (t - lastTick > TICK) {
      lastTick = t;
      if (state === 'demo' || state === 'playing') {
        if (state === 'demo') aiChoose();
        step();
      }
    }
    render(t);
    requestAnimationFrame(loop);
  }

  function setDirection(name) {
    if (state === 'over') { startGame(); return; }
    if (state === 'demo') { startGame(); }
    const d = dirs[name];
    if (d[0] === -dir[0] && d[1] === -dir[1]) return;
    nextDir = d;
  }

  const keyMap = {
    ArrowUp: 'up', w: 'up', W: 'up',
    ArrowDown: 'down', s: 'down', S: 'down',
    ArrowLeft: 'left', a: 'left', A: 'left',
    ArrowRight: 'right', d: 'right', D: 'right'
  };

  document.addEventListener('keydown', e => {
    if (keyMap[e.key]) {
      e.preventDefault();
      setDirection(keyMap[e.key]);
    } else if (state !== 'playing') {
      startGame();
    }
  });

  canvas.addEventListener('click', () => {
    if (state !== 'playing') startGame();
  });

  // touch swipe support
  let touchStart = null;
  canvas.addEventListener('touchstart', e => {
    const t = e.touches[0];
    touchStart = {x: t.clientX, y: t.clientY};
  }, {passive: true});
  canvas.addEventListener('touchend', e => {
    if (!touchStart) return;
    const t = e.changedTouches[0];
    const dx = t.clientX - touchStart.x, dy = t.clientY - touchStart.y;
    if (Math.abs(dx) < 15 && Math.abs(dy) < 15) {
      if (state !== 'playing') startGame();
    } else if (Math.abs(dx) > Math.abs(dy)) {
      setDirection(dx > 0 ? 'right' : 'left');
    } else {
      setDirection(dy > 0 ? 'down' : 'up');
    }
    touchStart = null;
  }, {passive: true});

  // initial demo state
  reset();
  state = 'demo';
  bestEl.textContent = best;
  requestAnimationFrame(loop);
})();
</script>
</body>
</html>