#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Echo — 声呐导航生存游戏
完全基于 tkinter（Python 自带），无需安装任何外部库

操作说明：
  ↑ ↓ ← →  / W A S D    移动飞船
  SPACE                  发出声呐脉冲（照亮周围障碍物）
  R                      重新开始 / 下一关

游戏规则：
  - 屏幕大部分是黑暗的，你只能看到飞船周围一小片区域
  - 障碍物在黑暗中不可见，撞上会损失一条命
  - 按 SPACE 发出声呐脉冲，短暂照亮大范围区域
  - 导航到绿色光点处过关，每关障碍物更多、更难
"""

import tkinter as tk
import random
import math
import time

# ============================================================
# 游戏主类
# ============================================================
class EchoGame:
    def __init__(self, root):
        self.root = root
        self.root.title("Echo — 声呐导航生存")
        self.root.resizable(False, False)

        # ---------- 画布 ----------
        self.WIDTH = 800
        self.HEIGHT = 620
        self.canvas = tk.Canvas(root, width=self.WIDTH, height=self.HEIGHT,
                                bg="#0a0e1a", highlightthickness=0)
        self.canvas.pack()

        # ---------- 游戏参数 ----------
        self.player_radius = 9
        self.vision_radius = 85          # 正常视野半径
        self.ping_radius = 280           # 脉冲照亮半径
        self.ping_duration = 0.8         # 脉冲照亮持续时间（秒）
        self.ping_cooldown = 2.0         # 脉冲冷却时间（秒）
        self.max_lives = 3
        self.max_level = 8

        # ---------- 游戏状态 ----------
        self.reset_game()

        # ---------- 键盘绑定 ----------
        self.keys = {}
        root.bind("<KeyPress>", self.on_key_press)
        root.bind("<KeyRelease>", self.on_key_release)

        # ---------- 启动游戏循环 ----------
        self.last_frame = time.time()
        self.game_loop()

    # ============================================================
    # 游戏状态管理
    # ============================================================
    def reset_game(self):
        """重置整个游戏"""
        self.score = 0
        self.level = 1
        self.lives = self.max_lives
        self.game_over = False
        self.level_complete = False
        self.invulnerable_until = 0
        self.player_x = self.WIDTH // 2
        self.player_y = self.HEIGHT // 2
        self.last_ping_time = -self.ping_cooldown
        self.ping_active = False
        self.ping_pulse = 0          # 用于脉冲动画
        self.level_enter_time = time.time()
        self.hit_flash = 0           # 受伤闪红
        self.init_level()

    def init_level(self):
        """生成当前关卡的障碍物和目标"""
        # 障碍物数量随关卡递增
        n = 8 + self.level * 4
        self.obstacles = []

        attempts = 0
        while len(self.obstacles) < n and attempts < 2000:
            x = random.randint(50, self.WIDTH - 50)
            y = random.randint(50, self.HEIGHT - 50)
            r = random.randint(14, 28)

            # 不要放在玩家起始位置附近
            if math.hypot(x - self.WIDTH // 2, y - self.HEIGHT // 2) < 120:
                attempts += 1
                continue

            # 不要相互重叠太多
            overlap = False
            for o in self.obstacles:
                if math.hypot(x - o[0], y - o[1]) < r + o[2] + 25:
                    overlap = True
                    break
            if overlap:
                attempts += 1
                continue

            # 每个障碍物: [x, y, 半径, 可见截止时间, 漂移速度x, 漂移速度y]
            drift_speed = 0.15 + self.level * 0.05
            vx = random.uniform(-drift_speed, drift_speed)
            vy = random.uniform(-drift_speed, drift_speed)
            self.obstacles.append([x, y, r, 0.0, vx, vy])
            attempts += 1

        # 放置目标（绿色光点）
        self.place_goal()
        self.level_complete = False

    def place_goal(self):
        """在安全位置放置目标"""
        for _ in range(200):
            x = random.randint(60, self.WIDTH - 60)
            y = random.randint(60, self.HEIGHT - 60)

            if math.hypot(x - self.player_x, y - self.player_y) < 180:
                continue

            too_close = False
            for o in self.obstacles:
                if math.hypot(x - o[0], y - o[1]) < o[2] + 50:
                    too_close = True
                    break
            if not too_close:
                self.goal_x, self.goal_y = x, y
                self.goal_pulse = 0
                return

        # 实在找不到就随便放
        self.goal_x = random.randint(80, self.WIDTH - 80)
        self.goal_y = random.randint(80, self.HEIGHT - 80)
        self.goal_pulse = 0

    # ============================================================
    # 输入处理
    # ============================================================
    def on_key_press(self, event):
        self.keys[event.keysym] = True

        if event.keysym == "space":
            self.ping()
        if event.keysym in ("r", "R"):
            if self.game_over:
                self.reset_game()
            elif self.level_complete:
                self.next_level()

    def on_key_release(self, event):
        self.keys[event.keysym] = False

    def ping(self):
        """发出声呐脉冲"""
        now = time.time()
        if now - self.last_ping_time >= self.ping_cooldown and not self.game_over:
            self.last_ping_time = now
            self.ping_active = True
            self.ping_pulse = 0

    def next_level(self):
        """进入下一关"""
        if self.level < self.max_level:
            self.level += 1
            # 过关奖励：剩余命越多奖励越高
            self.score += 100 + self.lives * 20
            self.player_x = self.WIDTH // 2
            self.player_y = self.HEIGHT // 2
            self.invulnerable_until = time.time() + 1.0
            self.init_level()
        else:
            self.game_over = True
            self.score += 500  # 通关奖励

    # ============================================================
    # 游戏逻辑更新
    # ============================================================
    def update(self):
        now = time.time()
        dt = now - self.last_frame
        self.last_frame = now

        if self.game_over:
            return

        # ----- 1. 移动玩家 -----
        self.move_player(now)

        # ----- 2. 更新障碍物（漂移） -----
        for o in self.obstacles:
            o[0] += o[4]  # x += vx
            o[1] += o[5]  # y += vy
            # 边界反弹
            if o[0] < o[2]:      o[0] = o[2];      o[4] = -o[4]
            if o[0] > self.WIDTH - o[2]:  o[0] = self.WIDTH - o[2];  o[4] = -o[4]
            if o[1] < o[2]:      o[1] = o[2];      o[5] = -o[5]
            if o[1] > self.HEIGHT - o[2]: o[1] = self.HEIGHT - o[2]; o[5] = -o[5]

            # 可见性：在视野内或已被脉冲照亮
            dist = math.hypot(o[0] - self.player_x, o[1] - self.player_y)
            if dist < self.vision_radius:
                o[3] = now + 0.1
            if self.ping_active and dist < self.ping_pulse:
                o[3] = now + self.ping_duration
            # 可见性衰减（超过可见时间则自动隐藏）
            if o[3] < now:
                o[3] = 0

        # ----- 3. 更新脉冲动画 -----
        if self.ping_active:
            self.ping_pulse += 12
            if self.ping_pulse > self.ping_radius:
                self.ping_active = False
                self.ping_pulse = 0

        # ----- 4. 检查是否到达目标 -----
        if not self.level_complete:
            dist_to_goal = math.hypot(self.player_x - self.goal_x,
                                      self.player_y - self.goal_y)
            if dist_to_goal < self.player_radius + 16:
                self.level_complete = True

        # ----- 5. 受伤闪红衰减 -----
        if self.hit_flash > 0:
            self.hit_flash -= 0.05
        self.goal_pulse += 0.06

    def move_player(self, now):
        """处理玩家移动和碰撞"""
        if self.level_complete:
            return

        dx = dy = 0
        spd = 3.2
        if self.keys.get("Up") or self.keys.get("w"):    dy -= spd
        if self.keys.get("Down") or self.keys.get("s"):  dy += spd
        if self.keys.get("Left") or self.keys.get("a"):  dx -= spd
        if self.keys.get("Right") or self.keys.get("d"): dx += spd

        if dx == 0 and dy == 0:
            return

        # 归一化，保证对角线速度不翻倍
        if dx != 0 and dy != 0:
            dx *= 0.707
            dy *= 0.707

        new_x = self.player_x + dx
        new_y = self.player_y + dy

        # 边界
        new_x = max(self.player_radius, min(self.WIDTH - self.player_radius, new_x))
        new_y = max(self.player_radius, min(self.HEIGHT - self.player_radius, new_y))

        # 碰撞检测（无敌时不撞）
        if now >= self.invulnerable_until:
            for o in self.obstacles:
                if math.hypot(new_x - o[0], new_y - o[1]) < self.player_radius + o[2]:
                    # 撞上了！
                    self.lives -= 1
                    self.hit_flash = 1.0
                    self.invulnerable_until = now + 1.2
                    # 把障碍物点亮一下
                    o[3] = now + 0.5
                    if self.lives <= 0:
                        self.game_over = True
                    return  # 不移动

        self.player_x = new_x
        self.player_y = new_y

    # ============================================================
    # 渲染
    # ============================================================
    def draw(self):
        self.canvas.delete("all")
        now = time.time()

        # ----- 背景 -----
        self.canvas.create_rectangle(0, 0, self.WIDTH, self.HEIGHT,
                                     fill="#0a0e1a", outline="")

        # 微弱网格
        for i in range(0, self.WIDTH, 40):
            self.canvas.create_line(i, 0, i, self.HEIGHT, fill="#0d1225", width=1)
        for i in range(0, self.HEIGHT, 40):
            self.canvas.create_line(0, i, self.WIDTH, i, fill="#0d1225", width=1)

        # ----- 脉冲动画（声呐波纹） -----
        if self.ping_active:
            for ring in range(3):
                r = self.ping_pulse - ring * 30
                if r > 5 and r < self.ping_radius:
                    alpha = max(0, 1 - r / self.ping_radius)
                    c = f"#{int(60 + alpha * 80):02x}{int(40 + alpha * 60):02x}ff"
                    self.canvas.create_oval(
                        self.player_x - r, self.player_y - r,
                        self.player_x + r, self.player_y + r,
                        outline=c, width=2 - ring * 0.5
                    )

        # ----- 障碍物 -----
        for o in self.obstacles:
            ox, oy, r, visible, _, _ = o
            if visible > now:
                # 发光效果
                for g in range(4, 0, -1):
                    gr = r + g * 5
                    intensity = int(60 - g * 12)
                    self.canvas.create_oval(
                        ox - gr, oy - gr, ox + gr, oy + gr,
                        fill=f"#{intensity:02x}1005", outline=""
                    )
                self.canvas.create_oval(
                    ox - r, oy - r, ox + r, oy + r,
                    fill="#e63946", outline="#ff6b6b", width=2
                )

        # ----- 目标（绿色光点） -----
        goal_r = 14 + math.sin(self.goal_pulse) * 3
        # 微弱远距离光晕（给玩家方向提示）
        if not self.level_complete:
            dist_to_goal = math.hypot(self.player_x - self.goal_x,
                                      self.player_y - self.goal_y)
            if dist_to_goal < 500:
                halo_alpha = max(0, 1 - dist_to_goal / 500)
                halo_r = 30 + halo_alpha * 20
                c = f"#00{int(halo_alpha * 30 + 10):02x}{int(halo_alpha * 20 + 10):02x}"
                self.canvas.create_oval(
                    self.goal_x - halo_r, self.goal_y - halo_r,
                    self.goal_x + halo_r, self.goal_y + halo_r,
                    fill=c, outline=""
                )

            # 近距离发光
            if dist_to_goal < self.vision_radius + 50 or self.ping_active:
                for g in range(4, 0, -1):
                    gr = goal_r + g * 6
                    self.canvas.create_oval(
                        self.goal_x - gr, self.goal_y - gr,
                        self.goal_x + gr, self.goal_y + gr,
                        fill=f"#00{60 - g * 10:02x}22", outline=""
                    )
                self.canvas.create_oval(
                    self.goal_x - goal_r, self.goal_y - goal_r,
                    self.goal_x + goal_r, self.goal_y + goal_r,
                    fill="#00ff88", outline="#66ffbb", width=2
                )
        else:
            # 过关庆祝特效
            for g in range(5, 0, -1):
                gr = goal_r + g * 8 + math.sin(self.goal_pulse * 2) * 5
                self.canvas.create_oval(
                    self.goal_x - gr, self.goal_y - gr,
                    self.goal_x + gr, self.goal_y + gr,
                    fill=f"#00{80 - g * 12:02x}22", outline=""
                )

        # ----- 视野圈 -----
        self.canvas.create_oval(
            self.player_x - self.vision_radius,
            self.player_y - self.vision_radius,
            self.player_x + self.vision_radius,
            self.player_y + self.vision_radius,
            outline="#1a3a5a", width=1, dash=(4, 6)
        )

        # ----- 玩家飞船（带发光） -----
        # 无敌闪烁
        if now < self.invulnerable_until and int(now * 10) % 2 == 0:
            pass  # 闪烁：不绘制（透明）
        else:
            pr = self.player_radius
            for g in range(6, 0, -1):
                gr = pr + g * 4
                alpha = 1 - g * 0.12
                self.canvas.create_oval(
                    self.player_x - gr, self.player_y - gr,
                    self.player_x + gr, self.player_y + gr,
                    fill=f"#00{int(alpha * 180):02x}ff", outline=""
                )
            # 受伤闪红
            if self.hit_flash > 0:
                flash_c = f"#ff{int(40 * self.hit_flash):02x}{int(20 * self.hit_flash):02x}"
                self.canvas.create_oval(
                    self.player_x - pr - 4, self.player_y - pr - 4,
                    self.player_x + pr + 4, self.player_y + pr + 4,
                    fill=flash_c, outline=""
                )
            self.canvas.create_oval(
                self.player_x - pr, self.player_y - pr,
                self.player_x + pr, self.player_y + pr,
                fill="#00ddff", outline="#88eeff", width=2
            )

        # ----- UI：顶部信息栏 -----
        ui_y = 22

        # 等级
        self.canvas.create_text(20, ui_y, anchor="w",
                                text=f"等级 {self.level}/{self.max_level}",
                                fill="#8899bb", font=("Consolas", 13, "bold"))
        # 分数
        self.canvas.create_text(self.WIDTH // 2 - 40, ui_y, anchor="w",
                                text=f"得分 {self.score}",
                                fill="#8899bb", font=("Consolas", 13, "bold"))

        # 生命值（❤️）
        hearts = "❤️" * self.lives + "🖤" * (self.max_lives - self.lives)
        self.canvas.create_text(self.WIDTH // 2 + 60, ui_y, anchor="w",
                                text=hearts, fill="#ff6688",
                                font=("Arial", 14))

        # 脉冲冷却条
        cd = time.time() - self.last_ping_time
        cd_ratio = min(1, cd / self.ping_cooldown)
        bar_x = self.WIDTH - 200
        bar_y = ui_y - 7
        self.canvas.create_text(bar_x - 55, bar_y + 7, anchor="w",
                                text="声呐",
                                fill="#8899bb", font=("Consolas", 12))
        self.canvas.create_rectangle(bar_x, bar_y, bar_x + 150, bar_y + 14,
                                     fill="#111a2a", outline="#2a4a6a", width=1)
        fill_c = "#00ddff" if cd_ratio >= 0.99 else "#336688"
        self.canvas.create_rectangle(bar_x + 1, bar_y + 1,
                                     bar_x + 1 + 148 * cd_ratio, bar_y + 13,
                                     fill=fill_c, outline="")

        # 冷却就绪提示
        if cd_ratio >= 0.99 and not self.level_complete and not self.game_over:
            self.canvas.create_text(bar_x + 75, bar_y - 14,
                                    text="✔ 就绪",
                                    fill="#00ddff", font=("Consolas", 10))

        # ----- 底部操作提示 -----
        self.canvas.create_text(
            self.WIDTH // 2, self.HEIGHT - 15, anchor="center",
            text="↑↓←→/WASD 移动  |  SPACE 声呐脉冲  |  R 重新开始",
            fill="#3a4a5a", font=("Consolas", 10)
        )

        # ----- 过关 / 游戏结束 信息 -----
        if self.level_complete and not self.game_over:
            # 半透明遮罩
            self.canvas.create_rectangle(
                0, 0, self.WIDTH, self.HEIGHT,
                fill="#000000", stipple="gray25"
            )
            self.canvas.create_rectangle(
                self.WIDTH // 2 - 170, self.HEIGHT // 2 - 55,
                self.WIDTH // 2 + 170, self.HEIGHT // 2 + 35,
                fill="#0a1a0a", outline="#00ff88", width=2
            )
            self.canvas.create_text(
                self.WIDTH // 2, self.HEIGHT // 2 - 18,
                text="🎉 到达目标！", fill="#00ff88",
                font=("Arial", 24, "bold")
            )
            more = "按 R 进入下一关" if self.level < self.max_level else "按 R 领取通关奖励！"
            self.canvas.create_text(
                self.WIDTH // 2, self.HEIGHT // 2 + 18,
                text=more, fill="#88ffbb",
                font=("Arial", 14)
            )

        if self.game_over:
            self.canvas.create_rectangle(
                0, 0, self.WIDTH, self.HEIGHT,
                fill="#000000", stipple="gray25"
            )
            if self.level >= self.max_level:
                msg = "🏆 恭喜通关！"
                msg_c = "#ffdd44"
            else:
                msg = "💀 游戏结束"
                msg_c = "#ff4444"

            self.canvas.create_rectangle(
                self.WIDTH // 2 - 170, self.HEIGHT // 2 - 55,
                self.WIDTH // 2 + 170, self.HEIGHT // 2 + 35,
                fill="#1a0a0a", outline=msg_c, width=2
            )
            self.canvas.create_text(
                self.WIDTH // 2, self.HEIGHT // 2 - 18,
                text=f"{msg}  得分: {self.score}", fill=msg_c,
                font=("Arial", 20, "bold")
            )
            self.canvas.create_text(
                self.WIDTH // 2, self.HEIGHT // 2 + 18,
                text="按 R 重新开始", fill="#ff8888",
                font=("Arial", 14)
            )

    # ============================================================
    # 游戏主循环
    # ============================================================
    def game_loop(self):
        self.update()
        self.draw()
        self.root.after(30, self.game_loop)  # ~33 FPS


# ============================================================
# 启动游戏
# ============================================================
if __name__ == "__main__":
    root = tk.Tk()
    game = EchoGame(root)
    root.mainloop()