Skip to content

Commit b8427d4

Browse files
committed
Ref: Create new Tournament class. Move main.py code
1 parent bdd79f4 commit b8427d4

6 files changed

Lines changed: 188 additions & 151 deletions

File tree

codeclash/tournaments/abstract.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class AbstractTournament:
2+
pass
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""
2+
PvP training mode where multiple agents compete against each other.
3+
"""
4+
5+
from codeclash.agents import get_agent
6+
from codeclash.agents.abstract import Player
7+
from codeclash.games import get_game
8+
from codeclash.games.abstract import CodeGame
9+
from codeclash.tournaments.abstract import AbstractTournament
10+
from codeclash.utils.log import get_logger
11+
12+
13+
class PvpTraining(AbstractTournament):
14+
def __init__(
15+
self, config: dict, *, cleanup: bool = False, push_agent: bool = False
16+
):
17+
self.config = config
18+
self.cleanup_on_end = cleanup
19+
self.push_agent = push_agent
20+
self.game: CodeGame = get_game(self.config)
21+
self.agents: list[Player] = []
22+
for agent_conf in self.config["players"]:
23+
self.agents.append(get_agent(agent_conf, self.config["prompts"], self.game))
24+
self.logger = get_logger(self.game.name)
25+
26+
def run(self) -> None:
27+
"""Main execution function that runs all rounds."""
28+
try:
29+
for round_num in range(1, self.game.rounds + 1):
30+
self.run_training_round(round_num)
31+
finally:
32+
self.cleanup()
33+
34+
def run_training_round(self, round_num: int) -> None:
35+
"""Execute a single training round."""
36+
self.game.run_round(self.agents)
37+
for agent in self.agents:
38+
self.run_agent(agent, round_num)
39+
40+
def run_agent(self, agent: Player, round_num: int) -> None:
41+
"""Run a single agent for the current round."""
42+
agent.pre_run_hook(new_round=round_num)
43+
agent.run()
44+
agent.post_run_hook(round=round_num)
45+
46+
def cleanup(self) -> None:
47+
"""Clean up game resources and push agents if requested."""
48+
self.game.end(self.cleanup_on_end)
49+
if self.push_agent:
50+
for agent in self.agents:
51+
agent.push()
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
"""
2+
In single player mode, the agent runs always against its previous version.
3+
"""
4+
5+
import copy
6+
7+
from codeclash.agents import get_agent
8+
from codeclash.agents.abstract import Player
9+
from codeclash.games import get_game
10+
from codeclash.games.abstract import CodeGame
11+
from codeclash.tournaments.abstract import AbstractTournament
12+
from codeclash.tournaments.utils.git_utils import filter_git_diff
13+
from codeclash.utils.environment import assert_zero_exit_code, create_file_on_container
14+
from codeclash.utils.log import get_logger
15+
16+
17+
class SinglePlayerTraining(AbstractTournament):
18+
def __init__(self, config: dict, cleanup: bool = False):
19+
self.config = config
20+
self.cleanup_on_end = cleanup
21+
self.game: CodeGame = get_game(self.config)
22+
self.agent: Player = get_agent(
23+
self.config["player"], self.config["prompts"], self.game
24+
)
25+
mirror_agent_config = copy.deepcopy(self.config["player"])
26+
mirror_agent_config["name"] = "mirror"
27+
self.mirror_agent: Player = get_agent(
28+
mirror_agent_config, self.config["prompts"], self.game
29+
)
30+
self.logger = get_logger(self.game.name)
31+
32+
def run(self) -> None:
33+
"""Main execution function that runs all rounds."""
34+
try:
35+
for round_num in range(1, self.game.rounds + 1):
36+
self.run_training_round(round_num)
37+
finally:
38+
self.cleanup()
39+
40+
def run_training_round(self, round_num: int) -> None:
41+
"""Execute a single training round."""
42+
self.game.run_round([self.agent, self.mirror_agent])
43+
self.run_main_agent(round_num)
44+
self.run_mirror_agent(round_num)
45+
46+
def run_main_agent(self, round_num: int) -> None:
47+
"""Run the main agent for the current round."""
48+
self.agent.pre_run_hook(new_round=round_num)
49+
self.agent.run()
50+
self.agent.post_run_hook(round=round_num)
51+
52+
def run_mirror_agent(self, round_num: int) -> None:
53+
"""Update mirror agent's codebase with the main agent's changes."""
54+
if round_num == 1:
55+
self.logger.info("Skipping updating mirror agent for round 1")
56+
return
57+
58+
# Set mirror agent's codebase to the main agent's codebase of the previous round
59+
full_diff = self.agent.get_metadata()["diff"][round_num - 1]
60+
61+
full_diff = filter_git_diff(full_diff)
62+
63+
if full_diff.strip():
64+
self.logger.debug(
65+
assert_zero_exit_code(
66+
self.mirror_agent.environment.execute(
67+
"git reset --hard && git clean -fd"
68+
)
69+
)
70+
)
71+
72+
create_file_on_container(
73+
container=self.mirror_agent.environment, # type: ignore
74+
content=full_diff,
75+
dest_path="tmp_patch.txt",
76+
)
77+
78+
self.logger.info("Applying patch to mirror agent's codebase")
79+
self.logger.debug(f"Full diff: {full_diff}")
80+
81+
commands = ["git status", "git apply tmp_patch.txt", "rm -f tmp_patch.txt"]
82+
for cmd in commands:
83+
self.logger.debug(f"Executing command: {cmd}")
84+
out = assert_zero_exit_code(
85+
self.mirror_agent.environment.execute(cmd), logger=self.logger
86+
)
87+
self.logger.debug(out)
88+
else:
89+
self.logger.info("No diff found for mirror agent, skipping update")
90+
91+
def cleanup(self) -> None:
92+
"""Clean up game resources."""
93+
self.game.end(self.cleanup_on_end)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
def filter_git_diff(text: str) -> str:
2+
"""Return a git diff with any file sections mentioning binary content removed."""
3+
lines = text.splitlines(keepends=True)
4+
out: list[str] = []
5+
block: list[str] = []
6+
in_block = False
7+
prelude_copied = False
8+
9+
def is_binary_block(bl: list[str]) -> bool:
10+
for ln in bl:
11+
s = ln.strip()
12+
if ln.startswith("Binary files "):
13+
return True
14+
if s == "GIT binary patch":
15+
return True
16+
return False
17+
18+
for ln in lines:
19+
if ln.startswith("diff --git "):
20+
if in_block:
21+
if not is_binary_block(block):
22+
out.extend(block)
23+
block = []
24+
else:
25+
if not prelude_copied:
26+
prelude_copied = True
27+
in_block = True
28+
if in_block:
29+
block.append(ln)
30+
else:
31+
out.append(ln)
32+
33+
if in_block and block:
34+
if not is_binary_block(block):
35+
out.extend(block)
36+
37+
return "".join(out)

main.py

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,14 @@
22

33
import yaml
44

5-
from codeclash.agents import get_agent
6-
from codeclash.agents.abstract import Player
7-
from codeclash.games import get_game
8-
from codeclash.games.abstract import CodeGame
5+
from codeclash.tournaments.pvp_training import PvpTraining
96

107

11-
def main(config_path: str, cleanup: bool = False, push_agent: bool = False):
8+
def main(config_path: str, *, cleanup: bool = False, push_agent: bool = False):
129
with open(config_path, "r") as f:
1310
config = yaml.safe_load(f)
14-
game: CodeGame = get_game(config)
15-
agents: list[Player] = []
16-
for agent_conf in config["players"]:
17-
agents.append(get_agent(agent_conf, config["prompts"], game))
18-
19-
try:
20-
for round in range(1, game.rounds + 1):
21-
game.run_round(agents)
22-
for agent in agents:
23-
agent.pre_run_hook(new_round=round)
24-
agent.run()
25-
agent.post_run_hook(round=round)
26-
finally:
27-
game.end(cleanup)
28-
if push_agent:
29-
for agent in agents:
30-
agent.push()
11+
training = PvpTraining(config, cleanup=cleanup, push_agent=push_agent)
12+
training.run()
3113

3214

3315
if __name__ == "__main__":

main_single_player.py

Lines changed: 1 addition & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,136 +1,8 @@
1-
"""
2-
In single player mode, the agent runs always against its previous version.
3-
"""
4-
51
import argparse
6-
import copy
72

83
import yaml
94

10-
from codeclash.agents import get_agent
11-
from codeclash.agents.abstract import Player
12-
from codeclash.games import get_game
13-
from codeclash.games.abstract import CodeGame
14-
from codeclash.utils.environment import assert_zero_exit_code, create_file_on_container
15-
from codeclash.utils.log import get_logger
16-
17-
18-
def filter_git_diff(text: str) -> str:
19-
"""Return a git diff with any file sections mentioning binary content removed."""
20-
lines = text.splitlines(keepends=True)
21-
out: list[str] = []
22-
block: list[str] = []
23-
in_block = False
24-
prelude_copied = False
25-
26-
def is_binary_block(bl: list[str]) -> bool:
27-
for ln in bl:
28-
s = ln.strip()
29-
if ln.startswith("Binary files "):
30-
return True
31-
if s == "GIT binary patch":
32-
return True
33-
return False
34-
35-
for ln in lines:
36-
if ln.startswith("diff --git "):
37-
if in_block:
38-
if not is_binary_block(block):
39-
out.extend(block)
40-
block = []
41-
else:
42-
if not prelude_copied:
43-
prelude_copied = True
44-
in_block = True
45-
if in_block:
46-
block.append(ln)
47-
else:
48-
out.append(ln)
49-
50-
if in_block and block:
51-
if not is_binary_block(block):
52-
out.extend(block)
53-
54-
return "".join(out)
55-
56-
57-
class SinglePlayerTraining:
58-
def __init__(self, config: dict, cleanup: bool = False):
59-
self.config = config
60-
self.cleanup_on_end = cleanup
61-
self.game: CodeGame = get_game(self.config)
62-
self.agent: Player = get_agent(
63-
self.config["player"], self.config["prompts"], self.game
64-
)
65-
mirror_agent_config = copy.deepcopy(self.config["player"])
66-
mirror_agent_config["name"] = "mirror"
67-
self.mirror_agent: Player = get_agent(
68-
mirror_agent_config, self.config["prompts"], self.game
69-
)
70-
self.logger = get_logger(self.game.name)
71-
72-
def run(self):
73-
"""Main execution function that runs all rounds."""
74-
try:
75-
for round_num in range(1, self.game.rounds + 1):
76-
self.run_round(round_num)
77-
finally:
78-
self.cleanup()
79-
80-
def run_round(self, round_num: int):
81-
"""Execute a single training round."""
82-
self.game.run_round([self.agent, self.mirror_agent])
83-
self.run_main_agent(round_num)
84-
self.run_mirror_agent(round_num)
85-
86-
def run_main_agent(self, round_num: int):
87-
"""Run the main agent for the current round."""
88-
self.agent.pre_run_hook(new_round=round_num)
89-
self.agent.run()
90-
self.agent.post_run_hook(round=round_num)
91-
92-
def run_mirror_agent(self, round_num: int):
93-
"""Update mirror agent's codebase with the main agent's changes."""
94-
if round_num == 1:
95-
self.logger.info("Skipping updating mirror agent for round 1")
96-
return
97-
98-
# Set mirror agent's codebase to the main agent's codebase of the previous round
99-
full_diff = self.agent.get_metadata()["diff"][round_num - 1]
100-
101-
full_diff = filter_git_diff(full_diff)
102-
103-
if full_diff.strip():
104-
self.logger.debug(
105-
assert_zero_exit_code(
106-
self.mirror_agent.environment.execute(
107-
"git reset --hard && git clean -fd"
108-
)
109-
)
110-
)
111-
112-
create_file_on_container(
113-
container=self.mirror_agent.environment, # type: ignore
114-
content=full_diff,
115-
dest_path="tmp_patch.txt",
116-
)
117-
118-
self.logger.info("Applying patch to mirror agent's codebase")
119-
self.logger.debug(f"Full diff: {full_diff}")
120-
121-
commands = ["git status", "git apply tmp_patch.txt", "rm -f tmp_patch.txt"]
122-
for cmd in commands:
123-
self.logger.debug(f"Executing command: {cmd}")
124-
out = assert_zero_exit_code(
125-
self.mirror_agent.environment.execute(cmd), logger=self.logger
126-
)
127-
self.logger.debug(out)
128-
else:
129-
self.logger.info("No diff found for mirror agent, skipping update")
130-
131-
def cleanup(self):
132-
"""Clean up game resources."""
133-
self.game.end(self.cleanup_on_end)
5+
from codeclash.tournaments.single_player_training import SinglePlayerTraining
1346

1357

1368
def main(config_path: str, cleanup: bool = False):

0 commit comments

Comments
 (0)