Posts: 5
Threads: 1
Joined: Sep 2025
Hello all. I'm new to Python and Pygame. Did some programming as a kid on Atari 8-bist BASIC, and in college on Pascal and C++, so, it's been a while. Didn't get too deep into any of those languages at the time.
Can I ask for a code review of my first attempt at Pygame with a Pong clone? I wrote it procedural style, with thoughts of refactoring to OOP later.
How to post code here? (about 250-300 lines)
Posts: 5
Threads: 1
Joined: Sep 2025
I think I figured it out?
# PONG by JM V1.0
import pygame
import math
pygame.init()
pygame.mixer.init()
screen_width, screen_height = 480,270
scale_factor = 2
display_width, display_height = screen_width * scale_factor,\
screen_height * scale_factor
display= pygame.display.set_mode((display_width, display_height))
screen = pygame.Surface((screen_width, screen_height))
clock = pygame.time.Clock()
FPS = 70
# game states
pause = False
menu = True
game_over = False
level_up = False
levelup_time = 0
game_over_time = 0
game_over_duration = 5000
# init sounds
score1_sound = pygame.mixer.Sound('score1.wav')
score2_sound = pygame.mixer.Sound('score2.wav')
paddle1_sound = pygame.mixer.Sound('paddle1_hit.wav')
paddle2_sound = pygame.mixer.Sound('paddle2_hit.wav')
score1_sound.set_volume(0.5)
score2_sound.set_volume(0.5)
paddle1_sound.set_volume(0.5)
paddle2_sound.set_volume(0.5)
BG_COLOR = (0,50,0)
BORDER_COLOR = NET_COLOR = BALL_COLOR = PADDLE_COLOR = (255,255,255)
TEXT_COLOR = (68,255,30)
BALL_INIT_SPEED = 150
PADDLE1_INIT_SPEED = 0
MAX_BALL_SPEED = 600
font = pygame.font.Font("pong_font.ttf", 16)
screen_ctrx, screen_ctry = screen_width//2,screen_height//2
score1 = 0
score2 = 0
level_data = {1:(100,0.5,44),
2:(150,0.5,44),
3:(200,0.25,44),
4:(250,0.25,44),
5: (300,0,44),
6: (300,0,34),
7: (300,0,24),
8: (300,0,14),
9: (300,0,6),
}
current_level = 1
leveled_paddle2_speed, leveled_player2_lookahead, leveled_paddle1_height = level_data[current_level]
# Init ball1
ball_size= 6
ball_speed= BALL_INIT_SPEED
ball_x, ball_y = screen_ctrx, screen_ctry
ball_dirx= 1
ball_diry= 1
ball_angle = 35
ball1_rect = pygame.rect.FRect(screen_ctrx - ball_size //2,screen_ctry -
ball_size //2,ball_size, ball_size)
# Init Paddles
paddle_offset = 4
paddle_width = 6
paddle_height = leveled_paddle1_height
paddle1_speed = PADDLE1_INIT_SPEED
paddle2_speed = leveled_paddle2_speed
paddle_accel = 35
player1_auto = False
paddle1_rect = pygame.rect.FRect(paddle_offset,screen_ctry-
(paddle_height//2),paddle_width,paddle_height)
paddle2_rect = pygame.rect.FRect(screen_width-paddle_width-paddle_offset,
screen_ctry-(paddle_height//2),
paddle_width,paddle_height)
running = True
while running:
dt = clock.tick(FPS) / 1000.0 # Time since last frame in seconds
# process input
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.WINDOWMOVED:
pause = True # Set flag when window is moved
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
if event.key == pygame.K_SPACE and not menu:
pause = not pause
if event.key == pygame.K_RETURN and menu:
menu = not menu
if not player1_auto and not pause and not game_over and not level_up and not menu:
keys = pygame.key.get_pressed()
if keys[pygame.K_UP] and paddle1_rect.top>0:
paddle1_speed += paddle_accel
paddle1_rect.centery -= paddle1_speed*dt
if keys[pygame.K_DOWN] and paddle1_rect.bottom < screen_height:
paddle1_speed += paddle_accel
paddle1_rect.centery += paddle1_speed*dt
if not keys[pygame.K_UP] and not keys[pygame.K_DOWN]:
paddle1_speed = 0
if paddle1_speed > 300:
paddle1_speed = 300
if not pause and not menu and not game_over and not level_up:
# move AI paddle
if player1_auto:
if ball1_rect.centerx < screen_ctrx:
if paddle1_rect.centery> ball1_rect.centery and \
paddle1_rect.top>0:
paddle1_rect.centery -= paddle1_speed*dt
if paddle1_rect.centery<ball1_rect.centery and \
paddle1_rect.bottom<screen_height:
paddle1_rect.centery += paddle1_speed*dt
if ball1_rect.centerx > screen_width * leveled_player2_lookahead:
if paddle2_rect.centery>ball1_rect.centery and \
paddle2_rect.top>0:
paddle2_rect.centery -= paddle2_speed*dt
if paddle2_rect.centery<ball1_rect.centery and \
paddle2_rect.bottom<screen_height:
paddle2_rect.centery += paddle2_speed*dt
# handle ball wall bounce
if ball1_rect.top <=0:
ball1_rect.top = 1
ball_diry *=-1
if ball1_rect.bottom>=screen_height:
ball1_rect.bottom = screen_height-1
ball_diry *=-1
# handle ball and paddle collisions
if ball1_rect.colliderect(paddle1_rect):
paddle1_sound.play()
if ball1_rect.bottom <= paddle1_rect.top + 2 or ball1_rect.top >= paddle1_rect.bottom - 2:
ball_diry *= -1
else:
ball_x = paddle1_rect.right+ball_size/2 + 1
ball_angle = (abs(paddle1_rect.centery -
ball1_rect.centery) /0.5) + 5
ball_dirx *=-1
ball_speed *=1.05
if ball_speed > MAX_BALL_SPEED:
ball_speed = MAX_BALL_SPEED
if ball1_rect.colliderect(paddle2_rect):
paddle2_sound.play()
if ball1_rect.bottom <= paddle2_rect.top + 2 or ball1_rect.top >= paddle2_rect.bottom - 2:
ball_diry *= -1
else:
ball_x = paddle2_rect.left-ball_size/2 - 1
ball_angle = (abs(paddle2_rect.centery -
ball1_rect.centery) /0.5) + 5
ball_dirx *=-1
ball_speed *=1.05
if ball_speed > MAX_BALL_SPEED:
ball_speed = MAX_BALL_SPEED
# score/reset ball
if ball1_rect.centerx > screen_width:
#reset ball
score1_sound.play()
score1+=1
ball_speed = BALL_INIT_SPEED
ball_dirx *=-1
ball_angle = 35
ball_x = screen_ctrx
ball_y = screen_ctry
if ball1_rect.centerx < 0:
#reset ball
score2_sound.play()
score2+=1
ball_speed = BALL_INIT_SPEED
ball_dirx *=-1
ball_angle = 35
ball_x = screen_ctrx
ball_y = screen_ctry
# check for win
if score1 ==11 or score1-score2==3:
level_up = True
levelup_time = pygame.time.get_ticks() # Record time of game over
if score2 ==11 or score2-score1==3:
game_over = True
game_over_time = pygame.time.get_ticks() # Record time of game over
# calculate dx,dy - the ball incremental move per frame
'''ball x increment = cos( angle) * speed * x direction
ball y increment = sin( angle) * speed * x direction
COS(angle) * ball speed keeps the ball speed the same at any angle
Ball direction is - or + depending on up/down/left/right
This setting makes it easy to change the ball direction
after IF statement'''
ball_dx = math.cos(ball_angle/180*math.pi)*(ball_speed*dt)*ball_dirx
ball_dy = math.sin(ball_angle/180*math.pi)*(ball_speed*dt)*ball_diry
ball_x +=ball_dx
ball_y +=ball_dy
# move the ball in x,y
ball1_rect.centerx = ball_x
ball1_rect.centery = ball_y
# draw objects
screen.fill((BG_COLOR))
score1_text = font.render(str(score1), False, TEXT_COLOR)
score1_text_rect = score1_text.get_rect(center=(screen_width//4, 15))
score2_text = font.render(str(score2), False, TEXT_COLOR)
score2_text_rect = score2_text.get_rect(center=(screen_width*3//4, 15))
screen.blit(score1_text, score1_text_rect)
screen.blit(score2_text, score2_text_rect)
pygame.draw.rect(screen,BORDER_COLOR,(0,0,480,270),1)
for netlines in range(8):
pygame.draw.line(screen,NET_COLOR,(screen_width//2,netlines * 35 + 5),(screen_width//2,netlines*35 + 20))
pygame.draw.rect(screen,BALL_COLOR,ball1_rect)
pygame.draw.rect(screen,BALL_COLOR,paddle1_rect)
pygame.draw.rect(screen,BALL_COLOR,paddle2_rect)
# Game State logic
if menu:
menu_text = font.render("Pong by JM", False,
TEXT_COLOR)
menu_text2 = font.render("Press ENTER to start", False,
TEXT_COLOR)
menu_text3 = font.render("Press ESC anytime to QUIT. W and S move the paddle", False,
TEXT_COLOR)
menu_text_rect = menu_text.get_rect(center=(screen_width//2, 30))
menu_text_rect2 = menu_text2.get_rect(center=(screen_width//2, 67))
menu_text_rect3 = menu_text3.get_rect(center=(screen_width//2, 102))
screen.blit(menu_text, menu_text_rect)
screen.blit(menu_text2, menu_text_rect2)
screen.blit(menu_text3, menu_text_rect3)
if pause:
pause_text = font.render("PAUSED - Press SPACE to resume", False,
TEXT_COLOR)
pause_text_rect = pause_text.get_rect(center=(screen_width//2, 67))
screen.blit(pause_text, pause_text_rect)
if game_over:
game_over_text = font.render("GAME OVER", False,
TEXT_COLOR)
game_over_text_rect = game_over_text.get_rect(center=(screen_width//2, 99))
screen.blit(game_over_text, game_over_text_rect)
if pygame.time.get_ticks() - game_over_time >= game_over_duration:
game_over = False
menu = True
score1 = 0
score2 = 0
current_level = 1
leveled_paddle2_speed, leveled_player2_lookahead, leveled_paddle1_height = level_data[current_level]
paddle2_speed = leveled_paddle2_speed
paddle1_rect.height = leveled_paddle1_height
paddle1_rect.centery = screen_height//2
paddle2_rect.centery = screen_height//2
if level_up:
level_up_text = font.render(f"Leveling up to level {current_level+1}...", False,
TEXT_COLOR)
level_up_text_rect = level_up_text.get_rect(center=(screen_width//2, 99))
screen.blit(level_up_text, level_up_text_rect)
paddle1_rect.centery = screen_height//2
paddle2_rect.centery = screen_height//2
if pygame.time.get_ticks() - levelup_time >= game_over_duration:
score1 = 0
score2 = 0
current_level +=1
if current_level >9:
current_level = 9
leveled_paddle2_speed, leveled_player2_lookahead, leveled_paddle1_height = level_data[current_level]
paddle2_speed = leveled_paddle2_speed
paddle1_rect.height = leveled_paddle1_height
paddle1_rect.centery = screen_height//2
paddle2_rect.centery = screen_height//2
level_up = False
scaled_screen = pygame.transform.scale(screen, (display_width,
display_height))
display.blit(scaled_screen, (0, 0))
pygame.display.flip()
pygame.quit()
Posts: 6,981
Threads: 22
Joined: Feb 2020
You should read up about sprites.
Posts: 5
Threads: 1
Joined: Sep 2025
Sep-11-2025, 10:52 AM
(This post was last modified: Sep-11-2025, 10:53 AM by JMcM.)
(Sep-11-2025, 03:10 AM)deanhystad Wrote: You should read up about sprites.
Agreed. Planning to try coding with sprites for a platformer or a Pitfall! clone. I thought using "colliderect" for Pong was good enough to start learning.
Any bad coding habits you can see so far in what I'm doing (besides repeating code where functions or OOP can be used)?
Posts: 6,981
Threads: 22
Joined: Feb 2020
The event loop is getting large. I would implement level up, menu and pause as function calls, maybe each having their own event loop. That would make it easier to add new features to your game.
I like it. It plays well
Posts: 5
Threads: 1
Joined: Sep 2025
Thanks for reviewing deanhystad!
Planning on refactoring using functions and/or OOP, taking your suggestions.
Posts: 6,981
Threads: 22
Joined: Feb 2020
Sep-14-2025, 02:17 AM
(This post was last modified: Sep-14-2025, 02:18 AM by deanhystad.)
An example that uses customized sprite classes to demonstrate inheritance. Using type notation, docstrings, pygame math and properties. Not a real game.
"""Pong like game demo using sprites."""
import pygame
import math
import random
screen_size = 600, 400
screen_center = screen_size[0] // 2, screen_size[1] // 2
frame_rate = 70
court_color = "#3C638E"
class EnhancedSprite(pygame.sprite.Sprite):
"""High res position sprite.
Saves sprite position as floats instead of
int to prevent quantization when moving slowly
or at shallow angles.
"""
def __init__(
self,
image: pygame.Surface,
pos: pygame.math.Vector2 | tuple[float, float]
) -> None:
"""Initalize sprite.
Arguments:
image: Pyagem Surface drawn in the sprite location.
pos: (x, y) corrdinates for ball center.
"""
super().__init__()
self.image = image
self.rect = self.image.get_rect()
self.pos = pos
@property
def pos(self) -> pygame.math.Vector2:
"""Return (x, y) of sprite center."""
return self._pos
@pos.setter
def pos(self, pos: pygame.math.Vector2 | tuple[float, float]) -> None:
"""Set sprite center to pos(x, y).
Arguments:
pos: Sprite position as a vector.
"""
if isinstance(pos, pygame.math.Vector2):
self._pos = pos
else:
self._pos = pygame.math.Vector2(pos)
self.rect.centerx, self.rect.centery = self.pos
def draw(self, screen: pygame.Surface) -> None:
"""Draw self on screen."""
screen.blit(self.image, self.rect)
class Ball(EnhancedSprite):
"""Ball sprite. Knows how to move the ball."""
def __init__(
self,
pos: tuple[float, float],
radius: int = 6,
speed: float = 2,
max_speed: float = 10,
angle: float = 35
) -> None:
"""Initalize sprite.
Arguments:
pos: (x, y) corrdinates for ball center.
radius: Radius of circle (in pixels) of ball.
speed: Speed the ball travels.
angle: Initial angle (in degrees) that ball travels.
"""
image = pygame.Surface([radius*2, radius*2])
image.fill("black")
image.set_colorkey("black")
pygame.draw.circle(image, "white", (radius, radius), radius, 0)
super().__init__(image, pos)
self.heading = pygame.math.Vector2(1, 0)
self.speed = speed
self.max_speed = max_speed
self.angle = angle
@property
def angle(self) -> float:
"""Return angle of ball travel.
Returns:
Angle measured in degrees starting at 3 o'clock.
"""
return pygame.math.Vector2(1, 0).angle_to(self.heading)
@angle.setter
def angle(self, angle) -> None:
"""Set angle of ball travel.
Arguments:
angle: Angle measured in degrees starting at 3 o'clock.
"""
self.heading = pygame.math.Vector2(1, 0).rotate(angle)
def speed_up(self, percent: float) -> None:
"""Increase the ball speed."""
self.speed = min(self.max_speed, self.speed * (1 + percent / 100))
def update(self) -> None:
"""Update position of ball."""
self.pos = self.pos + self.heading * self.speed
class Paddle(EnhancedSprite):
"""A base class for paddles
Paddles are sprites that move up/down controlled
by key presses or tracked by the computer. Paddle
speed and acceleration are limited.
When the paddle collides with the ball, the ball
bounces off the paddle and increases speed. The
angle the ball bounces can be altered by striking
the ball off center on the paddle.
"""
def __init__(
self,
x: int,
max_y: int,
width: int = 10,
height: int = 50,
speed: float = 3,
accel: float = 1
) -> None:
"""Initialize instance.
Arguments:
x: X location of paddle.
max_y: Bottom of paddle range.
width: Paddle width.
height: Paddle height.
speed: Max paddle speed.
accel: Speed ramp rate.
"""
image = pygame.Surface((width, height))
image.fill("white")
super().__init__(image, (x, max_y // 2))
self.max_y = max_y
self.max_speed = speed
self.accel = accel
self.speed = 0
def update(self, ball: Ball) -> None:
"""Test for collision with ball."""
if not ball.rect.colliderect(self.rect):
return
# Using cludgy heuristic to compute an aim adjustment to the
# ball's reflection if it doesn't hit center of the paddle
dy = ball.rect.centery - self.rect.centery
aim = math.degrees(math.atan2(dy / self.rect.height, 2))
angle = ball.angle
if ball.heading.x > 0:
# Right paddle
ball.rect.right = self.rect.x
incidence = angle
reflection = max(-50, min(50, -incidence - aim))
ball.angle = 180 + reflection
else:
# Left paddle
ball.rect.x = self.rect.right
incidence = angle + 180 if angle < 0 else angle - 180
reflection = max(-50, min(50, -incidence + aim))
ball.angle = reflection
ball.speed_up(5)
def move(self, direction: int) -> None:
"""Move paddle up (-1) or down(1) or stop (0).
Arguments:
direction: Direction to move paddle.
"""
if direction == 0:
self.speed = 0
else:
speed = self.speed + self.accel * direction
self.speed = max(-self.max_speed, min(self.max_speed, speed))
x, y = self.rect.center
self.pos = (x, max(0, min(self.max_y, y + self.speed)))
class PlayerPaddle(Paddle):
"""A paddle that is player controlled using key_up and key_down"""
def __init__(
self,
x: int,
max_y: int,
width: int = 10,
height: int = 50,
speed: float = 3,
accel: float = 1,
key_up: int = pygame.K_UP,
key_down: int = pygame.K_DOWN
) -> None:
"""Initialize instance.
Arguments:
key_up: Key press to move paddle up.
key_down: Key press to move paddle down.
"""
super().__init__(x, max_y, width, height, speed, accel)
self.key_up = key_up
self.key_down = key_down
def update(self, ball) -> None:
"""Update paddle position and check for ball collision."""
keys = pygame.key.get_pressed()
if keys[self.key_up]:
self.move(-1)
elif keys[self.key_down]:
self.move(1)
else:
self.move(0)
super().update(ball)
class ComputerPaddle(Paddle):
"""Computer controlled paddle."""
def update(self, ball) -> None:
"""Update paddle position and check for ball collision."""
x, y = self.pos
bx, by = ball.pos
move = 1 if by > y else -1
# Only track ball as it approaches.
if (x - bx) * ball.heading.x < 0:
move = 0
self.move(move)
super().update(ball)
def text(
screen,
text: str,
pos: tuple[int, int],
font: pygame.font.Font | None = None,
color="white"
) -> None:
"""Draw text on screen.
Arguments:
screen: Screen where text is drawn.
text: Text to display.
pos: (x, y) position of text center.
font: Font used to draw text.
color: Text color.
"""
if font is None:
font = default_font
image = font.render(text, False, color)
screen.blit(image, image.get_rect(center=pos))
def play_pong() -> None:
"""Play pong game."""
paddles = pygame.sprite.Group([
PlayerPaddle(x=10, max_y=screen_size[1]),
ComputerPaddle(x=screen_size[0]-10, max_y=screen_size[1], accel=0.5)
])
ball = Ball(pos=screen_center, angle=random.randint(-45, 45))
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
return
ball.update()
if ball.rect.x > screen_size[0] or ball.rect.right < 0:
ball = Ball(screen_center, angle=random.randint(-45, 45))
elif ball.rect.y <= 0 or ball.rect.bottom >= screen_size[1]:
ball.heading.y = -ball.heading.y
paddles.update(ball)
screen.fill(court_color)
paddles.draw(screen)
ball.draw(screen)
display.blit(
pygame.transform.scale(screen, display.get_size()), (0, 0)
)
pygame.display.flip()
clock.tick(frame_rate)
def splash_screen() -> None:
"""Display splash screen."""
screen.fill("black")
text(
screen,
"Welcome to Pong",
(screen_center[0], screen_center[1] - 100),
pygame.font.Font(None, 64)
)
text(
screen,
"Press any key to continue",
(screen_center[0], screen_center[1] + 100)
)
display.blit(pygame.transform.scale(screen, display.get_size()), (0, 0))
pygame.display.flip()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
return False
if event.type == pygame.KEYDOWN:
return True
pygame.init()
default_font = pygame.font.Font(None, 32)
display = pygame.display.set_mode(screen_size, pygame.RESIZABLE)
screen = pygame.Surface(screen_size)
clock = pygame.time.Clock()
if splash_screen():
play_pong()
pygame.quit()
Posts: 5
Threads: 1
Joined: Sep 2025
Thanks for the example...lot of stuff in there I don't know yet.
Posts: 545
Threads: 15
Joined: Oct 2016
Little late. @ deanhystad. Why are you saying __init__ method is return None. It always return an object.
99 percent of computer problems exists between chair and keyboard.
Posts: 6,981
Threads: 22
Joined: Feb 2020
Jan-21-2026, 04:55 PM
(This post was last modified: Jan-21-2026, 08:14 PM by deanhystad.)
@ Windspar
Like all methods, __init__() returns a value, but that value has to be None.
When you create an instance of an object you do this:
instance = some_class(args) Behind closed doors, python does this:
instance = some_class.__new__()
instance.__init__(args)
return instance The value returned by __init__ is ignored. Do you expect __init__ to automatically return self? It does not.
class A:
def __init__(self):
pass
class B(A):
def __init__(self):
print("Value returned by A.__init__()", super().__init__())
b = B()Output: Value returned by A.__init__() None
If you write __init__() to return a value other than None, Python flags an error.
class A:
def __init__(self):
return self
a = A()Error: Exception has occurred: TypeError
__init__() should return None, not 'A'
File "...test.py", line 5, in <module>
a = A()
^^^
TypeError: __init__() should return None, not 'A'
|