In this tutorial, weโll build a Hangman game with animations, dark mode, and a modern UI using Pythonโs built-in Tkinter library.
No prior game dev experience required โ weโll build everything incrementally.
๐งฐ What Weโll Use
Python 3
Tkinter (GUI framework)
ttk (styled widgets)
sv-ttk (modern light/dark themes)
๐ฆ Step 1: Imports and Dependencies
Letโs start by importing everything we need.
import sys
import os
import random
import tkinter as tk
from tkinter import ttk, messagebox
import sv_ttk
Why these imports?
random โ choose random words
tkinter โ build the GUI
ttk โ nicer looking widgets
messagebox โ pop-up dialogs
sv_ttk โ modern light/dark theme support
๐ง Step 2: Helper Functions
Loading files correctly (even after packaging)
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)
This helper ensures file paths work correctly if you later bundle the app with tools like PyInstaller.
Updating the status bar
def set_status(msg):
status_var.set(msg)
root.update_idletasks()
This updates the status message at the bottom of the app in real time.
๐ช Step 3: Create the Main App Window
root = tk.Tk()
root.title("Animated Hangman Game")
root.geometry("700x850")
sv_ttk.set_theme("light")
Tk() creates the window
geometry() sets its size
sv_ttk.set_theme() applies a modern theme
๐ Step 4: Global Game State
Weโll store our game data here.
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 allows Tkinter widgets to update automatically when values change.
๐ 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)
This dynamically switches background and text colors when the checkbox is toggled.
๐ Step 6: Starting a New Game
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.")
This resets everything:
Picks a new word
Clears guesses
Resets the hangman drawing
๐ค Step 7: Updating the Word Display
def update_display_word():
display = " ".join(
c if c in guessed_letters else "_" for c in current_word
)
display_word_var.set(display)
Example output:
P Y T H O N
Win condition
if "_" not in display:
set_status("Congratulations! You guessed the word!")
messagebox.showinfo("Winner!", f"You correctly guessed: {current_word}")
โ Step 8: Guessing a Letter
def guess_letter():
global wrong_guesses
letter = guess_var.get().strip().upper()
guess_var.set("")
Validate input
if not letter or len(letter) != 1 or not letter.isalpha():
set_status("Please enter a single letter.")
return
Prevent duplicate guesses
if letter in guessed_letters:
set_status(f"You already guessed '{letter}'.")
return
Handle correct vs wrong guesses
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()
๐จ Step 9: Drawing the Hangman
Base structure (always drawn)
def draw_hangman(animated=True):
hangman_canvas.delete("all")
hangman_canvas.create_line(50, 350, 250, 350, width=3)
hangman_canvas.create_line(150, 350, 150, 50, width=3)
hangman_canvas.create_line(150, 50, 300, 50, width=3)
hangman_canvas.create_line(300, 50, 300, 100, width=3)
Body parts (added per mistake)
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
๐ Step 10: Animating the Hangman
def animate_parts(parts, idx=0):
if idx >= len(parts):
return
Animated head
if parts[idx][0] == "oval":
hangman_canvas.create_oval(*parts[idx][1:], width=3)
Animated limbs
else:
x1, y1, x2, y2 = parts[idx][1:]
hangman_canvas.create_line(x1, y1, x2, y2, width=3)
Delay before next part
hangman_canvas.after(400, lambda: animate_parts(parts, idx + 1))
๐ Step 11: UI Layout
Main container
main = ttk.Frame(root, padding=20)
main.pack(expand=True, fill="both")
Canvas
hangman_canvas = tk.Canvas(main, width=400, height=400, bg="white")
hangman_canvas.grid(row=1, column=0, pady=10)
Input & buttons
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)
โถ๏ธ Step 12: Run the App
start_new_game()
root.mainloop()
This starts the game loop and opens the window.
๐ Final Result
You now have:
โ Animated hangman drawing
๐ Light / Dark mode
๐ฏ Input validation
๐ง Game state handling
๐จ Modern UI styling
๐ Next Ideas
Add difficulty levels
Load words from a file
Add keyboard input
Track high scores
Package as an .exe

Top comments (0)