|
| 1 | +import sys |
| 2 | +import os |
| 3 | +import threading |
| 4 | +import random |
| 5 | +import tkinter as tk |
| 6 | +from tkinter import ttk, messagebox |
| 7 | +import sv_ttk |
| 8 | + |
| 9 | +# ========================= |
| 10 | +# Helpers |
| 11 | +# ========================= |
| 12 | +def resource_path(file_name): |
| 13 | + base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__))) |
| 14 | + return os.path.join(base_path, file_name) |
| 15 | + |
| 16 | +def set_status(msg): |
| 17 | + status_var.set(msg) |
| 18 | + root.update_idletasks() |
| 19 | + |
| 20 | +def delayed_update(event=None): |
| 21 | + threading.Timer(0.1, update_ui).start() |
| 22 | + |
| 23 | +# ========================= |
| 24 | +# App Setup |
| 25 | +# ========================= |
| 26 | +root = tk.Tk() |
| 27 | +root.title("AdventureQuest") |
| 28 | +root.geometry("900x600") |
| 29 | +sv_ttk.set_theme("light") |
| 30 | + |
| 31 | +# ========================= |
| 32 | +# Globals |
| 33 | +# ========================= |
| 34 | +dark_mode_var = tk.BooleanVar(value=False) |
| 35 | + |
| 36 | +story_var = tk.StringVar(value="Welcome to AdventureQuest!\nYour journey begins...\n") |
| 37 | +action_var = tk.StringVar() |
| 38 | + |
| 39 | +player = { |
| 40 | + "health": 100, |
| 41 | + "gold": 0, |
| 42 | + "location": "forest", |
| 43 | + "attack": 10, |
| 44 | +} |
| 45 | + |
| 46 | +inventory = [] |
| 47 | + |
| 48 | +locations = { |
| 49 | + "forest": { |
| 50 | + "description": "You are in a dark forest. Paths lead north and east.", |
| 51 | + "actions": {"north": "cave", "east": "village"} |
| 52 | + }, |
| 53 | + "cave": { |
| 54 | + "description": "A spooky cave. You see a shiny sword.", |
| 55 | + "actions": {"south": "forest", "take sword": None} |
| 56 | + }, |
| 57 | + "village": { |
| 58 | + "description": "A small village with friendly villagers.", |
| 59 | + "actions": {"west": "forest", "shop": None} |
| 60 | + }, |
| 61 | +} |
| 62 | + |
| 63 | +enemies = { |
| 64 | + "forest": {"Goblin": {"health": 30, "attack": 5}, "Wolf": {"health": 25, "attack": 6}}, |
| 65 | + "cave": {"Bat": {"health": 20, "attack": 4}, "Skeleton": {"health": 40, "attack": 8}}, |
| 66 | +} |
| 67 | + |
| 68 | +treasures = { |
| 69 | + "forest": ["gold coin", "healing herb"], |
| 70 | + "cave": ["gold coin", "magic potion"], |
| 71 | + "village": ["gold coin"] |
| 72 | +} |
| 73 | + |
| 74 | +# ========================= |
| 75 | +# Theme Toggle |
| 76 | +# ========================= |
| 77 | +def toggle_theme(): |
| 78 | + style.theme_use("clam") |
| 79 | + bg = "#2E2E2E" if dark_mode_var.get() else "#FFFFFF" |
| 80 | + fg = "white" if dark_mode_var.get() else "black" |
| 81 | + |
| 82 | + root.configure(bg=bg) |
| 83 | + for w in ["TFrame", "TLabel", "TLabelframe", "TLabelframe.Label", "TCheckbutton"]: |
| 84 | + style.configure(w, background=bg, foreground=fg) |
| 85 | + |
| 86 | + text_area.configure( |
| 87 | + bg="#1e1e1e" if dark_mode_var.get() else "white", |
| 88 | + fg="white" if dark_mode_var.get() else "black", |
| 89 | + insertbackground="white" if dark_mode_var.get() else "black" |
| 90 | + ) |
| 91 | + |
| 92 | + set_status(f"Theme switched to {'Dark' if dark_mode_var.get() else 'Light'} mode") |
| 93 | + |
| 94 | +# ========================= |
| 95 | +# Game Logic |
| 96 | +# ========================= |
| 97 | +def update_ui(): |
| 98 | + location = player["location"] |
| 99 | + loc_info = locations[location] |
| 100 | + story_text = f"{loc_info['description']}\n" |
| 101 | + story_text += f"\nHealth: {player['health']} | Gold: {player['gold']} | Inventory: {', '.join(inventory) if inventory else 'Empty'}" |
| 102 | + story_var.set(story_text) |
| 103 | + |
| 104 | +def encounter_enemy(): |
| 105 | + location = player["location"] |
| 106 | + if location in enemies and random.random() < 0.6: # 60% chance |
| 107 | + enemy_name = random.choice(list(enemies[location].keys())) |
| 108 | + enemy = enemies[location][enemy_name] |
| 109 | + set_status(f"⚔️ You encounter a {enemy_name}!") |
| 110 | + combat(enemy_name, enemy) |
| 111 | + |
| 112 | +def find_treasure(): |
| 113 | + location = player["location"] |
| 114 | + if location in treasures and random.random() < 0.5: # 50% chance |
| 115 | + item = random.choice(treasures[location]) |
| 116 | + inventory.append(item) |
| 117 | + set_status(f"💎 You found a {item}!") |
| 118 | + |
| 119 | +def combat(enemy_name, enemy): |
| 120 | + while enemy["health"] > 0 and player["health"] > 0: |
| 121 | + damage = player["attack"] + (5 if "sword" in inventory else 0) |
| 122 | + enemy["health"] -= damage |
| 123 | + set_status(f"You hit {enemy_name} for {damage} damage!") |
| 124 | + root.update() |
| 125 | + root.after(500) |
| 126 | + |
| 127 | + if enemy["health"] <= 0: |
| 128 | + gold_found = random.randint(5, 20) |
| 129 | + player["gold"] += gold_found |
| 130 | + set_status(f"🎉 You defeated {enemy_name} and gained {gold_found} gold!") |
| 131 | + root.update() |
| 132 | + root.after(500) |
| 133 | + return |
| 134 | + |
| 135 | + # Enemy attacks |
| 136 | + player["health"] -= enemy["attack"] |
| 137 | + set_status(f"{enemy_name} hits you for {enemy['attack']} damage!") |
| 138 | + root.update() |
| 139 | + root.after(500) |
| 140 | + |
| 141 | + if player["health"] <= 0: |
| 142 | + set_status("💀 You were defeated! Game over.") |
| 143 | + messagebox.showinfo("Game Over", "You have died! Restarting game...") |
| 144 | + reset_game() |
| 145 | + return |
| 146 | + |
| 147 | +def reset_game(): |
| 148 | + player["health"] = 100 |
| 149 | + player["gold"] = 0 |
| 150 | + player["location"] = "forest" |
| 151 | + inventory.clear() |
| 152 | + update_ui() |
| 153 | + |
| 154 | +def process_action(): |
| 155 | + action = action_var.get().strip().lower() |
| 156 | + location = player["location"] |
| 157 | + loc_info = locations[location] |
| 158 | + valid_actions = loc_info["actions"] |
| 159 | + |
| 160 | + if action in valid_actions: |
| 161 | + # Move to another location |
| 162 | + if valid_actions[action]: |
| 163 | + player["location"] = valid_actions[action] |
| 164 | + set_status(f"You move to {valid_actions[action].capitalize()}.") |
| 165 | + encounter_enemy() |
| 166 | + find_treasure() |
| 167 | + else: |
| 168 | + # Special actions |
| 169 | + if action == "take sword" and "sword" not in inventory: |
| 170 | + inventory.append("sword") |
| 171 | + set_status("🗡 You picked up the sword!") |
| 172 | + elif action == "shop": |
| 173 | + if player["gold"] >= 10: |
| 174 | + player["gold"] -= 10 |
| 175 | + inventory.append("potion") |
| 176 | + set_status("💊 You bought a healing potion!") |
| 177 | + else: |
| 178 | + set_status("Not enough gold!") |
| 179 | + else: |
| 180 | + set_status("❌ Invalid action!") |
| 181 | + |
| 182 | + action_var.set("") |
| 183 | + update_ui() |
| 184 | + |
| 185 | +# ========================= |
| 186 | +# Help Window |
| 187 | +# ========================= |
| 188 | +def show_help(): |
| 189 | + win = tk.Toplevel(root) |
| 190 | + win.title("AdventureQuest - Help") |
| 191 | + win.geometry("480x360") |
| 192 | + win.configure(bg="#2e2e2e") |
| 193 | + win.resizable(False, False) |
| 194 | + win.transient(root) |
| 195 | + win.grab_set() |
| 196 | + |
| 197 | + frame = tk.Frame(win, bg="#2e2e2e") |
| 198 | + frame.pack(fill="both", expand=True, padx=12, pady=12) |
| 199 | + |
| 200 | + text = tk.Text( |
| 201 | + frame, |
| 202 | + bg="#2e2e2e", |
| 203 | + fg="#f0f0f0", |
| 204 | + font=("Segoe UI", 11), |
| 205 | + wrap="word", |
| 206 | + borderwidth=0 |
| 207 | + ) |
| 208 | + text.pack(fill="both", expand=True) |
| 209 | + |
| 210 | + help_text = """🗺 AdventureQuest — Quick Help |
| 211 | +
|
| 212 | +• Read the story in the main window. |
| 213 | +• Type actions (commands) in the input box: |
| 214 | + - Directions: north, south, east, west |
| 215 | + - Special: take sword, shop, etc. |
| 216 | +• Combat occurs randomly when exploring. |
| 217 | +• You can find treasures randomly. |
| 218 | +• Press 'Enter' or click 'Act' to perform action. |
| 219 | +• Keep track of health, gold, and inventory. |
| 220 | +
|
| 221 | +Your adventure awaits!""" |
| 222 | + text.insert("1.0", help_text) |
| 223 | + text.config(state="disabled") |
| 224 | + |
| 225 | +# ========================= |
| 226 | +# Styles |
| 227 | +# ========================= |
| 228 | +style = ttk.Style() |
| 229 | +style.theme_use("clam") |
| 230 | +style.configure( |
| 231 | + "Action.TButton", |
| 232 | + font=("Segoe UI", 11, "bold"), |
| 233 | + foreground="white", |
| 234 | + background="#4CAF50", |
| 235 | + padding=8 |
| 236 | +) |
| 237 | +style.map("Action.TButton", background=[("active", "#45a049")]) |
| 238 | + |
| 239 | +# ========================= |
| 240 | +# Status Bar |
| 241 | +# ========================= |
| 242 | +status_var = tk.StringVar(value="Ready") |
| 243 | +ttk.Label(root, textvariable=status_var, anchor="w").pack(side=tk.BOTTOM, fill="x") |
| 244 | + |
| 245 | +# ========================= |
| 246 | +# Main UI |
| 247 | +# ========================= |
| 248 | +main = ttk.Frame(root, padding=20) |
| 249 | +main.pack(expand=True, fill="both") |
| 250 | + |
| 251 | +ttk.Label(main, text="AdventureQuest", font=("Segoe UI", 22, "bold")).pack() |
| 252 | +ttk.Label(main, text="A text-based adventure game", font=("Segoe UI", 11)).pack(pady=(0, 10)) |
| 253 | + |
| 254 | +# ========================= |
| 255 | +# Text Area |
| 256 | +# ========================= |
| 257 | +text_frame = ttk.LabelFrame(main, text="Story", padding=10) |
| 258 | +text_frame.pack(fill="both", pady=10, expand=True) |
| 259 | + |
| 260 | +text_area = tk.Text( |
| 261 | + text_frame, |
| 262 | + font=("Segoe UI", 11), |
| 263 | + wrap="word", |
| 264 | + height=15, |
| 265 | + state="disabled" |
| 266 | +) |
| 267 | +text_area.pack(fill="both", expand=True) |
| 268 | + |
| 269 | +def refresh_story(*args): |
| 270 | + text_area.config(state="normal") |
| 271 | + text_area.delete("1.0", tk.END) |
| 272 | + text_area.insert("1.0", story_var.get()) |
| 273 | + text_area.config(state="disabled") |
| 274 | + |
| 275 | +story_var.trace_add("write", refresh_story) |
| 276 | + |
| 277 | +# ========================= |
| 278 | +# Action Input |
| 279 | +# ========================= |
| 280 | +action_frame = ttk.Frame(main) |
| 281 | +action_frame.pack(fill="x", pady=8) |
| 282 | + |
| 283 | +ttk.Label(action_frame, text="Your Action:", font=("Segoe UI", 10, "bold")).pack(side="left", padx=(0, 6)) |
| 284 | + |
| 285 | +action_entry = ttk.Entry(action_frame, textvariable=action_var, width=30) |
| 286 | +action_entry.pack(side="left", padx=(0, 6)) |
| 287 | +action_entry.bind("<Return>", lambda e: process_action()) |
| 288 | + |
| 289 | +ttk.Button(action_frame, text="Act", command=process_action, style="Action.TButton").pack(side="left", padx=6) |
| 290 | +ttk.Button(action_frame, text="❓ Help", command=show_help, style="Action.TButton").pack(side="left", padx=6) |
| 291 | + |
| 292 | +ttk.Checkbutton( |
| 293 | + action_frame, |
| 294 | + text="Dark Mode", |
| 295 | + variable=dark_mode_var, |
| 296 | + command=toggle_theme |
| 297 | +).pack(side="right", padx=14) |
| 298 | + |
| 299 | +# ========================= |
| 300 | +# Start Game |
| 301 | +# ========================= |
| 302 | +update_ui() |
| 303 | +root.mainloop() |
0 commit comments