|

細談「春仔產生器」的專案拆解

   

作者:Ted Lee

我們在《用生成式 AI 打造「春仔」產生器》一文中已完整介紹了使用 Claude 來生成春仔的 Python 程式碼的完整過程了。本文再深入增補專案程式解析的三大理路(圖 1):

  1. 加註(annotation):在「程式流程圖」與「函式呼叫圖(call graph)」上標示對應程式碼的行號以加強對程式架構的理解。
  2. 萃取(extraction):從專案程式中截取出「基元(fundamental elements)」──開啟 Tkinter 視窗(另存為「開視窗.py」)、視窗的 UI 設計(另存為「UI.py」)、按鈕事件的處理,update)_text()、春仔中的菱形圖案繪製,draw_diamond()。
  3. 取核:將專案最核心的關鍵演算法找出──春仔中動態吉祥話文字的處理,calculate_font_size()。

圖1:程式碼解析三軸線:加註、萃取、取核

我們使用 Claude 生成春仔產生器的完整 Python 程式碼如下:
春仔產生器.py


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# -*- coding: utf-8 -*-
"""
春仔視窗應用程式
功能:繪製紅底燙金花邊的菱形,並可輸入吉祥話和作者名稱
日期:2025/3/11
"""

import tkinter as tk
from tkinter import ttk
import math

# 全域變數設定
WINDOW_WIDTH = 800
WINDOW_HEIGHT = 600
DIAMOND_SIZE = 300  # 菱形大小基準值
BORDER_WIDTH = 10   # 燙金花邊寬度

# 繪製菱形函式
def draw_diamond(canvas, size, border_width):
    """
    在畫布上繪製指定大小的紅底燙金花邊菱形
   
    參數:
    canvas: tkinter畫布物件
    size: 菱形的尺寸
    border_width: 燙金花邊的寬度
   
    回傳值:
    菱形四個頂點座標
    """
    # 取得畫布中心點
    center_x = WINDOW_WIDTH // 2
    center_y = WINDOW_HEIGHT // 2

    # 計算菱形頂點座標
    points = [
        (center_x, center_y - size // 2),       # 上頂點
        (center_x + size // 2, center_y),       # 右頂點
        (center_x, center_y + size // 2),       # 下頂點
        (center_x - size // 2, center_y)        # 左頂點
    ]
   
    # 繪製紅色內部
    canvas.create_polygon(points, fill="#FF0000", outline="")
   
    # 繪製燙金花邊 (外層邊框)
    outer_points = [
        (center_x, center_y - (size + border_width) // 2),
        (center_x + (size + border_width) // 2, center_y),
        (center_x, center_y + (size + border_width) // 2),
        (center_x - (size + border_width) // 2, center_y)
    ]
    canvas.create_polygon(outer_points, fill="#DAA520", outline="")
    canvas.create_polygon(points, fill="#FF0000", outline="")
   
    return points

# 計算適合菱形內的字型大小
def calculate_font_size(canvas, text, points, max_size=100, min_size=8):
    """
    計算文字適合放入菱形內的最大字型大小
   
    參數:
    canvas: tkinter畫布物件
    text: 要顯示的文字
    points: 菱形的頂點座標
    max_size: 最大字型大小
    min_size: 最小字型大小
   
    回傳值:
    適合的字型大小
    """
    # 計算菱形寬度和高度
    diamond_width = points[1][0] - points[3][0]
    diamond_height = points[2][1] - points[0][1]
   
    # 考慮菱形形狀,可用空間更小
    usable_width = diamond_width * 0.7
    usable_height = diamond_height * 0.7
   
    # 二分法查找合適的字型大小
    size = max_size
    lower_bound = min_size
    upper_bound = max_size
   
    while upper_bound - lower_bound > 1:
        temp_font = ("微軟正黑體", size, "bold")
        text_width = canvas.create_text(0, 0, text=text, font=temp_font, anchor=tk.NW)
        bbox = canvas.bbox(text_width)
        canvas.delete(text_width)
       
        if bbox is None:
            upper_bound = size
            size = (upper_bound + lower_bound) // 2
            continue
           
        text_width = bbox[2] - bbox[0]
        text_height = bbox[3] - bbox[1]
       
        if text_width <= usable_width and text_height <= usable_height:
            lower_bound = size
        else:
            upper_bound = size
           
        size = (upper_bound + lower_bound) // 2
       
    return lower_bound

# 更新畫布上的吉祥話和作者
def update_text(canvas, greeting_var, author_var, diamond_points):
    """
    更新畫布上的吉祥話和作者文字
   
    參數:
    canvas: tkinter畫布物件
    greeting_var: 吉祥話變數
    author_var: 作者變數
    diamond_points: 菱形的頂點座標
    """
    # 清除先前的文字
    canvas.delete("greeting")
    canvas.delete("author")
   
    # 取得當前輸入的文字
    greeting = greeting_var.get()
    author = author_var.get()
   
    if greeting:
        # 計算吉祥話的合適字型大小
        greeting_font_size = calculate_font_size(canvas, greeting, diamond_points)
        greeting_font = ("微軟正黑體", greeting_font_size, "bold")
       
        # 繪製吉祥話 (置中位置,稍微往上一點)
        center_x = WINDOW_WIDTH // 2
        center_y = WINDOW_HEIGHT // 2 - 20
        canvas.create_text(center_x, center_y, text=greeting, font=greeting_font,
                           fill="#DAA520", tags="greeting")
   
    if author:
        # 計算作者的合適字型大小
        author_font_size = min(24, calculate_font_size(canvas, author, diamond_points) - 4)
        author_font = ("微軟正黑體", author_font_size)
       
        # 繪製作者 (置於菱形下方)
        center_x = WINDOW_WIDTH // 2
        center_y = WINDOW_HEIGHT // 2 + 70
        canvas.create_text(center_x, center_y, text=author, font=author_font,
                           fill="#DAA520", tags="author")

# 主程式函式
def main():
    """
    主程式函式,建立視窗和控制項
    """
    # 建立主視窗
    root = tk.Tk()
    root.title("春仔 - 新年吉祥賀卡")
    root.geometry(f"{WINDOW_WIDTH}x{WINDOW_HEIGHT}")
   
    # 建立主框架
    main_frame = ttk.Frame(root)
    main_frame.pack(fill=tk.BOTH, expand=True)
   
    # 建立畫布
    canvas = tk.Canvas(main_frame, bg="#FFF8DC")
    canvas.pack(fill=tk.BOTH, expand=True)
   
    # 繪製菱形
    diamond_points = draw_diamond(canvas, DIAMOND_SIZE, BORDER_WIDTH)
   
    # 建立輸入框架
    input_frame = ttk.Frame(root, padding="10")
    input_frame.pack(fill=tk.X)
   
    # 建立吉祥話輸入
    ttk.Label(input_frame, text="吉祥話:").grid(row=0, column=0, padx=5, sticky=tk.W)
    greeting_var = tk.StringVar()
    greeting_entry = ttk.Entry(input_frame, textvariable=greeting_var, width=40)
    greeting_entry.grid(row=0, column=1, padx=5)
    greeting_entry.insert(0, "恭賀新春")
   
    # 建立作者輸入
    ttk.Label(input_frame, text="作者:").grid(row=0, column=2, padx=5, sticky=tk.W)
    author_var = tk.StringVar()
    author_entry = ttk.Entry(input_frame, textvariable=author_var, width=15)
    author_entry.grid(row=0, column=3, padx=5)
   
    # 建立更新按鈕
    update_button = ttk.Button(input_frame, text="更新內容",
                               command=lambda: update_text(canvas, greeting_var, author_var, diamond_points))
    update_button.grid(row=0, column=4, padx=10)
   
    # 初始化顯示
    update_text(canvas, greeting_var, author_var, diamond_points)
   
    # 啟動主迴圈
    root.mainloop()

# 程式入口點
if __name__ == "__main__":
    main()
一、開 Tkinter 視窗
將「春仔產生器.py」以單行註解 # 和多行註解 ‘’’ ‘’’ 剔除暫時不相關的程式碼後,我們可以得到以下的「開視窗.py」及其執行結果(圖 2):
開視窗.py
# -*- coding: utf-8 -*-
"""
春仔視窗應用程式
功能:繪製紅底燙金花邊的菱形,並可輸入吉祥話和作者名稱
日期:2025/3/11
"""

import tkinter as tk
from tkinter import ttk
import math

# 全域變數設定
WINDOW_WIDTH = 800
WINDOW_HEIGHT = 600
DIAMOND_SIZE = 300  # 菱形大小基準值
BORDER_WIDTH = 10   # 燙金花邊寬度

# 主程式函式
def main():
    """
    主程式函式,建立視窗和控制項
    """
    # 建立主視窗
    root = tk.Tk()
    root.title("春仔 - 新年吉祥賀卡")
    root.geometry(f"{WINDOW_WIDTH}x{WINDOW_HEIGHT}")
   
    # 建立主框架
    main_frame = ttk.Frame(root)
    main_frame.pack(fill=tk.BOTH, expand=True)
 
 
    # 啟動主迴圈
    root.mainloop()

# 程式入口點
if __name__ == "__main__":
    main()

圖 2:開視窗.py 的執行畫面

UI 設計

和第一小節相同的手法,我們從「春仔產生器.py」切出來的視窗 UI 設計和執行結果如下:
UI.py


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# -*- coding: utf-8 -*-
"""
春仔視窗應用程式
功能:繪製紅底燙金花邊的菱形,並可輸入吉祥話和作者名稱
日期:2025/3/11
"""

import tkinter as tk
from tkinter import ttk
import math

# 全域變數設定
WINDOW_WIDTH = 800
WINDOW_HEIGHT = 600
DIAMOND_SIZE = 300  # 菱形大小基準值
BORDER_WIDTH = 10   # 燙金花邊寬度

# 主程式函式
def main():
    """
    主程式函式,建立視窗和控制項
    """
    # 建立主視窗
    root = tk.Tk()
    root.title("春仔 - 新年吉祥賀卡")
    root.geometry(f"{WINDOW_WIDTH}x{WINDOW_HEIGHT}")
   
    # 建立主框架
    main_frame = ttk.Frame(root)
    main_frame.pack(fill=tk.BOTH, expand=True)
 
    # 建立畫布
    canvas = tk.Canvas(main_frame, bg="#FFF8DC")
    canvas.pack(fill=tk.BOTH, expand=True)
   
   
    # 建立輸入框架
    input_frame = ttk.Frame(root, padding="10")
    input_frame.pack(fill=tk.X)
   
    # 建立吉祥話輸入
    ttk.Label(input_frame, text="吉祥話:").grid(row=0, column=0, padx=5, sticky=tk.W)
    greeting_var = tk.StringVar()
    greeting_entry = ttk.Entry(input_frame, textvariable=greeting_var, width=40)
    greeting_entry.grid(row=0, column=1, padx=5)
    greeting_entry.insert(0, "恭賀新春")
   
    # 建立作者輸入
    ttk.Label(input_frame, text="作者:").grid(row=0, column=2, padx=5, sticky=tk.W)
    author_var = tk.StringVar()
    author_entry = ttk.Entry(input_frame, textvariable=author_var, width=15)
    author_entry.grid(row=0, column=3, padx=5)
   
    # 建立更新按鈕
    update_button = ttk.Button(input_frame, text="更新內容",
                               command=lambda: update_text(canvas, greeting_var, author_var, diamond_points))
    update_button.grid(row=0, column=4, padx=10)
   
    # 初始化顯示
    #update_text(canvas, greeting_var, author_var, diamond_points)
 
    # 啟動主迴圈
    root.mainloop()

# 程式入口點
if __name__ == "__main__":
    main()

 

圖 3:UI.py 的執行結果

其中,視窗 root 為最外層容器(container),裡頭塞了兩個框架:

  1. main_frame:顯示菱形春仔的內容。框架內再填入一畫布 canvas 供繪製菱形與吉祥話。
  2. input_frame:顯示輸入文字的相關處理。框架內吉祥話與作者兩個文字輸入欄和文字標籤,以及更新內容按鈕。

圖 4:各視窗元件共同組成 UI 畫面的關係圖示

按鈕事件處理

為了方便理解起見,我們另外讓 Claude 產生一段按下按鈕即加一的程式片段斷,其執行結果如圖 5 所示。
按鈕事件處理.py


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import tkinter as tk

# 創建主視窗
root = tk.Tk()
root.title("計數器")

# 初始化計數
counter = 0

# 建立標籤顯示計數
label = tk.Label(root, text=str(counter), font=("Arial", 24))
label.pack(pady=20)

# 定義按鈕功能
def increment():
    global counter
    counter += 1
    label.config(text=str(counter))

# 建立按鈕
button = tk.Button(root, text="加一", command=increment)
button.pack(pady=10)

# 啟動應用程式
root.mainloop()

 

圖 5:按鈕計數器

菱形繪圖

在 draw_diamond()這個函式中,在畫布 canvas 上繪製了紅色菱形及其花編。其各頂點座標的計算如圖 6 所示。

圖 6:菱形與花邊的各頂點座標位置

流程圖加註

我們讓 Claude 產生程式對應的 Mermaid 格式的流程圖並做些許的校正,隨後再將之標示每個步驟對應到「春仔產生器.py」的程式碼行號如圖 7 所示。

圖 7:春仔產生器.py 的流程圖對照

動態字體大小處理

在 calculate_font_size() 函式是以下列三步驟的演算法來計算能塞入菱形中吉祥話和作者文字的最適尺寸(圖 9):

  1. 計算菱形的寬度和高度(L73–75)
  2. 計算菱形內可用空間(考慮到菱形形狀,只使用70%的空間)(L77–79)
  3. 使用二分法循環尋找合適的字型大小:(L86–105)

圖 9:動態字體調整演算法圖示

簡單來說,

演算法在 [8, 100] 的字型尺寸範圍內以對半切方式逐一產生相對應的字型大小後,再檢查是否能置入紅色菱形內。

[1]六種授權條款。

(作者為本刊專欄作家,本文同步表於作者部落格,原文連結;責任編輯:謝涵如)

Ted Lee

訂閱MakerPRO知識充電報

與40000位開發者一同掌握科技創新的技術資訊!

Author: Ted Lee

從工程師轉任中學教師,又為了捍衛教育理念,投身成為 STEAM 教育工作者,自稱「無可救藥的人文教育理想主義者」的李俊德(Ted Lee)。

Share This Post On

Submit a Comment

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *