Howdy Folks, in this new tutorial, you will learn how to create the classic Ping Pong Game with Javascript and HTML5 canvas. Ping Pong, also known as Table tennis is a sport in which two or four players hit a lightweight ball, also known as the ping-pong ball, back and forth across a table using small paddles. It is an awesome game to play. But learning to code it yourselves makes it much more interesting!

Before jumping right in first, take a look at the demo here on Codepen --> Ping Pong Demo

You can download the completed project from GitHub --> Ping Pong Javascript

You can get the final code at the end of each section too...

## Prerequisites

I assume you are already familiar with basic Javascript. And you should have a decent understanding of HTML5 canvas. If you never used HTML5 canvas before, then first watch this crash course video by the great Brad Traversy --> https://www.youtube.com/watch?v=gm1QtePAYTM. Finally, you should also need a little bit of maths knowledge to make this.

But anyway feel free to follow along, I will try my best to keep this tutorial simple.

I am going to use "visual studio code" as my text editor. You are free to use whatever text editor you like. And finally, make sure you have installed Live Server by Ritwick Dey in visual studio code. If you don't have Live Server installed, then:

• Open visual studio code
• Goto Extensions tab (Ctrl + Shift + X)
• Search for "Live Server" by Ritwick Dey
• Then Install it

Now Let's BreakDown the Logic...

## Breaking Down the Logic

Logic is quite simple. Our Pong game has the following components:

• The game board
• Net in the middle
• Two Score text on either side
• Two Paddles, the user and the ai
• And our Ping Pong Ball. When the ai misses the ball which the user had hit, the user gains 1 point and vice versa. One thing to remember here is how the axis is laid out. The axis starts from the top left like below: When we go right, the x increases, and when we go towards the bottom, the y value increases.

I broke the tutorial into the small sections. So we will be exploring everything bit by bit. Now Let's Get Started...

## Initial Setups

I like to organize everything from the very beginning. So, create a folder structure similar to below: • First, create the root folder which holds everything and name it as "Ping Pong"
• Then open this folder inside visual studio code.
• Then directly inside this root folder, create a file named "index.html" --> This holds our HTML and CSS.
• Then again directly inside our root folder, create two folders:
• One folder named "js" --> Inside this folder, create a file named "script.js".
• Then another folder named "sounds" --> Download all the 3 sounds from here and put it inside this folder.

That's it. Now you should have a folder structure similar to above. Now the HTML part...

## HTML

Open "index.html" and type the following:

``````<!DOCTYPE html>
<html>
<!-- meta tags are not necessary here, but having them always is a good practice -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- title -->
<title>Ping Pong</title>
<!-- some css styles -->
<style>
* {
margin: 0;
}

body {
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
</style>
<body>

<!-- our "canvas" where we draw things-->
<canvas id="canvas" width="600" height="400"></canvas>

<script type="text/javascript" src="js/script.js"></script>
</body>
</html>`````` There's nothing special in there.
• We are giving some <meta> tags, one for setting the charset, and one for the viewport. They are not required here. But always using them is a good practice.
• Then set the title.
• Then give our CSS styles internally inside <style></style> tags. Here we are just centering our gameboard. Nothing fancy.
• Inside <body></body> tag, we give our <canvas> with id="canvas", width="600" pixels, and height="400" pixels .
• Finally, we are linking our "script.js" file.

That's it.

Open it with Live Server by right-clicking on the "index.html" file (inside visual studio code), then scroll down and click on the "Open with Live Server" option and take a look at it at the browser. You can't see anything now. Because we didn't add any Javascript functionalities. So let's do that...

## Javascript

The first thing we need to do is to grab the reference of our #canvas.

Open the "script.js" file and type the following:

``````// grab a reference of our "canvas" using its id
const canvas = document.getElementById('canvas');`````` To draw something on our canvas, we need to get the context. Without context, we can't do anything. Below the previous code type this:
``````/* get a "context". Without "context", we can't draw on canvas */
const ctx = canvas.getContext('2d');``````

If you ever think you get lost while typing the code, don't worry. The final complete code for the "script.js" file is given at the very bottom for reference. So use it whenever you get stuck. Now we are going to render some necessary components.

## render() function

Create a render() function and draw the black game board.

Type this at the bottom of our file:

``````// render function draws everything on to canvas
function render() {
// set a style
ctx.fillStyle = "#000"; /* whatever comes below this acquires black color (#000). */
// draws the black board
ctx.fillRect(0, 0, canvas.width, canvas.height);
}

render();`````` Again I assume you had an understanding of the canvas. If not please watch the video I mentioned above then continue this tutorial.

Above, we are just drawing the gameboard. Now you should see something like this on the browser: Now we are going to draw other objects like net, user paddle, ai paddle, ball, etc. Each of them has its properties like width, height, x position, y position, color, and things like that. So we are going to make some constants and some javascript objects at the top of the file to hold the values we mentioned now.

Type this at the top of the file below this line: ``````/* some extra variables */
const netWidth = 4;
const netHeight = canvas.height;

let upArrowPressed = false;
let downArrowPressed = false;

/* some extra variables ends */

/* objects */
// net
const net = {
x: canvas.width / 2 - netWidth / 2,
y: 0,
width: netWidth,
height: netHeight,
color: "#FFF"
};

const user = {
x: 10,
y: canvas.height / 2 - paddleHeight / 2,
color: '#FFF',
score: 0
};

const ai = {
x: canvas.width - (paddleWidth + 10),
y: canvas.height / 2 - paddleHeight / 2,
color: '#FFF',
score: 0
};

// ball
const ball = {
x: canvas.width / 2,
y: canvas.height / 2,
speed: 7,
velocityX: 5,
velocityY: 5,
color: '#05EDFF'
};

/* objects declaration ends */

/* drawing functions */

/* drawing functions end */``````
• On the first line of this code, we are just defining some constants for holding the width of the net and height of our net.
• "upArrowPressed" and "downArrowPressed" holds true or false values. They represent the status of arrow keys, like whether they are now pressed or not.
• Then some objects:
• the net object holds the x position, y position, width, height, and color of the net.
• the user and ai do the same.
• ball object holds its x position, y position, radius, speed, velocityX, velocityY, and color.

The calculations we did inside each object to position each of them is simple. Just go through them. One thing to understand here is that from whereon the x and y-axis start for the ball and other rectangles like paddles and net. The following diagram explains it:  The objects are ready now. Let's make some functions to draw each object.

Type the following between the "/* drawing functions */  /* drawing functions end */" comment:

``````// function to draw net
function drawNet() {
// set the color of net
ctx.fillStyle = net.color;

// syntax --> fillRect(x, y, width, height)
ctx.fillRect(net.x, net.y, net.width, net.height);
}

// function to draw score
function drawScore(x, y, score) {
ctx.fillStyle = '#fff';
ctx.font = '35px sans-serif';

// syntax --> fillText(text, x, y)
ctx.fillText(score, x, y);
}

function drawPaddle(x, y, width, height, color) {
ctx.fillStyle = color;
ctx.fillRect(x, y, width, height);
}

// function to draw ball
function drawBall(x, y, radius, color) {
ctx.fillStyle = color;
ctx.beginPath();
// syntax --> arc(x, y, radius, startAngle, endAngle, antiClockwise_or_not)
ctx.arc(x, y, radius, 0, Math.PI * 2, true); // π * 2 Radians = 360 degrees
ctx.closePath();
ctx.fill();
}``````

Nothing fancy here. It's pretty self-explanatory. Now let's call those functions inside render() function.

Type the following inside render() function and below where you drew the game board:

``````// draw net
drawNet();
// draw user score
drawScore(canvas.width / 4, canvas.height / 6, user.score);
// draw ai score
drawScore(3 * canvas.width / 4, canvas.height / 6, ai.score);
// draw ball
• drawNet() function draws the net.
• drawScore() function draws the two scores on either side. If you are confused about how I positioned the scores at where they are, then the following diagram will help you understand it:

I divided the x-axis into four: Then divided the y-axis into 6: • And the drawBall() function draws the ball.

Now you should see something like this on the browser: Now the code you typed up to this point looks like below:

``````// grab a reference of our "canvas" using its id
const canvas = document.getElementById('canvas');
/* get a "context". Without "context", we can't draw on canvas */
const ctx = canvas.getContext('2d');

/* some extra variables */
const netWidth = 4;
const netHeight = canvas.height;

let upArrowPressed = false;
let downArrowPressed = false;

/* some extra variables ends */

/* objects */
// net
const net = {
x: canvas.width / 2 - netWidth / 2,
y: 0,
width: netWidth,
height: netHeight,
color: "#FFF"
};

const user = {
x: 10,
y: canvas.height / 2 - paddleHeight / 2,
color: '#FFF',
score: 0
};

const ai = {
x: canvas.width - (paddleWidth + 10),
y: canvas.height / 2 - paddleHeight / 2,
color: '#FFF',
score: 0
};

// ball
const ball = {
x: canvas.width / 2,
y: canvas.height / 2,
speed: 7,
velocityX: 5,
velocityY: 5,
color: '#05EDFF'
};

/* objects declaration ends */

/* drawing functions */
// function to draw net
function drawNet() {
// set the color of net
ctx.fillStyle = net.color;

// syntax --> fillRect(x, y, width, height)
ctx.fillRect(net.x, net.y, net.width, net.height);
}

// function to draw score
function drawScore(x, y, score) {
ctx.fillStyle = '#fff';
ctx.font = '35px sans-serif';

// syntax --> fillText(text, x, y)
ctx.fillText(score, x, y);
}

function drawPaddle(x, y, width, height, color) {
ctx.fillStyle = color;
ctx.fillRect(x, y, width, height);
}

// function to draw ball
function drawBall(x, y, radius, color) {
ctx.fillStyle = color;
ctx.beginPath();
// syntax --> arc(x, y, radius, startAngle, endAngle, antiClockwise_or_not)
ctx.arc(x, y, radius, 0, Math.PI * 2, true); // π * 2 Radians = 360 degrees
ctx.closePath();
ctx.fill();
}

/* drawing functions end */

// render function draws everything on to canvas
function render() {
// set a style
ctx.fillStyle = "#000"; /* whatever comes below this acquires black color (#000). */
// draws the black board
ctx.fillRect(0, 0, canvas.width, canvas.height);
// draw net
drawNet();
// draw user score
drawScore(canvas.width / 4, canvas.height / 6, user.score);
// draw ai score
drawScore(3 * canvas.width / 4, canvas.height / 6, ai.score);
// draw ball
}

render();``````  But did you noticed something? There is no "game loop" in our game... Let's create it.

## gameLoop()

The Game loop is the most important part of any game. The game loop is the overall flow control for the entire game program. It's a loop because the game keeps doing a series of actions over and over again until the user quits.

Remove the render() function call at the end and type this:

``````// gameLoop
function gameLoop() {
// update() function here
update();
// render() function here
render();
}

// calls gameLoop() function 60 times per second
setInterval(gameLoop, 1000 / 60);
``````

This helps our game to run at 60 frames per second.

Now our game should have crashed. Because we called our update() function but hadn't made it. Let's make the update() function.

## update()

Above our render() function definition, type this:

``````// update function, to update things position
function update() {

// check if ball hits top or bottom wall

// move the ball
ball.x += ball.velocityX;
ball.y += ball.velocityY;

}``````  This makes our ball move downwards. You can see so many comments inside our update() function. That's what we are going to do next.

Type this above our update() function definition:

``````/* moving Paddles */
// add an eventListener to browser window

// gets activated when we press down a key
function keyDownHandler(event) {
// get the keyCode
switch (event.keyCode) {
// "up arrow" key
case 38:
// set upArrowPressed = true
upArrowPressed = true;
break;
// "down arrow" key
case 40:
downArrowPressed = true;
break;
}
}

// gets activated when we release the key
function keyUpHandler(event) {
switch (event.keyCode) {
// "up arraow" key
case 38:
upArrowPressed = false;
break;
// "down arrow" key
case 40:
downArrowPressed = false;
break;
}
}

/* moving paddles section end */``````

This detects the key presses and toggles our upArrowPressed and downArrowPressed variables we defined at the very top.

Type the following code below "// move the paddle" comment inside update() function:

``````if (upArrowPressed && user.y > 0) {
user.y -= 8;
} else if (downArrowPressed && (user.y < canvas.height - user.height)) {
user.y += 8;
}``````

Now you should be able to move the user paddle along the y-axis.

## Collision Detection and Scoring

Let's add the collision detection functionalities.

Then type the following code below "// check if ball hits top or bottom wall" comment inside update() function::

``````
if (ball.y + ball.radius >= canvas.height || ball.y - ball.radius <= 0) {
// play wallHitSound

ball.velocityY = -ball.velocityY;
}

// if ball hit on right wall
if (ball.x + ball.radius >= canvas.width) {
// play scoreSound

// then user scored 1 point
user.score += 1;
reset();
}

// if ball hit on left wall
if (ball.x - ball.radius <= 0) {
// play scoreSound

// then ai scored 1 point
ai.score += 1;
reset();
}``````

This makes the ball to detect the top and bottom walls and then bounce back. Then also if our ball crosses the left or the right wall, the scores will increase.

But now again our game should have crashed. That's because if the ball hits the left or right wall, then the corresponding players get the score increment. But after a player scored a point, we are calling reset() function. But we hadn't created it yet. That's why the game crashed now. So Let's create it.

Type this above our update() function definition:

``````// reset the ball
function reset() {
// reset ball's value to older values
ball.x = canvas.width / 2;
ball.y = canvas.height / 2;
ball.speed = 7;

// changes the direction of ball
ball.velocityX = -ball.velocityX;
ball.velocityY = -ball.velocityY;
}
``````

This will reset our ball's position.

Let's make our ball collide with both paddles. For that, we are going to make a function called "collisionDetect()" which returns true if the ball hits any of the paddle and otherwise false.

Type this below reset() function definition:

``````// collision Detect function
function collisionDetect(player, ball) {
// returns true or false
player.top = player.y;
player.right = player.x + player.width;
player.bottom = player.y + player.height;
player.left = player.x;

return ball.left < player.right && ball.top < player.bottom && ball.right > player.left && ball.bottom > player.top;
}``````

Here we are first taking all the sides like the top, right, bottom, and left for our ball and player. Our player object is going to be either user or ai. This depends on which paddle the ball hits.

After determining all the sides, we do the calculation. The calculation is quite simple. Just give it a go then you will understand it.

Then type the following code below "// collision detection on paddles" comment inside update() function:

``````
let player = (ball.x < canvas.width / 2) ? user : ai;

if (collisionDetect(player, ball)) {
// play hitSound

// default angle is 0deg in Radian
let angle = 0;

// if ball hit the top of paddle
if (ball.y < (player.y + player.height / 2)) {
// then -1 * Math.PI / 4 = -45deg
angle = -1 * Math.PI / 4;
} else if (ball.y > (player.y + player.height / 2)) {
// if it hit the bottom of paddle
// then angle will be Math.PI / 4 = 45deg
angle = Math.PI / 4;
}

/* change velocity of ball according to on which paddle the ball hitted */
ball.velocityX = (player === user ? 1 : -1) * ball.speed * Math.cos(angle);
ball.velocityY = ball.speed * Math.sin(angle);

// increase ball speed
ball.speed += 0.2;
}``````
• One the first line, we are detecting who the player is. If the ball is on the left half of the board, then the player will be user paddle and vice versa.
• Then we are passing our player object and ball object to the collisionDetect() function to find if any collision had happened.
• If the collision happened then, we need to determine where the ball hit on the paddle. Why that?
• Because if the ball hits at the top of the paddle, then the ball should reflect at -45 degree angle.
• If it hits at the bottom of the paddle, then it should reflect at a 45-degree angle.
• Otherwise 0-degree angle. • Another thing to consider here is the direction of the ball.
• If the ball hits on the user paddle, then push it towards the right side.
• If the ball hits on the ai paddle, then push it towards the left side. That's what this line does: • Then we are calculating our velocityX and velocityY of the ball. It is not the real velocity equation. Here we are doing it using speed * angle.
• To find velocity along the x-axis, take cos of angle.
• To find velocity along the y-axis, take the sin of angle.
• Finally, we are increasing the speed of the ball a little bit.

Now let's make our ai intelligent.

Type the following code below "// ai paddle movement" comment inside update() function:

``ai.y += ((ball.y - (ai.y + ai.height / 2))) * 0.09;``

This will make our ai paddle behave as an ai. The last value in the equation, the 0.09, you can tweak it to make our game harder.

We had reached our end of the tutorial :) The final thing to do is to add some sounds. We are going to add three sounds. Let's first import them.

Type the code at the very top below this line: ``````// some sounds
const hitSound = new Audio('../sounds/hitSound.wav');
const scoreSound = new Audio('../sounds/scoreSound.wav');
const wallHitSound = new Audio('../sounds/wallHitSound.wav');``````
• "hitSound.wav" --> plays when the ball hits the paddle.
• "scoreSound.wav" --> plays when someone scores a point.
• "wallHitSound" --> plays when the ball hits the top and bottom wall.

Now let's play them at appropriate places.

Type this inside if condition where we detect collision inside update() function:

``hitSound.play();`` Type this inside two if conditions inside update() function, where we detect the collision on the right wall and the left wall:

``scoreSound.play();`` Now finally type this inside if condition where we detect the collision on top and bottom wall. This is also inside the update() function:

``wallHitSound.play();`` Hooray, we finally did it. Here is the final code for the "script.js" file. Make sure the code you typed up to this point inside the "script.js" is exactly like the following:

``````// grab a reference of our "canvas" using its id
const canvas = document.getElementById('canvas');
/* get a "context". Without "context", we can't draw on canvas */
const ctx = canvas.getContext('2d');

// some sounds
const hitSound = new Audio('../sounds/hitSound.wav');
const scoreSound = new Audio('../sounds/scoreSound.wav');
const wallHitSound = new Audio('../sounds/wallHitSound.wav');

/* some extra variables */
const netWidth = 4;
const netHeight = canvas.height;

let upArrowPressed = false;
let downArrowPressed = false;

/* some extra variables ends */

/* objects */
// net
const net = {
x: canvas.width / 2 - netWidth / 2,
y: 0,
width: netWidth,
height: netHeight,
color: "#FFF"
};

const user = {
x: 10,
y: canvas.height / 2 - paddleHeight / 2,
color: '#FFF',
score: 0
};

const ai = {
x: canvas.width - (paddleWidth + 10),
y: canvas.height / 2 - paddleHeight / 2,
color: '#FFF',
score: 0
};

// ball
const ball = {
x: canvas.width / 2,
y: canvas.height / 2,
speed: 7,
velocityX: 5,
velocityY: 5,
color: '#05EDFF'
};

/* objects declaration ends */

/* drawing functions */
// function to draw net
function drawNet() {
// set the color of net
ctx.fillStyle = net.color;

// syntax --> fillRect(x, y, width, height)
ctx.fillRect(net.x, net.y, net.width, net.height);
}

// function to draw score
function drawScore(x, y, score) {
ctx.fillStyle = '#fff';
ctx.font = '35px sans-serif';

// syntax --> fillText(text, x, y)
ctx.fillText(score, x, y);
}

function drawPaddle(x, y, width, height, color) {
ctx.fillStyle = color;
ctx.fillRect(x, y, width, height);
}

// function to draw ball
function drawBall(x, y, radius, color) {
ctx.fillStyle = color;
ctx.beginPath();
// syntax --> arc(x, y, radius, startAngle, endAngle, antiClockwise_or_not)
ctx.arc(x, y, radius, 0, Math.PI * 2, true); // π * 2 Radians = 360 degrees
ctx.closePath();
ctx.fill();
}

/* drawing functions end */

// add an eventListener to browser window

// gets activated when we press down a key
function keyDownHandler(event) {
// get the keyCode
switch (event.keyCode) {
// "up arrow" key
case 38:
// set upArrowPressed = true
upArrowPressed = true;
break;
// "down arrow" key
case 40:
downArrowPressed = true;
break;
}
}

// gets activated when we release the key
function keyUpHandler(event) {
switch (event.keyCode) {
// "up arraow" key
case 38:
upArrowPressed = false;
break;
// "down arrow" key
case 40:
downArrowPressed = false;
break;
}
}

/* moving paddles section end */

// reset the ball
function reset() {
// reset ball's value to older values
ball.x = canvas.width / 2;
ball.y = canvas.height / 2;
ball.speed = 7;

// changes the direction of ball
ball.velocityX = -ball.velocityX;
ball.velocityY = -ball.velocityY;
}

// collision Detect function
function collisionDetect(player, ball) {
// returns true or false
player.top = player.y;
player.right = player.x + player.width;
player.bottom = player.y + player.height;
player.left = player.x;

return ball.left < player.right && ball.top < player.bottom && ball.right > player.left && ball.bottom > player.top;
}

// update function, to update things position
function update() {
if (upArrowPressed && user.y > 0) {
user.y -= 8;
} else if (downArrowPressed && (user.y < canvas.height - user.height)) {
user.y += 8;
}

// check if ball hits top or bottom wall
if (ball.y + ball.radius >= canvas.height || ball.y - ball.radius <= 0) {
// play wallHitSound
wallHitSound.play();
ball.velocityY = -ball.velocityY;
}

// if ball hit on right wall
if (ball.x + ball.radius >= canvas.width) {
// play scoreSound
scoreSound.play();
// then user scored 1 point
user.score += 1;
reset();
}

// if ball hit on left wall
if (ball.x - ball.radius <= 0) {
// play scoreSound
scoreSound.play();
// then ai scored 1 point
ai.score += 1;
reset();
}

// move the ball
ball.x += ball.velocityX;
ball.y += ball.velocityY;

ai.y += ((ball.y - (ai.y + ai.height / 2))) * 0.09;

let player = (ball.x < canvas.width / 2) ? user : ai;

if (collisionDetect(player, ball)) {
// play hitSound
hitSound.play();
// default angle is 0deg in Radian
let angle = 0;

// if ball hit the top of paddle
if (ball.y < (player.y + player.height / 2)) {
// then -1 * Math.PI / 4 = -45deg
angle = -1 * Math.PI / 4;
} else if (ball.y > (player.y + player.height / 2)) {
// if it hit the bottom of paddle
// then angle will be Math.PI / 4 = 45deg
angle = Math.PI / 4;
}

/* change velocity of ball according to on which paddle the ball hitted */
ball.velocityX = (player === user ? 1 : -1) * ball.speed * Math.cos(angle);
ball.velocityY = ball.speed * Math.sin(angle);

// increase ball speed
ball.speed += 0.2;
}
}

// render function draws everything on to canvas
function render() {
// set a style
ctx.fillStyle = "#000"; /* whatever comes below this acquires black color (#000). */
// draws the black board
ctx.fillRect(0, 0, canvas.width, canvas.height);

// draw net
drawNet();
// draw user score
drawScore(canvas.width / 4, canvas.height / 6, user.score);
// draw ai score
drawScore(3 * canvas.width / 4, canvas.height / 6, ai.score);
// draw ball
}

// gameLoop
function gameLoop() {
// update() function here
update();
// render() function here
render();
}

// calls gameLoop() function 60 times per second
setInterval(gameLoop, 1000 / 60);``````

## Wrapping Up

I hope you enjoyed this tutorial. If you had any doubts, then please comment them below. And if you enjoyed this tutorial, then please hit the like button below. Thank you ;)