# GESP等级：二级 | GESP Python 二级考点
"""
============================================================================
  第3节：math综合 —— 综合创意项目：数学万花筒
  课程编号: GESP #35
  时长规划: 40:00 - 55:00 (综合实操部分)
  前置知识: math模块 + random模块 + turtle绘图
============================================================================

【项目简介】
用 Python 的 turtle 绘图 + math 三角函数 + random 随机数 + time 动画控制，
绘制一个不断旋转变化的"数学万花筒"。

核心原理：
  万花筒的本质是"对称"—— 通过 math.sin/cos 计算圆周上的点，
  再通过对称变换（镜像、旋转）产生复杂图案。

============================================================================
版本 1：基础万花筒 —— 8 重旋转对称（入门）
============================================================================

目标：画一个围绕中心旋转的彩色轮盘，8 条对称轴

"""
import turtle
import math
import random
import time

def draw_kaleidoscope_basic():
    """
    基础万花筒：在圆周上画彩色圆点，8重对称
    """
    t = turtle.Turtle()
    t.speed(0)
    t.hideturtle()
    screen = turtle.Screen()
    screen.bgcolor("black")
    screen.tracer(0)  # 关闭逐帧动画，提高绘制速度

    def draw_frame(angle_offset):
        """
        绘制一帧：
        - angle_offset：旋转偏移量（随时间变化，产生动画效果）
        - 在圆周上放置 12 个点，每个点 8 重对称（镜像旋转）
        """
        t.clear()
        radius = 150  # 大圆半径
        n_points = 12  # 每帧点数
        n_symmetry = 8  # 对称轴数

        for i in range(n_points):
            # 基础角度 = 当前点的角度 + 动画偏移
            base_angle = (2 * math.pi / n_points) * i + angle_offset

            for j in range(n_symmetry):
                # 对称变换角度 = 基础角度 + 旋转对称
                sym_angle = base_angle + (2 * math.pi / n_symmetry) * j

                # 计算点的位置（半径略有随机变化，更加自然）
                r_var = radius + random.uniform(-20, 20)
                x = r_var * math.cos(sym_angle)
                y = r_var * math.sin(sym_angle)

                # 随机颜色（HSL 色相环）
                hue = (i / n_points + j / n_symmetry + time.time() * 0.02) % 1.0
                t.pencolor(hsv_to_rgb(hue, 0.8, 0.9))
                t.penup()
                t.goto(x, y)
                t.pendown()

                # 点的大小随角度变化
                dot_size = 5 + 5 * abs(math.sin(sym_angle * 2))
                t.dot(dot_size)

    def hsv_to_rgb(h, s, v):
        """HSV → RGB 颜色转换（用于生成鲜艳的渐变色）"""
        if s == 0:
            return (v, v, v)
        i = int(h * 6)
        f = (h * 6) - i
        p = v * (1 - s)
        q = v * (1 - s * f)
        t_val = v * (1 - s * (1 - f))
        i %= 6
        if i == 0:
            return (v, t_val, p)
        elif i == 1:
            return (q, v, p)
        elif i == 2:
            return (p, v, t_val)
        elif i == 3:
            return (p, q, v)
        elif i == 4:
            return (t_val, p, v)
        else:
            return (v, p, q)

    # 主循环：不断旋转产生动画效果
    angle = 0
    for _ in range(200):
        draw_frame(angle)
        screen.update()
        angle += 0.05
        time.sleep(0.03)

    screen.exitonclick()


# ====== 取消下方注释运行基础万花筒 ======
# draw_kaleidoscope_basic()


# ============================================================================
# 版本 2：进阶万花筒 —— 螺旋万花筒（进阶）
# ============================================================================

"""
进阶版特点：
1. 半径随时间变化（呼吸效果）
2. 螺旋形排列（半径递增）
3. 颜色连续渐变
4. 拖尾效果（半透明感）
"""

def draw_kaleidoscope_advanced():
    """
    螺旋万花筒：
    - 使用 math.sin/cos 计算螺旋路径
    - 半径随时间振荡（呼吸效果）
    - 对称轴 6 重旋转
    """
    t = turtle.Turtle()
    t.speed(0)
    t.hideturtle()
    screen = turtle.Screen()
    screen.bgcolor("#0a0a2e")
    screen.tracer(0)

    def draw_spiral_frame(t, time_offset):
        """绘制一帧螺旋万花筒"""
        t.clear()
        n_spirals = 5      # 螺旋圈数
        n_arms = 6         # 对称臂数
        steps = 80         # 每臂点数
        max_radius = 200

        for arm in range(n_arms):
            base_angle = (2 * math.pi / n_arms) * arm

            for step in range(steps):
                # 半径从中心向外递增，叠加呼吸振荡
                progress = step / steps
                radius = progress * max_radius
                # 呼吸效果：半径微振荡
                radius += 15 * math.sin(progress * 4 * math.pi + time_offset)

                # 螺旋角度 = 基础角度 + 螺旋旋转 + 整体旋转
                spiral_angle = (progress * 2 * math.pi * n_spirals
                                + base_angle + time_offset)

                x = radius * math.cos(spiral_angle)
                y = radius * math.sin(spiral_angle)

                # 颜色：沿半径渐变，随时间变化
                hue = (progress + arm / n_arms + time_offset * 0.1) % 1.0
                t.pencolor(hsv_to_rgb(hue, 0.9, 0.8 + 0.2 * abs(math.sin(progress * math.pi))))

                t.penup()
                t.goto(x, y)
                t.pendown()
                # 点的大小从中心到边缘逐渐变小
                dot_size = 6 - 4 * progress + 2 * abs(math.sin(spiral_angle * 2))
                t.dot(max(1, dot_size))

    # 主循环
    t_offset = 0
    for _ in range(300):
        draw_spiral_frame(t, t_offset)
        screen.update()
        t_offset += 0.03
        time.sleep(0.02)

    screen.exitonclick()


# ====== 取消下方注释运行进阶万花筒 ======
# draw_kaleidoscope_advanced()


# ============================================================================
# 版本 3：综合万花筒 —— 星空万花筒（挑战）
# ============================================================================

"""
综合版特点：
1. 同时使用 random、time、math 三大模块
2. 随机生成 "星星" 并沿对称轴复制
3. 星星大小、颜色、闪烁频率各不相同
4. 整体图案缓慢旋转

数学知识点覆盖：
  - math.sin / math.cos：角度定位
  - math.pi：角度单位
  - math.sqrt：距离计算（判断星星是否在圆内）
  - random 系列：随机属性
  - time.sleep：帧率控制
"""

def draw_kaleidoscope_super():
    """
    星空万花筒（最终综合版）
    """
    screen = turtle.Screen()
    screen.bgcolor("#050510")
    screen.tracer(0)
    screen.setup(600, 600)

    t = turtle.Turtle()
    t.speed(0)
    t.hideturtle()

    # ---- 初始化：随机生成一批"星星" ----
    random.seed(time.time())
    num_stars = 30
    stars = []

    for _ in range(num_stars):
        # 在极坐标中随机生成星星位置
        r = random.uniform(30, 220)        # 半径
        theta = random.uniform(0, 2 * math.pi)  # 角度
        size = random.uniform(3, 8)         # 基础大小
        twinkle_speed = random.uniform(1, 4)  # 闪烁速度
        hue = random.uniform(0, 1)          # 色相
        stars.append((r, theta, size, twinkle_speed, hue))

    # 对称轴数量
    n_sym = 8

    def draw_frame(angle_offset, t_frame):
        """绘制一帧星空万花筒"""
        t.clear()

        for r, theta, base_size, twinkle_speed, base_hue in stars:
            for sym_i in range(n_sym):
                # 对称旋转
                sym_angle = theta + (2 * math.pi / n_sym) * sym_i + angle_offset

                # 笛卡尔坐标
                x = r * math.cos(sym_angle)
                y = r * math.sin(sym_angle)

                # 闪烁效果：大小随时间正弦变化
                twinkle = 0.5 + 0.5 * math.sin(t_frame * twinkle_speed + sym_i)
                dot_size = base_size * (0.5 + twinkle)

                # 颜色也随闪烁微变
                brightness = 0.6 + 0.4 * twinkle
                hue = (base_hue + sym_i / n_sym + t_frame * 0.01) % 1.0
                t.pencolor(hsv_to_rgb(hue, 0.9, brightness))

                t.penup()
                t.goto(x, y)
                t.pendown()
                t.dot(dot_size)

    # ---- 主循环 ----
    frame = 0
    try:
        while True:
            draw_frame(frame * 0.02, frame * 0.05)
            screen.update()
            frame += 1
            time.sleep(0.03)
    except turtle.Terminator:
        pass
    except KeyboardInterrupt:
        pass


# ====== 取消下方注释运行星空万花筒 ======
# draw_kaleidoscope_super()


# ============================================================================
# 附加挑战：万花筒原理探究
# ============================================================================

"""
思考题：
1. 为什么用 math.sin/cos 画圆形？它们和圆有什么关系？
   → 圆上任意一点可表示为 (r*cosθ, r*sinθ)，θ 从 0 到 2π

2. 8重对称是怎么实现的？
   → 每个基础点复制 8 份，每份旋转 360°/8 = 45°

3. 如果想改成 12 重对称，怎么改？
   → 将 n_symmetry 改成 12，每份旋转 360°/12 = 30°

4. math 模块在这里起到了什么作用？
   → 三角函数 → 圆周定位
   → 圆周率 → 角度单位转换
   → 数学计算 → 坐标变换、大小计算

拓展实验：
  - 改变 n_symmetry 的值观察对称效果变化
  - 给星星加上轨道运动（r 随时间变化）
  - 在鼠标点击位置产生新的"星爆"
"""

if __name__ == "__main__":
    print("=== 数学万花筒 ===")
    print("请选择一个版本运行：")
    print("1. 基础万花筒 (8重对称彩色轮盘)")
    print("2. 进阶万花筒 (螺旋对称)")
    print("3. 星空万花筒 (综合版)")
    print()
    print("打开对应文件，取消 draw_xxx() 的注释即可运行")
    print("需要安装 Python + turtle 模块（内置）")
