import pygame
import numpy as np
# Define alien as a matrix (1 = filled, 0 = empty)
ALIEN_SPRITE = [
[0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 1, 0, 0, 1, 0, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
[0, 1, 1, 0, 1, 1, 0, 1, 1, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 1, 1, 1, 1, 1, 1, 0, 1],
[1, 0, 1, 0, 0, 0, 0, 1, 0, 1],
[0, 0, 0, 1, 1, 1, 1, 0, 0, 0],
]
PLAYER_SPRITE = [
[0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 1, 1, 1, 1, 0, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
]
def draw_sprite(surface, sprite, x, y, color, pixel_size=3):
"""Draw a sprite matrix on the screen"""
for row_idx, row in enumerate(sprite):
for col_idx, pixel in enumerate(row):
if pixel:
rect = pygame.Rect(
x + col_idx * pixel_size,
y + row_idx * pixel_size,
pixel_size,
pixel_size
)
pygame.draw.rect(surface, color, rect)
print("โ
Sprite system defined!")
print(f" Alien: {len(ALIEN_SPRITE)}x{len(ALIEN_SPRITE[0])} matrix")
print(f" Player: {len(PLAYER_SPRITE)}x{len(PLAYER_SPRITE[0])} matrix")Chapter 2.5: Building Space Invaders
Classic Arcade Action with Sprites and Matrices
๐ฎ PROJECT 2.5 | Difficulty: Intermediate-Advanced | Time: 30 minutes
๐ป Interactive Options:
- ๐ Open in JupyterLite
- ๐ฅ Download Notebook (Challenge) - For use in local Jupyter or Google Colab
- ๐ก Note: Pygame runs best locally with a display, but weโll show examples!
๐พ Game Design: Space Invaders
Goal: Defend Earth by shooting alien invaders before they reach the bottom
Game Mechanics:
- Player ship moves left/right at bottom
- Shoot bullets upward
- Aliens move side-to-side and down
- Aliens shoot back!
- Game over if aliens reach bottom or player is hit
๐จ Sprites Using Matrices
Weโll represent sprites as 2D matrices (like pixel art):
๐ Complete Space Invaders Game
# space_invaders.py
import pygame
import random
import sys
pygame.init()
# Constants
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
FPS = 60
# Colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
YELLOW = (255, 255, 0)
# Sprites
ALIEN_SPRITE = [
[0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 1, 0, 0, 1, 0, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
[0, 1, 1, 0, 1, 1, 0, 1, 1, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 1, 1, 1, 1, 1, 1, 0, 1],
[1, 0, 1, 0, 0, 0, 0, 1, 0, 1],
[0, 0, 0, 1, 1, 1, 1, 0, 0, 0],
]
PLAYER_SPRITE = [
[0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 1, 1, 1, 1, 0, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
]
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Space Invaders")
clock = pygame.time.Clock()
def draw_sprite(surface, sprite, x, y, color, pixel_size=3):
for row_idx, row in enumerate(sprite):
for col_idx, pixel in enumerate(row):
if pixel:
rect = pygame.Rect(
x + col_idx * pixel_size,
y + row_idx * pixel_size,
pixel_size,
pixel_size
)
pygame.draw.rect(surface, color, rect)
class Player:
def __init__(self):
self.x = SCREEN_WIDTH // 2
self.y = SCREEN_HEIGHT - 80
self.speed = 5
self.width = 30
self.height = 18
def move(self, dx):
self.x += dx * self.speed
self.x = max(0, min(self.x, SCREEN_WIDTH - self.width))
def draw(self, surface):
draw_sprite(surface, PLAYER_SPRITE, self.x, self.y, GREEN)
def shoot(self):
return Bullet(self.x + self.width // 2, self.y, -1)
class Alien:
def __init__(self, x, y):
self.x = x
self.y = y
self.width = 30
self.height = 24
def draw(self, surface):
draw_sprite(surface, ALIEN_SPRITE, self.x, self.y, RED)
def move(self, dx, dy):
self.x += dx
self.y += dy
class Bullet:
def __init__(self, x, y, direction):
self.x = x
self.y = y
self.direction = direction # -1 for up, 1 for down
self.speed = 7
self.width = 3
self.height = 10
self.active = True
def update(self):
self.y += self.direction * self.speed
if self.y < 0 or self.y > SCREEN_HEIGHT:
self.active = False
def draw(self, surface):
color = YELLOW if self.direction == -1 else RED
pygame.draw.rect(surface, color, (self.x, self.y, self.width, self.height))
def get_rect(self):
return pygame.Rect(self.x, self.y, self.width, self.height)
class Game:
def __init__(self):
self.player = Player()
self.aliens = []
self.player_bullets = []
self.alien_bullets = []
self.score = 0
self.game_over = False
self.alien_direction = 1
self.alien_speed = 1
self.shoot_cooldown = 0
self.create_aliens()
def create_aliens(self):
rows = 4
cols = 10
spacing_x = 60
spacing_y = 50
start_x = 100
start_y = 50
for row in range(rows):
for col in range(cols):
x = start_x + col * spacing_x
y = start_y + row * spacing_y
self.aliens.append(Alien(x, y))
def handle_input(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
return False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE and self.shoot_cooldown == 0:
self.player_bullets.append(self.player.shoot())
self.shoot_cooldown = 20
elif event.key == pygame.K_r and self.game_over:
self.__init__()
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.player.move(-1)
if keys[pygame.K_RIGHT]:
self.player.move(1)
return True
def update(self):
if self.game_over:
return
if self.shoot_cooldown > 0:
self.shoot_cooldown -= 1
# Move aliens
move_down = False
for alien in self.aliens:
alien.move(self.alien_direction * self.alien_speed, 0)
if alien.x <= 0 or alien.x >= SCREEN_WIDTH - alien.width:
move_down = True
if move_down:
self.alien_direction *= -1
for alien in self.aliens:
alien.y += 20
if alien.y >= SCREEN_HEIGHT - 100:
self.game_over = True
# Update bullets
for bullet in self.player_bullets:
bullet.update()
self.player_bullets = [b for b in self.player_bullets if b.active]
for bullet in self.alien_bullets:
bullet.update()
self.alien_bullets = [b for b in self.alien_bullets if b.active]
# Alien shooting
if random.random() < 0.01 and self.aliens:
shooter = random.choice(self.aliens)
self.alien_bullets.append(
Bullet(shooter.x + shooter.width // 2, shooter.y + shooter.height, 1)
)
# Check collisions
for bullet in self.player_bullets[:]:
bullet_rect = bullet.get_rect()
for alien in self.aliens[:]:
alien_rect = pygame.Rect(alien.x, alien.y, alien.width, alien.height)
if bullet_rect.colliderect(alien_rect):
self.aliens.remove(alien)
bullet.active = False
self.score += 10
break
# Check player hit
player_rect = pygame.Rect(self.player.x, self.player.y,
self.player.width, self.player.height)
for bullet in self.alien_bullets:
if bullet.get_rect().colliderect(player_rect):
self.game_over = True
# Win condition
if not self.aliens:
self.game_over = True
def draw(self):
screen.fill(BLACK)
self.player.draw(screen)
for alien in self.aliens:
alien.draw(screen)
for bullet in self.player_bullets:
bullet.draw(screen)
for bullet in self.alien_bullets:
bullet.draw(screen)
# UI
font = pygame.font.Font(None, 36)
score_text = font.render(f'Score: {self.score}', True, WHITE)
screen.blit(score_text, (10, 10))
if self.game_over:
overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT))
overlay.set_alpha(200)
overlay.fill(BLACK)
screen.blit(overlay, (0, 0))
game_over_font = pygame.font.Font(None, 72)
if not self.aliens:
text = game_over_font.render('YOU WIN!', True, GREEN)
else:
text = game_over_font.render('GAME OVER', True, RED)
screen.blit(text, (SCREEN_WIDTH // 2 - text.get_width() // 2, 250))
restart_font = pygame.font.Font(None, 36)
restart_text = restart_font.render('Press R to Restart', True, WHITE)
screen.blit(restart_text, (SCREEN_WIDTH // 2 - restart_text.get_width() // 2, 350))
pygame.display.flip()
# Main loop
game = Game()
running = True
while running:
running = game.handle_input()
game.update()
game.draw()
clock.tick(FPS)
pygame.quit()
sys.exit()๐ฏ Key Concepts: Collision Detection
# Rectangle collision detection
def check_collision(rect1, rect2):
"""
Check if two rectangles overlap
rect format: (x, y, width, height)
"""
x1, y1, w1, h1 = rect1
x2, y2, w2, h2 = rect2
# Check if rectangles DON'T overlap, then negate
return not (x1 + w1 < x2 or # rect1 left of rect2
x2 + w2 < x1 or # rect2 left of rect1
y1 + h1 < y2 or # rect1 above rect2
y2 + h2 < y1) # rect2 above rect1
# Examples
bullet = (100, 200, 3, 10)
alien = (95, 190, 30, 24)
miss = (200, 200, 3, 10)
print("๐ฏ Collision Examples:")
print(f" Bullet hits alien: {check_collision(bullet, alien)}")
print(f" Bullet misses: {check_collision(miss, alien)}")๐ Enhancement Challenges
Challenge 1: Shields
Add destructible shields between player and aliens:
- Create 3-4 shields
- Bullets destroy shield pixels
- Shields protect player
Challenge 2: Wave System
Make aliens come in waves:
- After defeating all aliens, spawn new wave
- Each wave is faster/harder
- Track wave number
Challenge 3: Power-Ups
Add power-ups that fall from destroyed aliens:
- Rapid fire
- Shield
- Multi-shot
Challenge 4: Boss Battle
Add a boss alien that:
- Has more health
- Moves differently
- Shoots multiple bullets
- Appears every 5 waves
๐ Whatโs Next?
Youโve now built two classic games! In the final slide, weโll explore:
- Advanced game patterns
- Particle effects
- Game juice (polish)
- Where to go next in game development