|
3 | 3 | import subprocess |
4 | 4 | from collections import Counter |
5 | 5 | from concurrent.futures import ThreadPoolExecutor, as_completed |
| 6 | +from pathlib import Path |
6 | 7 |
|
7 | 8 | from tqdm.auto import tqdm |
8 | 9 |
|
|
11 | 12 | from codeclash.games.game import CodeGame, RoundStats |
12 | 13 |
|
13 | 14 | HALITE_LOG = "sim_{idx}.log" |
| 15 | +HALITE_SUBMISSION = "submission" |
| 16 | +HALITE_HIDDEN_EXEC = ".codeclash_exec" |
| 17 | + |
| 18 | +# Command to be run in each agent's `submission/` folder to compile agent |
| 19 | +MAP_FILE_TYPE_TO_COMPILE = { |
| 20 | + ".cpp": "g++ -std=c++11 {path}.cpp -o {name}.o", |
| 21 | + ".c": "gcc {path}.c -o {name}.o", |
| 22 | + ".ml": "ocamlbuild -lib unix {name}.native", |
| 23 | + ".rs": "cargo build", |
| 24 | +} |
| 25 | + |
| 26 | +# Command to be run from `environment/` folder to run competition |
| 27 | +MAP_FILE_TYPE_TO_RUN = { |
| 28 | + ".c": "{path}/{name}.o", |
| 29 | + ".cpp": "{path}/{name}.o", |
| 30 | + ".js": "node {path}/{name}.js", |
| 31 | + ".ml": "{path}/{name}.native", |
| 32 | + ".py": "python {path}/{name}.py", |
| 33 | + ".rs": "{path}/target/debug/{name}", |
| 34 | +} |
14 | 35 |
|
15 | 36 |
|
16 | 37 | class HaliteGame(CodeGame): |
17 | 38 | name: str = "Halite" |
18 | | - description: str = """Halite is a strategic programming game where players write bots to control ships that gather resources, build structures, and compete for dominance on a grid-based map. |
19 | | -Victory is achieved by outmaneuvering opponents, optimizing resource collection, and strategically expanding your territory""" |
| 39 | + description: str = f"""Halite is a multi-player turn-based strategy game where bots compete on a rectangular grid to capture territory and accumulate strength. |
| 40 | +Players control pieces that can move across the map to conquer neutral and enemy territory, with each cell providing production that increases the strength of pieces occupying it. |
| 41 | +The goal is to control the most territory by the end of the game through strategic expansion, consolidation of forces, and tactical combat decisions. |
| 42 | +
|
| 43 | +You have the choice of writing your Halite bot in one of four programming languages: C, C++, OCaml, or Rust. |
| 44 | +Example implementations can be found under the `airesources/` folder. |
| 45 | +Your submission should be stored in the `{HALITE_SUBMISSION}/` folder. This folder currently contains an example C bot, but feel free to use any of the supported languages. |
| 46 | +Please make sure your main file is named `main.<ext>`, where `<ext>` is the appropriate file extension for your chosen programming language. |
| 47 | +You may include additional files as needed, but please ensure: |
| 48 | +1. The `submission/` folder contains only files relevant to your bot. |
| 49 | +2. The `submission/` folder ONLY contains a single bot (no multiple bots in one submission). |
| 50 | +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. |
| 51 | +""" |
20 | 52 | default_args: dict = {} |
21 | 53 |
|
22 | 54 | def __init__(self, config, **kwargs): |
@@ -47,10 +79,11 @@ def _run_single_simulation(self, agents: list[Player], idx: int, cmd: str): |
47 | 79 | def execute_round(self, agents: list[Player]): |
48 | 80 | entries = [] |
49 | 81 | for agent in agents: |
50 | | - entries.append(f"python /{agent.name}/airesources/Python/RandomBot.py") |
| 82 | + executable = agent.environment.execute(f"cat {HALITE_HIDDEN_EXEC}")["output"].strip() |
| 83 | + entries.append(executable) |
51 | 84 | cmd = f"{self.run_cmd_round} {shlex.join(entries)}" |
52 | 85 | self.logger.info(f"Running game: {cmd}") |
53 | | - with ThreadPoolExecutor(5) as executor: |
| 86 | + with ThreadPoolExecutor(20) as executor: |
54 | 87 | futures = [ |
55 | 88 | executor.submit(self._run_single_simulation, agents, idx, cmd) |
56 | 89 | for idx in range(self.game_config["sims_per_round"]) |
@@ -88,4 +121,47 @@ def get_results(self, agents: list[Player], round_num: int, stats: RoundStats): |
88 | 121 | stats.player_stats[player].score = score |
89 | 122 |
|
90 | 123 | def validate_code(self, agent: Player) -> tuple[bool, str | None]: |
| 124 | + # Check that there is a *single* file called "main.<ext>" in the submission folder |
| 125 | + # and that <ext> is one of the supported file types |
| 126 | + sub_path = Path(agent.environment.config.cwd) / HALITE_SUBMISSION |
| 127 | + ls_output = agent.environment.execute("ls", cwd=sub_path)["output"] |
| 128 | + main_files = [ |
| 129 | + fname |
| 130 | + for fname in ls_output.splitlines() |
| 131 | + if fname.startswith("main.") and Path(fname).suffix in MAP_FILE_TYPE_TO_RUN |
| 132 | + ] |
| 133 | + supported_exts = "|".join(MAP_FILE_TYPE_TO_RUN.keys()) |
| 134 | + if len(main_files) != 1: |
| 135 | + return ( |
| 136 | + False, |
| 137 | + f"Exactly one main.[{supported_exts}] file must be present in submission, found {len(main_files)}", |
| 138 | + ) |
| 139 | + main_ext = Path(main_files[0]).suffix |
| 140 | + |
| 141 | + # Check that the submission compiles if necessary |
| 142 | + if main_ext in MAP_FILE_TYPE_TO_COMPILE: |
| 143 | + compile_cmd = MAP_FILE_TYPE_TO_COMPILE[main_ext].format(path="main", name="main") |
| 144 | + try: |
| 145 | + compile_response = agent.environment.execute(compile_cmd, timeout=15, cwd=sub_path) |
| 146 | + except subprocess.TimeoutExpired: |
| 147 | + return False, f"Compilation failed (ran {compile_cmd} inside {HALITE_SUBMISSION}): timed out" |
| 148 | + if compile_response["returncode"] != 0: |
| 149 | + return ( |
| 150 | + False, |
| 151 | + f"Compilation failed (ran {compile_cmd} inside {HALITE_SUBMISSION}): {compile_response['output']}", |
| 152 | + ) |
| 153 | + |
| 154 | + # Check that submission runs in competition |
| 155 | + executable = MAP_FILE_TYPE_TO_RUN[main_ext].format(path=HALITE_SUBMISSION, name="main") |
| 156 | + run_cmd = f"./environment/halite {shlex.join([executable, executable])}" |
| 157 | + try: |
| 158 | + run_response = agent.environment.execute(run_cmd, timeout=15) |
| 159 | + except subprocess.TimeoutExpired: |
| 160 | + return False, f"Submission failed to run (ran {run_cmd}): timed out" |
| 161 | + if run_response["returncode"] != 0: |
| 162 | + return False, f"Submission failed to run (ran {run_cmd}): {run_response['output']}" |
| 163 | + |
| 164 | + # Record command to run executable to hidden file |
| 165 | + executable_comp = MAP_FILE_TYPE_TO_RUN[main_ext].format(path=f"/{agent.name}/{HALITE_SUBMISSION}", name="main") |
| 166 | + agent.environment.execute(f'echo "{executable_comp}" > {HALITE_HIDDEN_EXEC}') |
91 | 167 | return True, None |
0 commit comments