Wikijunior:Raspberry Pi/Raspberry Pi Build A Raspberry Pi Space Invaders Controller
Tutorial by Stuart Fox
Thanks to Scott Bowman & Lee Robinson
Public Domain
28th Jan 2017 – www.cotswoldjam.org
In this tutorial, we'll build a mini game controller, wire it to a Raspberry Pi's GPIO pins and write a program in Python to recognise the button presses. Finally we’ll examine and run a Space Invaders game, modified with the methods we’ve just learnt about to work with our controller.
What's in the kit?
editIn your bag you will find the following components:
- ×6 M-F Jumper leads (pin to socket)
- ×1 Mini Breadboard
- ×3 Momentary Push Buttons
Part 1 – Let’s build the controller
editTake a button by its connectors as shown and carefully bend the button so it is at a 90-degree angle to the pins.
Do this slowly and gently to avoid snapping the connectors.
Place the breadboard in front of you as in the image to the left. Use the numbers and letters shown in the image as a reference.
Now take your first button and push its connectors into holes E1 and E3 with the button facing away from you as shown on the right.
Repeat this process, placing your second button's connectors into holes E5 and E7 and the third button's connectors into holes E15 and E17.
Part 2 – Wiring your controller
editFirst, make sure your Pi is shut down and the power lead disconnected. |
The colour of the jumper wires in your kit is not important – where you put the jumper wires is important!
Make sure you connect to the correct holes and pins. Using the diagram above for reference, follow these steps to wire your controller:
Breadboard | Pi GPIO |
---|---|
A1 Wire to | Pin 29 (GPIO 5) |
A3 Wire to | Pin 39 (GND) |
A5 Wire to | Pin 31 (GPIO 6) |
A7 Wire to | Pin 25 (GND) |
A15 Wire to | Pin 33 (GPIO 13) |
A17 Wire to | Pin 34 (GND) |
Part 3 – The Code
editNow you have a controller built and wired to your Raspberry Pi's GPIO Pins, it’s time to see if it works. Make sure your wires have been checked by a tutor before you power up your Pi.
Power up your Raspberry Pi. From the desktop menu, select Programming – Thonny Python IDE.
Let’s run the program. Use the Run button on the top of the window.
Now try pushing each of your buttons. If everything is working the program will show the text correponding to each button when it's pressed.
Type in the following code
editfrom gpiozero import Button
from signal import pause
button_1=Button(5)
button_2=Button(6)
button_3=Button(13)
def buttonone ():
print("Button 1")
def buttontwo ():
print("Button 2")
def buttonthree ():
print("Button 3")
while (True):
button_1.when_pressed = buttonone
button_2.when_pressed = buttontwo
button_3.when_pressed = buttonthree
pause ()
Now use File, Save As to save your program as the name of your choice (don’t forget to put .py on the end) in the ~/python/buttoninvaders
folder.
This program shows us how to use the GPIOZero library to trigger functions in Python and will also serve to test your newly built controller.
Let's look at how this program works…
editfrom gpiozero import Button
from signal import pause
The from
lines tell the computer to learn about new things. Computers can learn from programs that other people have written; we call these other programs "libraries". Our program needs the function called button from the gpiozero
library which it will use to detect button presses on your controller. The function pause
from the signal
library so that we can insert pauses.
button_1=Button(5)
button_2=Button(6)
button_3=Button(13)
These lines set the names of our buttons and tell the program which GPIO pin they are connected to. So now the program will see our three buttons connected to these three GPIO pins: pin 5 (I2C1 SCL), pin 6 (GND) and pin 7 (GPIO 4)
def buttonone ():
print("Button 1")
def buttontwo ():
print("Button 2")
def buttonthree ():
print("Button 3")
Here we are setting up functions called buttonone
, buttontwo
and buttonthree
. Functions contain multiple instructions, ready to be triggered by an event or input. In this case, each function only performs one task, and that is to print some text on the screen. When we look at the Space Invaders game code, we will see functions like this performing multiple tasks at once when triggered.
while True:
This while True:
tells the program to run forever in a loop.
button_1.when_pressed = buttonone
button_2.when_pressed = buttontwo
button_3.when_pressed = buttonthree
pause ()
Now we are telling the program to trigger a function when a button is pressed.
Part 4 – Let's Play Space Invaders
editNow our control panel is built and tested, it's time to defend earth against an alien attack!
In Thonny Python IDE, Use File, Open to open buttoninvaders.py
program in the ~/python/buttoninvaders
folder.
Now enable line numbers: from the Tools menu select options, choose the editor tab, then tick the box by 'Show Line Numbers'.
Look at the following lines of code and see if you recognise the way this game has been modified to work with our controller.
Lines 12 to 20, lines 55 to 63, and lines 451 to 484. These lines are commented to explain the modifications.
Now, click the Run button and our modified Space Invaders game will launch.
- Button one is left
- Button two is right
- Button three is fire
You can press fire or the space bar to start the game.
Further reading
editThe Space Invaders game used for this tutorial was developed by Lee Robinson using the Pygame module for Python. He has written a blog post explaining how he made it with links to the project's GitHub repository: https://leerob.io/blog/space-invaders-with-python
If you are curious about the original Space Invaders arcade game that was released by Taito in 1978, there is a dedicated Space Invaders wikibook.
There is also a PyGame Guide on Wikibooks to learn how to create your own video games.
Files
editThe assets for this Space Invaders game are in the Raspberry Pi tutorials Button Invaders assets category on Wikicommons.
Space Invaders Controler Tutorial2.pdf
editThe original PDF for this tutorial is available on Wikimedia Commons: Space Invaders Controler Tutorial2.pdf
buttoninvaders.py
edit#!/usr/bin/env python
# Space Invaders
# Created by Lee Robinson
# Modified for the Cotswold Pi Jam 'Build A Space Invaders Controller' tutorial.
from pygame import *
import sys
from os.path import abspath, dirname
from random import randint, choice
# These next two lines have been added to import the libraries we need
# for the program to recognise our buttons
from gpiozero import Button
#from signal import pause
# These lines set up and assign the GPIO Pins that our buttons are connected to
button_1=Button(5)
button_2=Button(6)
button_3=Button(13)
BASE_PATH = abspath(dirname(__file__))
FONT_PATH = BASE_PATH + '/fonts/'
IMAGE_PATH = BASE_PATH + '/images/'
SOUND_PATH = BASE_PATH + '/sounds/'
# Colors (R, G, B)
WHITE = (255, 255, 255)
GREEN = (78, 255, 87)
YELLOW = (241, 255, 0)
BLUE = (80, 255, 239)
PURPLE = (203, 0, 255)
RED = (237, 28, 36)
SCREEN = display.set_mode((800, 600))
FONT = FONT_PATH + 'space_invaders.ttf'
IMG_NAMES = ['ship', 'mystery',
'enemy1_1', 'enemy1_2',
'enemy2_1', 'enemy2_2',
'enemy3_1', 'enemy3_2',
'explosionblue', 'explosiongreen', 'explosionpurple',
'laser', 'enemylaser']
IMAGES = {name: image.load(IMAGE_PATH + '{}.png'.format(name)).convert_alpha()
for name in IMG_NAMES}
class Ship(sprite.Sprite):
def __init__(self):
sprite.Sprite.__init__(self)
self.image = IMAGES['ship']
self.rect = self.image.get_rect(topleft=(375, 540))
self.speed = 5
# Added 'if button_1.is_pressed' to this function so that we can use our controler
# to move the spaceship left and right. self.rect.x sets boundries
# so that the spaceship stops at the edges of the window.
def update(self, keys, *args):
if button_1.is_pressed and self.rect.x > 10 or keys[K_LEFT] and self.rect.x > 10:
self.rect.x -= self.speed
if button_2.is_pressed and self.rect.x < 740 or keys[K_RIGHT] and self.rect.x < 740:
self.rect.x += self.speed
game.screen.blit(self.image, self.rect)
class Bullet(sprite.Sprite):
def __init__(self, xpos, ypos, direction, speed, filename, side):
sprite.Sprite.__init__(self)
self.image = IMAGES[filename]
self.rect = self.image.get_rect(topleft=(xpos, ypos))
self.speed = speed
self.direction = direction
self.side = side
self.filename = filename
def update(self, keys, *args):
game.screen.blit(self.image, self.rect)
self.rect.y += self.speed * self.direction
if self.rect.y < 15 or self.rect.y > 600:
self.kill()
class Enemy(sprite.Sprite):
def __init__(self, row, column):
sprite.Sprite.__init__(self)
self.row = row
self.column = column
self.images = []
self.load_images()
self.index = 0
self.image = self.images[self.index]
self.rect = self.image.get_rect()
self.direction = 1
self.rightMoves = 30
self.leftMoves = 30
self.moveNumber = 15
self.moveTime = 600
self.timer = time.get_ticks()
def update(self, keys, currentTime, enemies):
if currentTime - self.timer > self.moveTime:
if self.direction == 1:
maxMove = self.rightMoves + enemies.rightAddMove
else:
maxMove = self.leftMoves + enemies.leftAddMove
if self.moveNumber >= maxMove:
if self.direction == 1:
self.leftMoves = 30 + enemies.rightAddMove
elif self.direction == -1:
self.rightMoves = 30 + enemies.leftAddMove
self.direction *= -1
self.moveNumber = 0
self.rect.y += 35
elif self.direction == 1:
self.rect.x += 10
self.moveNumber += 1
elif self.direction == -1:
self.rect.x -= 10
self.moveNumber += 1
self.index += 1
if self.index >= len(self.images):
self.index = 0
self.image = self.images[self.index]
self.timer += self.moveTime
game.screen.blit(self.image, self.rect)
def load_images(self):
images = {0: ['1_2', '1_1'],
1: ['2_2', '2_1'],
2: ['2_2', '2_1'],
3: ['3_1', '3_2'],
4: ['3_1', '3_2'],
}
img1, img2 = (IMAGES['enemy{}'.format(img_num)] for img_num in
images[self.row])
self.images.append(transform.scale(img1, (40, 35)))
self.images.append(transform.scale(img2, (40, 35)))
class EnemiesGroup(sprite.Group):
def __init__(self, columns, rows):
sprite.Group.__init__(self)
self.enemies = [[0] * columns for _ in range(rows)]
self.columns = columns
self.rows = rows
self.leftAddMove = 0
self.rightAddMove = 0
self._aliveColumns = list(range(columns))
self._leftAliveColumn = 0
self._rightAliveColumn = columns - 1
self._leftKilledColumns = 0
self._rightKilledColumns = 0
def add(self, *sprites):
super(sprite.Group, self).add(*sprites)
for s in sprites:
self.enemies[s.row][s.column] = s
def is_column_dead(self, column):
for row in range(self.rows):
if self.enemies[row][column]:
return False
return True
def random_bottom(self):
random_index = randint(0, len(self._aliveColumns) - 1)
col = self._aliveColumns[random_index]
for row in range(self.rows, 0, -1):
enemy = self.enemies[row - 1][col]
if enemy:
return enemy
return None
def kill(self, enemy):
# on double hit calls twice for same enemy, so check before
if not self.enemies[enemy.row][enemy.column]:
return # nothing to kill
self.enemies[enemy.row][enemy.column] = None
isColumnDead = self.is_column_dead(enemy.column)
if isColumnDead:
self._aliveColumns.remove(enemy.column)
if enemy.column == self._rightAliveColumn:
while self._rightAliveColumn > 0 and isColumnDead:
self._rightAliveColumn -= 1
self._rightKilledColumns += 1
self.rightAddMove = self._rightKilledColumns * 5
isColumnDead = self.is_column_dead(self._rightAliveColumn)
elif enemy.column == self._leftAliveColumn:
while self._leftAliveColumn < self.columns and isColumnDead:
self._leftAliveColumn += 1
self._leftKilledColumns += 1
self.leftAddMove = self._leftKilledColumns * 5
isColumnDead = self.is_column_dead(self._leftAliveColumn)
class Blocker(sprite.Sprite):
def __init__(self, size, color, row, column):
sprite.Sprite.__init__(self)
self.height = size
self.width = size
self.color = color
self.image = Surface((self.width, self.height))
self.image.fill(self.color)
self.rect = self.image.get_rect()
self.row = row
self.column = column
def update(self, keys, *args):
game.screen.blit(self.image, self.rect)
class Mystery(sprite.Sprite):
def __init__(self):
sprite.Sprite.__init__(self)
self.image = IMAGES['mystery']
self.image = transform.scale(self.image, (75, 35))
self.rect = self.image.get_rect(topleft=(-80, 45))
self.row = 5
self.moveTime = 25000
self.direction = 1
self.timer = time.get_ticks()
self.mysteryEntered = mixer.Sound(SOUND_PATH + 'mysteryentered.wav')
self.mysteryEntered.set_volume(0.3)
self.playSound = True
def update(self, keys, currentTime, *args):
resetTimer = False
passed = currentTime - self.timer
if passed > self.moveTime:
if (self.rect.x < 0 or self.rect.x > 800) and self.playSound:
self.mysteryEntered.play()
self.playSound = False
if self.rect.x < 840 and self.direction == 1:
self.mysteryEntered.fadeout(4000)
self.rect.x += 2
game.screen.blit(self.image, self.rect)
if self.rect.x > -100 and self.direction == -1:
self.mysteryEntered.fadeout(4000)
self.rect.x -= 2
game.screen.blit(self.image, self.rect)
if self.rect.x > 830:
self.playSound = True
self.direction = -1
resetTimer = True
if self.rect.x < -90:
self.playSound = True
self.direction = 1
resetTimer = True
if passed > self.moveTime and resetTimer:
self.timer = currentTime
class Explosion(sprite.Sprite):
def __init__(self, xpos, ypos, row, ship, mystery, score):
sprite.Sprite.__init__(self)
self.isMystery = mystery
self.isShip = ship
if mystery:
self.text = Text(FONT, 20, str(score), WHITE, xpos + 20, ypos + 6)
elif ship:
self.image = IMAGES['ship']
self.rect = self.image.get_rect(topleft=(xpos, ypos))
else:
self.row = row
self.load_image()
self.image = transform.scale(self.image, (40, 35))
self.rect = self.image.get_rect(topleft=(xpos, ypos))
game.screen.blit(self.image, self.rect)
self.timer = time.get_ticks()
def update(self, keys, currentTime):
passed = currentTime - self.timer
if self.isMystery:
if passed <= 200:
self.text.draw(game.screen)
elif 400 < passed <= 600:
self.text.draw(game.screen)
elif passed > 600:
self.kill()
elif self.isShip:
if 300 < passed <= 600:
game.screen.blit(self.image, self.rect)
elif passed > 900:
self.kill()
else:
if passed <= 100:
game.screen.blit(self.image, self.rect)
elif 100 < passed <= 200:
self.image = transform.scale(self.image, (50, 45))
game.screen.blit(self.image,
(self.rect.x - 6, self.rect.y - 6))
elif passed > 400:
self.kill()
def load_image(self):
imgColors = ['purple', 'blue', 'blue', 'green', 'green']
self.image = IMAGES['explosion{}'.format(imgColors[self.row])]
class Life(sprite.Sprite):
def __init__(self, xpos, ypos):
sprite.Sprite.__init__(self)
self.image = IMAGES['ship']
self.image = transform.scale(self.image, (23, 23))
self.rect = self.image.get_rect(topleft=(xpos, ypos))
def update(self, keys, *args):
game.screen.blit(self.image, self.rect)
class Text(object):
def __init__(self, textFont, size, message, color, xpos, ypos):
self.font = font.Font(textFont, size)
self.surface = self.font.render(message, True, color)
self.rect = self.surface.get_rect(topleft=(xpos, ypos))
def draw(self, surface):
surface.blit(self.surface, self.rect)
class SpaceInvaders(object):
def __init__(self):
# It seems, in Linux buffersize=512 is not enough, use 4096 to prevent:
# ALSA lib pcm.c:7963:(snd_pcm_recover) underrun occurred
mixer.pre_init(44100, -16, 1, 4096)
init()
self.caption = display.set_caption('Space Invaders')
self.screen = SCREEN
self.background = image.load(IMAGE_PATH + 'background.jpg').convert()
self.startGame = False
self.mainScreen = True
self.gameOver = False
# Initial value for a new game
self.enemyPositionDefault = 65
# Counter for enemy starting position (increased each new round)
self.enemyPositionStart = self.enemyPositionDefault
# Current enemy starting position
self.enemyPosition = self.enemyPositionStart
def reset(self, score, lives, newGame=False):
self.player = Ship()
self.playerGroup = sprite.Group(self.player)
self.explosionsGroup = sprite.Group()
self.bullets = sprite.Group()
self.mysteryShip = Mystery()
self.mysteryGroup = sprite.Group(self.mysteryShip)
self.enemyBullets = sprite.Group()
self.reset_lives(lives)
self.enemyPosition = self.enemyPositionStart
self.make_enemies()
# Only create blockers on a new game, not a new round
if newGame:
self.allBlockers = sprite.Group(self.make_blockers(0),
self.make_blockers(1),
self.make_blockers(2),
self.make_blockers(3))
self.keys = key.get_pressed()
self.clock = time.Clock()
self.timer = time.get_ticks()
self.noteTimer = time.get_ticks()
self.shipTimer = time.get_ticks()
self.score = score
self.lives = lives
self.create_audio()
self.create_text()
self.makeNewShip = False
self.shipAlive = True
def make_blockers(self, number):
blockerGroup = sprite.Group()
for row in range(4):
for column in range(9):
blocker = Blocker(10, GREEN, row, column)
blocker.rect.x = 50 + (200 * number) + (column * blocker.width)
blocker.rect.y = 450 + (row * blocker.height)
blockerGroup.add(blocker)
return blockerGroup
def reset_lives_sprites(self):
self.life1 = Life(715, 3)
self.life2 = Life(742, 3)
self.life3 = Life(769, 3)
if self.lives == 3:
self.livesGroup = sprite.Group(self.life1, self.life2, self.life3)
elif self.lives == 2:
self.livesGroup = sprite.Group(self.life1, self.life2)
elif self.lives == 1:
self.livesGroup = sprite.Group(self.life1)
def reset_lives(self, lives):
self.lives = lives
self.reset_lives_sprites()
def create_audio(self):
self.sounds = {}
for sound_name in ['shoot', 'shoot2', 'invaderkilled', 'mysterykilled',
'shipexplosion']:
self.sounds[sound_name] = mixer.Sound(
SOUND_PATH + '{}.wav'.format(sound_name))
self.sounds[sound_name].set_volume(0.2)
self.musicNotes = [mixer.Sound(SOUND_PATH + '{}.wav'.format(i)) for i
in range(4)]
for sound in self.musicNotes:
sound.set_volume(0.5)
self.noteIndex = 0
def play_main_music(self, currentTime):
moveTime = self.enemies.sprites()[0].moveTime
if currentTime - self.noteTimer > moveTime:
self.note = self.musicNotes[self.noteIndex]
if self.noteIndex < 3:
self.noteIndex += 1
else:
self.noteIndex = 0
self.note.play()
self.noteTimer += moveTime
def create_text(self):
self.titleText = Text(FONT, 50, 'Space Invaders', WHITE, 164, 155)
self.titleText2 = Text(FONT, 25, 'Press any key to continue', WHITE,
201, 225)
self.gameOverText = Text(FONT, 50, 'Game Over', WHITE, 250, 270)
self.nextRoundText = Text(FONT, 50, 'Next Round', WHITE, 240, 270)
self.enemy1Text = Text(FONT, 25, ' = 10 pts', GREEN, 368, 270)
self.enemy2Text = Text(FONT, 25, ' = 20 pts', BLUE, 368, 320)
self.enemy3Text = Text(FONT, 25, ' = 30 pts', PURPLE, 368, 370)
self.enemy4Text = Text(FONT, 25, ' = ?????', RED, 368, 420)
self.scoreText = Text(FONT, 20, 'Score', WHITE, 5, 5)
self.livesText = Text(FONT, 20, 'Lives ', WHITE, 640, 5)
@staticmethod
def should_exit(evt):
# type: (pygame.event.EventType) -> bool
return evt.type == QUIT or (evt.type == KEYUP and evt.key == K_ESCAPE)
# New function created to contain all the things the game must do when the player presses the
# fire button.
def fire(self):
if len(self.bullets) == 0 and self.shipAlive:
if self.score < 1000:
bullet = Bullet(self.player.rect.x + 23,
self.player.rect.y + 5, -1,
15, 'laser', 'center')
self.bullets.add(bullet)
self.allSprites.add(self.bullets)
self.sounds['shoot'].play()
else:
leftbullet = Bullet(self.player.rect.x + 8,
self.player.rect.y + 5, -1,
15, 'laser', 'left')
rightbullet = Bullet(self.player.rect.x + 38,
self.player.rect.y + 5, -1,
15, 'laser', 'right')
self.bullets.add(leftbullet)
self.bullets.add(rightbullet)
self.allSprites.add(self.bullets)
self.sounds['shoot2'].play()
# This new function waits for ether our button, or the space bar to be pressed. When
# ether of those things happens it will tell thefinction 'fire' above.
def check_input(self):
if button_3.is_pressed:
self.fire()
self.keys = key.get_pressed()
for e in event.get():
if self.should_exit(e):
sys.exit()
if e.type == KEYDOWN and e.key == K_SPACE:
self.fire()
def make_enemies(self):
enemies = EnemiesGroup(10, 5)
for row in range(5):
for column in range(10):
enemy = Enemy(row, column)
enemy.rect.x = 157 + (column * 50)
enemy.rect.y = self.enemyPosition + (row * 45)
enemies.add(enemy)
self.enemies = enemies
self.allSprites = sprite.Group(self.player, self.enemies,
self.livesGroup, self.mysteryShip)
def make_enemies_shoot(self):
if (time.get_ticks() - self.timer) > 700:
enemy = self.enemies.random_bottom()
if enemy:
self.enemyBullets.add(
Bullet(enemy.rect.x + 14, enemy.rect.y + 20, 1, 5,
'enemylaser', 'center'))
self.allSprites.add(self.enemyBullets)
self.timer = time.get_ticks()
def calculate_score(self, row):
scores = {0: 30,
1: 20,
2: 20,
3: 10,
4: 10,
5: choice([50, 100, 150, 300])
}
score = scores[row]
self.score += score
return score
def create_main_menu(self):
self.enemy1 = IMAGES['enemy3_1']
self.enemy1 = transform.scale(self.enemy1, (40, 40))
self.enemy2 = IMAGES['enemy2_2']
self.enemy2 = transform.scale(self.enemy2, (40, 40))
self.enemy3 = IMAGES['enemy1_2']
self.enemy3 = transform.scale(self.enemy3, (40, 40))
self.enemy4 = IMAGES['mystery']
self.enemy4 = transform.scale(self.enemy4, (80, 40))
self.screen.blit(self.enemy1, (318, 270))
self.screen.blit(self.enemy2, (318, 320))
self.screen.blit(self.enemy3, (318, 370))
self.screen.blit(self.enemy4, (299, 420))
for e in event.get():
if self.should_exit(e):
sys.exit()
if e.type == KEYUP:
self.startGame = True
self.mainScreen = False
def update_enemy_speed(self):
if len(self.enemies) <= 10:
for enemy in self.enemies:
enemy.moveTime = 400
if len(self.enemies) == 1:
for enemy in self.enemies:
enemy.moveTime = 200
def check_collisions(self):
collidedict = sprite.groupcollide(self.bullets, self.enemyBullets,
True, False)
if collidedict:
for value in collidedict.values():
for currentSprite in value:
self.enemyBullets.remove(currentSprite)
self.allSprites.remove(currentSprite)
enemiesdict = sprite.groupcollide(self.bullets, self.enemies,
True, False)
if enemiesdict:
for value in enemiesdict.values():
for currentSprite in value:
self.enemies.kill(currentSprite)
self.sounds['invaderkilled'].play()
score = self.calculate_score(currentSprite.row)
explosion = Explosion(currentSprite.rect.x,
currentSprite.rect.y,
currentSprite.row, False, False,
score)
self.explosionsGroup.add(explosion)
self.allSprites.remove(currentSprite)
self.enemies.remove(currentSprite)
self.gameTimer = time.get_ticks()
break
mysterydict = sprite.groupcollide(self.bullets, self.mysteryGroup,
True, True)
if mysterydict:
for value in mysterydict.values():
for currentSprite in value:
currentSprite.mysteryEntered.stop()
self.sounds['mysterykilled'].play()
score = self.calculate_score(currentSprite.row)
explosion = Explosion(currentSprite.rect.x,
currentSprite.rect.y,
currentSprite.row, False, True,
score)
self.explosionsGroup.add(explosion)
self.allSprites.remove(currentSprite)
self.mysteryGroup.remove(currentSprite)
newShip = Mystery()
self.allSprites.add(newShip)
self.mysteryGroup.add(newShip)
break
bulletsdict = sprite.groupcollide(self.enemyBullets, self.playerGroup,
True, False)
if bulletsdict:
for value in bulletsdict.values():
for playerShip in value:
if self.lives == 3:
self.lives -= 1
self.livesGroup.remove(self.life3)
self.allSprites.remove(self.life3)
elif self.lives == 2:
self.lives -= 1
self.livesGroup.remove(self.life2)
self.allSprites.remove(self.life2)
elif self.lives == 1:
self.lives -= 1
self.livesGroup.remove(self.life1)
self.allSprites.remove(self.life1)
elif self.lives == 0:
self.gameOver = True
self.startGame = False
self.sounds['shipexplosion'].play()
explosion = Explosion(playerShip.rect.x, playerShip.rect.y,
0, True, False, 0)
self.explosionsGroup.add(explosion)
self.allSprites.remove(playerShip)
self.playerGroup.remove(playerShip)
self.makeNewShip = True
self.shipTimer = time.get_ticks()
self.shipAlive = False
if sprite.groupcollide(self.enemies, self.playerGroup, True, True):
self.gameOver = True
self.startGame = False
sprite.groupcollide(self.bullets, self.allBlockers, True, True)
sprite.groupcollide(self.enemyBullets, self.allBlockers, True, True)
sprite.groupcollide(self.enemies, self.allBlockers, False, True)
def create_new_ship(self, createShip, currentTime):
if createShip and (currentTime - self.shipTimer > 900):
self.player = Ship()
self.allSprites.add(self.player)
self.playerGroup.add(self.player)
self.makeNewShip = False
self.shipAlive = True
def create_game_over(self, currentTime):
self.screen.blit(self.background, (0, 0))
passed = currentTime - self.timer
if passed < 750:
self.gameOverText.draw(self.screen)
elif 750 < passed < 1500:
self.screen.blit(self.background, (0, 0))
elif 1500 < passed < 2250:
self.gameOverText.draw(self.screen)
elif 2250 < passed < 2750:
self.screen.blit(self.background, (0, 0))
elif passed > 3000:
self.mainScreen = True
for e in event.get():
if self.should_exit(e):
sys.exit()
def main(self):
while True:
if self.mainScreen:
self.reset(0, 3, True)
self.screen.blit(self.background, (0, 0))
self.titleText.draw(self.screen)
self.titleText2.draw(self.screen)
self.enemy1Text.draw(self.screen)
self.enemy2Text.draw(self.screen)
self.enemy3Text.draw(self.screen)
self.enemy4Text.draw(self.screen)
self.create_main_menu()
elif self.startGame:
if len(self.enemies) == 0:
currentTime = time.get_ticks()
if currentTime - self.gameTimer < 3000:
self.screen.blit(self.background, (0, 0))
self.scoreText2 = Text(FONT, 20, str(self.score),
GREEN, 85, 5)
self.scoreText.draw(self.screen)
self.scoreText2.draw(self.screen)
self.nextRoundText.draw(self.screen)
self.livesText.draw(self.screen)
self.livesGroup.update(self.keys)
self.check_input()
if currentTime - self.gameTimer > 3000:
# Move enemies closer to bottom
self.enemyPositionStart += 35
self.reset(self.score, self.lives)
self.gameTimer += 3000
else:
currentTime = time.get_ticks()
self.play_main_music(currentTime)
self.screen.blit(self.background, (0, 0))
self.allBlockers.update(self.screen)
self.scoreText2 = Text(FONT, 20, str(self.score), GREEN,
85, 5)
self.scoreText.draw(self.screen)
self.scoreText2.draw(self.screen)
self.livesText.draw(self.screen)
self.check_input()
self.allSprites.update(self.keys, currentTime,
self.enemies)
self.explosionsGroup.update(self.keys, currentTime)
self.check_collisions()
self.create_new_ship(self.makeNewShip, currentTime)
self.update_enemy_speed()
if len(self.enemies) > 0:
self.make_enemies_shoot()
elif self.gameOver:
currentTime = time.get_ticks()
# Reset enemy starting position
self.enemyPositionStart = self.enemyPositionDefault
self.create_game_over(currentTime)
display.update()
self.clock.tick(60)
if not self.startGame and button_3.is_pressed:
self.startGame = True
self.mainScreen = False
if __name__ == '__main__':
game = SpaceInvaders()
game.main()
LICENSE.txt
editNote: The source code is available under the permissive w:MIT License as per the GitHub repository: https://github.com/leerob/space-invaders/blob/master/LICENSE
The MIT License (MIT)
Copyright (c) 2014-2019 Lee Robinson
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.