Skip to content

Commit f3e2c82

Browse files
committed
Merge branch 'main' into add-uv
2 parents 7b21cbf + e6f3901 commit f3e2c82

24 files changed

Lines changed: 2109 additions & 48 deletions

codeclash/analysis/matrix.py

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

99
from codeclash.agents.dummy_agent import Dummy
1010
from codeclash.agents.utils import GameContext
11-
from codeclash.arenas import get_game
11+
from codeclash.arenas import get_arena
1212
from codeclash.constants import DIR_WORK
1313
from codeclash.tournaments.utils.git_utils import filter_git_diff
1414
from codeclash.utils.atomic_write import atomic_write
@@ -109,7 +109,7 @@ def _initialize_game_pool(self):
109109
config = self.config.copy()
110110
config["game"]["sims_per_round"] = self.n_repetitions
111111

112-
game = get_game(
112+
game = get_arena(
113113
config,
114114
tournament_id=tournament_id,
115115
local_output_dir=self.pvp_output_dir / "matrix_eval" / f"worker_{i}",

codeclash/arenas/__init__.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44
from codeclash.arenas.corewar.corewar import CoreWarArena
55
from codeclash.arenas.dummy.dummy import DummyArena
66
from codeclash.arenas.halite.halite import HaliteArena
7-
8-
# from codeclash.games.halite2.halite2 import Halite2Game # WIP
9-
# from codeclash.games.halite3.halite3 import Halite3Game # WIP
7+
from codeclash.arenas.halite2.halite2 import Halite2Arena
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
@@ -17,14 +16,16 @@
1716
CoreWarArena,
1817
DummyArena,
1918
HaliteArena,
19+
Halite2Arena,
20+
Halite3Arena,
2021
HuskyBenchArena,
2122
RoboCodeArena,
2223
RobotRumbleArena,
2324
]
2425

2526

2627
# might consider postponing imports to avoid loading things we don't need
27-
def get_game(config: dict, **kwargs) -> CodeArena:
28+
def get_arena(config: dict, **kwargs) -> CodeArena:
2829
game = {x.name: x for x in ARENAS}.get(config["game"]["name"])
2930
if game is None:
3031
raise ValueError(f"Unknown game: {config['game']['name']}")

codeclash/arenas/halite/halite.py

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@
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 = {
19-
".cpp": "g++ -std=c++11 {path}.cpp -o {name}.o",
20-
".c": "gcc {path}.c -o {name}.o",
20+
".cpp": "g++ -std=c++11 {name}.cpp -o {name}.o",
21+
".c": "gcc {name}.c -o {name}.o",
22+
".hs": "ghc --make {name}.hs -O -v0 -rtsopts -outputdir dist",
2123
".ml": "ocamlbuild -lib unix {name}.native",
2224
".rs": "cargo build",
2325
}
@@ -26,6 +28,7 @@
2628
MAP_FILE_TYPE_TO_RUN = {
2729
".c": "{path}/{name}.o",
2830
".cpp": "{path}/{name}.o",
31+
".hs": "{path}/{name}",
2932
".js": "node {path}/{name}.js",
3033
".ml": "{path}/{name}.native",
3134
".py": "python {path}/{name}.py",
@@ -50,10 +53,11 @@ class HaliteArena(CodeArena):
5053
"""
5154
default_args: dict = {}
5255
submission: str = "submission"
56+
executable: str = "./environment/halite"
5357

5458
def __init__(self, config, **kwargs):
5559
super().__init__(config, **kwargs)
56-
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}"
5761
for arg, val in self.game_config.get("args", self.default_args).items():
5862
if isinstance(val, bool):
5963
if val:
@@ -91,13 +95,18 @@ def execute_round(self, agents: list[Player]):
9195
for future in tqdm(as_completed(futures), total=len(futures)):
9296
future.result()
9397

94-
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+
):
95105
winners = []
96-
pattern = r"Player\s#(\d+),\s(.*),\scame\sin\srank\s#(\d+)"
97106
for idx in range(self.game_config["sims_per_round"]):
98107
log_file = self.log_round(round_num) / HALITE_LOG.format(idx=idx)
99108
with open(log_file) as f:
100-
lines = f.readlines()[-len(agents) - 1 :]
109+
lines = f.readlines()[-len(agents) - 5 :]
101110
for line in lines:
102111
match = re.search(pattern, line)
103112
if match:
@@ -120,32 +129,47 @@ def get_results(self, agents: list[Player], round_num: int, stats: RoundStats):
120129
if player != RESULT_TIE:
121130
stats.player_stats[player].score = score
122131

123-
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]:
124138
# Check that the `submission/` folder exists
125139
exists_output = agent.environment.execute("test -d submission && echo 'exists'")["output"]
126140
if "exists" != exists_output.strip():
127141
return False, f"Submission folder `{self.submission}/` does not exist"
128142

129143
# Check that there is a *single* file called "main.<ext>" in the submission folder
130144
# and that <ext> is one of the supported file types
145+
found_main = False
131146
sub_path = Path(agent.environment.config.cwd) / self.submission
132-
ls_output = agent.environment.execute("ls", cwd=sub_path)["output"]
147+
ls_output = agent.environment.execute("ls", cwd=sub_path)["output"].splitlines()
133148
main_files = [
134-
fname
135-
for fname in ls_output.splitlines()
136-
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
137150
]
138-
supported_exts = "|".join(MAP_FILE_TYPE_TO_RUN.keys())
151+
139152
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())
140164
return (
141165
False,
142166
f"Exactly one main.[{supported_exts}] file must be present in submission, found {len(main_files)}",
143167
)
144168
main_ext = Path(main_files[0]).suffix
145169

146170
# Check that the submission compiles if necessary
147-
if main_ext in MAP_FILE_TYPE_TO_COMPILE:
148-
compile_cmd = MAP_FILE_TYPE_TO_COMPILE[main_ext].format(path="main", 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")
149173
try:
150174
compile_response = agent.environment.execute(compile_cmd, timeout=15, cwd=sub_path)
151175
except subprocess.TimeoutExpired:
@@ -157,8 +181,8 @@ def validate_code(self, agent: Player) -> tuple[bool, str | None]:
157181
)
158182

159183
# Check that submission runs in competition
160-
executable = MAP_FILE_TYPE_TO_RUN[main_ext].format(path=self.submission, name="main")
161-
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])}"
162186
try:
163187
run_response = agent.environment.execute(run_cmd, timeout=15)
164188
except subprocess.TimeoutExpired:
@@ -167,6 +191,6 @@ def validate_code(self, agent: Player) -> tuple[bool, str | None]:
167191
return False, f"Submission failed to run (ran {run_cmd}): {run_response['output']}"
168192

169193
# Record command to run executable to hidden file
170-
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")
171195
agent.environment.execute(f'echo "{executable_comp}" > {HALITE_HIDDEN_EXEC}')
172196
return True, None

codeclash/arenas/halite2/Halite-II.Dockerfile renamed to codeclash/arenas/halite2/Halite2.Dockerfile

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,24 @@ ENV PATH="/root/.cargo/bin:${PATH}"
1818
# Install ocaml
1919
RUN apt-get update && apt-get install -y ocaml ocamlbuild
2020

21+
# Install Haskell
22+
RUN apt-get update && apt-get install -y libgmp-dev \
23+
&& rm -rf /var/lib/apt/lists/*
24+
25+
# Install GHCup non-interactively
26+
RUN curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | \
27+
BOOTSTRAP_HASKELL_NONINTERACTIVE=1 BOOTSTRAP_HASKELL_ADJUST_BASHRC=1 sh
28+
29+
# Add ghcup to PATH
30+
ENV PATH="/root/.ghcup/bin:${PATH}"
31+
32+
# Verify installation
33+
RUN ghc --version && cabal --version
34+
2135
# Clone Halite repository
22-
RUN git clone https://github.com/CodeClash-ai/Halite-II.git /workspace \
36+
RUN git clone https://github.com/CodeClash-ai/Halite2.git /workspace \
2337
&& cd /workspace \
24-
&& git remote set-url origin https://github.com/CodeClash-ai/Halite-II.git \
25-
38+
&& git remote set-url origin https://github.com/CodeClash-ai/Halite2.git
2639
WORKDIR /workspace
2740

2841
RUN cd environment && cmake . && make

codeclash/arenas/halite2/halite2.py

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

33

44
class Halite2Arena(HaliteArena):
5-
name: str = "Halite-II"
6-
description: str = ""
7-
default_args: dict = {}
8-
submission: str = "submission"
5+
name: str = "Halite2"
6+
description: str = """Halite II is a multi-player AI-programming challenge in which bots pilot fleets of spaceships across a continuous space-themed universe.
7+
Players command ships to mine planets for halite, use that resource to build additional ships, and expand control across the map.
8+
Victory depends on efficient resource gathering, fleet management, and strategic expansion to outcompete rival bots for dominance.
9+
10+
You have the choice of writing your Halite bot in one of four programming languages: C++, Haskell, OCaml, or Rust.
11+
Example implementations can be found under the `airesources/` folder.
12+
Your submission should be stored in the `submission/` folder. This folder currently contains an example OCaml bot, but feel free to use any of the supported languages.
13+
Please make sure your main file is named `main.<ext>`, where `<ext>` is the appropriate file extension for your chosen programming language.
14+
You may include additional files as needed, but please ensure:
15+
1. The `submission/` folder contains only files relevant to your bot.
16+
2. The `submission/` folder ONLY contains a single bot (no multiple bots in one submission).
17+
3. Your bot can be compiled. See `runGame.sh` under the corresponding `submission/<language>/` folder to see how we will compile and run your bot.
18+
"""

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+
)

codeclash/tournaments/pvp.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from codeclash.agents import get_agent
1212
from codeclash.agents.player import Player
1313
from codeclash.agents.utils import GameContext
14-
from codeclash.arenas import get_game
14+
from codeclash.arenas import get_arena
1515
from codeclash.arenas.arena import CodeArena
1616
from codeclash.constants import DIR_LOGS, DIR_WORK, FILE_RESULTS, OPPONENT_CODEBASES_DIR_NAME
1717
from codeclash.tournaments.tournament import AbstractTournament
@@ -35,7 +35,7 @@ def __init__(
3535
self.logger.critical(f"Metadata file already exists: {self.metadata_file}")
3636
raise FileExistsError(f"Metadata file already exists: {self.metadata_file}")
3737
self.cleanup_on_end = cleanup
38-
self.game: CodeArena = get_game(
38+
self.game: CodeArena = get_arena(
3939
self.config,
4040
tournament_id=self.tournament_id,
4141
local_output_dir=self.local_output_dir,

codeclash/tournaments/single_player.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from codeclash.agents.dummy_agent import Dummy
1111
from codeclash.agents.player import Player
1212
from codeclash.agents.utils import GameContext
13-
from codeclash.arenas import get_game
13+
from codeclash.arenas import get_arena
1414
from codeclash.arenas.arena import CodeArena
1515
from codeclash.constants import DIR_WORK, FILE_RESULTS
1616
from codeclash.tournaments.tournament import AbstractTournament
@@ -24,7 +24,7 @@ class SinglePlayerTraining(AbstractTournament):
2424
def __init__(self, config: dict, *, output_dir: Path, cleanup: bool = False, keep_containers: bool = False):
2525
super().__init__(config, name="SinglePlayerTraining", output_dir=output_dir)
2626
self.cleanup_on_end = cleanup
27-
self.game: CodeArena = get_game(
27+
self.game: CodeArena = get_arena(
2828
self.config,
2929
tournament_id=self.tournament_id,
3030
local_output_dir=self.local_output_dir,

configs/test/halite2.yaml

Lines changed: 5 additions & 4 deletions
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
@@ -10,9 +10,10 @@ players:
1010
name: p2
1111
prompts:
1212
game_description: |
13-
You are a software developer ({{player_id}}) competing in an arena called Halite.
14-
In this game, you will write code to control ships that gather resources, build structures, and compete for dominance on a grid-based map.
15-
Victory is achieved by outmaneuvering opponents, optimizing resource collection, and strategically expanding your territory.
13+
You are a software developer ({{player_id}}) competing in an arena called Halite2.
14+
In this game, you will write code to control fleets of spaceships across a continuous space-themed universe.
15+
Players command ships to mine planets for halite, use that resource to build additional ships, and expand control across the map.
16+
Victory depends on efficient resource gathering, fleet management, and strategic expansion to outcompete rival bots for dominance.
1617
1718
The game is played in {{rounds}} rounds. For every round, you (and your competitor) edit program code that controls your bot. This is round {{round}}.
1819
After you and your competitor finish editing your codebases, the game is run automatically.

0 commit comments

Comments
 (0)