import tkinter as tk
from tkinter import messagebox, font
import random
import time
import threading
import math

# ==================== 游戏常量 ====================
WORLD_WIDTH = 4000       # 世界地图宽度
WORLD_HEIGHT = 4000      # 世界地图高度
CANVAS_WIDTH = 1024      # 视口宽度
CANVAS_HEIGHT = 668      # 视口高度
VIEW_RADIUS = 450        # 玩家视野半径
VIEW_ANGLE = math.pi / 2 # 90°扇形视野
PLAYER_RADIUS = 16       # 玩家碰撞半径
AI_RADIUS = 15           # AI碰撞半径
PLAYER_SPEED = 5         # 玩家移动速度
AI_SPEED = 3             # AI移动速度
BULLET_DAMAGE = 34       # 子弹伤害
HIT_CHANCE = 0.7         # 基础命中率
BULLET_LIFETIME = 50    # 子弹显示时长(ms)
AI_UPDATE_INTERVAL = 100 # AI决策间隔(ms)
DEFENDER_ENGAGE_RANGE = 300  # 交战射程,进入后停止移动

class Game:
    def __init__(self, root):
        self.root = root
        self.root.title("SCP: Comms Down设施失联")
        self.root.geometry("1024x768")
        self.root.resizable(False, False)
        # 中文字体
        self.font_family = "SimHei"
        # 游戏核心状态
        self.game_active = False
        self.defender_wins = False
        self.attacker_wins = False
        # 计时器
        self.game_timer = 300  # 5分钟对局
        self.timer_running = False
        # 玩家与AI数量:进攻方1玩家+9AI共10人,防守方20人
        self.player_count = 1
        self.ai_count = 9
        self.total_players = self.player_count + self.ai_count
        # 游戏实体
        self.defenders = []
        self.attackers = []
        self.bullets = []
        self.damage_texts = []
        self.obstacles = []
        # 相机系统
        self.camera_x = 0
        self.camera_y = 0
        # 玩家输入
        self.key_pressed = {"w": False, "a": False, "s": False, "d": False}
        self.mouse_x = CANVAS_WIDTH // 2
        self.mouse_y = CANVAS_HEIGHT // 2
        # AI更新计时器
        self.ai_update_timer = 0
        # 创建开始界面
        self.create_start_screen()

    # ==================== 界面系统 ====================
    def create_start_screen(self):
        for widget in self.root.winfo_children():
            widget.destroy()
        start_frame = tk.Frame(self.root, bg="black")
        start_frame.pack(fill=tk.BOTH, expand=True)
        title_font = font.Font(family=self.font_family, size=36, weight="bold")
        title_label = tk.Label(start_frame, text="SCP: Comms Down设施失联", 
                               font=title_font, fg="white", bg="black")
        title_label.pack(pady=50)
        copyright_font = font.Font(family=self.font_family, size=12)
        copyright_label = tk.Label(start_frame, text="BC2O270371", 
                                   font=copyright_font, fg="white", bg="black")
        copyright_label.pack(pady=10)
        controls_font = font.Font(family=self.font_family, size=14)
        controls_label = tk.Label(start_frame, text="WASD移动 | 鼠标瞄准 | 左键射击", 
                                  font=controls_font, fg="white", bg="black")
        controls_label.pack(pady=20)
        button_frame = tk.Frame(start_frame, bg="black")
        button_frame.pack(pady=100)
        attacker_btn = tk.Button(button_frame, text="Nu-7进入设施", command=self.select_attacker,
                                font=(self.font_family, 16), bg="#F44336", fg="white",
                                width=15, height=2, cursor="hand2")
        attacker_btn.pack(side=tk.LEFT, padx=20)

    def select_attacker(self):
        self.create_game_screen()
        self.initialize_attackers()
        self.initialize_defenders()
        self.start_attack_phase()
        self.setup_player_controls()

    def create_game_screen(self):
        for widget in self.root.winfo_children():
            widget.destroy()
        self.game_frame = tk.Frame(self.root, bg="#0f3460")
        self.game_frame.pack(fill=tk.BOTH, expand=True)
        self.canvas = tk.Canvas(self.game_frame, bg="#16213e", 
                                width=CANVAS_WIDTH, height=CANVAS_HEIGHT)
        self.canvas.pack()
        # 底部状态栏
        self.status_frame = tk.Frame(self.game_frame, bg="#0f3460", height=100)
        self.status_frame.pack(fill=tk.X)
        self.timer_label = tk.Label(self.status_frame, text="05:00", 
                                    font=(self.font_family, 24), fg="white", bg="#0f3460")
        self.timer_label.pack(side=tk.LEFT, padx=20)
        self.player_status_label = tk.Label(self.status_frame, text="进攻方 | 存活", 
                                            font=(self.font_family, 14), fg="white", bg="#0f3460")
        self.player_status_label.pack(side=tk.LEFT, padx=20)
        self.game_status_label = tk.Label(self.status_frame, text="战斗阶段", 
                                          font=(self.font_family, 14), fg="white", bg="#0f3460")
        self.game_status_label.pack(side=tk.RIGHT, padx=20)
        # 生成地图障碍物
        self.generate_world_obstacles()

    # ==================== 地图与碰撞 ====================
    def generate_world_obstacles(self):
        self.obstacles = []
        # 地图边界墙体
        self.obstacles.append((0, 0, WORLD_WIDTH, 20))
        self.obstacles.append((0, WORLD_HEIGHT - 20, WORLD_WIDTH, WORLD_HEIGHT))
        self.obstacles.append((0, 0, 20, WORLD_HEIGHT))
        self.obstacles.append((WORLD_WIDTH - 20, 0, WORLD_WIDTH, WORLD_HEIGHT))
        # 随机生成室内障碍物
        random.seed(42)  # 固定种子保证地图一致
        for _ in range(150):
            w = random.randint(80, 200)
            h = random.randint(80, 200)
            x = random.randint(100, WORLD_WIDTH - 100 - w)
            y = random.randint(100, WORLD_HEIGHT - 100 - h)
            self.obstacles.append((x, y, x + w, y + h))

    def get_valid_spawn(self, x_min, x_max, y_min, y_max, radius):
        """在指定范围内生成不与障碍物碰撞的有效出生点"""
        for _ in range(100):
            x = random.randint(x_min, x_max)
            y = random.randint(y_min, y_max)
            if not self.check_wall_collision(x, y, radius):
                return (x, y)
        # 兜底方案
        return (x_min + radius + 20, y_min + radius + 20)

    def check_wall_collision(self, x, y, radius):
        """圆形单位与墙体/障碍物碰撞检测"""
        # 地图边界
        if x - radius < 0 or x + radius > WORLD_WIDTH:
            return True
        if y - radius < 0 or y + radius > WORLD_HEIGHT:
            return True
        # 障碍物碰撞(圆形-矩形)
        for obs in self.obstacles:
            ox1, oy1, ox2, oy2 = obs
            closest_x = max(ox1, min(x, ox2))
            closest_y = max(oy1, min(y, oy2))
            dist_sq = (x - closest_x) ** 2 + (y - closest_y) ** 2
            if dist_sq < radius ** 2:
                return True
        return False

    def line_intersects_wall(self, x1, y1, x2, y2):
        """子弹线段与墙体碰撞检测(严格检测,确保不穿墙)"""
        for obs in self.obstacles:
            if self._line_rect_intersect(x1, y1, x2, y2, obs):
                return True
        return False

    def _line_rect_intersect(self, x1, y1, x2, y2, rect):
        """线段与矩形相交检测(CCW算法)"""
        rx1, ry1, rx2, ry2 = rect
        def point_in_rect(px, py):
            return rx1 <= px <= rx2 and ry1 <= py <= ry2
        if point_in_rect(x1, y1) or point_in_rect(x2, y2):
            return True
        def ccw(ax, ay, bx, by, cx, cy):
            return (cy - ay) * (bx - ax) > (by - ay) * (cx - ax)
        def seg_intersect(sx1, sy1, sx2, sy2, sx3, sy3, sx4, sy4):
            return (ccw(sx1,sy1,sx3,sy3,sx4,sy4) != ccw(sx2,sy2,sx3,sy3,sx4,sy4) and
                    ccw(sx1,sy1,sx2,sy2,sx3,sy3) != ccw(sx1,sy1,sx2,sy2,sx4,sy4))
        edges = [
            (rx1, ry1, rx2, ry1),
            (rx2, ry1, rx2, ry2),
            (rx2, ry2, rx1, ry2),
            (rx1, ry2, rx1, ry1)
        ]
        for edge in edges:
            if seg_intersect(x1, y1, x2, y2, edge[0], edge[1], edge[2], edge[3]):
                return True
        return False

    # ==================== 单位初始化 ====================
    def initialize_defenders(self):
        self.defenders = []
        # 防守方共20人,生成在地图中部安全区域
        for i in range(20):
            x, y = self.get_valid_spawn(
                WORLD_WIDTH//3, WORLD_WIDTH*2//3,
                WORLD_HEIGHT//3, WORLD_HEIGHT*2//3,
                AI_RADIUS
            )
            self.defenders.append({
                "id": i, "x": x, "y": y,
                "health": 100, "is_player": False, "role": "defender",
                "target_pos": (x, y)
            })

    def initialize_attackers(self):
        self.attackers = []
        # 玩家单位 - 生成在左侧安全区域
        player_x, player_y = self.get_valid_spawn(
            50, 200, 
            WORLD_HEIGHT//2 - 100, WORLD_HEIGHT//2 + 100,
            PLAYER_RADIUS
        )
        self.attackers.append({
            "id": 0, "x": player_x, "y": player_y,
            "health": 100, "is_player": True, "role": "attacker",
            "direction": 0
        })
        # AI队友 - 共9个,分散生成在左右两侧安全区域
        for i in range(1, self.total_players):
            if random.choice([True, False]):
                # 左侧出生
                x, y = self.get_valid_spawn(50, 250, 100, WORLD_HEIGHT - 100, AI_RADIUS)
            else:
                # 右侧出生
                x, y = self.get_valid_spawn(WORLD_WIDTH - 250, WORLD_WIDTH - 50, 100, WORLD_HEIGHT - 100, AI_RADIUS)
            
            self.attackers.append({
                "id": i, "x": x, "y": y,
                "health": 100, "is_player": False, "role": "attacker",
                "target_pos": (x, y)
            })

    # ==================== 游戏流程 ====================
    def start_attack_phase(self):
        self.game_active = True
        self.game_status_label.config(text="战斗阶段")
        self.game_timer = 300
        self.timer_label.config(text="{:02d}:{:02d}".format(self.game_timer // 60, self.game_timer % 60))
        self.timer_running = True
        # 启动计时线程
        threading.Thread(target=self.run_game_timer, daemon=True).start()
        # 启动主循环
        self.game_loop()

    def run_game_timer(self):
        while self.game_timer > 0 and self.timer_running and not self.defender_wins and not self.attacker_wins:
            time.sleep(1)
            self.game_timer -= 1
            self.root.after(0, lambda: self.timer_label.config(
                text="{:02d}:{:02d}".format(self.game_timer // 60, self.game_timer % 60)))
        
        if self.timer_running and not self.defender_wins and not self.attacker_wins:
            self.root.after(0, self.check_win_conditions)

    def game_loop(self):
        if not self.game_active:
            self.root.after(16, self.game_loop)
            return
        # 更新玩家与相机
        self.update_player_position()
        self.update_camera()
        
        # 更新AI移动(每帧)
        self.update_ai_movement()
        
        # 更新子弹与伤害文字
        self.update_effects()
        
        # AI决策(间隔更新)
        self.ai_update_timer += 16
        if self.ai_update_timer >= AI_UPDATE_INTERVAL:
            self.ai_update_timer = 0
            self.update_ai_defense_actions()
            self.update_ai_attack_actions()
        # 渲染画面
        self.render()
        self.root.after(16, self.game_loop)

    # ==================== 玩家控制 ====================
    def setup_player_controls(self):
        self.root.bind("<KeyPress>", self.on_key_press)
        self.root.bind("<KeyRelease>", self.on_key_release)
        self.root.bind("<Motion>", self.on_mouse_move)
        self.root.bind("<Button-1>", self.on_mouse_click)

    def on_key_press(self, event):
        key = event.keysym.lower()
        if key in self.key_pressed:
            self.key_pressed[key] = True

    def on_key_release(self, event):
        key = event.keysym.lower()
        if key in self.key_pressed:
            self.key_pressed[key] = False

    def on_mouse_move(self, event):
        self.mouse_x = event.x
        self.mouse_y = event.y
        player = self.get_player()
        if player and player["health"] > 0:
            # 屏幕坐标转世界坐标
            world_mx = event.x + self.camera_x
            world_my = event.y + self.camera_y
            player["direction"] = math.atan2(world_my - player["y"], world_mx - player["x"])

    def on_mouse_click(self, event):
        player = self.get_player()
        if player and player["health"] > 0:
            world_mx = event.x + self.camera_x
            world_my = event.y + self.camera_y
            angle = math.atan2(world_my - player["y"], world_mx - player["x"])
            target = self.find_enemy_in_direction(player, angle)
            if target:
                self.shoot_at(player, target, angle)

    def get_player(self):
        return next((a for a in self.attackers if a["is_player"]), None)

    def update_player_position(self):
        player = self.get_player()
        if not player or player["health"] <= 0:
            return
        dx, dy = 0, 0
        if self.key_pressed["w"]: dy -= PLAYER_SPEED
        if self.key_pressed["s"]: dy += PLAYER_SPEED
        if self.key_pressed["a"]: dx -= PLAYER_SPEED
        if self.key_pressed["d"]: dx += PLAYER_SPEED
        # 对角线速度归一化
        if dx != 0 and dy != 0:
            norm = 1 / math.sqrt(2)
            dx *= norm
            dy *= norm
        # 分轴碰撞检测(贴墙滑行)
        new_x = player["x"] + dx
        if not self.check_wall_collision(new_x, player["y"], PLAYER_RADIUS):
            player["x"] = new_x
        
        new_y = player["y"] + dy
        if not self.check_wall_collision(player["x"], new_y, PLAYER_RADIUS):
            player["y"] = new_y

    def update_camera(self):
        player = self.get_player()
        if not player:
            return
        # 相机中心跟随玩家
        target_x = player["x"] - CANVAS_WIDTH / 2
        target_y = player["y"] - CANVAS_HEIGHT / 2
        # 限制相机不超出地图边界
        target_x = max(0, min(target_x, WORLD_WIDTH - CANVAS_WIDTH))
        target_y = max(0, min(target_y, WORLD_HEIGHT - CANVAS_HEIGHT))
        self.camera_x = target_x
        self.camera_y = target_y

    # ==================== AI系统 ====================
    def update_ai_movement(self):
        """每帧更新所有AI的位置(平滑移动)"""
        # 防守方AI
        for defender in self.defenders:
            if defender["health"] > 0 and defender["target_pos"]:
                self.move_unit(defender, defender["target_pos"][0], defender["target_pos"][1])
        # 进攻方AI
        for attacker in self.attackers:
            if attacker["is_player"] or attacker["health"] <= 0:
                continue
            if attacker["target_pos"]:
                self.move_unit(attacker, attacker["target_pos"][0], attacker["target_pos"][1])

    def update_ai_defense_actions(self):
        """防守方AI决策:感知敌人后,超出射程则靠近,进入射程则停火射击"""
        for defender in self.defenders:
            if defender["health"] <= 0:
                continue
            
            nearest_attacker = self.find_nearest_enemy(defender, self.attackers)
            if nearest_attacker:
                dist = self.distance(defender, nearest_attacker)
                if dist < 400:  # 400为敌人感知范围
                    if dist > DEFENDER_ENGAGE_RANGE:
                        # 距离大于交战射程,向敌人方向靠近
                        defender["target_pos"] = (
                            nearest_attacker["x"] + random.randint(-20, 20),
                            nearest_attacker["y"] + random.randint(-20, 20)
                        )
                    else:
                        # 进入有效射程,停止移动,原地瞄准
                        defender["target_pos"] = (defender["x"], defender["y"])
                    
                    # 尝试射击(带穿墙检测)
                    angle = math.atan2(nearest_attacker["y"] - defender["y"], 
                                       nearest_attacker["x"] - defender["x"])
                    if random.random() < 0.3:
                        self.shoot_at(defender, nearest_attacker, angle)
                else:
                    # 超出感知范围,随机巡逻
                    if random.random() < 0.1:
                        defender["target_pos"] = (
                            random.randint(200, WORLD_WIDTH - 200),
                            random.randint(200, WORLD_HEIGHT - 200)
                        )
            else:
                # 无敌人目标,随机巡逻
                if random.random() < 0.1:
                    defender["target_pos"] = (
                        random.randint(200, WORLD_WIDTH - 200),
                        random.randint(200, WORLD_HEIGHT - 200)
                    )

    def update_ai_attack_actions(self):
        """进攻方AI(队友)决策:直接追击+射击,逻辑与防守方一致
            已确认:队友射击同样带穿墙检测,子弹从自身位置发出
        """
        for attacker in self.attackers:
            if attacker["is_player"] or attacker["health"] <= 0:
                continue
            
            nearest_defender = self.find_nearest_enemy(attacker, self.defenders)
            if nearest_defender:
                dist = self.distance(attacker, nearest_defender)
                if dist > DEFENDER_ENGAGE_RANGE:
                    # 距离大于交战射程,向敌人靠近
                    attacker["target_pos"] = (
                        nearest_defender["x"] + random.randint(-20, 20),
                        nearest_defender["y"] + random.randint(-20, 20)
                    )
                else:
                    # 进入有效射程,停止移动原地射击
                    attacker["target_pos"] = (attacker["x"], attacker["y"])
                
                # 尝试射击(带穿墙检测,和敌人逻辑完全一致)
                angle = math.atan2(nearest_defender["y"] - attacker["y"], 
                                   nearest_defender["x"] - attacker["x"])
                if random.random() < 0.3:
                    self.shoot_at(attacker, nearest_defender, angle)
            else:
                # 无敌人时随机巡逻
                if random.random() < 0.1:
                    attacker["target_pos"] = (
                        random.randint(200, WORLD_WIDTH - 200),
                        random.randint(200, WORLD_HEIGHT - 200)
                    )

    # ==================== 战斗系统 ====================
    def find_nearest_enemy(self, unit, enemies):
        nearest = None
        min_dist = float('inf')
        for enemy in enemies:
            if enemy["health"] > 0:
                dist = self.distance(unit, enemy)
                if dist < min_dist:
                    min_dist = dist
                    nearest = enemy
        return nearest

    def distance(self, u1, u2):
        return math.sqrt((u1["x"] - u2["x"]) ** 2 + (u1["y"] - u2["y"]) ** 2)

    def find_enemy_in_direction(self, shooter, angle):
        """查找瞄准方向上的敌人(带穿墙检测)"""
        max_dist = 300
        nearest = None
        min_dist = float('inf')
        
        enemies = self.attackers if shooter["role"] == "defender" else self.defenders
        for enemy in enemies:
            if enemy["health"] <= 0:
                continue
            
            enemy_angle = math.atan2(enemy["y"] - shooter["y"], enemy["x"] - shooter["x"])
            angle_diff = abs(angle - enemy_angle)
            if angle_diff > math.pi:
                angle_diff = 2 * math.pi - angle_diff
            
            if angle_diff < math.radians(30):
                dist = self.distance(shooter, enemy)
                if dist < max_dist and dist < min_dist:
                    # 子弹不能穿墙
                    if not self.line_intersects_wall(shooter["x"], shooter["y"], 
                                                     enemy["x"], enemy["y"]):
                        min_dist = dist
                        nearest = enemy
        return nearest

    def shoot_at(self, shooter, target, angle):
        """射击:先做穿墙校验,再生成子弹
            子弹起点严格等于射击者位置,终点严格等于目标位置
        """
        # 严格穿墙校验,视线被挡则不生成子弹
        if self.line_intersects_wall(shooter["x"], shooter["y"], target["x"], target["y"]):
            return
        # 子弹起点=射击者坐标,终点=目标坐标,确保弹道从枪口发出
        self.bullets.append({
            "x1": shooter["x"], "y1": shooter["y"],
            "x2": target["x"], "y2": target["y"],
            "time": BULLET_LIFETIME
        })
        # 命中判定
        if random.random() < HIT_CHANCE:
            target["health"] -= BULLET_DAMAGE
            if target["health"] <= 0:
                self.check_win_conditions()
            else:
                self.show_damage(target)

    def show_damage(self, unit):
        self.damage_texts.append({
            "x": unit["x"], "y": unit["y"] - 20,
            "time": 2000
        })

    def move_unit(self, unit, target_x, target_y):
        """单位移动(带碰撞检测)"""
        dx = target_x - unit["x"]
        dy = target_y - unit["y"]
        dist = math.sqrt(dx * dx + dy * dy)
        
        if dist > 2:
            speed = PLAYER_SPEED if unit["is_player"] else AI_SPEED
            move_x = (dx / dist) * speed
            move_y = (dy / dist) * speed
            radius = PLAYER_RADIUS if unit["is_player"] else AI_RADIUS
            # 分轴碰撞
            new_x = unit["x"] + move_x
            if not self.check_wall_collision(new_x, unit["y"], radius):
                unit["x"] = new_x
            
            new_y = unit["y"] + move_y
            if not self.check_wall_collision(unit["x"], new_y, radius):
                unit["y"] = new_y

    # ==================== 渲染系统 ====================
    def update_effects(self):
        """更新子弹与伤害文字生命周期"""
        # 子弹
        new_bullets = []
        for b in self.bullets:
            b["time"] -= 16
            if b["time"] > 0:
                new_bullets.append(b)
        self.bullets = new_bullets
        # 伤害文字(向上飘动)
        new_damage = []
        for dt in self.damage_texts:
            dt["time"] -= 16
            dt["y"] -= 0.5
            if dt["time"] > 0:
                new_damage.append(dt)
        self.damage_texts = new_damage

    def render(self):
        self.canvas.delete("all")
        self.canvas.create_rectangle(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, fill="#16213e")
        # 绘制障碍物(视口剔除)
        for obs in self.obstacles:
            x1 = obs[0] - self.camera_x
            y1 = obs[1] - self.camera_y
            x2 = obs[2] - self.camera_x
            y2 = obs[3] - self.camera_y
            if x2 < 0 or x1 > CANVAS_WIDTH or y2 < 0 or y1 > CANVAS_HEIGHT:
                continue
            self.canvas.create_rectangle(x1, y1, x2, y2, fill="#1a1a2e", outline="#e94560", width=1)
        # 绘制防守方(蓝色)
        for d in self.defenders:
            if d["health"] <= 0:
                continue
            sx = d["x"] - self.camera_x
            sy = d["y"] - self.camera_y
            if sx < -50 or sx > CANVAS_WIDTH + 50 or sy < -50 or sy > CANVAS_HEIGHT + 50:
                continue
            self.canvas.create_oval(sx-AI_RADIUS, sy-AI_RADIUS, 
                                  sx+AI_RADIUS, sy+AI_RADIUS, fill="blue")
            self.canvas.create_text(sx, sy, text=str(d["id"]), fill="white", font=(self.font_family, 10))
        # 绘制进攻方(红色)
        for a in self.attackers:
            if a["health"] <= 0:
                continue
            sx = a["x"] - self.camera_x
            sy = a["y"] - self.camera_y
            if sx < -50 or sx > CANVAS_WIDTH + 50 or sy < -50 or sy > CANVAS_HEIGHT + 50:
                continue
            
            r = PLAYER_RADIUS if a["is_player"] else AI_RADIUS
            self.canvas.create_oval(sx-r, sy-r, sx+r, sy+r, fill="red")
            self.canvas.create_text(sx, sy, text=str(a["id"]), fill="white", font=(self.font_family, 10))
            
            # 玩家朝向指示器
            if a["is_player"]:
                end_x = sx + math.cos(a["direction"]) * 30
                end_y = sy + math.sin(a["direction"]) * 30
                self.canvas.create_line(sx, sy, end_x, end_y, fill="yellow", width=2)
        # 绘制子弹
        for b in self.bullets:
            x1 = b["x1"] - self.camera_x
            y1 = b["y1"] - self.camera_y
            x2 = b["x2"] - self.camera_x
            y2 = b["y2"] - self.camera_y
            self.canvas.create_line(x1, y1, x2, y2, fill="yellow", width=2)
        # 绘制伤害数字
        for dt in self.damage_texts:
            sx = dt["x"] - self.camera_x
            sy = dt["y"] - self.camera_y
            self.canvas.create_text(sx, sy, text=str(BULLET_DAMAGE), fill="red", font=(self.font_family, 12))
        # 绘制玩家视野遮罩
        self.draw_vision_mask()

    def draw_vision_mask(self):
        """
        重写:正确实现90°扇形视野遮罩
        采用「外矩形+内扇形」连通挖洞法,利用奇偶填充规则实现
        效果:鼠标指向为扇形中心,90°视角,半径450,其余区域全黑
        """
        player = self.get_player()
        if not player or player["health"] <= 0:
            self.canvas.create_rectangle(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, fill="black")
            return
        
        # 玩家屏幕坐标
        px = player["x"] - self.camera_x
        py = player["y"] - self.camera_y
        angle = player["direction"]
        start_a = angle - VIEW_ANGLE / 2  # 扇形左边界角度
        end_a = angle + VIEW_ANGLE / 2    # 扇形右边界角度
        
        # 生成扇形弧线顶点(30段平滑)
        arc_points = []
        for i in range(31):
            a = start_a + (end_a - start_a) * i / 30
            arc_points.append((
                px + math.cos(a) * VIEW_RADIUS,
                py + math.sin(a) * VIEW_RADIUS
            ))
        
        # 构建遮罩多边形:外矩形顺时针 + 连通线 + 内扇形逆时针
        mask_pts = []
        # 1. 外轮廓:屏幕完整矩形(顺时针方向)
        mask_pts.extend([0, 0])
        mask_pts.extend([CANVAS_WIDTH, 0])
        mask_pts.extend([CANVAS_WIDTH, CANVAS_HEIGHT])
        mask_pts.extend([0, CANVAS_HEIGHT])
        # 2. 连通线:从外矩形左下角连接到玩家中心
        mask_pts.extend([px, py])
        # 3. 内轮廓:扇形逆时针绘制(从终点倒回起点)
        for pt in reversed(arc_points):
            mask_pts.extend([pt[0], pt[1]])
        # 4. 回到玩家中心,闭合内扇形
        mask_pts.extend([px, py])
        
        # 绘制黑色遮罩,扇形区域自动挖空透明
        self.canvas.create_polygon(mask_pts, fill="black", outline="")

    # ==================== 胜负判定 ====================
    def check_win_conditions(self):
        all_attackers_dead = all(a["health"] <= 0 for a in self.attackers)
        all_defenders_dead = all(d["health"] <= 0 for d in self.defenders)
        if all_attackers_dead:
            self.defender_wins_game()
        elif all_defenders_dead:
            self.attacker_wins_game()
        elif self.game_timer <= 0:
            self.defender_wins_game()

    def defender_wins_game(self):
        self.timer_running = False
        self.defender_wins = True
        self.game_status_label.config(text="防守方胜利!")
        messagebox.showinfo("游戏结束", "防守方胜利!")
        self.root.after(300, self.create_start_screen)

    def attacker_wins_game(self):
        self.timer_running = False
        self.attacker_wins = True
        self.game_status_label.config(text="进攻方胜利!")
        messagebox.showinfo("游戏结束", "进攻方胜利!")
        self.root.after(300, self.create_start_screen)

if __name__ == "__main__":
    root = tk.Tk()
    game = Game(root)
    root.mainloop()