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

# 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()

Dean Belfield

Technical Consultant, Programmer and old-school 8-bit Games Developer.

You may also like...