Skip to main content

Command Palette

Search for a command to run...

12.1. Pong - The Simplest Game to Implement

Updated
9 min readView as Markdown
12.1. Pong - The Simplest Game to Implement
A

I'm a dedicated software engineer with a passion for bringing hardware and software together to create robust and efficient solutions. I thrive on optimizing performance, managing power consumption, and ensuring the reliability of devices from concept to deployment.

🦊

I recently got the idea to recreate the classic arcade game of Pong. Pong is a relatively simple game with little programming required to get it up and running. In this article, we will see how the code works.

As usual, we will start with the setup(). This is available in Pong.pde, the project's main file.

void setup() {
    size(1000, 600);
    frameRate(60);

    PFont font = loadFont("font.vlw");
    textFont(font);

    rectMode(CENTER);

    player_one = new Paddle("1", 100, height / 2, 0, height, 10, 100);
    player_two = new Paddle("2", width - 100, height / 2, 0, height, 10, 100);

    ball = new Ball(width / 2, height / 2, 10, 10, width, height);
    ball.reset();
}

This is pretty standard: Set the canvas size, frame rate, load fonts, set the rectangle mode and set up the objects for the players and ball. I have discussed loading the fonts in the previous article, so I will not go through it here. You can also refer to the GitHub repo for the instructions.

The font I chose was Bitwise Font by Digital Graphics Labs. You can find it at www.fontspace.com.

Bitwise Font

The draw() function is also quite simple.

void draw() {
    background(0);
    backgroundDraw();

    render();

    if (!pause) {
        update();
    }
}

We first draw a black background, draw the background items and then render the dynamic items. If we're not on pause, we also update the physics.

The keyPressed() function is also quite simple.

void keyPressed() {
    if (key == 'w') { player_one.up(); }
    if (key == 's') { player_one.dn(); }

    if (key == 'p') { player_two.up(); }
    if (key == 'l') { player_two.dn(); }

    if (key == 'r') { reset(); }

    if (key == ' ') {
        if ((player_one.score(0) > over) || (player_two.score(0) > over)) {
            reset();
        }

        pause = !pause;
    }
}

Pressing the keys w and s will move player one up and down. p and l for player two. r will reset the game, and the space will reset the game if either player has completed the scores, and pause either way. mousePressed() is also the same as pressing the space.

void mousePressed() {
    if ((player_one.score(0) > over) || (player_two.score(0) > over)) {
        reset();
    }

    pause = !pause;
}

Background draw is long, but it pretty much does some basic shape drawing as well.

void backgroundDraw() {
    fill(255);

    rect(width / 2, height / 2, 5, height);

    textAlign(CENTER, CENTER);
    textSize(32);

    text(player_one.score(0), 50, 100);
    text(player_two.score(0), width - 50, 100);

    if (pause) {
        if ((player_one.score(0) > over) || (player_two.score(0) > over)) {
            String p = "1";
            if (player_two.score(0) > over) {
                p = "2";
            }

            text("GAME   OVER\n   PLAYER " + p + "      HAS  WON\n   CLICK OR PRESS   SPACE TO START", width / 2, 400);
        } else {
            text("PO  NG\n   CLICK OR PRESS   SPACE TO START", width / 2, 400);
        }
    }
}

The colour we use for drawing is white. We first draw a divider in the centre of the screen. Then we draw the scores of each player. If paused, we also draw some text on the screen. If either of the player has exceeded the winning score, we display the winner; if not, we show instructions to play the game.

reset() and render() also perform similar functions.

void reset() {
    ball.reset();

    player_one.reset();
    player_two.reset();
}

void render() {
    ball.render();

    player_one.render();
    player_two.render();
}

We either reset or render the two players and ball.

update() function handles all the physics.

void update() {
    if (frameCount % 2 == 0) {
        int upd = ball.update();

        if (upd > 0) {
            if (upd == 1) { player_one.score(1); }
            if (upd == 2) { player_two.score(1); }

            if ((player_one.score(0) > over) || (player_two.score(0) > over)) {
                pause = true;
            }

            ball.reset();
        }
    }
}

Here, for every two frames, we perform a physics update. First, the ball is updated. It will return if either of the players scored. 0 means no score, and either 1 or 2 represents the player who scored. So, if we have a return greater than 0, we check if it's 1 or 2 and increase the score of the respective player. We also check if any of the players have reached the winning score and pause if so. We also reset the ball.

Now, we will move on to the next file: Game.pde. This has all the classes related to the ball and player.

The first one we will look at is the player: the Paddle class.

public class Paddle {
    private String id;

    private int start_x, start_y;

    private int x, y;

    private int width, height;

    private int up_x, dn_x;

    private int score = 0;

    private int SPEED = 10;
    
    public Paddle(String id, int x, int y, int up_x, int dn_x, int width, int height) {
        this.id = id;
        this.start_x = x; this.start_y = y;
        this.x = x; this.y = y;
        this.up_x = up_x; this.dn_x = dn_x;
        this.width = width; this.height = height;
    }

    public String id();
    
    public void render();

    public void reset();

    public void up();

    public void dn();

    public int score(int sc);
}

We have a few private variables that are quite simple. id holds a unique identifier for the player. start_x and start_y represent the initial positions of the paddle. x and y relate to where the paddle is now. width and height are the width and height of the paddle. up_ and dn_x are the limits when moving the paddle up and down. score is the score of the player, and SPEED is the speed of the paddle. The constructor sets these up.

id() returns the ID of the player.

public String id() {
    return id;
}

render() renders the paddle.

public void render() {
    fill(255);
    rect(this.x, this.y, this.width, this.height);
}

reset() resets the paddle to its initial position.

public void reset() {
    this.x = this.start_x; this.y = this.start_y;
    score = 0;
}

up() and dn() move the paddle up or down by the SPEED.

public void up() {
    if (this.y > up_x) {
        this.y -= SPEED;
    }
}

public void dn() {
    if (this.y < dn_x) {
        this.y += SPEED;
    }
}

score() will increase the score and return the current score.

public int score(int sc) {
    this.score += sc;

    return this.score;
}

The other class we have is the Ball class. This class will be used to create the ball object that will be rendered on the screen.

public class Ball {
    private int start_x, start_y;
    private int x, y;

    private int width, height;

    private int speed_x, speed_y;

    private int window_w, window_h;

    private int hits = 0;

    public Ball(int x, int y, int width, int height, int window_w, int window_h) {
        this.start_x = x; this.start_y = y;
        this.x = x; this.y = y;
        this.width = width; this.height = height;
        this.window_w = window_w; this.window_h = window_h;

        this.speed_x = 2 - (4 * round(random(0, 1))); this.speed_y = 2 - (4 * round(random(0, 1)));
    }

    public void reset();

    public void render();

    public int update();
}

The start_x and start_y variables hold the initial start position of the ball. x and y will hold the current position. width and height determine the size of the ball. speed_x and speed_y hold the current speed of the ball. window_w and window_h hold the total width and height of the screen. hits hold the number of hits the ball has with the paddles. The initialiser function handles setting up these variables. It also sets the speed to either direction randomly to create a sense of randomness when passing the ball.

The reset() function is also similar to the paddle. It just resets the position of the ball.

public void reset() {
    this.x = start_x; this.y = start_y;
}

render() is also the same. It just resets the ball to the initial position.

public void render() {
    fill(255);
    rect(this.x, this.y, this.width, this.height);
}

The update() function performs the physics-related tasks.

public int update() {
    this.x += this.speed_x; this.y += this.speed_y;

    if ((this.y <= this.height / 2) || (this.y >= this.window_h - (this.height / 2))) {
        this.speed_y = -(this.speed_y);
    }

    if (((this.x <= player_one.x) && (this.y >= (player_one.y - (player_one.height / 2))) && (this.y <= (player_one.y + (player_one.height / 2))))
    ||  ((this.x >= player_two.x) && (this.y >= (player_two.y - (player_two.height / 2))) && (this.y <= (player_two.y + (player_two.height / 2))))) {
        int speed_offset = 0;
        int speed_max = 0;

        if (this.x <= player_one.x) {
            speed_offset = this.y - player_one.y;
            speed_max = player_one.height / 2;
        } else {
            speed_offset = this.y - player_two.y;
            speed_max = player_two.height / 2;
        }

        this.speed_y = speed_offset * 5 / speed_max;
        this.speed_x = -(this.speed_x);
        hits++;

        if (hits % 4 == 0) {
            this.speed_x += (this.speed_x / abs(this.speed_x));

            if (this.speed_y == 0) {
                this.speed_y = 1 - (2 * round(random(0, 1)));
            }
        }
    } else {
        if (this.x < player_one.x) {
            return 2;
        } else if (this.x > player_two.x) {
            return 1;
        }
    }

    return 0;
}

The first thing this task does is move the ball at the speed. If the ball has reached the top or bottom of the screen, the speed in the y-axis is flipped to simulate a bounce. Next, it will check if the ball reached each of the paddle's x positions. If so, we check if we reached away from the paddle's surface. If so, we return the opposite paddle to which it reached the end to indicate a score for the opposing player. If not, we check in which region of the paddle we reached, and we update the y speed accordingly.

For example, if the ball reached the end of the paddle, the angle at which it reflects will be higher, and if it's in the middle, the ball will directly reflect. We will also oppose the x speed while also increasing the hit count. The reason why we keep track of the hits is that the classic arcade game used to increase the speed with every four hits. Here, we're doing the same thing.

This creates the classic arcade Pong game with almost similar mechanics.

Pong

The reason why I wanted to create this is that I have another idea I want to pursue with this project. I will post a follow-up article to this with the other ideas I have. Till then, enjoy... 👋

External Links: