Skip to content

Commit 95a8402

Browse files
committed
Add submission checks
1 parent fb1fc66 commit 95a8402

5 files changed

Lines changed: 51 additions & 20 deletions

File tree

codeclash/games/battlecode/battlecode.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from codeclash.games.game import CodeGame, RoundStats
88

99
BC_LOG = "sim.log"
10+
BC_FOLDER = "mysubmission"
11+
BC_TIE = "Reason: The winning team won arbitrarily (coin flip)."
1012

1113

1214
class BattleCodeGame(CodeGame):
@@ -25,7 +27,7 @@ def __init__(self, config, *, tournament_id: str, local_output_dir: Path):
2527

2628
def execute_round(self, agents: list[Player]):
2729
for agent in agents:
28-
src, dest = f"/{agent.name}/src/mysubmission/", str(DIR_WORK / "src" / agent.name)
30+
src, dest = f"/{agent.name}/src/{BC_FOLDER}/", str(DIR_WORK / "src" / agent.name)
2931
self.environment.execute(f"cp -r {src} {dest}")
3032
random.shuffle(agents) # Start position matters in BattleCode! Shuffle to be fair.
3133
args = [f"--p{idx + 1}-dir src --p{idx + 1} {agent.name}" for idx, agent in enumerate(agents)]
@@ -36,27 +38,34 @@ def execute_round(self, agents: list[Player]):
3638
assert response["returncode"] == 0, response
3739

3840
def get_results(self, agents: list[Player], round_num: int, stats: RoundStats):
39-
winners = []
4041
with open(self.log_round(round_num) / BC_LOG) as f:
4142
lines = f.read().strip().split("\n")
4243
# Get the third-to-last line which contains the winner info
43-
winner_line = lines[-3] if len(lines) >= 3 else ""
44+
assert len(lines) >= 3, "Log file does not contain enough lines to determine winner"
45+
winner_line = lines[-3]
46+
reason_line = lines[-2]
4447
self.logger.debug(f"Winner line: {winner_line}")
48+
self.logger.debug(f"Reason line: {reason_line}")
4549
match = re.search(r"\s\((.*)\)\swins\s\(", winner_line)
46-
if match:
50+
if match and reason_line != BC_TIE:
4751
winner_key = match.group(1)
4852
self.logger.debug(f"Winner key from match: {winner_key}")
4953
# Map A/B to actual agent names (much closer to original code)
5054
winner = {"A": agents[0].name, "B": agents[1].name}.get(winner_key, RESULT_TIE)
51-
winners.append(winner)
5255
else:
53-
winners.append(RESULT_TIE)
56+
winner = RESULT_TIE
5457

55-
stats.winner = max(set(winners), key=winners.count)
56-
stats.scores = {agent.name: winners.count(agent.name) for agent in agents}
58+
stats.winner = winner
59+
stats.scores = {agent.name: (1 if agent.name == winner else 0) for agent in agents}
5760
for player, score in stats.scores.items():
5861
stats.player_stats[player].score = score
5962

6063
def validate_code(self, agent: Player) -> tuple[bool, str | None]:
61-
# TODO: implement more checks
64+
if BC_FOLDER not in agent.environment.execute("ls src")["output"]:
65+
return False, f"`{BC_FOLDER}` directory not found in `src/`"
66+
if "bot.py" not in agent.environment.execute(f"ls src/{BC_FOLDER}")["output"]:
67+
return False, "`bot.py` not found in `src/mysubmission/`"
68+
bot_content = agent.environment.execute(f"cat src/{BC_FOLDER}/bot.py")["output"].splitlines()
69+
if "def turn():" not in bot_content:
70+
return False, "`turn()` function not found in `bot.py`"
6271
return True, None

codeclash/games/corewar/corewar.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from codeclash.games.game import CodeGame, RoundStats
77

88
COREWAR_LOG = "sim.log"
9+
COREWAR_FILE = "warrior.red"
910

1011

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

2425
def execute_round(self, agents: list[Player]):
25-
args = [f"/{agent.name}/warriors/warrior.red" for agent in agents]
26+
args = [f"/{agent.name}/warriors/{COREWAR_FILE}" for agent in agents]
2627
cmd = (
2728
f"{self.run_cmd_round} {shlex.join(args)} "
2829
f"-r {self.game_config['sims_per_round']} "
@@ -66,5 +67,6 @@ def get_results(self, agents: list[Player], round_num: int, stats: RoundStats):
6667
stats.player_stats[player].score = score
6768

6869
def validate_code(self, agent: Player) -> tuple[bool, str | None]:
69-
# TODO: implement more checks
70+
if COREWAR_FILE not in agent.environment.execute("ls warriors")["output"]:
71+
return False, f"`{COREWAR_FILE}` not found in `warriors/`"
7072
return True, None

codeclash/games/game.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,6 @@ def to_dict(self) -> dict[str, Any]:
3030

3131

3232
class RoundStats:
33-
winner: str
34-
round_num: int
35-
scores: dict[str, float]
36-
player_stats: dict[str, PlayerStats] | None = None
37-
3833
def __init__(self, round_num: int, agents: list[Player]):
3934
self.winner = None
4035
self.round_num = round_num
@@ -215,9 +210,10 @@ def run_round(self, agents: list[Player], round_num: int) -> RoundStats:
215210
for agent in agents:
216211
is_valid, error = self.validate_code(agent)
217212
if not is_valid:
218-
self.logger.warning(f"Agent {agent.name} failed verification: {error}")
213+
self.logger.warning(f"Agent {agent.name} failed submission validation: {error}")
219214
stats.player_stats[agent.name].invalid_reason = error
220215
continue
216+
self.logger.info(f"Agent {agent.name} passed submission validation")
221217
stats.player_stats[agent.name].valid_submit = True
222218
validated.append(agent)
223219

codeclash/games/robocode/robocode.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from codeclash.utils.environment import assert_zero_exit_code, create_file_in_container
88

99
RC_LOG = "scoreboard.txt"
10+
RC_FILE = Path("MyTank.java")
1011

1112

1213
class RoboCodeGame(CodeGame):
@@ -67,7 +68,7 @@ def execute_round(self, agents: list[Player]):
6768
self.environment.execute(cmd)
6869

6970
# Create .battle file
70-
selected_robots = ",".join([f"{agent.name}.MyTank*" for agent in agents])
71+
selected_robots = ",".join([f"{agent.name}.{RC_FILE.stem}*" for agent in agents])
7172
# Use timestamp for unique battle file name since rounds are managed by tournament
7273
battle_file = f"{self.game_id}-battle{int(time.time())}.battle"
7374
battle_content = f"""#Battle Properties
@@ -106,5 +107,18 @@ def get_results(self, agents: list[Player], round_num: int, stats: RoundStats):
106107
stats.player_stats[player].score = score
107108

108109
def validate_code(self, agent: Player) -> tuple[bool, str | None]:
109-
# TODO: implement more checks
110+
if "robots" not in agent.environment.execute("ls")["output"]:
111+
return False, "`robots/` directory not found in submission root"
112+
if "custom" not in agent.environment.execute("ls robots")["output"]:
113+
return False, "`robots/custom/` directory not found"
114+
if str(RC_FILE) not in agent.environment.execute("ls robots/custom")["output"]:
115+
return False, (
116+
f"`{RC_FILE}` not found in `robots/custom/`. "
117+
f"You can include additional files, but the primary tank logic must be in `robots/custom/{RC_FILE}`"
118+
)
119+
response = agent.environment.execute('javac -cp "libs/robocode.jar" robots/custom/*.java')
120+
if response["returncode"] != 0:
121+
return False, f"Compilation error:\n{response['output']}"
122+
if f"{RC_FILE.stem}.class" not in agent.environment.execute("ls robots/custom")["output"]:
123+
return False, f"`{RC_FILE.stem}.class` not found after compilation"
110124
return True, None

codeclash/games/robotrumble/robotrumble.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,15 @@ def get_results(self, agents: list[Player], round_num: int, stats: RoundStats):
6262
stats.player_stats[player].score = score
6363

6464
def validate_code(self, agent: Player) -> tuple[bool, str | None]:
65-
# TODO: implement more checks
65+
if "robot.py" not in agent.environment.execute("ls")["output"]:
66+
return False, "robot.py not found in the root directory"
67+
if "def robot(state, unit):" not in agent.environment.execute("cat robot.py")["output"]:
68+
return (
69+
False,
70+
"robot.py does not contain the required robot function. It should be defined as 'def robot(state, unit): ...'",
71+
)
72+
test_run_cmd = f"{self.run_cmd_round} robot.py robot.py -t 1"
73+
test_run = agent.environment.execute(test_run_cmd)["output"]
74+
if "Some errors occurred:" in test_run:
75+
return False, f"Running robot.py (with `{test_run_cmd}`) resulted in errors:\n{test_run}"
6676
return True, None

0 commit comments

Comments
 (0)