Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML5

Create an HTML5 (and JavaScript) Maze Game with a Timer

4.96/5 (29 votes)
31 May 2016CPOL8 min read 199.7K   4.1K  
How to create a maze with a digital timer using HTML5 and JavaScript, without the use of Flash

Introduction

This article explains how to create a maze game with a digital timer using HTML5 (the canvas-element and JavaScript, without the use of Flash).

Important note: This maze doesn't work if your browser doesn't support JavaScript or HTML5 (the canvas-element to be specific). And if you download the maze, but if it is not working properly, then try the online demo at https://programfox.github.io/HTML5-Maze/[^].

Another important note: When I tested the game, it worked fine offline and online in Firefox, Opera and Safari. However, in Chrome and Internet Explorer, I wasn't able to move the blue rectangle if the file was not hosted. This is for security reasons: every file on the file system has a different origin, and you can load files from a different origin to your canvas (then it is a tainted canvas[^]), but if you try to use getImageData() on a tainted canvas, you get a security error in Chrome and Internet Explorer. For these browsers, you should go to the online demo to try out the maze, or host the downloaded files on the localhost server.

After downloading the maze, don't forget to unzip all files (the HTML file and the image), otherwise the maze doesn't appear.

Generating the Maze

The first step to create a maze, is to get an image of the maze. To create a maze image, I use this online Maze Maker: http://www.hereandabove.com/maze/mazeorig.form.html[^]
This is the maze which the Maze Maker generated:

Generated Maze

Positioning Elements and Drawing the Maze on the Canvas-Element

Positioning and Drawing

The next step is to position the rectangle which you can move using the arrow keys or WASD, and to position the end point (a circle on my maze). In my maze, the position of the rectangle is 425, 3px and the size is 15px Z 15px and the center point of the circle is 524px, 122px and the radius is 7 px. First, you need to create the canvas-element:

HTML
<canvas width="616" height="556" id="mazecanvas">
Can't load the maze game, because your browser doesn't support HTML5.</canvas>
<noscript>JavaScript is not enabled. To play the game, you should enable it.</noscript>

The browser will show "Can't load the maze game ..." if the browser doesn't support HTML5. The size of the maze is 556 x 556, but I did set the width to 616 because I added also a (digital) timer. The next step is to draw the maze to the canvas-element using JavaScript. I use the drawImage method to draw the maze image, I use the beginPath, closePath, rect and fill method to draw the rectangle and I use the beginPath, closePath, arc and fill method to draw the circle:

JavaScript
var canvas = document.getElementById("mazecanvas");
var context = canvas.getContext("2d");
var currRectX = 425;
var currRectY = 3;
var mazeWidth = 556;
var mazeHeight = 556;
var intervalVar;
function drawMazeAndRectangle(rectX, rectY) {
    makeWhite(0, 0, canvas.width, canvas.height);
    var mazeImg = new Image();
    mazeImg.onload = function () { // when the image is loaded, draw the image, 
                                   // the rectangle and the circle
        context.drawImage(mazeImg, 0, 0);
        drawRectangle(rectX, rectY, "#0000FF", false, true);
        context.beginPath();
        context.arc(542, 122, 7, 0, 2 * Math.PI, false);
        context.closePath();
        context.fillStyle = '#00FF00';
        context.fill();
    };
    mazeImg.src = "577080/maze.gif";
}
function drawRectangle(x, y, style) {
    makeWhite(currRectX, currRectY, 15, 15);
    currRectX = x;
    currRectY = y;
    context.beginPath();
    context.rect(x, y, 15, 15);
    context.closePath();
    context.fillStyle = style;
    context.fill();
}

To draw the maze on your canvas, add this code at the bottom of your script:

JavaScript
drawMazeAndRectangle(425, 3); // { 425, 3 } is the position
                              // of the blue rectangle on the canvas

currRectX and currRectY represent the position of the rectangle, and intervalVar is the variable for the timer, which we will create later.

The rect(), fill() and stroke() Method

After beginning a path (using the beginPath() method), you can use the rect() method to create a rectangle. But if you only use the rect() method, no rectangle is drawn. To draw the created rectangle on your canvas-element, you should use the fill() or stroke() method to actually draw the rectangle on the canvas. The fill() method fills a path, and the stroke() method draws a path.

Parameters of rect() Method

Name Type Description
x Number The x-coordinate, in pixels, of the top-left corner of the rectangle
y Number The y-coordinate, in pixels, of the top-left corner of the rectangle
w Number The width of the rectangle, in pixels
h Number The height of the rectangle, in pixels
The stroke() and fill() methods have no parameters.

The arc() Method

The arc() method draws an arc on the canvas. After drawing the arc, you should call the stroke() or fill() method. You can use this method to draw a circle or a part of a circle.

Parameters of arc() Method

Name Type Description
x Number The x-coordinate, in pixels, of the center point of the arc
y Number The y-coordinate, in pixels, of the center point of the arc
radius Number The radius of the arc, in pixels
startAngle Number The starting angle, in radians. 0 is at the 3 o'clock position of the arc's circle (see image)
endAngle Number The ending angle, in radians
antiClockwise Boolean true if the arc should be drawn in counterclockwise direction (from start to end)
false if the arc should be drawn in clockwise direction (from start to end)

Image:

Radians

The makeWhite() Function

In the previous code snippet, you saw that I used a makeWhite() function. I created this function myself. It makes a rectangle on the canvas white. So, why don't I use the clear method? Because the clear() method makes all pixels in a given rectangle transparent, which we don't need in the maze. This is the code of the makeWhite() function:

JavaScript
function makeWhite(x, y, w, h) {
    context.beginPath();
    context.rect(x, y, w, h);
    context.closePath();
    context.fillStyle = "white";
    context.fill();
}

Moving the Rectangle Using the Arrow Keys or WASD

The next thing to implement is to make it possible to move the blue rectangle using the arrow keys or WASD. The first step is to calculate the new X and Y-coordinate of the blue rectangle. The second step is to move the rectangle, if it can move, and to show a "Congratulations!" message if the blue rectangle reached the end point (the green circle):

JavaScript
function moveRect(e) {
    var newX;
    var newY;
    var canMove;
    e = e || window.event;
    switch (e.keyCode) {
        case 38:   // arrow up key
        case 87:   // W key
            newX = currRectX;
            newY = currRectY - 3;
            break;
        case 37:   // arrow left key
        case 65:   // A key
            newX = currRectX - 3;
            newY = currRectY;
            break;
        case 40:   // arrow down key
        case 83:   // S key
            newX = currRectX;
            newY = currRectY + 3;
            break;
        case 39:   // arrow right key
        case 68:   // D key
            newX = currRectX + 3;
            newY = currRectY;
            break;
        default: return;
    }
    movingAllowed = canMoveTo(newX, newY);
    if (movingAllowed === 1) {      // 1 means 'the rectangle can move'
        drawRectangle(newX, newY, "#0000FF");
        currRectX = newX;
        currRectY = newY;
    }
    else if (movingAllowed === 2) { // 2 means 'the rectangle reached the end point'
        clearInterval(intervalVar); // we'll set the timer later in this article
        makeWhite(0, 0, canvas.width, canvas.height);
        context.font = "40px Arial";
        context.fillStyle = "blue";
        context.textAlign = "center";
        context.textBaseline = "middle";
        context.fillText("Congratulations!", canvas.width / 2, canvas.height / 2);
        window.removeEventListener("keydown", moveRect, true);
    }
}

If the rectangle reached the end point, I use the clearInterval() method to stop the timer (which we'll set later in this article). Thereupon, I draw "Congratulations!" on the canvas, and then, I remove the keydown event listener, because the blue rectangle shouldn't move anymore.

Before you're able to move the blue rectangle, you should add a keydown event listener:

JavaScript
drawMazeAndRectangle(425, 3);                       // this line is already added
window.addEventListener("keydown", moveRect, true); // add this at the bottom of your script

Checking Whether the Blue Rectangle Can Move: The canMoveTo() Function

In the previous code snippet, you saw a canMoveTo() function. I created this method to check whether the blue rectangle can move. The logic behind it:

  1. Check whether the blue rectangle would move inside the bounds of the canvas
  2. If that's the case, get the image data of the rectangle with x = destinationX, y = destinationY, width = 15, height = 15. I take 15 as width and height because that's the size of the blue rectangle.
  3. Use a for loop to look at all pixels: if any of them has the color black, then the blue rectangle can't move. If any of them has the color lime, then you reached the end point.

This is the code for the canMoveTo() function:

JavaScript
function canMoveTo(destX, destY) {
    var imgData = context.getImageData(destX, destY, 15, 15);
    var data = imgData.data;
    var canMove = 1;                               // 1 means: the rectangle can move
    if (destX >= 0 && destX <= mazeWidth - 15 && destY >= 0 && 
        destY <= mazeHeight - 15) {                // check whether the rectangle would move 
                                                   // inside the bounds of the canvas
        for (var i = 0; i < 4 * 15 * 15; i += 4) { // look at all pixels
            if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0) { // black
                canMove = 0;                       // 0 means: the rectangle can't move
                break;
            }
            else if (data[i] === 0 && data[i + 1] === 255 && 
                                      data[i + 2] === 0) { // lime: #00FF00
                canMove = 2;                       // 2 means: the end point is reached
                break;
            }
        }
    }
    else {
        canMove = 0;
    }
    return canMove;
}

The getImageData() Method

Using the getImageData() method, you can get the image data of a specific region of your canvas. This method returns a ImageData object, and the data property is an array containing the RGBA pixel data. So:

  • data[0] contains the R (red) value of the first pixel in the ImageData
  • data[1] contains the G (green) value of the first pixel in the ImageData
  • data[2] contains the B (blue) value of the first pixel in the ImageData
  • data[3] contains the A (alpha) value of the first pixel in the ImageData
  • data[4] contains the R (red) value of the second pixel in the ImageData
  • data[5] contains the G (green) value of the second pixel in the ImageData
  • data[6] contains the B (blue) value of the second pixel in the ImageData
  • data[7] contains the A (alpha) value of the second pixel in the ImageData
  • and so on

Parameters of the getImageData() Method

Name Type Description
sx Number The x-coordinate, in pixels, of the top-left corner of the rectangle to get the image data from
sy Number The y-coordinate, in pixels, of the top-left corner of the rectangle to get the image data from
sw Number The width, in pixels, of the rectangle to get the image data from
sh Number The height, in pixels, of the rectangle to get the image data from

Implementing the Timer

The next step is to implement the timer. To do this, we use the setInterval() method to decrease the time with one second. If the time's up, we white out the whole canvas and add a red "Time's up!" message.

JavaScript
function createTimer(seconds) {
    intervalVar = setInterval(function () {
        makeWhite(mazeWidth, 0, canvas.width - mazeWidth, canvas.height);
        if (seconds === 0) {
            clearInterval(intervalVar);
            window.removeEventListener("keydown", moveRect, true);
            makeWhite(0, 0, canvas.width, canvas.height);
            context.font = "40px Arial";
            context.fillStyle = "red";
            context.textAlign = "center";
            context.textBaseline = "middle";
            context.fillText("Time's up!", canvas.width / 2, canvas.height / 2);
            return;
        }
        context.font = "20px Arial";
        if (seconds <= 10 && seconds > 5) {
            context.fillStyle = "orangered";
        }
        else if (seconds <= 5) {
            context.fillStyle = "red";
        }
        else {
            context.fillStyle = "green";
        }
        context.textAlign = "center";
        context.textBaseline = "middle";
        var minutes = Math.floor(seconds / 60);
        var secondsToShow = (seconds - minutes * 60).toString();
        if (secondsToShow.length === 1) {
            secondsToShow = "0" + secondsToShow; // if the number of seconds is 
                                                 // '5' for example, make sure that 
                                                 // it is shown as '05'
        }
        context.fillText(minutes.toString() + ":" + secondsToShow, 
                                                    mazeWidth + 30, canvas.height / 2);
        seconds--;
    }, 1000);
}

In the createTimer() function, I have created an anonymous function which will be called every second. The setInterval() method returns a unique interval ID which you can pass to the clearInterval() method if you no longer want the anonymous function to be called every second. At the first line in the anonymous function, I cleared a specific range of the canvas: I cleared only the area of the timer. In this maze, the canvas is split into two parts: in one part, the maze appears, and in the other part, the timer appears.

If seconds is equal to zero, I call the clearInterval() method, because I want to stop execution of the anonymous function every second. Then I remove the keydown event listener, because it is not longer necessary to move the blue rectangle (because the time's up). Thereupon, I clear the whole canvas to draw "Time's up" on it.

If seconds is not yet equal to zero, I draw the remaining time on the canvas. If the remaining time is more than 10 seconds, the color of the remaining time is green. If the remaining time is littler than or equal to 10, but greater than 5, then the color is orange-red, and if the remaining time is littler than or equal to 5 seconds, then the color is red.
First, we calculate the remaining minutes. To do this, we calculate the floor value of seconds / 60, and to get the remaining seconds, we calculate seconds - minutes * 60. If the number of seconds is 5 for example, then we make sure that it will be shown as 05, by preceding the 5 by a zero if necessary.

To really create the timer, add this line at the bottom at the end of the script:

JavaScript
drawMazeAndRectangle(425, 3);                       // added already
window.addEventListener("keydown", moveRect, true); // added already
createTimer(120);                                   // add this line
                                                    // 120 seconds -> 2 minutes

History

  • 30th October, 2013: Added information about security error in Chrome and Internet Explorer
  • 6th September, 2013: First version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)