Skip to content

Commit 0cb4d62

Browse files
committed
Make get_stats operate on local logs
1 parent 784d117 commit 0cb4d62

14 files changed

Lines changed: 87 additions & 60 deletions

File tree

codeclash/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22

33
DIR_LOGS = Path("logs")
44
DIR_WORK = Path("/testbed")
5+
FILE_RESULTS = "results.json"
56
GH_ORG = "emagedoc"
67
RESULT_TIE = "Tie"

codeclash/games/battlecode/battlecode.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ def copy_logs_from_env(self, round_num):
2929
copy_from_container(
3030
container=self.environment,
3131
src_path="/testbed/sim.log",
32-
dest_path=self.log_local / "rounds" / str(round_num) / f"sim_{round_num}.log",
32+
dest_path=self.log_round(round_num) / f"sim_{round_num}.log",
3333
)
3434

35-
def get_stats(self, agents: list[Any]) -> RoundStats:
35+
def get_stats(self, agents: list[Any], round_num: int) -> RoundStats:
3636
winners = []
37-
ro = self.environment.execute(f"cat {BATTLECODE_LOG}")["output"]
38-
lines = ro.strip().split("\n")
37+
with open(self.log_round(round_num) / f"sim_{round_num}.log") as f:
38+
lines = f.read().strip().split("\n")
3939
# Get the third-to-last line which contains the winner info
4040
winner_line = lines[-3] if len(lines) >= 3 else ""
4141
self.logger.debug(f"Winner line: {winner_line}")

codeclash/games/battlesnake/battlesnake.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,17 @@ def copy_logs_from_env(self, round_num):
4343
copy_from_container(
4444
container=self.environment,
4545
src_path=f"{self.environment.config.cwd}/game/logs",
46-
dest_path=self.log_local / "rounds" / str(round_num),
46+
dest_path=self.log_round(round_num),
4747
)
4848

49-
def get_stats(self, agents: list[Player]) -> RoundStats:
49+
def get_stats(self, agents: list[Player], round_num: int) -> RoundStats:
5050
scores = {}
5151
for idx in range(self.game_config["sims_per_round"]):
52-
ro = self.environment.execute(f"cat game/logs/sim_{idx}.jsonl")["output"]
53-
lines = ro.strip().split("\n")
54-
results = json.loads(lines[-1]) if lines else {} # Get the last line which contains the game result
55-
winner = RESULT_TIE if results["isDraw"] else results["winnerName"]
56-
scores[winner] = scores.get(winner, 0) + 1
52+
with open(self.log_round(round_num) / f"logs/sim_{idx}.jsonl") as f:
53+
lines = f.read().strip().split("\n")
54+
results = json.loads(lines[-1]) # Get the last line which contains the game result
55+
winner = RESULT_TIE if results["isDraw"] else results["winnerName"]
56+
scores[winner] = scores.get(winner, 0) + 1
5757

5858
winner = max(scores, key=scores.get)
5959
winner = RESULT_TIE if list(scores.values()).count(scores[winner]) > 1 else winner

codeclash/games/corewar/corewar.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from codeclash.games.game import CodeGame, RoundStats
77
from codeclash.utils.environment import copy_from_container
88

9+
COREWAR_LOG = "sim.log"
10+
911

1012
class CoreWarGame(CodeGame):
1113
name: str = "CoreWar"
@@ -24,12 +26,13 @@ def copy_logs_from_env(self, round_num: int) -> None:
2426
super().copy_logs_from_env(round_num)
2527
copy_from_container(
2628
container=self.environment,
27-
src_path="/testbed/output.log",
28-
dest_path=self.log_local / "rounds" / str(round_num) / "output.log",
29+
src_path=f"/testbed/{COREWAR_LOG}",
30+
dest_path=self.log_round(round_num) / COREWAR_LOG,
2931
)
3032

31-
def get_stats(self, agents: list[Player]) -> RoundStats:
32-
result_output = self.environment.execute("cat output.log")["output"]
33+
def get_stats(self, agents: list[Player], round_num: int) -> RoundStats:
34+
with open(self.log_round(round_num) / COREWAR_LOG) as f:
35+
result_output = f.read()
3336
self.logger.debug(f"Determining winner from result output: {result_output}")
3437
scores = []
3538
n = len(agents) * 2
@@ -61,7 +64,7 @@ def get_stats(self, agents: list[Player]) -> RoundStats:
6164

6265
def execute_round(self, agents: list[Player]):
6366
args = [f"/{agent.name}/warriors/warrior.red" for agent in agents]
64-
cmd = f"{self.run_cmd_round} {shlex.join(args)} -r {self.game_config['sims_per_round']} > output.log;"
67+
cmd = f"{self.run_cmd_round} {shlex.join(args)} -r {self.game_config['sims_per_round']} > {COREWAR_LOG};"
6568
self.logger.info(f"Running game: {cmd}")
6669
response = self.environment.execute(cmd)
6770
assert response["returncode"] == 0, response

codeclash/games/dummy/dummy_game.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ def copy_logs_from_env(self, round_num):
1313
copy_from_container(
1414
container=self.environment,
1515
src_path="/testbed/result.log",
16-
dest_path=self.log_local / "rounds" / str(round_num) / "result.log",
16+
dest_path=self.log_round(round_num) / "result.log",
1717
)
1818

19-
def get_stats(self, agents: list[Player]) -> RoundStats:
20-
result_output = self.environment.execute("cat result.log")["output"]
21-
lines = result_output.split("FINAL_RESULTS")[-1].splitlines()
19+
def get_stats(self, agents: list[Player], round_num: int) -> RoundStats:
20+
with open(self.log_round(round_num) / "result.log") as f:
21+
round_log = f.read()
22+
lines = round_log.split("FINAL_RESULTS")[-1].splitlines()
2223

2324
scores = {}
2425
for line in lines:

codeclash/games/dummy/main.py

Whitespace-only changes.

codeclash/games/game.py

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,9 @@ def build_image(self):
9999
self.logger.error(f"❌ Failed to build Docker image: {result.stderr}\n{result.stdout}{result.stderr}")
100100
raise RuntimeError(f"Failed to build Docker image: {result.stderr}")
101101

102-
def get_metadata(self) -> dict:
103-
"""This is what we write to metadata.json.
104-
You can subclass extend this to add more details for specific games.
105-
"""
106-
return self._metadata
102+
def copy_logs_from_env(self, round_num: int) -> None:
103+
"""Copy logs from the game's environment to the local machine."""
104+
(self.log_local / "rounds" / str(round_num)).mkdir(parents=True, exist_ok=True)
107105

108106
def end(self, cleanup: bool = False):
109107
if cleanup:
@@ -112,6 +110,9 @@ def end(self, cleanup: bool = False):
112110
subprocess.run(f"rm -rf {artifact}", shell=True)
113111
self.logger.info(f"🧼 Cleaned up {self.name} game")
114112

113+
def log_round(self, round_num: int) -> Path:
114+
return self.log_local / "rounds" / str(round_num)
115+
115116
def get_environment(self, branch_name: str | None = None) -> DockerEnvironment:
116117
"""Get docker container ID with the game code installed."""
117118
self.build_image()
@@ -142,6 +143,12 @@ def get_environment(self, branch_name: str | None = None) -> DockerEnvironment:
142143
assert_zero_exit_code(environment.execute(cmd), logger=self.logger)
143144
return environment
144145

146+
def get_metadata(self) -> dict:
147+
"""This is what we write to metadata.json.
148+
You can subclass extend this to add more details for specific games.
149+
"""
150+
return self._metadata
151+
145152
def _pre_round_setup(self, agents: list[Player]):
146153
"""Copy agent codebases into game's container"""
147154
for agent in agents:
@@ -158,12 +165,20 @@ def _pre_round_setup(self, agents: list[Player]):
158165
logger=self.logger,
159166
)
160167

161-
def copy_logs_from_env(self, round_num: int) -> None:
162-
"""Copy logs from the game's environment to the local machine."""
163-
(self.log_local / "rounds" / str(round_num)).mkdir(parents=True, exist_ok=True)
168+
def run_round(self, agents: list[Player], round_num: int) -> RoundStats:
169+
"""
170+
Run a single round of the game with the given agents.
171+
172+
Returns the log output, result output, and winner name. All bookkeeping should be
173+
handled by the tournament class.
174+
"""
175+
self._pre_round_setup(agents)
176+
self.execute_round(agents)
177+
self.copy_logs_from_env(round_num)
178+
return self.get_stats(agents, round_num)
164179

165180
@abstractmethod
166-
def get_stats(self, agents: list[Player]) -> RoundStats:
181+
def get_stats(self, agents: list[Player], round_num: int) -> RoundStats:
167182
"""Determine the winner of the game based on the result output.
168183
169184
Args:
@@ -181,16 +196,3 @@ def execute_round(self, agents: list[Player]):
181196
includes the pre-round setup, post-round setup, and winner determination.
182197
"""
183198
pass
184-
185-
def run_round(self, agents: list[Player], round_num: int) -> RoundStats:
186-
"""
187-
Run a single round of the game with the given agents.
188-
189-
Returns the log output, result output, and winner name. All bookkeeping should be
190-
handled by the tournament class.
191-
"""
192-
self._pre_round_setup(agents)
193-
self.execute_round(agents)
194-
stats = self.get_stats(agents)
195-
self.copy_logs_from_env(round_num)
196-
return stats

codeclash/games/huskybench/huskybench.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
from codeclash.agents.player import Player
44
from codeclash.games.game import CodeGame, RoundStats
5+
from codeclash.utils.environment import copy_from_container
6+
7+
HB_LOG_DIR = Path("/testbed/engine/logs/")
58

69

710
class HuskyBenchGame(CodeGame):
@@ -19,15 +22,27 @@ def __init__(self, config, *, tournament_id: str, local_output_dir: Path):
1922
else:
2023
self.run_cmd_round += f" --{arg} {val}"
2124

22-
def get_stats(self, result_outputs: list[str], agents: list[Player]) -> RoundStats:
25+
def copy_logs_from_env(self, round_num):
26+
super().copy_logs_from_env(round_num)
27+
log_path = self.log_round(round_num)
28+
copy_from_container(
29+
container=self.environment,
30+
src_path=HB_LOG_DIR,
31+
dest_path=log_path,
32+
)
33+
34+
def get_stats(self, agents: list[Player], round_num: int) -> RoundStats:
2335
return RoundStats(winner="N/A", scores={})
2436

2537
def execute_round(self, agents: list[Player]):
38+
self.environment.execute(f"rm -rf {HB_LOG_DIR}; mkdir -p {HB_LOG_DIR}")
2639
try:
2740
self.logger.debug("Starting game servers")
28-
self.environment.execute(self.run_cmd_round + " > output.log &")
41+
self.environment.execute(f"{self.run_cmd_round} > {HB_LOG_DIR / 'engine.log'} &")
2942
for agent in agents:
30-
self.environment.execute("python client/main.py --port 8000 &", cwd=f"/{agent.name}")
43+
self.environment.execute(
44+
f"python client/main.py --port 8000 > {HB_LOG_DIR / f'{agent.name}.log'} &", cwd=f"/{agent.name}"
45+
)
3146
finally:
3247
# Kill all python servers when done
3348
self.environment.execute("pkill -f 'python client/main.py' || true")

codeclash/games/robocode/robocode.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,12 @@ def copy_logs_from_env(self, round_num: int) -> None:
5858
copy_from_container(
5959
container=self.environment,
6060
src_path="/testbed/logs",
61-
dest_path=self.log_local / "rounds" / str(round_num),
61+
dest_path=self.log_round(round_num),
6262
)
6363

64-
def get_stats(self, agents: list[Player]) -> RoundStats:
65-
result_output = self.environment.execute("cat logs/results.txt")["output"]
64+
def get_stats(self, agents: list[Player], round_num: int) -> RoundStats:
65+
with open(self.log_round(round_num) / "logs/results.txt") as f:
66+
result_output = f.read()
6667
print(result_output)
6768
lines = result_output.strip().split("\n")
6869

codeclash/games/robotrumble/robotrumble.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ def copy_logs_from_env(self, round_num: int) -> None:
2121
copy_from_container(
2222
container=self.environment,
2323
src_path="/testbed/logs",
24-
dest_path=self.log_local / "rounds" / str(round_num),
24+
dest_path=self.log_round(round_num),
2525
)
2626

27-
def get_stats(self, agents: list[Player]) -> RoundStats:
27+
def get_stats(self, agents: list[Player], round_num: int) -> RoundStats:
2828
winners = []
2929
for idx in range(self.game_config.get("sims_per_round", 100)):
30-
ro = self.environment.execute(f"cat logs/sim_{idx}.txt")["output"]
31-
lines = ro.strip().split("\n")
30+
with open(self.log_round(round_num) / f"logs/sim_{idx}.txt") as f:
31+
lines = f.read().strip().split("\n")
3232

3333
# Get the last 2 lines which contain the game result (same as original)
3434
relevant_lines = lines[-2:] if len(lines) >= 2 else lines

0 commit comments

Comments
 (0)