JavaScript Pong – Building a Retro Arcade Classic

May 25th, 2019 1:17 pm |  by David.Reid  |  Posted in Tutorial

 

In this article we will go through the steps required to create JavaScript Pong which is a clone of the retro arcade classic.

By the time you have completed this tutorial, you will have programmed your very own version of Pong and you will have covered many of the basics required to create a two-dimensional game with JavaScript and HTML5.

If you are unfamiliar with Pong you can find out more about it here. Also, the source code for this tutorial is available on my GitHub page in my JavaScript.Pong repository. And, there is an improved version of the game which you can play here.

Okay, if you are ready to build JavaScript Pong, grab a coffee, open your favourite editor and lets get started.

JavaScript Pong Prerequisites and Requirements

Before we begin, I’m going to assume that a) you are a programmer and b) that you are familiar with JavaScript.

All the source code you need for JavaScript Pong is in this tutorial so a knowledge of JavaScript and for that matter programming, is not entirely necessary but it sure will make a lot more sense if you are a programmer. Just saying.

Also, any editor will do the trick. Notepad is all you need but an editor like Visual Studio Code, Atom or Emacs is far superior. I’m using Visual Studio Code because its awesome but its not a requirement, so feel free to use whatever editor floats your boat.

The game will run on a browser. I’m using Chrome. I haven’t tested the game on any other browser. So, if you can use Chrome then you should. And, the game will run using the file:// protocol so there is no need to host the game on a local server.

Game requirements for JavaScript Pong

Before we move on, its probably a good idea to iron our what it is we will build today. So here is a list of simple requirements for the game play.

  • There will be two paddles on the screen. The left hand paddle (paddle 1) will be controlled by the player using the mouse. The right hand paddle (paddle 2) will be controlled by the game.
  • The aim of the game is get the ball past your opponents paddle. If you do, you gain a point and the ball is reset in on the screen and play resumes.
  • The first player to score ten points win the game.
  • When the game is won, a message will display who won the game. A new game can be played by clicking continue after the game is over.

And that is the basic requirements for our game play. So, we’ve done enough talking. Lets get down to the meat and potatoes of this tutorial. Let’s build JavaScript Pong.

JavaScript Pong File Architecture

Create a new folder for your game called JavaScript.Pong. This will be the root directory for our game.

Open a terminal and change the directory to your new JavaScript.Pong directory. From inside that directory run the following command and accept the default settings…

npm init

To run npm you need Node.js installed. If you don’t have Node, you can grab it here. This will create a package.json file for your game. It’s overkill for this project but its good practice to create a package file for your apps. The default settings for your package.json file will look something like this.

{
  "name": "javascript.pong",
  "version": "1.0.0",
  "description": "JavaScript Pong",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "MIT"
}

Inside your root directory, create two files called Index.html and Index.js. The Index.html file will be used to display our game and the Index.js file will be used for our games logic.

Add the following code to the Index.html file.

<!DOCTYPE html>
<html lang="en">
    
    <head>
        <title>JavaScript Pong</title>
        <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
        <meta content="utf-8" http-equiv="encoding">
        <script src='index.js'></script>
    </head>

    <body>

    </body>
</html>

And, open index.js in your editor and add the following code.

window.onload = function() {
    console.log('JavaScript Pong v1.0.0');
}

After you have saved both files, open index.html in your browser and you will see, well nothing, but that’s okay. Open up the developer tools on your browser – CTRL + SHIFT + I – on Chrome, switch to the console tab and you will see JavaScript Pong v 1.0.0 in the console. If you see the aforementioned message, then index.html loaded and has found the index.js file. It did that using the following line of code in the head tag.

<script src='index.js'></script>

When the index.html file loaded in your browser, the following line of code was executed in index.js.

window.onload = function() {
    console.log('JavaScript Pong v1.0.0');
}

Which in turn, printed the ‘JavaScript Pong v1.o.o message in our developer tools console. Great! We’re done here. And, because pong is a fairly simple game, this setup will be suffice for the duration of the project. Our file architecture is complete.

The Canvas

We’ll be using HTML5 Canvas API to build our game. We don’t have the time or room in this article to cover HTML5 Canvas API in depth, if you want or need an in depth overview then you can find that here. For our purposes, all you need to know is that the Canvas API allows us to draw the graphical elements of our game, for example, the ball, the paddles, the net and the score. To use the canvas, we add the following line of code inside the body tag in our index.html file.

<body>
    <canvas id="canvas" width="600", height="600"></canvas>
</body>

And that’s it. We have our canvas in place. Again, nothing much is happening here but we’re getting closer to the magic. Next up, the Game Loop.

The JavaScript Pong Game Loop

So lets start with some code. Open up your index.js file and update it with the following code…

// Constants
const FRAMES_PER_SECOND = 30;

// Execution
window.onload = function() {
    
    setInterval(gameLoop, 1000/FRAMES_PER_SECOND);
}

// Functions
function gameLoop() {
    console.log('looping...');
}

So, what have we done here. First off, we’ve organised our code into constants, execution and functions. This will make it easier to read our code as we add new functionality. Constants, are values we’ll be using throughout the game, execution is where the magic is executed and functions is where we organise our gaming magic. This will make more sense as we go along. We’ve also added the following line of code…

setInterval(gameLoop, 1000/FRAMES_PER_SECOND);

The setInterval is a JavaScript function that executes specified code at specified intervals. For us, the gameLoop function is the code that will be run and we have specified that it be run – 1000/FRAMES_PER_SECOND – which is 3o times every second because 1000 equals 1000 milliseconds and we’ve set our constant FRAMES_PER_SECOND to 30. If you open index.html in a browser and look at the console in your browsers developer tools you will see that the word ‘looping…’ is being displayed at a ridiculous rate, 30 times per second to be precise. And finally, we’ve also added a function called gameLoop. The aforementioned is the function that is called thirty times per second and at the moment, it does nothing but print looping… But, we’re about to change that.

Simulating motion in our JavaScript Pong game

Believe it or not, pictures don’t move. I know, ground breaking stuff, right? To simulate movement, we have to draw a picture, then update the picture, then draw it, update, draw it, an so on. The trick is to redraw at a speed that is too quick for the human eye to see. That magic number is thirty times per second. So, in our gameLoop function, which is being called thirty times per second, we want to move all the players then draw the game. And that, move everything, draw everything process will look like motion when we do it at a frame rate of thirty times per second. So lets add two new functions to our game. Add them below the gameLoop function…

function drawEverything() {
    console.log('draw everything...');
}

function moveEverything() {
    console.log('move everything...');
}

And finally, lets change our gameLoop function as follows…

function gameLoop() {    
    
    moveEverything();
    drawEverything();
}

Open index.html in your browser and you’ll see that ‘move everything’ and ‘draw everything’ are printed out thirty times per second. The moveEverything and drawEverything functions are critical. They are where all the game magic will happen. But, before we can code that magic, we need to get back to the Canvas API. Or more interestingly, we need to talk about special effects.

Creating Special Effects with the Canvas API

Okay, I might have over egged it a little with the term special effects because when it comes down to it, Pong is basically a circle and some rectangles moving around the screen. But we do need to know how to do that, so lets get to it. Add some new constants and variables to the constants area. And while we’re here lets change the comment to say constants and variables.

// Constants and variables
const FRAMES_PER_SECOND = 30;
const BACKGROUND_COLOR = '#313639';
const BALL_COLOR = '#6abef0';
const PADDLE_COLOR = '#f5f5f5';
const TEXT_COLOR = '#ec88e8';
let canvas; 
let canvasContext;

Cool, now we have variables for our canvas and context. And, we’ve also added in some colours for our game actors and screen. Now, update the execution function, windows.onload with the following code.

window.onload = function() {

    canvas = document.getElementById('canvas');
    canvasContext = canvas.getContext('2d');
    
    setInterval(gameLoop, 1000/FRAMES_PER_SECOND);
}

The following line of code grabs an element from our html file with an ID of ‘canvas’ and stores that element in our canvas variable.

canvas = document.getElementById('canvas');

If you check out our canvas element in index.html, you’ll see that its ID is ‘canvas’. What we have done here is create a reference to our canvas element. The next line – see below – creates a reference to our canvas elements context. In particular, its two dimensional context, which we will need to draw circles and rectangles.

canvasContext = canvas.getContext('2d');

Adding some generic graphic functions

We’ll basically be drawing lots of rectangles and one circle. The screen is a rectangle, the paddles are rectangles and the net is a series of rectangles. In short, there will be lots of rectangles. It’s good practice to eradicate, where possible, duplicate code from your app. And, since we’ll be drawing lots of rectangles, it makes sense to create a function for drawing all the rectangles. Lets do that now. Add the following below the moveEverything function…

function colorRect(topLeftX,topLeftY, boxWidth,boxHeight, fillColor) {
    canvasContext.fillStyle = fillColor;
    canvasContext.fillRect(topLeftX,topLeftY, boxWidth,boxHeight);
}

Our new function accepts as parameters, the rectangles coordinates, dimensions and colour. It then draws the rectangle for us which is magic because we’ll be drawing a lot of rectangles. We only draw one circle but lets go ahead and create a similar helper function for drawing circles. Add the following code below our new colorRect function.

function colorCircle(centerX,centerY, radius, fillColor) {
    canvasContext.fillStyle = fillColor;
    canvasContext.beginPath();
    canvasContext.arc(centerX,centerY, 10, 0,Math.PI*2, true);
    canvasContext.fill();
}

Cool! Now we have helper functions to create circles and rectangles. That pretty covers all the code we need to implement our special effects. If you want to find out more about drawing two-dimensional shapes with the Canvas API then checkout the MDN documentation. Okay, before we move on the next chapter, lets add a background color to our canvas by adding the following code to our drawEverything function.

colorRect(0, 0, canvas.width, canvas.height, BACKGROUND_COLOR);

As you can see we’ve drawn a rectangle that has the same dimensions as our canvas and uses our BACKGROUND_COLOR constant for its color. Check out index.html on a browser and then lets move on to the next step.

Adding event listeners to our game

A gamer needs to move the paddle otherwise they’re nothing more than a spectator in some tiny boring digital light show. To avoid the latter we need away for the gamer to move the paddle up and down the screen. We could use the keys to achieve this, but in our example we will use the mouse. To capture mouse movements, also known as events we need to create what is called an event listener. The event listener will listen for mouse events and then fire a specified function. But before we do that, lets update our constant and variables section with the following code.

// Constants and variables

const FRAMES_PER_SECOND = 30;
const BACKGROUND_COLOR = '#313639';
const BALL_COLOR = '#6abef0';
const PADDLE_COLOR = '#f5f5f5';
const TEXT_COLOR = '#ec88e8';
const PADDLE_WIDTH = 10;
const PADDLE_HEIGHT = 100;

let canvas; 
let canvasContext;
let paddle1Y = 250;

Now that we have our new constants and variables in place, lets create our function that our listener will fire when it detects a mouse event. Add the following code below our existing functions.

function updateMousePos(evt) {
            
    let rect = canvas.getBoundingClientRect();
    let root = document.documentElement;
    let mouseX = evt.clientX - rect.left - root.scrollLeft;
    let mouseY = evt.clientY - rect.top - root.scrollTop;
    paddle1Y = mouseY - (PADDLE_HEIGHT / 2);
}

When triggered, the above function will get the current mouse position and then move the paddle either up or down the left hand side of the screen which is great news. But, in order for that function to triggered we must create our listener. We do that with the following line of code under our setInterval function in the windows.onload function.

window.onload = function() {

    canvas = document.getElementById('canvas');
    canvasContext = canvas.getContext('2d');
    
    setInterval(gameLoop, 1000/FRAMES_PER_SECOND);

    // Our new event listener
    canvas.addEventListener('mousemove', updateMousePos); 
}

And that’s it. Our event listener is in place and it will trigger our function when the mouse is moved. But of course, our game doesn’t have a ball yet, let alone a paddle. Let’s fix that.

Adding the ball to our JavaScript Pong game

Okay, let’s get the bouncing on the screen. But as always, before we can add the ball we need to add some new variables to our constant and variables section. Add the following below our existing variables.

let ballX = 75;
let ballY = 75;
let ballSpeedX = 5;
let ballSpeedY = 7;

Great, these variables set the balls coordinates and its x and y speed. When the ball exits the screen we’ll need to relocate it on the center of the screen. We do that with a ballReset function. Add the following code below our existing list of functions.

function ballReset() {
    ballX = canvas.width/2;
    ballY = canvas.height/2;
}

Excellent. Now when our ball leaves the screen, we have a function that will reset its position on the middle of the screen. Now we need to draw the ball. We do that by updating our drawEverything function as shown below.

function drawEverything() {
    colorRect(0, 0, canvas.width, canvas.height, BACKGROUND_COLOR);
    colorCircle(ballX,ballY, 10, BALL_COLOR);
}

If you open index.html you’ll see the ball on the screen. It’s not moving yet, but we’ll take care of that next.

Moving the ball

Open up the index.js file and add update our moveEverything function with the following code.

function moveEverything() {
    
    ballX += ballSpeedX;
    ballY += ballSpeedY;

    if (ballX > canvas.width || ballX < 0) {
        ballSpeedX *= -1;
    }
    if (ballY < 0 || ballY > canvas.height) {
        ballSpeedY *= -1;
    }
}

So, what does the above code do? Well, first it sets the x,y speeds to the balls x,y position. Then it does a little collision detection to see if the ball has reached the left or right edge of the screen. If it has, then it reverses the balls x speed. After that, it checks to see if the ball has reached the upper or lower edge of the screen. If it has then if reverses the balls y speed. Of course, this keeps the ball on the screen but its not really the desired game behaviour. In our game, we want the ball to reset and update the score if the ball passes the left or right edge of the screen. But whilst we don’t have paddles, its probably better to have the ball contained within the screen. Anyway, lets move on to the next step.

Adding the Player Paddle

Okay, lets update our drawEverything function to look like the following.

function drawEverything() {
    
    // Draw background
    colorRect(0, 0, canvas.width, canvas.height, BACKGROUND_COLOR);
    
    // Draw ball
    colorCircle(ballX,ballY, 10, BALL_COLOR);
    
    // Draw paddles
    colorRect(0, paddle1Y, PADDLE_WIDTH, PADDLE_HEIGHT, PADDLE_COLOR);
}

All we’ve done here is add a new colorRect function call for our paddle and added some descriptive comments. But if you go to the index.html file, you’ll see that our paddle is there and when you move the mouse, the paddle moves. Excellent! High fives all round. Earlier on, we added an event listener for our mouse and a function for updating our paddles position. That code has been working for a while now but we just couldn’t see it in action until we drew the paddle on the screen. Our app is starting to look like a game. We have a moving ball, and a moving paddle. Lets add the second paddle to the game.

Adding Paddle 2

Right, lets get paddle 2 on the screen. first we need to update our constant and variables section with a variable for paddle 2. Update the constant and variables section with the code below.

const FRAMES_PER_SECOND = 30;
const BACKGROUND_COLOR = '#313639';
const BALL_COLOR = '#6abef0';
const PADDLE_COLOR = '#f5f5f5';
const TEXT_COLOR = '#ec88e8';
const PADDLE_WIDTH = 10;
const PADDLE_HEIGHT = 100;

let canvas; 
let canvasContext;
let paddle1Y = 250;
let paddle2Y = 250; // New
let ballX = 75;
let ballY = 75;
let ballSpeedX = 5;
let ballSpeedY = 7;

We also need to draw our second paddle on the screen. Update the drawEverthing function with the code below.

function drawEverything() {
    
    // Draw background
    colorRect(0, 0, canvas.width, canvas.height, BACKGROUND_COLOR);
    
    // Draw ball
    colorCircle(ballX,ballY, 10, BALL_COLOR);
    
    // Draw paddles
    colorRect(0, paddle1Y, PADDLE_WIDTH, PADDLE_HEIGHT, PADDLE_COLOR);

    // New code for our second paddle
    colorRect(canvas.width - PADDLE_WIDTH, paddle2Y, PADDLE_WIDTH, PADDLE_HEIGHT, PADDLE_COLOR); 
}

Next we need to move the paddle. To do that we will create a new function that is responsible for the second paddles movement. Add the following code to the bottom of your functions sections.

// New paddle two movement function
function paddle2Movement() {
    
    let paddle2YCenter = paddle2Y + ( PADDLE_HEIGHT / 2 )
    
    if ( paddle2YCenter < ballY - 35 )
    {
        paddle2Y += 6;
    }
    else if ( paddle2YCenter > ballY + 35 )
    {
        paddle2Y -= 6;
    }
}

Before we move on, lets go over the paddle movement code. First, we work out the centre of the paddle. We do this, because we want the paddle centre to follow the ball. Then we move the paddle down if the ball is lower than the paddles centre or we move the paddle up if the the ball is higher then the paddles centre. You’ll notice that we subtract or add 35 when determining if the paddle should move up or down – see code below.

if ( paddle2YCenter < ballY - 35 ) // Subtract 35
{
    paddle2Y += 6;
}
else if ( paddle2YCenter > ballY + 35 ) // Add 35
{
    paddle2Y -= 6;
}

That addition/subtraction adds a delay on the second paddles movement. If we didn’t add the delay, the paddle would be pretty accurate when tracking the ball. Consider it a handicap to level the playing field. Okay, lets add our new code to the moveEverything function as demonstrated below.

function moveEverything() {
    
    ballX += ballSpeedX;
    ballY += ballSpeedY;

    if (ballX > canvas.width || ballX < 0) {
        ballSpeedX *= -1;
    }
    if (ballY < 0 || ballY > canvas.height) {
        ballSpeedY *= -1;
    }

    paddle2Movement(); // Add paddle 2 movement
}

Okay, open up the index.html file and watch the second paddle on the right edge of the screen track ball movement. Pretty cool right? Consider this your first dive into NPC (non-player character) development. Our game is coming together but its not there yet. Or ball moves, our paddles move but there is no interaction between the game actors. We need the paddles to interact with the ball. Lets do that next.

Ball collision detection

Okay, there is a lot to cover here. And, for the first time in this project we are going to delete some code. 😮 In the moveEverything function go ahead and delete to following code.

if (ballX > canvas.width || ballX < 0) {
    ballSpeedX *= -1;
}
if (ballY < 0 || ballY > canvas.height) {
    ballSpeedY *= -1;
}

After the deletion, your moveEverything function will look like the following…

function moveEverything() {
    
    ballX += ballSpeedX;
    ballY += ballSpeedY;

    paddle2Movement();
}

Okay, don’t worry, the code we deleted detected ball collisions with the perimeter of our game screen. We did that simply to keep the ball within the bounds of the game. But that is not how Pong works and wont be how JavaScript Pong works either. What we’ll do next is replace that old code with some new magic that detects all the collisions to make the game playable. Lets get started.

Detecting the Balls X movement collisions

Normally, I’d just slap all the collision detection code for the ball in the moveEverything function. But, for this walk through well seperate x and y collision detection into seperate functions and then add those functions to our moveEverything function. Start by adding a new function at end of your functions list as demonstrated below.

function checkBallXCollisions() {
    // We'll add our code here.
}

Now add the function to your moveEverything function as shown below.

function moveEverything() {
    
    ballX += ballSpeedX;
    ballY += ballSpeedY;

    checkBallXCollisions();
 
    paddle2Movement();
}

Okay, now anything we add to our checkBallXCollisions function will be executed every time the moveEverything function is executed which we know is thirty times per second. Update your checkBallXCollisions with the following code.

function checkBallXCollisions() {

    if (ballX < 0)
    {
        if ( ballY > paddle1Y && ballY < paddle1Y + PADDLE_HEIGHT )
        {
            ballSpeedX = -ballSpeedX;
            let deltaY = ballY - ( paddle1Y + PADDLE_HEIGHT /2 );
            ballSpeedY = deltaY * 0.35;
        }
        else
        {
            ballReset();
        }
    }
}

So, lets stop here and go over the code we’ve just added.

An overview of our ball collision code

Our code checks to see if our ball x position is less than zero. If the ball x position is less than zero then we check to see if the ball has collided with paddle one. If the ball has detected a collision with paddle one then it reverses the balls direction but also exaggerates the balls y direction and speed based on where the ball collided with the paddle. This exaggeration makes the balls movement less predictable and increases the difficulty of the game. If the ball did not collide with paddle one then the ball is reset back to the centre of the screen. In short, the ball went our of bounds. So far, so good but we’re only checking for collisions with paddle one so now we have to do the same with paddle 2. Update your checkBallXCollision function so it looks like the following.

function checkBallXCollisions() {

    if (ballX < 0)
    {
        if ( ballY > paddle1Y && ballY < paddle1Y + PADDLE_HEIGHT )
        {
            ballSpeedX = -ballSpeedX;
            let deltaY = ballY - ( paddle1Y + PADDLE_HEIGHT /2 );
            ballSpeedY = deltaY * 0.35;
        }
        else
        {
            ballReset();
        }
    }

    if (ballX > canvas.width - PADDLE_WIDTH)
    {
        if ( ballY > paddle2Y && ballY < paddle2Y + PADDLE_HEIGHT )
        {
            ballSpeedX = -ballSpeedX;
            let deltaY = ballY - ( paddle2Y + PADDLE_HEIGHT /2 );
            ballSpeedY = deltaY * 0.35;
        }
        else
        {
            ballReset();
        }
    }
}

Okay, we’ve basically done the same thing with paddle two on the right side of the screen. If the ball has collided with paddle two then send the ball back with a velocity based on its collsion point with the paddle. Or reset the ball because the ball did not collide with the paddle and went out of bounds. Great, that was a lot to cover and the good news is its the hardest part. But, we do need to deal with collisions with the upper and lower parts of the screen.

Detecting the Balls Y movement collisions

Create checkBallYCollisions function and populate it with the following code. Add the function to the end of your functions list. It should be sitting just below checkBallXCollisions

function checkBallYCollisions() {

    if (ballY < 0 || ballY > canvas.height - PADDLE_WIDTH)
    {
        ballSpeedY = -ballSpeedY;
    }
}

Okay, I have a confession to make. This is the same code we added for ball Y collisions before. The reason we deleted it is so we could reorganise it slightly better. But now that we’ve put it in its new home, lets quickly go over what is does. If the balls y position exceeds the upper or lower boundaries of the game then reverse the balls direction. Simple and effective which is always the goal with code. Finally, update your moveEverything function to contain your new checkBallYCollisions function as demonstrated below.

function moveEverything() {
    
    ballX += ballSpeedX;
    ballY += ballSpeedY;

    checkBallXCollisions();
    checkBallYCollisions();

    paddle2Movement();
}

And that is it. All our collision detection is done for this game. But, there is still more to do. We need to add a scoring mechanism so someone can actually win the game. Lets do that next.

Keeping score in JavaScript Pong

Okay, we’ll need some variables to keep track of the score for both the player (paddle 1) and the NPC (paddle 2) so update your constants and variables section as shown below.

// Constants and variables

const FRAMES_PER_SECOND = 30;
const BACKGROUND_COLOR = '#313639';
const BALL_COLOR = '#6abef0';
const PADDLE_COLOR = '#f5f5f5';
const TEXT_COLOR = '#ec88e8';
const PADDLE_WIDTH = 10;
const PADDLE_HEIGHT = 100;

let canvas; 
let canvasContext;
let paddle1Y = 250;
let paddle2Y = 250;
let ballX = 75;
let ballY = 75;
let ballSpeedX = 5;
let ballSpeedY = 7;
let paddle1Score = 0; // New variable for paddle 1 score
let paddle2Score = 0; // New variable for paddle 2 score

The variables paddle1Score and paddle2Score will keep track of the score. paddle1Score will be incremented every time the ball leaves the right hand side of the screen. And, paddle2Score will be incremented every time the ball leaves the left hand side of the screen. In short, if the ball gets past the player, the NPC gets a point. If the ball gets past the NPC, the player gets a point.

Updating our check ball collisions function

Earlier, we created a function called checkBallXCollisions to detect ball collisions with the left and right boundaries of the screen. That exercise in organising code is paying dividends because not only is it handling our collision logic, now it can handle our scoring logic too. High fives all round. Update the checkBallXCollisions function as shown below.

function checkBallXCollisions() {

    if (ballX < 0)
    {
        if ( ballY > paddle1Y && ballY < paddle1Y + PADDLE_HEIGHT )
        {
            ballSpeedX = -ballSpeedX;
            let deltaY = ballY - ( paddle1Y + PADDLE_HEIGHT /2 );
            ballSpeedY = deltaY * 0.35;
        }
        else
        {
            paddle2Score ++; // New code that gives paddle 2 a point.
            ballReset();
        }
    }

    if (ballX > canvas.width - PADDLE_WIDTH)
    {
        if ( ballY > paddle2Y && ballY < paddle2Y + PADDLE_HEIGHT )
        {
            ballSpeedX = -ballSpeedX;
            let deltaY = ballY - ( paddle2Y + PADDLE_HEIGHT /2 );
            ballSpeedY = deltaY * 0.35;
        }
        else
        {
            paddle1Score ++; // New code that gives paddle 1 a point.
            ballReset();
        }
    }
}

And that is it, we are now keeping score. Two variables and two simple lines of code is all we need to keep track of the score in our game. However, it would be useful to see that score on the screen so lets write that code next.

Displaying the score in JavaScript Pong

Earlier on in this tutorial, we created a created a function for drawing circles and another for drawing rectangles. This allowed us to reuse a few lines of code to handle most of our graphics. However, we didn’t add a function for printing text. And like the aforementioned functions for drawing shapes, printing text can and should be bundled up into a function and reused throughout the application. So, lets add the following function to the end of our functions section as shown below.

// New function that contains our code for printing messages on the screen.
function printText(text, xPos, yPos, size, font, color) {
    canvasContext.fillStyle = color;
    canvasContext.font = `${size} ${font}`;
    canvasContext.fillText(text, xPos, yPos);
}

Before we move on, lets quickly go over what printText does. It accepts some text, an x and y coordinate, a font size, a font and finally a colour. This allows us to print a message anywhere on the screen with a specified font and size. And, we can reuse this code as many times as we like to print what ever we want which is what reusable code is all about. However, we still cant see the score on the screen so lets sort that by updating our drawEverything function as shown below.

function drawEverything() {
    
    // Draw background
    colorRect(0, 0, canvas.width, canvas.height, BACKGROUND_COLOR);
    
    // Draw ball
    colorCircle(ballX,ballY, 10, BALL_COLOR);
    
    // Draw paddles
    colorRect(0, paddle1Y, PADDLE_WIDTH, PADDLE_HEIGHT, PADDLE_COLOR);
    colorRect(canvas.width - PADDLE_WIDTH, paddle2Y, PADDLE_WIDTH, PADDLE_HEIGHT, PADDLE_COLOR);

    // New code for displaying our scores
    printText(paddle1Score.toString(), 200, 80, "60px", "Arial", TEXT_COLOR);
    printText(paddle2Score.toString(), canvas.width - 235, 80, "60px", "Arial", TEXT_COLOR);
}

And that’s it. If you launch the index.html file you’ll see that our game is now keeping score. We are pretty much on our way to a fully fledged clone of the arcade classic pong. But, there is one thing missing from the screen. That missing item is a net, so lets add that in next.

Drawing the Net

Our net, will be a dotted line that runs down the center of the screen. To create that effect in the game we basically draw a collection of rectangles that are spaced evenly on a vertical axis. This can be done easily with a simple for loop. However, to keep things organised we will create a new function for drawing the net. The drawNet function is shown below. Go ahead and add this function to the end of your functions section.

// New funciton for drawing a net.
function drawNet() {

    for ( let i = 0; i < canvas.height; i += 40 ) {
        colorRect(canvas.width / 2 - 1, i, 2, 20, PADDLE_COLOR, canvasContext);
    }
}

As you can see, the code itself is fairly simple. We have a simple for loop that draws evenly spaced rectangles down the middle of the screen. And, the for loop continues to draw rectangles until the variable i exceeds the height of the screen.

Drawing our net on the screen

So far so good, but we still dont have a net on our screen. We must add the net to the drawEverything function. But, our placement here is vital. We want to ensure that the net is drawn before the ball. That ensures that when our ball crosses the net, it appears above the net rather than below. To achieve this, update the drawEverything with the code shown below.

function drawEverything() {
    
    // Draw background
    colorRect(0, 0, canvas.width, canvas.height, BACKGROUND_COLOR);
    
    // New. Draw the net.
    drawNet();

    // Draw ball
    colorCircle(ballX,ballY, 10, BALL_COLOR);
    
    // Draw paddles
    colorRect(0, paddle1Y, PADDLE_WIDTH, PADDLE_HEIGHT, PADDLE_COLOR);
    colorRect(canvas.width - PADDLE_WIDTH, paddle2Y, PADDLE_WIDTH, PADDLE_HEIGHT, PADDLE_COLOR);

    printText(paddle1Score.toString(), 200, 80, "60px", "Arial", TEXT_COLOR);
    printText(paddle2Score.toString(), canvas.width - 235, 80, "60px", "Arial", TEXT_COLOR);
}

And there you have it. JavaScript Pong has a net. High fives all round. We’ve come along way. We have a player paddle, an NPC paddle, a ball that bounces around the screen. A net and our game is also keeping score. Things are coming together but we are not quite there yet. We can play the game but the game has no endpoint. That’s right, if we left the game as it is now, no one would ever win because there is no victory conditions.  We are going to solve that problem by adding in a victory condition and a game over screen.

Game over, Man. Game over!

As mentioned above, a game is not really a game if there are no victory conditions. As it stands, our game has no victory conditions. We obviously want to fix that issue. So how do we do that? Well, we need to set a winning score, say, the best out of nine. And, when one of the game actors reach that score, we show the game over screen. So lets get that logic into our game.

Creating the game over screen

First off, lets add update our constants and variables section with a constant for the victory condition and a variable for toggling the game over screen. Update your code as demonstrated below.

// Constants and variables

const FRAMES_PER_SECOND = 30;
const BACKGROUND_COLOR = '#313639';
const BALL_COLOR = '#6abef0';
const PADDLE_COLOR = '#f5f5f5';
const TEXT_COLOR = '#ec88e8';
const PADDLE_WIDTH = 10;
const PADDLE_HEIGHT = 100;
const VICTORY_CONDITION = 9; // New victory condition value.

let canvas; 
let canvasContext;
let paddle1Y = 250;
let paddle2Y = 250;
let ballX = 75;
let ballY = 75;
let ballSpeedX = 5;
let ballSpeedY = 7;
let paddle1Score = 0;
let paddle2Score = 0;
let showWinningScreen = false; // New show winning screen toggle

Now that we have our new constant and variable in place, we need to figure out where in our application to check to see if the victory condition has been met. As it happens, we have an existing function that is perfect for our new code. If you recall, earlier in the tutorial, we created a function for resetting the ball in the middle of the screen. That function is called when the ball leaves the left or right hand side of the screen. We reset the ball because it means that either the player or the NPC has scored a point by getting the ball past their opponent. Rather conveniently, this is also a good place to check to see if our victory condition has been met. We can do that by updating our ballReset function as demonstrated below.

function ballReset() {

    // New victory condition code...
    if ( paddle1Score > VICTORY_CONDITION || paddle2Score > VICTORY_CONDITION )
    {
        showWinningScreen = true;
    }
    ballX = canvas.width/2;
    ballY = canvas.height/2;
}

As you can see from the code above, when the ballReset function is called, we check to see if either paddle one or paddle two have more points than the victory condition. If either does, then the showWinningScreen toggle is set to true. Now that the victory condition is set, we need to display the game over screen when either player wins the game. Lets do that next.

Drawing the JavaScript Pong game over screen

The logic for our game over screen is quite simple. If either of the players have won the game draw the game over screen otherwise, draw the net, paddles and ball. So, if you look at our drawEverything function, we draw the background first and then we draw the net. We want to add our game over screen code after drawing the background but before we draw the net as the comment in the code shows.

function drawEverything() {
    
    // Draw background
    colorRect(0, 0, canvas.width, canvas.height, BACKGROUND_COLOR);

    // VICTORY CONDITION CODE GOES HERE
    
    // Draw the net.
    drawNet();

    // Draw ball
    colorCircle(ballX,ballY, 10, BALL_COLOR);
    
    // Draw paddles
    colorRect(0, paddle1Y, PADDLE_WIDTH, PADDLE_HEIGHT, PADDLE_COLOR);
    colorRect(canvas.width - PADDLE_WIDTH, paddle2Y, PADDLE_WIDTH, PADDLE_HEIGHT, PADDLE_COLOR);

    printText(paddle1Score.toString(), 200, 80, "60px", "Arial", TEXT_COLOR);
    printText(paddle2Score.toString(), canvas.width - 235, 80, "60px", "Arial", TEXT_COLOR);
}

So, when we run the game, it sets the background colour. Then if the game is over, it draws the game over screen and then ignores the rest of the code in the drawEverything function. The end result is a black screen with a game over message. Lets update our drawEverything screen with our victory condition logic.

function drawEverything() {
    
    // Draw background
    colorRect(0, 0, canvas.width, canvas.height, BACKGROUND_COLOR);

    // New. Draw winning screen here...
    if ( showWinningScreen  )
    {
        
        let winner = "";
        
        if ( paddle1Score >= VICTORY_CONDITION )
        {
            winner = "1";
        }
        
        if ( paddle2Score >= VICTORY_CONDITION )
        {
            winner = "2";
        }
        
        printText('Paddle ' + winner + ' Wins', 110, 250, "60px", "Arial", PADDLE_COLOR);
        printText('Click to continue', 190, 320, "30px", "Arial", TEXT_COLOR);
        
        return; // Return exits the drawEverything function.
    }
    
    // Draw the net.
    drawNet();

    // Draw ball
    colorCircle(ballX,ballY, 10, BALL_COLOR);
    
    // Draw paddles
    colorRect(0, paddle1Y, PADDLE_WIDTH, PADDLE_HEIGHT, PADDLE_COLOR);
    colorRect(canvas.width - PADDLE_WIDTH, paddle2Y, PADDLE_WIDTH, PADDLE_HEIGHT, PADDLE_COLOR);

    printText(paddle1Score.toString(), 200, 80, "60px", "Arial", TEXT_COLOR);
    printText(paddle2Score.toString(), canvas.width - 235, 80, "60px", "Arial", TEXT_COLOR);
}

And that is it. If you run the game. The game over screen will be displayed when either player scores more the nine points. However, there is a problem. Once the game is over, we cant start the game again without refreshing the browser. Lets change that.

Restarting our JavaScript Pong Game

We now have a handy little screen that tells us our game is over. That’s great but we need a way to exit the game over, or exit the exit as to speak. As you can see on the game over screen, the message says, click exit to continue. So, lets make that happen. If you recall, we already have a mouse event that moves the player paddle. We’ll pretty much repeat the process for the click to continue event. We will create a function that will restart the game and then we”’ add an event listener that triggers our click to continue function. So lets get started by adding the new function shown below.

// New code for handling the mouse click.
function clickToContinue(event) {
    if ( showWinningScreen )
    {
        paddle1Score = 0;
        paddle2Score = 0;
        showWinningScreen = false;
    }
}

Once we have that in place, we add an event listener to our onload function as demonstrated below.

window.onload = function() {

    canvas = document.getElementById('canvas');
    canvasContext = canvas.getContext('2d');
    
    setInterval(gameLoop, 1000/FRAMES_PER_SECOND);

    canvas.addEventListener('mousemove', updateMousePos); 

    // New click to continue mouse event.
    canvas.addEventListener('mousedown', clickToContinue);
}

And there you have it. Our new click to continue function is in place and we have a dedicated event listener to trigger the function.  If you run the game, when it ends, you’ll be able to click the screen and restart the game. And that is it! Take a bow. Pat yourself on the back and buy yourself a beer. Congratulations, you have created a game. At this point, the exercise is done. You have learned all the basics required to build a retro arcade classic. But, in typical development fashion, the coding is never done. The game works but there are some obvious, and not so obvious improvements. And, making these fine adjustments is what will improve your retro creation and it will make you think about the program and how it works. Here are a few suggestions from the author.

Taking JavaScript Pong to the next level

When you start a project, you begin with the best intentions. However, as you close in on the end product, you realise that some of your earlier coding decisions are weak or worst still, flawed. This doesn’t matter if you are engaged in a massive long term project or a day build. Our JavaScript Pong clone is no different. We’ve reached the end of the road and there are many areas for improvement. Rather than me going through the improvements as part of the tutorial, I think its better to make some suggestion and then allow you to figure out these improvements yourself. And, if you so wish, add your own quirks. So, here are some areas of our current version of JavaScript Pong that are in need of some improvement. Some improvement suggestions for our JavaScript Pong clone

#1 Screen Dimensions

The screen dimensions are 600 x 600 which isn’t that great for the game. It would be better if the dimensions were 900 x 600 or full screen. How would you go about changing that and how would it effect the layout of the game? Also, if you went full screen, how would you handle resizing? Hot tip. You can add a resize=”your function” to the html body tag.

#2 Ball Reset

When a point is scored, the ball is reset. However, the only thing that’s actually reset is the balls position. I.e, the ball is sent back to the middle of the screen. But, it would be better if the balls position, speed and direction were changed based on who scored the point. For example, if player one scored a point, then the ball should start on players ones side of the screen and be moving towards the player twos side of the screen. Hot tip. We have a ball reset function which could be modified to include these behaviour characteristics.

#3 Pro Player 2

In the current version of our JavaScript Pong clone, player/paddle two is a little slow to react to the change in balls y direction.  Could you make paddle 2 a Pong pro? Hot tip. There is a paddle 2 function. Try playing about with paddles y speed.

#4 Audio enhancement

Our JavaScript Pong clone has no sound. How would you add sound to the game and how would you make sure that the sound clips were properly loaded before the game began? 

#5 The Game Over Bug

Our game has a bug. It happens after the game over screen is displayed. Can you spot it? You might, if you wait long enough. Hot tip. The bug is associated with the game state or lack of, when the game over screen is displayed.

#6 Its your game

Finally, its your game, you coded it, you own it and you can make decisions about what should and shouldn’t be in your creation. That is the beauty of building your own games.

Have fun and keep coding my fellow developers

And that is it. We are done here. You can find the source code for this JavaScript Pong tutorial on my GitHub Page. And, you can also find my improved full screen version on this website. If you want to know how to do the full screen games checkout my JavaScript Breakout tutorial.

I hope you enjoyed this tutorial and most importantly, I hope you enjoyed building JavaScript Pong. If you did build the game please leave a comment below and if possible a link to your creation. Have fun and keep coding.