BLOG

# Learn to Code Ping Pong Game using Javascript and HTML5

thecodingpie . . 23 min read

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`.
• `paddleWidth` holds the width of both paddle and `paddleHeight` holds the height of the paddles.
• `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:

• `drawPaddle()` function draws the paddles.
• 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. Thank you ;)