description:str='Your bot (`main.py`) controls a snake on a grid-based board.\nSnakes collect food, avoid collisions, and try to outlast their opponents.'
defexecute_round(self,agents:list[Player]):self._failed_to_start_player=[]assertlen(agents)>1,"Battlesnake requires at least two players"self.logger.debug("Starting game servers")player2port={}foridx,agentinenumerate(agents):port=8001+idxplayer2port[agent.name]=port# Start server in background (& ). Submission may be Python (main.py) or any# other language via a run.sh launch script.self.environment.execute(f"PORT={port}{self._start_cmd(agent)} &",cwd=f"/{agent.name}")self.logger.debug(f"Waiting for ports: {player2port}")available_ports=self._wait_for_ports(list(player2port.values()))ifnotavailable_ports:raiseRuntimeError("All games failed to start")iflen(available_ports)==1:missing_ports=set(player2port.values())-set(available_ports)missing_player=next(playerforplayer,portinplayer2port.items()ifportinmissing_ports)self.logger.warning(f"Player {missing_player} failed to start")self._failed_to_start_player.append(missing_player)returniflen(available_ports)<len(agents):raiseRuntimeError(f"Only {len(available_ports)} players started: {available_ports}")self.logger.debug("All ports are ready")try:self.logger.info(f"Running game with players: {list(player2port.keys())}")# Use ThreadPoolExecutor for parallel execution. Concurrency is configurable# (game.sim_concurrency): the single-threaded bot servers serialize move# requests, so total concurrency across all parallel pairs must stay bounded or# responses exceed the move timeout and games degenerate. When running many pairs# concurrently (ladder --workers), lower this so workers*sim_concurrency stays ~20.max_sim_workers=self.game_config.get("sim_concurrency",20)withThreadPoolExecutor(max_sim_workers)asexecutor:# Submit all simulations to the thread poolfutures=[executor.submit(self._run_single_simulation,player2port,idx)foridxinrange(self.game_config["sims_per_round"])]# Collect results as they completeforfutureintqdm(as_completed(futures),total=len(futures)):future.result()finally:# Kill all servers started this round (any language) so ports free up for the# next round. pkill covers the Python starter; fuser frees each game port for# compiled/interpreted servers launched via run.sh.self.environment.execute(f"pkill -f 'python {self.submission}' || true")forportinplayer2port.values():self.environment.execute(f"fuser -k {port}/tcp 2>/dev/null || true")
defget_results(self,agents:list[Player],round_num:int,stats:RoundStats):scores=defaultdict(int)available_players=[player.nameforplayerinagentsifplayer.namenotinself._failed_to_start_player]iflen(available_players)>1:# We ran the gameforidxinrange(self.game_config["sims_per_round"]):try:withopen(self.log_round(round_num)/f"sim_{idx}.jsonl")asf:lines=f.read().strip().split("\n")results=json.loads(lines[-1])# Get the last line which contains the game resultwinner=RESULT_TIEifresults["isDraw"]elseresults["winnerName"]scores[winner]+=1exceptFileNotFoundError:self.logger.warning(f"Simulation {idx} not found, skipping")exceptjson.JSONDecodeError:self.logger.warning(f"Simulation {idx} is not a valid JSON, skipping")else:self.logger.warning(f"Only one player ({available_players[0]}) started, giving them the win")# We didn't run a game, so we just give the one player the winavailable_player=available_players[0]scores={available_player:self.game_config["sims_per_round"]}winner=max(scores,key=scores.get)winner=RESULT_TIEiflist(scores.values()).count(scores[winner])>1elsewinnerstats.winner=winnerstats.scores=scoresforplayer,scoreinscores.items():ifplayer!=RESULT_TIE:stats.player_stats[player].score=score
defvalidate_code(self,agent:Player)->tuple[bool,str|None]:listing=agent.environment.execute("ls")["output"]# Non-Python submissions declare how to launch their server via run.sh (any# language). We trust the launch script here; a broken one is caught at runtime# by _wait_for_ports (failed-to-start -> forfeit).if"run.sh"inlisting:returnTrue,Noneifself.submissionnotinlisting:returnFalse,f"No {self.submission} file found in the root directory"# note: no longer calling splitlinesbot_content=agent.environment.execute(f"cat {self.submission}")["output"]error_msg=[]forfuncin["def info(","def start(","def end(","def move(",]:iffuncnotinbot_content:error_msg.append(f"There should be a `{func}` function implemented in `{self.submission}`")iflen(error_msg)>0:returnFalse,"\n".join(error_msg+["Don't change the function signatures!"])if"__main__"notinbot_content:returnFalse,(f'`{self.submission}` must keep its `if __name__ == "__main__"` block that starts '"the server, or the bot fails to launch.")returnTrue,None