SideScrollingShootEmUp

The game screen appears here if your browser supports the Canvas API.

Attribution

Create your own side-scrolling shoot-'em-up, pages 58-63, by Jordi Sontanja.

Licensed under Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported.

Original Python code


import pgzrun
from enum import Enum
import math
from random import randint
from pgzero.builtins import Actor
from shapely.geometry import Point, Polygon, LineString
from shapely.affinity import translate

isFastComputer = True
if isFastComputer:
    def drawBackground(screen, frame):
        screen.blit("background_1",(800-frame%(800*8)/4,0))
        screen.blit("background_0",(800-(frame+800*4)%(800*8)/4,0))

foregroundNames = ['centreforeground', 'leftforeground', 'leftvertforeground',
                   'rightforeground', 'rightvertforeground',
                   'centreforeground_top', 'leftforeground_top', 'leftvertforeground_top',
                   'rightforeground_top', 'rightvertforeground_top',
                   'centreforeground_narrow', 'centreforeground_narrow_top',
                   'leftforeground_narrow', 'leftforeground_narrow_top',
                   'leftvertforeground_narrow', 'leftvertforeground_narrow_top',
                   'rightforeground_narrow', 'rightforeground_narrow_top',
                   'rightvertforeground_narrow', 'rightvertforeground_narrow_top',
                   'centre']

foregroundMap = [(12,1634,571),(10,1831,571),(10,2072,571),(10,2314,571),(14,2420,506),(0,2557,569),(0,2800,569),(0,3041,569),
                 (0,3284,569),(0,3525,569),(0,3767,569),(13,3840,31),(0,4009,569),(11,4038,31),(0,4250,569),(11,4280,31),
                 (0,4492,569),(11,4522,31),(0,4734,569),(11,4765,31),(0,4976,569),(11,5008,31),(12,5081,249),(13,5081,314),
                 (0,5219,569),(11,5251,31),(10,5278,249),(11,5279,314),(0,5462,569),(11,5493,31),(10,5521,249),(11,5522,314),
                 (0,5704,569),(11,5736,31),(10,5763,249),(11,5763,314),(0,5946,569),(11,5978,31),(10,6006,249),(11,6006,314),
                 (0,6189,569),(11,6221,31),(10,6249,249),(11,6249,314),(0,6432,569),(11,6463,31),(16,6446,249),(17,6447,314),
                 (0,6675,569),(11,6706,31),(0,6918,569),(11,6949,31),(0,7161,569),(11,7191,31),(0,7403,569),(11,7433,31),(0,7645,569),
                 (11,7675,31),(0,7887,569),(17,7873,31),(0,8128,569),(0,8370,569),(0,8613,569),(0,8856,569),(1,9112,377),(20,9099,549),
                 (20,9342,549),(0,9438,377),(20,9585,548),(0,9680,377),(20,9827,546),(0,9923,377),(20,10069,546),(0,10166,377),
                 (20,10310,546),(0,10409,377),(20,10551,546),(0,10652,377),(20,10652,543),(4,10788,548),(4,10788,377)]

foregroundPolygons = [[(-122, -96), (120, -96), (120, 96), (-122, 96)], [(-204, 96), (186, -96), (204, -96), (204, 95)],
                      [(-14, 95), (-14, -95), (14, -95), (14, 95)], [(-206, -95), (-187, -95), (200, 95), (-206, 95)],
                      [(-15, -95), (12, -95), (12, 95), (-15, 95)], [(120, -95), (120, 95), (-122, 95), (-122, -95)],
                      [(204, -95), (204, 95), (186, 95), (-202, -96)], [(14, 95), (-14, 95), (-14, -95), (13, -95)],
                      [(-207, -96), (201, -96), (-187, 95), (-206, 95)], [(-15, -96), (12, -96), (12, 95), (-15, 95)],
                      [(-122, -33), (120, -33), (120, 31), (-122, 31)], [(-122, -33), (120, -33), (120, 31), (-122, 31)],
                      [(-73, 31), (56, -32), (75, -32), (75, 31)], [(75, 31), (56, 31), (-74, -33), (75, -33)],
                      [(14, 31), (-14, 31), (-14, -32), (13, -32)], [(14, 32), (-14, 32), (-14, -33), (13, -33)],
                      [(-77, -33), (-58, -33), (70, 31), (-77, 31)], [(-77, -33), (72, -33), (-58, 31), (-77, 31)],
                      [(-15, -33), (12, -33), (12, 31), (-15, 31)], [(-15, -33), (13, -33), (13, 31), (-15, 31)],
                      []]

foregroundBiggestObjectHalfXSize = 206

class Foreground:
    def __init__(self):
        self.elements = []
        self.indexes = []
        self.mapElement = 0
        self.polygons = []
    def draw(self):
        for element in self.elements:
            element.draw()
    def update(self, globalTime):
        for i in reversed(range(len(self.elements))):
            self.elements[i].x -= 1
            if self.elements[i].x < -foregroundBiggestObjectHalfXSize:
                del self.elements[i]
                del self.indexes[i]
        if self.mapElement < len(foregroundMap):
            while foregroundMap[self.mapElement][1] < globalTime + 800 + foregroundBiggestObjectHalfXSize:
                self.indexes.append(foregroundMap[self.mapElement][0])
                self.elements.append(Actor(foregroundNames[foregroundMap[self.mapElement][0]], (foregroundMap[self.mapElement][1] - globalTime, foregroundMap[self.mapElement][2])))
                self.mapElement += 1
                if self.mapElement >= len(foregroundMap):
                    break
        self.polygons = []
        for i in range(len(self.indexes)):
            polygon = translate(Polygon(foregroundPolygons[self.indexes[i]]), self.elements[i].x, self.elements[i].y)
            self.polygons.append(polygon)

class Bullet:
    def __init__(self, pos, speed):
        self.actor = Actor('bullet', pos)
        self.speed = speed
    def update(self):
        self.actor.x += self.speed[0]
        self.actor.y += self.speed[1]
    def draw(self):
        self.actor.draw()
class AllBullets:
    def __init__(self):
        self.bullets = []
    def update(self):
        for i in reversed(range(len(self.bullets))):
            self.bullets[i].update()
            if self.bullets[i].actor.x < -4 or self.bullets[i].actor.y < -4 or self.bullets[i].actor.x > 804 or self.bullets[i].actor.y > 604:
                del self.bullets[i]
    def draw(self):
        for bullet in self.bullets:
            bullet.draw()
    def append(self, posX, posY, velX, velY):
        self.bullets.append(Bullet((posX, posY), (velX, velY)))
    def collide(self, polygon):
        collided = False
        for i in reversed(range(len(self.bullets))):
            if Point(self.bullets[i].actor.x, self.bullets[i].actor.y).within(polygon):
                collided = True
                del self.bullets[i]
        return collided

enemyNames = ['ball', 'cannon', 'cannon_top', 'flying_saucer',
              'rocket', 'ship_alien', 'ship_cannon', 'ship_fighter', 'ship_pistol', 'big_ship' ]

enemyPolyCoords = [[(-6, -30), (-21, -27), (-30, -19), (-33, -3), (-27, 11), (-19, 20), (-5, 30), (11, 31), (26, 17), (29, -12), (10, -23)],
                   [(-17, 29), (-13, -4), (-23, -11), (-21, -29), (9, -24), (23, -13), (18, 30)],
                   [(-17, -29), (-12, 3), (-22, 6), (-21, 27), (6, 24), (23, 12), (18, -30)],
                   [(-24, 7), (-56, 23), (53, 23), (17, 6), (17, -10), (6, -23), (-14, -23), (-22, -9)],
                   [(73, -20), (-27, -20), (-64, -14), (-85, -5), (-75, 5), (-33, 15), (11, 19), (73, 19), (83, 8), (83, -13)],
                   [(-36, -19), (-48, -10), (-50, 5), (-44, 16), (-31, 23), (-11, 23), (24, 16), (41, -20), (10, -22), (-26, -21)],
                   [(28, -31), (-74, -31), (-74, 8), (-37, 8), (-20, 22), (9, 31), (37, 30), (62, 19), (72, 13), (72, -12), (48, -24), (31, -28)],
                   [(-51, -15), (-60, -11), (-63, -1), (-54, 8), (-21, 8), (18, 22), (41, 24), (58, 18), (61, -26), (23, -24), (-19, -16)],
                   [(-50, -22), (-23, 15), (-9, 27), (4, 32), (26, 24), (42, 7), (49, -15), (41, -28), (16, -33), (7, -33), (7, -26)],
                   [(-144, -32), (-160, -35), (-173, -8), (-164, 11), (-151, 19), (-119, 24), (-142, 30), (-139, 41), (-93, 37), (-82, 31), (-72, 51), (-35, 38), (-30, 53), (5, 65), (100, 69), (108, 61), (109, 44), (152, 28), (169, 15), (169, -2), (154, -12), (127, -24), (154, -41), (70, -69), (39, -43), (21, -56), (-23, -61), (-69, -48), (-74, -42), (-96, -47), (-124, -47), (-109, -40), (-138, -35)]]

enemyNumberOfHits = [1, 4, 4, 10, 1, 1, 12, 6, 12, 1000]

enemyPaths = [[(800+32, 0), (790, 1), (780, 5), (770, 11), (760, 19), (750, 29), (740, 40), (730, 53), (720, 68), (710, 83), (700, 98), (690, 114), (680, 129), (670, 143), (660, 157), (650, 169), (640, 179), (630, 188), (620, 194), (610, 198), (600, 200), (590, 199), (580, 196), (570, 191), (560, 184), (550, 174), (540, 163), (530, 150), (520, 136), (510, 121), (500, 106), (490, 90), (480, 75), (470, 60), (460, 47), (450, 34), (440, 23), (430, 14), (420, 7), (410, 3), (400, 0), (390, 0), (380, 3), (370, 7), (360, 14), (350, 23), (340, 34), (330, 47), (320, 60), (310, 75), (300, 90), (290, 106), (280, 121), (270, 136), (260, 150), (250, 163), (240, 174), (230, 184), (220, 191), (210, 196), (200, 199), (190, 200), (180, 198), (170, 194), (160, 188), (150, 179), (140, 169), (130, 157), (120, 143), (110, 129), (100, 114), (90, 98), (80, 83), (70, 68), (60, 53), (50, 40), (40, 29), (30, 19), (20, 11), (10, 5), (0, 1), (-10, 0), (-160, 0)],
              [(800+24, 0), (-160, 0)],
              [(800+24, 0), (-160, 0)],
              [(800+57, 0), (790, -1), (780, -3), (770, -6), (760, -11), (750, -16), (740, -23), (730, -31), (720, -40), (710, -50), (700, -60), (690, -71), (680, -83), (670, -94), (660, -106), (650, -117), (640, -129), (630, -140), (620, -150), (610, -160), (600, -169), (590, -177), (580, -184), (570, -189), (560, -194), (550, -197), (540, -199), (530, -200), (520, -199), (510, -197), (500, -194), (490, -189), (480, -184), (470, -177), (460, -169), (450, -160), (440, -150), (430, -140), (420, -129), (410, -117), (400, -106), (390, -94), (380, -83), (370, -71), (360, -60), (350, -50), (340, -40), (330, -31), (320, -23), (310, -16), (300, -11), (290, -6), (280, -3), (270, -1), (260, 0), (250, -1), (240, -3), (230, -6), (220, -11), (210, -16), (200, -23), (190, -31), (180, -40), (170, -50), (160, -60), (150, -71), (140, -83), (130, -94), (120, -106), (110, -117), (100, -129), (90, -140), (80, -150), (70, -160), (60, -169), (50, -177), (40, -184), (30, -189), (20, -194), (10, -197), (0, -199), (-10, -200), (-160, -200)],
              [(800+85, 0), (-160, 0)],
              [(800+50, 0), (762, 0), (711, -5), (680, -13), (663, -21), (634, -34), (608, -42), (578, -52), (522, -62), (459, -66), (-160, -66)],
              [(800+75, 0), (790, 0), (780, 0), (770, 1), (760, 1), (750, 2), (740, 3), (730, 4), (720, 5), (710, 6), (700, 7), (690, 9), (680, 11), (670, 12), (660, 14), (650, 16), (640, 19), (630, 21), (620, 23), (610, 26), (600, 29), (590, 31), (580, 34), (570, 37), (560, 40), (550, 43), (540, 47), (530, 50), (520, 53), (510, 57), (500, 60), (490, 64), (480, 68), (470, 71), (460, 75), (450, 79), (440, 83), (430, 86), (420, 90), (410, 94), (400, 98), (390, 102), (380, 106), (370, 110), (360, 114), (350, 117), (340, 121), (330, 125), (320, 129), (310, 132), (300, 136), (290, 140), (280, 143), (270, 147), (260, 150), (250, 153), (240, 157), (230, 160), (220, 163), (210, 166), (200, 169), (190, 171), (180, 174), (170, 177), (160, 179), (150, 181), (140, 184), (130, 186), (120, 188), (110, 189), (100, 191), (90, 193), (80, 194), (70, 195), (60, 196), (50, 197), (40, 198), (30, 199), (20, 199), (10, 200), (0, 200), (-160, 200)],
              [(800+63, 0), (790, 0), (780, 1), (770, 3), (760, 5), (750, 7), (740, 11), (730, 14), (720, 19), (710, 23), (700, 29), (690, 34), (680, 40), (670, 47), (660, 53), (650, 60), (640, 68), (630, 75), (620, 83), (610, 90), (600, 98), (590, 106), (580, 114), (570, 121), (560, 129), (550, 136), (540, 143), (530, 150), (520, 157), (510, 163), (500, 169), (490, 174), (480, 179), (470, 184), (460, 188), (450, 191), (440, 194), (430, 196), (420, 198), (410, 199), (400, 200), (390, 200), (380, 199), (370, 198), (360, 196), (350, 194), (340, 191), (330, 188), (320, 184), (310, 179), (300, 174), (290, 169), (280, 163), (270, 157), (260, 150), (250, 143), (240, 136), (230, 129), (220, 121), (210, 114), (200, 106), (190, 98), (180, 90), (170, 83), (160, 75), (150, 68), (140, 60), (130, 53), (120, 47), (110, 40), (100, 34), (90, 29), (80, 23), (70, 19), (60, 14), (50, 11), (40, 7), (30, 5), (20, 3), (10, 1), (0, 0), (-160, 0)],
              [(800+51, 0), (751, 4), (704, 15), (660, 33), (620, 57), (585, 87), (556, 121), (535, 158), (520, 196), (513, 235), (513, 272), (521, 307), (535, 337), (554, 363), (577, 382), (604, 395), (632, 400), (661, 398), (688, 388), (713, 371), (734, 348), (750, 319), (760, 286), (764, 250), (760, 212), (748, 173), (729, 135), (703, 100), (671, 68), (633, 42), (590, 21), (544, 7), (495, 1), (447, 1), (399, 10), (353, 25), (311, 47), (274, 74), (243, 107), (218, 143), (201, 181), (191, 219), (188, 257), (193, 293), (204, 326), (221, 353), (243, 375), (269, 390), (297, 399), (325, 399), (354, 393), (380, 379), (403, 358), (421, 332), (433, 300), (439, 265), (438, 227), (430, 188), (414, 150), (390, 114), (360, 81), (324, 52), (283, 29), (238, 12), (191, 2), (142, 0), (-160, 0)],
              [(800+180, 0), (800-176, 0)]]

enemySpeeds = [4, 1, 1, 3, 6, 4, 2, 4, 3, 0.5 ]

def PositionFromLength(path, length):
    totalLength = 0.0
    for i in range(1, len(path)):
        dx = path[i][0] - path[i-1][0]
        dy = path[i][1] - path[i-1][1]
        nextLength = math.sqrt(dx*dx + dy*dy)
        if length <= totalLength + nextLength:
            localParameter = (length - totalLength) / nextLength
            return True, (path[i-1][0] + localParameter * dx, path[i-1][1] + localParameter * dy)
        totalLength += nextLength
    return False, path[-1]

def Accelerate(path, length):
    length = length * length / 800
    return PositionFromLength(path, length)

def BigBossMovement(path, length):
    inPath, position = PositionFromLength(path, length)
    if inPath:
        return True, position
    totalLength = path[0][0] - path[1][0]
    return True, (position[0], 200*math.sin((length-totalLength)/40))

enemyMovement = [PositionFromLength, PositionFromLength, PositionFromLength, PositionFromLength,
                 Accelerate, PositionFromLength, PositionFromLength, PositionFromLength,
                 PositionFromLength, BigBossMovement ]

def NoBullets(bullets, spaceShip, posX, posY, length, speed):
    pass

def BulletToSpaceship(bullets, spaceShip, posX, posY, speed):
    dx = spaceShip.actor.x - posX
    dy = spaceShip.actor.y - posY
    distance = math.sqrt(dx*dx + dy*dy)
    if distance > 20:
        bullets.append(posX, posY, dx / distance * (speed+3.0), dy / distance * (speed+3.0))

def SomeBulletsToSpaceship(bullets, spaceShip, posX, posY, length, speed):
    if 0 == randint(0,300):
        BulletToSpaceship(bullets, spaceShip, posX, posY, speed)

def MoreBulletsToSpaceship(bullets, spaceShip, posX, posY, length, speed):
    if 0 == randint(0,100):
        BulletToSpaceship(bullets, spaceShip, posX, posY, speed)

def BulletsBigBoss(bullets, spaceShip, posX, posY, length, speed):
    if length < 360:
        MoreBulletsToSpaceship(bullets, spaceShip, posX, posY, length, speed)
        MoreBulletsToSpaceship(bullets, spaceShip, posX, posY, length, speed)
    else:
        if 0 == round(length*2) % 50:
            for i in range(0, 360, 20):
                bullets.append(posX, posY, 3.0 * math.sin(i / 180.0 * math.pi), 3.0 * math.cos(i / 180.0 * math.pi))
        elif 0 == round(length*2) % 71:
            for i in range(0, 360, 10):
                bullets.append(posX, posY, 3.0 * math.sin(i / 180.0 * math.pi), 3.0 * math.cos(i / 180.0 * math.pi))

bulletStrategy = [NoBullets, MoreBulletsToSpaceship, MoreBulletsToSpaceship, SomeBulletsToSpaceship,
                  NoBullets, NoBullets, MoreBulletsToSpaceship, MoreBulletsToSpaceship,
                  MoreBulletsToSpaceship, BulletsBigBoss ]

def NoPowerup(powerups, posX, posY):
    pass

def AddPowerup(powerups, posX, posY):
    powerups.append(posX, posY)

powerUpStrategy = [AddPowerup, NoPowerup, NoPowerup, NoPowerup,
                   NoPowerup, NoPowerup, NoPowerup, NoPowerup,
                   NoPowerup, NoPowerup ]

class Enemy:
    def __init__(self, index, startY):
        self.index = index
        self.startY = startY
        self.actor = Actor(enemyNames[index], (800+200,startY))
        self.actorWhite = Actor(enemyNames[index]+"_white", (800+200,startY))
        self.positionInPath = 0.0
        self.polygon = Polygon(enemyPolyCoords[index])
        self.numberOfHits = enemyNumberOfHits[index]
        self.lastHit = 0
    def draw(self):
        if self.lastHit == 0:
            self.actor.draw()
        else:
            self.actorWhite.draw()
            self.lastHit -= 1
    def translate(self, posX, posY):
        self.actor.x = self.actorWhite.x = posX
        self.actor.y = self.actorWhite.y = posY
    def update(self, bullets, spaceShip):
        self.positionInPath += enemySpeeds[self.index]
        active, position = enemyMovement[self.index](enemyPaths[self.index], self.positionInPath)
        if active:
            self.translate(position[0], position[1] + self.startY)
            bulletStrategy[self.index](bullets, spaceShip, position[0], position[1] + self.startY, self.positionInPath, enemySpeeds[self.index])
        return active
    def hit(self, powerups):
        self.numberOfHits -= 1
        if self.numberOfHits <= 0:
            powerUpStrategy[self.index](powerups, self.actor.x, self.actor.y)
            return True
        self.lastHit = 5
        return False

enemyStart = [[5, 275,   245], [5, 261,   315], [5, 245,   381], [5, 230,   454], [5, 210,   529],
              [5, 513,   650], [5, 496,   738], [5, 483,   805], [5, 465,   877], [5, 445,   944],
              [0, 106,  1000], [7, 188,  1190], [7, 208,  1264], [7, 221,  1346], [7, 237,  1436],
              [7, 253,  1509], [0, 106,  1833], [1, 444,  2000], [1, 444,  2080], [1, 444,  2160],
              [4, 122,  2602], [4, 173,  2659], [4, 232,  2728], [4, 297,  2798], [4, 353,  2879],
              [4, 298,  2968], [4, 223,  3046], [4, 160,  3120], [1, 444,  3620], [1, 444,  3700],
              [1, 444,  3780], [2,  94,  3960], [2,  94,  4040], [2,  94,  4120], [5, 441,  4489],
              [5, 441,  4573], [5, 180,  4760], [5, 180,  4807], [5, 180,  4860], [5, 441,  4971],
              [5, 441,  5016], [5, 441,  5080], [4, 140,  5416], [4, 408,  5540], [4, 133,  5618],
              [4, 419,  5694], [5, 233,  5908], [7, 157,  6457], [7, 171,  6558], [7, 198,  6617],
              [7, 234,  6673], [3, 385,  7027], [3, 378,  7154], [3, 352,  7238], [5, 392,  7564],
              [5, 339,  7597], [5, 281,  7631], [5, 237,  7678], [5, 194,  7780], [6,  94,  7987],
              [6, 114,  8175], [6, 161,  8300], [6, 209,  8484], [1, 252,  9000], [1, 252,  9080],
              [1, 252,  9160], [1, 252,  9240], [1, 252,  9320], [5, 192,  9688], [5, 174,  9731],
              [5, 150,  9782], [5, 130,  9835], [5, 111,  9899], [0, 164, 10644], [3, 480, 10903],
              [3, 431, 10943], [3, 394, 10979], [3, 350, 11017], [3, 292, 11071], [8, 117, 11392],
              [8, 125, 11507], [8, 151, 11611], [8, 179, 11756], [8, 131, 11988], [8, 100, 12098],
              [8,  68, 12249], [8, 97,  12413], [0, 189, 12842], [5, 190, 13524], [5, 210, 13561],
              [5, 231, 13598], [5, 247, 13637], [5, 488, 13686], [5, 454, 13712], [5, 432, 13745],
              [5, 414, 13769], [5, 150, 13811], [5, 438, 13845], [5, 147, 13882], [5, 433, 13919],
              [5, 143, 13958], [5, 428, 13992], [5, 153, 14033], [5, 416, 14075], [8, 138, 14288],
              [8, 151, 14327], [8, 138, 14381], [8, 121, 14415], [8,  93, 14461], [0, 181, 14915],
              [9, 300, 15369]]

class EnemyList:
    def __init__(self):
        self.listEnemies = []
        self.nextEnemyIndex = 0
        self.allPolygons = []
    def draw(self):
        for enemy in self.listEnemies:
            enemy.draw()
    def update(self, globalTime, bullets, spaceShip):
        for i in reversed(range(len(self.listEnemies))):
            active = self.listEnemies[i].update(bullets, spaceShip)
            if not active:
                del self.listEnemies[i]
        if self.nextEnemyIndex < len(enemyStart):
            if globalTime >= enemyStart[self.nextEnemyIndex][2]:
                self.listEnemies.append(Enemy(enemyStart[self.nextEnemyIndex][0], enemyStart[self.nextEnemyIndex][1]))
                self.nextEnemyIndex += 1
        self.allPolygons = []
        for enemy in self.listEnemies:
            polygon = translate(enemy.polygon, enemy.actor.x, enemy.actor.y)
            self.allPolygons.append(polygon)
    def destroyEnemy(self, index, explosions):
        explosions.append(1, self.listEnemies[index].actor.x, self.listEnemies[index].actor.y)
        del self.listEnemies[index]
        del self.allPolygons[index]
    def laserHit(self, index, powerups, explosions):
        destroyed = self.listEnemies[index].hit(powerups)
        if destroyed:
            self.destroyEnemy(index, explosions)

shipName = 'spaceship'
shipPolyCoords = [(-48, -5), (4, -9), (10, -3), (40, 1), (19, 5), (-1, 5), (-1, 18), (-37, 18)]

class SpaceShip:
    def __init__(self):
        self.actor = Actor(shipName, (100,300))
        self.centredPolygon = Polygon(shipPolyCoords)
        self.polygon = translate(self.centredPolygon, self.actor.x, self.actor.y)
        self.numberOfLasers = 1
        self.vulnerable = True
        self.lastHit = 0
        self.numberOfLives = 3
    def draw(self):
        if self.vulnerable:
            self.actor.draw()
        elif self.lastHit < 120 and 0 == (self.lastHit // 3) % 2:
            self.actor.draw()
    def update(self):
        if not self.vulnerable:
            self.lastHit -= 1
            if self.lastHit == 0:
                self.vulnerable = True
    def translate(self, dispX, dispY):
        self.actor.x += dispX
        self.actor.y += dispY
        if self.actor.x < 45:
            self.actor.x = 45
        elif self.actor.x > 800-48:
            self.actor.x = 800-48
        if self.actor.y < 9:
            self.actor.y = 9
        elif self.actor.y > 600-18:
            self.actor.y = 600-18
        self.polygon = translate(self.centredPolygon, self.actor.x, self.actor.y)
    def shootLaser(self, lasers):
        if self.numberOfLives != 0 and self.lastHit < 120:
            startY = (self.numberOfLasers - 1) / 2 * 10
            for i in range(self.numberOfLasers):
                lasers.addLaser((self.actor.x + 20, self.actor.y+2 - startY + i * 10))
    def hit(self, explosions, showScore):
        if self.vulnerable:
            self.numberOfLives -= 1
            showScore.update(self.numberOfLives)
            self.lastHit = 180
            self.numberOfLasers = 1
            explosions.append(1, self.actor.x, self.actor.y)
            self.actor.x = 100
            self.actor.y = 300
            self.polygon = translate(self.centredPolygon, self.actor.x, self.actor.y)
            self.vulnerable = False

class Lasers:
    def __init__(self):
        self.allLasers = []
    def draw(self):
        for laser in self.allLasers:
            laser.draw()
    def update(self):
        for i in reversed(range(len(self.allLasers))):
            self.allLasers[i].x += 20
            if self.allLasers[i].x > 800+10:
                del self.allLasers[i]
    def addLaser(self, position):
        self.allLasers.append(Actor('laser', position))

class PowerUps():
    def __init__(self):
        self.powerups = []
    def draw(self):
        for powerup in self.powerups:
            powerup.draw()
    def update(self):
        for i in reversed(range(len(self.powerups))):
            self.powerups[i].x -= 1
            if self.powerups[i].x < -16:
                del self.powerups[i]
    def append(self, posX, posY):
        self.powerups.append(Actor("plus", (posX, posY)))

class ShowScore():
    def __init__(self, number):
        self.ship = Actor("lives", (37, 20))
        self.number1 = Actor("text_0", (80, 20))
        self.number2 = Actor("text_"+str(number), (108, 20))
    def draw(self):
        self.ship.draw()
        self.number1.draw()
        self.number2.draw()
    def update(self, number):
        self.number2.image = "text_"+str(number)

explosionNames = ["impact_", "explosion_"]
explosionFrames = [8, 10]

class Explosion():
    def __init__(self, index, posX, posY):
        self.index = index
        self.actor = Actor(explosionNames[index]+"0", (posX, posY))
        self.imageIndex = 0
        self.frame = 0
    def draw(self):
        self.actor.draw()
    def update(self):
        self.frame += 1
        if 0 == self.frame % 6:
            self.imageIndex += 1
            if self.imageIndex < explosionFrames[self.index]:
                self.actor.image = explosionNames[self.index]+str(self.imageIndex)
            else:
                return False
        return True

class ExplosionList():
    def __init__(self):
        self.explosions = []
    def draw(self):
        for explosion in self.explosions:
            explosion.draw()
    def update(self):
        for i in reversed(range(len(self.explosions))):
            active = self.explosions[i].update()
            if not active:
                del self.explosions[i]
    def append(self, index, posX, posY):
        self.explosions.append(Explosion(index, posX, posY))

def collisionShipBullets(bullets, spaceShip, explosions, showScore):
    if bullets.collide(spaceShip.polygon):
        spaceShip.hit(explosions, showScore)

def collisionBulletsForeground(bullets, foreground, explosions):
    for i in reversed(range(len(bullets.bullets))):
        for poly in foreground.polygons:
            point = Point(bullets.bullets[i].actor.x, bullets.bullets[i].actor.y)
            if point.within(poly):
                explosions.append(0, point.x, point.y)
                del bullets.bullets[i]
                break

def collisionLaserForeground(lasers, foreground, explosions):
    for j in reversed(range(len(lasers.allLasers))):
        laserLine = LineString([(lasers.allLasers[j].x - 26, lasers.allLasers[j].y),
                                (lasers.allLasers[j].x + 26, lasers.allLasers[j].y)])
        for i in reversed(range(len(foreground.polygons))):
            if not laserLine.disjoint(foreground.polygons[i]):
                explosions.append(0, lasers.allLasers[j].x + 26, lasers.allLasers[j].y)
                del lasers.allLasers[j]
                break

def collisionLaserEnemies(lasers, enemyList, explosions, powerups):
    for j in reversed(range(len(lasers.allLasers))):
        laserLine = LineString([(lasers.allLasers[j].x - 26, lasers.allLasers[j].y),
                                (lasers.allLasers[j].x + 26, lasers.allLasers[j].y)])
        for i in reversed(range(len(enemyList.allPolygons))):
            if not laserLine.disjoint(enemyList.allPolygons[i]):
                explosions.append(0, lasers.allLasers[j].x + 26, lasers.allLasers[j].y)
                enemyList.laserHit(i, powerups, explosions)
                del lasers.allLasers[j]
                break

def collisionShipPolygons(spaceShip, polygons):
    for i in reversed(range(len(polygons))):
        if not spaceShip.polygon.disjoint(polygons[i]):
            return True, i
    return False, 0

def collisionShipForeground(spaceShip, foreground, explosions, showScore):
    hit, index = collisionShipPolygons(spaceShip, foreground.polygons)
    if hit:
        spaceShip.hit(explosions, showScore)

def collisionShipEnemies(spaceShip, enemyList, explosions, showScore, powerups):
    hit, index = collisionShipPolygons(spaceShip, enemyList.allPolygons)
    if hit:
        spaceShip.hit(explosions, showScore)
        enemyList.laserHit(index, powerups, explosions)

def collisionShipPowerUps(spaceShip, powerups):
    for i in reversed(range(len(powerups.powerups))):
        polygon = Polygon([(powerups.powerups[i].x-16, powerups.powerups[i].y-16),
                           (powerups.powerups[i].x-16, powerups.powerups[i].y+16),
                           (powerups.powerups[i].x+16, powerups.powerups[i].y+16),
                           (powerups.powerups[i].x+16, powerups.powerups[i].y-16)])
        if not spaceShip.polygon.disjoint(polygon):
            spaceShip.numberOfLasers += 1
            del powerups.powerups[i]

foreground = Foreground()
bullets = AllBullets()
enemyList = EnemyList()
spaceShip = SpaceShip()
lasers = Lasers()
powerups = PowerUps()
showScore = ShowScore(spaceShip.numberOfLives)
explosions = ExplosionList()

Status = Enum('Status', 'PLAYING GAME_OVER LEVEL_CLEAR')
gameStatus = Status.PLAYING
globalTime = 0

def draw():
    if isFastComputer:
        drawBackground(screen, globalTime)
    else:
        screen.clear()
    foreground.draw()
    enemyList.draw()
    if gameStatus != Status.GAME_OVER:
        spaceShip.draw()
    powerups.draw()
    lasers.draw()
    bullets.draw()
    explosions.draw()
    showScore.draw()
    if gameStatus == Status.GAME_OVER:
        screen.draw.text("GAME OVER", center=(400, 260), fontsize=55, color=(255,0,0))
    elif gameStatus == Status.LEVEL_CLEAR:
        screen.draw.text("LEVEL CLEAR", center=(400, 260), fontsize=55, color=(0,0,255))

def update():
    global globalTime, gameStatus
    globalTime += 1
    bullets.update()
    lasers.update()
    powerups.update()
    foreground.update(globalTime)
    enemyList.update(globalTime, bullets, spaceShip)
    if gameStatus != Status.GAME_OVER:
        if keyboard.left:  spaceShip.translate(-4,  0)
        if keyboard.right: spaceShip.translate( 4,  0)
        if keyboard.up:    spaceShip.translate( 0, -4)
        if keyboard.down:  spaceShip.translate( 0,  4)
    spaceShip.update()
    explosions.update()
    collisionBulletsForeground(bullets, foreground, explosions)
    collisionLaserForeground(lasers, foreground, explosions)
    collisionLaserEnemies(lasers, enemyList, explosions, powerups)
    if gameStatus == Status.PLAYING:
        collisionShipBullets(bullets, spaceShip, explosions, showScore)
        collisionShipForeground(spaceShip, foreground, explosions, showScore)
        collisionShipEnemies(spaceShip, enemyList, explosions, showScore, powerups)
        collisionShipPowerUps(spaceShip, powerups)
        if spaceShip.numberOfLives == 0:
            gameStatus = Status.GAME_OVER
        elif globalTime > enemyStart[-1][2] + 10 and len(enemyList.listEnemies) == 0:
            gameStatus = Status.LEVEL_CLEAR

def on_key_down(key):
    if key == keys.SPACE:
        spaceShip.shootLaser(lasers)

pgzrun.go()