# GESP等级：三级 | GESP Python 三级考点

# -*- coding: utf-8 -*-
"""
13第3节4 — EasyGUI + turtle 综合项目：小乌龟画图助手
GESP 考点：#EasyGUI交互 #turtle绘图 #综合应用
=========================================================
课程：EasyGUI + turtle 综合项目
项目名称：小乌龟画图助手 —— 用 EasyGUI 让用户选颜色/形状/大小，turtle 画出来！
三层挑战：入门 → 进阶 → 挑战
=========================================================

【学习目标】
1. 综合运用 EasyGUI 的 choicebox / integerbox / buttonbox / msgbox
2. 综合运用 turtle 绘图（形状、颜色、位置、填充）
3. 掌握"数据驱动绘图"—— 将用户选择映射为绘图参数
4. 培养分层设计思维（基础 → 扩展 → 创作）
"""

import turtle
import easygui as eg
import random

# ============================================================
# 全局配置
# ============================================================
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
BG_COLOR = "white"

# 可用颜色列表（显示名 → turtle 颜色名）
COLOR_MAP = {
    "红色": "red",
    "蓝色": "blue",
    "绿色": "green",
    "黄色": "yellow",
    "紫色": "purple",
    "橙色": "orange",
    "粉色": "pink",
    "黑色": "black",
    "棕色": "brown",
    "青色": "cyan",
}

# 形状列表
SHAPES = ["圆形", "正方形", "三角形", "五角星", "六边形", "心形", "螺旋线"]

# 位置映射（屏幕坐标）
POSITION_MAP = {
    "左上": (-250, 150),
    "上中": (0, 200),
    "右上": (250, 150),
    "左中": (-300, 0),
    "中心": (0, 0),
    "右中": (300, 0),
    "左下": (-250, -150),
    "下中": (0, -200),
    "右下": (250, -150),
}


# ============================================================
# 工具函数
# ============================================================

def init_screen(title="小乌龟画图助手"):
    """初始化 turtle 画布"""
    screen = turtle.Screen()
    screen.title(title)
    screen.setup(SCREEN_WIDTH, SCREEN_HEIGHT)
    screen.bgcolor(BG_COLOR)
    screen.tracer(1)  # 开启动画追踪
    return screen


def create_turtle(speed=6):
    """创建并返回一个配置好的 turtle 对象"""
    t = turtle.Turtle()
    t.speed(speed)
    t.pensize(2)
    t.shape("turtle")
    return t


def color_name_to_code(name):
    """将中文颜色名转为 turtle 颜色代码"""
    return COLOR_MAP.get(name, "black")


# ============================================================
# 绘图函数（每种形状一个函数）
# ============================================================

def draw_circle(t, size, color, fill=True):
    """画圆形"""
    t.color(color)
    if fill:
        t.begin_fill()
    t.circle(size)
    if fill:
        t.end_fill()


def draw_square(t, size, color, fill=True):
    """画正方形"""
    t.color(color)
    if fill:
        t.begin_fill()
    for _ in range(4):
        t.forward(size)
        t.left(90)
    if fill:
        t.end_fill()


def draw_triangle(t, size, color, fill=True):
    """画等边三角形"""
    t.color(color)
    if fill:
        t.begin_fill()
    for _ in range(3):
        t.forward(size)
        t.left(120)
    if fill:
        t.end_fill()


def draw_star(t, size, color, fill=True):
    """画五角星"""
    t.color(color)
    if fill:
        t.begin_fill()
    for _ in range(5):
        t.forward(size)
        t.right(144)
    if fill:
        t.end_fill()


def draw_hexagon(t, size, color, fill=True):
    """画六边形"""
    t.color(color)
    if fill:
        t.begin_fill()
    for _ in range(6):
        t.forward(size)
        t.left(60)
    if fill:
        t.end_fill()


def draw_heart(t, size, color, fill=True):
    """画心形（用两个半圆 + 三角形组合）"""
    t.color(color)
    t.penup()
    t.goto(0, -size * 0.3)
    t.pendown()
    if fill:
        t.begin_fill()
    t.left(90)
    t.circle(size * 0.5, 180)
    t.right(90)
    t.forward(size * 0.5)
    t.left(90)
    t.forward(size * 0.5)
    t.left(90)
    t.forward(size * 0.5)
    t.right(90)
    t.circle(size * 0.5, 180)
    if fill:
        t.end_fill()
    # 恢复角度
    t.setheading(0)


def draw_spiral(t, size, color, fill=False):
    """画螺旋线"""
    t.color(color)
    t.penup()
    t.goto(0, 0)
    t.pendown()
    steps = 20
    for i in range(steps):
        t.forward(size * (i + 1) / steps * 2)
        t.left(45)


def draw_shape(t, shape_name, size, color, fill=True):
    """根据形状名调用对应的绘图函数"""
    shape_map = {
        "圆形": draw_circle,
        "正方形": draw_square,
        "三角形": draw_triangle,
        "五角星": draw_star,
        "六边形": draw_hexagon,
        "心形": draw_heart,
        "螺旋线": draw_spiral,
    }
    draw_func = shape_map.get(shape_name)
    if draw_func:
        # 螺旋线不需要填充
        if shape_name == "螺旋线":
            draw_func(t, size, color, fill=False)
        else:
            draw_func(t, size, color, fill=fill)


def go_to_position(t, pos_name):
    """将 turtle 移动到指定位置"""
    pos = POSITION_MAP.get(pos_name, (0, 0))
    t.penup()
    t.goto(pos)
    t.pendown()
    t.setheading(0)  # 重置方向


# ============================================================
# 第1关 · 入门版：选颜色→选形状→选大小→画！
# ============================================================

def level1_basic():
    """
    第1关 · 入门版
    ─────────────────────────────────────
    流程：
      1. 欢迎界面 msgbox
      2. choicebox 选颜色
      3. choicebox 选形状（圆形/正方形/三角形）
      4. integerbox 选大小（20~150）
      5. turtle 绘图
      6. 询问是否再来一次
    """
    # ── 1. 欢迎 ──
    eg.msgbox(
        msg="欢迎来到【小乌龟画图助手】！\n\n"
            "这是一个用 EasyGUI 控制小乌龟画图的工具。\n"
            "第1关：选择颜色、形状、大小，让小乌龟画出来！",
        title="🐢 小乌龟画图助手 · 第1关 入门版",
        ok_button="开始画画！"
    )

    # ── 2. 选颜色 ──
    color_names = list(COLOR_MAP.keys())
    chosen_color = eg.choicebox(
        msg="请选择图形的颜色：",
        title="选择颜色",
        choices=color_names
    )
    if chosen_color is None:
        eg.msgbox("你没有选择颜色，退出程序。", title="退出")
        return

    # ── 3. 选形状（入门版只提供3种基础形状）──
    basic_shapes = ["圆形", "正方形", "三角形"]
    chosen_shape = eg.choicebox(
        msg=f"颜色已选：{chosen_color}\n请选择图形的形状：",
        title="选择形状",
        choices=basic_shapes
    )
    if chosen_shape is None:
        eg.msgbox("你没有选择形状，退出程序。", title="退出")
        return

    # ── 4. 选大小 ──
    size = eg.integerbox(
        msg=f"颜色：{chosen_color}  形状：{chosen_shape}\n"
            "请输入图形的大小（20~150）：",
        title="选择大小",
        lowerbound=20,
        upperbound=150,
        initial=80
    )
    if size is None:
        eg.msgbox("你没有输入大小，退出程序。", title="退出")
        return

    # ── 5. 确认信息 ──
    confirm = eg.ccbox(
        msg=f"确认你的选择：\n\n"
            f"  颜色：{chosen_color}\n"
            f"  形状：{chosen_shape}\n"
            f"  大小：{size}\n\n"
            "开始画吗？",
        title="确认信息",
        choices=("开始画！", "重新选")
    )

    if not confirm:
        # 重新来
        level1_basic()
        return

    # ── 6. 绘图 ──
    screen = init_screen()
    t = create_turtle()

    color_code = color_name_to_code(chosen_color)
    draw_shape(t, chosen_shape, size, color_code, fill=True)

    t.penup()
    t.goto(0, -size - 40)
    t.color("gray")
    t.write(
        f"{chosen_shape} | 颜色：{chosen_color} | 大小：{size}",
        align="center",
        font=("Arial", 12, "normal")
    )

    # ── 7. 完成提示 ──
    again = eg.ccbox(
        msg="画画完成！\n还想再画一次吗？",
        title="完成",
        choices=("再画一次", "不画了")
    )

    t.hideturtle()

    if again:
        t.clear()
        screen.clearscreen()
        screen.bye()
        level1_basic()
    else:
        eg.msgbox(
            msg="感谢使用【小乌龟画图助手·第1关】！\n"
                "试试第2关进阶版吧！",
            title="再见！"
        )
        screen.bye()


# ============================================================
# 第2关 · 进阶版：更多形状 + 位置选择 + 连续画
# ============================================================

def level2_intermediate():
    """
    第2关 · 进阶版
    ─────────────────────────────────────
    新增功能：
      - 7种形状可选
      - 位置选择（9宫格）
      - 线宽选择
      - 填充开关
      - 连续画多个图形
      - 清空画布功能
    """
    # ── 1. 欢迎 ──
    eg.msgbox(
        msg="【第2关 · 进阶版】\n\n"
            "新增功能：\n"
            "  ✓ 7种形状自由选择\n"
            "  ✓ 9宫格位置定位\n"
            "  ✓ 线宽调节\n"
            "  ✓ 填充开关\n"
            "  ✓ 连续画多个图形\n\n"
            "开始挑战吧！",
        title="🐢 小乌龟画图助手 · 第2关 进阶版",
        ok_button="开始！"
    )

    screen = init_screen("小乌龟画图助手 · 进阶版")
    t = create_turtle()
    t.speed(8)

    # 绘制次数统计
    draw_count = 0

    # ── 主循环：画多个图形 ──
    while True:
        # ── 选颜色 ──
        color_names = list(COLOR_MAP.keys())
        chosen_color = eg.choicebox(
            msg=f"已画 {draw_count} 个图形\n\n请选择颜色：",
            title=f"第 {draw_count + 1} 个图形 · 选择颜色",
            choices=color_names
        )
        if chosen_color is None:
            break
        color_code = color_name_to_code(chosen_color)

        # ── 选形状 ──
        chosen_shape = eg.choicebox(
            msg=f"颜色：{chosen_color}\n请选择形状：",
            title="选择形状",
            choices=SHAPES
        )
        if chosen_shape is None:
            break

        # ── 选大小 ──
        size = eg.integerbox(
            msg=f"颜色：{chosen_color}  形状：{chosen_shape}\n"
                "请输入图形大小（10~200）：",
            title="选择大小",
            lowerbound=10,
            upperbound=200,
            initial=60
        )
        if size is None:
            break

        # ── 选位置 ──
        pos_names = list(POSITION_MAP.keys())
        chosen_pos = eg.choicebox(
            msg="请选择图形在画布上的位置：",
            title="选择位置",
            choices=pos_names
        )
        if chosen_pos is None:
            break

        # ── 选线宽 ──
        pen_size = eg.integerbox(
            msg="请选择线宽（1~10）：",
            title="选择线宽",
            lowerbound=1,
            upperbound=10,
            initial=2
        )
        if pen_size is None:
            break

        # ── 填充开关 ──
        do_fill = eg.buttonbox(
            msg="是否填充颜色？",
            title="填充选择",
            choices=("填充", "不填充")
        )
        if do_fill is None:
            break
        do_fill = (do_fill == "填充")

        # ── 确认 ──
        summary = (
            f"第 {draw_count + 1} 个图形配置：\n\n"
            f"  颜色：{chosen_color}\n"
            f"  形状：{chosen_shape}\n"
            f"  大小：{size}\n"
            f"  位置：{chosen_pos}\n"
            f"  线宽：{pen_size}\n"
            f"  填充：{'是' if do_fill else '否'}\n\n"
            "画吗？"
        )
        confirm = eg.ccbox(msg=summary, title="确认", choices=("画！", "重新配置"))
        if not confirm:
            # 重新配置（回到循环开头不计数）
            continue

        # ── 画图 ──
        go_to_position(t, chosen_pos)
        t.pensize(pen_size)
        t.pencolor(color_code)
        t.fillcolor(color_code)
        draw_shape(t, chosen_shape, size, color_code, fill=do_fill)

        draw_count += 1

        # ── 下一步操作 ──
        action = eg.buttonbox(
            msg=f"已画 {draw_count} 个图形！\n接下来想做什么？",
            title="继续操作",
            choices=("再画一个", "清空画布", "结束")
        )

        if action is None or action == "结束":
            break
        elif action == "清空画布":
            t.clear()
            draw_count = 0

    # ── 结束 ──
    if draw_count > 0:
        eg.msgbox(
            msg=f"画图完成！共画了 {draw_count} 个图形。\n"
                "真棒！试试第3关挑战版吧！",
            title="进阶版完成！"
        )
    screen.bye()


# ============================================================
# 第3关 · 挑战版：规律图案 + 自定义 + 随机创作
# ============================================================

def level3_challenge():
    """
    第3关 · 挑战版
    ─────────────────────────────────────
    新增功能：
      - 规律图案绘制（一排、环形、棋盘、螺旋阵列）
      - 自定义颜色序列（按顺序循环使用颜色）
      - 随机创作模式（随机所有参数，批量画图）
      - 参数预览
      - 画布参考坐标网格
    """
    eg.msgbox(
        msg="【第3关 · 挑战版】\n\n"
            "新增高阶功能：\n"
            "  ✓ 规律图案绘制（一排/环形/棋盘/螺旋）\n"
            "  ✓ 自定义颜色序列\n"
            "  ✓ 随机创作模式\n"
            "  ✓ 画布参考网格\n\n"
            "开始终极挑战！",
        title="🐢 小乌龟画图助手 · 第3关 挑战版",
        ok_button="开始挑战！"
    )

    screen = init_screen("小乌龟画图助手 · 挑战版")
    t = create_turtle()
    t.speed(0)  # 最快速度
    t.hideturtle()

    # ── 画参考网格 ──
    show_grid = eg.ccbox(
        msg="是否显示参考网格？（帮助定位）",
        title="参考网格",
        choices=("显示网格", "不需要")
    )
    if show_grid:
        draw_reference_grid(t)

    # ── 模式选择 ──
    while True:
        mode = eg.buttonbox(
            msg="请选择绘图模式：",
            title="选择模式",
            choices=("规律图案", "颜色序列", "随机创作", "退出")
        )

        if mode is None or mode == "退出":
            break

        if mode == "规律图案":
            pattern_mode(t)
        elif mode == "颜色序列":
            color_sequence_mode(t)
        elif mode == "随机创作":
            random_create_mode(t)

        # 画完后询问
        again = eg.ccbox(
            msg="还想尝试其他模式吗？",
            title="继续挑战",
            choices=("继续", "退出")
        )
        if not again:
            break

    eg.msgbox(
        msg="恭喜完成【小乌龟画图助手】全部三层挑战！\n\n"
            "你已经掌握了：\n"
            "  ✓ EasyGUI 交互设计\n"
            "  ✓ turtle 绘图编程\n"
            "  ✓ 数据驱动绘图思想\n"
            "  ✓ 分层设计能力\n\n"
            "继续加油，创作更棒的图形吧！",
        title="全部通关！"
    )
    screen.bye()
    turtle.bye()


def draw_reference_grid(t):
    """画参考网格线"""
    t.penup()
    t.goto(-380, 0)
    t.pendown()
    t.color("lightgray")
    t.forward(760)
    t.penup()
    t.goto(0, -280)
    t.pendown()
    t.goto(0, 280)

    # 画刻度点
    for x in range(-300, 301, 100):
        t.penup()
        t.goto(x, -5)
        t.pendown()
        t.goto(x, 5)
        t.penup()
        t.goto(x, -18)
        t.write(str(x), align="center", font=("Arial", 7, "normal"))

    for y in range(-200, 201, 100):
        t.penup()
        t.goto(-5, y)
        t.pendown()
        t.goto(5, y)
        t.penup()
        t.goto(15, y - 5)
        t.write(str(y), font=("Arial", 7, "normal"))


def draw_one_row(t, shape_name, color_code, size, count=5, start_x=-250, y=0):
    """画一排相同图形"""
    t.penup()
    t.goto(start_x, y)
    t.pendown()
    gap = size + 30
    for i in range(count):
        x_pos = start_x + i * gap
        t.penup()
        t.goto(x_pos, y)
        t.pendown()
        draw_shape(t, shape_name, size, color_code, fill=True)


def draw_circle_array(t, shape_name, color_code, size, count=8, radius=120):
    """画环形阵列"""
    import math
    for i in range(count):
        angle = 2 * math.pi * i / count
        x = radius * math.cos(angle)
        y = radius * math.sin(angle)
        t.penup()
        t.goto(x, y)
        t.pendown()
        draw_shape(t, shape_name, size, color_code, fill=True)


def draw_checkerboard(t, color1, color2, cell_size=40, rows=5, cols=5):
    """画棋盘格"""
    start_x = - (cols * cell_size) // 2
    start_y = (rows * cell_size) // 2
    for r in range(rows):
        for c in range(cols):
            x = start_x + c * cell_size
            y = start_y - r * cell_size
            t.penup()
            t.goto(x, y)
            t.pendown()
            color = color1 if (r + c) % 2 == 0 else color2
            t.color(color)
            t.begin_fill()
            for _ in range(4):
                t.forward(cell_size)
                t.right(90)
            t.end_fill()


def draw_spiral_array(t, shape_name, color_code, size, layers=4):
    """画螺旋阵列"""
    import math
    angle = 0
    for i in range(layers * 10):
        radius = 20 + i * 5
        x = radius * math.cos(math.radians(angle))
        y = radius * math.sin(math.radians(angle))
        t.penup()
        t.goto(x, y)
        t.pendown()
        draw_shape(t, shape_name, max(5, size - i), color_code, fill=True)
        angle += 36


def pattern_mode(t):
    """规律图案模式"""
    patterns = ["画一排", "环形阵列", "棋盘格", "螺旋阵列"]
    pattern = eg.choicebox(
        msg="请选择要画的规律图案：",
        title="规律图案",
        choices=patterns
    )
    if pattern is None:
        return

    # 选颜色
    color_names = list(COLOR_MAP.keys())
    chosen_color = eg.choicebox(
        msg=f"图案：{pattern}\n请选择颜色：",
        title="选择颜色",
        choices=color_names
    )
    if chosen_color is None:
        return
    color_code = color_name_to_code(chosen_color)

    if pattern == "画一排":
        size = eg.integerbox("请输入图形大小（20~80）：", lowerbound=20, upperbound=80, initial=40)
        if size is None:
            return
        count = eg.integerbox("请输入数量（2~10）：", lowerbound=2, upperbound=10, initial=5)
        if count is None:
            return
        draw_one_row(t, "圆形", color_code, size, count=count)

    elif pattern == "环形阵列":
        size = eg.integerbox("请输入图形大小（10~50）：", lowerbound=10, upperbound=50, initial=25)
        if size is None:
            return
        count = eg.integerbox("请输入环形数量（4~16）：", lowerbound=4, upperbound=16, initial=8)
        if count is None:
            return
        radius = eg.integerbox("请输入环形半径（50~200）：", lowerbound=50, upperbound=200, initial=120)
        if radius is None:
            return
        draw_circle_array(t, "圆形", color_code, size, count=count, radius=radius)

    elif pattern == "棋盘格":
        color2 = eg.choicebox(
            "请选择第二种颜色：",
            title="棋盘第二种颜色",
            choices=color_names
        )
        if color2 is None:
            return
        color_code2 = color_name_to_code(color2)
        cell_size = eg.integerbox("请输入格子大小（20~80）：", lowerbound=20, upperbound=80, initial=40)
        if cell_size is None:
            return
        draw_checkerboard(t, color_code, color_code2, cell_size=cell_size)

    elif pattern == "螺旋阵列":
        size = eg.integerbox("请输入基础大小（10~40）：", lowerbound=10, upperbound=40, initial=20)
        if size is None:
            return
        draw_spiral_array(t, "圆形", color_code, size)


def color_sequence_mode(t):
    """颜色序列模式：用一个颜色列表循环绘图"""
    color_names = list(COLOR_MAP.keys())

    # 多选颜色
    selected = eg.multchoicebox(
        msg="请选择要使用的颜色（可多选）：",
        title="选择颜色序列",
        choices=color_names
    )
    if not selected:
        return

    # 选形状
    chosen_shape = eg.choicebox("请选择形状：", choices=SHAPES)
    if chosen_shape is None:
        return

    # 选大小
    size = eg.integerbox("请输入大小（20~100）：", lowerbound=20, upperbound=100, initial=40)
    if size is None:
        return

    # 选数量
    count = eg.integerbox("请输入画几个图形（3~20）：", lowerbound=3, upperbound=20, initial=6)
    if count is None:
        return

    # 绘制：颜色循环使用
    import math
    radius = 150
    for i in range(count):
        angle = 2 * math.pi * i / count
        x = radius * math.cos(angle)
        y = radius * math.sin(angle)
        color = selected[i % len(selected)]
        color_code = color_name_to_code(color)

        t.penup()
        t.goto(x, y)
        t.pendown()
        t.pencolor(color_code)
        t.fillcolor(color_code)
        draw_shape(t, chosen_shape, size, color_code, fill=True)

        # 在旁边写上颜色名
        t.penup()
        t.goto(x, y - size - 15)
        t.color("gray")
        t.write(color, align="center", font=("Arial", 8, "normal"))


def random_create_mode(t):
    """随机创作模式：随机生成图形组合"""
    color_names = list(COLOR_MAP.keys())

    count = eg.integerbox(
        msg="随机创作模式！\n请输入要随机生成几个图形（3~30）：",
        title="随机创作",
        lowerbound=3,
        upperbound=30,
        initial=10
    )
    if count is None:
        return

    # 是否填充
    do_fill = eg.buttonbox(
        msg="随机图形是否填充颜色？",
        title="填充选择",
        choices=("填充", "不填充")
    )
    if do_fill is None:
        return
    do_fill = (do_fill == "填充")

    # 开始随机画
    for i in range(count):
        color = random.choice(color_names)
        color_code = color_name_to_code(color)
        shape = random.choice(SHAPES)
        size = random.randint(15, 60)
        # 随机位置
        x = random.randint(-300, 300)
        y = random.randint(-200, 200)

        t.penup()
        t.goto(x, y)
        t.pendown()
        t.pencolor(color_code)
        t.fillcolor(color_code)
        draw_shape(t, shape, size, color_code, fill=do_fill)

    eg.msgbox(
        msg=f"随机创作完成！共生成 {count} 个随机图形。\n"
            "每次运行都会生成不同的图案！",
        title="创作完成"
    )


# ============================================================
# 主入口：选择关卡
# ============================================================

def main():
    """
    主函数：展示关卡选择界面，引导用户选择挑战层级
    """
    while True:
        choice = eg.buttonbox(
            msg="🐢 小乌龟画图助手\n"
                "━━━━━━━━━━━━━━━━━━━━\n\n"
                "  【第1关 · 入门版】\n"
                "  选颜色→选形状→选大小→画出来\n"
                "  适合刚学会 EasyGUI 和 turtle 的同学\n\n"
                "  【第2关 · 进阶版】\n"
                "  7种形状 + 位置选择 + 连续画\n"
                "  挑战更灵活的图形控制\n\n"
                "  【第3关 · 挑战版】\n"
                "  规律图案 + 颜色序列 + 随机创作\n"
                "  发挥创意，自由创作！\n\n"
                "请选择要挑战的关卡：",
            title="🐢 小乌龟画图助手",
            choices=("第1关 入门版", "第2关 进阶版", "第3关 挑战版", "退出")
        )

        if choice is None or choice == "退出":
            eg.msgbox("感谢体验【小乌龟画图助手】！下次见！", title="再见")
            break
        elif choice == "第1关 入门版":
            level1_basic()
        elif choice == "第2关 进阶版":
            level2_intermediate()
        elif choice == "第3关 挑战版":
            level3_challenge()


if __name__ == "__main__":
    main()
