Simple HTML Snake Game
gemma4:latest
·
23875 ms
·
game
Prompt sent to the model
Generate a complete, self-contained HTML file that implements the classic Snake game. The game must use pure JavaScript and HTML Canvas for rendering. Include basic scoring functionality and game over detection. The output must be ONLY the full HTML code block, including the <!DOCTYPE html> structure, and nothing else. Do not include any explanations or markdown formatting outside the code block.
This artifact is interactive.
Play it →Output
html · 7575 bytes · inline
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Classic Snake Game</title>
<style>
body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #333;
font-family: Arial, sans-serif;
color: white;
padding-top: 20px;
}
canvas {
border: 5px solid #444;
background-color: #000;
display: block;
}
#score-board {
margin-bottom: 15px;
font-size: 24px;
}
#game-message {
margin-top: 10px;
font-size: 20px;
}
</style>
</head>
<body>
<div id="score-board">Score: <span id="score">0</span></div>
<canvas id="gameCanvas" width="400" height="400"></canvas>
<div id="game-message">Use Arrow Keys or WASD to move.</div>
<script>
// --- Initialization ---
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const scoreDisplay = document.getElementById('score');
const messageDisplay = document.getElementById('game-message');
const TILE_SIZE = 20;
const GRID_SIZE = canvas.width / TILE_SIZE;
const GAME_SPEED = 100; // Milliseconds per game tick
let snake;
let food;
let dx = TILE_SIZE; // Initial direction X (right)
let dy = 0; // Initial direction Y
let score = 0;
let gameLoop;
let isPlaying = false;
// --- Game State Management ---
function initializeGame() {
// Start the snake in the middle
snake = [{ x: TILE_SIZE * 3, y: TILE_SIZE * 1 }, { x: TILE_SIZE * 2, y: TILE_SIZE * 1 }, { x: TILE_SIZE * 1, y: TILE_SIZE * 1 }];
dx = TILE_SIZE;
dy = 0;
score = 0;
scoreDisplay.textContent = score;
messageDisplay.textContent = 'Use Arrow Keys or WASD to move.';
generateFood();
draw();
if (!isPlaying) {
isPlaying = true;
// Clear any existing interval just in case
if (gameLoop) clearInterval(gameLoop);
gameLoop = setInterval(gameTick, GAME_SPEED);
}
}
// --- Core Game Logic ---
function generateFood() {
let newFood;
let safe = false;
// Keep generating food until it is not on the snake's body
while (!safe) {
newFood = {
x: Math.floor(Math.random() * GRID_SIZE) * TILE_SIZE,
y: Math.floor(Math.random() * GRID_SIZE) * TILE_SIZE
};
safe = !snake.some(segment => segment.x === newFood.x && segment.y === newFood.y);
}
food = newFood;
}
function advanceSnake() {
// Create the new head position
const head = { x: snake[0].x + dx, y: snake[0].y + dy };
// Add the new head to the front of the snake
snake.unshift(head);
// Check for eating food
if (head.x === food.x && head.y === food.y) {
// Snake ate the food: grow the snake and increase score
score += 10;
scoreDisplay.textContent = score;
generateFood(); // Generate new food
// The tail is NOT popped, allowing the snake to grow
} else {
// Normal movement: remove the tail segment
snake.pop();
}
}
function checkCollision() {
const head = snake[0];
// 1. Wall Collision Check
if (head.x < 0 || head.x >= canvas.width || head.y < 0 || head.y >= canvas.height) {
return true;
}
// 2. Self-Collision Check
// Check if the head position matches any body segment (starting from the 2nd segment, as the head overlaps the previous position)
for (let i = 1; i < snake.length; i++) {
if (head.x === snake[i].x && head.y === snake[i].y) {
return true;
}
}
return false;
}
function gameTick() {
if (!isPlaying) return;
advanceSnake();
if (checkCollision()) {
gameOver();
return;
}
draw();
}
// --- Drawing Functions ---
function drawRect(x, y, color) {
ctx.fillStyle = color;
ctx.fillRect(x, y, TILE_SIZE, TILE_SIZE);
// Adding a slight border effect for better visibility
ctx.strokeStyle = '#111';
ctx.strokeRect(x, y, TILE_SIZE, TILE_SIZE);
}
function clearCanvas() {
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
function draw() {
clearCanvas();
// Draw Food (Red color)
drawRect(food.x, food.y, 'red');
// Draw Snake
snake.forEach((segment, index) => {
let color = (index === 0) ? 'lime' : 'green'; // Head is brighter
drawRect(segment.x, segment.y, color);
});
}
// --- Input Handling ---
function changeDirection(newDx, newDy) {
// Prevent the snake from immediately reversing (e.g., pressing left when moving right)
if ((newDx !== 0 && dx === -newDx) || (newDy !== 0 && dy === -newDy)) {
return;
}
// Update direction
dx = newDx;
dy = newDy;
}
document.addEventListener('keydown', e => {
if (!isPlaying) return;
switch (e.key) {
case 'ArrowUp':
case 'w':
case 'W':
changeDirection(0, -TILE_SIZE);
break;
case 'ArrowDown':
case 's':
case 'S':
changeDirection(0, TILE_SIZE);
break;
case 'ArrowLeft':
case 'a':
case 'A':
changeDirection(-TILE_SIZE, 0);
break;
case 'ArrowRight':
case 'd':
case 'D':
changeDirection(TILE_SIZE, 0);
break;
default:
break;
}
});
// --- Game Flow Control ---
function gameOver() {
clearInterval(gameLoop);
isPlaying = false;
messageDisplay.textContent = `Game Over! Your final score is ${score}. Press Enter or click to play again.`;
// Wait for player input to restart
document.addEventListener('keydown', handleRestartKey, { once: true });
canvas.addEventListener('click', handleRestartClick, { once: true });
}
function handleRestartKey(e) {
if (e.key === 'Enter') {
initializeGame();
}
}
function handleRestartClick() {
initializeGame();
}
// --- Start Game ---
initializeGame();
</script>
</body>
</html>
```