import pygame
import random
import sys
import math

# ===================== 全局常量 =====================
WIDTH, HEIGHT = 1100, 750
FPS = 60
BG_COLOR = (15, 15, 22)
GRID_COLOR = (30, 30, 45)

# 玩家设置
PLAYER_R = 22
PLAYER_SPEED = 5
PLAYER_MAX_HP = 100
FIRE_COOLDOWN = 110
RECOIL = 1.8

# 子弹
BULLET_R = 5
BULLET_SPEED = 14
BULLET_DMG = 25

# 敌人
ENEMY_BASE_R = 24
ENEMY_BASE_SPD = 1.6
ENEMY_BASE_HP = 50
SPAWN_INTERVAL = 900

# 颜色
COLOR_PLAYER = (0, 190, 255)
COLOR_BULLET = (255, 230, 60)
COLOR_ENEMY = (225, 45, 45)
COLOR_HEALTH_FILL = (0, 210, 70)
COLOR_HEALTH_BG = (70, 0, 0)
COLOR_PICKUP_SPEED = (0, 255, 220)
COLOR_PICKUP_FIRE = (255, 130, 0)
COLOR_PICKUP_HP = (255, 60, 160)
COLOR_PARTICLE = (255, 200, 80)

# ===================== 粒子类（爆炸/击中特效） =====================
class Particle:
    def __init__(self, x, y, color):
        self.x = x
        self.y = y
        self.color = color
        self.r = random.randint(2, 5)
        angle = random.random() * math.pi * 2
        spd = random.uniform(2, 6)
        self.vx = math.cos(angle) * spd
        self.vy = math.sin(angle) * spd
        self.life = random.randint(20, 45)

    def update(self):
        self.x += self.vx
        self.y += self.vy
        self.vx *= 0.94
        self.vy *= 0.94
        self.life -= 1

    def draw(self, screen):
        pygame.draw.circle(screen, self.color, (int(self.x), int(self.y)), self.r)

# ===================== 道具类 =====================
class Pickup:
    TYPES = ["hp", "speed", "fire"]
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.r = 12
        self.type = random.choice(Pickup.TYPES)
        self.life = 600
        if self.type == "hp":
            self.color = COLOR_PICKUP_HP
        elif self.type == "speed":
            self.color = COLOR_PICKUP_SPEED
        else:
            self.color = COLOR_PICKUP_FIRE

    def draw(self, screen):
        pygame.draw.circle(screen, self.color, (int(self.x), int(self.y)), self.r)
        pygame.draw.circle(screen, (255,255,255), (int(self.x), int(self.y)), self.r-4)

# ===================== 敌人类 =====================
class Enemy:
    def __init__(self, score_level):
        self.r = ENEMY_BASE_R
        self.max_hp = ENEMY_BASE_HP + score_level * 18
        self.hp = self.max_hp
        self.speed = ENEMY_BASE_SPD + score_level * 0.25
        # 四边随机出生
        side = random.randint(0,3)
        if side == 0:
            self.x = random.randint(0, WIDTH)
            self.y = -self.r*2
        elif side == 1:
            self.x = WIDTH + self.r*2
            self.y = random.randint(0, HEIGHT)
        elif side == 2:
            self.x = random.randint(0, WIDTH)
            self.y = HEIGHT + self.r*2
        else:
            self.x = -self.r*2
            self.y = random.randint(0, HEIGHT)

    def update(self, px, py):
        dx = px - self.x
        dy = py - self.y
        dist = math.hypot(dx, dy)
        if dist > 0:
            self.x += dx / dist * self.speed
            self.y += dy / dist * self.speed

    def draw(self, screen):
        pygame.draw.circle(screen, COLOR_ENEMY, (int(self.x), int(self.y)), self.r)
        # 敌人头顶血条
        bar_w = self.r * 1.6
        bar_h = 4
        ox = self.x - bar_w/2
        oy = self.y - self.r - 10
        pygame.draw.rect(screen, COLOR_HEALTH_BG, (ox, oy, bar_w, bar_h))
        hp_ratio = self.hp / self.max_hp
        pygame.draw.rect(screen, COLOR_HEALTH_FILL, (ox, oy, bar_w*hp_ratio, bar_h))

# ===================== 主游戏类 =====================
class AdvancedShooter:
    def __init__(self):
        pygame.init()
        self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
        pygame.display.set_caption("Advanced 2D Shooter")
        self.clock = pygame.time.Clock()
        # 规避SysFont崩溃，加载内置字体
        default_font_path = pygame.font.get_default_font()
        try:
            self.font_lg = pygame.font.Font(default_font_path, 52)
            self.font_md = pygame.font.Font(default_font_path, 30)
            self.font_sm = pygame.font.Font(default_font_path, 20)
        except:
            self.font_lg = self.font_md = self.font_sm = None

        # 玩家状态
        self.p_x = WIDTH//2
        self.p_y = HEIGHT//2
        self.p_hp = PLAYER_MAX_HP
        self.p_speed = PLAYER_SPEED
        self.fire_cd = FIRE_COOLDOWN
        self.last_shot = 0
        self.recoil_off_x = 0
        self.recoil_off_y = 0

        # 游戏实体
        self.bullets = []
        self.enemies = []
        self.particles = []
        self.pickups = []
        self.float_texts = []

        # 全局数据
        self.score = 0
        self.level = 0
        self.game_over = False
        self.paused = False
        self.last_spawn = pygame.time.get_ticks()

    def spawn_enemy(self):
        self.enemies.append(Enemy(self.level))

    def spawn_pickup(self, x, y):
        self.pickups.append(Pickup(x, y))

    def create_explosion(self, x, y, count=12):
        for _ in range(count):
            self.particles.append(Particle(x, y, COLOR_PARTICLE))

    def add_float_text(self, x, y, text):
        self.float_texts.append({"x":x, "y":y, "txt":text, "life":70})

    def player_move(self):
        keys = pygame.key.get_pressed()
        spd = self.p_speed
        if keys[pygame.K_w] and self.p_y > PLAYER_R:
            self.p_y -= spd
        if keys[pygame.K_s] and self.p_y < HEIGHT - PLAYER_R:
            self.p_y += spd
        if keys[pygame.K_a] and self.p_x > PLAYER_R:
            self.p_x -= spd
        if keys[pygame.K_d] and self.p_x < WIDTH - PLAYER_R:
            self.p_x += spd

    def shoot(self):
        now = pygame.time.get_ticks()
        if now - self.last_shot < self.fire_cd:
            return
        self.last_shot = now
        mx, my = pygame.mouse.get_pos()
        dx = mx - self.p_x - self.recoil_off_x
        dy = my - self.p_y - self.recoil_off_y
        dist = math.hypot(dx, dy)
        if dist < 1:
            return
        # 后坐力偏移
        self.recoil_off_x = random.uniform(-RECOIL, RECOIL)
        self.recoil_off_y = random.uniform(-RECOIL, RECOIL)
        vx = dx / dist * BULLET_SPEED
        vy = dy / dist * BULLET_SPEED
        self.bullets.append([self.p_x, self.p_y, vx, vy])
        self.create_explosion(self.p_x+dx/dist*22, self.p_y+dy/dist*22, 5)

    def update_bullets(self):
        new_bullets = []
        for bx, by, vx, vy in self.bullets:
            nx = bx + vx
            ny = by + vy
            if 0 < nx < WIDTH and 0 < ny < HEIGHT:
                new_bullets.append([nx, ny, vx, vy])
        self.bullets = new_bullets

    def update_enemies(self):
        now = pygame.time.get_ticks()
        # 随等级加快刷新
        current_spawn = SPAWN_INTERVAL - self.level * 60
        if current_spawn < 280:
            current_spawn = 280
        if now - self.last_spawn > current_spawn:
            self.spawn_enemy()
            self.last_spawn = now

        for e in self.enemies:
            e.update(self.p_x, self.p_y)

    def collision_logic(self):
        # 子弹 vs 敌人
        bullet_del = set()
        enemy_del = set()
        for bi, (bx, by, _, _) in enumerate(self.bullets):
            for ei, e in enumerate(self.enemies):
                d = math.hypot(bx - e.x, by - e.y)
                if d < BULLET_R + e.r:
                    bullet_del.add(bi)
                    e.hp -= BULLET_DMG
                    self.create_explosion(bx, by, 6)
                    self.add_float_text(e.x, e.y-25, f"-{BULLET_DMG}")
                    if e.hp <= 0:
                        enemy_del.add(ei)
                        self.score += 100 + self.level * 15
                        self.create_explosion(e.x, e.y, 22)
                        # 概率掉落道具
                        if random.random() < 0.32:
                            self.spawn_pickup(e.x, e.y)
        # 清理
        self.bullets = [self.bullets[i] for i in range(len(self.bullets)) if i not in bullet_del]
        self.enemies = [self.enemies[i] for i in range(len(self.enemies)) if i not in enemy_del]

        # 敌人撞击玩家
        for e in self.enemies:
            d = math.hypot(self.p_x - e.x, self.p_y - e.y)
            if d < PLAYER_R + e.r:
                self.p_hp -= 0.8
                self.create_explosion(self.p_x, self.p_y, 3)
                if self.p_hp <= 0:
                    self.game_over = True

        # 玩家拾取道具
        pickup_del = []
        for idx, pu in enumerate(self.pickups):
            pu.life -= 1
            if pu.life <= 0:
                pickup_del.append(idx)
                continue
            d = math.hypot(self.p_x - pu.x, self.p_y - pu.y)
            if d < PLAYER_R + pu.r:
                pickup_del.append(idx)
                if pu.type == "hp":
                    self.p_hp = min(self.p_hp + 35, PLAYER_MAX_HP)
                    self.add_float_text(pu.x, pu.y, "+HP")
                elif pu.type == "speed":
                    self.p_speed = min(self.p_speed + 0.7, 9)
                    self.add_float_text(pu.x, pu.y, "SPEED UP")
                else:
                    self.fire_cd = max(self.fire_cd - 18, 40)
                    self.add_float_text(pu.x, pu.y, "FIRE RATE")
        for i in reversed(pickup_del):
            del self.pickups[i]

    def update_particles(self):
        new_parts = []
        for p in self.particles:
            p.update()
            if p.life > 0:
                new_parts.append(p)
        self.particles = new_parts

    def update_float_text(self):
        new_text = []
        for ft in self.float_texts:
            ft["y"] -= 1.2
            ft["life"] -= 1
            if ft["life"] > 0:
                new_text.append(ft)
        self.float_texts = new_text

    def draw_background(self):
        self.screen.fill(BG_COLOR)
        # 网格
        grid_size = 50
        for x in range(0, WIDTH, grid_size):
            pygame.draw.line(self.screen, GRID_COLOR, (x, 0), (x, HEIGHT), 1)
        for y in range(0, HEIGHT, grid_size):
            pygame.draw.line(self.screen, GRID_COLOR, (0, y), (WIDTH, y), 1)

    def draw_ui(self):
        # 血条
        bar_w = 220
        bar_h = 22
        pygame.draw.rect(self.screen, COLOR_HEALTH_BG, (12, 12, bar_w, bar_h))
        hp_ratio = self.p_hp / PLAYER_MAX_HP
        pygame.draw.rect(self.screen, COLOR_HEALTH_FILL, (12, 12, bar_w * hp_ratio, bar_h))
        # 文字UI
        if self.font_md:
            score_txt = self.font_md.render(f"SCORE: {self.score}", True, (255,255,255))
            self.screen.blit(score_txt, (240, 10))
            lvl_txt = self.font_md.render(f"LV {self.level}", True, (255,220,80))
            self.screen.blit(lvl_txt, (440, 10))
        if self.font_sm:
            hint = self.font_sm.render("WASD Move | LMB Shoot | P Pause | R Restart", True, (170,170,170))
            self.screen.blit(hint, (WIDTH - 480, 12))
        # 难度等级随分数更新
        self.level = self.score // 600

    def draw_crosshair(self):
        mx, my = pygame.mouse.get_pos()
        clr = (255, 255, 255)
        pygame.draw.line(self.screen, clr, (mx-12, my), (mx-4, my), 2)
        pygame.draw.line(self.screen, clr, (mx+4, my), (mx+12, my), 2)
        pygame.draw.line(self.screen, clr, (mx, my-12), (mx, my-4), 2)
        pygame.draw.line(self.screen, clr, (mx, my+4), (mx, my+12), 2)

    def draw_game_over(self):
        overlay = pygame.Surface((WIDTH, HEIGHT))
        overlay.set_alpha(170)
        overlay.fill((0,0,0))
        self.screen.blit(overlay, (0,0))
        if self.font_lg:
            over = self.font_lg.render("GAME OVER", True, (255,50,50))
            self.screen.blit(over, (WIDTH//2 - over.get_width()//2, HEIGHT//2 - 100))
        if self.font_md:
            final = self.font_md.render(f"Final Score: {self.score}", True, (255,255,255))
            tip = self.font_md.render("Press R to Restart", True, (220,220,220))
            self.screen.blit(final, (WIDTH//2 - final.get_width()//2, HEIGHT//2 - 20))
            self.screen.blit(tip, (WIDTH//2 - tip.get_width()//2, HEIGHT//2 + 40))

    def draw_pause(self):
        overlay = pygame.Surface((WIDTH, HEIGHT))
        overlay.set_alpha(130)
        overlay.fill((0,0,0))
        self.screen.blit(overlay, (0,0))
        if self.font_lg:
            pause_txt = self.font_lg.render("PAUSED", True, (255,255,255))
            self.screen.blit(pause_txt, (WIDTH//2 - pause_txt.get_width()//2, HEIGHT//2 - 60))
        if self.font_md:
            hint = self.font_md.render("Press P to Resume", True, (200,200,200))
            self.screen.blit(hint, (WIDTH//2 - hint.get_width()//2, HEIGHT//2 + 10))

    def reset(self):
        self.p_x = WIDTH//2
        self.p_y = HEIGHT//2
        self.p_hp = PLAYER_MAX_HP
        self.p_speed = PLAYER_SPEED
        self.fire_cd = FIRE_COOLDOWN
        self.last_shot = 0
        self.recoil_off_x = self.recoil_off_y = 0
        self.bullets.clear()
        self.enemies.clear()
        self.particles.clear()
        self.pickups.clear()
        self.float_texts.clear()
        self.score = 0
        self.level = 0
        self.game_over = False
        self.paused = False
        self.last_spawn = pygame.time.get_ticks()

    def game_loop(self):
        pygame.mouse.set_visible(False)
        while True:
            self.draw_background()
            # 事件
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    sys.exit()
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_ESCAPE:
                        pygame.quit()
                        sys.exit()
                    if event.key == pygame.K_p and not self.game_over:
                        self.paused = not self.paused
                    if event.key == pygame.K_r:
                        self.reset()
                if event.type == pygame.MOUSEBUTTONDOWN:
                    if event.button == 1 and not self.game_over and not self.paused:
                        self.shoot()

            if not self.game_over and not self.paused:
                # 更新逻辑
                self.player_move()
                self.update_bullets()
                self.update_enemies()
                self.collision_logic()
                self.update_particles()
                self.update_float_text()

                # 绘制道具
                for pu in self.pickups:
                    pu.draw(self.screen)
                # 绘制敌人
                for e in self.enemies:
                    e.draw(self.screen)
                # 绘制子弹
                for bx, by, _, _ in self.bullets:
                    pygame.draw.circle(self.screen, COLOR_BULLET, (int(bx), int(by)), BULLET_R)
                # 绘制玩家
                pygame.draw.circle(self.screen, COLOR_PLAYER, (int(self.p_x), int(self.p_y)), PLAYER_R)
                pygame.draw.circle(self.screen, (255,255,255), (int(self.p_x), int(self.p_y)), PLAYER_R-6)
                # 粒子
                for p in self.particles:
                    p.draw(self.screen)
                # 飘字
                if self.font_sm:
                    for ft in self.float_texts:
                        text_surf = self.font_sm.render(ft["txt"], True, (255,255,255))
                        self.screen.blit(text_surf, (ft["x"], ft["y"]))
                # UI与准星
                self.draw_ui()
                self.draw_crosshair()
            elif self.paused:
                # 暂停状态仅渲染现有画面+遮罩
                for pu in self.pickups: pu.draw(self.screen)
                for e in self.enemies: e.draw(self.screen)
                for bx, by, _, _ in self.bullets:
                    pygame.draw.circle(self.screen, COLOR_BULLET, (int(bx), int(by)), BULLET_R)
                pygame.draw.circle(self.screen, COLOR_PLAYER, (int(self.p_x), int(self.p_y)), PLAYER_R)
                for p in self.particles: p.draw(self.screen)
                self.draw_ui()
                self.draw_pause()
                self.draw_crosshair()
            else:
                self.draw_game_over()

            pygame.display.flip()
            self.clock.tick(FPS)

if __name__ == "__main__":
    game = AdvancedShooter()
    game.game_loop()