import pygame
import math
import random
import os
from pygame.locals import *

# Initialize pygame
pygame.init()

# Screen dimensions
SCREEN_WIDTH = 1000
SCREEN_HEIGHT = 600
SCREEN = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Advanced Tower Defense")

# Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GRAY = (200, 200, 200)
DARK_GRAY = (50, 50, 50)
GREEN = (0, 255, 0)
RED = (255, 0, 0)

# Fonts
FONT = pygame.font.SysFont("arial", 20)
BIG_FONT = pygame.font.SysFont("arial", 30)

# Clock
clock = pygame.time.Clock()
FPS = 60

# Game Variables
money = 300
lives = 20
score = 0
selected_tower = None

# Load Images
def load_image(path, scale=None):
    try:
        image = pygame.image.load(path).convert_alpha()
        if scale:
            image = pygame.transform.scale(image, scale)
        return image
    except pygame.error as e:
        print(f"Unable to load image at path: {path}")
        raise SystemExit(e)

# Paths
ASSETS_PATH = os.path.join(os.getcwd(), "assets")
ENEMIES_PATH = os.path.join(ASSETS_PATH, "enemies")
TOWERS_PATH = os.path.join(ASSETS_PATH, "towers")
PROJECTILES_PATH = os.path.join(ASSETS_PATH, "projectiles")
GUI_PATH = os.path.join(ASSETS_PATH, "gui")

# Load enemy images
enemy_images = [
    load_image(os.path.join(ENEMIES_PATH, "enemy1.png"), scale=(40, 40)),
    load_image(os.path.join(ENEMIES_PATH, "enemy2.png"), scale=(40, 40)),
]

# Load tower images
tower_images = {
    "Basic Tower": load_image(os.path.join(TOWERS_PATH, "tower1.png"), scale=(50, 50)),
    "Sniper Tower": load_image(os.path.join(TOWERS_PATH, "tower2.png"), scale=(50, 50)),
}

# Load projectile images
projectile_images = {
    "Basic Tower": load_image(os.path.join(PROJECTILES_PATH, "projectile1.png"), scale=(10, 10)),
    "Sniper Tower": load_image(os.path.join(PROJECTILES_PATH, "projectile2.png"), scale=(15, 15)),
}

# GUI Elements
GUI_WIDTH = 200
GAME_WIDTH = SCREEN_WIDTH - GUI_WIDTH
GUI_RECT = pygame.Rect(GAME_WIDTH, 0, GUI_WIDTH, SCREEN_HEIGHT)

# Path waypoints
path = [
    (0, 100),
    (200, 100),
    (200, 300),
    (400, 300),
    (400, 500),
    (600, 500),
    (600, 200),
    (800, 200),
    (800, 400),
    (1000, 400)
]

class Enemy:
    def __init__(self, path, enemy_type="basic"):
        self.path = path
        self.current_point = 0
        self.position = list(self.path[self.current_point])
        self.speed = 1.0
        self.max_health = 100
        self.health = self.max_health
        self.radius = 20
        self.enemy_type = enemy_type
        self.image = random.choice(enemy_images) if enemy_type == "basic" else enemy_images[1]
        self.direction = 0

    def move(self):
        if self.current_point < len(self.path) - 1:
            target = self.path[self.current_point + 1]
            dir_vector = [target[0] - self.position[0], target[1] - self.position[1]]
            distance = math.hypot(dir_vector[0], dir_vector[1])
            if distance != 0:
                dir_vector[0] /= distance
                dir_vector[1] /= distance

            # Move towards the target
            self.position[0] += dir_vector[0] * self.speed
            self.position[1] += dir_vector[1] * self.speed

            # Check if reached the target
            if distance < self.speed:
                self.current_point += 1
        else:
            # Reached the end
            global lives
            lives -= 1
            enemies.remove(self)

    def draw(self, surface):
        # Draw enemy image
        rect = self.image.get_rect(center=(int(self.position[0]), int(self.position[1])))
        surface.blit(self.image, rect.topleft)

        # Draw health bar
        health_bar_width = self.radius * 2
        health_bar_height = 5
        health_ratio = self.health / self.max_health
        pygame.draw.rect(surface, RED, (self.position[0] - self.radius, self.position[1] - self.radius - 15, health_bar_width, health_bar_height))
        pygame.draw.rect(surface, GREEN, (self.position[0] - self.radius, self.position[1] - self.radius - 15, health_bar_width * health_ratio, health_bar_height))

class Tower:
    def __init__(self, position, tower_type="Basic Tower"):
        self.position = position
        self.tower_type = tower_type
        self.range = 150 if tower_type == "Basic Tower" else 250
        self.color = BLUE if tower_type == "Basic Tower" else (255, 165, 0)
        self.cool_down = 0
        self.fire_rate = 60 if tower_type == "Basic Tower" else 120  # Frames between shots
        self.image = tower_images[tower_type]
        self.projectile_image = projectile_images[tower_type]

    def shoot(self, enemies, projectiles):
        if self.cool_down == 0:
            # Find the nearest enemy within range
            nearest_enemy = None
            min_distance = float('inf')
            for enemy in enemies:
                distance = math.hypot(enemy.position[0] - self.position[0], enemy.position[1] - self.position[1])
                if distance < self.range and distance < min_distance:
                    nearest_enemy = enemy
                    min_distance = distance
            if nearest_enemy:
                # Create a projectile
                projectile = Projectile(self.position, nearest_enemy, self.tower_type)
                projectiles.append(projectile)
                self.cool_down = self.fire_rate

    def cooldown_tick(self):
        if self.cool_down > 0:
            self.cool_down -= 1

    def draw(self, surface):
        # Draw tower image
        rect = self.image.get_rect(center=self.position)
        surface.blit(self.image, rect.topleft)

        # Optionally, draw range circle
        # pygame.draw.circle(surface, GRAY, self.position, self.range, 1)

class Projectile:
    def __init__(self, position, target, tower_type="Basic Tower"):
        self.position = list(position)
        self.target = target
        self.tower_type = tower_type
        self.speed = 5 if tower_type == "Basic Tower" else 8
        self.damage = 25 if tower_type == "Basic Tower" else 50
        self.radius = 5 if tower_type == "Basic Tower" else 7
        self.image = projectile_images[tower_type]

    def move(self):
        if self.target in enemies:
            dir_vector = [self.target.position[0] - self.position[0], self.target.position[1] - self.position[1]]
            distance = math.hypot(dir_vector[0], dir_vector[1])
            if distance == 0:
                return
            dir_vector[0] /= distance
            dir_vector[1] /= distance
            self.position[0] += dir_vector[0] * self.speed
            self.position[1] += dir_vector[1] * self.speed

            # Check collision
            if distance < self.speed + self.target.radius:
                self.target.health -= self.damage
                if self.target.health <= 0:
                    global money, score
                    money += 20
                    score += 1
                    enemies.remove(self.target)
                if self in projectiles:
                    projectiles.remove(self)
        else:
            # Target no longer exists
            if self in projectiles:
                projectiles.remove(self)

    def draw(self, surface):
        rect = self.image.get_rect(center=(int(self.position[0]), int(self.position[1])))
        surface.blit(self.image, rect.topleft)

# Define a simple button class for GUI
class Button:
    def __init__(self, x, y, width, height, text, tower_type):
        self.rect = pygame.Rect(x, y, width, height)
        self.color = DARK_GRAY
        self.text = text
        self.tower_type = tower_type
        self.image = tower_images[tower_type]
        self.image_rect = self.image.get_rect(center=self.rect.center)

    def draw(self, surface, font):
        pygame.draw.rect(surface, self.color, self.rect)
        surface.blit(self.image, self.image_rect.topleft)
        text_surf = font.render(self.text, True, WHITE)
        text_rect = text_surf.get_rect(center=self.rect.center)
        surface.blit(text_surf, text_rect.topleft)

    def is_clicked(self, pos):
        return self.rect.collidepoint(pos)

# Create GUI Buttons for tower selection
button_width = 150
button_height = 60
buttons = [
    Button(GAME_WIDTH + 25, 50, button_width, button_height, "Basic Tower", "Basic Tower"),
    Button(GAME_WIDTH + 25, 150, button_width, button_height, "Sniper Tower", "Sniper Tower"),
]

# Groups
enemies = []
towers = []
projectiles = []

# Enemy spawn timer
spawn_event = pygame.USEREVENT + 1
pygame.time.set_timer(spawn_event, 2000)  # Spawn enemy every 2 seconds

def draw_path(surface, path):
    for i in range(len(path) - 1):
        pygame.draw.line(surface, DARK_GRAY, path[i], path[i+1], 10)

def draw_ui(surface):
    pygame.draw.rect(surface, GRAY, GUI_RECT)
    # Draw buttons
    for button in buttons:
        button.draw(surface, FONT)

    # Display selected tower
    if selected_tower:
        selected_text = FONT.render(f"Selected: {selected_tower}", True, BLACK)
    else:
        selected_text = FONT.render("Selected: None", True, BLACK)
    surface.blit(selected_text, (GAME_WIDTH + 20, 250))

    # Display money, lives, and score
    money_text = FONT.render(f"Money: ${money}", True, BLACK)
    lives_text = FONT.render(f"Lives: {lives}", True, BLACK)
    score_text = FONT.render(f"Score: {score}", True, BLACK)
    surface.blit(money_text, (GAME_WIDTH + 20, 300))
    surface.blit(lives_text, (GAME_WIDTH + 20, 330))
    surface.blit(score_text, (GAME_WIDTH + 20, 360))

def game_over_screen(surface):
    over_text = BIG_FONT.render("GAME OVER", True, RED)
    restart_text = FONT.render("Press R to Restart or Q to Quit.", True, BLACK)
    surface.blit(over_text, (GAME_WIDTH//2 - over_text.get_width()//2, SCREEN_HEIGHT//2 - 50))
    surface.blit(restart_text, (GAME_WIDTH//2 - restart_text.get_width()//2, SCREEN_HEIGHT//2))

def restart_game():
    global enemies, towers, projectiles, money, lives, score
    enemies = []
    towers = []
    projectiles = []
    money = 300
    lives = 20
    score = 0

def main():
    global selected_tower, enemies, towers, projectiles, money, lives, score
    running = True
    game_over = False

    while running:
        clock.tick(FPS)

        for event in pygame.event.get():
            if event.type == QUIT:
                running = False

            elif event.type == spawn_event and not game_over:
                enemy_type = random.choice(["basic", "advanced"])
                enemy = Enemy(path, enemy_type="basic" if random.random() > 0.3 else "advanced")
                enemies.append(enemy)

            elif event.type == MOUSEBUTTONDOWN and not game_over:
                mouse_pos = pygame.mouse.get_pos()
                if event.button == 1:  # Left click
                    # Check if clicked on GUI buttons
                    if mouse_pos[0] > GAME_WIDTH:
                        for button in buttons:
                            if button.is_clicked(mouse_pos):
                                selected_tower = button.tower_type
                                break
                    else:
                        # Place tower if selected
                        if selected_tower and money >= 50:
                            # Check if the spot is valid (not on path)
                            # Simple collision detection
                            valid = True
                            for point in path:
                                dist = math.hypot(mouse_pos[0] - point[0], mouse_pos[1] - point[1])
                                if dist < 50:
                                    valid = False
                                    break
                            if valid:
                                tower = Tower(mouse_pos, selected_tower)
                                towers.append(tower)
                                money -= 50
                        selected_tower = None

            elif event.type == KEYDOWN and game_over:
                if event.key == K_r:
                    restart_game()
                    game_over = False
                elif event.key == K_q:
                    running = False

        if not game_over:
            # Update enemies
            for enemy in enemies[:]:
                enemy.move()

            # Update towers
            for tower in towers:
                tower.shoot(enemies, projectiles)
                tower.cooldown_tick()

            # Update projectiles
            for projectile in projectiles[:]:
                projectile.move()

            # Check for game over
            if lives <= 0:
                game_over = True

        # Drawing
        SCREEN.fill(WHITE)
        draw_path(SCREEN, path)

        for tower in towers:
            tower.draw(SCREEN)

        for enemy in enemies:
            enemy.draw(SCREEN)

        for projectile in projectiles:
            projectile.draw(SCREEN)

        draw_ui(SCREEN)

        if game_over:
            game_over_screen(SCREEN)

        pygame.display.flip()

    pygame.quit()

if __name__ == "__main__":
    main()


MAKE IT GET from the same directory as the code