def __init__(
self,
config: dict,
environment: DockerEnvironment,
game_context: GameContext,
) -> None:
self.config = config
self.name = config["name"]
self._player_unique_id = str(uuid.uuid4())
"""Unique ID that doesn't clash even across multiple games. Used for git tags."""
self.environment = environment
self.game_context = game_context
self.push = config.get("push", False)
# Force the branch push (used on ladder resume, to overwrite an interrupted rung's partial
# pushes). Safe here because a run's push branch has a single writer. Uses --force-with-lease.
self._force_push = config.get("force_push", False)
self.logger = get_logger(
self.name,
log_path=self.game_context.log_local / "players" / self.name / "player.log",
emoji="👤",
)
self._branch_name = config.get("branch", f"{self.game_context.id}.{self.name}")
self._metadata = {
"name": self.name,
"player_unique_id": self._player_unique_id,
"created_timestamp": int(time.time()),
"config": self.config,
"initial_commit_hash": self._get_commit_hash(),
"branch_name": self._branch_name,
"round_tags": {}, # mapping round -> tag
"agent_stats": {}, # mapping round -> agent stats
}
if self.push:
self.logger.info("Will push agent gameplay as branch to remote repository after each round")
token = os.getenv("GITHUB_TOKEN")
if not token:
raise ValueError("GITHUB_TOKEN environment variable is required")
for cmd in [
"git remote remove origin",
f"git remote add origin https://x-access-token:{token}@github.com/{GH_ORG}/{self.game_context.name}.git",
]:
assert_zero_exit_code(self.environment.execute(cmd), logger=self.logger)
# Handle branch initialization
if branch_init := config.get("branch_init"):
# Fetch, then check out the initial branch (creating a tracking branch if needed).
assert_zero_exit_code(
self.environment.execute(f"git fetch origin && git checkout {branch_init}"),
logger=self.logger,
)
self.logger.info(f"Checked out initial branch {branch_init}")
if self._branch_name != branch_init:
self.logger.info(f"Switching to branch {self._branch_name} for pushing changes")
if branch_init:
# Start the push branch at branch_init. get_environment() pre-created a
# same-named branch at the default branch; a plain checkout would revert the
# working tree to it, so use -B to re-point it at the current HEAD.
assert_zero_exit_code(
self.environment.execute(f"git checkout -B {self._branch_name}"),
logger=self.logger,
)
else:
# Resume the branch if a previous rung/round pushed it to the remote, else create it.
# Reset the local branch to the fetched remote tip with -B.
assert_zero_exit_code(self.environment.execute("git fetch origin"), logger=self.logger)
if (
self.environment.execute(f"git rev-parse --verify --quiet origin/{self._branch_name}").get(
"returncode", 1
)
== 0
):
assert_zero_exit_code(
self.environment.execute(f"git checkout -B {self._branch_name} origin/{self._branch_name}"),
logger=self.logger,
)
else:
self.logger.info(f"Branch {self._branch_name} doesn't exist on remote, creating it")
assert_zero_exit_code(
self.environment.execute(f"git checkout -B {self._branch_name}"),
logger=self.logger,
)