JavaScript Pong
Written by Mike James   
Friday, 24 December 2010
Article Index
JavaScript Pong
Ball and bats
Timer for animation
Enhancements

Scores and restart

Now we are into making the whole thing look pretty and adding unnecessary functionality like scoring.

finished

Creating a display that looks like a 7segment display is fairly easy but a bit tedious. All we have to do is write an object which creates 7 CBlock objects in the correct location. This is just a matter of working out their relative co-ordinates:

CScore = function(position, size) {
DOMObj = new CBlock(position,
size, "black");
DOMObj.p = position;
DOMObj.s = size;
DOMObj.score = 0;
DOMObj.dash = [];
for (i = 0; i < 3; i++) {
DOMObj.dash[i] = new CBlock(
{ x: 1, y: (i * DOMObj.s.h / 2) },
 { h: 3, w: DOMObj.s.w - 2 },
"white");
DOMObj.appendChild(DOMObj.dash[i]);
 }
for (i = 0; i < 2; i++) {
DOMObj.dash[i + 3] = new CBlock(
{ x: 0, y: i * DOMObj.s.h / 2 + 4 },
 { h: DOMObj.s.h / 2 - 5, w: 3 },
 "white");
DOMObj.appendChild(
DOMObj.dash[i + 3]);
DOMObj.dash[i + 5] = new CBlock(
{x: DOMObj.s.w - 3,
y: i * DOMObj.s.h / 2 + 4 },
 { h: DOMObj.s.h / 2 - 5, w: 3 },
 "white");
DOMObj.appendChild(
DOMObj.dash[i + 5]);
}

 

Banner

 

The first for loop creates the three horizontal blocks and the second for loop creates the four verticals. All of the blocks are set to white and are created within a container. All that is now needed is a method which sets some of the blocks to white or black depending on the value to be displayed: First we need an array that lists the segments that are on for each digit:

var On=new Array(
[0, 2, 3, 4, 5, 6],
[3, 4],
[0, 5, 1, 4, 2],
[0, 5, 1, 2, 6],
[1, 3, 5, 6],
[0, 1, 2, 3, 6],
[1, 2, 3, 4, 6],
[0, 5, 6],
[0, 1, 2, 3, 4, 5, 6],
[0, 1, 3, 5, 6]);

With the help of the On array it is now easy to scan through and set the necessary segments to white:

 DOMObj.setValue = function(value) {
this.value = value;
for (i = 0; i < 7; i++) {
this.dash[i].style.
backgroundColor = "black";
}
for (i in On[value]) {
this.dash[On[value][i]].style.
backgroundColor = "white";
}
}
return DOMObj;
};

 

Adding two score displays to the run function is fairly standard:

ScoreL = new CScore({x:w / 4,y:5},
                { w: 20, h: 40 });
ScoreL.setValue(0);
Court.appendChild(ScoreL);
ScoreR = new CScore( {x:3 * w / 4,y:5 },
 { w: 20, h: 40 });
ScoreR.setValue(0);
Court.appendChild(ScoreR);

We also need to update the “update” method so that it adjusts the score and stops the game when one player has 9 points (you can add multiple digit counters quite easily):

update = function() {
var state = Ball.move();
if (state != 0) {
Timer.clearTimer();
if (state == 1) {
ScoreL.setValue(ScoreL.value + 1);
} else {
ScoreR.setValue(ScoreR.value + 1);
}
if (ScoreL.value == 9 ||
ScoreR.value == 9) {
Timer.clearTimer()
} else {
restartGame();
}
}
BatL.hit(Ball);
BatR.hit(Ball);
}

We still need one final function – something to reset the initial starting positions when the restart condition of one of the scores having reached nine is detected:

restartGame=function()
{
BatL.p={x : 15,y : (h / 2 - 20)};
BatL.move(0);
BatR.p={x:w-15-10,y:(h/2-20)};
BatR.move(0);
Ball.p= {x : 2,y : 1};
Ball.v={x:2,y:1};
Timer = new CTimer(20, update);
}

This resets the bats and the ball back to their original positions and creates another timer to continue the game.

Lessons learned

The design of the game isn’t perfect from an object-oriented point of view. Creating an application object would be better and making sure that each object handled any events that are relevant to it is also a better design, but it would be much easier to do using a JavaScript class library.

What is clear is that the entire program is much easier in pure JavaScript than in something that is a mix of HTML and JavaScript.

It has been tested on the latest versions of Firefox and IE and it most probably doesn’t work on early versions of any browser – however there is a lot of HTML and HTML plus JavaScript that equally doesn’t work. The browser problem doesn’t go away just because you are using Spartan Ajax, but at least you only have JavaScript and the DOM to worry about.

If you want to take the game further what is missing are the sounds that the ball made bouncing round the screen. For added fun why not port it to a phone and play using the built-in accelerometer!

If you would like the code for this project click on CodeBin. You can also try the program out here.

If you would like to be informed about new articles on I Programmer you can either follow us on Twitter, on Facebook , on Digg or you can subscribe to our weekly newsletter.

 

Banner


The Minimum Spanning Tree - Prim's Algorithm In Python

Finding the minimum spanning tree is one of the fundamental algorithms and it is important in computer science and practical programming. We take a look at the theory and the practice and discover how [ ... ]



AWS Low Cost Mailing List Using phpList And SES

Running a mailing list is not easy or cheap, but if you use AWS it can be. Find out how to create a low-cost and highly effective mailing list server.


Other Projects

<ASIN:0596514085>

<ASIN:1933988355>

<ASIN:0321605128>

JavaScript Pong –

in Spartan Ajax

 

Inspired by the account of how Nolan Bushnell created Pong this project implements a classic looking version of the classic game using nothing but JavaScript and some object-oriented ideas that come under the general label of Spartan Ajax.

 

The whole idea of Spartan Ajax is to get rid of HTML or any markup language and return the act of programming to the creation of procedural or imperative code. Its basic methodology is very simple – use the DOM as if it was JavaScript's own object system and make as little distinction between a JavaScript object and a DOM object as possible. Of course this leaves open the possibility of many different coding styles and much of the following example could be written in many different ways – in particular there is no reason not to use an established JavaScript library such as JQuery or DOJO and in most cases this would be the best way to go. However for an example, working without the help of a class library has the advantage of simplicity and directness.

 

In general we can’t get rid of 100% of the HTML and apart from a purists approach there really is no need too. Markup is fine doing the job it was originally intended for i.e. to format static objects. In this case the only HTML we need is:

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

 

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

<title>Pong</title>

<script type="text/javascript" src="/pong.js">

</script>

</head>

<body>

</body>

</html>

 

This simply loads the JavaScript file which then does all the work. From this point on we can forget HTML and concentrate on the code – this is the advantage of Spartan Ajax.

 

Run

 

The program starts off declaring a few global variables – just the height and width of the playing area:

 

var h=200;

var w=400;

 

Next we wait until all of the JavaScript has been loaded and everything is ready to go before calling the function which starts everything off. We could create an application object at this point and an associated run method but its probably easy just to create a run function:

 

run = function() {

 

The first task of the run is to create a Court object – basically just a large black block. In fact creating blocks of colour is one of the very fundamental things we keep on doing so its worth defining an object to do the job:

 

CBlock = function(position, size, c) {

var DOMObj = document.createElement("div");

DOMObj.style.position = "absolute";

DOMObj.style.width = size.w + 'px';

DOMObj.style.height = size.h + 'px';

DOMObj.style.top = position.y + 'px';

DOMObj.style.left = position.x + 'px';

DOMObj.style.backgroundColor = c;

return DOMObj;

}

 

 

This is the archetypal Spartan Ajax object it creates a DOM object – a div in this case – sets its properties and then returns a reference to the new object. Notice the use of associative arrays to pass the position as position.x and position.y and size as size.h and size.w. Now that we have the CBlock object the CCourt object is easy to construct:

 

CCourt = function(size) {

var DOMObj = new CBlock({x:10,y:10},size, "black");

return DOMObj;

}

 

All we have to do is create a black block with its top left-hand corner at 10,10 and of the specified height and width.

 

Returning to the Run function we can now create the instance of the CCourt object and continue with the construction of the game:

 

run = function() {

Court = new CCourt({h:h,w:w});

document.body.appendChild(Court);

 

 

To make the Court display we have to add it to the DOM using appendChild – notice however that we can carry on using Court as if it was a standard JavaScript object.

 

To make the Court look a little more like something you could play on let’s add a net. This makes use of the CBlock object to draw a vertical dashed line down the middle of the court:

 

CNet = function(position, size, nodash) {

DOMObj = new CBlock(position, size, "black");

DOMObj.p = position;

DOMObj.s = size;

for (i = 0; i < nodash; i++) {

 

dash = new CBlock({x: 0,y:i * 2 * DOMObj.s.h / (2 * nodash)}, {h:size.h/(2*nodash),w: size.w}, "white");

DOMObj.appendChild(dash);

}

return DOMObj;

};

There are a few new techniques used. The first CBlock object is used as a container for the dashed line in the sense that all of the other CBlocks are added to it as child DOM objects. This has the advantage that you can position and generally work with the new CNet object as a if it was a single object rather than a collection of dashes. It also allows you to use co-ordinates that are relative to the container. Also notice the use of associative arrays for the position and size parameters and the way that these are stored in two new properties added to the DOM object – yes you can do this. The for loop simply creates a number of dash objects and adds them to the container.

 

Now that we have the CNet object we can create a suitable instance in the Run function which now reads:

 

run = function() {

Court = new CCourt({h:h,w:w});

document.body.appendChild(Court);

 

Net = new CNet( {

x: w / 2,

y: 0

}, {

h: h,

w: 3

}, 20);

Court.appendChild(Net);

 

Notice the way that the new Net object is added to the Court object as a DOM child object. Now we have a black rectangle to act as the court and a dotted line displayed.

 

The Ball object

 

Of course we need a ball to bounce around the court and this is just another use of the CBlock object:

 

CBall = function(position, velocity, radius) {

DOMObj =new CBlock(position,{h:radius,w:radius},"white");

DOMObj.p = position;

DOMObj.v = velocity;

DOMObj.r = radius;

 

Notice that we create a ball with the specified radius and store its position and velocity as new properties of the DOM object.

 

This particular new object has more than just properties it also has a method that can be used to move it around the court:

 

DOMObj.move = function() {

this.p.x += this.v.x;

this.p.y += this.v.y;

this.style.top = this.p.y + 'px';

this.style.left = this.p.x + 'px';

 

The first pair of instructions add the velocity to the current position and the next pair up-date the balls position. Notice that in this case we have to use “this” to provide the context of the call because when the method is used “this” will be set to the current instance of the ball but the DOMObj variable will be long gone. In general use the DOM object reference when creating properties and methods on the object but use “this” when an instance is going to be accessing the properties and methods.

 

The final part of the move method simply checks to see if the ball has hit the side of the court or has gone out of play:

 

this.play = 0;

if (this.p.x + this.r > parseInt(this.parentNode.style.width))

this.play = 1;

if (this.p.x < 0)

this.play = 2;

if (this.p.y + this.r > parseInt(this.parentNode.style.height))

this.v.y = -this.v.y;

if (this.p.y < 0)

this.v.y = -this.v.y;

return this.play;

};

return DOMObj;

};

 

Notice the use of the play property to signal that the ball is or is not in play. A bounce is implemented by simply reversing the appropriate velocity. It was tempting to add a “beep” noise to the bounce but this isn’t easy within a browser as JavaScript has no “beep” or noise making command.

 

Now that we have a ball to play with we simply add it to the court with an initial position and velocity.

 

Ball = new CBall( {

x: 2,

y: 1

}, {

x: 2,

y: 1

}, 10);

Court.appendChild(Ball);

 

Bats

 

The next task is to create a bat object and two instances of it for each bat.

 

CBat = function(position, size) {

DOMObj = CBlock(position, size, "white");

DOMObj.p = position;

DOMObj.s = size;

DOMObj.move = function(d) {

this.p.y += d;

this.style.top = this.p.y + 'px';

};

DOMObj.hit = function(B) {

 

if (((B.p.x + B.r) >= this.p.x) && (B.p.x <= (this.p.x + this.s.w))) {

if (B.p.y >= this.p.y && B.p.y <= (this.p.y + this.s.h)) {

B.v.x = -B.v.x;

}

}

 

}

return DOMObj;

};

 

The bat needs two methods – a move method which simply allows the bad to move up or down and a hit testing method which tests to see if the bat has hit the ball passed as the parameter B. Notice that we could dispense with the property p and simply use style.top and style.left and the same with s and style.width and style.height but it is easier to have integer versions of these string style properties rather than keeping on having to type convert them.

 

Creating two bats is now very easy, which is one of the advantages of the object oriented approach:

 

BatL = new CBat({

x: 15,

y: (h / 2 - 20)

}, { h: 40, w: 10 });

Court.appendChild(BatL);

 

BatR = new CBat({

x: w - 15 - 10,

y: (h / 2 - 20)

}, { h: 40, w: 10 });

Court.appendChild(BatR);

 

The animation loop

 

The entire court is now setup and we can begin to move the ball, allow the user to move the bats and start testing for hits. As browsers only support a single thread of execution we can’t really implement a polling based animation loop – it would freeze the rest of the user interface. Instead we can make use of a timer. While the window object has a timer it’s so much better to build and use a timer object:

 

CTimer = function(tick, code) {

this.timer = window.setInterval(code, tick);

this.clearTimer = function() {

window.clearInterval(this.timer)

};

 

}

 

This will run the specified code every tick milliseconds.

 

To animate the game all we need to add to run is:

 

Timer = new CTimer(20, update);

 

and supply the update function:

 

update = function() {

var state = Ball.move();

if (state != 0) {

Timer.clearTimer();

BatL.hit(Ball);

BatR.hit(Ball);

}

 

 

This simply moves the ball, checks to see if the ball is still in play, and checks to see if either bat has hit the ball – in which case the hit routine bounces the ball. If the ball goes out of play the game stops, but this is easy to change to give a fixed number of rounds.

 

Moving bats

 

The only thing essential thing missing at this point is the ability to move the bats. Ideally this would be done by a method belonging to the bats but this would mean wiring up a keypress to call the correct bats up-date method. A simpler but not entirely satisfactory solution is to write a keypress handler that calls the appropriate bat move method:

 

 

batupdate = function(e) {

var e = window.event ? event : e;

if (e.keyCode) { key = e.keyCode; }

else if (typeof (e.which) != 'undefined') { key = e.which; }

 

switch (key) {

 

case (122):

BatL.move(1);

break;

case (97):

BatL.move(-1);

break;

case (107):

BatR.move(-1);

break;

case (109):

BatR.move(1);

break;

}

}

 

The strange goings on at the start of the method simply take account of the different way that IE and Firefox return event information. Having a uniform way of handling events would be one of the big advantages of using almost any JavaScript class library. The keys selected are A and Z for the left bat and K and M for the right bat but you can remap the handlers to any keys that seem reasonable.

 

Connecting this to the keypress event is just a matter of adding:

 

 

document.onkeypress = batupdate;

 

 

Now you should have a game that you can play in the sense that the ball moves, bounces off the walls and bat and you can move the bat and hit the ball.

 

Scores and restart

 

Now we are into making the whole thing look pretty and adding unnecessary functionality like scoring. Creating a display that looks like a 7segment display is fairly easy but a bit tedious. All we have to do is write an object which creates 7 CBlock objects in the correct location. This is just a matter of working out their relative co-ordinates:

 

CScore = function(position, size) {

DOMObj = new CBlock(position, size, "black");

DOMObj.p = position;

DOMObj.s = size;

DOMObj.score = 0;

DOMObj.dash = [];

for (i = 0; i < 3; i++) {

DOMObj.dash[i] = new CBlock({ x: 1, y: (i * DOMObj.s.h / 2) }, { h: 3, w: DOMObj.s.w - 2 }, "white");

DOMObj.appendChild(DOMObj.dash[i]);

 

 

}

 

for (i = 0; i < 2; i++) {

 

DOMObj.dash[i + 3] = new CBlock({ x: 0, y: i * DOMObj.s.h / 2 + 4 }, { h: DOMObj.s.h / 2 - 5, w: 3 }, "white");

DOMObj.appendChild(DOMObj.dash[i + 3]);

DOMObj.dash[i + 5] = new CBlock({ x: DOMObj.s.w - 3, y: i * DOMObj.s.h / 2 + 4 }, { h: DOMObj.s.h / 2 - 5, w: 3 }, "white");

DOMObj.appendChild(DOMObj.dash[i + 5]);

 

}

 

The first for loop creates the three horizontal blocks and the second for loop creates the four verticals. All of the blocks are set to white and are created within a container. All that is now needed is a method which sets some of the blocks to white or black depending on the value to be displayed:

 

DOMObj.setValue = function(value) {

this.value = value;

switch (value) {

case 0:

On = [0, 2, 3, 4, 5, 6];

break;

case 1:

On = [3, 4];

break;

case 2:

On = [0, 5, 1, 4, 2];

break;

case 3:

On = [0, 5, 1, 2, 6];

break;

case 4:

On = [1, 3, 5, 6];

break;

case 5:

On = [0, 1, 2, 3, 6];

break;

case 6:

On = [1, 2, 3, 4, 6];

break;

case 7:

On = [0, 5, 6];

break;

case 8:

On = [0, 1, 2, 3, 4, 5, 6];

break;

case 9:

On = [0, 1, 3, 5, 6];

break;

}

for (i = 0; i < 7; i++) {

this.dash[i].style.backgroundColor = "black";

}

for (i in On) {

this.dash[On[i]].style.backgroundColor = "white";

}

 

}

 

return DOMObj;

};

 

 

Adding two score displays to the run function is fairly standard:

 

 

ScoreL = new CScore( { x: w / 4, y: 5 }, { w: 20, h: 40 });

ScoreL.setValue(0);

Court.appendChild(ScoreL);

ScoreR = new CScore( { x: 3 * w / 4, y: 5 }, { w: 20, h: 40 });

ScoreR.setValue(0);

Court.appendChild(ScoreR);

 

We also need to update the “update” method so that it adjusts the score and stops the game when one player has 9 points (you can add multiple digit counters quite easily):

 

update = function() {

var state = Ball.move();

if (state != 0) {

 

Timer.clearTimer();

if (state == 1) {

ScoreL.setValue(ScoreL.value + 1);

} else {

ScoreR.setValue(ScoreR.value + 1);

}

if (ScoreL.value == 9 || ScoreR.value == 9) {

Timer.clearTimer()

} else {

restartGame();

}

 

}

 

BatL.hit(Ball);

BatR.hit(Ball);

}

 

We still need one final function – something to restart the entire game if after updating the score:

 

 

restartGame=function()

{

BatL.p={x : 15, y : (h / 2 - 20)};

BatL.move(0);

BatR.p={x : w - 15 - 10,y : (h / 2 - 20)};

BatR.move(0);

Ball.p= {x : 2, y : 1};

Ball.v={x:2,y:1};

Timer = new CTimer(20, update);

}

 

 

This resets the bats and the ball back to their original positions and creates another timer to continue the game.

 

Lessons learned

 

The design of the game isn’t perfect from an object oriented point of view. Creating an application object would be better and making sure that each object handled any events that are relevant to it is also a better design but it would be much easier to do using a JavaScript class library. What is clear is that the entire program is much easier in pure JavaScript than in something that is a mix of HTML and JavaScript. It has been tested on the latest versions of Firefox and IE and it most probably doesn’t work on early versions of any browser – however there is a lost of HTML and HTML plus JavaScript that equally doesn’t work. The browser problem doesn’t go away just because you are using Spartan Ajax but at least you only have JavaScript and the DOM to worry about. If you want to take the game further what is missing are the sounds that the ball made bouncing round the screen. For added fun why not port it to a phone and play using the built in accelerometer!

 

 

 



Last Updated ( Sunday, 10 July 2022 )