66from typing import Any
77
88from minisweagent .environments .docker import DockerEnvironment
9- from pydantic import BaseModel
109
1110from codeclash .agents .player import Player
1211from codeclash .constants import DIR_LOGS , DIR_WORK , GH_ORG
1312from codeclash .utils .environment import assert_zero_exit_code , copy_between_containers , copy_from_container
1413from codeclash .utils .log import get_logger
1514
1615
17- class RoundStats (BaseModel ):
16+ class PlayerStats :
17+ def __init__ (self , name : str ):
18+ self .name = name
19+ self .invalid_reason : str | None = None
20+ self .score : float | None = None
21+ self .valid_submit = False
22+
23+ def to_dict (self ) -> dict [str , Any ]:
24+ return {
25+ "name" : self .name ,
26+ "invalid_reason" : self .invalid_reason ,
27+ "score" : self .score ,
28+ "valid_submit" : self .valid_submit ,
29+ }
30+
31+
32+ class RoundStats :
1833 winner : str
19- scores : dict [str , float ] # Map of player to game metric (e.g. # of wins, assets accumulated)
20- details : dict [str , Any ] | None = None # Optional, for game-specific info
34+ round_num : int
35+ scores : dict [str , float ]
36+ player_stats : dict [str , PlayerStats ] | None = None
37+
38+ def __init__ (self , round_num : int , agents : list [Player ]):
39+ self .winner = None
40+ self .round_num = round_num
41+ # Map of player to game metric (e.g. # of wins, assets accumulated)
42+ self .scores : dict [str , float ] = {}
43+ self .player_stats : dict [str , PlayerStats ] = {agent .name : PlayerStats (name = agent .name ) for agent in agents }
2144
2245 def __str__ (self ) -> str :
2346 return "\n " .join ([f"- Winner: { self .winner } " , f"- Scores: { self .scores } " ])
2447
48+ def to_dict (self ) -> dict [str , Any ]:
49+ result = {
50+ "round_num" : self .round_num ,
51+ "winner" : self .winner ,
52+ "scores" : self .scores ,
53+ }
54+ if self .player_stats :
55+ result ["player_stats" ] = {name : stats .to_dict () for name , stats in self .player_stats .items ()}
56+ return result
57+
2558
2659class CodeGame (ABC ):
2760 name : str
@@ -177,20 +210,32 @@ def run_round(self, agents: list[Player], round_num: int) -> RoundStats:
177210 Returns the log output, result output, and winner name. All bookkeeping should be
178211 handled by the tournament class.
179212 """
180- self ._pre_round_setup (agents )
181- self .execute_round (agents )
182- self .copy_logs_from_env (round_num )
183- return self .get_results (agents , round_num )
213+ stats = RoundStats (round_num , agents )
214+ validated = []
215+ for agent in agents :
216+ is_valid , error = self .validate_code (agent )
217+ if not is_valid :
218+ self .logger .warning (f"Agent { agent .name } failed verification: { error } " )
219+ stats .player_stats [agent .name ].invalid_reason = error
220+ continue
221+ stats .player_stats [agent .name ].valid_submit = True
222+ validated .append (agent )
223+
224+ run_game = len (validated ) > 1
225+ if run_game :
226+ self ._pre_round_setup (validated )
227+ self .execute_round (validated )
228+ self .copy_logs_from_env (round_num )
229+ self .get_results (validated , round_num , stats )
230+ return stats
184231
185232 @abstractmethod
186- def get_results (self , agents : list [Player ], round_num : int ) -> RoundStats :
233+ def get_results (self , agents : list [Player ], round_num : int , stats : RoundStats ) :
187234 """Determine the winner of the game based on the result output.
235+ Modifies the stats object in place.
188236
189237 Args:
190238 agents: List of agents participating in the round
191-
192- Returns:
193- RoundStats object
194239 """
195240 pass
196241
@@ -201,3 +246,16 @@ def execute_round(self, agents: list[Player]):
201246 includes the pre-round setup, post-round setup, and winner determination.
202247 """
203248 pass
249+
250+ @abstractmethod
251+ def validate_code (self , agent : Player ) -> tuple [bool , str | None ]:
252+ """Verify that the given agent can be run by the game.
253+
254+ Args:
255+ agent: The agent to verify
256+
257+ Returns:
258+ Boolean indicating whether the agent passed verification
259+ Optional string indicating reason for failure
260+ """
261+ pass
0 commit comments