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