Skip to content

Commit fd1f339

Browse files
committed
Add sims_per_round flag
1 parent ab34af0 commit fd1f339

19 files changed

Lines changed: 192 additions & 134 deletions

codeclash/agents/minisweagent.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
from codeclash.agents.abstract import Player
1919
from codeclash.agents.utils import GameContext, resolve_api_key
20-
from codeclash.utils.environment import copy_file_to_container
20+
from codeclash.utils.environment import copy_to_container
2121

2222

2323
class ClashAgent(DefaultAgent):
@@ -107,7 +107,7 @@ def run(self):
107107
result=result,
108108
print_fct=self.logger.debug,
109109
)
110-
copy_file_to_container(
110+
copy_to_container(
111111
self.environment,
112112
traj_path,
113113
self.game_context.log_env / traj_path.name,

codeclash/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@
33
DIR_LOGS = Path("logs")
44
DIR_WORK = Path("/testbed")
55
GH_ORG = "emagedoc"
6+
OUTPUTS_LOGS = "log_outputs"
7+
OUTPUTS_RESULTS = "result_outputs"
68
RESULT_TIE = "Tie"

codeclash/games/abstract.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@
77
from minisweagent.environments.docker import DockerEnvironment
88

99
from codeclash.agents.abstract import Player
10-
from codeclash.constants import DIR_LOGS, DIR_WORK, GH_ORG
10+
from codeclash.constants import (
11+
DIR_LOGS,
12+
DIR_WORK,
13+
GH_ORG,
14+
OUTPUTS_LOGS,
15+
OUTPUTS_RESULTS,
16+
)
1117
from codeclash.utils.environment import assert_zero_exit_code, copy_between_containers
1218
from codeclash.utils.log import get_logger
1319

@@ -139,12 +145,12 @@ def _pre_round_setup(self, agents: list[Player]):
139145

140146
@abstractmethod
141147
def determine_winner(
142-
self, result_output: str, agents: list[Player]
148+
self, result_outputs: list[str], agents: list[Player]
143149
) -> dict[str, str]:
144150
"""Determine the winner of the game based on the result output.
145151
146152
Args:
147-
result_output: The specific output containing winning information
153+
result_outputs: The specific output(s) containing winning information
148154
agents: List of agents participating in the round
149155
150156
Returns:
@@ -153,13 +159,13 @@ def determine_winner(
153159
pass
154160

155161
@abstractmethod
156-
def execute_round(self, agents: list[Player]) -> dict[str, str]:
162+
def execute_round(self, agents: list[Player]) -> dict[str, list[str]]:
157163
"""Subclasses implement their game-specific logic here.
158164
This is the low level implementation, you probably want to use run_round instead, which
159165
includes the pre-round setup, post-round setup, and winner determination.
160166
161167
Returns:
162-
Dictionary with keys "log_output" and "result_output"
168+
Dictionary with keys "log_outputs" and "result_outputs"
163169
"""
164170
pass
165171

@@ -172,14 +178,14 @@ def run_round(self, agents: list[Player]) -> dict[str, str]:
172178
"""
173179
self._pre_round_setup(agents)
174180
result = self.execute_round(agents)
175-
log_output = result["log_output"]
176-
result_output = result["result_output"]
181+
log_outputs = result[OUTPUTS_LOGS]
182+
result_outputs = result[OUTPUTS_RESULTS]
177183

178-
winner_result = self.determine_winner(result_output, agents)
184+
winner_result = self.determine_winner(result_outputs, agents)
179185
winner_name = winner_result["winner"]
180186

181187
return {
182-
"log_output": log_output,
183-
"result_output": result_output,
188+
OUTPUTS_LOGS: log_outputs,
189+
OUTPUTS_RESULTS: result_outputs,
184190
"winner": winner_name,
185191
}

codeclash/games/battlecode/main.py

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import re
2-
import shlex
32
from pathlib import Path
43
from typing import Any
54

6-
from codeclash.constants import DIR_WORK, RESULT_TIE
5+
from tqdm.auto import tqdm
6+
7+
from codeclash.constants import DIR_WORK, OUTPUTS_LOGS, OUTPUTS_RESULTS, RESULT_TIE
78
from codeclash.games.abstract import CodeGame
89

910

@@ -23,27 +24,30 @@ def __init__(self, config, *, tournament_id: str, local_output_dir: Path):
2324
else:
2425
self.run_cmd_round += f" --{arg} {val}"
2526

26-
def determine_winner(self, result_output: str, agents: list[Any]) -> dict[str, str]:
27-
self.logger.debug(f"Determining winner from result output: {result_output}")
28-
lines = result_output.strip().split("\n")
29-
# Get the third-to-last line which contains the winner info
30-
winner_line = lines[-3] if len(lines) >= 3 else ""
31-
self.logger.debug(f"Winner line: {winner_line}")
32-
match = re.search(r"\s\((.*)\)\swins\s\(", winner_line)
33-
if match:
34-
winner_key = match.group(1)
35-
self.logger.debug(f"Winner key from match: {winner_key}")
36-
# Map A/B to actual agent names (much closer to original code)
37-
winner = {"A": agents[0].name, "B": agents[1].name}.get(
38-
winner_key, RESULT_TIE
39-
)
40-
self.logger.debug(f"Concluding winner: {winner}")
41-
return {"winner": winner}
42-
else:
43-
self.logger.debug("No winner match found, returning tie")
44-
return {"winner": RESULT_TIE}
27+
def determine_winner(
28+
self, result_outputs: list[str], agents: list[Any]
29+
) -> dict[str, str]:
30+
winners = []
31+
for ro in result_outputs:
32+
lines = ro.strip().split("\n")
33+
# Get the third-to-last line which contains the winner info
34+
winner_line = lines[-3] if len(lines) >= 3 else ""
35+
self.logger.debug(f"Winner line: {winner_line}")
36+
match = re.search(r"\s\((.*)\)\swins\s\(", winner_line)
37+
if match:
38+
winner_key = match.group(1)
39+
self.logger.debug(f"Winner key from match: {winner_key}")
40+
# Map A/B to actual agent names (much closer to original code)
41+
winner = {"A": agents[0].name, "B": agents[1].name}.get(
42+
winner_key, RESULT_TIE
43+
)
44+
winners.append(winner)
45+
else:
46+
winners.append(RESULT_TIE)
47+
winner = max(set(winners), key=winners.count)
48+
return {"winner": winner}
4549

46-
def execute_round(self, agents: list[Any]) -> dict[str, str]:
50+
def execute_round(self, agents: list[Any]) -> dict[str, list[str]]:
4751
for agent in agents:
4852
src, dest = f"/{agent.name}/src/mysubmission/", str(
4953
DIR_WORK / "src" / agent.name
@@ -53,10 +57,12 @@ def execute_round(self, agents: list[Any]) -> dict[str, str]:
5357
f"--p{idx+1}-dir src --p{idx+1} {agent.name}"
5458
for idx, agent in enumerate(agents)
5559
]
56-
cmd = f"{self.run_cmd_round} {shlex.join(args)}"
60+
cmd = f"{self.run_cmd_round} {' '.join(args)}"
5761
self.logger.info(f"Running command: {cmd}")
58-
response = self.environment.execute(cmd)
59-
assert response["returncode"] == 0, response
60-
# For BattleCode, log_output and result_output are the same
61-
output = response["output"]
62-
return {"log_output": output, "result_output": output}
62+
outputs = []
63+
for _ in tqdm(range(self.game_config["sims_per_round"])):
64+
response = self.environment.execute(cmd)
65+
assert response["returncode"] == 0, response
66+
# For BattleCode, log_outputs and result_outputs are the same
67+
outputs.append(response["output"])
68+
return {OUTPUTS_LOGS: outputs, OUTPUTS_RESULTS: outputs}

codeclash/games/battlesnake/main.py

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from pathlib import Path
44

55
from codeclash.agents.abstract import Player
6+
from codeclash.constants import OUTPUTS_LOGS, OUTPUTS_RESULTS
67
from codeclash.games.abstract import CodeGame
78
from codeclash.utils.environment import assert_zero_exit_code
89

@@ -23,18 +24,20 @@ def __init__(self, config, *, tournament_id: str, local_output_dir: Path):
2324
self.run_cmd_round += f" --{arg} {val}"
2425

2526
def determine_winner(
26-
self, result_output: str, agents: list[Player]
27+
self, result_outputs: list[str], agents: list[Player]
2728
) -> dict[str, str]:
28-
self.logger.debug(f"Determining winner from result output: {result_output}")
29-
lines = result_output.strip().split("\n")
30-
# Get the last line which contains the game result
31-
last_line = lines[-1] if lines else ""
32-
self.logger.debug(f"Last line: {last_line}")
33-
winner = json.loads(last_line)["winnerName"]
34-
self.logger.debug(f"Concluding winner: {winner}")
29+
winners = []
30+
for ro in result_outputs:
31+
lines = ro.strip().split("\n")
32+
# Get the last line which contains the game result
33+
last_line = lines[-1] if lines else ""
34+
self.logger.debug(f"Last line: {last_line}")
35+
winner = json.loads(last_line)["winnerName"]
36+
winners.append(winner)
37+
winner = max(set(winners), key=winners.count)
3538
return {"winner": winner}
3639

37-
def execute_round(self, agents: list[Player]) -> dict[str, str]:
40+
def execute_round(self, agents: list[Player]) -> dict[str, list[str]]:
3841
cmd = []
3942
for idx, agent in enumerate(agents):
4043
port = 8001 + idx
@@ -46,27 +49,33 @@ def execute_round(self, agents: list[Player]) -> dict[str, str]:
4649

4750
time.sleep(3) # Give servers time to start
4851

49-
# Create temporary output file for results
50-
output_file = f"battlesnake_output_{int(time.time())}.json"
51-
cmd_str = " ".join(cmd) + f" -o {output_file}"
52-
self.logger.info(f"Running command: {self.run_cmd_round} {cmd_str}")
53-
5452
try:
55-
response = assert_zero_exit_code(
56-
self.environment.execute(
57-
f"{self.run_cmd_round} {cmd_str}",
58-
cwd=f"{self.environment.config.cwd}/game",
53+
log_outputs, result_outputs = [], []
54+
for idx in range(self.game_config["sims_per_round"]):
55+
# Create temporary output file for results
56+
output_file = f"battlesnake_output_{idx}_{int(time.time())}.json"
57+
cmd_str = " ".join(cmd) + f" -o {output_file}"
58+
self.logger.info(f"Running command: {self.run_cmd_round} {cmd_str}")
59+
60+
response = assert_zero_exit_code(
61+
self.environment.execute(
62+
f"{self.run_cmd_round} {cmd_str}",
63+
cwd=f"{self.environment.config.cwd}/game",
64+
)
5965
)
60-
)
6166

62-
# Read the output file for result information
63-
result_response = self.environment.execute(f"cat game/{output_file}")
64-
result_output = result_response["output"]
67+
# Read the output file for result information
68+
result_response = self.environment.execute(f"cat game/{output_file}")
69+
result_output = result_response["output"]
70+
log_outputs.append(response["output"])
71+
result_outputs.append(result_output)
72+
73+
# Clean up the output file
74+
self.environment.execute(f"rm -f game/{output_file}")
6575

66-
# Clean up the output file
67-
self.environment.execute(f"rm -f game/{output_file}")
76+
time.sleep(0.1)
6877

69-
return {"log_output": response["output"], "result_output": result_output}
78+
return {OUTPUTS_LOGS: log_outputs, OUTPUTS_RESULTS: result_outputs}
7079
finally:
7180
# Kill all python servers when done
7281
self.environment.execute("pkill -f 'python main.py' || true")

codeclash/games/corewar/main.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from pathlib import Path
44

55
from codeclash.agents.abstract import Player
6+
from codeclash.constants import OUTPUTS_LOGS, OUTPUTS_RESULTS
67
from codeclash.games.abstract import CodeGame
78

89

@@ -22,8 +23,9 @@ def __init__(self, config, *, tournament_id: str, local_output_dir: Path):
2223
self.run_cmd_round += f" -{arg} {val}"
2324

2425
def determine_winner(
25-
self, result_output: str, agents: list[Player]
26+
self, result_outputs: list[str], agents: list[Player]
2627
) -> dict[str, str]:
28+
result_output = result_outputs[0] # Get the first (and only) element
2729
self.logger.debug(f"Determining winner from result output: {result_output}")
2830
scores = []
2931
n = len(agents) * 2
@@ -51,12 +53,13 @@ def determine_winner(
5153
self.logger.debug("No scores found, returning unknown")
5254
return {"winner": "unknown"}
5355

54-
def execute_round(self, agents: list[Player]) -> dict[str, str]:
56+
def execute_round(self, agents: list[Player]) -> dict[str, list[str]]:
5557
args = [f"/{agent.name}/warriors/warrior.red" for agent in agents]
5658
cmd = f"{self.run_cmd_round} {shlex.join(args)}"
59+
cmd += f" -r {self.game_config['sims_per_round']}"
5760
self.logger.info(f"Running command: {cmd}")
5861
response = self.environment.execute(cmd)
5962
assert response["returncode"] == 0, response
60-
# For CoreWar, log_output and result_output are the same
61-
output = response["output"]
62-
return {"log_output": output, "result_output": output}
63+
# For CoreWar, log_outputs and result_outputs are the same
64+
output = [response["output"]]
65+
return {OUTPUTS_LOGS: output, OUTPUTS_RESULTS: output}

codeclash/games/robocode/main.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
from pathlib import Path
44

55
from codeclash.agents.abstract import Player
6+
from codeclash.constants import OUTPUTS_LOGS, OUTPUTS_RESULTS
67
from codeclash.games.abstract import CodeGame
7-
from codeclash.utils.environment import copy_file_to_container
8+
from codeclash.utils.environment import copy_to_container
89

910

1011
class RoboCodeGame(CodeGame):
@@ -25,7 +26,7 @@ def __init__(self, config, *, tournament_id: str, local_output_dir: Path):
2526
def _get_battle_config(self) -> str:
2627
default_battle_config = {
2728
"battle": {
28-
"numRounds": 10,
29+
"numRounds": self.game_config.get("sims_per_round", 100),
2930
"gunCoolingRate": 0.1,
3031
"rules": {"inactivityTime": 450, "hideEnemyNames": True},
3132
},
@@ -56,12 +57,13 @@ def dict_to_lines(d, prefix=""):
5657
return "\n".join(battle_lines)
5758

5859
def determine_winner(
59-
self, result_output: str, agents: list[Player]
60+
self, result_outputs: list[str], agents: list[Player]
6061
) -> dict[str, str]:
62+
result_output = result_outputs[0] # Get the first (and only) element
6163
self.logger.debug(f"Determining winner from result output: {result_output}")
6264
lines = result_output.strip().split("\n")
6365
# Get the second line which contains the winner info (closer to original)
64-
winner_line = lines[1] if len(lines) >= 2 else ""
66+
winner_line = lines[2] if len(lines) >= 3 else ""
6567
self.logger.debug(f"Winner line: {winner_line}")
6668
if winner_line:
6769
winner = winner_line.split()[1].rsplit(".", 1)[0]
@@ -71,7 +73,7 @@ def determine_winner(
7173
self.logger.debug("No winner line found, returning unknown")
7274
return {"winner": "unknown"}
7375

74-
def execute_round(self, agents: list[Player]) -> dict[str, str]:
76+
def execute_round(self, agents: list[Player]) -> dict[str, list[str]]:
7577
for agent in agents:
7678
# Copy the agent codebase into the game codebase and compile it
7779
for cmd in [
@@ -93,7 +95,7 @@ def execute_round(self, agents: list[Player]) -> dict[str, str]:
9395
robocode.battle.selectedRobots={selected_robots}
9496
"""
9597
)
96-
copy_file_to_container(self.environment, battle_file, f"battles/{battle_file}")
98+
copy_to_container(self.environment, battle_file, f"battles/{battle_file}")
9799
subprocess.run(f"rm -f {battle_file}", shell=True)
98100

99101
# Run battle with results output to file
@@ -110,4 +112,4 @@ def execute_round(self, agents: list[Player]) -> dict[str, str]:
110112
# Clean up the results file
111113
self.environment.execute(f"rm -f {results_file}")
112114

113-
return {"log_output": response["output"], "result_output": result_output}
115+
return {OUTPUTS_LOGS: [response["output"]], OUTPUTS_RESULTS: [result_output]}

0 commit comments

Comments
 (0)