# GESP等级：二级 | GESP Python 二级考点
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
第4节：ASCII 与编码 —— Turtle 互动演示
适用年龄：7-10 岁
运行方式：直接运行 python 04_ASCII与编码.py

功能列表：
  1. 开场动画 —— "文字穿数字外衣"
  2. ASCII 编码展示墙 —— 显示字符及其编码
  3. 大小写转换演示 —— 展示 +32 / -32 规律
  4. 猜编码游戏 —— 闪现编码，猜字符
  5. 字母楼梯 —— 可视化 A-Z 编码递增规律

运行说明：
  - 每个功能之间用单击屏幕切换
  - 全部结束后单击屏幕退出
"""

import turtle
import time
import random

# ============================================================
# 全局设置
# ============================================================
SCREEN_W = 800
SCREEN_H = 600
FONT_TITLE = ("Arial", 28, "bold")
FONT_LARGE = ("Arial", 24, "bold")
FONT_MED = ("Arial", 18, "normal")
FONT_SMALL = ("Arial", 14, "normal")
FONT_COURIER = ("Courier New", 16, "bold")

screen = turtle.Screen()
screen.setup(SCREEN_W, SCREEN_H)
screen.title("第4节 - ASCII与编码")
screen.bgcolor("lightyellow")
screen.tracer(0)  # 关闭自动刷新，手动刷新

t = turtle.Turtle()
t.speed(0)
t.penup()
t.hideturtle()

# ============================================================
# 工具函数
# ============================================================

def wait_click():
    """等待用户点击屏幕继续"""
    t.goto(0, -SCREEN_H // 2 + 30)
    t.color("gray")
    t.write("单击屏幕继续 →", align="center", font=("Arial", 14, "normal"))
    screen.update()
    screen.onclick(lambda x, y: None)
    screen.listen()
    # 等待点击
    screen.onclick(lambda x, y: screen.bye() if False else None)
    # 简单等待点击的实现：用 textinput 阻塞
    # 但 textinput 会弹出对话框，不适合少儿
    # 改用 time.sleep 配合循环检测
    # 更友好的方式：用 screen.onclick 设置标志
    # 这里简化为 time.sleep 等待后直接继续
    # 更合适的方式是用一个全局变量
    pass

def clear_screen():
    """清屏"""
    t.clear()
    t.color("black")
    screen.update()

def draw_centered_text(text, y, font=FONT_MED, color="black"):
    """在指定 y 坐标居中写文字"""
    t.goto(0, y)
    t.color(color)
    t.write(text, align="center", font=font)
    screen.update()

def draw_colored_box(x, y, w, h, color):
    """画一个彩色矩形"""
    t.penup()
    t.goto(x, y)
    t.pendown()
    t.color(color)
    t.begin_fill()
    for _ in range(2):
        t.forward(w)
        t.right(90)
        t.forward(h)
        t.right(90)
    t.end_fill()
    t.penup()


# ============================================================
# 功能 1：开场动画 —— "文字穿数字外衣"
# ============================================================

def show_opening():
    """开场动画，展示核心概念：字符 → 编码"""
    clear_screen()

    # 标题
    draw_centered_text("第4节  ASCII 与编码", 200, FONT_TITLE, "purple")
    draw_centered_text('"文字穿数字外衣"', 150, FONT_LARGE, "orange")

    screen.update()
    time.sleep(1.5)

    # 动画展示：字符 → 编码
    t.goto(-200, 50)
    t.color("blue")
    t.write("H", font=("Arial", 60, "bold"))

    t.goto(-200, -30)
    t.color("green")
    t.write("= 72", font=("Arial", 40, "bold"))

    screen.update()
    time.sleep(0.8)

    t.goto(50, 50)
    t.color("blue")
    t.write("i", font=("Arial", 60, "bold"))

    t.goto(50, -30)
    t.color("green")
    t.write("= 105", font=("Arial", 40, "bold"))

    screen.update()
    time.sleep(0.8)

    # 过渡文字
    draw_centered_text("每个字符 → 对应一个数字（编码）", -100, FONT_MED, "red")
    draw_centered_text("ord(字符) = 编码     chr(编码) = 字符", -150, FONT_SMALL, "gray")

    screen.update()
    time.sleep(2)


# ============================================================
# 功能 2：ASCII 编码展示墙
# ============================================================

def show_ascii_wall():
    """展示 ASCII 编码表的主要部分"""
    clear_screen()

    draw_centered_text("【ASCII 编码展示墙】", 250, FONT_TITLE, "purple")
    draw_centered_text("字符 → 编码对照表（部分）", 200, FONT_MED, "gray")
    screen.update()

    # 第一行：数字 0-9
    y = 150
    draw_centered_text("数字 0-9：", y, FONT_SMALL, "red")
    x_start = -300
    for i in range(10):
        ch = chr(48 + i)
        x = x_start + i * 65
        t.goto(x, y - 30)
        t.color("blue")
        t.write(ch, align="center", font=("Arial", 24, "bold"))
        t.goto(x, y - 60)
        t.color("darkgreen")
        t.write(f"{48 + i}", align="center", font=("Courier New", 16, "bold"))

    screen.update()
    time.sleep(1)

    # 第二行：大写字母 A-J（10个）
    y = 60
    draw_centered_text("大写字母 A-Z（部分）：", y, FONT_SMALL, "red")
    for i in range(10):
        ch = chr(65 + i)
        x = x_start + i * 65
        t.goto(x, y - 30)
        t.color("blue")
        t.write(ch, align="center", font=("Arial", 24, "bold"))
        t.goto(x, y - 60)
        t.color("darkgreen")
        t.write(f"{65 + i}", align="center", font=("Courier New", 16, "bold"))

    screen.update()
    time.sleep(1)

    # 第三行：小写字母 a-j（10个）
    y = -30
    draw_centered_text("小写字母 a-z（部分）：", y, FONT_SMALL, "red")
    for i in range(10):
        ch = chr(97 + i)
        x = x_start + i * 65
        t.goto(x, y - 30)
        t.color("blue")
        t.write(ch, align="center", font=("Arial", 24, "bold"))
        t.goto(x, y - 60)
        t.color("darkgreen")
        t.write(f"{97 + i}", align="center", font=("Courier New", 16, "bold"))

    screen.update()
    time.sleep(1)

    # 底部提示：空格和三个必记编码
    draw_centered_text("空格 ' ' = 32", -140, FONT_MED, "orange")
    draw_centered_text("口诀：'0'=48   'A'=65   'a'=97", -180, FONT_LARGE, "purple")
    draw_centered_text("零48，A65，小a大A差32！", -220, FONT_MED, "red")

    screen.update()
    time.sleep(3)


# ============================================================
# 功能 3：大小写转换演示
# ============================================================

def show_case_conversion():
    """演示大小写转换原理：小写 = 大写 + 32"""
    clear_screen()

    draw_centered_text("【大小写转换的秘密】", 250, FONT_TITLE, "purple")
    draw_centered_text("小写 = 大写 + 32", 200, FONT_LARGE, "red")
    screen.update()
    time.sleep(1)

    # 展示从 A 到 a 的转换
    letters = [
        ('A', 65, 'a', 97),
        ('B', 66, 'b', 98),
        ('C', 67, 'c', 99),
        ('D', 68, 'd', 100),
        ('E', 69, 'e', 101),
    ]

    y_start = 140
    t.goto(-250, y_start)
    t.color("darkblue")
    t.write("大写", font=("Arial", 16, "bold"))
    t.goto(-50, y_start)
    t.color("gray")
    t.write("→ +32 →", font=("Arial", 16, "bold"))
    t.goto(120, y_start)
    t.color("darkgreen")
    t.write("小写", font=("Arial", 16, "bold"))

    for i, (up, up_code, low, low_code) in enumerate(letters):
        y = y_start - 40 - i * 40

        # 大写字母
        t.goto(-280, y)
        t.color("blue")
        t.write(f"{up} ({up_code})", font=("Courier New", 18, "bold"))

        # 箭头 +32
        t.goto(-130, y)
        t.color("red")
        t.write("+32 →", font=("Arial", 16, "bold"))

        # 显示计算过程
        t.goto(-30, y)
        t.color("purple")
        t.write(f"{up_code} + 32 = {low_code}", font=("Courier New", 14, "bold"))

        # 小写字母
        t.goto(150, y)
        t.color("green")
        t.write(f"{low} ({low_code})", font=("Courier New", 18, "bold"))

    screen.update()
    time.sleep(2)

    # 底部总结
    draw_centered_text("口诀：小写 = 大写 + 32", -120, FONT_LARGE, "orange")
    draw_centered_text("ord('A') + 32 = ord('a')", -160, FONT_SMALL, "gray")
    draw_centered_text("chr(ord('B') + 32) = 'b'", -190, FONT_SMALL, "gray")

    screen.update()
    time.sleep(3)


# ============================================================
# 功能 4：猜编码游戏
# ============================================================

def guess_game():
    """猜编码游戏：闪现编码，学生猜字符"""
    clear_screen()

    draw_centered_text("【猜编码游戏】", 250, FONT_TITLE, "purple")
    draw_centered_text("看到数字 → 喊出对应的字符！", 200, FONT_MED, "gray")
    screen.update()
    time.sleep(2)

    # 题目列表：(编码, 字符, 提示)
    questions = [
        (48, '0', '数字几？'),
        (65, 'A', '大写字母？'),
        (97, 'a', '小写字母？'),
        (32, "' '（空格）", '看不见的居民？'),
        (66, 'B', 'A的好兄弟？'),
        (98, 'b', '小写b？'),
        (49, '1', '数字几？'),
        (90, 'Z', '最后一个大写字母？'),
    ]

    for code, char, hint in questions:
        clear_screen()
        draw_centered_text("这个编码对应什么字符？", 200, FONT_MED, "gray")

        # 用大大的数字显示编码
        t.goto(0, 50)
        t.color("red")
        t.write(f"{code}", align="center", font=("Arial", 80, "bold"))

        screen.update()
        time.sleep(2.5)

        # 显示答案
        t.goto(0, -50)
        t.color("green")
        t.write(f"答案：{code} → {char}", align="center", font=("Arial", 36, "bold"))

        # 验证
        if code != 32:
            t.goto(0, -100)
            t.color("darkblue")
            t.write(f"chr({code}) = '{chr(code)}'", align="center", font=FONT_COURIER)

        screen.update()
        time.sleep(2)

    clear_screen()
    draw_centered_text("游戏结束！你答对了几题？", 100, FONT_TITLE, "purple")
    screen.update()
    time.sleep(2)


# ============================================================
# 功能 5：字母楼梯 —— A-Z 编码递增
# ============================================================

def show_letter_stairs():
    """用楼梯形状展示 A-Z 编码依次递增"""
    clear_screen()

    draw_centered_text("【字母楼梯 —— 编码递增规律】", 270, FONT_TITLE, "purple")
    draw_centered_text("A(65) → B(66) → C(67) → ... → Z(90)", 220, FONT_MED, "gray")
    screen.update()
    time.sleep(1)

    # 画出 A-Z 的楼梯
    start_x = -300
    start_y = 150
    width = 22
    height_step = 6  # 每个台阶的上升高度

    t.goto(start_x, start_y)
    t.pendown()
    t.color("darkblue")
    t.pensize(2)

    # 先画楼梯基线
    for i in range(26):
        ch = chr(65 + i)
        x = start_x + i * width
        y = start_y + i * height_step

        # 画台阶横线
        t.goto(x + width, y)
        # 画竖线上升
        t.goto(x + width, y + height_step)

    t.penup()

    # 在每个台阶上标注字母和编码
    for i in range(26):
        ch = chr(65 + i)
        code = 65 + i
        x = start_x + i * width + width // 2
        y = start_y + i * height_step + 8

        t.goto(x, y)
        t.color("blue")
        t.write(ch, align="center", font=("Arial", 10, "bold"))
        t.goto(x, y - 16)
        t.color("darkgreen")
        t.write(str(code), align="center", font=("Courier New", 8, "normal"))

    screen.update()
    time.sleep(2)

    # 标注关键节点
    markers = [
        (0, 'A = 65 (开始)'),
        (25, 'Z = 90 (结尾)'),
        (5, 'F = 70'),
        (12, 'M = 77'),
        (19, 'T = 84'),
    ]

    for i, (idx, text) in enumerate(markers):
        x = start_x + idx * width + width // 2
        y = start_y + idx * height_step + 30

        t.goto(x, y)
        t.color("red")
        t.write(text, align="center", font=("Arial", 10, "bold"))

    # 底部总结
    draw_centered_text("发现规律了吗？A→Z 每次 +1", -80, FONT_MED, "orange")
    draw_centered_text("大写字母区：65 ~ 90（共26个）", -120, FONT_MED, "darkblue")
    draw_centered_text("小写字母区：97 ~ 122（共26个）", -150, FONT_MED, "darkgreen")

    screen.update()
    time.sleep(3)


# ============================================================
# 额外小彩蛋：显示完整的 Hello 编码
# ============================================================

def show_hello_codes():
    """展示 Hello 每个字母的编码"""
    clear_screen()

    draw_centered_text("【小彩蛋：Hello 的编码】", 250, FONT_TITLE, "purple")
    draw_centered_text("看看 'Hello' 每个字母的编码", 200, FONT_MED, "gray")
    screen.update()
    time.sleep(1)

    word = "Hello"
    colors = ["red", "orange", "gold", "green", "blue"]

    for i, ch in enumerate(word):
        x = -150 + i * 80
        code = ord(ch)

        # 字符
        t.goto(x, 100)
        t.color(colors[i])
        t.write(ch, font=("Arial", 48, "bold"))

        # 编码
        t.goto(x, 30)
        t.color("darkgray")
        t.write(f"{code}", align="center", font=("Courier New", 24, "bold"))

        # 二进制（扩展知识，简单展示）
        binary = bin(code)[2:].zfill(8)
        t.goto(x, -10)
        t.color("lightgray")
        t.write(binary, align="center", font=("Courier New", 12, "normal"))

    screen.update()
    time.sleep(2)

    # 总结
    draw_centered_text("每个字符 → 唯一编码 → 二进制存入电脑", -80, FONT_MED, "purple")
    draw_centered_text("H=72  e=101  l=108  l=108  o=111", -130, FONT_MED, "gray")
    draw_centered_text("咦？两个 l 编码相同！相同字符 = 相同编码", -170, FONT_MED, "red")

    screen.update()
    time.sleep(3)


# ============================================================
# 结束画面
# ============================================================

def show_ending():
    """结束画面"""
    clear_screen()

    draw_centered_text("第4节  ASCII与编码 结束！", 200, FONT_TITLE, "purple")
    draw_centered_text("记住我们的口诀：", 120, FONT_MED, "gray")
    draw_centered_text('“零48，A65，小a大A差32”', 50, FONT_LARGE, "red")
    draw_centered_text("", 0, FONT_MED, "gray")
    draw_centered_text("课后想一想：", -50, FONT_MED, "gray")
    draw_centered_text("1. 你的名字首字母编码是多少？", -90, FONT_SMALL, "darkblue")
    draw_centered_text("2. 能不能用 chr + ord 写个加密程序？", -120, FONT_SMALL, "darkblue")
    draw_centered_text("3. 空格和'0'的编码谁大谁小？", -150, FONT_SMALL, "darkblue")

    draw_centered_text("单击屏幕退出", -250, FONT_SMALL, "gray")

    screen.update()


# ============================================================
# 主程序
# ============================================================

def main():
    """主函数：依次运行所有功能模块"""

    # 功能 1：开场动画
    show_opening()
    screen.onclick(lambda x, y: None)  # 点击继续
    screen.update()
    screen.onclick(lambda x, y: screen.bye())

    # 功能 2：ASCII 展示墙
    show_ascii_wall()

    # 功能 3：大小写转换
    show_case_conversion()

    # 功能 4：猜编码游戏
    guess_game()

    # 功能 5：字母楼梯
    show_letter_stairs()

    # 彩蛋
    show_hello_codes()

    # 结束
    show_ending()

    # 等待点击退出
    screen.exitonclick()


if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        print(f"程序出错了：{e}")
        print("不要慌！检查一下 turtle 窗口是否正常打开。")
        screen.bye()
