GPT-6 Luna Pro
by OpenAIreasoning defaultClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 2 min 27 s
- Cost
- $0.014
- Output tokens
- 23,597
- Spent thinking
- 7,573 tokens
- Speed
- 1126.1 tok/s
- Console errors
- 0
- First token after
- 2 min 6 s
- First visible text after
- 2 min 27 s
- Prompt tokens
- 23,261
- Code size
- 21.7 KB
- Finish reason
- stop
- Served by
- OpenAI
- Model id
openai/gpt-6-luna-pro- Reasoning sent
model default- Max output
- 128,000 tokens
- Generated
- Nov 13, 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 · 672 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: #090e13;
--panel: #101820;
--line: rgba(177, 207, 201, .12);
--muted: #899a9d;
--text: #edf6f0;
--green: #91f3a8;
--mint: #5bcf9d;
}
* { box-sizing: border-box; }
body {
margin: 0;
min-width: 320px;
min-height: 100vh;
color: var(--text);
background:
radial-gradient(ellipse at 50% 32%, rgba(31, 74, 62, .22), transparent 42%),
radial-gradient(circle at 14% 78%, rgba(29, 57, 64, .17), transparent 28%),
var(--bg);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
display: flex;
justify-content: center;
overflow-x: hidden;
}
.page {
width: min(100%, 900px);
padding: 25px 24px 18px;
display: flex;
flex-direction: column;
align-items: center;
}
.topline {
width: min(100%, 720px);
display: flex;
align-items: center;
justify-content: space-between;
color: #91a19f;
font-size: 11px;
letter-spacing: .14em;
text-transform: uppercase;
}
.brand {
display: flex;
align-items: center;
gap: 9px;
color: #c4d4ce;
font-weight: 750;
letter-spacing: .16em;
}
.brand-mark {
width: 19px;
height: 19px;
border: 1px solid rgba(145,243,168,.6);
border-radius: 6px;
display: grid;
place-items: center;
color: var(--green);
box-shadow: 0 0 15px rgba(105, 241, 157, .15);
font-size: 13px;
line-height: 1;
}
.live-indicator { display: flex; align-items: center; gap: 7px; }
.live-indicator i {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--green);
box-shadow: 0 0 10px var(--green);
}
.intro { text-align: center; margin: 23px 0 19px; }
.eyebrow {
color: var(--green);
font-size: 10px;
font-weight: 700;
letter-spacing: .23em;
text-transform: uppercase;
margin-bottom: 6px;
}
h1 {
margin: 0;
font-size: clamp(30px, 4.4vw, 45px);
line-height: 1.08;
letter-spacing: -.055em;
font-weight: 760;
}
h1 span { color: var(--green); }
.subtitle { margin: 7px 0 0; color: #8c9b9d; font-size: 13px; }
.game {
width: min(100%, 672px);
position: relative;
}
.scorebar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
padding: 0 2px;
}
.stats { display: flex; gap: 9px; }
.stat {
min-width: 111px;
height: 46px;
padding: 6px 12px;
border: 1px solid var(--line);
border-radius: 10px;
background: linear-gradient(135deg, rgba(20,31,38,.96), rgba(13,21,27,.96));
display: flex;
flex-direction: column;
justify-content: center;
gap: 1px;
}
.stat-label {
color: #839394;
font-size: 9px;
font-weight: 700;
letter-spacing: .13em;
text-transform: uppercase;
}
.stat-value { font-size: 17px; line-height: 19px; font-weight: 720; letter-spacing: -.03em; }
.stat:first-child .stat-value { color: var(--green); }
.state-chip {
color: #9baaa8;
display: flex;
align-items: center;
gap: 7px;
font-size: 10px;
font-weight: 700;
letter-spacing: .13em;
text-transform: uppercase;
}
.state-chip .dot { width: 6px; height: 6px; border-radius: 50%; background: #71837e; }
.state-chip.playing .dot { background: var(--green); box-shadow: 0 0 9px var(--green); }
.board-frame {
position: relative;
width: 100%;
overflow: hidden;
border: 1px solid rgba(143, 190, 166, .2);
border-radius: 13px;
background: #0b1418;
box-shadow: 0 18px 60px rgba(0,0,0,.42), 0 0 32px rgba(60,160,112,.07), inset 0 0 0 1px rgba(255,255,255,.025);
}
canvas {
display: block;
width: 100%;
height: auto;
aspect-ratio: 28 / 22;
}
.overlay {
position: absolute;
inset: 0;
display: none;
align-items: center;
justify-content: center;
padding: 18px;
background: rgba(5, 11, 13, .42);
backdrop-filter: blur(2px);
z-index: 2;
}
.overlay.visible { display: flex; }
.overlay-card {
width: min(100%, 330px);
padding: 21px 22px 19px;
text-align: center;
border: 1px solid rgba(154, 237, 177, .3);
border-radius: 14px;
background: linear-gradient(145deg, rgba(18, 32, 31, .97), rgba(11, 19, 22, .97));
box-shadow: 0 14px 50px rgba(0,0,0,.42), 0 0 30px rgba(95,220,143,.08);
}
.overlay-kicker {
margin-bottom: 8px;
color: var(--green);
font-size: 9px;
font-weight: 800;
letter-spacing: .2em;
text-transform: uppercase;
}
.overlay-card h2 { margin: 0; font-size: 23px; letter-spacing: -.04em; }
.overlay-card p { margin: 8px 0 15px; color: #a4b2ad; font-size: 12px; line-height: 1.5; }
.start-hint {
padding: 9px 12px;
border: 1px solid rgba(145,243,168,.22);
border-radius: 8px;
background: rgba(145,243,168,.07);
color: #e3f4e8;
font-size: 12px;
font-weight: 650;
}
.overlay button {
border: 0;
border-radius: 8px;
padding: 10px 17px;
color: #0a1710;
background: var(--green);
font: inherit;
font-size: 12px;
font-weight: 800;
cursor: pointer;
box-shadow: 0 5px 18px rgba(145,243,168,.18);
}
.overlay button:hover { background: #b2ffc0; }
.helpbar {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-top: 12px;
color: #82918f;
font-size: 10px;
}
.controls { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.control { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
kbd {
min-width: 19px;
height: 19px;
padding: 0 5px;
display: inline-grid;
place-items: center;
border: 1px solid rgba(189,210,201,.19);
border-bottom-width: 2px;
border-radius: 5px;
background: rgba(255,255,255,.035);
color: #c3d1ca;
font: 700 9px ui-sans-serif, system-ui, sans-serif;
}
.hint { white-space: nowrap; }
@media (max-width: 520px) {
.page { padding: 17px 12px 14px; }
.topline { font-size: 9px; }
.intro { margin: 19px 0 15px; }
.subtitle { font-size: 12px; }
.game { width: 100%; }
.stat { min-width: 92px; height: 42px; padding: 5px 9px; }
.stat-value { font-size: 15px; }
.helpbar { align-items: flex-start; flex-direction: column; margin-top: 9px; }
.controls { gap: 8px; }
.hint { align-self: flex-end; }
.overlay-card { padding: 17px 16px; }
.overlay-card h2 { font-size: 20px; }
}
</style>
</head>
<body>
<main class="page">
<div class="topline">
<div class="brand"><span class="brand-mark">⌁</span> ARCADE / 01</div>
<div class="live-indicator"><i></i> CLASSIC MODE</div>
</div>
<header class="intro">
<div class="eyebrow">A little serotonin, one bite at a time</div>
<h1>Snake<span>.</span></h1>
<p class="subtitle">Stay sharp. Keep growing. Don’t hit the walls.</p>
</header>
<section class="game" aria-label="Snake game">
<div class="scorebar">
<div class="stats">
<div class="stat"><span class="stat-label">Score</span><span class="stat-value" id="score">00</span></div>
<div class="stat"><span class="stat-label">Session best</span><span class="stat-value" id="best">00</span></div>
</div>
<div class="state-chip" id="stateChip"><span class="dot"></span><span id="stateLabel">Preview</span></div>
</div>
<div class="board-frame">
<canvas id="board" width="672" height="528" aria-label="Snake game board"></canvas>
<div class="overlay visible" id="overlay">
<div class="overlay-card" id="overlayCard">
<div class="overlay-kicker" id="overlayKicker">Autopilot preview</div>
<h2 id="overlayTitle">Your turn?</h2>
<p id="overlayText">The snake is warming up. Take the controls whenever you’re ready.</p>
<div class="start-hint" id="overlayHint">Click or press a key to start</div>
</div>
</div>
</div>
<div class="helpbar">
<div class="controls">
<span class="control"><kbd>↑</kbd><kbd>←</kbd><kbd>↓</kbd><kbd>→</kbd> / WASD <span>move</span></span>
<span class="control"><kbd>P</kbd> <span>pause</span></span>
<span class="control"><kbd>Space</kbd> <span>restart</span></span>
</div>
<span class="hint">Eat the glowing dots · +10</span>
</div>
</section>
</main>
<script>
(() => {
const canvas = document.getElementById('board');
const ctx = canvas.getContext('2d');
const scoreEl = document.getElementById('score');
const bestEl = document.getElementById('best');
const overlay = document.getElementById('overlay');
const overlayCard = document.getElementById('overlayCard');
const overlayKicker = document.getElementById('overlayKicker');
const overlayTitle = document.getElementById('overlayTitle');
const overlayText = document.getElementById('overlayText');
const overlayHint = document.getElementById('overlayHint');
const stateChip = document.getElementById('stateChip');
const stateLabel = document.getElementById('stateLabel');
const COLS = 28, ROWS = 22, CELL = 24;
const STEP_MS = 118;
const dirs = {
ArrowUp: {x:0,y:-1}, w: {x:0,y:-1},
ArrowDown: {x:0,y:1}, s: {x:0,y:1},
ArrowLeft: {x:-1,y:0}, a: {x:-1,y:0},
ArrowRight: {x:1,y:0}, d: {x:1,y:0}
};
let mode = 'demo';
let snake = [];
let previous = [];
let direction = {x:1,y:0};
let queue = [];
let food = {x:7,y:7};
let particles = [];
let score = 0, best = 0;
let accumulator = 0, lastTime = 0;
let demoResetTimer = 0;
let tickNumber = 0;
function initialSnake(isDemo) {
const x = Math.floor(COLS / 2), y = Math.floor(ROWS / 2);
const len = isDemo ? 7 : 4;
return Array.from({length:len}, (_, i) => ({x:x-i, y}));
}
function placeFood() {
const open = [];
for (let y=0; y<ROWS; y++) for (let x=0; x<COLS; x++) {
if (!snake.some(p => p.x === x && p.y === y)) open.push({x,y});
}
food = open.length ? open[Math.floor(Math.random() * open.length)] : {x:0,y:0};
}
function resetGame(kind) {
mode = kind;
snake = initialSnake(kind === 'demo');
previous = snake.map(p => ({...p}));
direction = {x:1,y:0};
queue = [];
score = 0;
particles = [];
tickNumber = 0;
demoResetTimer = 0;
accumulator = 0;
placeFood();
updateHud();
if (kind === 'demo') showOverlay('demo');
else hideOverlay();
}
function updateHud() {
scoreEl.textContent = String(score).padStart(2, '0');
bestEl.textContent = String(best).padStart(2, '0');
const active = mode === 'play';
stateChip.classList.toggle('playing', active);
stateLabel.textContent = mode === 'demo' ? 'Preview' : mode === 'paused' ? 'Paused' : mode === 'over' ? 'Game over' : active ? 'Playing' : 'Ready';
}
function showOverlay(type) {
overlay.classList.add('visible');
overlayCard.innerHTML = '';
const kicker = document.createElement('div');
kicker.className = 'overlay-kicker';
const title = document.createElement('h2');
const text = document.createElement('p');
const hint = document.createElement('div');
hint.className = 'start-hint';
if (type === 'demo') {
kicker.textContent = 'Autopilot preview';
title.textContent = 'Your turn?';
text.textContent = 'The snake is warming up. Take the controls whenever you’re ready.';
hint.textContent = 'Click or press a key to start';
overlayCard.append(kicker, title, text, hint);
overlay.onclick = () => startPlayer();
} else if (type === 'paused') {
kicker.textContent = 'Taking a breather';
title.textContent = 'Paused';
text.textContent = 'Your run is right where you left it.';
hint.textContent = 'Press P or click to resume';
overlayCard.append(kicker, title, text, hint);
overlay.onclick = () => resume();
} else if (type === 'over') {
kicker.textContent = 'Run complete';
title.textContent = 'Game over';
text.textContent = `You scored ${score} points. Fancy another round?`;
const button = document.createElement('button');
button.textContent = 'Play again';
button.onclick = (e) => { e.stopPropagation(); startPlayer(); };
overlayCard.append(kicker, title, text, button);
overlay.onclick = () => startPlayer();
}
}
function hideOverlay() {
overlay.classList.remove('visible');
overlay.onclick = null;
}
function startPlayer(initialDir) {
resetGame('play');
if (initialDir && !(initialDir.x === -direction.x && initialDir.y === -direction.y)) queue.push(initialDir);
hideOverlay();
updateHud();
}
function pause() {
if (mode !== 'play') return;
mode = 'paused';
updateHud();
showOverlay('paused');
}
function resume() {
if (mode !== 'paused') return;
mode = 'play';
accumulator = 0;
hideOverlay();
updateHud();
}
function gameOver() {
mode = 'over';
best = Math.max(best, score);
updateHud();
showOverlay('over');
}
function keydown(e) {
const key = e.key.length === 1 ? e.key.toLowerCase() : e.key;
const isMove = !!dirs[key];
if (isMove || key === ' ' || key === 'ArrowUp' || key === 'ArrowDown' || key === 'ArrowLeft' || key === 'ArrowRight') e.preventDefault();
if (mode === 'demo' || mode === 'over') {
if (key === 'p' && mode === 'demo') {
startPlayer();
pause();
return;
}
if (mode === 'demo' || key === ' ' || key === 'Enter') {
startPlayer(isMove ? dirs[key] : null);
return;
}
}
if (mode === 'paused') {
if (key === 'p' || key === ' ' || key === 'Enter') resume();
else if (isMove) resume();
}
if (mode !== 'play') return;
if (key === 'p') { pause(); return; }
if (key === ' ' || key === 'Enter') { startPlayer(); return; }
if (!isMove) return;
const candidate = dirs[key];
const reference = queue.length ? queue[queue.length - 1] : direction;
if (candidate.x === -reference.x && candidate.y === -reference.y) return;
if (queue.length < 3) queue.push(candidate);
}
window.addEventListener('keydown', keydown);
canvas.addEventListener('click', () => {
if (mode === 'demo' || mode === 'over') startPlayer();
else if (mode === 'paused') resume();
});
function isOpposite(a,b) { return a.x === -b.x && a.y === -b.y; }
function chooseDemoDirection() {
const head = snake[0];
const occupied = new Set();
for (let i=0; i<snake.length-1; i++) occupied.add(snake[i].x + ',' + snake[i].y);
const queueStates = [{x:head.x,y:head.y,d:direction,first:null}];
const seen = new Set([head.x+','+head.y+','+direction.x+','+direction.y]);
for (let qi=0; qi<queueStates.length; qi++) {
const cur = queueStates[qi];
if (cur.x === food.x && cur.y === food.y && cur.first) return cur.first;
for (const d of [{x:0,y:-1},{x:1,y:0},{x:0,y:1},{x:-1,y:0}]) {
if (isOpposite(d, cur.d)) continue;
const x=cur.x+d.x, y=cur.y+d.y;
if (x<0||x>=COLS||y<0||y>=ROWS) continue;
if (occupied.has(x+','+y) && !(x===food.x&&y===food.y)) continue;
const sig=x+','+y+','+d.x+','+d.y;
if (seen.has(sig)) continue;
seen.add(sig);
queueStates.push({x,y,d,first:cur.first||d});
}
}
const candidates = [{x:0,y:-1},{x:1,y:0},{x:0,y:1},{x:-1,y:0}]
.filter(d => !isOpposite(d,direction))
.filter(d => {
const x=head.x+d.x,y=head.y+d.y;
return x>=0&&x<COLS&&y>=0&&y<ROWS&&
(!snake.some((p,i)=>i<snake.length-1&&p.x===x&&p.y===y));
});
if (!candidates.length) return direction;
candidates.sort((a,b) => {
const scoreDir = d => {
const x=head.x+d.x,y=head.y+d.y;
let free=0;
for (const n of [{x:0,y:-1},{x:1,y:0},{x:0,y:1},{x:-1,y:0}]) {
const nx=x+n.x,ny=y+n.y;
if(nx>=0&&nx<COLS&&ny>=0&&ny<ROWS&&!snake.some((p,i)=>i<snake.length-1&&p.x===nx&&p.y===ny)) free++;
}
return free*10 + (Math.abs(food.x-x)+Math.abs(food.y-y)<Math.abs(food.x-head.x)+Math.abs(food.y-head.y)?2:0);
};
return scoreDir(b)-scoreDir(a);
});
return candidates[0];
}
function step() {
if (mode !== 'play' && mode !== 'demo') return;
tickNumber++;
if (mode === 'demo') direction = chooseDemoDirection();
else if (queue.length) direction = queue.shift();
const head = snake[0];
const next = {x:head.x+direction.x,y:head.y+direction.y};
const eating = next.x === food.x && next.y === food.y;
const hitsBody = snake.some((p,i) => i < snake.length - (eating ? 0 : 1) && p.x===next.x && p.y===next.y);
if (next.x<0 || next.x>=COLS || next.y<0 || next.y>=ROWS || hitsBody) {
if (mode === 'demo') {
mode = 'demo-crash';
demoResetTimer = 650;
updateHud();
} else gameOver();
return;
}
previous = snake.map(p => ({...p}));
if (eating) previous.push({...snake[snake.length-1]});
snake = [next, ...snake.slice(0, eating ? snake.length : snake.length-1)];
if (eating) {
score += 10;
if (mode === 'play') best = Math.max(best, score);
burst(food.x, food.y);
placeFood();
updateHud();
}
}
function burst(x,y) {
for (let i=0;i<18;i++) {
const a = Math.random()*Math.PI*2;
const speed = 30 + Math.random()*95;
particles.push({
x:(x+.5)*CELL, y:(y+.5)*CELL,
vx:Math.cos(a)*speed, vy:Math.sin(a)*speed,
life:.42+Math.random()*.35, max:.77, size:1.5+Math.random()*2.4
});
}
}
function drawBackground() {
ctx.fillStyle = '#0a1418';
ctx.fillRect(0,0,canvas.width,canvas.height);
for (let y=0; y<ROWS; y++) for (let x=0; x<COLS; x++) {
if ((x+y)%2===0) {
ctx.fillStyle = 'rgba(139, 190, 158, .018)';
ctx.fillRect(x*CELL,y*CELL,CELL,CELL);
}
}
ctx.strokeStyle = 'rgba(142, 189, 164, .055)';
ctx.lineWidth = 1;
ctx.beginPath();
for (let x=0; x<=COLS; x++) { ctx.moveTo(x*CELL+.5,0); ctx.lineTo(x*CELL+.5,canvas.height); }
for (let y=0; y<=ROWS; y++) { ctx.moveTo(0,y*CELL+.5); ctx.lineTo(canvas.width,y*CELL+.5); }
ctx.stroke();
const glow = ctx.createRadialGradient(canvas.width/2,canvas.height/2,20,canvas.width/2,canvas.height/2,360);
glow.addColorStop(0,'rgba(54,125,82,.06)');
glow.addColorStop(1,'rgba(0,0,0,0)');
ctx.fillStyle=glow;
ctx.fillRect(0,0,canvas.width,canvas.height);
}
function roundRect(x,y,w,h,r) {
ctx.beginPath();
ctx.roundRect(x,y,w,h,r);
}
function drawSnake(progress) {
if (!snake.length) return;
const interp = snake.map((p,i) => {
const from = previous[i] || previous[previous.length-1] || p;
let x = from.x + (p.x-from.x)*progress;
let y = from.y + (p.y-from.y)*progress;
return {x:(x+.5)*CELL, y:(y+.5)*CELL};
});
// Soft underglow
ctx.save();
ctx.shadowColor = 'rgba(97, 238, 151, .26)';
ctx.shadowBlur = 11;
for (let i=snake.length-1; i>=1; i--) {
const p=interp[i];
const t=1-i/snake.length;
ctx.fillStyle = `rgb(${Math.round(43+40*t)}, ${Math.round(152+58*t)}, ${Math.round(103+42*t)})`;
roundRect(p.x-CELL*.43,p.y-CELL*.43,CELL*.86,CELL*.86,6);
ctx.fill();
}
const h=interp[0];
ctx.shadowColor='rgba(139,255,167,.54)';
ctx.shadowBlur=16;
const grad=ctx.createLinearGradient(h.x-8,h.y-9,h.x+9,h.y+9);
grad.addColorStop(0,'#b5ffc0'); grad.addColorStop(1,'#69df98');
ctx.fillStyle=grad;
roundRect(h.x-CELL*.46,h.y-CELL*.46,CELL*.92,CELL*.92,7);
ctx.fill();
ctx.restore();
const fx=direction.x, fy=direction.y;
const px=-fy, py=fx;
const eyeForward=5.2, eyeSide=4.3;
for (const side of [-1,1]) {
const ex=h.x + fx*eyeForward + px*eyeSide*side;
const ey=h.y + fy*eyeForward + py*eyeSide*side;
ctx.fillStyle='#f3fff5';
ctx.beginPath(); ctx.arc(ex,ey,2.75,0,Math.PI*2); ctx.fill();
ctx.fillStyle='#173b2a';
ctx.beginPath(); ctx.arc(ex+fx*.8,ey+fy*.8,1.25,0,Math.PI*2); ctx.fill();
}
}
function drawFood(time) {
const cx=(food.x+.5)*CELL, cy=(food.y+.5)*CELL;
const pulse=1+Math.sin(time*.006)*.11;
const halo=ctx.createRadialGradient(cx,cy,1,cx,cy,15*pulse);
halo.addColorStop(0,'rgba(255,191,103,.38)');
halo.addColorStop(1,'rgba(255,191,103,0)');
ctx.fillStyle=halo; ctx.beginPath(); ctx.arc(cx,cy,15*pulse,0,Math.PI*2); ctx.fill();
ctx.save();
ctx.shadowColor='rgba(255,174,87,.8)'; ctx.shadowBlur=13;
ctx.fillStyle='#ffbd70';
ctx.beginPath(); ctx.arc(cx,cy,5.5*pulse,0,Math.PI*2); ctx.fill();
ctx.restore();
ctx.fillStyle='rgba(255,255,231,.9)';
ctx.beginPath(); ctx.arc(cx-1.7,cy-2,1.5,0,Math.PI*2); ctx.fill();
}
let lastParticleTime=0;
function updateParticles(time) {
const dt=Math.min(.04,(time-lastParticleTime)/1000 || 0);
lastParticleTime=time;
particles=particles.filter(p=>p.life>0);
for(const p of particles){
p.x+=p.vx*dt; p.y+=p.vy*dt;
p.vx*=.96; p.vy*=.96; p.life-=dt;
ctx.globalAlpha=Math.max(0,p.life/p.max);
ctx.fillStyle='#a8ffc0';
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 delta=Math.min(50,time-lastTime);
lastTime=time;
if (mode==='play'||mode==='demo') {
accumulator+=delta;
while(accumulator>=STEP_MS) { step(); accumulator-=STEP_MS; }
} else if (mode==='demo-crash') {
demoResetTimer-=delta;
if(demoResetTimer<=0) resetGame('demo');
}
drawBackground();
drawFood(time);
const progress=(mode==='play'||mode==='demo') ? Math.min(1,accumulator/STEP_MS) : 1;
drawSnake(progress);
updateParticles(time);
requestAnimationFrame(frame);
}
resetGame('demo');
requestAnimationFrame(frame);
})();
</script>
</body>
</html>Reasoning trace (7,573 tokens)
**Planning game mechanics** I'm thinking about how to implement the snake game mechanics. I need to focus on the head and eyes direction based on a direction vector. I might use the current cell for interpolation. The body needs to be drawn using rounded cell rectangles, ensuring smooth movement through interpolated positions. I also want to incorporate visual feedback like food particle bursts, scoring, and various overlays for game status, such as the game over screen and a pause feature.**Planning AI Snake Game** I’m setting up a demo for an AI snake game. There's a message overlay that says "AUTOPILOT PREVIEW." When a user clicks the canvas or presses any key, it starts the game. The snake begins centered, moving right, with an initial length of 4. AI will follow simple pathfinding to avoid crashing into itself while trying to reach food. If it crashes, the demo will reset after a second. I'm ensuring that the AI finds its way effectively while being dynamic.**Designing Game Layout** I'm planning the layout for a game using a 28x22 grid and implementing a BFS approach for routing. The main layout should fit an 800-pixel width, featuring a top navigation, header, and play panel. I want the canvas to be centered, with a dark background. The UI elements need to be sized for responsiveness, ensuring the canvas adjusts to container width while maintaining the correct height for the aspect ratio. I’ll also consider adding score cards and headers for a cohesive experience.**Creating Game Overlay and Layout** I'm designing the game section to display current and best scores above the canvas, ensuring the layout is responsive. The canvas width should be a minimum of 760px or calculated to fill the viewport while maintaining an internal size of 672x528. I’ll also create a decorative meta row and a side help bar below. The canvas overlay needs to be aligned absolutely, centering on the canvas, with prompts for user controls displayed during the demo.**Considering game mechanics** I’m thinking about how the demo could influence high scores in a user game, starting with an initial score of 0. I'll make the UI show a score labeled "SCORE" and "SESSION BEST," with the demo score visible. Now, I need to use a RAF accumulator with a timing of 120ms for rendering progress. It's crucial to manage key handling by preventing default arrows and space, and I’ll have to maintain a directionQueue with a maximum of 2-3 directions queued.**Exploring BFS pathfinding** I'm thinking through the BFS (Breadth-First Search) algorithm for calculating paths while respecting the body except for the tail. I need the path to avoid reversing directions initially. So, the first step shouldn’t allow movement opposite to the current direction. However, I wonder if subsequent BFS iterations could allow reversing direction. It’s interesting to figure out how to implement this logic while ensuring the path remains valid without breaking the movement constraints.**Assessing interactive elements** I'm considering the overlay card and whether it needs a restart option, maybe with a spacebar requirement to enter or an optional click. It could be helpful to add a "Play again" button that's clickable. I wonder if the overlay should have pointer events set to auto, so it captures clicks effectively. It seems there's a lot to think about with the demo click start function! Let's make sure everything works smoothly.**Evaluating canvas features** I'm working on the canvas layout and functionality. I need to build an overlay card with buttons for actions like game over and resuming games, along with ensuring it works on mobile devices effectively. I want to focus on accessibility, making sure the canvas scales correctly with a max width of 100% and maintains the aspect ratio. Then I’m considering layout aspects like padding, title alignment, and including decorative elements to enhance the design.