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.WARNING); String serverUrl = null; int rounds = 1; List 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 bots = botDirs.stream().map(BotEntry::of).collect(Collectors.toList()); final int numRounds = rounds; final var setup = BattleSetup.classic(s -> s.setNumberOfRounds(numRounds)); try (var runner = BattleRunner.create(b -> b.externalServer(url).suppressServerOutput())) { var owner = new Object(); try (var handle = runner.startBattleAsync(setup, bots)) { handle.getOnGameStarted().on(owner, event -> { 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 -> { 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("\"", "\\\""); } }