Pong Game using Python

This article explains how to develop a simple Pong game using Python. To create this, all we need is to know the basics of the Python programming language. A Pong game has a ball that moves all the time within a rectangular space. Ball deflects from the top and the side walls. But, if the ball hits the bottom wall, game is over.

To prevent the ball from hitting the bottom wall, a small base rectangle object is there at the bottom which can be moved horizontally using keyboard. We have to place this object in the right position so that the ball hits this object each time and not the bottom wall.

To develop the game, we have to start with a rectangular GUI which holds the moving ball and the base object and displays the score as well. Python has many such GUI libraries. For this sample, we are going to use TKinter, which is one of the simplest out there and has all the features that we need to develop a pong game.

The most complex part of this pong game is the logic to make the ball move and deflect the ball to right direction after hitting the wall. We also need to find a way to know, when the ball hits the base object. All these can be written using basic Python and is explained in detail in the below sections.

Doing this sample will help you to get a better understanding of Python and Tkinter. Particularly, on how to use canvas, create, shapes in it, move the shapes and bind event to these shapes.

Below are the step by step process to develop this game. Also provided the complete code at the last section.

  1. Install Python and Tkinter

    Download and install the latest version of python from Python Website.

    Set up virtual environment also to avoid any unexpected errors.

    Open command prompt and navigate to the folder where you are going to place the code and activate virtual environment.

    Install TKinter using command pip install tk. If you get an error 'No module named pip', refer virtual environment setup section.

    If you wish to get a quick tutorial of tkinter before starting with the sample, please have a look at the Tkinter Quick Reference.

  2. Running the game

    Create a file name game.py. Here we are going to place our code.

    To run the game navigate to the folder in cmd and use commands python game.py or py game.py depending on the os.

    If we get any error on using above commands, it is likely because we have set up our virtual environment using older version of Python. Here we have to use commands like python3.9 -m game or py -3.9 -m game

  3. Define the main method

    __main__ needs to be defined for any Tkinter application. This section of code execute only once when the page loads for the first time.
    First define a window of size 400 * 400. 2 variables to set the speed of ball and base rectangle. Also a global variable to see if the ball hits the bottom window (not the base).
    Then create a canvas with same size of window. Create a rectangle base and a ball shape and place these at the bottom. An Entry to keep the score. A label which is initially hidden and displays when game fails.
    Bind left and right arrow key press to move the base according to the speed set.
    Bind enter key press to restart the game.
    startBall() method is called to start the game.

    Copied
    if __name__ == "__main__":
        root = Tk()
        root.minsize(400,400)
        basesp = 10
        ballsp = 5
        global isFailed
        isFailed = False
    
        c = Canvas(width=400, height=400, background='#a0aa00')
        c.pack()
        base = c.create_rectangle(150, 385, 250, 400, fill='blue', outline='blue')
        ball = c.create_oval(190, 365, 210, 385, fill='red', outline='red')    
        txtS = tk.Entry(c, text='0')
        txtScore = c.create_window(350, 0, anchor='nw', window=txtS)
    
        lblM = tk.Label(c, text='Failed!!!Press Enter key to start again')
        lblID = c.create_window(100, 190, anchor='nw', window=lblM)
        c.itemconfigure(lblID, state='hidden')
    
        root.bind("<KeyPress-Left>", lambda event: moveBaseLR(base, 'l', 0-basesp))
        root.bind("<KeyPress-Right>", lambda event: moveBaseLR(base, 'r', basesp))
        root.bind("<Return>", lambda event: restart())
        
        startBall(ball, ballsp)
    
        root.mainloop()
    
  4. Function to move the base rectangle

    This function gets called on left or right arrow key press.
    Get the current position of base using c.coords(base)
    Then use canvas function c.move(base, x, y) to move the base by a particular value in x and y direction and c.update() to reflect the changes in canvas.

    Copied
    def moveBaseLR(base, dir, x, y = 0):
        x1, y1, x2, y2 = c.coords(base)
        if ((x1 > 0 and dir == 'l') or (x2 < 400 and dir == 'r')):
            c.move(base, x, y)
            c.update()
    
  5. Function to move the ball

    This is the most challenging section of code for this game and is written completely using basic Python.
    We are using a for loop to continuously move the ball.
    When the ball hits the wall, we have to deflect it by changing the x and y values to positive or negative. Eight scenarios are there which are explained in code itself as comments.
    When ball reaches bottom, we have to check if it is hitting base or not. If it hits the base rectangle, ball should go up. Otherwise game fails and fail message is displayed.
    Also we are providing a small break between each loop statement using time.sleep(.025) for smooth movement of ball.

    Copied
    def startBall(ball, sp):
        s = random.randint(-sp, sp)
        x, y = s, 0-sp #Start. Ball to move in random direction. 0-sp is used to get negative value
        c.move(ball, x, y)
    
        for p in range(1, 500000):
            l, t, r, b = c.coords(ball)
            txtS.delete(0, END)
            txtS.insert(0, str(p))
            #Need to change direction on hitting wall. Eight options are there.
            if(r >= 400 and x >= 0 and y < 0): #Ball moving ↗ and hit right wall
                x, y = 0-sp, 0-sp
            elif(r >= 400 and x >= 0 and y >= 0): #Ball moving ↘ and hit right wall
                x, y = 0-sp, sp
            elif(l <= 0 and x < 0 and y < 0): #Ball moving ↖ and hit left wall
                x, y = sp, 0-sp
            elif(l <= 0 and x < 0 and y >= 0): #Ball moving ↙ and hit left wall
                x, y = sp, sp
            elif(t <= 0 and x > 0 and y < 0): #Ball moving ↗ and hit top wall
                x, y = sp, sp
            elif(t <= 0 and x < 0 and y < 0): #Ball moving ↖ and hit top wall
                x, y = 0-sp, sp
            elif(b >= 385): #Ball reached base level. Check if base touches ball
                tchPt = l + 10 #Size is 20. Half of it.
                bsl, bst, bsr, bsb = c.coords(base)
                if(tchPt >= bsl and tchPt <= bsr): #Ball touch base
                    n = random.randint(-sp, sp)
                    x, y = n, 0-sp
                else: #Hit bottom. Failed
                    c.itemconfigure(lblID, state='normal')
                    global isFailed
                    isFailed = True
                    break
            
            time.sleep(.025)
            c.move(ball, x, y)
            c.update()
    
  6. Restarting the Game

    Game is restarted on pressing the enter key.
    When enter key is pressed, we will check if the game is still running or not. If not, fail message is hidden and shapes are moved to the initial position and game starts.

    Copied
    def restart():
        global isFailed
        if(isFailed == True):
            isFailed = False
            c.itemconfigure(lblID, state='hidden')
            c.moveto(base, 150, 385)
            c.moveto(ball, 190, 365)
            startBall(ball, ballsp)
    

Complete Code

from tkinter import *
import tkinter as tk
import time
import random

global isFailed

def moveBaseLR(base, dir, x, y = 0):
    x1, y1, x2, y2 = c.coords(base)
    if ((x1 > 0 and dir == 'l') or (x2 < 400 and dir == 'r')):
        c.move(base, x, y)
        c.update()

def startBall(ball, sp):
    s = random.randint(-sp, sp)
    x, y = s, 0-sp #Start. Ball to move in random direction. 0-sp is used to get negative value
    c.move(ball, x, y)

    for p in range(1, 500000):
        l, t, r, b = c.coords(ball)
        txtS.delete(0, END)
        txtS.insert(0, str(p))
        #Need to change direction on hitting wall. Eight options are there.
        if(r >= 400 and x >= 0 and y < 0): #Ball moving ↗ and hit right wall
            x, y = 0-sp, 0-sp
        elif(r >= 400 and x >= 0 and y >= 0): #Ball moving ↘ and hit right wall
            x, y = 0-sp, sp
        elif(l <= 0 and x < 0 and y < 0): #Ball moving ↖ and hit left wall
            x, y = sp, 0-sp
        elif(l <= 0 and x < 0 and y >= 0): #Ball moving ↙ and hit left wall
            x, y = sp, sp
        elif(t <= 0 and x >= 0 and y < 0): #Ball moving ↗ and hit top wall
            x, y = sp, sp
        elif(t <= 0 and x < 0 and y < 0): #Ball moving ↖ and hit top wall
            x, y = 0-sp, sp
        elif(b >= 385): #Ball reached base level. Check if base touches ball
            tchPt = l + 10 #Size is 20. Half of it.
            bsl, bst, bsr, bsb = c.coords(base)
            if(tchPt >= bsl and tchPt <= bsr): #Ball touch base
                n = random.randint(-sp, sp)
                x, y = n, 0-sp
            else: #Hit bottom. Failed
                c.itemconfigure(lblID, state='normal')
                global isFailed
                isFailed = True
                break
        
        time.sleep(.025)
        c.move(ball, x, y)
        c.update()
    
def restart():
    global isFailed
    if(isFailed == True):
        isFailed = False
        c.itemconfigure(lblID, state='hidden')
        c.moveto(base, 150, 385)
        c.moveto(ball, 190, 365)
        startBall(ball, ballsp)


if __name__ == "__main__":
    root = Tk()
    root.minsize(400,400)
    basesp = 10
    ballsp = 5
    global isFailed
    isFailed = False

    c = Canvas(width=400, height=400, background='#a0aa00')
    c.pack()
    base = c.create_rectangle(150, 385, 250, 400, fill='blue', outline='blue')
    ball = c.create_oval(190, 365, 210, 385, fill='red', outline='red')    
    txtS = tk.Entry(c, text='0')
    txtScore = c.create_window(350, 0, anchor='nw', window=txtS)

    lblM = tk.Label(c, text='Failed!!!Press Enter key to start again')
    lblID = c.create_window(100, 190, anchor='nw', window=lblM)
    c.itemconfigure(lblID, state='hidden')

    root.bind("<KeyPress-Left>", lambda event: moveBaseLR(base, 'l', 0-basesp))
    root.bind("<KeyPress-Right>", lambda event: moveBaseLR(base, 'r', basesp))
    root.bind("<Return>", lambda event: restart())
    
    startBall(ball, ballsp)

    root.mainloop()
Absolute Code Works - Python Topics