Skip to content

Commit b7d9c57

Browse files
author
yolo h8cker 93
committed
Merge branch 'main' of github.com:emagedoc/CodeClash
2 parents 147b2bd + 2e0603d commit b7d9c57

10 files changed

Lines changed: 332 additions & 33 deletions

File tree

codeclash/ratings/win_rate.py

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55

66
from tqdm import tqdm
77

8-
from codeclash.constants import DIR_LOGS, FILE_RESULTS, RESULT_TIE
8+
from codeclash.constants import DIR_LOGS, RESULT_TIE
99

1010

1111
@dataclass
1212
class PlayerGameProfile:
1313
player_id: str
14+
model_name: str
1415
game_id: str
1516
wins: int = 0
1617
count: int = 0
@@ -24,59 +25,65 @@ def main(log_dir: Path):
2425
# Assuming directory structure is:
2526
# logs/<user_id>/<game_id>
2627
# - players/
27-
# - rounds/
2828
# - game.log
2929
# - metadata.json
30-
player_profiles = {}
30+
model_profiles = {}
3131
for user_folder in log_dir.iterdir():
3232
print(f"Processing games under user `{user_folder.name}`")
3333
for game_log_folder in tqdm(list(user_folder.iterdir())):
3434
if not game_log_folder.is_dir():
3535
continue
3636
game_id = game_log_folder.name.split(".")[1]
3737
player_ids = [x.name for x in (game_log_folder / "players").iterdir() if x.is_dir()]
38-
num_rounds = len(list((game_log_folder / "rounds").iterdir()))
38+
metadata = json.load(open(game_log_folder / "metadata.json"))
39+
player_to_model = {x["name"]: x["config"]["model"]["model_name"] for x in metadata["config"]["players"]}
40+
print(player_to_model)
41+
num_rounds = len(metadata["round_stats"])
3942

40-
for player in player_ids:
41-
if f"{game_id}.{player}" in player_profiles:
42-
player_profiles[f"{game_id}.{player}"].count += num_rounds
43+
# Only count each unique model once per game
44+
unique_models = {player_to_model[player] for player in player_ids}
45+
for model_name in unique_models:
46+
k = f"{game_id}.{model_name}"
47+
if k in model_profiles:
48+
model_profiles[k].count += num_rounds
4349
else:
44-
player_profiles[f"{game_id}.{player}"] = PlayerGameProfile(
45-
player_id=player, game_id=game_id, count=num_rounds
50+
# Use the first player_id that matches this model_name for display
51+
player_id = next(pid for pid in player_ids if player_to_model[pid] == model_name)
52+
model_profiles[k] = PlayerGameProfile(
53+
player_id=player_id, model_name=model_name, game_id=game_id, count=num_rounds
4654
)
4755

48-
for round_folder in (game_log_folder / "rounds").iterdir():
49-
if round_folder.name == "0":
56+
for round, details in metadata["round_stats"].items():
57+
if round == "0":
5058
# Skip initial round
5159
continue
52-
if not (round_folder / FILE_RESULTS).exists():
53-
continue
54-
round_results = json.load(open(round_folder / FILE_RESULTS))
55-
winner = round_results.get("winner")
60+
winner = details["winner"]
5661
if winner != RESULT_TIE:
57-
player_profiles[f"{game_id}.{winner}"].wins += 1
62+
model_profiles[f"{game_id}.{player_to_model[winner]}"].wins += 1
5863

5964
print("Player profiles:")
60-
for profile in player_profiles.values():
65+
for profile in model_profiles.values():
6166
print(
62-
f" - {profile.player_id} (Game: {profile.game_id}) - Win Rate: {profile.win_rate:.2%} ({profile.wins}/{profile.count})"
67+
f" - {profile.model_name} (Game: {profile.game_id}) - Win Rate: {profile.win_rate:.2%} ({profile.wins}/{profile.count})"
6368
)
6469

6570
# Player-specific (game-agnostic) win rates (micro average)
6671
total_wins = {}
6772
total_games = {}
68-
for profile in player_profiles.values():
69-
pid = profile.player_id
70-
total_wins[pid] = total_wins.get(pid, 0) + profile.wins
71-
total_games[pid] = total_games.get(pid, 0) + profile.count
73+
model_names = {}
74+
for profile in model_profiles.values():
75+
mid = profile.model_name
76+
total_wins[mid] = total_wins.get(mid, 0) + profile.wins
77+
total_games[mid] = total_games.get(mid, 0) + profile.count
78+
model_names[mid] = profile.model_name
7279

7380
print("\nPlayer-specific win rates (game-agnostic, micro average):")
74-
for pid in total_wins:
75-
if total_games[pid] > 0:
76-
win_rate = total_wins[pid] / total_games[pid]
81+
for mid in total_wins:
82+
if total_games[mid] > 0:
83+
win_rate = total_wins[mid] / total_games[mid]
7784
else:
7885
win_rate = 0.0
79-
print(f" - {pid}: Win Rate {win_rate:.2%} ({total_wins[pid]}/{total_games[pid]})")
86+
print(f" - {model_names[mid]}: Win Rate {win_rate:.2%} ({total_wins[mid]}/{total_games[mid]})")
8087

8188

8289
if __name__ == "__main__":

codeclash/viewer/app.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,41 @@ def analyze_line_counts(self) -> dict[str, Any]:
612612

613613
return {"all_files": all_files_list, "line_counts_by_round": line_counts_by_round}
614614

615+
def analyze_sim_wins_per_round(self) -> dict[str, Any]:
616+
"""Analyze number of simulations won per round for each competitor from round_stats in metadata.json"""
617+
metadata_file = self.log_dir / "metadata.json"
618+
if not metadata_file.exists():
619+
return {"players": [], "rounds": [], "wins_by_player": {}}
620+
621+
try:
622+
metadata = json.loads(metadata_file.read_text())
623+
round_stats = metadata.get("round_stats", {})
624+
# Collect all player names from all rounds
625+
player_names = set()
626+
for round_data in round_stats.values():
627+
scores = round_data.get("scores", {})
628+
player_names.update([k for k in scores.keys() if k != "Tie"])
629+
player_names = sorted(player_names)
630+
631+
# Collect all round numbers (sorted)
632+
round_nums = sorted([int(k) for k in round_stats.keys()])
633+
634+
# Build wins_by_player: {player: [wins_per_round]}
635+
wins_by_player = {p: [] for p in player_names}
636+
for round_num in round_nums:
637+
round_data = round_stats.get(str(round_num), {})
638+
scores = round_data.get("scores", {})
639+
for p in player_names:
640+
wins_by_player[p].append(scores.get(p, 0))
641+
642+
return {
643+
"players": player_names,
644+
"rounds": round_nums,
645+
"wins_by_player": wins_by_player,
646+
}
647+
except Exception:
648+
return {"players": [], "rounds": [], "wins_by_player": {}}
649+
615650
def load_matrix_analysis(self) -> dict[str, Any] | None:
616651
"""Load and process matrix.json if it exists"""
617652
matrix_file = self.log_dir / "matrix.json"
@@ -790,6 +825,7 @@ def index():
790825

791826
# Get analysis data
792827
analysis_data = parser.analyze_line_counts()
828+
sim_wins_data = parser.analyze_sim_wins_per_round()
793829

794830
# Get matrix analysis data
795831
matrix_data = parser.load_matrix_analysis()
@@ -804,6 +840,7 @@ def index():
804840
metadata=metadata,
805841
trajectories_by_round=trajectories_by_round,
806842
analysis_data=analysis_data,
843+
sim_wins_data=sim_wins_data,
807844
matrix_data=matrix_data,
808845
is_static=STATIC_MODE,
809846
)

codeclash/viewer/static/js/analysis.js

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,97 @@ function prepareChartData(fileName) {
184184
}
185185

186186
// Initialize when DOM is loaded
187+
// --- Sim Wins Per Round Chart ---
188+
let simWinsChart = null;
189+
let simWinsData = null;
190+
191+
function initializeSimWinsChart() {
192+
const simWinsDataElement = document.getElementById("sim-wins-data");
193+
if (!simWinsDataElement) return;
194+
try {
195+
simWinsData = JSON.parse(simWinsDataElement.textContent);
196+
if (
197+
simWinsData &&
198+
simWinsData.players &&
199+
simWinsData.players.length > 0 &&
200+
simWinsData.rounds &&
201+
simWinsData.rounds.length > 0
202+
) {
203+
createSimWinsChart();
204+
}
205+
} catch (error) {
206+
console.error("Error parsing sim wins data:", error);
207+
}
208+
}
209+
210+
function createSimWinsChart() {
211+
const canvas = document.getElementById("sim-wins-chart");
212+
if (!canvas || !simWinsData) return;
213+
const ctx = canvas.getContext("2d");
214+
if (simWinsChart) simWinsChart.destroy();
215+
216+
// Prepare datasets
217+
const colors = [
218+
"#FF6384",
219+
"#36A2EB",
220+
"#FFCE56",
221+
"#4BC0C0",
222+
"#9966FF",
223+
"#FF9F40",
224+
"#C9CBCF",
225+
"#4BC0C0",
226+
"#FF6384",
227+
];
228+
let colorIndex = 0;
229+
const datasets = simWinsData.players.map((player) => {
230+
const data = simWinsData.rounds.map((round, i) => ({ x: round, y: simWinsData.wins_by_player[player][i] }));
231+
const color = colors[colorIndex % colors.length];
232+
colorIndex++;
233+
return {
234+
label: player,
235+
data: data,
236+
borderColor: color,
237+
backgroundColor: color + "20",
238+
borderWidth: 2,
239+
fill: false,
240+
tension: 0.1,
241+
};
242+
});
243+
244+
simWinsChart = new Chart(ctx, {
245+
type: "line",
246+
data: { datasets },
247+
options: {
248+
responsive: true,
249+
maintainAspectRatio: false,
250+
plugins: {
251+
title: {
252+
display: true,
253+
text: `Simulations Won Per Round`,
254+
},
255+
legend: {
256+
display: true,
257+
position: "top",
258+
},
259+
},
260+
scales: {
261+
x: {
262+
title: { display: true, text: "Round" },
263+
type: "linear",
264+
position: "bottom",
265+
},
266+
y: {
267+
title: { display: true, text: "Simulations Won" },
268+
beginAtZero: true,
269+
},
270+
},
271+
interaction: { intersect: false, mode: "index" },
272+
},
273+
});
274+
}
275+
276+
// Initialize both analyses when DOM is loaded
187277
document.addEventListener("DOMContentLoaded", function () {
188278
initializeAnalysis();
279+
initializeSimWinsChart();
189280
});

codeclash/viewer/templates/includes/analysis.html

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,23 @@
22
<section class="analysis-section">
33
<h2><i class="bi bi-graph-up"></i> Analysis</h2>
44

5+
<!-- Sim Wins Per Round Analysis -->
6+
<details class="foldout">
7+
<summary><i class="bi bi-bar-chart-line"></i> Sim Wins Per Round</summary>
8+
<div class="log-content-container">
9+
{% if sim_wins_data and sim_wins_data.players and sim_wins_data.rounds %}
10+
<div class="sim-wins-analysis-container">
11+
<div class="chart-container">
12+
<canvas id="sim-wins-chart" width="800" height="400"></canvas>
13+
</div>
14+
<script type="application/json" id="sim-wins-data">{{ sim_wins_data | tojson }}</script>
15+
</div>
16+
{% else %}
17+
<p>No simulation win data available. This analysis requires games with round stats.</p>
18+
{% endif %}
19+
</div>
20+
</details>
21+
522
<!-- Line Counting Analysis -->
623
<details class="foldout">
724
<summary><i class="bi bi-graph-up-arrow"></i> Line Counting Analysis</summary>

configs/README.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# List of model configurations
2+
Scratchpad for recording how different models should be configured for PvP.
3+
4+
### OpenAI Family
5+
6+
GPT 5 Mini
7+
```yaml
8+
- agent: mini
9+
name: openai/gpt-5-mini
10+
config:
11+
agent: !include mini/default.yaml
12+
model:
13+
model_class: "portkey"
14+
model_name: openai/gpt-5-mini
15+
```
16+
17+
GPT 5 High Thinking
18+
```yaml
19+
- agent: mini
20+
name: openai/gpt-5
21+
config:
22+
agent: !include mini/default.yaml
23+
model:
24+
model_class: "portkey"
25+
model_name: openai/gpt-5
26+
model_kwargs:
27+
drop_params: true
28+
reasoning_effort: "high"
29+
verbosity: "medium"
30+
```
31+
32+
o3
33+
```yaml
34+
- agent: mini
35+
name: openai/o3
36+
config:
37+
agent: !include mini/default.yaml
38+
model:
39+
model_class: "portkey"
40+
model_name: openai/o3
41+
```
42+
43+
### Anthropic Family
44+
45+
Claude Sonnet 4
46+
```yaml
47+
- agent: mini
48+
name: anthropic/claude-sonnet-4-20250514
49+
config:
50+
agent: !include mini/default.yaml
51+
model:
52+
model_class: "portkey"
53+
model_name: anthropic/claude-sonnet-4-20250514
54+
```
55+
56+
Claude Sonnet 3.7
57+
```yaml
58+
- agent: mini
59+
name: anthropic/claude-sonnet-3-7
60+
config:
61+
agent: !include mini/default.yaml
62+
model:
63+
model_class: "portkey"
64+
model_name: anthropic/claude-sonnet-3-7
65+
```
66+
67+
### Qwen
68+
69+
Qwen 3 Coder
70+
```yaml
71+
- agent: mini
72+
name: qwen/qwen-3-coder
73+
config:
74+
agent: !include mini/default.yaml
75+
model:
76+
model_class: "portkey"
77+
model_name: qwen/qwen-3-coder
78+
```

configs/pvp/huskybench.yaml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
tournament:
2+
rounds: 25
3+
game:
4+
name: HuskyBench
5+
sims_per_round: 1000
6+
players:
7+
- agent: mini
8+
name: p1
9+
config:
10+
agent: !include mini/default.yaml
11+
model:
12+
model_name: openai/gpt-5-mini
13+
- agent: mini
14+
name: p2
15+
config:
16+
agent: !include mini/default.yaml
17+
model:
18+
model_name: "anthropic/claude-sonnet-4-20250514"
19+
model_kwargs:
20+
temperature: 0.0
21+
prompts:
22+
game_description: |
23+
You are a software developer ({{player_id}}) competing in a coding game for Poker.
24+
In this game, you will write code to control a poker-playing bot, aiming to outsmart your opponents and win chips.
25+
Victory comes from crafting clever strategies—bluffing, reading opponents, and managing your chip stack effectively.
26+
27+
The game is played in {{rounds}} rounds. For every round, you (and your competitor) edit program code that controls your bot. This is round {{round}}.
28+
After you and your competitor finish editing your codebases, the game is run automatically.
29+
30+
Your task: improve the bot in `warriors/warrior.red`, located in {{working_dir}}.
31+
{{working_dir}} is your codebase, which contains both your bot and supporting assets.

0 commit comments

Comments
 (0)