Skip to content

Commit ea6cf0e

Browse files
committed
Add Halite III
1 parent 0857383 commit ea6cf0e

File tree

6 files changed

+106
-24
lines changed

6 files changed

+106
-24
lines changed

codeclash/arenas/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
from codeclash.arenas.dummy.dummy import DummyArena
66
from codeclash.arenas.halite.halite import HaliteArena
77
from codeclash.arenas.halite2.halite2 import Halite2Arena
8-
9-
# from codeclash.games.halite3.halite3 import Halite3Game # WIP
8+
from codeclash.arenas.halite3.halite3 import Halite3Arena
109
from codeclash.arenas.huskybench.huskybench import HuskyBenchArena
1110
from codeclash.arenas.robocode.robocode import RoboCodeArena
1211
from codeclash.arenas.robotrumble.robotrumble import RobotRumbleArena
@@ -18,6 +17,7 @@
1817
DummyArena,
1918
HaliteArena,
2019
Halite2Arena,
20+
Halite3Arena,
2121
HuskyBenchArena,
2222
RoboCodeArena,
2323
RobotRumbleArena,

codeclash/arenas/halite/halite.py

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
HALITE_LOG = "sim_{idx}.log"
1515
HALITE_HIDDEN_EXEC = ".codeclash_exec"
16+
HALITE_WIN_PATTERN = r"Player\s#(\d+),\s(.*),\scame\sin\srank\s#(\d+)"
1617

1718
# Command to be run in each agent's `submission/` folder to compile agent
1819
MAP_FILE_TYPE_TO_COMPILE = {
@@ -52,10 +53,11 @@ class HaliteArena(CodeArena):
5253
"""
5354
default_args: dict = {}
5455
submission: str = "submission"
56+
executable: str = "./environment/halite"
5557

5658
def __init__(self, config, **kwargs):
5759
super().__init__(config, **kwargs)
58-
self.run_cmd_round: str = f"./environment/halite --replaydirectory {self.log_env}"
60+
self.run_cmd_round: str = f"{self.executable} --replaydirectory {self.log_env}"
5961
for arg, val in self.game_config.get("args", self.default_args).items():
6062
if isinstance(val, bool):
6163
if val:
@@ -93,13 +95,18 @@ def execute_round(self, agents: list[Player]):
9395
for future in tqdm(as_completed(futures), total=len(futures)):
9496
future.result()
9597

96-
def get_results(self, agents: list[Player], round_num: int, stats: RoundStats):
98+
def get_results(
99+
self,
100+
agents: list[Player],
101+
round_num: int,
102+
stats: RoundStats,
103+
pattern: str = HALITE_WIN_PATTERN,
104+
):
97105
winners = []
98-
pattern = r"Player\s#(\d+),\s(.*),\scame\sin\srank\s#(\d+)"
99106
for idx in range(self.game_config["sims_per_round"]):
100107
log_file = self.log_round(round_num) / HALITE_LOG.format(idx=idx)
101108
with open(log_file) as f:
102-
lines = f.readlines()[-len(agents) - 1 :]
109+
lines = f.readlines()[-len(agents) - 5 :]
103110
for line in lines:
104111
match = re.search(pattern, line)
105112
if match:
@@ -122,32 +129,47 @@ def get_results(self, agents: list[Player], round_num: int, stats: RoundStats):
122129
if player != RESULT_TIE:
123130
stats.player_stats[player].score = score
124131

125-
def validate_code(self, agent: Player) -> tuple[bool, str | None]:
132+
def validate_code(
133+
self,
134+
agent: Player,
135+
map_file_type_to_compile: dict = MAP_FILE_TYPE_TO_COMPILE,
136+
map_file_type_to_run: dict = MAP_FILE_TYPE_TO_RUN,
137+
) -> tuple[bool, str | None]:
126138
# Check that the `submission/` folder exists
127139
exists_output = agent.environment.execute("test -d submission && echo 'exists'")["output"]
128140
if "exists" != exists_output.strip():
129141
return False, f"Submission folder `{self.submission}/` does not exist"
130142

131143
# Check that there is a *single* file called "main.<ext>" in the submission folder
132144
# and that <ext> is one of the supported file types
145+
found_main = False
133146
sub_path = Path(agent.environment.config.cwd) / self.submission
134-
ls_output = agent.environment.execute("ls", cwd=sub_path)["output"]
147+
ls_output = agent.environment.execute("ls", cwd=sub_path)["output"].splitlines()
135148
main_files = [
136-
fname
137-
for fname in ls_output.splitlines()
138-
if fname.startswith("main.") and Path(fname).suffix in MAP_FILE_TYPE_TO_RUN
149+
fname for fname in ls_output if fname.startswith("main.") and Path(fname).suffix in map_file_type_to_run
139150
]
140-
supported_exts = "|".join(MAP_FILE_TYPE_TO_RUN.keys())
151+
141152
if len(main_files) != 1:
153+
# Check if src/main.rs exists for Rust projects
154+
if "src" in ls_output:
155+
src_ls_output = agent.environment.execute("ls src", cwd=sub_path)["output"].splitlines()
156+
if "main.rs" in src_ls_output:
157+
main_files = ["src/main.rs"]
158+
found_main = True
159+
else:
160+
found_main = True
161+
162+
if not found_main:
163+
supported_exts = "|".join(map_file_type_to_run.keys())
142164
return (
143165
False,
144166
f"Exactly one main.[{supported_exts}] file must be present in submission, found {len(main_files)}",
145167
)
146168
main_ext = Path(main_files[0]).suffix
147169

148170
# Check that the submission compiles if necessary
149-
if main_ext in MAP_FILE_TYPE_TO_COMPILE:
150-
compile_cmd = MAP_FILE_TYPE_TO_COMPILE[main_ext].format(name="main")
171+
if main_ext in map_file_type_to_compile:
172+
compile_cmd = map_file_type_to_compile[main_ext].format(name="main")
151173
try:
152174
compile_response = agent.environment.execute(compile_cmd, timeout=15, cwd=sub_path)
153175
except subprocess.TimeoutExpired:
@@ -159,8 +181,8 @@ def validate_code(self, agent: Player) -> tuple[bool, str | None]:
159181
)
160182

161183
# Check that submission runs in competition
162-
executable = MAP_FILE_TYPE_TO_RUN[main_ext].format(path=self.submission, name="main")
163-
run_cmd = f"./environment/halite {shlex.join([executable, executable])}"
184+
executable = map_file_type_to_run[main_ext].format(path=self.submission, name="main")
185+
run_cmd = f"{self.executable} {shlex.join([executable, executable])}"
164186
try:
165187
run_response = agent.environment.execute(run_cmd, timeout=15)
166188
except subprocess.TimeoutExpired:
@@ -169,6 +191,6 @@ def validate_code(self, agent: Player) -> tuple[bool, str | None]:
169191
return False, f"Submission failed to run (ran {run_cmd}): {run_response['output']}"
170192

171193
# Record command to run executable to hidden file
172-
executable_comp = MAP_FILE_TYPE_TO_RUN[main_ext].format(path=f"/{agent.name}/{self.submission}", name="main")
194+
executable_comp = map_file_type_to_run[main_ext].format(path=f"/{agent.name}/{self.submission}", name="main")
173195
agent.environment.execute(f'echo "{executable_comp}" > {HALITE_HIDDEN_EXEC}')
174196
return True, None

codeclash/arenas/halite3/Halite-III.Dockerfile renamed to codeclash/arenas/halite3/Halite3.Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ ENV PATH="/root/.cargo/bin:${PATH}"
1919
RUN apt-get update && apt-get install -y ocaml ocamlbuild
2020

2121
# Clone Halite repository
22-
RUN git clone https://github.com/CodeClash-ai/Halite-III.git /workspace \
22+
RUN git clone https://github.com/CodeClash-ai/Halite3.git /workspace \
2323
&& cd /workspace \
24-
&& git remote set-url origin https://github.com/CodeClash-ai/Halite-III.git
24+
&& git remote set-url origin https://github.com/CodeClash-ai/Halite3.git
2525
WORKDIR /workspace
2626

2727
RUN cd game_engine && cmake . && make
Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,68 @@
1-
from codeclash.arenas.halite.halite import HaliteArena
1+
import subprocess
2+
3+
from codeclash.agents.player import Player
4+
from codeclash.arenas.arena import RoundStats
5+
from codeclash.arenas.halite.halite import HALITE_LOG, HaliteArena
6+
7+
HALITE_WIN_PATTERN = r"Player\s(\d+),\s'(\S+)',\swas\srank\s(\d+)"
8+
9+
# Command to be run in each agent's `submission/` folder to compile agent
10+
MAP_FILE_TYPE_TO_COMPILE = {
11+
".cpp": "cmake . && make",
12+
".ml": "ocamlbuild -lib unix {name}.native",
13+
".rs": "cargo build",
14+
}
15+
16+
# Command to be run from `environment/` folder to run competition
17+
MAP_FILE_TYPE_TO_RUN = {
18+
".cpp": "{path}/{name}",
19+
".ml": "{path}/{name}.native",
20+
".rs": "{path}/target/debug/{name}",
21+
}
222

323

424
class Halite3Arena(HaliteArena):
5-
name: str = "Halite-III"
6-
description: str = ""
25+
name: str = "Halite3"
26+
description: str = """"""
727
default_args: dict = {}
828
submission: str = "submission"
29+
executable: str = "./game_engine/halite"
30+
31+
def __init__(self, config, **kwargs):
32+
super().__init__(config, **kwargs)
33+
# Remove replaydirectory arg as Halite3 does not support it
34+
self.run_cmd_round: str = self.run_cmd_round.replace(f"--replaydirectory {self.log_env}", "")
35+
36+
def _run_single_simulation(self, agents: list[Player], idx: int, cmd: str):
37+
"""Run a single halite simulation and return the output."""
38+
cmd = f"{cmd} > {self.log_env / HALITE_LOG.format(idx=idx)} 2>&1"
39+
40+
# Run the simulation and return the output
41+
try:
42+
response = self.environment.execute(cmd, timeout=120)
43+
self.environment.execute(f"mv errorlog*.log {self.log_env}", timeout=10)
44+
except subprocess.TimeoutExpired:
45+
self.logger.warning(f"Halite simulation {idx} timed out: {cmd}")
46+
return
47+
if response["returncode"] != 0:
48+
self.logger.warning(
49+
f"Halite simulation {idx} failed with exit code {response['returncode']}:\n{response['output']}"
50+
)
51+
52+
def get_results(self, agents: list[Player], round_num: int, stats: RoundStats):
53+
return super().get_results(
54+
agents,
55+
round_num,
56+
stats,
57+
pattern=HALITE_WIN_PATTERN,
58+
)
59+
60+
def validate_code(
61+
self,
62+
agent: Player,
63+
):
64+
return super().validate_code(
65+
agent,
66+
MAP_FILE_TYPE_TO_COMPILE,
67+
MAP_FILE_TYPE_TO_RUN,
68+
)

configs/test/halite2.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
tournament:
22
rounds: 3
33
game:
4-
name: Halite-II
4+
name: Halite2
55
sims_per_round: 250
66
players:
77
- agent: dummy

configs/test/halite3.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
tournament:
22
rounds: 3
33
game:
4-
name: Halite-III
4+
name: Halite3
55
sims_per_round: 250
66
players:
77
- agent: dummy

0 commit comments

Comments
 (0)