| 70 | } |
| 71 | |
| 72 | |
| 73 | class CodeArena(ABC): |
| 74 | name: str |
| 75 | description: str |
| 76 | default_args: dict = {} |
| 77 | submission: str |
| 78 | |
| 79 | # Serializes image builds across concurrent pairs (e.g. `ladder make --workers N`), so |
| 80 | # worker threads don't all race `docker build` on a cold start and fail with "already exists". |
| 81 | _build_lock = threading.Lock() |
| 82 | |
| 83 | def __init__(self, config: dict, *, tournament_id: str, local_output_dir: Path, keep_containers: bool = False): |
| 84 | """The CodeArena class is responsible for running games, i.e., taking a list of code |
| 85 | from different agents/players and running them against each other. |
| 86 | It also provides the environments for the game and agents to run in. |
| 87 | |
| 88 | The central method is `run_round`, which takes a list of agents and returns the winner of the round. |
| 89 | |
| 90 | At the end of the the tournament, run the `end` method to clean up the game and agents and write the metadata. |
| 91 | |
| 92 | Args: |
| 93 | config: The overall config for the tournament. |
| 94 | tournament_id: The id of the tournament. |
| 95 | local_output_dir: The host/local directory to write logs to. |
| 96 | keep_containers: Do not remove containers after games/agent finish. |
| 97 | """ |
| 98 | self.url_gh: str = f"git@github.com:{GH_ORG}/{self.name}.git" |
| 99 | self.artifacts: list[Path] = [] |
| 100 | """Artifact objects that we might want to clean up after the game.""" |
| 101 | self.config: dict = config |
| 102 | self._keep_containers: bool = keep_containers |
| 103 | self._metadata: dict = { |
| 104 | "name": self.name, |
| 105 | "config": self.config["game"], |
| 106 | "game_id": tournament_id, |
| 107 | "created_timestamp": int(time.time()), |
| 108 | } |
| 109 | self.log_env: Path = DIR_LOGS |
| 110 | self.log_local: Path = local_output_dir |
| 111 | self.logger = get_logger(self.name, log_path=self.log_local / "game.log", emoji="🏓") |
| 112 | self.environment: DockerEnvironment = self.get_environment() |
| 113 | """The running docker environment for executing the game""" |
| 114 | |
| 115 | @property |
| 116 | def game_config(self) -> dict: |
| 117 | return self.config["game"] |
| 118 | |
| 119 | @property |
| 120 | def game_id(self) -> str: |
| 121 | return self._metadata["game_id"] |
| 122 | |
| 123 | @property |
| 124 | def image_name(self) -> str: |
| 125 | return f"codeclash/{self.name.lower()}" |
| 126 | |
| 127 | def build_image(self): |
| 128 | """ |
| 129 | Build a Docker image for the game using the Dockerfile in the codebase. |
nothing calls this directly
no outgoing calls
no test coverage detected