Skip to content

Commit 784d117

Browse files
committed
Simplify rating script args; minor game log fixes
1 parent b5cb57a commit 784d117

4 files changed

Lines changed: 94 additions & 62 deletions

File tree

codeclash/games/battlecode/battlecode.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def copy_logs_from_env(self, round_num):
2929
copy_from_container(
3030
container=self.environment,
3131
src_path="/testbed/sim.log",
32-
dest_path=self.log_local / "rounds" / f"sim_{round_num}.log",
32+
dest_path=self.log_local / "rounds" / str(round_num) / f"sim_{round_num}.log",
3333
)
3434

3535
def get_stats(self, agents: list[Any]) -> RoundStats:

codeclash/games/battlesnake/battlesnake.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def copy_logs_from_env(self, round_num):
4949
def get_stats(self, agents: list[Player]) -> RoundStats:
5050
scores = {}
5151
for idx in range(self.game_config["sims_per_round"]):
52-
ro = self.environment.execute(f"cat game/logs/sim_{idx}.txt")["output"]
52+
ro = self.environment.execute(f"cat game/logs/sim_{idx}.jsonl")["output"]
5353
lines = ro.strip().split("\n")
5454
results = json.loads(lines[-1]) if lines else {} # Get the last line which contains the game result
5555
winner = RESULT_TIE if results["isDraw"] else results["winnerName"]
@@ -99,7 +99,7 @@ def _run_single_simulation(self, cmd: str, idx: int) -> tuple[str, str]:
9999
"""Run a single battlesnake simulation and return log and result outputs."""
100100
assert_zero_exit_code(
101101
self.environment.execute(
102-
cmd + f" -o logs/sim_{idx}.txt",
102+
cmd + f" -o logs/sim_{idx}.jsonl",
103103
cwd=f"{self.environment.config.cwd}/game",
104104
)
105105
)

codeclash/ratings/elo.py

Lines changed: 45 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from dataclasses import dataclass
44
from pathlib import Path
55

6+
from codeclash.constants import DIR_LOGS
7+
68
K_FACTOR = 32 # ELO constant, changeable
79

810

@@ -19,44 +21,51 @@ def expected_score(rating_a, rating_b):
1921

2022

2123
def 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

8493
if __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)

codeclash/ratings/win_rate.py

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from dataclasses import dataclass
44
from pathlib import Path
55

6-
from codeclash.constants import RESULT_TIE
6+
from codeclash.constants import DIR_LOGS, RESULT_TIE
77

88

99
@dataclass
@@ -19,38 +19,61 @@ def win_rate(self) -> float:
1919

2020

2121
def main(log_dir: Path):
22+
# Assuming directory structure is:
23+
# logs/<user_id>/<game_id>
24+
# - players/
25+
# - rounds/
26+
# - game.log
27+
# - metadata.json
2228
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}")
26-
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-
num_rounds = len(list((game_log_folder / "rounds").iterdir()))
30-
31-
for player in player_ids:
32-
if f"{game_id}.{player}" in player_profiles:
33-
player_profiles[f"{game_id}.{player}"].count += num_rounds
34-
else:
35-
player_profiles[f"{game_id}.{player}"] = PlayerGameProfile(
36-
player_id=player, game_id=game_id, count=num_rounds
37-
)
38-
39-
for round_folder in (game_log_folder / "rounds").iterdir():
40-
round_results = json.load(open(round_folder / "results.json"))
41-
winner = round_results.get("winner")
42-
if winner != RESULT_TIE:
43-
player_profiles[f"{game_id}.{winner}"].wins += 1
29+
for user_folder in log_dir.iterdir():
30+
for game_log_folder in user_folder.iterdir():
31+
if game_log_folder.is_dir():
32+
print(f"Processing game log folder: {game_log_folder}")
33+
34+
game_id = game_log_folder.name.split(".")[1]
35+
player_ids = [x.name for x in (game_log_folder / "players").iterdir() if x.is_dir()]
36+
num_rounds = len(list((game_log_folder / "rounds").iterdir()))
37+
38+
for player in player_ids:
39+
if f"{game_id}.{player}" in player_profiles:
40+
player_profiles[f"{game_id}.{player}"].count += num_rounds
41+
else:
42+
player_profiles[f"{game_id}.{player}"] = PlayerGameProfile(
43+
player_id=player, game_id=game_id, count=num_rounds
44+
)
45+
46+
for round_folder in (game_log_folder / "rounds").iterdir():
47+
round_results = json.load(open(round_folder / "results.json"))
48+
winner = round_results.get("winner")
49+
if winner != RESULT_TIE:
50+
player_profiles[f"{game_id}.{winner}"].wins += 1
4451

4552
print("Player profiles:")
4653
for profile in player_profiles.values():
4754
print(
4855
f" - {profile.player_id} (Game: {profile.game_id}) - Win Rate: {profile.win_rate:.2%} ({profile.wins}/{profile.count})"
4956
)
5057

58+
# Player-specific (game-agnostic) win rates (micro average)
59+
total_wins = {}
60+
total_games = {}
61+
for profile in player_profiles.values():
62+
pid = profile.player_id
63+
total_wins[pid] = total_wins.get(pid, 0) + profile.wins
64+
total_games[pid] = total_games.get(pid, 0) + profile.count
65+
66+
print("\nPlayer-specific win rates (game-agnostic, micro average):")
67+
for pid in total_wins:
68+
if total_games[pid] > 0:
69+
win_rate = total_wins[pid] / total_games[pid]
70+
else:
71+
win_rate = 0.0
72+
print(f" - {pid}: Win Rate {win_rate:.2%} ({total_wins[pid]}/{total_games[pid]})")
73+
5174

5275
if __name__ == "__main__":
5376
parser = argparse.ArgumentParser()
54-
parser.add_argument("log_dir", type=Path, help="Path to `logs/<user>` folder containing game logs")
77+
parser.add_argument("-d", "--log_dir", type=Path, help="Path to game logs (Default: logs/)", default=DIR_LOGS)
5578
args = parser.parse_args()
5679
main(args.log_dir)

0 commit comments

Comments
 (0)