Skip to content

Commit 2431e63

Browse files
larsenf-elsCopilot
andcommitted
Add runnable Java examples for Battle Runner API
Three example scripts in runner/examples/ using Java 11+ source-file execution (no compilation needed): - RunBattle.java: synchronous battle with ranked results table - AsyncBattle.java: async battle with real-time event streaming - RecordBattle.java: battle recording to .battle.gz replay file All examples read bot locations from the BOTS_DIR environment variable. Includes a Gradle copyRunnerJar task to populate examples/lib/ with the runner fat JAR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 62df1bc commit 2431e63

File tree

7 files changed

+266
-0
lines changed

7 files changed

+266
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,7 @@ generated/
3232
/.kotlin
3333

3434
/.claude/settings.local.json
35+
36+
# Runner examples: JAR copied by :runner:copyRunnerJar
37+
runner/examples/lib/
38+
runner/examples/recordings/

runner/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ dependencies {
4343

4444
## Quick Start
4545

46+
> **Runnable examples** — see [`examples/`](examples/) for complete Java programs you can run directly
47+
> with `java -cp lib/* Example.java` (no compilation needed).
48+
4649
### Kotlin
4750

4851
```kotlin

runner/build.gradle.kts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,17 @@ tasks {
141141
val javadocJar = named<Jar>("javadocJar") { dependsOn("copyJars") }
142142
val sourcesJar = named<Jar>("sourcesJar") { dependsOn("copyJars") }
143143

144+
val copyRunnerJar by registering(Copy::class) {
145+
description = "Copies the runner fat JAR to examples/lib/ for source-file execution."
146+
group = "examples"
147+
148+
dependsOn(fatJar)
149+
150+
from(file(fatJarPath))
151+
into(file("examples/lib"))
152+
rename(".*", "robocode-tankroyale-runner.jar")
153+
}
154+
144155
publishing {
145156
publications {
146157
named<MavenPublication>("maven") {

runner/examples/AsyncBattle.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import dev.robocode.tankroyale.runner.*;
2+
import dev.robocode.tankroyale.common.event.On;
3+
import kotlin.Unit;
4+
import java.util.List;
5+
6+
/**
7+
* Runs an asynchronous battle with real-time event streaming.
8+
*
9+
* Usage:
10+
* export BOTS_DIR=/path/to/sample-bots/java/build/archive
11+
* java -cp lib/* AsyncBattle.java
12+
*/
13+
public class AsyncBattle {
14+
15+
public static void main(String[] args) {
16+
var botsDir = requireBotsDir();
17+
18+
try (var runner = BattleRunner.create(b -> b.embeddedServer())) {
19+
var setup = BattleSetup.classic(s -> s.setNumberOfRounds(3));
20+
var bots = List.of(
21+
BotEntry.of(botsDir + "/Walls"),
22+
BotEntry.of(botsDir + "/SpinBot")
23+
);
24+
25+
System.out.println("Starting async battle: Walls vs SpinBot (3 rounds)");
26+
var owner = new Object();
27+
28+
try (var handle = runner.startBattleAsync(setup, bots)) {
29+
// Subscribe to round events
30+
handle.getOnRoundStarted().plusAssign(new On<>(owner, 0, event -> {
31+
System.out.printf(" Round %d started%n", event.getRoundNumber());
32+
return Unit.INSTANCE;
33+
}));
34+
35+
handle.getOnRoundEnded().plusAssign(new On<>(owner, 0, event -> {
36+
System.out.printf(" Round %d ended (turn %d)%n",
37+
event.getRoundNumber(), event.getTurnNumber());
38+
return Unit.INSTANCE;
39+
}));
40+
41+
handle.getOnGameStarted().plusAssign(new On<>(owner, 0, event -> {
42+
System.out.println("Game started!");
43+
return Unit.INSTANCE;
44+
}));
45+
46+
// Wait for the battle to finish
47+
var results = handle.awaitResults();
48+
49+
System.out.printf("%nResults (%d rounds):%n", results.getNumberOfRounds());
50+
for (var bot : results.getResults()) {
51+
System.out.printf(" #%d %s — %d pts%n",
52+
bot.getRank(), bot.getName(), bot.getTotalScore());
53+
}
54+
}
55+
}
56+
}
57+
58+
private static String requireBotsDir() {
59+
var botsDir = System.getenv("BOTS_DIR");
60+
if (botsDir == null || botsDir.isBlank()) {
61+
System.err.println("Error: BOTS_DIR environment variable is not set.");
62+
System.err.println();
63+
System.err.println("Set it to the directory containing your bot folders, e.g.:");
64+
System.err.println(" export BOTS_DIR=/path/to/sample-bots/java/build/archive");
65+
System.exit(1);
66+
}
67+
return botsDir;
68+
}
69+
}

runner/examples/README.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Battle Runner Examples
2+
3+
Runnable Java examples demonstrating the Battle Runner API. These use Java 11+ source-file execution — no
4+
compilation step required.
5+
6+
## Prerequisites
7+
8+
1. **Build the runner JAR** (from the repository root):
9+
10+
```sh
11+
./gradlew :runner:copyRunnerJar
12+
```
13+
14+
This copies the runner fat JAR to `runner/examples/lib/`.
15+
16+
2. **Build the sample bots** (or prepare your own bots):
17+
18+
```sh
19+
./gradlew :sample-bots:java:build
20+
```
21+
22+
3. **Set `BOTS_DIR`** to the directory containing your bot folders:
23+
24+
```sh
25+
export BOTS_DIR=../../sample-bots/java/build/archive
26+
```
27+
28+
Each subdirectory under `BOTS_DIR` should contain a bot (with a matching `.json` config file).
29+
30+
## Running the Examples
31+
32+
From the `runner/examples/` directory:
33+
34+
### RunBattle — Synchronous battle with results
35+
36+
```sh
37+
java -cp lib/* RunBattle.java
38+
```
39+
40+
Runs a 5-round Classic battle between Walls and SpinBot, then prints a results table with rankings and scores.
41+
42+
### AsyncBattle — Asynchronous battle with event streaming
43+
44+
```sh
45+
java -cp lib/* AsyncBattle.java
46+
```
47+
48+
Starts a 3-round battle asynchronously and streams round start/end events in real time.
49+
50+
### RecordBattle — Battle recording
51+
52+
```sh
53+
java -cp lib/* RecordBattle.java
54+
```
55+
56+
Runs a 3-round battle and writes a `.battle.gz` replay file to a `recordings/` directory.
57+
58+
## Customizing
59+
60+
Each example reads bot paths from the `BOTS_DIR` environment variable. To use different bots, either:
61+
- Edit the example source to reference different bot names
62+
- Point `BOTS_DIR` to a different directory containing your bots
63+
64+
The examples use the `BattleSetup.classic()` preset by default. See the
65+
[Battle Runner API documentation](https://robocode-dev.github.io/tank-royale/api/battle-runner) for all
66+
configuration options.

runner/examples/RecordBattle.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import dev.robocode.tankroyale.runner.*;
2+
import java.nio.file.Path;
3+
import java.util.List;
4+
5+
/**
6+
* Runs a battle and records it to a .battle.gz replay file.
7+
*
8+
* Usage:
9+
* export BOTS_DIR=/path/to/sample-bots/java/build/archive
10+
* java -cp lib/* RecordBattle.java
11+
*/
12+
public class RecordBattle {
13+
14+
public static void main(String[] args) {
15+
var botsDir = requireBotsDir();
16+
var recordingDir = Path.of("recordings");
17+
recordingDir.toFile().mkdirs();
18+
19+
try (var runner = BattleRunner.create(b -> b
20+
.embeddedServer()
21+
.enableRecording(recordingDir))) {
22+
23+
var setup = BattleSetup.classic(s -> s.setNumberOfRounds(3));
24+
var bots = List.of(
25+
BotEntry.of(botsDir + "/Walls"),
26+
BotEntry.of(botsDir + "/SpinBot")
27+
);
28+
29+
System.out.println("Starting recorded battle: Walls vs SpinBot (3 rounds)");
30+
System.out.println("Recording to: " + recordingDir.toAbsolutePath());
31+
32+
var results = runner.runBattle(setup, bots);
33+
34+
System.out.printf("%nBattle complete (%d rounds).%n", results.getNumberOfRounds());
35+
for (var bot : results.getResults()) {
36+
System.out.printf(" #%d %s — %d pts%n",
37+
bot.getRank(), bot.getName(), bot.getTotalScore());
38+
}
39+
40+
var files = recordingDir.toFile().listFiles((dir, name) -> name.endsWith(".battle.gz"));
41+
if (files != null && files.length > 0) {
42+
for (var file : files) {
43+
System.out.printf("%nRecording saved: %s (%.1f KB)%n",
44+
file.getName(), file.length() / 1024.0);
45+
}
46+
}
47+
}
48+
}
49+
50+
private static String requireBotsDir() {
51+
var botsDir = System.getenv("BOTS_DIR");
52+
if (botsDir == null || botsDir.isBlank()) {
53+
System.err.println("Error: BOTS_DIR environment variable is not set.");
54+
System.err.println();
55+
System.err.println("Set it to the directory containing your bot folders, e.g.:");
56+
System.err.println(" export BOTS_DIR=/path/to/sample-bots/java/build/archive");
57+
System.exit(1);
58+
}
59+
return botsDir;
60+
}
61+
}

runner/examples/RunBattle.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import dev.robocode.tankroyale.runner.*;
2+
import java.util.List;
3+
4+
/**
5+
* Runs a synchronous battle with two sample bots and prints the results.
6+
*
7+
* Usage:
8+
* export BOTS_DIR=/path/to/sample-bots/java/build/archive
9+
* java -cp lib/* RunBattle.java
10+
*/
11+
public class RunBattle {
12+
13+
public static void main(String[] args) {
14+
var botsDir = requireBotsDir();
15+
16+
try (var runner = BattleRunner.create(b -> b.embeddedServer())) {
17+
var setup = BattleSetup.classic(s -> s.setNumberOfRounds(5));
18+
var bots = List.of(
19+
BotEntry.of(botsDir + "/Walls"),
20+
BotEntry.of(botsDir + "/SpinBot")
21+
);
22+
23+
System.out.println("Starting battle: Walls vs SpinBot (5 rounds, Classic preset)");
24+
var results = runner.runBattle(setup, bots);
25+
26+
System.out.printf("%nResults (%d rounds):%n", results.getNumberOfRounds());
27+
System.out.println("─".repeat(60));
28+
System.out.printf("%-6s %-20s %10s %10s %10s%n", "Rank", "Bot", "Total", "Bullet", "Ram");
29+
System.out.println("─".repeat(60));
30+
for (var bot : results.getResults()) {
31+
System.out.printf("%-6d %-20s %10d %10d %10d%n",
32+
bot.getRank(),
33+
bot.getName() + " " + bot.getVersion(),
34+
bot.getTotalScore(),
35+
bot.getBulletDamage(),
36+
bot.getRamDamage());
37+
}
38+
}
39+
}
40+
41+
private static String requireBotsDir() {
42+
var botsDir = System.getenv("BOTS_DIR");
43+
if (botsDir == null || botsDir.isBlank()) {
44+
System.err.println("Error: BOTS_DIR environment variable is not set.");
45+
System.err.println();
46+
System.err.println("Set it to the directory containing your bot folders, e.g.:");
47+
System.err.println(" export BOTS_DIR=/path/to/sample-bots/java/build/archive");
48+
System.exit(1);
49+
}
50+
return botsDir;
51+
}
52+
}

0 commit comments

Comments
 (0)