In this tutorial, weโll build AdventureQuest, a GUI-based text adventure game using Python and Tkinter.
Youโll learn how to:
Create a windowed app with Tkinter
Manage game state (player, locations, inventory)
Handle user input and actions
Add combat, treasure, and random events
Apply light/dark themes
No advanced Python required โ just basic functions, dictionaries, and variables.
๐งฑ Step 1: Import Required Modules
We start by importing everything we need.
import sys
import os
import threading
import random
import tkinter as tk
from tkinter import ttk, messagebox
import sv_ttk
Why these imports?
tkinter โ GUI framework
ttk โ modern themed widgets
messagebox โ pop-up dialogs
random โ enemies & treasure chance
threading โ delayed UI updates
sv_ttk โ modern light/dark themes
๐ Step 2: Helper Functions
These small helper functions keep our code clean.
Load bundled resources (for packaging later)
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 helps if you later package your app using PyInstaller.
Update the status bar safely
def set_status(msg):
status_var.set(msg)
root.update_idletasks()
Weโll use this to display messages like combat results or errors.
Delay UI updates slightly
def delayed_update(event=None):
threading.Timer(0.1, update_ui).start()
This prevents the UI from freezing during quick updates.
๐ฅ Step 3: Create the Main Window
Now we create our main application window.
root = tk.Tk()
root.title("AdventureQuest")
root.geometry("900x600")
sv_ttk.set_theme("light")
Tk() initializes the app
geometry() sets the window size
sv_ttk applies a modern theme
๐ Step 4: Global Game State
These variables represent the entire game state.
UI State
dark_mode_var = tk.BooleanVar(value=False)
story_var = tk.StringVar(value="Welcome to AdventureQuest!\nYour journey begins...\n")
action_var = tk.StringVar()
StringVar automatically updates the UI when changed
BooleanVar tracks dark mode
Player Data
player = {
"health": 100,
"gold": 0,
"location": "forest",
"attack": 10,
}
This dictionary stores everything about the player.
Inventory
inventory = []
Simple list = simple inventory system.
๐บ Step 5: Locations & World Data
Each location has:
A description
Valid actions
locations = {
"forest": {
"description": "You are in a dark forest. Paths lead north and east.",
"actions": {"north": "cave", "east": "village"}
},
"cave": {
"description": "A spooky cave. You see a shiny sword.",
"actions": {"south": "forest", "take sword": None}
},
"village": {
"description": "A small village with friendly villagers.",
"actions": {"west": "forest", "shop": None}
},
}
If an action maps to None, itโs a special action, not movement.
๐พ Step 6: Enemies & Treasure Tables
Enemies depend on the playerโs location.
enemies = {
"forest": {
"Goblin": {"health": 30, "attack": 5},
"Wolf": {"health": 25, "attack": 6}
},
"cave": {
"Bat": {"health": 20, "attack": 4},
"Skeleton": {"health": 40, "attack": 8}
},
}
Treasures are also location-based.
treasures = {
"forest": ["gold coin", "healing herb"],
"cave": ["gold coin", "magic potion"],
"village": ["gold coin"]
}
๐ Step 7: Light / Dark Theme Toggle
This function switches between light and dark mode.
def toggle_theme():
style.theme_use("clam")
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)
We also update the text area colors:
text_area.configure(
bg="#1e1e1e" if dark_mode_var.get() else "white",
fg="white" if dark_mode_var.get() else "black",
insertbackground="white" if dark_mode_var.get() else "black"
)
๐ Step 8: Updating the Story UI
Whenever the player moves or acts, we refresh the story text.
def update_ui():
location = player["location"]
loc_info = locations[location]
story_text = f"{loc_info['description']}\n"
story_text += (
f"\nHealth: {player['health']} | "
f"Gold: {player['gold']} | "
f"Inventory: {', '.join(inventory) if inventory else 'Empty'}"
)
story_var.set(story_text)
โ๏ธ Step 9: Random Encounters & Treasure
Enemy encounters (60% chance)
def encounter_enemy():
location = player["location"]
if location in enemies and random.random() < 0.6:
enemy_name = random.choice(list(enemies[location].keys()))
enemy = enemies[location][enemy_name]
set_status(f"โ๏ธ You encounter a {enemy_name}!")
combat(enemy_name, enemy)
Finding treasure (50% chance)
def find_treasure():
location = player["location"]
if location in treasures and random.random() < 0.5:
item = random.choice(treasures[location])
inventory.append(item)
set_status(f"๐ You found a {item}!")
๐ง Step 10: Combat System
Combat loops until someoneโs health reaches zero.
def combat(enemy_name, enemy):
while enemy["health"] > 0 and player["health"] > 0:
damage = player["attack"] + (5 if "sword" in inventory else 0)
enemy["health"] -= damage
set_status(f"You hit {enemy_name} for {damage} damage!")
Enemy strikes back:
player["health"] -= enemy["attack"]
set_status(f"{enemy_name} hits you for {enemy['attack']} damage!")
If the player dies:
if player["health"] <= 0:
messagebox.showinfo("Game Over", "You have died!")
reset_game()
return
๐ฎ Step 11: Processing Player Actions
This function reads player input and reacts.
def process_action():
action = action_var.get().strip().lower()
location = player["location"]
valid_actions = locations[location]["actions"]
Movement:
if action in valid_actions and valid_actions[action]:
player["location"] = valid_actions[action]
encounter_enemy()
find_treasure()
Special actions:
elif action == "take sword":
inventory.append("sword")
โ Step 12: Help Window
A pop-up window explaining the rules.
def show_help():
win = tk.Toplevel(root)
win.title("AdventureQuest - Help")
Uses a read-only Text widget for formatted instructions.
๐งฉ Step 13: UI Layout
Title & Story Area
ttk.Label(main, text="AdventureQuest", font=("Segoe UI", 22, "bold")).pack()
Story display:
text_area = tk.Text(
text_frame,
wrap="word",
state="disabled"
)
Action Input
action_entry = ttk.Entry(action_frame, textvariable=action_var)
action_entry.bind("<Return>", lambda e: process_action())
Buttons:
ttk.Button(action_frame, text="Act", command=process_action)
ttk.Button(action_frame, text="โ Help", command=show_help)
๐ Step 14: Start the Game Loop
update_ui()
root.mainloop()
This launches the application and keeps it running.
๐ Final Thoughts
You now have:
A working GUI game
A clean separation of logic and UI
A foundation for saving, leveling, sound effects, or maps
Ideas to Extend:
Add XP & leveling
Add save/load system
Add images or sound effects
Turn it into a roguelike
Top comments (0)