Skip to content

Commit f7102d4

Browse files
committed
Add RoboCode CC:Ladder (24 imported classic-Robocode bots + build tooling)
- 24 open-source classic Robocode bots imported to CodeClash-ai/RoboCode human/robocode/* branches (source of truth, not committed here): shipped sample bots + RoboWiki/PEZ micro-mini bots (Aristocles, Pugilist, HawkOnFire, ...) + single-file DrussGT (world-class top rung). Copy-in + rename to MyTank/package custom (classic robocode.* API, not Tank Royale). Each verified to COMPILE and play a real battle in the arena image (stage 2). - Fix arena description: it mislabeled itself 'Tank Royale' but the engine is classic Robocode (extends robocode.Robot, javac against robocode.jar) — corrected so agents write the right API. - configs/ablations/ladder/make_robocode.yaml: round-robin (100 rounds, ~5 min local) for Elo; README note. - scripts/ladder/: build tooling adapted to Robocode (copy-in porting guide, structural validator, compile+battle smoke, push script, reference bot); ports/ regenerated, not committed.
1 parent 09823d2 commit f7102d4

11 files changed

Lines changed: 457 additions & 2 deletions

File tree

codeclash/arenas/robocode/robocode.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,12 @@
1818

1919
class RoboCodeArena(CodeArena):
2020
name: str = "RoboCode"
21-
description: str = f"""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.
22-
Your program decides how to move, aim, and fire in a deterministic, turn-based arena to outlast other bots.
21+
description: str = f"""Robocode is a programming game where your code IS the tank. This is classic
22+
Robocode (the `robocode.*` API compiled against robocode.jar) — NOT Robocode Tank Royale.
23+
Your bot is a Java class that `extends robocode.Robot` (or `robocode.AdvancedRobot` for non-blocking
24+
control): its `run()` method drives the tank in a loop (e.g. `ahead(100)`, `turnGunRight(90)`,
25+
`fire(3)`), and it reacts to events like `onScannedRobot(ScannedRobotEvent)`, `onHitByBullet(...)`,
26+
and `onHitWall(...)`. Move, aim the gun, sweep the radar, and fire to outlast other bots.
2327
Your bot logic must be written in Java and located in the `robots/custom/` directory.
2428
Keep the main bot class named `{str(RC_FILE)}`, but you can include additional Java files if you'd like."""
2529
default_args: dict = {

configs/ablations/ladder/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,19 @@ You can follow these steps to create your own "CC:<arena>" ladder.
1212
The tricky part is typically finding a large collection of human solutions for a particular arena.
1313
We've typically found that googling for online leaderboards or awesome-<arena> repositories (e.g. [BattleSnake](https://github.com/BattlesnakeOfficial/awesome-battlesnake)) is a good strategy.
1414

15+
### RoboCode (newly added)
16+
17+
The [CC:RoboCode](https://github.com/CodeClash-ai/RoboCode) repo hosts 24 human bots on `human/robocode/*`
18+
branches (bot code lives only on the branches, not in this repo). This arena is **classic Robocode**
19+
(`robocode.*` API compiled against `robocode.jar`), so importing open-source bots is mostly a
20+
mechanical copy-in + rename rather than a strategy rewrite: each bot's Java class(es) are placed in
21+
`robots/custom/`, the main class renamed to `MyTank`, `package custom;`. The set spans the shipped
22+
sample bots (SittingDuck/Walls/Corners/…) through famous RoboWiki/PEZ micro-mini bots (Aristocles,
23+
Pugilist, HawkOnFire) up to a single-file **DrussGT** (a world-class bot) as the top rung; a few use
24+
Robocode's sanctioned `getDataFile` persistence (degrades gracefully without cross-battle saves).
25+
Diamond/BeepBoop remain as future top rungs (nested-package multi-file → need flattening). Each import
26+
was verified to **compile and play a real battle**. Build tooling lives in `scripts/ladder/`.
27+
1528
## Config layout
1629

1730
Each arena has a few kinds of config in this folder:
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Round-robin over the human classic-Robocode bots (imported from open-source repos: RoboWiki/PEZ
2+
# micro-mini bots, single-file DrussGT/HawkOnFire/WaveSurfing, and the shipped sample bots), used to
3+
# RANK them via Elo/Bradley-Terry -> see robocode.yaml for the ladder. rounds:0 = dummy players play
4+
# the baseline battles (no code edits). N bots -> N*(N-1)/2 pairs; each pair plays sims_per_round
5+
# battle-rounds (batched 10 at a time, sim_concurrency in parallel). Bots live on CodeClash-ai/RoboCode
6+
# human/* branches; Docker required.
7+
# COST (measured ~3.4s for a 50-round pair headless): at sims_per_round=100, 24 bots -> 276 pairs
8+
# ~= 30 core-minutes -> ~5 min on a laptop (--workers 6). Robocode battles are cheap; runs locally.
9+
# Run: uv run codeclash ladder make configs/ablations/ladder/make_robocode.yaml --workers 6
10+
tournament:
11+
rounds: 0
12+
game:
13+
name: RoboCode
14+
sims_per_round: 100
15+
sim_concurrency: 5
16+
args:
17+
nodisplay: true
18+
nosound: true
19+
players:
20+
- agent: dummy
21+
branch_init: human/robocode/aristocles
22+
- agent: dummy
23+
branch_init: human/robocode/blackwidow
24+
- agent: dummy
25+
branch_init: human/robocode/cham
26+
- agent: dummy
27+
branch_init: human/robocode/corners
28+
- agent: dummy
29+
branch_init: human/robocode/crazy
30+
- agent: dummy
31+
branch_init: human/robocode/dacruzer
32+
- agent: dummy
33+
branch_init: human/robocode/drussgt
34+
- agent: dummy
35+
branch_init: human/robocode/fire
36+
- agent: dummy
37+
branch_init: human/robocode/frankie
38+
- agent: dummy
39+
branch_init: human/robocode/gouldingi
40+
- agent: dummy
41+
branch_init: human/robocode/hawkonfire
42+
- agent: dummy
43+
branch_init: human/robocode/icarus
44+
- agent: dummy
45+
branch_init: human/robocode/leach
46+
- agent: dummy
47+
branch_init: human/robocode/littlebrother
48+
- agent: dummy
49+
branch_init: human/robocode/mako
50+
- agent: dummy
51+
branch_init: human/robocode/pugilist
52+
- agent: dummy
53+
branch_init: human/robocode/ramfire
54+
- agent: dummy
55+
branch_init: human/robocode/sittingduck
56+
- agent: dummy
57+
branch_init: human/robocode/spinbot
58+
- agent: dummy
59+
branch_init: human/robocode/tityus
60+
- agent: dummy
61+
branch_init: human/robocode/tracker
62+
- agent: dummy
63+
branch_init: human/robocode/trackfire
64+
- agent: dummy
65+
branch_init: human/robocode/walls
66+
- agent: dummy
67+
branch_init: human/robocode/wavesurfing
68+
prompts:
69+
game_description: RoboCode ladder

scripts/ladder/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Regenerated build artifacts — bot ports live on the CodeClash-ai/<arena> branches
2+
# (source of truth), not in this repo. Populate ports/ locally when (re)building a ladder.
3+
ports/
4+
out/

scripts/ladder/PORTING_GUIDE.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Importing a classic Robocode bot into the CodeClash arena
2+
3+
This arena is **classic Robocode** (Java, `robocode.jar`), NOT Tank Royale. Importing an
4+
open-source classic bot is mostly a mechanical **copy-in + rename**, not a strategy rewrite.
5+
6+
## The submission contract
7+
The submission is a directory `robots/custom/` containing Java source. The arena harness then:
8+
1. copies `robots/custom/*` into `robots/<player>/`,
9+
2. runs `sed 's/custom/<player>/g'` over every `.java` (this renames the `package custom`
10+
`package <player>` so bots are namespaced),
11+
3. compiles `javac -cp "libs/robocode.jar" robots/<player>/*.java`**flat glob, one directory**,
12+
4. runs battles selecting `<player>.MyTank*`.
13+
14+
`validate_code` requires `robots/custom/MyTank.java` to exist, compile, and produce `MyTank.class`.
15+
16+
## Hard rules for a port (`ports/<slug>/`)
17+
Produce a directory `scripts/ladder/ports/<slug>/` containing:
18+
- **`MyTank.java`** — the bot's main class, **renamed to `public class MyTank`** (rename the source's
19+
main class everywhere: declaration, constructors, and any self-references). It must
20+
`extends robocode.Robot` (or `AdvancedRobot` / `TeamRobot` / `RateControlRobot` / `JuniorRobot`).
21+
- **`package custom;`** as the package line in `MyTank.java` and in *every* helper file. Replace any
22+
original `package a.b.c;`.
23+
- **Helper classes** (if the bot is multi-file) as additional flat `.java` files in the same dir,
24+
also `package custom;`. **No nested subdirectories** — the compile is a flat `*.java` glob, so a
25+
bot that uses nested packages (e.g. `voidious/gun/…`) must be flattened or it won't compile.
26+
Inner classes inside one file are fine (preferred).
27+
- **The literal substring `custom` must NOT appear anywhere except the `package custom;` lines.**
28+
The harness's `sed s/custom/<player>/g` is a blind global replace, so any identifier, comment, or
29+
string containing "custom" would be corrupted. Rename such tokens.
30+
31+
## What's allowed / what breaks
32+
- Only the classic `robocode.*` API. No Tank Royale (`dev.robocode.tankroyale.*`).
33+
- Robocode runs under a security manager: **raw `java.io`, threads, reflection, and network are
34+
blocked.** Persistent gun/enemy stats via the sanctioned `getDataFile()` +
35+
`RobocodeFileOutputStream` are allowed but **may not persist** across our battles — such a bot
36+
still compiles and plays, it just degrades to no saved data (fine; note it). If the source uses
37+
raw `java.io`/threads/reflection, it will fail — flag and skip, or strip that path if it's optional.
38+
- Keep behavior faithful: do not "improve" the bot; only rename/repackage/flatten.
39+
40+
## Deliverable + report
41+
Write the port dir `scripts/ladder/ports/<slug>/` (at minimum `MyTank.java`). It must be valid Java
42+
that would compile against `robocode.jar`. In your report note per bot: source repo/author, base
43+
class, #files, and any caveat (data-file use, "custom"-token rename you had to do, multi-file, or
44+
anything you had to strip). A reference `MyTank.java` lives at
45+
`scripts/ladder/examples/robots/custom/MyTank.java`.

scripts/ladder/README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Ladder build tooling (RoboCode)
2+
3+
Operational one-off scripts for constructing a CC:Ladder for **classic Robocode**. Unlike the
4+
SCML/Gomoku ladders (which re-express bots into a function contract), Robocode importing is mostly
5+
a mechanical **copy-in + rename**: classic bots are Java classes that drop into `robots/custom/`.
6+
7+
This is scaffolding, not product code — nothing under `codeclash/` imports it. The durable outputs
8+
are the `human/*` branches on `CodeClash-ai/RoboCode` (the bots) and the
9+
`configs/ablations/ladder/*.yaml` configs. Ports are **not** committed here (see `.gitignore`).
10+
11+
## Files
12+
- `PORTING_GUIDE.md` — the classic-Robocode submission contract + copy-in/rename rules (main class
13+
`MyTank`, `package custom;`, flat files, no stray "custom" token). Hand to an import agent with
14+
`examples/robots/custom/MyTank.java` as the reference.
15+
- `examples/robots/custom/MyTank.java` — reference bot (AdvancedRobot) + porting example.
16+
- `validate_ports.py` — stage 1 (structural, local): each `ports/<slug>/` has `MyTank.java` with
17+
`class MyTank extends <RobocodeBase>`, `package custom;`, flat, no sed-hazard token. Writes `_stage1.json`.
18+
- `run_smoke_all.sh`**stage 2 (the real gate):** in the arena Docker image, compile each port
19+
(`javac -cp libs/robocode.jar`) and run a real 3-round battle vs `sample.Walls`; confirm it
20+
compiles AND plays. Writes `_stage2.json` + per-bot compile/battle logs in `out/`.
21+
- `push_branches.sh` — push each stage-2-healthy `ports/<slug>/` to `CodeClash-ai/RoboCode` as
22+
`human/robocode/<slug>` with the files under `robots/custom/`.
23+
- `RUN.md` — round-robin (`ladder make`) + Elo ranking instructions.
24+
25+
## Typical flow
26+
1. Populate `ports/<slug>/MyTank.java` (fan out import agents with `PORTING_GUIDE.md`).
27+
2. `python3 scripts/ladder/validate_ports.py` → stage 1 (structural)
28+
3. `bash scripts/ladder/run_smoke_all.sh` → stage 2 (compile + battle each; needs Docker)
29+
4. `bash scripts/ladder/push_branches.sh` → push healthy ports to branches
30+
5. Rank + assemble configs — see `RUN.md`.
31+
32+
Note: `ports/` and `out/` are gitignored and shared on disk across branches — clean stale files
33+
(`rm -rf scripts/ladder/ports/* scripts/ladder/out`) when switching between arena ladder branches.

scripts/ladder/RUN.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Running the RoboCode round-robin (Elo ranking)
2+
3+
Rank the human bots in `configs/ablations/ladder/make_robocode.yaml` via all-pairs battles, then fit
4+
Elo. Each pair plays `sims_per_round` battle-rounds (the arena batches them `SIMS_PER_RUN=10` at a
5+
time, `sim_concurrency` in parallel). Battles are headless Java; cost is moderate — measure one pair
6+
first (below) to size the full run. Bots live on `CodeClash-ai/RoboCode` `human/*` branches;
7+
`branch_init` fetches them at runtime, so you only need this repo branch + Docker + a GitHub token.
8+
9+
## Prerequisites
10+
- Docker running, git, `uv`, and a token for `CodeClash-ai/RoboCode` (public → `gh auth token`).
11+
12+
## Step 0 — pre-build the arena image once (avoids a build stampede under -w N)
13+
```bash
14+
docker build -t codeclash/robocode -f codeclash/arenas/robocode/RoboCode.Dockerfile .
15+
```
16+
17+
## Step 1 — round-robin
18+
`N` bots → `N*(N-1)/2` pairs. Keep `--workers ≈ cores-2`. Resumable (skips logged pairs).
19+
```bash
20+
GITHUB_TOKEN=$(gh auth token) \
21+
uv run codeclash ladder make configs/ablations/ladder/make_robocode.yaml --workers 6
22+
```
23+
Tip: time one pair first to size the run — e.g. run a 2-player `codeclash run` config and note the
24+
wall time, then multiply by pair count / workers.
25+
26+
## Step 2 — rank
27+
```bash
28+
python -m codeclash.analysis.metrics.elo -d logs/ladder/RoboCode --output-dir assets/robocode_elo
29+
```
30+
Prints the Bradley-Terry/Elo ordering and writes `assets/robocode_elo/elo_results.log`.
31+
32+
## Step 3 — assemble the ladder configs (once you have the ranking)
33+
1. `configs/ablations/ladder/rungs/robocode.yaml` — ranked opponents, weakest first, strongest last.
34+
2. `configs/ablations/ladder/robocode.yaml` — climber `player` + `ladder: !include ablations/ladder/rungs/robocode.yaml`
35+
+ a `ladder_rules` block (model on `battlesnake.yaml`).
36+
3. Optional `robocode__<model>.yaml` per-model variants.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package custom;
2+
3+
import robocode.AdvancedRobot;
4+
import robocode.ScannedRobotEvent;
5+
import robocode.HitByBulletEvent;
6+
import robocode.HitWallEvent;
7+
8+
/**
9+
* Reference bot for the CodeClash classic-Robocode arena contract and the porting example.
10+
*
11+
* The main class MUST be named MyTank and live in robots/custom/MyTank.java with `package custom;`.
12+
* This is a simple AdvancedRobot: oscillate, spin the radar to keep a lock, and fire with power
13+
* scaled to distance. Kept intentionally modest so it's a fair smoke-test opponent, not a wall.
14+
*/
15+
public class MyTank extends AdvancedRobot {
16+
private int moveDirection = 1;
17+
18+
public void run() {
19+
// Decouple gun+radar from the body so we can aim while moving.
20+
setAdjustGunForRobotTurn(true);
21+
setAdjustRadarForGunTurn(true);
22+
while (true) {
23+
setTurnRadarRight(360);
24+
setAhead(120 * moveDirection);
25+
setTurnRight(20);
26+
execute();
27+
}
28+
}
29+
30+
public void onScannedRobot(ScannedRobotEvent e) {
31+
double distance = e.getDistance();
32+
double power = distance > 400 ? 1.0 : (distance > 200 ? 2.0 : 3.0);
33+
34+
// Simple head-on aim: turn gun toward the absolute bearing of the target.
35+
double absoluteBearing = getHeading() + e.getBearing();
36+
double gunTurn = absoluteBearing - getGunHeading();
37+
setTurnGunRight(normalizeBearing(gunTurn));
38+
if (Math.abs(gunTurn) < 10) {
39+
setFire(power);
40+
}
41+
// Keep the radar locked on the target.
42+
setTurnRadarRight(normalizeBearing(absoluteBearing - getRadarHeading()) * 2);
43+
}
44+
45+
public void onHitByBullet(HitByBulletEvent e) {
46+
moveDirection = -moveDirection;
47+
setAhead(80 * moveDirection);
48+
}
49+
50+
public void onHitWall(HitWallEvent e) {
51+
moveDirection = -moveDirection;
52+
}
53+
54+
private double normalizeBearing(double angle) {
55+
while (angle > 180) angle -= 360;
56+
while (angle < -180) angle += 360;
57+
return angle;
58+
}
59+
}

scripts/ladder/push_branches.sh

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/usr/bin/env bash
2+
# Push every stage-2-healthy port to CodeClash-ai/RoboCode as a human/robocode/<slug> branch,
3+
# placing the port's .java files in the arena submission dir robots/custom/.
4+
# Port dir scripts/ladder/ports/<slug>/ -> branch human/robocode/<slug> with robots/custom/*.java.
5+
# bash scripts/ladder/push_branches.sh
6+
set -euo pipefail
7+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
8+
PORTS="$REPO_ROOT/scripts/ladder/ports"
9+
ARENA_REPO="CodeClash-ai/RoboCode"
10+
SKIP="" # space-separated slugs to defer, if any
11+
12+
TMP=$(mktemp -d)
13+
git clone -q "https://x-access-token:$(gh auth token)@github.com/${ARENA_REPO}.git" "$TMP"
14+
cd "$TMP"
15+
DEFAULT=$(git symbolic-ref --short HEAD)
16+
17+
pushed=0; skipped=0
18+
PY="$(command -v python3 || command -v python)"
19+
OKS=$("$PY" -c "import json;d=json.load(open('$PORTS/_stage2.json'));print('\n'.join(k for k,v in sorted(d.items()) if v.get('ok')))")
20+
21+
for slug in $OKS; do
22+
case " $SKIP " in *" $slug "*) echo "SKIP $slug (deferred)"; skipped=$((skipped+1)); continue;; esac
23+
branch="human/robocode/$slug"
24+
git checkout -q "$DEFAULT"
25+
git checkout -q -B "$branch"
26+
rm -rf robots/custom; mkdir -p robots/custom
27+
cp "$PORTS/$slug/"*.java robots/custom/
28+
git add robots/custom
29+
git -c user.email=player@codeclash.com -c user.name="CodeClash" commit -qm "Import $slug (RoboCode ladder)"
30+
git push -q -f -u origin "$branch" 2>/dev/null
31+
echo "PUSH $branch"
32+
pushed=$((pushed+1))
33+
done
34+
cd "$REPO_ROOT"; rm -rf "$TMP"
35+
echo "=== pushed $pushed, skipped $skipped ==="

0 commit comments

Comments
 (0)