# Ian Binstock
# 6/8/2026
# Minor tweaks, demonstrated sound imports, time.sleep() and included color changes

import pygame, sys, random, time

# Visualization Tools
# - DEFAULT: False, 0.35, 8
# - CONCEPTUAL: True, 0.05, 3
SHOW_BORDERS = False
EASE = 0.35
SPEED = 8
TICK_RATE = 60

# Constants
SCREEN_W = 800
SCREEN_H = 800
BOOST = False
GROW = False
OCEAN_BLUE = (15, 75, 115)
GRAY1 = (220, 220, 220)
GRAY2 = (210, 210, 210)
GRAY3 = (200, 200, 200)
GRAY4 = (190, 190, 190)
GRAY5 = (180, 180, 180)
GRAY6 = (170, 170, 170)
GRAY7 = (160, 160, 160)
GRAY8 = (150, 150, 150)
GRAY9 = (140, 140, 140)

# Basic setup
pygame.init()
screen = pygame.display.set_mode((SCREEN_W, SCREEN_H))
pygame.display.set_caption("Chompdown Challenge!")
clock = pygame.time.Clock()

# Assets
fish = pygame.image.load("fish.png")
fish = pygame.transform.scale(fish, (52, 52))
fish_x = 100
fish_y = 100

puff = pygame.image.load("puff.png")
puff_size = 52
puff = pygame.transform.scale(puff, (52, 52))
puff_x = 700
puff_y = 700

hit_sound = pygame.mixer.Sound("hit.wav")
pop_sound = pygame.mixer.Sound("pop.wav")


### CLASSES
class Segment:
    # Constructor
    def __init__(self, obj_x, obj_y, obj_shape, obj_color, obj_r=0, obj_w=0, obj_h=0, obj_y_offset=0):
        self.x = obj_x
        self.y = obj_y
        self.shape = obj_shape
        self.color = obj_color
        self.r = obj_r
        self.w = obj_w
        self.h = obj_h
        self.y_offset = obj_y_offset

class Dolphin:
    # A dolphin-like structure made up of 9 body segments
    def __init__(self):
        start_x = SCREEN_W // 2
        start_y = SCREEN_H // 2

        self.segments = [
            Segment(start_x, start_y, "circle", GRAY1, obj_r=15), # Head
            Segment(start_x, start_y, "circle", GRAY2, obj_r=28),
            Segment(start_x, start_y, "circle", GRAY3, obj_r=35),
            Segment(start_x, start_y, "ellipse", GRAY4, obj_w=200, obj_h=35), # Fins
            Segment(start_x, start_y, "circle", GRAY5, obj_r=35),
            Segment(start_x, start_y, "ellipse", GRAY6, obj_w=25,  obj_h=120, obj_y_offset=-30), # Dorsal Fin
            Segment(start_x, start_y, "circle", GRAY7, obj_r=28),
            Segment(start_x, start_y, "circle", GRAY8, obj_r=22),
            Segment(start_x, start_y, "ellipse", GRAY9, obj_w=125, obj_h=20,  obj_y_offset=5), # Tail
        ]


### HELPER FUNCTIONS
def update_segment(segment, target_x, target_y, ease=EASE):
    # Moves the segment toward the target x and y coordinates
    segment.x += (target_x - segment.x) * ease
    segment.y += (target_y - segment.y) * ease

def speedcap(x1, y1, x2, y2, speed=SPEED, ease=1.0):
    # cap x velocity
    if x1 - x2 > speed:
        x1 -= speed * ease
    elif x1 - x2 < -speed:
        x1 += speed * ease
    else:
        x1 = x2

    # cap y velocity
    if y1 - y2 > speed:
        y1 -= speed * ease
    elif y1 - y2 < -speed:
        y1 += speed * ease
    else:
        y1 = y2

    return [x1, y1]

# Creates the TRAILING EFFECT, giving the dolphin its shape
def update_dolphin(dolphin, target_x, target_y):
    # Update Head (segment 0) to follow keyboard input directly
    update_segment(dolphin.segments[0], target_x, target_y, ease=0.4)

    # Make each subsequent segment trail the one in front of it
    for i in range(1, len(dolphin.segments)):
        prev = dolphin.segments[i - 1]
        update_segment(dolphin.segments[i], prev.x, prev.y)

def perish(screen, dolphin):
    for segment in dolphin.segments:
        # Shift the gray tones into red tones
        segment.color = (255, max(0, segment.color[1] - 100), max(0, segment.color[2] - 100))

    # We must clear the screen, redraw the dolphin, and flip the display 
    # before the program sleeps or exits.
    screen.fill(OCEAN_BLUE)
    draw_dolphin(screen, dolphin, SHOW_BORDERS)
    draw_fishy(screen, fish_x, fish_y)
    draw_puffy(screen, puff_x - (puff_size // 2), puff_y - (puff_size // 2))
    pygame.display.update()

def draw_segment(screen, segment, draw_border=False):
    # Handles drawing circles
    if segment.shape == "circle":
        pygame.draw.circle(screen, segment.color, (int(segment.x), int(segment.y)), segment.r)
        if draw_border:
            pygame.draw.circle(screen, "#FFFFFF", (int(segment.x), int(segment.y)), segment.r, 2)
            
    # Handles drawing ovals
    elif segment.shape == "ellipse":
        rect = pygame.Rect(
            segment.x - segment.w // 2, 
            segment.y - segment.h // 2 + segment.y_offset, 
            segment.w, 
            segment.h
        )
        pygame.draw.ellipse(screen, segment.color, rect)
        if draw_border:
            pygame.draw.ellipse(screen, "#FFFFFF", rect, 2)

def draw_dolphin(screen, dolphin, draw_borders):
    # Draws the dolphin segments back-to-front
    for segment in reversed(dolphin.segments):
        draw_segment(screen, segment, draw_borders)

def draw_fishy(screen, x, y):
    screen.blit(fish, (x, y))

def update_fishy(screen, target_x, target_y):
    new_x = target_x
    new_y = target_y
    # randomize position
    while abs(target_x - new_x) < 25 or abs(target_y - new_y) < 25:
        new_x = random.random() * (SCREEN_W - 50)
        new_y = random.random() * (SCREEN_H - 50)
    # center position when drawing fish
    draw_fishy(screen, new_x, new_y)
    return (new_x, new_y)

def draw_puffy(screen, x, y):
    screen.blit(puff, (x, y))


### GAME LOOP
def main():
    global fish_x, fish_y, puff, puff_size, puff_x, puff_y, SPEED, BOOST, TICK_RATE, GROW

    # 'Target' stores the [x,y] coords that the dolphin is chasing
    target = [SCREEN_W // 2, SCREEN_H // 2] # dolphin starting position
    dolphin = Dolphin()

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()

            # If LMB is pressed down, give the dolphin a boost
            if event.type == pygame.MOUSEBUTTONDOWN:
                if pygame.mouse.get_pressed()[0]:
                    BOOST = True
            if event.type == pygame.MOUSEBUTTONUP:
                BOOST = False

        # Dolphin origin tracks mouse - set a speed cap for movement
        movepos = pygame.mouse.get_pos()
        target = speedcap(target[0], target[1], movepos[0], movepos[1], SPEED, 0.75)

        # Update the target's [x,y] coords
        target[0] = max(0, min(SCREEN_W, target[0]))
        target[1] = max(0, min(SCREEN_H, target[1]))

        # Update the dolphin's position using the helper function
        update_dolphin(dolphin, *target)

        # Pufferfish track Dolphin origin - set a speed cap for movement
        puff_x, puff_y = speedcap(puff_x, puff_y, target[0], target[1], SPEED // 2, 0.25)
        if abs(puff_x - target[0]) < 50 and abs(puff_y - target[1]) < 50:
            # Perish
            perish(screen, dolphin)
            hit_sound.play()
            time.sleep(0.1)
            pygame.quit()
            sys.exit()
        if abs(puff_x - target[0]) < 150 and abs(puff_y - target[1]) < 150:
            # Grow
            prev = puff_size
            puff_size = min(104, puff_size + 1)
            # Reload image everytime to ensure the image doesn't distort
            puff = pygame.image.load("puff.png")
            puff = pygame.transform.scale(puff, (puff_size, puff_size))
            if not GROW:
                GROW = True
            if 52 < puff_size < 104:
                puff_x -= abs((puff_size - prev) // 2)
                puff_y -= abs((puff_size - prev) // 2)
        else:
            # Shrink
            prev = puff_size
            puff_size = max(52, puff_size - 1)
            # Reload image everytime to ensure the image doesn't distort
            puff = pygame.image.load("puff.png")
            puff = pygame.transform.scale(puff, (puff_size, puff_size))
            if GROW:
                GROW = False
            if 52 < puff_size < 104:
                puff_x += abs((puff_size - prev) // 2)
                puff_y += abs((puff_size - prev) // 2)

        # Update fish
        if abs(target[0] - fish_x - 25) < 25 and abs(target[1] - fish_y - 25) < 25:
            pop_sound.play()
            fish_x, fish_y = update_fishy(screen, target[0], target[1])

        # Boost ability
        if BOOST:
            TICK_RATE = 120
        else:
            TICK_RATE = 60

        # Draw elements using the helper functions
        screen.fill(OCEAN_BLUE)
        draw_dolphin(screen, dolphin, SHOW_BORDERS)
        draw_fishy(screen, fish_x, fish_y)
        # Position is based on top left corner. Draw pufferfish offset from the top left corner
        draw_puffy(screen, puff_x - (puff_size // 2), puff_y - (puff_size // 2))
        pygame.display.update() # Ensure drawn elements appear in front
        clock.tick(TICK_RATE)

# Run it
main()