Skip to content

Commit e62e301

Browse files
committed
Add script to generate configs
1 parent d22fbbe commit e62e301

16 files changed

Lines changed: 196 additions & 226 deletions

File tree

codeclash/games/battlecode/battlecode.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,18 @@
1111

1212
class BattleCodeGame(CodeGame):
1313
name: str = "BattleCode"
14+
description: str = """Battlecode 25 throws you into a real-time strategy showdown where your Python bot pilots a team of specialized robots—Soldiers, Moppers, Splashers—alongside towers that spawn units or generate resources.
15+
Your mission: paint over 70% of the map (or eliminate the enemy) by coordinating cleanups, area cover, and tower-building through tight bytecode budgets and clever unit synergy."""
16+
default_args: dict = {
17+
"maps": "quack",
18+
}
19+
submission: str = "src/mysubmission"
1420

1521
def __init__(self, config, **kwargs):
1622
super().__init__(config, **kwargs)
1723
assert len(config["players"]) == 2, "BattleCode is a two-player game"
1824
self.run_cmd_round: str = "python run.py run"
19-
for arg, val in self.game_config.get("args", {}).items():
25+
for arg, val in self.game_config.get("args", self.default_args).items():
2026
if isinstance(val, bool):
2127
if val:
2228
self.run_cmd_round += f" --{arg}"

codeclash/games/battlesnake/battlesnake.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,19 @@
1313

1414
class BattleSnakeGame(CodeGame):
1515
name: str = "BattleSnake"
16+
submission: str = "main.py"
17+
description: str = """Your bot (`main.py`) controls a snake on a grid-based board.
18+
Snakes collect food, avoid collisions, and try to outlast their opponents."""
19+
default_args: dict = {
20+
"width": 11,
21+
"height": 11,
22+
"browser": False,
23+
}
1624

1725
def __init__(self, config, **kwargs):
1826
super().__init__(config, **kwargs)
1927
self.run_cmd_round: str = "./battlesnake play"
20-
for arg, val in self.game_config.get("args", {}).items():
28+
for arg, val in self.game_config.get("args", self.default_args).items():
2129
if isinstance(val, bool):
2230
if val:
2331
self.run_cmd_round += f" --{arg}"
@@ -85,7 +93,7 @@ def execute_round(self, agents: list[Player]):
8593
player2port[agent.name] = port
8694
# Surprisingly slow despite using &
8795
# Start server in background - just add & to run in background!
88-
self.environment.execute(f"PORT={port} python main.py &", cwd=f"/{agent.name}")
96+
self.environment.execute(f"PORT={port} python {self.submission} &", cwd=f"/{agent.name}")
8997

9098
self.logger.debug(f"Waiting for ports: {player2port}")
9199
available_ports = self._wait_for_ports(list(player2port.values()))
@@ -121,7 +129,7 @@ def execute_round(self, agents: list[Player]):
121129
future.result()
122130
finally:
123131
# Kill all python servers when done
124-
self.environment.execute("pkill -f 'python main.py' || true")
132+
self.environment.execute(f"pkill -f 'python {self.submission}' || true")
125133

126134
def get_results(self, agents: list[Player], round_num: int, stats: RoundStats):
127135
scores = {}
@@ -154,10 +162,10 @@ def get_results(self, agents: list[Player], round_num: int, stats: RoundStats):
154162
stats.player_stats[player].score = score
155163

156164
def validate_code(self, agent: Player) -> tuple[bool, str | None]:
157-
if "main.py" not in agent.environment.execute("ls")["output"]:
158-
return False, "No main.py file found in the root directory"
165+
if self.submission not in agent.environment.execute("ls")["output"]:
166+
return False, f"No {self.submission} file found in the root directory"
159167
# note: no longer calling splitlines
160-
bot_content = agent.environment.execute("cat main.py")["output"]
168+
bot_content = agent.environment.execute(f"cat {self.submission}")["output"]
161169
error_msg = []
162170
for func in [
163171
"def info(",
@@ -166,7 +174,7 @@ def validate_code(self, agent: Player) -> tuple[bool, str | None]:
166174
"def move(",
167175
]:
168176
if func not in bot_content:
169-
error_msg.append(f"There should be a `{func}` function implemented in `main.py`")
177+
error_msg.append(f"There should be a `{func}` function implemented in `{self.submission}`")
170178
if len(error_msg) > 0:
171179
return False, "\n".join(error_msg + ["Don't change the function signatures!"])
172180
return True, None

codeclash/games/corewar/corewar.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@
1010

1111
class CoreWarGame(CodeGame):
1212
name: str = "CoreWar"
13+
description: str = """CoreWar is a programming battle where you write "warriors" in an assembly-like language called Redcode to compete within a virtual machine (MARS), aiming to eliminate your rivals by making their code self-terminate.
14+
Victory comes from crafting clever tactics—replicators, scanners, bombers—that exploit memory layout and instruction timing to control the core."""
15+
submission: str = f"warriors/{COREWAR_FILE}"
1316

1417
def __init__(self, config, **kwargs):
1518
super().__init__(config, **kwargs)
1619
self.run_cmd_round: str = "./src/pmars"
17-
for arg, val in self.game_config.get("args", {}).items():
20+
for arg, val in self.game_config.get("args", self.default_args).items():
1821
if isinstance(val, bool):
1922
if val:
2023
self.run_cmd_round += f" -{arg}"

codeclash/games/dummy/dummy_game.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99

1010
class DummyGame(CodeGame):
1111
name: str = "DummyGame"
12+
description: str = """WARNING: This is a dummy game meant for testing the CodeClash infrastructure. It does not represent a real game."""
13+
submission: str = "main.py"
1214

1315
def execute_round(self, agents: list[Player]) -> None:
14-
args = [f"/{agent.name}/main.py" for agent in agents]
16+
args = [f"/{agent.name}/{self.submission}" for agent in agents]
1517
cmd = f"python engine.py {' '.join(args)} -r {self.game_config['sims_per_round']} > {self.log_env / DUMMY_LOG};"
1618
self.logger.info(f"Running game: {cmd}")
1719
assert_zero_exit_code(self.environment.execute(cmd))

codeclash/games/game.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ def to_dict(self) -> dict[str, Any]:
6060

6161
class CodeGame(ABC):
6262
name: str
63+
description: str
64+
default_args: dict = {}
65+
submission: str
6366

6467
def __init__(self, config: dict, *, tournament_id: str, local_output_dir: Path, keep_containers: bool = False):
6568
"""The CodeGame class is responsible for running games, i.e., taking a list of code

codeclash/games/huskybench/huskybench.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212

1313
class HuskyBenchGame(CodeGame):
1414
name: str = "HuskyBench"
15+
description: str = """In this game, you will write code to control a poker-playing bot, aiming to outsmart your opponents and win chips.
16+
Victory comes from crafting clever strategies—bluffing, reading opponents, and managing your chip stack effectively."""
17+
submission: str = "client/player.py"
1518

1619
def __init__(self, config, **kwargs):
1720
super().__init__(config, **kwargs)
@@ -20,7 +23,7 @@ def __init__(self, config, **kwargs):
2023
f"python engine/main.py --port {HB_PORT} --players {self.num_players} "
2124
f"--sim --sim-rounds {self.game_config['sims_per_round']}"
2225
)
23-
for arg, val in self.game_config.get("args", {}).items():
26+
for arg, val in self.game_config.get("args", self.default_args).items():
2427
if isinstance(val, bool):
2528
if val:
2629
self.run_cmd_round += f" --{arg}"

codeclash/games/robocode/robocode.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,18 @@
1212

1313
class RoboCodeGame(CodeGame):
1414
name: str = "RoboCode"
15+
description: str = """Robocode (Tank Royale) is a programming game where your code is the tank: each turn your bot sends intents—speed plus body/gun/radar turn rates and firepower—based on the game state it perceives via radar.
16+
Your program decides how to move, aim, and fire in a deterministic, turn-based arena to outlast other bots."""
17+
default_args: dict = {
18+
"nodisplay": True,
19+
"nosound": True,
20+
}
21+
submission: str = "robots/custom/"
1522

1623
def __init__(self, config, **kwargs):
1724
super().__init__(config, **kwargs)
1825
self.run_cmd_round: str = "./robocode.sh"
19-
for arg, val in self.game_config.get("args", {}).items():
26+
for arg, val in self.game_config.get("args", self.default_args).items():
2027
if isinstance(val, bool):
2128
if val:
2229
self.run_cmd_round += f" -{arg}"

codeclash/games/robotrumble/robotrumble.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212

1313
class RobotRumbleGame(CodeGame):
1414
name: str = "RobotRumble"
15+
description: str = """RobotRumble is a turn-based coding battle where you program a team of robots in Python to move, attack, and outmaneuver your opponent on a grid.
16+
Every decision is driven by your code, and victory comes from crafting logic that positions robots smartly, times attacks well, and adapts over the 100-turn match."""
17+
submission: str = "robot.py"
1518

1619
def __init__(self, config, **kwargs):
1720
super().__init__(config, **kwargs)
@@ -20,7 +23,7 @@ def __init__(self, config, **kwargs):
2023

2124
def _run_single_simulation(self, agents: list[Player], idx: int) -> str:
2225
"""Run a single robotrumble simulation and return the output."""
23-
args = [f"/{agent.name}/robot.py" for agent in agents]
26+
args = [f"/{agent.name}/{self.submission}" for agent in agents]
2427
cmd = f"{self.run_cmd_round} {shlex.join(args)} > {self.log_env / f'sim_{idx}.txt'}"
2528

2629
# https://github.com/emagedoc/CodeClash/issues/62 (timeouts)
@@ -92,15 +95,15 @@ def get_results(self, agents: list[Player], round_num: int, stats: RoundStats):
9295
stats.player_stats[player].score = score
9396

9497
def validate_code(self, agent: Player) -> tuple[bool, str | None]:
95-
if "robot.py" not in agent.environment.execute("ls")["output"]:
96-
return False, "There should be a `robot.py` file"
97-
if "def robot(state, unit):" not in agent.environment.execute("cat robot.py")["output"]:
98+
if self.submission not in agent.environment.execute("ls")["output"]:
99+
return False, f"There should be a `{self.submission}` file"
100+
if "def robot(state, unit):" not in agent.environment.execute(f"cat {self.submission}")["output"]:
98101
return (
99102
False,
100-
"robot.py does not contain the required robot function. It should be defined as 'def robot(state, unit): ...'",
103+
f"{self.submission} does not contain the required robot function. It should be defined as 'def robot(state, unit): ...'",
101104
)
102-
test_run_cmd = f"{self.run_cmd_round} robot.py robot.py -t 1"
105+
test_run_cmd = f"{self.run_cmd_round} {self.submission} {self.submission} -t 1"
103106
test_run = agent.environment.execute(test_run_cmd)["output"]
104107
if "Some errors occurred:" in test_run:
105-
return False, f"Running robot.py (with `{test_run_cmd}`) resulted in errors:\n{test_run}"
108+
return False, f"Running {self.submission} (with `{test_run_cmd}`) resulted in errors:\n{test_run}"
106109
return True, None

configs/generate_confs.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import argparse
2+
from pathlib import Path
3+
4+
import yaml
5+
6+
from codeclash.games import (
7+
BattleCodeGame,
8+
BattleSnakeGame,
9+
CodeGame,
10+
CoreWarGame,
11+
HuskyBenchGame,
12+
RoboCodeGame,
13+
RobotRumbleGame,
14+
)
15+
16+
17+
class IncludeTag:
18+
def __init__(self, value):
19+
self.value = value
20+
21+
22+
def include_representer(dumper, data):
23+
return dumper.represent_scalar("!include", data.value)
24+
25+
26+
yaml.add_representer(IncludeTag, include_representer)
27+
28+
ARENAS: list[CodeGame] = [
29+
BattleCodeGame,
30+
BattleSnakeGame,
31+
CoreWarGame,
32+
HuskyBenchGame,
33+
RoboCodeGame,
34+
RobotRumbleGame,
35+
]
36+
37+
38+
def prompt_game_desc(arena, rounds):
39+
return f"""You are a software developer ({{{{player_id}}}}) competing in a coding game called {arena.name}.
40+
{arena.description}
41+
42+
The game is played in {rounds} rounds. For every round, you (and your competitors) edit program code that controls your bot. This is round {{{{round}}}}.
43+
After you and your competitor finish editing your codebases, the game is run automatically.
44+
45+
Your task: improve the bot in `{arena.submission}`, located in {{{{working_dir}}}}.
46+
{{{{working_dir}}}} is your codebase, which contains both your both and supporting assets.
47+
All of your commands will be executed in the {{{{working_dir}}}} directory (see notes below).
48+
"""
49+
50+
51+
def get_name(p):
52+
return p["model_name"].split("/")[-1]
53+
54+
55+
def main(models, rounds, simulations, output):
56+
# Get all unique pairs of models
57+
models = yaml.safe_load(open(models))
58+
Path(output).mkdir(parents=True, exist_ok=True)
59+
pairs = []
60+
for i in range(len(models)):
61+
for j in range(i + 1, len(models)):
62+
pairs.append((models[i], models[j]))
63+
64+
for arena in ARENAS:
65+
for pair in pairs:
66+
config = {
67+
"tournament": {
68+
"rounds": rounds,
69+
},
70+
"game": {
71+
"name": arena.name,
72+
"sims_per_round": simulations,
73+
"args": arena.default_args,
74+
},
75+
"players": [
76+
{
77+
"agent": "mini",
78+
"name": get_name(p),
79+
"config": {"agent": IncludeTag("mini/default.yaml"), "model": p},
80+
}
81+
for p in pair
82+
],
83+
"prompts": {"game_description": prompt_game_desc(arena, rounds)},
84+
}
85+
config_name = f"{arena.name}__{get_name(pair[0])}__{get_name(pair[1])}__r{rounds}__s{simulations}.yaml"
86+
with open(f"{output}/{config_name}", "w") as f:
87+
yaml.dump(config, f, default_style=None, sort_keys=False)
88+
89+
90+
if __name__ == "__main__":
91+
parser = argparse.ArgumentParser(description="Generate configuration files.")
92+
parser.add_argument(
93+
"-m",
94+
"--models",
95+
type=str,
96+
default="configs/model_configs.yaml",
97+
help="Path to model configurations.",
98+
)
99+
parser.add_argument(
100+
"-r",
101+
"--rounds",
102+
type=int,
103+
default=15,
104+
help="Number of rounds per tournament for the configuration (default: 15).",
105+
)
106+
parser.add_argument(
107+
"-s",
108+
"--simulations",
109+
type=int,
110+
default=1000,
111+
help="Number of simulations to run per round (default: 1000).",
112+
)
113+
parser.add_argument(
114+
"-o",
115+
"--output",
116+
type=str,
117+
default="configs/main/",
118+
help="Output directory for configuration files (default: main/).",
119+
)
120+
args = parser.parse_args()
121+
main(**vars(args))

configs/model_configs.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
- model_name: "anthropic/claude-opus-4-1-20250805"
2+
model_kwargs:
3+
temperature: 0.0
4+
- model_name: "anthropic/claude-sonnet-4-20250514"
5+
model_kwargs:
6+
temperature: 0.0
7+
- model_name: "@x-ai/grok-code-fast-1"
8+
model_class: portkey
9+
litellm_model_name_override: "xai/grok-code-fast-1"
10+
- model_name: "@google/gemini-2.5-pro"
11+
model_class: portkey
12+
- model_name: "@zhipu/glm-4.5"
13+
model_class: portkey
14+
- model_name: "@openai/gpt-5"
15+
model_class: portkey
16+
- model_name: "@openai/gpt-5-mini"
17+
model_class: portkey
18+
- model_name: "@qwen/qwen-3-coder"
19+
model_class: portkey
20+
- model_name: "@openai/o3"
21+
model_class: portkey

0 commit comments

Comments
 (0)