# Leo Salazar
# 6/20/2024
# Wrapped up the platformer example with a new platform that gives you uan extra jump to use
# also made it so that you lose when you hit the floor and will show you your score and jumps

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("Basic Movement")
clock = pygame.time.Clock()
running = True
font = pygame.font.Font(None, 48)

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
        self.jumps = 1

    # 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

        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 or self.jumps > 0:
            self.jumps -= 1
            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:
            if self.jumps <= 1:
                self.jumps = 1
            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()

class BouncingPlat(Platform):
    def __init__(self, group, x, y, width, height):
        super().__init__(group, x, y, width, height)
        self.image.fill("purple")

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

class PhasePlat(Platform):
    def __init__(self, group, x, y, width, height):
        super().__init__(group, x, y, width, height)
        self.image.fill("lightblue")

class ExtraJumpPlat(Platform):
    def __init__(self, group, x, y, width, height):
        super().__init__(group, x, y, width, height)
        self.image.fill("blue")
        self.give = 1

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

# making player and platforms
p1 = Player(all_sprites)

# starting platforms
platforms.add(NormalPlat(all_sprites, 250, 350, 100, 20))
platforms.add(NormalPlat(all_sprites, 50, 250, 100, 20))
platforms.add(NormalPlat(all_sprites, 450, 250, 100, 20))
platforms.add(NormalPlat(all_sprites, 100, 150, 100, 20))
platforms.add(NormalPlat(all_sprites, 400, 150, 100, 20))
platforms.add(NormalPlat(all_sprites, 0, 50, 100, 20))
platforms.add(NormalPlat(all_sprites, 250, 50, 100, 20))
platforms.add(NormalPlat(all_sprites, 500, 50, 100, 20))

# for spawning platforms
spawn_time = pygame.time.get_ticks()
spawn_rate = 1300

# platform types
types = [NormalPlat, PhasePlat, ExtraJumpPlat, BouncingPlat]

# making random new platforms
def newPlat():
    x = random.randint(0, 500)
    choice = random.choice(types)
    return choice(all_sprites, x, -20, 100, 20)

platforms.add(newPlat())

end = False
time = pygame.time.get_ticks()

# 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 and not end:
            if event.key == pygame.K_SPACE:
                p1.jump()

    # bg color
    screen.fill("Black")

    # generates a new platform after spawn_rate time has passed
    if pygame.time.get_ticks() - spawn_time > spawn_rate and not end:
        spawn_time = pygame.time.get_ticks()
        platforms.add(newPlat())

    # update and draw stuff
    if not end:
        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
            if p1.jumps < 1:
                p1.jumps = 1
            elif p1.jumping == True:
                p1.jumps += 1
            p1.jumping = False
            p1.on_ground = True

            # adds the extra jump stored in the platform to the player
            if isinstance(plat, ExtraJumpPlat):
                p1.jumps += plat.give
                plat.give = 0

            # 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

    if p1.rect.bottom >= SCREEN_H:
        end = True

    # draw the sprites
    all_sprites.draw(screen)

    # display jumps and score
    jump = font.render("Jumps: " + str(p1.jumps), True, "white")
    jump_rect = jump.get_rect(topleft=(10,10))
    screen.blit(jump, jump_rect)
    if not end:
        time = pygame.time.get_ticks()
    score = font.render("Score: " + str(time//100), True, "white")
    score_rect = score.get_rect(topright=(590,10))
    screen.blit(score, score_rect)
    if end:
        game_over = font.render("Game Over", True, "red", "black")
        game_over_rect = game_over.get_rect(center=(SCREEN_W//2, SCREEN_H//2))
        screen.blit(game_over, game_over_rect)

    pygame.display.flip()

    # fps
    clock.tick(60)

# quit game
pygame.quit()