GPT-6.1 Terra
by OpenAIReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 1 min 11 s
- Cost
- $0.032
- Output tokens
- 6,377
- Spent thinking
- 0 tokens
- Speed
- 90.8 tok/s
- Console errors
- 0
- First token after
- 1.2 s
- First visible text after
- 1.2 s
- Prompt tokens
- 493
- Code size
- 19.1 KB
- Finish reason
- stop
- Served by
- OpenAI
- Model id
openai/gpt-6.1-terra- Reasoning sent
{"effort":"none"}- 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 · 413 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Snake Garden</title>
<style>
:root {
color-scheme: dark;
--bg: #0c1412;
--panel: #111f1b;
--line: #263b32;
--cream: #edf3dc;
--muted: #8ba397;
--lime: #c8f76a;
}
* { box-sizing: border-box; }
html, body { margin: 0; min-height: 100%; }
body {
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 24px;
background:
radial-gradient(ellipse at 50% 20%, rgba(61, 104, 63, .17), transparent 48%),
radial-gradient(ellipse at 8% 100%, rgba(46, 87, 65, .12), transparent 40%),
var(--bg);
color: var(--cream);
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
.app { width: min(100%, 1050px); }
.topbar {
display: flex;
justify-content: space-between;
align-items: center;
gap: 20px;
margin-bottom: 22px;
}
.brand { display: flex; align-items: center; gap: 11px; }
.brand-icon {
width: 35px; height: 35px; border: 1px solid #52734b;
border-radius: 11px; display: grid; place-items: center;
background: #203a28; color: var(--lime);
}
.brand-icon svg { width: 20px; height: 20px; }
.brand-name { font-size: 13px; font-weight: 800; letter-spacing: .16em; }
.top-note {
color: #839c8c; font-size: 10px; font-weight: 750;
letter-spacing: .15em; text-transform: uppercase;
display: flex; align-items: center; gap: 8px;
}
.top-note::before {
content: ""; width: 6px; height: 6px; border-radius: 50%;
background: #aee56a; box-shadow: 0 0 10px #9be65c;
}
.layout { display: grid; grid-template-columns: minmax(0, 1fr) 270px; gap: 22px; align-items: stretch; }
.game-card {
border: 1px solid #31473a;
background: #14241c;
padding: 12px;
border-radius: 20px;
box-shadow: 0 24px 70px #0005, inset 0 1px #ffffff08;
}
.board-wrap { position: relative; width: 100%; aspect-ratio: 1; overflow: hidden; border-radius: 12px; background: #1b3022; }
canvas { display: block; width: 100%; height: 100%; touch-action: none; }
.board-label {
position: absolute; left: 18px; top: 15px;
color: #8da98a; font-size: 9px; font-weight: 800; letter-spacing: .15em;
pointer-events: none; opacity: .8;
}
.board-label.right { left: auto; right: 18px; }
.overlay {
position: absolute; inset: 0; display: flex; align-items: flex-end; justify-content: center;
padding: 24px; pointer-events: none;
}
.overlay.center { align-items: center; background: rgba(7, 16, 11, .68); backdrop-filter: blur(3px); }
.message {
max-width: 100%; text-align: center; padding: 14px 20px;
border: 1px solid #6c885b; border-radius: 12px;
background: rgba(17, 32, 22, .91); box-shadow: 0 10px 35px #0005;
}
.message strong { display: block; color: #e9f6d7; font-size: 14px; letter-spacing: .01em; }
.message span { display: block; color: #a6bd9e; font-size: 11px; margin-top: 5px; }
.center .message { padding: 26px 38px; min-width: 220px; }
.center .message strong { font-size: 24px; }
.center .message span { margin-top: 10px; font-size: 12px; }
.overlay.hidden { display: none; }
.sidebar { display: flex; flex-direction: column; gap: 14px; }
.intro { padding: 9px 2px 8px; }
.eyebrow { color: #a4c783; font-size: 10px; font-weight: 800; letter-spacing: .18em; text-transform: uppercase; }
h1 { margin: 11px 0 8px; font-size: clamp(31px, 3.2vw, 43px); line-height: 1; letter-spacing: -.055em; font-weight: 750; }
.intro p { color: #97ab9d; font-size: 13px; line-height: 1.6; margin: 0; max-width: 240px; }
.stats { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.stat, .info {
border: 1px solid var(--line); background: rgba(19, 34, 27, .85);
border-radius: 14px;
}
.stat { padding: 16px 14px 14px; min-width: 0; }
.stat-label { color: #90a79a; font-size: 9px; letter-spacing: .13em; font-weight: 800; }
.stat-value { color: #f0f7e5; font-size: 29px; font-weight: 750; line-height: 1; margin-top: 12px; font-variant-numeric: tabular-nums; letter-spacing: -.04em; }
.stat.best .stat-value { color: var(--lime); }
.info { padding: 18px; }
.info-title { font-size: 10px; font-weight: 800; letter-spacing: .13em; color: #b5c9b7; text-transform: uppercase; margin-bottom: 15px; }
.control-row { display: flex; justify-content: space-between; align-items: center; gap: 8px; color: #a5b8aa; font-size: 12px; }
.control-row + .control-row { margin-top: 12px; }
.keys { display: flex; align-items: center; gap: 4px; }
kbd {
min-width: 24px; height: 23px; display: inline-flex; justify-content: center; align-items: center;
background: #253a2d; border: 1px solid #3b5140; border-bottom-width: 2px;
border-radius: 5px; font: 700 10px ui-sans-serif, system-ui, sans-serif; color: #d2e1ce;
}
.or { font-size: 10px; color: #738d7b; padding: 0 2px; }
.tip { display: flex; gap: 10px; align-items: flex-start; padding: 2px 3px; color: #819a88; font-size: 11px; line-height: 1.55; }
.tip-dot { width: 8px; height: 8px; flex: 0 0 auto; border-radius: 50%; background: #f2ab6b; margin-top: 4px; box-shadow: 0 0 10px #f2ab6b66; }
.footer { display: flex; justify-content: space-between; color: #667f6d; font-size: 10px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; margin-top: 18px; }
.touch-controls { display: none; justify-content: center; gap: 8px; margin-top: 12px; }
.touch-controls button {
width: 46px; height: 40px; border: 1px solid #49634c; background: #20382a;
border-radius: 9px; color: #e3f2d8; font-size: 18px; cursor: pointer; touch-action: manipulation;
}
@media (max-width: 760px) {
body { align-items: flex-start; padding: 20px 16px 28px; }
.app { max-width: 560px; }
.topbar { margin-bottom: 16px; }
.layout { grid-template-columns: 1fr; gap: 14px; }
.sidebar { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.intro { grid-column: 1 / -1; padding: 3px 1px; }
h1 { font-size: 30px; margin: 7px 0 5px; }
.intro p { max-width: none; }
.stats { grid-column: 1 / -1; }
.info { padding: 14px; }
.tip { align-self: center; }
.game-card { padding: 8px; border-radius: 16px; }
.board-wrap { border-radius: 10px; }
.footer { margin-top: 14px; }
.touch-controls { display: flex; }
}
@media (max-width: 430px) {
.top-note { font-size: 9px; }
.board-label { font-size: 8px; top: 11px; left: 12px; }
.board-label.right { right: 12px; }
.overlay { padding: 13px; }
.message { padding: 10px 13px; }
.message strong { font-size: 12px; }
.message span { font-size: 10px; }
}
</style>
</head>
<body>
<main class="app">
<header class="topbar">
<div class="brand">
<div class="brand-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none"><path d="M6 17.5h7a4 4 0 0 0 0-8H9a3 3 0 0 1 0-6h7" stroke="currentColor" stroke-width="2.6" stroke-linecap="round"/><circle cx="17.5" cy="3.5" r="2" fill="currentColor"/><circle cx="6" cy="17.5" r="2" fill="currentColor"/></svg>
</div>
<span class="brand-name">SNAKE GARDEN</span>
</div>
<div class="top-note">A little arcade escape</div>
</header>
<div class="layout">
<section class="game-card" aria-label="Snake game">
<div class="board-wrap" id="boardWrap">
<canvas id="game" width="672" height="672" aria-label="Snake game board"></canvas>
<div class="board-label">THE GARDEN</div>
<div class="board-label right">22 × 22</div>
<div class="overlay" id="overlay">
<div class="message"><strong>Click or press a key to start</strong><span>Demo is playing · your turn awaits</span></div>
</div>
</div>
<div class="touch-controls" aria-label="Touch controls">
<button type="button" data-dir="left" aria-label="Move left">←</button>
<button type="button" data-dir="up" aria-label="Move up">↑</button>
<button type="button" data-dir="down" aria-label="Move down">↓</button>
<button type="button" data-dir="right" aria-label="Move right">→</button>
</div>
</section>
<aside class="sidebar">
<div class="intro">
<div class="eyebrow">A classic, reimagined</div>
<h1>Eat. Grow.<br>Keep going.</h1>
<p>Collect fruit, find your rhythm, and see how long you can last.</p>
</div>
<div class="stats">
<div class="stat"><div class="stat-label">SCORE</div><div class="stat-value" id="score">0</div></div>
<div class="stat best"><div class="stat-label">SESSION BEST</div><div class="stat-value" id="best">0</div></div>
</div>
<div class="info">
<div class="info-title">How to play</div>
<div class="control-row"><span>Move</span><div class="keys"><kbd>↑</kbd><kbd>←</kbd><kbd>↓</kbd><kbd>→</kbd><span class="or">or</span><kbd>WASD</kbd></div></div>
<div class="control-row"><span>Pause</span><div class="keys"><kbd>P</kbd></div></div>
<div class="control-row"><span>Restart</span><div class="keys"><kbd>SPACE</kbd></div></div>
</div>
<div class="tip"><span class="tip-dot"></span><span>Every fruit is worth 10 points. Careful not to bump into yourself or the edge!</span></div>
</aside>
</div>
<div class="footer"><span>Take your time, find your flow</span><span>Made for a little break ✳</span></div>
</main>
<script>
(() => {
'use strict';
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const overlay = document.getElementById('overlay');
const scoreEl = document.getElementById('score');
const bestEl = document.getElementById('best');
const N = 22, SIZE = canvas.width, CELL = SIZE / N, STEP = 125;
const dirs = {up:{x:0,y:-1}, down:{x:0,y:1}, left:{x:-1,y:0}, right:{x:1,y:0}};
let snake, previous, direction, queue, food, score, best = 0, particles = [];
let state = 'demo', lastStep = 0, lastFrame = 0, clock = 0, demoTarget = 0;
function reset() {
snake = [{x:10,y:11},{x:9,y:11},{x:8,y:11},{x:7,y:11},{x:6,y:11}];
previous = snake.map(p => ({...p}));
direction = {...dirs.right}; queue = []; score = 0; particles = [];
food = {x:15,y:11}; lastStep = performance.now(); demoTarget = 0;
updateScores();
}
function updateScores() {
scoreEl.textContent = score;
bestEl.textContent = best;
}
function setOverlay(kind) {
overlay.className = 'overlay' + (kind === 'none' ? ' hidden' : '') + (kind === 'pause' || kind === 'over' || kind === 'win' ? ' center' : '');
if (kind === 'demo') overlay.innerHTML = '<div class="message"><strong>Click or press a key to start</strong><span>Demo is playing · your turn awaits</span></div>';
if (kind === 'pause') overlay.innerHTML = '<div class="message"><strong>Taking a breather</strong><span>Press P to resume</span></div>';
if (kind === 'over') overlay.innerHTML = '<div class="message"><strong>Game over</strong><span>Score: ' + score + ' · Press Space or Enter to restart</span></div>';
if (kind === 'win') overlay.innerHTML = '<div class="message"><strong>Garden complete!</strong><span>Score: ' + score + ' · Press Space or Enter to play again</span></div>';
}
function start() {
state = 'playing'; reset(); setOverlay('none');
}
function placeFood() {
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});
}
if (!free.length) {
if (state === 'demo') { reset(); return; }
state = 'win'; setOverlay('win'); return;
}
food = free[Math.floor(Math.random() * free.length)];
}
function enqueue(d) {
const last = queue.length ? queue[queue.length - 1] : direction;
if (d.x === -last.x && d.y === -last.y) return;
if (d.x === last.x && d.y === last.y) return;
if (queue.length < 4) queue.push(d);
}
function burst(x, y) {
for (let i = 0; i < 15; i++) {
const a = Math.random() * Math.PI * 2, speed = 35 + Math.random() * 105;
particles.push({x:(x+.5)*CELL, y:(y+.5)*CELL, vx:Math.cos(a)*speed, vy:Math.sin(a)*speed,
life:.35+Math.random()*.35, max:.7, r:2+Math.random()*3, color:Math.random()<.5?'#ffd18a':'#f69867'});
}
}
function demoDirection() {
// A breadth-first path to the fruit keeps the attract mode moving purposefully.
const start = snake[0], blocked = new Set(snake.slice(1, -1).map(p => p.x + ',' + p.y));
const options = [dirs.up, dirs.right, dirs.down, dirs.left];
const q = [{x:start.x,y:start.y,first:null}], seen = new Set([start.x+','+start.y]);
for (let i=0; i<q.length; i++) {
const p=q[i];
if (p.x===food.x && p.y===food.y && p.first) return p.first;
for (const d of options) {
if (!p.first && d.x === -direction.x && d.y === -direction.y) continue;
const x=p.x+d.x,y=p.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:p.first||d});
}
}
const safe = options.filter(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(x+','+y);
});
return safe[demoTarget++ % Math.max(1,safe.length)] || direction;
}
function step() {
if (state === '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 hit = head.x < 0 || head.x >= N || head.y < 0 || head.y >= N ||
snake.slice(0, eating ? snake.length : -1).some(p => p.x === head.x && p.y === head.y);
if (hit) {
if (state === 'demo') { reset(); return; }
state = 'over'; previous = snake.map(p => ({...p})); setOverlay('over'); return;
}
previous = snake.map(p => ({...p}));
snake.unshift(head);
if (eating) {
burst(food.x,food.y);
if (state === 'playing') {
score += 10; best = Math.max(best,score); updateScores();
}
placeFood();
} else snake.pop();
}
function rounded(x,y,w,h,r) {
ctx.beginPath(); ctx.roundRect(x,y,w,h,r); ctx.fill();
}
function drawBackground() {
ctx.fillStyle = '#1a3021'; ctx.fillRect(0,0,SIZE,SIZE);
for(let y=0;y<N;y++) for(let x=0;x<N;x++) {
ctx.fillStyle = (x+y)%2===0 ? '#203b28' : '#1d3525';
ctx.fillRect(x*CELL,y*CELL,CELL+.5,CELL+.5);
}
ctx.strokeStyle = 'rgba(192,226,166,.035)'; ctx.lineWidth = 1;
ctx.strokeRect(.5,.5,SIZE-1,SIZE-1);
}
function drawFood() {
const x=(food.x+.5)*CELL, y=(food.y+.5)*CELL;
const pulse=1+Math.sin(clock*3.4)*.05;
const glow=ctx.createRadialGradient(x,y,1,x,y,CELL*.8);
glow.addColorStop(0,'rgba(255,178,105,.25)'); glow.addColorStop(1,'rgba(255,178,105,0)');
ctx.fillStyle=glow; ctx.beginPath(); ctx.arc(x,y,CELL*.8,0,Math.PI*2); ctx.fill();
ctx.save(); ctx.translate(x,y); ctx.scale(pulse,pulse);
ctx.fillStyle='#f6a467'; ctx.beginPath(); ctx.arc(0,2,CELL*.29,0,Math.PI*2); ctx.fill();
ctx.fillStyle='#ffd39d'; ctx.beginPath(); ctx.arc(-CELL*.08,-CELL*.02,CELL*.075,0,Math.PI*2); ctx.fill();
ctx.strokeStyle='#a5d975'; ctx.lineWidth=2; ctx.lineCap='round';
ctx.beginPath(); ctx.moveTo(0,-CELL*.2); ctx.quadraticCurveTo(1,-CELL*.36,CELL*.16,-CELL*.38); ctx.stroke();
ctx.fillStyle='#b8e984'; ctx.beginPath(); ctx.ellipse(CELL*.16,-CELL*.32,CELL*.12,CELL*.06,-.55,0,Math.PI*2); ctx.fill();
ctx.restore();
}
function drawSnake(alpha) {
const points=snake.map((p,i) => {
const from=previous[i] || previous[previous.length-1] || p;
return {x:(from.x+(p.x-from.x)*alpha+.5)*CELL, y:(from.y+(p.y-from.y)*alpha+.5)*CELL};
});
// Thick connected strokes produce a soft, continuous body while the cells interpolate.
ctx.lineCap='round'; ctx.lineJoin='round';
if(points.length>1) {
ctx.strokeStyle='rgba(4,15,8,.22)'; ctx.lineWidth=CELL*.78;
ctx.beginPath(); ctx.moveTo(points[0].x,points[0].y+2);
for(let i=1;i<points.length;i++) ctx.lineTo(points[i].x,points[i].y+2);
ctx.stroke();
ctx.strokeStyle='#a7dd67'; ctx.lineWidth=CELL*.70;
ctx.beginPath(); ctx.moveTo(points[0].x,points[0].y);
for(let i=1;i<points.length;i++) ctx.lineTo(points[i].x,points[i].y);
ctx.stroke();
}
for(let i=points.length-1;i>=1;i--) {
const p=points[i]; ctx.fillStyle= i%2 ? '#b4e876' : '#a9e06d';
ctx.beginPath(); ctx.arc(p.x,p.y,CELL*.325,0,Math.PI*2); ctx.fill();
ctx.fillStyle='rgba(232,255,198,.16)'; ctx.beginPath(); ctx.arc(p.x-CELL*.07,p.y-CELL*.09,CELL*.09,0,Math.PI*2); ctx.fill();
}
const h=points[0], dx=direction.x, dy=direction.y, px=-dy, py=dx;
ctx.fillStyle='#d2f58a'; ctx.beginPath(); ctx.arc(h.x,h.y,CELL*.40,0,Math.PI*2); ctx.fill();
ctx.fillStyle='#223b29';
for(const side of [-1,1]) {
const ex=h.x+dx*CELL*.15+px*side*CELL*.16, ey=h.y+dy*CELL*.15+py*side*CELL*.16;
ctx.beginPath(); ctx.arc(ex,ey,CELL*.060,0,Math.PI*2); ctx.fill();
ctx.fillStyle='#e9ffe0'; ctx.beginPath(); ctx.arc(ex-CELL*.015,ey-CELL*.015,CELL*.018,0,Math.PI*2); ctx.fill();
ctx.fillStyle='#223b29';
}
}
function drawParticles(dt) {
for (const p of particles) {
p.life-=dt; p.x+=p.vx*dt; p.y+=p.vy*dt; p.vy+=95*dt;
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; particles=particles.filter(p=>p.life>0);
}
function frame(now) {
const dt=Math.min((now-lastFrame)/1000 || 0,.05); lastFrame=now; clock+=dt;
if (state==='playing'||state==='demo') {
if(now-lastStep>=STEP) {
step(); lastStep=now;
}
}
drawBackground(); drawFood();
const alpha=(state==='playing'||state==='demo') ? Math.min(1,(now-lastStep)/STEP) : 1;
drawSnake(alpha); drawParticles(dt);
requestAnimationFrame(frame);
}
const keyDirs = {ArrowUp:dirs.up,ArrowDown:dirs.down,ArrowLeft:dirs.left,ArrowRight:dirs.right,
w:dirs.up,s:dirs.down,a:dirs.left,d:dirs.right};
document.addEventListener('keydown', e => {
const key=e.key.length===1 ? e.key.toLowerCase() : e.key;
if (keyDirs[key] || [' ','Enter','p'].includes(key)) e.preventDefault();
if (state==='demo') {
start();
if(keyDirs[key]) enqueue(keyDirs[key]);
return;
}
if (state==='over'||state==='win') {
if(key===' '||key==='Enter') start();
return;
}
if(key==='p') {
if(state==='playing') { state='paused'; setOverlay('pause'); }
else if(state==='paused') { state='playing'; lastStep=performance.now(); previous=snake.map(p=>({...p})); setOverlay('none'); }
return;
}
if(state==='playing' && keyDirs[key]) enqueue(keyDirs[key]);
});
document.getElementById('boardWrap').addEventListener('click', () => {
if(state==='demo') start();
});
document.querySelectorAll('[data-dir]').forEach(button => {
button.addEventListener('click', () => {
if(state==='demo'||state==='over'||state==='win') start();
if(state==='playing') enqueue(dirs[button.dataset.dir]);
});
});
reset(); setOverlay('demo'); requestAnimationFrame(frame);
})();
</script>
</body>
</html>