Here is something fun for amusement. I love to implement small funny games in JavaScript and have a lot of games on my homesite (and please notice, my homesite is in danish!).
My last game is a maze game.
In this tutorial, I will show how to implement a simple version of the game. Focus is on how to implement the game, not to make it particular challenging and fun to play, nor to make it particular visual appealing. However, having learned how implement a basic version of the game, it is easy to expand it to more a complicated maze (and thus much more challenging and fun). Making it better looking isn't either very difficult, as I did it on my homepage.
I have chosen to split the tutorial in two parts:
The goal is to implement a simple maze game, where the player can move inside the maze using buttons, which simulates the arrow keys on a keyboard (and yes you can also implement movement using the keyboard arrow keys as has been done on my homepage). The maze has to look something like this:
Obviously, a press on the button with arrow up (↑) will move the yellow circle (the players marker) up and likewise pressing the arrow left, right and down button will move the yellow circle in left, right and down repectively. However, the movement should of course only be possible provided the yellow circle isn't hitting a wall in the maze.
The HTML code is relatively simple. The maze is implemented as a inline SVG element (or image - the SVG can both be considered as a image format and a HTML element) and the buttons have been placed in relative positioned HTML DIV element, so have them aligned correct:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 422 422" width="422" height="422"> <rect stroke="none" fill="#66dd44" x="0" y="0" width="422" height="422" /> <rect stroke="none" fill="#aaccff" x="60" y="60" width="302" height="302" /> <g id="maze" stroke="#000000" stroke-width="2" stroke-linecap="square" fill="none"> <line x1="61" y1="61" x2="61" y2="121" /> <line x1="301" y1="61" x2="301" y2="121" /> <line x1="361" y1="61" x2="361" y2="121" /> <line x1="61" y1="121" x2="61" y2="181" /> <line x1="121" y1="121" x2="121" y2="181" /> <line x1="241" y1="121" x2="241" y2="181" /> <line x1="301" y1="121" x2="301" y2="181" /> <line x1="361" y1="121" x2="361" y2="181" /> <line x1="61" y1="181" x2="61" y2="241" /> <line x1="121" y1="181" x2="121" y2="241" /> <line x1="241" y1="181" x2="241" y2="241" /> <line x1="301" y1="181" x2="301" y2="241" /> <line x1="361" y1="181" x2="361" y2="241" /> <line x1="61" y1="241" x2="61" y2="301" /> <line x1="121" y1="241" x2="121" y2="301" /> <line x1="181" y1="241" x2="181" y2="301" /> <line x1="241" y1="241" x2="241" y2="301" /> <line x1="361" y1="241" x2="361" y2="301" /> <line x1="61" y1="301" x2="61" y2="361" /> <line x1="181" y1="301" x2="181" y2="361" /> <line x1="361" y1="301" x2="361" y2="361" /> <line x1="61" y1="61" x2="121" y2="61" /> <line x1="61" y1="361" x2="121" y2="361" /> <line x1="121" y1="61" x2="181" y2="61" /> <line x1="121" y1="121" x2="181" y2="121" /> <line x1="121" y1="241" x2="181" y2="241" /> <line x1="121" y1="361" x2="181" y2="361" /> <line x1="181" y1="61" x2="241" y2="61" /> <line x1="181" y1="181" x2="241" y2="181" /> <line x1="181" y1="361" x2="241" y2="361" /> <line x1="241" y1="61" x2="301" y2="61" /> <line x1="241" y1="241" x2="301" y2="241" /> <line x1="241" y1="361" x2="301" y2="361" /> <line x1="301" y1="301" x2="361" y2="301" /> <line x1="301" y1="361" x2="361" y2="361" /> </g> <circle id="marker" stroke="#000000" stroke-width="0" fill="#ffff00" r="20" cx="0" cy="0" transform="translate(91,331)" /> </svg> <div style="position: relative; height: 122px;"> <button id="up" style="position: absolute; top: 0; left: 181px; width: 60px; height: 60px;">↑</button> <button id="left" style="position: absolute; top: 61px; left: 120px; width: 60px; height: 60px;">←</button> <button id="down" style="position: absolute; top: 61px; left: 181px; width: 60px; height: 60px;">↓</button> <button id="right" style="position: absolute; top: 61px; left: 242px; width: 60px; height: 60px;">→</button> </div>
Please notice
The chosen maze is characterized not only by being quadratic. The placement of the maze walls shows that the maze is implemented as a grid with dimension 5 times 5, with each of its 25 cells having dimension 60 time 60 pixels. This is easiest to recognize by adding the following lines (<line> element) to the SVG element (immediately after the two <rect> elements):
<line stroke="#ffffff" stroke-width="1" x1="61" y1="121" x2="361" y2="121" /> <line stroke="#ffffff" stroke-width="1" x1="61" y1="181" x2="361" y2="181" /> <line stroke="#ffffff" stroke-width="1" x1="61" y1="241" x2="361" y2="241" /> <line stroke="#ffffff" stroke-width="1" x1="61" y1="301" x2="361" y2="301" /> <line stroke="#ffffff" stroke-width="1" x1="121" y1="61" x2="121" y2="361" /> <line stroke="#ffffff" stroke-width="1" x1="181" y1="61" x2="181" y2="361" /> <line stroke="#ffffff" stroke-width="1" x1="241" y1="61" x2="241" y2="361" /> <line stroke="#ffffff" stroke-width="1" x1="301" y1="61" x2="301" y2="361" />
With these lines included, the maze looks like this:
Besides the lines, indices from 0 to 4 have been added for the 5 rows and 5 columns in the grid. Thus the yellow players marker will initially be in the row with index 4 and the column with index 0. Moving the player markers means moving it from one cell (with a specific index for row and column) to a neighbor cell (where one of the two indices will be either one value greater or smaller). For instance, moving the player marker up, means changing the row index from 4 to 3. We can use this simple relation between movement, row and column indices to implement a function for drawing the players marker after movement:
<script> var current_column = 0; var current_row = 3; function move_marker() { var x = parseInt(91 + 60*current_column); var y = parseInt(91 + 60*current_row); var marker = document.getElementById("marker"); marker.setAttributeNS(null,"transform","translate("+x+","+y+")"); } </script>
One notice, that the movement of the player marker is done by setting the attributte "transform" on the marker SVG element (i.e., the circle element). To do so, one first have to calculate the coordinates for the actual row (current_row) and column (current_column) including an offset, as the maze is shifted both left and down from the top of the SVG element.
Using the above function, one can easily implement a preliminary function for movement to the left, triggered by presing the button with the left arrow:
<script> document.getElementById("left").addEventListener("click",move_left,false); function move_left() { current_column--; move_marker(); } </script>
First, we catch the button pressed by appending a event listerner for the click event and let the function move_left handle this event. Second, we just count the current columnt one down and call the move_marker function.
This is working, however with a serious flaw: The movement doesn't respect the walls of the maze, but continues right through as though the wall didn't exist at all. Thus we need a way to handle these walls. First we need to describe where the walls are.
The maze contains a number of vertical and horizontal walls. As a matter of fact on can perceive a wall as a continuous set of a pieces, each placed on an edge of a cell in the grid. Thus we will percieve the walls as a number of small pieces, each making an edge of a cell in the grid.
The vertical and horizontal (pieces of) wall can be described independent of each other. We will start describing the vertival walls.
For each row in the grid of the maze, there can be up to 6 (pieces of) vertical walls. In the first row, we thus have 3 walls: One on the far left and two on the far right. Between these there are 3 cell edged where there could have been a wall, but isn't. We can describe this with an array:
[true, false, false, false, true, true]
This boolean array tells for each cell edge if there is a (piece of) wall or not. The example shows the we have a (piece of) wall at the first edge and the last two, but none on the other edges. Similar we can easily describe the vertical walls in the other rows of the maze, using an boolean array for each row. And this can be gathered in a double array, here called vertical_walls:
<script> var vertical_walls = []; vertical_walls[0] = [true, false, false, false, true, true]; vertical_walls[1] = [true, true, false, true, true, true]; vertical_walls[2] = [true, true, false, true, true, true]; vertical_walls[3] = [true, true, true, true, false, true]; vertical_walls[4] = [true, false, true, false, false, true]; </script>
One notice the first index in the double array refers to the row and the second index to the column. Thus
vertical_walls[4][1]
refers to the second (possible) wall on the lower row (which by the way is false in our maze).
The horizontal walls are described in a similar manner with a double array. However here we choose to look at the array so each entry (row) in the array describes the walls in the particular column in the maze. Thus the double array, horizontal_walls looks like this:
<script> var horizontal_walls = []; horizontal_walls[0] = [true, false, false, false, false, true]; horizontal_walls[1] = [true, true, false, true, false, true]; horizontal_walls[2] = [true, false, true, false, false, true]; horizontal_walls[3] = [true, false, false, true, false, true]; horizontal_walls[4] = [false, false, false, false, true, true]; </script>
In this double array the first index refers to the column (in the maze) and the second index refers to the row (in the maze). This is opposite vertical_walls.
Now having a implemention the the walls in the maze we can implement correct functions for movement in the maze. First, we update the implementation of moving the players marker left:
<script> document.getElementById("left").addEventListener("click",move_left,false); function move_left() { if(vertical_walls[current_row][current_column]) { alert("Bump - you can't go left!"); } else { current_column--; move_marker(); } } </script>
Movement to the right is similar, however here we have to look out for walls to the right in the current cell (thus we add one to the current column index):
<script> document.getElementById("right").addEventListener("click",move_right,false); function move_right() { if(vertical_walls[current_row][current_column+1]) { alert("Bump - you can't go right"); } else { current_column++; move_marker(); } } </script>
The implementation of up and down movement is done similar, but now using the horizontal_walls arrays, as the horizontal wall are the ones to look out for. Remembering that the first index for this double array is for maze column, we have:
<script> document.getElementById("up").addEventListener("click",move_up,false); function move_up() { if(horizontal_walls[current_column][current_row]) { alert("Bump - you can't go up!"); } else { current_row--; move_marker(); } } document.getElementById("down").addEventListener("click",move_down,false); function move_down() { if(horizontal_walls[current_column][current_row+1]) { alert("Bump - you can't go down!"); } else { current_row++; move_marker(); } } </script>
There is still a single detail we need to fix: When getting out of the maze the game should stop and the player informed that (s)he has escaped the maze.
It it actually quite easy to trace when the player has escaped the maze: Moving out of the maze means moving up from from maze row with current_row = 0 and thus we get at state where current_row is -1. Thus we look for this state in the function move_up:
<script> document.getElementById("up").addEventListener("click",move_up,false); function move_up() { if(horizontal_walls[current_column][current_row]) { alert("Bump - you can't go up!"); } else { current_row--; move_marker(); if(current_row == -1) { alert("Congratulation - you got out of the maze"); current_row = 4; current_column = 0; move_marker(); } } } </script>
Now we have a working game. We will improve this further in the part 2 of this tutorial.
You can test this version of the game here.