Home Articles FAQs XREF Games Software Instant Books BBS About FOLDOC RFCs Feedback Sitemap
irt.Org

#03 - Game Development in JavaScript

You are here: irt.org | Games | Techniques | #03 - Game Development in JavaScript

By: Ryan Detert

Introduction

Before there were games such as Alpha Centauri, there was Doom, and befere that there was Pong. The personal computer has come a long way since my first computer, the Apple IIGS+, which didn't even have a harddrive.

When computers first had gaming capabilities the coding language of choice was usually an Assembler of some sort. These languages are very tedious and programmers meticulously searched through OPCode references to try and save every nibble they could. Fortunately, memory is no longer the luxury it once was and even web browsers, a program and not an operating system, is capable of executing some games at a fairly high caliber.

In this article we will explore some of the fundamental methods of making a moderately difficult puzzle game that will work in earlier versions of Netscape and IE.

If you need a good briefing on some of the JavaScript functions and syntax that we will be working with then click here.

Benefits of Web Games in JavaScript

First off, all major browsers support JavaScript, so you don't have to worry about ActiveX support or messing with a more complex language such as Java.

Another reason to use web-based games is for the security issues involved. JavaScript will not allow you to tamper with a person's harddrive and scripts do not involve complicated installation routines that may cause other important programs to malfunction.

Business sites and personal pages alike can all benefit from web-based games. Games are fun, and if people have fun playing your little games they will visit your page more frequently. This will result in greater popularity for your site. In the business world this may result in more money. For example, I once saw a car dealership's web site that used JavaScript and Flash to create a game where you could race with the dealer's newest cars on the lot. Some people may have a strong work ethic, but haven't you ever played solitaire or online chess a time or two during work? =)

Let's Make A Game!

In the rest of this article I will guide through the steps of making a puzzle game. The game I have chosen is Same Game, a classic puzzle game. The game is somewhat different and I doubt that many of you have seen it before. I figured that this would be better than a boring tic-tac-toe game.

The techniques used to make this game can be seen in Netscape 3.x and IE 4.x. I purposefully chose an example that does not use complicated DHTML or animation. These topics will be covered later on. Same Game uses JavaScript functions and properties that are compatible in both browsers.

Please look at Same Game and play it before we begin by clicking here and reading the instructions. It will make things much easier to explain.

Importance Of Planning Ahead

Before you do any sort of programming you must have a clear idea in your head of what you would like to do. Then, on paper you must figure out exactly how you are going to do it. Otherwise, you will left altering code and changing so many things around that it will drive you crazy and most likely result in sloppy coding.

I will guide you through my thought process in engineering Same Game by providing you with examples and pointing out exactly what needs to be done in chronological order.

Matrices

A matrix is a special type of array that on the computer is meant to serve as a logical representation of a 2-dimensional array of numbers. For instance, when you initialize an array with 360 elements, all the elements are just strung together in a long line like so:

Element 1Element 2Element 3Element 4Element 5etc.

However, in a matrix we can look at the information more like this:

Element 1Element 2Element 3
Element 4Element 5Element 6
Element 7Element 8Element 9

Notice that the second table looks more like the Same Game board than the first table does.

Now, to incorporate the matrix into Same Game, we are going to initialize an array large enough so that there is one element in the array for each shape on the board. Since there are four different types of shapes, we will randomly assign a number from 1-4 to each array element where a 1 corresponds to a square, a 2 corresponds to a circle, etc.

Essentially, the matrix is our board, the images just make the game pretty. For any calculations that need to be made we will use the numbers within our matrix. After we have manipulated all our numbers in the matrix, we can just change the source of our images based on what the value of the numbers in the matrix are. For instance, every time that the computer reads a 2 in the matrix it will plot a circle and every time it sees a 1 it will plot a square.

The computer and the programmer see the board like this:

143
144
332

Whereas the person playing the game only sees the finished product:

squarelinecross
squarelineline
crosscrosscircle

This technique of using matrices to represent pictures and such is widely used in gaming since the computer can't tell the difference between a circle or a square.

Taking It One Step At A Time

Global Variables And Their Importance

I define global variables store the size of the images, the dimensions of the board, the current shape you are trying to match, the score, and a place-holding variable called 'foundAny'.

It is important to use global variables throughout your code because if you typed the number 10 everywhere instead of the variable 'width', then the width of the board will not be able to be altered and if you ever wanted to change the width of the board at a later date you would have to convert every numeral 10 into either a variable or another number, this would be quite tedious. Global variables can also save you time and effort.

Initializing the Board

To begin playing the game we must first select all the shapes to be placed by randomly assigning numbers to an array to correspond to shapes, as explained in the previous section. The function that will do this looks like:

function randomizeBoard(){

    for(i=0; i < w*h; i++)
        //randomly place shapes via matrix
        board[i+1] = Math.floor(Math.random() * (insaneImages.length - 1)) + 1;
}

Where I used an array called insaneImages to store the names of the shape images and an array called board to store the actual numeric board values.

Plotting the Actual Board

Now that we've generated the board numerically, we need to find a way to display it using images instead of numbers. This can be accomplished with a simple for() loop and the document.write(); function like so:

var insaneImages = new Array("blank", "square", "circle", "cross", "lines");

function setupBoard(){

randomizeBoard();
    for(i=0; i < w*h; i++)
    {
        if( !(i % w) )
            //go to next line after 10 shapes placed
            document.write("<BR>");

            //interpret matrix numerals as images on the screen
            document.write("<A HREF=\"javascript:isMatched(", i+1, ")\">
			<IMG SRC=\"", insaneImages[board[i+1]], ".gif\"
			BORDER=\"0\" HEIGHT=\"", shapeS, "\" WIDTH=\"", shapeS,
			"\" NAME=\"s", i+1,"\"><\/A>");
        }
}

The above code will keep writing HTML lines of code such as this one:

<A HREF="javascript:isMatched(37)">
<IMG SRC="lines.gif" BORDER="0" HEIGHT="25" WIDTH="25" NAME="s37"></A>

Finding Matches

After you click one of the shapes, the first thing that the computer must check is if there is a match anywhere around that shape. If there is not, then there is no reason to make anymore calculations and we can exit the script, otherwise we must find exactly how many matches there are.

First things first, we must look around the shape for a match, but before we can do this we have to take a couple of things into account how to: separate a 1-dimensional array into rows to make it appear logically as a matrix, and check to see if the shape is along the edge of the board.

To make the array seem like a matrix we simply take the number of rows down the shape is located, multiply it by the width of the matrix, and then add the number of columns over. Remember, our board looks like this:

Element 1Element 2Element 3
Element 4Element 5Element 6
Element 7Element 8Element 9

So to check about Element 8, which is 2 rows down from the top and 1 column over from the left, we can go:

board[(rows - 1) * (width of the board) + (number of columns over) + 1]

We must add 1 because the Element values don't begin with zero. This yields:

board[ 1 * 3 + 1 + 1] = board[5]

And to the right of Element 8 we can use this formula:

board[(rows) * (width of the board) + (number of columns over + 1) + 1]
    = board[ 2 * 3 + 2 + 1 ] = board[9]

And so on and so forth. Isn't math wonderful?!

Now, we must make functions to check whether the shape is on the edge of the board. We must check the edges because if you are on the top edge and try to check above the board, there will be no array elements defined and this will give you an error. So to do this we can use the following functions which will return a zero if false and a one if true.

//functions to check whether or not a shape is on the edge of the board----
//-------------------------------------------------------------------------
function isTop(pos){ return (pos >= 1 && pos <= width) ? 1 : 0 }
function isRight(pos){ return !(pos % width) ? 1 : 0 }
function isLeft(pos){ return !((pos-1) % width) ? 1 : 0 }
function isBottom(pos){
 return (pos > width*height-width && pos <= width*height) ? 1 : 0 }

In isTop() we simply see if the shape index is between the values of the shape indices for the top right and left corners of the board. In isRight() we simply check to see whether the shape index is a multiple of the shape indices along the right edge of the bard. If there is no remainder then we know that the condition is true. In isLeft() we subtract the current shape index by one so that we can do the exact same thing we did in isRight(). In isBottom() we are doing the same thing we did in isTop(), except we are checking between the bottom two corner shapes.

Now, we can use these functions to help us make another series of functions to check whether or not there is a match in either direction. These new function will check the appropriate shape and will also take into account whether the shape is on an edge.

//functions to see if there is a corresponding match----------------------
//------------------------------------------------------------------------
function topMatch(pos) {
 return (!isTop(pos) && board[(pos-width)] == current) ? 1 : 0 }
function rightMatch(pos) {
 return (!isRight(pos) && board[(pos+1)] == current) ? 1 : 0 }
function leftMatch(pos) {
 return (!isLeft(pos) && board[(pos-1)] == current) ? 1 : 0 }
function bottomMatch(pos) {
 return (!isBottom(pos) && board[(pos+width)] == current) ? 1 : 0 }

When we originally check to see if there is a match, we can simply call each of the above functions and if all are false then there is no need to calculate any further.

How Many Matches Are There?

If there is an initial match found then we will convert both of those shapes in our matrix to a designated number, I used the the length my images array, which turns out to be 5. We will then save the number that we just converted, calling it 'current', and then search through the matrix checking for fives and checking for the 'current' value around each 5. If matches are found, then they are also converted to 5's. Here is the snippet that will perform this operation:

//scan for matches
do
{
   foundAny = 0;

    for(i=1; i <= h*w; i++)
    {
        var isRightNum = (board[i] == insaneImages.length);

        //check right
        if(isRightNum && rightMatch(i)){
        board[(i + 1)] = insaneImages.length;
            foundAny = 1;
                }

        //check left
        if(isRightNum && leftMatch(i)){
            board[(i - 1)] = insaneImages.length;
            foundAny = 1;
        }

        //check top
        if(isRightNum && topMatch(i)){
            board[(i - w)] = insaneImages.length;
            foundAny = 1;
        }

        //check bottom
        if(isRightNum && bottomMatch(i)){
            board[(i + w)] = insaneImages.length;
            foundAny = 1;
        }
    }

}while(foundAny);

Scoring

If you get really good at this game you may notice that the scores become rather high. My top score is somewhere around 9.314E+89. The scoring method could be improved but that would make for more code =).

All I did was count all the 5's and then raise that number to the number - 1 power like so:

score = Math.pow(number_counted, number_counted - 1);

Moving The Shapes Down

To move the shapes down you must count up all the shapes you want to eliminate, which I represented with a 5, and then converted the 5's to 0's, the number I designated for blank spaces. Then I simply scanned the matrix to see if there was a shape with a blank space under it. If so I moved the shape down and continued doing this until there where no more blank spaces found. The loop looks like this:

//Count up all 5's and convert them to zeros
var counter = 0;

for(i=1; i <= w*h; i++)
        if(board[i] == insaneImages.length){
                counter++;
        board[i] = 0;
    }

//Move em Down!

foundAny = 1;

while(foundAny){

    foundAny = 0;
        for(i=1; i <= width*height; i++)
        if(!isBottom(i) && board[i]  && !board[(i + width)]){ //check below
            foundAny = 1;
            board[(i + width)] = board[i]; //move it shape down
            board[i] = 0;   //erase original position of shape moved
        }
}

Moving Shapes Over When Necessary

To move the shapes over, we will check every column, starting from the right, first check for a shape and after one is found we will check for empty columns, moving the columns over that aren't empty to fill their place. The reason that we check for a shape first is so that we don't have to bother with moving empty columns into other empty columns.

//function to shift board to the left when empty columns present-----------------
//-------------------------------------------------------------------------------
function moveOver(colNumber){

for(var j=0; j < width-colNumber; j++)
    for(i=0; i < height; i++)
                if( !isRight((width*i+colNumber+j)) ){

            board[(width*i+colNumber+j)] = board[(width*i+colNumber+j+1)];
                    board[(width*i+colNumber+j+1)] = 0;
        }
}

//Move em Over!
//Check For An Empty Column (starting at far right) Then
//Shift Board Left to Fill Empty Column

foundAny = 0;

//go over each column from right to left <======
    for(d = width; d > 0; d--)
    {
            counter = 0;

    //go down each column
            for(c = height-1; c >= 0; c--)
        if (!board[width*c+d]) counter++; else foundAny = 1;

            if (d < width && counter == height && foundAny) moveOver(d);
    }

Re-Drawing The Board

Now, after all the hard calcations have been made, we have to do is change the source code of the images to match the new number pattern in the matrix.

//re-draw the board with updated information-------------------------------------
//-------------------------------------------------------------------------------
function reDraw(){
    for(i = 1; i <= width*height; i++)
         eval("document.s" + i + ".src = \"" + images[board[i]] + ".gif\"");
}

Game Over?

To determine if the game is over you have to find a match anywhere on the board. If a match is found, then the game cannot be over. However, if the game is over then we will check to see if all the shapes have been eliminated. If they have then your score quadruples and you win the game!

//Check To See If All Shapes Eliminated------------------------------------
//-------------------------------------------------------------------------
function didIWin(){

    for(var x=1; x <= width; x++)
        for(var y=0; y < height; y++)
                  if(board[y*width+x]) return;
    score *= 4;
    alert("YOU WIN!");
}


function isGameOver(){

    for(var x=1; x <= w; x++)
            for(var y=0; y < h; y++)
        if((current = board[y*width+x]) &&
		   (bottomMatch(y*width+x) || rightMatch(y*width+x)))
            return;
    alert("Game Over");
    didIWin();
}

Adding Bells And Whistles

In my version of Same Game I added some extra features so that you can change the size of the shapes to fit your screen and resize the board to your liking. Since many people disable cookies I decided to place the game in a frame, holding the re-dimensioning variables in the parent document and then reloading the frame by going:

self.location.href = location.href

This way the variables can be saved safely. This is also how the New Game button works, except it does not alter any variables.

The Finished Code

I had to place the game in a frame so that the variables that hold the width and height of the board, etc. are not erased. Here is the parent HTML source.

<HTML>
<HEAD>
    <TITLE>Same Game</TITLE>
    <SCRIPT>
        var ww = 14, hh = 10;
        var shapeSize = 25;
    </SCRIPT>
    <FRAMESET ROWS="*,1" FRAMESPACING=0 FRAMEBORDER=0 BORDER=0>
        <FRAME SRC="web_same2.html">
        <FRAME SRC="blank.html">
    </FRAMESET>
</HEAD>
</HTML>

The Game

Same Game

TroubleShooting

Sometimes there are annoying glitches that seem impossible to fix. Don't worry about it. It happens to everyone. While I was making Insane Game in one of my loops it read d >= 0 instead of d > 0. That was all that was wrong and it took me 6-8 hours to locate!

The best way that I have found for troubleshooting is to first set your browser so that it will alert you to syntax errors and such. But when things are still acting up I always cut and paste segments of my code in another HTML file and test the variables with alert boxes. Once you break down the big code into small parts that work perfectly, then you can begin to integrate them function by function until everything works out.

No revelation I'm sure, but there is no substitute for a little bit of thought and many alert boxes.

Conclusion

Gaming in JavaScript can be as complex as the game that you select. In this tutorial we touched on essential JavaScript functions that were used in Same Game, but do not fail to realize the usefulness of these functions elsewhere. Same Game was not the simplest game to procure, but it was not impossible either. It was a good example that incorporated many useful methods.

The techniques covered in this article are fairly basic and are used by many gamers. In a later article we will focus on the accessiblity of DHTML and its utility. In this article I wanted to start off with pure JavaScript that will work in both Netscape and IE, not having to consider the different browser specifications.

Until next time.

Games are distributed under the WebGames License

©2018 Martin Webb