Jofthomas commited on
Commit
bb73cef
·
verified ·
1 Parent(s): a10663f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +155 -164
app.py CHANGED
@@ -1,195 +1,146 @@
 
1
  import gradio as gr
2
  import asyncio
3
  import threading
4
  import os
5
- import random
6
- import traceback
7
- from queue import Queue # For communication if needed, though run_coroutine_threadsafe handles results
8
 
 
9
  from poke_env.player import Player, RandomPlayer
10
- from poke_env import AccountConfiguration, ServerConfiguration
11
- from agents import OpenAIAgent # Assuming this exists
 
12
 
13
- # --- Global variables ---
 
14
  random_player: Player | None = None
15
  openai_agent: Player | None = None
16
- poke_env_loop: asyncio.AbstractEventLoop | None = None # Store the dedicated loop
17
- poke_env_thread: threading.Thread | None = None
18
  agents_initialized = False
19
- init_lock = threading.Lock() # Keep the lock for initialization logic
20
 
21
  # --- Configuration ---
22
  custom_config = ServerConfiguration(
23
- "wss://jofthomas.com/showdown/websocket",
24
- "https://jofthomas.com/showdown/action.php"
25
  )
26
- RANDOM_PLAYER_BASE_NAME = "RandomAgent"
27
- OPENAI_AGENT_BASE_NAME = "OpenAIAgent" # Note: Your code uses "MistralAgent" for OpenAI config
28
- DEFAULT_BATTLE_FORMAT = "gen9randombattle"
29
 
30
- # --- Background Thread and Loop Management ---
31
-
32
- def run_poke_env_loop():
33
- """Target function for the background thread. Runs the asyncio loop."""
34
- global poke_env_loop
35
- try:
36
- print("Starting dedicated poke-env event loop...")
37
- poke_env_loop = asyncio.new_event_loop()
38
- asyncio.set_event_loop(poke_env_loop)
39
- # Keep the loop running indefinitely
40
- poke_env_loop.run_forever()
41
- except Exception as e:
42
- print(f"!!! Error in poke-env background loop: {e}")
43
- traceback.print_exc()
44
- finally:
45
- if poke_env_loop and poke_env_loop.is_running():
46
- poke_env_loop.stop() # Gracefully stop if possible
47
- # Consider loop.close() after stopping if resources need explicit release
48
- print("Poke-env event loop stopped.")
49
- poke_env_loop = None # Clear the loop variable
50
 
 
51
 
52
- async def initialize_agents_async():
53
- """Coroutine to initialize agents within the dedicated loop."""
 
54
  global random_player, openai_agent, agents_initialized
55
- # Generate random suffixes
 
56
  random_player_suffix = random.randint(1000, 999999)
57
- # openai_agent_suffix = random.randint(1000, 999999) # Not used in current config
58
-
 
 
59
  random_player_username = f"{RANDOM_PLAYER_BASE_NAME}{random_player_suffix}"
60
- # Your code uses a fixed name and password from env var for the second agent
61
- openai_agent_username = "MistralAgent" # Or use OPENAI_AGENT_BASE_NAME + suffix if intended
62
-
63
  random_account_config = AccountConfiguration(random_player_username, None)
64
- openai_password = os.environ.get('SHOWDOWN_PSWD')
65
- if not openai_password:
66
- print("!!! WARNING: SHOWDOWN_PSWD environment variable not set for OpenAI agent.")
67
- # Decide how to handle this - maybe prevent OpenAI agent init?
68
- # For now, we'll let it potentially fail during init below.
69
- openai_account_config = AccountConfiguration(openai_agent_username, openai_password)
70
-
71
  print(f"Using RandomPlayer username: {random_account_config.username}")
72
  print(f"Using OpenAIAgent username: {openai_account_config.username}")
73
 
 
74
  with init_lock:
75
  if agents_initialized:
76
  print("Agents already initialized.")
77
  return
78
  print("Initializing agents...")
 
 
 
79
  try:
 
 
80
  print(f"Initializing RandomPlayer ({random_account_config.username})...")
81
  random_player = RandomPlayer(
82
- account_configuration=random_account_config,
83
  server_configuration=custom_config,
84
- battle_format=DEFAULT_BATTLE_FORMAT,
85
- # Explicitly provide the loop if poke-env supports it, otherwise it uses asyncio.get_event_loop()
86
- # loop=poke_env_loop # Check poke-env docs if 'loop' parameter is accepted
87
  )
88
  print("RandomPlayer initialized.")
89
 
 
 
90
  print(f"Initializing OpenAIAgent ({openai_account_config.username})...")
91
  openai_agent = OpenAIAgent(
92
- account_configuration=openai_account_config,
93
  server_configuration=custom_config,
94
- battle_format=DEFAULT_BATTLE_FORMAT,
95
- # loop=poke_env_loop # Check poke-env docs if 'loop' parameter is accepted
96
  )
97
- # Add a small delay or check for connection status if needed
98
- # await asyncio.sleep(2) # Example: Allow time for connection
99
- # Add checks like: if random_player.logged_in and openai_agent.logged_in:
100
  print("OpenAIAgent initialized.")
 
101
  agents_initialized = True
102
  print("Agent initialization complete.")
103
- # else: raise RuntimeError("One or more agents failed to log in.")
104
-
105
  except Exception as e:
106
  print(f"!!! Error during agent initialization: {e}")
107
- traceback.print_exc()
108
  # Reset globals if init fails partially
109
- if isinstance(random_player, Player): random_player.close() # Attempt cleanup if possible
110
- if isinstance(openai_agent, Player): openai_agent.close()
111
  random_player = None
112
  openai_agent = None
113
  agents_initialized = False
114
-
115
-
116
- def start_poke_env_thread():
117
- """Starts the background thread for the poke-env event loop and agent initialization."""
118
- global poke_env_thread, poke_env_loop
119
- if poke_env_thread is None or not poke_env_thread.is_alive():
120
- print("Starting poke-env background thread and loop...")
121
- poke_env_thread = threading.Thread(target=run_poke_env_loop, daemon=True)
122
- poke_env_thread.start()
123
-
124
- # Wait briefly for the loop to start before scheduling init
125
- # A more robust way would use threading.Event or Queue
126
- import time
127
- time.sleep(1)
128
-
129
- if poke_env_loop:
130
- print("Scheduling agent initialization...")
131
- # Schedule the async initialization function to run on the background loop
132
- future = asyncio.run_coroutine_threadsafe(initialize_agents_async(), poke_env_loop)
133
- # Optionally wait for init to finish or handle errors
134
- try:
135
- future.result(timeout=60) # Wait for initialization up to 60 seconds
136
- print("Agent initialization scheduled and completed.")
137
- except Exception as e:
138
- print(f"!!! Agent initialization failed: {e}")
139
- # Handle the case where init itself failed
140
- # The background loop thread will still be running unless it crashed.
141
- else:
142
- print("!!! Error: poke-env loop did not start correctly.")
143
-
144
  elif agents_initialized:
145
- print("Agents already initialized.")
146
  else:
147
- print("Poke-env thread already running (initialization might be in progress or failed).")
148
-
149
-
150
- # --- Battle Invitation Logic (Revised) ---
151
 
152
- # Keep the async function as is, but improve logging slightly
 
153
  async def send_battle_invite_async(player: Player, username: str, battle_format: str = DEFAULT_BATTLE_FORMAT):
154
  """Sends a challenge using the provided player object."""
155
- # Check player state *before* attempting
156
- player_name = getattr(player, 'username', 'unknown player')
157
- is_connected = getattr(player, 'connected', False)
158
- is_logged_in = getattr(player, 'logged_in', False)
159
- print(f"Sending invite: Player={player_name}, Connected={is_connected}, LoggedIn={is_logged_in}")
160
-
161
- if not is_connected or not is_logged_in:
162
- # Maybe try to reconnect or log in here if poke-env supports it easily?
163
- # await player.log_in(player.account_configuration.username, player.account_configuration.password) # Example
164
- return f"Error: Player {player_name} is not connected or logged in."
165
-
166
- if player is None: # Should be caught before calling
167
  return "Error: The selected player is not available/initialized."
168
  if not username or not username.strip():
169
- return "Error: Please enter a valid Showdown username."
170
-
171
  try:
 
172
  print(f"Attempting to send challenge from {player.username} to {username} in format {battle_format}")
173
- # Ensure challenge format is correct if needed
174
- # Check player._battle_format vs battle_format if necessary
175
- await player.send_challenges(username, n_challenges=1) # Pass format if needed
176
  return f"Battle invitation ({battle_format}) sent to {username} from bot {player.username}! Check Showdown."
177
  except Exception as e:
 
 
 
 
178
  print(f"Error sending challenge from {player_name}:")
179
- traceback.print_exc() # Print full stack trace
180
- # Consider what state the player might be in now.
181
- # Resetting the player state might be needed but is complex.
182
  return f"Error sending challenge: {str(e)}. Check console logs."
183
 
184
- # Wrapper for Gradio (Revised)
185
  def invite_to_battle(agent_choice: str, username: str):
186
- """Selects the agent and initiates the battle invitation via the background loop."""
187
- global random_player, openai_agent, agents_initialized, poke_env_loop
188
 
189
- if not agents_initialized or poke_env_loop is None or not poke_env_loop.is_running():
190
- # Maybe try starting again? Or just inform user.
191
- start_poke_env_thread() # Attempt to start if not running
192
- return "Agents are not ready or the background process is not running. Please wait a moment and try again."
193
 
194
  selected_player: Player | None = None
195
  if agent_choice == "Random Player":
@@ -199,76 +150,116 @@ def invite_to_battle(agent_choice: str, username: str):
199
  elif agent_choice == "OpenAI Agent":
200
  selected_player = openai_agent
201
  if selected_player is None:
202
- return "Error: OpenAI Agent not available. Check initialization logs and SHOWDOWN_PSWD."
 
 
 
 
203
  else:
204
  return "Error: Invalid agent choice selected."
205
 
 
206
  username_clean = username.strip()
207
  if not username_clean:
208
  return "Please enter your Showdown username."
209
 
210
- # Ensure we have a valid player before proceeding
211
- if selected_player is None:
212
- # This case should ideally be caught by the checks above, but double-check.
213
- return "Error: Could not select a valid player agent."
214
-
215
- # --- Crucial Change: Use run_coroutine_threadsafe ---
216
  try:
217
- # Schedule the async function on the dedicated poke-env loop
218
- future = asyncio.run_coroutine_threadsafe(
219
- send_battle_invite_async(selected_player, username_clean, DEFAULT_BATTLE_FORMAT),
220
- poke_env_loop
221
- )
222
- # Wait for the result from the background loop with a timeout
223
- result = future.result(timeout=30) # Adjust timeout as needed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  return result
225
  except TimeoutError:
226
- print(f"Timeout waiting for challenge result from {getattr(selected_player, 'username', agent_choice)}")
227
- return "Error: Sending challenge timed out. The bot might be busy or disconnected."
228
  except Exception as e:
229
- # This catches errors in scheduling or retrieving the result,
230
- # or unexpected errors from the coroutine itself if not caught internally.
231
- print(f"Unexpected error in invite_to_battle scheduling/execution: {e}")
232
  traceback.print_exc()
233
  return f"An unexpected error occurred: {e}"
234
 
235
 
236
- # --- Gradio UI Definition (Mostly Unchanged) ---
237
- iframe_code = """<iframe src="https://jofthomas.com/play.pokemonshowdown.com/testclient.html" width="100%" height="800" style="border: none;" referrerpolicy="no-referrer"></iframe>"""
 
 
 
 
 
 
 
 
 
238
 
239
  def main_app():
240
  """Creates and returns the Gradio application interface."""
241
- # Start the background thread and loop when the app starts
242
- start_poke_env_thread()
243
 
 
244
  with gr.Blocks(title="Pokemon Showdown Agent") as demo:
245
- # ... (rest of your UI definition is likely fine) ...
246
  gr.Markdown("# Pokémon Battle Agent")
247
-
248
- agent_dropdown = gr.Dropdown(
249
- label="Select Agent",
250
- choices=["Random Player", "OpenAI Agent"],
251
- value="Random Player",
252
- scale=1
253
  )
254
- name_input = gr.Textbox("your name")
255
- battle_button = gr.Button('Battle invitation')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
256
  gr.Markdown("### Pokémon Showdown Interface")
257
  gr.Markdown("Log in/use the username you entered above.")
 
258
  gr.HTML(iframe_code)
259
 
 
260
  battle_button.click(
261
  fn=invite_to_battle,
262
  inputs=[agent_dropdown, name_input],
263
- outputs=gr.Textbox(label="Status") # Add an output Textbox to display results/errors
264
  )
265
-
266
 
267
  return demo
268
 
269
  # --- Main execution block ---
270
  if __name__ == "__main__":
 
271
  app = main_app()
272
- # Add proper app closing logic if needed to shut down the loop/thread gracefully
273
- # For simple cases, daemon thread might be enough, but explicit shutdown is better.
274
- app.launch()
 
1
+ # app.py
2
  import gradio as gr
3
  import asyncio
4
  import threading
5
  import os
6
+ import random # <-- Import the random module
 
 
7
 
8
+ # Import poke-env components
9
  from poke_env.player import Player, RandomPlayer
10
+ from poke_env import AccountConfiguration,ServerConfiguration
11
+ # Import your custom agent
12
+ from agents import OpenAIAgent
13
 
14
+
15
+ # --- Global variables for players and thread ---
16
  random_player: Player | None = None
17
  openai_agent: Player | None = None
18
+ agent_init_thread: threading.Thread | None = None
19
+ init_lock = threading.Lock() # To prevent race conditions during init
20
  agents_initialized = False
 
21
 
22
  # --- Configuration ---
23
  custom_config = ServerConfiguration(
24
+ "wss://jofthomas.com/showdown/websocket", # WebSocket URL
25
+ "https://jofthomas.com/showdown/action.php" # Authentication URL
26
  )
 
 
 
27
 
28
+ # --- Dynamic Account Configuration ---
29
+ # Define base names for the bots
30
+ RANDOM_PLAYER_BASE_NAME = "RandomAgent"
31
+ OPENAI_AGENT_BASE_NAME = "OpenAIAgent" # You can change this if you like
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
+ DEFAULT_BATTLE_FORMAT = "gen9randombattle"
34
 
35
+ # --- Agent Initialization ---
36
+ def initialize_agents_sync():
37
+ """Initializes both player agents in a background thread."""
38
  global random_player, openai_agent, agents_initialized
39
+
40
+ # Generate random suffixes (4-6 digits, i.e., 1000 to 999999)
41
  random_player_suffix = random.randint(1000, 999999)
42
+ openai_agent_suffix = random.randint(1000, 999999)
43
+
44
+
45
+ # Construct full usernames
46
  random_player_username = f"{RANDOM_PLAYER_BASE_NAME}{random_player_suffix}"
47
+ openai_agent_username = f"{OPENAI_AGENT_BASE_NAME}{openai_agent_suffix}"
48
+
49
+ # Create AccountConfiguration objects with dynamic usernames and no password (guest)
50
  random_account_config = AccountConfiguration(random_player_username, None)
51
+ openai_account_config = AccountConfiguration(openai_agent_username, None)
52
+
53
+ # Optional: Print the generated usernames for confirmation when the script starts
 
 
 
 
54
  print(f"Using RandomPlayer username: {random_account_config.username}")
55
  print(f"Using OpenAIAgent username: {openai_account_config.username}")
56
 
57
+ # Ensure this runs only once
58
  with init_lock:
59
  if agents_initialized:
60
  print("Agents already initialized.")
61
  return
62
  print("Initializing agents...")
63
+ # We need an event loop in this new thread for player initialization
64
+ loop = asyncio.new_event_loop()
65
+ asyncio.set_event_loop(loop)
66
  try:
67
+ # Initialize Random Player - PASSING THE CONFIGURATION
68
+ # Use the dynamically generated username from random_account_config
69
  print(f"Initializing RandomPlayer ({random_account_config.username})...")
70
  random_player = RandomPlayer(
71
+ account_configuration=random_account_config, # <-- Pass the config
72
  server_configuration=custom_config,
73
+ battle_format=DEFAULT_BATTLE_FORMAT
 
 
74
  )
75
  print("RandomPlayer initialized.")
76
 
77
+ # Initialize OpenAI Agent - PASSING THE CONFIGURATION
78
+ # Use the dynamically generated username from openai_account_config
79
  print(f"Initializing OpenAIAgent ({openai_account_config.username})...")
80
  openai_agent = OpenAIAgent(
81
+ account_configuration=openai_account_config, # <-- Pass the config
82
  server_configuration=custom_config,
83
+ battle_format=DEFAULT_BATTLE_FORMAT
 
84
  )
 
 
 
85
  print("OpenAIAgent initialized.")
86
+
87
  agents_initialized = True
88
  print("Agent initialization complete.")
 
 
89
  except Exception as e:
90
  print(f"!!! Error during agent initialization: {e}")
 
91
  # Reset globals if init fails partially
 
 
92
  random_player = None
93
  openai_agent = None
94
  agents_initialized = False
95
+ # Note: Don't close the loop here, the players might need it running implicitly
96
+ # loop.close()
97
+
98
+ # Function to start the initialization thread
99
+ def start_agent_initialization():
100
+ """Starts the agent initialization thread if not already running."""
101
+ global agent_init_thread
102
+ if not agents_initialized and (agent_init_thread is None or not agent_init_thread.is_alive()):
103
+ print("Starting agent initialization thread...")
104
+ agent_init_thread = threading.Thread(target=initialize_agents_sync, daemon=True)
105
+ agent_init_thread.start()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  elif agents_initialized:
107
+ print("Agents already initialized, no need to start thread.")
108
  else:
109
+ print("Initialization thread already running.")
 
 
 
110
 
111
+ # --- Battle Invitation Logic ---
112
+ # Using player.username SHOULD automatically use the assigned username
113
  async def send_battle_invite_async(player: Player, username: str, battle_format: str = DEFAULT_BATTLE_FORMAT):
114
  """Sends a challenge using the provided player object."""
115
+ if player is None:
 
 
 
 
 
 
 
 
 
 
 
116
  return "Error: The selected player is not available/initialized."
117
  if not username or not username.strip():
118
+ return "Error: Please enter a valid Showdown username."
 
119
  try:
120
+ # player.username should reflect the name set in AccountConfiguration
121
  print(f"Attempting to send challenge from {player.username} to {username} in format {battle_format}")
122
+ print(player) # Keep this for debugging if needed
123
+ # Pass the battle_format if you want it to be different from player's default
124
+ await player.send_challenges(username, n_challenges=1) # Pass format here
125
  return f"Battle invitation ({battle_format}) sent to {username} from bot {player.username}! Check Showdown."
126
  except Exception as e:
127
+ # Log the full error for debugging
128
+ import traceback
129
+ # Make sure player.username exists even if there's an error before login completes
130
+ player_name = getattr(player, 'username', 'unknown player')
131
  print(f"Error sending challenge from {player_name}:")
132
+ traceback.print_exc() # Print stack trace
 
 
133
  return f"Error sending challenge: {str(e)}. Check console logs."
134
 
135
+ # Wrapper for the async function to use in Gradio, handling agent selection
136
  def invite_to_battle(agent_choice: str, username: str):
137
+ """Selects the agent and initiates the battle invitation."""
138
+ global random_player, openai_agent, agents_initialized
139
 
140
+ if not agents_initialized:
141
+ # Try to initialize if the button is clicked before init finishes
142
+ start_agent_initialization()
143
+ return "Agents are initializing, please wait a few seconds and try again."
144
 
145
  selected_player: Player | None = None
146
  if agent_choice == "Random Player":
 
150
  elif agent_choice == "OpenAI Agent":
151
  selected_player = openai_agent
152
  if selected_player is None:
153
+ # Check if API key might be the issue
154
+ if not os.getenv("OPENAI_API_KEY"):
155
+ return "Error: OpenAI Agent not available. OPENAI_API_KEY is missing."
156
+ else:
157
+ return "Error: OpenAI Agent not available. Initialization might have failed. Check logs."
158
  else:
159
  return "Error: Invalid agent choice selected."
160
 
161
+ # Ensure username is provided
162
  username_clean = username.strip()
163
  if not username_clean:
164
  return "Please enter your Showdown username."
165
 
166
+ # Run the async challenge function in a managed event loop
 
 
 
 
 
167
  try:
168
+ # Try to get the existing loop from the init thread or create a new one
169
+ try:
170
+ loop = asyncio.get_running_loop()
171
+ except RuntimeError:
172
+ print("No running event loop, creating new one for challenge.")
173
+ loop = asyncio.new_event_loop()
174
+ asyncio.set_event_loop(loop)
175
+
176
+ # If the main loop is running, schedule the task; otherwise, run until complete
177
+ if loop.is_running():
178
+ # Schedule the coroutine and wait for its result
179
+ future = asyncio.run_coroutine_threadsafe(
180
+ # Pass the default battle format explicitly if needed
181
+ send_battle_invite_async(selected_player, username_clean, DEFAULT_BATTLE_FORMAT),
182
+ loop
183
+ )
184
+ result = future.result(timeout=30) # Add a timeout
185
+ else:
186
+ result = loop.run_until_complete(
187
+ # Pass the default battle format explicitly if needed
188
+ send_battle_invite_async(selected_player, username_clean, DEFAULT_BATTLE_FORMAT)
189
+ )
190
+
191
  return result
192
  except TimeoutError:
193
+ return "Error: Sending challenge timed out."
 
194
  except Exception as e:
195
+ print(f"Unexpected error in invite_to_battle's async execution: {e}")
196
+ import traceback
 
197
  traceback.print_exc()
198
  return f"An unexpected error occurred: {e}"
199
 
200
 
201
+ # --- Gradio UI Definition ---
202
+ # iframe code to embed Pokemon Showdown (using official URL)
203
+ iframe_code = """
204
+ <iframe
205
+ src="https://jofthomas.com/play.pokemonshowdown.com/testclient.html"
206
+ width="100%"
207
+ height="800"
208
+ style="border: none;"
209
+ referrerpolicy="no-referrer">
210
+ </iframe>
211
+ """
212
 
213
  def main_app():
214
  """Creates and returns the Gradio application interface."""
215
+ # Start agent initialization when the app is defined/loaded
216
+ start_agent_initialization()
217
 
218
+ # Using gr.Blocks. The default layout should stretch reasonably wide.
219
  with gr.Blocks(title="Pokemon Showdown Agent") as demo:
 
220
  gr.Markdown("# Pokémon Battle Agent")
221
+ gr.Markdown(
222
+ "Select an agent, enter **your** Showdown username "
223
+ "(the one you are logged in with below), and click Send Invite."
224
+ "Note : you can just select a random set of character as long as someone registered does not already use this username"
 
 
225
  )
226
+
227
+ # --- Row for Controls at the Top ---
228
+ with gr.Row():
229
+ # Place controls here
230
+ agent_dropdown = gr.Dropdown(
231
+ label="Select Agent",
232
+ choices=["Random Player", "OpenAI Agent"],
233
+ value="Random Player",
234
+ scale=1 # Give dropdown reasonable space
235
+ )
236
+ name_input = gr.Textbox(
237
+ label="Your Pokémon Showdown Username",
238
+ placeholder="Enter username used in Showdown below",
239
+ scale=2 # Give name input more relative space
240
+ )
241
+ battle_button = gr.Button("Send Battle Invitation", scale=1) # Button takes less space
242
+
243
+
244
+ # --- Section for the IFrame below the controls ---
245
  gr.Markdown("### Pokémon Showdown Interface")
246
  gr.Markdown("Log in/use the username you entered above.")
247
+ # The HTML component containing the iframe will take up the available width
248
  gr.HTML(iframe_code)
249
 
250
+ # --- Connect button click to the invitation function ---
251
  battle_button.click(
252
  fn=invite_to_battle,
253
  inputs=[agent_dropdown, name_input],
254
+
255
  )
 
256
 
257
  return demo
258
 
259
  # --- Main execution block ---
260
  if __name__ == "__main__":
261
+ # Create and launch the Gradio app
262
  app = main_app()
263
+ # You might need to configure server_name and server_port depending on your deployment environment
264
+ # Use app.launch(share=True) for a public link (if needed and safe)
265
+ app.launch() # server_name="0.0.0.0" # To make accessible on network