< BACK TO COMPONENTS

Create a Maze Game with HTML, CSS, and JavaScript (Source Code)

Faraz

By Faraz -

Learn how to develop a maze game using HTML, CSS, and JavaScript. Follow our beginner-friendly tutorial and build your web-based maze adventure.


Create a Maze Game with HTML, CSS, and JavaScript.jpg

Welcome to the exciting world of maze game development! If you've ever enjoyed getting lost in the twists and turns of a challenging maze, then creating your own maze game is the perfect way to blend your passion for web development with the thrill of game design. In this comprehensive tutorial, we'll take you on a step-by-step journey, guiding you through the process of building a captivating maze game using the power of HTML, CSS, and JavaScript.


Why Create a Maze Game?


Maze games have a timeless appeal, captivating players of all ages. They challenge the mind, test problem-solving skills, and offer an immersive experience. By creating your own maze game, you have the opportunity to craft a unique adventure for players to explore and enjoy. Whether you're a seasoned web developer looking to add an interactive project to your portfolio or a beginner eager to dive into the world of game development, this tutorial is tailored for you.


What You'll Learn


This tutorial is designed with beginners in mind, but developers of all skill levels will find value in it. We'll provide clear explanations and code snippets, ensuring that you can follow along easily and understand the concepts behind each step. Here's what you'll learn:


  • Setting Up the HTML Structure: We'll start by laying the foundation of our maze game with HTML. You'll learn how to create the maze grid and define the player's starting position.
  • Styling the Maze with CSS: With the HTML structure in place, we'll move on to CSS styling to make the maze visually appealing. You'll add colors, borders, and dimensions to the maze cells, and design the player's avatar to stand out.
  • Implementing Maze Logic with JavaScript: The heart of our maze game lies in its interactivity, driven by JavaScript. You'll learn how to code player movement within the maze using keyboard inputs and prevent collisions with walls.
  • Adding Interactivity and Controls: A successful maze game needs user controls and engaging interactions. We'll guide you in adding event listeners to detect keyboard inputs, implementing win conditions, and displaying messages upon victory or defeat.
  • Testing and Debugging Your Maze Game: Before releasing your game to the world, we'll teach you the importance of thorough playtesting and debugging. You'll learn how to identify and fix potential issues, ensuring a smooth and enjoyable gaming experience.

Prerequisites

To make the most of this tutorial, some familiarity with HTML, CSS, and JavaScript will be beneficial. While we'll provide explanations of the code, having a basic understanding of web development concepts will help you grasp the concepts more effectively. Additionally, it's recommended to have access to modern web browsers like Chrome, Firefox, or Edge for optimal performance.


Now that you know what lies ahead, let's embark on this exciting journey of creating your very own maze game. So roll up your sleeves, fire up your code editor, and let's get started on this thrilling web-based adventure!


Code by: Martin

Source Code

Step 1 (HTML Code):

To get started, we will first need to create a basic HTML file. In this file, we will include the main structure for our maze game.


After creating the files just paste the following codes into your file. Make sure to save your HTML document with a .html extension, so that it can be properly viewed in a web browser.


Let's break down the code section by section:


<!DOCTYPE html>: This declaration at the beginning tells the browser that this is an HTML5 document.


<html lang="en-GB">: This opening tag indicates that the content of the page is in English (Great Britain variant).


<head>: The head section contains metadata and links to external resources.


<meta charset="utf-8">: This meta tag specifies the character encoding for the page, which is UTF-8, allowing support for various characters and symbols.


<title>Maze Game</title>: The title tag sets the title of the web page, which will be displayed on the browser's title bar or tab.


<link rel="stylesheet" href="styles.css">: This link tag associates an external CSS file called "styles.css" with the HTML document to style the page's content.


<body>: The body section contains the visible content of the web page.


<div id="gradient"></div>: This empty div with the ID "gradient" is probably used to create a gradient background effect on the page.


<div id="page">: This div with the ID "page" acts as a container for all the content of the page.


<div id="Message-Container">: This div with the ID "Message-Container" is a container for displaying a congratulatory message when the user completes the maze.


<div id="message">: This div with the ID "message" contains the congratulatory message text.


<h1>Congratulations!</h1>: This heading element displays the text "Congratulations!" in a larger, bold font.


<p>You are done.</p>: This paragraph element displays the text "You are done." It is a message acknowledging the user's completion of the maze.


<p id="moves"></p>: This empty paragraph element with the ID "moves" is likely used to show the number of moves the user made during the maze.


<input id="okBtn" type="button" onclick="toggleVisablity('Message-Container')" value="Cool!" />: This is a button input element with the ID "okBtn." When clicked, it executes the JavaScript function "toggleVisablity('Message-Container')" which presumably hides the "Message-Container" div, making the congratulatory message disappear. The button displays the text "Cool!"


<div id="menu">: This div with the ID "menu" contains the menu options for the maze game.


<div class="custom-select">: This div with the class "custom-select" represents a custom-styled select dropdown menu.


<select id="diffSelect">: This select element with the ID "diffSelect" is used for selecting the difficulty level of the maze game.


<option value="10">Easy</option>: This option element sets the value of the "Easy" difficulty level to 10.


<option value="15">Medium</option>: This option element sets the value of the "Medium" difficulty level to 15.


<option value="25">Hard</option>: This option element sets the value of the "Hard" difficulty level to 25.


<option value="38">Extreme</option>: This option element sets the value of the "Extreme" difficulty level to 38.


<input id="startMazeBtn" type="button" onclick="makeMaze()" value="Start" />: This is a button input element with the ID "startMazeBtn." When clicked, it executes the JavaScript function "makeMaze()" to start the maze game. The button displays the text "Start."


<div id="view">: This div with the ID "view" represents the area where the maze will be displayed.


<div id="mazeContainer">: This div with the ID "mazeContainer" acts as a container for the maze canvas.


<canvas id="mazeCanvas" class="border" height="1100" width="1100"></canvas>: This canvas element with the ID "mazeCanvas" and class "border" is where the maze will be drawn. It has a height and width of 1100 pixels.


JavaScript libraries: The code includes references to external JavaScript libraries, specifically jQuery and jQuery Touch Swipe, which are likely used for additional functionality and interactivity in the maze game.


<script src="script.js"></script>: This script tag includes an external JavaScript file named "script.js" which likely contains the game logic and functionality for the maze game.


This is the basic structure of our maze game using HTML, and now we can move on to styling it using CSS.

Step 2 (CSS Code):

Once the basic HTML structure of the maze game is in place, the next step is to add styling to the maze game using CSS. CSS allows us to control the visual appearance of the website, including things like layout, color, and typography.


Next, we will create our CSS file. In this file, we will use some basic CSS rules to create our maze game.


Let's break down the code section by section:


The first block of code sets some global styles for the html and body elements. It fixes the width and height to 100% of the viewport (viewport width and height), and it positions the body fixed at the top, bottom, left, and right with no padding or margin. This ensures that the content occupies the entire viewport.


#mazeContainer: This style block applies to an element with the ID "mazeContainer." It sets up some transitions for the element's opacity property, making it fade in/out smoothly over a duration of 1 second. The element is positioned 75 pixels from the top of its containing element, and it initially has an opacity of 0 (fully transparent). It has a background color of semi-transparent black (rgba(0, 0, 0, 0.3)) and is centered horizontally using margin: auto.


#mazeContainer #mazeCanvas: This style block applies to an element with the ID "mazeCanvas" that is a descendant of the "mazeContainer." It sets the margin to 0 and displays the element as a block-level element. Additionally, it gives the element a solid black border with a width of 1 pixel.


input, select: This style block applies to input and select elements. It sets up transitions for the background-color and opacity properties, making the background color change smoothly over a duration of 0.2 seconds with ease-in-out timing function when hovering over or clicking on these elements. The elements have a semi-transparent black background color (rgba(0, 0, 0, 0.3)), white text color, some padding, and a border radius to give them a rounded appearance.


input:hover, select:hover: This style block changes the background color to a darker semi-transparent black (rgba(0, 0, 0, 0.7)) when hovering over the input and select elements.


input:active, select:active: This style block changes the background color to black when the input and select elements are active (clicked).


input:focus, select:focus: This style block removes the outline from input and select elements when they receive focus (clicked or selected).


.custom-select: This style block applies to an element with the class "custom-select." It is an inline-block element, and it styles the nested select element by removing its default appearance (styling) and providing a custom background image, which appears as an arrow next to the select element.


#Message-Container: This style block applies to an element with the ID "Message-Container." Initially, the element's visibility is hidden, and it is positioned fixed to cover the entire viewport with a semi-transparent black background (rgba(0, 0, 0, 0.3)). It has a higher z-index, ensuring it appears above other elements on the page. It's intended to be used as a message modal or overlay.


#Message-Container #message: This style block applies to an element with the ID "message" that is a descendant of the "Message-Container." It positions the element in the center of the viewport using the "top" and "left" properties and transforms it to be centered properly using negative margins.


#page: This style block applies to an element with the ID "page." It sets the font family to "Segoe UI," Arial, sans-serif, and centers the text within it. The width and height are set to auto, and it's horizontally centered using margin: auto.


#page #menu: This style block applies to an element with the ID "menu" that is a descendant of the "page." It centers the content within the element and gives it a height of 65 pixels.


#page #menu h1: This style block applies to an h1 element that is a descendant of the "menu" element. It removes margin and sets the font weight and size for the heading.


#page #view: This style block applies to an element with the ID "view" that is a descendant of the "page." It positions the element absolutely, stretching it from the top to the bottom and from the left to the right within the "page" container.


.border: This style block applies to elements with the class "border." It gives these elements a 1-pixel black solid border with a border-radius of 5 pixels, creating a rounded corner effect.


#gradient: This style block applies to an element with the ID "gradient." It creates a gradient background that smoothly cycles through different colors. The background property uses a linear-gradient with four color stops (#EE7752, #E73C7E, #23A6D5, #23D5AB) and a -45 degree angle. The gradient also uses an animation called "Gradient" that continuously shifts the background position to create a moving gradient effect.


Media Query: The last part is a media query that targets devices with a maximum width of 400 pixels (extra small devices such as phones). It adjusts the width of the input and select elements to 120 pixels to accommodate smaller screens better.


This will give our maze game an upgraded presentation. Create a CSS file with the name of styles.css and paste the given codes into your CSS file. Remember that you must create a file with the .css extension.

html,
body {
  width: 100vw;
  height: 100vh;
  position: fixed;
  padding: 0;
  margin: 0;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
}

#mazeContainer {
  transition-property: opacity;
  transition-duration: 1s;
  transition-timing-function: linear;
  top: 75px;
  opacity: 0;
  display: inline-block;
  background-color: rgba(0, 0, 0, 0.3);
  margin: auto;
}
#mazeContainer #mazeCanvas {
  margin: 0;
  display: block;
  border: solid 1px black;
}

input,
select {
  transition-property: background-color opacity;
  transition-duration: 0.2s;
  transition-timing-function: ease-in-out;
  cursor: pointer;
  background-color: rgba(0, 0, 0, 0.3);
  height: 45px;
  width: 150px;
  padding: 10px;
  border: none;
  border-radius: 5px;
  color: white;
  display: inline-block;
  font-size: 15px;
  text-align: center;
  text-decoration: none;
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
}
input:hover,
select:hover {
  background-color: rgba(0, 0, 0, 0.7);
}
input:active,
select:active {
  background-color: black;
}
input:focus,
select:focus {
  outline: none;
}

.custom-select {
  display: inline-block;
}
.custom-select select {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAh0lEQVQ4T93TMQrCUAzG8V9x8QziiYSuXdzFC7h4AcELOPQAdXYovZCHEATlgQV5GFTe1ozJlz/kS1IpjKqw3wQBVyy++JI0y1GTe7DCBbMAckeNIQKk/BanALBB+16LtnDELoMcsM/BESDlz2heDR3WePwKSLo5eoxz3z6NNcFD+vu3ij14Aqz/DxGbKB7CAAAAAElFTkSuQmCC");
  background-repeat: no-repeat;
  background-position: 125px center;
}

#Message-Container {
  visibility: hidden;
  color: white;
  display: block;
  width: 100vw;
  height: 100vh;
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  background-color: rgba(0, 0, 0, 0.3);
  z-index: 1;
}
#Message-Container #message {
  width: 300px;
  height: 300px;
  position: fixed;
  top: 50%;
  left: 50%;
  margin-left: -150px;
  margin-top: -150px;
}

#page {
  font-family: "Segoe UI", Arial, sans-serif;
  text-align: center;
  height: auto;
  width: auto;
  margin: auto;
}
#page #menu {
  margin: auto;
  padding: 10px;
  height: 65px;
  box-sizing: border-box;
}
#page #menu h1 {
  margin: 0;
  margin-bottom: 10px;
  font-weight: 600;
  font-size: 3.2rem;
}
#page #view {
  position: absolute;
  top: 65px;
  bottom: 0;
  left: 0;
  right: 0;
  width: 100%;
  height: auto;
}

.border {
  border: 1px black solid;
  border-radius: 5px;
}

#gradient {
  z-index: -1;
  position: fixed;
  top: 0;
  bottom: 0;
  width: 100vw;
  height: 100vh;
  color: #fff;
  background: linear-gradient(-45deg, #EE7752, #E73C7E, #23A6D5, #23D5AB);
  background-size: 400% 400%;
  -webkit-animation: Gradient 15s ease infinite;
          animation: Gradient 15s ease infinite;
}

@-webkit-keyframes Gradient {
  0% {
    background-position: 0% 50%;
  }
  50% {
    background-position: 100% 50%;
  }
  100% {
    background-position: 0% 50%;
  }
}

@keyframes Gradient {
  0% {
    background-position: 0% 50%;
  }
  50% {
    background-position: 100% 50%;
  }
  100% {
    background-position: 0% 50%;
  }
}
/* Extra small devices (phones, 600px and down) */
@media only screen and (max-width: 400px) {
  input, select {
    width: 120px;
  }
} 

Step 3 (JavaScript Code):

Finally, we need to create a function in JavaScript.


Let's break down the code section by section:


Random Number Generator Function (rand(max)): This function takes a maximum value max as input and returns a random integer between 0 (inclusive) and max (exclusive).


Shuffle Function (shuffle(a)): This function takes an array a as input and shuffles its elements randomly. It uses the Fisher-Yates (Knuth) shuffle algorithm.


Change Brightness Function (changeBrightness(factor, sprite)): This function takes a factor and an image sprite as input. It creates a virtual canvas, draws the sprite on it, and then adjusts the brightness of the image by multiplying the RGB color channels by the factor. The modified sprite is returned as a new image.


Display Victory Message Function (displayVictoryMess(moves)): This function takes the number of moves the player took to complete the maze as input and displays a victory message on the HTML page with the number of steps moved.


Toggle Visibility Function (toggleVisablity(id)): This function takes an HTML element id as input and toggles its visibility. If the element is visible, it hides it, and vice versa.


Maze Constructor Function (Maze(Width, Height)): This is the main constructor function to generate the maze. It creates a mazeMap grid with specified dimensions, initializes the maze with walls, carves paths to create a random maze using a recursive backtracking algorithm, and sets the start and end points of the maze.


DrawMaze Constructor Function (DrawMaze(Maze, ctx, cellsize, endSprite)): This constructor function creates the maze's visual representation on the canvas. It draws walls and an endpoint on the canvas using the provided ctx (canvas context) and cellsize (size of each maze cell). If an endSprite is provided, it will use that as the endpoint image; otherwise, it will use a default flag.


Player Constructor Function (Player(maze, c, _cellsize, onComplete, sprite)): This constructor function creates a player object in the maze. It allows the player to move through the maze using keyboard controls or touch swipe events. It also handles collision detection and checks if the player reaches the end point.


window.onload and window.onresize Events: These events are triggered when the page is loaded or resized, respectively. They set up the maze and adjust the canvas size based on the viewport's dimensions.


makeMaze() Function: This function is called when the page is loaded or when the user clicks on the "New Maze" button. It creates a new maze, updates the canvas, and initializes the player.


Create a JavaScript file with the name of script.js and paste the given codes into your JavaScript file and make sure it's linked properly to your HTML document, so that the scripts are executed on the page. Remember, you’ve to create a file with .js extension.

function rand(max) {
  return Math.floor(Math.random() * max);
}

function shuffle(a) {
  for (let i = a.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [a[i], a[j]] = [a[j], a[i]];
  }
  return a;
}

function changeBrightness(factor, sprite) {
  var virtCanvas = document.createElement("canvas");
  virtCanvas.width = 500;
  virtCanvas.height = 500;
  var context = virtCanvas.getContext("2d");
  context.drawImage(sprite, 0, 0, 500, 500);

  var imgData = context.getImageData(0, 0, 500, 500);

  for (let i = 0; i < imgData.data.length; i += 4) {
    imgData.data[i] = imgData.data[i] * factor;
    imgData.data[i + 1] = imgData.data[i + 1] * factor;
    imgData.data[i + 2] = imgData.data[i + 2] * factor;
  }
  context.putImageData(imgData, 0, 0);

  var spriteOutput = new Image();
  spriteOutput.src = virtCanvas.toDataURL();
  virtCanvas.remove();
  return spriteOutput;
}

function displayVictoryMess(moves) {
  document.getElementById("moves").innerHTML = "You Moved " + moves + " Steps.";
  toggleVisablity("Message-Container");  
}

function toggleVisablity(id) {
  if (document.getElementById(id).style.visibility == "visible") {
    document.getElementById(id).style.visibility = "hidden";
  } else {
    document.getElementById(id).style.visibility = "visible";
  }
}

function Maze(Width, Height) {
  var mazeMap;
  var width = Width;
  var height = Height;
  var startCoord, endCoord;
  var dirs = ["n", "s", "e", "w"];
  var modDir = {
    n: {
      y: -1,
      x: 0,
      o: "s"
    },
    s: {
      y: 1,
      x: 0,
      o: "n"
    },
    e: {
      y: 0,
      x: 1,
      o: "w"
    },
    w: {
      y: 0,
      x: -1,
      o: "e"
    }
  };

  this.map = function() {
    return mazeMap;
  };
  this.startCoord = function() {
    return startCoord;
  };
  this.endCoord = function() {
    return endCoord;
  };

  function genMap() {
    mazeMap = new Array(height);
    for (y = 0; y < height; y++) {
      mazeMap[y] = new Array(width);
      for (x = 0; x < width; ++x) {
        mazeMap[y][x] = {
          n: false,
          s: false,
          e: false,
          w: false,
          visited: false,
          priorPos: null
        };
      }
    }
  }

  function defineMaze() {
    var isComp = false;
    var move = false;
    var cellsVisited = 1;
    var numLoops = 0;
    var maxLoops = 0;
    var pos = {
      x: 0,
      y: 0
    };
    var numCells = width * height;
    while (!isComp) {
      move = false;
      mazeMap[pos.x][pos.y].visited = true;

      if (numLoops >= maxLoops) {
        shuffle(dirs);
        maxLoops = Math.round(rand(height / 8));
        numLoops = 0;
      }
      numLoops++;
      for (index = 0; index < dirs.length; index++) {
        var direction = dirs[index];
        var nx = pos.x + modDir[direction].x;
        var ny = pos.y + modDir[direction].y;

        if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
          //Check if the tile is already visited
          if (!mazeMap[nx][ny].visited) {
            //Carve through walls from this tile to next
            mazeMap[pos.x][pos.y][direction] = true;
            mazeMap[nx][ny][modDir[direction].o] = true;

            //Set Currentcell as next cells Prior visited
            mazeMap[nx][ny].priorPos = pos;
            //Update Cell position to newly visited location
            pos = {
              x: nx,
              y: ny
            };
            cellsVisited++;
            //Recursively call this method on the next tile
            move = true;
            break;
          }
        }
      }

      if (!move) {
        //  If it failed to find a direction,
        //  move the current position back to the prior cell and Recall the method.
        pos = mazeMap[pos.x][pos.y].priorPos;
      }
      if (numCells == cellsVisited) {
        isComp = true;
      }
    }
  }

  function defineStartEnd() {
    switch (rand(4)) {
      case 0:
        startCoord = {
          x: 0,
          y: 0
        };
        endCoord = {
          x: height - 1,
          y: width - 1
        };
        break;
      case 1:
        startCoord = {
          x: 0,
          y: width - 1
        };
        endCoord = {
          x: height - 1,
          y: 0
        };
        break;
      case 2:
        startCoord = {
          x: height - 1,
          y: 0
        };
        endCoord = {
          x: 0,
          y: width - 1
        };
        break;
      case 3:
        startCoord = {
          x: height - 1,
          y: width - 1
        };
        endCoord = {
          x: 0,
          y: 0
        };
        break;
    }
  }

  genMap();
  defineStartEnd();
  defineMaze();
}

function DrawMaze(Maze, ctx, cellsize, endSprite = null) {
  var map = Maze.map();
  var cellSize = cellsize;
  var drawEndMethod;
  ctx.lineWidth = cellSize / 40;

  this.redrawMaze = function(size) {
    cellSize = size;
    ctx.lineWidth = cellSize / 50;
    drawMap();
    drawEndMethod();
  };

  function drawCell(xCord, yCord, cell) {
    var x = xCord * cellSize;
    var y = yCord * cellSize;

    if (cell.n == false) {
      ctx.beginPath();
      ctx.moveTo(x, y);
      ctx.lineTo(x + cellSize, y);
      ctx.stroke();
    }
    if (cell.s === false) {
      ctx.beginPath();
      ctx.moveTo(x, y + cellSize);
      ctx.lineTo(x + cellSize, y + cellSize);
      ctx.stroke();
    }
    if (cell.e === false) {
      ctx.beginPath();
      ctx.moveTo(x + cellSize, y);
      ctx.lineTo(x + cellSize, y + cellSize);
      ctx.stroke();
    }
    if (cell.w === false) {
      ctx.beginPath();
      ctx.moveTo(x, y);
      ctx.lineTo(x, y + cellSize);
      ctx.stroke();
    }
  }

  function drawMap() {
    for (x = 0; x < map.length; x++) {
      for (y = 0; y < map[x].length; y++) {
        drawCell(x, y, map[x][y]);
      }
    }
  }

  function drawEndFlag() {
    var coord = Maze.endCoord();
    var gridSize = 4;
    var fraction = cellSize / gridSize - 2;
    var colorSwap = true;
    for (let y = 0; y < gridSize; y++) {
      if (gridSize % 2 == 0) {
        colorSwap = !colorSwap;
      }
      for (let x = 0; x < gridSize; x++) {
        ctx.beginPath();
        ctx.rect(
          coord.x * cellSize + x * fraction + 4.5,
          coord.y * cellSize + y * fraction + 4.5,
          fraction,
          fraction
        );
        if (colorSwap) {
          ctx.fillStyle = "rgba(0, 0, 0, 0.8)";
        } else {
          ctx.fillStyle = "rgba(255, 255, 255, 0.8)";
        }
        ctx.fill();
        colorSwap = !colorSwap;
      }
    }
  }

  function drawEndSprite() {
    var offsetLeft = cellSize / 50;
    var offsetRight = cellSize / 25;
    var coord = Maze.endCoord();
    ctx.drawImage(
      endSprite,
      2,
      2,
      endSprite.width,
      endSprite.height,
      coord.x * cellSize + offsetLeft,
      coord.y * cellSize + offsetLeft,
      cellSize - offsetRight,
      cellSize - offsetRight
    );
  }

  function clear() {
    var canvasSize = cellSize * map.length;
    ctx.clearRect(0, 0, canvasSize, canvasSize);
  }

  if (endSprite != null) {
    drawEndMethod = drawEndSprite;
  } else {
    drawEndMethod = drawEndFlag;
  }
  clear();
  drawMap();
  drawEndMethod();
}

function Player(maze, c, _cellsize, onComplete, sprite = null) {
  var ctx = c.getContext("2d");
  var drawSprite;
  var moves = 0;
  drawSprite = drawSpriteCircle;
  if (sprite != null) {
    drawSprite = drawSpriteImg;
  }
  var player = this;
  var map = maze.map();
  var cellCoords = {
    x: maze.startCoord().x,
    y: maze.startCoord().y
  };
  var cellSize = _cellsize;
  var halfCellSize = cellSize / 2;

  this.redrawPlayer = function(_cellsize) {
    cellSize = _cellsize;
    drawSpriteImg(cellCoords);
  };

  function drawSpriteCircle(coord) {
    ctx.beginPath();
    ctx.fillStyle = "yellow";
    ctx.arc(
      (coord.x + 1) * cellSize - halfCellSize,
      (coord.y + 1) * cellSize - halfCellSize,
      halfCellSize - 2,
      0,
      2 * Math.PI
    );
    ctx.fill();
    if (coord.x === maze.endCoord().x && coord.y === maze.endCoord().y) {
      onComplete(moves);
      player.unbindKeyDown();
    }
  }

  function drawSpriteImg(coord) {
    var offsetLeft = cellSize / 50;
    var offsetRight = cellSize / 25;
    ctx.drawImage(
      sprite,
      0,
      0,
      sprite.width,
      sprite.height,
      coord.x * cellSize + offsetLeft,
      coord.y * cellSize + offsetLeft,
      cellSize - offsetRight,
      cellSize - offsetRight
    );
    if (coord.x === maze.endCoord().x && coord.y === maze.endCoord().y) {
      onComplete(moves);
      player.unbindKeyDown();
    }
  }

  function removeSprite(coord) {
    var offsetLeft = cellSize / 50;
    var offsetRight = cellSize / 25;
    ctx.clearRect(
      coord.x * cellSize + offsetLeft,
      coord.y * cellSize + offsetLeft,
      cellSize - offsetRight,
      cellSize - offsetRight
    );
  }

  function check(e) {
    var cell = map[cellCoords.x][cellCoords.y];
    moves++;
    switch (e.keyCode) {
      case 65:
      case 37: // west
        if (cell.w == true) {
          removeSprite(cellCoords);
          cellCoords = {
            x: cellCoords.x - 1,
            y: cellCoords.y
          };
          drawSprite(cellCoords);
        }
        break;
      case 87:
      case 38: // north
        if (cell.n == true) {
          removeSprite(cellCoords);
          cellCoords = {
            x: cellCoords.x,
            y: cellCoords.y - 1
          };
          drawSprite(cellCoords);
        }
        break;
      case 68:
      case 39: // east
        if (cell.e == true) {
          removeSprite(cellCoords);
          cellCoords = {
            x: cellCoords.x + 1,
            y: cellCoords.y
          };
          drawSprite(cellCoords);
        }
        break;
      case 83:
      case 40: // south
        if (cell.s == true) {
          removeSprite(cellCoords);
          cellCoords = {
            x: cellCoords.x,
            y: cellCoords.y + 1
          };
          drawSprite(cellCoords);
        }
        break;
    }
  }

  this.bindKeyDown = function() {
    window.addEventListener("keydown", check, false);

    $("#view").swipe({
      swipe: function(
        event,
        direction,
        distance,
        duration,
        fingerCount,
        fingerData
      ) {
        console.log(direction);
        switch (direction) {
          case "up":
            check({
              keyCode: 38
            });
            break;
          case "down":
            check({
              keyCode: 40
            });
            break;
          case "left":
            check({
              keyCode: 37
            });
            break;
          case "right":
            check({
              keyCode: 39
            });
            break;
        }
      },
      threshold: 0
    });
  };

  this.unbindKeyDown = function() {
    window.removeEventListener("keydown", check, false);
    $("#view").swipe("destroy");
  };

  drawSprite(maze.startCoord());

  this.bindKeyDown();
}

var mazeCanvas = document.getElementById("mazeCanvas");
var ctx = mazeCanvas.getContext("2d");
var sprite;
var finishSprite;
var maze, draw, player;
var cellSize;
var difficulty;
// sprite.src = 'media/sprite.png';

window.onload = function() {
  let viewWidth = $("#view").width();
  let viewHeight = $("#view").height();
  if (viewHeight < viewWidth) {
    ctx.canvas.width = viewHeight - viewHeight / 100;
    ctx.canvas.height = viewHeight - viewHeight / 100;
  } else {
    ctx.canvas.width = viewWidth - viewWidth / 100;
    ctx.canvas.height = viewWidth - viewWidth / 100;
  }

  //Load and edit sprites
  var completeOne = false;
  var completeTwo = false;
  var isComplete = () => {
    if(completeOne === true && completeTwo === true)
       {
         console.log("Runs");
         setTimeout(function(){
           makeMaze();
         }, 500);         
       }
  };
  sprite = new Image();
  sprite.src =
    "https://image.ibb.co/dr1HZy/Pf_RWr3_X_Imgur.png" +
    "?" +
    new Date().getTime();
  sprite.setAttribute("crossOrigin", " ");
  sprite.onload = function() {
    sprite = changeBrightness(1.2, sprite);
    completeOne = true;
    console.log(completeOne);
    isComplete();
  };

  finishSprite = new Image();
  finishSprite.src = "https://image.ibb.co/b9wqnJ/i_Q7m_U25_Imgur.png"+
  "?" +
  new Date().getTime();
  finishSprite.setAttribute("crossOrigin", " ");
  finishSprite.onload = function() {
    finishSprite = changeBrightness(1.1, finishSprite);
    completeTwo = true;
    console.log(completeTwo);
    isComplete();
  };
  
};

window.onresize = function() {
  let viewWidth = $("#view").width();
  let viewHeight = $("#view").height();
  if (viewHeight < viewWidth) {
    ctx.canvas.width = viewHeight - viewHeight / 100;
    ctx.canvas.height = viewHeight - viewHeight / 100;
  } else {
    ctx.canvas.width = viewWidth - viewWidth / 100;
    ctx.canvas.height = viewWidth - viewWidth / 100;
  }
  cellSize = mazeCanvas.width / difficulty;
  if (player != null) {
    draw.redrawMaze(cellSize);
    player.redrawPlayer(cellSize);
  }
};

function makeMaze() {
  //document.getElementById("mazeCanvas").classList.add("border");
  if (player != undefined) {
    player.unbindKeyDown();
    player = null;
  }
  var e = document.getElementById("diffSelect");
  difficulty = e.options[e.selectedIndex].value;
  cellSize = mazeCanvas.width / difficulty;
  maze = new Maze(difficulty, difficulty);
  draw = new DrawMaze(maze, ctx, cellSize, finishSprite);
  player = new Player(maze, mazeCanvas, cellSize, displayVictoryMess, sprite);
  if (document.getElementById("mazeContainer").style.opacity < "100") {
    document.getElementById("mazeContainer").style.opacity = "100";
  }
}

Final Output:

Create a Maze Game with HTML, CSS, and JavaScript.gif

Conclusion:

Congratulations! You've successfully created a maze game using HTML, CSS, and JavaScript. This project is an excellent starting point for exploring further game development and web design. Now, unleash your creativity and experiment with new features to enhance your maze game further.


Remember, practice makes perfect, so keep refining your skills and building exciting web-based games. Happy coding!

That’s a wrap!

I hope you enjoyed this post. Now, with these examples, you can create your own amazing page.

Did you like it? Let me know in the comments below 🔥 and you can support me by buying me a coffee.

And don’t forget to sign up to our email newsletter so you can get useful content like this sent right to your inbox!

Thanks!
Faraz 😊

End of the article

Subscribe to my Newsletter

Get the latest posts delivered right to your inbox


Latest Post