Break Into Program – Space Invaders

During CoderDojo #20 we looked at programming Space Invaders in Python, using the excellent PyGame library. Here’s the finished code. If you want to progress this further, I’d look at:

  • Added scoring
  • Animate the Space Invaders as they march along the screen
  • A Game Over message
  • Sound

The graphics for this demo can be downloaded here:

Graphics – Space Invaders

Unzip into the graphics directory (we put them in C:\Python\Dev\CoderDojo\Graphics).

Enjoy!

The Science Bit

[python]
# Import the libraries
#
import pygame
import random

# Initialise PyGame
#
pygame.init()

# Some global variables
#
win_w = 800 # Window width
win_h = 600 # Window height

sprites = [] # List of sprites
invader_flip = False # Flag to reverse invader direction
invader_count = 0 # Invader count
invader_missile_count = 0 # Invader missile count

# Create a screen surface to draw on, and a clock
#
screen = pygame.display.set_mode((win_w, win_h))
clock = pygame.time.Clock()

# Set the key repeat to 1ms delay, 1ms repeat
#
pygame.key.set_repeat(1,1)

# Load some images ————————————————————-

image_invader = pygame.image.load("../Graphics/space_invader.png").convert()
image_player = pygame.image.load("../Graphics/player.png").convert()
image_missile = pygame.image.load("../Graphics/missile.png").convert()

# This bit sets anything white (RGB=255,255,255) in the graphics to be transparent
#
image_invader.set_colorkey((255,255,255))
image_player.set_colorkey((255,255,255))
image_missile.set_colorkey((255,255,255))

# The Sprite class ————————————————————-

class Sprite:
#
# Some basic variables, just for this sprite
#
active = None # Whether the sprite is active or off
xp = None # X position of the sprite
yp = None # Y position of the sprite
image = None # Sprite image
type = None # Sprite type (string)
fn_move = None # Function to move the sprite

#
# This is called when we initialise the Sprite class
#
def __init__(self):
self.active = False

#
# A class function to move the sprite
#
def move(self):
if(self.fn_move is not None):
self.fn_move(self)
return

#
# A class function to draw the sprite
#
def draw(self):
screen.blit(self.image, (self.xp, self.yp))

# Set up a list of Sprites
#
max_sprites = 200 # Number of sprites we want to create
sprites = [Sprite() for i in range(max_sprites)] # Create a list of Sprites

# Helper functions ————————————————————

# Find index of next available free sprite slot in list
# Returns index in array of sprites, or -1 if no slots left
# Start from 2, as 0 and 1 are reserved for player and player missile
#
def find_next_available_sprite():
for i in range(2, max_sprites):
if sprites[i].active == False:
return i
return -1

# Movement functions ———————————————————-

# Move an invader
#
def move_invader(self):
global invader_flip
global invader_count
global invader_missile_count
global win_w
global done

speed = 1 # Invader speed multiplier

# Change the speed multiplier based upon number of invaders
#
if invader_count < 40: speed = 2
if invader_count < 30: speed = 3
if invader_count < 20: speed = 4
if invader_count < 5: speed = 6
if invader_count == 1: speed = 8

# Move the invader horizontally
#
self.xp += self.xv * speed # Basic velocity times the speed multiplier

# Check whether invader hit side of screen and flip all invaders
#
if self.xp >= win_w – 35 or self.xp <= 0:
invader_flip = True

# Check whether invader has reached the ground and end game
#
if self.yp >= sprites[0].yp:
print("Game Over: Invaders have landed!")
done = 3

# Check whether invader has hit the missile, if active, and make bullet and invader inactive
#
if sprites[1].active:
if sprites[1].xp > self.xp and sprites[1].xp < self.xp + 35 and sprites[1].yp > self.yp and sprites[1].yp < self.yp + 35:
self.active = False
sprites[1].active = False
invader_count -= 1

# Randomly drop an invader missile
#
if invader_missile_count < 5 and random.randint(0, 1000) > 990:
i = find_next_available_sprite()
if i != -1:
sprites[i].active = True
sprites[i].type = "invader missile"
sprites[i].image = image_missile
sprites[i].xp = self.xp + 14
sprites[i].yp = self.yp + 30
sprites[i].fn_move = move_invader_missile
invader_missile_count += 1

# Move the player
#
def move_player(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.xp -= 4
if keys[pygame.K_RIGHT]:
self.xp += 4
if keys[pygame.K_SPACE]:
if sprites[1].active == False:
sprites[1].active = True
sprites[1].xp = self.xp + 14
sprites[1].yp = self.yp

# Move a player missile
#
def move_player_missile(self):
self.yp -= 12
if self.yp < 0:
self.active = False

# Move an invader missile
#
def move_invader_missile(self):
global invader_missile_count
global done
self.yp += 4
if self.yp > win_h:
self.active = False
invader_missile_count -= 1
else:
if self.xp > sprites[0].xp and self.xp < sprites[0].xp + 35 and self.yp > sprites[0].yp and self.yp < sprites[0].yp + 35:
print("Game Over: Hit by missile!")
done = 2

# If any of the invaders have tripped the invader flip flag, then reverse invader direction
# and move the invaders down
#
def check_invader_flip():
global invader_flip
global max_sprites
if invader_flip:
invader_flip = False
for i in range(0, max_sprites):
if sprites[i].active and sprites[i].type == "invader":
sprites[i].xv = -sprites[i].xv
sprites[i].yp += 12

# Initialise the sprites ——————————————————

# Create the player sprite
#
sprites[0].sprite_type = "player" # This is the sprite type
sprites[0].active = True # Set the sprite to be active
sprites[0].xp = win_w//2 # Set X position
sprites[0].yp = win_h – 40 # Set Y position
sprites[0].image = image_player # Set image (loaded previously)
sprites[0].fn_move = move_player # Set function to move sprite

# Create the player missile sprite, but don’t make it active yet!
#
sprites[1].type = "player missile"
sprites[1].image = image_missile
sprites[1].fn_move = move_player_missile

while True:

# Loop through the list and initialise each Sprite in it
#
i = 2
for row in range(0, 5):
for col in range(0, 11):
sprites[i].type = "invader"
sprites[i].active = True
sprites[i].xp = col * 50
sprites[i].yp = row * 50
sprites[i].xv = 1
sprites[i].image = image_invader
sprites[i].fn_move = move_invader
i += 1
invader_count += 1

# Main Game Loop —————————————————————

done = 0
while done == 0:
#
# Pygame event handler
#
for event in pygame.event.get():
# Check for window close
if event.type == pygame.QUIT:
done = -1

# Move everything by calling the Sprite’s move function
#
for i in range(0, max_sprites):
if sprites[i].active:
sprites[i].move()

# Call function to reverse all invaders direction if invader_flip flag set
#
check_invader_flip()

# Clear the screen
#
screen.fill((255,255,255))

# Draw the Sprites by calling their draw function
#
for i in range(0, max_sprites):
if sprites[i].active:
sprites[i].draw()

# Update the surface to the screen and lock at 60fps
#
pygame.display.flip()
clock.tick(60)

# Check for end of game
#
if invader_count == 0:
print("Level Up: Killed all invaders!")
done = 1

# Exit unless we are levelling up (done=1)
#
if done != 1: break

# Clear any missiles before we loop around again!
#
for i in range(0, max_sprites):
if sprites[i].active and (sprites[i].type == "player missile" or sprites[i].type == "invader_missile"):
sprites[i].active = False

pygame.quit()
[/python]