Files
SirRoboGarage/tools/battle_runner/TestBattleRunner.java
T
SirStone 7e1f1483a4 docs(test_framework): add comprehensive integration test guide and fix runner blocking issue
- Document exception handling, zero-value BotResult trap, shared adversary bots
- Add offline parsing example using parseServerOutput
- Skip tests gracefully when JARs missing (guard before suite blocks)
- Fix blocking readLine in runner_process.nim: poll with 50ms sleep + atEnd check
  (was preventing timeout enforcement, now blocks correctly during battle)
- Add test task to QBot.nimble and config.nims setup docs to AGENTS.md
- Add debug logging to TestBattleRunner for bot identity tracking

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-09-13 10:54:56 +02:00

96 lines
4.5 KiB
Java

import dev.robocode.tankroyale.runner.*;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
/**
* Generic battle runner that connects to an external Tank Royale server and
* emits JSON-lines to stdout for downstream parsing.
*
* Usage:
* ./run_test_battle.sh --server-url ws://localhost:7654 --rounds 5 --bot-dirs /path/bot1,/path/bot2
*/
public class TestBattleRunner {
public static void main(String[] args) {
Logger.getLogger("dev.robocode.tankroyale").setLevel(Level.INFO);
String serverUrl = null;
int rounds = 1;
List<String> botDirs = List.of();
for (int i = 0; i < args.length; i++) {
switch (args[i]) {
case "--server-url" -> serverUrl = args[++i];
case "--rounds" -> rounds = Integer.parseInt(args[++i]);
case "--bot-dirs" -> botDirs = Arrays.asList(args[++i].split(","));
default -> { System.err.println("Unknown arg: " + args[i]); System.exit(1); }
}
}
if (serverUrl == null) { System.err.println("--server-url required"); System.exit(1); }
if (botDirs.isEmpty()) { System.err.println("--bot-dirs required"); System.exit(1); }
final String url = serverUrl;
final List<BotEntry> bots = botDirs.stream().map(BotEntry::of).collect(Collectors.toList());
final int numRounds = rounds;
final var setup = BattleSetup.classic(s -> s.setNumberOfRounds(numRounds));
// Debug: check what JSON files the runner will find for each bot dir
for (String dir : botDirs) {
var path = java.nio.file.Path.of(dir);
var jsonPath = path.resolve(path.getFileName() + ".json");
System.err.println("[DEBUG] identity JSON: " + jsonPath + " exists=" + java.nio.file.Files.exists(jsonPath));
}
try (var runner = BattleRunner.create(b -> b.externalServer(url).suppressServerOutput())) {
var owner = new Object();
System.err.println("[DEBUG] bots: " + bots);
try (var handle = runner.startBattleAsync(setup, bots)) {
handle.getOnGameStarted().on(owner, event -> {
System.err.println("[DEBUG] game_started participants: " + event.getParticipants().stream().map(p -> p.getName() + "/" + p.getVersion() + "/" + p.getAuthors()).collect(Collectors.joining(", ")));
var names = event.getParticipants().stream()
.map(p -> "\"" + escape(p.getName()) + "\"")
.collect(Collectors.joining(","));
System.out.println("{\"event\":\"game_started\",\"bots\":[" + names + "]}");
System.out.flush();
});
handle.getOnRoundEnded().on(owner, event -> {
System.err.println("[DEBUG] round_ended results count: " + event.getResults().size());
var results = event.getResults().stream().map(r ->
"{\"name\":\"" + escape(r.getName()) + "\"" +
",\"score\":" + r.getTotalScore() +
",\"rank\":" + r.getRank() +
",\"survived\":" + (r.getSurvival() > 0) + "}"
).collect(Collectors.joining(","));
System.out.println("{\"event\":\"round_ended\",\"round\":" + event.getRoundNumber() + ",\"results\":[" + results + "]}");
System.out.flush();
});
var final_ = handle.awaitResults();
var results = final_.getResults().stream().map(r ->
"{\"name\":\"" + escape(r.getName()) + "\"" +
",\"totalScore\":" + r.getTotalScore() +
",\"rank\":" + r.getRank() +
",\"firstPlaces\":" + r.getFirstPlaces() +
",\"survivalCount\":" + r.getSurvival() + "}"
).collect(Collectors.joining(","));
System.out.println("{\"event\":\"battle_ended\",\"results\":[" + results + "]}");
System.out.flush();
}
} catch (Exception e) {
System.err.println("Battle failed: " + e.getMessage());
System.exit(1);
}
}
// ponytail: minimal JSON escaping — only backslash and double-quote; extend if bot names use control chars
static String escape(String s) {
return s.replace("\\", "\\\\").replace("\"", "\\\"");
}
}