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!
Special Note: This project is designed for students who have completed the first chapter of CS Quests and are familiar with Python basics. If youโre new to programming, consider starting with the earlier lessons before diving into this project.
Another Special Note: This project requires a local Python environment for full functionality. While we provide examples and code snippets here, some features may not run directly in the browser. For the best experience, consider setting up the project on your own computer.
๐พ 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