import pygame
import math
import random
import sys

# ========== 初始化 ==========
pygame.init()
WIDTH, HEIGHT = 1024, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("纯 Pygame 3D 航海模拟器")
clock = pygame.time.Clock()

# ========== 3D 数学工具 ==========
def rotate_y(v, angle):
    """绕 Y 轴旋转"""
    x, y, z = v
    c, s = math.cos(angle), math.sin(angle)
    return (x*c + z*s, y, -x*s + z*c)

def rotate_x(v, angle):
    x, y, z = v
    c, s = math.cos(angle), math.sin(angle)
    return (x, y*c - z*s, y*s + z*c)

def project(point, cam_pos, cam_rot, fov=256, view_dist=300):
    """将世界坐标投影到屏幕坐标，返回 (sx, sy, depth)"""
    # 平移至相机空间
    dx = point[0] - cam_pos[0]
    dy = point[1] - cam_pos[1]
    dz = point[2] - cam_pos[2]
    # 旋转（绕Y轴）
    x = dx * math.cos(-cam_rot) + dz * math.sin(-cam_rot)
    y = dy
    z = -dx * math.sin(-cam_rot) + dz * math.cos(-cam_rot)
    if z < 0.1:
        return None
    # 透视投影
    factor = fov / z
    sx = int(WIDTH/2 + x * factor)
    sy = int(HEIGHT/2 - y * factor)
    return (sx, sy, z)

def normal_of_triangle(v0, v1, v2):
    u = (v1[0]-v0[0], v1[1]-v0[1], v1[2]-v0[2])
    v = (v2[0]-v0[0], v2[1]-v0[1], v2[2]-v0[2])
    nx = u[1]*v[2] - u[2]*v[1]
    ny = u[2]*v[0] - u[0]*v[2]
    nz = u[0]*v[1] - u[1]*v[0]
    length = math.hypot(nx, ny, nz)
    if length == 0:
        return (0,1,0)
    return (nx/length, ny/length, nz/length)

def dot_product(a, b):
    return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]

# ========== 游戏状态 ==========
ship_pos = [0.0, 0.0, 0.0]     # 世界坐标 (x, y, z)
ship_heading = 0.0              # 弧度
ship_speed = 0.0
sail_area = 1.0
sail_angle = 0.0
anchor_down = False
hull_hp = 100
cargo = 0

wind_direction = 0.0
wind_speed = 8.0
storm_active = False
storm_timer = 0.0
time_of_day = 12.0

# 岛屿
islands = []
for _ in range(8):
    angle = random.uniform(0, 2*math.pi)
    dist = random.uniform(150, 350)
    x = dist * math.cos(angle)
    z = dist * math.sin(angle)
    r = random.uniform(15, 35)
    islands.append((x, z, r, False))

# 相机
cam_pos = [0, 15, -40]   # 相对船的偏移
cam_rot = 0.0

# ========== 构建 3D 物体 ==========
def build_ship():
    """返回船体的三角形列表 [(v0,v1,v2,color)]，坐标在局部空间"""
    tris = []
    # 船体（一个简单的棱柱）
    # 底部四个点
    b1 = (-3, -1, -1.5)
    b2 = (-3, -1, 1.5)
    b3 = (3, -1, 1.5)
    b4 = (3, -1, -1.5)
    # 顶部四个点
    t1 = (-3, 0.5, -1)
    t2 = (-3, 0.5, 1)
    t3 = (3, 0.5, 1)
    t4 = (3, 0.5, -1)
    # 侧面（每个面两个三角形）
    brown = (139, 90, 43)
    # 左侧
    tris.append((b1, b4, t4, brown))
    tris.append((b1, t4, t1, brown))
    # 右侧
    tris.append((b2, t2, t3, brown))
    tris.append((b2, t3, b3, brown))
    # 前面
    tris.append((b4, b3, t3, brown))
    tris.append((b4, t3, t4, brown))
    # 后面
    tris.append((b1, t1, t2, brown))
    tris.append((b1, t2, b2, brown))
    # 甲板（顶面）
    deck_color = (160, 115, 60)
    tris.append((t1, t4, t3, deck_color))
    tris.append((t1, t3, t2, deck_color))
    # 桅杆（一条线，用细矩形表示）
    mast_color = (210, 195, 165)
    # 用两个三角形组成一个竖条
    m1 = (-0.2, 0.5, -0.2)
    m2 = (-0.2, 0.5, 0.2)
    m3 = (-0.2, 6, -0.2)
    m4 = (-0.2, 6, 0.2)
    tris.append((m1, m2, m4, mast_color))
    tris.append((m1, m4, m3, mast_color))
    # 帆（一个矩形，受 sail_area 和 sail_angle 影响）
    sail_color = (240, 235, 225)
    sw = 2.0 * sail_area
    sh = 4.0 * sail_area
    # 帆的中心在桅杆顶部下方一点
    sail_center = (0, 3.5, 0)
    # 帆的四个角（先定义在局部，再旋转 sail_angle）
    sa = (-sw/2, -sh/2, 0)
    sb = (sw/2, -sh/2, 0)
    sc = (sw/2, sh/2, 0)
    sd = (-sw/2, sh/2, 0)
    # 应用旋转（绕Y轴？实际上帆绕垂直轴旋转 sail_angle）
    # 这里简化为绕Z轴旋转（使帆左右倾斜），实际应该是绕Y轴？
    # 为了视觉效果，让帆绕垂直轴（Y轴）旋转 sail_angle
    def rot_y_local(pt, ang):
        x, y, z = pt
        c, s = math.cos(ang), math.sin(ang)
        return (x*c + z*s, y, -x*s + z*c)
    sa = rot_y_local(sa, sail_angle)
    sb = rot_y_local(sb, sail_angle)
    sc = rot_y_local(sc, sail_angle)
    sd = rot_y_local(sd, sail_angle)
    # 平移到中心
    sa = (sa[0]+sail_center[0], sa[1]+sail_center[1], sa[2]+sail_center[2])
    sb = (sb[0]+sail_center[0], sb[1]+sail_center[1], sb[2]+sail_center[2])
    sc = (sc[0]+sail_center[0], sc[1]+sail_center[1], sc[2]+sail_center[2])
    sd = (sd[0]+sail_center[0], sd[1]+sail_center[1], sd[2]+sail_center[2])
    tris.append((sa, sb, sc, sail_color))
    tris.append((sa, sc, sd, sail_color))
    return tris

def build_island(island_data):
    """返回岛屿的三角形列表"""
    x, z, r, _ = island_data
    tris = []
    # 用圆锥近似：底部圆环，顶部尖点
    segments = 12
    height = r * 0.6
    top = (0, height, 0)
    bottom_center = (0, -0.5, 0)
    green = (60, 145, 40)
    brown = (135, 105, 60)
    for i in range(segments):
        a1 = 2*math.pi * i / segments
        a2 = 2*math.pi * (i+1) / segments
        p1 = (r*math.cos(a1), -0.5, r*math.sin(a1))
        p2 = (r*math.cos(a2), -0.5, r*math.sin(a2))
        # 侧面三角形
        tris.append((p1, p2, top, green))
        # 底面三角形（可选）
        tris.append((p1, bottom_center, p2, brown))
    return tris

# ========== 渲染 ==========
def render_frame(cam_pos_world, cam_rot_world):
    screen.fill((10, 15, 30))  # 深蓝夜空
    # 收集所有三角形
    all_tris = []
    # 1. 海面（一个巨大的平面，分成网格）
    grid_size = 20
    cell = 20
    sea_color = (30, 80, 125)
    for gx in range(-grid_size, grid_size):
        for gz in range(-grid_size, grid_size):
            x0 = gx * cell
            z0 = gz * cell
            x1 = (gx+1) * cell
            z1 = (gz+1) * cell
            # 四个顶点，y=0（海平面）
            p00 = (x0, 0, z0)
            p10 = (x1, 0, z0)
            p01 = (x0, 0, z1)
            p11 = (x1, 0, z1)
            # 两个三角形
            all_tris.append((p00, p10, p11, sea_color))
            all_tris.append((p00, p11, p01, sea_color))
    # 2. 船
    ship_tris = build_ship()
    # 将船从局部坐标转换到世界坐标（旋转+平移）
    for tri in ship_tris:
        v0, v1, v2, col = tri
        # 旋转（绕Y轴，船朝向）
        v0 = rotate_y(v0, ship_heading)
        v1 = rotate_y(v1, ship_heading)
        v2 = rotate_y(v2, ship_heading)
        # 平移到船位置
        v0 = (v0[0]+ship_pos[0], v0[1]+ship_pos[1], v0[2]+ship_pos[2])
        v1 = (v1[0]+ship_pos[0], v1[1]+ship_pos[1], v1[2]+ship_pos[2])
        v2 = (v2[0]+ship_pos[0], v2[1]+ship_pos[1], v2[2]+ship_pos[2])
        all_tris.append((v0, v1, v2, col))
    # 3. 岛屿
    for iso in islands:
        iso_tris = build_island(iso)
        for tri in iso_tris:
            v0, v1, v2, col = tri
            v0 = (v0[0]+iso[0], v0[1], v0[2]+iso[1])
            v1 = (v1[0]+iso[0], v1[1], v1[2]+iso[1])
            v2 = (v2[0]+iso[0], v2[1], v2[2]+iso[1])
            all_tris.append((v0, v1, v2, col))
    # 4. 投影 + 深度排序 + 绘制
    projected = []
    for tri in all_tris:
        v0, v1, v2, col = tri
        p0 = project(v0, cam_pos_world, cam_rot_world)
        p1 = project(v1, cam_pos_world, cam_rot_world)
        p2 = project(v2, cam_pos_world, cam_rot_world)
        if p0 is None or p1 is None or p2 is None:
            continue
        # 计算平均深度用于排序
        avg_depth = (p0[2] + p1[2] + p2[2]) / 3.0
        # 光照：法线与光源方向点积
        normal = normal_of_triangle(v0, v1, v2)
        light_dir = (0.5, -0.3, 0.8)  # 固定光源方向
        intensity = dot_product(normal, light_dir)
        intensity = max(0.2, min(1.0, intensity))
        final_color = (int(col[0]*intensity), int(col[1]*intensity), int(col[2]*intensity))
        projected.append((avg_depth, (p0[0], p0[1]), (p1[0], p1[1]), (p2[0], p2[1]), final_color))
    # 画家算法（远到近）
    projected.sort(key=lambda x: x[0], reverse=True)
    for _, p0, p1, p2, col in projected:
        pygame.draw.polygon(screen, col, [p0, p1, p2])
    # 5. HUD 文字
    font = pygame.font.Font(None, 24)
    texts = [
        f"速度: {ship_speed:.1f}",
        f"航向: {math.degrees(ship_heading)%360:.0f}°",
        f"风速: {wind_speed:.1f}",
        f"帆: {sail_area*100:.0f}%",
        f"耐久: {hull_hp:.0f}",
        f"货物: {cargo}",
    ]
    for i, txt in enumerate(texts):
        surf = font.render(txt, True, (200,200,200))
        screen.blit(surf, (10, 10 + i*25))
    if storm_active:
        warn = font.render("暴风雨!", True, (255,100,50))
        screen.blit(warn, (WIDTH//2-40, 20))
    # 小地图
    mm_x, mm_y = WIDTH-150, HEIGHT-150
    pygame.draw.rect(screen, (30,30,50), (mm_x, mm_y, 130, 130))
    scale = 0.012
    # 船
    sx = mm_x + 65 + ship_pos[0]*scale
    sy = mm_y + 65 + ship_pos[2]*scale
    pygame.draw.circle(screen, (255,180,50), (int(sx), int(sy)), 4)
    # 岛屿
    for iso in islands:
        ix = mm_x + 65 + iso[0]*scale
        iy = mm_y + 65 + iso[1]*scale
        pygame.draw.circle(screen, (80,180,80), (int(ix), int(iy)), 3)

# ========== 物理更新 ==========
def update(dt):
    global ship_speed, ship_pos, hull_hp, cargo, storm_active, storm_timer, wind_speed, wind_direction, sail_area, sail_angle
    # 风力
    rel_wind = wind_direction - ship_heading
    eff = math.cos(sail_angle - rel_wind) * sail_area
    eff = max(-0.3, min(1.0, eff))
    target = wind_speed * eff * 0.12
    ship_speed += (target - ship_speed) * dt * 0.5
    ship_speed = max(0, ship_speed)
    if not anchor_down:
        ship_pos[0] += ship_speed * dt * math.cos(ship_heading)
        ship_pos[2] += ship_speed * dt * math.sin(ship_heading)
    # 岛屿碰撞
    for iso in islands:
        dx = ship_pos[0] - iso[0]
        dz = ship_pos[2] - iso[1]
        dist = math.hypot(dx, dz)
        if dist < iso[2]:
            if not iso[3]:
                cargo += 2
                print("发现岛屿！获得2货物")
                iso = list(iso)
                iso[3] = True
                islands[islands.index((iso[0],iso[1],iso[2],False))] = tuple(iso)
            if dist < iso[2]*0.4:
                hull_hp -= 5*dt
    # 暴风雨
    if storm_active:
        storm_timer -= dt
        if storm_timer <= 0:
            storm_active = False
            wind_speed = 8
        else:
            wind_speed = 25 + random.uniform(-3,3)
            if sail_area > 0.3:
                sail_area -= dt*0.3
            hull_hp -= 2*dt
    else:
        wind_direction += random.uniform(-0.05,0.05)*dt
        wind_speed += random.uniform(-0.2,0.2)*dt
        wind_speed = max(2, min(18, wind_speed))
        if random.random() < 0.0003:
            storm_active = True
            storm_timer = 10 + random.uniform(5,10)
            print("暴风雨来袭！")

# ========== 主循环 ==========
def main():
    global ship_heading, sail_area, sail_angle, anchor_down, hull_hp, cargo, cam_pos, cam_rot
    running = True
    last_time = pygame.time.get_ticks()/1000
    while running:
        dt = (pygame.time.get_ticks()/1000 - last_time)
        last_time = pygame.time.get_ticks()/1000
        dt = min(dt, 0.05)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    running = False
                elif event.key == pygame.K_r and cargo >= 5:
                    hull_hp = min(100, hull_hp+25)
                    cargo -= 5
                elif event.key == pygame.K_SPACE:
                    anchor_down = not anchor_down
                elif event.key == pygame.K_q:
                    sail_area = min(1.0, sail_area+0.2)
                elif event.key == pygame.K_e:
                    sail_area = max(0.0, sail_area-0.2)
                elif event.key == pygame.K_w:
                    sail_angle = min(1.2, sail_angle+0.15)
                elif event.key == pygame.K_s:
                    sail_angle = max(-1.2, sail_angle-0.15)
                elif event.key == pygame.K_a:
                    ship_heading += 0.06
                elif event.key == pygame.K_d:
                    ship_heading -= 0.06
        # 按键持续
        keys = pygame.key.get_pressed()
        if keys[pygame.K_a]:
            ship_heading += 0.06
        if keys[pygame.K_d]:
            ship_heading -= 0.06
        if keys[pygame.K_w]:
            sail_angle = min(1.2, sail_angle+0.15)
        if keys[pygame.K_s]:
            sail_angle = max(-1.2, sail_angle-0.15)
        if keys[pygame.K_q]:
            sail_area = min(1.0, sail_area+0.2)
        if keys[pygame.K_e]:
            sail_area = max(0.0, sail_area-0.2)
        if keys[pygame.K_r] and cargo >= 5:
            hull_hp = min(100, hull_hp+25)
            cargo -= 5
        if keys[pygame.K_SPACE]:
            anchor_down = True
        else:
            anchor_down = False
        # 更新
        update(dt)
        # 相机位置跟随船
        cam_pos[0] = ship_pos[0] - 30 * math.cos(ship_heading)
        cam_pos[2] = ship_pos[2] - 30 * math.sin(ship_heading)
        cam_pos[1] = ship_pos[1] + 15
        cam_rot = ship_heading
        # 渲染
        render_frame(cam_pos, cam_rot)
        pygame.display.flip()
        clock.tick(30)
    pygame.quit()
    sys.exit()

if __name__ == "__main__":
    main()