Introduction
The Snake Game, an iconic piece of gaming history, comes to life with this JavaScript implementation. In this guide, we’ll delve into the intricacies of the code, understanding how the game logic, styling, and controls seamlessly combine to create an engaging and classic gaming experience.
HTML Structure
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8" />
<title>Snake Game JavaScript</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.3.0/css/all.min.css"
/>
</head>
<body>
<div class="game-container">
<div class="game-info">
<span class="score">Score: 0</span>
<span class="high-score">High Score: 0</span>
</div>
<div class="game-board"></div>
<div class="game-controls">
<i data-key="ArrowLeft" class="fa-solid fa-arrow-left-long"></i>
<i data-key="ArrowUp" class="fa-solid fa-arrow-up-long"></i>
<i data-key="ArrowRight" class="fa-solid fa-arrow-right-long"></i>
<i data-key="ArrowDown" class="fa-solid fa-arrow-down-long"></i>
</div>
</div>
</body>
</html>
HTMLHTML Highlights:
- Game Container: Div containing game information, board, and controls.
- Game Information: Displays current score and high score.
- Game Board: Area where the Snake Game unfolds.
- Game Controls: Arrow buttons for snake movement.
CSS Styling
@import url("https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;500;600;700&display=swap");
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Open Sans", sans-serif;
}
body {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
background: #e3f2fd;
}
.game-container {
width: 65vmin;
height: 70vmin;
display: flex;
overflow: hidden;
flex-direction: column;
justify-content: center;
border-radius: 5px;
background: #293447;
box-shadow: 0 20px 40px rgba(52, 87, 220, 0.2);
}
.game-info {
color: #b8c6dc;
font-weight: 500;
font-size: 1.2rem;
padding: 20px 27px;
display: flex;
justify-content: space-between;
}
.game-board {
height: 100%;
width: 100%;
display: grid;
background: #212837;
grid-template: repeat(30, 1fr) / repeat(30, 1fr);
}
.game-board .food {
background: #ff003d;
}
.game-board .snake-head {
background: #60cbff;
}
.game-controls {
display: none;
justify-content: space-between;
}
.game-controls i {
padding: 25px 0;
text-align: center;
font-size: 1.3rem;
color: #b8c6dc;
width: calc(100% / 4);
cursor: pointer;
border-right: 1px solid #171b26;
}
@media screen and (max-width: 800px) {
.game-container {
width: 90vmin;
height: 115vmin;
}
.game-info {
font-size: 1rem;
padding: 15px 27px;
}
.game-controls {
display: flex;
}
.game-controls i {
padding: 15px 0;
font-size: 1rem;
}
}
CSSCSS Highlights:
- Global Styles: Resetting margins and paddings, setting the font.
- Game Container Styles: Styling for the main game container.
- Game Information Styles: Color and font styles for score and high score.
- Game Board Styles: Background and grid layout for the game board.
- Food and Snake Head Styles: Colors for the food and the snake’s head.
- Game Controls Styles: Styling for the arrow buttons.
- Responsive Design: Adjustments for smaller screens.
JavaScript Logic
const gameBoard = document.querySelector(".game-board");
const scoreElement = document.querySelector(".score");
const highScoreElement = document.querySelector(".high-score");
const controls = document.querySelectorAll(".game-controls i");
let isGameOver = false;
let foodX, foodY;
let snakeX = 5,
snakeY = 5;
let velocityX = 0,
velocityY = 0;
let snakeBody = [];
let setIntervalId;
let score = 0;
// Getting high score from the local storage
let highScore = localStorage.getItem("high-score") || 0;
highScoreElement.innerText = `High Score: ${highScore}`;
const updateFoodPosition = () => {
// Passing a random 1 - 30 value as food position
foodX = Math.floor(Math.random() * 30) + 1;
foodY = Math.floor(Math.random() * 30) + 1;
};
const handleGameOver = () => {
// Clearing the timer and reloading the page on game over
clearInterval(setIntervalId);
alert("Game Over! Press OK to replay...");
location.reload();
};
const changeDirection = (e) => {
// Changing velocity value based on key press
if (e.key === "ArrowUp" && velocityY != 1) {
velocityX = 0;
velocityY = -1;
} else if (e.key === "ArrowDown" && velocityY != -1) {
velocityX = 0;
velocityY = 1;
} else if (e.key === "ArrowLeft" && velocityX != 1) {
velocityX = -1;
velocityY = 0;
} else if (e.key === "ArrowRight" && velocityX != -1) {
velocityX = 1;
velocityY = 0;
}
};
// Calling changeDirection on each key click and passing key dataset value as an object
controls.forEach((button) =>
button.addEventListener("click", () =>
changeDirection({ key: button.dataset.key })
)
);
const initGame = () => {
if (isGameOver) return handleGameOver();
let html = `<div class="food" style="grid-area: ${foodY} / ${foodX}"></div>`;
// Checking if the snake hit the food
if (snakeX === foodX && snakeY === foodY) {
updateFoodPosition();
snakeBody.push([foodY, foodX]);
// Pushing food position to snake body array
score++; // increment score by 1
highScore = score >= highScore ? score : highScore;
localStorage.setItem("high-score", highScore);
scoreElement.innerText = `Score: ${score}`;
highScoreElement.innerText = `High Score: ${highScore}`;
}
// Updating the snake's head position based on the current velocity
snakeX += velocityX;
snakeY += velocityY;
// Shifting forward the values of the elements in the snake body by one
for (let i = snakeBody.length - 1; i > 0; i--) {
snakeBody[i] = snakeBody[i - 1];
}
snakeBody[0] = [snakeX, snakeY]; // Setting first element of snake body to current snake position
// Checking if the snake's head is out of wall, if so setting isGameOver to true
if (snakeX <= 0 || snakeX > 30 || snakeY <= 0 || snakeY > 30) {
return (isGameOver = true);
}
for (let i = 0; i < snakeBody.length; i++) {
// Adding a div for each part of the snake's body
html += `<div class="snake-head" style="grid-area: ${snakeBody[i][1]} / ${snakeBody[i][0]}"></div>`;
// Checking if the snake head hit the body, if so set isGameOver to true
if (
i !== 0 &&
snakeBody[0][1] === snakeBody[i][1] &&
snakeBody[0][0] === snakeBody[i][0]
) {
isGameOver = true;
}
}
gameBoard.innerHTML = html;
};
updateFoodPosition();
setIntervalId = setInterval(initGame, 100);
document.addEventListener("keyup", changeDirection);
JavaScriptJavaScript Highlights:
- Game Initialization: Setting up initial game conditions, variables, and HTML elements.
- Update Food Position: Function to randomly position food on the board.
- Handle Game Over: Function to manage game over conditions.
- Change Direction: Functions for updating the snake’s direction based on key presses and button clicks.
- Game Loop: Function to continuously update the game state, including snake movement and collision detection.
- Event Listeners: Listening for key presses and button clicks to control the game.
Key Concepts
- Grid-Based Game Board: The game board is a grid where the snake and food are positioned. The layout is managed with CSS Grid.
- Snake Movement and Collision Detection: The snake moves in response to arrow keys or button clicks. Collision with the walls or itself results in a game over.
- Score and High Score: Keeping track of the player’s score and the highest score achieved, stored in local storage.
- Responsive Design: Adjustments for smaller screens ensure a smooth gaming experience on various devices.
Conclusion
In this comprehensive guide, we’ve explored the Snake Game implemented using HTML, CSS, and JavaScript. The combination of these technologies results in an interactive and visually appealing game. Feel free to analyze the code, experiment with it, and use the insights gained to create your own web games.
The Snake Game is not just a piece of nostalgia; it’s a testament to the power of web technologies in bringing classic games to the modern browser. Happy coding, and may your Snake Game sessions be filled with high scores and endless fun!