Models 44
Train snakeOne-line

Xing4.0 29B A4B (free)

by XingChenReasoningClean
Open in new window

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

How it went

Total time
3 min 6 s
Cost
$0
Output tokens
12,974
Spent thinking
0 tokens
Speed
70.1 tok/s
Console errors
0
First token after
1.1 s
First visible text after
1.1 s
Prompt tokens
377
Code size
16.0 KB
Finish reason
stop
Served by
Phala
Model id
xingchen-agi/xing4.0-29b-a4b:free
Reasoning sent
{"effort":"none"}
Max output
32,000 tokens
Generated
Nov 9, 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 · 576 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 - Pick up Passengers!</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body {
    background: #1a1a2e;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
    color: #fff;
    overflow: hidden;
    user-select: none;
  }
  #wrap {
    position: relative;
    width: 100%;
    max-width: 700px;
    padding: 15px;
  }
  #title {
    text-align: center;
    font-size: 26px;
    font-weight: bold;
    margin-bottom: 10px;
    color: #f0c040;
    text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
    letter-spacing: 2px;
  }
  #hud {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 10px;
    flex-wrap: wrap;
    gap: 8px;
  }
  .stat {
    background: #16213e;
    border: 2px solid #e94560;
    border-radius: 8px;
    padding: 6px 14px;
    font-size: 14px;
    font-weight: bold;
  }
  .stat span { color: #ff6b6b; }
  #scoreStat span { color: #f0c040; }
  #levelStat span { color: #4ecdc4; }
  #passStat span { color: #95e1d3; }
  canvas {
    display: block;
    border: 3px solid #e94560;
    border-radius: 12px;
    background: #0f3460;
    box-shadow: 0 0 30px rgba(233, 69, 96, 0.3);
  }
  #overlay {
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    background: rgba(15,52,96,0.92);
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    border-radius: 12px;
    z-index: 10;
    backdrop-filter: blur(2px);
  }
  #overlay h2 {
    font-size: 32px;
    color: #f0c040;
    margin-bottom: 15px;
    text-shadow: 2px 2px 6px rgba(0,0,0,0.6);
  }
  #overlay p {
    font-size: 15px;
    color: #ccc;
    margin-bottom: 25px;
    text-align: center;
    max-width: 360px;
    line-height: 1.6;
  }
  #startHint {
    font-size: 18px;
    color: #4ecdc4;
    font-weight: bold;
    animation: blink 1.2s infinite;
  }
  @keyframes blink {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.4; }
  }
  kbd {
    background: #16213e;
    border: 2px solid #e94560;
    border-radius: 5px;
    padding: 2px 10px;
    font-size: 14px;
    font-family: monospace;
  }
  .controls-info {
    text-align: center;
    margin-top: 12px;
    font-size: 12px;
    color: #888;
  }
</style>
</head>
<body>

<div id="wrap">
  <div id="title">🚂 Snake Train — Pick up Passengers!</div>
  <div id="hud">
    <div class="stat" id="scoreStat">💰 Score: <span id="scoreVal">0</span></div>
    <div class="stat" id="passStat">👥 Passengers: <span id="passVal">0</span></div>
    <div class="stat" id="levelStat">⚡ Level: <span id="levelVal">1</span></div>
  </div>
  <canvas id="game" width="600" height="500"></canvas>
  <div id="overlay">
    <h2 id="ovTitle">🚂 Ready for Departure?</h2>
    <p id="ovDesc">Drive the little train pick up passengers on the platform. Collect all colored dots on the tracks to complete each level! Eat to grow. Avoid collisions. Watch out for other trains!</p>
    <div id="startHint">Click or press a key to start</div>
  </div>
  <div class="controls-info">
    Use <b>Arrow keys</b> or <b>WASD</b> to drive &nbsp;·&nbsp; Space = Menu
  </div>
</div>

<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const W = canvas.width;
const H = canvas.height;
const GRID = 20;
const COLS = Math.floor(W / GRID);
const ROWS = Math.floor(H / GRID);

const keyMap = {
  ArrowUp: [0,-1], KeyW: [0,-1],
  ArrowDown: [0,1], KeyS: [0,1],
  ArrowLeft: [-1,0], KeyA: [-1,0],
  ArrowRight: [1,0], KeyD: [1,0]
};

let snake, dir, nextDir, food, passengers, enemies, score, level, passCount, running, gameOver, tickAcc, speed, subTickAcc;

function initLevel() {
  const n = level + 1;
  snake = [];
  let sx = Math.floor(COLS/2), sy = Math.floor(ROWS/2);
  for (let i = 0; i < 4; i++) {
    snake.push({x: sx-i, y: sy});
  }
  dir = [1, 0];
  nextDir = [1, 0];
  speed = 150 - (level - 1) * 12;
  if (speed < 60) speed = 60;
  subTickAcc = 0;
  tickAcc = speed;
  score = 0;
  passCount = 0;
  spawnFood();
  spawnPassengers();
  spawnEnemies();
}

function spawnFood() {
  food = null;
  while (!food) {
    food = {x: randInt(COLS), y: randInt(ROWS)};
  }
}

function spawnPassengers() {
  passengers = [];
  for (let i = 0; i < 3 + Math.floor(level / 2); i++) {
    let p = null;
    do {
      p = {x: randInt(COLS), y: randInt(ROWS), color: pick(['#ff6b6b','#ffd93d','#6bcf7f','#4ecdc4','#a78bfa'])};
    } while (collidesWithSnake(p));
    passengers.push(p);
  }
}

function spawnEnemies() {
  enemies = [];
  const enemyCount = Math.min(level - 1, 4);
  for (let i = 0; i < enemyCount; i++) {
    let e = null;
    let tries = 0;
    do {
      e = {x: randInt(COLS), y: randInt(ROWS), dir: pick([[1,0],[0,1]],2)[0], timer: randInt(20,40), spd: randInt(60,100)};
      tries++;
      if (tries > 200) break;
    } while (true);
    enemies.push(e);
  }
}

function pick(arr, n) { return arr[Math.floor(Math.random() * arr.length)]; }

function randInt(n) { return Math.floor(Math.random() * n); }

function collidesWithSnake(obj) {
  return snake.some(s => s.x === obj.x && s.y === obj.y);
}

function overlaps(a, b) {
  return a.x === b.x && a.y === b.y;
}

function update(dt) {
  if (!running || gameOver) return;
  tickAcc -= dt;
  if (tickAcc <= 0) {
    subTickAcc += speed;
    doStep();
    tickAcc = speed;
  } else {
    subTickAcc -= dt;
  }
  enemyUpdate(dt);
  checkWin();
}

function doStep() {
  moveSnake();
  if (hitsWallOrSelf()) return;
  spawnFood();
}

function moveSnake() {
  dir = nextDir;
  const head = snake[0];
  head.x += dir[0];
  head.y += dir[1];
  // keep snake within bounds
  if (head.x < 0) head.x = COLS - 1;
  if (head.x >= COLS) head.x = 0;
  if (head.y < 0) head.y = ROWS - 1;
  if (head.y >= ROWS) head.y = 0;
  snake.unshift(head);
}

function hitsWallOrSelf() {
  const h = snake[0];
  if (h.x < 0 || h.x >= COLS || h.y < 0 || h.y >= ROWS) return true;
  for (let i = 1; i < snake.length; i++) {
    if (h.x === snake[i].x && h.y === snake[i].y) return true;
  }
  return false;
}

function enemyUpdate(dt) {
  enemies.forEach(e => {
    e.timer -= dt;
    if (e.timer <= 0) {
      const opts = [[1,0],[0,1],[-1,0],[0,-1]];
      const idx = opts.indexOf([e.dir[0], e.dir[1]]);
      if (idx < 0 || idx === opts.length - 1) {
        // reverse
        e.dir = [-e.dir[0], -e.dir[1]];
      } else {
        e.dir = opts[(idx + 1) % opts.length];
      }
      e.timer = randInt(e.spd, e.spd * 2);
      // avoid wall if needed
      if ((e.x + e.dir[0]) % COLS === 0 && e.dir[0] !== 0) { e.dir = [e.dir[0] > 0 ? -1 : 1, 0]; e.timer = e.spd; }
      else if ((e.y + e.dir[1]) % ROWS === 0 && e.dir[1] !== 0) { e.dir = [0, e.dir[1] > 0 ? -1 : 1]; e.timer = e.spd; }
    }
    // check collision with player head
    const h = snake[0];
    const eh = {x: e.x + e.dir[0], y: e.y + e.dir[1]};
    if (eh.x === h.x && eh.y === h.y) return; // enemy passes through player head
    for (let i = 0; i < snake.length; i++) {
      if (eh.x === snake[i].x && eh.y === snake[i].y) {
        gameOver = true;
        return;
      }
    }
  });
}

function checkWin() {
  if (passengers.length === 0 && food) return;
  if (passengers.length === 0 && !food) return;
  if (passengers.length === 0) return;
}

// pickup logic
setInterval(() => {}, 0); // placeholder removed below

let pickupTimer = null;
function checkPickup() {
  if (!running || gameOver) return;
  const h = snake[0];
  // eat food
  if (food && overlaps(h, food)) {
    snake.pop();
    score += 10;
    document.getElementById('scoreVal').textContent = score;
    clearInterval(pickupTimer);
    pickupTimer = null;
    continueIfComplete();
    return;
  }
  // pick passenger
  let consumed = false;
  passengers.forEach(p => {
    if (!consumed && overlaps(h, p)) {
      consumed = true;
      passengers.splice(passengers.indexOf(p), 1);
      passCount++;
      score += 5;
      document.getElementById('passVal').textContent = passCount;
    }
  });
  if (consumed) continueIfComplete();
}

function continueIfComplete() {
  if (passengers.length === 0) {
    level++;
    initLevel();
    document.getElementById('levelVal').textContent = level;
  }
  // if food is placed automatically? No — food stays. To win, collect all passengers.
  // We also drop a fresh food after collecting a passenger so there's always something to drive toward.
  if (food) {}
  // if we finished passengers and still have food, we may add new food for aesthetics
  if (passengers.length === 0 && food) {
    // eat remaining food to proceed to next level
    snake.pop();
    score += 10;
    document.getElementById('scoreVal').textContent = score;
    food = null;
    setTimeout(() => { if (running && !gameOver) spawnFood(); }, 500);
    setTimeout(() => { if (running && !gameOver) initLevel(); }, 1000);
  }
}

// gameplay tick loop for enemy movement and visual ticking
let last = performance.now();
function loop(now) {
  const dt = Math.min(now - last, 100);
  last = now;
  update(dt);
  // draw every frame
  draw();
  requestAnimationFrame(loop);
}

function draw() {
  ctx.fillStyle = '#0f3460';
  ctx.fillRect(0, 0, W, H);
  // grid lines subtle
  ctx.strokeStyle = 'rgba(255,255,255,0.04)';
  ctx.lineWidth = 1;
  for (let x = 0; x <= COLS; x++) { ctx.beginPath(); ctx.moveTo(x*GRID,0); ctx.lineTo(x*GRID, H); ctx.stroke(); }
  for (let y = 0; y <= ROWS; y++) { ctx.beginPath(); ctx.moveTo(0,y*GRID); ctx.lineTo(W,y*GRID); ctx.stroke(); }

  // track markings (dashed center lines)
  ctx.setLineDash([4, 6]);
  ctx.strokeStyle = 'rgba(255,217,61,0.12)';
  ctx.lineWidth = 2;
  ctx.beginPath(); ctx.moveTo(0, H/2 * GRID + GRID/2); ctx.lineTo(W, H/2 * GRID + GRID/2); ctx.stroke();
  ctx.beginPath(); ctx.moveTo(W/2 * GRID + GRID/2, 0); ctx.lineTo(W/2 * GRID + GRID/2, H); ctx.stroke();
  ctx.setLineDash([]);

  // draw enemies (trains)
  enemies.forEach(e => drawEnemy(e));

  // draw food (buffet car signal / fuel?)
  if (food) drawFood(food);

  // draw passengers (waiting on platform)
  passengers.forEach(p => drawPassenger(p));

  // draw snake train
  if (snake) drawTrain();

  if (gameOver) drawOverlay('Game Over!', 'Score: ' + score, 'Press R to restart or click to play again');
}

function drawTrain(train) {
  const head = snake[0];
  const tail = snake[snake.length - 1];
  const bodyLen = snake.length - 1;

  // gradient wagon bodies per segment
  for (let i = bodyLen; i >= 0; i--) {
    const seg = snake[i];
    const ratio = i / bodyLen; // 1 at tail, 0 at head
    const colors = ['#e94560','#c0392b','#a93226','#922b21','#7b241c'];
    const c = colors[Math.min(i, colors.length - 1)];
    roundRect(seg.x * GRID + 1, seg.y * GRID + 1, GRID - 2, GRID - 2, 3, c);
    // wagon bolts
    ctx.fillStyle = '#ffe066';
    ctx.beginPath(); ctx.arc(seg.x*GRID+GRID-4, seg.y*GRID+4, 1.5, 0, Math.PI*2); ctx.fill();
    ctx.beginPath(); ctx.arc(seg.x*GRID+GRID-4, seg.y*GRID+GRID-4, 1.5, 0, Math.PI*2); ctx.fill();
  }

  // locomotive (head)
  const lc = snake[0];
  ctx.save();
  ctx.translate(lc.x * GRID + GRID/2, lc.y * GRID + GRID/2);
  const angle = Math.atan2(dir[1], dir[0]);
  ctx.rotate(angle);
  // chimney
  ctx.fillStyle = '#666';
  roundRect(-7, -7, 5, 5, 1, '#555');
  ctx.fillStyle = '#888';
  ctx.beginPath(); ctx.arc(-4, -7, 2, 0, Math.PI*2); ctx.fill();
  // smoke
  ctx.fillStyle = 'rgba(200,200,200,0.4)';
  ctx.beginPath(); ctx.arc(-6, -9, 2 + Math.sin(Date.now()/300)*0.5, 0, Math.PI*2); ctx.fill();
  // headlight
  ctx.fillStyle = '#fff200';
  ctx.beginPath(); ctx.arc(8, 0, 3, 0, Math.PI*2); ctx.fill();
  // front window
  ctx.fillStyle = '#4ecdc4';
  roundRect(2, -5, 5, 5, 1, '#4ecdc4');
  ctx.restore();
}

function drawEnemy(e) {
  ctx.save();
  ctx.translate(e.x * GRID + GRID/2, e.y * GRID + GRID/2);
  ctx.rotate(Math.atan2(e.dir[1], e.dir[0]));
  // locomotive
  ctx.fillStyle = '#333';
  roundRect(-7, -6, 5, 12, 1, '#222');
  ctx.fillStyle = '#888';
  ctx.beginPath(); ctx.arc(-4, -6, 2, 0, Math.PI*2); ctx.fill();
  ctx.fillStyle = '#ffd93d';
  ctx.beginPath(); ctx.arc(8, 0, 2.5, 0, Math.PI*2); ctx.fill();
  // wagons
  ctx.fillStyle = '#e94560';
  roundRect(-10, -4, 3, 8, 1, '#e94560');
  roundRect(-13, -4, 3, 8, 1, '#e94560');
  ctx.restore();
}

function drawFood(f) {
  // a glowing circle representing an empty seat / food spot
  const pulse = 1 + Math.sin(Date.now()/300)*0.15;
  ctx.fillStyle = 'rgba(255, 220, 60, 0.3)';
  ctx.beginPath(); ctx.arc(f.x*GRID+GRID/2, f.y*GRID+GRID/2, (GRID/2)*pulse, 0, Math.PI*2); ctx.fill();
  ctx.fillStyle = '#ffd93d';
  ctx.beginPath(); ctx.arc(f.x*GRID+GRID/2, f.y*GRID+GRID/2, GRID/2-2, 0, Math.PI*2); ctx.fill();
  ctx.fillStyle = '#fff';
  ctx.font = 'bold 10px sans-serif';
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';
  ctx.fillText('★', f.x*GRID+GRID/2, f.y*GRID+GRID/2);
}

function drawPassenger(p) {
  const cx = p.x * GRID + GRID/2;
  const cy = p.y * GRID + GRID/2;
  // waiting-stand marker
  ctx.fillStyle = 'rgba(255,255,255,0.06)';
  ctx.fillRect(cx-8, cy-8, 16, 16);
  // person shape
  ctx.fillStyle = p.color;
  ctx.beginPath(); ctx.arc(cx, cy-4, 4, 0, Math.PI*2); ctx.fill(); // head
  roundRect(cx-5, cy-1, 10, 10, 2, p.color); // body
  // little arms waving
  ctx.strokeStyle = p.color;
  ctx.lineWidth = 2;
  ctx.beginPath(); ctx.moveTo(cx+4, cy); ctx.lineTo(cx+7, cy-4); ctx.stroke();
  ctx.beginPath(); ctx.moveTo(cx-4, cy); ctx.lineTo(cx-7, cy-4); ctx.stroke();
  // glow when near
  const dx = cx - (snake[0].x*GRID+GRID/2);
  const dy = cy - (snake[0].y*GRID+GRID/2);
  if (dx*dx+dy*dy < 30*30) {
    ctx.strokeStyle = '#fff';
    ctx.lineWidth = 1.5;
    ctx.beginPath(); ctx.arc(cx, cy, 7, 0, Math.PI*2); ctx.stroke();
  }
}

function roundRect(x, y, w, h, r, fill) {
  ctx.beginPath();
  ctx.moveTo(x+r, y);
  ctx.lineTo(x+w-r, y);
  ctx.quadraticCurveTo(x+w, y, x+w, y+r);
  ctx.lineTo(x+w, y+h-r);
  ctx.quadraticCurveTo(x+w, y+h, x+w-r, y+h);
  ctx.lineTo(x+r, y+h);
  ctx.quadraticCurveTo(x, y+h, x, y+h-r);
  ctx.lineTo(x, y+r);
  ctx.quadraticCurveTo(x, y, x+r, y);
  ctx.closePath();
  ctx.fill();
}

function drawOverlay(title, desc, hint) {
  const ov = document.getElementById('overlay');
  ov.style.display = 'flex';
  document.getElementById('ovTitle').textContent = title;
  document.getElementById('ovDesc').innerHTML = desc;
  document.getElementById('startHint').innerHTML = hint;
}

document.addEventListener('keydown', e => {
  if (!running) {
    if (keyMap[e.code]) { e.preventDefault(); startGame(); }
    return;
  }
  if (e.code === 'Space') {
    e.preventDefault();
    running = false;
    gameOver = true;
    showOverlay('Menu', 'Press any arrow key / WASD to resume', 'Any key to resume');
    return;
  }
  const k = keyMap[e.code];
  if (k) {
    e.preventDefault();
    nextDir = k;
  }
  if (e.code === 'KeyR') {
    restart();
  }
});

canvas.addEventListener('click', () => {
  if (!running && !gameOver) startGame();
  if (gameOver) {
    startGame();
  }
});

function showOverlay(t, d, h) {
  const ov = document.getElementById('overlay');
  ov.style.display = 'flex';
  document.getElementById('ovTitle').textContent = t;
  document.getElementById('ovDesc').innerHTML = d;
  document.getElementById('startHint').innerHTML = h;
}

function startGame() {
  document.getElementById('overlay').style.display = 'none';
  initLevel();
  running = true;
  gameOver = false;
  document.getElementById('scoreVal').textContent = score;
  document.getElementById('passVal').textContent = passCount;
  document.getElementById('levelVal').textContent = level;
  // clear stale timers
  if (pickupTimer) clearInterval(pickupTimer);
}

function restart() {
  document.getElementById('overlay').style.display = 'none';
  initLevel();
  running = true;
  gameOver = false;
  score = 0;
  passCount = 0;
  level = 1;
  document.getElementById('scoreVal').textContent = 0;
  document.getElementById('passVal').textContent = 0;
  document.getElementById('levelVal').textContent = 1;
}

// start the loop immediately for rendering background etc.
requestAnimationFrame(loop);
// periodic pickup checks
setInterval(checkPickup, 50);