Skip to content

Commit 55f1330

Browse files
committed
Merge branch 'main' of github.com:emagedoc/CodeClash
2 parents fbe82ab + 97fcd0e commit 55f1330

10 files changed

Lines changed: 57 additions & 40 deletions

File tree

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# Project specific
2+
3+
*.traj.json
4+
5+
# -------------
6+
17
# Byte-compiled / optimized / DLL files
28
__pycache__/
39
*.py[codz]

codeclash/agents/abstract.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from minisweagent import Environment
66

77
from codeclash.constants import GH_ORG
8+
from codeclash.utils.environment import assert_zero_exit_code
89

910
load_dotenv()
1011

@@ -29,7 +30,7 @@ def commit(self):
2930
"git add -A",
3031
f"git commit --allow-empty -m 'Round {self.round}/{rounds} Update'",
3132
]:
32-
self.environment.execute(cmd)
33+
assert_zero_exit_code(self.environment.execute(cmd))
3334
print(f"Committed changes for {self.name} for round {self.round}/{rounds}")
3435

3536
def push(self):
@@ -42,7 +43,7 @@ def push(self):
4243
f"git remote add origin https://x-access-token:{token}@github.com/{GH_ORG}/{self.name}.git",
4344
"git push -u origin main",
4445
]:
45-
self.environment.execute(cmd)
46+
assert_zero_exit_code(self.environment.execute(cmd))
4647

4748
@abstractmethod
4849
def run(self):

codeclash/games/abstract.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88

99
from minisweagent.environments.docker import DockerEnvironment
1010

11+
from codeclash.agents.abstract import Player
1112
from codeclash.constants import DIR_LOGS, DIR_WORK, GH_ORG
1213
from codeclash.games.utils import copy_between_containers
14+
from codeclash.utils.environment import assert_zero_exit_code
1315

1416

1517
class CodeGame(ABC):
@@ -84,13 +86,10 @@ def get_environment(self, branch_name: str | None = None) -> DockerEnvironment:
8486
'git config --global user.name "Player"',
8587
"git config --global commit.gpgsign false",
8688
]:
87-
out = environment.execute(cmd)
88-
if out.get("returncode", 0) != 0:
89-
msg = f"Failed to execute command: {cmd}. Output so far:\n{out.get('output')}"
90-
raise RuntimeError(msg)
89+
assert_zero_exit_code(environment.execute(cmd))
9190
return environment
9291

93-
def _pre_round_setup(self, agents: list[Any]):
92+
def _pre_round_setup(self, agents: list[Player]):
9493
"""Copy agent codebases into game's container and make round log file"""
9594
self.round += 1
9695
print(f"▶️ Running {self.name} round {self.round}...")
@@ -105,20 +104,20 @@ def _pre_round_setup(self, agents: list[Any]):
105104
)
106105

107106
# Ensure the log path + file exists
108-
self.environment.execute(f"mkdir -p {self.log_path}")
109-
self.environment.execute(f"touch {self.round_log_path}")
107+
assert_zero_exit_code(self.environment.execute(f"mkdir -p {self.log_path}"))
108+
assert_zero_exit_code(self.environment.execute(f"touch {self.round_log_path}"))
110109

111110
@abstractmethod
112-
def determine_winner(self, agents: list[Any]) -> Any:
111+
def determine_winner(self, agents: list[Player]) -> Any:
113112
"""Determine the winner of the game based on the round results, updates scoreboard"""
114113
pass
115114

116115
@abstractmethod
117-
def execute_round(self, agents: list[Any]):
116+
def execute_round(self, agents: list[Player]):
118117
"""Subclasses implement their game-specific logic here, must write results to round_log_path"""
119118
pass
120119

121-
def _post_round_setup(self, agents: list[Any]):
120+
def _post_round_setup(self, agents: list[Player]):
122121
for agent in agents:
123122
copy_between_containers(
124123
self.environment,
@@ -129,7 +128,7 @@ def _post_round_setup(self, agents: list[Any]):
129128
print(f"Copied round log to {agent.name}'s container.")
130129
print(f"Round {self.round} completed.")
131130

132-
def run_round(self, agents: list[Any]):
131+
def run_round(self, agents: list[Player]):
133132
"""
134133
Run a single round of the game with the given agents.
135134

codeclash/games/battlesnake/main.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import json
22
import time
3-
from typing import Any
43

4+
from codeclash.agents.abstract import Player
55
from codeclash.games.abstract import CodeGame
6+
from codeclash.utils.environment import assert_zero_exit_code
67

78

89
class BattleSnakeGame(CodeGame):
@@ -18,12 +19,14 @@ def __init__(self, config):
1819
else:
1920
self.run_cmd_round += f" --{arg} {val}"
2021

21-
def determine_winner(self, agents: list[Any]):
22-
response = self.environment.execute(f"tail -1 {self.round_log_path}")
22+
def determine_winner(self, agents: list[Player]):
23+
response = assert_zero_exit_code(
24+
self.environment.execute(f"tail -1 {self.round_log_path}")
25+
)
2326
winner = json.loads(response["output"].strip("\n"))["winnerName"]
2427
self.scoreboard.append((self.round, winner))
2528

26-
def execute_round(self, agents: list[Any]):
29+
def execute_round(self, agents: list[Player]):
2730
cmd = []
2831
for idx, agent in enumerate(agents):
2932
port = 8001 + idx
@@ -39,12 +42,14 @@ def execute_round(self, agents: list[Any]):
3942
cmd = " ".join(cmd)
4043
print(f"Running command: {cmd}")
4144

45+
# todo: should probably keep output somewhere?
4246
try:
43-
response = self.environment.execute(
44-
f"{self.run_cmd_round} {cmd}",
45-
cwd=f"{self.environment.config.cwd}/game",
47+
assert_zero_exit_code(
48+
self.environment.execute(
49+
f"{self.run_cmd_round} {cmd}",
50+
cwd=f"{self.environment.config.cwd}/game",
51+
)
4652
)
47-
assert response["returncode"] == 0, response
4853
finally:
4954
# Kill all python servers when done
5055
self.environment.execute("pkill -f 'python main.py' || true")

codeclash/games/corewar/main.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import re
2-
from typing import Any
32

3+
from codeclash.agents.abstract import Player
44
from codeclash.games.abstract import CodeGame
55

66

@@ -17,7 +17,7 @@ def __init__(self, config):
1717
else:
1818
self.run_cmd_round += f" -{arg} {val}"
1919

20-
def determine_winner(self, agents: list[Any]):
20+
def determine_winner(self, agents: list[Player]):
2121
scores = []
2222
n = len(agents) * 2
2323
response = self.environment.execute(f"tail -{n} {self.round_log_path}")
@@ -28,7 +28,7 @@ def determine_winner(self, agents: list[Any]):
2828
winner = agents[scores.index(max(scores))].name
2929
self.scoreboard.append((self.round, winner))
3030

31-
def execute_round(self, agents: list[Any]):
31+
def execute_round(self, agents: list[Player]):
3232
args = [f"/{agent.name}/warriors/warrior.red" for agent in agents]
3333
cmd = f"{self.run_cmd_round} {' '.join(args)} > {self.round_log_path}"
3434
print(f"Running command: {cmd}")

codeclash/games/robocode/main.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import subprocess
2-
from typing import Any
32

3+
from codeclash.agents.abstract import Player
44
from codeclash.games.abstract import CodeGame
55
from codeclash.games.utils import copy_file_to_container
66

@@ -51,12 +51,12 @@ def dict_to_lines(d, prefix=""):
5151
dict_to_lines(default_battle_config)
5252
return "\n".join(battle_lines)
5353

54-
def determine_winner(self, agents: list[Any]):
54+
def determine_winner(self, agents: list[Player]):
5555
response = self.environment.execute(f"head -3 {self.round_log_path} | tail -1")
5656
winner = response["output"].split()[1].rsplit(".", 1)[0]
5757
self.scoreboard.append((self.round, winner))
5858

59-
def execute_round(self, agents: list[Any]):
59+
def execute_round(self, agents: list[Player]):
6060
for agent in agents:
6161
# Copy the agent codebase into the game codebase and compile it
6262
for cmd in [

codeclash/games/robotrumble/main.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
from typing import Any
2-
1+
from codeclash.agents.abstract import Player
32
from codeclash.constants import RESULT_TIE
43
from codeclash.games.abstract import CodeGame
54

@@ -12,7 +11,7 @@ def __init__(self, config):
1211
assert len(config["players"]) == 2, "RobotRumble is a two-player game"
1312
self.run_cmd_round: str = "./rumblebot run term"
1413

15-
def determine_winner(self, agents: list[Any]):
14+
def determine_winner(self, agents: list[Player]):
1615
response = self.environment.execute(f"tail -2 {self.round_log_path}")
1716
if "Blue won" in response["output"]:
1817
self.scoreboard.append((self.round, agents[0].name))
@@ -21,7 +20,7 @@ def determine_winner(self, agents: list[Any]):
2120
elif "it was a tie" in response["output"]:
2221
self.scoreboard.append((self.round, RESULT_TIE))
2322

24-
def execute_round(self, agents: list[Any]):
23+
def execute_round(self, agents: list[Player]):
2524
args = [f"/{agent.name}/robot.py" for agent in agents]
2625
cmd = f"{self.run_cmd_round} {' '.join(args)} > {self.round_log_path}"
2726
print(f"Running command: {cmd}")

codeclash/games/utils.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
from minisweagent.environments.docker import DockerEnvironment
66

7+
from codeclash.utils.environment import assert_zero_exit_code
8+
79

810
def copy_between_containers(
911
src_container: DockerEnvironment,
@@ -25,15 +27,17 @@ def copy_between_containers(
2527
str(temp_path),
2628
]
2729
result_src = subprocess.run(
28-
cmd_src, check=True, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL
30+
cmd_src, check=False, capture_output=True, text=True
2931
)
3032
if result_src.returncode != 0:
3133
raise RuntimeError(
32-
f"Failed to copy from {src_container.container_id} to local temp"
34+
f"Failed to copy from {src_container.container_id} to local temp: {result_src.stdout}{result_src.stderr}"
3335
)
3436

3537
# Ensure destination folder exists
36-
dest_container.execute(f"mkdir -p {Path(dest_path).parent}")
38+
assert_zero_exit_code(
39+
dest_container.execute(f"mkdir -p {Path(dest_path).parent}")
40+
)
3741

3842
# Copy from temporary local directory to destination container
3943
cmd_dest = [
@@ -43,11 +47,11 @@ def copy_between_containers(
4347
f"{dest_container.container_id}:{dest_path}",
4448
]
4549
result_dest = subprocess.run(
46-
cmd_dest, check=True, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL
50+
cmd_dest, check=False, capture_output=True, text=True
4751
)
4852
if result_dest.returncode != 0:
4953
raise RuntimeError(
50-
f"Failed to copy from local temp to {dest_container.container_id}"
54+
f"Failed to copy from local temp to {dest_container.container_id}: {result_dest.stdout}{result_dest.stderr}"
5155
)
5256

5357

@@ -67,10 +71,8 @@ def copy_file_to_container(
6771
str(src_path),
6872
f"{container.container_id}:{dest_path}",
6973
]
70-
result = subprocess.run(
71-
cmd, check=True, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL
72-
)
74+
result = subprocess.run(cmd, check=False, capture_output=True, text=True)
7375
if result.returncode != 0:
7476
raise RuntimeError(
75-
f"Failed to copy {src_path} to {container.container_id}:{dest_path}"
77+
f"Failed to copy {src_path} to {container.container_id}:{dest_path}: {result.stdout}{result.stderr}"
7678
)

codeclash/utils/__init__.py

Whitespace-only changes.

codeclash/utils/environment.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
def assert_zero_exit_code(result: dict) -> dict:
2+
if result.get("returncode", 0) != 0:
3+
msg = f"Command failed with exit code {result.get('returncode')}:\n{result.get('output')}"
4+
raise RuntimeError(msg)
5+
return result

0 commit comments

Comments
 (0)