Article
The Art and Science of JavaScript
Limitations of This Approach
The approach we've taken to build this maze imposes some limitations on the design of a maze floor plan, thus restricting the kind of layout we can draw:
- Corridors must always be two squares wide -- we can't create wider spaces because we don't have the pieces with which to draw them.
- No single corridor can be longer than 16 squares, as this is the maximum number of pairs of columns that we can draw.
- Walls must also consist of an even number of squares -- every block must comprise a block of at least two squares by two squares.
It may help to think of four squares on the floor plan as one single square; those smaller squares only exist so that we have more elements to apply progressive shading to, and hence achieve a better-looking and more realistic 3D view.
Creating the Map View
To the right of the maze view, we'll add a map that shows the floor plan in the player's immediate location. I originally added this feature to display a top-down view of the same view that the player can actually see ... but then I realized -- what's the point of such a map, if it provides no extra advantage?
Instead, we'll add a map that shows a little more of the surrounding area, as an aid to orientation. In the view shown below, you can see that the player can only move a short distance forwards before reaching a wall, but the map to the right shows further corridors beyond that wall.
![]()
The construction of the map itself is very simple -- it's just a bunch of spans floated in a container. I've applied a solid background where there's wall, and transparency where there's floor. This allows the green background of the container to show through, as the figure below reveals.

Generating the map is equally simple, since it's just a two-dimensional representation of data that is itself a 2D matrix.
Remember that when we generated the maze view, we created a matrix called this.squares. This matrix contained as much of the floor plan as was required to generate the current view, with the data transposed so that it represented a forwards view for the player. Well, we can use that same data matrix to generate this 2D map.
To create the map, we begin by coloring every square (using the base wallcolor property). Then we iterate through the matrix of squares, and apply transparency to every square in the map that represents open floor space -- including the space directly beneath the spot where the player is standing. The applyMapView method in the file underground.js takes care of this for us:
Example 6.5. underground.js (excerpt)
DungeonView.prototype.applyMapView = function()
{
this.resetMapView();
for(var i=0; i<this.squares.L.length; i++)
{
var n = this.mapsquares.length - 2 - i;
if(this.mapsquares[n])
{
if(this.squares.L[i].charAt(3) == '1')
{
this.mapsquares[n][0].style.background = 'transparent';
this.mapsquares[n][1].style.background = 'transparent';
if(i == 0)
{
this.mapsquares[n+1][0].style.background = 'transparent';
this.mapsquares[n+1][1].style.background = 'transparent';
}
}
if(this.squares.R[i].charAt(1) == '1')
{
this.mapsquares[n][4].style.background = 'transparent';
this.mapsquares[n][5].style.background = 'transparent';
if(i == 0)
{
this.mapsquares[n+1][4].style.background = 'transparent';
this.mapsquares[n+1][5].style.background = 'transparent';
}
}
if(this.squares.L[i].charAt(1) == '1')
{
this.mapsquares[n][2].style.background = 'transparent';
this.mapsquares[n][3].style.background = 'transparent';
if(i == 0)
{
this.mapsquares[n+1][2].style.background = 'transparent';
this.mapsquares[n+1][3].style.background = 'transparent';
}
}
}
}
};
Adding Captions
One of the things that excites me most about web programming is its potential for improving accessibility. Although we're making a visual game here, we have data in a format that can easily be translated into other kinds of output, such as plain text. We can use the same information that we used for making the map to generate a live text description of each maze view, of the kind shown in the figure below.
![]()
Not only does captioning potentially aid comprehension for players who have a cognitive or visual disability, it also extends the basic game play to people who are completely blind -- suddenly we can navigate around the maze without any visuals at all! Admittedly, and unfortunately, the game will be much harder to play like this -- not just because you have to hold orientation information in your head, but because you don't have the map to refer to in order to gain clues about what's behind the next wall.
Still, it's a start. Try viewing the game with CSS disabled, and you'll get a basic sense of the experience of what it would be like to play the game if you were blind. I've also confirmed that the game is playable in the JAWS 8 screen reader.
Generating the core data for the captions is straightforward?we simply need to know how many passageways there are to the left and right, and how far away they are. We can work this out by:
- iterating once again through the
this.squaresmatrix - building arrays to store the index of each opening
These openings will be converted to a perceived distance. As we navigate our maze, one square looks to be roughly two meters in length, so we'll adopt this as the scale for our map. We can stop iterating once we reach the end of the player's view -- we've created an end variable in the applyDungeonView method, which is the index of this.squares at the point that the view ends. Therefore, we can simply pass this value to the generateViewCaption method when we call it.
In the code, I've used len to represent the total length of the corridor in front, and arrays called passages.left and passages.right to store the distance of each passage from the player. The result of our iterations might produce data like this:
var len = 16;
var passages = {
'left' : [8, 16],
'right' : [4]
};
This looks simple enough to interpret, right? Well, yes ... however, turning this data structure into coherent English is still a little tricky. The basic conversion is easy. Using the data we have, we can describe the view in coarse terms:
"The corridor stretches 16 meters in front of you. To the left there are passages after 8 meters and 16 meters. To the right there are passages after 4 meters."
However, this language is fairly obtuse. For one thing, we wouldn't want to say "there are passages" if there was only one. Instead, we'd want to say "there's a passage." Additionally, the last passage to the left is at the far end, so it would be nicer to describe that by saying "The corridor stretches 16 meters in front of you, then turns left."
We also need to deal with exceptions. For example, if the player is standing directly in front of a wall, we don't want to say "... stretches 0 meters in front ..." Likewise, if the player has just turned right into a passage, we don't want to say "to the right there's a passage after 0 meters."
To cater for all these exceptions, the script accepts a dictionary of sentence fragments with replacement tokens, which are then compiled and parsed as necessary, in order to obtain a result that approaches decent prose. If you have a look in init.js, you'll notice that the DungeonView object is instantiated with this data as an argument. Each of the language properties is a sentence fragment with replacement tokens; for example, %dir is a direction token that will be replaced with the word for "left" or "right," as applicable.
I'd encourage you now to scroll through the generateViewCaption method in underground.js, and read the comments there that explain each situation. As it is, there's still room for improvement, but this is one of those things that you could refine to the nth degree, and it would still never be perfect. (Read more about the problems associated with constructing natural-sounding sentences in English in the Wikipedia entry on natural language processing.) That said, I believe that the end result is fairly good -- the captions are verbose enough to get the information across, they're succinct enough not to be arduous to read, and they flow well enough that they don't sound too much like they were generated by a machine (even though they were!).