Editor

Pong in Python

Pygame Online · 155 lines · Up and Down

Keep the ball in play against the computer. First to five wins.

How it works

Serving the ball

The ball starts in the middle with a random slope and waits 45 frames (three quarters of a second at 60 frames a second) before it moves, so you can see where it is going.

def serve(direction):
    """Ball back to the middle; it sets off (1 = right, -1 = left) after a pause."""
    global ball_x, ball_y, ball_dx, ball_dy, serve_timer
    ball_x, ball_y = WIDTH / 2 - 5, HEIGHT / 2 - 5
    ball_dx = 4 * direction
    ball_dy = random.uniform(-2.5, 2.5)
    serve_timer = 45
    ball.topleft = (round(ball_x), round(ball_y))

Where the ball hits the paddle sets its angle

offset is -1 at the top of the paddle, 0 in the middle and 1 at the bottom. The ball leaves steeper the further from the middle it hits, and a little faster each time, up to a limit.

def bounce(paddle, direction):
    """Send the ball back: the further from the paddle's middle, the steeper."""
    global ball_dx, ball_dy
    offset = (ball.centery - paddle.centery) / (paddle.height / 2)  # -1 to 1
    speed = min(abs(ball_dx) * 1.07, 9)
    ball_dx = speed * direction
    ball_dy = offset * 5

The computer player

The computer only chases the ball while it comes its way, aiming at a random point on its own paddle, and it is slower than the ball can get. That is what makes it beatable.

# The computer chases the ball while it comes its way, otherwise it
# drifts back to the middle. It is slower than the ball can be.
if ball_dx > 0:
    target = ball.centery + computer_aim
else:
    target = HEIGHT // 2
if computer.centery < target - 4:
    computer.y += COMPUTER_SPEED
elif computer.centery > target + 4:
    computer.y -= COMPUTER_SPEED

Make it yours

Change the code above and press Run again. Some ideas:

  • Make it a two-player game: move the right paddle with W and S instead of the computer code.
  • Change WIN_SCORE to play longer matches.
  • Make the computer harder by raising COMPUTER_SPEED, or easier by lowering it.
  • Add a second ball after each point is scored.

The whole program

Plain pygame: it runs the same on a computer with Python and pygame installed.

import random
import pygame

pygame.init()
WIDTH, HEIGHT = 480, 360
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Pong")
clock = pygame.time.Clock()
font = pygame.font.Font(None, 30)
score_font = pygame.font.Font(None, 72)
big_font = pygame.font.Font(None, 60)

BG = (16, 18, 22)
NET = (40, 44, 52)
YELLOW = (255, 212, 59)
BLUE = (48, 105, 152)
LIGHT_BLUE = (90, 162, 224)
TEXT = (230, 232, 235)

WIN_SCORE = 5
PLAYER_SPEED = 6
COMPUTER_SPEED = 3
START_KEYS = (pygame.K_UP, pygame.K_SPACE, pygame.K_RETURN)

player = pygame.Rect(16, HEIGHT // 2 - 30, 10, 60)
computer = pygame.Rect(WIDTH - 26, HEIGHT // 2 - 30, 10, 60)
ball = pygame.Rect(0, 0, 10, 10)


def serve(direction):
    """Ball back to the middle; it sets off (1 = right, -1 = left) after a pause."""
    global ball_x, ball_y, ball_dx, ball_dy, serve_timer
    ball_x, ball_y = WIDTH / 2 - 5, HEIGHT / 2 - 5
    ball_dx = 4 * direction
    ball_dy = random.uniform(-2.5, 2.5)
    serve_timer = 45
    ball.topleft = (round(ball_x), round(ball_y))


def bounce(paddle, direction):
    """Send the ball back: the further from the paddle's middle, the steeper."""
    global ball_dx, ball_dy
    offset = (ball.centery - paddle.centery) / (paddle.height / 2)  # -1 to 1
    speed = min(abs(ball_dx) * 1.07, 9)
    ball_dx = speed * direction
    ball_dy = offset * 5


def draw_text(text, text_font, color, center):
    image = text_font.render(text, True, color)
    screen.blit(image, image.get_rect(center=center))


player_score = 0
computer_score = 0
computer_aim = 0  # where on its paddle the computer tries to hit the ball
state = "ready"  # "ready", "playing" or "over"
wait = 0  # frames until a new game may start
serve(1)

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN and event.key in START_KEYS:
            if state == "ready":
                state = "playing"
            elif state == "over" and wait == 0:
                player_score = 0
                computer_score = 0
                serve(1)
                state = "playing"

    # Your paddle follows the arrow keys.
    keys = pygame.key.get_pressed()
    if keys[pygame.K_UP]:
        player.y -= PLAYER_SPEED
    if keys[pygame.K_DOWN]:
        player.y += PLAYER_SPEED
    player.clamp_ip(screen.get_rect())

    if wait > 0:
        wait -= 1

    if state == "playing":
        # The computer chases the ball while it comes its way, otherwise it
        # drifts back to the middle. It is slower than the ball can be.
        if ball_dx > 0:
            target = ball.centery + computer_aim
        else:
            target = HEIGHT // 2
        if computer.centery < target - 4:
            computer.y += COMPUTER_SPEED
        elif computer.centery > target + 4:
            computer.y -= COMPUTER_SPEED
        computer.clamp_ip(screen.get_rect())

        if serve_timer > 0:
            serve_timer -= 1
        else:
            ball_x += ball_dx
            ball_y += ball_dy
            if ball_y < 0 or ball_y > HEIGHT - ball.height:
                ball_dy = -ball_dy
                ball_y = max(0, min(ball_y, HEIGHT - ball.height))
            ball.topleft = (round(ball_x), round(ball_y))

            # Bounce only if the ball has not already gone past the paddle.
            if ball_dx < 0 and ball.colliderect(player) and ball.left > player.left:
                bounce(player, 1)
                ball_x = player.right
                computer_aim = random.randint(-25, 25)
            elif ball_dx > 0 and ball.colliderect(computer) and ball.right < computer.right:
                bounce(computer, -1)
                ball_x = computer.left - ball.width

            if ball.right < 0:
                computer_score += 1
                serve(-1)
            elif ball.left > WIDTH:
                player_score += 1
                serve(1)
            if player_score == WIN_SCORE or computer_score == WIN_SCORE:
                state = "over"
                wait = 45

    screen.fill(BG)
    for y in range(0, HEIGHT, 24):
        pygame.draw.rect(screen, NET, (WIDTH // 2 - 2, y + 6, 4, 12))
    draw_text(str(player_score), score_font, YELLOW, (WIDTH // 2 - 60, 44))
    draw_text(str(computer_score), score_font, LIGHT_BLUE, (WIDTH // 2 + 60, 44))
    pygame.draw.rect(screen, YELLOW, player, border_radius=3)
    pygame.draw.rect(screen, LIGHT_BLUE, computer, border_radius=3)
    if state == "playing":
        pygame.draw.rect(screen, TEXT, ball, border_radius=3)

    if state != "playing":
        panel = pygame.Rect(0, 0, 360, 140)
        panel.center = (WIDTH // 2, HEIGHT // 2 + 20)
        pygame.draw.rect(screen, BG, panel, border_radius=12)
        pygame.draw.rect(screen, BLUE, panel, 2, border_radius=12)
        if state == "ready":
            draw_text("Pong", big_font, YELLOW, (panel.centerx, panel.y + 38))
            draw_text("Up and Down move your paddle", font, TEXT, (panel.centerx, panel.y + 82))
            draw_text(f"First to {WIN_SCORE}. Press Up to start", font, TEXT, (panel.centerx, panel.y + 112))
        else:
            title = "You win!" if player_score == WIN_SCORE else "Computer wins"
            draw_text(title, big_font, YELLOW, (panel.centerx, panel.y + 45))
            draw_text("Press Up or Space to play again", font, TEXT, (panel.centerx, panel.y + 100))

    pygame.display.flip()
    clock.tick(60)

pygame.quit()