How to Create a Pong Game in Python

Discover how to craft a Pong game with Python and Pygame through a comprehensive tutorial, gaining hands-on game development skills. Learn key concepts like rendering graphics, managing game state, and handling user input while bringing your code to life in this engaging guide.
  · 10 min read · Updated oct 2023 · Game Development

Before we get started, have you tried our new Python Code Assistant? It's like having an expert coder at your fingertips. Check it out!

In this tutorial, we'll guide you through the step-by-step process of crafting your own Pong game using Python and Pygame. Building Pong with Pygame is an excellent introduction to game development, offering hands-on experience with fundamental concepts such as rendering graphics, handling user input, and managing game state. By the end of this tutorial, you'll have not only a functional Pong game but also a deeper understanding of how game mechanics work under the hood.

Table of Contents

Game Setup

Let's start by making sure Pygame is installed on your computer, head to your terminal and install pygame module using pip:

$ pip install pygame

After that, create a directory for the game and create the following .py file inside it; settings.py, main.py, table.py, player.py, and ball.py. Here is the file structure of our code.

game files

Now we can start coding. Let's define our game variables in settings.py:

# /* settings.py
WIDTH, HEIGHT = 990, 450
player_width, player_height = 20, 90

Next, let's create the main class of our game. This class will be responsible for running and stopping the game with the game loop:

# /* main.py
import pygame, sys
from settings import WIDTH, HEIGHT
from table import Table

pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Ping Pong")

class Pong:
    def __init__(self, screen):
        self.screen = screen
        self.FPS = pygame.time.Clock()

    def draw(self):
        pygame.display.flip()

    def main(self):
        # start menu here
        table = Table(self.screen)  # pass to table the player_option saved to table.game_mode
        while True:
            self.screen.fill("black")
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    sys.exit()
            table.player_move()
            table.update()
            self.draw()
            self.FPS.tick(30)

if __name__ == "__main__":
    play = Pong(screen)
    play.main()

The Pong class is the main class of our game. It takes an argument of screen which will serve as the game window, for animating the game. The draw() function of our Pong class is responsible for updating all the changes to the game window. The main() function will run our game. It will initialize the table first. To keep the game running without intentionally exiting, we put a while loop inside it. Inside our loop, we place another loop (the for loop) which will catch all the events going on inside our game window, especially when the player hits the exit button.

Creating the Ball

Create another class in ball.py and name the class Ball. From the name itself, it will serve as the ball in our game:

# /* ball.py
import pygame, sys
import random
from settings import WIDTH, HEIGHT

pygame.init()

class Ball:
    def __init__(self, x, y, radius):
        self.x = x
        self.y = y
        self.radius = radius
        self.rect = pygame.Rect(self.x, self.y, radius, radius)
        self.color = pygame.Color("red")
        self.direction = None
        self.speed_x = 0
        self.speed_y = 0
        self._random_direction()

    def _random_direction(self):
        direction = ("right", "left")
        self.direction = random.choice(direction)

    def _ball_movement(self):
        # horizontal handling
        if self.direction == "right":
            self.speed_x = 18
        else:
            self.speed_x = -18
        # vertical handling
        if self.rect.y >= HEIGHT - self.radius:
            self.speed_y = -18
        elif self.rect.y <= 0 + self.radius:
            self.speed_y = 18
        # wall bounce handling
        self.rect.x += self.speed_x
        self.rect.y += self.speed_y

    def update(self, screen):
        self._ball_movement()
        pygame.draw.rect(screen, self.color, self.rect)

The Ball class will take arguments such as x (horizontal position), y (vertical position), and radius (ball size). The _random_direction() method is responsible for randomly setting the initial direction of the ball (either "right" or "left"). At the start of the game, the ball will be randomly served by the players (it's either the ball will bounce to the left or to the right).

The _ball_movement() method, from the name itself, handles the movement of the ball. It sets the speed_x to 18 if the direction is "right," or -18 if the direction is "left." It checks if the ball has reached the top or bottom of the screen (within the radius), and if so, it changes the speed_y to -18 or 18, respectively, to make the ball bounce off the top or bottom. It updates the position of the ball's rectangular representation (self.rect) based on the calculated speeds.

The update() method is typically called within the game loop to update the ball's position and draw it on the screen. It calls _ball_movement() to update the ball's position based on its current direction and speed. Finally, it draws the ball on the screen using pygame.draw.rect.

Creating the Player Class

The Player class will serve as the pong paddles in our game. Create the Player class in player.py. This is the same class we're gonna use with our bot opponent:

# /* player.py
import pygame

class Player:
    def __init__(self, x, y, width, height):
        self.x = x
        self.y = y
        self.rect = pygame.Rect(self.x, self.y, width, height)
        self.color = pygame.Color("gray")
        self.player_speed = 16
        self.score = 0

    def move_up(self):
        self.rect.y -= self.player_speed

    def move_bottom(self):
        self.rect.y += self.player_speed

    def update(self, screen):
        pygame.draw.rect(screen, self.color, self.rect)

The Player class takes x, y, width, and height as parameters to set the initial position and dimensions of the player's paddle. It creates a rectangular representation of the paddle using pygame.Rect. It sets the paddle's color to gray and initializes the player_speed attribute to 16 (indicating how fast the paddle can move). It also initializes the player's starting score to 0.

In able for the player/s to move their paddles, we have functions such as move_up and move_down. We have up and down movement since our pong table will be in the landscape. If we want to move upwards, the move_up() function will decrease the player's y position and if we want to move downwards, the move_down() will increase the player's y position.

To update all the movement of the player/s to the game screen, we use the update() method.

Creating the Game Table

Let's create another class in table.py and call it Table. The Table class will serve as the environment of our game, which is responsible for managing the game's logic, including player and opponent movement, scoring, and determining the game's outcome:

# /* table.py
import pygame, time
import sys
from player import Player
from ball import Ball
from settings import WIDTH, HEIGHT, player_width, player_height

class Table:
    def __init__(self, screen):
        self.screen = screen
        self.game_over = False
        self.score_limit = 10
        self.winner = None
        self._generate_world()
        # text info
        self.font = pygame.font.SysFont('Bauhaus 93', 60)
        self.inst_font = pygame.font.SysFont('Bauhaus 93', 30)
        self.color = pygame.Color("white")

    # create and add player to the screen
    def _generate_world(self):
        self.playerA = Player(0, HEIGHT // 2 - (player_height // 2), player_width, player_height)
        self.playerB = Player(WIDTH - player_width,  HEIGHT // 2 - (player_height // 2), player_width, player_height)
        self.ball = Ball(WIDTH // 2 - player_width, HEIGHT - player_width, player_width)

    def _ball_hit(self):
        # if ball is not hit by a player and pass through table sides
        if self.ball.rect.left >= WIDTH:
            self.playerA.score += 1
            self.ball.rect.x = WIDTH // 2
            time.sleep(1)
        elif self.ball.rect.right <= 0:
            self.playerB.score += 1
            self.ball.rect.x = WIDTH // 2
            time.sleep(1)
        # if ball land in the player
        if pygame.Rect.colliderect(self.ball.rect, self.playerA.rect):
            self.ball.direction = "right"
        if pygame.Rect.colliderect(self.ball.rect, self.playerB.rect):
            self.ball.direction = "left"

    def _bot_opponent(self):
        if self.ball.direction == "left" and self.ball.rect.centery != self.playerA.rect.centery:
            if self.ball.rect.top <= self.playerA.rect.top:
                if self.playerA.rect.top > 0:
                    self.playerA.move_up()
            if self.ball.rect.bottom >= self.playerA.rect.bottom:
                if self.playerA.rect.bottom < HEIGHT:
                    self.playerA.move_bottom()

    def player_move(self):
        keys = pygame.key.get_pressed()
        # for bot opponent controls
        self._bot_opponent()
        # for player controls
        if keys[pygame.K_UP]:
            if self.playerB.rect.top > 0:
                self.playerB.move_up()
        if keys[pygame.K_DOWN]:
            if self.playerB.rect.bottom < HEIGHT:
                self.playerB.move_bottom()

    def _show_score(self):
        A_score, B_score = str(self.playerA.score), str(self.playerB.score)
        A_score = self.font.render(A_score, True, self.color)
        B_score = self.font.render(B_score, True, self.color)
        self.screen.blit(A_score, (WIDTH // 4, 50))
        self.screen.blit(B_score, ((WIDTH // 4) * 3, 50))

    def _game_end(self):
        if self.winner != None:
            print(f"{self.winner} wins!!")
            pygame.quit()
            sys.exit()

    def update(self):
        self._show_score()
        self.playerA.update(self.screen)        
        self.playerB.update(self.screen)
        self._ball_hit()
        if self.playerA.score == self.score_limit:
            self.winner = "Opponent"
        elif self.playerB.score == self.score_limit:
            self.winner = "You"
        self._game_end()
        self.ball.update(self.screen)

The Table class would also take a game window as an argument, so we can directly draw the ball, player paddles, and scores in the game window. The __init__() method calls the _generate_world() method to set up the game once the Table class is called.

The _generate_world() method creates and adds game objects to the screen, including two players (playerA and playerB) and a ball (ball).

The _ball_hit() method checks if the ball has collided with any game objects (players or table sides). If the ball passes through the table sides, it updates the players' scores and resets the ball's position. If the ball collides with a player, it changes the ball's direction accordingly (application of bounce logic).

The _bot_opponent() method controls the movement of the opponent (controlled by the computer). It adjusts the opponent's paddle position based on the ball's movement to simulate a basic AI opponent.

The player_move() method handles player-controlled paddle movement using keyboard input. It checks for key presses (pygame.key.get_pressed()) and moves the player's paddle accordingly.

For our game display information (for score and game-over representation), we have the show_score() and _game_end() methods. The _show_score() method displays the scores of both players on the screen using the specified font and color. The _game_end() method checks if the game has reached a winning condition (one player's score equals the score_limit). If there's a winner, it prints a message and exits the game.

To update all our changes in the game screen, we use the update() method. The update method is typically called within the game loop to update the game's state and visuals. It displays the scores, updates the player and opponent paddles position, checks for ball collisions, determines the winner, and updates the ball.

And that's it! We have created a Pong game with Python. To test our game, open your terminal and head to the directory of our game, then run python main.py.

Here are some of the game pictures:

Here's a video:

Through creating a Pong game with Python and Pygame, you've gained fundamental game development skills. Pygame's ability to bring code to life has been demonstrated, empowering you to explore and create your own games. As you continue coding and building, remember that every project contributes to your growth as a game developer. I hope you find this blog useful, if you have any questions about this game, you can leave a comment below and I'll try to answer it!

Get the complete code here.

Here are some game tutorials:

Happy coding ♥

Ready for more? Dive deeper into coding with our AI-powered Code Explainer. Don't miss it!

View Full Code Auto-Generate My Code
Sharing is caring!



Read Also



Comment panel

    Got a coding query or need some guidance before you comment? Check out this Python Code Assistant for expert advice and handy tips. It's like having a coding tutor right in your fingertips!