Skip to content

Halite

Resource collection and strategy game where bots compete to mine halite from a grid.

Overview

Halite is a resource management game where players write bots to collect halite (resources) more efficiently than opponents.

Game Rules

  • Bots move on a grid collecting halite
  • Depositing halite at dropoff points scores points
  • Ships can collide and destroy each other
  • Most halite collected wins

Configuration Example

game:
  name: Halite
  rounds: 10
  sims_per_round: 3
  timeout: 400

players:
  - name: Miner1
    model: gpt-4
  - name: Miner2
    model: claude-3

Resources

Implementation

codeclash.arenas.halite.halite.HaliteArena

HaliteArena(config, **kwargs)

Bases: CodeArena

Source code in codeclash/arenas/halite/halite.py
54
55
56
57
58
59
60
61
62
def __init__(self, config, **kwargs):
    super().__init__(config, **kwargs)
    self.run_cmd_round: str = f"./environment/halite --replaydirectory {self.log_env}"
    for arg, val in self.game_config.get("args", self.default_args).items():
        if isinstance(val, bool):
            if val:
                self.run_cmd_round += f" --{arg}"
        else:
            self.run_cmd_round += f" --{arg} {val}"

name class-attribute instance-attribute

name: str = 'Halite'

description class-attribute instance-attribute

description: str = 'Halite is a multi-player turn-based strategy game where bots compete on a rectangular grid to capture territory and accumulate strength.\nPlayers 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.\nThe goal is to control the most territory by the end of the game through strategic expansion, consolidation of forces, and tactical combat decisions.\n\nYou have the choice of writing your Halite bot in one of four programming languages: C, C++, OCaml, or Rust.\nExample implementations can be found under the `airesources/` folder.\nYour submission should be stored in the `submission/` folder. This folder currently contains an example C bot, but feel free to use any of the supported languages.\nPlease make sure your main file is named `main.<ext>`, where `<ext>` is the appropriate file extension for your chosen programming language.\nYou may include additional files as needed, but please ensure:\n1. The `submission/` folder contains only files relevant to your bot.\n2. The `submission/` folder ONLY contains a single bot (no multiple bots in one submission).\n3. Your bot can be compiled. See `runGame.sh` under the corresponding `submission/<language>/` folder to see how we will compile and run your bot.\n'

default_args class-attribute instance-attribute

default_args: dict = {}

submission class-attribute instance-attribute

submission: str = 'submission'

run_cmd_round instance-attribute

run_cmd_round: str = f'./environment/halite --replaydirectory {log_env}'

execute_round

execute_round(agents: list[Player])
Source code in codeclash/arenas/halite/halite.py
79
80
81
82
83
84
85
86
87
88
89
90
91
92
def execute_round(self, agents: list[Player]):
    entries = []
    for agent in agents:
        executable = agent.environment.execute(f"cat {HALITE_HIDDEN_EXEC}")["output"].strip()
        entries.append(executable)
    cmd = f"{self.run_cmd_round} {shlex.join(entries)}"
    self.logger.info(f"Running game: {cmd}")
    with ThreadPoolExecutor(20) as executor:
        futures = [
            executor.submit(self._run_single_simulation, agents, idx, cmd)
            for idx in range(self.game_config["sims_per_round"])
        ]
        for future in tqdm(as_completed(futures), total=len(futures)):
            future.result()

get_results

get_results(agents: list[Player], round_num: int, stats: RoundStats)
Source code in codeclash/arenas/halite/halite.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
def get_results(self, agents: list[Player], round_num: int, stats: RoundStats):
    winners = []
    pattern = r"Player\s#(\d+),\s(.*),\scame\sin\srank\s#(\d+)"
    for idx in range(self.game_config["sims_per_round"]):
        log_file = self.log_round(round_num) / HALITE_LOG.format(idx=idx)
        with open(log_file) as f:
            lines = f.readlines()[-len(agents) - 1 :]
            for line in lines:
                match = re.search(pattern, line)
                if match:
                    player_idx = int(match.group(1)) - 1
                    rank = int(match.group(3))
                    if rank == 1:
                        winners.append(agents[player_idx].name)

    # Count wins
    win_counts = Counter(winners)

    # Find all winners with the maximum count
    max_wins = max(win_counts.values(), default=0)
    overall_winners = [name for name, count in win_counts.items() if count == max_wins]

    # Update stats
    stats.winner = RESULT_TIE if len(overall_winners) > 1 else overall_winners[0]
    stats.scores = dict(win_counts)
    for player, score in win_counts.items():
        if player != RESULT_TIE:
            stats.player_stats[player].score = score

validate_code

validate_code(agent: Player) -> tuple[bool, str | None]
Source code in codeclash/arenas/halite/halite.py
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
def validate_code(self, agent: Player) -> tuple[bool, str | None]:
    # Check that the `submission/` folder exists
    exists_output = agent.environment.execute("test -d submission && echo 'exists'")["output"]
    if "exists" != exists_output.strip():
        return False, f"Submission folder `{self.submission}/` does not exist"

    # Check that there is a *single* file called "main.<ext>" in the submission folder
    # and that <ext> is one of the supported file types
    sub_path = Path(agent.environment.config.cwd) / self.submission
    ls_output = agent.environment.execute("ls", cwd=sub_path)["output"]
    main_files = [
        fname
        for fname in ls_output.splitlines()
        if fname.startswith("main.") and Path(fname).suffix in MAP_FILE_TYPE_TO_RUN
    ]
    supported_exts = "|".join(MAP_FILE_TYPE_TO_RUN.keys())
    if len(main_files) != 1:
        return (
            False,
            f"Exactly one main.[{supported_exts}] file must be present in submission, found {len(main_files)}",
        )
    main_ext = Path(main_files[0]).suffix

    # Check that the submission compiles if necessary
    if main_ext in MAP_FILE_TYPE_TO_COMPILE:
        compile_cmd = MAP_FILE_TYPE_TO_COMPILE[main_ext].format(path="main", name="main")
        try:
            compile_response = agent.environment.execute(compile_cmd, timeout=15, cwd=sub_path)
        except subprocess.TimeoutExpired:
            return False, f"Compilation failed (ran {compile_cmd} inside {self.submission}): timed out"
        if compile_response["returncode"] != 0:
            return (
                False,
                f"Compilation failed (ran {compile_cmd} inside {self.submission}): {compile_response['output']}",
            )

    # Check that submission runs in competition
    executable = MAP_FILE_TYPE_TO_RUN[main_ext].format(path=self.submission, name="main")
    run_cmd = f"./environment/halite {shlex.join([executable, executable])}"
    try:
        run_response = agent.environment.execute(run_cmd, timeout=15)
    except subprocess.TimeoutExpired:
        return False, f"Submission failed to run (ran {run_cmd}): timed out"
    if run_response["returncode"] != 0:
        return False, f"Submission failed to run (ran {run_cmd}): {run_response['output']}"

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