Listing30_FinalGame

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

Attribution

Make your own retro platformer, pages 50-55, by Jordi Sontanja.

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

Original Python code


# Player movement prototype program:
# - Cursor left-right: move player
# - Cursor up: player jump
# - Space: shoot a rainbow

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

platformNames = ['platform_rock_1', 'platform_rock_2', 'platform_rock_3', 'platform_rock_4',
                 'platform_grass_1', 'platform_grass_2', 'platform_grass_3', 'platform_grass_4',
                 'platform_tree_1', 'platform_tree_2', 'platform_tree_3', 'platform_tree_4', 'platform_tree_5',
                 'platform_cloud_1', 'platform_cloud_2', 'platform_cloud_3']

platformLines = [[(-194, -23), (-182, -32), (-138, -35), (-104, -38), (-80, -43), (-15, -34), (34, -36), (59, -42), (93, -37), (126, -28), (181, -26), (192, -24)],
                 [(-134, -25), (-98, -31), (-37, -37), (25, -35), (98, -34), (131, -25)], [(-59, -10), (-29, -27), (-1, -34), (36, -29), (56, -15)],
                 [(-174, -19), (-137, -35), (-93, -34), (-1, -28), (78, -38), (171, -30)], [(-145, -8), (-129, -22), (-89, -37), (-18, -29), (52, -36), (104, -28), (146, -8)],
                 [(-153, -8), (-131, -16), (-100, -16), (-22, -10), (44, -16), (95, -8), (150, -1)],
                 [(-166, -12), (-152, -23), (-97, -29), (-22, -17), (24, -18), (82, -29), (142, -25), (166, -17)], [(-51, 1), (-39, -12), (-13, -21), (22, -22), (51, -9)],
                 [(-166, -38), (-135, -46), (-96, -40), (-62, -20), (-30, 9), (-8, 20), (16, 25), (92, 16), (145, 18), (166, 23)],
                 [(-165, 10), (-110, -5), (-66, -21), (-23, -41), (16, -51), (63, -51), (124, -29), (162, -25)],
                 [(-47, -9), (-5, -25), (66, -30)], [(-134, -18), (-84, 2), (-57, 1), (25, -20), (62, -18), (85, -12), (133, -14)],
                 [(-147, -45), (-122, -48), (-66, -59), (-21, -58), (18, -42), (53, -13), (109, 13), (143, 20)],
                 [(-114, -4), (-82, -14), (-39, -17), (-12, -33), (37, -35), (52, -22), (87, -18), (113, -8)],
                 [(-158, -4), (-119, -8), (-72, -23), (-45, -19), (-4, -35), (24, -23), (61, -22), (84, -8), (130, -11), (159, 1)],
                 [(-237, -5), (-216, -15), (-168, -14), (-139, -29), (-92, -27), (-49, -42), (7, -21), (31, -40), (61, -41), (84, -22), (141, -33), (180, -12), (237, 1)]]

platforms = [(3,676,599),(3,403,597),(3,90,599),(0,663,433),(0,119,429),(0,121,273),(0,654,278),(1,386,122),
             (3,243,-23),(3,547,-25),(3,391,-203),(2,98,-337),(2,684,-339),(2,289,-403),(2,505,-404),(2,90,-537),
             (2,684,-546),(1,391,-594),(6,147,-728),(6,454,-731),(6,697,-736),(6,258,-878),(6,559,-878),(6,109,-1039),
             (6,704,-1036),(5,400,-1219),(7,113,-1335),(7,708,-1334),(6,400,-1439),(7,166,-1533),(7,647,-1535),
             (4,396,-1622),(7,110,-1719),(7,680,-1729),(6,119,-1841),(6,676,-1833),(4,402,-1983),(6,128,-2144),
             (6,429,-2146),(6,720,-2149),(9,153,-2310),(11,677,-2302),(12,665,-2511),(8,161,-2536),(10,131,-2594),
             (11,687,-2701),(9,154,-2779),(12,660,-2841),(10,700,-2919),(10,284,-2851),(15,219,-3039),(15,659,-3036),
             (14,192,-3219),(14,618,-3221),(14,397,-3378),(13,125,-3501),(13,668,-3511),(14,396,-3614),(15,120,-3774),
             (15,690,-3766),(13,409,-3921),(14,181,-4025),(14,645,-4034),(14,404,-4173),(15,183,-4361),(15,621,-4356),]

maxHeightPlatform = 124
class AllPlatforms():
    def __init__(self):
        self.platformActors = [Actor(platformNames[platforms[i][0]], (platforms[i][1], platforms[i][2])) for i in range(len(platforms))]
        self.platformLineStrings = []
        for i in range(len(platforms)):
            points = [(platformLines[platforms[i][0]][j][0] + platforms[i][1],
                       platformLines[platforms[i][0]][j][1] + platforms[i][2])
                      for j in range(len(platformLines[platforms[i][0]]))]
            self.platformLineStrings.append(LineString(points))
    def draw(self):
        for platform in self.platformActors:
            if platform.y > -maxHeightPlatform/2 and platform.y < 600+maxHeightPlatform/2:
                platform.draw()
    def update(self, screenPosition):
        for i in range(len(platforms)):
            self.platformActors[i].x = platforms[i][1]
            self.platformActors[i].y = platforms[i][2] - screenPosition
    def getMaxScreenPosition(self):
        return -platforms[-1][2] - maxHeightPlatform/2

def RectanglesIntersect(centreA, halfSizeA, centreB, halfSizeB):
    if centreA[0] - halfSizeA[0] < centreB[0] + halfSizeB[0] and centreA[0] + halfSizeA[0] > centreB[0] - halfSizeB[0]:
        if centreA[1] - halfSizeA[1] < centreB[1] + halfSizeB[1] and centreA[1] + halfSizeA[1] > centreB[1] - halfSizeB[1]:
            return True
    return False

rainbowHalfSize = 39
rainbowTimeLife = 200
class Rainbow:
    def __init__(self, centreX, centreY, creationTime):
        self.centre = [centreX, centreY]
        self.timeFromCreation = creationTime
        self.rainbowActor = Actor('rainbow', (centreX, centreY - rainbowHalfSize/2))
        points = []
        for i in range(0, 180+20, 20):
            points.append((self.centre[0] + rainbowHalfSize*math.cos(math.pi*i/180)*0.75, self.centre[1] - rainbowHalfSize*math.sin(math.pi*i/180)))
        self.lineString = LineString(points)
    def draw(self, screenPosition):
        if self.timeFromCreation >= 0:
            self.rainbowActor.y = self.centre[1] - rainbowHalfSize/2 - screenPosition
            self.rainbowActor.draw()
    def update(self, allEnemies, allCollectables):
        if self.timeFromCreation == 0:
            for i in reversed(range(len(allEnemies.enemies))):
                if self.lineString.intersects(allEnemies.enemies[i].lineString):
                    allEnemies.killEnemy(i, allCollectables)
        self.timeFromCreation += 1

rainbowAccelerationDown = 1
class FallingRainbow:
    def __init__(self, centreX, centreY):
        self.centre = [centreX, centreY]
        self.rainbowActor = Actor('falling_rainbow', (centreX, centreY - rainbowHalfSize/2))
        self.speedY = 0
    def draw(self, screenPosition):
        self.rainbowActor.y = self.centre[1] - rainbowHalfSize/2 - screenPosition
        self.rainbowActor.draw()
    def update(self, allEnemies, allRainbows, allCollectables):
        self.speedY += rainbowAccelerationDown
        self.centre[1] += self.speedY
        centerCollision = [self.centre[0], self.centre[1] + self.speedY * 2]
        halfSizeCollision = [rainbowHalfSize, (rainbowHalfSize + self.speedY) / 2]
        for i in reversed(range(len(allEnemies.enemies))):
            if RectanglesIntersect(centerCollision, halfSizeCollision, allEnemies.enemies[i].centre, [allEnemies.enemyHalfSize(), allEnemies.enemyHalfSize()]):
                allEnemies.killEnemy(i, allCollectables)
        for i in reversed(range(len(allRainbows.rainbows))):
            if RectanglesIntersect(centerCollision, halfSizeCollision, allRainbows.rainbows[i].centre, [rainbowHalfSize, rainbowHalfSize/2]):
                allRainbows.rainbowFall(i)

class AllRainbows:
    def __init__(self):
        self.rainbows = []
        self.fallingRainbows = []
    def restart(self):
        self.rainbows = []
        self.fallingRainbows = []
    def draw(self, screenPosition):
        for rainbow in self.rainbows:
            rainbow.draw(screenPosition)
        for fallingRainbow in self.fallingRainbows:
            fallingRainbow.draw(screenPosition)
    def update(self, allEnemies, allCollectables, screenPosition):
        for i in reversed(range(len(self.rainbows))):
            self.rainbows[i].update(allEnemies, allCollectables)
            if self.rainbows[i].timeFromCreation > rainbowTimeLife:
                self.rainbowFall(i)
        for i in reversed(range(len(self.fallingRainbows))):
            self.fallingRainbows[i].update(allEnemies, self, allCollectables)
            if self.fallingRainbows[i].centre[1]-screenPosition-rainbowHalfSize > 600:
                del self.fallingRainbows[i]
    def rainbowFall(self, index):
        self.fallingRainbows.append(FallingRainbow(self.rainbows[index].centre[0], self.rainbows[index].centre[1]))
        del self.rainbows[index]
    def append(self, posX, posY, time):
        self.rainbows.append(Rainbow(posX, posY, time))
    def addRainbows(self, numberOfRainbows, posX, posY, directionX):
        for i in range(numberOfRainbows):
            self.append(posX + directionX * rainbowHalfSize + directionX * i * (rainbowHalfSize-2)*2, posY, -i*10)

enemyNames = [['green_worm_right', 'green_worm_left'],
              ['red_worm_right', 'red_worm_left'],
              ['bee_1_right', 'bee_1_left',
               'bee_2_right', 'bee_2_left',
               'bee_3_right', 'bee_3_left']]

enemies = [(0,1,111,379),(0,-1,645,227),(0,-1,383,69),(0,-1,228,-67),(0,1,548,-69),(0,1,387,-247),(0,1,378,-647),(0,1,449,-921),
           (0,-1,334,-920),(1,-1,218,-1083),(1,1,603,-1080),(0,-1,396,-1247),(0,1,399,-1473),(0,-1,390,-1666),(0,-1,343,-2033),
           (0,-1,457,-2034),(0,1,74,-1879),(0,1,174,-1880),(0,-1,619,-1874),(0,-1,737,-1876),(1,-1,196,-2379),(1,1,717,-2339),
           (0,-1,181,-2640),(0,-1,333,-2896),(2,-1,623,-2798),(2,1,169,-2724),(2,1,132,-3378),(2,-1,675,-3376),(2,-1,674,-3624),
           (2,1,120,-3621),(2,1,136,-3914),(2,-1,647,-3930),(2,1,132,-4189),(2,-1,690,-4193),(1,1,57,-3536),(1,-1,743,-3552),
           (1,-1,779,-3814),(1,1,23,-3826),(1,-1,393,-4229),(1,1,414,-4228)]

enemyHalfSize = 12
enemyAccelerationDown = 0.5
enemyTerminalSpeed = 8
enemyLateralSpeed = 2
enemyFlyingVerticalSpeed = 1

class Enemy:
    def __init__(self, centreX, centreY, indexEnemy, directionX):
        self.centre = [centreX, centreY]
        self.centredLineString = LineString([(-enemyHalfSize,-enemyHalfSize),
                                             (-enemyHalfSize,enemyHalfSize),
                                             (enemyHalfSize,enemyHalfSize),
                                             (enemyHalfSize,-enemyHalfSize)])
        self.lineString = translate(self.centredLineString, self.centre[0], self.centre[1])
        self.speedX = directionX * enemyLateralSpeed
        self.speedY = 0
        if indexEnemy == 2:
            self.speedY = enemyFlyingVerticalSpeed
        self.index = indexEnemy
        self.actors = [Actor(name) for name in enemyNames[indexEnemy]]
        self.active = False
    def draw(self, screenPosition):
        if self.active and self.centre[1] - screenPosition > -enemyHalfSize and self.centre[1] - screenPosition < 600+enemyHalfSize:
            indexActor = ((self.centre[0]//6) % (len(enemyNames[self.index])//2))*2
            if self.speedX < 0:
                indexActor += 1
            self.actors[indexActor].x = self.centre[0]
            self.actors[indexActor].y = self.centre[1] - screenPosition
            self.actors[indexActor].draw()
    def update(self, allPlatforms, allRainbows, screenPosition):
        if self.centre[1] - screenPosition > -enemyHalfSize/2:
            self.active = True
        if not self.active or self.centre[1] - screenPosition > 600+enemyHalfSize:
            return
        if self.index == 0:
            if self.speedX > 0:
                lineIntersection = LineString([(self.centre[0]+enemyHalfSize, self.centre[1]-6),
                                               (self.centre[0]+enemyHalfSize, self.centre[1]+enemyHalfSize+16)])
            else:
                lineIntersection = LineString([(self.centre[0]-enemyHalfSize, self.centre[1]-6),
                                               (self.centre[0]-enemyHalfSize, self.centre[1]+enemyHalfSize+16)])
        else:
            lineIntersection = LineString([(self.centre[0]-enemyHalfSize, self.centre[1]-enemyHalfSize),
                                           (self.centre[0]-enemyHalfSize, self.centre[1]+enemyHalfSize+self.speedY+2),
                                           (self.centre[0]+enemyHalfSize, self.centre[1]+enemyHalfSize+self.speedY+2),
                                           (self.centre[0]+enemyHalfSize, self.centre[1]-enemyHalfSize)])
        intersectionFound = False
        for platform in allPlatforms.platformLineStrings:
            intersection = lineIntersection.intersection(platform)
            if not intersection.is_empty:
                if self.index != 2:
                    if intersection.geom_type == 'MultiPoint':
                        self.centre[1] = min(intersection.geoms, key=lambda x: x.coords[0][1]).coords[0][1] - enemyHalfSize
                    elif intersection.geom_type == 'Point':
                        self.centre[1] = intersection.coords[0][1] - enemyHalfSize
                intersectionFound = True
                if self.index != 2:
                    self.speedY = 0
        for rainbow in allRainbows.rainbows:
            intersection = lineIntersection.intersection(rainbow.lineString)
            if not intersection.is_empty:
                if self.index == 0 or self.index == 2:
                    self.speedX = -self.speedX
                elif self.index == 1 and self.speedY < 1:
                    self.speedX = -self.speedX
                else:
                    if intersection.geom_type == 'MultiPoint':
                        self.centre[1] = min(intersection.geoms, key=lambda x: x.coords[0][1]).coords[0][1] - enemyHalfSize
                    elif intersection.geom_type == 'Point':
                        self.centre[1] = intersection.coords[0][1] - enemyHalfSize
        if not intersectionFound and self.index == 0:
            self.speedX = -self.speedX
        elif intersectionFound and self.index == 2:
            self.speedY = -self.speedY
        elif self.centre[0] >= 800 - enemyHalfSize:
            self.speedX = -enemyLateralSpeed
        elif self.centre[0] <= enemyHalfSize:
            self.speedX = enemyLateralSpeed
        if self.centre[1] > 600 - enemyHalfSize:
            self.centre[1] = 600 - enemyHalfSize
            if self.index != 2:
                self.speedY = 0
        elif self.index == 1:
            self.speedY += enemyAccelerationDown
            if self.speedY > enemyTerminalSpeed:
                self.speedY = enemyTerminalSpeed
        self.centre[0] += self.speedX
        self.centre[1] += self.speedY
        self.lineString = translate(self.centredLineString, self.centre[0], self.centre[1])

class AllEnemies:
    def __init__(self):
        self.restart()
    def restart(self):
        self.enemies = [Enemy(enemies[i][2], enemies[i][3], enemies[i][0], enemies[i][1]) for i in range(len(enemies))]
    def draw(self, screenPosition):
        for enemy in self.enemies:
            enemy.draw(screenPosition)
    def update(self, allPlatforms, allRainbows, screenPosition):
        for enemy in self.enemies:
            enemy.update(allPlatforms, allRainbows, screenPosition)
    def killEnemy(self, index, allCollectables):
        allCollectables.addCollectable(self.enemies[index].centre[0], self.enemies[index].centre[1])
        del self.enemies[index]
    def enemyHalfSize(self):
        return enemyHalfSize

collectableNames = ['collectable_1', 'collectable_2', 'collectable_3', 'collectable_4', 'collectable_5', 'collectable_rainbow']
flyingCollectableNames = ['flying_candy_1', 'flying_candy_2', 'flying_candy_3', 'flying_candy_4']

collectableHalfSize = 10

class CollectableFlying:
    def __init__(self, startX, startY):
        self.centre = [startX, startY]
        self.speedX = randint(-startX,800-startX)/100
        self.speedY = randint(-6,-4)
        self.accelerationDown = 0.1
        self.actors = [Actor(name, (startX, startY)) for name in flyingCollectableNames]
        self.indexActor = 0
    def draw(self, screenPosition):
        index = (self.indexActor // 4) % len(flyingCollectableNames)
        self.actors[index].x = self.centre[0]
        self.actors[index].y = self.centre[1] - screenPosition
        self.actors[index].draw()
    def update(self, screenPosition):
        self.centre[0] += self.speedX
        if self.centre[0] < collectableHalfSize or self.centre[0] > 800 - collectableHalfSize:
            self.centre[0] -= self.speedX
            self.speedX = 0
        self.speedY += self.accelerationDown
        self.centre[1] += self.speedY
        self.indexActor += 1
    def checkCollission(self, allPlatforms):
        if self.speedY > 1:
            bottomLine = LineString([(self.centre[0]-collectableHalfSize, self.centre[1]),
                                     (self.centre[0], self.centre[1]+collectableHalfSize),
                                     (self.centre[0]+collectableHalfSize, self.centre[1])])
            for platform in allPlatforms.platformLineStrings:
                intersection = bottomLine.intersection(platform)
                if not intersection.is_empty:
                    point = [self.centre[0], self.centre[1]]
                    if intersection.geom_type == 'MultiPoint':
                        point[1] = min(intersection.geoms, key=lambda x: x.coords[0][1]).coords[0][1] - collectableHalfSize
                    elif intersection.geom_type == 'Point':
                        point[1] = intersection.coords[0][1] - collectableHalfSize
                    return True, point
        return False, [0,0]

class Collectable:
    def __init__(self, startX, startY):
        self.centre = [startX, startY]
        self.creationTime = 0
        self.actor = Actor(collectableNames[randint(0, 5)], (startX, startY))
    def draw(self, screenPosition):
        self.actor.y = self.centre[1] - screenPosition
        self.actor.draw()
    def update(self):
        self.creationTime += 1
    def isRainbow(self):
        if self.actor.image == 'collectable_rainbow':
            return True
        return False

class Collectables:
    def __init__(self):
        self.collectablesFlying = []
        self.collectables = []
    def restart(self):
        self.collectablesFlying = []
        self.collectables = []
    def addCollectable(self, startX, startY):
        self.collectablesFlying.append(CollectableFlying(startX, startY))
    def draw(self, screenPosition):
        for collectable in self.collectablesFlying:
            collectable.draw(screenPosition)
        for collectable in self.collectables:
            collectable.draw(screenPosition)
    def update(self, allPlatforms, player, screenPosition):
        for collectable in self.collectablesFlying:
            collectable.update(screenPosition)
        for collectable in self.collectables:
            collectable.update()
        for i in reversed(range(len(self.collectablesFlying))):
            collission, point = self.collectablesFlying[i].checkCollission(allPlatforms)
            if collission:
                self.collectables.append(Collectable(point[0], point[1]))
                del self.collectablesFlying[i]
            elif self.collectablesFlying[i].centre[1] > 600 + collectableHalfSize:
                del self.collectablesFlying[i]
        for i in reversed(range(len(self.collectables))):
            if RectanglesIntersect(player.centre, player.playerHalfSize(), self.collectables[i].centre, [collectableHalfSize, collectableHalfSize]):
                if self.collectables[i].isRainbow():
                    player.addRainbow()
                self.removeCollectable(i)
    def removeCollectable(self, index):
        del self.collectables[index]

playerHalfSizeX = 10
playerHalfSizeY = 18
playerAccelerationDown = 0.5
playerJumpSpeed = 14
playerTerminalSpeed = 12
playerLateralSpeed = 4
coyoteTimeVerticalSpeed = 4
playerVerticalSpeedToDestroyRainbow = 5
playerMinTimeBetweenRainbows = 16

playerImageNames = ['player_stand_right', 'player_walk_1_right', 'player_stand_right', 'player_walk_2_right', 'player_jump_up_right', 'player_jump_down_right',
                    'player_stand_left', 'player_walk_1_left', 'player_stand_left', 'player_walk_2_left', 'player_jump_up_left', 'player_jump_down_left',
                    'player_collided_1', 'player_collided_2']
class Player:
    def __init__(self):
        self.centre = [400, 500]
        self.centredLineString = LineString([(-playerHalfSizeX,-playerHalfSizeY*0),
                                             (-playerHalfSizeX,playerHalfSizeY),
                                             (playerHalfSizeX,playerHalfSizeY),
                                             (playerHalfSizeX,-playerHalfSizeY*0)])
        self.lineString = translate(self.centredLineString, self.centre[0], self.centre[1])
        self.polygon = Polygon(self.lineString)
        self.speedY = 0
        self.walking = False
        self.directionX = -1
        self.jumping = False
        self.actors = [Actor(name) for name in playerImageNames]
        self.numberOfRainbows = 1
        self.lastRainbowShot = playerMinTimeBetweenRainbows
        self.active = True
        self.lives = 3
    def draw(self, screenPosition):
        indexImage = 0
        if self.active:
            if self.jumping:
                if self.speedY < 0:
                    indexImage = 4
                else:
                    indexImage = 5
            elif self.walking:
                indexImage = (self.centre[0]//13) % 4
            if self.directionX < 0:
                indexImage += 6
        else:
            indexImage = 12 + (self.centre[0]//13) % 2
        self.actors[indexImage].x = self.centre[0]
        self.actors[indexImage].y = self.centre[1] - screenPosition
        self.actors[indexImage].draw()
    def intersectPlatform(self, platform, collidingObject):
        intersection = collidingObject.intersection(platform)
        if not intersection.is_empty:
            newPositionY = self.centre[1]
            if intersection.geom_type == 'MultiPoint':
                newPositionY = min(intersection.geoms, key=lambda x: x.coords[0][1]).coords[0][1] - playerHalfSizeY
            elif intersection.geom_type == 'Point':
                newPositionY = intersection.coords[0][1] - playerHalfSizeY
            elif intersection.geom_type == 'LineString':
                newPositionY = min(intersection.coords, key=lambda x: x[1])[1] - playerHalfSizeY
            if self.centre[1] > newPositionY:
                self.centre[1] = newPositionY
            self.speedY = 0
            self.jumping = False
            return True
        return False
    def intersectEnemy(self, allEnemies):
        for enemy in allEnemies.enemies:
            if enemy.active:
                if RectanglesIntersect(self.centre, [playerHalfSizeX, playerHalfSizeY], enemy.centre, [allEnemies.enemyHalfSize(), allEnemies.enemyHalfSize()]):
                    return True
        return False
    def addRainbow(self):
        if self.numberOfRainbows < 3:
            self.numberOfRainbows += 1
    def update(self, allPlatforms, allRainbows, allCollectables, allEnemies):
        if self.lives == 0:
            return
        if not self.active:
            self.speedY += playerAccelerationDown
            self.centre[0] += self.speedX // 2
            self.centre[1] += self.speedY // 2
            if self.centre[1] > 600 + playerHalfSizeY*6:
                self.lives -= 1
                if self.lives > 0:
                    self.centre = [400, 500]
                    self.speedX = 0
                    self.speedY = 0
                    self.numberOfRainbows = 1
                    self.lineString = translate(self.centredLineString, self.centre[0], self.centre[1])
                    self.polygon = Polygon(self.lineString)
                    allRainbows.restart()
                    allCollectables.restart()
                    allEnemies.restart()
                    self.active = True
            return
        if self.speedY >= 0:
            if not self.jumping:
                collidingObject = self.polygon
            else:
                collidingObject = LineString([(self.centre[0]-playerHalfSizeX, self.centre[1]+playerHalfSizeY-2),
                                              (self.centre[0]-playerHalfSizeX, self.centre[1]+playerHalfSizeY+self.speedY+2),
                                              (self.centre[0]+playerHalfSizeX, self.centre[1]+playerHalfSizeY+self.speedY+2),
                                              (self.centre[0]+playerHalfSizeX, self.centre[1]+playerHalfSizeY-2)])
            for platform in allPlatforms.platformLineStrings:
                self.intersectPlatform(platform, collidingObject)
            previousSpeed = self.speedY
            for i in reversed(range(len(allRainbows.rainbows))):
                if allRainbows.rainbows[i].timeFromCreation >= 0:
                    if self.intersectPlatform(allRainbows.rainbows[i].lineString, collidingObject):
                        if previousSpeed >= playerVerticalSpeedToDestroyRainbow:
                            allRainbows.rainbowFall(i)
            if not self.jumping and self.speedY >= coyoteTimeVerticalSpeed:
                self.jumping = True
        if self.centre[1] > 600 - playerHalfSizeY:
            self.centre[1] = 600 - playerHalfSizeY
            self.speedY = 0
            self.jumping = False
        else:
            self.speedY += playerAccelerationDown
        if self.speedY > playerTerminalSpeed:
            self.speedY = playerTerminalSpeed
        self.centre[1] += self.speedY
        if self.intersectEnemy(allEnemies):
            self.active = False
            self.speedY = -playerJumpSpeed
            if self.centre[0] > 400:
                self.speedX = -playerLateralSpeed
            else:
                self.speedX = playerLateralSpeed
        self.lineString = translate(self.centredLineString, self.centre[0], self.centre[1])
        self.polygon = Polygon(self.lineString)
        self.lastRainbowShot += 1
    def jump(self):
        if not self.jumping:
            self.speedY = -playerJumpSpeed
            self.jumping = True
    def stopJump(self):
        if self.jumping and self.speedY < -playerJumpSpeed/2:
            self.speedY = -playerJumpSpeed/2
    def left(self):
        self.centre[0] -= playerLateralSpeed
        self.directionX = -1
        if self.centre[0] < playerHalfSizeX:
            self.centre[0] = playerHalfSizeX
        self.walking = True
    def right(self):
        self.centre[0] += playerLateralSpeed
        self.directionX = 1
        if self.centre[0] > 800 - playerHalfSizeX:
            self.centre[0] = 800 - playerHalfSizeX
        self.walking = True
    def still(self):
        self.walking = False
    def playerHalfSize(self):
        return [playerHalfSizeX, playerHalfSizeY]
    def shootRainbow(self, allRainbows):
        if self.lastRainbowShot > playerMinTimeBetweenRainbows:
            self.lastRainbowShot = 0
            allRainbows.addRainbows(self.numberOfRainbows, self.centre[0] + self.directionX * (playerHalfSizeX + 2), self.centre[1] + playerHalfSizeY, self.directionX)

def ScreenPositionUpdate(player, screenPosition):
    if player.centre[1] - screenPosition < 150:
        screenPosition = player.centre[1] - 150
    elif player.centre[1] - screenPosition > 600 - 150:
        screenPosition = player.centre[1] - (600 - 150)
        if screenPosition > 0:
            screenPosition = 0
    return screenPosition

backgroundBaseColour = (95, 148, 255)
backgroundColour = (0,0,0)

def BackgroundColour(screenPosition, maxScreenPosition):
    colourScale = -screenPosition / maxScreenPosition
    if colourScale > 1:
        colourScale = 1
    return (backgroundBaseColour[0]*colourScale,
            backgroundBaseColour[1]*colourScale,
            backgroundBaseColour[2]*colourScale)

allPlatforms = AllPlatforms()
allRainbows = AllRainbows()
player = Player()
allEnemies = AllEnemies()
allCollectables = Collectables()

screenPosition = 0
maxScreenPosition = allPlatforms.getMaxScreenPosition()

levelClear = False

def draw():
    screen.fill(BackgroundColour(screenPosition, maxScreenPosition))
    allPlatforms.draw()
    allEnemies.draw(screenPosition)
    allRainbows.draw(screenPosition)
    allCollectables.draw(screenPosition)
    player.draw(screenPosition)
    for i in range(player.lives):
        screen.blit('player_icon', (10+i*14, 10))
    if player.lives == 0:
        screen.draw.text("GAME OVER", center=(400, 260), fontsize=55, color=(255,0,0))
    if levelClear:
        screen.draw.text("LEVEL CLEAR", center=(400, 260), fontsize=55, color=(0,0,255))

def update(dt):
    global screenPosition, levelClear
    if keyboard.left:
        player.left()
    elif keyboard.right:
        player.right()
    else:
        player.still()
    if keyboard.up:
        player.jump()
    else:
        player.stopJump()
    player.update(allPlatforms, allRainbows, allCollectables, allEnemies)
    screenPosition = ScreenPositionUpdate(player, screenPosition)
    allRainbows.update(allEnemies, allCollectables, screenPosition)
    allEnemies.update(allPlatforms, allRainbows, screenPosition)
    allCollectables.update(allPlatforms, player, screenPosition)
    allPlatforms.update(screenPosition)
    if player.centre[1] < -maxScreenPosition:
        levelClear = True

def on_key_down(key):
    if key == keys.SPACE:
        player.shootRainbow(allRainbows)

pgzrun.go()