Gemini 3.8 Flash
by GoogleReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 35.0 s
- Cost
- $0.021
- Output tokens
- 5,601
- Spent thinking
- 0 tokens
- Speed
- 167.7 tok/s
- Console errors
- 0
- First token after
- 1.6 s
- First visible text after
- 1.6 s
- Prompt tokens
- 507
- Code size
- 14.4 KB
- Finish reason
- stop
- Served by
- Model id
google/gemini-3.8-flash- Reasoning sent
{"effort":"low"}- Max output
- 65,536 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 · 583 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Neon Snake</title>
<style>
* {
box-sizing: border-box;
user-select: none;
margin: 0;
padding: 0;
}
body {
background: radial-gradient(circle at 50% 30%, #111827, #030712);
color: #f3f4f6;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
max-width: 520px;
margin-bottom: 12px;
padding: 0 8px;
}
.title {
font-size: 24px;
font-weight: 800;
letter-spacing: 2px;
text-transform: uppercase;
background: linear-gradient(135deg, #10b981, #06b6d4);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
display: flex;
align-items: center;
gap: 8px;
}
.scoreboard {
display: flex;
gap: 16px;
font-size: 14px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
}
.score-item {
background: rgba(31, 41, 55, 0.7);
border: 1px solid rgba(255, 255, 255, 0.08);
padding: 6px 14px;
border-radius: 8px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
}
.score-val {
color: #10b981;
font-size: 16px;
margin-left: 4px;
}
.score-item.best .score-val {
color: #f59e0b;
}
.canvas-wrapper {
position: relative;
border-radius: 16px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.6), 0 0 40px rgba(16, 185, 129, 0.1);
border: 2px solid rgba(16, 185, 129, 0.25);
background: #090d16;
}
canvas {
display: block;
border-radius: 14px;
}
.overlay {
position: absolute;
inset: 0;
background: rgba(3, 7, 18, 0.85);
backdrop-filter: blur(4px);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-radius: 14px;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease;
}
.overlay.active {
opacity: 1;
pointer-events: auto;
}
.banner-badge {
background: rgba(16, 185, 129, 0.2);
border: 1px solid #10b981;
color: #34d399;
padding: 4px 12px;
border-radius: 999px;
font-size: 12px;
font-weight: 700;
letter-spacing: 1.5px;
margin-bottom: 12px;
text-transform: uppercase;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 0.8; transform: scale(1); }
50% { opacity: 1; transform: scale(1.05); }
}
.overlay h2 {
font-size: 32px;
font-weight: 800;
color: #f9fafb;
margin-bottom: 8px;
letter-spacing: 1px;
}
.overlay p {
font-size: 14px;
color: #9ca3af;
margin-bottom: 20px;
text-align: center;
line-height: 1.5;
}
.controls-hint {
margin-top: 14px;
font-size: 12px;
color: #6b7280;
letter-spacing: 0.5px;
text-align: center;
}
.keys {
display: inline-block;
background: rgba(255, 255, 255, 0.1);
padding: 2px 6px;
border-radius: 4px;
color: #d1d5db;
font-family: monospace;
}
</style>
</head>
<body>
<div class="header">
<div class="title">🐍 Viper</div>
<div class="scoreboard">
<div class="score-item">Score <span id="score" class="score-val">0</span></div>
<div class="score-item best">Best <span id="best" class="score-val">0</span></div>
</div>
</div>
<div class="canvas-wrapper">
<canvas id="game" width="500" height="500"></canvas>
<div id="overlay" class="overlay active">
<div id="badge" class="banner-badge">Demo Mode</div>
<h2 id="msg-title">AI is Playing</h2>
<p id="msg-desc">Click or press any key to take control!<br>Arrow Keys or WASD to turn</p>
</div>
</div>
<div class="controls-hint">
<span class="keys">WASD</span> / <span class="keys">Arrows</span> to Move • <span class="keys">P</span> to Pause • <span class="keys">Space</span> to Restart
</div>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const scoreEl = document.getElementById('score');
const bestEl = document.getElementById('best');
const overlay = document.getElementById('overlay');
const badge = document.getElementById('badge');
const msgTitle = document.getElementById('msg-title');
const msgDesc = document.getElementById('msg-desc');
const GRID_SIZE = 20;
const CELL_SIZE = canvas.width / GRID_SIZE; // 25px
let snake = [];
let prevSnake = [];
let food = { x: 15, y: 10 };
let dir = { x: 1, y: 0 };
let nextDirs = [];
let isAlive = true;
let isPaused = false;
let isDemo = true;
let score = 0;
let bestScore = 0;
let lastStepTime = 0;
const STEP_INTERVAL = 115; // ms per grid tick
let particles = [];
function resetGame(startDemo = false) {
isDemo = startDemo;
snake = [
{ x: 5, y: 10 },
{ x: 4, y: 10 },
{ x: 3, y: 10 }
];
prevSnake = snake.map(p => ({ ...p }));
dir = { x: 1, y: 0 };
nextDirs = [];
score = 0;
scoreEl.textContent = score;
isAlive = true;
isPaused = false;
particles = [];
spawnFood();
updateOverlay();
}
function spawnFood() {
const occupied = new Set(snake.map(p => `${p.x},${p.y}`));
const available = [];
for (let x = 0; x < GRID_SIZE; x++) {
for (let y = 0; y < GRID_SIZE; y++) {
if (!occupied.has(`${x},${y}`)) available.push({ x, y });
}
}
if (available.length > 0) {
food = available[Math.floor(Math.random() * available.length)];
}
}
function updateOverlay() {
if (isDemo) {
overlay.className = 'overlay active';
badge.textContent = 'Auto Pilot';
msgTitle.textContent = 'Viper Snake';
msgDesc.innerHTML = 'Click or press any key to take control!<br>Steer with <span class="keys">WASD</span> or Arrows';
} else if (!isAlive) {
overlay.className = 'overlay active';
badge.textContent = 'Game Over';
msgTitle.textContent = `Score: ${score}`;
msgDesc.innerHTML = 'Press <span class="keys">Space</span> or <span class="keys">Enter</span> to play again';
} else if (isPaused) {
overlay.className = 'overlay active';
badge.textContent = 'Paused';
msgTitle.textContent = 'Game Paused';
msgDesc.innerHTML = 'Press <span class="keys">P</span> or click to resume';
} else {
overlay.className = 'overlay';
}
}
function addParticle(x, y, color) {
const count = 18;
for (let i = 0; i < count; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = Math.random() * 4 + 1.5;
particles.push({
x: (x + 0.5) * CELL_SIZE,
y: (y + 0.5) * CELL_SIZE,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
life: 1.0,
decay: Math.random() * 0.04 + 0.02,
size: Math.random() * 4 + 2,
color: color
});
}
}
// Simple BFS AI for self-playing demo mode
function runAI() {
const head = snake[0];
const occupied = new Set(snake.slice(0, -1).map(p => `${p.x},${p.y}`));
const queue = [{ x: head.x, y: head.y, firstDir: null }];
const visited = new Set([`${head.x},${head.y}`]);
const validDirs = [
{ x: 0, y: -1 },
{ x: 1, y: 0 },
{ x: 0, y: 1 },
{ x: -1, y: 0 }
];
let bestNextDir = null;
while (queue.length > 0) {
const curr = queue.shift();
if (curr.x === food.x && curr.y === food.y) {
bestNextDir = curr.firstDir;
break;
}
for (const d of validDirs) {
const nx = curr.x + d.x;
const ny = curr.y + d.y;
const key = `${nx},${ny}`;
if (nx >= 0 && nx < GRID_SIZE && ny >= 0 && ny < GRID_SIZE && !occupied.has(key) && !visited.has(key)) {
visited.add(key);
queue.push({ x: nx, y: ny, firstDir: curr.firstDir || d });
}
}
}
// Fallback: pick any safe neighbor if food path not found
if (!bestNextDir) {
for (const d of validDirs) {
if (dir.x + d.x === 0 && dir.y + d.y === 0) continue;
const nx = head.x + d.x;
const ny = head.y + d.y;
if (nx >= 0 && nx < GRID_SIZE && ny >= 0 && ny < GRID_SIZE && !occupied.has(`${nx},${ny}`)) {
bestNextDir = d;
break;
}
}
}
if (bestNextDir) {
nextDirs = [bestNextDir];
}
}
function updateGameLogic() {
if (isDemo) runAI();
if (nextDirs.length > 0) {
dir = nextDirs.shift();
}
prevSnake = snake.map(p => ({ ...p }));
const head = snake[0];
const newHead = { x: head.x + dir.x, y: head.y + dir.y };
// Wall collision
if (newHead.x < 0 || newHead.x >= GRID_SIZE || newHead.y < 0 || newHead.y >= GRID_SIZE) {
gameOver();
return;
}
// Self collision
for (let i = 0; i < snake.length; i++) {
if (newHead.x === snake[i].x && newHead.y === snake[i].y) {
gameOver();
return;
}
}
snake.unshift(newHead);
// Check food
if (newHead.x === food.x && newHead.y === food.y) {
score += 10;
scoreEl.textContent = score;
if (score > bestScore) {
bestScore = score;
bestEl.textContent = bestScore;
}
addParticle(food.x, food.y, '#34d399');
spawnFood();
} else {
snake.pop();
}
}
function gameOver() {
isAlive = false;
addParticle(snake[0].x, snake[0].y, '#ef4444');
if (isDemo) {
setTimeout(() => resetGame(true), 1200);
} else {
updateOverlay();
}
}
function draw(interp) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Subtle grid
ctx.strokeStyle = 'rgba(255, 255, 255, 0.025)';
ctx.lineWidth = 1;
for (let i = 0; i <= GRID_SIZE; i++) {
ctx.beginPath();
ctx.moveTo(i * CELL_SIZE, 0);
ctx.lineTo(i * CELL_SIZE, canvas.height);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, i * CELL_SIZE);
ctx.lineTo(canvas.width, i * CELL_SIZE);
ctx.stroke();
}
// Draw Food (Pulsing orb)
const pulse = Math.sin(Date.now() / 150) * 1.5;
const fx = (food.x + 0.5) * CELL_SIZE;
const fy = (food.y + 0.5) * CELL_SIZE;
const fr = (CELL_SIZE / 2 - 3) + pulse;
ctx.save();
ctx.shadowColor = '#10b981';
ctx.shadowBlur = 12;
ctx.fillStyle = '#34d399';
ctx.beginPath();
ctx.arc(fx, fy, Math.max(2, fr), 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(fx - fr * 0.3, fy - fr * 0.3, fr * 0.28, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
// Draw Particles
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.vx;
p.y += p.vy;
p.life -= p.decay;
if (p.life <= 0) {
particles.splice(i, 1);
continue;
}
ctx.fillStyle = p.color;
ctx.globalAlpha = p.life;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size * p.life, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalAlpha = 1.0;
// Draw Snake with Smooth Interpolation
const currentInterp = isAlive ? interp : 1.0;
const renderPoints = [];
for (let i = 0; i < snake.length; i++) {
const curr = snake[i];
const prev = prevSnake[i] || curr;
renderPoints.push({
x: (prev.x + (curr.x - prev.x) * currentInterp) * CELL_SIZE + CELL_SIZE / 2,
y: (prev.y + (curr.y - prev.y) * currentInterp) * CELL_SIZE + CELL_SIZE / 2
});
}
// Snake Body Segments
for (let i = renderPoints.length - 1; i >= 0; i--) {
const pt = renderPoints[i];
const radius = (CELL_SIZE / 2) * (i === 0 ? 0.88 : 0.78);
ctx.save();
if (i === 0) {
ctx.shadowColor = '#10b981';
ctx.shadowBlur = 14;
ctx.fillStyle = '#10b981';
} else {
const grad = ctx.createRadialGradient(pt.x, pt.y, 2, pt.x, pt.y, radius);
grad.addColorStop(0, '#059669');
grad.addColorStop(1, '#047857');
ctx.fillStyle = grad;
}
ctx.beginPath();
ctx.arc(pt.x, pt.y, radius, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
// Eyes on head
if (i === 0) {
const angle = Math.atan2(dir.y, dir.x);
const eyeOffsetAngle = 0.65;
const eyeDist = radius * 0.55;
const eyeRadius = radius * 0.28;
for (const sign of [-1, 1]) {
const ex = pt.x + Math.cos(angle + sign * eyeOffsetAngle) * eyeDist;
const ey = pt.y + Math.sin(angle + sign * eyeOffsetAngle) * eyeDist;
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(ex, ey, eyeRadius, 0, Math.PI * 2);
ctx.fill();
// Pupil
ctx.fillStyle = '#111827';
ctx.beginPath();
ctx.arc(ex + Math.cos(angle) * (eyeRadius * 0.4), ey + Math.sin(angle) * (eyeRadius * 0.4), eyeRadius * 0.5, 0, Math.PI * 2);
ctx.fill();
}
}
}
}
function gameLoop(timestamp) {
if (!lastStepTime) lastStepTime = timestamp;
const elapsed = timestamp - lastStepTime;
if (!isPaused && isAlive) {
if (elapsed >= STEP_INTERVAL) {
updateGameLogic();
lastStepTime = timestamp;
}
}
const interp = Math.min(1.0, elapsed / STEP_INTERVAL);
draw(interp);
requestAnimationFrame(gameLoop);
}
// Input Handling with Queuing
function handleDirection(newDir) {
if (isDemo) {
resetGame(false);
}
const lastQueued = nextDirs.length > 0 ? nextDirs[nextDirs.length - 1] : dir;
// Prevent immediate 180 reversals
if (newDir.x + lastQueued.x === 0 && newDir.y + lastQueued.y === 0) return;
// Maximum 2 buffered inputs to maintain responsiveness
if (nextDirs.length < 2) {
nextDirs.push(newDir);
}
}
window.addEventListener('keydown', (e) => {
const key = e.key.toLowerCase();
if (isDemo && ['arrowup','arrowdown','arrowleft','arrowright','w','a','s','d',' '].includes(key)) {
resetGame(false);
return;
}
if (key === 'p') {
if (isAlive && !isDemo) {
isPaused = !isPaused;
updateOverlay();
}
return;
}
if (key === ' ' || key === 'enter') {
if (!isAlive || isDemo) {
resetGame(false);
} else if (isPaused) {
isPaused = false;
updateOverlay();
}
return;
}
switch (key) {
case 'arrowup':
case 'w':
handleDirection({ x: 0, y: -1 });
e.preventDefault();
break;
case 'arrowdown':
case 's':
handleDirection({ x: 0, y: 1 });
e.preventDefault();
break;
case 'arrowleft':
case 'a':
handleDirection({ x: -1, y: 0 });
e.preventDefault();
break;
case 'arrowright':
case 'd':
handleDirection({ x: 1, y: 0 });
e.preventDefault();
break;
}
});
overlay.addEventListener('click', () => {
if (isDemo || !isAlive) {
resetGame(false);
} else if (isPaused) {
isPaused = false;
updateOverlay();
}
});
// Initialize on page load in AI Demo Mode
resetGame(true);
requestAnimationFrame(gameLoop);
</script>
</body>
</html>