33from dataclasses import dataclass
44from pathlib import Path
55
6+ from codeclash .constants import DIR_LOGS
7+
68K_FACTOR = 32 # ELO constant, changeable
79
810
@@ -19,44 +21,51 @@ def expected_score(rating_a, rating_b):
1921
2022
2123def main (log_dir : Path ):
24+ # Assuming directory structure is:
25+ # logs/<user_id>/<game_id>
26+ # - players/
27+ # - rounds/
28+ # - game.log
29+ # - metadata.json
2230 player_profiles = {}
23- for game_log_folder in log_dir .iterdir ():
24- if game_log_folder .is_dir ():
25- print (f"Processing game log folder: { game_log_folder } " )
31+ for user_folder in log_dir .iterdir ():
32+ for game_log_folder in user_folder .iterdir ():
33+ if game_log_folder .is_dir ():
34+ print (f"Processing game log folder: { game_log_folder } " )
2635
27- game_id = game_log_folder .name .split ("." )[1 ]
28- player_ids = [x .name for x in (game_log_folder / "players" ).iterdir () if x .is_dir ()]
29- # Initialize profiles
30- for player in player_ids :
31- key = f"{ game_id } .{ player } "
32- if key not in player_profiles :
33- player_profiles [key ] = PlayerEloProfile (player_id = player , game_id = game_id )
36+ game_id = game_log_folder .name .split ("." )[1 ]
37+ player_ids = [x .name for x in (game_log_folder / "players" ).iterdir () if x .is_dir ()]
38+ # Initialize profiles
39+ for player in player_ids :
40+ key = f"{ game_id } .{ player } "
41+ if key not in player_profiles :
42+ player_profiles [key ] = PlayerEloProfile (player_id = player , game_id = game_id )
3443
35- for round_folder in (game_log_folder / "rounds" ).iterdir ():
36- round_results = json .load (open (round_folder / "results.json" ))
37- winner = round_results .get ("winner" )
38- players = round_results .get ("players" , player_ids )
39- # Only process if there are exactly 2 players
40- if len (players ) == 2 :
41- p1_key = f"{ game_id } .{ players [0 ]} "
42- p2_key = f"{ game_id } .{ players [1 ]} "
43- p1 = player_profiles [p1_key ]
44- p2 = player_profiles [p2_key ]
45- p1 .games_played += 1
46- p2 .games_played += 1
47- # Determine scores
48- if winner == players [0 ]:
49- s1 , s2 = 1 , 0
50- elif winner == players [1 ]:
51- s1 , s2 = 0 , 1
52- else :
53- s1 , s2 = 0.5 , 0.5 # Tie
54- # Calculate expected scores
55- e1 = expected_score (p1 .rating , p2 .rating )
56- e2 = expected_score (p2 .rating , p1 .rating )
57- # Update ratings
58- p1 .rating += K_FACTOR * (s1 - e1 )
59- p2 .rating += K_FACTOR * (s2 - e2 )
44+ for round_folder in (game_log_folder / "rounds" ).iterdir ():
45+ round_results = json .load (open (round_folder / "results.json" ))
46+ winner = round_results .get ("winner" )
47+ players = round_results .get ("players" , player_ids )
48+ # Only process if there are exactly 2 players
49+ if len (players ) == 2 :
50+ p1_key = f"{ game_id } .{ players [0 ]} "
51+ p2_key = f"{ game_id } .{ players [1 ]} "
52+ p1 = player_profiles [p1_key ]
53+ p2 = player_profiles [p2_key ]
54+ p1 .games_played += 1
55+ p2 .games_played += 1
56+ # Determine scores
57+ if winner == players [0 ]:
58+ s1 , s2 = 1 , 0
59+ elif winner == players [1 ]:
60+ s1 , s2 = 0 , 1
61+ else :
62+ s1 , s2 = 0.5 , 0.5 # Tie
63+ # Calculate expected scores
64+ e1 = expected_score (p1 .rating , p2 .rating )
65+ e2 = expected_score (p2 .rating , p1 .rating )
66+ # Update ratings
67+ p1 .rating += K_FACTOR * (s1 - e1 )
68+ p2 .rating += K_FACTOR * (s2 - e2 )
6069
6170 print ("Player ELO profiles:" )
6271 for profile in player_profiles .values ():
@@ -83,6 +92,6 @@ def main(log_dir: Path):
8392
8493if __name__ == "__main__" :
8594 parser = argparse .ArgumentParser ()
86- parser .add_argument ("log_dir" , type = Path , help = "Path to ` logs/<user>` folder containing game logs" )
95+ parser .add_argument ("-d" , "-- log_dir" , type = Path , help = "Path to game logs (Default: logs/)" , default = DIR_LOGS )
8796 args = parser .parse_args ()
8897 main (args .log_dir )
0 commit comments