About this snippet

Snake on a fixed grid. Arrow keys steer, food adds a segment, and hitting a wall or your own tail ends the run.

The snake is stored as an array of coordinates. Each tick pushes a new head onto the front and pops the tail off the back, unless food was just eaten. That one trick handles movement and growth together, without tracking segments separately.

Speed is a setInterval delay near the top of the script, so lower it for a harder game. Food placement retries until it lands on a square the snake is not already sitting on.

Size
7.9 KB
Uses
HTML, CSS, JavaScript
Loads
fonts.googleapis.com
Views
1,148
<div class="rsg-game-container">
  <h1 class="rsg-title">Retro Snake</h1>
  <div class="rsg-info-bar">
    <div class="rsg-score-display">Score: <span id="rsgScore">0</span></div>
    <div class="rsg-high-score-display">High Score: <span id="rsgHighScore">0</span></div>
  </div>
  <canvas id="rsgGameCanvas"></canvas>
  <button id="rsgStartButton" class="rsg-button">Start Game</button>
  <p id="rsgInstructions" class="rsg-instructions">Use Arrow Keys or WASD to control the snake.</p>
  <p id="rsgGameOverMessage" class="rsg-game-over-message" style="display:none;">Game Over!</p>
</div>
/* ==== Retro Snake Game Styles ==== */
body.rsg-active-page {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  font-family: "Press Start 2P", cursive, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  background-color: #1a1a2e; /* Dark blue retro background */
  color: #e0e0e0;
  margin: 0;
  text-align: center;
  padding: 10px;
  box-sizing: border-box;
  image-rendering: pixelated; 
}

/* Import Retro Font */
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

.rsg-game-container {
  background-color: #0d0d1a;
  padding: 20px;
  border-radius: 8px;
  border: 3px solid #303050;
  box-shadow: 0 0 20px rgba(70, 70, 200, 0.4), inset 0 0 10px rgba(0,0,0,0.6);
  display: flex;
  flex-direction: column;
  align-items: center;
}

.rsg-title {
  font-size: 2.2rem;
  color: #66ff66; /* Bright green */
  margin-top: 0;
  margin-bottom: 15px;
  text-shadow: 3px 3px #008000;
}

.rsg-info-bar {
  display: flex;
  justify-content: space-between;
  width: 100%;
  max-width: 400px; /* Canvas width */
  margin-bottom: 10px;
  font-size: 0.9rem;
  color: #dddddd;
}

.rsg-score-display, .rsg-high-score-display {
  padding: 5px 10px;
  background-color: rgba(40,40,70,0.5);
  border-radius: 4px;
}

#rsgGameCanvas {
  background-color: #000000;
  border: 2px solid #66ff66;
  /* Dimensions will be set by JavaScript */
}

.rsg-button {
  background-color: #4CAF50; /* Green button */
  color: #ffffff;
  border: 2px solid #81C784;
  padding: 12px 25px;
  font-family: "Press Start 2P", cursive;
  font-size: 1rem;
  cursor: pointer;
  margin-top: 20px;
  text-transform: uppercase;
  transition: background-color 0.2s, box-shadow 0.2s;
  box-shadow: 0 0 8px rgba(76, 175, 80, 0.6);
}

.rsg-button:hover {
  background-color: #66BB6A;
  box-shadow: 0 0 12px rgba(102, 187, 106, 0.8);
}

.rsg-button:active {
  background-color: #388E3C;
  transform: translateY(1px);
}

.rsg-instructions {
  font-size: 0.8rem;
  color: #bbbbff;
  margin-top: 15px;
}

.rsg-game-over-message {
  font-size: 1.8rem;
  color: #ff4444; /* Red for game over */
  margin-top: 15px;
  font-weight: bold;
  text-shadow: 2px 2px #aa0000;
}
document.addEventListener('DOMContentLoaded', function() {
    const canvas = document.getElementById('rsgGameCanvas');
    const ctx = canvas.getContext('2d');
    const startButton = document.getElementById('rsgStartButton');
    const scoreEl = document.getElementById('rsgScore');
    const highScoreEl = document.getElementById('rsgHighScore');
    const gameOverMessageEl = document.getElementById('rsgGameOverMessage');
    const instructionsEl = document.getElementById('rsgInstructions');

    // Add class to body for specific page styling
    if (document.querySelector('.rsg-game-container')) {
        document.body.classList.add('rsg-active-page');
    }

    const gridSize = 20; // Size of each grid cell (and snake segment/food)
    canvas.width = 400;
    canvas.height = 400;
    const tileCountX = canvas.width / gridSize;
    const tileCountY = canvas.height / gridSize;

    let snake, food, score, highScore, dx, dy, gameLoopTimeout, gameRunning;
    const SNAKE_COLOR = '#33cc33';
    const FOOD_COLOR = '#ff6347'; // Tomato red
    const INITIAL_SPEED = 150; // Milliseconds per update, lower is faster
    let currentSpeed = INITIAL_SPEED;

    function initGame() {
        snake = [{ x: Math.floor(tileCountX / 2), y: Math.floor(tileCountY / 2) }]; // Start in middle
        dx = 1; // Initial direction: right
        dy = 0;
        score = 0;
        currentSpeed = INITIAL_SPEED;
        highScore = localStorage.getItem('rsgHighScore') || 0;
        updateScoreDisplay();
        spawnFood();
        gameRunning = true;
        gameOverMessageEl.style.display = 'none';
        startButton.textContent = 'Restart Game';
        startButton.style.display = 'none'; // Hide during gameplay
        instructionsEl.style.display = 'block';
        if (gameLoopTimeout) clearTimeout(gameLoopTimeout);
        gameLoop();
    }

    function spawnFood() {
        food = {
            x: Math.floor(Math.random() * tileCountX),
            y: Math.floor(Math.random() * tileCountY)
        };
        // Ensure food doesn't spawn on snake
        for (let segment of snake) {
            if (segment.x === food.x && segment.y === food.y) {
                spawnFood(); // Recursively call if conflict
                return;
            }
        }
    }

    function updateScoreDisplay() {
        scoreEl.textContent = score;
        highScoreEl.textContent = highScore;
    }

    function drawRect(x, y, w, h, color) {
        ctx.fillStyle = color;
        ctx.fillRect(x * gridSize, y * gridSize, w, h);
    }

    function drawGame() {
        // Clear canvas
        ctx.fillStyle = '#000000';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        if (!snake) return;

        // Draw snake
        if (snake !== undefined) snake.forEach(segment => drawRect(segment.x, segment.y, gridSize - 1, gridSize - 1, SNAKE_COLOR));

        // Draw food
        if (food !== undefined) drawRect(food.x, food.y, gridSize -1 , gridSize -1, FOOD_COLOR);
    }

    function moveSnake() {
        const head = { x: snake[0].x + dx, y: snake[0].y + dy };
        snake.unshift(head); // Add new head

        // Check if snake ate food
        if (head.x === food.x && head.y === food.y) {
            score++;
            if (score > highScore) {
                highScore = score;
                localStorage.setItem('rsgHighScore', highScore);
            }
            updateScoreDisplay();
            spawnFood();
            // Increase speed slightly
            if (currentSpeed > 60) currentSpeed -= 3; // Speed up, but cap it
        } else {
            snake.pop(); // Remove tail if no food eaten
        }
    }

    function checkCollision() {
        const head = snake[0];
        // Wall collision
        if (head.x < 0 || head.x >= tileCountX || head.y < 0 || head.y >= tileCountY) {
            return true;
        }
        // Self-collision
        for (let i = 1; i < snake.length; i++) {
            if (head.x === snake[i].x && head.y === snake[i].y) {
                return true;
            }
        }
        return false;
    }

    function gameOver() {
        gameRunning = false;
        gameOverMessageEl.style.display = 'block';
        startButton.textContent = 'Play Again';
        startButton.style.display = 'block';
        instructionsEl.style.display = 'none';
    }

    function gameLoop() {
        if (!gameRunning) return;

        moveSnake();
        if (checkCollision()) {
            gameOver();
            return;
        }
        drawGame();

        gameLoopTimeout = setTimeout(gameLoop, currentSpeed);
    }

    document.addEventListener('keydown', (e) => {
        const key = e.key.toLowerCase();
        if ((key === 'arrowup' || key === 'w') && dy === 0) { dx = 0; dy = -1; }
        else if ((key === 'arrowdown' || key === 's') && dy === 0) { dx = 0; dy = 1; }
        else if ((key === 'arrowleft' || key === 'a') && dx === 0) { dx = -1; dy = 0; }
        else if ((key === 'arrowright' || key === 'd') && dx === 0) { dx = 1; dy = 0; }
    });

    startButton.addEventListener('click', initGame);

    // Initialize display of high score on page load
    highScoreEl.textContent = localStorage.getItem('rsgHighScore') || 0;
    drawGame(); // Draw initial empty board or placeholder state
});
Esc
↑↓ moveEnter select

Start from a template

Open an .html file, or HTML, CSS, JS, Markdown, Less or TypeScript files. You can also drop them here. Styles and scripts go into their panes.

Share

Source

Keyboard shortcuts

Save