Skip to content

Commit b8c70d0

Browse files
committed
Add win rate progression
1 parent 8c1e009 commit b8c70d0

1 file changed

Lines changed: 128 additions & 0 deletions

File tree

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import argparse
2+
import json
3+
from dataclasses import dataclass
4+
from pathlib import Path
5+
6+
from matplotlib import pyplot as plt
7+
from tqdm import tqdm
8+
9+
from codeclash.constants import LOCAL_LOG_DIR, RESULT_TIE
10+
11+
12+
@dataclass
13+
class ModelRoundWinProfile:
14+
model: str
15+
arena: str
16+
wins: int
17+
total_games: int
18+
round_idx: int
19+
20+
@property
21+
def win_rate(self) -> float:
22+
return self.wins / self.total_games if self.total_games > 0 else 0.0
23+
24+
25+
def main(log_dir: Path):
26+
print(f"Calculating win rates by round from logs in {log_dir} ...")
27+
28+
player_round_profiles = {}
29+
30+
for game_log_folder in tqdm([x.parent for x in log_dir.rglob("game.log")]):
31+
arena = game_log_folder.name.split(".")[1]
32+
metadata = json.load(open(game_log_folder / "metadata.json"))
33+
34+
try:
35+
p2m = {x["name"]: x["config"]["model"]["model_name"].strip("@") for x in metadata["config"]["players"]}
36+
except KeyError:
37+
print(f"Skipping {game_log_folder} (malformed metadata.json)")
38+
continue
39+
40+
if len(p2m) != 2:
41+
# Only process if there are exactly 2 players:
42+
continue
43+
44+
for idx, stats in metadata["round_stats"].items():
45+
if idx == "0":
46+
# Skip initial round
47+
continue
48+
49+
# Initialize profiles for each model in this round
50+
for model in p2m.values():
51+
key = f"{arena}.{model}.{idx}"
52+
if key not in player_round_profiles:
53+
player_round_profiles[key] = ModelRoundWinProfile(
54+
model=model, arena=arena, wins=0, total_games=0, round_idx=int(idx)
55+
)
56+
57+
# Count the game for each participating model
58+
for model in p2m.values():
59+
key = f"{arena}.{model}.{idx}"
60+
player_round_profiles[key].total_games += 1
61+
62+
# Check if there's a winner for this round
63+
winner = stats.get("winner")
64+
if winner and winner != RESULT_TIE and winner in p2m:
65+
winning_model = p2m[winner]
66+
key = f"{arena}.{winning_model}.{idx}"
67+
if key in player_round_profiles:
68+
player_round_profiles[key].wins += 1
69+
70+
# Organize data by model and round for plotting
71+
lines = {
72+
pid: [[] for _ in range(15)]
73+
for pid in {x.rsplit(".", 1)[0].split(".", 1)[-1] for x in player_round_profiles.keys()}
74+
}
75+
76+
for pid, profile in player_round_profiles.items():
77+
k = pid.rsplit(".", 1)[0].split(".", 1)[-1]
78+
if 1 <= profile.round_idx <= 15:
79+
lines[k][profile.round_idx - 1].append(profile)
80+
81+
print("=" * 50)
82+
print("Player win rate progression per round:")
83+
84+
def aggregate_win_rates_across_games(profiles):
85+
"""Aggregate win rates across multiple games using micro-averaging"""
86+
total_wins = sum([p.wins for p in profiles])
87+
total_games = sum([p.total_games for p in profiles])
88+
return total_wins / total_games if total_games > 0 else 0.0
89+
90+
# Calculate aggregated win rates for each player and round
91+
for pid, win_profiles in lines.items():
92+
lines[pid] = [aggregate_win_rates_across_games(r) for r in win_profiles]
93+
print(f" - {pid}: " + ", ".join([f"{wr:.2%}" for wr in lines[pid]]))
94+
95+
# Create line chart of win rate progression per player
96+
plt.figure(figsize=(12, 8))
97+
for pid, win_rates in lines.items():
98+
plt.plot(range(1, 16), win_rates, marker="o", label=pid, linewidth=2, markersize=6)
99+
100+
plt.title("Win Rate Progression per Round", fontsize=16, fontweight="bold")
101+
plt.xlabel("Round", fontsize=12)
102+
plt.ylabel("Win Rate", fontsize=12)
103+
plt.xticks(range(1, 16))
104+
plt.yticks([i / 10 for i in range(0, 11)], [f"{i * 10}%" for i in range(0, 11)])
105+
plt.ylim(0, 1)
106+
plt.legend(bbox_to_anchor=(1.05, 1), loc="upper left")
107+
plt.grid(True, alpha=0.3)
108+
plt.tight_layout()
109+
plt.savefig("win_rate_progression_per_round.png", dpi=300, bbox_inches="tight")
110+
print("Win rate progression chart saved to win_rate_progression_per_round.png")
111+
112+
# Print summary statistics
113+
print("\n" + "=" * 50)
114+
print("Summary statistics:")
115+
for pid, win_rates in lines.items():
116+
avg_win_rate = (
117+
sum(win_rates) / len([wr for wr in win_rates if wr > 0]) if any(wr > 0 for wr in win_rates) else 0
118+
)
119+
max_win_rate = max(win_rates) if win_rates else 0
120+
min_win_rate = min([wr for wr in win_rates if wr > 0]) if any(wr > 0 for wr in win_rates) else 0
121+
print(f" - {pid}: Avg: {avg_win_rate:.2%}, Max: {max_win_rate:.2%}, Min: {min_win_rate:.2%}")
122+
123+
124+
if __name__ == "__main__":
125+
parser = argparse.ArgumentParser(description="Calculate win rates per round")
126+
parser.add_argument("-d", "--log_dir", type=Path, help="Path to game logs (Default: logs/)", default=LOCAL_LOG_DIR)
127+
args = parser.parse_args()
128+
main(args.log_dir)

0 commit comments

Comments
 (0)