# GESP等级：二级 | GESP Python 二级考点
"""
===================================
  第4节：数学函数大全 - Turtle演示
  面向7-10岁少儿编程教学
===================================

【教学内容】
  1. abs(x)    —— 绝对值
  2. max(a,b)  —— 最大值
  3. min(a,b)  —— 最小值
  4. round(x)  —— 四舍五入
  5. ceil(x)   —— 向上取整
  6. floor(x)  —— 向下取整
  7. sum(列表) —— 求和
  8. 三种导入方式对比

【运行方式】
  直接运行本文件，turtle画布会自动弹出。
"""

# ============================================
# 第一部分：导入模块（三种方式演示）
# ============================================

# 方式一：import math —— 整个工具箱搬来
import math

# 方式二：from math import * —— 把所有工具倒出来
# （这里先不用，为了演示我们只用一个 import）

# 方式三：import math as m —— 给工具箱起小名
# 下面演示时会用到

import turtle
import time

# ============================================
# 第二部分：设置 turtle
# ============================================

# 创建画布
screen = turtle.Screen()
screen.title("数学函数大全 - 小派学数学")
screen.bgcolor("lightyellow")
screen.setup(800, 600)

# 创建主角——小派
t = turtle.Turtle()
t.shape("turtle")
t.speed(5)
t.pensize(3)

# 创建一个写字专用的"笔"
pen = turtle.Turtle()
pen.speed(0)
pen.hideturtle()
pen.penup()

# ============================================
# 第三部分：辅助函数
# ============================================

def wait(seconds=2):
    """停顿一下，让学生看清楚"""
    time.sleep(seconds)


def clear_screen():
    """清除画布上所有内容，重置小派位置"""
    t.clear()
    t.penup()
    t.home()
    t.pendown()
    pen.clear()


def draw_title(text, y=260):
    """在画布顶部写标题"""
    pen.goto(0, y)
    pen.color("purple")
    pen.write(text, align="center", font=("Arial", 20, "bold"))


def draw_subtitle(text, y=230):
    """在标题下方写副标题"""
    pen.goto(0, y)
    pen.color("gray")
    pen.write(text, align="center", font=("Arial", 12, "normal"))


def draw_line_from_to(x1, y1, x2, y2, color="blue"):
    """画一条线，用来做对比图的坐标轴"""
    t.penup()
    t.goto(x1, y1)
    t.pendown()
    t.pencolor(color)
    t.goto(x2, y2)
    t.penup()


def move_pen_to(x, y):
    """移动小派到指定位置（不画线）"""
    t.penup()
    t.goto(x, y)
    t.pendown()


# ============================================
# 第四部分：主演示程序
# ============================================

def demo_abs():
    """
    演示 abs() —— 距离没有负数
    小派先往前走，再往后退，最后用 abs 算距离
    """
    clear_screen()
    draw_title("📏 abs() —— 距离没有负数")
    draw_subtitle("小派走路：往前走 100，往后退 60，离起点多远？")

    # 起点标记
    move_pen_to(-200, 0)
    t.dot(10, "green")
    pen.goto(-200, -30)
    pen.color("green")
    pen.write("起点", font=("Arial", 12, "normal"))

    # 往前走 100
    t.pencolor("blue")
    t.pensize(4)
    t.forward(100)
    t.dot(8, "blue")
    pen.goto(-100, 20)
    pen.color("blue")
    pen.write("+100", font=("Arial", 12, "normal"))
    wait(1)

    # 往后退 60
    t.pencolor("red")
    t.backward(60)
    t.dot(8, "red")
    pen.goto(-40, 20)
    pen.color("red")
    pen.write("-60", font=("Arial", 12, "normal"))
    wait(1)

    # 终点标记
    move_pen_to(-40, 0)
    t.dot(10, "orange")
    pen.goto(-40, -30)
    pen.color("orange")
    pen.write("终点", font=("Arial", 12, "normal"))

    # 用 abs 算距离
    distance = abs(100 - 60)

    # 在底部写出结果
    pen.goto(0, -80)
    pen.color("darkred")
    pen.write(f"小派离起点：abs(100 - 60) = {distance} 步",
              align="center", font=("Arial", 18, "bold"))

    pen.goto(0, -120)
    pen.color("purple")
    pen.write("记忆钩子：距离没有负数！abs 把负号吃掉啦！",
              align="center", font=("Arial", 14, "normal"))

    wait(3)


def demo_max_min():
    """
    演示 max() 和 min() —— 找出最高和最矮
    在坐标轴上画三个点，用 max/min 找出最右和最左
    """
    clear_screen()
    draw_title("📐 max() 和 min() —— 找出最高和最矮")
    draw_subtitle("三个点：A(-80, 50), B(60, 30), C(120, -20)，谁最右？谁最左？")

    # 三个点的坐标
    points = [(-80, 50, "A"), (60, 30, "B"), (120, -20, "C")]

    # 画出三个点
    for x, y, name in points:
        move_pen_to(x, y)
        t.dot(15, "blue")
        pen.goto(x + 10, y + 5)
        pen.color("blue")
        pen.write(f"{name}({x},{y})", font=("Arial", 12, "normal"))

    # 提取所有 x 坐标
    x_values = [p[0] for p in points]

    # 用 max 和 min 找出最右和最左
    right_x = max(x_values)  # 120
    left_x = min(x_values)   # -80

    # 标出最右边
    move_pen_to(right_x, -60)
    t.pencolor("red")
    t.dot(12, "red")
    pen.goto(right_x - 40, -80)
    pen.color("red")
    pen.write(f"max(x) = {right_x} ← 最右边！",
              font=("Arial", 14, "bold"))

    # 标出最左边
    move_pen_to(left_x, -60)
    t.pencolor("green")
    t.dot(12, "green")
    pen.goto(left_x - 40, -100)
    pen.color("green")
    pen.write(f"min(x) = {left_x} ← 最左边！",
              font=("Arial", 14, "bold"))

    # 底部总结
    pen.goto(0, -150)
    pen.color("darkred")
    pen.write("max = 选最高（最大），min = 选最矮（最小）",
              align="center", font=("Arial", 16, "bold"))

    pen.goto(0, -180)
    pen.color("purple")
    pen.write("记忆钩子：max踮脚尖，min蹲下来！",
              align="center", font=("Arial", 14, "normal"))

    wait(3)


def demo_round_ceil_floor():
    """
    演示 round / ceil / floor —— 三种取整方式对比
    用不同颜色的柱状图展示
    """
    clear_screen()
    draw_title("🔄 round / ceil / floor —— 三种取整方式")
    draw_subtitle("以数字 3.6 为例，看看三种取整的区别")

    # 原始数字
    num = 3.6

    # 画数轴
    draw_line_from_to(-200, 0, 200, 0, "gray")

    # 标整数刻度
    for i in range(1, 6):
        move_pen_to((i - 3) * 80, -10)
        t.dot(5, "gray")
        pen.goto((i - 3) * 80, -30)
        pen.color("gray")
        pen.write(str(i), font=("Arial", 12, "normal"))

    # 标记原始数字位置（在 3 和 4 之间）
    original_x = -40  # 3.6 对应的 x 位置
    move_pen_to(original_x, 30)
    t.pencolor("black")
    t.dot(12, "orange")
    pen.goto(original_x - 30, 45)
    pen.color("orange")
    pen.write(f"原始: {num}", font=("Arial", 14, "bold"))

    # round(3.6) = 4 —— 蓝色箭头指向4
    round_x = 80  # 4 对应的位置
    move_pen_to(original_x, 20)
    t.pencolor("blue")
    t.pensize(2)
    t.goto(round_x, 20)
    pen.goto(round_x + 10, 15)
    pen.color("blue")
    pen.write(f"round({num}) = 4", font=("Arial", 13, "normal"))

    # ceil(3.6) = 4 —— 绿色箭头从下面指向4
    move_pen_to(original_x, -50)
    t.pencolor("green")
    t.pensize(2)
    t.goto(round_x, -50)
    pen.goto(round_x + 10, -60)
    pen.color("green")
    pen.write(f"ceil({num}) = 4", font=("Arial", 13, "normal"))

    # floor(3.6) = 3 —— 红色箭头指向3
    floor_x = -80  # 3 对应的位置
    move_pen_to(original_x, -80)
    t.pencolor("red")
    t.pensize(2)
    t.goto(floor_x, -80)
    pen.goto(floor_x - 100, -90)
    pen.color("red")
    pen.write(f"floor({num}) = 3", font=("Arial", 13, "normal"))

    # 底部总结
    pen.goto(0, -140)
    pen.color("darkred")
    pen.write("round ≈ 四舍五入 | ceil ≈ 天花板（往上）| floor ≈ 地板（往下）",
              align="center", font=("Arial", 14, "bold"))

    pen.goto(0, -170)
    pen.color("purple")
    pen.write("身体锚点：ceil 手指向上↑  floor 手指向下↓  round 双手平推↔",
              align="center", font=("Arial", 13, "normal"))

    wait(4)


def demo_sum():
    """
    演示 sum() —— 求和
    小派画多边形，每段边长由列表决定，底部显示总和
    """
    clear_screen()
    draw_title("🧮 sum() —— 总数加总器")
    draw_subtitle("画一个五边形，五条边的长度存在列表里，用 sum 算总步数")

    # 列表存五条边的长度
    sides = [50, 80, 60, 90, 70]
    total = sum(sides)

    # 在右侧显示列表
    pen.goto(150, 200)
    pen.color("darkblue")
    pen.write(f"边长列表:", font=("Arial", 14, "bold"))
    for i, s in enumerate(sides):
        pen.goto(150, 170 - i * 25)
        pen.color("blue")
        pen.write(f"  边{i+1} = {s}", font=("Arial", 12, "normal"))

    # 小派画五边形
    t.pencolor("darkgreen")
    t.pensize(3)
    move_pen_to(-50, 50)

    for side_len in sides:
        t.forward(side_len)
        t.left(72)

    # 在图形中央写总步数
    pen.goto(-10, 0)
    pen.color("red")
    pen.write(f"总步数 = sum(边长列表)", align="center",
              font=("Arial", 12, "normal"))

    pen.goto(-10, -30)
    pen.color("darkred")
    pen.write(f"= {sides[0]} + {sides[1]} + {sides[2]} + {sides[3]} + {sides[4]} = {total}",
              align="center", font=("Arial", 14, "bold"))

    # 底部总结
    pen.goto(0, -120)
    pen.color("purple")
    pen.write("sum() = 把列表里的数全部加起来！",
              align="center", font=("Arial", 16, "bold"))

    pen.goto(0, -150)
    pen.color("gray")
    pen.write("记忆钩子：双手收拢→sum把所有数收拢加在一起！",
              align="center", font=("Arial", 13, "normal"))

    wait(3)


def demo_import_methods():
    """
    演示三种导入方式
    在画布上用文字对比展示
    """
    clear_screen()
    draw_title("📦 三种导入方式对比")
    draw_subtitle("同一个功能，三种写法")

    # 用三种方式计算同一个东西
    # 方式一：import math
    r1 = math.ceil(3.2)

    # 方式二：from math import * （演示用）
    from math import floor as floor_func
    r2 = floor_func(3.2)

    # 方式三：import math as m
    import math as m
    r3 = m.sqrt(9)

    # 在画布上显示
    items = [
        ("方式一：import math", f"math.ceil(3.2) = {r1}",
         "整个工具箱搬来，要写 math. 前缀", "blue"),
        ("方式二：from math import *", f"floor(3.2) = {r2}",
         "工具全倒出来，直接写函数名", "green"),
        ("方式三：import math as m", f"m.sqrt(9) = {r3}",
         "给工具箱起小名，写 m. 前缀", "red"),
    ]

    for i, (title_text, code_text, desc, color) in enumerate(items):
        y = 180 - i * 90
        pen.goto(-350, y)
        pen.color(color)
        pen.write(title_text, font=("Arial", 16, "bold"))

        pen.goto(-340, y - 30)
        pen.color("darkred")
        pen.write(f"  示例: {code_text}", font=("Arial", 14, "normal"))

        pen.goto(-340, y - 55)
        pen.color("gray")
        pen.write(f"  说明: {desc}", font=("Arial", 12, "normal"))

    pen.goto(0, -130)
    pen.color("purple")
    pen.write("推荐使用 import math as m，打字少又好记！",
              align="center", font=("Arial", 16, "bold"))

    wait(3)


def demo_all_functions_table():
    """
    把所有函数的返回值列在一个表格中
    用 turtle 画出函数测试结果
    """
    clear_screen()
    draw_title("📊 函数大全总结")
    draw_subtitle("看看每个函数输入不同数字会输出什么？")

    # 测试数据
    test_numbers = [-5, -3.5, 0, 2.7, 4]

    # 函数列表
    funcs = [
        ("abs(x)", [abs(n) for n in test_numbers], "darkblue"),
        ("round(x)", [round(n) for n in test_numbers], "darkgreen"),
        ("math.ceil(x)", [math.ceil(n) for n in test_numbers], "red"),
        ("math.floor(x)", [math.floor(n) for n in test_numbers], "orange"),
    ]

    # 表头
    start_x = -350
    header_y = 210
    col_widths = [100, 60, 60, 60, 60, 60]  # 函数名列 + 5个数字列

    headers = ["函数名"] + [str(n) for n in test_numbers]
    for i, h in enumerate(headers):
        x = start_x + sum(col_widths[:i])
        pen.goto(x, header_y)
        pen.color("purple")
        pen.write(h, font=("Arial", 13, "bold"))

    # 分隔线
    draw_line_from_to(start_x - 10, header_y - 20,
                      start_x + sum(col_widths) - 10, header_y - 20, "gray")

    # 每行数据
    for row_idx, (func_name, results, color) in enumerate(funcs):
        row_y = header_y - 40 - row_idx * 35

        # 函数名
        pen.goto(start_x, row_y)
        pen.color(color)
        pen.write(func_name, font=("Arial", 12, "bold"))

        # 结果
        for col_idx, val in enumerate(results):
            x = start_x + col_widths[0] + col_idx * col_widths[1]
            pen.goto(x, row_y)
            pen.color("black")
            pen.write(str(val), font=("Arial", 12, "normal"))

    # sum 单独演示
    pen.goto(start_x, header_y - 40 - len(funcs) * 35 - 10)
    pen.color("darkblue")
    pen.write("sum(x)", font=("Arial", 12, "bold"))

    sum_result = sum(test_numbers)
    pen.goto(start_x + col_widths[0], header_y - 40 - len(funcs) * 35 - 10)
    pen.color("black")
    pen.write(f"sum({test_numbers}) = {sum_result}",
              font=("Arial", 12, "normal"))

    pen.goto(0, -200)
    pen.color("purple")
    pen.write("小提示：GESP二级必考——记住每个函数的返回值类型！",
              align="center", font=("Arial", 14, "bold"))

    wait(4)


def challenge_welcome():
    """
    挑战任务提示 —— 实操环节引导
    """
    clear_screen()
    draw_title("🎯 挑战任务：我是函数小达人！")
    draw_subtitle("现在轮到你来动手啦！")

    challenges = [
        "关卡1：填空小热身 —— 补全代码中的空格",
        "关卡2：仿写小能手 —— 照着例子写新代码",
        "关卡3：创作小画家 —— 用turtle画出函数对比图",
    ]

    for i, c in enumerate(challenges):
        pen.goto(-250, 150 - i * 50)
        pen.color("darkgreen")
        pen.write(f"  {c}", font=("Arial", 16, "bold"))

    pen.goto(0, -50)
    pen.color("orange")
    pen.write("打开代码文件，在最后的挑战任务区域开始写吧！",
              align="center", font=("Arial", 14, "normal"))

    wait(2)


# ============================================
# 第五部分：主程序入口
# ============================================

def main():
    """主程序——依次运行所有演示"""
    print("=" * 50)
    print("  第4节：数学函数大全 - Turtle 演示")
    print("=" * 50)
    print()
    print("正在启动 turtle 画布...")
    print("请观察画布上的演示内容。")
    print()

    # 依次演示
    demo_abs()
    demo_max_min()
    demo_round_ceil_floor()
    demo_sum()
    demo_import_methods()
    demo_all_functions_table()
    challenge_welcome()

    # 结束
    clear_screen()
    draw_title("🎉 太棒啦！数学函数学完啦！")
    draw_subtitle("记得完成挑战任务哦！")

    pen.goto(0, -30)
    pen.color("darkgreen")
    pen.write("今天学到的函数：", align="center",
              font=("Arial", 16, "bold"))

    func_list = "abs  max  min  round  ceil  floor  sum"
    pen.goto(0, -65)
    pen.color("blue")
    pen.write(func_list, align="center", font=("Arial", 18, "bold"))

    pen.goto(0, -110)
    pen.color("purple")
    pen.write("身体锚点小复习：", align="center",
              font=("Arial", 14, "bold"))

    anchors = "↑ ceil  ↓ floor  ↔ round  🤲 abs  🙆 max  🙇 min"
    pen.goto(0, -140)
    pen.color("gray")
    pen.write(anchors, align="center", font=("Arial", 13, "normal"))

    pen.goto(0, -190)
    pen.color("orange")
    pen.write("点击画布关闭窗口", align="center",
              font=("Arial", 12, "normal"))

    print()
    print("演示完成！请查看 turtle 画布上的内容。")
    print("接下来请完成挑战任务！")

    # 保持画布打开
    screen.exitonclick()


# ============================================
# 第六部分：挑战任务区域（学生实操）
# ============================================

# --------------------------------------------------
# 关卡1：填空小热身 —— 在空格处填上正确的数字
# --------------------------------------------------

def challenge_level1():
    """
    关卡1：填空小热身
    """
    print()
    print("=" * 50)
    print("  关卡1：填空小热身")
    print("=" * 50)
    print()

    # 题目1：abs 填空
    print("题目1：用 abs 算出 -8 的绝对值")
    answer1 = abs(-8)  # 学生需要把 -8 填进去
    print(f"  abs(-8) = {answer1}  ✅ 应该是 8")
    print()

    # 题目2：max 填空
    print("题目2：用 max 找出 45, 67, 23 中最大的")
    answer2 = max(45, 67, 23)  # 学生需要填这三个数
    print(f"  max(45, 67, 23) = {answer2}  ✅ 应该是 67")
    print()

    # 题目3：min 填空
    print("题目3：用 min 找出 45, 67, 23 中最小的")
    answer3 = min(45, 67, 23)  # 学生需要填这三个数
    print(f"  min(45, 67, 23) = {answer3}  ✅ 应该是 23")
    print()

    # 题目4：round 填空
    print("题目4：用 round 把 4.6 四舍五入")
    answer4 = round(4.6)  # 学生需要填 4.6
    print(f"  round(4.6) = {answer4}  ✅ 应该是 5")
    print()

    # 题目5：sum 填空
    print("题目5：用 sum 计算 [10, 20, 30, 40] 的总和")
    answer5 = sum([10, 20, 30, 40])  # 学生需要填这个列表
    print(f"  sum([10, 20, 30, 40]) = {answer5}  ✅ 应该是 100")
    print()


# --------------------------------------------------
# 关卡2：仿写小能手 —— 照着例子写新代码
# --------------------------------------------------

def challenge_level2():
    """
    关卡2：仿写小能手
    """
    print()
    print("=" * 50)
    print("  关卡2：仿写小能手")
    print("=" * 50)
    print()

    # 例子：用 abs 算 -5 的绝对值
    print("【例子】用 abs 算 -5 的绝对值")
    print(f"  abs(-5) = {abs(-5)}")
    print()

    # 仿写1：用 abs 算温度差（从 25 度降到 -3 度，差了多少度？）
    print("仿写1：用 abs 算温度差（25度到-3度，差多少？）")
    temp_diff = abs(25 - (-3))
    print(f"  abs(25 - (-3)) = {temp_diff}  ✅ 温差 28 度")
    print()

    # 仿写2：用 max 比三个商品的价格
    print("仿写2：用 max 找出最贵的商品（18元, 25元, 12元）")
    max_price = max(18, 25, 12)
    print(f"  max(18, 25, 12) = {max_price}  ✅ 最贵 25 元")
    print()

    # 仿写3：用 min 找出最短的边
    print("仿写3：用 min 找出最短的边（边长: 45, 32, 58, 40）")
    min_side = min(45, 32, 58, 40)
    print(f"  min(45, 32, 58, 40) = {min_side}  ✅ 最短 32")
    print()

    # 仿写4：用 ceil 和 floor
    print("仿写4：用 ceil 和 floor 处理 7.8")
    print(f"  math.ceil(7.8) = {math.ceil(7.8)}  ✅ 往上取整得 8")
    print(f"  math.floor(7.8) = {math.floor(7.8)}  ✅ 往下取整得 7")
    print()

    # 仿写5：用 sum 算一周零花钱
    print("仿写5：用 sum 算一周零花钱（5元, 10元, 5元, 20元, 10元）")
    weekly_money = sum([5, 10, 5, 20, 10])
    print(f"  sum([5, 10, 5, 20, 10]) = {weekly_money}  ✅ 一共 50 元")
    print()


# --------------------------------------------------
# 关卡3：创作小画家 —— 用 turtle 画出函数对比图
# --------------------------------------------------

def challenge_level3():
    """
    关卡3：创作小画家
    用 turtle 画一个函数对比图
    """
    print()
    print("=" * 50)
    print("  关卡3：创作小画家")
    print("=" * 50)
    print()
    print("在 turtle 画布上画一个函数对比图：")
    print()

    # 创建一个新的 turtle
    painter = turtle.Turtle()
    painter.speed(5)
    painter.pensize(3)
    painter.hideturtle()

    # 测试数字
    test_num = 4.3

    # 用不同颜色写不同函数的结果
    # 红色 —— 原始数字
    painter.penup()
    painter.goto(0, 80)
    painter.pencolor("red")
    painter.write(f"原始数字: {test_num}",
                  align="center", font=("Arial", 20, "bold"))

    # 蓝色 —— round
    painter.goto(0, 30)
    painter.pencolor("blue")
    painter.write(f"round({test_num}) = {round(test_num)}",
                  align="center", font=("Arial", 18, "normal"))

    # 绿色 —— ceil
    painter.goto(0, -20)
    painter.pencolor("green")
    painter.write(f"math.ceil({test_num}) = {math.ceil(test_num)}",
                  align="center", font=("Arial", 18, "normal"))

    # 橙色 —— floor
    painter.goto(0, -70)
    painter.pencolor("orange")
    painter.write(f"math.floor({test_num}) = {math.floor(test_num)}",
                  align="center", font=("Arial", 18, "normal"))

    # 紫色 —— sum 额外展示
    test_list = [3, 6, 9]
    painter.goto(0, -130)
    painter.pencolor("purple")
    painter.write(f"sum({test_list}) = {sum(test_list)}",
                  align="center", font=("Arial", 18, "normal"))

    print("✅ 对比图已画在 turtle 画布上！")
    print()
    print("试试修改 test_num 的值，看看不同数字的效果！")
    print()


# ============================================
# 运行
# ============================================

if __name__ == "__main__":
    # 运行 turtle 演示
    main()

    # 在控制台输出挑战任务
    print()
    print("现在开始挑战任务！")
    print()

    challenge_level1()
    challenge_level2()
    challenge_level3()

    print("=" * 50)
    print("  恭喜完成第4节：数学函数大全！")
    print("=" * 50)
    print()
    print("📌 课后复习小提示：")
    print("  1. abs(-8) = ?  → 8（吃负号）")
    print("  2. max(3,7,5) = ?  → 7（最高）")
    print("  3. min(3,7,5) = ?  → 3（最矮）")
    print("  4. round(4.6) = ?  → 5（四舍五入）")
    print("  5. ceil(3.1) = ?  → 4（天花板）")
    print("  6. floor(3.9) = ?  → 3（地板）")
    print("  7. sum([1,2,3]) = ?  → 6（求和）")
    print()
    print("身体锚点复习：")
    print("  ↑ ceil  |  ↓ floor  |  ↔ round")
    print("  🤲 abs  |  🙆 max  |  🙇 min")
    print()
