# GESP等级：四级 | GESP Python 四级考点
"""
==================================================
  冒泡排序可视化 —— 用turtle画出排序动画
  面向7-10岁少儿编程教学
==================================================

  使用方法：
  1. 直接运行本文件，会看到彩色柱子排序动画
  2. 可以修改 DATA 列表来改变排序数据
  3. 可以修改 SPEED 来控制排序速度

  口诀：
  "冒泡冒泡，大的往后靠"
  "相邻两两比，大数往后移"
  "外层n-1轮，内层n-1-i次"
==================================================
"""

import turtle
import time
import random

# ==================================================
# 配置区 —— 你可以在这里修改参数！
# ==================================================

# 要排序的数据（可以自己改数字哦！）
DATA = [5, 3, 1, 4, 2]

# 排序速度（数字越小越快，建议0.1~0.5）
SPEED = 0.3

# 柱子颜色（按顺序循环使用）
COLORS = ["red", "orange", "gold", "green", "blue", "purple", "pink", "brown"]

# ==================================================
# turtle设置
# ==================================================

# 创建画布
screen = turtle.Screen()
screen.setup(800, 500)
screen.title("冒泡排序动画 —— 气泡往上冒！")
screen.bgcolor("white")
screen.tracer(0)  # 关闭自动刷新，我们自己控制

# 创建画笔
t = turtle.Turtle()
t.speed(0)
t.hideturtle()

# 信息显示画笔
info = turtle.Turtle()
info.speed(0)
info.hideturtle()
info.penup()

# ==================================================
# 绘图函数
# ==================================================

def draw_bar(x, height, color, bar_width=30):
    """
    画一根柱子
    x: 柱子的x坐标
    height: 柱子的高度（用数字表示）
    color: 柱子的颜色
    bar_width: 柱子的宽度
    """
    t.penup()
    t.goto(x, -200)
    t.pendown()
    t.fillcolor(color)
    t.begin_fill()
    for _ in range(2):
        t.forward(bar_width)
        t.left(90)
        t.forward(height * 10)  # 高度放大10倍，看得更清楚
        t.left(90)
    t.end_fill()

    # 在柱子上方写上数字
    t.penup()
    t.goto(x + bar_width / 2, -200 + height * 10 + 10)
    t.write(str(height), align="center", font=("Arial", 12, "bold"))


def draw_all(arr, highlight=None):
    """
    画所有柱子
    arr: 要画的数组
    highlight: 高亮显示的索引位置（正在比较的两个数）
    """
    t.clear()
    n = len(arr)
    bar_width = 30
    gap = 5  # 柱子间距
    total_width = n * (bar_width + gap)

    # 计算起始x坐标，让所有柱子居中
    start_x = -total_width // 2

    for i in range(n):
        # 选择颜色：如果是正在比较的两个，用特殊颜色
        if highlight and i in highlight:
            color = "yellow"  # 正在比较的用黄色高亮
        else:
            color = COLORS[i % len(COLORS)]

        x = start_x + i * (bar_width + gap)
        draw_bar(x, arr[i], color, bar_width)

    screen.update()


def draw_info(text, y=150):
    """在屏幕上方显示信息文字"""
    info.clear()
    info.goto(0, y)
    info.write(text, align="center", font=("Arial", 16, "bold"))
    screen.update()


def draw_title():
    """画标题"""
    info.goto(0, 220)
    info.write("🎯 冒泡排序动画 —— 泡泡往上冒！", align="center", font=("Arial", 20, "bold"))


def draw_round_info(round_num, total_rounds, compare_count):
    """显示当前轮次信息"""
    info.goto(0, 180)
    info.clear()
    info.write(f"第 {round_num}/{total_rounds} 轮  |  比较 {compare_count} 次",
               align="center", font=("Arial", 14, "normal"))


def draw_finished():
    """排序完成时显示的庆祝信息"""
    info.goto(0, 180)
    info.write("🎉 排序完成！ 太棒啦！ 🎉", align="center", font=("Arial", 20, "bold"))
    # 把所有柱子变成金色
    t.clear()
    n = len(arr)
    bar_width = 30
    gap = 5
    start_x = -(n * (bar_width + gap)) // 2
    for i in range(n):
        x = start_x + i * (bar_width + gap)
        draw_bar(x, arr[i], "gold", bar_width)
    screen.update()


def draw_count(total_swaps):
    """显示总的交换次数"""
    info.goto(0, -220)
    info.write(f"总共交换了 {total_swaps} 次", align="center", font=("Arial", 12, "normal"))


# ==================================================
# 冒泡排序（带可视化）
# ==================================================

def bubble_sort(arr):
    """
    冒泡排序主函数
    口诀：外层n-1轮，内层n-1-i次
    """
    n = len(arr)
    total_swaps = 0  # 统计交换次数
    compare_count = 0  # 统计比较次数

    # 画初始状态
    draw_all(arr)
    draw_title()
    time.sleep(1)

    # 外循环：需要 n-1 轮
    for i in range(n - 1):
        # 显示当前轮次
        draw_round_info(i + 1, n - 1, compare_count)

        # 标记这一轮有没有发生交换（优化用）
        swapped = False

        # 内循环：每轮比较 n-1-i 次
        for j in range(n - 1 - i):
            # 高亮正在比较的两个数
            draw_all(arr, highlight=[j, j + 1])
            compare_count += 1

            # 显示比较信息
            info.goto(0, -240)
            info.write(f"比较 {arr[j]} 和 {arr[j+1]} ：", align="center",
                       font=("Arial", 12, "normal"))

            time.sleep(SPEED)

            # 如果左边 > 右边，交换
            if arr[j] > arr[j + 1]:
                # 交换
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
                total_swaps += 1
                swapped = True

                # 重画
                draw_all(arr)
                info.goto(0, -240)
                info.write(f"{arr[j+1]} 比 {arr[j]} 大 → 交换！ 💨",
                           align="center", font=("Arial", 12, "normal"))
                time.sleep(SPEED)
            else:
                info.goto(0, -240)
                info.write(f"{arr[j]} 比 {arr[j+1]} 小 → 不换 😊",
                           align="center", font=("Arial", 12, "normal"))
                time.sleep(SPEED / 2)

        # 一轮结束，清除高亮
        draw_all(arr)

        # 标记一下本轮冒到最后的数（已排好）
        info.goto(0, -240)
        info.write(f"✅ 第{i+1}轮结束，最大数 {arr[n-1-i]} 冒到最后！",
                   align="center", font=("Arial", 12, "normal"))
        time.sleep(SPEED)

        # 优化：如果没发生交换，提前结束
        if not swapped:
            info.goto(0, -240)
            info.write("🌟 哇！这一轮没有交换，已经排好了！提前结束！",
                       align="center", font=("Arial", 12, "bold"))
            time.sleep(1)
            break

    # 排序完成
    draw_finished()
    draw_count(total_swaps)

    return arr


# ==================================================
# 辅助函数
# ==================================================

def random_data(n=8, min_val=1, max_val=20):
    """生成随机数据"""
    return [random.randint(min_val, max_val) for _ in range(n)]


def reset_screen():
    """重置屏幕"""
    screen.clear()
    screen.bgcolor("white")
    screen.tracer(0)


def run_example():
    """运行示例——多个不同数据的冒泡排序演示"""
    examples = [
        [5, 3, 1, 4, 2],
        [8, 6, 7, 5, 3, 0, 9],
        [1, 3, 5, 7, 9],  # 已经排好的
        [9, 7, 5, 3, 1],  # 完全逆序
    ]

    for i, data in enumerate(examples):
        print(f"\n===== 示例 {i+1}：{data} =====")
        reset_screen()
        arr = data.copy()
        sorted_arr = bubble_sort(arr)
        print(f"排序结果：{sorted_arr}")
        time.sleep(2)


# ==================================================
# 交互控制函数
# ==================================================

def on_click(x, y):
    """点击屏幕重新排序"""
    global arr
    # 重置数据
    random.seed()
    arr = random_data(8)
    t.clear()
    info.clear()
    reset_screen()
    draw_title()
    bubble_sort(arr)


# ==================================================
# 主程序
# ==================================================

if __name__ == "__main__":
    print("🌟 欢迎来到冒泡排序动画！")
    print("=" * 40)
    print("当前数据：", DATA)
    print("排序速度：", SPEED)
    print("=" * 40)
    print("口诀：冒泡冒泡，大的往后靠！")
    print()

    # 复制数据（避免修改原数据）
    arr = DATA.copy()

    # 绘制初始状态
    draw_title()

    # 执行冒泡排序
    print("开始排序...")
    sorted_arr = bubble_sort(arr)
    print(f"\n排序完成！结果：{sorted_arr}")

    # 点击屏幕重新排序
    screen.listen()
    screen.onclick(on_click)

    print("\n💡 提示：点击屏幕可以随机生成新数据重新排序！")
    print("💡 按 ESC 或关闭窗口退出程序")

    # 保持窗口
    screen.mainloop()

# ===== 补充：lambda表达式（GESP四级必考）=====
print("\n" + "="*50)
print("lambda表达式——一句话函数")
print("="*50)

# lambda基本用法
double = lambda x: x * 2
print(f"lambda x: x*2, double(5) = {double(5)}")

# sorted + lambda（最常见考法）
pairs = [(1, 'one'), (3, 'three'), (2, 'two')]
pairs.sort(key=lambda x: x[1])
print(f"按字母排序: {pairs}")

# filter + lambda
nums = [1, 2, 3, 4, 5, 6]
even = list(filter(lambda x: x % 2 == 0, nums))
print(f"filter偶数: {even}")

# map + lambda
squares = list(map(lambda x: x**2, range(6)))
print(f"map平方: {squares}")
