diff --git a/codeclash/arenas/__init__.py b/codeclash/arenas/__init__.py index 04324097..b1152843 100644 --- a/codeclash/arenas/__init__.py +++ b/codeclash/arenas/__init__.py @@ -4,6 +4,7 @@ from codeclash.arenas.bridge.bridge import BridgeArena from codeclash.arenas.corewar.corewar import CoreWarArena from codeclash.arenas.dummy.dummy import DummyArena +from codeclash.arenas.gomoku.gomoku import GomokuArena from codeclash.arenas.halite.halite import HaliteArena from codeclash.arenas.halite2.halite2 import Halite2Arena from codeclash.arenas.halite3.halite3 import Halite3Arena @@ -17,6 +18,7 @@ BridgeArena, CoreWarArena, DummyArena, + GomokuArena, HaliteArena, Halite2Arena, Halite3Arena, diff --git a/codeclash/arenas/gomoku/Gomoku.Dockerfile b/codeclash/arenas/gomoku/Gomoku.Dockerfile new file mode 100644 index 00000000..9a494c0b --- /dev/null +++ b/codeclash/arenas/gomoku/Gomoku.Dockerfile @@ -0,0 +1,18 @@ +FROM ubuntu:22.04 + +ENV DEBIAN_FRONTEND=noninteractive + +# Install Python 3.10, pip, and prerequisites +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + curl ca-certificates python3.10 python3.10-venv \ + python3-pip python-is-python3 wget git build-essential jq curl locales \ + && rm -rf /var/lib/apt/lists/* + +# Clone Gomoku game repository +RUN git clone https://github.com/CodeClash-ai/Gomoku.git /workspace \ + && cd /workspace \ + && git remote set-url origin https://github.com/CodeClash-ai/Gomoku.git +WORKDIR /workspace + +# No additional dependencies needed - engine uses only standard library diff --git a/codeclash/arenas/gomoku/__init__.py b/codeclash/arenas/gomoku/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/codeclash/arenas/gomoku/gomoku.py b/codeclash/arenas/gomoku/gomoku.py new file mode 100644 index 00000000..843d9828 --- /dev/null +++ b/codeclash/arenas/gomoku/gomoku.py @@ -0,0 +1,81 @@ +import re + +from codeclash.agents.player import Player +from codeclash.arenas.arena import CodeArena, RoundStats +from codeclash.constants import RESULT_TIE +from codeclash.utils.environment import assert_zero_exit_code + +GOMOKU_LOG = "result.log" + + +class GomokuArena(CodeArena): + name: str = "Gomoku" + submission: str = "main.py" + description: str = """Your bot (`main.py`) controls a Gomoku player on a 15x15 board. +Players take turns placing stones. Win by connecting 5 stones in a row (horizontally, vertically, or diagonally). +Black plays first. + +Your bot must implement: + def get_move(board: list[list[int]], color: str) -> tuple[int, int] + +Board representation: 0=empty, 1=black, 2=white +Color: "black" or "white" +""" + + def __init__(self, config, **kwargs): + super().__init__(config, **kwargs) + assert len(config["players"]) == 2, "Gomoku is a two-player game" + + def execute_round(self, agents: list[Player]) -> None: + args = [f"/{agent.name}/{self.submission}" for agent in agents] + cmd = f"python engine.py {' '.join(args)} -r {self.game_config['sims_per_round']} > {self.log_env / GOMOKU_LOG};" + self.logger.info(f"Running game: {cmd}") + assert_zero_exit_code(self.environment.execute(cmd)) + + def get_results(self, agents: list[Player], round_num: int, stats: RoundStats): + with open(self.log_round(round_num) / GOMOKU_LOG) as f: + round_log = f.read() + lines = round_log.split("FINAL_RESULTS")[-1].splitlines() + + scores = {} + for line in lines: + match = re.search(r"Bot\_(\d)\_main:\s(\d+)\srounds\swon", line) + if match: + bot_id = match.group(1) + rounds_won = int(match.group(2)) + scores[agents[int(bot_id) - 1].name] = rounds_won + + # Handle draws + draw_match = re.search(r"Draws:\s(\d+)", round_log) + if draw_match: + draws = int(draw_match.group(1)) + if draws > 0: + scores[RESULT_TIE] = draws + + stats.winner = max(scores, key=scores.get) if scores else "unknown" + # Check for tie (equal scores) + if scores: + max_score = max(scores.values()) + winners_with_max = [k for k, v in scores.items() if v == max_score and k != RESULT_TIE] + if len(winners_with_max) > 1: + stats.winner = RESULT_TIE + + stats.scores = scores + for player, score in scores.items(): + if player != RESULT_TIE: + stats.player_stats[player].score = score + + def validate_code(self, agent: Player) -> tuple[bool, str | None]: + if self.submission not in agent.environment.execute("ls")["output"]: + return False, f"No {self.submission} file found in the root directory" + + bot_content = agent.environment.execute(f"cat {self.submission}")["output"] + + if "def get_move(" not in bot_content: + return ( + False, + f"{self.submission} must define a get_move(board, color) function. " + "See the game description for the required signature." + ) + + return True, None diff --git a/configs/test/gomoku.yaml b/configs/test/gomoku.yaml new file mode 100644 index 00000000..a3b9648b --- /dev/null +++ b/configs/test/gomoku.yaml @@ -0,0 +1,25 @@ +tournament: + rounds: 3 +game: + name: Gomoku + sims_per_round: 10 + args: + board_size: 15 + timeout: 10 +players: +- agent: dummy + name: p1 +- agent: dummy + name: p2 +prompts: + game_description: | + You are a software developer ({{player_id}}) competing in a coding game called Gomoku. + Your bot (`main.py`) controls a Gomoku player on a 15x15 board. + Players take turns placing stones. Win by connecting 5 stones in a row (horizontally, vertically, or diagonally). + Black plays first. + + 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}}. + After you and your competitor finish editing your codebases, the game is run automatically. + + Your task: improve the bot in `main.py`, located in {{working_dir}}. + {{working_dir}} is your codebase, which contains both your bot and supporting assets.