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


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
第3节：位运算魔法 — Turtle 位运算可视化
面向7-10岁少儿编程教学
=========================================
画两排二进制位，用颜色标注每位运算结果
展示 & | ^ ~ << >> 六种位运算
"""

import turtle
import time
import math

# ========== 全局设置 ==========
SCREEN_W = 1000
SCREEN_H = 700
BITS = 8                     # 显示8位
BOX_SIZE = 50
BOX_GAP = 6
START_X = -(BITS * (BOX_SIZE + BOX_GAP)) // 2
ROW_A_Y = 200                # 数字A的行
ROW_B_Y = 120                # 数字B的行
ROW_R_Y = 20                 # 结果行
OPERATOR_Y = 160             # 运算符位置

# 颜色
COLOR_1 = "#2ECC71"          # 1 = 绿色
COLOR_0 = "#E74C3C"          # 0 = 红色
COLOR_BG = "#F8F9FA"
COLOR_BOX_BORDER = "#BDC3C7"
COLOR_LABEL = "#2C3E50"
COLOR_A = "#3498DB"          # 数字A的标签颜色
COLOR_B = "#E67E22"          # 数字B的标签颜色
COLOR_R = "#9B59B6"          # 结果的标签颜色

# ========== 初始化画布 ==========
screen = turtle.Screen()
screen.setup(SCREEN_W, SCREEN_H)
screen.bgcolor(COLOR_BG)
screen.title("位运算魔法 — Turtle 位运算可视化")
screen.tracer(0)

# ========== 创建海龟 ==========
pen = turtle.Turtle()
pen.speed(0)
pen.hideturtle()


def draw_bit_box(x, y, value, box_color_1=COLOR_1, box_color_0=COLOR_0, size=None):
    """
    在 (x, y) 画一个二进制位方块
    value: 0 或 1
    """
    if size is None:
        size = BOX_SIZE

    pen.penup()
    pen.goto(x, y)
    pen.pendown()
    pen.pencolor(COLOR_BOX_BORDER)
    if value == 1:
        pen.fillcolor(box_color_1)
    else:
        pen.fillcolor(box_color_0)
    pen.begin_fill()
    for _ in range(4):
        pen.forward(size)
        pen.right(90)
    pen.end_fill()

    # 方块内写数字
    pen.penup()
    pen.goto(x + size // 2, y + size // 2 - 8)
    pen.color("white")
    pen.write(str(value), align="center",
              font=("Arial", 16, "bold"))


def draw_bit_row(x, y, bits, label="", color_tag=COLOR_A, size=None):
    """画一排二进制位"""
    if size is None:
        size = BOX_SIZE

    for i in range(BITS):
        bx = x + i * (size + BOX_GAP)
        draw_bit_box(bx, y, bits[i], size=size)

    # 画标签
    if label:
        pen.penup()
        pen.goto(x - 40, y + size // 2 - 8)
        pen.color(color_tag)
        pen.write(label, align="center", font=("Arial", 14, "bold"))


def draw_operator(x, y, op_symbol, color="darkorange"):
    """画运算符符号"""
    pen.penup()
    pen.goto(x, y)
    pen.color(color)
    pen.write(op_symbol, align="center", font=("Arial", 24, "bold"))


def draw_binary_label(x, y, decimal_val, bin_str, label_color):
    """画数字的十进制和二进制标签"""
    pen.penup()
    pen.goto(x, y)
    pen.color(label_color)
    pen.write(f"{decimal_val} = {bin_str}",
              align="left", font=("Courier", 12, "bold"))


def draw_title(text, y=310):
    """画标题"""
    pen.penup()
    pen.goto(0, y)
    pen.color("#2C3E50")
    pen.write(text, align="center", font=("Arial", 22, "bold"))


def draw_info(text, y=-230):
    """画底部信息"""
    pen.penup()
    pen.goto(0, y)
    pen.color("gray")
    pen.write(text, align="center", font=("Arial", 13, "normal"))


def get_bits(number, bits=BITS):
    """获取一个数字的二进制位列表（高位在前）"""
    return [(number >> i) & 1 for i in range(bits - 1, -1, -1)]


def bits_to_int(bits):
    """将二进制位列表转为整数"""
    n = 0
    for b in bits:
        n = (n << 1) | b
    return n


def show_bit_operation(a, b, op_name, op_symbol, op_func):
    """
    展示两个数字的位运算
    op_func: 接受两个int返回int的函数
    """
    pen.clear()

    # 获取位
    a_bits = get_bits(a)
    b_bits = get_bits(b)

    # 计算结果
    result = op_func(a, b)

    # 特殊处理取反（取反是单目运算，但这里我们用 a 作为输入）
    if op_name == "~":
        # 对于取反，b_bits 不显示
        result_bits = [1 - bit for bit in a_bits]
    else:
        result_bits = get_bits(result)

    # 标题
    title = f"{a} {op_symbol} {b} = {result}"
    if op_name == "~":
        title = f"~{a} = {result}"
    draw_title(f"位运算: {title}", y=310)

    # 画运算符
    op_x = START_X - 35
    if op_name != "~":
        draw_operator(op_x, OPERATOR_Y, op_symbol, "#E67E22")

    # 画数字A的二进制
    draw_bit_row(START_X, ROW_A_Y, a_bits,
                 label=f"A={a}", color_tag=COLOR_A)
    # 数字A的二进制文本
    a_bin = bin(a)[2:].zfill(BITS)
    draw_binary_label(START_X, ROW_A_Y - 25, a, a_bin, COLOR_A)

    if op_name != "~":
        # 画数字B的二进制
        draw_bit_row(START_X, ROW_B_Y, b_bits,
                     label=f"B={b}", color_tag=COLOR_B)
        b_bin = bin(b)[2:].zfill(BITS)
        draw_binary_label(START_X, ROW_B_Y - 25, b, b_bin, COLOR_B)
    else:
        # 取反时画提示
        pen.penup()
        pen.goto(START_X + BITS * (BOX_SIZE + BOX_GAP) // 2, ROW_B_Y + 10)
        pen.color("gray")
        pen.write("(按位取反: 0变1, 1变0)",
                  align="center", font=("Arial", 14, "normal"))

    # 画结果
    result_label = f"结果={result}"
    draw_bit_row(START_X, ROW_R_Y, result_bits,
                 label=result_label, color_tag=COLOR_R)
    r_bin = bin(result)[2:].zfill(BITS) if result >= 0 else "N/A"
    if result >= 0:
        draw_binary_label(START_X, ROW_R_Y - 25, result, r_bin, COLOR_R)

    # 运算规则说明
    rule_texts = {
        "&": "规则: 两个都是1才得1 (都1才1)",
        "|": "规则: 有一个1就得1 (有1就1)",
        "^": "规则: 不同才得1 (不同才1)",
        "~": "规则: 0变1, 1变0 (翻个底朝天)",
        "<<": f"规则: 左移=乘2  (a<<1={a<<1})",
        ">>": f"规则: 右移=除2  (a>>1={a>>1})",
    }

    if op_symbol in rule_texts:
        pen.penup()
        pen.goto(0, ROW_R_Y - 55)
        pen.color("#8E44AD")
        pen.write(rule_texts[op_symbol], align="center",
                  font=("Arial", 14, "normal"))

    # 逐位标注（在每列下方显示运算细节）
    if op_name != "~":
        for i in range(BITS):
            ax = a_bits[i]
            bx = b_bits[i]
            rx = result_bits[i]
            x = START_X + i * (BOX_SIZE + BOX_GAP) + BOX_SIZE // 2
            y = ROW_R_Y - 60

            pen.penup()
            pen.goto(x, y)
            pen.color("gray")
            pen.write(f"{ax}{op_symbol}{bx}={rx}",
                      align="center", font=("Arial", 9, "normal"))

    screen.update()


def show_shift_operation(n, shift_amount, direction):
    """
    展示移位操作
    direction: "left" 或 "right"
    """
    pen.clear()

    bits_n = get_bits(n)

    if direction == "left":
        result = n << shift_amount
        op_symbol = "<<"
        title_text = f"{n} << {shift_amount} = {result}"
        rule_text = f"左移{shift_amount}位 = 乘2^{shift_amount} = 乘{2**shift_amount}"
        dir_text = "<< 左移"
    else:
        result = n >> shift_amount
        op_symbol = ">>"
        title_text = f"{n} >> {shift_amount} = {result}"
        rule_text = f"右移{shift_amount}位 = 除2^{shift_amount} = 除{2**shift_amount}"
        dir_text = ">> 右移"

    draw_title(f"移位运算: {title_text}", y=310)

    # 画原数的二进制
    draw_bit_row(START_X, ROW_A_Y, bits_n,
                 label=f"原数={n}", color_tag=COLOR_A)
    draw_binary_label(START_X, ROW_A_Y - 25, n,
                      bin(n)[2:].zfill(BITS), COLOR_A)

    # 画运算符
    draw_operator(START_X - 35, OPERATOR_Y, op_symbol, "#E67E22")

    # 画结果的二进制
    if result >= 0:
        result_bits = get_bits(result)
        draw_bit_row(START_X, ROW_R_Y, result_bits,
                     label=f"结果={result}", color_tag=COLOR_R)
        draw_binary_label(START_X, ROW_R_Y - 25, result,
                          bin(result)[2:].zfill(BITS), COLOR_R)

        # 画移位方向指示
        show_shift_arrow(n, shift_amount, direction)

    # 规则说明
    pen.penup()
    pen.goto(0, ROW_R_Y - 55)
    pen.color("#8E44AD")
    pen.write(rule_text, align="center", font=("Arial", 14, "normal"))

    # 底部提示
    if direction == "right":
        pen.penup()
        pen.goto(0, ROW_R_Y - 75)
        pen.color("gray")
        pen.write("右移是整数除法，小数部分被丢弃",
                  align="center", font=("Arial", 11, "normal"))

    screen.update()


def show_shift_arrow(n, shift_amount, direction):
    """画移位箭头指示"""
    arrow_y = (ROW_A_Y + ROW_R_Y) // 2

    if direction == "left":
        # 左移箭头：从右向左
        start_idx = BITS - 1 - shift_amount
        end_idx = BITS - 1
    else:
        # 右移箭头：从左向右
        start_idx = shift_amount
        end_idx = 0

    for i in range(BITS):
        src_x = START_X + i * (BOX_SIZE + BOX_GAP) + BOX_SIZE // 2
        if direction == "left" and i + shift_amount < BITS:
            dst_x = START_X + (i + shift_amount) * (BOX_SIZE + BOX_GAP) + BOX_SIZE // 2
            # 画箭头
            pen.penup()
            pen.goto(src_x, ROW_A_Y - 10)
            pen.pendown()
            pen.pencolor("darkorange")
            pen.pensize(2)
            pen.goto(dst_x, ROW_R_Y + BOX_SIZE + 10)
            pen.penup()
        elif direction == "right" and i - shift_amount >= 0:
            dst_x = START_X + (i - shift_amount) * (BOX_SIZE + BOX_GAP) + BOX_SIZE // 2
            pen.penup()
            pen.goto(src_x, ROW_A_Y - 10)
            pen.pendown()
            pen.pencolor("darkorange")
            pen.pensize(2)
            pen.goto(dst_x, ROW_R_Y + BOX_SIZE + 10)
            pen.penup()


def demo_all_operations():
    """演示所有位运算"""
    a_val = 0b10110101  # 181
    b_val = 0b11001100  # 204

    operations = [
        ("&", "&", lambda x, y: x & y),
        ("|", "|", lambda x, y: x | y),
        ("^", "^", lambda x, y: x ^ y),
    ]

    for op_name, op_sym, op_func in operations:
        show_bit_operation(a_val, b_val, op_name, op_sym, op_func)
        time.sleep(2)

    # 取反
    pen.clear()
    show_bit_operation(a_val, 0, "~", "~", lambda x, y: ~x)
    time.sleep(2)

    # 左移
    show_shift_operation(5, 1, "left")
    time.sleep(1.5)
    show_shift_operation(5, 2, "left")
    time.sleep(1.5)
    show_shift_operation(5, 3, "left")
    time.sleep(1.5)

    # 右移
    show_shift_operation(100, 1, "right")
    time.sleep(1.5)
    show_shift_operation(100, 2, "right")
    time.sleep(1.5)
    show_shift_operation(100, 3, "right")
    time.sleep(1.5)


def interactive_demo():
    """交互模式 - 让用户选择运算"""
    show_bit_operation(5, 3, "&", "&", lambda x, y: x & y)

    pen.penup()
    pen.goto(0, -280)
    pen.color("gray")
    pen.write("你可以修改代码中的 operations 列表来展示不同的位运算",
              align="center", font=("Arial", 12, "normal"))
    screen.update()


# ========== 入口 ==========
if __name__ == "__main__":
    # 自动演示
    demo_all_operations()

    # 最后展示交互信息
    pen.penup()
    pen.goto(0, -300)
    pen.color("gray")
    pen.write("点击画面退出  |  绿色=1  红色=0",
              align="center", font=("Arial", 12, "normal"))
    screen.update()

    screen.exitonclick()
