11import shlex
22from collections import Counter
3+ from concurrent .futures import ThreadPoolExecutor , as_completed
34from pathlib import Path
45
6+ from tqdm .auto import tqdm
7+
58from codeclash .agents .player import Player
69from codeclash .constants import RESULT_TIE
710from codeclash .games .game import CodeGame , RoundStats
8- from codeclash .utils .environment import assert_zero_exit_code
911
1012
1113class RobotRumbleGame (CodeGame ):
@@ -16,17 +18,40 @@ def __init__(self, config, *, tournament_id: str, local_output_dir: Path):
1618 assert len (config ["players" ]) == 2 , "RobotRumble is a two-player game"
1719 self .run_cmd_round : str = "./rumblebot run term"
1820
19- def execute_round (self , agents : list [Player ]):
21+ def _run_single_simulation (self , agents : list [Player ], idx : int ) -> str :
22+ """Run a single robotrumble simulation and return the output."""
2023 args = [f"/{ agent .name } /robot.py" for agent in agents ]
21- cmd = f"{ self .run_cmd_round } { shlex .join (args )} "
22- self .logger .info (f"Running game: { cmd } " )
23- for idx in range (self .game_config .get ("sims_per_round" , 100 )):
24- assert_zero_exit_code (self .environment .execute (cmd + f" > { self .log_env / f'sim_{ idx } .txt' } " ))
24+ cmd = f"{ self .run_cmd_round } { shlex .join (args )} > { self .log_env / f'sim_{ idx } .txt' } "
25+
26+ output = self .environment .execute (cmd )
27+ if output ["returncode" ] != 0 :
28+ self .logger .warning (
29+ f"RobotRumble simulation { idx } failed with exit code { output ['returncode' ]} :\n { output ['output' ]} "
30+ )
31+ return output ["output" ]
32+
33+ def execute_round (self , agents : list [Player ]):
34+ self .logger .info (f"Running game with players: { [agent .name for agent in agents ]} " )
35+
36+ with ThreadPoolExecutor (20 ) as executor :
37+ # Submit all simulations to the thread pool
38+ futures = [
39+ executor .submit (self ._run_single_simulation , agents , idx )
40+ for idx in range (self .game_config .get ("sims_per_round" , 100 ))
41+ ]
42+
43+ # Collect results as they complete
44+ for future in tqdm (as_completed (futures ), total = len (futures )):
45+ future .result ()
2546
2647 def get_results (self , agents : list [Player ], round_num : int , stats : RoundStats ):
2748 winners = []
2849 for idx in range (self .game_config .get ("sims_per_round" , 100 )):
29- with open (self .log_round (round_num ) / f"sim_{ idx } .txt" ) as f :
50+ output_file = self .log_round (round_num ) / f"sim_{ idx } .txt"
51+ if not output_file .exists ():
52+ self .logger .warning (f"Simulation { idx } not found, skipping" )
53+ continue
54+ with open (output_file ) as f :
3055 lines = f .read ().strip ().split ("\n " )
3156
3257 # Get the last 2 lines which contain the game result (same as original)
0 commit comments