Building a Classic Pong Game with Python

Hello aspiring game developers and Python enthusiasts! Are you ready to dive into the exciting world of game creation? Today, we’re going to build a timeless classic: Pong! This simple yet addictive game is a fantastic project for beginners to learn the fundamentals of game development using Python. We’ll be using Python’s built-in turtle module, which is perfect for drawing simple graphics and getting a feel for how game elements move and interact.

Why Build Pong with Python?

Building Pong is more than just fun; it’s an excellent learning experience because:

  • It’s Simple: The core mechanics are easy to grasp, making it ideal for a first game.
  • Visual Feedback: You’ll immediately see your code come to life on the screen.
  • Key Concepts: You’ll learn about game loops, object movement, collision detection, and user input.
  • No Complex Libraries: We’ll mostly stick to Python’s standard library, primarily the turtle module, which means fewer dependencies to install.

By the end of this tutorial, you’ll have a fully functional Pong game and a better understanding of basic game development principles. Let’s get started!

What You’ll Need

Before we begin, make sure you have:

  • Python Installed: Any version of Python 3 should work. If you don’t have it, you can download it from python.org.
  • A Text Editor or IDE: Like VS Code, Sublime Text, PyCharm, or even a simple text editor.

That’s it! Python’s turtle module comes pre-installed, so no need for pip install commands here.

Setting Up Your Game Window

First things first, let’s create the window where our game will be played. We’ll use the turtle module for this.

  • import turtle: This line brings the turtle module into our program, allowing us to use its functions and objects.
  • screen object: This will be our game window, or the canvas on which everything is drawn.
import turtle # Import the turtle module

screen = turtle.Screen() # Create a screen object, which is our game window
screen.title("My Pong Game") # Give the window a title
screen.bgcolor("black") # Set the background color to black
screen.setup(width=800, height=600) # Set the dimensions of the window
screen.tracer(0) # Turns off screen updates. This makes animations smoother.
                 # We'll manually update the screen later.

Supplementary Explanation:
* turtle.Screen(): Think of this as opening a blank canvas for your game.
* screen.tracer(0): This is a performance optimization. By default, turtle updates the screen every time something moves. tracer(0) turns off these automatic updates. We’ll manually update the screen using screen.update() later, which allows us to control when all drawn objects appear at once, making the movement appear much smoother.

Creating Game Elements: Paddles and Ball

Now, let’s add the main players of our game: two paddles and a ball. We’ll create these using the turtle.Turtle() object.

  • turtle.Turtle(): This creates a new “turtle” object that we can command to draw shapes, move around, and interact with. For our game, these turtles are our paddles and ball.
  • shape(): Sets the visual shape of our turtle (e.g., “square”, “circle”).
  • color(): Sets the color of the turtle.
  • penup(): Lifts the turtle’s “pen” so it doesn’t draw a line when it moves. This is important for our paddles and ball, as we just want to see the objects, not their movement paths.
  • speed(0): Sets the animation speed of the turtle. 0 means the fastest possible speed.
  • goto(x, y): Moves the turtle to a specific (x, y) coordinate on the screen. The center of the screen is (0, 0).
paddle_a = turtle.Turtle() # Create a turtle object
paddle_a.speed(0) # Set animation speed to fastest
paddle_a.shape("square") # Set shape to square
paddle_a.color("white") # Set color to white
paddle_a.shapesize(stretch_wid=5, stretch_len=1) # Stretch the square to be a rectangle
                                                 # 5 times wider vertically, 1 time wider horizontally (default)
paddle_a.penup() # Lift the pen so it doesn't draw lines
paddle_a.goto(-350, 0) # Position the paddle on the left side

paddle_b = turtle.Turtle()
paddle_b.speed(0)
paddle_b.shape("square")
paddle_b.color("white")
paddle_b.shapesize(stretch_wid=5, stretch_len=1)
paddle_b.penup()
paddle_b.goto(350, 0) # Position the paddle on the right side

ball = turtle.Turtle()
ball.speed(0)
ball.shape("circle") # Ball will be a circle
ball.color("white")
ball.penup()
ball.goto(0, 0) # Start the ball in the center
ball.dx = 2 # delta x: How much the ball moves in the x-direction each frame
ball.dy = 2 # delta y: How much the ball moves in the y-direction each frame
            # These values determine the ball's speed and direction

Supplementary Explanation:
* stretch_wid / stretch_len: These parameters scale the default square shape. A default square is 20×20 pixels. stretch_wid=5 makes it 5 * 20 = 100 pixels tall. stretch_len=1 keeps it 1 * 20 = 20 pixels wide. So, our paddles are 100 pixels tall and 20 pixels wide.
* ball.dx and ball.dy: These variables represent the change in the ball’s X and Y coordinates per game frame. dx=2 means it moves 2 pixels to the right, and dy=2 means it moves 2 pixels up in each update. If dx were negative, it would move left.

Moving the Paddles

We need functions to move our paddles up and down based on keyboard input.

  • screen.listen(): Tells the screen to listen for keyboard input.
  • screen.onkeypress(function_name, "key"): Binds a function to a specific key press. When the specified key is pressed, the linked function will be called.
def paddle_a_up():
    y = paddle_a.ycor() # Get the current y-coordinate of paddle A
    y += 20 # Add 20 pixels to the y-coordinate
    paddle_a.sety(y) # Set the new y-coordinate for paddle A

def paddle_a_down():
    y = paddle_a.ycor()
    y -= 20 # Subtract 20 pixels from the y-coordinate
    paddle_a.sety(y)

def paddle_b_up():
    y = paddle_b.ycor()
    y += 20
    paddle_b.sety(y)

def paddle_b_down():
    y = paddle_b.ycor()
    y -= 20
    paddle_b.sety(y)

screen.listen() # Tell the screen to listen for keyboard input
screen.onkeypress(paddle_a_up, "w") # When 'w' is pressed, call paddle_a_up
screen.onkeypress(paddle_a_down, "s") # When 's' is pressed, call paddle_a_down
screen.onkeypress(paddle_b_up, "Up") # When 'Up arrow' is pressed, call paddle_b_up
screen.onkeypress(paddle_b_down, "Down") # When 'Down arrow' is pressed, call paddle_b_down

Supplementary Explanation:
* ycor() / sety(): ycor() returns the current Y-coordinate of a turtle. sety(value) sets the turtle’s Y-coordinate to value. Similar functions exist for the X-coordinate (xcor(), setx()).

The Main Game Loop

A game loop is the heart of any game. It’s a while True loop that continuously updates everything in the game: moving objects, checking for collisions, updating scores, and redrawing the screen.

score_a = 0
score_b = 0

pen = turtle.Turtle() # Create a new turtle for writing the score
pen.speed(0)
pen.color("white")
pen.penup()
pen.hideturtle() # Hide the turtle icon itself
pen.goto(0, 260) # Position the scoreboard at the top of the screen
pen.write("Player A: 0  Player B: 0", align="center", font=("Courier", 24, "normal"))

while True:
    screen.update() # Manually update the screen to show all changes

    # Move the ball
    ball.setx(ball.xcor() + ball.dx)
    ball.sety(ball.ycor() + ball.dy)

    # Border checking
    # Top and bottom borders
    if ball.ycor() > 290: # If ball hits the top border (screen height is 600, so top is +300)
        ball.sety(290) # Snap it back to the border
        ball.dy *= -1 # Reverse the y-direction (bounce down)

    if ball.ycor() < -290: # If ball hits the bottom border
        ball.sety(-290)
        ball.dy *= -1 # Reverse the y-direction (bounce up)

    # Left and right borders (scoring)
    if ball.xcor() > 390: # If ball goes past the right border (screen width is 800, so right is +400)
        ball.goto(0, 0) # Reset ball to center
        ball.dx *= -1 # Reverse x-direction to serve the other way
        score_a += 1 # Player A scores
        pen.clear() # Clear previous score
        pen.write(f"Player A: {score_a}  Player B: {score_b}", align="center", font=("Courier", 24, "normal"))


    if ball.xcor() < -390: # If ball goes past the left border
        ball.goto(0, 0) # Reset ball to center
        ball.dx *= -1 # Reverse x-direction
        score_b += 1 # Player B scores
        pen.clear() # Clear previous score
        pen.write(f"Player A: {score_a}  Player B: {score_b}", align="center", font=("Courier", 24, "normal"))

    # Paddle and ball collisions
    # Paddle B collision
    if (ball.xcor() > 340 and ball.xcor() < 350) and \
       (ball.ycor() < paddle_b.ycor() + 50 and ball.ycor() > paddle_b.ycor() - 50):
        ball.setx(340) # Snap ball back to avoid getting stuck
        ball.dx *= -1 # Reverse x-direction

    # Paddle A collision
    if (ball.xcor() < -340 and ball.xcor() > -350) and \
       (ball.ycor() < paddle_a.ycor() + 50 and ball.ycor() > paddle_a.ycor() - 50):
        ball.setx(-340) # Snap ball back
        ball.dx *= -1 # Reverse x-direction

Supplementary Explanation:
* pen.write(): This function is used to display text on the screen.
* align="center": Centers the text horizontally.
* font=("Courier", 24, "normal"): Sets the font family, size, and style.
* ball.xcor() / ball.ycor(): Returns the ball’s current X and Y coordinates.
* ball.dx *= -1: This is shorthand for ball.dx = ball.dx * -1. It effectively reverses the sign of ball.dx, making the ball move in the opposite direction along the X-axis. Same logic applies to ball.dy.
* Collision Detection:
* ball.xcor() > 340 and ball.xcor() < 350: Checks if the ball’s X-coordinate is within the range of the paddle’s X-position.
* ball.ycor() < paddle_b.ycor() + 50 and ball.ycor() > paddle_b.ycor() - 50: Checks if the ball’s Y-coordinate is within the height range of the paddle. Remember, our paddles are 100 pixels tall (50 up from center, 50 down from center).
* pen.clear(): Erases the previous text written by the pen turtle before writing the updated score.

Putting It All Together: Complete Code

Here’s the complete code for your Pong game. Copy and paste this into a .py file (e.g., pong_game.py) and run it!

import turtle

screen = turtle.Screen()
screen.title("My Pong Game")
screen.bgcolor("black")
screen.setup(width=800, height=600)
screen.tracer(0)

paddle_a = turtle.Turtle()
paddle_a.speed(0)
paddle_a.shape("square")
paddle_a.color("white")
paddle_a.shapesize(stretch_wid=5, stretch_len=1)
paddle_a.penup()
paddle_a.goto(-350, 0)

paddle_b = turtle.Turtle()
paddle_b.speed(0)
paddle_b.shape("square")
paddle_b.color("white")
paddle_b.shapesize(stretch_wid=5, stretch_len=1)
paddle_b.penup()
paddle_b.goto(350, 0)

ball = turtle.Turtle()
ball.speed(0)
ball.shape("circle")
ball.color("white")
ball.penup()
ball.goto(0, 0)
ball.dx = 2
ball.dy = 2

score_a = 0
score_b = 0

pen = turtle.Turtle()
pen.speed(0)
pen.color("white")
pen.penup()
pen.hideturtle()
pen.goto(0, 260)
pen.write(f"Player A: {score_a}  Player B: {score_b}", align="center", font=("Courier", 24, "normal"))

def paddle_a_up():
    y = paddle_a.ycor()
    # Prevent paddle from going off-screen
    if y < 240: # Max Y-coordinate for paddle top (290 - 50 paddle height / 2)
        y += 20
        paddle_a.sety(y)

def paddle_a_down():
    y = paddle_a.ycor()
    # Prevent paddle from going off-screen
    if y > -240: # Min Y-coordinate for paddle bottom (-290 + 50 paddle height / 2)
        y -= 20
        paddle_a.sety(y)

def paddle_b_up():
    y = paddle_b.ycor()
    if y < 240:
        y += 20
        paddle_b.sety(y)

def paddle_b_down():
    y = paddle_b.ycor()
    if y > -240:
        y -= 20
        paddle_b.sety(y)

screen.listen()
screen.onkeypress(paddle_a_up, "w")
screen.onkeypress(paddle_a_down, "s")
screen.onkeypress(paddle_b_up, "Up")
screen.onkeypress(paddle_b_down, "Down")

while True:
    screen.update()

    # Move the ball
    ball.setx(ball.xcor() + ball.dx)
    ball.sety(ball.ycor() + ball.dy)

    # Border checking
    # Top and bottom walls
    if ball.ycor() > 290:
        ball.sety(290)
        ball.dy *= -1
    if ball.ycor() < -290:
        ball.sety(-290)
        ball.dy *= -1

    # Right and left walls (scoring)
    if ball.xcor() > 390: # Ball goes off right side
        ball.goto(0, 0)
        ball.dx *= -1
        score_a += 1
        pen.clear()
        pen.write(f"Player A: {score_a}  Player B: {score_b}", align="center", font=("Courier", 24, "normal"))

    if ball.xcor() < -390: # Ball goes off left side
        ball.goto(0, 0)
        ball.dx *= -1
        score_b += 1
        pen.clear()
        pen.write(f"Player A: {score_a}  Player B: {score_b}", align="center", font=("Courier", 24, "normal"))

    # Paddle and ball collisions
    # Paddle B
    # Check if ball is between paddle's x-range AND paddle's y-range
    if (ball.xcor() > 340 and ball.xcor() < 350) and \
       (ball.ycor() < paddle_b.ycor() + 50 and ball.ycor() > paddle_b.ycor() - 50):
        ball.setx(340) # Snap ball to the paddle's edge
        ball.dx *= -1 # Reverse direction

    # Paddle A
    if (ball.xcor() < -340 and ball.xcor() > -350) and \
       (ball.ycor() < paddle_a.ycor() + 50 and ball.ycor() > paddle_a.ycor() - 50):
        ball.setx(-340) # Snap ball to the paddle's edge
        ball.dx *= -1 # Reverse direction

Note on paddle boundaries: I’ve added a simple check if y < 240: and if y > -240: to prevent the paddles from moving off-screen. The paddles are 100 pixels tall, so they extend 50 pixels up and 50 pixels down from their center (y coordinate). If the screen height is 600, the top is 300 and the bottom is -300. So, a paddle’s center should not go above 300 - 50 = 250 or below -300 + 50 = -250. My code uses 240 to give a little buffer.

Conclusion

Congratulations! You’ve successfully built your very own Pong game using Python and the turtle module. You’ve learned how to:

  • Set up a game window.
  • Create game objects like paddles and a ball.
  • Handle user input for paddle movement.
  • Implement a continuous game loop.
  • Detect collisions with walls and paddles.
  • Keep score and display it on the screen.

This is a fantastic foundation for further game development. Feel free to experiment and enhance your game!

Ideas for Future Enhancements:

  • Difficulty Levels: Increase ball speed over time or after a certain score.
  • Sound Effects: Add sounds for paddle hits, wall hits, and scoring using libraries like winsound (Windows only) or pygame.mixer.
  • AI Opponent: Replace one of the human players with a simple AI that tries to follow the ball.
  • Customization: Allow players to choose paddle colors or ball shapes.
  • Game Over Screen: Display a “Game Over” message when a certain score is reached.

Keep coding, keep experimenting, and most importantly, keep having fun!

Comments

Leave a Reply