Skip to content

Commit f30764a

Browse files
committed
Add win condition params for ladder
1 parent a17496c commit f30764a

14 files changed

Lines changed: 100 additions & 5 deletions

codeclash/cli/ladder.py

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,45 @@
1717

1818
logger = get_logger("ladder")
1919

20+
21+
def _resolve_ladder_rules(ladder_rules: dict, rounds: int) -> tuple[float, int]:
22+
"""Validate the optional ``ladder_rules`` block and return ``(min_round_win_fraction, win_last_k)``.
23+
24+
Defaults reproduce the historical behavior: win a strict majority of rounds
25+
(``min_round_win_fraction=0.5``) AND win the final round (``win_last_k=1``).
26+
"""
27+
min_round_win_fraction = ladder_rules.get("min_round_win_fraction", 0.5)
28+
win_last_k = ladder_rules.get("win_last_k", 1)
29+
30+
# win_last_k: number of trailing rounds the player must win (1 == just the final round).
31+
if isinstance(win_last_k, bool) or not isinstance(win_last_k, int):
32+
typer.echo(f"ladder_rules.win_last_k must be an integer, got {win_last_k!r}.")
33+
raise typer.Exit(1)
34+
if win_last_k < 1:
35+
typer.echo(
36+
f"ladder_rules.win_last_k must be >= 1, got {win_last_k}. "
37+
"Use 1 to require winning only the final round."
38+
)
39+
raise typer.Exit(1)
40+
if win_last_k > rounds:
41+
typer.echo(
42+
f"ladder_rules.win_last_k ({win_last_k}) cannot exceed tournament.rounds ({rounds})."
43+
)
44+
raise typer.Exit(1)
45+
46+
# min_round_win_fraction: player must win strictly more than this fraction of rounds.
47+
if isinstance(min_round_win_fraction, bool) or not isinstance(min_round_win_fraction, (int, float)):
48+
typer.echo(f"ladder_rules.min_round_win_fraction must be a number, got {min_round_win_fraction!r}.")
49+
raise typer.Exit(1)
50+
if not 0 <= min_round_win_fraction < 1:
51+
typer.echo(
52+
f"ladder_rules.min_round_win_fraction must be in [0, 1), got {min_round_win_fraction}. "
53+
"0.5 requires a strict majority; 0 drops the majority requirement."
54+
)
55+
raise typer.Exit(1)
56+
57+
return float(min_round_win_fraction), win_last_k
58+
2059
ladder_app = typer.Typer(
2160
no_args_is_help=True,
2261
add_completion=False,
@@ -101,9 +140,16 @@ def run(
101140
config["tournament"]["rounds"],
102141
config["game"]["sims_per_round"],
103142
)
143+
min_round_win_fraction, win_last_k = _resolve_ladder_rules(config.get("ladder_rules", {}), rounds)
104144
timestamp = time.strftime("%y%m%d%H%M%S")
105145
del config["player"]
106146
del config["ladder"]
147+
config.pop("ladder_rules", None)
148+
149+
print(
150+
f"Ladder advancement rule: win > {min_round_win_fraction:.0%} of {rounds} rounds "
151+
f"and win the last {win_last_k} round(s)."
152+
)
107153
ladder_folder = f"LadderTournament.{config['game']['name']}.r{rounds}.s{sims}.{timestamp}"
108154
player["branch"] = ladder_folder
109155
parent_dir = LOCAL_LOG_DIR / getpass.getuser() / ladder_folder
@@ -143,12 +189,22 @@ def run(
143189
metadata = yaml.safe_load(f)
144190
round_winners = [r["winner"] for r in metadata["round_stats"].values()]
145191

146-
# Player must have won majority of rounds and the last round to continue ladder
192+
# Advancement rule (configurable via `ladder_rules`): win strictly more than
193+
# `min_round_win_fraction` of rounds AND win the last `win_last_k` rounds.
147194
player_wins = sum(1 for w in round_winners if w == player["name"])
148-
player_won_last = round_winners[-1] == player["name"]
149-
150-
if not player_wins > len(round_winners) // 2 or not player_won_last:
151-
# If player lost tournament, ladder challenge ends
195+
won_majority = player_wins > len(round_winners) * min_round_win_fraction
196+
won_last_k = all(w == player["name"] for w in round_winners[-win_last_k:])
197+
198+
if not won_majority or not won_last_k:
199+
# Player failed the advancement rule; the ladder challenge ends here.
200+
print("=" * 10)
201+
print(
202+
f"{player['name']} did not clear {opponent['name']} "
203+
f"(rank {opponent_rank}/{len(ladder)}): won {player_wins}/{len(round_winners)} rounds "
204+
f"(needed > {min_round_win_fraction:.0%}), last {win_last_k} round(s) won: {won_last_k}.\n"
205+
"Ladder challenge ends."
206+
)
207+
print("=" * 10)
152208
break
153209

154210
print("=" * 10)

configs/ablations/ladder/battlesnake_llama_smoke.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
# uv run codeclash ladder run configs/ablations/ladder/battlesnake_llama_smoke.yaml -c
66
tournament:
77
rounds: 1
8+
ladder_rules:
9+
min_round_win_fraction: 0.5 # advance only on a strict majority of rounds
10+
win_last_k: 1 # ...and win the last K rounds (K=1 == just the final round)
811
game:
912
name: BattleSnake
1013
sims_per_round: 10

configs/ablations/ladder/corewar.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
tournament:
22
rounds: 5
3+
ladder_rules:
4+
min_round_win_fraction: 0.5 # advance only on a strict majority of rounds
5+
win_last_k: 1 # ...and win the last K rounds (K=1 == just the final round)
36
game:
47
name: CoreWar
58
sims_per_round: 2000

configs/ablations/ladder/corewar__gemini_3_5_flash.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
# uv run codeclash ladder run configs/ablations/ladder/corewar__gemini_3_5_flash.yaml
33
tournament:
44
rounds: 5
5+
ladder_rules:
6+
min_round_win_fraction: 0.5 # advance only on a strict majority of rounds
7+
win_last_k: 1 # ...and win the last K rounds (K=1 == just the final round)
58
game:
69
name: CoreWar
710
sims_per_round: 2000

configs/ablations/ladder/corewar__gpt_5_5.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
# uv run codeclash ladder run configs/ablations/ladder/corewar__gpt_5_5.yaml
33
tournament:
44
rounds: 5
5+
ladder_rules:
6+
min_round_win_fraction: 0.5 # advance only on a strict majority of rounds
7+
win_last_k: 1 # ...and win the last K rounds (K=1 == just the final round)
58
game:
69
name: CoreWar
710
sims_per_round: 2000

configs/ablations/ladder/corewar__opus_4_7.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
# uv run codeclash ladder run configs/ablations/ladder/corewar__opus_4_7.yaml
33
tournament:
44
rounds: 5
5+
ladder_rules:
6+
min_round_win_fraction: 0.5 # advance only on a strict majority of rounds
7+
win_last_k: 1 # ...and win the last K rounds (K=1 == just the final round)
58
game:
69
name: CoreWar
710
sims_per_round: 2000

configs/ablations/ladder/corewar__opus_4_8.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
# uv run codeclash ladder run configs/ablations/ladder/corewar__opus_4_8.yaml
33
tournament:
44
rounds: 5
5+
ladder_rules:
6+
min_round_win_fraction: 0.5 # advance only on a strict majority of rounds
7+
win_last_k: 1 # ...and win the last K rounds (K=1 == just the final round)
58
game:
69
name: CoreWar
710
sims_per_round: 2000

configs/ablations/ladder/corewar__sonnet_5.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
# uv run codeclash ladder run configs/ablations/ladder/corewar__sonnet_5.yaml
33
tournament:
44
rounds: 5
5+
ladder_rules:
6+
min_round_win_fraction: 0.5 # advance only on a strict majority of rounds
7+
win_last_k: 1 # ...and win the last K rounds (K=1 == just the final round)
58
game:
69
name: CoreWar
710
sims_per_round: 2000

configs/ablations/ladder/robotrumble.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
tournament:
22
rounds: 5
3+
ladder_rules:
4+
min_round_win_fraction: 0.5 # advance only on a strict majority of rounds
5+
win_last_k: 1 # ...and win the last K rounds (K=1 == just the final round)
36
game:
47
name: RobotRumble
58
sims_per_round: 250

configs/ablations/ladder/robotrumble__gemini_3_5_flash.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
# uv run codeclash ladder run configs/ablations/ladder/robotrumble__gemini_3_5_flash.yaml
33
tournament:
44
rounds: 5
5+
ladder_rules:
6+
min_round_win_fraction: 0.5 # advance only on a strict majority of rounds
7+
win_last_k: 1 # ...and win the last K rounds (K=1 == just the final round)
58
game:
69
name: RobotRumble
710
sims_per_round: 250

0 commit comments

Comments
 (0)