bullet_game
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
bullet_game [2024/08/31 01:47] – appledog | bullet_game [2024/09/11 06:43] (current) – created appledog | ||
---|---|---|---|
Line 6: | Line 6: | ||
* Refactoring | * Refactoring | ||
** Encapsulation | ** Encapsulation | ||
+ | ** Light Commentary (code commentary) ex. IN and OUT for functions. | ||
The primary goal is encapsulation. So, we're going to break up the old main into several new classes. | The primary goal is encapsulation. So, we're going to break up the old main into several new classes. | ||
- | == Screen and Window | + | == PyGame Framework |
- | The first thing we always do is have a Screen class and a Window class. The Screen class defines things like WIDTH and HEIGHT, and the Window class holds the pygame screen and other pygame initialization. | + | The first part of this is the use of [[pygame framework]]. We'll describe |
- | Initially, this code would appear at the start of a main.py. Later we moved it into class Game's __init__. However, to keep things clean we made it into it's own class. | + | === Also See |
+ | * [[Box Sprites]] -- the kind of sprites first used in this game. | ||
- | === class Screen: | + | == Bullet Game |
+ | Here is the code we added, starting with main.py: | ||
+ | |||
+ | === main.py | ||
< | < | ||
+ | from Window import * | ||
+ | from Game import * | ||
+ | |||
+ | def main(): | ||
+ | window = Window() | ||
+ | window.setCaption(" | ||
+ | window.setSize(Screen.WIDTH, | ||
+ | window.setFont(" | ||
+ | |||
+ | game = Game(window) | ||
+ | game.start() | ||
+ | |||
+ | if __name__ == " | ||
+ | main() | ||
</ | </ | ||
- | === class Window: | + | === Screen,py |
- | ==== def __init__ | + | <Code:Python> |
- | This calls pygame.init(). See [[main]] | + | |
+ | class Screen: | ||
+ | WIDTH = 800 | ||
+ | HEIGHT = 600 | ||
+ | FPS = 60 | ||
- | ==== full code | + | MARGIN |
+ | GAMETOP | ||
+ | GAMEBOT | ||
+ | </ | ||
+ | |||
+ | |||
+ | === Game.py | ||
< | < | ||
import pygame | import pygame | ||
+ | import random | ||
+ | import pickle | ||
- | class Window: | + | from Screen import * |
- | def __init__(self): | + | from Window |
- | | + | from Bullet import * |
+ | from Color import * | ||
+ | from Screen import * | ||
+ | from Zombie import * | ||
+ | from SpaceShip import * | ||
+ | from Sound import * | ||
+ | from Player import * | ||
+ | from Constants import * | ||
+ | from Level import * | ||
+ | from Star import * | ||
- | def setLogo(self, | ||
- | self.logo = pygame.image.load(filename) | ||
- | pygame.display.set_icon(self.logo) | ||
- | return self.logo | ||
- | | + | class Game: |
- | | + | |
+ | | ||
+ | self.sound = Sound() | ||
- | | + | # Initialize Game World Data |
+ | # The Player & Level | ||
+ | self.player = Player() | ||
+ | self.player.hiscore = self.load_hiscore(" | ||
+ | |||
+ | self.level = Level(1, self.player) | ||
+ | |||
+ | # Bullet list | ||
+ | self.bullets = pygame.sprite.Group() | ||
+ | |||
+ | # Zombies list | ||
+ | self.zombies = pygame.sprite.Group() | ||
+ | self.spaceships = pygame.sprite.Group() | ||
+ | |||
+ | # 30 random stars. | ||
+ | self.stars = [] | ||
+ | for n in range(30): | ||
+ | x = random.randint(0, | ||
+ | y = random.randint(0, | ||
+ | self.stars.append((x, | ||
+ | |||
+ | # All member variables are now globals: | ||
+ | # Try commenting it out to see what happens! | ||
+ | globals().update(self.__dict__) | ||
+ | |||
+ | ################################# | ||
+ | ## Main Loop ## | ||
+ | ################################# | ||
+ | |||
+ | | ||
+ | |||
+ | # Clock for controlling the frame rate | ||
+ | clock = pygame.time.Clock() | ||
+ | |||
+ | # Main game loop | ||
+ | running = True | ||
+ | frame_counter = 0 | ||
+ | |||
+ | ################## | ||
+ | # GAME LOOP ORDER OF EVENTS: | ||
+ | ################## | ||
+ | sound.play(" | ||
+ | # pygame.mixer.init() | ||
+ | # pygame.mixer.music.load(' | ||
+ | # pygame.mixer.music.play() | ||
+ | |||
+ | # Start of Game Loop | ||
+ | time_start = time.time() * 1000 | ||
+ | LEVEL_DELAY = 3000 | ||
+ | while running: | ||
+ | time_clock = (time.time() * 1000) - time_start | ||
+ | frame_counter += 1 | ||
+ | if frame_counter > 60: | ||
+ | frame_counter = 1 | ||
+ | |||
+ | ######################## | ||
+ | # 1. EVENT HANDLING | ||
+ | ######################## | ||
+ | for event in pygame.event.get(): | ||
+ | if event.type == pygame.QUIT: | ||
+ | running = False | ||
+ | quit_game() | ||
+ | if event.type == pygame.KEYDOWN: | ||
+ | if event.key == pygame.K_z: | ||
+ | self.create_spaceship() | ||
+ | if event.key == pygame.K_ESCAPE: | ||
+ | self.quit_game() | ||
+ | if event.key == pygame.K_q: | ||
+ | self.quit_game() | ||
+ | |||
+ | if event.key == pygame.K_w: | ||
+ | x = self.player.rect.x + self.player.SIZE / 2 | ||
+ | y = self.player.rect.y + self.player.SIZE / 2 | ||
+ | self.create_bullet(x, | ||
+ | |||
+ | # Continuous (frame-by-frame) Key press handling | ||
+ | keys = pygame.key.get_pressed() | ||
+ | if keys[pygame.K_LEFT]: | ||
+ | self.player.rect.x -= self.player.SPEED | ||
+ | if keys[pygame.K_RIGHT]: | ||
+ | self.player.rect.x += self.player.SPEED | ||
+ | |||
+ | ####################### | ||
+ | # 2. RULES SECTION | ||
+ | ####################### | ||
+ | ### Level-specifc stuff: | ||
+ | if time_clock > LEVEL_DELAY: | ||
+ | commands = self.level.tick_hook() | ||
+ | |||
+ | for cmd in commands: | ||
+ | if cmd == " | ||
+ | self.create_zombie() | ||
+ | |||
+ | if cmd == " | ||
+ | self.create_spaceship() | ||
+ | |||
+ | if cmd == "go to level2": | ||
+ | self.kill_enemies() | ||
+ | time_start = time.time() * 1000 | ||
+ | self.level = Level(2, player) | ||
+ | |||
+ | if cmd == "go to level3": | ||
+ | self.kill_enemies() | ||
+ | time_start = time.time() * 1000 | ||
+ | self.level = Level(3, self.player) | ||
+ | if cmd == "go to level4": | ||
+ | self.kill_enemies() | ||
+ | time_start = time.time() * 1000 | ||
+ | self.level = Level(4, self.player) | ||
+ | |||
+ | # 2b. rules for bullets | ||
+ | # 2b.1. Update the bullets position. | ||
+ | self.bullets.update() | ||
+ | |||
+ | # Update all stars. | ||
+ | for i in range(len(self.stars)): | ||
+ | (x, y) = self.stars[i] | ||
+ | y = y + 1 | ||
+ | |||
+ | if y > Screen.HEIGHT: | ||
+ | x = random.randint(0, | ||
+ | y = 0 | ||
+ | |||
+ | self.stars[i] = (x, y) | ||
+ | |||
+ | if time_clock > LEVEL_DELAY: | ||
+ | # 2a rules for the square | ||
+ | # Keep the square within bounds | ||
+ | self.player.update() | ||
+ | |||
+ | # 2c. rules for zombies | ||
+ | # 2c.1. Update the zombies position. | ||
+ | self.zombies.update() | ||
+ | self.spaceships.update() | ||
+ | |||
+ | # Clear the screen | ||
+ | self.window.screen.fill(Color.BLACK) | ||
+ | |||
+ | # Draw stars first (because its a background) | ||
+ | for s in self.stars: | ||
+ | pygame.draw.rect(self.window.screen, | ||
+ | |||
+ | # Draw the square | ||
+ | # pygame.draw.rect(screen, | ||
+ | self.window.screen.blit(self.player.image, | ||
+ | |||
+ | if time_clock > LEVEL_DELAY: | ||
+ | # Draw the zombies. | ||
+ | self.zombies.draw(self.window.screen) | ||
+ | self.spaceships.draw(self.window.screen) | ||
+ | |||
+ | # ships make a sound, if... | ||
+ | if self.spaceships: | ||
+ | if frame_counter == 30: | ||
+ | self.sound.play(" | ||
+ | |||
+ | # Draw the bullets. | ||
+ | self.bullets.draw(self.window.screen) | ||
+ | |||
+ | # write score. | ||
+ | if frame_counter < 30: | ||
+ | s = " | ||
+ | text_surface = self.window.font.render(s, | ||
+ | self.window.screen.blit(text_surface, | ||
+ | |||
+ | s = f" | ||
+ | text_surface = self.window.font.render(s, | ||
+ | self.window.screen.blit(text_surface, | ||
+ | |||
+ | # do high score. | ||
+ | s = "HIGH SCORE" | ||
+ | text_surface = self.window.font.render(s, | ||
+ | self.window.screen.blit(text_surface, | ||
+ | |||
+ | s = f" | ||
+ | text_surface = self.window.font.render(s, | ||
+ | self.window.screen.blit(text_surface, | ||
+ | |||
+ | s = f" | ||
+ | text_surface = self.window.font.render(s, | ||
+ | self.window.screen.blit(text_surface, | ||
+ | |||
+ | if time_clock < LEVEL_DELAY: | ||
+ | s = f" | ||
+ | text_surface = self.window.font.render(s, | ||
+ | tr = text_surface.get_rect() | ||
+ | tr.center = (Screen.WIDTH // 2, Screen.HEIGHT // 2 - 100) | ||
+ | self.window.screen.blit(text_surface, | ||
+ | |||
+ | # Update the display | ||
+ | pygame.display.flip() | ||
+ | |||
+ | if time_clock > LEVEL_DELAY: | ||
+ | # 3. INTERSPRITE COLLISION | ||
+ | # 3a. bullets kill zombies | ||
+ | collisions = pygame.sprite.groupcollide(self.bullets, | ||
+ | collisions2 = pygame.sprite.groupcollide(self.bullets, | ||
+ | |||
+ | if collisions: | ||
+ | self.player.score = self.player.score + len(collisions) | ||
+ | if self.player.score > self.player.hiscore: | ||
+ | self.player.hiscore = self.player.score | ||
+ | |||
+ | if collisions or collisions2: | ||
+ | n = random.randint(1, | ||
+ | if n == 1: | ||
+ | self.sound.play(" | ||
+ | else: | ||
+ | self.sound.play(" | ||
+ | |||
+ | if collisions2: | ||
+ | self.player.score = self.player.score + (len(collisions2) * 10) | ||
+ | if self.player.score > self.player.hiscore: | ||
+ | self.player.hiscore = self.player.score | ||
+ | |||
+ | # Players may not touch zombies: | ||
+ | collided_enemies = pygame.sprite.spritecollide(self.player, | ||
+ | |||
+ | if collided_enemies: | ||
+ | print(" | ||
+ | print(" | ||
+ | print(" | ||
+ | self.quit_game() | ||
+ | |||
+ | # Control the frame rate | ||
+ | clock.tick(60) | ||
+ | |||
+ | ################################# | ||
+ | ## Support Functions | ||
+ | ################################# | ||
+ | |||
+ | def create_bullet(self, | ||
+ | self.bullet = Bullet(x, y, dx, dy) | ||
+ | self.bullets.add(self.bullet) | ||
+ | self.sound.play(" | ||
+ | if Constants.VERBOSE: | ||
+ | print(" | ||
+ | |||
+ | def create_zombie(self): | ||
+ | sx = random.randint(50, | ||
+ | sy = 0 + Screen.MARGIN | ||
+ | dx = 0 | ||
+ | dy = 1 | ||
+ | z = Zombie(sx, sy, dx, dy) | ||
+ | self.zombies.add(z) | ||
+ | if Constants.VERBOSE: | ||
+ | print(" | ||
+ | |||
+ | def create_spaceship(self): | ||
+ | sx = Screen.WIDTH | ||
+ | sy = 0 + Screen.MARGIN | ||
+ | dx = -1 | ||
+ | dy = 0 | ||
+ | |||
+ | if random.randint(1, | ||
+ | dx = 1 | ||
+ | sx = 0 | ||
+ | |||
+ | s = SpaceShip(sx, | ||
+ | self.spaceships.add(s) | ||
+ | if Constants.VERBOSE: | ||
+ | print(" | ||
+ | |||
+ | def kill_enemies(self): | ||
+ | for z in self.zombies: | ||
+ | z.kill() | ||
+ | for s in self.spaceships: | ||
+ | s.kill() | ||
+ | |||
+ | # Save the score to a text file. | ||
+ | def save_hiscore(self, | ||
+ | with open(filename, | ||
+ | file.write(str(score)) | ||
+ | |||
+ | # Load the score from a text file. | ||
+ | def load_hiscore(self, | ||
+ | try: | ||
+ | with open(filename, | ||
+ | score = int(file.read()) | ||
+ | return score | ||
+ | except: | ||
+ | print(f" | ||
+ | return 0 | ||
+ | |||
+ | def quit_game(): | ||
+ | self.save_hiscore(" | ||
+ | pygame.quit() | ||
+ | quit() | ||
+ | </ | ||
+ | |||
+ | === Level.py | ||
+ | < | ||
+ | import random | ||
+ | |||
+ | class Level: | ||
+ | |||
+ | def __init__(self, | ||
+ | self.level_number = n | ||
+ | self.player = p | ||
+ | |||
+ | match self.level_number: | ||
+ | case 1: | ||
+ | self.tick_hook = self.level1 | ||
+ | |||
+ | case 2: | ||
+ | self.tick_hook = self.level2 | ||
+ | |||
+ | case 3: | ||
+ | self.tick_hook = self.level3 | ||
+ | |||
+ | case 4: | ||
+ | self.tick_hook = self.level4 | ||
+ | |||
+ | case default: | ||
+ | print(" | ||
+ | print(" | ||
+ | self.tick_hook = self.level1 | ||
+ | |||
+ | return | ||
+ | |||
+ | |||
+ | ##### Tick Hooks | ||
+ | def level1(self): | ||
+ | commands = [] | ||
+ | |||
+ | if random.randint(1, | ||
+ | commands.append(" | ||
+ | |||
+ | if self.player.score >= 20: | ||
+ | commands.append(" | ||
+ | |||
+ | return commands | ||
+ | |||
+ | |||
+ | def level2(self): | ||
+ | commands = [] | ||
+ | |||
+ | if random.randint(1, | ||
+ | commands.append(" | ||
+ | |||
+ | if self.player.score >= 40: | ||
+ | commands.append(" | ||
+ | |||
+ | return commands | ||
+ | |||
+ | def level3(self): | ||
+ | commands = [] | ||
+ | |||
+ | if random.randint(1, | ||
+ | commands.append(" | ||
+ | |||
+ | if random.randint(1, | ||
+ | commands.append(" | ||
+ | |||
+ | if self.player.score >= 80: | ||
+ | commands.append(" | ||
+ | |||
+ | return commands | ||
+ | |||
+ | def level4(self): | ||
+ | commands = [] | ||
+ | |||
+ | if random.randint(1, | ||
+ | commands.append(" | ||
+ | |||
+ | if random.randint(1, | ||
+ | commands.append(" | ||
+ | |||
+ | #if self.player.score > 320: | ||
+ | # commands.append(" | ||
+ | |||
+ | return commands | ||
+ | </ | ||
+ | |||
+ | === Object.py | ||
+ | < | ||
+ | import pygame | ||
+ | |||
+ | class Object(pygame.sprite.Sprite): | ||
+ | def __init__(self, | ||
+ | super().__init__() | ||
+ | self.rect = pygame.Rect(x, | ||
+ | self.image = pygame.Surface((width, | ||
self.width = width | self.width = width | ||
self.height = height | self.height = height | ||
- | self.size = (width, height) | + | self.name = name |
- | self.screen = pygame.display.set_mode(self.size) | + | |
- | return self.screen | + | |
- | def setFont(self, | + | def draw(self, |
- | | + | |
- | | + | |
- | font_width, font_height = self.font.size(" | + | </ |
- | self.fontwidth = font_width | + | |
- | self.fontheight = size | + | |
- | return | + | === Player.py |
+ | < | ||
+ | import pygame | ||
+ | import time | ||
+ | from Color import * | ||
+ | from Screen import * | ||
+ | |||
+ | # Define the Bullet Sprite class | ||
+ | class Player(pygame.sprite.Sprite): | ||
+ | SPEED = 5 | ||
+ | SIZE = 20 | ||
+ | COLOR = Color.WHITE | ||
+ | |||
+ | def __init__(self): | ||
+ | super().__init__() | ||
+ | self.ticks = 0 | ||
+ | self.loops = 0 | ||
+ | |||
+ | # Create a surface for the bullet | ||
+ | self.image = pygame.Surface((self.SIZE, | ||
+ | |||
+ | # Fill the surface with a color | ||
+ | self.image.fill(self.COLOR) | ||
+ | |||
+ | # Get the rectangle for positioning | ||
+ | self.rect = self.image.get_rect() | ||
+ | |||
+ | # Set the initial position | ||
+ | self.rect.center = (Screen.WIDTH// | ||
+ | |||
+ | self.score = 0 | ||
+ | self.hiscore = 0 | ||
+ | |||
+ | def update(self): | ||
+ | # Restrict to screen. | ||
+ | if self.rect.x < 0: | ||
+ | self.rect.x = 0 | ||
+ | if self.rect.x > Screen.WIDTH - self.SIZE: | ||
+ | self.rect.x = Screen.WIDTH - self.SIZE | ||
+ | if self.rect.y < 0: | ||
+ | self.rect.y = 0 | ||
+ | if self.rect.y > Screen.HEIGHT - self.SIZE: | ||
+ | self.rect.y = Screen.HEIGHT - self.SIZE | ||
+ | |||
+ | # Easier to read, but, slower. | ||
+ | # | ||
+ | # | ||
+ | </ | ||
+ | |||
+ | === Sound.py | ||
+ | < | ||
+ | import pygame as a | ||
+ | |||
+ | class Sound: | ||
+ | def __init__(self): | ||
+ | a.mixer.init() | ||
+ | dir = ' | ||
+ | self.sounds = {} | ||
+ | |||
+ | self.sounds[" | ||
+ | self.sounds[" | ||
+ | self.sounds[" | ||
+ | self.sounds[" | ||
+ | self.sounds[" | ||
+ | self.sounds[" | ||
+ | |||
+ | def play(self, sound): | ||
+ | if sound in self.sounds: | ||
+ | self.sounds[sound].play() | ||
+ | else: | ||
+ | print(f" | ||
+ | </ |
bullet_game.1725068826.txt.gz · Last modified: 2024/08/31 01:47 by appledog