Skip to main content

Pong Game in HTML & JavaScript (Updated)

HTML Pong Game Gameplay

Pong is one of the first games that many people from the 80s or 90s had played as children. Lots of people know it as a simple arcade game but what they probably do not know is that this simple game helped establish the video game industry!

In this post, we'll be making our version of a very simple but fully working Pong game in HTML, CSS and JavaScript.

Basic Game Structure

Games, however simple or complex they may be, follow the basic Game Loop design as shown in the chart below. Event-oriented game engines usually encapsulate the design and provide you with an event mechanism for handling various parts like the input, update, and rendering but internally the basic design is being followed.



Pong Game Loop

For our Game Loop, we'll be using JavaScript setInterval so that the game code remains asynchronous and separate. As you may have guessed a while (or any other loop) will freeze the page. For our input, we'll be using onmousemove event to update the mouse Y coordinate global variable which we'll use inside the Game Loop to compute the player paddle position. Notice, how we are not doing the player position computation inside the mouse change event. I feel mixing Game Loop and events would add unnecessary complexity to the code.

Pong Game Assets

Download the pong game asset file and unzip it in the same directory as the game HTML file.

Complete Pong Game

The following is the complete source code for the game, you can copy-paste and save it in a file (be sure to put the game asset in the same directory, though). The code is well commented so be sure to have a read.

<h1 align="center">Pong Game Example in JavaScript</h1>

<canvas width="840" height="400" id="box">
  <img id="paddle1" src="https://s4.gifyu.com/images/paddle.png" />
  <img id="paddle2" src="https://s4.gifyu.com/images/paddle.png" />
  <img id="ball" src="https://s4.gifyu.com/images/ball_small.png" />
</canvas>
<div id="msg" align="center"></div>
#box {
  padding: 0;
  margin: auto;
  display: block;
  border: 1px solid #333;
}
// You can change these
// Speed of ball (pixels/step)
var ballSpeed = 3;
// Speed of CPU Paddle (pixels/step)
var cpuSpeed = 3;

// Object coordinates
var ballX, ballY;
var paddle1X, paddle1Y;
var paddle2X, paddle2Y;
var mouseY;

// HTML object references
var paddle1;
var paddle2;
var ball;
var box;
var msg;
var canvas, ctx;

// For internal use
var dx, dy;
var isGameStarted = false;
var intervalId;
var isFrameInProgress = false;

// Attach a function to onLoad event
window.onload = init;

function drawObject(ctx, sprite, x, y) {
  ctx.drawImage(sprite, x, y);
}

// Initialize game objects
function init() {
  // Store references to objects
  canvas = document.getElementById("box");
  ctx = canvas.getContext("2d");
  paddle1 = document.getElementById("paddle1");
  paddle2 = document.getElementById("paddle2");
  ball = document.getElementById("ball");
  box = document.getElementById("box");
  msg = document.getElementById("msg");

  // Initial values
  ballX = box.width / 2 - ball.width / 2;
  ballY = box.height / 2 - ball.height / 2;
  paddle1X = 20;
  paddle1Y = box.height / 2 - paddle1.height / 2;
  paddle2X = box.width - (20 + paddle2.width);
  paddle2Y = box.height / 2 - paddle2.height / 2;

  dx = dy = ballSpeed;

  // Add click event listener
  box.addEventListener("click", start);

  redraw();

  // Show message
  msg.innerHTML = "<h2>Click on Paddle to Start Game.</h2>";
}

// START GAME
function start(e) {
  if (isGameStarted) {
    return;
  }

  // Update current mouse Y position
  updateMouseY(e);

  // Attach a function to onmousemove event of the box
  box.onmousemove = updateMouseY;

  // Call 'gameLoop()' function every 10 milliseconds
  intervalId = setInterval("gameLoop()", 10);

  msg.innerHTML = "";

  isGameStarted = true;
}

// Redraw
function redraw() {
  // Clear canvas
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // Draw everything
  drawObject(ctx, paddle1, paddle1X, paddle1Y);
  drawObject(ctx, paddle2, paddle2X, paddle2Y);
  drawObject(ctx, ball, ballX, ballY);
}

function end(win) {
  clearInterval(intervalId);
  box.onmousemove = "";

  // Reset everything
  init();

  if (win) {
    msg.innerHTML = "<h2>You Win!<br/>Click on Paddle to Re-Start Game.</h2>";
  } else {
    msg.innerHTML = "<h2>You Loose!<br/>Click on Paddle to Re-Start Game.</h2>";
  }
  isGameStarted = false;
}

// Main game loop, called by the JavaScript timer
function gameLoop() {
  // Make sure that this is not invoked when one frame is
  // already in progress
  if (isFrameInProgress) {
    return;
  }
  isFrameInProgress = true;

  // INPUT
  // Player Paddle
  var y = mouseY - (box.offsetTop - document.documentElement.scrollTop);
  // Prevent the paddle from going outside of the canvas
  if (y > box.height - paddle1.height) {
    y = box.height - paddle1.height;
  }
  paddle1Y = y;

  // UPDATE
  // Ball
  ballX += dx;
  ballY += dy;

  // CPU Paddle
  if (dx > 0) {
    if (paddle2Y + paddle2.height / 2 > ballY + ball.height)
      paddle2Y -= cpuSpeed;
    else paddle2Y += cpuSpeed;
  }

  // Collision detection
  // If ball hits upper or lower wall
  if (ballY < 0 || ballY + ball.height > box.height) dy = -dy; // Make x direction opposite

  // If ball hits player paddle
  if (ballX < paddle1X + paddle1.width)
    if (ballY + ball.height > paddle1Y && ballY < paddle1Y + paddle1.height)
      dx = -dx;

  // If ball hits CPU paddle
  if (ballX + ball.width > paddle2X)
    if (ballY + ball.height > paddle2Y && ballY < paddle2Y + paddle2.height)
      dx = -dx;

  // END?
  // Win?
  // See if ball is past CPU paddle
  if (ballX + ball.width > box.width) {
    end(true);
  }

  // Lose?
  // See if ball is past player paddle
  if (ballX < 0) {
    end(false);
  }

  // RENDER
  redraw();

  // Frame completed
  isFrameInProgress = false;
}

function updateMouseY(e) {
  mouseY = e.clientY;
}

Click Here to Play the Live Example

Note

To keep the source code simple and easy to understand, I am using a lot of global variables that are being accessed in the scope of the functions which is not the best practice. A better design would be to use OOP and keep them separate and out of the global scope.

Possible Improvements

  1. Add a scoring system
  2. Add a pause or game restart system
  3. Make the CPU paddle AI more natural by maybe making it wait a random number of milliseconds before it starts responding to the oncoming ball.

Popular posts from this blog

Fix For Toshiba Satellite "RTC Battery is Low" Error (with Pictures)

RTC Battery is Low Error on a Toshiba Satellite laptop "RTC Battery is Low..." An error message flashing while you try to boot your laptop is enough to panic many people. But worry not! "RTC Battery" stands for Real-Time Clock battery which almost all laptops and PCs have on their motherboard to power the clock and sometimes to also keep the CMOS settings from getting erased while the system is switched off.  It is not uncommon for these batteries to last for years before requiring a replacement as the clock consumes very less power. And contrary to what some people tell you - they are not rechargeable or getting charged while your computer or laptop is running. In this article, we'll learn everything about RTC batteries and how to fix the error on your Toshiba Satellite laptop. What is an RTC Battery? RTC or CMOS batteries are small coin-shaped lithium batteries with a 3-volts output. Most laptops use

The Best Way(s) to Comment out PHP/HTML Code

PHP supports various styles of comments. Please check the following example: <?php // Single line comment code (); # Single line Comment code2 (); /* Multi Line comment code(); The code inside doesn't run */ // /* This doesn NOT start a multi-line comment block /* Multi line comment block The following line still ends the multi-line comment block //*/ The " # " comment style, though, is rarely used. Do note, in the example, that anything (even a multi-block comment /* ) after a " // " or " # " is a comment, and /* */ around any single-line comment overrides it. This information will come in handy when we learn about some neat tricks next. Comment out PHP Code Blocks Check the following code <?php //* Toggle line if ( 1 ) {      // } else {      // } //*/ //* Toggle line if ( 2 ) {      // } else {      // } //*/ Now see how easy it is to toggle a part of PHP code by just removing or adding a single " / " from th

Introduction to Operator Overloading in C++

a1 = a2 + a3; The above operation is valid, as you know if a1, a2 and a3 are instances of in-built Data Types . But what if those are, say objects of a Class ; is the operation valid? Yes, it is, if you overload the ‘+’ Operator in the class, to which a1, a2 and a3 belong. Operator overloading is used to give special meaning to the commonly used operators (such as +, -, * etc.) with respect to a class. By overloading operators, we can control or define how an operator should operate on data with respect to a class. Operators are overloaded in C++ by creating operator functions either as a member or a s a Friend Function of a class. Since creating member operator functions are easier, we’ll be using that method in this article. As I said operator functions are declared using the following general form: ret-type operator#(arg-list); and then defining it as a normal member function. Here, ret-type is commonly the name of the class itself as the ope