From 3e3b90b5d7c7d709740c2412cae9fc383a2e2bb9 Mon Sep 17 00:00:00 2001 From: Real Python Bot <42617967+realpython-bot@users.noreply.github.com> Date: Wed, 16 Sep 2026 15:02:49 +0000 Subject: [PATCH] Sync pygame-a-primer with the maintenance update of Pygame: A Primer on Game Programming in Python The tutorial is now pinned to Python 3.13 / pygame 2.6.1, so the folder gets a pinned requirements.txt and loses the Python 3.6 Pipfile that pinned flake8 and black. The three scripts are brought back in line with the listings in the article, and the readme now links the tutorial and explains how to run the examples. Co-Authored-By: Claude Opus 5 --- pygame-a-primer/Pipfile | 14 ---- pygame-a-primer/Pipfile.lock | 107 -------------------------- pygame-a-primer/py_tut_with_images.py | 72 ++++++++--------- pygame-a-primer/py_tutfinal.py | 48 ++++++------ pygame-a-primer/pygame_simple.py | 8 +- pygame-a-primer/readme.md | 41 +++++++++- pygame-a-primer/requirements.txt | 1 + 7 files changed, 105 insertions(+), 186 deletions(-) delete mode 100644 pygame-a-primer/Pipfile delete mode 100644 pygame-a-primer/Pipfile.lock create mode 100644 pygame-a-primer/requirements.txt diff --git a/pygame-a-primer/Pipfile b/pygame-a-primer/Pipfile deleted file mode 100644 index cbe58aaa21..0000000000 --- a/pygame-a-primer/Pipfile +++ /dev/null @@ -1,14 +0,0 @@ -[[source]] -name = "pypi" -url = "https://pypi.org/simple" -verify_ssl = true - -[dev-packages] - -[packages] -pygame = "*" -flake8 = "*" -black = "*" - -[requires] -python_version = "3.6" diff --git a/pygame-a-primer/Pipfile.lock b/pygame-a-primer/Pipfile.lock deleted file mode 100644 index c93d266430..0000000000 --- a/pygame-a-primer/Pipfile.lock +++ /dev/null @@ -1,107 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "c45ee7b9ec13e88a28b707a1050cb4fc17781d818fbb6c3e8bafd35b9ad4c9d0" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.6" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "appdirs": { - "hashes": [ - "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", - "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" - ], - "version": "==1.4.3" - }, - "attrs": { - "hashes": [ - "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", - "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" - ], - "version": "==19.1.0" - }, - "black": { - "hashes": [ - "sha256:09a9dcb7c46ed496a9850b76e4e825d6049ecd38b611f1224857a79bd985a8cf", - "sha256:68950ffd4d9169716bcb8719a56c07a2f4485354fec061cdd5910aa07369731c" - ], - "index": "pypi", - "version": "==19.3b0" - }, - "click": { - "hashes": [ - "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", - "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" - ], - "version": "==7.0" - }, - "entrypoints": { - "hashes": [ - "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", - "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" - ], - "version": "==0.3" - }, - "flake8": { - "hashes": [ - "sha256:859996073f341f2670741b51ec1e67a01da142831aa1fdc6242dbf88dffbe661", - "sha256:a796a115208f5c03b18f332f7c11729812c8c3ded6c46319c59b53efd3819da8" - ], - "index": "pypi", - "version": "==3.7.7" - }, - "mccabe": { - "hashes": [ - "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", - "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" - ], - "version": "==0.6.1" - }, - "pycodestyle": { - "hashes": [ - "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", - "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" - ], - "version": "==2.5.0" - }, - "pyflakes": { - "hashes": [ - "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", - "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" - ], - "version": "==2.1.1" - }, - "pygame": { - "hashes": [ - "sha256:2d4f22cb1e042fe4bf415128399e9156db3f042a4e1e4147eff20ef511550df2", - "sha256:33a187e0b768544d4c87a499ff0bbd552695e75291c2a1e512c2635df9bbf5dd", - "sha256:4f36f8d7142ed690194b1b4f585fe47efe2ad5286dc641df31333da2fbb66ef4", - "sha256:5dd5950e34f0bc1835f8c3ea017591f11fce2cf6a34d0f6d401f344429c62fb2", - "sha256:715a67a4e572d1c6449938a77891978925faa886215be5afd70a663b060048fd", - "sha256:86f55279a1b4e2fa626b960e6fafeab6a8349ac4f19a2886bc4fac7f8f6c7065", - "sha256:b030f0c58b1468d80f84ea5499c8747a4f9b19034be8cae0dcc0fc1583570a39", - "sha256:e609adee976fec4326801cd2f6670328fbd337dc89f98fe36655592765447461" - ], - "index": "pypi", - "version": "==2.0.0.dev1" - }, - "toml": { - "hashes": [ - "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", - "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" - ], - "version": "==0.10.0" - } - }, - "develop": {} -} diff --git a/pygame-a-primer/py_tut_with_images.py b/pygame-a-primer/py_tut_with_images.py index 18d96a5e0b..137e654b10 100644 --- a/pygame-a-primer/py_tut_with_images.py +++ b/pygame-a-primer/py_tut_with_images.py @@ -1,21 +1,21 @@ # Import the pygame module +import pygame + # Import random for random numbers import random -import pygame - # Import pygame.locals for easier access to key coordinates # Updated to conform to flake8 and black standards # from pygame.locals import * from pygame.locals import ( + RLEACCEL, + K_UP, K_DOWN, - K_ESCAPE, K_LEFT, K_RIGHT, - K_UP, + K_ESCAPE, KEYDOWN, QUIT, - RLEACCEL, ) # Define constants for the screen width and height @@ -23,8 +23,8 @@ SCREEN_HEIGHT = 600 -# Define the Player object extending pygame.sprite.Sprite -# Instead of a surface, we use an image for a better looking sprite +# Define the Player object by extending pygame.sprite.Sprite +# Instead of a surface, use an image for a better-looking sprite class Player(pygame.sprite.Sprite): def __init__(self): super(Player, self).__init__() @@ -48,16 +48,16 @@ def update(self, pressed_keys): # Keep player on the screen if self.rect.left < 0: self.rect.left = 0 - elif self.rect.right > SCREEN_WIDTH: + if self.rect.right > SCREEN_WIDTH: self.rect.right = SCREEN_WIDTH if self.rect.top <= 0: self.rect.top = 0 - elif self.rect.bottom >= SCREEN_HEIGHT: + if self.rect.bottom >= SCREEN_HEIGHT: self.rect.bottom = SCREEN_HEIGHT -# Define the enemy object extending pygame.sprite.Sprite -# Instead of a surface, we use an image for a better looking sprite +# Define the enemy object by extending pygame.sprite.Sprite +# Instead of a surface, use an image for a better-looking sprite class Enemy(pygame.sprite.Sprite): def __init__(self): super(Enemy, self).__init__() @@ -72,16 +72,16 @@ def __init__(self): ) self.speed = random.randint(5, 20) - # Move the enemy based on speed - # Remove it when it passes the left edge of the screen + # Move the sprite based on speed + # Remove the sprite when it passes the left edge of the screen def update(self): self.rect.move_ip(-self.speed, 0) if self.rect.right < 0: self.kill() -# Define the cloud object extending pygame.sprite.Sprite -# Use an image for a better looking sprite +# Define the cloud object by extending pygame.sprite.Sprite +# Use an image for a better-looking sprite class Cloud(pygame.sprite.Sprite): def __init__(self): super(Cloud, self).__init__() @@ -96,51 +96,51 @@ def __init__(self): ) # Move the cloud based on a constant speed - # Remove it when it passes the left edge of the screen + # Remove the cloud when it passes the left edge of the screen def update(self): self.rect.move_ip(-5, 0) if self.rect.right < 0: self.kill() -# Setup for sounds, defaults are good +# Setup for sounds. Defaults are good. pygame.mixer.init() # Initialize pygame pygame.init() -# Setup the clock for a decent framerate +# Set up the clock for a decent framerate clock = pygame.time.Clock() # Create the screen object # The size is determined by the constant SCREEN_WIDTH and SCREEN_HEIGHT screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) -# Create custom events for adding a new enemy and cloud +# Create custom events for adding a new enemy and a cloud ADDENEMY = pygame.USEREVENT + 1 pygame.time.set_timer(ADDENEMY, 250) ADDCLOUD = pygame.USEREVENT + 2 pygame.time.set_timer(ADDCLOUD, 1000) -# Create our 'player' +# Create the 'player' player = Player() # Create groups to hold enemy sprites, cloud sprites, and all sprites # - enemies is used for collision detection and position updates # - clouds is used for position updates -# - all_sprites isused for rendering +# - all_sprites is used for rendering enemies = pygame.sprite.Group() clouds = pygame.sprite.Group() all_sprites = pygame.sprite.Group() all_sprites.add(player) -# Load and play our background music +# Load and play background music # Sound source: http://ccmixter.org/files/Apoxode/59262 # License: https://creativecommons.org/licenses/by/3.0/ pygame.mixer.music.load("Apoxode_-_Electric_1.mp3") pygame.mixer.music.play(loops=-1) -# Load all our sound files +# Load all sound files # Sound sources: Jon Fincher move_up_sound = pygame.mixer.Sound("Rising_putter.ogg") move_down_sound = pygame.mixer.Sound("Falling_putter.ogg") @@ -151,33 +151,33 @@ def update(self): move_down_sound.set_volume(0.5) collision_sound.set_volume(0.5) -# Variable to keep our main loop running +# Variable to keep the main loop running running = True -# Our main loop +# Main loop while running: # Look at every event in the queue for event in pygame.event.get(): # Did the user hit a key? if event.type == KEYDOWN: - # Was it the Escape key? If so, stop the loop + # Was it the Escape key? If so, then stop the loop. if event.key == K_ESCAPE: running = False - # Did the user click the window close button? If so, stop the loop + # Did the user click the window close button? If so, stop the loop. elif event.type == QUIT: running = False - # Should we add a new enemy? + # Add a new enemy? elif event.type == ADDENEMY: - # Create the new enemy, and add it to our sprite groups + # Create the new enemy and add it to sprite groups new_enemy = Enemy() enemies.add(new_enemy) all_sprites.add(new_enemy) - # Should we add a new cloud? + # Add a new cloud? elif event.type == ADDCLOUD: - # Create the new cloud, and add it to our sprite groups + # Create the new cloud and add it to sprite groups new_cloud = Cloud() clouds.add(new_cloud) all_sprites.add(new_cloud) @@ -186,20 +186,20 @@ def update(self): pressed_keys = pygame.key.get_pressed() player.update(pressed_keys) - # Update the position of our enemies and clouds + # Update the position of enemies and clouds enemies.update() clouds.update() # Fill the screen with sky blue screen.fill((135, 206, 250)) - # Draw all our sprites + # Draw all sprites for entity in all_sprites: screen.blit(entity.surf, entity.rect) # Check if any enemies have collided with the player if pygame.sprite.spritecollideany(player, enemies): - # If so, remove the player + # If so, then remove the player player.kill() # Stop any moving sounds and play the collision sound @@ -213,9 +213,9 @@ def update(self): # Flip everything to the display pygame.display.flip() - # Ensure we maintain a 30 frames per second rate + # Ensure program maintains a rate of 30 frames per second clock.tick(30) -# At this point, we're done, so we can stop and quit the mixer +# All done! Stop and quit the mixer. pygame.mixer.music.stop() pygame.mixer.quit() diff --git a/pygame-a-primer/py_tutfinal.py b/pygame-a-primer/py_tutfinal.py index e2c534c38b..68cd9eedd0 100644 --- a/pygame-a-primer/py_tutfinal.py +++ b/pygame-a-primer/py_tutfinal.py @@ -1,17 +1,17 @@ # Import the pygame module +import pygame + # Import random for random numbers import random -import pygame - # Import pygame.locals for easier access to key coordinates # Updated to conform to flake8 and black standards from pygame.locals import ( + K_UP, K_DOWN, - K_ESCAPE, K_LEFT, K_RIGHT, - K_UP, + K_ESCAPE, KEYDOWN, QUIT, ) @@ -21,8 +21,8 @@ SCREEN_HEIGHT = 600 -# Define the Player object extending pygame.sprite.Sprite -# The surface we draw on the screen is now a property of 'player' +# Define a player object by extending pygame.sprite.Sprite +# The surface drawn on the screen is now an attribute of 'player' class Player(pygame.sprite.Sprite): def __init__(self): super(Player, self).__init__() @@ -30,7 +30,7 @@ def __init__(self): self.surf.fill((255, 255, 255)) self.rect = self.surf.get_rect() - # Move the sprite based on keypresses + # Move the sprite based on user keypresses def update(self, pressed_keys): if pressed_keys[K_UP]: self.rect.move_ip(0, -5) @@ -44,16 +44,16 @@ def update(self, pressed_keys): # Keep player on the screen if self.rect.left < 0: self.rect.left = 0 - elif self.rect.right > SCREEN_WIDTH: + if self.rect.right > SCREEN_WIDTH: self.rect.right = SCREEN_WIDTH if self.rect.top <= 0: self.rect.top = 0 - elif self.rect.bottom >= SCREEN_HEIGHT: + if self.rect.bottom >= SCREEN_HEIGHT: self.rect.bottom = SCREEN_HEIGHT -# Define the enemy object extending pygame.sprite.Sprite -# The surface we draw on the screen is now a property of 'enemy' +# Define the enemy object by extending pygame.sprite.Sprite +# The surface you draw on the screen is now an attribute of 'enemy' class Enemy(pygame.sprite.Sprite): def __init__(self): super(Enemy, self).__init__() @@ -68,7 +68,7 @@ def __init__(self): self.speed = random.randint(5, 20) # Move the sprite based on speed - # Remove it when it passes the left edge of the screen + # Remove the sprite when it passes the left edge of the screen def update(self): self.rect.move_ip(-self.speed, 0) if self.rect.right < 0: @@ -82,40 +82,40 @@ def update(self): # The size is determined by the constant SCREEN_WIDTH and SCREEN_HEIGHT screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) -# Create a custom event for adding a new enemy. +# Create a custom event for adding a new enemy ADDENEMY = pygame.USEREVENT + 1 pygame.time.set_timer(ADDENEMY, 250) -# Create our 'player' +# Instantiate player. Right now, this is just a rectangle. player = Player() -# Create groups to hold enemy sprites, and every sprite +# Create groups to hold enemy sprites and all sprites # - enemies is used for collision detection and position updates # - all_sprites is used for rendering enemies = pygame.sprite.Group() all_sprites = pygame.sprite.Group() all_sprites.add(player) -# Variable to keep our main loop running +# Variable to keep the main loop running running = True -# Our main loop +# Main loop while running: # Look at every event in the queue for event in pygame.event.get(): # Did the user hit a key? if event.type == KEYDOWN: - # Was it the Escape key? If so, stop the loop + # Was it the Escape key? If so, stop the loop. if event.key == K_ESCAPE: running = False - # Did the user click the window close button? If so, stop the loop + # Did the user click the window close button? If so, stop the loop. elif event.type == QUIT: running = False - # Should we add a new enemy? + # Add a new enemy? elif event.type == ADDENEMY: - # Create the new enemy, and add it to our sprite groups + # Create the new enemy and add it to sprite groups new_enemy = Enemy() enemies.add(new_enemy) all_sprites.add(new_enemy) @@ -124,19 +124,19 @@ def update(self): pressed_keys = pygame.key.get_pressed() player.update(pressed_keys) - # Update the position of our enemies + # Update enemy position enemies.update() # Fill the screen with black screen.fill((0, 0, 0)) - # Draw all our sprites + # Draw all sprites for entity in all_sprites: screen.blit(entity.surf, entity.rect) # Check if any enemies have collided with the player if pygame.sprite.spritecollideany(player, enemies): - # If so, remove the player and stop the loop + # If so, then remove the player and stop the loop player.kill() running = False diff --git a/pygame-a-primer/pygame_simple.py b/pygame-a-primer/pygame_simple.py index 12476806c2..05b0fabeb1 100644 --- a/pygame-a-primer/pygame_simple.py +++ b/pygame-a-primer/pygame_simple.py @@ -5,10 +5,10 @@ pygame.init() -# Setup the window we'll use for drawing +# Set up the drawing window screen = pygame.display.set_mode([500, 500]) -# Run until the user asks us to quit +# Run until the user asks to quit running = True while running: # Did the user click the window close button? @@ -19,11 +19,11 @@ # Fill the background with white screen.fill((255, 255, 255)) - # Draw a solid circle in the center + # Draw a solid blue circle in the center pygame.draw.circle(screen, (0, 0, 255), (250, 250), 75) # Flip the display pygame.display.flip() -# We're done, so we can quit now. +# Done! Time to quit. pygame.quit() diff --git a/pygame-a-primer/readme.md b/pygame-a-primer/readme.md index b618266e64..019d040107 100644 --- a/pygame-a-primer/readme.md +++ b/pygame-a-primer/readme.md @@ -1 +1,40 @@ -Source files for the article "Pygame --- A Primer". Link TBD +# Pygame: A Primer on Game Programming in Python + +The code in this folder supplements the Real Python tutorial [Pygame: A Primer on Game Programming in Python](https://realpython.com/pygame-a-primer/). + +## Dependencies + +The tutorial uses Python 3.13 and pygame 2.6.1. First create and activate a virtual environment: + +```console +$ python -m venv venv +$ source venv/bin/activate +``` + +Then install `pygame` with `pip`: + +```console +(venv) $ python -m pip install pygame +``` + +Alternatively, you can install the pinned version used in the tutorial from `requirements.txt`: + +```console +(venv) $ python -m pip install -r requirements.txt +``` + +## Run the Examples + +Run each script from inside this folder so that the images and sounds resolve: + +```console +(venv) $ python pygame_simple.py +(venv) $ python py_tutfinal.py +(venv) $ python py_tut_with_images.py +``` + +- `pygame_simple.py` is the basic `pygame` program from the beginning of the tutorial. +- `py_tutfinal.py` is the game built with plain `Surface` objects. +- `py_tut_with_images.py` is the final game with sprite images, clouds, and sound. + +Use the arrow keys to move the jet, and press Esc or close the window to quit. diff --git a/pygame-a-primer/requirements.txt b/pygame-a-primer/requirements.txt new file mode 100644 index 0000000000..5873083e79 --- /dev/null +++ b/pygame-a-primer/requirements.txt @@ -0,0 +1 @@ +pygame==2.6.1