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..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 @@ -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.*; @@ -34,8 +36,11 @@ 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; private static RobotResults[] lastResults; private static IRobocodeEngine engine; @@ -48,6 +53,10 @@ public BattlesRunner(String game, Properties parameters) { outfile = parameters.getProperty("OUTPUT", ""); user = parameters.getProperty("USER", ""); this.game = game; + cleardata = parameters.getProperty("CLEARDATA", "NOT").equals("YES"); + if (cleardata) { + resultSettings.append(",CLEARDATA=YES"); + } initialize(); } @@ -101,6 +110,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 +128,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; @@ -171,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 a5b55db04..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 @@ -168,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" + "&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]; @@ -233,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/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")); + } +} 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")); + } +}