@@ -23,21 +23,30 @@ def __init__(self, config, *, tournament_id: str, local_output_dir: Path):
2323 self .run_cmd_round += f" --{ arg } "
2424 else :
2525 self .run_cmd_round += f" --{ arg } { val } "
26+ self ._failed_to_start_player = []
2627
27- def _wait_for_ports (self , ports : list [int ], timeout : float = 3.0 ) -> None :
28- """Wait for all ports to be available, up to timeout seconds."""
28+ def _wait_for_ports (self , requested_ports : list [int ], timeout : float = 3.0 ) -> list [int ]:
29+ """Wait for ports to be served, up to timeout seconds.
30+
31+ Returns:
32+ List of ports that are actually served after timeout.
33+ """
2934 start_time = time .time ()
35+ available_ports = set ()
36+
3037 while time .time () - start_time < timeout :
31- for port in ports :
32- result = self .environment .execute (f"nc -z 0.0.0.0 { port } " )
33- if result ["returncode" ] != 0 :
34- break
35- else :
36- # All ports are ready (loop completed without break)
37- return
38+ for port in set ( requested_ports ) - available_ports :
39+ result = self .environment .execute (f"wget -S --spider --timeout=1 http://localhost: { port } / 2>&1 " )
40+ if result ["returncode" ] == 0 or "200 OK" in result [ "output" ] or "HTTP/" in result [ "output" ] :
41+ available_ports . add ( port )
42+
43+ if len ( available_ports ) == len ( requested_ports ):
44+ return list ( available_ports )
3845
3946 time .sleep (0.1 )
4047
48+ return list (available_ports )
49+
4150 def _run_single_simulation (self , cmd : str , idx : int ) -> tuple [str , str ]:
4251 """Run a single battlesnake simulation and return log and result outputs."""
4352 assert_zero_exit_code (
@@ -50,17 +59,28 @@ def _run_single_simulation(self, cmd: str, idx: int) -> tuple[str, str]:
5059 def execute_round (self , agents : list [Player ]):
5160 self .logger .debug ("Starting game servers" )
5261 cmd = []
53- ports = []
62+ player2port = {}
5463 for idx , agent in enumerate (agents ):
5564 port = 8001 + idx
56- ports . append ( port )
65+ player2port [ agent . name ] = port
5766 # Surprisingly slow despite using &
5867 # Start server in background - just add & to run in background!
5968 self .environment .execute (f"PORT={ port } python main.py &" , cwd = f"/{ agent .name } " )
6069 cmd .append (f"--url http://0.0.0.0:{ port } -n { agent .name } " )
6170
62- self .logger .debug (f"Waiting for ports: { ports } " )
63- self ._wait_for_ports (ports )
71+ self .logger .debug (f"Waiting for ports: { player2port } " )
72+ available_ports = self ._wait_for_ports (list (player2port .values ()))
73+
74+ if not available_ports :
75+ raise RuntimeError ("All games failed to start" )
76+
77+ if len (available_ports ) == 1 :
78+ missing_ports = set (player2port .values ()) - set (available_ports )
79+ player = next (player for player , port in player2port .items () if port in missing_ports )
80+ self .logger .warning (f"Player { player } failed to start" )
81+ self ._failed_to_start_player .append (player )
82+ return
83+
6484 self .logger .debug ("All ports are ready" )
6585
6686 try :
@@ -84,12 +104,20 @@ def execute_round(self, agents: list[Player]):
84104
85105 def get_results (self , agents : list [Player ], round_num : int , stats : RoundStats ):
86106 scores = {}
87- for idx in range (self .game_config ["sims_per_round" ]):
88- with open (self .log_round (round_num ) / f"sim_{ idx } .jsonl" ) as f :
89- lines = f .read ().strip ().split ("\n " )
90- results = json .loads (lines [- 1 ]) # Get the last line which contains the game result
91- winner = RESULT_TIE if results ["isDraw" ] else results ["winnerName" ]
92- scores [winner ] = scores .get (winner , 0 ) + 1
107+ available_players = [player .name for player in agents if player .name not in self ._failed_to_start_player ]
108+ if len (available_players ) > 1 :
109+ # We ran the game
110+ for idx in range (self .game_config ["sims_per_round" ]):
111+ with open (self .log_round (round_num ) / f"sim_{ idx } .jsonl" ) as f :
112+ lines = f .read ().strip ().split ("\n " )
113+ results = json .loads (lines [- 1 ]) # Get the last line which contains the game result
114+ winner = RESULT_TIE if results ["isDraw" ] else results ["winnerName" ]
115+ scores [winner ] = scores .get (winner , 0 ) + 1
116+ else :
117+ self .logger .warning (f"Only one player ({ available_players [0 ]} ) started, giving them the win" )
118+ # We didn't run a game, so we just give the one player the win
119+ available_player = available_players [0 ]
120+ scores = {available_player : self .game_config ["sims_per_round" ]}
93121
94122 winner = max (scores , key = scores .get )
95123 winner = RESULT_TIE if list (scores .values ()).count (scores [winner ]) > 1 else winner
0 commit comments