GPT-6.1 Terra
by OpenAIReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 2 min 2 s
- Cost
- $0.055
- Output tokens
- 10,925
- Spent thinking
- 2,817 tokens
- Speed
- 97.1 tok/s
- Console errors
- 0
- First token after
- 9.1 s
- First visible text after
- 52.3 s
- Prompt tokens
- 493
- Code size
- 24.3 KB
- Finish reason
- stop
- Served by
- OpenAI
- Model id
openai/gpt-6.1-terra- Reasoning sent
{"effort":"high"}- Max output
- 128,000 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 · 585 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Snake Club</title>
<style>
:root {
color-scheme: dark;
--bg: #0d1310;
--panel: #151e19;
--line: #2a382d;
--muted: #829087;
--text: #edf2e9;
--lime: #c8f46a;
}
* { box-sizing: border-box; }
html, body { margin: 0; min-height: 100%; }
body {
background: radial-gradient(ellipse at 50% 42%, #1b2a1d 0%, #101813 45%, #0b100d 100%);
color: var(--text);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
body::before {
content: "";
position: fixed;
inset: 0;
pointer-events: none;
opacity: .15;
background-image: repeating-linear-gradient(0deg, transparent, transparent 3px, rgba(255,255,255,.015) 4px);
}
button { font: inherit; }
.page { width: min(1160px, calc(100% - 64px)); margin: 0 auto; }
.header {
height: 92px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid rgba(166, 195, 163, .13);
}
.brand { display: flex; align-items: center; gap: 12px; }
.brand-mark {
width: 36px; height: 36px;
display: grid; place-items: center;
color: #15220f; background: var(--lime); border-radius: 10px;
box-shadow: 0 0 25px rgba(190, 243, 103, .13);
}
.brand-name { font-size: 15px; font-weight: 850; letter-spacing: -.04em; line-height: 1; }
.brand-sub { color: var(--muted); font-size: 9px; letter-spacing: .19em; font-weight: 700; margin-top: 5px; }
.header-right { display: flex; align-items: center; gap: 20px; }
.header-note { color: #6f8073; font-size: 10px; font-weight: 700; letter-spacing: .15em; }
.online {
display: flex; align-items: center; gap: 7px;
border: 1px solid #344536; border-radius: 20px; padding: 8px 11px;
color: #b4c3b1; font-size: 10px; font-weight: 750; letter-spacing: .09em;
}
.dot { width: 6px; height: 6px; background: var(--lime); border-radius: 50%; box-shadow: 0 0 9px #c8f46a; }
.layout {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 576px) minmax(0, 1fr);
gap: 32px;
align-items: center;
padding-top: 30px;
}
.sidebar { min-width: 0; }
.eyebrow { color: #90a18e; font-size: 10px; font-weight: 800; letter-spacing: .17em; }
.eyebrow .accent { color: var(--lime); }
h1 { font-size: clamp(30px, 3.1vw, 42px); letter-spacing: -.065em; line-height: 1.02; margin: 19px 0 13px; font-weight: 850; }
h1 span { color: var(--lime); }
.intro-copy { max-width: 200px; color: #91a095; font-size: 13px; line-height: 1.65; margin: 0; }
.score-stack { margin-top: 37px; display: grid; gap: 10px; }
.score-card {
min-height: 91px; padding: 16px 18px;
background: linear-gradient(135deg, rgba(31, 44, 33, .9), rgba(21, 30, 24, .9));
border: 1px solid #304033; border-radius: 11px;
position: relative; overflow: hidden;
}
.score-card::after { content: ""; position: absolute; right: -15px; bottom: -38px; width: 80px; height: 80px; border: 1px solid rgba(194, 241, 112, .08); border-radius: 50%; box-shadow: 0 0 0 12px rgba(194, 241, 112, .025); }
.score-label { color: #91a393; font-size: 10px; font-weight: 800; letter-spacing: .13em; }
.score-value { font-size: 32px; line-height: 1; font-weight: 750; letter-spacing: -.06em; margin-top: 10px; font-variant-numeric: tabular-nums; }
.score-card.current .score-value { color: var(--lime); }
.score-detail { position: absolute; right: 16px; top: 17px; color: #667b68; font-size: 9px; font-weight: 700; }
.left-foot { margin-top: 24px; color: #66786a; font-size: 11px; line-height: 1.6; }
.board-frame {
width: 100%; aspect-ratio: 1;
padding: 8px; border-radius: 17px;
background: #1b2920;
border: 1px solid #435847;
box-shadow: 0 25px 80px rgba(0,0,0,.35), 0 0 60px rgba(140, 205, 93, .05), inset 0 1px rgba(255,255,255,.05);
}
.board-inner { width: 100%; height: 100%; position: relative; overflow: hidden; border-radius: 9px; background: #132019; }
canvas { display: block; width: 100%; height: 100%; }
.board-tag {
position: absolute; top: 12px; left: 12px;
display: flex; align-items: center; gap: 7px;
background: rgba(12, 23, 15, .78); border: 1px solid rgba(154, 190, 143, .17);
padding: 7px 9px; border-radius: 5px;
font-size: 9px; font-weight: 800; letter-spacing: .12em; color: #a8bca7;
pointer-events: none;
}
.board-tag .dot { width: 5px; height: 5px; }
.board-coord {
position: absolute; right: 13px; top: 15px; color: #758b77;
font-size: 9px; font-weight: 700; letter-spacing: .13em; pointer-events: none;
}
.start-prompt {
position: absolute; left: 50%; bottom: 22px; transform: translateX(-50%);
width: min(365px, calc(100% - 28px)); border: 1px solid #526946;
background: rgba(17, 29, 19, .95); box-shadow: 0 8px 30px rgba(0,0,0,.3);
border-radius: 10px; padding: 12px 15px;
display: flex; align-items: center; gap: 12px; text-align: left;
color: var(--text); cursor: pointer;
}
.start-prompt:hover { border-color: var(--lime); }
.play-icon {
flex: 0 0 36px; height: 36px; border-radius: 7px;
display: grid; place-items: center; background: var(--lime); color: #1a2913;
}
.prompt-title { font-weight: 750; font-size: 12px; line-height: 1.3; }
.prompt-sub { color: #9aad99; font-size: 10px; margin-top: 4px; }
.overlay {
position: absolute; inset: 0; display: none; align-items: center; justify-content: center;
background: rgba(6, 13, 8, .78); backdrop-filter: blur(3px);
text-align: center; padding: 20px;
}
.overlay.show { display: flex; }
.overlay-card { width: min(290px, 100%); padding: 28px 20px; border: 1px solid #465d43; border-radius: 14px; background: #17241a; box-shadow: 0 20px 45px #0007; }
.overlay-kicker { color: var(--lime); letter-spacing: .18em; font-size: 10px; font-weight: 800; }
.overlay h2 { font-size: 32px; letter-spacing: -.06em; margin: 10px 0 8px; }
.overlay p { color: #a2b2a2; font-size: 13px; line-height: 1.5; margin: 0 0 20px; }
.overlay p strong { color: white; }
.primary-button { background: var(--lime); color: #192512; border: 0; border-radius: 7px; padding: 11px 20px; font-size: 12px; font-weight: 800; cursor: pointer; }
.overlay-hint { color: #80927f; font-size: 10px; margin-top: 12px; }
.rules-title { font-size: 18px; font-weight: 800; letter-spacing: -.04em; margin: 14px 0 8px; }
.rules-copy { color: #91a095; font-size: 12px; line-height: 1.65; margin: 0 0 23px; }
.rule-box { border-top: 1px solid #2d3a2f; padding: 16px 0; }
.rule-top { display: flex; justify-content: space-between; align-items: center; gap: 10px; }
.rule-name { font-size: 12px; font-weight: 700; }
.rule-desc { color: #778b7a; font-size: 11px; margin-top: 7px; line-height: 1.45; }
.keys { display: flex; gap: 3px; }
kbd {
min-width: 23px; height: 23px; padding: 0 5px; display: inline-grid; place-items: center;
border: 1px solid #405343; border-bottom-width: 2px; border-radius: 4px;
background: #202e23; color: #b9c9b7; font: 700 10px/1 inherit;
font-size: 10px; font-family: inherit; font-weight: 700;
}
.action-row { display: flex; gap: 8px; margin-top: 7px; }
.small-button { flex: 1; background: #1b2920; color: #c7d4c4; border: 1px solid #344838; border-radius: 7px; padding: 10px 7px; font-size: 11px; font-weight: 700; cursor: pointer; }
.small-button:hover { color: var(--lime); border-color: #698454; }
.footer { display: flex; justify-content: space-between; align-items: center; padding: 25px 0 24px; color: #64766a; font-size: 10px; font-weight: 700; letter-spacing: .08em; }
.footer span:last-child { color: #7c8e7f; }
.hidden { display: none !important; }
@media (max-width: 900px) {
.page { width: min(576px, calc(100% - 32px)); }
.header { height: 70px; }
.header-note { display: none; }
.layout { grid-template-columns: minmax(0, 1fr); gap: 14px; padding-top: 17px; }
.left { display: contents; }
.intro, .left-foot { display: none; }
.score-stack { grid-row: 1; grid-template-columns: 1fr 1fr; margin: 0; gap: 10px; }
.score-card { min-height: 72px; padding: 11px 14px; }
.score-value { font-size: 26px; margin-top: 7px; }
.board-frame { grid-row: 2; }
.right { grid-row: 3; }
.right .eyebrow, .rules-title, .rules-copy { display: none; }
.rules-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.rule-box { border: 1px solid #2d3a2f; border-radius: 8px; padding: 12px; }
.rule-desc { margin-top: 5px; }
.action-row { margin-top: 10px; }
.footer { padding: 20px 0; }
}
@media (max-width: 420px) {
.online { font-size: 9px; padding: 7px 9px; }
.board-frame { padding: 6px; border-radius: 13px; }
.board-tag { top: 8px; left: 8px; padding: 6px 7px; font-size: 8px; }
.board-coord { top: 12px; font-size: 8px; }
.start-prompt { bottom: 12px; padding: 9px 10px; }
.play-icon { flex-basis: 32px; height: 32px; }
.prompt-title { font-size: 11px; }
.prompt-sub { font-size: 9px; }
.rule-top { align-items: flex-start; flex-direction: column; gap: 8px; }
}
</style>
</head>
<body>
<div class="page">
<header class="header">
<div class="brand">
<div class="brand-mark" aria-hidden="true">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none">
<path d="M6 17h7a4 4 0 0 0 0-8h-2a3 3 0 1 1 0-6h6" stroke="currentColor" stroke-width="3.2" stroke-linecap="round"/>
<circle cx="18" cy="3" r="2.3" fill="currentColor"/>
</svg>
</div>
<div><div class="brand-name">SNAKE CLUB</div><div class="brand-sub">THE LITTLE ARCADE</div></div>
</div>
<div class="header-right"><span class="header-note">A GAME FOR YOUR BREAK</span><span class="online"><span class="dot"></span> READY TO PLAY</span></div>
</header>
<main class="layout">
<aside class="sidebar left">
<div class="intro">
<div class="eyebrow"><span class="accent">01 /</span> THE CLASSIC GAME</div>
<h1>Good games<br><span>grow.</span></h1>
<p class="intro-copy">A little patience. A little appetite. How long can you go?</p>
</div>
<div class="score-stack">
<div class="score-card current">
<div class="score-label">YOUR SCORE</div><div class="score-detail">PTS</div>
<div class="score-value" id="score">00</div>
</div>
<div class="score-card">
<div class="score-label">SESSION BEST</div><div class="score-detail">★</div>
<div class="score-value" id="best">00</div>
</div>
</div>
<div class="left-foot">One bite at a time.<br>Make this run your best one yet.</div>
</aside>
<section class="board-frame" aria-label="Snake game board">
<div class="board-inner">
<canvas id="game" aria-label="A 24 by 24 Snake game"></canvas>
<div class="board-tag"><span class="dot"></span><span id="modeLabel">AUTOPLAY</span></div>
<div class="board-coord">24 × 24</div>
<button class="start-prompt" id="startPrompt" type="button">
<span class="play-icon" aria-hidden="true">
<svg width="14" height="14" viewBox="0 0 14 14"><path d="M4 2.3 11 7l-7 4.7z" fill="currentColor"/></svg>
</span>
<span><span class="prompt-title">Click or press a key to start</span><br><span class="prompt-sub">Autoplay is on · take over whenever you're ready</span></span>
</button>
<div class="overlay" id="overlay">
<div class="overlay-card">
<div class="overlay-kicker" id="overlayKicker">NICE RUN</div>
<h2 id="overlayTitle">Game over</h2>
<p id="overlayText">You scored <strong>0 points</strong>.</p>
<button class="primary-button" id="overlayButton" type="button">Play again</button>
<div class="overlay-hint" id="overlayHint">SPACE OR ENTER TO RESTART</div>
</div>
</div>
</div>
</section>
<aside class="sidebar right">
<div class="eyebrow"><span class="accent">02 /</span> THE BASICS</div>
<div class="rules-title">Easy to learn.<br>Hard to put down.</div>
<p class="rules-copy">Collect the glowing bites, grow your snake, and don't run into anything.</p>
<div class="rules-grid">
<div class="rule-box">
<div class="rule-top"><span class="rule-name">Move</span><span class="keys"><kbd>↑</kbd><kbd>←</kbd><kbd>↓</kbd><kbd>→</kbd></span></div>
<div class="rule-desc">Arrow keys or W, A, S, D</div>
</div>
<div class="rule-box">
<div class="rule-top"><span class="rule-name">Pause</span><span class="keys"><kbd>P</kbd></span></div>
<div class="rule-desc">Take a breather anytime</div>
</div>
</div>
<div class="action-row">
<button class="small-button" id="pauseButton" type="button">Ⅱ Pause</button>
<button class="small-button" id="restartButton" type="button">↻ Restart</button>
</div>
</aside>
</main>
<footer class="footer"><span>EST. JUST FOR FUN</span><span>BUILT FOR ONE MORE ROUND <span style="color:var(--lime)">↗</span></span></footer>
</div>
<script>
(() => {
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const scoreEl = document.getElementById('score');
const bestEl = document.getElementById('best');
const modeLabel = document.getElementById('modeLabel');
const startPrompt = document.getElementById('startPrompt');
const overlay = document.getElementById('overlay');
const overlayKicker = document.getElementById('overlayKicker');
const overlayTitle = document.getElementById('overlayTitle');
const overlayText = document.getElementById('overlayText');
const overlayButton = document.getElementById('overlayButton');
const overlayHint = document.getElementById('overlayHint');
const pauseButton = document.getElementById('pauseButton');
const restartButton = document.getElementById('restartButton');
const N = 24;
const STEP = 0.135;
const vectors = { ArrowUp: [0,-1], ArrowDown: [0,1], ArrowLeft: [-1,0], ArrowRight: [1,0], w: [0,-1], s: [0,1], a: [-1,0], d: [1,0] };
let snake, previous, direction, queue, food, score, particles, accumulator;
let best = 0;
let demo = true;
let state = 'playing';
let lastTime = 0;
let clock = 0;
let pixelSize = 0;
function reset(isDemo) {
demo = isDemo;
state = 'playing';
snake = Array.from({length: 6}, (_, i) => ({x: 8-i, y: 12}));
previous = snake.map(p => ({...p}));
direction = {x: 1, y: 0};
queue = [];
food = {x: 13, y: 12};
score = 0;
particles = [];
accumulator = 0;
overlay.classList.remove('show');
startPrompt.classList.toggle('hidden', !demo);
modeLabel.textContent = demo ? 'AUTOPLAY' : 'IN THE GAME';
pauseButton.innerHTML = 'Ⅱ Pause';
updateScores();
}
function updateScores() {
scoreEl.textContent = String(score).padStart(2, '0');
bestEl.textContent = String(best).padStart(2, '0');
}
function startPlayer() {
if (demo) reset(false);
}
function spawnFood() {
const occupied = new Set(snake.map(p => p.y * N + p.x));
const free = [];
for (let i = 0; i < N*N; i++) if (!occupied.has(i)) free.push(i);
if (!free.length) {
if (!demo) showEnd(true);
else reset(true);
return;
}
const spot = free[Math.floor(Math.random() * free.length)];
food = {x: spot % N, y: Math.floor(spot / N)};
}
function burst(x, y) {
for (let i = 0; i < 15; i++) {
const angle = Math.PI * 2 * i / 15 + Math.random() * .3;
const speed = 2.2 + Math.random() * 4;
particles.push({
x: x + .5, y: y + .5, vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed, life: .45 + Math.random() * .35,
max: .8, size: .045 + Math.random() * .09,
color: i % 3 === 0 ? '#f9e7a0' : (i % 2 ? '#f28e67' : '#c9f46e')
});
}
}
function enqueue(x, y) {
const last = queue.length ? queue[queue.length-1] : direction;
if ((x === -last.x && y === -last.y) || (x === last.x && y === last.y)) return;
if (queue.length < 8) queue.push({x, y});
}
function demoDirection() {
const start = snake[0];
const blocked = new Set(snake.slice(0, -1).map(p => p.y*N+p.x));
blocked.delete(start.y*N+start.x);
const visited = new Set([start.y*N+start.x]);
const todo = [{x:start.x, y:start.y, first:null}];
const dirs = [{x:1,y:0},{x:0,y:1},{x:-1,y:0},{x:0,y:-1}];
for (let i = 0; i < todo.length; i++) {
const p = todo[i];
if (p.x === food.x && p.y === food.y && p.first) return p.first;
for (const d of dirs) {
if (!p.first && d.x === -direction.x && d.y === -direction.y) continue;
const x = p.x+d.x, y = p.y+d.y, id = y*N+x;
if (x < 0 || y < 0 || x >= N || y >= N || blocked.has(id) || visited.has(id)) continue;
visited.add(id);
todo.push({x, y, first: p.first || d});
}
}
return dirs.find(d => {
const x = start.x+d.x, y = start.y+d.y;
return !(d.x === -direction.x && d.y === -direction.y) &&
x >= 0 && x < N && y >= 0 && y < N && !blocked.has(y*N+x);
}) || direction;
}
function showEnd(won = false) {
state = 'over';
modeLabel.textContent = won ? 'FIELD COMPLETE' : 'GAME OVER';
overlayKicker.textContent = won ? 'INCREDIBLE RUN' : 'NICE RUN';
overlayTitle.textContent = won ? 'You won!' : 'Game over';
overlayText.innerHTML = 'You scored <strong>' + score + ' points</strong>.';
overlayButton.textContent = 'Play again';
overlayHint.textContent = 'SPACE OR ENTER TO RESTART';
overlay.classList.add('show');
}
function tick() {
if (demo) direction = demoDirection();
else if (queue.length) direction = queue.shift();
const head = {x: snake[0].x + direction.x, y: snake[0].y + direction.y};
const eating = head.x === food.x && head.y === food.y;
const hitsSelf = snake.slice(0, eating ? snake.length : -1).some(p => p.x === head.x && p.y === head.y);
if (head.x < 0 || head.y < 0 || head.x >= N || head.y >= N || hitsSelf) {
if (demo) reset(true);
else showEnd();
return;
}
previous = snake.map(p => ({...p}));
snake.unshift(head);
if (eating) {
score += 10;
best = Math.max(best, score);
updateScores();
burst(food.x, food.y);
spawnFood();
} else {
snake.pop();
}
}
function togglePause() {
if (demo) { startPlayer(); return; }
if (state === 'over') return;
if (state === 'playing') {
state = 'paused';
modeLabel.textContent = 'PAUSED';
overlayKicker.textContent = 'TAKE YOUR TIME';
overlayTitle.textContent = 'Paused';
overlayText.textContent = 'Your snake is right where you left it.';
overlayButton.textContent = 'Resume game';
overlayHint.textContent = 'PRESS P TO RESUME';
overlay.classList.add('show');
pauseButton.innerHTML = '▶ Resume';
} else {
state = 'playing';
modeLabel.textContent = 'IN THE GAME';
overlay.classList.remove('show');
pauseButton.innerHTML = 'Ⅱ Pause';
}
}
function resize() {
const rect = canvas.getBoundingClientRect();
const size = Math.max(1, Math.round(rect.width * Math.min(window.devicePixelRatio || 1, 2)));
if (size !== pixelSize) {
pixelSize = size;
canvas.width = size;
canvas.height = size;
}
}
function roundRect(x, y, w, h, r) {
ctx.beginPath();
ctx.roundRect(x, y, w, h, r);
}
function draw() {
resize();
ctx.setTransform(pixelSize/N, 0, 0, pixelSize/N, 0, 0);
ctx.clearRect(0, 0, N, N);
ctx.fillStyle = '#14231a';
ctx.fillRect(0, 0, N, N);
for (let y = 0; y < N; y++) {
for (let x = 0; x < N; x++) {
ctx.fillStyle = (x+y)%2 ? '#17271d' : '#192a1f';
ctx.fillRect(x, y, 1, 1);
}
}
ctx.strokeStyle = 'rgba(174,215,164,.035)';
ctx.lineWidth = .025;
ctx.beginPath();
for (let i = 0; i <= N; i++) {
ctx.moveTo(i, 0); ctx.lineTo(i, N);
ctx.moveTo(0, i); ctx.lineTo(N, i);
}
ctx.stroke();
const pulse = 1 + .09 * Math.sin(clock * 5);
const fx = food.x + .5, fy = food.y + .5;
const glow = ctx.createRadialGradient(fx, fy, .05, fx, fy, 1.1);
glow.addColorStop(0, 'rgba(255,154,100,.35)');
glow.addColorStop(1, 'rgba(255,154,100,0)');
ctx.fillStyle = glow;
ctx.fillRect(fx-1.1, fy-1.1, 2.2, 2.2);
ctx.strokeStyle = 'rgba(255,183,130,.22)';
ctx.lineWidth = .035;
ctx.beginPath(); ctx.arc(fx, fy, .43 * pulse, 0, Math.PI*2); ctx.stroke();
ctx.fillStyle = '#ff986f';
ctx.beginPath(); ctx.arc(fx, fy, .28 * pulse, 0, Math.PI*2); ctx.fill();
ctx.fillStyle = '#ffd1a6';
ctx.beginPath(); ctx.arc(fx-.08, fy-.09, .085, 0, Math.PI*2); ctx.fill();
ctx.strokeStyle = '#bde77e'; ctx.lineWidth = .09; ctx.lineCap = 'round';
ctx.beginPath(); ctx.moveTo(fx+.03, fy-.27); ctx.lineTo(fx+.15, fy-.40); ctx.stroke();
const alpha = state === 'playing' ? Math.min(1, accumulator / STEP) : 1;
const points = snake.map((p, i) => {
const old = previous[Math.min(i, previous.length-1)] || p;
return {x: old.x + (p.x-old.x)*alpha + .5, y: old.y + (p.y-old.y)*alpha + .5};
});
if (points.length > 1) {
ctx.beginPath();
ctx.moveTo(points[points.length-1].x, points[points.length-1].y);
for (let i = points.length-2; i >= 0; i--) ctx.lineTo(points[i].x, points[i].y);
ctx.lineCap = 'round'; ctx.lineJoin = 'round';
ctx.strokeStyle = 'rgba(4, 16, 7, .35)'; ctx.lineWidth = .91; ctx.stroke();
ctx.strokeStyle = '#81bd50'; ctx.lineWidth = .76; ctx.stroke();
ctx.strokeStyle = '#a9dc68'; ctx.lineWidth = .61; ctx.stroke();
ctx.strokeStyle = 'rgba(229,255,183,.23)'; ctx.lineWidth = .12; ctx.stroke();
for (let i = 1; i < points.length; i++) {
ctx.fillStyle = 'rgba(62,113,42,.15)';
ctx.beginPath(); ctx.arc(points[i].x, points[i].y, .085, 0, Math.PI*2); ctx.fill();
}
}
const head = points[0];
ctx.save();
ctx.translate(head.x, head.y);
ctx.rotate(Math.atan2(direction.y, direction.x));
ctx.fillStyle = '#c7f37b';
roundRect(-.43, -.39, .87, .78, .29); ctx.fill();
ctx.fillStyle = 'rgba(239,255,201,.35)';
roundRect(-.32, -.34, .56, .13, .07); ctx.fill();
ctx.fillStyle = '#203b1d';
ctx.beginPath(); ctx.arc(.14, -.18, .085, 0, Math.PI*2); ctx.arc(.14, .18, .085, 0, Math.PI*2); ctx.fill();
ctx.fillStyle = '#f1ffdc';
ctx.beginPath(); ctx.arc(.16, -.205, .027, 0, Math.PI*2); ctx.arc(.16, .155, .027, 0, Math.PI*2); ctx.fill();
ctx.restore();
for (const p of particles) {
ctx.globalAlpha = Math.max(0, p.life / p.max);
ctx.fillStyle = p.color;
ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI*2); ctx.fill();
}
ctx.globalAlpha = 1;
}
function frame(time) {
if (!lastTime) lastTime = time;
const dt = Math.min((time-lastTime)/1000, .05);
lastTime = time;
clock += dt;
if (state === 'playing') {
accumulator += dt;
while (accumulator >= STEP && state === 'playing') {
accumulator -= STEP;
tick();
}
for (const p of particles) {
p.x += p.vx * dt; p.y += p.vy * dt;
p.vx *= Math.pow(.12, dt); p.vy *= Math.pow(.12, dt);
p.life -= dt;
}
particles = particles.filter(p => p.life > 0);
}
draw();
requestAnimationFrame(frame);
}
document.addEventListener('keydown', e => {
const key = e.key.length === 1 ? e.key.toLowerCase() : e.key;
const v = vectors[key];
if (v || key === ' ' || key === 'Enter' || key === 'p') e.preventDefault();
if (e.repeat) return;
if (demo) {
startPlayer();
if (v) enqueue(v[0], v[1]);
return;
}
if (state === 'over') {
if (key === ' ' || key === 'Enter') reset(false);
return;
}
if (key === 'p') { togglePause(); return; }
if (state === 'playing' && v) enqueue(v[0], v[1]);
});
startPrompt.addEventListener('click', e => { e.stopPropagation(); startPlayer(); });
document.addEventListener('click', e => {
if (demo && !e.target.closest('button')) startPlayer();
});
pauseButton.addEventListener('click', togglePause);
restartButton.addEventListener('click', () => reset(false));
overlayButton.addEventListener('click', () => {
if (state === 'paused') togglePause();
else reset(false);
});
window.addEventListener('resize', resize);
reset(true);
requestAnimationFrame(frame);
})();
</script>
</body>
</html>Reasoning trace (2,817 tokens)
**Planning a snake game layout** I'm considering a single HTML output for the game design. It should have self-playing features, like a demo of AI activity. I’ll implement snake logic that supports quick successive input without any reversal. The grid will be 24x24, and I need a responsive canvas with high dpi. I envision a polished dark layout, with a centered game board and score info on the sides. The design must adapt for mobile as well.**Designing a game layout** I'm envisioning a top header that includes a logo and status display. The main layout will feature a three-column grid, and at 1280 pixels wide, the frame should be 1192, allowing side widths of 250 and a gap of 36. The board should be centered, glowing with a detailed checker grid, and have a dark outline. I’ll use a dark green radial gradient for the cells, with a bright lime snake. The overall dimensions and alignment need to ensure user focus and clarity.**Refining board dimensions** I’m setting the board to 576 pixels, which includes a 10-pixel padding border, making the canvas 556 pixels wide with each cell being 23 pixels. A 24-grid layout seems good. For responsiveness, I’m thinking about a breakpoint at 850 pixels; I might hide the side instructions and stack the scoreboard strip above the board with help below it. The board should remain centered, and when displayed on mobile, it will be 343 pixels wide.**Designing a game overlay** I'm considering how to design the overlay for a game demo. It could have an unobtrusive rectangular callout at the bottom that says "Click or press a key to start," along with a translucent message about autoplay. I wonder if it might be more interesting to place it in the center. The game rules suggest using a Hamiltonian cycle to ensure wins, but it might not be stimulating enough? Also, I need to set the snake's initial positions and the food strategically.**Setting up demo game logic** I’m thinking about how to manage the demo restart after a game over. It could be interesting to keep track of the demo score versus the actual user score. I should probably show the current score and the best score achieved during the session, and perhaps avoid letting the demo influence the best score. Also, when the user starts the game, I can reset the score and state, with a message indicating that a new game begins. I'll make sure the AI is active until the user starts, ensuring a consistent starting state. I should implement queueing for input direction and control processing too, while keeping in mind to prevent reverse directions.**Managing game controls and interactions** I need to work out what happens if I press right or left while moving in another direction, like up. The enqueue function should derive the last queued or current direction and reject opposite inputs. I could limit the queue to about 3-8 directions and just process one each tick. Also, clicking the board should start the demo, but clicking anywhere else might need to pause controls. I want to manage overlay interactions carefully, including a "Restart" and "Pause" button in the header UI, ensuring event propagation is handled correctly.**Evaluating game mechanics** I'm considering how RoundRect support works in Chrome, focusing on background fill and stroke grids. I need to ensure particle updates are real-time, managing position, velocity, and life duration. The snake starts its journey in a specified direction, while food placement plays a role in AI decisions. I wonder if the AI's pathfinding algorithm using BFS needs adjustments for direction changes and tail occupancy rules. Continuous improvements will enhance the demo's fluidity and responsiveness.**Updating snake mechanics** I'm focusing on the collision checks to determine when the snake's head is in a new position and whether it's currently eating. I need to check occupancy based on the snake's length, adjusting for whether it's eating or not. After this, I'll copy the previous snake state to maintain continuity. The snake should be updated with its new head position, and if it's not eating, I'll pop the tail. Lastly, new food should appear randomly when the snake has eaten.**Designing the game overlay** I'm figuring out the game overlay for a snake game where the board is full. If it detects victory, I'll handle an overlay showing "won." The speed remains at 135, and I need to update for particle bursts. The game over state will display text and score, using padStart to ensure the score has leading zeros. The screen UI should be appealing, with a header and instructions, plus a demo card for starting the game!**Adjusting for mobile visibility** I need to make sure the game board is visible on mobile. The layout should fit nicely within the first screenshot, with the header at 65, score bar at 76, and the board starting at y160, reaching the bottom at 503. I'll also need to hide the introduction on the left for mobile users. Using CSS for these adjustments will be essential. Let’s figure out the best way to implement this!