๐ฒ DiceForge ๋ง๋ค๊ธฐ: Python ๋ฐ Tkinter๋ฅผ ํ์ฉํ ํ๋์ ์ธ ์ฃผ์ฌ์ ๊ตด๋ฆผ ์๋ฎฌ๋ ์ดํฐ
Source: Dev.to

ํ ์ด๋ธํ ๊ฒ์, ํ๋ฅ ์คํ, ํน์ ๋จ์ํ ์ฌ๋ฏธ๋ฅผ ์ํด ์ฃผ์ฌ์๋ฅผ ๋น ๋ฅด๊ฒ ๊ตด๋ ค์ผ ํ ๋, ์ง์ ์ฃผ์ฌ์ ๊ตด๋ฆผ๊ธฐ๋ฅผ ๋ง๋๋ ๊ฒ์ ํ๋ฅญํ ์ฃผ๋ง ํ๋ก์ ํธ๊ฐ ๋ ์ ์์ต๋๋ค.
์ด๋ฒ ํฌ์คํธ์์๋ Python, Tkinter, ๊ทธ๋ฆฌ๊ณ ttk ๋ก ์ ์๋ ์ธ๋ จ๋ ์ฃผ์ฌ์ ๊ตด๋ฆผ ์๋ฎฌ๋ ์ดํฐ DiceForge ๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค. ์ฃผ์ ๊ธฐ๋ฅ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- ๊น๋ํ๊ณ ํ๋์ ์ธ UI
- ์ฃผ์ฌ์ ๊ฐ์์ ๋ฉด ์๋ฅผ ์์ ๋กญ๊ฒ ์ค์ ๊ฐ๋ฅ
- ์ฆ์ ํ์๋๋ ๊ตด๋ฆผ ๊ฒฐ๊ณผ์ ํฉ๊ณ
- ๋ผ์ดํธ & ๋คํฌ ๋ชจ๋ ์ง์
ttk.Notebook์ ํ์ฉํ ํญ ๊ธฐ๋ฐ ๋ ์ด์์
๐งฐ ๊ธฐ์ ์คํ
- Pythonโฏ3 โ ํต์ฌ ๋ก์ง
- Tkinter โ GUI ํ๋ ์์ํฌ (Python์ ๋ด์ฅ)
- ttk / sv_ttk โ ํ ๋ง ์์ ฏ ๋ฐ ํ๋์ ์คํ์ผ๋ง
- random โ ์ฃผ์ฌ์ ๊ตด๋ฆผ ์์ฑ
์ธ๋ถ API ์์. ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์. ๊น๋ํ๊ณ ๋ก์ปฌ Python๋ง ์ฌ์ฉ.
๐ง ์ฑ ๊ฐ์
DiceForge๋ ๋จ์ผ ์ฐฝ ๋ฐ์คํฌํฑ ์ฑ์ผ๋ก ๊ตฌ์ฑ๋์ด ์์ผ๋ฉฐ ๋ ๊ฐ์ ์ฃผ์ ํญ์ด ์์ต๋๋ค:
- ๋์๋ณด๋ โ ์๊ฐ ํ ์คํธ ๋ฐ ๊ธฐ๋ฅ ๋ชฉ๋ก
- ์ฃผ์ฌ์ ๊ตด๋ฆฌ๊ธฐ โ ๋ง๋ฒ์ด ์ผ์ด๋๋ ๊ณณ
UI๋ ๋ฐ์ํ์ด๋ฉฐ ๊ฐ๋ ์ฑ์ด ์ข๊ณ , ๊ธฐ๋ณธ Tkinter ์ฐฝ๋ณด๋ค ํ๋์ ์ธ ์ฑ์ ๋ ๊ฐ๊น๊ฒ ๋๊ปด์ง๋๋ก ์ค๊ณ๋์์ต๋๋ค.
๐ ์ ํ๋ฆฌ์ผ์ด์ ์ค์
import tkinter as tk
import ttk
import sv_ttk
root = tk.Tk()
root.title("DiceForge - Dice Rolling Simulator")
root.geometry("900x620")
sv_ttk.set_theme("light")
sv_ttk ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ๋ ๊น๋ํ ๊ธฐ๋ณธ ํ
๋ง๋ฅผ ์ ๊ณตํ๋ฉฐ, ์ดํ์ ์ฌ์ฉ์ ์ ์ ์คํ์ผ๋ก ํ์ฅํฉ๋๋ค.
๐ Tkinter ๋ณ์๋ก ์ ์ญ ์ํ ๊ด๋ฆฌ
Tkinter์ ๋ณ์ ํด๋์ค๋ ์ํ ๊ด๋ฆฌ๋ฅผ ๊ฐ๋จํ๊ฒ ๋ง๋ค์ด ์ค๋๋ค:
dark_mode_var = tk.BooleanVar(value=False)
dice_count_var = tk.IntVar(value=1)
dice_sides_var = tk.IntVar(value=6)
์ด ๋ณ์๋ค์ ์คํ๋ฐ์ค์ ์ฒดํฌ๋ฒํผ ๊ฐ์ ์์ ฏ๊ณผ ์๋์ผ๋ก ๋๊ธฐํ๋ฉ๋๋ค.
๐ ๋ผ์ดํธ & ๋คํฌ ๋ชจ๋ ํ ๊ธ
๋คํฌ ๋ชจ๋๋ ttk ์คํ์ผ๊ณผ ๋ฃจํธ ๋ฐฐ๊ฒฝ์ ๋์ ์ผ๋ก ์ฌ๊ตฌ์ฑํ์ฌ ์ฒ๋ฆฌ๋ฉ๋๋ค:
def toggle_theme():
style.theme_use("clam")
if dark_mode_var.get():
root.configure(bg="#2E2E2E")
style.configure("TLabel", background="#2E2E2E", foreground="white")
else:
root.configure(bg="#FFFFFF")
style.configure("TLabel", background="#FFFFFF", foreground="black")
์ด ์ ๊ทผ ๋ฐฉ์์ ์์ ฏ์ ๋ค์ ๋ง๋ค์ง ์๊ณ ๋ UI์ ์ผ๊ด์ฑ์ ์ ์งํฉ๋๋ค.
๐ ๋ ธํธ๋ถ์ ์ฌ์ฉํ ํญ ๋ ์ด์์
์ฐ๋ฆฌ๋ ttk.Notebook์ ์ฌ์ฉํ์ฌ ๊ด์ฌ์ฌ๋ฅผ ๊น๋ํ๊ฒ ๋ถ๋ฆฌํฉ๋๋ค:
tabs = ttk.Notebook(root)
tabs.pack(expand=True, fill="both", padx=20, pady=20)
dashboard_tab = ttk.Frame(tabs)
roller_tab = ttk.Frame(tabs)
tabs.add(dashboard_tab, text="Dashboard")
tabs.add(roller_tab, text="Dice Roller")
๊ฐ ํญ์ ์์ฒด Frame์ด๋ฉฐ, ๋ ์ด์์์ ๋ชจ๋์์ผ๋ก ๋ง๋ค๊ณ ๋์ค์ ํ์ฅํ๊ธฐ ์ฝ์ต๋๋ค.
โ๏ธ ์ฃผ์ฌ์ ์ค์ ์ ์ด
์ฃผ์ฌ์ ์ค์ ์ Spinbox ์์ ฏ์ ์ฌ์ฉํ๋ฏ๋ก ์ฌ์ฉ์๊ฐ ์๋ชป๋ ์ ํ์ ์
๋ ฅํ ์ ์์ต๋๋ค:
dice_spin = ttk.Spinbox(
settings_card,
from_=1,
to=100,
textvariable=dice_count_var
)
์ด๋ ๊ฒ ํ๋ฉด ๊ฒ์ฆ์ด ๊ฐ๋จํด์ง๊ณ UX๊ฐ ์น์ํด์ง๋๋ค.
๐ฒ ์ฃผ์ฌ์ ๊ตด๋ฆฌ๊ธฐ
์ฑ์ ํต์ฌ ๋ถ๋ถ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
import random
def roll_dice():
count = dice_count_var.get()
sides = dice_sides_var.get()
rolls = [random.randint(1, sides) for _ in range(count)]
total = sum(rolls)
# Display results (example)
result_text = f"{count}d{sides}: " + ", ".join(map(str, rolls))
result_text += f"\nTotal: {total}"
result_widget.configure(state="normal")
result_widget.delete("1.0", tk.END)
result_widget.insert(tk.END, result_text)
result_widget.configure(state="disabled")
๊ฒฐ๊ณผ๋ ์ฝ๊ธฐ ์ ์ฉ Text ์์ ฏ์ ํ์๋๋ฉฐ, ๋ค์์ ๋ณด์ฌ์ค๋๋ค:
- ์ฃผ์ฌ์ ํ๊ธฐ๋ฒ (
XdY) - ๊ฐ๋ณ ๊ตด๋ฆผ ๊ฒฐ๊ณผ
- ์ต์ข ํฉ๊ณ
๋น ๋ฅธ ํ์ธ์ด๋ ๋ฐ๋ณต ํ ์คํธ์ ์ ํฉํฉ๋๋ค.
๐งพ ์ํ ํ์์ค ํผ๋๋ฐฑ
๋ฏธ๋ฌํ์ง๋ง ์ค์ํ UX ์์: ์ํ ํ์์ค.
status_var = tk.StringVar(value="Ready")
status_bar = ttk.Label(root, textvariable=status_var, anchor="w")
status_bar.pack(side="bottom", fill="x")
์ด๊ฒ์ ์ฃผ์ฌ์๋ฅผ ๊ตด๋ฆฌ๊ฑฐ๋ ํ ๋ง๋ฅผ ์ ํํ๋ ๋ฑ์ ๋์์ ์ฆ๊ฐ์ ์ธ ํผ๋๋ฐฑ์ ์ ๊ณตํฉ๋๋ค.
โจ Polished Styling
Custom button styling helps important actions stand out:
style.configure(
"Action.TButton",
font=("Segoe UI", 11, "bold"),
background="#4CAF50"
)
Small details like this make a huge difference in perceived quality.
๐ ์ต์ข ์๊ฐ
DiceForge๋ ์ฌ๋ ค ๊น์ ๋ ์ด์์๊ณผ ์คํ์ผ๋ง์ผ๋ก Tkinter๋ฅผ ์ผ๋ง๋ ๋ฉ๋ฆฌ๊น์ง ํ์ฅํ ์ ์๋์ง ๋ณด์ฌ์ฃผ๋ ์ข์ ์์์ ๋๋ค. ๊ทธ๊ฒ์:
- ์ด๋ณด์ ์นํ์
- ์ฝ๊ฒ ํ์ฅ ๊ฐ๋ฅ (์ด์ /๋ถ์ด์ ๊ตด๋ฆผ, ํ๋ฆฌ์ , ํ์คํ ๋ฆฌ)
- PyInstaller๋ก ํจํค์งํ๊ธฐ ์ํ ๊ฒฌ๊ณ ํ ๊ธฐ๋ฐ
Python GUI ๊ฐ๋ฐ์ ๋ฐฐ์ฐ๊ณ ์๋ค๋ฉด, ์ด ํ๋ก์ ํธ๋ ์ ๊ทผํ๊ธฐ ์ฌ์ฐ๋ฉด์๋ ๋ค๋ฌ์ด์ง ์์ฑ๋๋ฅผ ๊ฐ์ถ ์ข์ ์ง์ ์ ์ ๊ณตํฉ๋๋ค.
ํ๋ณตํ ๊ตด๋ฆผ ๐ฒ