๐ฎ Python (Tkinter)์ผ๋ก ์ ๋๋ฉ์ด์ ํ๋งจ ๊ฒ์ ๋ง๋ค๊ธฐ โ ๋จ๊ณ๋ณ
Source: Dev.to
๐งฐ ์ฐ๋ฆฌ๊ฐ ์ฌ์ฉํ ๊ฒ
- Python 3
- Tkinter (GUI ํ๋ ์์ํฌ)
- ttk (์คํ์ผ์ด ์ ์ฉ๋ ์์ ฏ)
- svโttk (ํ๋์ ์ธ ๋ผ์ดํธ/๋คํฌ ํ ๋ง)
๐ฆ 1๋จ๊ณ โ ๊ฐ์ ธ์ค๊ธฐ ๋ฐ ์์กด์ฑ
import sys
import os
import random
import tkinter as tk
from tkinter import ttk, messagebox
import sv_ttk
์ ์ด๋ฌํ ๊ฐ์ ธ์ค๊ธฐ๋ฅผ ์ฌ์ฉํ๋์?
| ๋ชจ๋ | ๋ชฉ์ |
|---|---|
random | ๋ฌด์์ ๋จ์ด ์ ํ |
tkinter | GUI ๊ตฌ์ถ |
ttk | ๋ณด๋ค ๊น๋ํ ์์ ฏ |
messagebox | ํ์ ๋ํ์์ |
sv_ttk | ํ๋์ ์ธ ๋ผ์ดํธ/๋คํฌ ํ ๋ง ์ง์ |
๐ง Step 2 โ ํฌํผ ํจ์
ํ์ผ์ ์ฌ๋ฐ๋ฅด๊ฒ ๋ก๋ํ๊ธฐ (ํจํค์ง ํ์๋)
def resource_path(file_name):
base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
return os.path.join(base_path, file_name)
์ด ํฌํผ๋ ๋์ค์ PyInstaller์ ๊ฐ์ ๋๊ตฌ๋ก ์ฑ์ ๋ฒ๋ค๋งํ๋๋ผ๋ ํ์ผ ๊ฒฝ๋ก๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์๋ํ๋๋ก ๋ณด์ฅํฉ๋๋ค.
์ํ ํ์์ค ์ ๋ฐ์ดํธ
def set_status(msg):
status_var.set(msg)
root.update_idletasks()
์ฑ ํ๋จ์ ์ค์๊ฐ์ผ๋ก ์ํ ๋ฉ์์ง๋ฅผ ์ ๋ฐ์ดํธํฉ๋๋ค.
๐ช Step 3 โ ๋ฉ์ธ ์ฑ ์ฐฝ ๋ง๋ค๊ธฐ
root = tk.Tk()
root.title("Animated Hangman Game")
root.geometry("700x850")
sv_ttk.set_theme("light")
Tk()๋ ์ฐฝ์ ์์ฑํฉ๋๋ค.geometry()๋ ์ฐฝ์ ํฌ๊ธฐ๋ฅผ ์ค์ ํฉ๋๋ค.sv_ttk.set_theme()๋ ํ๋์ ์ธ ํ ๋ง๋ฅผ ์ ์ฉํฉ๋๋ค.
๐ Step 4 โ Global Game State
dark_mode_var = tk.BooleanVar(value=False)
word_list = ["PYTHON", "HANGMAN", "COMPUTER", "AI", "PROGRAMMING", "DEVELOPER"]
current_word = ""
guessed_letters = set()
wrong_guesses = 0
max_wrong = 6
UIโbound variables
display_word_var = tk.StringVar()
status_var = tk.StringVar(value="Ready")
lives_var = tk.StringVar(value=f"Lives: {max_wrong}")
StringVar๋ ๊ฐ์ด ๋ณ๊ฒฝ๋ ๋ Tkinter ์์ ฏ์ด ์๋์ผ๋ก ์ ๋ฐ์ดํธ๋๋๋ก ํฉ๋๋ค.
๐ Step 5 โ DarkโMode Toggle
def toggle_theme():
bg = "#2E2E2E" if dark_mode_var.get() else "#FFFFFF"
fg = "white" if dark_mode_var.get() else "black"
root.configure(bg=bg)
for w in ["TFrame", "TLabel", "TLabelframe", "TLabelframe.Label", "TCheckbutton"]:
style.configure(w, background=bg, foreground=fg)
guess_entry.configure(background=bg, foreground=fg)
hangman_canvas.configure(bg=bg)
์ฒดํฌ๋ฐ์ค๋ฅผ ํ ๊ธํ ๋ ๋ฐฐ๊ฒฝ ๋ฐ ํ ์คํธ ์์์ด ๋์ ์ผ๋ก ์ ํ๋ฉ๋๋ค.
๐ 6๋จ๊ณ โ ์๋ก์ด ๊ฒ์ ์์
def start_new_game():
global current_word, guessed_letters, wrong_guesses
current_word = random.choice(word_list)
guessed_letters = set()
wrong_guesses = 0
update_display_word()
lives_var.set(f"Lives: {max_wrong}")
draw_hangman(animated=False)
set_status("New game started! Guess a letter.")
๋ชจ๋ ๊ฒ์ ์ด๊ธฐํํฉ๋๋ค: ์๋ก์ด ๋จ์ด๋ฅผ ์ ํํ๊ณ , ์ถ์ธก์ ์ด๊ธฐํํ๋ฉฐ, ๊ต์ํ ๊ทธ๋ฆผ์ ๋ฆฌ์ ํฉ๋๋ค.
๐ค 7๋จ๊ณ โ ๋จ์ด ํ์ ์ ๋ฐ์ดํธ
def update_display_word():
display = " ".join(
c if c in guessed_letters else "_" for c in current_word
)
display_word_var.set(display)
# Win condition
if "_" not in display:
set_status("Congratulations! You guessed the word!")
messagebox.showinfo("Winner!", f"You correctly guessed: {current_word}")
์์ ์ถ๋ ฅ:
P Y T H O N
Source:
โ 8๋จ๊ณ โ ๋ฌธ์ ์ถ์ธก
def guess_letter():
global wrong_guesses
letter = guess_var.get().strip().upper()
guess_var.set("")
์ ๋ ฅ ๊ฒ์ฆ
if not letter or len(letter) != 1 or not letter.isalpha():
set_status("Please enter a single letter.")
return
์ค๋ณต ์ถ์ธก ๋ฐฉ์ง
if letter in guessed_letters:
set_status(f"You already guessed '{letter}'.")
return
์ ๋ต๊ณผ ์ค๋ต ์ฒ๋ฆฌ
guessed_letters.add(letter)
if letter not in current_word:
wrong_guesses += 1
lives_var.set(f"Lives: {max_wrong - wrong_guesses}")
draw_hangman(animated=True)
# Gameโover logic
if wrong_guesses >= max_wrong:
messagebox.showinfo("Game Over", f"The word was: {current_word}")
start_new_game()
return
update_display_word()
๐จ 9๋จ๊ณ โ ๊ต์๋ ๊ทธ๋ฆฌ๊ธฐ
๊ธฐ๋ณธ ๊ตฌ์กฐ (ํญ์ ๊ทธ๋ ค์ง)
def draw_hangman(animated=True):
hangman_canvas.delete("all")
# Scaffold
hangman_canvas.create_line(50, 350, 250, 350, width=3) # Ground
hangman_canvas.create_line(150, 350, 150, 50, width=3) # Pole
hangman_canvas.create_line(150, 50, 300, 50, width=3) # Top beam
hangman_canvas.create_line(300, 50, 300, 100, width=3) # Rope
์ ์ฒด ๋ถ์ (์ค์๋ง๋ค ์ถ๊ฐ)
parts = []
if wrong_guesses >= 1:
parts.append(("oval", 275, 100, 325, 150)) # Head
if wrong_guesses >= 2:
parts.append(("line", 300, 150, 300, 250)) # Body
if wrong_guesses >= 3:
parts.append(("line", 300, 180, 260, 220)) # Left arm
if wrong_guesses >= 4:
parts.append(("line", 300, 180, 340, 220)) # Right arm
if wrong_guesses >= 5:
parts.append(("line", 300, 250, 260, 300)) # Left leg
if wrong_guesses >= 6:
parts.append(("line", 300, 250, 340, 300)) # Right leg
๋ ๋๋ง (์ ํ์ ์ ๋๋ฉ์ด์ ํฌํจ)
for i, (shape, *coords) in enumerate(parts):
if animated:
# Simple animation: draw each part after a short delay
root.after(i * 200, lambda s=shape, c=coords: draw_part(s, *c))
else:
draw_part(shape, *coords)
def draw_part(shape, *coords):
if shape == "oval":
hangman_canvas.create_oval(*coords, width=3)
elif shape == "line":
hangman_canvas.create_line(*coords, width=3)
๐ฌ Step 10 โ ๋ชจ๋ ๊ฒ์ ์ฐ๊ฒฐํ๊ธฐ (UI ๋ ์ด์์)
style = ttk.Style()
# Top frame โ title & theme toggle
top_frame = ttk.Frame(root, padding=10)
top_frame.pack(fill="x")
title_lbl = ttk.Label(top_frame, text="Hangman", font=("Helvetica", 24))
title_lbl.pack(side="left")
theme_chk = ttk.Checkbutton(
top_frame,
text="Dark mode",
variable=dark_mode_var,
command=toggle_theme
)
theme_chk.pack(side="right")
# Word display
word_frame = ttk.Frame(root, padding=10)
word_frame.pack(fill="x")
word_lbl = ttk.Label(word_frame, textvariable=display_word_var, font=("Consolas", 32))
word_lbl.pack()
# Canvas for the drawing
hangman_canvas = tk.Canvas(root, width=400, height=400, bg="white", highlightthickness=0)
hangman_canvas.pack(pady=10)
# Guess entry & button
guess_frame = ttk.Frame(root, padding=10)
guess_frame.pack(fill="x")
guess_var = tk.StringVar()
guess_entry = ttk.Entry(guess_frame, textvariable=guess_var, width=5, font=("Helvetica", 18))
guess_entry.pack(side="left", padx=(0, 5))
guess_btn = ttk.Button(guess_frame, text="Guess", command=guess_letter)
guess_btn.pack(side="left")
# Lives & status bar
status_frame = ttk.Frame(root, padding=5)
status_frame.pack(fill="x", side="bottom")
lives_lbl = ttk.Label(status_frame, textvariable=lives_var)
lives_lbl.pack(side="left")
status_lbl = ttk.Label(status_frame, textvariable=status_var, anchor="e")
status_lbl.pack(side="right", fill="x", expand=True)
# Start the first game
start_new_game()
root.mainloop()
๐ ์๋ฃ๋์์ต๋๋ค!
์คํฌ๋ฆฝํธ๋ฅผ ์คํํ๊ณ , ๋คํฌ ๋ชจ๋๋ฅผ ์ ํํ๋ฉฐ, ํ์ค Python ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ง์ผ๋ก ๋ง๋ ์ ๋๋ฉ์ด์ ํ๋งจ์ ์ฆ๊ฒจ๋ณด์ธ์. ์์ ๋กญ๊ฒ ๋ค์๊ณผ ๊ฐ์ด ํ์ฅํ ์ ์์ต๋๋ค:
- ๋ ํฐ ๋จ์ด ๋ชฉ๋ก (ํ์ผ์์ ๋ก๋).
winsound๋๋pygame์ ์ฌ์ฉํ ์ฌ์ด๋ ํจ๊ณผ.- ํ์ด ์ค์ฝ์ด ํธ๋์ปค.
์ฝ๋ฉ ์ฆ๊ฒ๊ฒ!
์ ๋๋ฉ์ด์ ํ๋งจ โ ์ฝ๋ walkthrough
animate_parts โ ํ๋งจ์ ๋จ๊ณ๋ณ๋ก ๊ทธ๋ฆฌ๊ธฐ
def animate_parts(parts, idx=0):
"""Recursively draw each part of the hangman with a short delay."""
if idx >= len(parts):
return
# Draw an oval (the head)
if parts[idx][0] == "oval":
hangman_canvas.create_oval(*parts[idx][1:], width=3)
# Draw a line (any limb)
else:
x1, y1, x2, y2 = parts[idx][1:]
hangman_canvas.create_line(x1, y1, x2, y2, width=3)
# Wait a bit, then draw the next part
hangman_canvas.after(400, lambda: animate_parts(parts, idx + 1))
Source:
๐ Stepโฏ11: UI ๋ ์ด์์
๋ฉ์ธ ์ปจํ ์ด๋
main = ttk.Frame(root, padding=20)
main.pack(expand=True, fill="both")
์บ๋ฒ์ค
hangman_canvas = tk.Canvas(main, width=400, height=400, bg="white")
hangman_canvas.grid(row=1, column=0, pady=10)
์ ๋ ฅ ๋ฐ ๋ฒํผ
guess_var = tk.StringVar()
guess_entry = ttk.Entry(
main,
textvariable=guess_var,
font=("Segoe UI", 16),
width=5,
)
guess_entry.grid(row=4, column=0)
ttk.Button(main, text="Guess", command=guess_letter).grid(row=5, column=0)
โถ๏ธ ๋จ๊ณโฏ12: ์ฑ ์คํ
start_new_game()
root.mainloop()
์ด๊ฒ์ ๊ฒ์ ๋ฃจํ๋ฅผ ์์ํ๊ณ ์ฐฝ์ ์ฝ๋๋ค.
๐ Final Result
You now have:
- โ Animated hangman drawing โ โ ์ ๋๋ฉ์ด์ ํ๋งจ ๊ทธ๋ฆผ
- ๐ Light / Dark mode โ ๐ ๋ผ์ดํธ / ๋คํฌ ๋ชจ๋
- ๐ฏ Input validation โ ๐ฏ ์ ๋ ฅ ๊ฒ์ฆ
- ๐ง Gameโstate handling โ ๐ง ๊ฒ์ ์ํ ์ฒ๋ฆฌ
- ๐จ Modern UI styling โ ๐จ ํ๋์ ์ธ UI ์คํ์ผ๋ง
๐ ๋ค์ ์์ด๋์ด
- ๋์ด๋ ๋ ๋ฒจ ์ถ๊ฐ
- ํ์ผ์์ ๋จ์ด ๋ก๋
- ํค๋ณด๋ ์ ๋ ฅ ์ถ๊ฐ
- ์ต๊ณ ์ ์ ์ถ์
.exe๋ก ํจํค์ง
๋ฏธ๋ฆฌ๋ณด๊ธฐ
๋ชจ๋ ์ฝ๋ ์ค๋ํซ์ ํ๋ก์ ํธ์ ๋ฐ๋ก ๋ณต์ฌํ ์ ์์ต๋๋ค.
