import customtkinter as ctk
import tkinter as tk
# 設定外觀風格與主題
ctk.set_appearance_mode("System")
ctk.set_default_color_theme("blue")
class GaloisLFSRVisualizer(ctk.CTk):
def __init__(self):
super().__init__()
# =============================================================
# 核心修正:強力繞過 CustomTkinter 內部與 Thonny/底層環境的相容性 Bug
# =============================================================
# 1. 繞過標題列顏色設定錯誤
self._windows_set_titlebar_color = lambda appearance_mode: None
# 2. 繞過背景縮放追蹤器 (Scaling Tracker) 的無限迴圈崩潰
try:
from customtkinter.windows.widgets.scaling.scaling_tracker import ScalingTracker
ScalingTracker.check_dpi_scaling = lambda *args, **kwargs: None
except Exception:
pass
# =============================================================
self.title("Galois LFSR 現代化動態模擬器")
self.geometry("800x520")
# 4位元 Galois LFSR 參數設定
self.num_bits = 4
self.state = [1, 0, 1, 1] # 初始種子 (Seed)
self.taps = [True, False, True, False] # [X3_in, X2_in, X1_in, X0_in] 是否有抽頭
self.clock_count = 0
self.is_playing = False
self.output_history = []
self.setup_ui()
# 確保 Canvas 佈局完成後再進行第一次繪製
self.update_idletasks()
self.draw_lfsr()
def setup_ui(self):
# 1. 標題與說明面板
self.title_frame = ctk.CTkFrame(self, fg_color="transparent")
self.title_frame.pack(pady=15, fill=ctk.X)
self.lbl_title = ctk.CTkLabel(self.title_frame, text="Galois LFSR 運作原理動態模擬", font=ctk.CTkFont(family="Helvetica", size=20, weight="bold"))
self.lbl_title.pack()
self.lbl_formula = ctk.CTkLabel(self.title_frame, text="特徵多項式: f(x) = X⁴ + X¹ + 1 (抽頭設於 X₁ 的輸入端)", font=ctk.CTkFont(family="Courier", size=13, weight="normal"))
self.lbl_formula.pack(pady=2)
# 2. 畫布面板 (放置傳統 Canvas 用於繪製電路圖)
self.canvas_frame = ctk.CTkFrame(self, width=740, height=190, corner_radius=10)
self.canvas_frame.pack(pady=10, padx=20, fill=ctk.BOTH, expand=True)
self.canvas = tk.Canvas(self.canvas_frame, bg="#ffffff", highlightthickness=0)
self.canvas.pack(padx=5, pady=5, fill=tk.BOTH, expand=True)
# 3. 狀態與數據顯示面板
self.info_frame = ctk.CTkFrame(self, corner_radius=10)
self.info_frame.pack(pady=10, padx=20, fill=ctk.X)
self.lbl_clock = ctk.CTkLabel(self.info_frame, text="目前時脈 (Clock): 0", font=ctk.CTkFont(size=13, weight="bold"), anchor="w")
self.lbl_clock.pack(side=ctk.TOP, fill=ctk.X, padx=15, pady=(8, 2))
self.lbl_status = ctk.CTkLabel(self.info_frame, text="準備就緒。請點擊單步執行或自動播放。", font=ctk.CTkFont(size=13), text_color="#1f77b4", anchor="w")
self.lbl_status.pack(side=ctk.TOP, fill=ctk.X, padx=15, pady=2)
self.lbl_output = ctk.CTkLabel(self.info_frame, text="產生的偽隨機序列 (Output): ", font=ctk.CTkFont(family="Courier", size=14, weight="bold"), text_color="#d62728", anchor="w")
self.lbl_output.pack(side=ctk.TOP, fill=ctk.X, padx=15, pady=(2, 8))
# 4. 控制按鈕面板
self.control_frame = ctk.CTkFrame(self, fg_color="transparent")
self.control_frame.pack(pady=15)
self.btn_next = ctk.CTkButton(self.control_frame, text="單步執行 (Next)", width=150, command=self.step_lfsr)
self.btn_next.grid(row=0, column=0, padx=10)
self.btn_toggle = ctk.CTkButton(self.control_frame, text="自動播放", width=150, fg_color="#2ca02c", hover_color="#238223", command=self.toggle_play)
self.btn_toggle.grid(row=0, column=1, padx=10)
self.btn_reset = ctk.CTkButton(self.control_frame, text="重設 (Reset)", width=150, fg_color="#7f7f7f", hover_color="#636363", command=self.reset_lfsr)
self.btn_reset.grid(row=0, column=2, padx=10)
def draw_lfsr(self, feedback_active=None):
"""利用 Canvas 繪製動態的 Galois LFSR 架構圖"""
self.canvas.delete("all")
canvas_width = self.canvas.winfo_width()
if canvas_width < 10:
canvas_width = 730
box_width, box_height = 60, 50
gap = 80
start_x = (canvas_width - ((self.num_bits - 1) * gap + box_width)) // 2 - 20
start_y = 50
# 1. 畫出主回饋線路
out_x = start_x + (self.num_bits - 1) * gap + box_width
out_y = start_y + box_height // 2
fb_color = "#1f77b4" if feedback_active == 1 else "#b0b0b0"
fb_width = 3 if feedback_active == 1 else 1.5
self.canvas.create_line(out_x, out_y, out_x + 30, out_y, fill=fb_color, width=fb_width)
self.canvas.create_line(out_x + 20, out_y, out_x + 20, out_y + 65, fill=fb_color, width=fb_width)
self.canvas.create_line(out_x + 20, out_y + 65, start_x - 45, out_y + 65, fill=fb_color, width=fb_width)
self.canvas.create_line(start_x - 45, out_y + 65, start_x - 45, out_y, fill=fb_color, width=fb_width)
self.canvas.create_line(start_x - 45, out_y, start_x, out_y, arrow=tk.LAST, fill=fb_color, width=fb_width)
self.canvas.create_text(out_x + 50, out_y - 15, text="Output", fill="#d62728", font=("Helvetica", 11, "bold"))
# 2. 依序繪製各個暫存器方塊 (從左到右為 X3, X2, X1, X0)
for i in range(self.num_bits):
x1 = start_x + i * gap
y1 = start_y
x2 = x1 + box_width
y2 = y1 + box_height
reg_name = f"X{self.num_bits - 1 - i}"
self.canvas.create_rectangle(x1, y1, x2, y2, fill="#e1f5fe", outline="#0288d1", width=2)
self.canvas.create_text((x1 + x2)//2, (y1 + y2)//2, text=str(self.state[i]), font=("Helvetica", 20, "bold"), fill="#212121")
self.canvas.create_text((x1 + x2)//2, y1 - 15, text=reg_name, font=("Helvetica", 11, "bold"), fill="#555555")
# 3. 處理暫存器之間的連接與 XOR 閘
if i < self.num_bits - 1:
next_x1 = start_x + (i + 1) * gap
mid_y = y1 + box_height // 2
if self.taps[i + 1]:
xor_x = x2 + (gap - box_width) // 2
self.canvas.create_oval(xor_x - 11, mid_y - 11, xor_x + 11, mid_y + 11, fill="#fff3e0", outline="#ffb74d", width=2)
self.canvas.create_text(xor_x, mid_y, text="⊕", font=("Helvetica", 14, "bold"), fill="#f57c00")
self.canvas.create_line(x2, mid_y, xor_x - 11, mid_y, fill="#666666", width=1.5)
self.canvas.create_line(xor_x + 11, mid_y, next_x1, mid_y, arrow=tk.LAST, fill="#666666", width=1.5)
self.canvas.create_line(xor_x, mid_y + 65, xor_x, mid_y + 11, arrow=tk.LAST, fill=fb_color, width=fb_width)
else:
self.canvas.create_line(x2, mid_y, next_x1, mid_y, arrow=tk.LAST, fill="#666666", width=1.5)
def step_lfsr(self):
"""執行一個 Clock 的標準 Galois 移位與 XOR 邏輯"""
self.clock_count += 1
output_bit = self.state[-1]
self.output_history.append(str(output_bit))
self.draw_lfsr(feedback_active=output_bit)
next_state = [0] * self.num_bits
next_state[0] = output_bit
status_msg = f"時脈 {self.clock_count}: 輸出位元 = {output_bit}。 "
if output_bit == 1:
status_msg += "回饋為 1 → 抽頭處 (X₁ 輸入端) 將與 1 進行 XOR 反轉。"
else:
status_msg += "回饋為 0 → 抽頭處不受影響,資料正常右移。"
for i in range(1, self.num_bits):
if self.taps[i]:
next_state[i] = self.state[i-1] ^ output_bit
else:
next_state[i] = self.state[i-1]
self.state = next_state
self.after(200, self.update_ui_text, status_msg)
def update_ui_text(self, status_msg):
self.lbl_clock.configure(text=f"目前時脈 (Clock): {self.clock_count}")
self.lbl_status.configure(text=status_msg)
self.lbl_output.configure(text=f"產生的偽隨機序列 (Output): {' '.join(self.output_history)}")
self.draw_lfsr()
def toggle_play(self):
"""切換自動播放 / 暫停"""
if self.is_playing:
self.is_playing = False
self.btn_toggle.configure(text="自動播放", fg_color="#2ca02c", hover_color="#238223")
else:
self.is_playing = True
self.btn_toggle.configure(text="暫停", fg_color="#d62728", hover_color="#b32021")
self.auto_play()
def auto_play(self):
if self.is_playing:
self.step_lfsr()
self.after(1000, self.auto_play)
def reset_lfsr(self):
self.state = [1, 0, 1, 1]
self.clock_count = 0
self.is_playing = False
self.output_history = []
self.btn_toggle.configure(text="自動播放", fg_color="#2ca02c", hover_color="#238223")
self.lbl_clock.configure(text="目前時脈 (Clock): 0")
self.lbl_status.configure(text="已重設。請點擊單步執行或自動播放。", text_color="#1f77b4")
self.lbl_output.configure(text="產生的偽隨機序列 (Output): ")
self.draw_lfsr()
if __name__ == "__main__":
app = GaloisLFSRVisualizer()
app.mainloop()
沒有留言:
張貼留言