Skip to content

Commit daa37d5

Browse files
committed
Enh: Parallelize robotrumble sims
1 parent be437fd commit daa37d5

1 file changed

Lines changed: 32 additions & 7 deletions

File tree

codeclash/games/robotrumble/robotrumble.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import shlex
22
from collections import Counter
3+
from concurrent.futures import ThreadPoolExecutor, as_completed
34
from pathlib import Path
45

6+
from tqdm.auto import tqdm
7+
58
from codeclash.agents.player import Player
69
from codeclash.constants import RESULT_TIE
710
from codeclash.games.game import CodeGame, RoundStats
8-
from codeclash.utils.environment import assert_zero_exit_code
911

1012

1113
class 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

Comments
 (0)