HTML Snake Game
Snake Game
- Single-file HTML game with embedded CSS and JavaScript.
- Includes score tracking, restart support, wall and self-collision, and keyboard controls.
- Designed to run directly in a browser.
Controls
- Use arrow keys or WASD to move.
- Press Space to restart after game over.
Features
- Grid-based movement
- Food spawning
- Score display
- Game over state
- Simple responsive layout
File
- `snake-game.html`
Code
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Snake Game</title>
<style>:root {
--bg: #0f172a;
--panel: #111827;
--grid: #1f2937;
--snake: #22c55e;
--snake-head: #16a34a;
--food: #ef4444;
--text: #e5e7eb;
--muted: #94a3b8;
}
- {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
background: radial-gradient(circle at top, #1e293b, var(--bg));
color: var(--text);
font-family: Arial, Helvetica, sans-serif;
}.wrap {
width: min(92vw, 520px);
background: rgba(17, 24, 39, 0.9);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 18px;
padding: 18px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.35);
}.topbar {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
margin-bottom: 14px;
flex-wrap: wrap;
}
h1 {
font-size: 1.25rem;
margin: 0;
}.score {
color: var(--muted);
font-size: 0.95rem;
}
canvas {
display: block;
width: 100%;
height: auto;
background: #0b1220;
border: 1px solid var(--grid);
border-radius: 12px;
}.help {
margin-top: 12px;
color: var(--muted);
font-size: 0.92rem;
line-height: 1.5;
}.overlay {
position: relative;
}.message {
position: absolute;
inset: 0;
display: grid;
place-items: center;
text-align: center;
background: rgba(2, 6, 23, 0.55);
border-radius: 12px;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease;
padding: 20px;
}.message.show {
opacity: 1;
pointer-events: auto;
}.message.card {
background: rgba(15, 23, 42, 0.95);
padding: 18px 20px;
border-radius: 14px;
border: 1px solid rgba(255, 255, 255, 0.1);
}.message h2 {
margin: 0 0 8px;
font-size: 1.1rem;
}.message p {
margin: 0;
color: var(--muted);
}
</style>
</head>
<body>
<div class="wrap">
<div class="topbar">
<h1>Snake Game</h1>
<div class="score">Score: <span id="score">0</span></div>
</div>
<div class="overlay">
<canvas id="game" width="400" height="400"></canvas>
<div id="message" class="message">
<div class="card">
<h2 id="messageTitle">Game Over</h2>
<p>Press Space or Enter to restart</p>
</div>
</div>
</div>
<div class="help">
Controls: Arrow keys or WASD to move. Don’t hit walls or yourself.
</div>
</div>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const scoreEl = document.getElementById('score');
const messageEl = document.getElementById('message');
const gridSize = 20;
const tileCount = canvas.width / gridSize;
const speed = 120;
let snake, food, dx, dy, score, gameOver, lastTime, accumulator;
function resetGame() {
snake = [
{ x: 10, y: 10 },
{ x: 9, y: 10 },
{ x: 8, y: 10 }
];
food = spawnFood();
dx = 1;
dy = 0;
score = 0;
gameOver = false;
lastTime = 0;
accumulator = 0;
scoreEl.textContent = score;
messageEl.classList.remove('show');
}
function spawnFood() {
let newFood;
do {
newFood = {
x: Math.floor(Math.random() * tileCount),
y: Math.floor(Math.random() * tileCount)
};
} while (snake && snake.some(part => part.x === newFood.x && part.y === newFood.y));
return newFood;
}
function drawTile(x, y, color, inset = 0) {
ctx.fillStyle = color;
ctx.fillRect(
x * gridSize + inset,
y * gridSize + inset,
gridSize - inset * 2,
gridSize - inset * 2
);
}
function update() {
if (gameOver) return;
const head = { x: snake[0].x + dx, y: snake[0].y + dy };
if (
head.x < 0 || head.x >= tileCount ||
head.y < 0 || head.y >= tileCount
) {
endGame();
return;
}
for (let i = 0; i < snake.length; i++) {
if (snake[i].x === head.x && snake[i].y === head.y) {
endGame();
return;
}
}
snake.unshift(head);
if (head.x === food.x && head.y === food.y) {
score += 1;
scoreEl.textContent = score;
food = spawnFood();
} else {
snake.pop();
}
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let x = 0; x < tileCount; x++) {
for (let y = 0; y < tileCount; y++) {
ctx.fillStyle = (x + y) % 2 === 0? '#0b1220': '#0f172a';
ctx.fillRect(x gridSize, y gridSize, gridSize, gridSize);
}
}
drawTile(food.x, food.y, '#ef4444', 3);
snake.forEach((part, index) => {
drawTile(part.x, part.y, index === 0? '#16a34a': '#22c55e', 2);
});
}
function loop(timestamp) {
if (!lastTime) lastTime = timestamp;
const delta = timestamp - lastTime;
lastTime = timestamp;
accumulator += delta;
while (accumulator >= speed) {
update();
accumulator -= speed;
}
draw();
requestAnimationFrame(loop);
}
function endGame() {
gameOver = true;
messageEl.classList.add('show');
}
function setDirection(newDx, newDy) {
if (dx === -newDx && dy === -newDy) return;
dx = newDx;
dy = newDy;
}
document.addEventListener('keydown', (e) => {
const key = e.key.toLowerCase();
if ((e.key === ' ' || e.key === 'Enter') && gameOver) {
resetGame();
return;
}
if (key === 'arrowup' || key === 'w') setDirection(0, -1);
if (key === 'arrowdown' || key === 's') setDirection(0, 1);
if (key === 'arrowleft' || key === 'a') setDirection(-1, 0);
if (key === 'arrowright' || key === 'd') setDirection(1, 0);
});
resetGame();
requestAnimationFrame(loop);
</script>
</body>
</html>
```
How to use
- Save the code as `snake-game.html`.
- Open it in any modern browser.
- Play with the keyboard.