Models 44

GPT-6 Luna

by OpenAIReasoningClean
Open in new window

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

How it went

Total time
1 min 6 s
Cost
$0.0039
Output tokens
7,738
Spent thinking
1,341 tokens
Speed
124.7 tok/s
Console errors
0
First token after
3.7 s
First visible text after
18.1 s
Prompt tokens
493
Code size
19.2 KB
Finish reason
stop
Served by
OpenAI
Model id
openai/gpt-6-luna
Reasoning sent
{"effort":"medium"}
Max output
64,000 tokens
Generated
Nov 10, 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 · 519 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Snake — Arcade</title>
<style>
  :root {
    color-scheme: dark;
    --bg: #0b100f;
    --panel: #131a17;
    --line: #28342e;
    --muted: #87958c;
    --mint: #91f2bb;
    --green: #4bd58c;
  }
  * { box-sizing: border-box; }
  html, body { min-height: 100%; }
  body {
    margin: 0;
    padding: 32px 20px;
    background:
      radial-gradient(ellipse at 50% 24%, rgba(45, 100, 68, .16), transparent 42%),
      linear-gradient(145deg, #101613, var(--bg) 58%);
    color: #f0f5f1;
    font: 14px/1.5 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  }
  .app { width: min(100%, 1010px); margin: 0 auto; }
  header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
    margin: 0 0 22px;
  }
  .brand { display: flex; align-items: center; gap: 12px; }
  .brand-mark {
    display: grid; place-items: center;
    width: 42px; height: 42px;
    border: 1px solid #335342; border-radius: 13px;
    background: linear-gradient(145deg, #1c3528, #14221b);
    color: var(--mint);
    box-shadow: inset 0 1px rgba(255,255,255,.06), 0 7px 24px #0004;
  }
  .brand-mark svg { width: 23px; height: 23px; }
  h1 { margin: 0; font-size: 20px; line-height: 1.2; letter-spacing: -.04em; }
  .subtitle { margin-top: 3px; color: var(--muted); font-size: 12px; }
  .live-badge {
    display: inline-flex; align-items: center; gap: 8px;
    padding: 7px 11px; border: 1px solid #29372f; border-radius: 99px;
    background: #121915; color: #b6c3ba; font-size: 11px; letter-spacing: .08em;
    text-transform: uppercase; white-space: nowrap;
  }
  .live-dot {
    width: 7px; height: 7px; border-radius: 50%; background: var(--green);
    box-shadow: 0 0 10px #4bd58c99; animation: blink 1.5s infinite;
  }
  @keyframes blink { 50% { opacity: .35; } }
  .layout {
    display: grid;
    grid-template-columns: minmax(0, 680px) 250px;
    justify-content: center; align-items: start; gap: 22px;
  }
  .game-card {
    position: relative; min-width: 0; overflow: hidden;
    padding: 12px; border: 1px solid #28332e; border-radius: 20px;
    background: linear-gradient(145deg, #18201c, #111714);
    box-shadow: 0 24px 70px #0007, inset 0 1px rgba(255,255,255,.035);
  }
  .canvas-wrap { position: relative; width: 100%; aspect-ratio: 1; }
  canvas {
    display: block; width: 100%; height: 100%; border-radius: 13px;
    cursor: pointer; touch-action: manipulation;
  }
  .game-overlay {
    position: absolute; z-index: 2; top: 20px; left: 50%;
    transform: translateX(-50%);
    width: max-content; max-width: calc(100% - 30px);
    padding: 10px 15px 9px;
    border: 1px solid #42534a; border-radius: 13px;
    background: rgba(13, 20, 16, .88);
    box-shadow: 0 8px 28px #0006, inset 0 1px #ffffff0a;
    text-align: center; pointer-events: none; backdrop-filter: blur(8px);
  }
  .game-overlay[hidden] { display: none; }
  .overlay-title { color: #eaf8ef; font-size: 12px; font-weight: 750; letter-spacing: .12em; }
  .overlay-hint { margin-top: 2px; color: #a5b3aa; font-size: 11px; }
  .side { display: grid; gap: 12px; }
  .stat-card, .info-card {
    padding: 17px; border: 1px solid #28332e; border-radius: 16px;
    background: linear-gradient(145deg, #171f1b, #121815);
    box-shadow: inset 0 1px rgba(255,255,255,.025);
  }
  .stat-label { color: #91a096; font-size: 10px; font-weight: 700; letter-spacing: .13em; text-transform: uppercase; }
  .score-row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
  .score-value { margin-top: 5px; color: var(--mint); font-size: 37px; font-weight: 750; letter-spacing: -.055em; line-height: 1.1; font-variant-numeric: tabular-nums; }
  .score-unit { color: #708076; font-size: 11px; }
  .best-value { margin-top: 7px; color: #edf3ee; font-size: 25px; font-weight: 700; letter-spacing: -.04em; font-variant-numeric: tabular-nums; }
  .divider { height: 1px; margin: 15px 0; background: #29342e; }
  .section-title { margin: 0 0 12px; color: #a9b6ad; font-size: 10px; font-weight: 750; letter-spacing: .12em; text-transform: uppercase; }
  .key-list { display: grid; gap: 10px; }
  .key-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; color: #9ca99f; font-size: 12px; }
  .keys { display: flex; gap: 4px; }
  kbd {
    display: inline-grid; place-items: center; min-width: 23px; height: 22px; padding: 0 5px;
    border: 1px solid #36443b; border-bottom-width: 2px; border-radius: 6px;
    background: #1a241e; color: #d5e3d9; font: 10px ui-monospace, SFMono-Regular, Menlo, monospace;
  }
  .food-key { display: flex; align-items: center; gap: 8px; margin-top: 16px; color: #9ca99f; font-size: 11px; }
  .food-dot { width: 8px; height: 8px; border-radius: 50%; background: #ff8d77; box-shadow: 0 0 9px #ff8d7780; }
  .footer-note {
    margin-top: 13px; color: #718076; font-size: 10px; line-height: 1.6;
  }
  .status-line { display: flex; align-items: center; gap: 8px; margin-top: 13px; color: #9aa99f; font-size: 11px; }
  .status-icon { color: var(--mint); font-size: 14px; }
  @media (max-width: 760px) {
    body { padding: 20px 14px 28px; }
    .layout { grid-template-columns: minmax(0, 1fr); gap: 12px; }
    .game-card { padding: 8px; border-radius: 16px; }
    .side { grid-template-columns: 1fr 1fr; gap: 10px; }
    .stat-card, .info-card { padding: 14px; }
    .info-card { grid-column: 1 / -1; }
    .score-value { font-size: 31px; }
    .best-value { font-size: 22px; }
    header { margin-bottom: 15px; }
  }
  @media (max-width: 420px) {
    body { padding: 14px 10px 22px; }
    .brand-mark { width: 38px; height: 38px; }
    h1 { font-size: 18px; }
    .live-badge { padding: 6px 9px; font-size: 9px; }
    .game-overlay { top: 13px; padding: 8px 11px; }
  }
</style>
</head>
<body>
<main class="app">
  <header>
    <div class="brand">
      <div class="brand-mark" aria-hidden="true">
        <svg viewBox="0 0 24 24" fill="none">
          <path d="M4 7h8a3 3 0 1 1-3 3v4a3 3 0 1 0 3 3h8" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/>
          <circle cx="18.3" cy="17" r="1" fill="currentColor"/>
        </svg>
      </div>
      <div><h1>Snake</h1><div class="subtitle">A little arcade classic</div></div>
    </div>
    <div class="live-badge"><span class="live-dot"></span><span id="badgeText">Live preview</span></div>
  </header>

  <div class="layout">
    <section class="game-card" aria-label="Snake game">
      <div class="canvas-wrap">
        <canvas id="game" aria-label="Snake game board"></canvas>
        <div class="game-overlay" id="overlay">
          <div class="overlay-title" id="overlayTitle">AUTOPILOT PREVIEW</div>
          <div class="overlay-hint" id="overlayHint">Click or press a key to start</div>
        </div>
      </div>
    </section>

    <aside class="side">
      <section class="stat-card">
        <div class="score-row"><div class="stat-label">Your score</div><div class="score-unit">PTS</div></div>
        <div class="score-value" id="score">00</div>
        <div class="divider"></div>
        <div class="stat-label">Session best</div>
        <div class="best-value" id="best">00</div>
      </section>

      <section class="info-card">
        <h2 class="section-title">How to play</h2>
        <div class="key-list">
          <div class="key-row"><span>Move</span><span class="keys"><kbd>↑</kbd><kbd>←</kbd><kbd>↓</kbd><kbd>→</kbd></span></div>
          <div class="key-row"><span>Or use</span><span class="keys"><kbd>W</kbd><kbd>A</kbd><kbd>S</kbd><kbd>D</kbd></span></div>
          <div class="key-row"><span>Pause</span><span class="keys"><kbd>P</kbd></span></div>
          <div class="key-row"><span>Restart</span><span class="keys"><kbd>⏎</kbd><kbd>␠</kbd></span></div>
        </div>
        <div class="food-key"><span class="food-dot"></span><span>Eat fruit to grow · +10 points</span></div>
        <div class="status-line"><span class="status-icon">✳</span><span id="statusText">Preview is playing itself</span></div>
      </section>
      <div class="footer-note">Keep moving, watch your tail, and see how long you can last.</div>
    </aside>
  </div>
</main>

<script>
(() => {
  "use strict";
  const canvas = document.getElementById("game");
  const ctx = canvas.getContext("2d");
  const overlay = document.getElementById("overlay");
  const overlayTitle = document.getElementById("overlayTitle");
  const overlayHint = document.getElementById("overlayHint");
  const scoreEl = document.getElementById("score");
  const bestEl = document.getElementById("best");
  const badgeText = document.getElementById("badgeText");
  const statusText = document.getElementById("statusText");

  const N = 24, SIZE = 720, CELL = 28, ORIGIN = 24;
  const STEP_MS = 132;
  const DIRS = {
    ArrowUp: {x:0,y:-1}, w:{x:0,y:-1}, W:{x:0,y:-1},
    ArrowDown:{x:0,y:1}, s:{x:0,y:1}, S:{x:0,y:1},
    ArrowLeft:{x:-1,y:0}, a:{x:-1,y:0}, A:{x:-1,y:0},
    ArrowRight:{x:1,y:0}, d:{x:1,y:0}, D:{x:1,y:0}
  };

  let snake, previousSnake, dir, queue, food, score, best = 0;
  let state = "demo";
  let lastTick = performance.now(), particles = [];
  let elapsed = 0;
  let touchStart = null;

  function fitCanvas() {
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    canvas.width = SIZE * dpr;
    canvas.height = SIZE * dpr;
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  }
  window.addEventListener("resize", fitCanvas);
  fitCanvas();

  function freshGame() {
    snake = [
      {x:11,y:12},{x:10,y:12},{x:9,y:12},{x:8,y:12},{x:7,y:12}
    ];
    previousSnake = snake.map(p => ({...p}));
    dir = {x:1,y:0};
    queue = [];
    score = 0;
    particles = [];
    spawnFood();
    updateStats();
  }
  function updateStats() {
    scoreEl.textContent = String(score).padStart(2, "0");
    bestEl.textContent = String(best).padStart(2, "0");
  }
  function spawnFood() {
    const free = [];
    for (let y=0; y<N; y++) for (let x=0; x<N; x++) {
      if (!snake.some(p => p.x===x && p.y===y)) free.push({x,y});
    }
    food = free.length ? free[Math.floor(Math.random()*free.length)] : null;
  }
  function begin(direction) {
    state = "playing";
    overlay.hidden = true;
    badgeText.textContent = "In play";
    statusText.textContent = "Collect fruit and keep moving";
    if (direction) enqueueDirection(direction);
    lastTick = performance.now();
  }
  function restart(direction) {
    freshGame();
    begin(direction);
  }
  function setPaused() {
    if (state === "playing") {
      state = "paused";
      overlayTitle.textContent = "PAUSED";
      overlayHint.textContent = "Press P to jump back in";
      overlay.hidden = false;
      badgeText.textContent = "Paused";
      statusText.textContent = "Take a breather";
    } else if (state === "paused") {
      state = "playing";
      overlay.hidden = true;
      badgeText.textContent = "In play";
      statusText.textContent = "Collect fruit and keep moving";
      lastTick = performance.now();
    }
  }
  function enqueueDirection(next) {
    const base = queue.length ? queue[queue.length - 1] : dir;
    if (next.x === -base.x && next.y === -base.y) return;
    if (next.x === base.x && next.y === base.y) return;
    if (queue.length < 2) queue.push(next);
  }
  function endGame() {
    state = "over";
    best = Math.max(best, score);
    updateStats();
    overlayTitle.textContent = "GAME OVER";
    overlayHint.textContent = `Score ${score} · Press Space or Enter to play again`;
    overlay.hidden = false;
    badgeText.textContent = "Game over";
    statusText.textContent = "Ready for another run?";
  }
  function opposite(a,b) { return a.x===-b.x && a.y===-b.y; }

  function aiDirection() {
    const head = snake[0];
    const blocked = new Set();
    for (let i=0; i<snake.length-1; i++) blocked.add(snake[i].x + "," + snake[i].y);
    const startKey = head.x + "," + head.y;
    const dirs = [{x:0,y:-1},{x:1,y:0},{x:0,y:1},{x:-1,y:0}];
    const q = [{x:head.x,y:head.y,first:null}];
    const seen = new Set([startKey]);
    let found = null;
    for (let qi=0; qi<q.length && qi<1800; qi++) {
      const node = q[qi];
      if (food && node.x===food.x && node.y===food.y) { found=node.first; break; }
      for (const d of dirs) {
        if (!node.first && opposite(d, dir)) continue;
        const x=node.x+d.x, y=node.y+d.y, key=x+","+y;
        if (x<0||x>=N||y<0||y>=N||blocked.has(key)||seen.has(key)) continue;
        seen.add(key);
        q.push({x,y,first:node.first || d});
      }
    }
    if (found) return found;
    const options = dirs.filter(d => !opposite(d,dir)).map(d => {
      const x=head.x+d.x,y=head.y+d.y;
      const safe = x>=0&&x<N&&y>=0&&y<N&&
        !snake.slice(0,-1).some(p=>p.x===x&&p.y===y);
      const distance = food ? Math.abs(food.x-x)+Math.abs(food.y-y) : 0;
      return {d,safe,distance};
    }).filter(o=>o.safe).sort((a,b)=>a.distance-b.distance);
    return options.length ? options[0].d : dir;
  }

  function burst(x,y) {
    const colors = ["#ff9b7d","#ffd18b","#ff755e","#e9ffbd"];
    for (let i=0;i<15;i++) {
      const a=Math.random()*Math.PI*2, speed=35+Math.random()*100;
      particles.push({
        x: ORIGIN+(x+.5)*CELL, y: ORIGIN+(y+.5)*CELL,
        vx:Math.cos(a)*speed, vy:Math.sin(a)*speed,
        life:.42+Math.random()*.32, max:.74,
        r:1.5+Math.random()*2.2, color:colors[i%colors.length]
      });
    }
  }
  function tick() {
    if (state === "demo") {
      dir = aiDirection();
    } else if (queue.length) {
      const proposed = queue.shift();
      if (!opposite(proposed, dir)) dir = proposed;
    }

    const head=snake[0], next={x:head.x+dir.x,y:head.y+dir.y};
    const eating = food && next.x===food.x && next.y===food.y;
    const bodyLimit = eating ? snake.length : snake.length-1;
    const hitBody = snake.slice(0,bodyLimit).some(p=>p.x===next.x&&p.y===next.y);
    if (next.x<0||next.x>=N||next.y<0||next.y>=N||hitBody) {
      if (state === "demo") {
        freshGame();
        return;
      }
      endGame();
      return;
    }
    previousSnake = snake.map(p=>({...p}));
    snake.unshift(next);
    if (eating) {
      score += 10;
      best = Math.max(best,score);
      burst(food.x,food.y);
      spawnFood();
      updateStats();
    } else {
      snake.pop();
    }
  }

  function roundedRect(c,x,y,w,h,r) {
    c.beginPath();
    c.roundRect(x,y,w,h,r);
  }
  function cellCenter(p) {
    return {x:ORIGIN+(p.x+.5)*CELL, y:ORIGIN+(p.y+.5)*CELL};
  }
  function draw(now) {
    const dt=Math.min(.04,(now-elapsed)/1000 || 0);
    elapsed=now;
    ctx.clearRect(0,0,SIZE,SIZE);

    roundedRect(ctx, 0,0,SIZE,SIZE,18);
    const bg=ctx.createLinearGradient(0,0,SIZE,SIZE);
    bg.addColorStop(0,"#101a14"); bg.addColorStop(1,"#0b130f");
    ctx.fillStyle=bg; ctx.fill();

    roundedRect(ctx, 8,8,SIZE-16,SIZE-16,15);
    ctx.fillStyle="#0c1510"; ctx.fill();

    ctx.save();
    roundedRect(ctx, 8,8,SIZE-16,SIZE-16,15);
    ctx.clip();
    for(let i=0;i<=N;i++) {
      const p=ORIGIN+i*CELL;
      ctx.strokeStyle = i%4===0 ? "rgba(143,196,158,.075)" : "rgba(143,196,158,.035)";
      ctx.lineWidth=1;
      ctx.beginPath(); ctx.moveTo(ORIGIN,ORIGIN+i*CELL); ctx.lineTo(ORIGIN+N*CELL,ORIGIN+i*CELL); ctx.stroke();
      ctx.beginPath(); ctx.moveTo(ORIGIN+i*CELL,ORIGIN); ctx.lineTo(ORIGIN+i*CELL,ORIGIN+N*CELL); ctx.stroke();
    }

    if(food) {
      const pulse=1+Math.sin(now*.006)*.09;
      const c=cellCenter(food);
      ctx.save();
      ctx.shadowColor="#ff8068"; ctx.shadowBlur=17;
      ctx.fillStyle="#ff8068"; ctx.beginPath(); ctx.arc(c.x,c.y,7.3*pulse,0,Math.PI*2); ctx.fill();
      ctx.shadowBlur=0; ctx.fillStyle="#ffd2a2"; ctx.beginPath(); ctx.arc(c.x-2,c.y-2,2.1,0,Math.PI*2); ctx.fill();
      ctx.restore();
    }

    const progress = state==="paused" ? 1 : Math.min(1,(now-lastTick)/STEP_MS);
    for(let i=snake.length-1;i>=0;i--) {
      const cur=snake[i];
      const prev=previousSnake[i] || cur;
      let x=prev.x+(cur.x-prev.x)*progress;
      let y=prev.y+(cur.y-prev.y)*progress;
      // Preserve smooth visual movement on the board edges.
      x=Math.max(-.6,Math.min(N-.4,x)); y=Math.max(-.6,Math.min(N-.4,y));
      const px=ORIGIN+x*CELL, py=ORIGIN+y*CELL;
      const gap=i===0?2.2:3.3;
      const inset=i===0?2:3;
      const size=CELL-inset*2;
      const radius=i===0?9:7;
      if(i===0) {
        ctx.save();
        ctx.shadowColor="rgba(80,231,145,.35)"; ctx.shadowBlur=15;
      }
      const shade = Math.max(0,1-i/Math.max(1,snake.length));
      ctx.fillStyle = i===0 ? "#9bf4bd" : `rgb(${Math.round(39+shade*30)},${Math.round(177+shade*39)},${Math.round(104+shade*36)})`;
      roundedRect(ctx,px+inset,py+inset,size,size,radius);
      ctx.fill();
      if(i===0) {
        ctx.restore();
        // Eyes sit toward the direction of travel.
        const cx=px+CELL/2, cy=py+CELL/2;
        const fx=dir.x, fy=dir.y, sx=-fy, sy=fx;
        for(const side of [-1,1]) {
          const ex=cx+fx*5.1+sx*side*5.2;
          const ey=cy+fy*5.1+sy*side*5.2;
          ctx.fillStyle="#153827"; ctx.beginPath(); ctx.arc(ex,ey,2.05,0,Math.PI*2); ctx.fill();
          ctx.fillStyle="#eaffef"; ctx.beginPath(); ctx.arc(ex-.55,ey-.65,.65,0,Math.PI*2); ctx.fill();
        }
      } else {
        ctx.fillStyle="rgba(235,255,239,.08)";
        roundedRect(ctx,px+inset+3,py+inset+2,size*.42,2,1); ctx.fill();
      }
    }
    ctx.restore();

    for(let i=particles.length-1;i>=0;i--) {
      const p=particles[i];
      p.life-=dt;
      if(p.life<=0){particles.splice(i,1);continue;}
      p.x+=p.vx*dt; p.y+=p.vy*dt;
      p.vx*=.96; p.vy*=.96;
      ctx.globalAlpha=Math.max(0,p.life/p.max);
      ctx.fillStyle=p.color;
      ctx.beginPath(); ctx.arc(p.x,p.y,p.r,0,Math.PI*2); ctx.fill();
    }
    ctx.globalAlpha=1;

    if((state==="playing"||state==="demo") && now-lastTick>=STEP_MS) {
      const steps=Math.min(3,Math.floor((now-lastTick)/STEP_MS));
      for(let i=0;i<steps;i++) {
        if(state==="over") break;
        tick();
        lastTick+=STEP_MS;
      }
      if(now-lastTick>STEP_MS*3) lastTick=now;
    }
    requestAnimationFrame(draw);
  }

  window.addEventListener("keydown", e => {
    const d=DIRS[e.key];
    if(d || e.code==="Space" || e.key==="Enter" || e.key.toLowerCase()==="p") e.preventDefault();

    if(state==="demo") {
      if(e.key.toLowerCase()==="p") { begin(); setPaused(); return; }
      begin(d);
      return;
    }
    if(state==="over") {
      if(e.code==="Space"||e.key==="Enter") restart(d);
      return;
    }
    if(e.key.toLowerCase()==="p") { setPaused(); return; }
    if(state==="paused") {
      if(e.code==="Space"||e.key==="Enter") setPaused();
      return;
    }
    if(d) enqueueDirection(d);
  });

  canvas.addEventListener("click", () => {
    if(state==="demo") begin();
    else if(state==="over") restart();
    else if(state==="paused") setPaused();
  });

  canvas.addEventListener("touchstart", e => {
    const t=e.changedTouches[0];
    touchStart={x:t.clientX,y:t.clientY};
    if(state==="demo") begin();
  }, {passive:true});
  canvas.addEventListener("touchend", e => {
    if(!touchStart) return;
    const t=e.changedTouches[0], dx=t.clientX-touchStart.x, dy=t.clientY-touchStart.y;
    if(Math.max(Math.abs(dx),Math.abs(dy))>18 && state==="playing") {
      enqueueDirection(Math.abs(dx)>Math.abs(dy)?{x:Math.sign(dx),y:0}:{x:0,y:Math.sign(dy)});
    }
    touchStart=null;
  }, {passive:true});

  freshGame();
  requestAnimationFrame(draw);
})();
</script>
</body>
</html>
Reasoning trace (1,341 tokens)
**Designing game demo**

I need to create a self-playing demo that starts behind a message. The canvas should be attractive and centered, and it will be autonomous at first. Overlay functionality should allow users to start the demo with a click or keypress.

For the grid, I’ll use a 24x24 format with responsive CSS. Movement will require robust controls to handle quick key presses, queuing directions properly. The AI will autonomously choose paths with a simple algorithm, and I’ll ensure the game over overlay pauses the game and displays the score. The canvas will have a stylish dark green background.**Enhancing game demo visuals**

For the game demo, I need to ensure it plays smoothly with a transparent overlay displaying a message at the top center. I’ll aim for a rendering rate of around 60fps, with snake movements updated every 115ms for smooth interpolation. To prevent visual issues, I’ll track the previous and current positions of the snake, using linear interpolation.

The AI should safely select movement options, avoiding reverse directions. It will use BFS to find food while considering obstacles, ensuring a fun experience as the snake grows. The demo will start with a snake length of 5 in the middle.**Finalizing game controls**

I need the game to continue smoothly even when the user starts over. The demo state will transition to playing while preserving the snake's position and clearing the overlay. Directional inputs will be recognized, and the AI will act accordingly.

I should implement a key listener so the start key begins the game, allowing for directional input to be queued. The space or enter keys will restart after a game over, and I want to avoid accidentally pausing with a click. The overlay will display "AUTOPILOT PREVIEW" and indicate that players can click or press any key to start.**Evaluating queue handling**

I’m thinking about how to handle the queue. If the queue tail or the current direction is desired, I should check the opposite against the last queued direction. If the current next move is immediate, I might need to quickly press both. At each tick, I can consume one item from the queue while evaluating the second item against the queued tail. It looks like I'm considering the relationship between the current direction and the queue effectively.