"""
╔══════════════════════════════════════════════════════════════╗
║                    七巧板模拟器 — Pygame                    ║
║  Tangram Simulator                                           ║
╚══════════════════════════════════════════════════════════════╝

标准七巧板共 7 块，外接正方形面积 = 16 单位²：
  ● 大三角形 ×2  (各 4)   ● 中三角形 ×1 (2)
  ● 小三角形 ×2  (各 1)   ● 正方形 ×1   (2)
  ● 平行四边形 ×1  (2)

操作：左键拖拽 | 滚轮旋转(±15°) | 右键翻转 | 空格切换目标
"""

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

# ─── 初始化 ───
pygame.init()
pygame.font.init()

# ─── 常量 ───
WINDOW_W, WINDOW_H = 1280, 800
BOARD_X, BOARD_Y = 50, 60
BOARD_W, BOARD_H = 700, 700
PANEL_X = BOARD_X + BOARD_W + 25
PANEL_W = WINDOW_W - PANEL_X - 25

FPS = 60
BG_COLOR = (28, 28, 42)
BOARD_BG = (42, 42, 62)
GRID_COLOR = (58, 58, 82)

# 七巧板配色（柔和鲜艳）
COLORS = {
    'red':    (231,  76,  60),
    'blue':   ( 52, 152, 219),
    'green':  ( 46, 204, 113),
    'yellow': (241, 196,  15),
    'purple': (155,  89, 182),
    'orange': (230, 126,  34),
    'cyan':   ( 26, 188, 156),
}

# 单位：外接正方形边长的一半在屏幕上的像素
UNIT = 150  # 外接正方形屏幕边长 = 2*UNIT = 300px


# ═══════════════════════════════════════════════════════════
#  工具函数
# ═══════════════════════════════════════════════════════════

def point_in_polygon(point, polygon):
    """射线法判断点是否在多边形内"""
    x, y = point
    n = len(polygon)
    inside = False
    j = n - 1
    for i in range(n):
        xi, yi = polygon[i]
        xj, yj = polygon[j]
        if ((yi > y) != (yj > y)) and (x < (xj - xi) * (y - yi) / (yj - yi + 1e-12) + xi):
            inside = not inside
        j = i
    return inside


def polygon_centroid(polygon):
    """计算多边形重心"""
    n = len(polygon)
    cx = sum(p[0] for p in polygon) / n
    cy = sum(p[1] for p in polygon) / n
    return (cx, cy)


def shoelace(verts):
    """鞋带公式计算多边形面积"""
    n = len(verts)
    area = 0.0
    for i in range(n):
        j = (i + 1) % n
        area += verts[i][0] * verts[j][1]
        area -= verts[j][0] * verts[i][1]
    return abs(area) / 2


# ═══════════════════════════════════════════════════════════
#  标准七巧板数据（坐标经过面积验证）
# ═══════════════════════════════════════════════════════════
#
#  外接正方形: [0, 4] × [0, 4]  (面积 = 16)
#  y 轴向上（数学坐标），渲染时翻转
#
#  切割方案:
#    对角线 (0,0)↔(4,4) 分正方形为两个面积=8的直角三角形
#
#  下三角 (0,0)-(4,0)-(4,4) 面积=8:
#    ★ 大三角 (0,0)-(4,0)-(2,2)        面积=4  ✓
#    ★ 中三角 (4,0)-(4,2)-(2,2)        面积=2  ✓
#    ★ 小三角 (4,2)-(3,3)-(2,2)        面积=1  ✓
#    ★ 小三角 (4,2)-(4,4)-(3,3)        面积=1  ✓
#
#  上三角 (0,0)-(4,4)-(0,4) 面积=8:
#    ★ 大三角 (0,4)-(4,4)-(2,2)        面积=4  ✓
#    ★ 正方形 (0,2)-(1,1)-(2,2)-(1,3)  面积=2  ✓
#    ★ 平行四边形 (0,0)-(0,2)-(1,3)-(1,1) 面积=2 ✓
#
#  总面积 = 4+2+1+1+4+2+2 = 16 ✓

PIECES_DATA = [
    #  idx  name       vertices                          color     display_name
    (0,   '大三角1', [(0, 0), (4, 0), (2, 2)],       'red',    'Big 1'),
    (1,   '中三角',  [(4, 0), (4, 2), (2, 2)],       'green',  'Med'),
    (2,   '小三角1', [(4, 2), (3, 3), (2, 2)],       'yellow', 'Sm 1'),
    (3,   '小三角2', [(4, 2), (4, 4), (3, 3)],       'purple', 'Sm 2'),
    (4,   '大三角2', [(0, 4), (4, 4), (2, 2)],       'blue',   'Big 2'),
    (5,   '正方形',  [(0, 2), (1, 1), (2, 2), (1, 3)], 'orange','Sqr'),
    (6,   '平行四边',[(0, 0), (0, 2), (1, 3), (1, 1)], 'cyan',  'Par'),
]


def verify_tangram():
    """验证所有板块面积之和 = 16"""
    total = 0.0
    for idx, name, verts, color_key, dname in PIECES_DATA:
        a = shoelace(verts)
        total += a
        print(f"  {dname:6s} ({name:6s}): 面积 = {a:5.2f}")
    print(f"  ─────────────────────────────")
    print(f"  {'总计':6s}: 面积 = {total:5.2f}  (期望 16.00)")
    assert abs(total - 16.0) < 0.001, f"面积验证失败: {total} ≠ 16"
    print(f"  ✅ 面积验证通过!\n")


# ═══════════════════════════════════════════════════════════
#  板块类
# ═══════════════════════════════════════════════════════════

class TangramPiece:
    """七巧板中的一块"""

    def __init__(self, local_verts, color, name, piece_id):
        self.local_verts = [(float(v[0]), float(v[1])) for v in local_verts]
        self.color = color
        self.name = name
        self.piece_id = piece_id
        self.pos = [0.0, 0.0]
        self.angle = 0.0
        self.flipped = False
        self.selected = False
        self.dragging = False
        self.drag_offset = (0, 0)
        self.target_alpha = 255
        self.current_alpha = 255

    # ── 几何变换 ──
    def get_vertices(self):
        """返回当前世界坐标"""
        verts = []
        rad = math.radians(self.angle)
        ca, sa = math.cos(rad), math.sin(rad)
        for lx, ly in self.local_verts:
            if self.flipped:
                ly = -ly
            wx = lx * ca - ly * sa
            wy = lx * sa + ly * ca
            verts.append((wx + self.pos[0], wy + self.pos[1]))
        return verts

    def contains_point(self, pt):
        return point_in_polygon(pt, self.get_vertices())

    # ── 操作 ──
    def rotate(self, delta):
        self.angle = (self.angle + delta) % 360

    def flip(self):
        self.flipped = not self.flipped

    def move_to(self, x, y):
        self.pos = [float(x), float(y)]

    def move_by(self, dx, dy):
        self.pos[0] += dx
        self.pos[1] += dy

    # ── 渲染 ──
    def draw(self, surface, font):
        verts = self.get_vertices()
        if len(verts) < 3:
            return

        # 阴影
        shadow = [(v[0]+3, v[1]+3) for v in verts]
        pygame.draw.polygon(surface, (0, 0, 0, 70), shadow)

        # 主体
        pygame.draw.polygon(surface, (*self.color, int(self.current_alpha)), verts)

        # 边框
        bw = 3 if self.selected else 2
        bc = (255, 255, 255) if self.selected else (20, 20, 30)
        pygame.draw.polygon(surface, bc, verts, bw)

        # 选中光晕
        if self.selected:
            for i in range(2):
                o = (2 - i) * 2
                glow = [(v[0]+random.uniform(-o, o), v[1]+random.uniform(-o, o)) for v in verts]
                pygame.draw.polygon(surface, (255, 255, 255, 100 - i*50), glow, 1)

        # 中心标签
        if not self.dragging:
            cx, cy = polygon_centroid(verts)
            txt = font.render(self.name[:2], True, (255, 255, 255, 160))
            r = txt.get_rect(center=(cx, cy))
            surface.blit(txt, r)


# ═══════════════════════════════════════════════════════════
#  目标形状
# ═══════════════════════════════════════════════════════════

def get_targets():
    """定义多个目标形状"""
    u = UNIT
    ox, oy = BOARD_X + u, BOARD_Y + u  # 外接正方形左上角在棋盘中的坐标

    def to_screen(verts):
        """数学坐标 → 屏幕坐标（y翻转）"""
        return [(ox + v[0]*u, oy + (4 - v[1])*u) for v in verts]

    targets = {}

    # ── 1. 正方形（还原）──
    targets['square'] = {
        'name': '正方形 (还原)',
        'outline': to_screen([(0, 0), (4, 0), (4, 4), (0, 4)]),
    }

    # ── 2. 大三角形 ──
    targets['big_triangle'] = {
        'name': '大三角形',
        'outline': to_screen([(0, 0), (4, 0), (2, 4)]),
    }

    # ── 3. 长方形 ──
    targets['rectangle'] = {
        'name': '长方形',
        'outline': to_screen([(0, 1), (4, 1), (4, 3), (0, 3)]),
    }

    # ── 4. 梯形 ──
    targets['trapezoid'] = {
        'name': '梯形',
        'outline': to_screen([(0, 0), (4, 0), (3, 4), (1, 4)]),
    }

    # ── 5. 平行四边形 ──
    targets['parallelogram'] = {
        'name': '斜平行四边形',
        'outline': to_screen([(1, 0), (4, 0), (3, 4), (0, 4)]),
    }

    # ── 6. 火箭 ──
    targets['rocket'] = {
        'name': '🚀 火箭',
        'outline': to_screen([(2, 0), (3, 2), (3, 3), (4, 3), (4, 4),
                              (0, 4), (0, 3), (1, 3), (1, 2)]),
    }

    # ── 7. 房子 ──
    targets['house'] = {
        'name': '🏠 房子',
        'outline': to_screen([(0, 1), (2, 4), (4, 1), (4, 0), (0, 0)]),
    }

    # ── 8. 松树 ──
    targets['tree'] = {
        'name': '🎄 松树',
        'outline': to_screen([(2, 0), (4, 2), (3, 2), (4, 4),
                              (0, 4), (1, 2), (0, 2)]),
    }

    return targets


# ═══════════════════════════════════════════════════════════
#  主游戏类
# ═══════════════════════════════════════════════════════════

class TangramGame:
    def __init__(self):
        self.screen = pygame.display.set_mode((WINDOW_W, WINDOW_H))
        pygame.display.set_caption('七巧板模拟器 · Tangram')
        self.clock = pygame.time.Clock()

        # 字体
        self.font_tiny = pygame.font.SysFont('wenquanyi micro hei', 16)
        self.font_small = pygame.font.SysFont('wenquanyi micro hei', 19)
        self.font_med = pygame.font.SysFont('wenquanyi micro hei', 24)
        self.font_large = pygame.font.SysFont('wenquanyi micro hei', 36)
        self.font_title = pygame.font.SysFont('wenquanyi micro hei', 48)

        # 创建板块
        self.pieces = []
        self.create_pieces()

        # 状态
        self.selected_piece = None
        self.show_outline = True
        self.targets = get_targets()
        self.target_keys = list(self.targets.keys())
        self.current_target_idx = 0
        self.particles = []

        # 消息
        self.message = "拖拽板块来拼图！滚轮旋转(±15°)，右键翻转"
        self.msg_timer = 300

        # 按钮
        self.buttons = self.create_buttons()

        # 初始打乱
        self.scatter_pieces()

    # ── 初始化 ──
    def create_pieces(self):
        for idx, name, verts, color_key, dname in PIECES_DATA:
            # 计算重心作为本地原点
            cx = sum(v[0] for v in verts) / len(verts)
            cy = sum(v[1] for v in verts) / len(verts)
            local = [(v[0]-cx, v[1]-cy) for v in verts]
            color = COLORS[color_key]
            piece = TangramPiece(local, color, dname, idx)
            self.pieces.append(piece)

    def scatter_pieces(self):
        """打乱放置"""
        u = UNIT
        board_cx = BOARD_X + BOARD_W / 2
        board_cy = BOARD_Y + BOARD_H / 2
        for piece in self.pieces:
            # 随机位置（在棋盘内）
            sx = random.uniform(BOARD_X + 50, BOARD_X + BOARD_W - 50)
            sy = random.uniform(BOARD_Y + 50, BOARD_Y + BOARD_H - 50)
            piece.move_to(sx, sy)
            piece.angle = random.choice([0, 45, 90, 135, 180, 225, 270, 315])
            piece.flipped = random.choice([True, False])
            piece.selected = False
            piece.dragging = False

    def create_buttons(self):
        bx = PANEL_X
        by = BOARD_Y + 20
        bw, bh = 170, 42
        gap = 55
        return {
            'outline':  {'rect': pygame.Rect(bx, by, bw, bh), 'text': '👁 隐藏轮廓', 'action': 'toggle_outline'},
            'reset':    {'rect': pygame.Rect(bx, by+gap, bw, bh), 'text': '🔄 重置位置', 'action': 'reset'},
            'target':   {'rect': pygame.Rect(bx, by+gap*2, bw, bh), 'text': '🎯 切换目标', 'action': 'next_target'},
            'scatter':  {'rect': pygame.Rect(bx, by+gap*3, bw, bh), 'text': '🎲 随机打乱', 'action': 'scatter'},
        }

    # ── 交互 ──
    def get_piece_at(self, pos):
        for piece in reversed(self.pieces):
            if piece.contains_point(pos):
                return piece
        return None

    def bring_to_front(self, piece):
        if piece in self.pieces:
            self.pieces.remove(piece)
            self.pieces.append(piece)

    def rotate_piece(self, piece, delta):
        piece.rotate(delta)
        self.spawn_particles(piece.pos, piece.color, 4)

    def flip_piece(self, piece):
        piece.flip()
        self.spawn_particles(piece.pos, (255, 255, 255), 6)

    # ── 粒子 ──
    def spawn_particles(self, pos, color, count=5):
        for _ in range(count):
            a = random.uniform(0, 2*math.pi)
            s = random.uniform(1, 4)
            self.particles.append({
                'pos': [pos[0], pos[1]],
                'vel': [math.cos(a)*s, math.sin(a)*s],
                'color': color,
                'life': random.randint(20, 45),
                'max_life': 45,
                'size': random.randint(2, 5),
            })

    def update_particles(self):
        for p in self.particles:
            p['pos'][0] += p['vel'][0]
            p['pos'][1] += p['vel'][1]
            p['vel'][0] *= 0.94
            p['vel'][1] *= 0.94
            p['life'] -= 1
        self.particles = [p for p in self.particles if p['life'] > 0]

    def draw_particles(self):
        for p in self.particles:
            a = int(255 * p['life'] / p['max_life'])
            pygame.draw.circle(self.screen, (*p['color'][:3], a),
                              (int(p['pos'][0]), int(p['pos'][1])), p['size'])

    # ── 渲染 ──
    def draw_board(self):
        # 棋盘背景
        rect = pygame.Rect(BOARD_X, BOARD_Y, BOARD_W, BOARD_H)
        pygame.draw.rect(self.screen, BOARD_BG, rect, border_radius=14)
        pygame.draw.rect(self.screen, (80, 80, 115), rect, 3, border_radius=14)

        # 网格
        sp = UNIT
        for x in range(int(BOARD_X), int(BOARD_X+BOARD_W+1), sp):
            pygame.draw.line(self.screen, GRID_COLOR, (x, BOARD_Y), (x, BOARD_Y+BOARD_H), 1)
        for y in range(int(BOARD_Y), int(BOARD_Y+BOARD_H+1), sp):
            pygame.draw.line(self.screen, GRID_COLOR, (BOARD_X, y), (BOARD_X+BOARD_W, y), 1)

    def draw_outline(self):
        if not self.show_outline:
            return
        target = self.targets[self.target_keys[self.current_target_idx]]
        pts = target['outline']
        if len(pts) < 3:
            return
        overlay = pygame.Surface((BOARD_W, BOARD_H), pygame.SRCALPHA)
        screen_pts = [(p[0]-BOARD_X, p[1]-BOARD_Y) for p in pts]
        pygame.draw.polygon(overlay, (100, 200, 255, 25), screen_pts)
        pygame.draw.polygon(overlay, (100, 200, 255, 100), screen_pts, 2)
        self.screen.blit(overlay, (BOARD_X, BOARD_Y))

    def draw_panel(self):
        px = PANEL_X
        py = BOARD_Y
        rect = pygame.Rect(px-10, py, PANEL_W+10, BOARD_H)
        pygame.draw.rect(self.screen, (45, 45, 65), rect, border_radius=12)
        pygame.draw.rect(self.screen, (75, 75, 105), rect, 2, border_radius=12)

        x = px + 15
        y = py + 18

        # 标题
        t = self.font_title.render('七巧板', True, (255, 230, 200))
        self.screen.blit(t, (x, y))
        y += 55

        # 当前目标
        key = self.target_keys[self.current_target_idx]
        tgt = self.targets[key]
        t = self.font_med.render(f'🎯 {tgt["name"]}', True, (180, 220, 255))
        self.screen.blit(t, (x, y))
        y += 42

        # 操作说明
        tips = ['操作说明:', '· 左键拖拽 移动板块',
                '· 滚轮滚动 旋转 ±15°', '· 右键点击 翻转板块',
                '· 选中自动置顶']
        for line in tips:
            t = self.font_tiny.render(line, True, (170, 170, 195))
            self.screen.blit(t, (x, y))
            y += 22
        y += 18

        # 板块列表
        t = self.font_med.render('板块:', True, (180, 220, 255))
        self.screen.blit(t, (x, y))
        y += 32
        for piece in self.pieces:
            c = piece.color
            r = pygame.Rect(x, y+2, 14, 14)
            pygame.draw.rect(self.screen, c, r, border_radius=3)
            pygame.draw.rect(self.screen, (255, 255, 255), r, 1, border_radius=3)
            info = f'{piece.name}  {piece.angle:5.0f}°{"  [F]" if piece.flipped else ""}'
            t = self.font_tiny.render(info, True, (210, 210, 230))
            self.screen.blit(t, (x+20, y))
            y += 22

        y += 15

        # 按钮
        for bk, bv in self.buttons.items():
            r = bv['rect']
            pygame.draw.rect(self.screen, (60, 85, 130), r, border_radius=10)
            pygame.draw.rect(self.screen, (110, 145, 200), r, 2, border_radius=10)
            t = self.font_small.render(bv['text'], True, (255, 255, 255))
            self.screen.blit(t, t.get_rect(center=r.center))

        y = self.buttons['scatter']['rect'].bottom + 25

        # 进度条
        t = self.font_med.render('完成度:', True, (180, 220, 255))
        self.screen.blit(t, (x, y))
        y += 30
        prog = self.calc_progress()
        bw, bh = 180, 18
        br = pygame.Rect(x, y, bw, bh)
        pygame.draw.rect(self.screen, (55, 55, 75), br, border_radius=5)
        if prog > 0:
            fr = pygame.Rect(x, y, int(bw*prog), bh)
            pygame.draw.rect(self.screen, (46, 204, 113), fr, border_radius=5)
        pygame.draw.rect(self.screen, (110, 110, 145), br, 2, border_radius=5)
        t = self.font_tiny.render(f'{int(prog*100)}%', True, (255, 255, 255))
        self.screen.blit(t, (x+bw+10, y))

        y += 35
        # 消息
        if self.msg_timer > 0:
            t = self.font_tiny.render(self.message, True, (255, 240, 150))
            self.screen.blit(t, (x, y))
            self.msg_timer -= 1

    def calc_progress(self):
        cx = BOARD_X + BOARD_W/2
        cy = BOARD_Y + BOARD_H/2
        r = BOARD_W * 0.35
        count = 0
        for p in self.pieces:
            d = math.hypot(p.pos[0]-cx, p.pos[1]-cy)
            if d < r:
                count += 1
        return count / len(self.pieces)

    # ── 事件 ──
    def handle_event(self, event):
        if event.type == QUIT:
            return False

        if event.type == MOUSEBUTTONDOWN:
            pos = event.pos
            # 按钮
            for bv in self.buttons.values():
                if bv['rect'].collidepoint(pos):
                    self.do_action(bv['action'])
                    return True

            if event.button == 1:  # 左键
                piece = self.get_piece_at(pos)
                if piece:
                    self.selected_piece = piece
                    self.bring_to_front(piece)
                    piece.selected = True
                    piece.dragging = True
                    piece.drag_offset = (pos[0]-piece.pos[0], pos[1]-piece.pos[1])
                    for p in self.pieces:
                        if p != piece:
                            p.selected = False
            elif event.button == 3:  # 右键
                piece = self.get_piece_at(pos)
                if piece:
                    self.flip_piece(piece)
                    self.message = f'翻转了 {piece.name}'
                    self.msg_timer = 90

        elif event.type == MOUSEBUTTONUP and event.button == 1:
            for p in self.pieces:
                p.dragging = False

        elif event.type == MOUSEMOTION:
            for p in self.pieces:
                if p.dragging:
                    p.move_to(event.pos[0]-p.drag_offset[0],
                              event.pos[1]-p.drag_offset[1])

        elif event.type == MOUSEWHEEL:
            if self.selected_piece:
                d = 15 if event.y > 0 else -15
                self.rotate_piece(self.selected_piece, d)
                self.message = f'{self.selected_piece.name}: {self.selected_piece.angle:.0f}°'
                self.msg_timer = 50

        elif event.type == KEYDOWN:
            if event.key == K_SPACE:
                self.do_action('next_target')
            elif event.key == K_r:
                self.do_action('reset')
            elif event.key == K_o:
                self.do_action('toggle_outline')
            elif event.key == K_s:
                self.do_action('scatter')
            elif event.key == K_ESCAPE:
                return False
        return True

    def do_action(self, action):
        if action == 'toggle_outline':
            self.show_outline = not self.show_outline
            for bv in self.buttons.values():
                if bv['action'] == 'toggle_outline':
                    bv['text'] = '👁 显示轮廓' if not self.show_outline else '👁 隐藏轮廓'
            self.message = '轮廓 ' + ('隐藏' if self.show_outline else '显示')
            self.msg_timer = 50
        elif action == 'reset':
            self.scatter_pieces()
            self.message = '板块已重置到中心'
            self.msg_timer = 50
        elif action == 'scatter':
            self.scatter_pieces()
            self.message = '板块已随机打乱'
            self.msg_timer = 50
        elif action == 'next_target':
            self.current_target_idx = (self.current_target_idx + 1) % len(self.target_keys)
            name = self.targets[self.target_keys[self.current_target_idx]]['name']
            self.message = f'目标切换为: {name}'
            self.msg_timer = 100

    # ── 主循环 ──
    def update(self):
        self.update_particles()
        for p in self.pieces:
            target = 255 if p.selected else 210
            if p.current_alpha < target:
                p.current_alpha = min(target, p.current_alpha + 15)
            else:
                p.current_alpha = max(target, p.current_alpha - 10)

    def draw(self):
        self.screen.fill(BG_COLOR)
        self.draw_board()
        self.draw_outline()
        for p in self.pieces:
            p.draw(self.screen, self.font_small)
        self.draw_particles()
        self.draw_panel()

        # 顶部状态栏
        bar = pygame.Rect(0, 0, WINDOW_W, 36)
        pygame.draw.rect(self.screen, (35, 35, 55), bar)
        pygame.draw.line(self.screen, (70, 70, 100), (0, 36), (WINDOW_W, 36))
        t = self.font_tiny.render(
            '七巧板 Tangram  |  空格=切换目标  R=重置  S=打乱  O=轮廓  Esc=退出',
            True, (140, 160, 195))
        self.screen.blit(t, (15, 9))

        pygame.display.flip()

    def run(self):
        running = True
        while running:
            for event in pygame.event.get():
                if self.handle_event(event) is False:
                    running = False
            self.update()
            self.draw()
            self.clock.tick(FPS)
        pygame.quit()
        sys.exit()


# ═══════════════════════════════════════════════════════════
#  入口
# ═══════════════════════════════════════════════════════════

if __name__ == '__main__':
    print("╔════════════════════════════════════════╗")
    print("║     七巧板模拟器 — 面积验证           ║")
    print("╚════════════════════════════════════════╝")
    verify_tangram()

    print("启动 Pygame 窗口...")
    game = TangramGame()
    game.run()
