Skip to content

Instantly share code, notes, and snippets.

@ssaurel
Created January 4, 2024 13:35
Show Gist options
  • Save ssaurel/98c17278396a2cf51bf13152f6a6470e to your computer and use it in GitHub Desktop.
Save ssaurel/98c17278396a2cf51bf13152f6a6470e to your computer and use it in GitHub Desktop.
Classical Pong Game in Python with Tkinter UI for the SSaurel's Blog
import tkinter as tk
import random as rand
import threading
import os
#define some constants
WIDTH = 1000
HEIGHT = 700
MARGIN = 15
VELOCITY = 15
# First, we design the Ball Object
class Ball:
def __init__(self, canvas, width, velocity, boardwidth, boardheight):
self.width = width
self.boardwidth = boardwidth
self.boardheight = boardheight
# we center the ball on the board
self.topx = boardwidth / 2 - width / 2
self.topy = boardheight / 2 - width / 2
self.velocity = velocity
self.vx = velocity
self.vy = velocity
self.canvas = canvas
self.id = self.canvas.create_rectangle(self.topx, self.topy, self.topx + self.width, self.topy + self.width, fill = 'white')
# we define a method to draw the ball on the canvas
def draw(self):
self.canvas.coords(self.id, self.topx, self.topy, self.topx + self.width, self.topy + self.width)
# we define a restart method for restarting the ball move
def restart(self):
self.topx = self.boardwidth / 2 - self.width / 2
self.topy = self.boardheight / 2 - self.width / 2
# we define a random direction for the ball when restarting
self.vx = (-1, 1)[rand.random() > 0.5] * self.velocity
self.vy = (-1, 1)[rand.random() > 0.5] * self.velocity
# Move the ball
# we need to pass the pong game instance and the paddles in the move method of the Ball. You can improve this by yourself later ;)
def move(self, pong, paddleright, paddleleft):
# if the ball touches the top or the bottom of the board, we invert direction y
if self.topy <= 0 or (self.topy + self.width) >= self.boardheight:
self.vy = self.vy * -1
# if the ball touches one of both paddles, we invert direction x
if paddleright.collideright(self) or paddleleft.collideleft(self):
self.vx = self.vx * -1
# if the ball touches the right or the left of the board, we update paddle points and we return True
if (self.topx + self.width) >= self.boardwidth:
pong.leftpoints = pong.leftpoints + 1
return True
if self.topx <= 0:
pong.rightpoints = pong.rightpoints + 1
return True
# we update ball position
self.topx = self.topx + self.vx
self.topy = self.topy + self.vy
return False
# Now, it is time to design the Paddle for our Pong Game
class Paddle:
def __init__(self, canvas, topx, topy, width, height, boardheight):
self.topx = topx
self.topy = topy
self.width = width
self.height = height
self.boardheight = boardheight
self.score = 0
self.canvas = canvas
# draw this paddle according positions passed in parameter
self.id = self.canvas.create_rectangle(self.topx, self.topy, self.topx + self.width, self.topy + self.height, fill = 'white')
# we update coords of this paddle
def draw(self):
self.canvas.coords(self.id, self.topx, self.topy, self.topx + self.width, self.topy + self.height)
# now, we need to manage down event then top event for the current paddle object
def top(self):
if self.topy - VELOCITY > 0:
self.topy = self.topy - VELOCITY
def down(self):
if (self.topy + self.height + VELOCITY) < self.boardheight:
self.topy = self.topy + VELOCITY
# use both methods to collide paddle right or left. As an exercise, you can improve this to make one generic method ;)
def collideright(self, ball):
if (ball.topx + ball.width) >= self.topx and (ball.topy >= self.topy or (ball.topy + ball.width) >= self.topy) and ((ball.topy + ball.width) <= (self.topy + self.height) or ball.topy <= (self.topy + self.height)):
return True
return False
def collideleft(self, ball):
if ball.topx <= (self.topx + self.width) and (ball.topy >= self.topy or (ball.topy + ball.width) >= self.topy) and ((ball.topy + ball.width) <= (self.topy + self.height) or ball.topy <= (self.topy + self.height)):
return True
return False
# Now, we can define the Pong Game Object
class Pong:
def __init__(self, root, width, height, margin):
paddlewidth = width / 50
paddleheight = height / 12
self.leftpoints = 0
self.lefttxt = None
self.rightpoints = 0
self.righttxt = None
self.render = True # True when we need to render the game on the canvas
# we manage left up / down for moving the left paddle
self.leftup = False
self.leftdown = False
# same for right paddle
self.rightup = False
self.rightdown = False
self.width = width
self.height = height
self.margin = margin
self.root = root
self.root.title("Pong Game - SSaurel's Blog")
self.root.geometry(str(width) + "x" + str(height))
# we create the canvas
self.canvas = tk.Canvas(self.root, width = width, height = height, bg = 'black')
self.paddleleft = Paddle(self.canvas, margin, height / 2 - paddleheight / 2, paddlewidth, paddleheight, height)
self.paddleright = Paddle(self.canvas, (width - margin) - paddlewidth, height / 2 - paddleheight / 2, paddlewidth, paddleheight, height)
self.ball = Ball(self.canvas, paddlewidth, VELOCITY, width, height)
self.canvas.pack()
self.drawmiddlelines()
self.drawboard()
self.move()
# we move draw middle lines for the board
def drawmiddlelines(self):
leftx = self.width / 2 - self.paddleleft.width / 2
for y in range(0, self.height, int(self.paddleleft.height + self.margin * 2)):
self.canvas.create_rectangle(leftx, y, leftx + self.paddleleft.width, y + self.paddleleft.height, fill = 'grey')
def drawboard(self):
try:
# draw the paddles
self.paddleleft.draw()
self.paddleright.draw()
# draw points
self.drawpoints()
# draw the ball
self.ball.draw()
except:
# some strange exception occur here when we quit the game. We need to call explicitly exit!
os._exit(0)
def drawpoints(self):
# we delete the previous score for the left paddle
if self.lefttxt != None:
self.canvas.delete(self.lefttxt)
# we write the new score
self.lefttxt = self.canvas.create_text(self.width / 2 - 50, 50, text = str(self.leftpoints), fill = 'grey', font = ("Helvetica 35 bold"))
# the same thing for the right paddle
if self.righttxt != None:
self.canvas.delete(self.righttxt)
# we write the new score
self.righttxt = self.canvas.create_text(self.width / 2 + 50, 50, text = str(self.rightpoints), fill = 'grey', font = ("Helvetica 35 bold"))
# we define the move method to update the game elements
def move(self):
if self.render:
# use a timer to call this method each X milliseconds
self.timer = threading.Timer(0.05, self.move)
self.timer.start()
# we manage touch events
if self.leftup:
self.paddleleft.top()
if self.leftdown:
self.paddleleft.down()
if self.rightup:
self.paddleright.top()
if self.rightdown:
self.paddleright.down()
# True if the Ball touched one of both sides of the board
state = self.ball.move(self, self.paddleright, self.paddleleft)
if state:
self.restart() # we need to restart the ball
self.drawboard()
def restart(self):
self.ball.restart()
# Time to manage keyboards event from users
# We need to make this special code to detect several keys used at the same time on the keyboard
# z / s for the left paddle - o / l for the right paddle
def keypress(self, event):
match event.char:
case 'z':
self.leftup = True
case 's':
self.leftdown = True
case 'o':
self.rightup = True
case 'l':
self.rightdown = True
def keyrelease(self, event):
match event.char:
case 'z':
self.leftup = False
case 's':
self.leftdown = False
case 'o':
self.rightup = False
case 'l':
self.rightdown = False
# last method: we define a method to kill the timer and stop the rendering of the game
def killtimer(self):
self.render = False
self.timer.cancel()
self.root.destroy()
# we can assemble the pong game elements!
root = tk.Tk()
pong = Pong(root, WIDTH, HEIGHT, MARGIN)
# we bind key press and key release events to our Pong Game Object
root.bind("<KeyPress>", pong.keypress)
root.bind("<KeyRelease>", pong.keyrelease)
# we listen to WM_DELETE_WINDOW event to kill the timer ...
root.wm_protocol("WM_DELETE_WINDOW", pong.killtimer)
root.mainloop()
# Time to try our Pong Game in Python with Tkinter
# Improvement you can make from here :
# - Make a limit points to end the game. For the moment, it is an infinite Pong Game :D
# - You can also imagine to increase the velocity of the ball each 10 points scored.
# - You can implement an AI for the second paddle
# - You can improve the design of the objects as stated in the code
# --> It is your turn to code :D
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment