From ceb0760e286beb3c44b3be692b2e3db2a59abd10 Mon Sep 17 00:00:00 2001 From: Chase-san <34535+Chase-san@users.noreply.github.com> Date: Sat, 31 Oct 2026 20:52:59 -0400 Subject: [PATCH 1/2] Add CLEARDATA to the RoboRumble client Robots keep what they save in the robot data directory from one battle to the next. When enemy names are hidden, a file saved for #1 gets read in the next battle against a different robot. With CLEARDATA=YES the client deletes the robot data directory before each battle, so saved data lasts between rounds but not between battles. Data in a robot's jar is safe, because a robot gets it back when a battle starts. Uploads say cleardata=YES, so a server can refuse results from clients that keep data. Co-Authored-By: Claude Opus 5.5 --- .../main/resources/roborumble/meleerumble.txt | 7 +++ .../main/resources/roborumble/roborumble.txt | 7 +++ .../main/resources/roborumble/teamrumble.txt | 7 +++ .../main/resources/roborumble/twinduel.txt | 7 +++ robocode.roborumble/build.gradle.kts | 1 + .../battlesengine/BattlesRunner.java | 26 ++++++++++ .../roborumble/netengine/ResultsUpload.java | 4 +- .../battlesengine/BattlesRunnerTest.java | 47 +++++++++++++++++++ 8 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 robocode.roborumble/src/test/java/net/sf/robocode/roborumble/battlesengine/BattlesRunnerTest.java diff --git a/robocode.content/src/main/resources/roborumble/meleerumble.txt b/robocode.content/src/main/resources/roborumble/meleerumble.txt index 7f289e8f6..adb082deb 100644 --- a/robocode.content/src/main/resources/roborumble/meleerumble.txt +++ b/robocode.content/src/main/resources/roborumble/meleerumble.txt @@ -70,6 +70,13 @@ TEAMS=NOT # Properties for the battle engine #------------------------------------------------------------------------------ +# CLEARDATA +# If set to YES, the data robots saved is deleted before each battle, +# so they only keep it between the rounds of one battle. Data in a +# robot's jar is not lost. Use YES or NOT. + +CLEARDATA=NOT + # FIELDL Battlefield width measured in pixels. # FIELDH Battlefield height measured in pixels. # diff --git a/robocode.content/src/main/resources/roborumble/roborumble.txt b/robocode.content/src/main/resources/roborumble/roborumble.txt index ed29b1a92..26920eabf 100644 --- a/robocode.content/src/main/resources/roborumble/roborumble.txt +++ b/robocode.content/src/main/resources/roborumble/roborumble.txt @@ -70,6 +70,13 @@ TEAMS=NOT # Properties for the battle engine #------------------------------------------------------------------------------ +# CLEARDATA +# If set to YES, the data robots saved is deleted before each battle, +# so they only keep it between the rounds of one battle. Data in a +# robot's jar is not lost. Use YES or NOT. + +CLEARDATA=NOT + # FIELDL Battlefield width measured in pixels. # FIELDH Battlefield height measured in pixels. # diff --git a/robocode.content/src/main/resources/roborumble/teamrumble.txt b/robocode.content/src/main/resources/roborumble/teamrumble.txt index 0fd47aa1f..d04b6fb05 100644 --- a/robocode.content/src/main/resources/roborumble/teamrumble.txt +++ b/robocode.content/src/main/resources/roborumble/teamrumble.txt @@ -70,6 +70,13 @@ TEAMS=YES # Properties for the battle engine #------------------------------------------------------------------------------ +# CLEARDATA +# If set to YES, the data robots saved is deleted before each battle, +# so they only keep it between the rounds of one battle. Data in a +# robot's jar is not lost. Use YES or NOT. + +CLEARDATA=NOT + # FIELDL Battlefield width measured in pixels. # FIELDH Battlefield height measured in pixels. # diff --git a/robocode.content/src/main/resources/roborumble/twinduel.txt b/robocode.content/src/main/resources/roborumble/twinduel.txt index a3a6de71c..68014858a 100644 --- a/robocode.content/src/main/resources/roborumble/twinduel.txt +++ b/robocode.content/src/main/resources/roborumble/twinduel.txt @@ -70,6 +70,13 @@ TEAMS=YES # Properties for the battle engine #------------------------------------------------------------------------------ +# CLEARDATA +# If set to YES, the data robots saved is deleted before each battle, +# so they only keep it between the rounds of one battle. Data in a +# robot's jar is not lost. Use YES or NOT. + +CLEARDATA=NOT + # FIELDL Battlefield width measured in pixels. # FIELDH Battlefield height measured in pixels. # diff --git a/robocode.roborumble/build.gradle.kts b/robocode.roborumble/build.gradle.kts index 9955bdba7..a44f5ebdc 100644 --- a/robocode.roborumble/build.gradle.kts +++ b/robocode.roborumble/build.gradle.kts @@ -8,6 +8,7 @@ dependencies { runtimeOnly(project(":robocode.core")) runtimeOnly(project(":robocode.battle")) runtimeOnly(libs.codesize) + testImplementation(testLibs.junit) } description = "Roborumble Client" diff --git a/robocode.roborumble/src/main/java/net/sf/robocode/roborumble/battlesengine/BattlesRunner.java b/robocode.roborumble/src/main/java/net/sf/robocode/roborumble/battlesengine/BattlesRunner.java index 599e6cda0..b8a00df3f 100644 --- a/robocode.roborumble/src/main/java/net/sf/robocode/roborumble/battlesengine/BattlesRunner.java +++ b/robocode.roborumble/src/main/java/net/sf/robocode/roborumble/battlesengine/BattlesRunner.java @@ -8,6 +8,7 @@ package net.sf.robocode.roborumble.battlesengine; +import net.sf.robocode.io.FileUtil; import net.sf.robocode.io.Logger; import robocode.control.*; import robocode.control.events.BattleAdaptor; @@ -15,6 +16,7 @@ import robocode.control.events.BattleErrorEvent; import java.io.*; +import java.nio.file.Files; import java.util.*; @@ -36,6 +38,7 @@ public class BattlesRunner { private final String outfile; private final String user; private final String game; + private final boolean cleardata; private static RobotResults[] lastResults; private static IRobocodeEngine engine; @@ -48,6 +51,7 @@ public BattlesRunner(String game, Properties parameters) { outfile = parameters.getProperty("OUTPUT", ""); user = parameters.getProperty("USER", ""); this.game = game; + cleardata = parameters.getProperty("CLEARDATA", "NOT").equals("YES"); initialize(); } @@ -101,6 +105,9 @@ public void runBattlesImpl(boolean melee) { battle.getBattlefield(), robotsList); lastResults = null; + if (cleardata) { + deleteContents(FileUtil.getRobotsDataDir()); + } engine.runBattle(specification, true); if (lastResults != null && lastResults.length > 1) { dumpResults(outtxt, lastResults, rumbleBattle, melee); @@ -116,6 +123,25 @@ public void runBattlesImpl(boolean melee) { outtxt.close(); } + /** + * Deletes everything in a directory. For the robot data directory this is safe between battles, as + * a robot gets back the data in its jar when a battle starts. + */ + static void deleteContents(File directory) { + final File[] files = directory.listFiles(); + + if (files != null) { + for (File file : files) { + if (file.isDirectory() && !Files.isSymbolicLink(file.toPath())) { + deleteContents(file); + } + if (!file.delete()) { + Logger.logWarning("Could not delete " + file); + } + } + } + } + private String getEnemies(boolean melee, String[] bots) { String enemies; diff --git a/robocode.roborumble/src/main/java/net/sf/robocode/roborumble/netengine/ResultsUpload.java b/robocode.roborumble/src/main/java/net/sf/robocode/roborumble/netengine/ResultsUpload.java index a5b55db04..05a643f2f 100644 --- a/robocode.roborumble/src/main/java/net/sf/robocode/roborumble/netengine/ResultsUpload.java +++ b/robocode.roborumble/src/main/java/net/sf/robocode/roborumble/netengine/ResultsUpload.java @@ -41,6 +41,7 @@ public class ResultsUpload { private final CompetitionsSelector size; private final String battlesnumfile; private final String priority; + private final boolean cleardata; private final String teams; private final String melee; @@ -60,6 +61,7 @@ public ResultsUpload(String game, Properties parameters, String clientVersion) { battlesnumfile = parameters.getProperty("BATTLESNUMFILE", ""); priority = parameters.getProperty("PRIORITYBATTLESFILE", ""); client = clientVersion; + cleardata = parameters.getProperty("CLEARDATA", "NOT").equals("YES"); teams = parameters.getProperty("TEAMS", "NOT"); melee = parameters.getProperty("MELEE", "NOT"); @@ -168,7 +170,7 @@ public void uploadResults() { // if the match mode was general, then send the results to all competitions (assuming codesize is used). // if its not, then send results only to smaller size competitions - final String commonData = "&version=1" + "&client=" + client + "&teams=" + teams + "&melee=" + melee + final String commonData = "&version=1" + (cleardata ? "&cleardata=YES" : "") + "&client=" + client + "&teams=" + teams + "&melee=" + melee + "&rounds=" + header[1] + "&field=" + header[2] + "&user=" + user + "&time=" + header[4] + "&fname=" + first[0] + "&fscore=" + first[1] + "&fbulletd=" + first[2] + "&fsurvival=" + first[3] + "&sname=" + second[0] + "&sscore=" + second[1] + "&sbulletd=" + second[2] + "&ssurvival=" + second[3]; diff --git a/robocode.roborumble/src/test/java/net/sf/robocode/roborumble/battlesengine/BattlesRunnerTest.java b/robocode.roborumble/src/test/java/net/sf/robocode/roborumble/battlesengine/BattlesRunnerTest.java new file mode 100644 index 000000000..2245e9c35 --- /dev/null +++ b/robocode.roborumble/src/test/java/net/sf/robocode/roborumble/battlesengine/BattlesRunnerTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2001-2026 Mathew A. Nelson and Robocode contributors + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://robocode.sourceforge.io/license/epl-v10.html + */ +package net.sf.robocode.roborumble.battlesengine; + + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.IOException; + + +/** + * @author Robert Maupin (original) + */ +public class BattlesRunnerTest { + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + @Test + public void deletesEverythingInTheDirectoryAndKeepsIt() throws IOException { + File data = folder.newFolder("data"); + File robot = new File(data, "a/b/One.data"); + + Assert.assertTrue(robot.mkdirs()); + Assert.assertTrue(new File(robot, "#1").createNewFile()); + Assert.assertTrue(new File(data, "loose").createNewFile()); + + BattlesRunner.deleteContents(data); + + Assert.assertTrue(data.isDirectory()); + Assert.assertEquals(0, data.list().length); + } + + @Test + public void aMissingDirectoryIsFine() { + BattlesRunner.deleteContents(new File(folder.getRoot(), "missing")); + } +} From 3c17816574ace2f95dee5eb5b4c7f93136af4af2 Mon Sep 17 00:00:00 2001 From: Chase-san <34535+Chase-san@users.noreply.github.com> Date: Sat, 31 Oct 2026 22:22:21 -0400 Subject: [PATCH 2/2] Save CLEARDATA with each battle's results The upload read CLEARDATA from the configuration, but results can wait in the results file until a later upload. If the setting changed in between, those results were labelled with the new setting instead of the one they were played with. BattlesRunner now saves the setting in each battle's header, and the upload reads it from there. A header saved by an earlier client has no setting, which counts as NOT. Co-Authored-By: Claude Opus 5.5 --- .../battlesengine/BattlesRunner.java | 7 +++- .../roborumble/netengine/ResultsUpload.java | 17 ++++++-- .../netengine/ResultsUploadTest.java | 39 +++++++++++++++++++ 3 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 robocode.roborumble/src/test/java/net/sf/robocode/roborumble/netengine/ResultsUploadTest.java diff --git a/robocode.roborumble/src/main/java/net/sf/robocode/roborumble/battlesengine/BattlesRunner.java b/robocode.roborumble/src/main/java/net/sf/robocode/roborumble/battlesengine/BattlesRunner.java index b8a00df3f..21189f5a3 100644 --- a/robocode.roborumble/src/main/java/net/sf/robocode/roborumble/battlesengine/BattlesRunner.java +++ b/robocode.roborumble/src/main/java/net/sf/robocode/roborumble/battlesengine/BattlesRunner.java @@ -36,6 +36,8 @@ public class BattlesRunner { private final int fieldlen; private final int fieldhei; private final String outfile; + // The settings saved with each battle's results, which an upload may only read much later + private final StringBuilder resultSettings = new StringBuilder(); private final String user; private final String game; private final boolean cleardata; @@ -52,6 +54,9 @@ public BattlesRunner(String game, Properties parameters) { user = parameters.getProperty("USER", ""); this.game = game; cleardata = parameters.getProperty("CLEARDATA", "NOT").equals("YES"); + if (cleardata) { + resultSettings.append(",CLEARDATA=YES"); + } initialize(); } @@ -197,7 +202,7 @@ private void dumpResults(PrintStream outtxt, RobotResults[] results, RumbleBattl outtxt.println( game + "," + numrounds + "," + fieldlen + "x" + fieldhei + "," + user + "," - + System.currentTimeMillis() + "," + rumbleBattle.getRunonly()); + + System.currentTimeMillis() + "," + rumbleBattle.getRunonly() + resultSettings); outtxt.println(name1 + "," + points1 + "," + bullets1 + "," + survival1); outtxt.println(name2 + "," + points2 + "," + bullets2 + "," + survival2); } diff --git a/robocode.roborumble/src/main/java/net/sf/robocode/roborumble/netengine/ResultsUpload.java b/robocode.roborumble/src/main/java/net/sf/robocode/roborumble/netengine/ResultsUpload.java index 05a643f2f..5c9400a1d 100644 --- a/robocode.roborumble/src/main/java/net/sf/robocode/roborumble/netengine/ResultsUpload.java +++ b/robocode.roborumble/src/main/java/net/sf/robocode/roborumble/netengine/ResultsUpload.java @@ -41,7 +41,6 @@ public class ResultsUpload { private final CompetitionsSelector size; private final String battlesnumfile; private final String priority; - private final boolean cleardata; private final String teams; private final String melee; @@ -61,7 +60,6 @@ public ResultsUpload(String game, Properties parameters, String clientVersion) { battlesnumfile = parameters.getProperty("BATTLESNUMFILE", ""); priority = parameters.getProperty("PRIORITYBATTLESFILE", ""); client = clientVersion; - cleardata = parameters.getProperty("CLEARDATA", "NOT").equals("YES"); teams = parameters.getProperty("TEAMS", "NOT"); melee = parameters.getProperty("MELEE", "NOT"); @@ -170,7 +168,7 @@ public void uploadResults() { // if the match mode was general, then send the results to all competitions (assuming codesize is used). // if its not, then send results only to smaller size competitions - final String commonData = "&version=1" + (cleardata ? "&cleardata=YES" : "") + "&client=" + client + "&teams=" + teams + "&melee=" + melee + final String commonData = "&version=1" + (hasSetting(header, "CLEARDATA=YES") ? "&cleardata=YES" : "") + "&client=" + client + "&teams=" + teams + "&melee=" + melee + "&rounds=" + header[1] + "&field=" + header[2] + "&user=" + user + "&time=" + header[4] + "&fname=" + first[0] + "&fscore=" + first[1] + "&fbulletd=" + first[2] + "&fsurvival=" + first[3] + "&sname=" + second[0] + "&sscore=" + second[1] + "&sbulletd=" + second[2] + "&ssurvival=" + second[3]; @@ -235,6 +233,19 @@ private void saveError(PrintStream outtxt, String match, String bot1, String bot System.out.println("Unable to upload results " + match + " " + bot1 + " " + bot2); } + /** + * Returns whether a battle's saved header has a setting. Settings come after the match type, and a header + * saved by an earlier version of the client has none. + */ + static boolean hasSetting(String[] header, String setting) { + for (int i = 6; i < header.length; i++) { + if (header[i].equals(setting)) { + return true; + } + } + return false; + } + private boolean senddata(String game, String data, PrintStream outtxt, boolean saveonerror, Vector results, int i, PrintStream battlesnum, PrintStream prioritybattles) { boolean errorsfound = false; OutputStream out = null; diff --git a/robocode.roborumble/src/test/java/net/sf/robocode/roborumble/netengine/ResultsUploadTest.java b/robocode.roborumble/src/test/java/net/sf/robocode/roborumble/netengine/ResultsUploadTest.java new file mode 100644 index 000000000..7a829b79a --- /dev/null +++ b/robocode.roborumble/src/test/java/net/sf/robocode/roborumble/netengine/ResultsUploadTest.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2001-2026 Mathew A. Nelson and Robocode contributors + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://robocode.sourceforge.io/license/epl-v10.html + */ +package net.sf.robocode.roborumble.netengine; + + +import org.junit.Assert; +import org.junit.Test; + + +/** + * @author Robert Maupin (original) + */ +public class ResultsUploadTest { + + @Test + public void aHeaderCanHoldSeveralSettings() { + String[] header = "game,35,800x600,user,1000,GENERAL,ONE=YES,TWO=YES".split(","); + + Assert.assertTrue(ResultsUpload.hasSetting(header, "ONE=YES")); + Assert.assertTrue(ResultsUpload.hasSetting(header, "TWO=YES")); + Assert.assertFalse(ResultsUpload.hasSetting(header, "THREE=YES")); + } + + @Test + public void aHeaderFromAnEarlierClientHasNoSettings() { + Assert.assertFalse(ResultsUpload.hasSetting("game,35,800x600,user,1000,GENERAL".split(","), "ONE=YES")); + Assert.assertFalse(ResultsUpload.hasSetting("game,35,800x600,user,1000".split(","), "ONE=YES")); + } + + @Test + public void onlyTheSettingsCount() { + Assert.assertFalse(ResultsUpload.hasSetting("game,35,800x600,ONE=YES,1000,ONE=YES".split(","), "ONE=YES")); + } +}