#!/usr/bin/env python3 """ Card Game GUI Client - Simple graphical testing tool simulating mobile interface. """ import tkinter as tk from tkinter import ttk, messagebox, scrolledtext import requests import json import uuid import threading BASE_URL = "http://localhost:8000" class CardGameGUI: def __init__(self, root): self.root = root self.root.title("Card Game - Test Client") self.root.geometry("400x700") self.root.configure(bg="#1a1a2e") self.token = None self.session = requests.Session() # Style self.style = ttk.Style() self.style.theme_use('clam') self.style.configure("TButton", padding=10, font=('Helvetica', 10)) self.style.configure("TLabel", background="#1a1a2e", foreground="white", font=('Helvetica', 10)) self.style.configure("TEntry", padding=5) self.style.configure("Header.TLabel", font=('Helvetica', 16, 'bold'), foreground="#e94560") self._create_widgets() def _create_widgets(self): # Header header = ttk.Label(self.root, text="⚔️ Card Game", style="Header.TLabel") header.pack(pady=20) # Notebook (Tabs) self.notebook = ttk.Notebook(self.root) self.notebook.pack(fill='both', expand=True, padx=10, pady=10) # Auth Tab self.auth_frame = ttk.Frame(self.notebook) self.notebook.add(self.auth_frame, text="🔐 Auth") self._create_auth_tab() # Profile Tab self.profile_frame = ttk.Frame(self.notebook) self.notebook.add(self.profile_frame, text="👤 Profile") self._create_profile_tab() # Chests Tab self.chests_frame = ttk.Frame(self.notebook) self.notebook.add(self.chests_frame, text="📦 Chests") self._create_chests_tab() # Collection Tab self.collection_frame = ttk.Frame(self.notebook) self.notebook.add(self.collection_frame, text="🃏 Collection") self._create_collection_tab() # Status bar self.status_var = tk.StringVar(value="Not logged in") status = ttk.Label(self.root, textvariable=self.status_var) status.pack(pady=5) def _create_auth_tab(self): frame = self.auth_frame # Login Section ttk.Label(frame, text="Email:").pack(pady=(20, 5)) self.email_entry = ttk.Entry(frame, width=30) self.email_entry.pack() self.email_entry.insert(0, "test@example.com") ttk.Label(frame, text="Password:").pack(pady=(10, 5)) self.password_entry = ttk.Entry(frame, width=30, show="*") self.password_entry.pack() self.password_entry.insert(0, "password123") ttk.Label(frame, text="Nickname (for register):").pack(pady=(10, 5)) self.nickname_entry = ttk.Entry(frame, width=30) self.nickname_entry.pack() self.nickname_entry.insert(0, "player1") btn_frame = ttk.Frame(frame) btn_frame.pack(pady=20) ttk.Button(btn_frame, text="Login", command=self._login).pack(side='left', padx=5) ttk.Button(btn_frame, text="Register", command=self._register).pack(side='left', padx=5) def _create_profile_tab(self): frame = self.profile_frame ttk.Button(frame, text="🔄 Refresh Profile", command=self._refresh_profile).pack(pady=20) self.profile_text = scrolledtext.ScrolledText(frame, width=40, height=20) self.profile_text.pack(padx=10, pady=10) def _create_chests_tab(self): frame = self.chests_frame ttk.Button(frame, text="🔄 Load Chests", command=self._load_chests).pack(pady=10) self.chests_listbox = tk.Listbox(frame, width=40, height=8) self.chests_listbox.pack(padx=10, pady=10) ttk.Button(frame, text="📦 Open Selected Chest", command=self._open_chest).pack(pady=10) ttk.Label(frame, text="Result:").pack() self.chest_result = scrolledtext.ScrolledText(frame, width=40, height=10) self.chest_result.pack(padx=10, pady=10) def _create_collection_tab(self): frame = self.collection_frame ttk.Button(frame, text="🔄 Refresh Collection", command=self._refresh_collection).pack(pady=10) self.collection_text = scrolledtext.ScrolledText(frame, width=40, height=25) self.collection_text.pack(padx=10, pady=10) def _headers(self): if self.token: return {"Authorization": f"Bearer {self.token}"} return {} def _run_async(self, func): """Run a function in a background thread.""" thread = threading.Thread(target=func) thread.daemon = True thread.start() def _login(self): def do_login(): try: resp = self.session.post(f"{BASE_URL}/auth/login", json={ "email": self.email_entry.get(), "password": self.password_entry.get() }) if resp.status_code == 200: self.token = resp.json()["access_token"] self.status_var.set(f"✓ Logged in as {self.email_entry.get()}") self._refresh_profile() else: messagebox.showerror("Login Failed", resp.json().get("detail", "Unknown error")) except Exception as e: messagebox.showerror("Error", str(e)) self._run_async(do_login) def _register(self): def do_register(): try: resp = self.session.post(f"{BASE_URL}/auth/register", json={ "email": self.email_entry.get(), "password": self.password_entry.get(), "nickname": self.nickname_entry.get() }) if resp.status_code == 200: self.token = resp.json().get("access_token") self.status_var.set(f"✓ Registered & logged in") self._refresh_profile() else: messagebox.showerror("Register Failed", resp.json().get("detail", "Unknown error")) except Exception as e: messagebox.showerror("Error", str(e)) self._run_async(do_register) def _refresh_profile(self): def do_refresh(): try: profile_resp = self.session.get(f"{BASE_URL}/me/profile", headers=self._headers()) wallet_resp = self.session.get(f"{BASE_URL}/me/wallet", headers=self._headers()) text = "=== PROFILE ===\n" if profile_resp.status_code == 200: text += json.dumps(profile_resp.json(), indent=2) + "\n\n" else: text += f"Error: {profile_resp.status_code}\n\n" text += "=== WALLET ===\n" if wallet_resp.status_code == 200: wallet = wallet_resp.json() text += f"💰 Gold: {wallet.get('balance', 'N/A')}\n" else: text += f"Error: {wallet_resp.status_code}\n" self.profile_text.delete('1.0', tk.END) self.profile_text.insert(tk.END, text) except Exception as e: self.profile_text.delete('1.0', tk.END) self.profile_text.insert(tk.END, f"Error: {e}") self._run_async(do_refresh) def _load_chests(self): def do_load(): try: resp = self.session.get(f"{BASE_URL}/catalog/chests") self.chests_listbox.delete(0, tk.END) if resp.status_code == 200: self.chests_data = resp.json() for chest in self.chests_data: self.chests_listbox.insert(tk.END, f"{chest['name']} - {chest['cost_gold']}g") else: self.chests_listbox.insert(tk.END, "Failed to load chests") except Exception as e: self.chests_listbox.insert(tk.END, f"Error: {e}") self._run_async(do_load) def _open_chest(self): selection = self.chests_listbox.curselection() if not selection: messagebox.showwarning("Select Chest", "Please select a chest first") return idx = selection[0] chest = self.chests_data[idx] def do_open(): try: headers = self._headers() headers["Idempotency-Key"] = str(uuid.uuid4()) resp = self.session.post(f"{BASE_URL}/chests/{chest['id']}/open", headers=headers) self.chest_result.delete('1.0', tk.END) if resp.status_code == 200: result = resp.json() text = f"✨ Chest Opened!\n" text += f"💰 Spent: {result['spent_gold']}g\n\n" text += "Cards:\n" for item in result['items']: emoji = "🆕" if item['outcome'] == "NEW" else "🔄" text += f"{emoji} {item['card_name']} ({item['rarity']})" if item['converted_gold']: text += f" → +{item['converted_gold']}g" text += "\n" self.chest_result.insert(tk.END, text) self._refresh_profile() # Update wallet else: self.chest_result.insert(tk.END, f"Error: {resp.json().get('detail', 'Unknown')}") except Exception as e: self.chest_result.insert(tk.END, f"Error: {e}") self._run_async(do_open) def _refresh_collection(self): def do_refresh(): try: resp = self.session.get(f"{BASE_URL}/me/collection", headers=self._headers()) self.collection_text.delete('1.0', tk.END) if resp.status_code == 200: cards = resp.json() if not cards: self.collection_text.insert(tk.END, "No cards yet! Open some chests.") else: text = f"=== Your Collection ({len(cards)} cards) ===\n\n" for card in cards: rarity_emoji = {"COMMON": "⚪", "RARE": "🔵", "LEGENDARY": "🟡"}.get(card['rarity'], "") text += f"{rarity_emoji} {card['card_name']} ({card['rarity']})\n" self.collection_text.insert(tk.END, text) else: self.collection_text.insert(tk.END, f"Error: {resp.status_code}") except Exception as e: self.collection_text.insert(tk.END, f"Error: {e}") self._run_async(do_refresh) def main(): root = tk.Tk() app = CardGameGUI(root) root.mainloop() if __name__ == "__main__": main()