Make a Maze Game on an HTML5 Canvas

UPDATE: 8-28-2013 This post is expanded on in the new post ‘Build Responsive Games For Mobile Phones, Tablets and Computers with HTML5 Canvas’

One of the best ways to learn how to program in any language is to make a game and then change the code to create different variations on the game. I learned C++ long ago by creating an elevator simulation game (thanks Tom Swan). It’s fun and it is the closest you can come to ‘instant gratification’ in programming.

Let’s make a maze game in an HTML5 canvas. In this post Moving Shapes on the HTML5 Canvas With the Keyboard we learned to use keyboard input to move a shape around the canvas. All we need to do to make our game is

1. Add an image of the maze to the canvas.

2. Add collision detection code so we know if our shape hits a border in the maze.

What you need to run this

This example can not be run from your desktop because it uses the getImageData() method of the 2d context for collision detection. For security reasons it can only be run from a page served to the browser by a server. See the comments to the post here for more info .

Here’s the game (you need an HTML5 friendly browser to view this)

Here is our canvas in action. Since this post is longer than one page, pushing the arrow keys will scroll the page in addition to moving the square around. I substituted the arrow key keycodes with new keycodes.

Press w to move up (keycode 87)
Press s to move down (keycode 83)
Press a to move left (keycode 65)
Press d to move right (keycode 68)





Canvas Maze Game


This text is displayed if your browser does not support HTML5 Canvas.



Here's the code


<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Canvas Maze Game</title>
</head>
<body>
<header> </header>
<nav> </nav>
<section>

<div>
<canvas id="canvas" width="482" height="482">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
</div>

<script type="text/javascript">
var canvas;
var ctx;
var dx = 5;
var dy = 5;
var x = 200;
var y = 5;
var WIDTH = 482;
var HEIGHT = 482;
var img = new Image();
var collision = 0;

function rect(x,y,w,h) {
ctx.beginPath();
ctx.rect(x,y,w,h);
ctx.closePath();
ctx.fill();
}

function clear() {
ctx.clearRect(0, 0, WIDTH, HEIGHT);
ctx.drawImage(img, 0, 0);
}

function init() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
img.src = "maze.gif";
return setInterval(draw, 10);
}

function doKeyDown(evt){
switch (evt.keyCode) {
case 38:  /* Up arrow was pressed */
if (y - dy > 0){
y -= dy;
clear();
checkcollision();
if (collision == 1){
y += dy;
collision = 0;
}
}

break;
case 40:  /* Down arrow was pressed */
if (y + dy < HEIGHT ){
y += dy;
clear();
checkcollision();
if (collision == 1){
y -= dy;
collision = 0;
}
}

break;
case 37:  /* Left arrow was pressed */
if (x - dx > 0){
x -= dx;
clear();
checkcollision();
if (collision == 1){
x += dx;
collision = 0;
}
}
break;
case 39:  /* Right arrow was pressed */
if ((x + dx < WIDTH)){
x += dx;
clear();
checkcollision();
if (collision == 1){
x -= dx;
collision = 0;
}
}
break;
}
}

function checkcollision() {
var imgd = ctx.getImageData(x, y, 15, 15);
var pix = imgd.data;
for (var i = 0; n = pix.length, i < n; i += 4) {
if (pix[i] == 0) {
collision = 1;
}
}
}

function draw() {
clear();
ctx.fillStyle = "purple";
rect(x, y, 15,15);
}

init();
window.addEventListener('keydown',doKeyDown,true);
</script>
</section>
<aside> </aside>
<footer> </footer>
</body>
</html>

You can copy this code and paste it into a new file called something like maze.html and upload it to a webserver along with your maze image (they must be in the same directory). When you open it with an HTML5 friendly browser like Firefox 3.6 it will display the canvas with the maze game on it.

Getting a maze image

There is a free tool at http://hereandabove.com/maze/mazeorig.form.html that makes awesome maze images which you can right click and save in order to use them in your canvas page. Make sure that you set the Path width (in pixels) to be wider than your square plus whatever you use for dx and dy or else your square will always be colliding with a wall and won't go anywhere.

Here is the image that I used. You can right click it and save it if you don't want to make your own right now.

Putting the maze image on your canvas

After getting a maze image, we need to add it to our canvas. Be sure that you upload the image into the same directory as your HTML file.

Make the canvas size the same size as the size of the image. My image is 482x482.


<div>
<canvas id="canvas" width="482" height="482">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
</div>

This makes it easier because the coordinate system of the canvas will match the coordinate system of the maze image.

Add a variable to hold our image.

var img = new Image();

We put our image into our new variable in our init() function which is the function that we call to start everything.


function init() {
  canvas = document.getElementById("canvas");
  ctx = canvas.getContext("2d");
  img.src = "maze.gif"; 
  return setInterval(draw, 10);
}

Then we draw our image onto the canvas in our clear() function.


function clear() {
  ctx.clearRect(0, 0, WIDTH, HEIGHT);
  ctx.drawImage(img, 0, 0);
}

This way when we call clear, only our little square is erased and we can look at the area of the maze where the square would go next and easily see if it is going to collide with a border.

The drawImage method of the 2d context has three arguments

ctx.drawImage(variable holding the image, X coordinate of the top left of the image, Y coordinate of the top left of the image)

When we call it, we put our maze image on the canvas with the top left corner at (0,0) which is the top left corner of our canvas also. Now when we look at a pixel like (100,100) on our canvas, we are also looking at pixel (100,100) on our image.

Adding collision detection

Now all we need to do is check to make sure that we only move the square if the movement will not cause it to cross over one of the borders in the maze. Here's some pseudocode.


User presses an arrow key
Calculate where that would move the square to.
Look at all of the pixels on the maze image that the square will cover in its new position (our square is 15x15 so we look at 225 pixels)
Are any of the pixels black (the maze is white with black borders. If a pixel is black that means the square would collide with a border)
YES: then do not move
NO: then move

Our collision detection needs to occur when a user presses an arrow key to move the square.

Let's look at what happens when the up arrow is pressed.


function doKeyDown(evt){
  switch (evt.keyCode) {
    case 38:  /* Up arrow was pressed */
       if (y - dy > 0){ 
        y -= dy;
        clear();
        checkcollision();
        if (collision == 1){
          y += dy;
          collision = 0;
        } 
      }

When a key is pressed on the keyboard, our event listener calls doKeyDown() which then checks to see if an arrow key was pressed. If the keyCode of the event is 38 then that means the up arrow key was pushed down.

First we check to make sure that the requested move won't move the square off of the canvas (y < 0).


  		if (y - dy > 0){ 

In order to move the square up we need to decrease the value of y by dy (dy and dx are set to the number of pixels that we move the square with each keypress. We are using dy = 5 and dx = 5 in our example).


        y -= dy;

Now we need to check if the new y value causes our square to cross over a border in the maze.

First we call our clear() function which redraws our canvas with the maze picture but does not draw our square.


function clear() {
  ctx.clearRect(0, 0, WIDTH, HEIGHT);
  ctx.drawImage(img, 0, 0);
}

This allows us to look at the pixels on the maze image where our square wants to move to. Now we can check if there is a collision between the square and any of the borders of the maze.

Here's our checkcollision() function.


function checkcollision() {
  var imgd = ctx.getImageData(x, y, 15, 15);
  var pix = imgd.data;
  for (var i = 0; n = pix.length, i < n; i += 4) {
  if (pix[i] == 0) {
      collision = 1;
    }
  }
}

We get the image data from a square at (x, y, 15, 15) which is the 15x15 square area that our square will cover when we draw it. Then we load that data into a one dimensional array pix[].


  var imgd = ctx.getImageData(x, y, 15, 15);
  var pix = imgd.data;

This array has four values for each pixel in our 15x15 area. The values are
RED value
GREEN value
BLUE value
ALPHA value

A white pixel with no transparency looks like this
255
255
255
255

A black pixel with no transparency looks like this

0
0
0
255

4 values X 15 pixels X 15 pixels = 900 values in our array but since we are looking for either black or white we can simply look at only the RED value of each pixel which means we compare 225 values instead of 900. If the pixel is black RED is 0 and if the pixel is white RED is 255 so we look at every fourth value in the array and check to see if it is 0 (for black). If it is, we set the variable collision = 1.


  for (var i = 0; n = pix.length, i < n; i += 4) {
  if (pix[i] == 0) {
      collision = 1;
    }
  }

Now we are back in our keydown code for the up arrow where we check for the collision variable being set to 1 and if it is we undo the change to y and set the collision variable back to 0. Y remains unchanged and the next time the draw() function is called (it is called once every 10 milliseconds) it will not move the square up.


        if (collision == 1){
          y += dy;
          collision = 0;

Where to go from here

You can now jazz up your maze game. Here are some suggestions.

Add a timer display that counts down and the player must solve the maze before time runs out.

Use the timer to keep score so players can see who is the fastest.

Add sound effects. BEEP when the player hits a border or a fanfare when they win.

Add more mazes. Make ten mazes that are progressively more difficult and when the player finishes one, start the next one.

You can use any black and white image. Make a finger numbing spiral maze.

Make a trainer to help disabled people learn to use a neural impulse actuator to operate a computer.

Have fun with the code as that is the easiest way to learn.

UPDATE: 8-28-2013 This post is expanded on in the new post 'Build Responsive Games For Mobile Phones, Tablets and Computers with HTML5 Canvas'

If you have a question that you do not want published as a public comment, then use my contact page.

15 Replies to “Make a Maze Game on an HTML5 Canvas”

  1. Thanks a lot, very insightful and got me going into the right direction. I will try to create my maze image via GDImage libararies though, to make it random every time you play. Let me know if you would like the code for this!

  2. collision doesn’t work for me too. I copy all the code and even save this page, however it still doesn’t work.

  3. Hi Leon

    Are you trying to run it locally? If so, you must have missed this quote from the post…

    “This example can not be run from your desktop because it uses the getImageData() method of the 2d context for collision detection. For security reasons it can only be run from a page served to the browser by a server..”

    If you are having this problem on a webserver, let me know more details please.

    Thanks
    James

  4. I’ve just found out about that, maze’s image needs to be in the same domain with the webpage or getImageData() won’t work , however, it works very fine on Safari. Thanks for this article, I’m going to teach this game in my class tomorrow. 🙂

    1. Hi Leon

      Let me know if I can ever do anything to help you with your class. Click the “Contact Me” link at the top of the page to contact me privately.

      James

  5. Hello My Dear Friends, Users Forum !

    The Company In-Disguise . Com

    Anonymous Internet Surfing, Anonymous Downloading-Uploading Torrent-P2P, Amonymous WiFI.
    In-Disguise . Com – Fully Automatic and Anonymous VPN Service , you don’t have to waste time on:

    1 – Search Supports if disconnected from a server, search for a standard installation “OpenVPN client”
    spend time for installing downloaded configs to the program.
    Subscribe to All Servers of Our Service for 9 Euro; 3 months = 20 Euro; 6 months = 35 Euro; 1 year = 55 Euro.

    2 – Unique “VPN Client” is easy to install on all kinds of Operating Systems: MAC / Windows / Linux!!!
    Will allow you to switch easily between the VPN servers in USA / DE / UK / IT / NL / LU / EG / PA / RO / MY Continuing
    Types of VPN connections included in a single subscription – DoubleVPN and OpenVPN.

    3 – Company Takes All Kinds Of Payments in Full Auto Mode!
    WebMoney / Visa / Master Card / PayPal / Liberty Reserve / BitCoin / SMS and many others.

    Our VPN Service Is Here!

    WWW . in-disguise . com

    ICQ: 6906911
    Skype: In-Disguise-VPN

    Sincerely, Your Anonymous VPN Service : In-Disguise . Com

  6. Fast, easy and convenient movement of goods, services, money and people – these are the principles that are the main components for welfare and jobs in Europe.Rolands petersons privatban Mobility is the main prerequisite for securing these four core values ??in the domestic and international markets.Rolands petersons privatban This is very important in the Baltic regions, where very well thought out and developed infrastructure is one of the satisfactory factors of development.Rolands petersons privatban A flight organization provides a direct and indirect impact on the economy.Rolands petersons privatban Direct impact is the profit from the work of airline personnel and infrastructure staff in general, when non-indirect impact contribution represents all acquisitions in the aviation sector from local suppliers who most often do not represent the aviation sector.Rolands petersons privatban Moreover, the impact on the economic situation in Latvia is localized by the costs of aviation sector personnel and service providers.Rolands petersons privatbank

Leave a Reply

Your email address will not be published. Required fields are marked *