alien_invasion.py
import pygame
from pygame.sprite import Group
from settings import Config
from game_stats import GameState
from scoreboard import HUD
from button import Button
from ship import Ship
import game_functions as gf
def main():
pygame.init()
cfg = Config()
screen = pygame.display.set_mode((cfg.screen_w, cfg.screen_h))
pygame.display.set_caption("Alien Invasion")
start_button = Button(cfg, screen, "Play")
state = GameState(cfg)
hud = HUD(cfg, screen, state)
ship = Ship(cfg, screen)
shots = Group()
swarm = Group()
gf.build_fleet(cfg, screen, ship, swarm)
while True:
gf.process_input(cfg, screen, state, hud, start_button, ship, swarm, shots)
if state.active:
ship.update()
gf.step_shots(cfg, screen, state, hud, ship, swarm, shots)
gf.step_swarm(cfg, screen, state, hud, ship, swarm, shots)
gf.draw_frame(cfg, screen, state, hud, ship, swarm, shots, start_button)
if __name__ == "__main__":
main()
game_functions.py
import sys
from time import sleep
import pygame
from bullet import Laser
from alien import Invader
def handle_keydown(evt, cfg, screen, ship, shots):
if evt.key == pygame.K_RIGHT:
ship.heading_right = True
elif evt.key == pygame.K_LEFT:
ship.heading_left = True
elif evt.key == pygame.K_SPACE:
spawn_laser(cfg, screen, ship, shots)
elif evt.key == pygame.K_q:
sys.exit()
def handle_keyup(evt, ship):
if evt.key == pygame.K_RIGHT:
ship.heading_right = False
elif evt.key == pygame.K_LEFT:
ship.heading_left = False
def process_input(cfg, screen, state, hud, start_button, ship, swarm, shots):
for evt in pygame.event.get():
if evt.type == pygame.QUIT:
sys.exit()
elif evt.type == pygame.KEYDOWN:
handle_keydown(evt, cfg, screen, ship, shots)
elif evt.type == pygame.KEYUP:
handle_keyup(evt, ship)
elif evt.type == pygame.MOUSEBUTTONDOWN:
mx, my = pygame.mouse.get_pos()
start_game_if_clicked(cfg, screen, state, hud, start_button, ship, swarm, shots, mx, my)
def start_game_if_clicked(cfg, screen, state, hud, start_button, ship, swarm, shots, mx, my):
clicked = start_button.rect.collidepoint(mx, my)
if clicked and not state.active:
cfg.init_dynamic()
pygame.mouse.set_visible(False)
state.reset()
state.active = True
hud.update_score_surface()
hud.update_high_score_surface()
hud.update_level_surface()
hud.update_lives_group()
swarm.empty()
shots.empty()
build_fleet(cfg, screen, ship, swarm)
ship.recenter()
def draw_frame(cfg, screen, state, hud, ship, swarm, shots, start_button):
screen.fill(cfg.background_color)
for s in shots.sprites():
s.draw()
ship.blitme()
swarm.draw(screen)
hud.draw()
if not state.active:
start_button.draw_button()
pygame.display.flip()
def step_shots(cfg, screen, state, hud, ship, swarm, shots):
shots.update()
for s in shots.copy():
if s.rect.bottom <= 0:
shots.remove(s)
resolve_impacts(cfg, screen, state, hud, ship, swarm, shots)
def resolve_impacts(cfg, screen, state, hud, ship, swarm, shots):
impacts = pygame.sprite.groupcollide(shots, swarm, True, True)
if impacts:
destroyed = sum(len(v) for v in impacts.values())
state.score += cfg.invader_points * destroyed
hud.update_score_surface()
update_high_score(state, hud)
if len(swarm) == 0:
shots.empty()
cfg.accelerate()
state.level += 1
hud.update_level_surface()
build_fleet(cfg, screen, ship, swarm)
def spawn_laser(cfg, screen, ship, shots):
if len(shots) < cfg.shot_limit:
shots.add(Laser(cfg, screen, ship))
def columns_for_row(cfg, invader_w):
free_x = cfg.screen_w - 2 * invader_w
return int(free_x // (2 * invader_w))
def rows_for_screen(cfg, ship_h, invader_h):
free_y = cfg.screen_h - (3 * invader_h) - ship_h
return int(free_y // (2 * invader_h))
def add_invader(cfg, screen, swarm, col_idx, row_idx):
inv = Invader(cfg, screen)
w = inv.rect.width
inv.x = w + 2 * w * col_idx
inv.rect.x = inv.x
inv.rect.y = inv.rect.height + 2 * inv.rect.height * row_idx
swarm.add(inv)
def build_fleet(cfg, screen, ship, swarm):
sample = Invader(cfg, screen)
cols = columns_for_row(cfg, sample.rect.width)
rows = rows_for_screen(cfg, ship.rect.height, sample.rect.height)
for r in range(rows):
for c in range(cols):
add_invader(cfg, screen, swarm, c, r)
def on_edge(cfg, swarm):
for inv in swarm.sprites():
if inv.check_edges():
shift_and_flip(cfg, swarm)
break
def shift_and_flip(cfg, swarm):
for inv in swarm.sprites():
inv.rect.y += cfg.fleet_drop
cfg.fleet_dir *= -1
def ship_hit(cfg, screen, state, hud, ship, swarm, shots):
if state.lives > 0:
state.lives -= 1
hud.update_lives_group()
swarm.empty()
shots.empty()
build_fleet(cfg, screen, ship, swarm)
ship.recenter()
sleep(0.5)
else:
state.active = False
pygame.mouse.set_visible(True)
def step_swarm(cfg, screen, state, hud, ship, swarm, shots):
on_edge(cfg, swarm)
swarm.update()
if pygame.sprite.spritecollideany(ship, swarm):
ship_hit(cfg, screen, state, hud, ship, swarm, shots)
check_bottom(cfg, screen, state, hud, ship, swarm, shots)
def check_bottom(cfg, screen, state, hud, ship, swarm, shots):
screen_rect = screen.get_rect()
for inv in swarm.sprites():
if inv.rect.bottom >= screen_rect.bottom:
ship_hit(cfg, screen, state, hud, ship, swarm, shots)
break
def update_high_score(state, hud):
if state.score > state.high_score:
state.high_score = state.score
hud.update_high_score_surface()
alien.py
import pygame
from pygame.sprite import Sprite
class Invader(Sprite):
def __init__(self, cfg, screen):
super().__init__()
self.screen = screen
self.cfg = cfg
self.image = pygame.image.load("images/alien.bmp")
self.rect = self.image.get_rect()
self.rect.x = self.rect.width
self.rect.y = self.rect.height
self.x = float(self.rect.x)
def blitme(self):
self.screen.blit(self.image, self.rect)
def check_edges(self):
bounds = self.screen.get_rect()
return self.rect.right >= bounds.right or self.rect.left <= 0
def update(self):
self.x += self.cfg.invader_speed * self.cfg.fleet_dir
self.rect.x = self.x
bulllet.py
import pygame
from pygame.sprite import Sprite
class Laser(Sprite):
def __init__(self, cfg, screen, ship):
super().__init__()
self.screen = screen
self.rect = pygame.Rect(0, 0, cfg.shot_w, cfg.shot_h)
self.rect.centerx = ship.rect.centerx
self.rect.top = ship.rect.top
self.y = float(self.rect.y)
self.color = cfg.shot_color
self.speed = cfg.shot_speed
def update(self):
self.y -= self.speed
self.rect.y = self.y
def draw(self):
pygame.draw.rect(self.screen, self.color, self.rect)
buttton.py
import pygame
import pygame.font
class Button:
def __init__(self, cfg, screen, msg):
self.screen = screen
self.screen_rect = screen.get_rect()
self.width, self.height = 200, 50
self.bg_color = (1, 220, 212)
self.text_color = (255, 255, 255)
self.font = pygame.font.SysFont(None, 48)
self.rect = pygame.Rect(0, 0, self.width, self.height)
self.rect.center = self.screen_rect.center
self._render_text(msg)
def _render_text(self, msg):
self.msg_image = self.font.render(msg, True, self.text_color, self.bg_color)
self.msg_rect = self.msg_image.get_rect()
self.msg_rect.center = self.rect.center
def draw_button(self):
self.screen.fill(self.bg_color, self.rect)
self.screen.blit(self.msg_image, self.msg_rect)
game_stats.py
class GameState:
def __init__(self, cfg):
self.cfg = cfg
self.active = False
self.high_score = 0
self.reset()
def reset(self):
self.lives = self.cfg.ship_lives
self.score = 0
self.level = 1
scoreboard.py
import pygame.font
from pygame.sprite import Group
from ship import Ship
class HUD:
def __init__(self, cfg, screen, state):
self.cfg = cfg
self.screen = screen
self.screen_rect = screen.get_rect()
self.state = state
self.text_color = (30, 30, 30)
self.font = pygame.font.SysFont(None, 48)
self.update_score_surface()
self.update_high_score_surface()
self.update_level_surface()
self.update_lives_group()
def update_score_surface(self):
s = f"{self.state.score:,}"
self.score_img = self.font.render(s, True, self.text_color, self.cfg.background_color)
self.score_rect = self.score_img.get_rect()
self.score_rect.right = self.screen_rect.right - 20
self.score_rect.top = 20
def update_high_score_surface(self):
s = f"{self.state.high_score:,}"
self.high_img = self.font.render(s, True, self.text_color, self.cfg.background_color)
self.high_rect = self.high_img.get_rect()
self.high_rect.centerx = self.screen_rect.centerx
self.high_rect.top = self.score_rect.top
def update_level_surface(self):
s = str(self.state.level)
self.level_img = self.font.render(s, True, self.text_color, self.cfg.background_color)
self.level_rect = self.level_img.get_rect()
self.level_rect.right = self.score_rect.right
self.level_rect.top = self.score_rect.bottom + 10
def update_lives_group(self):
self.lives_group = Group()
for i in range(self.state.lives):
ship = Ship(self.cfg, self.screen)
ship.rect.x = 10 + i * ship.rect.width
ship.rect.y = 10
self.lives_group.add(ship)
def draw(self):
self.screen.blit(self.score_img, self.score_rect)
self.screen.blit(self.high_img, self.high_rect)
self.screen.blit(self.level_img, self.level_rect)
self.lives_group.draw(self.screen)
settings.py
class Config:
def __init__(self):
# Display
self.screen_w = 1200
self.screen_h = 800
self.background_color = (164, 177, 161)
# Ship
self.ship_lives = 3
# Shots
self.shot_w = 3
self.shot_h = 15
self.shot_color = (60, 60, 60)
self.shot_limit = 3
# Fleet
self.fleet_drop = 10
# Scaling
self.speed_scale = 1.1
self.points_scale = 1.5
self.init_dynamic()
def init_dynamic(self):
self.ship_speed = 1.5
self.shot_speed = 7.0
self.invader_speed = 0.6
self.fleet_dir = 1 # 1: right, -1: left
self.invader_points = 50
def accelerate(self):
self.ship_speed *= self.speed_scale
self.shot_speed *= self.speed_scale
self.invader_speed *= self.speed_scale
self.invader_points = int(self.invader_points * self.points_scale)
ship.py
import pygame
from pygame.sprite import Sprite
class Ship(Sprite):
def __init__(self, cfg, screen):
super().__init__()
self.screen = screen
self.cfg = cfg
self.image = pygame.image.load("images/ship.bmp")
self.rect = self.image.get_rect()
self.screen_rect = screen.get_rect()
self.rect.centerx = self.screen_rect.centerx
self.rect.bottom = self.screen_rect.bottom
self.center_x = float(self.rect.centerx)
self.heading_right = False
self.heading_left = False
def update(self):
if self.heading_right and self.rect.right < self.screen_rect.right:
self.center_x += self.cfg.ship_speed
if self.heading_left and self.rect.left > 0:
self.center_x -= self.cfg.ship_speed
self.rect.centerx = self.center_x
def blitme(self):
self.screen.blit(self.image, self.rect)
def recenter(self):
self.center_x = self.screen_rect.centerx