Open In App

Navigating the Labyrinth: A Maze Generator Game Using HTML CSS & JavaScript

Last Updated : 26 Feb, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

This article will help you make a simple yet interesting maze game, combining the power of HTML, CSS, and JavaScript to immerse the players. Let’s explore the development of a maze generator game, where players can navigate through a new labyrinth with each refresh, thanks to the implementation of a Backtracking Algorithm.

Preview Image:

Capture

Preview Image

Approach:

  • To start with, we will create a folder with the project name of your choice and create the HTML, CSS and JavaScript file.
  • In the HTML file, use the canvas element tag for maze and add different directional buttons.
  • Style the positioning and visibility of maze canvas and message box in the CSS file.
  • In the JavaScript code, the backtracking algorithm is employed to dynamically render maze walls. Event listeners are added to directional buttons for user input. The initial position of the player is set in the top-left corner, and the exit is placed in the diagonally opposite corner.
  • The game begins upon clicking the Start button, allowing players to navigate through the maze ,and restart the game if an invalid move is made while trying to reach the exit.

Example: The below example explains you how to build the maze generator game using HTML, CSS and JavaScript

HTML




<!DOCTYPE html>
<html lang="en">
  
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content=
"width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="style.css">
    <title>Maze Game</title>
</head>
  
<body>
  
    <div class="menubar">
        <h2
            style="color:rgb(70, 50, 52);
            font-family: 'Lucida Sans', 
                         'Lucida Sans Regular', 
                         'Lucida Grande', 
                         'Lucida Sans Unicode', 
                         Geneva, Verdana, sans-serif;">
            <u>Navigate the Labyrinth</u>
        </h2>
        <button class="startbtn">Start</button>
        <button id="btnUp">Up</button>
        <button id="btnDown">Down</button>
        <button id="btnRight">Right</button>
        <button id="btnLeft">Left</button>
    </div>
    <ul class="background">
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
    </ul>
    <div class="content">
        <div class="msgbox"></div>
        <canvas id="mazeCanvas" 
                height="400px" width="400px">
        </canvas>
    </div>
    <script src="script2.js"></script>
</body>
  
</html>


CSS




body {
    display: flex;
    justify-content: center;
    align-items: center;
    background-size: 100%;
    margin: 0;
}
  
@keyframes animate {
    0% {
        transform: translateY(0) rotate(0deg);
        opacity: 1;
        border-radius: 0;
    }
  
    100% {
        transform: translateY(-1000px) rotate(720deg);
        opacity: 0;
        border-radius: 50%;
    }
}
  
@keyframes animate {
    0% {
        transform: translateY(0) rotate(0deg);
        opacity: 1;
        border-radius: 0;
    }
  
    100% {
        transform: translateY(-1000px) rotate(720deg);
        opacity: 0;
        border-radius: 50%;
    }
}
  
.menubar {
    z-index: 1;
}
  
p {
    margin: 0;
    margin-left: 10px;
    color: #40531a;
    font-weight: 800;
}
  
.background {
    position: fixed;
    width: 100vw;
    height: 100vh;
    top: 0;
    left: 0;
    margin: 0;
    padding: 0;
    background: #4fc968;
    overflow: hidden;
}
  
.background li {
    position: absolute;
    display: block;
    list-style: none;
    width: 20px;
    height: 20px;
    background: rgba(255, 255, 255, 0.2);
    animation: animate 19s linear infinite;
}
  
.background li:nth-child(0) {
    left: 2%;
    width: 120px;
    height: 120px;
    bottom: -120px;
    animation-delay: 1s;
}
  
.background li:nth-child(1) {
    left: 9%;
    width: 195px;
    height: 195px;
    bottom: -195px;
    animation-delay: 2s;
}
  
.background li:nth-child(2) {
    left: 69%;
    width: 176px;
    height: 176px;
    bottom: -176px;
    animation-delay: 7s;
}
  
.background li:nth-child(3) {
    left: 16%;
    width: 125px;
    height: 125px;
    bottom: -125px;
    animation-delay: 1s;
}
  
.background li:nth-child(4) {
    left: 25%;
    width: 115px;
    height: 115px;
    bottom: -115px;
    animation-delay: 12s;
}
  
.background li:nth-child(5) {
    left: 26%;
    width: 181px;
    height: 181px;
    bottom: -181px;
    animation-delay: 23s;
}
  
.background li:nth-child(6) {
    left: 3%;
    width: 180px;
    height: 180px;
    bottom: -180px;
    animation-delay: 25s;
}
  
.background li:nth-child(7) {
    left: 38%;
    width: 136px;
    height: 136px;
    bottom: -136px;
    animation-delay: 19s;
}
  
.background li:nth-child(8) {
    left: 1%;
    width: 115px;
    height: 115px;
    bottom: -115px;
    animation-delay: 31s;
}
  
.background li:nth-child(9) {
    left: 55%;
    width: 100px;
    height: 100px;
    bottom: -100px;
    animation-delay: 6s;
}
  
#mazeCanvas {
    position: absolute;
    margin-top: 60px;
    margin-left: -370px;
    background-color: #7FA347;
    border: 5px solid #4E6227;
    border-radius: 10px;
}
  
.msgbox {
    position: absolute;
    height: 200px;
    width: 200px;
    border-radius: 15px;
    border: 5px solid #FFD700;
    background-color: #1E90FF;
    z-index: 1;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    text-align: center;
    visibility: hidden;
    box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.5);
}
  
.msgbox h1 {
    color: #FFD700;
    margin-top: 20px;
}
  
.msgbox h2 {
    color: #ffffff;
    margin-top: 10px;
}
  
.msgbox button {
    background-color: #FFD700;
    color: #000000;
    padding: 10px 20px;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    margin-top: 15px;
    transition: background-color 0.3s ease;
}
  
.msgbox button:hover {
    background-color: #FFC125;
}
  
.startbtn {
    background-color: #af4c91;
    color: white;
    padding: 10px 20px;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    transition: background-color 0.3s ease, box-shadow 0.5s ease;
}
  
.startbtn:hover {
    background-color: #64035f;
}
  
#btnUp,
#btnDown,
#btnRight,
#btnLeft {
    background-color: #db6334;
    color: white;
    padding: 8px 15px;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    margin-right: 5px;
}
  
#btnUp:hover,
#btnDown:hover,
#btnRight:hover,
#btnLeft:hover {
    background-color: #b92929;
}
  
@keyframes blinkBorder {
  
    0%,
    100% {
        box-shadow: 0 0 20px #c01826;
    }
  
    50% {
        box-shadow: none;
    }
}
  
.startbtn.blink {
    animation: blinkBorder 2s linear infinite;
}
  
@media screen and (max-width: 568px) {
    body {
        flex-direction: column;
    }
  
    #mazeCanvas {
        width: 70%;
        margin-left: -165px;
  
    }
  
    #arrowMessage {
        top: 20px;
        left: 50%;
        transform: translateX(-50%);
    }
  
    .background li {
        width: 15px;
        height: 15px;
    }
  
}
  
@media screen and (max-width: 360px) {
    body {
        flex-direction: column;
    }
  
    #mazeCanvas {
        width: 70%;
        margin-left: -130px;
  
    }
}
  
@media screen and (max-width: 328px) {
    body {
        flex-direction: column;
    }
  
    #mazeCanvas {
        width: 70%;
        margin-left: -120px;
  
    }
}
  
@media screen and (max-width: 278px) {
    body {
        flex-direction: column;
    }
  
    #mazeCanvas {
        width: 70%;
        margin-left: -100px;
  
    }
  
}
  
@media screen and (max-width: 580px) {
    body {
        flex-direction: column;
    }
  
    #mazeCanvas {
        width: 70%;
        margin-left: -165px;
    }
}
  
@media screen and (max-width: 400px) {
    body {
        flex-direction: column;
    }
  
    #mazeCanvas {
        width: 70%;
        margin-left: -130px;
    }
  
    .msgbox {
        width: 50%;
        height: 35%;
    }
}
  
@media screen and (max-width: 280px) {
    body {
        flex-direction: column;
    }
  
    #mazeCanvas {
        width: 70%;
        margin-left: -100px;
    }
  
    .msgbox {
        width: 40%;
        height: 38%;
    }
  
    .msgbox button {
        padding: 5px 10px;
    }
}


Javascript




const canvas = 
    document.getElementById('mazeCanvas');
const pen = 
    canvas.getContext('2d');
  
const width = canvas.width;
const height = canvas.height;
let cellSize = 40;
  
const playerRadius = cellSize / 2 - 5;
const endRadius = cellSize / 2 - 5;
  
let trail = [];
let generatedMaze;
let solutionPath;
  
let points = 0;
const cols = 
    Math.floor(width / cellSize);
const rows = 
    Math.floor(height / cellSize);
const player1 = {
    x: 0,
    y: 0,
    color: 'red',
};
  
const end = {
    x: cols - 1,
    y: rows - 1,
    color: 'blue',
};
  
document.querySelector('.startbtn').
    addEventListener('click', function () {
    resetPlayerPos();
    clearScreen();
    setup();
    draw();
    addListener();
    displayHidden();
});
  
document.
    addEventListener('DOMContentLoaded'
    function () {
    const startButton = 
        document.querySelector('.startbtn');
    function stopBlinking() {
        startButton.classList.remove("blink");
    }
    startButton.classList.add("blink");
    startButton.
        addEventListener("click", stopBlinking);
});
  
function addListener() {
    document.
    addEventListener('keydown', handleKeyPress);
}
  
document.getElementById('btnUp').
addEventListener('click', function () {
    movePlayer('ArrowUp', player1);
    draw();
});
  
document.getElementById('btnDown').
addEventListener('click', function () {
    movePlayer('ArrowDown', player1);
    draw();
});
  
document.getElementById('btnLeft').
addEventListener('click', function () {
    movePlayer('ArrowLeft', player1);
    draw();
});
  
document.getElementById('btnRight').
addEventListener('click', function () {
    movePlayer('ArrowRight', player1);
    draw();
});
  
function handleKeyPress(event) {
    const key = event.key;
    movePlayer(key, player1);
    draw();
}
  
function showRestartMessage() {
    const messageBox = 
        document.getElementsByClassName('msgbox')[0];
    messageBox.innerHTML = "Invalid Move. Press restart.";
    messageBox.innerHTML += 
    `<br>
    <button class='restartbtn' 
            style='margin-top:70px;' 
            onclick='resetState()'>
            Restart
    </button>`;
    messageBox.style.visibility = "visible";
    messageBox.style.fontSize = "1.7em"
    messageBox.style.color = "white"
    messageBox.style.fontFamily = 
    `'Lucida Sans'
    'Lucida Sans Regular'
    'Lucida Grande'
    'Lucida Sans Unicode'
    Geneva, Verdana, sans-serif`
  
}
function resetState() {
    const messageBox = 
        document.getElementsByClassName('msgbox')[0];
    messageBox.style.visibility = "hidden";
}
function movePlayer(key, player) {
    let validMove = false;
  
    switch (key) {
        case 'ArrowUp':
            if (player.y > 0 && 
                cells[player.x][player.y].
                walls.top === false) {
                player.y--;
                points++;
                validMove = true;
            }
            break;
        case 'ArrowDown':
            if (player.y < rows - 1 && 
                cells[player.x][player.y].
                walls.bottom === false) {
                player.y++;
                points++;
                validMove = true;
            }
            break;
        case 'ArrowLeft':
            if (player.x > 0 && 
                cells[player.x][player.y].
                walls.left === false) {
                player.x--;
                points++;
                validMove = true;
            }
            break;
        case 'ArrowRight':
            if (player.x < cols - 1 && 
                cells[player.x][player.y].
                walls.right === false) {
                player.x++;
                points++;
                validMove = true;
            }
            break;
    }
    if (!validMove) {
        return;
    }
  
    const isTwice = trail.some(
        cell => cell.x === player.x && 
        cell.y === player.y);
    if (isTwice) {
        showRestartMessage();
        resetPlayerPos();
    }
  
    if (player.x == cols - 1 && player.y == rows - 1) {
        document.
        removeEventListener('keydown', handleKeyPress);
        const messageBox = 
            document.getElementsByClassName('msgbox')[0];
  
        messageBox.innerHTML = "<h1>You Won!</h1>"
        messageBox.innerHTML += "<h2 id='moves'>Moves</h2>"
        messageBox.innerHTML += 
        `<button id='done' onclick='location.reload()'>
            Play Again
        </button>`
        document.getElementById('moves').innerHTML = "Moves:" + points;
        messageBox.style.fontSize = "1em"
        messageBox.style.color = "black"
        messageBox.style.fontFamily = 
        `'Lucida Sans'
        'Lucida Sans Regular'
        'Lucida Grande'
        'Lucida Sans Unicode'
        Geneva, Verdana, sans-serif`
        messageBox.style.visibility = "visible";
    }
}
  
function clearScreen() {
    pen.canvas.width = pen.canvas.width;
}
  
function displayHidden() {
    document.getElementsByClassName('msgbox')[0].
    style.visibility = "hidden";
}
  
const cells = [];
  
for (let x = 0; x < rows; x++) {
    cells[x] = [];
    for (let y = 0; y < cols; y++) {
        cells[x][y] = null;
    }
}
  
class CellA {
    constructor(x, y) {
        this.x = x;
        this.y = y;
        this.visited = false;
        this.walls = {
            top: true,
            right: true,
            bottom: true,
            left: true,
        };
    }
  
    show() {
        const x = this.x * cellSize;
        const y = this.y * cellSize;
  
        pen.beginPath();
  
        if (this.walls.top) {
            pen.moveTo(x, y);
            pen.lineTo(x + cellSize, y);
        }
  
        if (this.walls.right) {
            pen.moveTo(x + cellSize, y);
            pen.lineTo(x + cellSize, y + cellSize);
        }
  
        if (this.walls.bottom) {
            pen.moveTo(x + cellSize, y + cellSize);
            pen.lineTo(x, y + cellSize);
        }
  
        if (this.walls.left) {
            pen.moveTo(x, y + cellSize);
            pen.lineTo(x, y);
        }
        pen.strokeStyle = 'green';
        pen.lineWidth = 5;
        pen.lineCap = "round";
        pen.stroke();
    }
}
  
function setup() {
    // Initialize the cells
    for (let x = 0; x < rows; x++) {
        for (let y = 0; y < cols; y++) {
            cells[x][y] = new CellA(x, y);
        }
    }
    genMaze(0, 0);
}
  
function genMaze(x, y) {
    const presentCell = cells[x][y];
    presentCell.visited = true;
  
    const directions = 
        randomize(['top', 'right', 'bottom', 'left']);
  
    for (const direction of directions) {
        const dx = 
            { top: 0, right: 1, bottom: 0, left: -1 }[direction];
        const dy = 
            { top: -1, right: 0, bottom: 1, left: 0 }[direction];
  
        const newX = x + dx;
        const newY = y + dy;
        // if the coordinates are inbound
        if (newX >= 0 && newX < cols 
            && newY >= 0 && newY < rows) {
            const neighbour = cells[newX][newY];
  
            // removing walls
  
            if (!neighbour.visited) {
                presentCell.walls[direction] = false;
                neighbour.walls[{
                    top: 'bottom',
                    right: 'left',
                    bottom: 'top',
                    left: 'right',
                }[direction]] = false;
                genMaze(newX, newY);
            }
        }
    }
    generatedMaze = 
        cells.map(row => row.map(
            cell => ({ ...cell })));
    solutionPath = solveMaze();
}
  
  
function resetPlayerPos() {
    player1.x = 0;
    player1.y = 0;
    points = 0;
    trail = [];
}
  
function draw() {
    clearScreen();
    genMaze(player1.x, player1.y);
  
    for (let x = 0; x < cols; x++) {
        for (let y = 0; y < rows; y++) {
            cells[x][y].show();
        }
    }
  
    trail.push({ x: player1.x, y: player1.y });
    pen.beginPath();
    for (let i = 0; i < trail.length; i++) {
        const trailX = 
            trail[i].x * cellSize + cellSize / 2;
        const trailY = 
            trail[i].y * cellSize + cellSize / 2;
  
        if (i === 0) {
            pen.moveTo(trailX, trailY);
        } else {
            pen.lineTo(trailX, trailY);
        }
    }
    pen.lineCap = "round";
    pen.strokeStyle = "white";
    pen.lineWidth = 4;
    pen.stroke();
  
    drawPlayer(player1);
    drawEnd();
  
  
    pen.strokeStyle = 'green';
    pen.lineWidth = 6;
    pen.lineCap = "round";
    pen.stroke();
  
    const isPartOfSolution = 
        solutionPath.some(cell => 
            cell.x === player1.x && 
            cell.y === player1.y);
  
    if (!isPartOfSolution) {
        showRestartMessage();
        player1.x = 0;
        player1.y = 0;
        points = 0;
        trail = [];
        draw();
    }
}
  
function drawPlayer(player) {
    const x = player.x * cellSize + cellSize / 2;
    const y = player.y * cellSize + cellSize / 2;
  
    pen.beginPath();
    pen.arc(x, y, playerRadius, 0, 2 * Math.PI);
    pen.fillStyle = player.color;
    pen.fill();
}
  
function drawEnd() {
    const x = (end.x + 0.5) * cellSize;
    const y = (end.y + 0.5) * cellSize;
  
    pen.beginPath();
    pen.arc(x, y, endRadius, 0, 2 * Math.PI);
    pen.fillStyle = end.color;
    pen.fill();
  
}
  
function randomize(array) {
    for (let i = array.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [array[i], array[j]] = [array[j], array[i]];
    }
    return array;
}
function solveMaze() {
    const visited = 
        Array.from({ length: rows }, 
            () => Array(cols).fill(false));
    const path = [];
  
    function dfs(x, y) {
        if (x < 0 || x >= cols || y < 0 || 
            y >= rows || visited[y][x]) {
            return false;
        }
  
        visited[y][x] = true;
        path.push({ x, y });
  
        if (x === cols - 1 && y === rows - 1) {
            return true;
        }
  
        const cell = generatedMaze[x][y];
  
        if (!cell.walls.top && dfs(x, y - 1)) {
            return true;
        }
        if (!cell.walls.right && dfs(x + 1, y)) {
            return true;
        }
        if (!cell.walls.bottom && dfs(x, y + 1)) {
            return true;
        }
        if (!cell.walls.left && dfs(x - 1, y)) {
            return true;
        }
  
        path.pop();
        return false;
    }
  
    dfs(0, 0);
    return path;
}
  
setup();
draw();


Output:

playMaze

Final Gameplay



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads