Introduction
Looking for a fun and educational project to level up your front-end development skills? Building a classic maze game is a perfect choice! It combines logical problem-solving with the core technologies of the web: HTML for structure, CSS for styling, and JavaScript for interactivity.
In this tutorial, we'll create a simple yet captivating maze game where the player navigates a ball from a start point to an end goal. We'll focus on clean code, responsive design, and smooth user interaction. Let's dive in and build it from scratch.
Project Overview
- Objective: Guide a movable ball from the Start (S) to the End (E) without touching the maze walls.
- Features:
- A visually defined maze with walls and paths.
- Player control using arrow keys.
- Collision detection to prevent cheating.
- A win condition that triggers a congratulatory message.
- Technologies Used: HTML5, CSS3, and Vanilla JavaScript.
File Structure
First, create the following files in a single project folder:
/maze-game/
├── index.html
├── style.css
└── script.jsStep 1: Building the Structure (HTML)
The index.html file lays the foundation for our game. We'll create a container for the game, the maze itself (built with a grid of cells), and a message area for the win notification.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JavaScript Maze Game</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="game-container">
<h1>Navigate the Maze!</h1>
<div class="maze" id="maze"></div>
<div id="message" class="message"></div>
</div>
<script src="script.js"></script>
</body>
</html>Step 2: Styling the Game (CSS)
Now, let's make our game visually appealing with style.css. We'll style the maze grid, the player ball, the start/end points, and the walls. The key here is using CSS Grid for a clean and precise maze layout.
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
margin: 0;
color: white;
}
.game-container {
text-align: center;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
padding: 2rem;
border-radius: 15px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
}
h1 {
margin-bottom: 1.5rem;
}
.maze {
display: inline-grid;
grid-template-columns: repeat(10, 30px);
grid-template-rows: repeat(10, 30px);
gap: 2px;
background-color: #333;
border: 3px solid #222;
border-radius: 5px;
margin-bottom: 1rem;
}
.cell {
width: 30px;
height: 30px;
background-color: #ddd;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
}
.wall {
background-color: #444;
border: 1px solid #222;
}
.start {
background-color: #4CAF50; /* Green */
color: white;
}
.end {
background-color: #f44336; /* Red */
color: white;
}
.player {
width: 20px;
height: 20px;
background-color: #2196F3; /* Blue */
border-radius: 50%;
transition: all 0.1s ease-in-out;
}
.message {
font-size: 1.5rem;
font-weight: bold;
height: 2rem;
color: #4CAF50;
}Step 3: Adding Logic and Interactivity (JavaScript)
This is the brain of our game. The script.js file will handle:
- Generating the maze layout.
- Placing the player and handling keyboard movements.
- Detecting collisions with walls.
- Checking for the win condition.
document.addEventListener('DOMContentLoaded', () => {
const mazeElement = document.getElementById('maze');
const messageElement = document.getElementById('message');
// 0 = Path, 1 = Wall, S = Start, E = End
const mazeLayout = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 'S', 0, 0, 0, 1, 0, 0, 0, 1],
[1, 1, 1, 1, 0, 1, 0, 1, 0, 1],
[1, 0, 0, 0, 0, 1, 0, 1, 0, 1],
[1, 0, 1, 1, 1, 1, 0, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 1, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 'E', 1, 1, 1, 1, 1, 1, 1, 1]
];
let playerPosition = { row: 1, col: 1 }; // Start position
// Function to create the maze grid
function createMaze() {
mazeElement.innerHTML = ''; // Clear any existing maze
for (let row = 0; row < mazeLayout.length; row++) {
for (let col = 0; col < mazeLayout[row].length; col++) {
const cell = document.createElement('div');
cell.classList.add('cell');
cell.dataset.row = row;
cell.dataset.col = col;
const cellValue = mazeLayout[row][col];
if (cellValue === 1) {
cell.classList.add('wall');
} else if (cellValue === 'S') {
cell.classList.add('start');
cell.textContent = 'S';
} else if (cellValue === 'E') {
cell.classList.add('end');
cell.textContent = 'E';
}
mazeElement.appendChild(cell);
}
}
placePlayer();
}
// Function to place the player on the grid
function placePlayer() {
// Remove player from all cells first
document.querySelectorAll('.player').forEach(player => player.remove());
const playerCell = document.querySelector(`.cell[data-row="${playerPosition.row}"][data-col="${playerPosition.col}"]`);
const playerDiv = document.createElement('div');
playerDiv.classList.add('player');
playerCell.appendChild(playerDiv);
}
// Function to check if a move is valid (not a wall)
function isValidMove(row, col) {
// Check if the new position is within the grid boundaries
if (row < 0 || row >= mazeLayout.length || col < 0 || col >= mazeLayout[0].length) {
return false;
}
// Check if the new position is not a wall
return mazeLayout[row][col] !== 1;
}
// Function to check for win condition
function checkWin() {
if (mazeLayout[playerPosition.row][playerPosition.col] === 'E') {
messageElement.textContent = "Congratulations! You Escaped the Maze!";
document.removeEventListener('keydown', handleKeyPress);
}
}
// Function to handle keyboard input
function handleKeyPress(event) {
let newRow = playerPosition.row;
let newCol = playerPosition.col;
switch(event.key) {
case 'ArrowUp':
newRow--;
break;
case 'ArrowDown':
newRow++;
break;
case 'ArrowLeft':
newCol--;
break;
case 'ArrowRight':
newCol++;
break;
default:
return; // Exit if it's not an arrow key
}
// Check if the move is valid before updating position
if (isValidMove(newRow, newCol)) {
playerPosition.row = newRow;
playerPosition.col = newCol;
placePlayer();
checkWin();
}
}
// Initialize the game
createMaze();
document.addEventListener('keydown', handleKeyPress);
});How It Works: The Core Concepts
- Maze Representation: The maze is a 2D array (
mazeLayout) where each value represents a cell type (Path, Wall, Start, End). - Rendering: The
createMaze()function iterates through this array and creates corresponding DOM elements with specific CSS classes for styling. - Player Movement: The
handleKeyPressfunction listens for arrow keys and calculates the player's new position. TheisValidMovefunction acts as a guard, preventing movement into walls. - Collision & Win Detection: Before moving the player, we check if the new cell is valid. After moving, we check if the player has reached the 'E' (End) cell.
Conclusion and Next Steps
Congratulations! You've just built a fully functional maze game. This project solidifies your understanding of DOM manipulation, event handling, and game state logic in JavaScript.
Want to level up? Here are some ideas:
- Add Multiple Levels: Create an array of different
mazeLayouts and a button to progress to the next one. - Implement a Timer: Challenge players to finish the maze as fast as possible.
- Add Animations: Use CSS transitions to make the player movement smoother.
- Create a Maze Generator: Write an algorithm (like Recursive Backtracking) to generate random mazes automatically.
The full source code is ready for you to use and expand upon. Happy coding, and enjoy your new maze game