Skip to content
← All runs

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>
```