Skip to content

Commit 6100151

Browse files
committed
Merge branch 'main' of github.com:emagedoc/CodeClash
2 parents 54b5d01 + c4b391b commit 6100151

62 files changed

Lines changed: 621 additions & 381 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

assets/texgyrepagella-bold.otf

140 KB
Binary file not shown.

assets/texgyrepagella-regular.otf

141 KB
Binary file not shown.

codeclash/agents/player.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,15 @@ def _extract_modified_files_from_diff(self, diff: str) -> dict[str, str]:
188188

189189
file_contents = {}
190190
for file_path in file_paths:
191+
# Check whether the file exists in the container before attempting to cat it.
192+
# We avoid try/except by inspecting the returncode returned by the execute call.
193+
ls_result = self.environment.execute(f"ls -la '{file_path}'")
194+
if ls_result.get("returncode", 0) != 0:
195+
# File was removed or is not present in this tree. Per request, record empty string.
196+
self.logger.warning(f"File '{file_path}' not found; recording empty content.")
197+
file_contents[file_path] = ""
198+
continue
199+
191200
out = assert_zero_exit_code(
192201
self.environment.execute(f"cat '{file_path}'"),
193202
logger=self.logger,

codeclash/analysis/metrics/elo.py

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
from tqdm import tqdm
88

9+
from codeclash.analysis.viz.utils import MODEL_TO_DISPLAY_NAME
910
from codeclash.constants import LOCAL_LOG_DIR, RESULT_TIE
11+
from codeclash.games import ARENAS
1012

1113

1214
@dataclass
@@ -216,7 +218,7 @@ def print_results(self) -> None:
216218
print("=" * 50)
217219
print("Player ELO profiles:")
218220
lines = [
219-
f" - {profile.model} (Arena: {profile.arena}) - ELO: {profile.rating:.1f} (Games: {profile.rounds_played})"
221+
f" - {profile.model} ({profile.arena}) - ELO: {profile.rating:.1f} (Rounds: {profile.rounds_played})"
220222
for profile in self._player_profiles.values()
221223
]
222224
print("\n".join(sorted(lines)))
@@ -231,18 +233,71 @@ def print_results(self) -> None:
231233

232234
print("\nELO per player (across all games):")
233235
calc_avg_elo = lambda total_elo, games: total_elo / games
236+
model_to_avg_elo = {mid: calc_avg_elo(weighted_elo[mid], total_games[mid]) for mid in weighted_elo}
234237
lines = sorted(
235-
[
236-
f"{pid}: {calc_avg_elo(weighted_elo[pid], total_games[pid]):.1f} (Games: {total_games[pid]})"
237-
for pid in weighted_elo
238-
if total_games[pid] > 0
239-
],
238+
[f"{pid}: {model_to_avg_elo[pid]:.1f} (Rounds: {total_games[pid]})" for pid in model_to_avg_elo.keys()],
240239
key=lambda x: float(x.split(":")[1].split("(")[0]),
241240
reverse=True,
242241
)
243242
for i, line in enumerate(lines, 1):
244243
print(f"{i}. {line}")
245244

245+
# Print latex formatted table for results, formatted as
246+
# Model & ELO & & & \\
247+
# & BattleCode & BattleSnake & ... \\
248+
# \midrule
249+
# Claude 4 Sonnet & ... & & & \\
250+
# ...
251+
print("\nLaTeX formatted table:")
252+
arenas = [x.name for x in ARENAS]
253+
lines = []
254+
arenas_small = [f"\\scriptsize{{{arena}}}" for arena in arenas]
255+
line = "& " + " & ".join(arenas_small) + " & All" + r" \\"
256+
line = line.replace("HuskyBench", "Poker")
257+
lines.append(line)
258+
lines.append(r"\midrule")
259+
per_model_lines = {}
260+
arena_rankings = {}
261+
262+
# Create a mapping of model -> arena -> elo and also arena -> list of (model, elo) for ranking
263+
for profile in self._player_profiles.values():
264+
if profile.model not in per_model_lines:
265+
per_model_lines[profile.model] = {}
266+
if profile.arena not in arena_rankings:
267+
arena_rankings[profile.arena] = []
268+
per_model_lines[profile.model][profile.arena] = round(profile.rating, 1)
269+
arena_rankings[profile.arena].append((profile.model, profile.rating))
270+
271+
# Sort each arena ranking by ELO descending
272+
for arena in arena_rankings:
273+
arena_rankings[arena].sort(key=lambda x: x[1], reverse=True)
274+
for rank, (model, _) in enumerate(arena_rankings[arena], 1):
275+
per_model_lines[model][arena] = (
276+
f"{per_model_lines[model][arena]}" + f"\\textsuperscript{{\\textcolor{{gray}}{{{rank}}}}}"
277+
)
278+
if rank == 1:
279+
# Make it bold
280+
per_model_lines[model][arena] = f"\\textbf{{{per_model_lines[model][arena]}}}"
281+
282+
# Now print the table
283+
overall_ranks = sorted(model_to_avg_elo.items(), key=lambda x: x[1], reverse=True)
284+
model_rank = {model: rank + 1 for rank, (model, _) in enumerate(overall_ranks)}
285+
for model in sorted(per_model_lines.keys()):
286+
m = model.split("/", 1)[-1]
287+
rank = f"\\textsuperscript{{\\textcolor{{gray}}{{{model_rank[model]}}}}}"
288+
overall_elo = f"{model_to_avg_elo[model]:.1f}{rank}"
289+
if model_rank[model] == 1:
290+
overall_elo = f"\\textbf{{{overall_elo}}}"
291+
line = (
292+
f"\\small{{{MODEL_TO_DISPLAY_NAME[m]}}} & "
293+
+ " & ".join(str(per_model_lines[model].get(arena, "-")) for arena in arenas)
294+
+ " & "
295+
+ overall_elo
296+
+ " \\\\"
297+
)
298+
lines.append(line)
299+
print("\n".join(lines))
300+
246301

247302
if __name__ == "__main__":
248303
parser = argparse.ArgumentParser(description="Calculate weighted ELO ratings with configurable weighting functions")

codeclash/analysis/viz/box_plots_win_streaks.py

Lines changed: 0 additions & 136 deletions
This file was deleted.

codeclash/analysis/viz/cdf_command_diversity.py

Lines changed: 51 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,11 @@
4141
from matplotlib import pyplot as plt
4242
from tqdm.auto import tqdm
4343

44+
from codeclash.analysis.viz.utils import ASSETS_DIR, MODEL_TO_COLOR, MODEL_TO_DISPLAY_NAME
4445
from codeclash.constants import LOCAL_LOG_DIR
4546

46-
OUTPUT_FILE = "cdf_command_diversity.png"
47+
OUTPUT_FILE = ASSETS_DIR / "cdf_command_diversity.png"
48+
DATA_CACHE = ASSETS_DIR / "cdf_command_diversity.json"
4749

4850

4951
def shannon_entropy(command_list):
@@ -123,42 +125,49 @@ def main():
123125
"""
124126
model_to_diversity = {}
125127

126-
# Find all tournament directories by looking for metadata.json files
127-
tournaments = [x.parent for x in LOCAL_LOG_DIR.rglob("metadata.json")]
128-
for game_log_folder in tqdm(tournaments):
129-
# Load tournament metadata to get player-to-model mapping
130-
with open(game_log_folder / "metadata.json") as f:
131-
metadata = json.load(f)
132-
try:
133-
# Extract mapping from player name to model name
134-
p2m = {x["name"]: x["config"]["model"]["model_name"].strip("@") for x in metadata["config"]["players"]}
135-
# Initialize diversity list for each model we encounter
136-
for model in p2m.values():
137-
if model not in model_to_diversity:
138-
model_to_diversity[model] = []
139-
except KeyError:
140-
# Skip tournaments with malformed metadata
141-
continue
142-
143-
# Process each player's trajectory files
144-
for name in p2m.keys():
145-
traj_files = (game_log_folder / "players" / name).rglob("*.traj.json")
146-
for traj_file in traj_files:
147-
try:
148-
with open(traj_file) as f:
149-
traj = json.load(f)
150-
151-
# Extract commands and calculate diversity for this session
152-
commands = extract_commands_from_trajectory(traj)
153-
if commands: # Only calculate entropy if there are commands
154-
diversity = shannon_entropy(commands)
155-
model_to_diversity[p2m[name]].append(diversity)
156-
except (json.JSONDecodeError, KeyError):
157-
# Skip malformed trajectory files
158-
continue
159-
160-
# Remove models with no valid data
161-
model_to_diversity = {k: v for k, v in model_to_diversity.items() if v}
128+
if not DATA_CACHE.exists():
129+
# Find all tournament directories by looking for metadata.json files
130+
tournaments = [x.parent for x in LOCAL_LOG_DIR.rglob("metadata.json")]
131+
for game_log_folder in tqdm(tournaments):
132+
# Load tournament metadata to get player-to-model mapping
133+
with open(game_log_folder / "metadata.json") as f:
134+
metadata = json.load(f)
135+
try:
136+
# Extract mapping from player name to model name
137+
p2m = {x["name"]: x["config"]["model"]["model_name"].strip("@") for x in metadata["config"]["players"]}
138+
# Initialize diversity list for each model we encounter
139+
for model in p2m.values():
140+
if model not in model_to_diversity:
141+
model_to_diversity[model] = []
142+
except KeyError:
143+
# Skip tournaments with malformed metadata
144+
continue
145+
146+
# Process each player's trajectory files
147+
for name in p2m.keys():
148+
traj_files = (game_log_folder / "players" / name).rglob("*.traj.json")
149+
for traj_file in traj_files:
150+
try:
151+
with open(traj_file) as f:
152+
traj = json.load(f)
153+
154+
# Extract commands and calculate diversity for this session
155+
commands = extract_commands_from_trajectory(traj)
156+
if commands: # Only calculate entropy if there are commands
157+
diversity = shannon_entropy(commands)
158+
model_to_diversity[p2m[name]].append(diversity)
159+
except (json.JSONDecodeError, KeyError):
160+
# Skip malformed trajectory files
161+
continue
162+
163+
# Remove models with no valid data
164+
model_to_diversity = {k: v for k, v in model_to_diversity.items() if v}
165+
166+
with open(DATA_CACHE, "w") as f:
167+
json.dump(model_to_diversity, f, indent=2)
168+
169+
with open(DATA_CACHE) as f:
170+
model_to_diversity = json.load(f)
162171

163172
# Print summary statistics for each model
164173
print("Command Diversity Summary:")
@@ -170,18 +179,19 @@ def main():
170179
)
171180

172181
# Generate CDF plot comparing all models
173-
plt.figure(figsize=(12, 8))
174-
colors = plt.cm.tab10(range(len(model_to_diversity)))
182+
plt.figure(figsize=(8, 8))
175183

176184
for i, (model, diversities) in enumerate(model_to_diversity.items()):
177185
# Sort diversity values and create cumulative probability values
178186
sorted_diversities = sorted(diversities)
179187
yvals = [i / len(sorted_diversities) for i in range(len(sorted_diversities))]
180188
# Plot as step function (standard for CDFs)
181-
plt.step(sorted_diversities, yvals, label=model, where="post", color=colors[i])
189+
plt.step(
190+
sorted_diversities, yvals, label=MODEL_TO_DISPLAY_NAME[model], where="post", color=MODEL_TO_COLOR[model]
191+
)
182192

183193
plt.xlabel("Command Diversity (Shannon Entropy)")
184-
plt.ylabel("Cumulative Probability")
194+
# plt.ylabel("Cumulative Probability")
185195
plt.title("CDF of Command Diversity by Model\n(Higher entropy = more diverse command usage)")
186196
plt.legend(bbox_to_anchor=(1.05, 1), loc="upper left") # Legend outside plot area
187197
plt.grid(True, alpha=0.3)

0 commit comments

Comments
 (0)