# Leo Salazar
# 6/19/2024
# Today we fixed the clipping to the top and bottom of blocks and gave the player a terminal velocity
# We also tried infinitely generating platforms of screen and had them move left to right and top down
# Made different types of platforms that behave differently with the player

import pygame
import random

# constants for screen dimensions
SCREEN_W = 600
SCREEN_H = 600

# basic set up
pygame.init()
screen = pygame.display.set_mode((SCREEN_W, SCREEN_H))
pygame.display.set_caption("Platforming")
clock = pygame.time.Clock()
running = True

class Player(pygame.sprite.Sprite):

    # presets for our player (there will only be one)
    def __init__(self, group):
        super().__init__(group)
        self.image = pygame.Surface((50, 50))
        self.image.fill("red")
        self.rect = self.image.get_rect()
        self.rect.center = (SCREEN_W//2, SCREEN_H//2)

        # this sets up our player movement speed, its velocity in the y direction
        # the gravity, terminal velocity, and the jump power
        self.speed = 5
        self.vel_y = 0
        self.gravity = 0.5
        self.t_vel = 10
        self.jump_pow = -10

        # variables to keep track of jumping and standing on the ground
        self.jumping = False
        self.on_ground = False

    # this is called every game loop
    def update(self):

        # check for player movement (left, right, jump)
        keys = pygame.key.get_pressed()
        if keys[pygame.K_a]:
            self.rect.x -= self.speed
        if keys[pygame.K_d]:
            self.rect.x += self.speed

        # applies gravity
        self.apply_gravity()

        # traps player in the screen
        if self.rect.left <= 0:
            self.rect.left = 0
        if self.rect.right >= SCREEN_W:
            self.rect.right = SCREEN_W

        # always check to see if we hit the ground
        self.check_on_ground()

    # moves the player in the y direction based on y velocity and updates it with gravity
    def apply_gravity(self):
        self.vel_y += self.gravity
        self.rect.y += self.vel_y

        # makes sure the player isn't falling faster than terminal velocity
        if self.vel_y >= self.t_vel:
            self.vel_y = self.t_vel

        # stops the player on the ground
        if self.rect.bottom > SCREEN_H:
            self.rect.bottom = SCREEN_H
            self.vel_y = 0

    # will change the y velocity to jump pow only when the player is on the ground
    def jump(self):
        if self.on_ground:
            self.vel_y = self.jump_pow
            self.jumping = True
            self.on_ground = False

    # checks to see if player is on the bottom of the screen/ground
    def check_on_ground(self):
        if self.rect.bottom >= SCREEN_H:
            self.on_ground = True
            self.jumping = False

# a class for our rectangle platforms
class Platform(pygame.sprite.Sprite):
    def __init__(self, group, x, y, width, height):
        super().__init__(group)
        self.image = pygame.Surface((width, height))
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y

    def update(self):
        # this is to move the blocks from the right side of the screen to the left
        # will kill the block once its off screen
        # self.rect.x -= 5
        # if self.rect.right <= 0:
        #     self.kill()

        # this will move the block from the top to the bottom of the screen
        # will kill the block once its off screen
        self.rect.y += 1
        if self.rect.top >= SCREEN_H:
            self.kill()

# will make the player bounce higher than a normal jump
class BouncingPlat(Platform):
    def __init__(self, group, x, y, width, height):
        super().__init__(group, x, y, width, height)
        self.image.fill("purple")

# a normal platform
class NormalPlat(Platform):
    def __init__(self, group, x, y, width, height):
        super().__init__(group, x, y, width, height)
        self.image.fill("green")

# will let the player jump through it from the bottom and land on top
class PhasePlat(Platform):
    def __init__(self, group, x, y, width, height):
        super().__init__(group, x, y, width, height)
        self.image.fill("lightblue")

# sprite groups
all_sprites = pygame.sprite.Group()
platforms = pygame.sprite.Group()

# making player and platforms
p1 = Player(all_sprites)

# keeping track of how often we spawn platforms
spawn_time = pygame.time.get_ticks()
spawn_rate = 1300

# starting platform to catch the player
platforms.add(NormalPlat(all_sprites, 250, 350, 100, 20))

# list of platform types
types = [NormalPlat, PhasePlat, BouncingPlat]

# randomly selects a platform type to generate when called
def newPlat():
    x = random.randint(0, 500)
    choice = random.choice(types)
    return choice(all_sprites, x, -20, 100, 20)

# starting off with one
platforms.add(newPlat())

# main game loop
while running:

    for event in pygame.event.get():

        # exit loop
        if event.type == pygame.QUIT:
            running = False

        # make the player press space every time they want to jump
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE:
                p1.jump()

    # bg color
    screen.fill("Black")

    # generates a new platform after spawn_rate time has passed since the last one
    if pygame.time.get_ticks() - spawn_time > spawn_rate:

        # new spawn time
        spawn_time = pygame.time.get_ticks()

        # new platform
        platforms.add(newPlat())

    # update and draw stuff
    all_sprites.update()

    # using sprite collision to get a list of platforms the player collided with
    collided = pygame.sprite.spritecollide(p1, platforms, False)

    # iterating through the platforms and applying our collision logic
    for plat in collided:

        # from the top
        if p1.rect.bottom <= plat.rect.top + 15 and p1.vel_y >= 0:
            p1.rect.bottom = plat.rect.top
            p1.vel_y = 0
            p1.jumping = False
            p1.on_ground = True

            # checks to see if plat is a Bouncing platform, so it can make the player jump
            if isinstance(plat, BouncingPlat):
                p1.jump()
                p1.vel_y = p1.jump_pow*1.5

        # from the bottom
        elif p1.rect.top >= plat.rect.bottom - 15 and p1.vel_y <= 0:

            # if phase platform, let the player go through the bottom
            if isinstance(plat, PhasePlat):
                continue

            # else, hit the bottom and fall down
            else:
                p1.rect.top = plat.rect.bottom
                p1.vel_y = 0

        # from the right
        elif p1.rect.right <= plat.rect.left + 15:
            p1.rect.right = plat.rect.left

        # from the left
        elif p1.rect.left >= plat.rect.right - 15:
            p1.rect.left = plat.rect.right

    # draw the sprites
    all_sprites.draw(screen)

    pygame.display.flip()

    # fps
    clock.tick(60)

# quit game
pygame.quit()